Current Path : /sys/dev/cmx/ |
FreeBSD hs32.drive.ne.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #1: Wed Jan 14 12:18:08 JST 2015 root@hs32.drive.ne.jp:/sys/amd64/compile/hs32 amd64 |
Current File : //sys/dev/cmx/cmx.c |
/*- * Copyright (c) 2006-2007 Daniel Roethlisberger <daniel@roe.ch> * Copyright (c) 2000-2004 OMNIKEY GmbH (www.omnikey.com) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <sys/cdefs.h> __FBSDID("$FreeBSD: release/9.1.0/sys/dev/cmx/cmx.c 176868 2008-03-06 08:09:45Z rink $"); /* * OMNIKEY CardMan 4040 a.k.a. CardMan eXtended (cmx) driver. * This is a PCMCIA based smartcard reader which seems to work * like an I/O port mapped USB CCID smartcard device. * * I/O originally based on Linux driver version 1.1.0 by OMNIKEY. * Dual GPL/BSD. Almost all of the code has been rewritten. * $Omnikey: cm4040_cs.c,v 1.7 2004/10/04 09:08:50 jp Exp $ */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/sockio.h> #include <sys/mbuf.h> #include <sys/poll.h> #include <sys/conf.h> #include <sys/fcntl.h> #include <sys/uio.h> #include <sys/selinfo.h> #include <sys/module.h> #include <sys/bus.h> #include <machine/bus.h> #include <machine/resource.h> #include <sys/rman.h> #include <dev/cmx/cmxvar.h> #include <dev/cmx/cmxreg.h> #ifdef CMX_DEBUG #define DEBUG_printf(dev, fmt, args...) \ device_printf(dev, "%s: " fmt, __FUNCTION__, ##args) #else #define DEBUG_printf(dev, fmt, args...) #endif #define SPIN_COUNT 1000 #define WAIT_TICKS (hz/100) #define POLL_TICKS (hz/10) /* possibly bogus */ #define CCID_DRIVER_BULK_DEFAULT_TIMEOUT (150*hz) #define CCID_DRIVER_ASYNC_POWERUP_TIMEOUT (35*hz) #define CCID_DRIVER_MINIMUM_TIMEOUT (3*hz) #ifdef CMX_DEBUG static char BSRBITS[] = "\020" "\01BULK_OUT_FULL" /* 0x01 */ "\02BULK_IN_FULL" /* 0x02 */ "\03(0x04)"; /* 0x04 */ #ifdef CMX_INTR static char SCRBITS[] = "\020" "\01POWER_DOWN" /* 0x01 */ "\02PULSE_INTERRUPT" /* 0x02 */ "\03HOST_TO_READER_DONE" /* 0x04 */ "\04READER_TO_HOST_DONE" /* 0x08 */ "\05ACK_NOTIFY" /* 0x10 */ "\06EN_NOTIFY" /* 0x20 */ "\07ABORT" /* 0x40 */ "\10HOST_TO_READER_START"; /* 0x80 */ #endif /* CMX_INTR */ static char POLLBITS[] = "\020" "\01POLLIN" /* 0x0001 */ "\02POLLPRI" /* 0x0002 */ "\03POLLOUT" /* 0x0004 */ "\04POLLERR" /* 0x0008 */ "\05POLLHUP" /* 0x0010 */ "\06POLLINVAL" /* 0x0020 */ "\07POLLRDNORM" /* 0x0040 */ "\10POLLRDBAND" /* 0x0080 */ "\11POLLWRBAND"; /* 0x0100 */ static char MODEBITS[] = "\020" "\01READ" /* 0x0001 */ "\02WRITE" /* 0x0002 */ "\03NONBLOCK" /* 0x0004 */ "\04APPEND" /* 0x0008 */ "\05SHLOCK" /* 0x0010 */ "\06EXLOCK" /* 0x0020 */ "\07ASYNC" /* 0x0040 */ "\10FSYNC" /* 0x0080 */ "\11NOFOLLOW" /* 0x0100 */ "\12CREAT" /* 0x0200 */ "\13TRUNK" /* 0x0400 */ "\14EXCL" /* 0x0800 */ "\15(0x1000)" /* 0x1000 */ "\16(0x2000)" /* 0x2000 */ "\17HASLOCK" /* 0x4000 */ "\20NOCTTY" /* 0x8000 */ "\21DIRECT"; /* 0x00010000 */ #endif /* CMX_DEBUG */ devclass_t cmx_devclass; static d_open_t cmx_open; static d_close_t cmx_close; static d_read_t cmx_read; static d_write_t cmx_write; static d_poll_t cmx_poll; #ifdef CMX_INTR static void cmx_intr(void *arg); #endif static struct cdevsw cmx_cdevsw = { .d_version = D_VERSION, .d_open = cmx_open, .d_close = cmx_close, .d_read = cmx_read, .d_write = cmx_write, .d_poll = cmx_poll, .d_name = "cmx", }; /* * Initialize the softc structure. Must be called from * the bus specific device allocation routine. */ void cmx_init_softc(device_t dev) { struct cmx_softc *sc = device_get_softc(dev); sc->dev = dev; sc->timeout = CCID_DRIVER_MINIMUM_TIMEOUT; } /* * Allocate driver resources. Must be called from the * bus specific device allocation routine. Caller must * ensure to call cmx_release_resources to free the * resources when detaching. * Return zero if successful, and ENOMEM if the resources * could not be allocated. */ int cmx_alloc_resources(device_t dev) { struct cmx_softc *sc = device_get_softc(dev); #ifdef CMX_INTR int rv; #endif sc->ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->ioport_rid, RF_ACTIVE); if (!sc->ioport) { device_printf(dev, "failed to allocate io port\n"); return ENOMEM; } sc->bst = rman_get_bustag(sc->ioport); sc->bsh = rman_get_bushandle(sc->ioport); #ifdef CMX_INTR sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE); if (!sc->irq) { device_printf(dev, "failed to allocate irq\n"); return ENOMEM; } if ((rv = bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, cmx_intr, sc, &sc->ih)) != 0) { device_printf(dev, "failed to set up irq\n"); return ENOMEM; } #endif mtx_init(&sc->mtx, device_get_nameunit(dev), "cmx softc lock", MTX_DEF | MTX_RECURSE); callout_init_mtx(&sc->ch, &sc->mtx, 0); return 0; } /* * Release the resources allocated by cmx_allocate_resources. */ void cmx_release_resources(device_t dev) { struct cmx_softc *sc = device_get_softc(dev); mtx_destroy(&sc->mtx); #ifdef CMX_INTR if (sc->ih) { bus_teardown_intr(dev, sc->irq, sc->ih); sc->ih = NULL; } if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq); sc->irq = NULL; } #endif if (sc->ioport) { bus_deactivate_resource(dev, SYS_RES_IOPORT, sc->ioport_rid, sc->ioport); bus_release_resource(dev, SYS_RES_IOPORT, sc->ioport_rid, sc->ioport); sc->ioport = NULL; } return; } /* * Bus independant device attachment routine. Creates the * character device node. */ int cmx_attach(device_t dev) { struct cmx_softc *sc = device_get_softc(dev); if (!sc || sc->dying) return ENXIO; sc->cdev = make_dev(&cmx_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "cmx%d", device_get_unit(dev)); if (!sc->cdev) { device_printf(dev, "failed to create character device\n"); return ENOMEM; } sc->cdev->si_drv1 = sc; return 0; } /* * Bus independant device detachment routine. Makes sure all * allocated resources are freed, callouts disabled and waiting * processes unblocked. */ int cmx_detach(device_t dev) { struct cmx_softc *sc = device_get_softc(dev); DEBUG_printf(dev, "called\n"); sc->dying = 1; CMX_LOCK(sc); if (sc->polling) { DEBUG_printf(sc->dev, "disabling polling\n"); callout_stop(&sc->ch); sc->polling = 0; CMX_UNLOCK(sc); callout_drain(&sc->ch); selwakeuppri(&sc->sel, PZERO); } else { CMX_UNLOCK(sc); } wakeup(sc); destroy_dev(sc->cdev); DEBUG_printf(dev, "releasing resources\n"); cmx_release_resources(dev); return 0; } /* * Wait for buffer status register events. If test is non-zero, * wait until flags are set, otherwise wait until flags are unset. * Will spin SPIN_COUNT times, then sleep until timeout is reached. * Returns zero if event happened, EIO if the timeout was reached, * and ENXIO if the device was detached in the meantime. When that * happens, the caller must quit immediately, since a detach is * in progress. */ static inline int cmx_wait_BSR(struct cmx_softc *sc, uint8_t flags, int test) { int rv; for (int i = 0; i < SPIN_COUNT; i++) { if (cmx_test_BSR(sc, flags, test)) return 0; } for (int i = 0; i * WAIT_TICKS < sc->timeout; i++) { if (cmx_test_BSR(sc, flags, test)) return 0; rv = tsleep(sc, PWAIT|PCATCH, "cmx", WAIT_TICKS); /* * Currently, the only reason for waking up with * rv == 0 is when we are detaching, in which * case sc->dying is always 1. */ if (sc->dying) return ENXIO; if (rv != EAGAIN) return rv; } /* timeout */ return EIO; } /* * Set the sync control register to val. Before and after writing * to the SCR, we wait for the BSR to not signal BULK_OUT_FULL. * Returns zero if successful, or whatever errors cmx_wait_BSR can * return. ENXIO signals that the device has been detached in the * meantime, and that we should leave the kernel immediately. */ static inline int cmx_sync_write_SCR(struct cmx_softc *sc, uint8_t val) { int rv = 0; if ((rv = cmx_wait_BSR(sc, BSR_BULK_OUT_FULL, 0)) != 0) { return rv; } cmx_write_SCR(sc, val); if ((rv = cmx_wait_BSR(sc, BSR_BULK_OUT_FULL, 0)) != 0) { return rv; } return 0; } /* * Returns a suitable timeout value based on the given command byte. * Some commands appear to need longer timeout values than others. */ static inline unsigned long cmx_timeout_by_cmd(uint8_t cmd) { switch (cmd) { 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: return CCID_DRIVER_BULK_DEFAULT_TIMEOUT; case CMD_PC_TO_RDR_ICCPOWERON: return CCID_DRIVER_ASYNC_POWERUP_TIMEOUT; 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: return CCID_DRIVER_MINIMUM_TIMEOUT; } } /* * Periodical callout routine, polling the reader for data * availability. If the reader signals data ready for reading, * wakes up the processes which are waiting in select()/poll(). * Otherwise, reschedules itself with a delay of POLL_TICKS. */ static void cmx_tick(void *xsc) { struct cmx_softc *sc = xsc; uint8_t bsr; CMX_LOCK(sc); if (sc->polling && !sc->dying) { bsr = cmx_read_BSR(sc); DEBUG_printf(sc->dev, "BSR=%b\n", bsr, BSRBITS); if (cmx_test(bsr, BSR_BULK_IN_FULL, 1)) { sc->polling = 0; selwakeuppri(&sc->sel, PZERO); } else { callout_reset(&sc->ch, POLL_TICKS, cmx_tick, sc); } } CMX_UNLOCK(sc); } /* * Open the character device. Only a single process may open the * device at a time. */ static int cmx_open(struct cdev *cdev, int flags, int fmt, struct thread *td) { struct cmx_softc *sc = cdev->si_drv1; if (sc == NULL || sc->dying) return ENXIO; CMX_LOCK(sc); if (sc->open) { CMX_UNLOCK(sc); return EBUSY; } sc->open = 1; CMX_UNLOCK(sc); DEBUG_printf(sc->dev, "open (flags=%b thread=%p)\n", flags, MODEBITS, td); return 0; } /* * Close the character device. */ static int cmx_close(struct cdev *cdev, int flags, int fmt, struct thread *td) { struct cmx_softc *sc = cdev->si_drv1; if (sc == NULL || sc->dying) return ENXIO; CMX_LOCK(sc); if (!sc->open) { CMX_UNLOCK(sc); return EINVAL; } if (sc->polling) { DEBUG_printf(sc->dev, "disabling polling\n"); callout_stop(&sc->ch); sc->polling = 0; CMX_UNLOCK(sc); callout_drain(&sc->ch); selwakeuppri(&sc->sel, PZERO); CMX_LOCK(sc); } sc->open = 0; CMX_UNLOCK(sc); DEBUG_printf(sc->dev, "close (flags=%b thread=%p)\n", flags, MODEBITS, td); return 0; } /* * Read from the character device. * Returns zero if successful, ENXIO if dying, EINVAL if an attempt * was made to read less than CMX_MIN_RDLEN bytes or less than the * device has available, or any of the errors that cmx_sync_write_SCR * can return. Partial reads are not supported. */ static int cmx_read(struct cdev *cdev, struct uio *uio, int flag) { struct cmx_softc *sc = cdev->si_drv1; unsigned long bytes_left; uint8_t uc; int rv, amnt, offset; if (sc == NULL || sc->dying) return ENXIO; DEBUG_printf(sc->dev, "called (len=%d flag=%b)\n", uio->uio_resid, flag, MODEBITS); CMX_LOCK(sc); if (sc->polling) { DEBUG_printf(sc->dev, "disabling polling\n"); callout_stop(&sc->ch); sc->polling = 0; CMX_UNLOCK(sc); callout_drain(&sc->ch); selwakeuppri(&sc->sel, PZERO); } else { CMX_UNLOCK(sc); } if (uio->uio_resid == 0) { return 0; } if (uio->uio_resid < CMX_MIN_RDLEN) { return EINVAL; } if (flag & O_NONBLOCK) { if (cmx_test_BSR(sc, BSR_BULK_IN_FULL, 0)) { return EAGAIN; } } for (int i = 0; i < 5; i++) { if ((rv = cmx_wait_BSR(sc, BSR_BULK_IN_FULL, 1)) != 0) { return rv; } sc->buf[i] = cmx_read_DTR(sc); DEBUG_printf(sc->dev, "buf[%02x]=%02x\n", i, sc->buf[i]); } bytes_left = CMX_MIN_RDLEN + (0x000000FF&((char)sc->buf[1])) + (0x0000FF00&((char)sc->buf[2] << 8)) + (0x00FF0000&((char)sc->buf[3] << 16)) + (0xFF000000&((char)sc->buf[4] << 24)); DEBUG_printf(sc->dev, "msgsz=%lu\n", bytes_left); if (uio->uio_resid < bytes_left) { return EINVAL; } offset = 5; /* prefetched header */ while (bytes_left > 0) { amnt = MIN(bytes_left, sizeof(sc->buf)); for (int i = offset; i < amnt; i++) { if ((rv = cmx_wait_BSR(sc, BSR_BULK_IN_FULL, 1))!=0) { return rv; } sc->buf[i] = cmx_read_DTR(sc); DEBUG_printf(sc->dev, "buf[%02x]=%02x\n", i, sc->buf[i]); } if ((rv = uiomove(sc->buf, amnt, uio)) != 0) { DEBUG_printf(sc->dev, "uiomove failed (%d)\n", rv); return rv; } if (offset) offset = 0; bytes_left -= amnt; } if ((rv = cmx_wait_BSR(sc, BSR_BULK_IN_FULL, 1)) != 0) { return rv; } if ((rv = cmx_sync_write_SCR(sc, SCR_READER_TO_HOST_DONE)) != 0) { return rv; } uc = cmx_read_DTR(sc); DEBUG_printf(sc->dev, "success (DTR=%02x)\n", uc); return 0; } /* * Write to the character device. * Returns zero if successful, NXIO if dying, EINVAL if less data * written than CMX_MIN_WRLEN, or any of the errors that cmx_sync_SCR * can return. */ static int cmx_write(struct cdev *cdev, struct uio *uio, int flag) { struct cmx_softc *sc = cdev->si_drv1; int rv, amnt; if (sc == NULL || sc->dying) return ENXIO; DEBUG_printf(sc->dev, "called (len=%d flag=%b)\n", uio->uio_resid, flag, MODEBITS); if (uio->uio_resid == 0) { return 0; } if (uio->uio_resid < CMX_MIN_WRLEN) { return EINVAL; } if ((rv = cmx_sync_write_SCR(sc, SCR_HOST_TO_READER_START)) != 0) { return rv; } sc->timeout = 0; while (uio->uio_resid > 0) { amnt = MIN(uio->uio_resid, sizeof(sc->buf)); if ((rv = uiomove(sc->buf, amnt, uio)) != 0) { DEBUG_printf(sc->dev, "uiomove failed (%d)\n", rv); /* wildly guessed attempt to notify device */ sc->timeout = CCID_DRIVER_MINIMUM_TIMEOUT; cmx_sync_write_SCR(sc, SCR_HOST_TO_READER_DONE); return rv; } if (sc->timeout == 0) { sc->timeout = cmx_timeout_by_cmd(sc->buf[0]); DEBUG_printf(sc->dev, "cmd=%02x timeout=%lu\n", sc->buf[0], sc->timeout); } for (int i = 0; i < amnt; i++) { if ((rv = cmx_wait_BSR(sc, BSR_BULK_OUT_FULL, 0))!=0) { return rv; } cmx_write_DTR(sc, sc->buf[i]); DEBUG_printf(sc->dev, "buf[%02x]=%02x\n", i, sc->buf[i]); } } if ((rv = cmx_sync_write_SCR(sc, SCR_HOST_TO_READER_DONE)) != 0) { return rv; } DEBUG_printf(sc->dev, "success\n"); return 0; } /* * Poll handler. Writing is always possible, reading is only possible * if BSR_BULK_IN_FULL is set. Will start the cmx_tick callout and * set sc->polling. */ static int cmx_poll(struct cdev *cdev, int events, struct thread *td) { struct cmx_softc *sc = cdev->si_drv1; int revents = 0; uint8_t bsr = 0; if (sc == NULL || sc->dying) return ENXIO; bsr = cmx_read_BSR(sc); DEBUG_printf(sc->dev, "called (events=%b BSR=%b)\n", events, POLLBITS, bsr, BSRBITS); revents = events & (POLLOUT | POLLWRNORM); if (events & (POLLIN | POLLRDNORM)) { if (cmx_test(bsr, BSR_BULK_IN_FULL, 1)) { revents |= events & (POLLIN | POLLRDNORM); } else { selrecord(td, &sc->sel); CMX_LOCK(sc); if (!sc->polling) { DEBUG_printf(sc->dev, "enabling polling\n"); sc->polling = 1; callout_reset(&sc->ch, POLL_TICKS, cmx_tick, sc); } else { DEBUG_printf(sc->dev, "already polling\n"); } CMX_UNLOCK(sc); } } DEBUG_printf(sc->dev, "success (revents=%b)\n", revents, POLLBITS); return revents; } #ifdef CMX_INTR /* * Interrupt handler. Currently has no function except to * print register status (if debugging is also enabled). */ static void cmx_intr(void *arg) { struct cmx_softc *sc = (struct cmx_softc *)arg; if (sc == NULL || sc->dying) return; DEBUG_printf(sc->dev, "received interrupt (SCR=%b BSR=%b)\n", cmx_read_SCR(sc), SCRBITS, cmx_read_BSR(sc), BSRBITS); return; } #endif