xref: /openbsd/sys/dev/usb/ukbd.c (revision 6cdbbe9b)
1 /*	$OpenBSD: ukbd.c,v 1.84 2021/09/12 06:58:08 anton 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/kernel.h>
57 #include <sys/device.h>
58 #include <sys/ioctl.h>
59 
60 #include <machine/bus.h>
61 
62 #include <dev/usb/usb.h>
63 #include <dev/usb/usbhid.h>
64 
65 #include <dev/usb/usbdi.h>
66 #include <dev/usb/usbdivar.h> /* needs_reattach() */
67 #include <dev/usb/usbdi_util.h>
68 #include <dev/usb/usbdevs.h>
69 #include <dev/usb/usb_quirks.h>
70 #include <dev/usb/uhidev.h>
71 #include <dev/usb/ukbdvar.h>
72 
73 #include <dev/wscons/wsconsio.h>
74 #include <dev/wscons/wskbdvar.h>
75 #include <dev/wscons/wsksymdef.h>
76 #include <dev/wscons/wsksymvar.h>
77 
78 #include <dev/hid/hidkbdsc.h>
79 
80 #ifdef UKBD_DEBUG
81 #define DPRINTF(x)	do { if (ukbddebug) printf x; } while (0)
82 #define DPRINTFN(n,x)	do { if (ukbddebug>(n)) printf x; } while (0)
83 int	ukbddebug = 0;
84 #else
85 #define DPRINTF(x)
86 #define DPRINTFN(n,x)
87 #endif
88 
89 const kbd_t ukbd_countrylayout[1 + HCC_MAX] = {
90 	(kbd_t)-1,
91 	(kbd_t)-1,	/* arabic */
92 	KB_BE,		/* belgian */
93 	(kbd_t)-1,	/* canadian bilingual */
94 	KB_CF,		/* canadian french */
95 	(kbd_t)-1,	/* czech */
96 	KB_DK,		/* danish */
97 	(kbd_t)-1,	/* finnish */
98 	KB_FR,		/* french */
99 	KB_DE,		/* german */
100 	(kbd_t)-1,	/* greek */
101 	(kbd_t)-1,	/* hebrew */
102 	KB_HU,		/* hungary */
103 	(kbd_t)-1,	/* international (iso) */
104 	KB_IT,		/* italian */
105 	KB_JP,		/* japanese (katakana) */
106 	(kbd_t)-1,	/* korean */
107 	KB_LA,		/* latin american */
108 	(kbd_t)-1,	/* netherlands/dutch */
109 	KB_NO,		/* norwegian */
110 	(kbd_t)-1,	/* persian (farsi) */
111 	KB_PL,		/* polish */
112 	KB_PT,		/* portuguese */
113 	KB_RU,		/* russian */
114 	(kbd_t)-1,	/* slovakia */
115 	KB_ES,		/* spanish */
116 	KB_SV,		/* swedish */
117 	KB_SF,		/* swiss french */
118 	KB_SG,		/* swiss german */
119 	(kbd_t)-1,	/* switzerland */
120 	(kbd_t)-1,	/* taiwan */
121 	KB_TR,		/* turkish Q */
122 	KB_UK,		/* uk */
123 	KB_US,		/* us */
124 	(kbd_t)-1,	/* yugoslavia */
125 	(kbd_t)-1	/* turkish F */
126 };
127 
128 struct ukbd_softc {
129 	struct uhidev		sc_hdev;
130 #define sc_ledsize		sc_hdev.sc_osize
131 
132 	struct hidkbd		sc_kbd;
133 	int			sc_spl;
134 	struct hid_location	sc_apple_fn;
135 	void			(*sc_munge)(void *, uint8_t *, u_int);
136 
137 #ifdef DDB
138 	struct timeout		sc_ddb;	/* for entering DDB */
139 #endif
140 };
141 
142 void	ukbd_cngetc(void *, u_int *, int *);
143 void	ukbd_cnpollc(void *, int);
144 void	ukbd_cnbell(void *, u_int, u_int, u_int);
145 void	ukbd_debugger(void *);
146 
147 const struct wskbd_consops ukbd_consops = {
148 	ukbd_cngetc,
149 	ukbd_cnpollc,
150 	ukbd_cnbell,
151 #ifdef DDB
152 	ukbd_debugger,
153 #endif
154 };
155 
156 void	ukbd_intr(struct uhidev *addr, void *ibuf, u_int len);
157 
158 void	ukbd_db_enter(void *);
159 int	ukbd_enable(void *, int);
160 void	ukbd_set_leds(void *, int);
161 int	ukbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
162 
163 const struct wskbd_accessops ukbd_accessops = {
164 	ukbd_enable,
165 	ukbd_set_leds,
166 	ukbd_ioctl,
167 };
168 
169 int	ukbd_match(struct device *, void *, void *);
170 void	ukbd_attach(struct device *, struct device *, void *);
171 int	ukbd_detach(struct device *, int);
172 
173 struct cfdriver ukbd_cd = {
174 	NULL, "ukbd", DV_DULL
175 };
176 
177 const struct cfattach ukbd_ca = {
178 	sizeof(struct ukbd_softc), ukbd_match, ukbd_attach, ukbd_detach
179 };
180 
181 struct ukbd_translation {
182 	uint8_t original;
183 	uint8_t translation;
184 };
185 
186 #ifdef __loongson__
187 void	ukbd_gdium_munge(void *, uint8_t *, u_int);
188 #endif
189 void	ukbd_apple_munge(void *, uint8_t *, u_int);
190 void	ukbd_apple_mba_munge(void *, uint8_t *, u_int);
191 void	ukbd_apple_iso_munge(void *, uint8_t *, u_int);
192 void	ukbd_apple_iso_mba_munge(void *, uint8_t *, u_int);
193 void	ukbd_apple_translate(void *, uint8_t *, u_int,
194 	    const struct ukbd_translation *, u_int);
195 uint8_t	ukbd_translate(const struct ukbd_translation *, size_t, uint8_t);
196 
197 int
198 ukbd_match(struct device *parent, void *match, void *aux)
199 {
200 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
201 	int size;
202 	void *desc;
203 
204 	uhidev_get_report_desc(uha->parent, &desc, &size);
205 	if (!hid_is_collection(desc, size, uha->reportid,
206 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
207 		return (UMATCH_NONE);
208 
209 	return (UMATCH_IFACECLASS);
210 }
211 
212 void
213 ukbd_attach(struct device *parent, struct device *self, void *aux)
214 {
215 	struct ukbd_softc *sc = (struct ukbd_softc *)self;
216 	struct hidkbd *kbd = &sc->sc_kbd;
217 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
218 	struct usb_hid_descriptor *hid;
219 	u_int32_t quirks, qflags = 0;
220 	int dlen, repid;
221 	int console = 1;
222 	void *desc;
223 	kbd_t layout = (kbd_t)-1;
224 
225 	sc->sc_hdev.sc_intr = ukbd_intr;
226 	sc->sc_hdev.sc_parent = uha->parent;
227 	sc->sc_hdev.sc_udev = uha->uaa->device;
228 	sc->sc_hdev.sc_report_id = uha->reportid;
229 
230 	usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
231 
232 	uhidev_get_report_desc(uha->parent, &desc, &dlen);
233 	repid = uha->reportid;
234 	sc->sc_hdev.sc_isize = hid_report_size(desc, dlen, hid_input, repid);
235 	sc->sc_hdev.sc_osize = hid_report_size(desc, dlen, hid_output, repid);
236 	sc->sc_hdev.sc_fsize = hid_report_size(desc, dlen, hid_feature, repid);
237 
238 	 /*
239 	  * Since the HID-Proxy is always detected before any
240 	  * real keyboard, do not let it grab the console.
241 	  */
242 	if (uha->uaa->vendor == USB_VENDOR_APPLE &&
243 	    uha->uaa->product == USB_PRODUCT_APPLE_BLUETOOTH_HCI)
244 		console = 0;
245 
246 	quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags;
247 	if (quirks & UQ_SPUR_BUT_UP)
248 		qflags |= HIDKBD_SPUR_BUT_UP;
249 
250 	if (hidkbd_attach(self, kbd, console, qflags, repid, desc, dlen) != 0)
251 		return;
252 
253 	if (uha->uaa->vendor == USB_VENDOR_APPLE) {
254 		if (hid_locate(desc, dlen, HID_USAGE2(HUP_APPLE, HUG_FN_KEY),
255 		    uha->reportid, hid_input, &sc->sc_apple_fn, &qflags)) {
256 			if (qflags & HIO_VARIABLE) {
257 				switch (uha->uaa->product) {
258 				case USB_PRODUCT_APPLE_FOUNTAIN_ISO:
259 				case USB_PRODUCT_APPLE_GEYSER_ISO:
260 				case USB_PRODUCT_APPLE_GEYSER3_ISO:
261 				case USB_PRODUCT_APPLE_WELLSPRING6_ISO:
262 				case USB_PRODUCT_APPLE_WELLSPRING8_ISO:
263 					sc->sc_munge = ukbd_apple_iso_munge;
264 					break;
265 				case USB_PRODUCT_APPLE_WELLSPRING_ISO:
266 				case USB_PRODUCT_APPLE_WELLSPRING4_ISO:
267 				case USB_PRODUCT_APPLE_WELLSPRING4A_ISO:
268 					sc->sc_munge = ukbd_apple_iso_mba_munge;
269 					break;
270 				case USB_PRODUCT_APPLE_WELLSPRING_ANSI:
271 				case USB_PRODUCT_APPLE_WELLSPRING_JIS:
272 				case USB_PRODUCT_APPLE_WELLSPRING4_ANSI:
273 				case USB_PRODUCT_APPLE_WELLSPRING4_JIS:
274 				case USB_PRODUCT_APPLE_WELLSPRING4A_ANSI:
275 				case USB_PRODUCT_APPLE_WELLSPRING4A_JIS:
276 					sc->sc_munge = ukbd_apple_mba_munge;
277 					break;
278 				default:
279 					sc->sc_munge = ukbd_apple_munge;
280 					break;
281 				}
282 			}
283 		}
284 	}
285 
286 	if (uha->uaa->vendor == USB_VENDOR_TOPRE &&
287 	    uha->uaa->product == USB_PRODUCT_TOPRE_HHKB) {
288 		/* ignore country code on purpose */
289 	} else {
290 		usb_interface_descriptor_t *id;
291 
292 		id = usbd_get_interface_descriptor(uha->uaa->iface);
293 		hid = usbd_get_hid_descriptor(uha->uaa->device, id);
294 
295 		if (hid->bCountryCode <= HCC_MAX)
296 			layout = ukbd_countrylayout[hid->bCountryCode];
297 #ifdef DIAGNOSTIC
298 		if (hid->bCountryCode != 0)
299 			printf(", country code %d", hid->bCountryCode);
300 #endif
301 	}
302 	if (layout == (kbd_t)-1) {
303 #ifdef UKBD_LAYOUT
304 		layout = UKBD_LAYOUT;
305 #else
306 		layout = KB_US | KB_DEFAULT;
307 #endif
308 	}
309 
310 	printf("\n");
311 
312 #ifdef __loongson__
313 	if (uha->uaa->vendor == USB_VENDOR_CYPRESS &&
314 	    uha->uaa->product == USB_PRODUCT_CYPRESS_LPRDK)
315 		sc->sc_munge = ukbd_gdium_munge;
316 #endif
317 
318 	if (kbd->sc_console_keyboard) {
319 		extern struct wskbd_mapdata ukbd_keymapdata;
320 
321 		DPRINTF(("ukbd_attach: console keyboard sc=%p\n", sc));
322 		ukbd_keymapdata.layout = layout;
323 		wskbd_cnattach(&ukbd_consops, sc, &ukbd_keymapdata);
324 		ukbd_enable(sc, 1);
325 	}
326 
327 	/* Flash the leds; no real purpose, just shows we're alive. */
328 	ukbd_set_leds(sc, WSKBD_LED_SCROLL | WSKBD_LED_NUM |
329 		          WSKBD_LED_CAPS | WSKBD_LED_COMPOSE);
330 	usbd_delay_ms(sc->sc_hdev.sc_udev, 400);
331 	ukbd_set_leds(sc, 0);
332 
333 	hidkbd_attach_wskbd(kbd, layout, &ukbd_accessops);
334 
335 #ifdef DDB
336 	timeout_set(&sc->sc_ddb, ukbd_db_enter, sc);
337 #endif
338 }
339 
340 int
341 ukbd_detach(struct device *self, int flags)
342 {
343 	struct ukbd_softc *sc = (struct ukbd_softc *)self;
344 	struct hidkbd *kbd = &sc->sc_kbd;
345 	int rv;
346 
347 	rv = hidkbd_detach(kbd, flags);
348 
349 	/* The console keyboard does not get a disable call, so check pipe. */
350 	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
351 		uhidev_close(&sc->sc_hdev);
352 
353 	return (rv);
354 }
355 
356 void
357 ukbd_intr(struct uhidev *addr, void *ibuf, u_int len)
358 {
359 	struct ukbd_softc *sc = (struct ukbd_softc *)addr;
360 	struct hidkbd *kbd = &sc->sc_kbd;
361 
362 	if (kbd->sc_enabled != 0) {
363 		if (sc->sc_munge != NULL)
364 			(*sc->sc_munge)(sc, (uint8_t *)ibuf, len);
365 		hidkbd_input(kbd, (uint8_t *)ibuf, len);
366 	}
367 }
368 
369 int
370 ukbd_enable(void *v, int on)
371 {
372 	struct ukbd_softc *sc = v;
373 	struct hidkbd *kbd = &sc->sc_kbd;
374 	int rv;
375 
376 	if (on && usbd_is_dying(sc->sc_hdev.sc_udev))
377 		return EIO;
378 
379 	if ((rv = hidkbd_enable(kbd, on)) != 0)
380 		return rv;
381 
382 	if (on) {
383 		return uhidev_open(&sc->sc_hdev);
384 	} else {
385 		uhidev_close(&sc->sc_hdev);
386 		return 0;
387 	}
388 }
389 
390 void
391 ukbd_set_leds(void *v, int leds)
392 {
393 	struct ukbd_softc *sc = v;
394 	struct hidkbd *kbd = &sc->sc_kbd;
395 	u_int8_t res;
396 
397 	if (usbd_is_dying(sc->sc_hdev.sc_udev))
398 		return;
399 
400 	if (sc->sc_ledsize && hidkbd_set_leds(kbd, leds, &res) != 0)
401 		uhidev_set_report_async(sc->sc_hdev.sc_parent,
402 		    UHID_OUTPUT_REPORT, sc->sc_hdev.sc_report_id, &res, 1);
403 }
404 
405 int
406 ukbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
407 {
408 	struct ukbd_softc *sc = v;
409 	struct hidkbd *kbd = &sc->sc_kbd;
410 	int rc;
411 
412 	switch (cmd) {
413 	case WSKBDIO_GTYPE:
414 		*(int *)data = WSKBD_TYPE_USB;
415 		return (0);
416 	case WSKBDIO_SETLEDS:
417 		ukbd_set_leds(v, *(int *)data);
418 		return (0);
419 	default:
420 		rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
421 		if (rc != -1)
422 			return rc;
423 		else
424 			return hidkbd_ioctl(kbd, cmd, data, flag, p);
425 	}
426 }
427 
428 /* Console interface. */
429 void
430 ukbd_cngetc(void *v, u_int *type, int *data)
431 {
432 	struct ukbd_softc *sc = v;
433 	struct hidkbd *kbd = &sc->sc_kbd;
434 
435 	DPRINTFN(0,("ukbd_cngetc: enter\n"));
436 	kbd->sc_polling = 1;
437 	while (kbd->sc_npollchar <= 0)
438 		usbd_dopoll(sc->sc_hdev.sc_udev);
439 	kbd->sc_polling = 0;
440 	hidkbd_cngetc(kbd, type, data);
441 	DPRINTFN(0,("ukbd_cngetc: return 0x%02x\n", *data));
442 }
443 
444 void
445 ukbd_cnpollc(void *v, int on)
446 {
447 	struct ukbd_softc *sc = v;
448 
449 	DPRINTFN(2,("ukbd_cnpollc: sc=%p on=%d\n", v, on));
450 
451 	if (on)
452 		sc->sc_spl = splusb();
453 	else
454 		splx(sc->sc_spl);
455 	usbd_set_polling(sc->sc_hdev.sc_udev, on);
456 }
457 
458 void
459 ukbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
460 {
461 	hidkbd_bell(pitch, period, volume, 1);
462 }
463 
464 #ifdef DDB
465 void
466 ukbd_debugger(void *v)
467 {
468 	struct ukbd_softc *sc = v;
469 
470 	/*
471 	 * For the console keyboard we can't deliver CTL-ALT-ESC
472 	 * from the interrupt routine.  Doing so would start
473 	 * polling from inside the interrupt routine and that
474 	 * loses bigtime.
475 	 */
476 	timeout_add(&sc->sc_ddb, 1);
477 }
478 
479 void
480 ukbd_db_enter(void *xsc)
481 {
482 	db_enter();
483 }
484 #endif
485 
486 int
487 ukbd_cnattach(void)
488 {
489 	struct ukbd_softc *sc;
490 	int i;
491 
492 	/*
493 	 * XXX USB requires too many parts of the kernel to be running
494 	 * XXX in order to work, so we can't do much for the console
495 	 * XXX keyboard until autconfiguration has run its course.
496 	 */
497 	hidkbd_is_console = 1;
498 
499 	if (!cold) {
500 		/*
501 		 * When switching console dynamically force all USB keyboards
502 		 * to re-attach and possibly became the 'console' keyboard.
503 		 */
504 		for (i = 0; i < ukbd_cd.cd_ndevs; i++) {
505 			if ((sc = ukbd_cd.cd_devs[i]) != NULL) {
506 				usb_needs_reattach(sc->sc_hdev.sc_udev);
507 				break;
508 			}
509 		}
510 	}
511 
512 	return (0);
513 }
514 
515 uint8_t
516 ukbd_translate(const struct ukbd_translation *table, size_t tsize,
517     uint8_t keycode)
518 {
519 	for (; tsize != 0; table++, tsize--)
520 		if (table->original == keycode)
521 			return table->translation;
522 	return 0;
523 }
524 
525 void
526 ukbd_apple_translate(void *vsc, uint8_t *ibuf, u_int ilen,
527     const struct ukbd_translation* trans, u_int tlen)
528 {
529 	struct ukbd_softc *sc = vsc;
530 	struct hidkbd *kbd = &sc->sc_kbd;
531 	uint8_t *pos, *spos, *epos, xlat;
532 
533 	spos = ibuf + kbd->sc_keycodeloc.pos / 8;
534 	epos = spos + kbd->sc_nkeycode;
535 
536 	for (pos = spos; pos != epos; pos++) {
537 		xlat = ukbd_translate(trans, tlen, *pos);
538 		if (xlat != 0)
539 			*pos = xlat;
540 	}
541 }
542 
543 void
544 ukbd_apple_munge(void *vsc, uint8_t *ibuf, u_int ilen)
545 {
546 	struct ukbd_softc *sc = vsc;
547 
548 	static const struct ukbd_translation apple_fn_trans[] = {
549 		{ 40, 73 },	/* return -> insert */
550 		{ 42, 76 },	/* backspace -> delete */
551 #ifdef notyet
552 		{ 58, 0 },	/* F1 -> screen brightness down */
553 		{ 59, 0 },	/* F2 -> screen brightness up */
554 		{ 60, 0 },	/* F3 */
555 		{ 61, 0 },	/* F4 */
556 		{ 62, 0 },	/* F5 -> keyboard backlight down */
557 		{ 63, 0 },	/* F6 -> keyboard backlight up */
558 		{ 64, 0 },	/* F7 -> audio back */
559 		{ 65, 0 },	/* F8 -> audio pause/play */
560 		{ 66, 0 },	/* F9 -> audio next */
561 #endif
562 #ifdef __macppc__
563 		{ 58, 233 },	/* F1 -> screen brightness down */
564 		{ 59, 232 },	/* F2 -> screen brightness up */
565 		{ 60, 127 },	/* F3 -> audio mute */
566 		{ 61, 129 },	/* F4 -> audio lower */
567 		{ 62, 128 },	/* F5 -> audio raise */
568 #else
569 		{ 67, 127 },	/* F10 -> audio mute */
570 		{ 68, 129 },	/* F11 -> audio lower */
571 		{ 69, 128 },	/* F12 -> audio raise */
572 #endif
573 		{ 79, 77 },	/* right -> end */
574 		{ 80, 74 },	/* left -> home */
575 		{ 81, 78 },	/* down -> page down */
576 		{ 82, 75 }	/* up -> page up */
577 	};
578 
579 	if (!hid_get_data(ibuf, ilen, &sc->sc_apple_fn))
580 		return;
581 
582 	ukbd_apple_translate(vsc, ibuf, ilen, apple_fn_trans,
583 	    nitems(apple_fn_trans));
584 }
585 
586 void
587 ukbd_apple_mba_munge(void *vsc, uint8_t *ibuf, u_int ilen)
588 {
589 	struct ukbd_softc *sc = vsc;
590 
591 	static const struct ukbd_translation apple_fn_trans[] = {
592 		{ 40, 73 },	/* return -> insert */
593 		{ 42, 76 },	/* backspace -> delete */
594 #ifdef notyet
595 		{ 58, 0 },	/* F1 -> screen brightness down */
596 		{ 59, 0 },	/* F2 -> screen brightness up */
597 		{ 60, 0 },	/* F3 */
598 		{ 61, 0 },	/* F4 */
599 		{ 62, 0 },	/* F5 */
600 		{ 63, 0 },	/* F6 -> audio back */
601 		{ 64, 0 },	/* F7 -> audio pause/play */
602 		{ 65, 0 },	/* F8 -> audio next */
603 #endif
604 		{ 66, 127 },	/* F9 -> audio mute */
605 		{ 67, 129 },	/* F10 -> audio lower */
606 		{ 68, 128 },	/* F11 -> audio raise */
607 #ifdef notyet
608 		{ 69, 0 },	/* F12 -> eject */
609 #endif
610 		{ 79, 77 },	/* right -> end */
611 		{ 80, 74 },	/* left -> home */
612 		{ 81, 78 },	/* down -> page down */
613 		{ 82, 75 }	/* up -> page up */
614 	};
615 
616 	if (!hid_get_data(ibuf, ilen, &sc->sc_apple_fn))
617 		return;
618 
619 	ukbd_apple_translate(vsc, ibuf, ilen, apple_fn_trans,
620 	    nitems(apple_fn_trans));
621 }
622 
623 void
624 ukbd_apple_iso_munge(void *vsc, uint8_t *ibuf, u_int ilen)
625 {
626 	static const struct ukbd_translation apple_iso_trans[] = {
627 		{ 53, 100 },	/* less -> grave */
628 		{ 100, 53 },
629 	};
630 
631 	ukbd_apple_translate(vsc, ibuf, ilen, apple_iso_trans,
632 	    nitems(apple_iso_trans));
633 	ukbd_apple_munge(vsc, ibuf, ilen);
634 }
635 
636 void
637 ukbd_apple_iso_mba_munge(void *vsc, uint8_t *ibuf, u_int ilen)
638 {
639 	static const struct ukbd_translation apple_iso_trans[] = {
640 		{ 53, 100 },	/* less -> grave */
641 		{ 100, 53 },
642 	};
643 
644 	ukbd_apple_translate(vsc, ibuf, ilen, apple_iso_trans,
645 	    nitems(apple_iso_trans));
646 	ukbd_apple_mba_munge(vsc, ibuf, ilen);
647 }
648 
649 #ifdef __loongson__
650 /*
651  * Software Fn- translation for Gdium Liberty keyboard.
652  */
653 #define	GDIUM_FN_CODE	0x82
654 void
655 ukbd_gdium_munge(void *vsc, uint8_t *ibuf, u_int ilen)
656 {
657 	struct ukbd_softc *sc = vsc;
658 	struct hidkbd *kbd = &sc->sc_kbd;
659 	uint8_t *pos, *spos, *epos, xlat;
660 	int fn;
661 
662 	static const struct ukbd_translation gdium_fn_trans[] = {
663 #ifdef notyet
664 		{ 58, 0 },	/* F1 -> toggle camera */
665 		{ 59, 0 },	/* F2 -> toggle wireless */
666 #endif
667 		{ 60, 127 },	/* F3 -> audio mute */
668 		{ 61, 128 },	/* F4 -> audio raise */
669 		{ 62, 129 },	/* F5 -> audio lower */
670 #ifdef notyet
671 		{ 63, 0 },	/* F6 -> toggle ext. video */
672 		{ 64, 0 },	/* F7 -> toggle mouse */
673 		{ 65, 0 },	/* F8 -> brightness up */
674 		{ 66, 0 },	/* F9 -> brightness down */
675 		{ 67, 0 },	/* F10 -> suspend */
676 		{ 68, 0 },	/* F11 -> user1 */
677 		{ 69, 0 },	/* F12 -> user2 */
678 		{ 70, 0 },	/* print screen -> sysrq */
679 #endif
680 		{ 76, 71 },	/* delete -> scroll lock */
681 		{ 81, 78 },	/* down -> page down */
682 		{ 82, 75 }	/* up -> page up */
683 	};
684 
685 	spos = ibuf + kbd->sc_keycodeloc.pos / 8;
686 	epos = spos + kbd->sc_nkeycode;
687 
688 	/*
689 	 * Check for Fn key being down and remove it from the report.
690 	 */
691 
692 	fn = 0;
693 	for (pos = spos; pos != epos; pos++)
694 		if (*pos == GDIUM_FN_CODE) {
695 			fn = 1;
696 			*pos = 0;
697 			break;
698 		}
699 
700 	/*
701 	 * Rewrite keycodes on the fly to perform Fn-key translation.
702 	 * Keycodes without a translation are passed unaffected.
703 	 */
704 
705 	if (fn != 0)
706 		for (pos = spos; pos != epos; pos++) {
707 			xlat = ukbd_translate(gdium_fn_trans,
708 			    nitems(gdium_fn_trans), *pos);
709 			if (xlat != 0)
710 				*pos = xlat;
711 		}
712 
713 }
714 #endif
715