| 1 | /*- |
| 2 | * Copyright (c) 2006-2007 Daniel Roethlisberger <daniel@roe.ch> |
| 3 | * Copyright (c) 2000-2004 OMNIKEY GmbH (www.omnikey.com) |
| 4 | * All rights reserved. |
| 5 | * |
| 6 | * Redistribution and use in source and binary forms, with or without |
| 7 | * modification, are permitted provided that the following conditions |
| 8 | * are met: |
| 9 | * 1. Redistributions of source code must retain the above copyright |
| 10 | * notice unmodified, this list of conditions, and the following |
| 11 | * disclaimer. |
| 12 | * 2. Redistributions in binary form must reproduce the above copyright |
| 13 | * notice, this list of conditions and the following disclaimer in the |
| 14 | * documentation and/or other materials provided with the distribution. |
| 15 | * |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 26 | * SUCH DAMAGE. |
| 27 | * |
| 28 | * $FreeBSD: src/sys/dev/cmx/cmx.c,v 1.1 2008/03/06 08:09:45 rink Exp $ |
| 29 | * $DragonFly: src/sys/dev/misc/cmx/cmx.c,v 1.2 2008/08/08 18:33:11 hasso Exp $ |
| 30 | */ |
| 31 | |
| 32 | /* |
| 33 | * OMNIKEY CardMan 4040 a.k.a. CardMan eXtended (cmx) driver. |
| 34 | * This is a PCMCIA based smartcard reader which seems to work |
| 35 | * like an I/O port mapped USB CCID smartcard device. |
| 36 | * |
| 37 | * I/O originally based on Linux driver version 1.1.0 by OMNIKEY. |
| 38 | * Dual GPL/BSD. Almost all of the code has been rewritten. |
| 39 | * $Omnikey: cm4040_cs.c,v 1.7 2004/10/04 09:08:50 jp Exp $ |
| 40 | */ |
| 41 | |
| 42 | #include <sys/param.h> |
| 43 | #include <sys/systm.h> |
| 44 | #include <sys/kernel.h> |
| 45 | #include <sys/sockio.h> |
| 46 | #include <sys/mbuf.h> |
| 47 | #include <sys/event.h> |
| 48 | #include <sys/conf.h> |
| 49 | #include <sys/fcntl.h> |
| 50 | #include <sys/uio.h> |
| 51 | #include <sys/types.h> |
| 52 | #include <sys/lock.h> |
| 53 | #include <sys/device.h> |
| 54 | #include <sys/thread2.h> |
| 55 | |
| 56 | #include <sys/module.h> |
| 57 | #include <sys/bus.h> |
| 58 | #include <sys/resource.h> |
| 59 | #include <sys/rman.h> |
| 60 | |
| 61 | #include "cmxvar.h" |
| 62 | #include "cmxreg.h" |
| 63 | |
| 64 | #ifdef CMX_DEBUG |
| 65 | #define DEBUG_printf(dev, fmt, args...) \ |
| 66 | device_printf(dev, "%s: " fmt, __FUNCTION__, ##args) |
| 67 | #else |
| 68 | #define DEBUG_printf(dev, fmt, args...) |
| 69 | #endif |
| 70 | |
| 71 | #define SPIN_COUNT 1000 |
| 72 | #define WAIT_TICKS (hz/100) |
| 73 | #define POLL_TICKS (hz/10) |
| 74 | |
| 75 | /* possibly bogus */ |
| 76 | #define CCID_DRIVER_BULK_DEFAULT_TIMEOUT (150*hz) |
| 77 | #define CCID_DRIVER_ASYNC_POWERUP_TIMEOUT (35*hz) |
| 78 | #define CCID_DRIVER_MINIMUM_TIMEOUT (3*hz) |
| 79 | |
| 80 | #ifdef CMX_DEBUG |
| 81 | static char BSRBITS[] = "\020" |
| 82 | "\01BULK_OUT_FULL" /* 0x01 */ |
| 83 | "\02BULK_IN_FULL" /* 0x02 */ |
| 84 | "\03(0x04)"; /* 0x04 */ |
| 85 | #ifdef CMX_INTR |
| 86 | static char SCRBITS[] = "\020" |
| 87 | "\01POWER_DOWN" /* 0x01 */ |
| 88 | "\02PULSE_INTERRUPT" /* 0x02 */ |
| 89 | "\03HOST_TO_READER_DONE" /* 0x04 */ |
| 90 | "\04READER_TO_HOST_DONE" /* 0x08 */ |
| 91 | "\05ACK_NOTIFY" /* 0x10 */ |
| 92 | "\06EN_NOTIFY" /* 0x20 */ |
| 93 | "\07ABORT" /* 0x40 */ |
| 94 | "\10HOST_TO_READER_START"; /* 0x80 */ |
| 95 | #endif /* CMX_INTR */ |
| 96 | static char POLLBITS[] = "\020" |
| 97 | "\01POLLIN" /* 0x0001 */ |
| 98 | "\02POLLPRI" /* 0x0002 */ |
| 99 | "\03POLLOUT" /* 0x0004 */ |
| 100 | "\04POLLERR" /* 0x0008 */ |
| 101 | "\05POLLHUP" /* 0x0010 */ |
| 102 | "\06POLLINVAL" /* 0x0020 */ |
| 103 | "\07POLLRDNORM" /* 0x0040 */ |
| 104 | "\10POLLRDBAND" /* 0x0080 */ |
| 105 | "\11POLLWRBAND"; /* 0x0100 */ |
| 106 | static char MODEBITS[] = "\020" |
| 107 | "\01READ" /* 0x0001 */ |
| 108 | "\02WRITE" /* 0x0002 */ |
| 109 | "\03NONBLOCK" /* 0x0004 */ |
| 110 | "\04APPEND" /* 0x0008 */ |
| 111 | "\05SHLOCK" /* 0x0010 */ |
| 112 | "\06EXLOCK" /* 0x0020 */ |
| 113 | "\07ASYNC" /* 0x0040 */ |
| 114 | "\10FSYNC" /* 0x0080 */ |
| 115 | "\11NOFOLLOW" /* 0x0100 */ |
| 116 | "\12CREAT" /* 0x0200 */ |
| 117 | "\13TRUNK" /* 0x0400 */ |
| 118 | "\14EXCL" /* 0x0800 */ |
| 119 | "\15(0x1000)" /* 0x1000 */ |
| 120 | "\16(0x2000)" /* 0x2000 */ |
| 121 | "\17HASLOCK" /* 0x4000 */ |
| 122 | "\20NOCTTY" /* 0x8000 */ |
| 123 | "\21DIRECT"; /* 0x00010000 */ |
| 124 | #endif /* CMX_DEBUG */ |
| 125 | |
| 126 | devclass_t cmx_devclass; |
| 127 | |
| 128 | static d_open_t cmx_open; |
| 129 | static d_close_t cmx_close; |
| 130 | static d_read_t cmx_read; |
| 131 | static d_write_t cmx_write; |
| 132 | static d_kqfilter_t cmx_kqfilter; |
| 133 | #ifdef CMX_INTR |
| 134 | static void cmx_intr(void *arg); |
| 135 | #endif |
| 136 | |
| 137 | static void cmx_filter_detach(struct knote *); |
| 138 | static int cmx_filter_read(struct knote *, long); |
| 139 | static int cmx_filter_write(struct knote *, long); |
| 140 | |
| 141 | #define CDEV_MAJOR 185 |
| 142 | static struct dev_ops cmx_ops = { |
| 143 | { "cmx", CDEV_MAJOR, D_KQFILTER }, |
| 144 | .d_open = cmx_open, |
| 145 | .d_close = cmx_close, |
| 146 | .d_read = cmx_read, |
| 147 | .d_write = cmx_write, |
| 148 | .d_kqfilter = cmx_kqfilter |
| 149 | }; |
| 150 | |
| 151 | /* |
| 152 | * Initialize the softc structure. Must be called from |
| 153 | * the bus specific device allocation routine. |
| 154 | */ |
| 155 | void |
| 156 | cmx_init_softc(device_t dev) |
| 157 | { |
| 158 | struct cmx_softc *sc = device_get_softc(dev); |
| 159 | sc->dev = dev; |
| 160 | sc->timeout = CCID_DRIVER_MINIMUM_TIMEOUT; |
| 161 | } |
| 162 | |
| 163 | /* |
| 164 | * Allocate driver resources. Must be called from the |
| 165 | * bus specific device allocation routine. Caller must |
| 166 | * ensure to call cmx_release_resources to free the |
| 167 | * resources when detaching. |
| 168 | * Return zero if successful, and ENOMEM if the resources |
| 169 | * could not be allocated. |
| 170 | */ |
| 171 | int |
| 172 | cmx_alloc_resources(device_t dev) |
| 173 | { |
| 174 | struct cmx_softc *sc = device_get_softc(dev); |
| 175 | #ifdef CMX_INTR |
| 176 | int rv; |
| 177 | #endif |
| 178 | |
| 179 | sc->ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, |
| 180 | &sc->ioport_rid, RF_ACTIVE); |
| 181 | if (!sc->ioport) { |
| 182 | device_printf(dev, "failed to allocate io port\n"); |
| 183 | return ENOMEM; |
| 184 | } |
| 185 | sc->bst = rman_get_bustag(sc->ioport); |
| 186 | sc->bsh = rman_get_bushandle(sc->ioport); |
| 187 | |
| 188 | #ifdef CMX_INTR |
| 189 | sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, |
| 190 | &sc->irq_rid, RF_ACTIVE); |
| 191 | if (!sc->irq) { |
| 192 | device_printf(dev, "failed to allocate irq\n"); |
| 193 | return ENOMEM; |
| 194 | } |
| 195 | if ((rv = bus_setup_intr(dev, sc->irq, 0, cmx_intr, sc, |
| 196 | &sc->ih, NULL)) != 0) { |
| 197 | device_printf(dev, "failed to set up irq\n"); |
| 198 | return ENOMEM; |
| 199 | } |
| 200 | #endif |
| 201 | |
| 202 | lockinit(&sc->mtx, "cmx softc lock", 0, LK_CANRECURSE); |
| 203 | callout_init(&sc->ch); |
| 204 | |
| 205 | return 0; |
| 206 | } |
| 207 | |
| 208 | /* |
| 209 | * Release the resources allocated by cmx_allocate_resources. |
| 210 | */ |
| 211 | void |
| 212 | cmx_release_resources(device_t dev) |
| 213 | { |
| 214 | struct cmx_softc *sc = device_get_softc(dev); |
| 215 | |
| 216 | lockuninit(&sc->mtx); |
| 217 | |
| 218 | #ifdef CMX_INTR |
| 219 | if (sc->ih) { |
| 220 | bus_teardown_intr(dev, sc->irq, sc->ih); |
| 221 | sc->ih = NULL; |
| 222 | } |
| 223 | if (sc->irq) { |
| 224 | bus_release_resource(dev, SYS_RES_IRQ, |
| 225 | sc->irq_rid, sc->irq); |
| 226 | sc->irq = NULL; |
| 227 | } |
| 228 | #endif |
| 229 | |
| 230 | if (sc->ioport) { |
| 231 | bus_deactivate_resource(dev, SYS_RES_IOPORT, |
| 232 | sc->ioport_rid, sc->ioport); |
| 233 | bus_release_resource(dev, SYS_RES_IOPORT, |
| 234 | sc->ioport_rid, sc->ioport); |
| 235 | sc->ioport = NULL; |
| 236 | } |
| 237 | return; |
| 238 | } |
| 239 | |
| 240 | /* |
| 241 | * Bus independant device attachment routine. Creates the |
| 242 | * character device node. |
| 243 | */ |
| 244 | int |
| 245 | cmx_attach(device_t dev) |
| 246 | { |
| 247 | struct cmx_softc *sc = device_get_softc(dev); |
| 248 | |
| 249 | if (!sc || sc->dying) |
| 250 | return ENXIO; |
| 251 | |
| 252 | sc->cdev = make_dev(&cmx_ops, 0, UID_ROOT, GID_WHEEL, 0600, |
| 253 | "cmx%d", device_get_unit(dev)); |
| 254 | if (!sc->cdev) { |
| 255 | device_printf(dev, "failed to create character device\n"); |
| 256 | return ENOMEM; |
| 257 | } |
| 258 | sc->cdev->si_drv1 = sc; |
| 259 | |
| 260 | return 0; |
| 261 | } |
| 262 | |
| 263 | /* |
| 264 | * Bus independant device detachment routine. Makes sure all |
| 265 | * allocated resources are freed, callouts disabled and waiting |
| 266 | * processes unblocked. |
| 267 | */ |
| 268 | int |
| 269 | cmx_detach(device_t dev) |
| 270 | { |
| 271 | struct cmx_softc *sc = device_get_softc(dev); |
| 272 | |
| 273 | DEBUG_printf(dev, "called\n"); |
| 274 | |
| 275 | sc->dying = 1; |
| 276 | |
| 277 | CMX_LOCK(sc); |
| 278 | if (sc->polling) { |
| 279 | DEBUG_printf(sc->dev, "disabling polling\n"); |
| 280 | callout_stop(&sc->ch); |
| 281 | sc->polling = 0; |
| 282 | CMX_UNLOCK(sc); |
| 283 | KNOTE(&sc->kq.ki_note, 0); |
| 284 | } else { |
| 285 | CMX_UNLOCK(sc); |
| 286 | } |
| 287 | |
| 288 | wakeup(sc); |
| 289 | DEBUG_printf(dev, "releasing resources\n"); |
| 290 | cmx_release_resources(dev); |
| 291 | dev_ops_remove_minor(&cmx_ops, device_get_unit(dev)); |
| 292 | |
| 293 | return 0; |
| 294 | } |
| 295 | |
| 296 | /* |
| 297 | * Wait for buffer status register events. If test is non-zero, |
| 298 | * wait until flags are set, otherwise wait until flags are unset. |
| 299 | * Will spin SPIN_COUNT times, then sleep until timeout is reached. |
| 300 | * Returns zero if event happened, EIO if the timeout was reached, |
| 301 | * and ENXIO if the device was detached in the meantime. When that |
| 302 | * happens, the caller must quit immediately, since a detach is |
| 303 | * in progress. |
| 304 | */ |
| 305 | static inline int |
| 306 | cmx_wait_BSR(struct cmx_softc *sc, uint8_t flags, int test) |
| 307 | { |
| 308 | int rv; |
| 309 | |
| 310 | for (int i = 0; i < SPIN_COUNT; i++) { |
| 311 | if (cmx_test_BSR(sc, flags, test)) |
| 312 | return 0; |
| 313 | } |
| 314 | |
| 315 | for (int i = 0; i * WAIT_TICKS < sc->timeout; i++) { |
| 316 | if (cmx_test_BSR(sc, flags, test)) |
| 317 | return 0; |
| 318 | rv = tsleep(sc, PCATCH, "cmx", WAIT_TICKS); |
| 319 | /* |
| 320 | * Currently, the only reason for waking up with |
| 321 | * rv == 0 is when we are detaching, in which |
| 322 | * case sc->dying is always 1. |
| 323 | */ |
| 324 | if (sc->dying) |
| 325 | return ENXIO; |
| 326 | if (rv != EAGAIN) |
| 327 | return rv; |
| 328 | } |
| 329 | |
| 330 | /* timeout */ |
| 331 | return EIO; |
| 332 | } |
| 333 | |
| 334 | /* |
| 335 | * Set the sync control register to val. Before and after writing |
| 336 | * to the SCR, we wait for the BSR to not signal BULK_OUT_FULL. |
| 337 | * Returns zero if successful, or whatever errors cmx_wait_BSR can |
| 338 | * return. ENXIO signals that the device has been detached in the |
| 339 | * meantime, and that we should leave the kernel immediately. |
| 340 | */ |
| 341 | static inline int |
| 342 | cmx_sync_write_SCR(struct cmx_softc *sc, uint8_t val) |
| 343 | { |
| 344 | int rv = 0; |
| 345 | |
| 346 | if ((rv = cmx_wait_BSR(sc, BSR_BULK_OUT_FULL, 0)) != 0) { |
| 347 | return rv; |
| 348 | } |
| 349 | |
| 350 | cmx_write_SCR(sc, val); |
| 351 | |
| 352 | if ((rv = cmx_wait_BSR(sc, BSR_BULK_OUT_FULL, 0)) != 0) { |
| 353 | return rv; |
| 354 | } |
| 355 | |
| 356 | return 0; |
| 357 | } |
| 358 | |
| 359 | /* |
| 360 | * Returns a suitable timeout value based on the given command byte. |
| 361 | * Some commands appear to need longer timeout values than others. |
| 362 | */ |
| 363 | static inline unsigned long |
| 364 | cmx_timeout_by_cmd(uint8_t cmd) |
| 365 | { |
| 366 | switch (cmd) { |
| 367 | case CMD_PC_TO_RDR_XFRBLOCK: |
| 368 | case CMD_PC_TO_RDR_SECURE: |
| 369 | case CMD_PC_TO_RDR_TEST_SECURE: |
| 370 | case CMD_PC_TO_RDR_OK_SECURE: |
| 371 | return CCID_DRIVER_BULK_DEFAULT_TIMEOUT; |
| 372 | |
| 373 | case CMD_PC_TO_RDR_ICCPOWERON: |
| 374 | return CCID_DRIVER_ASYNC_POWERUP_TIMEOUT; |
| 375 | |
| 376 | case CMD_PC_TO_RDR_GETSLOTSTATUS: |
| 377 | case CMD_PC_TO_RDR_ICCPOWEROFF: |
| 378 | case CMD_PC_TO_RDR_GETPARAMETERS: |
| 379 | case CMD_PC_TO_RDR_RESETPARAMETERS: |
| 380 | case CMD_PC_TO_RDR_SETPARAMETERS: |
| 381 | case CMD_PC_TO_RDR_ESCAPE: |
| 382 | case CMD_PC_TO_RDR_ICCCLOCK: |
| 383 | default: |
| 384 | return CCID_DRIVER_MINIMUM_TIMEOUT; |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | /* |
| 389 | * Periodical callout routine, polling the reader for data |
| 390 | * availability. If the reader signals data ready for reading, |
| 391 | * wakes up the processes which are waiting in select()/poll()/kevent(). |
| 392 | * Otherwise, reschedules itself with a delay of POLL_TICKS. |
| 393 | */ |
| 394 | static void |
| 395 | cmx_tick(void *xsc) |
| 396 | { |
| 397 | struct cmx_softc *sc = xsc; |
| 398 | uint8_t bsr; |
| 399 | |
| 400 | CMX_LOCK(sc); |
| 401 | if (sc->polling && !sc->dying) { |
| 402 | bsr = cmx_read_BSR(sc); |
| 403 | DEBUG_printf(sc->dev, "BSR=%b\n", bsr, BSRBITS); |
| 404 | if (cmx_test(bsr, BSR_BULK_IN_FULL, 1)) { |
| 405 | sc->polling = 0; |
| 406 | KNOTE(&sc->kq.ki_note, 0); |
| 407 | } else { |
| 408 | callout_reset(&sc->ch, POLL_TICKS, cmx_tick, sc); |
| 409 | } |
| 410 | } |
| 411 | CMX_UNLOCK(sc); |
| 412 | } |
| 413 | |
| 414 | /* |
| 415 | * Open the character device. Only a single process may open the |
| 416 | * device at a time. |
| 417 | */ |
| 418 | static int |
| 419 | cmx_open(struct dev_open_args *ap) |
| 420 | { |
| 421 | cdev_t dev = ap->a_head.a_dev; |
| 422 | struct cmx_softc *sc; |
| 423 | |
| 424 | sc = devclass_get_softc(cmx_devclass, minor(dev)); |
| 425 | if (sc == NULL || sc->dying) |
| 426 | return ENXIO; |
| 427 | |
| 428 | CMX_LOCK(sc); |
| 429 | if (sc->open) { |
| 430 | CMX_UNLOCK(sc); |
| 431 | return EBUSY; |
| 432 | } |
| 433 | sc->open = 1; |
| 434 | CMX_UNLOCK(sc); |
| 435 | |
| 436 | DEBUG_printf(sc->dev, "open (flags=%b thread=%p)\n", |
| 437 | ap->a_oflags, MODEBITS, curthread); |
| 438 | return 0; |
| 439 | } |
| 440 | |
| 441 | /* |
| 442 | * Close the character device. |
| 443 | */ |
| 444 | static int |
| 445 | cmx_close(struct dev_close_args *ap) |
| 446 | { |
| 447 | cdev_t dev = ap->a_head.a_dev; |
| 448 | struct cmx_softc *sc; |
| 449 | |
| 450 | sc = devclass_get_softc(cmx_devclass, minor(dev)); |
| 451 | if (sc == NULL || sc->dying) |
| 452 | return ENXIO; |
| 453 | |
| 454 | CMX_LOCK(sc); |
| 455 | if (!sc->open) { |
| 456 | CMX_UNLOCK(sc); |
| 457 | return EINVAL; |
| 458 | } |
| 459 | if (sc->polling) { |
| 460 | DEBUG_printf(sc->dev, "disabling polling\n"); |
| 461 | callout_stop(&sc->ch); |
| 462 | sc->polling = 0; |
| 463 | CMX_UNLOCK(sc); |
| 464 | KNOTE(&sc->kq.ki_note, 0); |
| 465 | CMX_LOCK(sc); |
| 466 | } |
| 467 | sc->open = 0; |
| 468 | CMX_UNLOCK(sc); |
| 469 | |
| 470 | DEBUG_printf(sc->dev, "close (flags=%b thread=%p)\n", |
| 471 | ap->a_fflag, MODEBITS, curthread); |
| 472 | return 0; |
| 473 | } |
| 474 | |
| 475 | /* |
| 476 | * Read from the character device. |
| 477 | * Returns zero if successful, ENXIO if dying, EINVAL if an attempt |
| 478 | * was made to read less than CMX_MIN_RDLEN bytes or less than the |
| 479 | * device has available, or any of the errors that cmx_sync_write_SCR |
| 480 | * can return. Partial reads are not supported. |
| 481 | */ |
| 482 | static int |
| 483 | cmx_read(struct dev_read_args *ap) |
| 484 | { |
| 485 | cdev_t dev = ap->a_head.a_dev; |
| 486 | struct cmx_softc *sc; |
| 487 | struct uio *uio = ap->a_uio; |
| 488 | unsigned long bytes_left; |
| 489 | uint8_t uc; |
| 490 | int rv, amnt, offset; |
| 491 | |
| 492 | sc = devclass_get_softc(cmx_devclass, minor(dev)); |
| 493 | if (sc == NULL || sc->dying) |
| 494 | return ENXIO; |
| 495 | |
| 496 | DEBUG_printf(sc->dev, "called (len=%d flag=%b)\n", |
| 497 | uio->uio_resid, ap->a_ioflag, MODEBITS); |
| 498 | |
| 499 | CMX_LOCK(sc); |
| 500 | if (sc->polling) { |
| 501 | DEBUG_printf(sc->dev, "disabling polling\n"); |
| 502 | callout_stop(&sc->ch); |
| 503 | sc->polling = 0; |
| 504 | CMX_UNLOCK(sc); |
| 505 | KNOTE(&sc->kq.ki_note, 0); |
| 506 | } else { |
| 507 | CMX_UNLOCK(sc); |
| 508 | } |
| 509 | |
| 510 | if (uio->uio_resid == 0) { |
| 511 | return 0; |
| 512 | } |
| 513 | |
| 514 | if (uio->uio_resid < CMX_MIN_RDLEN) { |
| 515 | return EINVAL; |
| 516 | } |
| 517 | |
| 518 | if (ap->a_ioflag & O_NONBLOCK) { |
| 519 | if (cmx_test_BSR(sc, BSR_BULK_IN_FULL, 0)) { |
| 520 | return EAGAIN; |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | for (int i = 0; i < 5; i++) { |
| 525 | if ((rv = cmx_wait_BSR(sc, BSR_BULK_IN_FULL, 1)) != 0) { |
| 526 | return rv; |
| 527 | } |
| 528 | sc->buf[i] = cmx_read_DTR(sc); |
| 529 | DEBUG_printf(sc->dev, "buf[%02x]=%02x\n", i, sc->buf[i]); |
| 530 | } |
| 531 | |
| 532 | bytes_left = CMX_MIN_RDLEN + |
| 533 | (0x000000FF&((char)sc->buf[1])) + |
| 534 | (0x0000FF00&((char)sc->buf[2] << 8)) + |
| 535 | (0x00FF0000&((char)sc->buf[3] << 16)) + |
| 536 | (0xFF000000&((char)sc->buf[4] << 24)); |
| 537 | DEBUG_printf(sc->dev, "msgsz=%lu\n", bytes_left); |
| 538 | |
| 539 | if (uio->uio_resid < bytes_left) { |
| 540 | return EINVAL; |
| 541 | } |
| 542 | |
| 543 | offset = 5; /* prefetched header */ |
| 544 | while (bytes_left > 0) { |
| 545 | amnt = MIN(bytes_left, sizeof(sc->buf)); |
| 546 | |
| 547 | for (int i = offset; i < amnt; i++) { |
| 548 | if ((rv = cmx_wait_BSR(sc, BSR_BULK_IN_FULL, 1))!=0) { |
| 549 | return rv; |
| 550 | } |
| 551 | sc->buf[i] = cmx_read_DTR(sc); |
| 552 | DEBUG_printf(sc->dev, "buf[%02x]=%02x\n", |
| 553 | i, sc->buf[i]); |
| 554 | } |
| 555 | |
| 556 | if ((rv = uiomove(sc->buf, amnt, uio)) != 0) { |
| 557 | DEBUG_printf(sc->dev, "uiomove failed (%d)\n", rv); |
| 558 | return rv; |
| 559 | } |
| 560 | |
| 561 | if (offset) |
| 562 | offset = 0; |
| 563 | bytes_left -= amnt; |
| 564 | } |
| 565 | |
| 566 | if ((rv = cmx_wait_BSR(sc, BSR_BULK_IN_FULL, 1)) != 0) { |
| 567 | return rv; |
| 568 | } |
| 569 | |
| 570 | if ((rv = cmx_sync_write_SCR(sc, SCR_READER_TO_HOST_DONE)) != 0) { |
| 571 | return rv; |
| 572 | } |
| 573 | |
| 574 | uc = cmx_read_DTR(sc); |
| 575 | DEBUG_printf(sc->dev, "success (DTR=%02x)\n", uc); |
| 576 | return 0; |
| 577 | } |
| 578 | |
| 579 | /* |
| 580 | * Write to the character device. |
| 581 | * Returns zero if successful, NXIO if dying, EINVAL if less data |
| 582 | * written than CMX_MIN_WRLEN, or any of the errors that cmx_sync_SCR |
| 583 | * can return. |
| 584 | */ |
| 585 | static int |
| 586 | cmx_write(struct dev_write_args *ap) |
| 587 | { |
| 588 | cdev_t dev = ap->a_head.a_dev; |
| 589 | struct cmx_softc *sc; |
| 590 | struct uio *uio = ap->a_uio; |
| 591 | int rv, amnt; |
| 592 | |
| 593 | sc = devclass_get_softc(cmx_devclass, minor(dev)); |
| 594 | if (sc == NULL || sc->dying) |
| 595 | return ENXIO; |
| 596 | |
| 597 | DEBUG_printf(sc->dev, "called (len=%d flag=%b)\n", |
| 598 | uio->uio_resid, ap->a_ioflag, MODEBITS); |
| 599 | |
| 600 | if (uio->uio_resid == 0) { |
| 601 | return 0; |
| 602 | } |
| 603 | |
| 604 | if (uio->uio_resid < CMX_MIN_WRLEN) { |
| 605 | return EINVAL; |
| 606 | } |
| 607 | |
| 608 | if ((rv = cmx_sync_write_SCR(sc, SCR_HOST_TO_READER_START)) != 0) { |
| 609 | return rv; |
| 610 | } |
| 611 | |
| 612 | sc->timeout = 0; |
| 613 | while (uio->uio_resid > 0) { |
| 614 | amnt = MIN(uio->uio_resid, sizeof(sc->buf)); |
| 615 | |
| 616 | if ((rv = uiomove(sc->buf, amnt, uio)) != 0) { |
| 617 | DEBUG_printf(sc->dev, "uiomove failed (%d)\n", rv); |
| 618 | /* wildly guessed attempt to notify device */ |
| 619 | sc->timeout = CCID_DRIVER_MINIMUM_TIMEOUT; |
| 620 | cmx_sync_write_SCR(sc, SCR_HOST_TO_READER_DONE); |
| 621 | return rv; |
| 622 | } |
| 623 | |
| 624 | if (sc->timeout == 0) { |
| 625 | sc->timeout = cmx_timeout_by_cmd(sc->buf[0]); |
| 626 | DEBUG_printf(sc->dev, "cmd=%02x timeout=%lu\n", |
| 627 | sc->buf[0], sc->timeout); |
| 628 | } |
| 629 | |
| 630 | for (int i = 0; i < amnt; i++) { |
| 631 | if ((rv = cmx_wait_BSR(sc, BSR_BULK_OUT_FULL, 0))!=0) { |
| 632 | return rv; |
| 633 | } |
| 634 | cmx_write_DTR(sc, sc->buf[i]); |
| 635 | DEBUG_printf(sc->dev, "buf[%02x]=%02x\n", |
| 636 | i, sc->buf[i]); |
| 637 | } |
| 638 | } |
| 639 | |
| 640 | if ((rv = cmx_sync_write_SCR(sc, SCR_HOST_TO_READER_DONE)) != 0) { |
| 641 | return rv; |
| 642 | } |
| 643 | |
| 644 | DEBUG_printf(sc->dev, "success\n"); |
| 645 | return 0; |
| 646 | } |
| 647 | |
| 648 | static struct filterops cmx_read_filterops = |
| 649 | { 1, NULL, cmx_filter_detach, cmx_filter_read }; |
| 650 | static struct filterops cmx_write_filterops = |
| 651 | { 1, NULL, cmx_filter_detach, cmx_filter_write }; |
| 652 | |
| 653 | /* |
| 654 | * Kevent handler. Writing is always possible, reading is only possible |
| 655 | * if BSR_BULK_IN_FULL is set. Will start the cmx_tick callout and |
| 656 | * set sc->polling. |
| 657 | */ |
| 658 | static int |
| 659 | cmx_kqfilter(struct dev_kqfilter_args *ap) |
| 660 | { |
| 661 | cdev_t dev = ap->a_head.a_dev; |
| 662 | struct knote *kn = ap->a_kn; |
| 663 | struct cmx_softc *sc; |
| 664 | struct klist *klist; |
| 665 | |
| 666 | ap->a_result = 0; |
| 667 | |
| 668 | sc = devclass_get_softc(cmx_devclass, minor(dev)); |
| 669 | |
| 670 | switch (kn->kn_filter) { |
| 671 | case EVFILT_READ: |
| 672 | kn->kn_fop = &cmx_read_filterops; |
| 673 | kn->kn_hook = (caddr_t)sc; |
| 674 | break; |
| 675 | case EVFILT_WRITE: |
| 676 | kn->kn_fop = &cmx_write_filterops; |
| 677 | kn->kn_hook = (caddr_t)sc; |
| 678 | break; |
| 679 | default: |
| 680 | ap->a_result = EOPNOTSUPP; |
| 681 | return (0); |
| 682 | } |
| 683 | |
| 684 | klist = &sc->kq.ki_note; |
| 685 | knote_insert(klist, kn); |
| 686 | |
| 687 | return (0); |
| 688 | } |
| 689 | |
| 690 | static void |
| 691 | cmx_filter_detach(struct knote *kn) |
| 692 | { |
| 693 | struct cmx_softc *sc = (struct cmx_softc *)kn->kn_hook; |
| 694 | struct klist *klist = &sc->kq.ki_note; |
| 695 | |
| 696 | knote_remove(klist, kn); |
| 697 | } |
| 698 | |
| 699 | static int |
| 700 | cmx_filter_read(struct knote *kn, long hint) |
| 701 | { |
| 702 | struct cmx_softc *sc = (struct cmx_softc *)kn->kn_hook; |
| 703 | int ready = 0; |
| 704 | uint8_t bsr = 0; |
| 705 | |
| 706 | if (sc == NULL || sc->dying) { |
| 707 | kn->kn_flags |= EV_EOF; |
| 708 | return (1); |
| 709 | } |
| 710 | |
| 711 | bsr = cmx_read_BSR(sc); |
| 712 | if (cmx_test(bsr, BSR_BULK_IN_FULL, 1)) { |
| 713 | ready = 1; |
| 714 | } else { |
| 715 | CMX_LOCK(sc); |
| 716 | if (!sc->polling) { |
| 717 | sc->polling = 1; |
| 718 | callout_reset(&sc->ch, POLL_TICKS, |
| 719 | cmx_tick, sc); |
| 720 | } |
| 721 | CMX_UNLOCK(sc); |
| 722 | } |
| 723 | |
| 724 | return (ready); |
| 725 | } |
| 726 | |
| 727 | static int |
| 728 | cmx_filter_write(struct knote *kn, long hint) |
| 729 | { |
| 730 | return (1); |
| 731 | } |
| 732 | |
| 733 | #ifdef CMX_INTR |
| 734 | /* |
| 735 | * Interrupt handler. Currently has no function except to |
| 736 | * print register status (if debugging is also enabled). |
| 737 | */ |
| 738 | static void |
| 739 | cmx_intr(void *arg) |
| 740 | { |
| 741 | struct cmx_softc *sc = (struct cmx_softc *)arg; |
| 742 | |
| 743 | if (sc == NULL || sc->dying) |
| 744 | return; |
| 745 | |
| 746 | DEBUG_printf(sc->dev, "received interrupt (SCR=%b BSR=%b)\n", |
| 747 | cmx_read_SCR(sc), SCRBITS, |
| 748 | cmx_read_BSR(sc), BSRBITS); |
| 749 | |
| 750 | return; |
| 751 | } |
| 752 | #endif |
| 753 | |