This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit f0a870baab6bf3da0696b4d908276e661dc9ea8d
Author: Jorge Guzman <[email protected]>
AuthorDate: Thu Jul 30 15:08:32 2026 -0300

    drivers/usbhost: report the HID keyboard through the keyboard upper half
    
    The driver kept a character device, a ring buffer, a poll waiter list
    and an encoder of its own, in parallel with everything the keyboard
    upper half already provides.  A USB keyboard was therefore the one
    keyboard an application could not read like any other.
    
    Register with keyboard_register() and report with keyboard_event(),
    which removes the private character device and the four hundred lines
    that served it.  Special keys are reported with the SPEC event types
    carrying a keycode, so an application no longer has to guess whether a
    value in the character range is a character or an arrow key.
    
    HIDKBD_ENCODED and HIDKBD_NODEBOUNCE go away with the code they guarded.
    Encoding is now inherent to the event, and the previous report is no
    longer an optimisation:  a HID keyboard reports the keys that are down
    rather than the transitions, so it is what tells a new press from a key
    still held, and what tells that a key has been released.
    
    Reporting the modifiers as keys is new, so it is behind
    HIDKBD_REPORT_MODIFIERS and off by default.
    
    Signed-off-by: Jorge Guzman <[email protected]>
---
 drivers/usbhost/Kconfig          |  46 +--
 drivers/usbhost/usbhost_hidkbd.c | 807 ++++++++++++---------------------------
 2 files changed, 260 insertions(+), 593 deletions(-)

diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig
index 90b68044e86..4d98e382bb6 100644
--- a/drivers/usbhost/Kconfig
+++ b/drivers/usbhost/Kconfig
@@ -344,9 +344,16 @@ config USBHOST_HIDKBD
        bool "HID Keyboard Class Support"
        default n
        depends on !INT_DISABLE
+       select INPUT
+       select INPUT_KEYBOARD
        ---help---
                Enable support for the keyboard class driver.
 
+               The keyboard is reported through the keyboard upper half, so it
+               delivers struct keyboard_event_s events on read() just like any
+               other keyboard.  Set INPUT_KEYBOARD_BYTESTREAM if the 
application
+               expects the plain byte stream that this driver used to provide.
+
 if USBHOST_HIDKBD
 
 config HIDKBD_POLLUSEC
@@ -368,17 +375,11 @@ config HIDKBD_STACKSIZE
                Stack size for polling thread.  Default: 1024
 
 config HIDKBD_BUFSIZE
-       int "Scancode Buffer Size"
+       int "Event Buffer Size"
        default 64
        ---help---
-               Scancode buffer size.  Default: 64.
-
-config HIDKBD_NPOLLWAITERS
-       int "Max Number of Waiters for Poll Event"
-       default 2
-       ---help---
-               If the poll() method is enabled, this defines the maximum number
-               of threads that can be waiting for keyboard events.  Default: 2.
+               Number of keyboard events buffered by the upper half for each
+               open file description.  Default: 64.
 
 config HIDKBD_RAWSCANCODES
        bool "Use Raw Scan Codes"
@@ -387,18 +388,6 @@ config HIDKBD_RAWSCANCODES
                If set to y no conversions will be made on the raw keyboard scan
                codes.  This option is useful during testing. Default: ASCII 
conversion.
 
-config HIDKBD_ENCODED
-       bool "Encode Special Keys"
-       default n
-       depends on !HIDKBD_RAWSCANCODES && LIBC_KBDCODEC
-       ---help---
-               Encode special key press events in the user buffer.  In this 
case,
-               the user end must decode the encoded special key values using 
the
-               interfaces defined in include/nuttx/input/kbd_codec.h.  These
-               special keys include such things as up/down arrows, home and end
-               keys, etc.  If this not defined, only 7-bit print-able and 
control
-               ASCII characters will be provided to the user.
-
 config HIDKBD_ALLSCANCODES
        bool "Use All Scancodes"
        default n
@@ -406,12 +395,19 @@ config HIDKBD_ALLSCANCODES
                If set to y all 231 possible scancodes will be converted to
                something.  Default:  104 key US keyboard.
 
-config HIDKBD_NODEBOUNCE
-       bool "Disable Debounce"
+config HIDKBD_REPORT_MODIFIERS
+       bool "Report modifier keys"
        default n
+       depends on !HIDKBD_RAWSCANCODES
        ---help---
-               If set to y normal debouncing is disabled.  Default:
-               Debounce enabled (No repeat keys).
+               Report Ctrl, Shift, Alt and GUI as keys in their own right, 
using
+               the KEYCODE_LCTRL..KEYCODE_RGUI keycodes, in addition to 
applying
+               them to the character that they modify.  This lets an 
application
+               bind an action to a modifier or know that one is being held 
down,
+               which a game needs and a terminal does not.
+
+               Presses are reported before the keys that they modify and
+               releases after.
 
 config HIDKBD_NOGETREPORT
        bool "Use Interrupt pipe to get keys"
