diff --git a/common/usb_kbd.c b/common/usb_kbd.c index d178af248a..a6221ef716 100644 --- a/common/usb_kbd.c +++ b/common/usb_kbd.c @@ -75,13 +75,12 @@ static const unsigned char usb_kbd_num_keypad[] = { '.', 0, 0, 0, '=' }; -/* - * map arrow keys to ^F/^B ^N/^P, can't really use the proper - * ANSI sequence for arrow keys because the queuing code breaks - * when a single keypress expands to 3 queue elements - */ -static const unsigned char usb_kbd_arrow[] = { - 0x6, 0x2, 0xe, 0x10 +static const u8 usb_special_keys[] = { +#ifdef CONFIG_USB_KEYBOARD_FN_KEYS + '2', 'H', '5', '3', 'F', '6', 'C', 'D', 'B', 'A' +#else + 'C', 'D', 'B', 'A' +#endif }; /* @@ -96,12 +95,6 @@ static const unsigned char usb_kbd_arrow[] = { #define USB_KBD_LEDMASK \ (USB_KBD_NUMLOCK | USB_KBD_CAPSLOCK | USB_KBD_SCROLLLOCK) -/* - * USB Keyboard reports are 8 bytes in boot protocol. - * Appendix B of HID Device Class Definition 1.11 - */ -#define USB_KBD_BOOT_REPORT_SIZE 8 - struct usb_kbd_pdata { unsigned long intpipe; int intpktsize; @@ -127,7 +120,7 @@ extern int __maybe_unused net_busy_flag; static unsigned long __maybe_unused kbd_testc_tms; /* Puts character in the queue and sets up the in and out pointer. */ -static void usb_kbd_put_queue(struct usb_kbd_pdata *data, char c) +static void usb_kbd_put_queue(struct usb_kbd_pdata *data, u8 c) { if (data->usb_in_pointer == USB_KBD_BUFFER_LEN - 1) { /* Check for buffer full. */ @@ -146,12 +139,6 @@ static void usb_kbd_put_queue(struct usb_kbd_pdata *data, char c) data->usb_kbd_buffer[data->usb_in_pointer] = c; } -static void usb_kbd_put_sequence(struct usb_kbd_pdata *data, char *s) -{ - for (; *s; s++) - usb_kbd_put_queue(data, *s); -} - /* * Set the LEDs. Since this is used in the irq routine, the control job is * issued with a timeout of 0. This means, that the job is queued without @@ -214,10 +201,6 @@ static int usb_kbd_translate(struct usb_kbd_pdata *data, unsigned char scancode, keycode = usb_kbd_numkey[scancode - 0x1e]; } - /* Arrow keys */ - if ((scancode >= 0x4f) && (scancode <= 0x52)) - keycode = usb_kbd_arrow[scancode - 0x4f]; - /* Numeric keypad */ if ((scancode >= 0x54) && (scancode <= 0x67)) keycode = usb_kbd_num_keypad[scancode - 0x54]; @@ -242,28 +225,58 @@ static int usb_kbd_translate(struct usb_kbd_pdata *data, unsigned char scancode, } /* Report keycode if any */ - if (keycode) + if (keycode) { debug("%c", keycode); - - switch (keycode) { - case 0x0e: /* Down arrow key */ - usb_kbd_put_sequence(data, "\e[B"); - break; - case 0x10: /* Up arrow key */ - usb_kbd_put_sequence(data, "\e[A"); - break; - case 0x06: /* Right arrow key */ - usb_kbd_put_sequence(data, "\e[C"); - break; - case 0x02: /* Left arrow key */ - usb_kbd_put_sequence(data, "\e[D"); - break; - default: usb_kbd_put_queue(data, keycode); - break; + return 0; } +#ifdef CONFIG_USB_KEYBOARD_FN_KEYS + if (scancode < 0x3a || scancode > 0x52 || + scancode == 0x46 || scancode == 0x47) + return 1; + + usb_kbd_put_queue(data, 0x1b); + if (scancode < 0x3e) { + /* F1 - F4 */ + usb_kbd_put_queue(data, 0x4f); + usb_kbd_put_queue(data, scancode - 0x3a + 'P'); + return 0; + } + usb_kbd_put_queue(data, '['); + if (scancode < 0x42) { + /* F5 - F8 */ + usb_kbd_put_queue(data, '1'); + if (scancode == 0x3e) + --scancode; + keycode = scancode - 0x3f + '7'; + } else if (scancode < 0x49) { + /* F9 - F12 */ + usb_kbd_put_queue(data, '2'); + if (scancode > 0x43) + ++scancode; + keycode = scancode - 0x42 + '0'; + } else { + /* + * INSERT, HOME, PAGE UP, DELETE, END, PAGE DOWN, + * RIGHT, LEFT, DOWN, UP + */ + keycode = usb_special_keys[scancode - 0x49]; + } + usb_kbd_put_queue(data, keycode); + if (scancode < 0x4f && scancode != 0x4a && scancode != 0x4d) + usb_kbd_put_queue(data, '~'); return 0; +#else + /* Left, Right, Up, Down */ + if (scancode > 0x4e && scancode < 0x53) { + usb_kbd_put_queue(data, 0x1b); + usb_kbd_put_queue(data, '['); + usb_kbd_put_queue(data, usb_special_keys[scancode - 0x4f]); + return 0; + } + return 1; +#endif /* CONFIG_USB_KEYBOARD_FN_KEYS */ } static uint32_t usb_kbd_service_key(struct usb_device *dev, int i, int up) @@ -339,7 +352,7 @@ static inline void usb_kbd_poll_for_event(struct usb_device *dev) #if defined(CONFIG_SYS_USB_EVENT_POLL) struct usb_kbd_pdata *data = dev->privptr; - /* Submit a interrupt transfer request */ + /* Submit an interrupt transfer request */ if (usb_int_msg(dev, data->intpipe, &data->new[0], data->intpktsize, data->intinterval, true) >= 0) usb_kbd_irq_worker(dev); diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index 716096abc5..be1e103cf4 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -181,6 +181,7 @@ CONFIG_USB=y CONFIG_DM_USB=y CONFIG_USB_EMUL=y CONFIG_USB_KEYBOARD=y +CONFIG_USB_KEYBOARD_FN_KEYS=y CONFIG_DM_VIDEO=y CONFIG_CONSOLE_ROTATION=y CONFIG_CONSOLE_TRUETYPE=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index a8144436eb..eda595fca9 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -203,6 +203,7 @@ CONFIG_USB=y CONFIG_DM_USB=y CONFIG_USB_EMUL=y CONFIG_USB_KEYBOARD=y +CONFIG_USB_KEYBOARD_FN_KEYS=y CONFIG_DM_VIDEO=y CONFIG_CONSOLE_ROTATION=y CONFIG_CONSOLE_TRUETYPE=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index 774c278bce..02969f95f1 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -163,6 +163,7 @@ CONFIG_USB=y CONFIG_DM_USB=y CONFIG_USB_EMUL=y CONFIG_USB_KEYBOARD=y +CONFIG_USB_KEYBOARD_FN_KEYS=y CONFIG_DM_VIDEO=y CONFIG_CONSOLE_ROTATION=y CONFIG_CONSOLE_TRUETYPE=y diff --git a/configs/sandbox_spl_defconfig b/configs/sandbox_spl_defconfig index 02702fa7a5..3b0f15de88 100644 --- a/configs/sandbox_spl_defconfig +++ b/configs/sandbox_spl_defconfig @@ -183,6 +183,7 @@ CONFIG_USB=y CONFIG_DM_USB=y CONFIG_USB_EMUL=y CONFIG_USB_KEYBOARD=y +CONFIG_USB_KEYBOARD_FN_KEYS=y CONFIG_DM_VIDEO=y CONFIG_CONSOLE_ROTATION=y CONFIG_CONSOLE_TRUETYPE=y diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 9af78e8822..bea4a92b61 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -100,6 +100,12 @@ config USB_KEYBOARD if USB_KEYBOARD +config USB_KEYBOARD_FN_KEYS + bool "USB keyboard function key support" + help + Say Y here if you want support for keys F1 - F12, INS, HOME, DELETE, + END, PAGE UP, and PAGE DOWN. + choice prompt "USB keyboard polling" default SYS_USB_EVENT_POLL diff --git a/drivers/usb/emul/sandbox_keyb.c b/drivers/usb/emul/sandbox_keyb.c index dc43880d27..32bc9a1698 100644 --- a/drivers/usb/emul/sandbox_keyb.c +++ b/drivers/usb/emul/sandbox_keyb.c @@ -155,14 +155,20 @@ static void *keyb_desc_list[] = { NULL, }; -int sandbox_usb_keyb_add_string(struct udevice *dev, const char *str) +/** + * sandbox_usb_keyb_add_string() - provide a USB scancode buffer + * + * @dev: the keyboard emulation device + * @scancode: scancode buffer with USB_KBD_BOOT_REPORT_SIZE bytes + */ +int sandbox_usb_keyb_add_string(struct udevice *dev, + const char scancode[USB_KBD_BOOT_REPORT_SIZE]) { struct sandbox_keyb_priv *priv = dev_get_priv(dev); - int len, ret; + int ret; - len = strlen(str); - ret = membuff_put(&priv->in, str, len); - if (ret != len) + ret = membuff_put(&priv->in, scancode, USB_KBD_BOOT_REPORT_SIZE); + if (ret != USB_KBD_BOOT_REPORT_SIZE) return -ENOSPC; return 0; @@ -183,12 +189,12 @@ static int sandbox_keyb_interrupt(struct udevice *dev, struct usb_device *udev, { struct sandbox_keyb_priv *priv = dev_get_priv(dev); uint8_t *data = buffer; - int ch; memset(data, '\0', length); - ch = membuff_getbyte(&priv->in); - if (ch != -1) - data[2] = 4 + ch - 'a'; + if (length < USB_KBD_BOOT_REPORT_SIZE) + return 0; + + membuff_get(&priv->in, buffer, USB_KBD_BOOT_REPORT_SIZE); return 0; } @@ -213,7 +219,8 @@ static int sandbox_keyb_probe(struct udevice *dev) { struct sandbox_keyb_priv *priv = dev_get_priv(dev); - return membuff_new(&priv->in, 256); + /* Provide an 80 character keyboard buffer */ + return membuff_new(&priv->in, 80 * USB_KBD_BOOT_REPORT_SIZE); } static const struct dm_usb_ops sandbox_usb_keyb_ops = { diff --git a/include/usb.h b/include/usb.h index bcad552f85..efb67ea33f 100644 --- a/include/usb.h +++ b/include/usb.h @@ -242,6 +242,12 @@ int usb_host_eth_scan(int mode); #ifdef CONFIG_USB_KEYBOARD +/* + * USB Keyboard reports are 8 bytes in boot protocol. + * Appendix B of HID Device Class Definition 1.11 + */ +#define USB_KBD_BOOT_REPORT_SIZE 8 + int drv_usb_kbd_init(void); int usb_kbd_deregister(int force); diff --git a/test/dm/usb.c b/test/dm/usb.c index ef454b0ae5..e396c2a0ea 100644 --- a/test/dm/usb.c +++ b/test/dm/usb.c @@ -15,6 +15,12 @@ #include #include +struct keyboard_test_data { + const char modifiers; + const char scancode; + const char result[6]; +}; + /* Test that sandbox USB works correctly */ static int dm_test_usb_base(struct unit_test_state *uts) { @@ -115,9 +121,263 @@ static int dm_test_usb_stop(struct unit_test_state *uts) } DM_TEST(dm_test_usb_stop, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); +/** + * dm_test_usb_keyb() - test USB keyboard driver + * + * This test copies USB keyboard scan codes into the key buffer of the USB + * keyboard emulation driver. These are picked up during emulated interrupts + * by the USB keyboard driver and converted to characters and escape sequences. + * The test then reads and verifies these characters and escape sequences from + * the standard input. + * + * TODO: The following features are not yet tested: + * + * * LED status + * * caps-lock + * * num-lock + * * numerical pad keys + * + * TODO: The following features are not yet implemented by the USB keyboard + * driver and therefore not tested: + * + * * modifiers for non-alpha-numeric keys, e.g. and + * * some special keys, e.g. + * * some modifiers, e.g. and + * * alternative keyboard layouts + * + * @uts: unit test state + * Return: 0 on success + */ static int dm_test_usb_keyb(struct unit_test_state *uts) { struct udevice *dev; + const struct keyboard_test_data *pos; + const struct keyboard_test_data kbd_test_data[] = { + /* */ + {0x00, 0x04, "a"}, + /* */ + {0x00, 0x05, "b"}, + /* */ + {0x00, 0x06, "c"}, + /* */ + {0x00, 0x07, "d"}, + /* */ + {0x00, 0x08, "e"}, + /* */ + {0x00, 0x09, "f"}, + /* */ + {0x00, 0x0a, "g"}, + /* */ + {0x00, 0x0b, "h"}, + /* */ + {0x00, 0x0c, "i"}, + /* */ + {0x00, 0x0d, "j"}, + /* */ + {0x00, 0x0e, "k"}, + /* */ + {0x00, 0x0f, "l"}, + /* */ + {0x00, 0x10, "m"}, + /* */ + {0x00, 0x11, "n"}, + /* */ + {0x00, 0x12, "o"}, + /*

*/ + {0x00, 0x13, "p"}, + /* */ + {0x00, 0x14, "q"}, + /* */ + {0x00, 0x15, "r"}, + /* */ + {0x00, 0x16, "s"}, + /* */ + {0x00, 0x17, "t"}, + /* */ + {0x00, 0x18, "u"}, + /* */ + {0x00, 0x19, "v"}, + /* */ + {0x00, 0x1a, "w"}, + /* */ + {0x00, 0x1b, "x"}, + /* */ + {0x00, 0x1c, "y"}, + /* */ + {0x00, 0x1d, "z"}, + + /* */ + {0x02, 0x04, "A"}, + /* */ + {0x20, 0x1d, "Z"}, + + /* */ + {0x01, 0x04, "\x01"}, + /* */ + {0x10, 0x1d, "\x1a"}, + + /* <1> */ + {0x00, 0x1e, "1"}, + /* <2> */ + {0x00, 0x1f, "2"}, + /* <3> */ + {0x00, 0x20, "3"}, + /* <4> */ + {0x00, 0x21, "4"}, + /* <5> */ + {0x00, 0x22, "5"}, + /* <6> */ + {0x00, 0x23, "6"}, + /* <7> */ + {0x00, 0x24, "7"}, + /* <8> */ + {0x00, 0x25, "8"}, + /* <9> */ + {0x00, 0x26, "9"}, + /* <0> */ + {0x00, 0x27, "0"}, + + /* <1> */ + {0x02, 0x1e, "!"}, + /* <2> */ + {0x20, 0x1f, "@"}, + /* <3> */ + {0x02, 0x20, "#"}, + /* <4> */ + {0x20, 0x21, "$"}, + /* <5> */ + {0x02, 0x22, "%"}, + /* <6> */ + {0x20, 0x23, "^"}, + /* <7> */ + {0x02, 0x24, "&"}, + /* <8> */ + {0x20, 0x25, "*"}, + /* <9> */ + {0x02, 0x26, "("}, + /* <0> */ + {0x20, 0x27, ")"}, + + /* */ + {0x00, 0x28, "\r"}, + /* */ + {0x00, 0x29, "\x1b"}, + /* */ + {0x00, 0x2a, "\x08"}, + /* */ + {0x00, 0x2b, "\x09"}, + /* */ + {0x00, 0x2c, " "}, + /* */ + {0x00, 0x2d, "-"}, + /* */ + {0x00, 0x2e, "="}, + /* */ + {0x00, 0x2f, "["}, + /* */ + {0x00, 0x30, "]"}, + /* */ + {0x00, 0x31, "\\"}, + /* */ + {0x00, 0x32, "#"}, + /* */ + {0x00, 0x33, ";"}, + /* */ + {0x00, 0x34, "'"}, + /* */ + {0x00, 0x35, "`"}, + /* */ + {0x00, 0x36, ","}, + /* */ + {0x00, 0x37, "."}, + /* */ + {0x00, 0x38, "/"}, + + /* */ + {0x02, 0x28, "\r"}, + /* */ + {0x20, 0x29, "\x1b"}, + /* */ + {0x02, 0x2a, "\x08"}, + /* */ + {0x20, 0x2b, "\x09"}, + /* */ + {0x02, 0x2c, " "}, + /* */ + {0x20, 0x2d, "_"}, + /* */ + {0x02, 0x2e, "+"}, + /* */ + {0x20, 0x2f, "{"}, + /* */ + {0x02, 0x30, "}"}, + /* */ + {0x20, 0x31, "|"}, + /* */ + {0x02, 0x32, "~"}, + /* */ + {0x20, 0x33, ":"}, + /* */ + {0x02, 0x34, "\""}, + /* */ + {0x20, 0x35, "~"}, + /* */ + {0x02, 0x36, "<"}, + /* */ + {0x20, 0x37, ">"}, + /* */ + {0x02, 0x38, "?"}, +#ifdef CONFIG_USB_KEYBOARD_FN_KEYS + /* */ + {0x00, 0x3a, "\x1bOP"}, + /* */ + {0x00, 0x3b, "\x1bOQ"}, + /* */ + {0x00, 0x3c, "\x1bOR"}, + /* */ + {0x00, 0x3d, "\x1bOS"}, + /* */ + {0x00, 0x3e, "\x1b[15~"}, + /* */ + {0x00, 0x3f, "\x1b[17~"}, + /* */ + {0x00, 0x40, "\x1b[18~"}, + /* */ + {0x00, 0x41, "\x1b[19~"}, + /* */ + {0x00, 0x42, "\x1b[20~"}, + /* */ + {0x00, 0x43, "\x1b[21~"}, + /* */ + {0x00, 0x44, "\x1b[23~"}, + /* */ + {0x00, 0x45, "\x1b[24~"}, + /* */ + {0x00, 0x49, "\x1b[2~"}, + /* */ + {0x00, 0x4a, "\x1b[H"}, + /* */ + {0x00, 0x4b, "\x1b[5~"}, + /* */ + {0x00, 0x4c, "\x1b[3~"}, + /* */ + {0x00, 0x4d, "\x1b[F"}, + /* */ + {0x00, 0x4e, "\x1b[6~"}, + /* */ + {0x00, 0x4f, "\x1b[C"}, + /* */ + {0x00, 0x50, "\x1b[D"}, + /* */ + {0x00, 0x51, "\x1b[B"}, + /* */ + {0x00, 0x52, "\x1b[A"}, +#endif /* CONFIG_USB_KEYBOARD_FN_KEYS */ + + /* End of list */ + {0x00, 0x00, "\0"} + }; + state_set_skip_delays(true); ut_assertok(usb_init()); @@ -129,16 +389,24 @@ static int dm_test_usb_keyb(struct unit_test_state *uts) &dev)); /* - * Add a string to the USB keyboard buffer - it should appear in - * stdin + * Add scan codes to the USB keyboard buffer. They should appear as + * corresponding characters and escape sequences in stdin. */ - ut_assertok(sandbox_usb_keyb_add_string(dev, "ab")); - ut_asserteq(1, tstc()); - ut_asserteq('a', getc()); - ut_asserteq(1, tstc()); - ut_asserteq('b', getc()); - ut_asserteq(0, tstc()); + for (pos = kbd_test_data; pos->scancode; ++pos) { + const char *c; + char scancodes[USB_KBD_BOOT_REPORT_SIZE] = {0}; + scancodes[0] = pos->modifiers; + scancodes[2] = pos->scancode; + + ut_assertok(sandbox_usb_keyb_add_string(dev, scancodes)); + + for (c = pos->result; *c; ++c) { + ut_asserteq(1, tstc()); + ut_asserteq(*c, getc()); + } + ut_asserteq(0, tstc()); + } ut_assertok(usb_stop()); return 0;