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