/* * Simple Magstripe Reader and Decoder * * Driver for a Neuron MCR-370T-1R-xxxx card reader * ------------------------------------------------ * This reader accepts a single-track card with a * 21-character BCD string encoded in the stripe. * * When read, the driver will decode and return the * entire track of the card. Partial swipes, bad * stripe data, or swipes which take too long will * cause the driver to return an empty line. The * driver returns 'F/f' or 'R/r' when the device's * switches transition: F for front, R for rear, * upper-case for open, lower-case for closed. * * See this blog post for an old description: * http://boundarydevices.com/magnetic-stripe-driver-for-linux-gpio/ * * Author: Ryan Stewart (ryan@boundarydevices.com) * * Copyright (C) 2011-2015, Boundary Devices */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define INSERT 1 #define REMOVE 0 #define DATASIZE 32 #define EVENTSIZE 32 #define MAXREAD 85 #define DATAMASK (DATASIZE - 1) #define DATABITMASK (DATASIZE * 8 - 1) #define EVENTMASK (EVENTSIZE - 1) #define BITS_PER_CHAR 5 static int mag_major; static dev_t devnum; static struct class *magdecode_class; #define DRIVER_NAME "magdecode" struct event { u8 type; u8 ptr; /* for data events, holds end of data string */ }; /* * Device structure, to keep track of everything device-specific here */ struct mag_dev { struct cdev cdev; struct device *chrdev; struct platform_device *pdev; wait_queue_head_t queue; spinlock_t lock; /* only one IRQ at a time */ struct timer_list data_timer, front_timer, rear_timer; int open_count; /* * gpio pin numbers and flags */ int front_pin; int front_pin_close_high; int rear_pin; int rear_pin_close_high; int clock_pin; int clock_pin_active_high; int data_pin; int data_pin_active_high; char edge; char last_front; char last_rear; int timeout; /* * Event buffer - Records the order of switch open/close events and * data string completion events, so that we can distinguish between * insertion and removal. */ struct event events[EVENTSIZE]; u8 e_add; /* Buffer index at which data is to be added */ u8 e_take; /* Buffer index from which data is to be read */ /* * Data buffer - Records bits in the order that they are read from the * stripe; bits packed into unsigned chars */ u8 data[DATASIZE]; u8 d_add; /* Bit index at which data is to be added */ u8 d_take; /* Bit index from which data is to be read */ u8 d_addprime; /* Bit index of data strings */ /* * the data buffer, and helps us to keep from pushing meaningless data * events into the event buffer */ u8 dd; /* Swipe direction (INSERT or REMOVE) */ /* * Used for sysfs entries */ u8 take_start; u8 take_end; u8 take_dd; }; static void flush_data(struct mag_dev *dev) { if (dev->d_add != dev->d_addprime) { int bitcount = (dev->d_add - dev->d_take) & DATABITMASK; if (bitcount >= 2*BITS_PER_CHAR) { dev->events[dev->e_add].type = 'd'; dev->events[dev->e_add].ptr = dev->d_add; dev->e_add = (dev->e_add + 1) & EVENTMASK; wake_up(&dev->queue); } dev->d_addprime = dev->d_add; } } static void data_timer(unsigned long arg) { unsigned long flags; struct mag_dev *dev = (struct mag_dev *)arg; spin_lock_irqsave(&dev->lock, flags); flush_data(dev); spin_unlock_irqrestore(&dev->lock, flags); } static void check_pin(struct mag_dev *dev, int pin, int active_high) { u8 level, event; level = (0 != gpio_get_value(pin)) ^ active_high; /* Determine which switch it was, and whether it was opened or closed */ if (pin == dev->front_pin) dev->last_front = event = 'F' | (level << 5); else dev->last_rear = event = 'R' | (level << 5); /* Add to event queue */ dev->events[dev->e_add].type = event; /* advance e_add */ dev->e_add = (dev->e_add + 1) & EVENTMASK; wake_up(&dev->queue); } static void mag_switch_handler(struct mag_dev *dev, int pin, int active_high) { check_pin(dev, pin, active_high); } static void front_timer(unsigned long arg) { unsigned long flags; struct mag_dev *dev = (struct mag_dev *)arg; spin_lock_irqsave(&dev->lock, flags); mag_switch_handler(dev, dev->front_pin, dev->front_pin_close_high); spin_unlock_irqrestore(&dev->lock, flags); } static irqreturn_t front_switch_handler(int irq, void *dev_id) { struct mag_dev *dev = dev_id; mod_timer(&dev->front_timer, jiffies + dev->timeout); return IRQ_HANDLED; } static void rear_timer(unsigned long arg) { unsigned long flags; struct mag_dev *dev = (struct mag_dev *)arg; spin_lock_irqsave(&dev->lock, flags); mag_switch_handler(dev, dev->rear_pin, dev->rear_pin_close_high); spin_unlock_irqrestore(&dev->lock, flags); } static irqreturn_t rear_switch_handler(int irq, void *dev_id) { struct mag_dev *dev = dev_id; mod_timer(&dev->rear_timer, jiffies + dev->timeout); return IRQ_HANDLED; } static irqreturn_t mag_clock_handler(int irq, void *dev_id) { struct mag_dev *dev = dev_id; int level = (0 != gpio_get_value(dev->data_pin)); /* save data value to buffer */ if (level ^ dev->data_pin_active_high) dev->data[dev->d_add / 8] |= 1 << (dev->d_add & 0x07); else dev->data[dev->d_add / 8] &= ~(1 << (dev->d_add & 0x07)); /* advance d_add */ dev->d_add = (dev->d_add + 1) & DATABITMASK; /* (re)start data_timer */ mod_timer(&dev->data_timer, jiffies + dev->timeout); return IRQ_HANDLED; } static int mag_open(struct inode *inode, struct file *file) { int result; struct mag_dev *dev; dev = container_of(inode->i_cdev, struct mag_dev, cdev); file->private_data = dev; /* Set up on first open */ if (!dev->open_count) { /* init wait queue and spinlock */ init_waitqueue_head(&dev->queue); spin_lock_init(&dev->lock); init_timer(&dev->data_timer); dev->data_timer.function = data_timer; dev->data_timer.data = (unsigned long) dev; init_timer(&dev->front_timer); dev->front_timer.function = front_timer; dev->front_timer.data = (unsigned long) dev; init_timer(&dev->rear_timer); dev->rear_timer.function = rear_timer; dev->rear_timer.data = (unsigned long) dev; check_pin(dev, dev->rear_pin, dev->rear_pin_close_high); check_pin(dev, dev->front_pin, dev->front_pin_close_high); result = devm_request_irq(&dev->pdev->dev, gpio_to_irq(dev->front_pin), front_switch_handler, IRQ_TYPE_EDGE_BOTH, "magfront", file->private_data); if (result) goto fail_rirq1; result = devm_request_irq(&dev->pdev->dev, gpio_to_irq(dev->rear_pin), rear_switch_handler, IRQ_TYPE_EDGE_BOTH, "magrear", file->private_data); if (result) goto fail_rirq2; result = devm_request_irq(&dev->pdev->dev, gpio_to_irq(dev->clock_pin), mag_clock_handler, dev->clock_pin_active_high ? IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING, "magclock", file->private_data); if (result) goto fail_rirq3; } dev->open_count++; return 0; fail_rirq3: devm_free_irq(&dev->pdev->dev, gpio_to_irq(dev->rear_pin), file->private_data); fail_rirq2: devm_free_irq(&dev->pdev->dev, gpio_to_irq(dev->front_pin), file->private_data); fail_rirq1: return result; } static int mag_release(struct inode *inode, struct file *file) { struct mag_dev *dev = file->private_data; dev->open_count--; /* Clean up on last close */ if (!dev->open_count) { /* IRQ release */ devm_free_irq(&dev->pdev->dev, gpio_to_irq(dev->front_pin), dev); devm_free_irq(&dev->pdev->dev, gpio_to_irq(dev->rear_pin), dev); devm_free_irq(&dev->pdev->dev, gpio_to_irq(dev->clock_pin), dev); } return 0; } static const char lut_bcdp[64] = { 'X', '0', '8', 'X', '4', 'X', 'X', '<', '2', 'X', 'X', ':', 'X', '6', '>', 'X', '1', 'X', 'X', '9', 'X', '5', '=', 'X', 'X', '3', ';', 'X', '7', 'X', 'X', '?', 'X', '1', '2', 'X', '4', 'X', 'X', '7', '8', 'X', 'X', ';', 'X', '=', '>', 'X', '0', 'X', 'X', '3', 'X', '5', '6', 'X', 'X', '9', ':', 'X', '<', 'X', 'X', '?', }; /* * Translate an unaligned 5-bit BCD character * (4 bits + parity) */ static char mag_charat(char *data, int index, char dd) { /* * The idea is to treat data[] as a continuous ring of bits and * treat index as a true bit index into that ring, reading a 5-bit * character that starts at index rather than being forced to pull the * data out in 8-bit chunks. */ /* We grab the byte at the bit index and the one above it */ unsigned char thischar, nextchar, result = 0x00; thischar = data[(index / 8) & DATAMASK]; nextchar = data[(index / 8 + 1) & DATAMASK]; /* first bit in LSB */ thischar >>= (index & 0x7); /* above first byte's MSB */ nextchar <<= 8 - (index & 0x7); /* Combine and cut off high 3 bits. This is our 5-bit character. */ result = (thischar | nextchar) & 0x1F; /* Translate using the LUT, and return */ return lut_bcdp[result | (dd << 5)]; } static int mag_decode(struct mag_dev *dev, char *buf) { char *data = dev->data; char dd = dev->dd; int bitcount, scale, bi, starti, endi, i, sc, bc; dev->take_end = dev->events[dev->e_take].ptr; dev->take_start = dev->d_take; dev->take_dd = dd; bitcount = (dev->take_end - dev->take_start) & DATABITMASK; if (dd == INSERT) { scale = 1; bi = dev->d_take; } else { scale = -1; bi = dev->d_take + bitcount - 5; } starti = -1; endi = -1; i = 0; sc = 0; /* Step through circular buffer looking for valid data */ for (bc = 0; bc < bitcount && i < MAXREAD; bi += scale, bc += (dd ? scale : -scale)) { char c = mag_charat(data, bi, dd); if (starti >= 0) { if (c == '?') { /* end reached, so quit loop */ buf[i] = '\n'; endi = bi; break; } else if (c == 'X') { /* * lookup table returns X on parity error * if that happens, forget everything and * start looking for sentinels again */ bi = starti; bc = sc; scale /= 5; starti = -1; i = 0; continue; } else { /* else put this character into the buffer */ buf[i] = c; i++; } /* * If we haven't recorded start sentinel position yet, * check whether character at this bit is start sentinel */ } else if (c == ';') { /* If so, start stepping 5 bits at a time */ scale *= 5; /* Save bc and bi in case this is the wrong sentinel */ sc = bc; starti = bi; } } /* Return an empty line on faulty output */ if (starti < 0 || endi < 0) { dev_dbg(dev->chrdev, "decode err(%d) dir(%d) at [%d..%d]\n", i, dev->take_dd, dev->take_start, dev->take_end); dev_dbg(dev->chrdev, "starti %d, endi %d\n", starti, endi); i = 0; buf[i] = '\n'; } else { dev_dbg(dev->chrdev, "decode(%d) dir(%d) at [%d..%d]\n", i, dev->take_dd, dev->take_start, dev->take_end); } return i + 1; } static ssize_t mag_read (struct file *file, char __user *buf, size_t count, loff_t *ppos) { ssize_t result; struct mag_dev *dev = (struct mag_dev *)file->private_data; if (dev == NULL) return -EINVAL; if ((dev->e_add == dev->e_take) && !(file->f_flags & O_NONBLOCK)) wait_event_interruptible(dev->queue, (dev->e_add != dev->e_take)); if (dev->e_add != dev->e_take) { /* read! */ u8 event = dev->events[dev->e_take].type; if (event == 'd') { char temp[MAXREAD]; result = mag_decode(dev, temp); dev->d_take = dev->events[dev->e_take].ptr; if (result) { if (copy_to_user(buf, temp, result)) return -EFAULT; } } else { /* switch closure */ /* determine direction of card swipe */ char temp[2] = { event, '\n' }; if (event == 'F') /* F = front switch closed */ dev->dd = INSERT; else if (event == 'f') /* f = front switch open */ dev->dd = REMOVE; else if (event == 'R') /* R = rear switch closed */ dev->dd = INSERT; else if (event == 'r') /* r = rear switch open */ dev->dd = REMOVE; result = 2; if (copy_to_user(buf, temp, result)) return -EFAULT; } dev->e_take = (dev->e_take + 1) & EVENTMASK; return result; } else { return -EINTR; } } static unsigned int mag_poll(struct file *file, struct poll_table_struct *table) { struct mag_dev *dev = (struct mag_dev *)file->private_data; if (!dev) return -EINVAL; poll_wait(file, &dev->queue, table); if (dev->e_add != dev->e_take) return POLLIN | POLLRDNORM; else return 0; } static struct file_operations const mag_fops = { .owner = THIS_MODULE, .read = mag_read, .poll = mag_poll, .open = mag_open, .release = mag_release, }; struct gpio_def { char const *name; int *gpio_pin; int *active_high; }; static ssize_t show_front(struct device *dev, struct device_attribute *attr, char *buf) { struct mag_dev *mag = dev_get_drvdata(dev); return sprintf(buf, "%d\n", (0 != gpio_get_value(mag->front_pin)) ^ mag->front_pin_close_high); } static struct kobj_attribute front = __ATTR(front, 0644, (void *)show_front, NULL); static ssize_t show_rear(struct device *dev, struct device_attribute *attr, char *buf) { struct mag_dev *mag = dev_get_drvdata(dev); return sprintf(buf, "%d\n", (0 != gpio_get_value(mag->rear_pin)) ^ mag->rear_pin_close_high); } static struct kobj_attribute rear = __ATTR(rear, 0644, (void *)show_rear, NULL); static ssize_t show_last_front(struct device *dev, struct device_attribute *attr, char *buf) { struct mag_dev *mag = dev_get_drvdata(dev); return sprintf(buf, "%c\n", mag->last_front); } static struct kobj_attribute last_front = __ATTR(last_front, 0644, (void *)show_last_front, NULL); static ssize_t show_last_rear(struct device *dev, struct device_attribute *attr, char *buf) { struct mag_dev *mag = dev_get_drvdata(dev); return sprintf(buf, "%c\n", mag->last_rear); } static struct kobj_attribute last_rear = __ATTR(last_rear, 0644, (void *)show_last_rear, NULL); static ssize_t show_raw(struct device *dev, struct device_attribute *attr, char *buf) { struct mag_dev *mag = dev_get_drvdata(dev); int const max = PAGE_SIZE - 20; int i = 0; int count; u8 next = mag->take_start; buf += sprintf(buf, "%d:%d-%d:", mag->take_dd, mag->take_start, mag->take_end); count = (mag->take_end - next + DATASIZE) & DATABITMASK; if (count > max) count = max; for (i=0; i < count; i++) { char bit = (0 != (mag->data[next/8] & (1 << (next&7)))); *buf++ = '0' + bit; next = (next + 1) & DATABITMASK; } *buf = '\n'; return i+1; } static struct kobj_attribute raw = __ATTR(raw, 0644, (void *)show_raw, NULL); static struct attribute *mag_attrs[] = { &front.attr, &rear.attr, &last_front.attr, &last_rear.attr, &raw.attr, NULL, }; static struct attribute_group mag_attr_grp = { .attrs = mag_attrs, }; static int mag_of_probe(struct platform_device *pdev, struct device_node *np, struct mag_dev *dev) { int i; struct gpio_def pins[] = { { "front_pin", &dev->front_pin, &dev->front_pin_close_high } , { "rear_pin", &dev->rear_pin, &dev->rear_pin_close_high} , { "clock_pin", &dev->clock_pin, &dev->clock_pin_active_high} , { "data_pin", &dev->data_pin, &dev->data_pin_active_high} }; for (i = 0; i < ARRAY_SIZE(pins); i++) *pins[i].gpio_pin = -1; for (i = 0; i < ARRAY_SIZE(pins); i++) { int rv; enum of_gpio_flags gpio_flags; int pin = of_get_named_gpio_flags(np, pins[i].name, 0, &gpio_flags); if (!gpio_is_valid(pin)) { dev_err(&pdev->dev, "Invalid %s\n", pins[i].name); break; } *pins[i].gpio_pin = pin; *pins[i].active_high = !(gpio_flags & OF_GPIO_ACTIVE_LOW); rv = devm_gpio_request(&pdev->dev, pin, pins[i].name); if (rv) { dev_err(&pdev->dev, "Error %d requesting pin %d:%s\n", rv, pin, pins[i].name); break; } } if (i < ARRAY_SIZE(pins)) { while (0 <= i) { devm_gpio_free(&pdev->dev, *pins[i].gpio_pin); *pins[i].gpio_pin = -1; i--; } } return (i == ARRAY_SIZE(pins)) ? 0 : -EINVAL; } static int mag_probe(struct platform_device *pdev) { int result = 0; struct mag_dev *dev; struct device_node *np = pdev->dev.of_node; if (!np) return -ENODEV; dev = devm_kzalloc(&pdev->dev, sizeof(struct mag_dev), GFP_KERNEL); if (!dev) { dev_err(&pdev->dev, "%s: alloc failure", DRIVER_NAME); result = -ENOMEM; goto fail_entry; } dev->pdev = pdev; dev->timeout = 10; cdev_init(&dev->cdev, &mag_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &mag_fops; if (0 != mag_of_probe(pdev, np, dev)) { dev_err(&pdev->dev, "Invalid dt spec\n"); result = -ENODEV; goto fail_cdevadd; } result = cdev_add(&dev->cdev, devnum, 1); if (result < 0) { dev_err(&pdev->dev, "%s: couldn't add device: err %d\n", DRIVER_NAME, result); goto fail_cdevadd; } dev->chrdev = device_create(magdecode_class, &platform_bus, devnum, 0, "%s", DRIVER_NAME); platform_set_drvdata(pdev, dev); result = sysfs_create_group(&pdev->dev.kobj, &mag_attr_grp); if (result) dev_err(&pdev->dev, "failed to create sysfs entries"); return 0; fail_cdevadd: devm_kfree(&pdev->dev, dev); fail_entry: return result; } static int mag_remove(struct platform_device *pdev) { struct mag_dev *dev = platform_get_drvdata(pdev); if (dev) { int i; int pins[] = { dev->front_pin, dev->rear_pin, dev->clock_pin, dev->data_pin, }; sysfs_remove_group(&pdev->dev.kobj, &mag_attr_grp); if (!IS_ERR(dev->chrdev)) { device_destroy(magdecode_class, devnum); dev->chrdev = 0; } for (i = 0; i < ARRAY_SIZE(pins); i++) if (gpio_is_valid(pins[i])) devm_gpio_free(&pdev->dev, pins[i]); } return 0; } static const struct of_device_id magdecode_ids[] = { { .compatible = "boundary,magdecode", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, magdecode_ids); static struct platform_driver mag_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = magdecode_ids, }, .probe = mag_probe, .remove = mag_remove, }; static char *magdecode_devnode(struct device *dev, umode_t *mode) { if (mode) *mode = S_IRUGO | S_IWUSR; return kasprintf(GFP_KERNEL, "%s", DRIVER_NAME); } int __init magdecode_init(void) { int result; /* Create a sysfs class. */ magdecode_class = class_create(THIS_MODULE, "magdecode"); if (IS_ERR(magdecode_class)) { result = PTR_ERR(magdecode_class); goto fail_class; } magdecode_class->devnode = magdecode_devnode; result = alloc_chrdev_region(&devnum, 0, 1, DRIVER_NAME); if (result < 0) { pr_err("%s: couldn't chrdevs: err %d\n", DRIVER_NAME, result); goto fail_chrdev; } mag_major = MAJOR(devnum); result = platform_driver_register(&mag_driver); if (result < 0) { pr_err("%s: couldn't register driver, err %d\n", DRIVER_NAME, result); goto fail_platform; } pr_info("driver %s registered\n", DRIVER_NAME); return 0; fail_platform: unregister_chrdev_region(devnum, 1); fail_chrdev: class_destroy(magdecode_class); magdecode_class = 0; fail_class: return result; } void __exit magdecode_cleanup(void) { platform_driver_unregister(&mag_driver); unregister_chrdev_region(devnum, 1); if (!IS_ERR(magdecode_class)) class_destroy(magdecode_class); pr_info("%s unloaded\n", DRIVER_NAME); } module_init(magdecode_init); module_exit(magdecode_cleanup); MODULE_LICENSE("GPL");