Added
Link Here
|
1 |
/*- |
2 |
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD |
3 |
* |
4 |
* Copyright (c) 2020, 2022 Vladimir Kondratyev <wulf@FreeBSD.org> |
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, this list of conditions and the following disclaimer. |
11 |
* 2. Redistributions in binary form must reproduce the above copyright |
12 |
* notice, this list of conditions and the following disclaimer in the |
13 |
* documentation and/or other materials provided with the distribution. |
14 |
* |
15 |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
16 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
19 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
20 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
21 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
22 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
23 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
24 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
25 |
* SUCH DAMAGE. |
26 |
*/ |
27 |
|
28 |
#include "opt_hid.h" |
29 |
|
30 |
#include <sys/param.h> |
31 |
#ifdef COMPAT_FREEBSD32 |
32 |
#include <sys/abi_compat.h> |
33 |
#endif |
34 |
#include <sys/bus.h> |
35 |
#include <sys/conf.h> |
36 |
#include <sys/fcntl.h> |
37 |
#include <sys/filio.h> |
38 |
#include <sys/ioccom.h> |
39 |
#include <sys/kernel.h> |
40 |
#include <sys/lock.h> |
41 |
#include <sys/malloc.h> |
42 |
#include <sys/module.h> |
43 |
#include <sys/mutex.h> |
44 |
#include <sys/poll.h> |
45 |
#include <sys/priv.h> |
46 |
#include <sys/proc.h> |
47 |
#include <sys/selinfo.h> |
48 |
#include <sys/sysctl.h> |
49 |
#include <sys/systm.h> |
50 |
#include <sys/uio.h> |
51 |
|
52 |
#include <dev/evdev/input.h> |
53 |
|
54 |
#define HID_DEBUG_VAR fido_debug |
55 |
#include <dev/hid/hid.h> |
56 |
#include <dev/hid/hidbus.h> |
57 |
#include <dev/hid/hidquirk.h> |
58 |
|
59 |
#include <dev/usb/usb_ioctl.h> |
60 |
|
61 |
#ifdef HID_DEBUG |
62 |
static int fido_debug = 0; |
63 |
static SYSCTL_NODE(_hw_hid, OID_AUTO, fido, CTLFLAG_RW, 0, "Yubico FIDO key"); |
64 |
SYSCTL_INT(_hw_hid_fido, OID_AUTO, debug, CTLFLAG_RWTUN, |
65 |
&fido_debug, 0, "Debug level"); |
66 |
#endif |
67 |
|
68 |
#define FIDO_MAKE_UHID_ALIAS |
69 |
#define GID_U2F 116 |
70 |
#define FIDO_REPORT_SIZE 64 |
71 |
#define FIDO_REPORT_NUM 2 |
72 |
|
73 |
/* A match on these entries will load hms */ |
74 |
static const struct hid_device_id fido_devs[] = { |
75 |
{ HID_BUS(BUS_USB), HID_TLC(HUP_FIDO, HUF_U2FHID) }, |
76 |
}; |
77 |
|
78 |
struct fido_softc { |
79 |
device_t sc_dev; /* base device */ |
80 |
struct cdev *dev; |
81 |
|
82 |
struct mtx sc_mtx; /* hidbus private mutex */ |
83 |
struct hid_rdesc_info *sc_rdesc; |
84 |
struct selinfo sc_rsel; |
85 |
struct { /* driver state */ |
86 |
bool open:1; /* device is open */ |
87 |
bool aslp:1; /* waiting for device data in read() */ |
88 |
bool sel:1; /* waiting for device data in poll() */ |
89 |
int reserved:29; |
90 |
} sc_state; |
91 |
int sc_fflags; /* access mode for open lifetime */ |
92 |
|
93 |
int sc_head; |
94 |
int sc_tail; |
95 |
uint8_t sc_q[FIDO_REPORT_SIZE * FIDO_REPORT_NUM]; |
96 |
}; |
97 |
|
98 |
static d_open_t fido_open; |
99 |
static d_read_t fido_read; |
100 |
static d_write_t fido_write; |
101 |
static d_ioctl_t fido_ioctl; |
102 |
static d_poll_t fido_poll; |
103 |
static d_kqfilter_t fido_kqfilter; |
104 |
|
105 |
static d_priv_dtor_t fido_dtor; |
106 |
|
107 |
static struct cdevsw fido_cdevsw = { |
108 |
.d_version = D_VERSION, |
109 |
.d_open = fido_open, |
110 |
.d_read = fido_read, |
111 |
.d_write = fido_write, |
112 |
.d_ioctl = fido_ioctl, |
113 |
.d_poll = fido_poll, |
114 |
.d_kqfilter = fido_kqfilter, |
115 |
.d_name = "fido", |
116 |
}; |
117 |
|
118 |
static hid_intr_t fido_intr; |
119 |
|
120 |
static device_probe_t fido_probe; |
121 |
static device_attach_t fido_attach; |
122 |
static device_detach_t fido_detach; |
123 |
|
124 |
static int fido_kqread(struct knote *, long); |
125 |
static void fido_kqdetach(struct knote *); |
126 |
static void fido_notify(struct fido_softc *); |
127 |
|
128 |
static struct filterops fido_filterops_read = { |
129 |
.f_isfd = 1, |
130 |
.f_detach = fido_kqdetach, |
131 |
.f_event = fido_kqread, |
132 |
}; |
133 |
|
134 |
static int |
135 |
fido_probe(device_t self) |
136 |
{ |
137 |
int error; |
138 |
|
139 |
error = HIDBUS_LOOKUP_DRIVER_INFO(self, fido_devs); |
140 |
if (error != 0) |
141 |
return (error); |
142 |
|
143 |
hidbus_set_desc(self, NULL); |
144 |
|
145 |
return (BUS_PROBE_GENERIC); |
146 |
} |
147 |
|
148 |
static int |
149 |
fido_attach(device_t self) |
150 |
{ |
151 |
struct fido_softc *sc = device_get_softc(self); |
152 |
struct hid_device_info *hw = __DECONST(struct hid_device_info *, |
153 |
hid_get_device_info(self)); |
154 |
struct make_dev_args mda; |
155 |
int error; |
156 |
|
157 |
sc->sc_dev = self; |
158 |
sc->sc_rdesc = hidbus_get_rdesc_info(self); |
159 |
|
160 |
if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) |
161 |
return (ENXIO); |
162 |
|
163 |
mtx_init(&sc->sc_mtx, "fido lock", NULL, MTX_DEF); |
164 |
knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx); |
165 |
|
166 |
make_dev_args_init(&mda); |
167 |
mda.mda_flags = MAKEDEV_WAITOK; |
168 |
mda.mda_devsw = &fido_cdevsw; |
169 |
mda.mda_uid = UID_ROOT; |
170 |
mda.mda_gid = GID_U2F; |
171 |
mda.mda_mode = 0660; |
172 |
mda.mda_si_drv1 = sc; |
173 |
|
174 |
error = make_dev_s(&mda, &sc->dev, "fido%d", device_get_unit(self)); |
175 |
if (error) { |
176 |
device_printf(self, "Can not create character device\n"); |
177 |
fido_detach(self); |
178 |
return (error); |
179 |
} |
180 |
#ifdef FIDO_MAKE_UHID_ALIAS |
181 |
(void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(self)); |
182 |
#endif |
183 |
|
184 |
hid_add_dynamic_quirk(hw, HQ_NO_READAHEAD); |
185 |
|
186 |
hidbus_set_lock(self, &sc->sc_mtx); |
187 |
hidbus_set_intr(self, fido_intr, sc); |
188 |
|
189 |
return (0); |
190 |
} |
191 |
|
192 |
static int |
193 |
fido_detach(device_t self) |
194 |
{ |
195 |
struct fido_softc *sc = device_get_softc(self); |
196 |
|
197 |
DPRINTF("sc=%p\n", sc); |
198 |
|
199 |
if (sc->dev != NULL) { |
200 |
mtx_lock(&sc->sc_mtx); |
201 |
sc->dev->si_drv1 = NULL; |
202 |
/* Wake everyone */ |
203 |
fido_notify(sc); |
204 |
mtx_unlock(&sc->sc_mtx); |
205 |
destroy_dev(sc->dev); |
206 |
} |
207 |
|
208 |
knlist_clear(&sc->sc_rsel.si_note, 0); |
209 |
knlist_destroy(&sc->sc_rsel.si_note); |
210 |
seldrain(&sc->sc_rsel); |
211 |
mtx_destroy(&sc->sc_mtx); |
212 |
|
213 |
return (0); |
214 |
} |
215 |
|
216 |
void |
217 |
fido_intr(void *context, void *buf, hid_size_t len) |
218 |
{ |
219 |
struct fido_softc *sc = context; |
220 |
int next; |
221 |
|
222 |
mtx_assert(&sc->sc_mtx, MA_OWNED); |
223 |
|
224 |
DPRINTFN(5, "len=%d\n", len); |
225 |
DPRINTFN(5, "data = %*D\n", len, buf, " "); |
226 |
|
227 |
next = (sc->sc_tail + 1) % FIDO_REPORT_NUM; |
228 |
if (next == sc->sc_head) |
229 |
return; |
230 |
|
231 |
if (len > FIDO_REPORT_SIZE) |
232 |
len = FIDO_REPORT_SIZE; |
233 |
|
234 |
bcopy(buf, sc->sc_q + sc->sc_tail * FIDO_REPORT_SIZE, len); |
235 |
|
236 |
/* Make sure we don't process old data */ |
237 |
if (len < FIDO_REPORT_SIZE) |
238 |
bzero(sc->sc_q + sc->sc_tail * FIDO_REPORT_SIZE + len, |
239 |
FIDO_REPORT_SIZE - len); |
240 |
|
241 |
sc->sc_tail = next; |
242 |
|
243 |
fido_notify(sc); |
244 |
} |
245 |
|
246 |
static int |
247 |
fido_open(struct cdev *dev, int flag, int mode, struct thread *td) |
248 |
{ |
249 |
struct fido_softc *sc = dev->si_drv1; |
250 |
int error; |
251 |
|
252 |
if (sc == NULL) |
253 |
return (ENXIO); |
254 |
|
255 |
DPRINTF("sc=%p\n", sc); |
256 |
|
257 |
mtx_lock(&sc->sc_mtx); |
258 |
if (sc->sc_state.open) { |
259 |
mtx_unlock(&sc->sc_mtx); |
260 |
return (EBUSY); |
261 |
} |
262 |
sc->sc_state.open = true; |
263 |
mtx_unlock(&sc->sc_mtx); |
264 |
|
265 |
error = devfs_set_cdevpriv(sc, fido_dtor); |
266 |
if (error != 0) { |
267 |
mtx_lock(&sc->sc_mtx); |
268 |
sc->sc_state.open = false; |
269 |
mtx_unlock(&sc->sc_mtx); |
270 |
return (error); |
271 |
} |
272 |
|
273 |
/* Set up interrupt pipe. */ |
274 |
sc->sc_head = sc->sc_tail = 0; |
275 |
sc->sc_fflags = flag; |
276 |
|
277 |
return (0); |
278 |
} |
279 |
|
280 |
|
281 |
static void |
282 |
fido_dtor(void *data) |
283 |
{ |
284 |
struct fido_softc *sc = data; |
285 |
|
286 |
/* Disable interrupts. */ |
287 |
hidbus_intr_stop(sc->sc_dev); |
288 |
|
289 |
mtx_lock(&sc->sc_mtx); |
290 |
sc->sc_state.open = false; |
291 |
mtx_unlock(&sc->sc_mtx); |
292 |
} |
293 |
|
294 |
static int |
295 |
fido_read(struct cdev *dev, struct uio *uio, int flag) |
296 |
{ |
297 |
struct fido_softc *sc = dev->si_drv1; |
298 |
size_t length; |
299 |
int error; |
300 |
|
301 |
DPRINTFN(1, "\n"); |
302 |
|
303 |
if (sc == NULL) |
304 |
return (EIO); |
305 |
|
306 |
hidbus_intr_start(sc->sc_dev); |
307 |
|
308 |
mtx_lock(&sc->sc_mtx); |
309 |
if (dev->si_drv1 == NULL) { |
310 |
error = EIO; |
311 |
goto exit; |
312 |
} |
313 |
|
314 |
while (sc->sc_tail == sc->sc_head) { |
315 |
if (flag & O_NONBLOCK) { |
316 |
error = EWOULDBLOCK; |
317 |
goto exit; |
318 |
} |
319 |
sc->sc_state.aslp = true; |
320 |
DPRINTFN(5, "sleep on %p\n", &sc->sc_q); |
321 |
error = mtx_sleep(&sc->sc_q, &sc->sc_mtx, PZERO | PCATCH, |
322 |
"fidord", 0); |
323 |
DPRINTFN(5, "woke, error=%d\n", error); |
324 |
if (dev->si_drv1 == NULL) |
325 |
error = EIO; |
326 |
if (error) { |
327 |
sc->sc_state.aslp = false; |
328 |
goto exit; |
329 |
} |
330 |
} |
331 |
|
332 |
while (sc->sc_tail != sc->sc_head && uio->uio_resid > 0) { |
333 |
length = min(uio->uio_resid, FIDO_REPORT_SIZE); |
334 |
mtx_unlock(&sc->sc_mtx); |
335 |
|
336 |
/* Copy the data to the user process. */ |
337 |
DPRINTFN(5, "got %lu chars\n", (u_long)length); |
338 |
error = uiomove(sc->sc_q + sc->sc_head * FIDO_REPORT_SIZE, |
339 |
length, uio); |
340 |
|
341 |
mtx_lock(&sc->sc_mtx); |
342 |
if (error != 0) |
343 |
goto exit; |
344 |
/* Remove a small chunk from the input queue. */ |
345 |
sc->sc_head = (sc->sc_head + 1) % FIDO_REPORT_NUM; |
346 |
} |
347 |
exit: |
348 |
mtx_unlock(&sc->sc_mtx); |
349 |
|
350 |
return (error); |
351 |
} |
352 |
|
353 |
static int |
354 |
fido_write(struct cdev *dev, struct uio *uio, int flag) |
355 |
{ |
356 |
uint8_t buf[FIDO_REPORT_SIZE]; |
357 |
struct fido_softc *sc = dev->si_drv1; |
358 |
int error; |
359 |
|
360 |
DPRINTFN(1, "\n"); |
361 |
|
362 |
if (sc == NULL) |
363 |
return (EIO); |
364 |
|
365 |
if (uio->uio_resid != FIDO_REPORT_SIZE) |
366 |
return (EINVAL); |
367 |
error = uiomove(buf, uio->uio_resid, uio); |
368 |
if (error == 0) |
369 |
error = hid_write(sc->sc_dev, buf, FIDO_REPORT_SIZE); |
370 |
|
371 |
return (error); |
372 |
} |
373 |
|
374 |
#ifdef COMPAT_FREEBSD32 |
375 |
static void |
376 |
update_ugd32(const struct usb_gen_descriptor *ugd, |
377 |
struct usb_gen_descriptor32 *ugd32) |
378 |
{ |
379 |
/* Don't update hgd_data pointer */ |
380 |
CP(*ugd, *ugd32, ugd_lang_id); |
381 |
CP(*ugd, *ugd32, ugd_maxlen); |
382 |
CP(*ugd, *ugd32, ugd_actlen); |
383 |
CP(*ugd, *ugd32, ugd_offset); |
384 |
CP(*ugd, *ugd32, ugd_config_index); |
385 |
CP(*ugd, *ugd32, ugd_string_index); |
386 |
CP(*ugd, *ugd32, ugd_iface_index); |
387 |
CP(*ugd, *ugd32, ugd_altif_index); |
388 |
CP(*ugd, *ugd32, ugd_endpt_index); |
389 |
CP(*ugd, *ugd32, ugd_report_type); |
390 |
/* Don't update reserved */ |
391 |
} |
392 |
#endif |
393 |
|
394 |
static int |
395 |
fido_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, |
396 |
struct thread *td) |
397 |
{ |
398 |
#ifdef COMPAT_FREEBSD32 |
399 |
struct usb_gen_descriptor local_ugd; |
400 |
struct usb_gen_descriptor32 *ugd32 = NULL; |
401 |
#endif |
402 |
struct fido_softc *sc = dev->si_drv1; |
403 |
struct usb_gen_descriptor *ugd = (struct usb_gen_descriptor *)addr; |
404 |
uint32_t size; |
405 |
|
406 |
DPRINTFN(2, "cmd=%lx\n", cmd); |
407 |
|
408 |
if (sc == NULL) |
409 |
return (EIO); |
410 |
|
411 |
#ifdef COMPAT_FREEBSD32 |
412 |
switch (cmd) { |
413 |
case USB_GET_REPORT_DESC32: |
414 |
cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor); |
415 |
ugd32 = (struct usb_gen_descriptor32 *)addr; |
416 |
ugd = &local_ugd; |
417 |
PTRIN_CP(*ugd32, *ugd, ugd_data); |
418 |
CP(*ugd32, *ugd, ugd_lang_id); |
419 |
CP(*ugd32, *ugd, ugd_maxlen); |
420 |
CP(*ugd32, *ugd, ugd_actlen); |
421 |
CP(*ugd32, *ugd, ugd_offset); |
422 |
CP(*ugd32, *ugd, ugd_config_index); |
423 |
CP(*ugd32, *ugd, ugd_string_index); |
424 |
CP(*ugd32, *ugd, ugd_iface_index); |
425 |
CP(*ugd32, *ugd, ugd_altif_index); |
426 |
CP(*ugd32, *ugd, ugd_endpt_index); |
427 |
CP(*ugd32, *ugd, ugd_report_type); |
428 |
/* Don't copy reserved */ |
429 |
break; |
430 |
} |
431 |
#endif |
432 |
|
433 |
/* fixed-length ioctls handling */ |
434 |
switch (cmd) { |
435 |
case FIONBIO: |
436 |
/* All handled in the upper FS layer. */ |
437 |
return (0); |
438 |
|
439 |
case USB_GET_REPORT_DESC: |
440 |
if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) |
441 |
return (EOPNOTSUPP); |
442 |
if (sc->sc_rdesc->len > ugd->ugd_maxlen) { |
443 |
size = ugd->ugd_maxlen; |
444 |
} else { |
445 |
size = sc->sc_rdesc->len; |
446 |
} |
447 |
ugd->ugd_actlen = size; |
448 |
#ifdef COMPAT_FREEBSD32 |
449 |
if (ugd32 != NULL) |
450 |
update_ugd32(ugd, ugd32); |
451 |
#endif |
452 |
if (ugd->ugd_data == NULL) |
453 |
return (0); /* descriptor length only */ |
454 |
|
455 |
return (copyout(sc->sc_rdesc->data, ugd->ugd_data, size)); |
456 |
|
457 |
case USB_GET_DEVICEINFO: |
458 |
return(hid_ioctl( |
459 |
sc->sc_dev, USB_GET_DEVICEINFO, (uintptr_t)addr)); |
460 |
} |
461 |
|
462 |
return (ENOTTY); |
463 |
} |
464 |
|
465 |
static int |
466 |
fido_poll(struct cdev *dev, int events, struct thread *td) |
467 |
{ |
468 |
struct fido_softc *sc = dev->si_drv1; |
469 |
int revents = 0; |
470 |
|
471 |
if (sc == NULL) |
472 |
return (POLLHUP); |
473 |
|
474 |
if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE)) |
475 |
revents |= events & (POLLOUT | POLLWRNORM); |
476 |
if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) { |
477 |
hidbus_intr_start(sc->sc_dev); |
478 |
mtx_lock(&sc->sc_mtx); |
479 |
if (sc->sc_head != sc->sc_tail) |
480 |
revents |= events & (POLLIN | POLLRDNORM); |
481 |
else { |
482 |
sc->sc_state.sel = true; |
483 |
selrecord(td, &sc->sc_rsel); |
484 |
} |
485 |
mtx_unlock(&sc->sc_mtx); |
486 |
} |
487 |
|
488 |
return (revents); |
489 |
} |
490 |
|
491 |
static int |
492 |
fido_kqfilter(struct cdev *dev, struct knote *kn) |
493 |
{ |
494 |
struct fido_softc *sc = dev->si_drv1; |
495 |
|
496 |
if (sc == NULL) |
497 |
return (ENXIO); |
498 |
|
499 |
switch(kn->kn_filter) { |
500 |
case EVFILT_READ: |
501 |
if (sc->sc_fflags & FREAD) { |
502 |
hidbus_intr_start(sc->sc_dev); |
503 |
kn->kn_fop = &fido_filterops_read; |
504 |
break; |
505 |
} |
506 |
/* FALLTHROUGH */ |
507 |
default: |
508 |
return(EINVAL); |
509 |
} |
510 |
kn->kn_hook = sc; |
511 |
|
512 |
knlist_add(&sc->sc_rsel.si_note, kn, 0); |
513 |
return (0); |
514 |
} |
515 |
|
516 |
static int |
517 |
fido_kqread(struct knote *kn, long hint) |
518 |
{ |
519 |
struct fido_softc *sc = kn->kn_hook; |
520 |
int ret; |
521 |
|
522 |
mtx_assert(&sc->sc_mtx, MA_OWNED); |
523 |
|
524 |
if (sc->dev->si_drv1 == NULL) { |
525 |
kn->kn_flags |= EV_EOF; |
526 |
ret = 1; |
527 |
} else |
528 |
ret = (sc->sc_head != sc->sc_tail) ? 1 : 0; |
529 |
|
530 |
return (ret); |
531 |
} |
532 |
|
533 |
static void |
534 |
fido_kqdetach(struct knote *kn) |
535 |
{ |
536 |
struct fido_softc *sc = kn->kn_hook; |
537 |
|
538 |
knlist_remove(&sc->sc_rsel.si_note, kn, 0); |
539 |
} |
540 |
|
541 |
static void |
542 |
fido_notify(struct fido_softc *sc) |
543 |
{ |
544 |
mtx_assert(&sc->sc_mtx, MA_OWNED); |
545 |
|
546 |
if (sc->sc_state.aslp) { |
547 |
sc->sc_state.aslp = false; |
548 |
DPRINTFN(5, "waking %p\n", &sc->sc_q); |
549 |
wakeup(&sc->sc_q); |
550 |
} |
551 |
if (sc->sc_state.sel) { |
552 |
sc->sc_state.sel = false; |
553 |
selwakeuppri(&sc->sc_rsel, PZERO); |
554 |
} |
555 |
KNOTE_LOCKED(&sc->sc_rsel.si_note, 0); |
556 |
} |
557 |
|
558 |
static device_method_t fido_methods[] = { |
559 |
/* Device interface */ |
560 |
DEVMETHOD(device_probe, fido_probe), |
561 |
DEVMETHOD(device_attach, fido_attach), |
562 |
DEVMETHOD(device_detach, fido_detach), |
563 |
|
564 |
DEVMETHOD_END |
565 |
}; |
566 |
|
567 |
static driver_t fido_driver = { |
568 |
#ifdef FIDO_MAKE_UHID_ALIAS |
569 |
"uhid", |
570 |
#else |
571 |
"fido", |
572 |
#endif |
573 |
fido_methods, |
574 |
sizeof(struct fido_softc) |
575 |
}; |
576 |
|
577 |
DRIVER_MODULE(fido, hidbus, fido_driver, NULL, NULL); |
578 |
MODULE_DEPEND(fido, hidbus, 1, 1, 1); |
579 |
MODULE_DEPEND(fido, hid, 1, 1, 1); |
580 |
MODULE_DEPEND(fido, usb, 1, 1, 1); |
581 |
MODULE_VERSION(fido, 1); |
582 |
HID_PNP_INFO(fido_devs); |