ee4e7828cd
PR: kern/114582 Submitted by: Daniel Roethlisberger <daniel@roe.ch> Reviewed by: imp, myself Tested by: johans, myself MFC after: 2 weeks
702 lines
17 KiB
C
702 lines
17 KiB
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$");
|
|
|
|
/*
|
|
* 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
|
|
|