360 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  * SCR24x PCMCIA Smart Card Reader Driver
 | |
|  *
 | |
|  * Copyright (C) 2005-2006 TL Sudheendran
 | |
|  * Copyright (C) 2016 Lubomir Rintel
 | |
|  *
 | |
|  * Derived from "scr24x_v4.2.6_Release.tar.gz" driver by TL Sudheendran.
 | |
|  */
 | |
| 
 | |
| #include <linux/device.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/cdev.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/fs.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/uaccess.h>
 | |
| 
 | |
| #include <pcmcia/cistpl.h>
 | |
| #include <pcmcia/ds.h>
 | |
| 
 | |
| #define CCID_HEADER_SIZE	10
 | |
| #define CCID_LENGTH_OFFSET	1
 | |
| #define CCID_MAX_LEN		271
 | |
| 
 | |
| #define SCR24X_DATA(n)		(1 + n)
 | |
| #define SCR24X_CMD_STATUS	7
 | |
| #define CMD_START		0x40
 | |
| #define CMD_WRITE_BYTE		0x41
 | |
| #define CMD_READ_BYTE		0x42
 | |
| #define STATUS_BUSY		0x80
 | |
| 
 | |
| struct scr24x_dev {
 | |
| 	struct device *dev;
 | |
| 	struct cdev c_dev;
 | |
| 	unsigned char buf[CCID_MAX_LEN];
 | |
| 	int devno;
 | |
| 	struct mutex lock;
 | |
| 	struct kref refcnt;
 | |
| 	u8 __iomem *regs;
 | |
| };
 | |
| 
 | |
| #define SCR24X_DEVS 8
 | |
| static DECLARE_BITMAP(scr24x_minors, SCR24X_DEVS);
 | |
| 
 | |
| static struct class *scr24x_class;
 | |
| static dev_t scr24x_devt;
 | |
| 
 | |
| static void scr24x_delete(struct kref *kref)
 | |
| {
 | |
| 	struct scr24x_dev *dev = container_of(kref, struct scr24x_dev,
 | |
| 								refcnt);
 | |
| 
 | |
| 	kfree(dev);
 | |
| }
 | |
| 
 | |
| static int scr24x_wait_ready(struct scr24x_dev *dev)
 | |
| {
 | |
| 	u_char status;
 | |
| 	int timeout = 100;
 | |
| 
 | |
| 	do {
 | |
| 		status = ioread8(dev->regs + SCR24X_CMD_STATUS);
 | |
| 		if (!(status & STATUS_BUSY))
 | |
| 			return 0;
 | |
| 
 | |
| 		msleep(20);
 | |
| 	} while (--timeout);
 | |
| 
 | |
| 	return -EIO;
 | |
| }
 | |
| 
 | |
| static int scr24x_open(struct inode *inode, struct file *filp)
 | |
| {
 | |
| 	struct scr24x_dev *dev = container_of(inode->i_cdev,
 | |
| 				struct scr24x_dev, c_dev);
 | |
| 
 | |
| 	kref_get(&dev->refcnt);
 | |
| 	filp->private_data = dev;
 | |
| 
 | |
| 	return stream_open(inode, filp);
 | |
| }
 | |
| 
 | |
| static int scr24x_release(struct inode *inode, struct file *filp)
 | |
