Skip to content

Feature/kbd upperhalf - #19586

Open
JorgeGzm wants to merge 10 commits into
apache:masterfrom
JorgeGzm:feature/kbd-upperhalf
Open

Feature/kbd upperhalf#19586
JorgeGzm wants to merge 10 commits into
apache:masterfrom
JorgeGzm:feature/kbd-upperhalf

Conversation

@JorgeGzm

Copy link
Copy Markdown
Contributor

Summary

This is the change asked for in the review of #19579, done on
its own rather than inside a board port.

usbhost_hidkbd 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. It now registers with
keyboard_register() and reports with keyboard_event(), which removes the
private character device and the four hundred lines that served it.

While looking at why six of the nine drivers that register a keyboard never
report a special key, the answer turned out to be in the header:

/* include/nuttx/input/keyboard.h, before this change */

#define KEYBOARD_PRESS   0
#define KEYBOARD_RELEASE 1

The type field of struct keyboard_event_s has four values, not two. The
other two live in kbd_codec.h under a different prefix. Somebody writing a
driver reads keyboard.h, sees two types and implements two types, and the
failure is silent: the build is clean and the symptom is a key that does
nothing. All four are declared here now, as aliases of the kbd_decode()
return values so that a driver can feed both interfaces from one source.

The rest follows from that:

  • kmatrix reported every key as a plain press, so a board whose matrix has
    arrows had no way to say so. A keymap entry wrapped in KMATRIX_SPECIAL()
    now declares that it holds a keycode rather than a character.

  • sim_keyboard reported any X11 keysym its table did not know as an
    ordinary key press carrying the raw keysym, so an application looking for
    a byte of text got 65307 for Escape and 65289 for Tab, and the modifiers
    arrived as 65505 and friends. NXDoom on the simulator has therefore had no
    menu, no map and no fire, which is most of the game. Escape and Tab are
    control characters and are reported as such, the modifiers get the
    keycodes the codec now has for them, and an unknown keysym above the
    Latin-1 range is not reported at all, which closes the case rather than
    the three instances of it.

  • The keyboard driver documentation described the byte stream codec and
    nothing else. It never mentioned keyboard_register(), so there was
    nowhere to look up the interface that every keyboard actually uses. The
    contract is written down now, along with why the event type matters, what
    to name the device, how to get a matrix keyboard working without writing a
    driver, and how to test the result with or without the hardware.

  • The M5Stack Cardputer reported its Fn cursor cluster as ordinary presses
    carrying 0x80 to 0x83, chosen to sit above the printable range so that an
    application could tell them apart from characters. That is the same
    collision, solved by hand. They are keycodes now.

This is the first of three parts, and it stands on its own.
apache/nuttx-apps#PENDING is the application half and needs this one merged
first, since it uses the keycodes added here. A follow-up here will then
update the two board configurations that name an application option.

Impact

Nine configurations have an application that reads the USB keyboard as a
byte stream.
They keep working through INPUT_KEYBOARD_BYTESTREAM, which
renders each event with the keyboard codec instead of copying the event
structure. Only presses are rendered, which is what a keyboard reporting
through a character device has always delivered, so this reproduces the
previous behaviour rather than adding to it. The option is selected in those
nine defconfigs here.

Out of tree, NSH_USBKBD and MICROWINDOWS_KBD_RAW also read the byte
stream
and need the same option. Both now depend on it, in the
applications PR, so Kconfig refuses the combination that cannot work. No
in-tree configuration selects either.

HIDKBD_ENCODED and HIDKBD_NODEBOUNCE are gone, with the code they
guarded. Encoding is inherent to the event now. The previous report is no
longer an optimisation either: 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. It matters for a game, where
fire, run and strafe are bound to Ctrl, Shift and Alt, and not for a
terminal.

USBHOST_HIDKBD now selects INPUT and INPUT_KEYBOARD, the same way
USBHOST_HIDMOUSE already selects INPUT and INPUT_MOUSE.

INPUT_KMATRIX_DEVPATH defaults to /dev/kbd0 instead of
/dev/keypad0. Applications look for a keyboard under the former, so the
matrix driver was out of reach of all of them. No in-tree configuration
enables INPUT_KMATRIX.

LAST_KEYCODE moves to the end of the enumeration, which it has to: the
codec range checks against it, so the new keycodes would otherwise trip an
assertion in kbd_specpress() and be rejected by kbd_decode().

Testing

