diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index 90b68044e86..26ac25b8370 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -399,6 +399,35 @@ config HIDKBD_ENCODED keys, etc. If this not defined, only 7-bit print-able and control ASCII characters will be provided to the user. +config HIDKBD_KBDUPPER + bool "Register as a keyboard upper-half device" + default n + depends on !HIDKBD_RAWSCANCODES + select INPUT + select INPUT_KEYBOARD + ---help--- + Register /dev/kbd[n] through the keyboard upper-half driver rather + than as a raw character device. Applications then read + struct keyboard_event_s samples instead of a byte stream, which + reports key release events in addition to key press events, and + reports modifiers (Ctrl, Shift, Alt, GUI) as keys of their own. + + This is what an application needs when it must know how long a key + is held down, such as a game. Note that the byte stream interface + is not available at the same time, so this breaks applications that + expect to read characters from /dev/kbd[n]. + +if HIDKBD_KBDUPPER + +config HIDKBD_KBDUPPER_BUFNUMBER + int "Keyboard upper-half buffer size" + default 8 + ---help--- + The number of keyboard events buffered by the upper-half driver + for each opened instance. + +endif # HIDKBD_KBDUPPER + config HIDKBD_ALLSCANCODES bool "Use All Scancodes" default n diff --git a/drivers/usbhost/usbhost_hidkbd.c b/drivers/usbhost/usbhost_hidkbd.c index a52625257b5..c82b411814e 100644 --- a/drivers/usbhost/usbhost_hidkbd.c +++ b/drivers/usbhost/usbhost_hidkbd.c @@ -56,11 +56,16 @@ #include #include -#ifdef CONFIG_HIDKBD_ENCODED +#if defined(CONFIG_HIDKBD_ENCODED) || defined(CONFIG_HIDKBD_KBDUPPER) # include # include #endif +#ifdef CONFIG_HIDKBD_KBDUPPER +# include +# include +#endif + /* Don't compile if prerequisites are not met */ #if defined(CONFIG_USBHOST) && !defined(CONFIG_USBHOST_INT_DISABLE) @@ -133,6 +138,19 @@ #ifdef CONFIG_HIDKBD_RAWSCANCODES # undef CONFIG_HIDKBD_ENCODED +# undef CONFIG_HIDKBD_KBDUPPER +#endif + +/* The table that maps scancodes to special key encodings is shared by the + * encoded byte stream and by the keyboard upper-half interface. + */ + +#if defined(CONFIG_HIDKBD_ENCODED) || defined(CONFIG_HIDKBD_KBDUPPER) +# define HAVE_KBD_ENCODING 1 +#endif + +#ifndef CONFIG_HIDKBD_KBDUPPER_BUFNUMBER +# define CONFIG_HIDKBD_KBDUPPER_BUFNUMBER 8 #endif /* Driver support ***********************************************************/ @@ -238,8 +256,12 @@ struct usbhost_state_s struct work_s rwork; /* For interrupt transfer work */ int16_t nbytes; /* # of bytes actually transferred */ #endif -#ifndef CONFIG_HIDKBD_NODEBOUNCE - uint8_t lastkey[6]; /* For debouncing */ +#if !defined(CONFIG_HIDKBD_NODEBOUNCE) || defined(CONFIG_HIDKBD_KBDUPPER) + uint8_t lastkey[6]; /* Keys down in the previous report */ +#endif +#ifdef CONFIG_HIDKBD_KBDUPPER + struct keyboard_lowerhalf_s lower; /* Keyboard upper-half interface */ + uint8_t lastmod; /* Modifiers held in previous report */ #endif }; @@ -272,8 +294,10 @@ static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, /* Keyboard polling thread */ static void usbhost_destroy(FAR void *arg); +#ifndef CONFIG_HIDKBD_KBDUPPER static void usbhost_putbuffer(FAR struct usbhost_state_s *priv, uint8_t keycode); +#endif #ifdef CONFIG_HIDKBD_ENCODED static void usbhost_putstream(FAR struct lib_outstream_s *self, int ch); #endif @@ -335,8 +359,17 @@ static int usbhost_connect(FAR struct usbhost_class_s *usbclass, FAR const uint8_t *configdesc, int desclen); static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass); -/* Driver methods. We export the keyboard as a standard character driver */ +/* Driver methods. We export the keyboard either as a standard character + * driver or through the keyboard upper-half driver. + */ +static int usbhost_open_priv(FAR struct usbhost_state_s *priv); +static int usbhost_close_priv(FAR struct usbhost_state_s *priv); + +#ifdef CONFIG_HIDKBD_KBDUPPER +static int usbhost_kbdupper_open(FAR struct keyboard_lowerhalf_s *lower); +static int usbhost_kbdupper_close(FAR struct keyboard_lowerhalf_s *lower); +#else static int usbhost_open(FAR struct file *filep); static int usbhost_close(FAR struct file *filep); static ssize_t usbhost_read(FAR struct file *filep, @@ -345,6 +378,7 @@ static ssize_t usbhost_write(FAR struct file *filep, FAR const char *buffer, size_t len); static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); +#endif /**************************************************************************** * Private Data @@ -374,6 +408,7 @@ static struct usbhost_registry_s g_hidkbd = &g_hidkbd_id /* id[] */ }; +#ifndef CONFIG_HIDKBD_KBDUPPER static const struct file_operations g_hidkbd_fops = { usbhost_open, /* open */ @@ -386,6 +421,24 @@ static const struct file_operations g_hidkbd_fops = NULL, /* truncate */ usbhost_poll /* poll */ }; +#endif + +#ifdef CONFIG_HIDKBD_KBDUPPER + +/* Maps the bits of the HID report modifier byte onto keycodes, so that a + * modifier can be reported as a key in its own right. Indexed by bit + * number: the modifier byte is defined by the HID specification to hold + * LCtrl, LShift, LAlt, LGUI, RCtrl, RShift, RAlt and RGUI, in that order. + */ + +#define USBHID_NMODIFIERS 8 + +static const uint8_t g_modkeycode[USBHID_NMODIFIERS] = +{ + KEYCODE_LCTRL, KEYCODE_LSHIFT, KEYCODE_LALT, KEYCODE_LGUI, + KEYCODE_RCTRL, KEYCODE_RSHIFT, KEYCODE_RALT, KEYCODE_RGUI +}; +#endif /* This is a bitmap that is used to allocate device names /dev/kbda-z. */ @@ -403,7 +456,7 @@ static bool g_caps_lock = false; */ #ifndef CONFIG_HIDKBD_RAWSCANCODES -#ifdef CONFIG_HIDKBD_ENCODED +#ifdef HAVE_KBD_ENCODING /* The first and last scancode values with encode-able values */ @@ -800,7 +853,11 @@ static void usbhost_destroy(FAR void *arg) uinfo("Unregister driver\n"); usbhost_mkdevname(priv, devname); +#ifdef CONFIG_HIDKBD_KBDUPPER + keyboard_unregister(&priv->lower, devname); +#else unregister_driver(devname); +#endif /* Release the device name used by this connection */ @@ -860,6 +917,7 @@ static void usbhost_destroy(FAR void *arg) * ****************************************************************************/ +#ifndef CONFIG_HIDKBD_KBDUPPER static void usbhost_putbuffer(FAR struct usbhost_state_s *priv, uint8_t keycode) { @@ -899,6 +957,7 @@ static void usbhost_putbuffer(FAR struct usbhost_state_s *priv, priv->headndx = head; } +#endif /* CONFIG_HIDKBD_KBDUPPER */ /**************************************************************************** * Name: usbhost_putstream @@ -1076,6 +1135,161 @@ static inline void usbhost_toggle_capslock(void) spin_unlock_irqrestore(&g_lock, flags); } +/**************************************************************************** + * Name: usbhost_kbdupper_keycode + * + * Description: + * Map a HID scancode onto the keycode space used by the keyboard + * upper-half driver: 7-bit ASCII for printable keys, and the values of + * enum kbd_keycode_e for everything else. + * + * Input Parameters: + * scancode - Scan code to be mapped. + * modifier - Ctrl, Alt, Shift, GUI modifier bits + * + * Returned Value: + * The keycode, or zero if the scancode has no representation. + * + ****************************************************************************/ + +#ifdef CONFIG_HIDKBD_KBDUPPER +static uint32_t usbhost_kbdupper_keycode(uint8_t scancode, uint8_t modifier) +{ + /* Check for a special key first. Otherwise a key such as ENTER would be + * reported as the control character that it also maps to. + */ + + if (scancode >= FIRST_ENCODING && scancode <= LAST_ENCODING) + { + uint8_t encoded = encoding[scancode - FIRST_ENCODING]; + + if (encoded != 0) + { + return encoded; + } + } + + /* Otherwise fall back to the printable ASCII mapping. Note that Ctrl is + * deliberately not folded into a control character here: modifiers are + * reported as keys of their own, so the application still knows that Ctrl + * is down and can see which key it is being held with. + */ + + return usbhost_mapscancode(scancode, modifier); +} + +/**************************************************************************** + * Name: usbhost_kbdupper_report + * + * Description: + * Turn a HID keyboard report into key press and key release events and + * hand them to the keyboard upper-half driver. + * + * The HID specification gives no significance to the order of the + * keycodes in the report array, so the only way to know what changed is + * to compare the report against the previous one. + * + * Input Parameters: + * priv - Driver internal state + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_kbdupper_report(FAR struct usbhost_state_s *priv) +{ + FAR struct usbhid_kbdreport_s *rpt = + (FAR struct usbhid_kbdreport_s *)priv->tbuffer; + uint32_t keycode; + uint8_t changed; + int i; + int j; + + /* Report the modifiers whose state changed since the last report */ + + changed = rpt->modifier ^ priv->lastmod; + for (i = 0; changed != 0 && i < USBHID_NMODIFIERS; i++) + { + if ((changed & (1 << i)) != 0) + { + keyboard_event(&priv->lower, g_modkeycode[i], + (rpt->modifier & (1 << i)) != 0 ? + KEYBOARD_PRESS : KEYBOARD_RELEASE); + } + } + + /* Report the keys that were released: down in the last report, but not + * in this one. + */ + + for (i = 0; i < 6; i++) + { + if (priv->lastkey[i] == USBHID_KBDUSE_NONE) + { + continue; + } + + for (j = 0; j < 6; j++) + { + if (rpt->key[j] == priv->lastkey[i]) + { + break; + } + } + + if (j >= 6) + { + keycode = usbhost_kbdupper_keycode(priv->lastkey[i], + rpt->modifier); + if (keycode != 0) + { + keyboard_event(&priv->lower, keycode, KEYBOARD_RELEASE); + } + } + } + + /* Report the keys that were pressed: down in this report, but not in the + * last one. + */ + + for (i = 0; i < 6; i++) + { + if (rpt->key[i] == USBHID_KBDUSE_NONE) + { + continue; + } + + for (j = 0; j < 6; j++) + { + if (priv->lastkey[j] == rpt->key[i]) + { + break; + } + } + + if (j >= 6) + { + if (rpt->key[i] == USBHID_KBDUSE_CAPSLOCK) + { + usbhost_toggle_capslock(); + } + + keycode = usbhost_kbdupper_keycode(rpt->key[i], rpt->modifier); + if (keycode != 0) + { + keyboard_event(&priv->lower, keycode, KEYBOARD_PRESS); + } + } + } + + /* Remember this report so that the next one can be compared against it */ + + memcpy(priv->lastkey, rpt->key, sizeof(priv->lastkey)); + priv->lastmod = rpt->modifier; +} +#endif /* CONFIG_HIDKBD_KBDUPPER */ + /**************************************************************************** * Name: usbhost_extract_keys * @@ -1095,6 +1309,14 @@ static inline void usbhost_toggle_capslock(void) static int usbhost_extract_keys(FAR struct usbhost_state_s *priv) { +#ifdef CONFIG_HIDKBD_KBDUPPER + /* The upper-half driver does its own buffering and poll notification, so + * there is nothing else to do here. + */ + + usbhost_kbdupper_report(priv); + return OK; +#else struct usbhid_kbdreport_s *rpt = (struct usbhid_kbdreport_s *)priv->tbuffer; uint8_t keycode; @@ -1234,6 +1456,7 @@ static int usbhost_extract_keys(FAR struct usbhost_state_s *priv) nxmutex_unlock(&priv->lock); return 0; +#endif /* CONFIG_HIDKBD_KBDUPPER */ } /**************************************************************************** @@ -1991,7 +2214,20 @@ static inline int usbhost_devinit(FAR struct usbhost_state_s *priv) uinfo("Register driver\n"); usbhost_mkdevname(priv, devname); +#ifdef CONFIG_HIDKBD_KBDUPPER + /* Do not set lower.priv here: keyboard_register() claims that field for + * the upper half. We recover our own state with container_of(). + */ + + priv->lower.open = usbhost_kbdupper_open; + priv->lower.close = usbhost_kbdupper_close; + priv->lower.write = NULL; + + ret = keyboard_register(&priv->lower, devname, + CONFIG_HIDKBD_KBDUPPER_BUFNUMBER); +#else ret = register_driver(devname, &g_hidkbd_fops, 0600, priv); +#endif /* We now have to be concerned about asynchronous modification of crefs * because the driver has been registered. @@ -2468,23 +2704,21 @@ static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass) } /**************************************************************************** - * Name: usbhost_open + * Name: usbhost_open_priv * * Description: - * Standard character driver open method. + * Take a reference on the driver instance. This is the common part of + * opening the device, shared by the character driver and the keyboard + * upper-half interfaces. * ****************************************************************************/ -static int usbhost_open(FAR struct file *filep) +static int usbhost_open_priv(FAR struct usbhost_state_s *priv) { - FAR struct inode *inode; - FAR struct usbhost_state_s *priv; irqstate_t flags; int ret; uinfo("Entry\n"); - inode = filep->f_inode; - priv = inode->i_private; /* Make sure that we have exclusive access to the private data structure */ @@ -2526,23 +2760,22 @@ static int usbhost_open(FAR struct file *filep) } /**************************************************************************** - * Name: usbhost_close + * Name: usbhost_close_priv * * Description: - * Standard character driver close method. + * Drop a reference on the driver instance, destroying it if this was the + * last reference to a disconnected device. This is the common part of + * closing the device, shared by the character driver and the keyboard + * upper-half interfaces. * ****************************************************************************/ -static int usbhost_close(FAR struct file *filep) +static int usbhost_close_priv(FAR struct usbhost_state_s *priv) { - FAR struct inode *inode; - FAR struct usbhost_state_s *priv; irqstate_t flags; int ret; uinfo("Entry\n"); - inode = filep->f_inode; - priv = inode->i_private; /* Decrement the reference count on the driver */ @@ -2621,6 +2854,68 @@ static int usbhost_close(FAR struct file *filep) return OK; } +#ifdef CONFIG_HIDKBD_KBDUPPER + +/**************************************************************************** + * Name: usbhost_kbdupper_open + * + * Description: + * Keyboard upper-half open method. + * + ****************************************************************************/ + +static int usbhost_kbdupper_open(FAR struct keyboard_lowerhalf_s *lower) +{ + /* Note that lower->priv cannot be used to find our state: it belongs to + * the upper half, which overwrites it in keyboard_register(). + */ + + return usbhost_open_priv(container_of(lower, struct usbhost_state_s, + lower)); +} + +/**************************************************************************** + * Name: usbhost_kbdupper_close + * + * Description: + * Keyboard upper-half close method. + * + ****************************************************************************/ + +static int usbhost_kbdupper_close(FAR struct keyboard_lowerhalf_s *lower) +{ + return usbhost_close_priv(container_of(lower, struct usbhost_state_s, + lower)); +} + +#else /* CONFIG_HIDKBD_KBDUPPER */ + +/**************************************************************************** + * Name: usbhost_open + * + * Description: + * Standard character driver open method. + * + ****************************************************************************/ + +static int usbhost_open(FAR struct file *filep) +{ + return usbhost_open_priv(filep->f_inode->i_private); +} + +/**************************************************************************** + * Name: usbhost_close + * + * Description: + * Standard character driver close method. + * + ****************************************************************************/ + +static int usbhost_close(FAR struct file *filep) +{ + return usbhost_close_priv(filep->f_inode->i_private); +} + /**************************************************************************** * Name: usbhost_read * @@ -2851,6 +3146,8 @@ errout: return ret; } +#endif /* CONFIG_HIDKBD_KBDUPPER */ + /**************************************************************************** * Public Functions ****************************************************************************/