diff --git a/drivers/usbhost/usbhost_hidkbd.c b/drivers/usbhost/usbhost_hidkbd.c
index a52625257b5..67c8778e64d 100644
--- a/drivers/usbhost/usbhost_hidkbd.c
+++ b/drivers/usbhost/usbhost_hidkbd.c
@@ -42,6 +42,7 @@
 
 #include <nuttx/debug.h>
 #include <nuttx/irq.h>
+#include <nuttx/nuttx.h>
 #include <nuttx/kmalloc.h>
 #include <nuttx/kthread.h>
 #include <nuttx/arch.h>
@@ -56,10 +57,8 @@
 #include <nuttx/usb/hid.h>
 #include <nuttx/usb/usbhost_devaddr.h>
 
-#ifdef CONFIG_HIDKBD_ENCODED
-#  include <nuttx/streams.h>
-#  include <nuttx/input/kbd_codec.h>
-#endif
+#include <nuttx/input/kbd_codec.h>
+#include <nuttx/input/keyboard.h>
 
 /* Don't compile if prerequisites are not met */
 
@@ -101,10 +100,6 @@
 #  define CONFIG_HIDKBD_BUFSIZE 64
 #endif
 
-#ifndef CONFIG_HIDKBD_NPOLLWAITERS
-#  define CONFIG_HIDKBD_NPOLLWAITERS 2
-#endif
-
 /* The default is to support scancode mapping for the standard 104 key
  * keyboard.  Setting CONFIG_HIDKBD_RAWSCANCODES will disable all scancode
  * mapping; Setting CONFIG_HIDKBD_ALLSCANCODES will enable mapping of all
@@ -119,20 +114,14 @@
 #  endif
 #endif
 
-/* We can't support encoding of special characters of unless the Keyboard
- * CODEC is enabled.
+/* Special keys are reported as KEYBOARD_SPECPRESS/KEYBOARD_SPECREL events
+ * carrying a value from enum kbd_keycode_e.  That translation is driven by
+ * a scancode table, so it is not available when raw scancodes are being
+ * passed through untranslated.
  */
 
-#ifndef CONFIG_LIBC_KBDCODEC
-#  undef CONFIG_HIDKBD_ENCODED
-#endif
-
-/* If we are using raw scancodes, then we cannot support encoding of
- * special characters either.
- */
-
-#ifdef CONFIG_HIDKBD_RAWSCANCODES
-#  undef CONFIG_HIDKBD_ENCODED
+#ifndef CONFIG_HIDKBD_RAWSCANCODES
+#  define HAVE_SPECIAL_KEYS 1
 #endif
 
 /* Driver support ***********************************************************/
@@ -182,18 +171,23 @@ struct usbhost_state_s
 
   struct usbhost_class_s  usbclass;
 
+  /* The keyboard is reported through the keyboard upper half, which owns
+   * the character device and the buffering.  This must not be moved: the
+   * lower half callbacks recover this structure with container_of().
+   */
+
+  struct keyboard_lowerhalf_s lower;
+
   /* The remainder of the fields are provided to the keyboard class driver */
 
   char                    devchar;      /* Character identifying the 
/dev/kbd[n] device */
   volatile bool           disconnected; /* TRUE: Device has been disconnected 
*/
   volatile bool           polling;      /* TRUE: Poll thread is running */
   volatile bool           open;         /* TRUE: The keyboard device is open */
-  volatile bool           waiting;      /* TRUE: waiting for keyboard data */
   uint8_t                 ifno;         /* Interface number */
   int16_t                 crefs;        /* Reference count on the driver 
instance */
   spinlock_t              spinlock;     /* Used to protect critical section */
   mutex_t                 lock;         /* Used to maintain mutual exclusive 
access */
-  sem_t                   waitsem;      /* Used to wait for keyboard data */
   FAR uint8_t            *tbuffer;      /* The allocated transfer buffer */
   size_t                  tbuflen;      /* Size of the allocated transfer 
buffer */
   pid_t                   pollpid;      /* PID of the poll task */
@@ -214,44 +208,26 @@ struct usbhost_state_s
   usbhost_ep_t            epin;         /* Interrupt IN endpoint */
   usbhost_ep_t            epout;        /* Optional interrupt OUT endpoint */
 
-  /* The following is a list if poll structures of threads waiting for
-   * driver events. The 'struct pollfd' reference for each open is also
-   * retained in the f_priv field of the 'struct file'.
-   */
-
-  FAR struct pollfd      *fds[CONFIG_HIDKBD_NPOLLWAITERS];
-
-  /* Buffer used to collect and buffer incoming keyboard characters */
-
-  volatile uint16_t       headndx;      /* Buffer head index */
-  volatile uint16_t       tailndx;      /* Buffer tail index */
-  uint8_t                 kbdbuffer[CONFIG_HIDKBD_BUFSIZE];
-
   FAR uint8_t            *ctrlreq;      /* Allocated ctrl request structure */
   size_t                  ctrllen;      /* Size of the allocated control 
buffer */
   bool                    caps_lock;    /* Private caps lock status */
   bool                    sync_led;     /* For LED update */
-  bool                    empty;        /* Keep track of data availability */
   sem_t                   syncsem;      /* Semaphore to wait for a poll thread 
*/
 
 #ifdef CONFIG_HIDKBD_NOGETREPORT
   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 */
