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