Added
Link Here
|
1 |
/*- |
2 |
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD |
3 |
* |
4 |
* Copyright (c) 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_MAX_REPORT_SIZE 64 |
71 |
|
72 |
/* A match on these entries will load hms */ |
73 |
static const struct hid_device_id fido_devs[] = { |
74 |
{ HID_BUS(BUS_USB), HID_TLC(HUP_FIDO, HUF_U2FHID) }, |
75 |
}; |
76 |
|
77 |
struct fido_softc { |
78 |
device_t sc_dev; /* base device */ |
79 |
struct cdev *dev; |
80 |
|
81 |
struct mtx sc_mtx; /* hidbus private mutex */ |
82 |
void *sc_rdesc; |
83 |
hid_size_t sc_rdesc_size; |
84 |
hid_size_t sc_isize; |
85 |
hid_size_t sc_osize; |
86 |
struct selinfo sc_rsel; |
87 |
struct { /* driver state */ |
88 |
bool open:1; /* device is open */ |
89 |
bool aslp:1; /* waiting for device data in read() */ |
90 |
bool sel:1; /* waiting for device data in poll() */ |
91 |
bool data:1; /* input report is stored in sc_buf */ |
92 |
int reserved:28; |
93 |
} sc_state; |
94 |
int sc_fflags; /* access mode for open lifetime */ |
95 |
|
96 |
uint8_t sc_buf[FIDO_MAX_REPORT_SIZE]; |
97 |
}; |
98 |
|
99 |
static d_open_t fido_open; |
100 |
static d_read_t fido_read; |
101 |
static d_write_t fido_write; |
102 |
static d_ioctl_t fido_ioctl; |
103 |
static d_poll_t fido_poll; |
104 |
static d_kqfilter_t fido_kqfilter; |
105 |
|
106 |
static d_priv_dtor_t fido_dtor; |
107 |
|
108 |
static struct cdevsw fido_cdevsw = { |
109 |
.d_version = D_VERSION, |
110 |
.d_open = fido_open, |
111 |
.d_read = fido_read, |
112 |
.d_write = fido_write, |
113 |
.d_ioctl = fido_ioctl, |
114 |
.d_poll = fido_poll, |
115 |
.d_kqfilter = fido_kqfilter, |
116 |
.d_name = "fido", |
117 |
}; |
118 |
|
119 |
static hid_intr_t fido_intr; |
120 |
|
121 |
static device_probe_t fido_probe; |
122 |
static device_attach_t fido_attach; |
123 |
static device_detach_t fido_detach; |
124 |
|
125 |
static int fido_kqread(struct knote *, long); |
126 |
static void fido_kqdetach(struct knote *); |
127 |
static void fido_notify(struct fido_softc *); |
128 |
|
129 |
static struct filterops fido_filterops_read = { |
130 |
.f_isfd = 1, |
131 |
.f_detach = fido_kqdetach, |
132 |
.f_event = fido_kqread, |
133 |
}; |
134 |
|
135 |
static int |
136 |
fido_probe(device_t dev) |
137 |
{ |
138 |
int error; |
139 |
|
140 |
error = HIDBUS_LOOKUP_DRIVER_INFO(dev, fido_devs); |
141 |
if (error != 0) |
142 |
return (error); |
143 |
|
144 |
hidbus_set_desc(dev, NULL); |
145 |
|
146 |
return (BUS_PROBE_GENERIC); |
147 |
} |
148 |
|
149 |
static int |
150 |
fido_attach(device_t dev) |
151 |
{ |
152 |
struct fido_softc *sc = device_get_softc(dev); |
153 |
struct hid_device_info *hw = __DECONST(struct hid_device_info *, |
154 |
hid_get_device_info(dev)); |
155 |
struct make_dev_args mda; |
156 |
int error; |
157 |
|
158 |
sc->sc_dev = dev; |
159 |
|
160 |
error = hid_get_report_descr(dev, &sc->sc_rdesc, &sc->sc_rdesc_size); |
161 |
if (error != 0) |
162 |
return (ENXIO); |
163 |
sc->sc_isize = hid_report_size_max(sc->sc_rdesc, sc->sc_rdesc_size, |
164 |
hid_input, NULL); |
165 |
if (sc->sc_isize > FIDO_MAX_REPORT_SIZE) { |
166 |
device_printf(dev, "Input report size too large. Truncate.\n"); |
167 |
sc->sc_isize = FIDO_MAX_REPORT_SIZE; |
168 |
} |
169 |
sc->sc_osize = hid_report_size_max(sc->sc_rdesc, sc->sc_rdesc_size, |
170 |
hid_output, NULL); |
171 |
if (sc->sc_osize > FIDO_MAX_REPORT_SIZE) { |
172 |
device_printf(dev, "Output report size too large. Truncate.\n"); |
173 |
sc->sc_osize = FIDO_MAX_REPORT_SIZE; |
174 |
} |
175 |
|
176 |
mtx_init(&sc->sc_mtx, "fido lock", NULL, MTX_DEF); |
177 |
knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx); |
178 |
|
179 |
make_dev_args_init(&mda); |
180 |
mda.mda_flags = MAKEDEV_WAITOK; |
181 |
mda.mda_devsw = &fido_cdevsw; |
182 |
mda.mda_uid = UID_ROOT; |
183 |
mda.mda_gid = GID_U2F; |
184 |
mda.mda_mode = 0660; |
185 |
mda.mda_si_drv1 = sc; |
186 |
|
187 |
error = make_dev_s(&mda, &sc->dev, "fido%d", device_get_unit(dev)); |
188 |
if (error) { |
189 |
device_printf(dev, "Can not create character device\n"); |
190 |
fido_detach(dev); |
191 |
return (error); |
192 |
} |
193 |
#ifdef FIDO_MAKE_UHID_ALIAS |
194 |
(void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(dev)); |
195 |
#endif |
196 |
|
197 |
hid_add_dynamic_quirk(hw, HQ_NO_READAHEAD); |
198 |
|
199 |
hidbus_set_lock(dev, &sc->sc_mtx); |
200 |
hidbus_set_intr(dev, fido_intr, sc); |
201 |
|
202 |
return (0); |
203 |
} |
204 |
|
205 |
static int |
206 |
fido_detach(device_t dev) |
207 |
{ |
208 |
struct fido_softc *sc = device_get_softc(dev); |
209 |
|
210 |
DPRINTF("sc=%p\n", sc); |
211 |
|
212 |
if (sc->dev != NULL) { |
213 |
mtx_lock(&sc->sc_mtx); |
214 |
sc->dev->si_drv1 = NULL; |
215 |
/* Wake everyone */ |
216 |
fido_notify(sc); |
217 |
mtx_unlock(&sc->sc_mtx); |
218 |
destroy_dev(sc->dev); |
219 |
} |
220 |
|
221 |
knlist_clear(&sc->sc_rsel.si_note, 0); |
222 |
knlist_destroy(&sc->sc_rsel.si_note); |
223 |
seldrain(&sc->sc_rsel); |
224 |
mtx_destroy(&sc->sc_mtx); |
225 |
|
226 |
return (0); |
227 |
} |
228 |
|
229 |
void |
230 |
fido_intr(void *context, void *buf, hid_size_t len) |
231 |
{ |
232 |
struct fido_softc *sc = context; |
233 |
|
234 |
mtx_assert(&sc->sc_mtx, MA_OWNED); |
235 |
|
236 |
DPRINTFN(5, "len=%d\n", len); |
237 |
DPRINTFN(5, "data = %*D\n", len, buf, " "); |
238 |
|
239 |
if (sc->sc_state.data) |
240 |
return; |
241 |
|
242 |
if (len > sc->sc_isize) |
243 |
len = sc->sc_isize; |
244 |
|
245 |
bcopy(buf, sc->sc_buf, len); |
246 |
|
247 |
/* Make sure we don't process old data */ |
248 |
if (len < sc->sc_isize) |
249 |
bzero(sc->sc_buf + len, sc->sc_isize - len); |
250 |
|
251 |
sc->sc_state.data = true; |
252 |
|
253 |
fido_notify(sc); |
254 |
} |
255 |
|
256 |
static int |
257 |
fido_open(struct cdev *dev, int flag, int mode, struct thread *td) |
258 |
{ |
259 |
struct fido_softc *sc = dev->si_drv1; |
260 |
int error; |
261 |
|
262 |
if (sc == NULL) |
263 |
return (ENXIO); |
264 |
|
265 |
DPRINTF("sc=%p\n", sc); |
266 |
|
267 |
mtx_lock(&sc->sc_mtx); |
268 |
if (sc->sc_state.open) { |
269 |
mtx_unlock(&sc->sc_mtx); |
270 |
return (EBUSY); |
271 |
} |
272 |
sc->sc_state.open = true; |
273 |
mtx_unlock(&sc->sc_mtx); |
274 |
|
275 |
error = devfs_set_cdevpriv(sc, fido_dtor); |
276 |
if (error != 0) { |
277 |
mtx_lock(&sc->sc_mtx); |
278 |
sc->sc_state.open = false; |
279 |
mtx_unlock(&sc->sc_mtx); |
280 |
return (error); |
281 |
} |
282 |
|
283 |
/* Set up interrupt pipe. */ |
284 |
sc->sc_state.data = false; |
285 |
sc->sc_fflags = flag; |
286 |
|
287 |
return (0); |
288 |
} |
289 |
|
290 |
|
291 |
static void |
292 |
fido_dtor(void *data) |
293 |
{ |
294 |
struct fido_softc *sc = data; |
295 |
|
296 |
/* Disable interrupts. */ |
297 |
hidbus_intr_stop(sc->sc_dev); |
298 |
|
299 |
mtx_lock(&sc->sc_mtx); |
300 |
sc->sc_state.open = false; |
301 |
mtx_unlock(&sc->sc_mtx); |
302 |
} |
303 |
|
304 |
static int |
305 |
fido_read(struct cdev *dev, struct uio *uio, int flag) |
306 |
{ |
307 |
struct fido_softc *sc = dev->si_drv1; |
308 |
size_t length; |
309 |
int error; |
310 |
|
311 |
DPRINTFN(1, "\n"); |
312 |
|
313 |
if (sc == NULL) |
314 |
return (EIO); |
315 |
|
316 |
if (!sc->sc_state.data) |
317 |
hidbus_intr_start(sc->sc_dev); |
318 |
|
319 |
mtx_lock(&sc->sc_mtx); |
320 |
if (dev->si_drv1 == NULL) { |
321 |
error = EIO; |
322 |
goto exit; |
323 |
} |
324 |
|
325 |
while (!sc->sc_state.data) { |
326 |
if (flag & O_NONBLOCK) { |
327 |
error = EWOULDBLOCK; |
328 |
goto exit; |
329 |
} |
330 |
sc->sc_state.aslp = true; |
331 |
DPRINTFN(5, "sleep on %p\n", &sc->sc_buf); |
332 |
error = mtx_sleep(&sc->sc_buf, &sc->sc_mtx, PZERO | PCATCH, |
333 |
"fidord", 0); |
334 |
DPRINTFN(5, "woke, error=%d\n", error); |
335 |
if (dev->si_drv1 == NULL) |
336 |
error = EIO; |
337 |
if (error) { |
338 |
sc->sc_state.aslp = false; |
339 |
goto exit; |
340 |
} |
341 |
} |
342 |
|
343 |
if (sc->sc_state.data && uio->uio_resid > 0) { |
344 |
length = min(uio->uio_resid, sc->sc_isize); |
345 |
mtx_unlock(&sc->sc_mtx); |
346 |
|
347 |
/* Copy the data to the user process. */ |
348 |
DPRINTFN(5, "got %lu chars\n", (u_long)length); |
349 |
error = uiomove(sc->sc_buf, length, uio); |
350 |
|
351 |
mtx_lock(&sc->sc_mtx); |
352 |
if (error != 0) |
353 |
goto exit; |
354 |
sc->sc_state.data = false; |
355 |
} |
356 |
exit: |
357 |
mtx_unlock(&sc->sc_mtx); |
358 |
|
359 |
return (error); |
360 |
} |
361 |
|
362 |
static int |
363 |
fido_write(struct cdev *dev, struct uio *uio, int flag) |
364 |
{ |
365 |
uint8_t buf[FIDO_MAX_REPORT_SIZE]; |
366 |
struct fido_softc *sc = dev->si_drv1; |
367 |
int error; |
368 |
|
369 |
DPRINTFN(1, "\n"); |
370 |
|
371 |
if (sc == NULL) |
372 |
return (EIO); |
373 |
|
374 |
if (uio->uio_resid != sc->sc_osize) |
375 |
return (EINVAL); |
376 |
error = uiomove(buf, uio->uio_resid, uio); |
377 |
if (error == 0) |
378 |
error = hid_write(sc->sc_dev, buf, sc->sc_osize); |
379 |
|
380 |
return (error); |
381 |
} |
382 |
|
383 |
#ifdef COMPAT_FREEBSD32 |
384 |
static void |
385 |
update_ugd32(const struct usb_gen_descriptor *ugd, |
386 |
struct usb_gen_descriptor32 *ugd32) |
387 |
{ |
388 |
/* Don't update hgd_data pointer */ |
389 |
CP(*ugd, *ugd32, ugd_lang_id); |
390 |
CP(*ugd, *ugd32, ugd_maxlen); |
391 |
CP(*ugd, *ugd32, ugd_actlen); |
392 |
CP(*ugd, *ugd32, ugd_offset); |
393 |
CP(*ugd, *ugd32, ugd_config_index); |
394 |
CP(*ugd, *ugd32, ugd_string_index); |
395 |
CP(*ugd, *ugd32, ugd_iface_index); |
396 |
CP(*ugd, *ugd32, ugd_altif_index); |
397 |
CP(*ugd, *ugd32, ugd_endpt_index); |
398 |
CP(*ugd, *ugd32, ugd_report_type); |
399 |
/* Don't update reserved */ |
400 |
} |
401 |
#endif |
402 |
|
403 |
static int |
404 |
fido_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, |
405 |
struct thread *td) |
406 |
{ |
407 |
#ifdef COMPAT_FREEBSD32 |
408 |
struct usb_gen_descriptor local_ugd; |
409 |
struct usb_gen_descriptor32 *ugd32 = NULL; |
410 |
#endif |
411 |
struct fido_softc *sc = dev->si_drv1; |
412 |
struct usb_gen_descriptor *ugd = (struct usb_gen_descriptor *)addr; |
413 |
uint32_t size; |
414 |
|
415 |
DPRINTFN(2, "cmd=%lx\n", cmd); |
416 |
|
417 |
if (sc == NULL) |
418 |
return (EIO); |
419 |
|
420 |
#ifdef COMPAT_FREEBSD32 |
421 |
switch (cmd) { |
422 |
case USB_GET_REPORT_DESC32: |
423 |
cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor); |
424 |
ugd32 = (struct usb_gen_descriptor32 *)addr; |
425 |
ugd = &local_ugd; |
426 |
PTRIN_CP(*ugd32, *ugd, ugd_data); |
427 |
CP(*ugd32, *ugd, ugd_lang_id); |
428 |
CP(*ugd32, *ugd, ugd_maxlen); |
429 |
CP(*ugd32, *ugd, ugd_actlen); |
430 |
CP(*ugd32, *ugd, ugd_offset); |
431 |
CP(*ugd32, *ugd, ugd_config_index); |
432 |
CP(*ugd32, *ugd, ugd_string_index); |
433 |
CP(*ugd32, *ugd, ugd_iface_index); |
434 |
CP(*ugd32, *ugd, ugd_altif_index); |
435 |
CP(*ugd32, *ugd, ugd_endpt_index); |
436 |
CP(*ugd32, *ugd, ugd_report_type); |
437 |
/* Don't copy reserved */ |
438 |
break; |
439 |
} |
440 |
#endif |
441 |
|
442 |
/* fixed-length ioctls handling */ |
443 |
switch (cmd) { |
444 |
case FIONBIO: |
445 |
/* All handled in the upper FS layer. */ |
446 |
return (0); |
447 |
|
448 |
case USB_GET_REPORT_DESC: |
449 |
size = MIN(sc->sc_rdesc_size, ugd->ugd_maxlen); |
450 |
ugd->ugd_actlen = size; |
451 |
#ifdef COMPAT_FREEBSD32 |
452 |
if (ugd32 != NULL) |
453 |
update_ugd32(ugd, ugd32); |
454 |
#endif |
455 |
if (ugd->ugd_data == NULL) |
456 |
return (0); /* descriptor length only */ |
457 |
|
458 |
return (copyout(sc->sc_rdesc, ugd->ugd_data, size)); |
459 |
|
460 |
case USB_GET_DEVICEINFO: |
461 |
return(hid_ioctl( |
462 |
sc->sc_dev, USB_GET_DEVICEINFO, (uintptr_t)addr)); |
463 |
} |
464 |
|
465 |
return (EINVAL); |
466 |
} |
467 |
|
468 |
static int |
469 |
fido_poll(struct cdev *dev, int events, struct thread *td) |
470 |
{ |
471 |
struct fido_softc *sc = dev->si_drv1; |
472 |
int revents = 0; |
473 |
|
474 |
if (sc == NULL) |
475 |
return (POLLHUP); |
476 |
|
477 |
if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE)) |
478 |
revents |= events & (POLLOUT | POLLWRNORM); |
479 |
if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) { |
480 |
if (!sc->sc_state.data) |
481 |
hidbus_intr_start(sc->sc_dev); |
482 |
mtx_lock(&sc->sc_mtx); |
483 |
if (sc->sc_state.data) |
484 |
revents |= events & (POLLIN | POLLRDNORM); |
485 |
else { |
486 |
sc->sc_state.sel = true; |
487 |
selrecord(td, &sc->sc_rsel); |
488 |
} |
489 |
mtx_unlock(&sc->sc_mtx); |
490 |
} |
491 |
|
492 |
return (revents); |
493 |
} |
494 |
|
495 |
static int |
496 |
fido_kqfilter(struct cdev *dev, struct knote *kn) |
497 |
{ |
498 |
struct fido_softc *sc = dev->si_drv1; |
499 |
|
500 |
if (sc == NULL) |
501 |
return (ENXIO); |
502 |
|
503 |
switch(kn->kn_filter) { |
504 |
case EVFILT_READ: |
505 |
if (sc->sc_fflags & FREAD) { |
506 |
if (!sc->sc_state.data) |
507 |
hidbus_intr_start(sc->sc_dev); |
508 |
kn->kn_fop = &fido_filterops_read; |
509 |
break; |
510 |
} |
511 |
/* FALLTHROUGH */ |
512 |
default: |
513 |
return(EINVAL); |
514 |
} |
515 |
kn->kn_hook = sc; |
516 |
|
517 |
knlist_add(&sc->sc_rsel.si_note, kn, 0); |
518 |
return (0); |
519 |
} |
520 |
|
521 |
static int |
522 |
fido_kqread(struct knote *kn, long hint) |
523 |
{ |
524 |
struct fido_softc *sc = kn->kn_hook; |
525 |
int ret; |
526 |
|
527 |
mtx_assert(&sc->sc_mtx, MA_OWNED); |
528 |
|
529 |
if (sc->dev->si_drv1 == NULL) { |
530 |
kn->kn_flags |= EV_EOF; |
531 |
ret = 1; |
532 |
} else |
533 |
ret = sc->sc_state.data ? 1 : 0; |
534 |
|
535 |
return (ret); |
536 |
} |
537 |
|
538 |
static void |
539 |
fido_kqdetach(struct knote *kn) |
540 |
{ |
541 |
struct fido_softc *sc = kn->kn_hook; |
542 |
|
543 |
knlist_remove(&sc->sc_rsel.si_note, kn, 0); |
544 |
} |
545 |
|
546 |
static void |
547 |
fido_notify(struct fido_softc *sc) |
548 |
{ |
549 |
mtx_assert(&sc->sc_mtx, MA_OWNED); |
550 |
|
551 |
if (sc->sc_state.aslp) { |
552 |
sc->sc_state.aslp = false; |
553 |
DPRINTFN(5, "waking %p\n", &sc->sc_buf); |
554 |
wakeup(&sc->sc_buf); |
555 |
} |
556 |
if (sc->sc_state.sel) { |
557 |
sc->sc_state.sel = false; |
558 |
selwakeuppri(&sc->sc_rsel, PZERO); |
559 |
} |
560 |
KNOTE_LOCKED(&sc->sc_rsel.si_note, 0); |
561 |
} |
562 |
|
563 |
static device_method_t fido_methods[] = { |
564 |
/* Device interface */ |
565 |
DEVMETHOD(device_probe, fido_probe), |
566 |
DEVMETHOD(device_attach, fido_attach), |
567 |
DEVMETHOD(device_detach, fido_detach), |
568 |
|
569 |
DEVMETHOD_END |
570 |
}; |
571 |
|
572 |
static driver_t fido_driver = { |
573 |
#ifdef FIDO_MAKE_UHID_ALIAS |
574 |
"uhid", |
575 |
#else |
576 |
"fido", |
577 |
#endif |
578 |
fido_methods, |
579 |
sizeof(struct fido_softc) |
580 |
}; |
581 |
|
582 |
#if __FreeBSD_version < 1400014 |
583 |
devclass_t fido_devclass; |
584 |
DRIVER_MODULE(fido, hidbus, fido_driver, fido_devclass, NULL, NULL); |
585 |
#else |
586 |
DRIVER_MODULE(fido, hidbus, fido_driver, NULL, NULL); |
587 |
#endif |
588 |
MODULE_DEPEND(fido, hidbus, 1, 1, 1); |
589 |
MODULE_DEPEND(fido, hid, 1, 1, 1); |
590 |
MODULE_DEPEND(fido, usb, 1, 1, 1); |
591 |
MODULE_VERSION(fido, 1); |
592 |
HID_PNP_INFO(fido_devs); |