mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 05:32:00 +00:00
c82ee6d3be
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program see the file copying if not write to the free software foundation 675 mass ave cambridge ma 02139 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 52 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Jilayne Lovejoy <opensource@jilayne.com> Reviewed-by: Steve Winslow <swinslow@gmail.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190519154042.342335923@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
361 lines
7.6 KiB
C
361 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;
|
|
dev->c_dev.ops = &scr24x_fops;
|
|
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(THIS_MODULE, "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");
|