-#endif
-};
 
-/* This type is used for encoding special characters */
+  /* The previous report.  A HID keyboard reports the set of keys that are
+   * currently down, not the transitions, so the previous report is what
+   * tells a newly pressed key from one that is still being held, and what
+   * tells that a key that is gone from the report has been released.
+   */
 
-#ifdef CONFIG_HIDKBD_ENCODED
-struct usbhost_outstream_s
-{
-  struct lib_outstream_s stream;
-  FAR struct usbhost_state_s *priv;
+  uint8_t                 lastkey[6];   /* Scancodes in the last report */
+  uint8_t                 lastmod;      /* Modifiers in the last report */
 };
-#endif
 
 /****************************************************************************
  * Private Function Prototypes
@@ -272,17 +248,10 @@ static inline void usbhost_mkdevname(FAR struct 
usbhost_state_s *priv,
 /* Keyboard polling thread */
 
 static void usbhost_destroy(FAR void *arg);
-static void usbhost_putbuffer(FAR struct usbhost_state_s *priv,
-                              uint8_t keycode);
-#ifdef CONFIG_HIDKBD_ENCODED
-static void usbhost_putstream(FAR struct lib_outstream_s *self, int ch);
-#endif
 static inline uint8_t usbhost_mapscancode(uint8_t scancode,
                                           uint8_t modifier);
-#ifdef CONFIG_HIDKBD_ENCODED
-static inline void usbhost_encodescancode(FAR struct usbhost_state_s *priv,
-                                          uint8_t scancode,
-                                          uint8_t modifier);
+#ifdef HAVE_SPECIAL_KEYS
+static inline uint8_t usbhost_mapspeckey(uint8_t scancode);
 #endif
 static int  usbhost_kbdpoll(int argc, FAR char *argv[]);
 
@@ -335,16 +304,13 @@ 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 */
+/* Keyboard lower half methods.  The upper half owns the character device,
+ * so all this driver has to do is to start and stop the polling of the
+ * device as the character device is opened and closed.
+ */
 
-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,
-                            FAR char *buffer, size_t len);
-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);
+static int usbhost_kbd_open(FAR struct keyboard_lowerhalf_s *lower);
+static int usbhost_kbd_close(FAR struct keyboard_lowerhalf_s *lower);
 
 /****************************************************************************
  * Private Data
@@ -374,19 +340,6 @@ static struct usbhost_registry_s g_hidkbd =
   &g_hidkbd_id              /* id[] */
 };
 
-static const struct file_operations g_hidkbd_fops =
-{
-  usbhost_open,             /* open */
-  usbhost_close,            /* close */
-  usbhost_read,             /* read */
-  usbhost_write,            /* write */
-  NULL,                     /* seek */
-  NULL,                     /* ioctl */
-  NULL,                     /* mmap */
-  NULL,                     /* truncate */
-  usbhost_poll              /* poll */
-};
-
 /* This is a bitmap that is used to allocate device names /dev/kbda-z. */
 
 static uint32_t g_devinuse;
@@ -403,7 +356,6 @@ static bool g_caps_lock = false;
  */
 
 #ifndef CONFIG_HIDKBD_RAWSCANCODES
-#ifdef CONFIG_HIDKBD_ENCODED
 
 /* The first and last scancode values with encode-able values */
 
@@ -588,8 +540,6 @@ static const uint8_t encoding[USBHID_NUMENCODINGS] =
 #endif
 };
 
-#endif
-
 static const uint8_t ucmap[USBHID_NUMSCANCODES] =
 {
   0,    0,      0,      0,       'A',  'B',  'C',    'D',  /* 0x00-0x07: 
Reserved, errors, A-D */
@@ -661,6 +611,24 @@ static const uint8_t lcmap[USBHID_NUMSCANCODES] =
 };
 #endif /* CONFIG_HIDKBD_RAWSCANCODES */
 
+#ifdef CONFIG_HIDKBD_REPORT_MODIFIERS
+
+/* Map the bits of the HID report modifier byte to keycodes, so that a
+ * modifier can be reported as a key in its own right.  The order follows
+ * the bit order of the report, not the order of enum kbd_keycode_e.
+ */
+
+#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
+
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
@@ -800,7 +768,7 @@ static void usbhost_destroy(FAR void *arg)
 
   uinfo("Unregister driver\n");
   usbhost_mkdevname(priv, devname);
-  unregister_driver(devname);
+  keyboard_unregister(&priv->lower, devname);
 
   /* Release the device name used by this connection */
 
@@ -829,7 +797,6 @@ static void usbhost_destroy(FAR void *arg)
   /* Destroy the mutex & semaphores */
 
   nxmutex_destroy(&priv->lock);
-  nxsem_destroy(&priv->waitsem);
 
   /* Disconnect the USB host device */
 
@@ -845,89 +812,6 @@ static void usbhost_destroy(FAR void *arg)
   usbhost_freeclass(priv);
 }
 