Validated on hardware in two independent backends, a USB HID keyboard on a
Linum STM32H753BI and X11 on the simulator, in both the event model and the
byte stream compatibility mode.

Counted rather than eyeballed. Typing normally on the Linum produced 40
presses and 40 releases, perfectly paired, with no key pressed twice while
held, no orphan release, nothing stuck down at the end, and four keys held
at once reported in the right order. The arrows, Ctrl and Shift gave 29
press and release pairs on the same terms, including two arrows held
together, and arrived as keycodes 5 to 8, 88 and 90.

On the simulator, dumping the events while typing shows Escape as 27, Tab as
9 and the modifiers as keycodes 88 to 95, none of which were reachable
before. NXDoom runs there and plays.

Two things worth knowing for anybody repeating this, both now documented:

Without HIDKBD_NOGETREPORT the driver samples the keyboard over the
control pipe every 40 ms, and a key pressed and released between two samples
is simply not there. Six keys arrived in seventy five seconds of typing.
That is the sampling rate, not the reporting, and it behaves the same way on
master. The Linum configuration enables it.

UINPUT_KEYBOARD_BUFNUMBER counts events rather than keys, so its default
of eight holds four keystrokes, and a console hands over a whole line at
once. The upper half overwrites the oldest event when the buffer is full, so
a typed line arrives with its beginning missing and nothing says so. This is
pre-existing and only shows up when uinput is actually used as a keyboard.
The Linum configuration raises it.

Built every configuration in the tree that enables USBHOST_HIDKBD,
INPUT_KEYBOARD, INPUT_KMATRIX, UINPUT_KEYBOARD, EXAMPLES_LVGLTERM,
EXAMPLES_KEYBOARD, EXAMPLES_HIDKBD or SYSTEM_KBD. 21 of them were also
built inside the CI container, which covers the Xtensa and x86_64 toolchains
that are not on my machine, and all 21 pass. That includes both Cardputer
configurations, which is the only way I could check the driver change above,
since the board was not available.

The remaining failures are stm32butterfly2:nsh and :nshusbhost, which
select ARM_TOOLCHAIN_BUILDROOT and need a compiler that is not in the CI
container either (the CI builds them with ARM_TOOLCHAIN_CLANG), and
ci20:jumbo, which needs MIPS. All three fail the same way on master.

sim:nxdoom builds and runs. nxstyle is clean on every file touched.

JorgeGzm added 2 commits July 30, 2026 20:02
A keyboard driver that tracks modifier state has no way to report it.
Folding the modifier into the character that it produces loses the fact
that the key is down, so an application cannot bind an action to Ctrl or
Shift, and cannot tell that one is being held.

Add the eight modifiers to enum kbd_keycode_e and move LAST_KEYCODE to
the new end of the enumeration.  Leaving LAST_KEYCODE behind would make
kbd_specpress() assert and kbd_decode() reject the new keycodes, since
both range check against it.

The keycodes are appended, so the values of the existing ones do not
change.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
keyboard.h declares KEYBOARD_PRESS and KEYBOARD_RELEASE only, but the
type field of struct keyboard_event_s has four values:  the two special
key types live in kbd_codec.h, under a different prefix.

Somebody writing a keyboard driver reads keyboard.h, sees two types, and
implements two types.  Six of the nine drivers that register a keyboard
never report a special key at all, and the failure is silent:  the build
is clean and the symptom is a key that does nothing.

Define all four here, as aliases of the kbd_decode() return values so
that a driver can feed both this interface and the byte stream codec
from a single source.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
@github-actions github-actions Bot added Arch: simulator Issues related to the SIMulator Area: Drivers Drivers issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. Board: arm Board: mips Board: x86_64 Board: xtensa labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

@JorgeGzm
JorgeGzm force-pushed the feature/kbd-upperhalf branch 2 times, most recently from e4a99c9 to bb21a91 Compare July 31, 2026 01:23
Comment thread drivers/input/Kconfig Outdated
Comment thread drivers/input/keyboard_upper.c Outdated
Comment thread drivers/input/keyboard_upper.c Outdated
Comment thread drivers/input/keyboard_upper.c
Comment thread drivers/usbhost/Kconfig
Comment thread drivers/usbhost/usbhost_hidkbd.c Outdated
Comment thread drivers/usbhost/usbhost_hidkbd.c Outdated
JorgeGzm added 3 commits July 31, 2026 00:14
The USB HID keyboard driver is about to report through the upper half
rather than through a character device of its own, which changes what
read() returns from a byte stream to struct keyboard_event_s.  Ten
in-tree configurations have an application that consumes the byte
stream.

