1 /* $OpenBSD: ukbd.c,v 1.90 2024/05/23 03:21:09 jsg 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)
81 int ukbddebug = 0;
82 #else
83 #define DPRINTF(x)
84 #define DPRINTFN(n,x)
85 #endif
86
87 const 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
126 struct 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
138 void ukbd_cngetc(void *, u_int *, int *);
139 void ukbd_cnpollc(void *, int);
140 void ukbd_cnbell(void *, u_int, u_int, u_int);
141 void ukbd_debugger(void *);
142
143 const struct wskbd_consops ukbd_consops = {
144 ukbd_cngetc,
145 ukbd_cnpollc,
146 ukbd_cnbell,
147 #ifdef DDB
148 ukbd_debugger,
149 #endif
150 };
151
152 void ukbd_intr(struct uhidev *addr, void *ibuf, u_int len);
153
154 void ukbd_db_enter(void *);
155 int ukbd_enable(void *, int);
156 void ukbd_set_leds(void *, int);
157 int ukbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
158
159 const struct wskbd_accessops ukbd_accessops = {
160 ukbd_enable,
161 ukbd_set_leds,
162 ukbd_ioctl,
163 };
164
165 int ukbd_match(struct device *, void *, void *);
166 void ukbd_attach(struct device *, struct device *, void *);
167 int ukbd_detach(struct device *, int);
168
169 struct cfdriver ukbd_cd = {
170 NULL, "ukbd", DV_DULL
171 };
172
173 const struct cfattach ukbd_ca = {
174 sizeof(struct ukbd_softc), ukbd_match, ukbd_attach, ukbd_detach
175 };
176
177 #ifdef __loongson__
178 void ukbd_gdium_munge(void *, uint8_t *, u_int);
179 #endif
180
181 const 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
192 int
ukbd_match(struct device * parent,void * match,void * aux)193 ukbd_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 if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
200 return (UMATCH_NONE);
201
202 uhidev_get_report_desc(uha->parent, &desc, &size);
203 if (!hid_is_collection(desc, size, uha->reportid,
204 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
205 return (UMATCH_NONE);
206
207 return (UMATCH_IFACECLASS);
208 }
209
210 void
ukbd_attach(struct device * parent,struct device * self,void * aux)211 ukbd_attach(struct device *parent, struct device *self, void *aux)
212 {
213 struct ukbd_softc *sc = (struct ukbd_softc *)self;
214 struct hidkbd *kbd = &sc->sc_kbd;
215 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
216 struct usb_hid_descriptor *hid;
217 u_int32_t quirks, qflags = 0;
218 int dlen, repid;
219 int console = 1;
220 void *desc;
221 kbd_t layout = (kbd_t)-1;
222
223 sc->sc_hdev.sc_intr = ukbd_intr;
224 sc->sc_hdev.sc_parent = uha->parent;
225 sc->sc_hdev.sc_udev = uha->uaa->device;
226 sc->sc_hdev.sc_report_id = uha->reportid;
227
228 usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
229
230 uhidev_get_report_desc(uha->parent, &desc, &dlen);
231 repid = uha->reportid;
232 sc->sc_hdev.sc_isize = hid_report_size(desc, dlen, hid_input, repid);
233 sc->sc_hdev.sc_osize = hid_report_size(desc, dlen, hid_output, repid);
234 sc->sc_hdev.sc_fsize = hid_report_size(desc, dlen, hid_feature, repid);
235
236 /*
237 * Do not allow unwanted devices to claim the console.
238 */
239 if (usb_lookup(ukbd_never_console, uha->uaa->vendor, uha->uaa->product))
240 console = 0;
241
242 quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags;
243 if (quirks & UQ_SPUR_BUT_UP)
244 qflags |= HIDKBD_SPUR_BUT_UP;
245
246 if (hidkbd_attach(self, kbd, console, qflags, repid, desc, dlen) != 0)
247 return;
248
249 if (uha->uaa->vendor == USB_VENDOR_APPLE) {
250 if (hid_locate(desc, dlen, HID_USAGE2(HUP_APPLE, HUG_FN_KEY),
251 uha->reportid, hid_input, &kbd->sc_fn, &qflags)) {
252 if (qflags & HIO_VARIABLE) {
253 switch (uha->uaa->product) {
254 case USB_PRODUCT_APPLE_FOUNTAIN_ISO:
255 case USB_PRODUCT_APPLE_GEYSER_ISO:
256 case USB_PRODUCT_APPLE_GEYSER3_ISO:
257 case USB_PRODUCT_APPLE_WELLSPRING6_ISO:
258 case USB_PRODUCT_APPLE_WELLSPRING8_ISO:
259 kbd->sc_munge = hidkbd_apple_iso_munge;
260 break;
261 case USB_PRODUCT_APPLE_WELLSPRING_ISO:
262 case USB_PRODUCT_APPLE_WELLSPRING4_ISO:
263 case USB_PRODUCT_APPLE_WELLSPRING4A_ISO:
264 kbd->sc_munge = hidkbd_apple_iso_mba_munge;
265 break;
266 case USB_PRODUCT_APPLE_WELLSPRING_ANSI:
267 case USB_PRODUCT_APPLE_WELLSPRING_JIS:
268 case USB_PRODUCT_APPLE_WELLSPRING4_ANSI:
269 case USB_PRODUCT_APPLE_WELLSPRING4_JIS:
270 case USB_PRODUCT_APPLE_WELLSPRING4A_ANSI:
271 case USB_PRODUCT_APPLE_WELLSPRING4A_JIS:
272 kbd->sc_munge = hidkbd_apple_mba_munge;
273 break;
274 default:
275 kbd->sc_munge = hidkbd_apple_munge;
276 break;
277 }
278 }
279 }
280 }
281
282 if (uha->uaa->vendor == USB_VENDOR_TOPRE &&
283 uha->uaa->product == USB_PRODUCT_TOPRE_HHKB) {
284 /* ignore country code on purpose */
285 } else {
286 usb_interface_descriptor_t *id;
287
288 id = usbd_get_interface_descriptor(uha->uaa->iface);
289 hid = usbd_get_hid_descriptor(uha->uaa->device, id);
290
291 if (hid->bCountryCode <= HCC_MAX)
292 layout = ukbd_countrylayout[hid->bCountryCode];
293 #ifdef DIAGNOSTIC
294 if (hid->bCountryCode != 0)
295 printf(", country code %d", hid->bCountryCode);
296 #endif
297 }
298 if (layout == (kbd_t)-1) {
299 #ifdef UKBD_LAYOUT
300 layout = UKBD_LAYOUT;
301 #else
302 layout = KB_US | KB_DEFAULT;
303 #endif
304 }
305
306 printf("\n");
307
308 #ifdef __loongson__
309 if (uha->uaa->vendor == USB_VENDOR_CYPRESS &&
310 uha->uaa->product == USB_PRODUCT_CYPRESS_LPRDK)
311 kbd->sc_munge = ukbd_gdium_munge;
312 #endif
313
314 if (kbd->sc_console_keyboard) {
315 extern struct wskbd_mapdata ukbd_keymapdata;
316
317 DPRINTF(("ukbd_attach: console keyboard sc=%p\n", sc));
318 ukbd_keymapdata.layout = layout;
319 wskbd_cnattach(&ukbd_consops, sc, &ukbd_keymapdata);
320 ukbd_enable(sc, 1);
321 }
322
323 /* Flash the leds; no real purpose, just shows we're alive. */
324 ukbd_set_leds(sc, WSKBD_LED_SCROLL | WSKBD_LED_NUM |
325 WSKBD_LED_CAPS | WSKBD_LED_COMPOSE);
326 usbd_delay_ms(sc->sc_hdev.sc_udev, 400);
327 ukbd_set_leds(sc, 0);
328
329 hidkbd_attach_wskbd(kbd, layout, &ukbd_accessops);
330
331 #ifdef DDB
332 timeout_set(&sc->sc_ddb, ukbd_db_enter, sc);
333 #endif
334 }
335
336 int
ukbd_detach(struct device * self,int flags)337 ukbd_detach(struct device *self, int flags)
338 {
339 struct ukbd_softc *sc = (struct ukbd_softc *)self;
340 struct hidkbd *kbd = &sc->sc_kbd;
341 int rv;
342
343 rv = hidkbd_detach(kbd, flags);
344
345 /* The console keyboard does not get a disable call, so check pipe. */
346 if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
347 uhidev_close(&sc->sc_hdev);
348
349 return (rv);
350 }
351
352 void
ukbd_intr(struct uhidev * addr,void * ibuf,u_int len)353 ukbd_intr(struct uhidev *addr, void *ibuf, u_int len)
354 {
355 struct ukbd_softc *sc = (struct ukbd_softc *)addr;
356 struct hidkbd *kbd = &sc->sc_kbd;
357
358 if (kbd->sc_enabled != 0)
359 hidkbd_input(kbd, (uint8_t *)ibuf, len);
360 }
361
362 int
ukbd_enable(void * v,int on)363 ukbd_enable(void *v, int on)
364 {
365 struct ukbd_softc *sc = v;
366 struct hidkbd *kbd = &sc->sc_kbd;
367 int rv;
368
369 if (on && usbd_is_dying(sc->sc_hdev.sc_udev))
370 return EIO;
371
372 if ((rv = hidkbd_enable(kbd, on)) != 0)
373 return rv;
374
375 if (on) {
376 return uhidev_open(&sc->sc_hdev);
377 } else {
378 uhidev_close(&sc->sc_hdev);
379 return 0;
380 }
381 }
382
383 void
ukbd_set_leds(void * v,int leds)384 ukbd_set_leds(void *v, int leds)
385 {
386 struct ukbd_softc *sc = v;
387 struct hidkbd *kbd = &sc->sc_kbd;
388 u_int8_t res;
389
390 if (usbd_is_dying(sc->sc_hdev.sc_udev))
391 return;
392
393 if (sc->sc_ledsize && hidkbd_set_leds(kbd, leds, &res) != 0)
394 uhidev_set_report_async(sc->sc_hdev.sc_parent,
395 UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, &res, 1);
396 }
397
398 int
ukbd_ioctl(void * v,u_long cmd,caddr_t data,int flag,struct proc * p)399 ukbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
400 {
401 struct ukbd_softc *sc = v;
402 struct hidkbd *kbd = &sc->sc_kbd;
403 int rc;
404
405 switch (cmd) {
406 case WSKBDIO_GTYPE:
407 *(int *)data = WSKBD_TYPE_USB;
408 return (0);
409 case WSKBDIO_SETLEDS:
410 ukbd_set_leds(v, *(int *)data);
411 return (0);
412 default:
413 rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
414 if (rc != -1)
415 return rc;
416 else
417 return hidkbd_ioctl(kbd, cmd, data, flag, p);
418 }
419 }
420
421 /* Console interface. */
422 void
ukbd_cngetc(void * v,u_int * type,int * data)423 ukbd_cngetc(void *v, u_int *type, int *data)
424 {
425 struct ukbd_softc *sc = v;
426 struct hidkbd *kbd = &sc->sc_kbd;
427
428 DPRINTFN(0,("ukbd_cngetc: enter\n"));
429 kbd->sc_polling = 1;
430 while (kbd->sc_npollchar <= 0)
431 usbd_dopoll(sc->sc_hdev.sc_udev);
432 kbd->sc_polling = 0;
433 hidkbd_cngetc(kbd, type, data);
434 DPRINTFN(0,("ukbd_cngetc: return 0x%02x\n", *data));
435 }
436
437 void
ukbd_cnpollc(void * v,int on)438 ukbd_cnpollc(void *v, int on)
439 {
440 struct ukbd_softc *sc = v;
441
442 DPRINTFN(2,("ukbd_cnpollc: sc=%p on=%d\n", v, on));
443
444 if (on)
445 sc->sc_spl = splusb();
446 else
447 splx(sc->sc_spl);
448 usbd_set_polling(sc->sc_hdev.sc_udev, on);
449 }
450
451 void
ukbd_cnbell(void * v,u_int pitch,u_int period,u_int volume)452 ukbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
453 {
454 hidkbd_bell(pitch, period, volume, 1);
455 }
456
457 #ifdef DDB
458 void
ukbd_debugger(void * v)459 ukbd_debugger(void *v)
460 {
461 struct ukbd_softc *sc = v;
462
463 /*
464 * For the console keyboard we can't deliver CTL-ALT-ESC
465 * from the interrupt routine. Doing so would start
466 * polling from inside the interrupt routine and that
467 * loses bigtime.
468 */
469 timeout_add(&sc->sc_ddb, 1);
470 }
471
472 void
ukbd_db_enter(void * xsc)473 ukbd_db_enter(void *xsc)
474 {
475 db_enter();
476 }
477 #endif
478
479 int
ukbd_cnattach(void)480 ukbd_cnattach(void)
481 {
482 struct ukbd_softc *sc;
483 int i;
484
485 /*
486 * XXX USB requires too many parts of the kernel to be running
487 * XXX in order to work, so we can't do much for the console
488 * XXX keyboard until autoconfiguration has run its course.
489 */
490 hidkbd_is_console = 1;
491
492 if (!cold) {
493 /*
494 * When switching console dynamically force all USB keyboards
495 * to re-attach and possibly became the 'console' keyboard.
496 */
497 for (i = 0; i < ukbd_cd.cd_ndevs; i++) {
498 if ((sc = ukbd_cd.cd_devs[i]) != NULL) {
499 usb_needs_reattach(sc->sc_hdev.sc_udev);
500 break;
501 }
502 }
503 }
504
505 return (0);
506 }
507
508 #ifdef __loongson__
509 /*
510 * Software Fn- translation for Gdium Liberty keyboard.
511 */
512 #define GDIUM_FN_CODE 0x82
513 void
ukbd_gdium_munge(void * vsc,uint8_t * ibuf,u_int ilen)514 ukbd_gdium_munge(void *vsc, uint8_t *ibuf, u_int ilen)
515 {
516 struct ukbd_softc *sc = vsc;
517 struct hidkbd *kbd = &sc->sc_kbd;
518 uint8_t *pos, *spos, *epos, xlat;
519 int fn;
520
521 static const struct hidkbd_translation gdium_fn_trans[] = {
522 #ifdef notyet
523 { 58, 0 }, /* F1 -> toggle camera */
524 { 59, 0 }, /* F2 -> toggle wireless */
525 #endif
526 { 60, 127 }, /* F3 -> audio mute */
527 { 61, 128 }, /* F4 -> audio raise */
528 { 62, 129 }, /* F5 -> audio lower */
529 #ifdef notyet
530 { 63, 0 }, /* F6 -> toggle ext. video */
531 { 64, 0 }, /* F7 -> toggle mouse */
532 { 65, 0 }, /* F8 -> brightness up */
533 { 66, 0 }, /* F9 -> brightness down */
534 { 67, 0 }, /* F10 -> suspend */
535 { 68, 0 }, /* F11 -> user1 */
536 { 69, 0 }, /* F12 -> user2 */
537 { 70, 0 }, /* print screen -> sysrq */
538 #endif
539 { 76, 71 }, /* delete -> scroll lock */
540 { 81, 78 }, /* down -> page down */
541 { 82, 75 } /* up -> page up */
542 };
543
544 spos = ibuf + kbd->sc_keycodeloc.pos / 8;
545 epos = spos + kbd->sc_nkeycode;
546
547 /*
548 * Check for Fn key being down and remove it from the report.
549 */
550
551 fn = 0;
552 for (pos = spos; pos != epos; pos++)
553 if (*pos == GDIUM_FN_CODE) {
554 fn = 1;
555 *pos = 0;
556 break;
557 }
558
559 /*
560 * Rewrite keycodes on the fly to perform Fn-key translation.
561 * Keycodes without a translation are passed unaffected.
562 */
563
564 if (fn != 0)
565 for (pos = spos; pos != epos; pos++) {
566 xlat = hidkbd_translate(gdium_fn_trans,
567 nitems(gdium_fn_trans), *pos);
568 if (xlat != 0)
569 *pos = xlat;
570 }
571
572 }
573 #endif
574