#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/event.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/lock.h>
#include <sys/device.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/resource.h>
#include <sys/rman.h>
#include "cmxvar.h"
#include "cmxreg.h"
#ifdef CMX_DEBUG
#define DEBUG_printf(dev, fmt, args...) \
device_printf(dev, "%s: " fmt, __func__, ##args)
#else
#define DEBUG_printf(dev, fmt, args...)
#endif
#define SPIN_COUNT 1000
#define WAIT_TICKS (hz/100)
#define POLL_TICKS (hz/10)
#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"
"\02BULK_IN_FULL"
"\03(0x04)";
#ifdef CMX_INTR
static char SCRBITS[] = "\020"
"\01POWER_DOWN"
"\02PULSE_INTERRUPT"
"\03HOST_TO_READER_DONE"
"\04READER_TO_HOST_DONE"
"\05ACK_NOTIFY"
"\06EN_NOTIFY"
"\07ABORT"
"\10HOST_TO_READER_START";
#endif
static char POLLBITS[] = "\020"
"\01POLLIN"
"\02POLLPRI"
"\03POLLOUT"
"\04POLLERR"
"\05POLLHUP"
"\06POLLINVAL"
"\07POLLRDNORM"
"\10POLLRDBAND"
"\11POLLWRBAND";
static char MODEBITS[] = "\020"
"\01READ"
"\02WRITE"
"\03NONBLOCK"
"\04APPEND"
"\05SHLOCK"
"\06EXLOCK"
"\07ASYNC"
"\10FSYNC"
"\11NOFOLLOW"
"\12CREAT"
"\13TRUNK"
"\14EXCL"
"\15(0x1000)"
"\16(0x2000)"
"\17HASLOCK"
"\20NOCTTY"
"\21DIRECT";
#endif
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_kqfilter_t cmx_kqfilter;
#ifdef CMX_INTR
static void cmx_intr(void *arg);
#endif
static void cmx_filter_detach(struct knote *);
static int cmx_filter_read(struct knote *, long);
static int cmx_filter_write(struct knote *, long);
static struct dev_ops cmx_ops = {
{ "cmx", 0, 0 },
.d_open = cmx_open,
.d_close = cmx_close,
.d_read = cmx_read,
.d_write = cmx_write,
.d_kqfilter = cmx_kqfilter
};
void
cmx_init_softc(device_t dev)
{
struct cmx_softc *sc = device_get_softc(dev);
sc->dev = dev;
sc->timeout = CCID_DRIVER_MINIMUM_TIMEOUT;
}
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, 0, cmx_intr, sc,
&sc->ih, NULL)) != 0) {
device_printf(dev, "failed to set up irq\n");
return ENOMEM;
}
#endif
lockinit(&sc->mtx, "cmx softc lock", 0, LK_CANRECURSE);
callout_init(&sc->ch);
return 0;
}
void
cmx_release_resources(device_t dev)
{
struct cmx_softc *sc = device_get_softc(dev);
lockuninit(&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;
}
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_ops, 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;
}
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);
KNOTE(&sc->kq.ki_note, 0);
} else {
CMX_UNLOCK(sc);
}
wakeup(sc);
DEBUG_printf(dev, "releasing resources\n");
cmx_release_resources(dev);
dev_ops_remove_minor(&cmx_ops, device_get_unit(dev));
return 0;
}
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, PCATCH, "cmx", WAIT_TICKS);
if (sc->dying)
return ENXIO;
if (rv != EAGAIN)
return rv;
}
return EIO;
}
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;
}
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;
}
}
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=%pb%i\n", BSRBITS, bsr);
if (cmx_test(bsr, BSR_BULK_IN_FULL, 1)) {
sc->polling = 0;
KNOTE(&sc->kq.ki_note, 0);
} else {
callout_reset(&sc->ch, POLL_TICKS, cmx_tick, sc);
}
}
CMX_UNLOCK(sc);
}
static int
cmx_open(struct dev_open_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct cmx_softc *sc;
sc = devclass_get_softc(cmx_devclass, minor(dev));
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=%pb%i thread=%p)\n",
MODEBITS, ap->a_oflags, curthread);
return 0;
}
static int
cmx_close(struct dev_close_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct cmx_softc *sc;
sc = devclass_get_softc(cmx_devclass, minor(dev));
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);
KNOTE(&sc->kq.ki_note, 0);
CMX_LOCK(sc);
}
sc->open = 0;
CMX_UNLOCK(sc);
DEBUG_printf(sc->dev, "close (flags=%pb%i thread=%p)\n",
MODEBITS, ap->a_fflag, curthread);
return 0;
}
static int
cmx_read(struct dev_read_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct cmx_softc *sc;
struct uio *uio = ap->a_uio;
unsigned long bytes_left;
uint8_t uc;
int rv, amnt, offset;
sc = devclass_get_softc(cmx_devclass, minor(dev));
if (sc == NULL || sc->dying)
return ENXIO;
DEBUG_printf(sc->dev, "called (len=%d flag=%pb%i)\n",
uio->uio_resid, MODEBITS, ap->a_ioflag);
CMX_LOCK(sc);
if (sc->polling) {
DEBUG_printf(sc->dev, "disabling polling\n");
callout_stop(&sc->ch);
sc->polling = 0;
CMX_UNLOCK(sc);
KNOTE(&sc->kq.ki_note, 0);
} else {
CMX_UNLOCK(sc);
}
if (uio->uio_resid == 0) {
return 0;
}
if (uio->uio_resid < CMX_MIN_RDLEN) {
return EINVAL;
}
if (ap->a_ioflag & 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;
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;
}
static int
cmx_write(struct dev_write_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct cmx_softc *sc;
struct uio *uio = ap->a_uio;
int rv, amnt;
sc = devclass_get_softc(cmx_devclass, minor(dev));
if (sc == NULL || sc->dying)
return ENXIO;
DEBUG_printf(sc->dev, "called (len=%d flag=%pb%i)\n",
uio->uio_resid, MODEBITS, ap->a_ioflag);
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);
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;
}
static struct filterops cmx_read_filterops =
{ FILTEROP_ISFD, NULL, cmx_filter_detach, cmx_filter_read };
static struct filterops cmx_write_filterops =
{ FILTEROP_ISFD, NULL, cmx_filter_detach, cmx_filter_write };
static int
cmx_kqfilter(struct dev_kqfilter_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct knote *kn = ap->a_kn;
struct cmx_softc *sc;
struct klist *klist;
ap->a_result = 0;
sc = devclass_get_softc(cmx_devclass, minor(dev));
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &cmx_read_filterops;
kn->kn_hook = (caddr_t)sc;
break;
case EVFILT_WRITE:
kn->kn_fop = &cmx_write_filterops;
kn->kn_hook = (caddr_t)sc;
break;
default:
ap->a_result = EOPNOTSUPP;
return (0);
}
klist = &sc->kq.ki_note;
knote_insert(klist, kn);
return (0);
}
static void
cmx_filter_detach(struct knote *kn)
{
struct cmx_softc *sc = (struct cmx_softc *)kn->kn_hook;
struct klist *klist = &sc->kq.ki_note;
knote_remove(klist, kn);
}
static int
cmx_filter_read(struct knote *kn, long hint)
{
struct cmx_softc *sc = (struct cmx_softc *)kn->kn_hook;
int ready = 0;
uint8_t bsr = 0;
if (sc == NULL || sc->dying) {
kn->kn_flags |= (EV_EOF | EV_NODATA);
return (1);
}
bsr = cmx_read_BSR(sc);
if (cmx_test(bsr, BSR_BULK_IN_FULL, 1)) {
ready = 1;
} else {
CMX_LOCK(sc);
if (!sc->polling) {
sc->polling = 1;
callout_reset(&sc->ch, POLL_TICKS,
cmx_tick, sc);
}
CMX_UNLOCK(sc);
}
return (ready);
}
static int
cmx_filter_write(struct knote *kn, long hint)
{
return (1);
}
#ifdef CMX_INTR
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=%pb%i BSR=%pb%i)\n",
SCRBITS, cmx_read_SCR(sc),
BSRBITS, cmx_read_BSR(sc));
return;
}
#endif