-/****************************************************************************
- * Name: usbhost_putbuffer
- *
- * Description:
- *   Add one character to the user buffer.
- *
- * Input Parameters:
- *   priv - Driver internal state
- *   keycode - The value to add to the user buffer
- *
- * Returned Value:
- *   None
- *
- ****************************************************************************/
-
-static void usbhost_putbuffer(FAR struct usbhost_state_s *priv,
-                              uint8_t keycode)
-{
-  register unsigned int head;
-  register unsigned int tail;
-
-  /* Copy the next keyboard character into the user buffer. */
-
-  head = priv->headndx;
-  priv->kbdbuffer[head] = keycode;
-
-  /* Increment the head index */
-
-  if (++head >= CONFIG_HIDKBD_BUFSIZE)
-    {
-      head = 0;
-    }
-
-  /* If the buffer is full, then increment the tail index to make space.  Is
-   * it better to lose old keystrokes or new?
-   */
-
-  tail = priv->tailndx;
-  if (tail == head)
-    {
-      if (++tail >= CONFIG_HIDKBD_BUFSIZE)
-        {
-          tail = 0;
-        }
-
-      /* Save the updated tail index */
-
-      priv->tailndx = tail;
-    }
-
-  /* Save the updated head index */
-
-  priv->headndx = head;
-}
-
-/****************************************************************************
- * Name: usbhost_putstream
- *
- * Description:
- *   A wrapper for usbhost_putc that is compatible with the lib_outstream_s
- *   putc methods.
- *
- * Input Parameters:
- *   stream - The struct lib_outstream_s reference
- *   ch - The character to add to the user buffer
- *
- * Returned Value:
- *   None
- *
- ****************************************************************************/
-
-#ifdef CONFIG_HIDKBD_ENCODED
-static void usbhost_putstream(FAR struct lib_outstream_s *stream, int ch)
-{
-  FAR struct usbhost_outstream_s *privstream =
-    (FAR struct usbhost_outstream_s *)stream;
-
-  DEBUGASSERT(privstream && privstream->priv);
-  usbhost_putbuffer(privstream->priv, (uint8_t)ch);
-  stream->nput++;
-}
-#endif
-
 /****************************************************************************
  * Name: usbhost_mapscancode
  *
@@ -976,54 +860,30 @@ static inline uint8_t usbhost_mapscancode(uint8_t 
scancode, uint8_t modifier)
 }
 
 /****************************************************************************
- * Name: usbhost_encodescancode
+ * Name: usbhost_mapspeckey
  *
  * Description:
- *  Check if the key has a special function encoding and, if it does, add
- *  the encoded value to the user buffer.
+ *  Map a scancode to a special function keycode from enum kbd_keycode_e.
  *
  * Input Parameters:
- *   priv - Driver internal state
  *   scancode - Scan code to be mapped.
- *   modifier - Ctrl, Alt, Shift, GUI modifier bits
  *
  * Returned Value:
- *   None
+ *   The keycode, or zero if the scancode is not a special function key.
  *
  ****************************************************************************/
 
-#ifdef CONFIG_HIDKBD_ENCODED
-static inline void usbhost_encodescancode(FAR struct usbhost_state_s *priv,
-                                          uint8_t scancode, uint8_t modifier)
+#ifdef HAVE_SPECIAL_KEYS
+static inline uint8_t usbhost_mapspeckey(uint8_t scancode)
 {
-  uint8_t encoded;
-
   /* Check if the raw scancode is in a valid range */
 
-  if (scancode >= FIRST_ENCODING && scancode <= LAST_ENCODING)
+  if (scancode < FIRST_ENCODING || scancode > LAST_ENCODING)
     {
-      /* Yes the value is within range */
-
-      encoded = encoding[scancode - FIRST_ENCODING];
-      iinfo("  scancode: %02x modifier: %02x encoded: %d\n",
-            scancode, modifier, encoded);
-
-      if (encoded)
-        {
-          struct usbhost_outstream_s usbstream;
-
-          /* And it does correspond to a special function key */
-
-          usbstream.stream.putc = usbhost_putstream;
-          usbstream.stream.nput = 0;
-          usbstream.priv        = priv;
-
-          /* Add the special function value to the user buffer */
-
-          kbd_specpress((enum kbd_keycode_e)encoded,
-                        (FAR struct lib_outstream_s *)&usbstream);
-        }
+      return 0;
     }
+
+  return encoding[scancode - FIRST_ENCODING];
 }
 #endif
 
@@ -1077,162 +937,203 @@ static inline void usbhost_toggle_capslock(void)
 }
 
 /****************************************************************************
- * Name: usbhost_extract_keys
+ * Name: usbhost_report_key
  *
  * Description:
- *  Check if the key has a special function encoding and, if it does, add
- *  the encoded value to the user buffer.
+ *   Report one key transition to the keyboard upper half.
  *
  * Input Parameters:
  *   priv - Driver internal state
- *   scancode - Scan code to be mapped.
+ *   scancode - The scancode of the key that changed state
  *   modifier - Ctrl, Alt, Shift, GUI modifier bits
+ *   press - True if the key went down, false if it came up
  *
  * Returned Value:
  *   None
  *
  ****************************************************************************/
 
