jcs's openbsd hax
openbsd
1/* $OpenBSD: ukbd.c,v 1.91 2025/08/14 14:39:44 deraadt Exp $ */
2/* $NetBSD: ukbd.c,v 1.85 2003/03/11 16:44:00 augustss Exp $ */
3
4/*
5 * Copyright (c) 2010 Miodrag Vallat.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19/*
20 * Copyright (c) 1998 The NetBSD Foundation, Inc.
21 * All rights reserved.
22 *
23 * This code is derived from software contributed to The NetBSD Foundation
24 * by Lennart Augustsson (lennart@augustsson.net) at
25 * Carlstedt Research & Technology.
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 * notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 *
36 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
37 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
38 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
39 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
40 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 * POSSIBILITY OF SUCH DAMAGE.
47 */
48
49/*
50 * HID spec: https://www.usb.org/sites/default/files/hid1_11.pdf
51 */
52
53#include <sys/param.h>
54#include <sys/systm.h>
55#include <sys/timeout.h>
56#include <sys/device.h>
57
58#include <machine/bus.h>
59
60#include <dev/usb/usb.h>
61#include <dev/usb/usbhid.h>
62
63#include <dev/usb/usbdi.h>
64#include <dev/usb/usbdivar.h> /* needs_reattach() */
65#include <dev/usb/usbdi_util.h>
66#include <dev/usb/usbdevs.h>
67#include <dev/usb/usb_quirks.h>
68#include <dev/usb/uhidev.h>
69#include <dev/usb/ukbdvar.h>
70
71#include <dev/wscons/wsconsio.h>
72#include <dev/wscons/wskbdvar.h>
73#include <dev/wscons/wsksymdef.h>
74#include <dev/wscons/wsksymvar.h>
75
76#include <dev/hid/hidkbdsc.h>
77
78#ifdef UKBD_DEBUG
79#define DPRINTF(x) do { if (ukbddebug) printf x; } while (0)
80#define DPRINTFN(n,x) do { if (ukbddebug>(n)) printf x; } while (0)
81int ukbddebug = 0;
82#else
83#define DPRINTF(x)
84#define DPRINTFN(n,x)
85#endif
86
87const kbd_t ukbd_countrylayout[1 + HCC_MAX] = {
88 (kbd_t)-1,
89 (kbd_t)-1, /* arabic */
90 KB_BE, /* belgian */
91 (kbd_t)-1, /* canadian bilingual */
92 KB_CF, /* canadian french */
93 (kbd_t)-1, /* czech */
94 KB_DK, /* danish */
95 (kbd_t)-1, /* finnish */
96 KB_FR, /* french */
97 KB_DE, /* german */
98 (kbd_t)-1, /* greek */
99 (kbd_t)-1, /* hebrew */
100 KB_HU, /* hungary */
101 (kbd_t)-1, /* international (iso) */
102 KB_IT, /* italian */
103 KB_JP, /* japanese (katakana) */
104 (kbd_t)-1, /* korean */
105 KB_LA, /* latin american */
106 (kbd_t)-1, /* netherlands/dutch */
107 KB_NO, /* norwegian */
108 (kbd_t)-1, /* persian (farsi) */
109 KB_PL, /* polish */
110 KB_PT, /* portuguese */
111 KB_RU, /* russian */
112 (kbd_t)-1, /* slovakia */
113 KB_ES, /* spanish */
114 KB_SV, /* swedish */
115 KB_SF, /* swiss french */
116 KB_SG, /* swiss german */
117 (kbd_t)-1, /* switzerland */
118 (kbd_t)-1, /* taiwan */
119 KB_TR, /* turkish Q */
120 KB_UK, /* uk */
121 KB_US, /* us */
122 (kbd_t)-1, /* yugoslavia */
123 (kbd_t)-1 /* turkish F */
124};
125
126struct ukbd_softc {
127 struct uhidev sc_hdev;
128#define sc_ledsize sc_hdev.sc_osize
129
130 struct hidkbd sc_kbd;
131 int sc_spl;
132
133#ifdef DDB
134 struct timeout sc_ddb; /* for entering DDB */
135#endif
136};
137
138void ukbd_cngetc(void *, u_int *, int *);
139void ukbd_cnpollc(void *, int);
140void ukbd_cnbell(void *, u_int, u_int, u_int);
141void ukbd_debugger(void *);
142
143const struct wskbd_consops ukbd_consops = {
144 ukbd_cngetc,
145 ukbd_cnpollc,
146 ukbd_cnbell,
147#ifdef DDB
148 ukbd_debugger,
149#endif
150};
151
152void ukbd_intr(struct uhidev *addr, void *ibuf, u_int len);
153
154void ukbd_db_enter(void *);
155int ukbd_enable(void *, int);
156void ukbd_set_leds(void *, int);
157int ukbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
158
159const struct wskbd_accessops ukbd_accessops = {
160 ukbd_enable,
161 ukbd_set_leds,
162 ukbd_ioctl,
163};
164
165int ukbd_match(struct device *, void *, void *);
166void ukbd_attach(struct device *, struct device *, void *);
167int ukbd_detach(struct device *, int);
168
169struct cfdriver ukbd_cd = {
170 NULL, "ukbd", DV_DULL
171};
172
173const struct cfattach ukbd_ca = {
174 sizeof(struct ukbd_softc), ukbd_match, ukbd_attach, ukbd_detach
175};
176
177#ifdef __loongson__
178void ukbd_gdium_munge(void *, uint8_t *, u_int);
179#endif
180
181const struct usb_devno ukbd_never_console[] = {
182 /* Apple HID-proxy is always detected before any real USB keyboard */
183 { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_BLUETOOTH_HCI },
184 /* ugold(4) devices, which also present themselves as ukbd */
185 { USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
186 { USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPERHUM },
187 { USB_VENDOR_PCSENSORS, USB_PRODUCT_PCSENSORS_TEMPER },
188 { USB_VENDOR_RDING, USB_PRODUCT_RDING_TEMPER },
189 { USB_VENDOR_WCH2, USB_PRODUCT_WCH2_TEMPER },
190};
191
192int
193ukbd_match(struct device *parent, void *match, void *aux)
194{
195 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
196 int size;
197 void *desc;
198
199 /*
200 * Most Yubikey have OTP enabled by default, and the feature
201 * is difficult to disable. Policy decision: Don't attach
202 * as a keyboard.
203 */
204 if (uha->uaa->vendor == USB_VENDOR_YUBICO)
205 return (UMATCH_NONE);
206
207 if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
208 return (UMATCH_NONE);
209
210 uhidev_get_report_desc(uha->parent, &desc, &size);
211 if (!hid_is_collection(desc, size, uha->reportid,
212 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
213 return (UMATCH_NONE);
214
215 return (UMATCH_IFACECLASS);
216}
217
218void
219ukbd_attach(struct device *parent, struct device *self, void *aux)
220{
221 struct ukbd_softc *sc = (struct ukbd_softc *)self;
222 struct hidkbd *kbd = &sc->sc_kbd;
223 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
224 struct usb_hid_descriptor *hid;
225 u_int32_t quirks, qflags = 0;
226 int dlen, repid;
227 int console = 1;
228 void *desc;
229 kbd_t layout = (kbd_t)-1;
230
231 sc->sc_hdev.sc_intr = ukbd_intr;
232 sc->sc_hdev.sc_parent = uha->parent;
233 sc->sc_hdev.sc_udev = uha->uaa->device;
234 sc->sc_hdev.sc_report_id = uha->reportid;
235
236 usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
237
238 uhidev_get_report_desc(uha->parent, &desc, &dlen);
239 repid = uha->reportid;
240 sc->sc_hdev.sc_isize = hid_report_size(desc, dlen, hid_input, repid);
241 sc->sc_hdev.sc_osize = hid_report_size(desc, dlen, hid_output, repid);
242 sc->sc_hdev.sc_fsize = hid_report_size(desc, dlen, hid_feature, repid);
243
244 /*
245 * Do not allow unwanted devices to claim the console.
246 */
247 if (usb_lookup(ukbd_never_console, uha->uaa->vendor, uha->uaa->product))
248 console = 0;
249
250 quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags;
251 if (quirks & UQ_SPUR_BUT_UP)
252 qflags |= HIDKBD_SPUR_BUT_UP;
253
254 if (hidkbd_attach(self, kbd, console, qflags, repid, desc, dlen) != 0)
255 return;
256
257 if (uha->uaa->vendor == USB_VENDOR_APPLE) {
258 if (hid_locate(desc, dlen, HID_USAGE2(HUP_APPLE, HUG_FN_KEY),
259 uha->reportid, hid_input, &kbd->sc_fn, &qflags)) {
260 if (qflags & HIO_VARIABLE) {
261 switch (uha->uaa->product) {
262 case USB_PRODUCT_APPLE_FOUNTAIN_ISO:
263 case USB_PRODUCT_APPLE_GEYSER_ISO:
264 case USB_PRODUCT_APPLE_GEYSER3_ISO:
265 case USB_PRODUCT_APPLE_WELLSPRING6_ISO:
266 case USB_PRODUCT_APPLE_WELLSPRING8_ISO:
267 kbd->sc_munge = hidkbd_apple_iso_munge;
268 break;
269 case USB_PRODUCT_APPLE_WELLSPRING_ISO:
270 case USB_PRODUCT_APPLE_WELLSPRING4_ISO:
271 case USB_PRODUCT_APPLE_WELLSPRING4A_ISO:
272 kbd->sc_munge = hidkbd_apple_iso_mba_munge;
273 break;
274 case USB_PRODUCT_APPLE_WELLSPRING_ANSI:
275 case USB_PRODUCT_APPLE_WELLSPRING_JIS:
276 case USB_PRODUCT_APPLE_WELLSPRING4_ANSI:
277 case USB_PRODUCT_APPLE_WELLSPRING4_JIS:
278 case USB_PRODUCT_APPLE_WELLSPRING4A_ANSI:
279 case USB_PRODUCT_APPLE_WELLSPRING4A_JIS:
280 kbd->sc_munge = hidkbd_apple_mba_munge;
281 break;
282 default:
283 kbd->sc_munge = hidkbd_apple_munge;
284 break;
285 }
286 }
287 }
288 }
289
290 if (uha->uaa->vendor == USB_VENDOR_TOPRE &&
291 uha->uaa->product == USB_PRODUCT_TOPRE_HHKB) {
292 /* ignore country code on purpose */
293 } else {
294 usb_interface_descriptor_t *id;
295
296 id = usbd_get_interface_descriptor(uha->uaa->iface);
297 hid = usbd_get_hid_descriptor(uha->uaa->device, id);
298
299 if (hid->bCountryCode <= HCC_MAX)
300 layout = ukbd_countrylayout[hid->bCountryCode];
301#ifdef DIAGNOSTIC
302 if (hid->bCountryCode != 0)
303 printf(", country code %d", hid->bCountryCode);
304#endif
305 }
306 if (layout == (kbd_t)-1) {
307#ifdef UKBD_LAYOUT
308 layout = UKBD_LAYOUT;
309#else
310 layout = KB_US | KB_DEFAULT;
311#endif
312 }
313
314 printf("\n");
315
316#ifdef __loongson__
317 if (uha->uaa->vendor == USB_VENDOR_CYPRESS &&
318 uha->uaa->product == USB_PRODUCT_CYPRESS_LPRDK)
319 kbd->sc_munge = ukbd_gdium_munge;
320#endif
321
322 if (kbd->sc_console_keyboard) {
323 extern struct wskbd_mapdata ukbd_keymapdata;
324
325 DPRINTF(("ukbd_attach: console keyboard sc=%p\n", sc));
326 ukbd_keymapdata.layout = layout;
327 wskbd_cnattach(&ukbd_consops, sc, &ukbd_keymapdata);
328 ukbd_enable(sc, 1);
329 }
330
331 /* Flash the leds; no real purpose, just shows we're alive. */
332 ukbd_set_leds(sc, WSKBD_LED_SCROLL | WSKBD_LED_NUM |
333 WSKBD_LED_CAPS | WSKBD_LED_COMPOSE);
334 usbd_delay_ms(sc->sc_hdev.sc_udev, 400);
335 ukbd_set_leds(sc, 0);
336
337 hidkbd_attach_wskbd(kbd, layout, &ukbd_accessops);
338
339#ifdef DDB
340 timeout_set(&sc->sc_ddb, ukbd_db_enter, sc);
341#endif
342}
343
344int
345ukbd_detach(struct device *self, int flags)
346{
347 struct ukbd_softc *sc = (struct ukbd_softc *)self;
348 struct hidkbd *kbd = &sc->sc_kbd;
349 int rv;
350
351 rv = hidkbd_detach(kbd, flags);
352
353 /* The console keyboard does not get a disable call, so check pipe. */
354 if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
355 uhidev_close(&sc->sc_hdev);
356
357 return (rv);
358}
359
360void
361ukbd_intr(struct uhidev *addr, void *ibuf, u_int len)
362{
363 struct ukbd_softc *sc = (struct ukbd_softc *)addr;
364 struct hidkbd *kbd = &sc->sc_kbd;
365
366 if (kbd->sc_enabled != 0)
367 hidkbd_input(kbd, (uint8_t *)ibuf, len);
368}
369
370int
371ukbd_enable(void *v, int on)
372{
373 struct ukbd_softc *sc = v;
374 struct hidkbd *kbd = &sc->sc_kbd;
375 int rv;
376
377 if (on && usbd_is_dying(sc->sc_hdev.sc_udev))
378 return EIO;
379
380 if ((rv = hidkbd_enable(kbd, on)) != 0)
381 return rv;
382
383 if (on) {
384 return uhidev_open(&sc->sc_hdev);
385 } else {
386 uhidev_close(&sc->sc_hdev);
387 return 0;
388 }
389}
390
391void
392ukbd_set_leds(void *v, int leds)
393{
394 struct ukbd_softc *sc = v;
395 struct hidkbd *kbd = &sc->sc_kbd;
396 u_int8_t res;
397
398 if (usbd_is_dying(sc->sc_hdev.sc_udev))
399 return;
400
401 if (sc->sc_ledsize && hidkbd_set_leds(kbd, leds, &res) != 0)
402 uhidev_set_report_async(sc->sc_hdev.sc_parent,
403 UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, &res, 1);
404}
405
406int
407ukbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
408{
409 struct ukbd_softc *sc = v;
410 struct hidkbd *kbd = &sc->sc_kbd;
411 int rc;
412
413 switch (cmd) {
414 case WSKBDIO_GTYPE:
415 *(int *)data = WSKBD_TYPE_USB;
416 return (0);
417 case WSKBDIO_SETLEDS:
418 ukbd_set_leds(v, *(int *)data);
419 return (0);
420 default:
421 rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
422 if (rc != -1)
423 return rc;
424 else
425 return hidkbd_ioctl(kbd, cmd, data, flag, p);
426 }
427}
428
429/* Console interface. */
430void
431ukbd_cngetc(void *v, u_int *type, int *data)
432{
433 struct ukbd_softc *sc = v;
434 struct hidkbd *kbd = &sc->sc_kbd;
435
436 DPRINTFN(0,("ukbd_cngetc: enter\n"));
437 kbd->sc_polling = 1;
438 while (kbd->sc_npollchar <= 0)
439 usbd_dopoll(sc->sc_hdev.sc_udev);
440 kbd->sc_polling = 0;
441 hidkbd_cngetc(kbd, type, data);
442 DPRINTFN(0,("ukbd_cngetc: return 0x%02x\n", *data));
443}
444
445void
446ukbd_cnpollc(void *v, int on)
447{
448 struct ukbd_softc *sc = v;
449
450 DPRINTFN(2,("ukbd_cnpollc: sc=%p on=%d\n", v, on));
451
452 if (on)
453 sc->sc_spl = splusb();
454 else
455 splx(sc->sc_spl);
456 usbd_set_polling(sc->sc_hdev.sc_udev, on);
457}
458
459void
460ukbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
461{
462 hidkbd_bell(pitch, period, volume, 1);
463}
464
465#ifdef DDB
466void
467ukbd_debugger(void *v)
468{
469 struct ukbd_softc *sc = v;
470
471 /*
472 * For the console keyboard we can't deliver CTL-ALT-ESC
473 * from the interrupt routine. Doing so would start
474 * polling from inside the interrupt routine and that
475 * loses bigtime.
476 */
477 timeout_add(&sc->sc_ddb, 1);
478}
479
480void
481ukbd_db_enter(void *xsc)
482{
483 db_enter();
484}
485#endif
486
487int
488ukbd_cnattach(void)
489{
490 struct ukbd_softc *sc;
491 int i;
492
493 /*
494 * XXX USB requires too many parts of the kernel to be running
495 * XXX in order to work, so we can't do much for the console
496 * XXX keyboard until autoconfiguration has run its course.
497 */
498 hidkbd_is_console = 1;
499
500 if (!cold) {
501 /*
502 * When switching console dynamically force all USB keyboards
503 * to re-attach and possibly became the 'console' keyboard.
504 */
505 for (i = 0; i < ukbd_cd.cd_ndevs; i++) {
506 if ((sc = ukbd_cd.cd_devs[i]) != NULL) {
507 usb_needs_reattach(sc->sc_hdev.sc_udev);
508 break;
509 }
510 }
511 }
512
513 return (0);
514}
515
516#ifdef __loongson__
517/*
518 * Software Fn- translation for Gdium Liberty keyboard.
519 */
520#define GDIUM_FN_CODE 0x82
521void
522ukbd_gdium_munge(void *vsc, uint8_t *ibuf, u_int ilen)
523{
524 struct ukbd_softc *sc = vsc;
525 struct hidkbd *kbd = &sc->sc_kbd;
526 uint8_t *pos, *spos, *epos, xlat;
527 int fn;
528
529 static const struct hidkbd_translation gdium_fn_trans[] = {
530#ifdef notyet
531 { 58, 0 }, /* F1 -> toggle camera */
532 { 59, 0 }, /* F2 -> toggle wireless */
533#endif
534 { 60, 127 }, /* F3 -> audio mute */
535 { 61, 128 }, /* F4 -> audio raise */
536 { 62, 129 }, /* F5 -> audio lower */
537#ifdef notyet
538 { 63, 0 }, /* F6 -> toggle ext. video */
539 { 64, 0 }, /* F7 -> toggle mouse */
540 { 65, 0 }, /* F8 -> brightness up */
541 { 66, 0 }, /* F9 -> brightness down */
542 { 67, 0 }, /* F10 -> suspend */
543 { 68, 0 }, /* F11 -> user1 */
544 { 69, 0 }, /* F12 -> user2 */
545 { 70, 0 }, /* print screen -> sysrq */
546#endif
547 { 76, 71 }, /* delete -> scroll lock */
548 { 81, 78 }, /* down -> page down */
549 { 82, 75 } /* up -> page up */
550 };
551
552 spos = ibuf + kbd->sc_keycodeloc.pos / 8;
553 epos = spos + kbd->sc_nkeycode;
554
555 /*
556 * Check for Fn key being down and remove it from the report.
557 */
558
559 fn = 0;
560 for (pos = spos; pos != epos; pos++)
561 if (*pos == GDIUM_FN_CODE) {
562 fn = 1;
563 *pos = 0;
564 break;
565 }
566
567 /*
568 * Rewrite keycodes on the fly to perform Fn-key translation.
569 * Keycodes without a translation are passed unaffected.
570 */
571
572 if (fn != 0)
573 for (pos = spos; pos != epos; pos++) {
574 xlat = hidkbd_translate(gdium_fn_trans,
575 nitems(gdium_fn_trans), *pos);
576 if (xlat != 0)
577 *pos = xlat;
578 }
579
580}
581#endif