| {
 | |
| 	struct scr24x_dev *dev = filp->private_data;
 | |
| 
 | |
| 	/* We must not take the dev->lock here as scr24x_delete()
 | |
| 	 * might be called to remove the dev structure altogether.
 | |
| 	 * We don't need the lock anyway, since after the reference
 | |
| 	 * acquired in probe() is released in remove() the chrdev
 | |
| 	 * is already unregistered and noone can possibly acquire
 | |
| 	 * a reference via open() anymore. */
 | |
| 	kref_put(&dev->refcnt, scr24x_delete);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int read_chunk(struct scr24x_dev *dev, size_t offset, size_t limit)
 | |
| {
 | |
| 	size_t i, y;
 | |
| 	int ret;
 | |
| 
 | |
| 	for (i = offset; i < limit; i += 5) {
 | |
| 		iowrite8(CMD_READ_BYTE, dev->regs + SCR24X_CMD_STATUS);
 | |
| 		ret = scr24x_wait_ready(dev);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		for (y = 0; y < 5 && i + y < limit; y++)
 | |
| 			dev->buf[i + y] = ioread8(dev->regs + SCR24X_DATA(y));
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static ssize_t scr24x_read(struct file *filp, char __user *buf, size_t count,
 | |
| 								loff_t *ppos)
 | |
| {
 | |
| 	struct scr24x_dev *dev = filp->private_data;
 | |
| 	int ret;
 | |
| 	int len;
 | |
| 
 | |
| 	if (count < CCID_HEADER_SIZE)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (mutex_lock_interruptible(&dev->lock))
 | |
| 		return -ERESTARTSYS;
 | |
| 
 | |
| 	if (!dev->dev) {
 | |
| 		ret = -ENODEV;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = scr24x_wait_ready(dev);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 	len = CCID_HEADER_SIZE;
 | |
| 	ret = read_chunk(dev, 0, len);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	len += le32_to_cpu(*(__le32 *)(&dev->buf[CCID_LENGTH_OFFSET]));
 | |
| 	if (len > sizeof(dev->buf)) {
 | |
| 		ret = -EIO;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	ret = read_chunk(dev, CCID_HEADER_SIZE, len);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	if (len < count)
 | |
| 		count = len;
 | |
| 
 | |
| 	if (copy_to_user(buf, dev->buf, count)) {
 | |
| 		ret = -EFAULT;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = count;
 | |
| out:
 | |
| 	mutex_unlock(&dev->lock);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t scr24x_write(struct file *filp, const char __user *buf,
 | |
| 					size_t count, loff_t *ppos)
 | |
| {
 | |
| 	struct scr24x_dev *dev = filp->private_data;
 | |
| 	size_t i, y;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (mutex_lock_interruptible(&dev->lock))
 | |
| 		return -ERESTARTSYS;
 | |
| 
 | |
| 	if (!dev->dev) {
 | |
| 		ret = -ENODEV;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (count > sizeof(dev->buf)) {
 | |
| 		ret = -EINVAL;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (copy_from_user(dev->buf, buf, count)) {
 | |
| 		ret = -EFAULT;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = scr24x_wait_ready(dev);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	iowrite8(CMD_START, dev->regs + SCR24X_CMD_STATUS);
 | |
| 	ret = scr24x_wait_ready(dev);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	for (i = 0; i < count; i += 5) {
 | |
| 		for (y = 0; y < 5 && i + y < count; y++)
 | |
| 			iowrite8(dev->buf[i + y], dev->regs + SCR24X_DATA(y));
 | |
| 
 | |
| 		iowrite8(CMD_WRITE_BYTE, dev->regs + SCR24X_CMD_STATUS);
 | |
| 		ret = scr24x_wait_ready(dev);
 | |
| 		if (ret < 0)
 | |
| 			goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = count;
 | |
| out:
 | |
| 	mutex_unlock(&dev->lock);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct file_operations scr24x_fops = {
 | |
| 	.owner		= THIS_MODULE,
 | |
| 	.read		= scr24x_read,
 | |
| 	.write		= scr24x_write,
 | |
| 	.open		= scr24x_open,
 | |
| 	.release	= scr24x_release,
 | |
| 	.llseek		= no_llseek,
 | |
| };
 | |
| 
 | |
| static int scr24x_config_check(struct pcmcia_device *link, void *priv_data)
 | |
| {
 | |
| 	if (resource_size(link->resource[PCMCIA_IOPORT_0]) != 0x11)
 | |
| 		return -ENODEV;
 | |
| 	return pcmcia_request_io(link);
 | |
| }
 | |
| 
 | |
| static int scr24x_probe(struct pcmcia_device *link)
 | |
| {
 | |
| 	struct scr24x_dev *dev;
 | |
| 	int ret;
 | |
| 
 | |
| 	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 | |
| 	if (!dev)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	dev->devno = find_first_zero_bit(scr24x_minors, SCR24X_DEVS);
 | |
| 	if (dev->devno >= SCR24X_DEVS) {
 | |
| 		ret = -EBUSY;
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	mutex_init(&dev->lock);
 | |
| 	kref_init(&dev->refcnt);
 | |
| 
 | |
| 	link->priv = dev;
 | |
| 	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 | |
| 
 | |
| 	ret = pcmcia_loop_config(link, scr24x_config_check, NULL);
 | |
| 	if (ret < 0)
 | |
| 		goto err;
 | |
| 
 | |
| 	dev->dev = &link->dev;
 | |
| 	dev->regs = devm_ioport_map(&link->dev,
 | |
| 				link->resource[PCMCIA_IOPORT_0]->start,
 | |
| 				resource_size(link->resource[PCMCIA_IOPORT_0]));
 | |
| 	if (!dev->regs) {
 | |
| 		ret = -EIO;
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	cdev_init(&dev->c_dev, &scr24x_fops);
 | |
| 	dev->c_dev.owner = THIS_MODULE;
 | |
| 	ret = cdev_add(&dev->c_dev, MKDEV(MAJOR(scr24x_devt), dev->devno), 1);
 | |
| 	if (ret < 0)
 | |
| 		goto err;
 | |
| 
 | |
| 	ret = pcmcia_enable_device(link);
 | |
| 	if (ret < 0) {
 | |
| 		pcmcia_disable_device(link);
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	device_create(scr24x_class, NULL, MKDEV(MAJOR(scr24x_devt), dev->devno),
 | |
| 		      NULL, "scr24x%d", dev->devno);
 | |
| 
 | |
| 	dev_info(&link->dev, "SCR24x Chip Card Interface\n");
 | |
| 	return 0;
 | |
| 
 | |
| err:
 | |
| 	if (dev->devno < SCR24X_DEVS)
 | |
| 		clear_bit(dev->devno, scr24x_minors);
 | |
| 	kfree (dev);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void scr24x_remove(struct pcmcia_device *link)
 | |
| {
 | |
| 	struct scr24x_dev *dev = (struct scr24x_dev *)link->priv;
 | |
| 
 | |
| 	device_destroy(scr24x_class, MKDEV(MAJOR(scr24x_devt), dev->devno));
 | |
| 	mutex_lock(&dev->lock);
 | |
| 	pcmcia_disable_device(link);
 | |
| 	cdev_del(&dev->c_dev);
 | |
| 	clear_bit(dev->devno, scr24x_minors);
 | |
| 	dev->dev = NULL;
 | |
| 	mutex_unlock(&dev->lock);
 | |
| 
 | |
| 	kref_put(&dev->refcnt, scr24x_delete);
 | |
| }
 | |
| 
 | |
| static const struct pcmcia_device_id scr24x_ids[] = {
 | |
| 	PCMCIA_DEVICE_PROD_ID12("HP", "PC Card Smart Card Reader",
 | |
| 					0x53cb94f9, 0xbfdf89a5),
 | |
| 	PCMCIA_DEVICE_PROD_ID1("SCR241 PCMCIA", 0x6271efa3),
 | |
| 	PCMCIA_DEVICE_PROD_ID1("SCR243 PCMCIA", 0x2054e8de),
 | |
| 	PCMCIA_DEVICE_PROD_ID1("SCR24x PCMCIA", 0x54a33665),
 | |
| 	PCMCIA_DEVICE_NULL
 | |
| };
 | |
| MODULE_DEVICE_TABLE(pcmcia, scr24x_ids);
 | |
| 
 | |
| static struct pcmcia_driver scr24x_driver = {
 | |
| 	.owner		= THIS_MODULE,
 | |
| 	.name		= "scr24x_cs",
 | |
| 	.probe		= scr24x_probe,
 | |
| 	.remove		= scr24x_remove,
 | |
| 	.id_table	= scr24x_ids,
 | |
| };
 | |
| 
 | |
| static int __init scr24x_init(void)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	scr24x_class = class_create("scr24x");
 | |
| 	if (IS_ERR(scr24x_class))
 | |
| 		return PTR_ERR(scr24x_class);
 | |
| 
 | |
| 	ret = alloc_chrdev_region(&scr24x_devt, 0, SCR24X_DEVS, "scr24x");
 | |
| 	if (ret < 0)  {
 | |
| 		class_destroy(scr24x_class);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = pcmcia_register_driver(&scr24x_driver);
 | |
| 	if (ret < 0) {
 | |
| 		unregister_chrdev_region(scr24x_devt, SCR24X_DEVS);
 | |
| 		class_destroy(scr24x_class);
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void __exit scr24x_exit(void)
 | |
| {
 | |
| 	pcmcia_unregister_driver(&scr24x_driver);
 | |
| 	unregister_chrdev_region(scr24x_devt, SCR24X_DEVS);
 | |
| 	class_destroy(scr24x_class);
 | |
| }
 | |
| 
 | |
| module_init(scr24x_init);
 | |
| module_exit(scr24x_exit);
 | |
| 
 | |
| MODULE_AUTHOR("Lubomir Rintel");
 | |
| MODULE_DESCRIPTION("SCR24x PCMCIA Smart Card Reader Driver");
 | |
| MODULE_LICENSE("GPL");
 |