-static int usbhost_extract_keys(FAR struct usbhost_state_s *priv)
+static void usbhost_report_key(FAR struct usbhost_state_s *priv,
+                               uint8_t scancode, uint8_t modifier,
+                               bool press)
 {
-  struct usbhid_kbdreport_s *rpt =
-    (struct usbhid_kbdreport_s *)priv->tbuffer;
   uint8_t keycode;
-  int i;
-  int ret;
-  bool newstate;
 
-  /* Add the newly received keystrokes to our internal buffer */
+  /* Caps lock is a keyboard mode rather than a character, so it is applied
+   * here and never reported.  The state is toggled on the press only.
+   */
 
-  ret = nxmutex_lock(&priv->lock);
-  if (ret < 0)
+  if (scancode == USBHID_KBDUSE_CAPSLOCK)
     {
-      return ret;
+      if (press)
+        {
+          usbhost_toggle_capslock();
+        }
+
+      return;
     }
 
-  for (i = 0; i < 6; i++)
+  /* Does the scancode map to a printable character? */
+
+  keycode = usbhost_mapscancode(scancode, modifier);
+  if (keycode != 0)
     {
-      /* Is this key pressed?  But not pressed last time?
-       * HID spec: "The order of keycodes in array fields has no
-       * significance. Order determination is done by the host
-       * software comparing the contents of the previous report to
-       * the current report. If two or more keys are reported in
-       * one report, their order is indeterminate. Keyboards may
-       * buffer events that would have otherwise resulted in
-       * multiple event in a single report.
-       *
-       * "'Repeat Rate' and 'Delay Before First Repeat' are
-       * implemented by the host and not in the keyboard (this
-       * means the BIOS in legacy mode). The host may use the
-       * device report rate and the number of reports to determine
-       * how long a key is being held down. Alternatively, the host
-       * may use its own clock or the idle request for the timing
-       * of these features."
-       */
+      /* Yes.  Ctrl turns it into the matching control character. */
 
-      if (rpt->key[i] != USBHID_KBDUSE_NONE
-#ifndef CONFIG_HIDKBD_NODEBOUNCE
-        && rpt->key[i] != priv->lastkey[0]
-        && rpt->key[i] != priv->lastkey[1]
-        && rpt->key[i] != priv->lastkey[2]
-        && rpt->key[i] != priv->lastkey[3]
-        && rpt->key[i] != priv->lastkey[4]
-        && rpt->key[i] != priv->lastkey[5]
-#endif
-         )
+      if ((modifier & (USBHID_MODIFER_LCTRL |
+                       USBHID_MODIFER_RCTRL)) != 0)
         {
-          /* Yes.. Add it to the buffer. */
+          keycode &= 0x1f;
+        }
 
-          /* Map the keyboard scancode to a printable ASCII
-           * character.  There is no support here for function keys
-           * or cursor controls in this version of the driver.
-           */
+      iinfo("Key %02x: %c %s\n", scancode, keycode,
+            press ? "press" : "release");
 
-          keycode = usbhost_mapscancode(rpt->key[i], rpt->modifier);
-          iinfo("Key %d: %02x keycode:%c modifier: %02x\n",
-                 i, rpt->key[i], keycode ? keycode : ' ',
-                 rpt->modifier);
+      keyboard_event(&priv->lower, keycode,
+                     press ? KEYBOARD_PRESS : KEYBOARD_RELEASE);
+      return;
+    }
 
-          if (rpt->key[i] == 57)
-            {
-              usbhost_toggle_capslock();
-            }
+  /* No.  It might still be a special function key, which is reported with
+   * a keycode from enum kbd_keycode_e and one of the SPEC event types.
+   * The two ranges overlap, so the type is what tells them apart.
+   */
 
-          /* Zero at this point means that the key does not map to a
-           * printable character.
-           */
+#ifdef HAVE_SPECIAL_KEYS
+  keycode = usbhost_mapspeckey(scancode);
+  if (keycode != 0)
+    {
+      iinfo("Key %02x: keycode %d %s\n", scancode, keycode,
+            press ? "specpress" : "specrel");
 
-          if (keycode != 0)
-            {
-              /* Handle control characters.  Zero after this means
-               * a valid, NUL character.
-               */
+      keyboard_event(&priv->lower, keycode,
+                     press ? KEYBOARD_SPECPRESS : KEYBOARD_SPECREL);
+    }
+#endif
+}
 
-              if ((rpt->modifier & (USBHID_MODIFER_LCTRL |
-                                    USBHID_MODIFER_RCTRL)) != 0)
-                {
-                  keycode &= 0x1f;
-                }
+/****************************************************************************
+ * Name: usbhost_extract_keys
+ *
+ * Description:
+ *  Compare the report just received against the previous one and report
+ *  every key that changed state to the keyboard upper half.
+ *
+ * Input Parameters:
+ *   priv - Driver internal state
+ *
+ * Returned Value:
+ *   Zero on success, a negated errno value on failure.
+ *
+ ****************************************************************************/
 
-              /* Copy the next keyboard character into the user
-               * buffer.
-               */
+static int usbhost_extract_keys(FAR struct usbhost_state_s *priv)
+{
+  FAR struct usbhid_kbdreport_s *rpt =
+    (FAR struct usbhid_kbdreport_s *)priv->tbuffer;
+  int i;
+  int j;
+  int ret;
 
-              usbhost_putbuffer(priv, keycode);
-            }
+  ret = nxmutex_lock(&priv->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
 
-          /* The zero might, however, map to a special keyboard
-           * action (such as a cursor movement or function key).
-           * Attempt to encode the special key.
-           */
+  /* A HID keyboard reports the set of keys that are currently down, in no
+   * particular order, rather than the transitions.  So a key is newly
+   * pressed if it is in this report but was in no slot of the previous
+   * one, and it has been released if it was in the previous report but is
+   * in no slot of this one.
+   */
 
-#ifdef CONFIG_HIDKBD_ENCODED
-          else
-            {
-              usbhost_encodescancode(priv, rpt->key[i],
-                                     rpt->modifier);
-            }
+#ifdef CONFIG_HIDKBD_REPORT_MODIFIERS
+
+  /* Modifiers are a bitmap rather than slots, so they are diffed first and
+   * separately.  A modifier is reported as a key in its own right, which
+   * lets an application bind an action to it or know that it is being held
+   * down.  Presses are reported before the keys that they modify and
+   * releases after, so that the application sees a consistent state.
+   */
+
+  for (i = 0; i < USBHID_NMODIFIERS; i++)
+    {
+      uint8_t bit = 1 << i;
+
+      if ((rpt->modifier & bit) != 0 && (priv->lastmod & bit) == 0)
+        {
+          keyboard_event(&priv->lower, g_modkeycode[i],
+                         KEYBOARD_SPECPRESS);
+        }
+    }
 #endif
+
+  /* Report the keys that came up */
+
+  for (i = 0; i < 6; i++)
+    {
+      if (priv->lastkey[i] == USBHID_KBDUSE_NONE)
+        {
+          continue;
         }
 
-      /* Save the scancode (or lack thereof) for key debouncing on
-       * next keyboard report.
-       */
+      for (j = 0; j < 6; j++)
+        {
+          if (rpt->key[j] == priv->lastkey[i])
+            {
+              break;
+            }
+        }
 
-#ifndef CONFIG_HIDKBD_NODEBOUNCE
-      priv->lastkey[i] = rpt->key[i];
-#endif
+      if (j >= 6)
+        {
+          usbhost_report_key(priv, priv->lastkey[i], rpt->modifier, false);
+        }
     }
 
-  /* Is there data available? */
+  /* Report the keys that went down */
 
-  newstate = (priv->headndx == priv->tailndx);
-  if (!newstate)
+  for (i = 0; i < 6; i++)
     {
-      /* Did we just transition from no data available to data
-       * available?  If so, wake up any threads waiting for the
-       * POLLIN event.
-       */
-
-      if (priv->empty)
+      if (rpt->key[i] == USBHID_KBDUSE_NONE)
         {
-          poll_notify(priv->fds, CONFIG_HIDKBD_NPOLLWAITERS, POLLIN);
+          continue;
         }
 
-      /* Yes.. Is there a thread waiting for keyboard data now? */
+      for (j = 0; j < 6; j++)
+        {
+          if (priv->lastkey[j] == rpt->key[i])
+            {
+              break;
+            }
+        }
 
-      if (priv->waiting)
+      if (j >= 6)
         {
-          /* Yes.. wake it up */
+          usbhost_report_key(priv, rpt->key[i], rpt->modifier, true);
+        }
+    }
+
+#ifdef CONFIG_HIDKBD_REPORT_MODIFIERS
+  for (i = 0; i < USBHID_NMODIFIERS; i++)
+    {
+      uint8_t bit = 1 << i;
 
-          nxsem_post(&priv->waitsem);
-          priv->waiting = false;
+      if ((rpt->modifier & bit) == 0 && (priv->lastmod & bit) != 0)
+        {
+          keyboard_event(&priv->lower, g_modkeycode[i],
+                         KEYBOARD_SPECREL);
         }
     }
+#endif
 
-  priv->empty = newstate;
-  nxmutex_unlock(&priv->lock);
+  /* Remember this report so that the next one can be diffed against it */
+
+  memcpy(priv->lastkey, rpt->key, sizeof(priv->lastkey));
+  priv->lastmod = rpt->modifier;
 
+  nxmutex_unlock(&priv->lock);
   return 0;
 }
 
@@ -1987,11 +1888,19 @@ static inline int usbhost_devinit(FAR struct 
usbhost_state_s *priv)
       goto errout;
     }
 
