Add Omnikey CardMan 4040 Driver Signed-off-by: Harald Welte --- commit 0e760a5785ebb83b932d104679cc2b2b4070b1c1 tree 46d71d0c6b41b441a1a8d725b741450f1334296e parent c4ab879b6ef599bf88d19b9b145878ef73400ce7 author Harald Welte So, 04 Sep 2005 13:05:44 +0200 committer Harald Welte So, 04 Sep 2005 13:05:44 +0200 MAINTAINERS | 5 drivers/char/pcmcia/Kconfig | 13 + drivers/char/pcmcia/Makefile | 1 drivers/char/pcmcia/cm4040_cs.c | 900 +++++++++++++++++++++++++++++++++++++++ drivers/char/pcmcia/cm4040_cs.h | 52 ++ 5 files changed, 971 insertions(+), 0 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1737,6 +1737,11 @@ L: linux-tr@linuxtr.net W: http://www.linuxtr.net S: Maintained +OMNIKEY CARDMAN 4040 DRIVER +P: Harald Welte +M: laforge@gnumonks.org +S: Maintained + ONSTREAM SCSI TAPE DRIVER P: Willem Riede M: osst@riede.org diff --git a/drivers/char/pcmcia/Kconfig b/drivers/char/pcmcia/Kconfig --- a/drivers/char/pcmcia/Kconfig +++ b/drivers/char/pcmcia/Kconfig @@ -18,5 +18,18 @@ config SYNCLINK_CS The module will be called synclinkmp. If you want to do that, say M here. +config CARDMAN_4040 + tristate "Omnikey CardMan 4040 support" + depends on PCMCIA + help + Enable support for the Omnikey CardMan 4040 PCMCIA Smartcard + reader. + + This card is basically a USB CCID device connected to a FIFO + in I/O space. To use the kernel driver, you will need either the + PC/SC ifdhandler provided from the Omnikey homepage + (http://www.omnikey.com/), or a current development version of OpenCT + (http://www.opensc.org/). + endmenu diff --git a/drivers/char/pcmcia/Makefile b/drivers/char/pcmcia/Makefile --- a/drivers/char/pcmcia/Makefile +++ b/drivers/char/pcmcia/Makefile @@ -5,3 +5,4 @@ # obj-$(CONFIG_SYNCLINK_CS) += synclink_cs.o +obj-$(CONFIG_CARDMAN_4040) += cm4040_cs.o diff --git a/drivers/char/pcmcia/cm4040_cs.c b/drivers/char/pcmcia/cm4040_cs.c new file mode 100644 --- /dev/null +++ b/drivers/char/pcmcia/cm4040_cs.c @@ -0,0 +1,900 @@ + /* + * A driver for the Omnikey PCMCIA smartcard reader CardMan 4040 + * + * (c) 2000-2004 Omnikey AG (http://www.omnikey.com/) + * + * (C) 2005 Harald Welte + * - add support for poll() + * - driver cleanup + * - add waitqueues + * - adhere to linux kenrel coding style and policies + * - support 2.6.13 "new style" pcmcia interface + * + * All rights reserved, Dual BSD/GPL Licensed. + */ + +/* #define PCMCIA_DEBUG 6 */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "cm4040_cs.h" + +static atomic_t cmx_num_devices_open; + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0600); +#define DEBUG(n, x, args...) do { if (pc_debug >= (n)) \ + printk(KERN_DEBUG "%s:%s:" x, MODULE_NAME, \ + __FUNCTION__, ##args); } while (0) +#else +#define DEBUG(n, args...) +#endif + +static volatile char *version = +"OMNIKEY CardMan 4040 v1.1.0gm3 - All bugs added by Harald Welte"; + +#define CCID_DRIVER_BULK_DEFAULT_TIMEOUT (150*HZ) +#define CCID_DRIVER_ASYNC_POWERUP_TIMEOUT (35*HZ) +#define CCID_DRIVER_MINIMUM_TIMEOUT (3*HZ) +#define READ_WRITE_BUFFER_SIZE 512 +#define POLL_LOOP_COUNT 1000 + +/* how often to poll for fifo status change */ +#define POLL_PERIOD (HZ/100) + +static void reader_release(u_long arg); +static void reader_detach(dev_link_t *link); + +static int major; + +#define BS_READABLE 0x01 +#define BS_WRITABLE 0x02 + +typedef struct reader_dev_t { + dev_link_t link; + dev_node_t node; + wait_queue_head_t devq; + + wait_queue_head_t poll_wait; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + unsigned int buffer_status; + + unsigned int fTimerExpired; + struct timer_list timer; + unsigned long timeout; + unsigned char sBuf[READ_WRITE_BUFFER_SIZE]; + unsigned char rBuf[READ_WRITE_BUFFER_SIZE]; + struct task_struct *owner; +} reader_dev_t; + +static dev_info_t dev_info = MODULE_NAME; +static dev_link_t *dev_table[CM_MAX_DEV] = { NULL, }; + +static struct timer_list cmx_poll_timer; + +#if (!defined PCMCIA_DEBUG) || (PCMCIA_DEBUG < 7) +#define xoutb outb +#define xinb inb +#else +static inline void xoutb(unsigned char val,unsigned short port) +{ + DEBUG(7, "outb(val=%.2x,port=%.4x)\n", val, port); + outb(val,port); +} + +static unsigned char xinb(unsigned short port) +{ + unsigned char val; + + val = inb(port); + DEBUG(7, "%.2x=inb(%.4x)\n", val, port); + return val; +} +#endif + +/* poll the device fifo status register. not to be confused with + * the poll syscall. */ +static void cmx_do_poll(unsigned long dummy) +{ + unsigned int i; + /* walk through all devices */ + for (i = 0; dev_table[i]; i++) { + dev_link_t *dl = dev_table[i]; + reader_dev_t *dev = dl->priv; + unsigned int obs = xinb(dl->io.BasePort1 + + REG_OFFSET_BUFFER_STATUS); + + dev->buffer_status = 0; + + if ((obs & BSR_BULK_IN_FULL) == BSR_BULK_IN_FULL) { + dev->buffer_status |= BS_READABLE; + DEBUG(4, "waking up read_wait\n"); + wake_up_interruptible(&dev->read_wait); + } + + if ((obs & BSR_BULK_OUT_FULL) == 0) { + dev->buffer_status |= BS_WRITABLE; + DEBUG(4, "waking up write_wait\n"); + wake_up_interruptible(&dev->write_wait); + } + + if (dev->buffer_status) + wake_up_interruptible(&dev->poll_wait); + } + + if (atomic_read(&cmx_num_devices_open)) + mod_timer(&cmx_poll_timer, jiffies + POLL_PERIOD); +} + +static loff_t cmx_llseek(struct file * filp, loff_t off, int whence) +{ + return -ESPIPE; +} + +static inline int cmx_waitForBulkOutReady(reader_dev_t *dev) +{ + register int i; + register int iobase = dev->link.io.BasePort1; + + for (i=0; i < POLL_LOOP_COUNT; i++) { + if ((xinb(iobase + REG_OFFSET_BUFFER_STATUS) + & BSR_BULK_OUT_FULL) == 0) { + DEBUG(4, "BulkOut empty\n"); + return 1; + } + } + + interruptible_sleep_on_timeout(&dev->write_wait, dev->timeout); + if (dev->buffer_status & BS_WRITABLE) { + DEBUG(4, "woke up: BulkOut empty\n"); + return 1; + } + + DEBUG(4, "woke up: BulkOut full, returning 0 :(\n"); + return 0; +} + +/* Write to Sync Control Register */ +static inline int cmx_writeSync(unsigned char val,reader_dev_t *dev) +{ + register int iobase = dev->link.io.BasePort1; + int rc; + + rc = cmx_waitForBulkOutReady(dev); + if (rc != 1) + return 0; + + xoutb(val,iobase + REG_OFFSET_SYNC_CONTROL); + rc = cmx_waitForBulkOutReady(dev); + if (rc != 1) + return 0; + + return 1; +} + +static inline int cmx_waitForBulkInReady(reader_dev_t *dev) +{ + register int i; + register int iobase = dev->link.io.BasePort1; + + for (i=0; i < POLL_LOOP_COUNT; i++) { + if ((xinb(iobase + REG_OFFSET_BUFFER_STATUS) + & BSR_BULK_IN_FULL) == BSR_BULK_IN_FULL) { + DEBUG(3, "BulkIn full\n"); + return 1; + } + } + + DEBUG(4, "interruptible_sleep_on_timeout(read_wait, timeout=%ld\n", dev->timeout); + interruptible_sleep_on_timeout(&dev->read_wait, dev->timeout); + if (dev->buffer_status & BS_READABLE) { + DEBUG(4, "woke up: BulkIn full\n"); + return 1; + } + + DEBUG(4, "woke up: BulkIn not full, returning 0 :(\n"); + return 0; +} + +static ssize_t cmx_read(struct file *filp,char *buf,size_t count,loff_t *ppos) +{ + register reader_dev_t *dev=(reader_dev_t *)filp->private_data; + register int iobase=dev->link.io.BasePort1; + unsigned long ulBytesToRead; + unsigned long i; + unsigned long ulMin; + int rc; + unsigned char uc; + + DEBUG(2, "-> cmx_read(%s,%d)\n", current->comm,current->pid); + + if (count==0) + return 0; + + if (count < 10) + return -EFAULT; + + if (filp->f_flags & O_NONBLOCK) { + DEBUG(4, "filep->f_flags O_NONBLOCK set\n"); + DEBUG(4, "<- cmx_read (failure)\n"); + return -EAGAIN; + } + + if ((dev->link.state & DEV_PRESENT)==0) + return -ENODEV; + + for (i=0; i<5; i++) { + rc = cmx_waitForBulkInReady(dev); + if (rc != 1) { + DEBUG(5,"cmx_waitForBulkInReady rc=%.2x\n",rc); + DEBUG(2, "<- cmx_read (failed)\n"); + return -EIO; + } + dev->rBuf[i] = xinb(iobase + REG_OFFSET_BULK_IN); +#ifdef PCMCIA_DEBUG + if (pc_debug >= 6) + printk(KERN_DEBUG "%lu:%2x ", i, dev->rBuf[i]); + } + printk("\n"); +#else + } +#endif + + ulBytesToRead = 5 + + (0x000000FF&((char)dev->rBuf[1])) + + (0x0000FF00&((char)dev->rBuf[2] << 8)) + + (0x00FF0000&((char)dev->rBuf[3] << 16)) + + (0xFF000000&((char)dev->rBuf[4] << 24)); + + DEBUG(6, "BytesToRead=%lu\n", ulBytesToRead); + + ulMin = (count < (ulBytesToRead+5))?count:(ulBytesToRead+5); + + DEBUG(6, "Min=%lu\n", ulMin); + + for (i=0; i < (ulMin-5); i++) { + rc = cmx_waitForBulkInReady(dev); + if (rc != 1) { + DEBUG(5,"cmx_waitForBulkInReady rc=%.2x\n",rc); + DEBUG(2, "<- cmx_read (failed)\n"); + return -EIO; + } + dev->rBuf[i+5] = xinb(iobase + REG_OFFSET_BULK_IN); + DEBUG(6, "%lu:%2x ", i, dev->rBuf[i]); + } + DEBUG(6, "\n"); + + *ppos = ulMin; + copy_to_user(buf, dev->rBuf, ulMin); + + + rc = cmx_waitForBulkInReady(dev); + if (rc != 1) { + DEBUG(5,"cmx_waitForBulkInReady rc=%.2x\n",rc); + DEBUG(2, "<- cmx_read (failed)\n"); + return -EIO; + } + + rc = cmx_writeSync(SCR_READER_TO_HOST_DONE, dev); + + if (rc != 1) { + DEBUG(5,"cmx_writeSync c=%.2x\n",rc); + DEBUG(2, "<- cmx_read (failed)\n"); + return -EIO; + } + + uc = xinb(iobase + REG_OFFSET_BULK_IN); + + DEBUG(2,"<- cmx_read (successfully)\n"); + return ulMin; +} + +static ssize_t cmx_write(struct file *filp,const char *buf,size_t count, + loff_t *ppos) +{ + register reader_dev_t *dev=(reader_dev_t *)filp->private_data; + register int iobase=dev->link.io.BasePort1; + ssize_t rc; + int i; + unsigned int uiBytesToWrite; + + DEBUG(2, "-> cmx_write(%s,%d)\n", current->comm, current->pid); + + if (count == 0) { + DEBUG(2, "<- cmx_write nothing to do (successfully)\n"); + return 0; + } + + if (count < 5) { + DEBUG(2, "<- cmx_write buffersize=%Zd < 5\n", count); + return -EIO; + } + + if (filp->f_flags & O_NONBLOCK) { + DEBUG(4, "filep->f_flags O_NONBLOCK set\n"); + DEBUG(4, "<- cmx_write (failure)\n"); + return -EAGAIN; + } + + if ((dev->link.state & DEV_PRESENT) == 0) + return -ENODEV; + + uiBytesToWrite = count; + copy_from_user(dev->sBuf, buf, uiBytesToWrite); + + switch (dev->sBuf[0]) { + case CMD_PC_TO_RDR_XFRBLOCK: + case CMD_PC_TO_RDR_SECURE: + case CMD_PC_TO_RDR_TEST_SECURE: + case CMD_PC_TO_RDR_OK_SECURE: + dev->timeout = CCID_DRIVER_BULK_DEFAULT_TIMEOUT; + break; + + case CMD_PC_TO_RDR_ICCPOWERON: + dev->timeout = CCID_DRIVER_ASYNC_POWERUP_TIMEOUT; + break; + + case CMD_PC_TO_RDR_GETSLOTSTATUS: + case CMD_PC_TO_RDR_ICCPOWEROFF: + case CMD_PC_TO_RDR_GETPARAMETERS: + case CMD_PC_TO_RDR_RESETPARAMETERS: + case CMD_PC_TO_RDR_SETPARAMETERS: + case CMD_PC_TO_RDR_ESCAPE: + case CMD_PC_TO_RDR_ICCCLOCK: + default: + dev->timeout = CCID_DRIVER_MINIMUM_TIMEOUT; + break; + } + + rc = cmx_writeSync(SCR_HOST_TO_READER_START, dev); + + DEBUG(4, "start \n"); + + for (i=0; i < uiBytesToWrite; i++) { + rc = cmx_waitForBulkOutReady(dev); + if (rc != 1) { + DEBUG(5, "cmx_waitForBulkOutReady rc=%.2Zx\n", rc); + DEBUG(2, "<- cmx_write (failed)\n"); + return -EIO; + } + + xoutb(dev->sBuf[i],iobase + REG_OFFSET_BULK_OUT); + DEBUG(4, "%.2x ", dev->sBuf[i]); + } + DEBUG(4, "end\n"); + + rc = cmx_writeSync(SCR_HOST_TO_READER_DONE, dev); + + if (rc != 1) { + DEBUG(5, "cmx_writeSync c=%.2Zx\n", rc); + DEBUG(2, "<- cmx_write (failed)\n"); + return -EIO; + } + + DEBUG(2, "<- cmx_write (successfully)\n"); + return count; +} + +static unsigned int cmx_poll(struct file *filp, poll_table *wait) +{ + reader_dev_t *dev=(reader_dev_t *)filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &dev->poll_wait, wait); + + if (dev->buffer_status & BS_READABLE) + mask |= POLLIN | POLLRDNORM; + if (dev->buffer_status & BS_WRITABLE) + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +static int cmx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd, + unsigned long arg) +{ + dev_link_t *link; + int rc, size; + + link=dev_table[MINOR(inode->i_rdev)]; + if (!(DEV_OK(link))) { + DEBUG(4, "DEV_OK false\n"); + return -ENODEV; + } + if (_IOC_TYPE(cmd)!=CM_IOC_MAGIC) { + DEBUG(4,"ioctype mismatch\n"); + return -EINVAL; + } + if (_IOC_NR(cmd)>CM_IOC_MAXNR) { + DEBUG(4,"iocnr mismatch\n"); + return -EINVAL; + } + size = _IOC_SIZE(cmd); + rc = 0; + DEBUG(4,"iocdir=%.4x iocr=%.4x iocw=%.4x iocsize=%d cmd=%.4x\n", + _IOC_DIR(cmd),_IOC_READ,_IOC_WRITE,size,cmd); + + if (_IOC_DIR(cmd)&_IOC_READ) { + if (!access_ok(VERIFY_WRITE, (void *)arg, size)) + return -EFAULT; + } + if (_IOC_DIR(cmd)&_IOC_WRITE) { + if (!access_ok(VERIFY_READ, (void *)arg, size)) + return -EFAULT; + } + + return rc; +} + +static int cmx_open (struct inode *inode, struct file *filp) +{ + reader_dev_t *dev; + dev_link_t *link; + int i; + + DEBUG(2, "-> cmx_open(device=%d.%d process=%s,%d)\n", + MAJOR(inode->i_rdev), MINOR(inode->i_rdev), + current->comm, current->pid); + + i = MINOR(inode->i_rdev); + if (i >= CM_MAX_DEV) { + DEBUG(4, "MAX_DEV reached\n"); + DEBUG(4, "<- cmx_open (failure)\n"); + return -ENODEV; + } + link = dev_table[MINOR(inode->i_rdev)]; + if (link == NULL || !(DEV_OK(link))) { + DEBUG(4, "link== NULL || DEV_OK false\n"); + DEBUG(4, "<- cmx_open (failure)\n"); + return -ENODEV; + } + if (link->open) { + DEBUG(4, "DEVICE BUSY\n"); + DEBUG(4, "<- cmx_open (failure)\n"); + return -EBUSY; + } + + dev = (reader_dev_t *)link->priv; + filp->private_data = dev; + + if (filp->f_flags & O_NONBLOCK) { + DEBUG(4, "filep->f_flags O_NONBLOCK set\n"); + DEBUG(4, "<- cmx_open (failure)\n"); + return -EAGAIN; + } + + dev->owner = current; + link->open = 1; + + atomic_inc(&cmx_num_devices_open); + mod_timer(&cmx_poll_timer, jiffies + POLL_PERIOD); + + DEBUG(2, "<- cmx_open (successfully)\n"); + return 0; +} + +static int cmx_close(struct inode *inode,struct file *filp) +{ + reader_dev_t *dev; + dev_link_t *link; + int i; + + DEBUG(2, "-> cmx_close(maj/min=%d.%d)\n", + MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); + + i = MINOR(inode->i_rdev); + if (i >= CM_MAX_DEV) + return -ENODEV; + + link = dev_table[MINOR(inode->i_rdev)]; + if (link == NULL) + return -ENODEV; + + dev = (reader_dev_t *)link->priv; + + link->open = 0; + wake_up(&dev->devq); + + atomic_dec(&cmx_num_devices_open); + + DEBUG(2, "<- cmx_close\n"); + return 0; +} + +static void cmx_reader_release(dev_link_t *link) +{ + reader_dev_t *dev = (reader_dev_t *)link->priv; + + DEBUG(3, "-> cmx_reader_release\n"); + while (link->open) { + DEBUG(3, KERN_INFO MODULE_NAME ": delaying release until " + "process '%s', pid %d has terminated\n", + dev->owner->comm,dev->owner->pid); + wait_event(dev->devq, (link->open == 0)); + } + DEBUG(3, "<- cmx_reader_release\n"); + return; +} + +static void reader_config(dev_link_t *link, int devno) +{ + client_handle_t handle; + reader_dev_t *dev; + tuple_t tuple; + cisparse_t parse; + config_info_t conf; + u_char buf[64]; + int fail_fn,fail_rc; + int rc; + + DEBUG(2, "-> reader_config\n"); + + handle = link->handle; + + tuple.DesiredTuple = CISTPL_CONFIG; + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + + if ((fail_rc = pcmcia_get_first_tuple(handle,&tuple)) != CS_SUCCESS) { + fail_fn = GetFirstTuple; + goto cs_failed; + } + if ((fail_rc = pcmcia_get_tuple_data(handle,&tuple)) != CS_SUCCESS) { + fail_fn = GetTupleData; + goto cs_failed; + } + if ((fail_rc = pcmcia_parse_tuple(handle,&tuple,&parse)) + != CS_SUCCESS) { + fail_fn = ParseTuple; + goto cs_failed; + } + if ((fail_rc = pcmcia_get_configuration_info(handle,&conf)) + != CS_SUCCESS) { + fail_fn = GetConfigurationInfo; + goto cs_failed; + } + + link->state |= DEV_CONFIG; + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + link->conf.Vcc = conf.Vcc; + DEBUG(2, "link->conf.Vcc=%d\n", link->conf.Vcc); + + link->io.BasePort2 = 0; + link->io.NumPorts2 = 0; + link->io.Attributes2 = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + for (rc = pcmcia_get_first_tuple(handle, &tuple); + rc == CS_SUCCESS; + rc = pcmcia_get_next_tuple(handle, &tuple)) { + DEBUG(2, "Examing CIS Tuple!\n"); + rc = pcmcia_get_tuple_data(handle, &tuple); + if (rc != CS_SUCCESS) + continue; + rc = pcmcia_parse_tuple(handle, &tuple, &parse); + if (rc != CS_SUCCESS) + continue; + + DEBUG(2, "tupleIndex=%d\n", parse.cftable_entry.index); + link->conf.ConfigIndex = parse.cftable_entry.index; + + if (parse.cftable_entry.io.nwin) { + link->io.BasePort1 = parse.cftable_entry.io.win[0].base; + link->io.NumPorts1 = parse.cftable_entry.io.win[0].len; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if(!(parse.cftable_entry.io.flags & CISTPL_IO_8BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + if(!(parse.cftable_entry.io.flags & CISTPL_IO_16BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.IOAddrLines = parse.cftable_entry.io.flags + & CISTPL_IO_LINES_MASK; + DEBUG(2,"io.BasePort1=%.4x\n", link->io.BasePort1); + DEBUG(2,"io.NumPorts1=%.4x\n", link->io.NumPorts1); + DEBUG(2,"io.BasePort2=%.4x\n", link->io.BasePort2); + DEBUG(2,"io.NumPorts2=%.4x\n", link->io.NumPorts2); + DEBUG(2,"io.IOAddrLines=%.4x\n", + link->io.IOAddrLines); + rc = pcmcia_request_io(handle, &link->io); + if (rc == CS_SUCCESS) { + DEBUG(2, "RequestIO OK\n"); + break; + } else + DEBUG(2, "RequestIO failed\n"); + } + } + if (rc != CS_SUCCESS) { + DEBUG(2, "Couldn't configure reader\n"); + goto cs_release; + } + + link->conf.IntType = 00000002; + + if ((fail_rc = pcmcia_request_configuration(handle,&link->conf)) + !=CS_SUCCESS) { + fail_fn = RequestConfiguration; + DEBUG(1, "pcmcia_request_configuration failed 0x%x\n", fail_rc); + goto cs_release; + } + + DEBUG(2, "RequestConfiguration OK\n"); + + dev = link->priv; + sprintf(dev->node.dev_name, DEVICE_NAME "%d", devno); + dev->node.major = major; + dev->node.minor = devno; + dev->node.next = NULL; + link->dev = &dev->node; + link->state &= ~DEV_CONFIG_PENDING; + + DEBUG(2, "device " DEVICE_NAME "%d at 0x%.4x-0x%.4x\n", devno, + link->io.BasePort1, link->io.BasePort1+link->io.NumPorts1); + DEBUG(2, "<- reader_config (succ)\n"); + + return; + +cs_failed: + cs_error(handle, fail_fn, fail_rc); +cs_release: + reader_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + DEBUG(2, "<- reader_config (failure)\n"); +} + +static int reader_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link; + reader_dev_t *dev; + int devno; + + DEBUG(3,"-> reader_event\n"); + link = args->client_data; + dev = link->priv; + for (devno = 0; devno < CM_MAX_DEV; devno++) { + if (dev_table[devno]==link) + break; + } + if (devno == CM_MAX_DEV) + return CS_BAD_ADAPTER; + + switch (event) { + case CS_EVENT_CARD_INSERTION: + DEBUG(5, "CS_EVENT_CARD_INSERTION\n"); + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + reader_config(link,devno); + break; + case CS_EVENT_CARD_REMOVAL: + DEBUG(5, "CS_EVENT_CARD_REMOVAL\n"); + link->state &= ~DEV_PRESENT; + break; + case CS_EVENT_PM_SUSPEND: + DEBUG(5, "CS_EVENT_PM_SUSPEND " + "(fall-through to CS_EVENT_RESET_PHYSICAL)\n"); + link->state |= DEV_SUSPEND; + + case CS_EVENT_RESET_PHYSICAL: + DEBUG(5, "CS_EVENT_RESET_PHYSICAL\n"); + if (link->state & DEV_CONFIG) { + DEBUG(5, "ReleaseConfiguration\n"); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + DEBUG(5, "CS_EVENT_PM_RESUME " + "(fall-through to CS_EVENT_CARD_RESET)\n"); + link->state &= ~DEV_SUSPEND; + + case CS_EVENT_CARD_RESET: + DEBUG(5, "CS_EVENT_CARD_RESET\n"); + if ((link->state & DEV_CONFIG)) { + DEBUG(5, "cmx: RequestConfiguration\n"); + pcmcia_request_configuration(link->handle, + &link->conf); + } + break; + default: + DEBUG(5, "reader_event: unknown event %.2x\n", event); + break; + } + DEBUG(3, "<- reader_event\n"); + return CS_SUCCESS; +} + +static void reader_release(u_long arg) +{ + dev_link_t *link; + int rc; + + DEBUG(3, "-> reader_release\n"); + link = (dev_link_t *)arg; + cmx_reader_release(link->priv); + rc = pcmcia_release_configuration(link->handle); + if (rc != CS_SUCCESS) + DEBUG(5, "couldn't ReleaseConfiguration " + "reasoncode=%.2x\n", rc); + rc = pcmcia_release_io(link->handle, &link->io); + if (rc != CS_SUCCESS) + DEBUG(5, "couldn't ReleaseIO reasoncode=%.2x\n", rc); + + DEBUG(3, "<- reader_release\n"); +} + +static dev_link_t *reader_attach(void) +{ + reader_dev_t *dev; + dev_link_t *link; + client_reg_t client_reg; + int i; + + DEBUG(3, "reader_attach\n"); + for (i=0; i < CM_MAX_DEV; i++) { + if (dev_table[i] == NULL) + break; + } + + if (i == CM_MAX_DEV) { + printk(KERN_NOTICE "all devices in use\n"); + return NULL; + } + + DEBUG(5, "create reader device instance\n"); + dev = kmalloc(sizeof(reader_dev_t), GFP_KERNEL); + if (dev == NULL) + return NULL; + + memset(dev, 0, sizeof(reader_dev_t)); + dev->timeout = CCID_DRIVER_MINIMUM_TIMEOUT; + dev->fTimerExpired = 0; + + link = &dev->link; + link->priv = dev; + + link->conf.IntType = INT_MEMORY_AND_IO; + dev_table[i] = link; + + + DEBUG(5, "Register with Card Services\n"); + client_reg.dev_info = &dev_info; + client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; + client_reg.EventMask= + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + i = pcmcia_register_client(&link->handle, &client_reg); + if (i) { + cs_error(link->handle, RegisterClient, i); + reader_detach(link); + return NULL; + } + init_waitqueue_head(&dev->devq); + init_waitqueue_head(&dev->poll_wait); + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + init_timer(&cmx_poll_timer); + cmx_poll_timer.function = &cmx_do_poll; + + return link; +} + +static void reader_detach_by_devno(int devno,dev_link_t *link) +{ + reader_dev_t *dev=link->priv; + + DEBUG(3, "-> detach_by_devno(devno=%d)\n", devno); + if (link->state & DEV_CONFIG) { + DEBUG(5, "device still configured (try to release it)\n"); + reader_release((u_long)link); + } + + pcmcia_deregister_client(link->handle); + dev_table[devno] = NULL; + DEBUG(5, "freeing dev=%p\n", dev); + kfree(dev); + DEBUG(3, "<- detach_by-devno\n"); + return; +} + +static void reader_detach(dev_link_t *link) +{ + int i; + DEBUG(3, "-> reader_detach(link=%p\n", link); + /* find device */ + for(i=0; i < CM_MAX_DEV; i++) { + if (dev_table[i] == link) + break; + } + if (i == CM_MAX_DEV) { + printk(KERN_WARNING MODULE_NAME + ": detach for unkown device aborted\n"); + return; + } + reader_detach_by_devno(i, link); + DEBUG(3, "<- reader_detach\n"); + return; +} + +static struct file_operations reader_fops = { + .owner = THIS_MODULE, + .llseek = cmx_llseek, + .read = cmx_read, + .write = cmx_write, + .ioctl = cmx_ioctl, + .open = cmx_open, + .release = cmx_close, + .poll = cmx_poll, +}; + +static struct pcmcia_device_id cm4040_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x0223, 0x0200), + PCMCIA_DEVICE_PROD_ID12("OMNIKEY", "CardMan 4040", + 0xE32CDD8C, 0x8F23318B), + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, cm4040_ids); + +static struct pcmcia_driver reader_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "cm4040_cs", + }, + .attach = reader_attach, + .detach = reader_detach, + .event = reader_event, + .id_table = cm4040_ids, +}; + +static int __init cmx_init(void) +{ + printk(KERN_INFO "%s\n", version); + pcmcia_register_driver(&reader_driver); + major = register_chrdev(0, DEVICE_NAME, &reader_fops); + if (major < 0) { + printk(KERN_WARNING MODULE_NAME + ": could not get major number\n"); + return -1; + } + return 0; +} + +static void __exit cmx_exit(void) +{ + int i; + + printk(KERN_INFO MODULE_NAME ": unloading\n"); + pcmcia_unregister_driver(&reader_driver); + for (i=0; i < CM_MAX_DEV; i++) { + if (dev_table[i]) + reader_detach_by_devno(i, dev_table[i]); + } + unregister_chrdev(major, DEVICE_NAME); +} + +module_init(cmx_init); +module_exit(cmx_exit); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/char/pcmcia/cm4040_cs.h b/drivers/char/pcmcia/cm4040_cs.h new file mode 100644 --- /dev/null +++ b/drivers/char/pcmcia/cm4040_cs.h @@ -0,0 +1,52 @@ +#ifndef _CM4040_H_ +#define _CM4040_H_ + +#define CM_MAX_DEV 4 + +#define CM_IOC_MAGIC 'c' +#define CM_IOC_MAXNR 255 + +#define CM_IOSDBGLVL _IOW(CM_IOC_MAGIC, 250, int*) + +#define DEVICE_NAME "cmx" +#define MODULE_NAME "cm4040_cs" + +#define REG_OFFSET_BULK_OUT 0 +#define REG_OFFSET_BULK_IN 0 +#define REG_OFFSET_BUFFER_STATUS 1 +#define REG_OFFSET_SYNC_CONTROL 2 + +#define BSR_BULK_IN_FULL 0x02 +#define BSR_BULK_OUT_FULL 0x01 + +#define SCR_HOST_TO_READER_START 0x80 +#define SCR_ABORT 0x40 +#define SCR_EN_NOTIFY 0x20 +#define SCR_ACK_NOTIFY 0x10 +#define SCR_READER_TO_HOST_DONE 0x08 +#define SCR_HOST_TO_READER_DONE 0x04 +#define SCR_PULSE_INTERRUPT 0x02 +#define SCR_POWER_DOWN 0x01 + + +#define CMD_PC_TO_RDR_ICCPOWERON 0x62 +#define CMD_PC_TO_RDR_GETSLOTSTATUS 0x65 +#define CMD_PC_TO_RDR_ICCPOWEROFF 0x63 +#define CMD_PC_TO_RDR_SECURE 0x69 +#define CMD_PC_TO_RDR_GETPARAMETERS 0x6C +#define CMD_PC_TO_RDR_RESETPARAMETERS 0x6D +#define CMD_PC_TO_RDR_SETPARAMETERS 0x61 +#define CMD_PC_TO_RDR_XFRBLOCK 0x6F +#define CMD_PC_TO_RDR_ESCAPE 0x6B +#define CMD_PC_TO_RDR_ICCCLOCK 0x6E +#define CMD_PC_TO_RDR_TEST_SECURE 0x74 +#define CMD_PC_TO_RDR_OK_SECURE 0x89 + + +#define CMD_RDR_TO_PC_SLOTSTATUS 0x81 +#define CMD_RDR_TO_PC_DATABLOCK 0x80 +#define CMD_RDR_TO_PC_PARAMETERS 0x82 +#define CMD_RDR_TO_PC_ESCAPE 0x83 +#define CMD_RDR_TO_PC_OK_SECURE 0x89 + +#endif /* _CM4040_H_ */