feat(usb): a USB keyboard -- a full one -- and no hang on a PC without PS/2
boards/BRINGUP.md A3 and S3, FABRIC-3 SXLI. A HID boot keyboard is configured (interrupt IN endpoint, SET_PROTOCOL boot) and its reports become the same Linux-keycode events as PS/2 and virtio (usb_kbd.c): the full layout -- F-keys, navigation block, arrows, Num Lock, keypad. The console treats Caps and Num Lock as toggles shown on the keyboard's lights, and the keypad as digits or navigation. While a USB keyboard runs, its event ring is serviced every tick, not once a second. Found on the way: with no i8042 (port 0x64 reads 0xFF) the kernel hung at boot in an endless drain -- a legacy-free PC would stop there. Detected and skipped now; drains bounded. QEMU, amd64 with the USB keyboard the only one: 1 2 + . on main keys and keypad, Caps Lock, Num Lock all right; aarch64/riscv64 configure it too. Acceptance on three ISAs unchanged; identities hot-plug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
080b7012ba
commit
739cb9c163
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2026 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* usb_kbd.c - a USB boot-protocol keyboard's reports, as key events
|
||||
* (2026-10-14, boards/BRINGUP.md A3). See usb_kbd.h.
|
||||
*
|
||||
* A boot report says which keys are held now, not what changed: what was
|
||||
* held in the report before and is not now has been released, and the
|
||||
* other way round has been pressed. The eight modifier bits are keys too
|
||||
* (left/right Ctrl, Shift, Alt, GUI). A report whose keys are all 0x01 is
|
||||
* the keyboard's "too many keys" (ErrorRollOver) and changes nothing.
|
||||
*
|
||||
* Usage IDs (HID Usage Tables 1.12, page 7) become Linux keycodes
|
||||
* (input-event-codes.h) -- the same numbers the PS/2 and virtio keyboards
|
||||
* already give sk_key_event_poll(). Only the keys a console uses are
|
||||
* mapped; others are dropped. The keyboard does not repeat a held key:
|
||||
* held is held, and nothing here repeats it either.
|
||||
*/
|
||||
|
||||
#include "starkernel/usb_kbd.h"
|
||||
|
||||
/* HID usage ID -> Linux keycode; 0 = not mapped. A full-size keyboard:
|
||||
* the main block, F1-F12, the navigation block, arrows and the keypad
|
||||
* (Captain Bob, 2026-10-14: "include caps lock, numlock, keypad and
|
||||
* arrow"). What a key means -- Caps and Num Lock as toggles, the keypad
|
||||
* as digits or navigation -- is the console's (repl.c), for every
|
||||
* keyboard alike. */
|
||||
static const uint8_t usage_to_key[0x66] = {
|
||||
[0x04] = 30, [0x05] = 48, [0x06] = 46, [0x07] = 32, [0x08] = 18, /* a b c d e */
|
||||
[0x09] = 33, [0x0A] = 34, [0x0B] = 35, [0x0C] = 23, [0x0D] = 36, /* f g h i j */
|
||||
[0x0E] = 37, [0x0F] = 38, [0x10] = 50, [0x11] = 49, [0x12] = 24, /* k l m n o */
|
||||
[0x13] = 25, [0x14] = 16, [0x15] = 19, [0x16] = 31, [0x17] = 20, /* p q r s t */
|
||||
[0x18] = 22, [0x19] = 47, [0x1A] = 17, [0x1B] = 45, [0x1C] = 21, /* u v w x y */
|
||||
[0x1D] = 44, /* z */
|
||||
[0x1E] = 2, [0x1F] = 3, [0x20] = 4, [0x21] = 5, [0x22] = 6, /* 1 2 3 4 5 */
|
||||
[0x23] = 7, [0x24] = 8, [0x25] = 9, [0x26] = 10, [0x27] = 11, /* 6 7 8 9 0 */
|
||||
[0x28] = 28, [0x29] = 1, [0x2A] = 14, [0x2B] = 15, [0x2C] = 57, /* enter esc bksp tab space */
|
||||
[0x2D] = 12, [0x2E] = 13, [0x2F] = 26, [0x30] = 27, [0x31] = 43, /* - = [ ] \ */
|
||||
[0x32] = 43, [0x33] = 39, [0x34] = 40, [0x35] = 41, [0x36] = 51, /* non-US # ; ' ` , */
|
||||
[0x37] = 52, [0x38] = 53, [0x39] = 58, /* . / capslock */
|
||||
[0x3A] = 59, [0x3B] = 60, [0x3C] = 61, [0x3D] = 62, [0x3E] = 63, /* F1-F5 */
|
||||
[0x3F] = 64, [0x40] = 65, [0x41] = 66, [0x42] = 67, [0x43] = 68, /* F6-F10 */
|
||||
[0x44] = 87, [0x45] = 88, /* F11 F12 */
|
||||
[0x46] = 99, [0x47] = 70, [0x48] = 119, /* printscreen scrolllock pause */
|
||||
[0x49] = 110, [0x4A] = 102, [0x4B] = 104, /* insert home pageup */
|
||||
[0x4C] = 111, [0x4D] = 107, [0x4E] = 109, /* delete end pagedown */
|
||||
[0x4F] = 106, [0x50] = 105, [0x51] = 108, [0x52] = 103, /* right left down up */
|
||||
[0x53] = 69, /* numlock */
|
||||
[0x54] = 98, [0x55] = 55, [0x56] = 74, [0x57] = 78, [0x58] = 96, /* kp/ kp* kp- kp+ kpenter */
|
||||
[0x59] = 79, [0x5A] = 80, [0x5B] = 81, [0x5C] = 75, [0x5D] = 76, /* kp1 kp2 kp3 kp4 kp5 */
|
||||
[0x5E] = 77, [0x5F] = 71, [0x60] = 72, [0x61] = 73, [0x62] = 82, /* kp6 kp7 kp8 kp9 kp0 */
|
||||
[0x63] = 83, [0x64] = 86, [0x65] = 127, /* kp. non-US\ compose(menu) */
|
||||
};
|
||||
|
||||
/* Modifier bit n -> Linux keycode: LCtrl LShift LAlt LGUI RCtrl RShift RAlt RGUI. */
|
||||
static const uint8_t modifier_key[8] = { 29, 42, 56, 125, 97, 54, 100, 126 };
|
||||
|
||||
#define KBD_EVENTS 64u
|
||||
static struct { uint16_t keycode; uint8_t pressed; } g_events[KBD_EVENTS];
|
||||
static uint32_t g_head, g_count;
|
||||
static uint8_t g_prev[8];
|
||||
/* Num Lock on to start, matching the console (repl.c); 0xFF = nothing
|
||||
* sent yet, so a new keyboard is always told. */
|
||||
static uint8_t g_leds_want = USB_KBD_LED_NUM;
|
||||
static uint8_t g_leds_sent = 0xFFu;
|
||||
|
||||
static void push(uint16_t keycode, int pressed)
|
||||
{
|
||||
if (keycode == 0 || g_count == KBD_EVENTS) return; /* a full queue drops: nothing reads it */
|
||||
g_events[(g_head + g_count) % KBD_EVENTS].keycode = keycode;
|
||||
g_events[(g_head + g_count) % KBD_EVENTS].pressed = (uint8_t)(pressed != 0);
|
||||
g_count++;
|
||||
}
|
||||
|
||||
static uint16_t key_for(uint8_t usage)
|
||||
{
|
||||
return usage < sizeof usage_to_key ? usage_to_key[usage] : 0;
|
||||
}
|
||||
|
||||
static int held(const uint8_t *keys, uint32_t n, uint8_t usage)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < n; i++)
|
||||
if (keys[i] == usage) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void usb_kbd_report(const uint8_t *report, uint32_t len)
|
||||
{
|
||||
uint8_t now[8] = {0};
|
||||
uint32_t i, n;
|
||||
|
||||
if (!report || len < 3) return;
|
||||
if (len > 8) len = 8;
|
||||
for (i = 0; i < len; i++) now[i] = report[i];
|
||||
if (now[2] == 0x01) return; /* ErrorRollOver: nothing known */
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
uint8_t bit = (uint8_t)(1u << i);
|
||||
if ((now[0] & bit) != (g_prev[0] & bit)) push(modifier_key[i], (now[0] & bit) != 0);
|
||||
}
|
||||
n = 6;
|
||||
for (i = 2; i < 8; i++) /* released first, then pressed */
|
||||
if (g_prev[i] > 0x01 && !held(now + 2, n, g_prev[i])) push(key_for(g_prev[i]), 0);
|
||||
for (i = 2; i < 8; i++)
|
||||
if (now[i] > 0x01 && !held(g_prev + 2, n, now[i])) push(key_for(now[i]), 1);
|
||||
|
||||
for (i = 0; i < 8; i++) g_prev[i] = now[i];
|
||||
}
|
||||
|
||||
void usb_kbd_reset(void)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < 8; i++) g_prev[i] = 0;
|
||||
g_leds_sent = 0xFFu;
|
||||
}
|
||||
|
||||
void usb_kbd_set_leds(uint8_t leds) { g_leds_want = leds; }
|
||||
|
||||
int usb_kbd_leds_pending(uint8_t *leds)
|
||||
{
|
||||
if (g_leds_want == g_leds_sent) return 0;
|
||||
*leds = g_leds_want;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void usb_kbd_leds_sent(uint8_t leds) { g_leds_sent = leds; }
|
||||
|
||||
int usb_kbd_pop_event(uint16_t *keycode, int *pressed)
|
||||
{
|
||||
if (g_count == 0) return 0;
|
||||
*keycode = g_events[g_head].keycode;
|
||||
*pressed = g_events[g_head].pressed;
|
||||
g_head = (g_head + 1u) % KBD_EVENTS;
|
||||
g_count--;
|
||||
return 1;
|
||||
}
|
||||
+274
-3
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user