-  /* Register the driver */
+  /* Register the driver with the keyboard upper half.  Note that priv is
+   * not passed as the lower half private data:  keyboard_register()
+   * overwrites that field with its own state, so the lower half callbacks
+   * recover priv with container_of() instead.
+   */
 
   uinfo("Register driver\n");
   usbhost_mkdevname(priv, devname);
-  ret = register_driver(devname, &g_hidkbd_fops, 0600, priv);
+
+  priv->lower.open  = usbhost_kbd_open;
+  priv->lower.close = usbhost_kbd_close;
+
+  ret = keyboard_register(&priv->lower, devname, CONFIG_HIDKBD_BUFSIZE);
 
   /* We now have to be concerned about asynchronous modification of crefs
    * because the driver has been registered.
@@ -2295,12 +2204,9 @@ usbhost_create(FAR struct usbhost_hubport_s *hport,
           /* Initialize mutex & semaphores */
 
           nxmutex_init(&priv->lock);
-          nxsem_init(&priv->waitsem, 0, 0);
           nxsem_init(&priv->syncsem, 0, 0);
           spin_lock_init(&priv->spinlock);
 
-          priv->empty = true;
-
           /* Return the instance of the USB keyboard class driver */
 
           return &priv->usbclass;
@@ -2424,15 +2330,9 @@ static int usbhost_disconnected(FAR struct 
usbhost_class_s *usbclass)
   priv->disconnected = true;
   uinfo("Disconnected\n");
 
-  /* Is there a thread waiting for keyboard data that will never come? */
-
-  if (priv->waiting)
-    {
-      /* Yes.. wake it up */
-
-      nxsem_post(&priv->waitsem);
-      priv->waiting = false;
-    }
+  /* Any thread blocked reading the character device is woken by the upper
+   * half when the device is unregistered from usbhost_destroy().
+   */
 
   /* Possibilities:
    *
@@ -2468,27 +2368,30 @@ static int usbhost_disconnected(FAR struct 
usbhost_class_s *usbclass)
 }
 
 /****************************************************************************
- * Name: usbhost_open
+ * Name: usbhost_kbd_open
  *
  * Description:
- *   Standard character driver open method.
+ *   Keyboard lower half open method.  Takes a reference on the driver
+ *   instance.
  *
  ****************************************************************************/
 
-static int usbhost_open(FAR struct file *filep)
+static int usbhost_kbd_open(FAR struct keyboard_lowerhalf_s *lower)
 {
-  FAR struct inode           *inode;
-  FAR struct usbhost_state_s *priv;
+  /* Note that lower->priv cannot be used to find our state:  it belongs to
+   * the upper half, which overwrites it in keyboard_register().
+   */
+
+  FAR struct usbhost_state_s *priv =
+    container_of(lower, struct usbhost_state_s, lower);
   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 */
 
-  DEBUGASSERT(priv && priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS);
+  DEBUGASSERT(priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS);
   ret = nxmutex_lock(&priv->lock);
   if (ret < 0)
     {
@@ -2526,23 +2429,23 @@ static int usbhost_open(FAR struct file *filep)
 }
 
 /****************************************************************************
- * Name: usbhost_close
+ * Name: usbhost_kbd_close
  *
  * Description:
- *   Standard character driver close method.
+ *   Keyboard lower half close method.  Drops a reference on the driver
+ *   instance and, if this was the last one and the device is gone,
+ *   destroys it.
  *
  ****************************************************************************/
 
-static int usbhost_close(FAR struct file *filep)
+static int usbhost_kbd_close(FAR struct keyboard_lowerhalf_s *lower)
 {
-  FAR struct inode *inode;
-  FAR struct usbhost_state_s *priv;
+  FAR struct usbhost_state_s *priv =
+    container_of(lower, struct usbhost_state_s, lower);
   irqstate_t flags;
   int ret;
 
   uinfo("Entry\n");
-  inode = filep->f_inode;
-  priv  = inode->i_private;
 
   /* Decrement the reference count on the driver */
 
@@ -2560,7 +2463,7 @@ static int usbhost_close(FAR struct file *filep)
   flags = spin_lock_irqsave(&priv->spinlock);
   priv->crefs--;
 
-  /* Check if the USB mouse device is still connected.  If the device is
+  /* Check if the USB keyboard device is still connected.  If the device is
    * no longer connected, then unregister the driver and free the driver
    * class instance.
    */
@@ -2581,9 +2484,7 @@ static int usbhost_close(FAR struct file *filep)
         {
           /* Yes.. In either case, then the driver is no longer open */
 
-          priv->open    = false;
-          priv->headndx = 0;
-          priv->tailndx = 0;
+          priv->open = false;
 
           /* Check if the USB keyboard device is still connected. */
 
@@ -2621,236 +2522,6 @@ static int usbhost_close(FAR struct file *filep)
   return OK;
 }
 
-/****************************************************************************
- * Name: usbhost_read
- *
- * Description:
- *   Standard character driver read method.
- *
- ****************************************************************************/
-
-static ssize_t usbhost_read(FAR struct file *filep, FAR char *buffer,
-                            size_t len)
-{
-  FAR struct inode           *inode;
-  FAR struct usbhost_state_s *priv;
-  size_t                      nbytes;
-  unsigned int                tail;
-  int                         ret;
-
-  uinfo("Entry\n");
-  DEBUGASSERT(buffer);
-  inode = filep->f_inode;
-  priv  = inode->i_private;
-
-  /* Make sure that we have exclusive access to the private data structure */
-
-  DEBUGASSERT(priv && priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS);
-  ret = nxmutex_lock(&priv->lock);
-  if (ret < 0)
-    {
-      return ret;
-    }
-
-  /* Check if the keyboard is still connected.  We need to disable interrupts
-   * momentarily to assure that there are no asynchronous disconnect events.
-   */
-
-  if (priv->disconnected)
-    {
-      /* No... the driver is no longer bound to the class.  That means that
-       * the USB keyboard is no longer connected.  Refuse any further
-       * attempts to access the driver.
-       */
-
-      ret = -ENODEV;
-    }
-  else
-    {
-      /* Is there keyboard data now? */
-
-      while (priv->tailndx == priv->headndx)
-        {
-          /* No.. were we open non-blocking? */
-
-          if (filep->f_oflags & O_NONBLOCK)
-            {
-              /* Yes.. then return a failure */
-
-              ret = -EAGAIN;
-              goto errout;
-            }
-
-          /* Wait for data to be available */
-
-          uinfo("Waiting...\n");
-
-          priv->waiting = true;
-          nxmutex_unlock(&priv->lock);
-          ret = nxsem_wait_uninterruptible(&priv->waitsem);
-          if (ret < 0)
-            {
-              return ret;
-            }
-
-          ret = nxmutex_lock(&priv->lock);
-          if (ret < 0)
-            {
-              return ret;
-            }
-
-          /* Did the keyboard become disconnected while we were waiting */
-
-          if (priv->disconnected)
-            {
-              ret = -ENODEV;
-              goto errout;
-            }
-        }
-
-      /* Read data from our internal buffer of received characters */
-
-      for (tail  = priv->tailndx, nbytes = 0;
-           tail != priv->headndx && nbytes < len;
-           nbytes++)
-        {
-          /* Copy the next keyboard character into the user buffer */
-
-          *buffer++ = priv->kbdbuffer[tail];
-
-          /* Handle wrap-around of the tail index */
-
-          if (++tail >= CONFIG_HIDKBD_BUFSIZE)
-            {
-              tail = 0;
-            }
-        }
-
-      ret = nbytes;
-
-      /* Update the tail index (perhaps marking the buffer empty) */
-
-      priv->tailndx = tail;
-    }
-
-errout:
-  nxmutex_unlock(&priv->lock);
-  return (ssize_t)ret;
-}
-
-/****************************************************************************
- * Name: usbhost_write
- *
- * Description:
- *   Standard character driver write method.
- *
- ****************************************************************************/
-
-static ssize_t usbhost_write(FAR struct file *filep, FAR const char *buffer,
-                             size_t len)
-{
-  /* We won't try to write to the keyboard */
-
-  return -ENOSYS;
-}
-
-/****************************************************************************
- * Name: usbhost_poll
- *
- * Description:
- *   Standard character driver poll method.
- *
- ****************************************************************************/
-
-static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds,
-                        bool setup)
-{
-  FAR struct inode           *inode;
-  FAR struct usbhost_state_s *priv;
-  int                         ret;
-  int                         i;
-
-  uinfo("Entry\n");
-  DEBUGASSERT(fds);
-  inode = filep->f_inode;
-  priv  = inode->i_private;
-
-  /* Make sure that we have exclusive access to the private data structure */
-
-  DEBUGASSERT(priv);
-  ret = nxmutex_lock(&priv->lock);
-  if (ret < 0)
-    {
-      return ret;
-    }
-
-  /* Check if the keyboard is still connected.  We need to disable interrupts
-   * momentarily to assure that there are no asynchronous disconnect events.
-   */
-
-  if (priv->disconnected)
-    {
-      /* No... the driver is no longer bound to the class.  That means that
-       * the USB keyboard is no longer connected.  Refuse any further
-       * attempts to access the driver.
-       */
-
-      ret = -ENODEV;
-    }
-  else if (setup)
-    {
-      /* This is a request to set up the poll.  Find an available slot for
-       * the poll structure reference
-       */
-
-      for (i = 0; i < CONFIG_HIDKBD_NPOLLWAITERS; i++)
-        {
-          /* Find an available slot */
-
-          if (!priv->fds[i])
-            {
-              /* Bind the poll structure and this slot */
-
-              priv->fds[i] = fds;
-              fds->priv    = &priv->fds[i];
-              break;
-            }
-        }
-
-      if (i >= CONFIG_HIDKBD_NPOLLWAITERS)
-        {
-          fds->priv = NULL;
-          ret       = -EBUSY;
-          goto errout;
-        }
-
-      /* Should we immediately notify on any of the requested events? Notify
-       * the POLLIN event if there is buffered keyboard data.
-       */
-
-      if (priv->headndx != priv->tailndx)
-        {
-          poll_notify(&fds, 1, POLLIN);
-        }
-    }
-  else
-    {
-      /* This is a request to tear down the poll. */
-
-      FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
-      DEBUGASSERT(slot);
-
-      /* Remove all memory of the poll setup */
-
-      *slot     = NULL;
-      fds->priv = NULL;
-    }
-
-errout:
-  nxmutex_unlock(&priv->lock);
-  return ret;
-}
-
 /****************************************************************************
  * Public Functions
  ****************************************************************************/

Reply via email to