Add INPUT_KEYBOARD_BYTESTREAM, which renders each event with the
keyboard codec instead of copying the event structure, so those
applications keep working while they are converted.

Only the press events are rendered.  A byte stream has no way to say
that a key came up, which is exactly what a keyboard reporting through a
character device has always delivered, so this reproduces the previous
behaviour rather than adding to it.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
Both switches end with a bare default label and no statement after it,
which a compiler is entitled to reject:  a label has to label something.
GCC for MIPS does, and the file is new enough that no configuration had
compiled it yet.

The next commit makes USBHOST_HIDKBD select INPUT_KEYBOARD, which pulls
this file into twenty five configurations for the first time, ci20:jumbo
among them, so fix it here rather than let that commit break them.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
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 <jorge.gzm@gmail.com>
@JorgeGzm
JorgeGzm force-pushed the feature/kbd-upperhalf branch from bb21a91 to bd96abb Compare July 31, 2026 03:31
JorgeGzm added 5 commits July 31, 2026 01:01
These are the configurations whose application reads the USB HID keyboard
as a byte stream.  Now that the driver reports through the keyboard upper
half, they need INPUT_KEYBOARD_BYTESTREAM to keep behaving as before.

INPUT drops out of the normalised defconfigs because USBHOST_HIDKBD now
selects it.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
The matrix driver reported every key with KEYBOARD_PRESS and
KEYBOARD_RELEASE, so a board whose matrix has arrows or function keys
had no way to say so:  the keycode ranges overlap the character range,
and the event type is what tells them apart.

A keymap entry is a uint32_t, so wrap the entry in KMATRIX_SPECIAL() to
declare that it holds a value from enum kbd_keycode_e.  Existing keymaps
hold characters and are unaffected.

While here, drop the cast that truncated the keycode to sixteen bits,
and default the device to /dev/kbd0.  Applications look for a keyboard
under that name, and /dev/keypad0 kept the matrix out of reach of every
one of them.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
The keyboard driver documentation described the byte stream codec and
nothing else.  It never mentioned keyboard_register(), keyboard_event()
or the event types, so somebody writing a driver had no way to find the
interface that every keyboard in the tree actually uses.

That omission has a cost that can be counted:  six of the nine drivers
that register a keyboard report only the press and release types and
never the special ones, which means their arrow keys are silently
dropped by any application that follows the contract.  The header they
would read to find out declares two of the four types.

Document the contract, why the event type is what tells an arrow key
from the character that shares its value, what to name the device, how
to get a matrix keyboard working without writing a driver at all, and
how to test the result with or without the hardware.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
The X11 keyboard reported any keysym that its translation table did not
know as an ordinary key press carrying the raw keysym.  An application
looking for a byte of text got 65307 for Escape and 65289 for Tab, and
the modifiers arrived as 65505 and friends.

That is not a character, so it cannot be a KEYBOARD_PRESS.  NXDoom on
the simulator has therefore had no menu, no map and no fire, which is
most of the game.

Escape and Tab are control characters and are reported as such.  The
eight modifiers get the keycodes that the codec now has for them.  And a
keysym above the Latin-1 range that no table knows is not reported at
all, which closes the case rather than the three instances of it.

Verified by dumping the events while typing:  Escape arrives as 27, Tab
as 9, the modifiers as keycodes 88 to 95, and the game plays.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
… keycodes

The Fn cursor cluster was reported as ordinary key presses carrying 0x80
to 0x83, chosen to sit above the printable range so that an application
could tell them apart from characters.  That is the collision this work
exists to remove:  a key that produces no character is a special key, and
the event type is what says so.

Report them as KEYBOARD_SPECPRESS carrying KEYCODE_UP and friends, which
is what every other keyboard does and what the applications now expect.

The character tables are untouched, so the layout and the shift map behave
exactly as before.  Only the four Fn cursor keys change.

Not tested on hardware:  the board was not available.  It builds, and the
change is confined to the two lines that pick the event type.

Signed-off-by: Jorge Guzman <jorge.gzm@gmail.com>
@JorgeGzm
JorgeGzm force-pushed the feature/kbd-upperhalf branch from bd96abb to 4a128c6 Compare July 31, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: simulator Issues related to the SIMulator Area: Drivers Drivers issues Board: arm Board: mips Board: x86_64 Board: xtensa Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants