/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. Licensed under the StarForth License, Version 1.0 */ /* keyboard_words.c — raw scancode-ring diagnostic word (FABRIC.md item * 4.3.5). Kernel-only, amd64-only; no-op elsewhere. */ #include "include/keyboard_words.h" #include "../../include/word_registry.h" #if defined(__STARKERNEL__) && defined(ARCH_AMD64) #include "starkernel/i8042.h" #endif #if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64)) #include "starkernel/virtio_input.h" #endif #if defined(__STARKERNEL__) #include "starkernel/console.h" #endif /* KBD-SCAN ( -- c -1 | 0 ) */ static void kbw_scan(VM *vm) { #if defined(__STARKERNEL__) && defined(ARCH_AMD64) uint8_t sc; if (i8042_pop_scancode(&sc)) { vm_push(vm, (cell_t)sc); vm_push(vm, -1); } else { vm_push(vm, 0); } #else vm_push(vm, 0); #endif } /* KBD-DEBUG ( -- isr_count spurious_count ): cheap standing diagnostic, * not scaffolding -- confirms the interrupt path is alive (isr_count) and * flags the failure mode item 4.3.5 found (a real IRQ misreported as * spurious) without needing a live debugger. */ extern volatile uint32_t g_i8042_isr_count; extern volatile uint32_t g_spurious_count; static void kbw_debug(VM *vm) { #if defined(__STARKERNEL__) && defined(ARCH_AMD64) vm_push(vm, (cell_t)g_i8042_isr_count); vm_push(vm, (cell_t)g_spurious_count); #else vm_push(vm, 0); vm_push(vm, 0); #endif } /* VKBD-EVENT ( -- code value -1 | 0 ): item 4.3.5c (riscv64) / 4.3.5e * (aarch64) virtio-input. Pop one decoded EV_KEY event off the * interrupt-fed ring buffer. Pushes code and value and -1 (true) if one * was available, or just 0 (false) if the buffer was empty. Distinct word * from KBD-SCAN rather than a shared one: different device, different * event shape (Linux-style code/value pair vs. a raw XT scancode byte) -- * convergence onto one interface is item 4.3.5f's explicit job, not this * one's. */ static void kbw_vevent(VM *vm) { #if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64)) uint16_t code; uint32_t value; if (virtio_input_pop_event(&code, &value)) { vm_push(vm, (cell_t)code); vm_push(vm, (cell_t)value); vm_push(vm, -1); } else { vm_push(vm, 0); } #else vm_push(vm, 0); #endif } /* VKBD-DEBUG ( -- isr_count ): standing diagnostic, same shape as * KBD-DEBUG -- confirms the virtio-input interrupt path is alive. */ extern volatile uint32_t g_virtio_input_isr_count; static void kbw_vdebug(VM *vm) { #if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64)) vm_push(vm, (cell_t)g_virtio_input_isr_count); #else vm_push(vm, 0); #endif } /* KEY-EVENT ( -- keycode pressed -1 | 0 ): item 4.3.5f convergence * checkpoint. One shared shape across all three architectures instead of * KBD-SCAN's raw XT scancode byte or VKBD-EVENT's Linux code/value pair. * keycode is the Linux input keycode namespace (linux/input-event-codes.h); * pressed is 1 (press) or 0 (release). * * amd64 needs a real translation, not an invented one: XT Set-1's make * code for a key and Linux's keycode for that same key are the *same * number*, confirmed against this build host's own * /usr/include/linux/input-event-codes.h and against this tree's own * live-observed values (item 4.3.5's done note: 'a' -> 30, 'b' -> 48, * matching KEY_A=30/KEY_B=48 exactly) -- not a coincidence, a documented * historical property of how the Linux input keycode numbering was * originally defined around the AT/XT scancode set. Break codes set XT's * bit 7 (make|0x80) rather than carrying a separate press/release field, * so `keycode = sc & 0x7F, pressed = !(sc & 0x80)` is the whole * translation for the entire non-extended key range. Extended * (0xE0-prefixed) scancodes are not decoded here -- this checkpoint needs * one shared shape proven live per architecture, not full keyboard * coverage; that belongs to the REPL wiring item (M8) this checkpoint * unblocks. * * riscv64/aarch64 need no translation at all -- virtio-input's `code` * field already lives in the same Linux keycode namespace; `value` is * mapped 1:1 except autorepeat (value=2), folded into "still pressed" * here since this checkpoint's shape only distinguishes press/release. */ /* FABRIC.md item 4.4v: shared C-level implementation, so the REPL * keyboard bridge (repl.c) and the KEY-EVENT FORTH word below poll the * exact same converged event stream rather than each re-deriving the * per-arch translation kbw_key_event's own doc comment already explains * (XT Set-1 make code == Linux keycode for the non-extended range; break * codes set bit 7 instead of carrying a separate field). */ int sk_key_event_poll(uint16_t *keycode, int *pressed) { #if defined(__STARKERNEL__) && defined(ARCH_AMD64) uint8_t sc; if (i8042_pop_scancode(&sc)) { *keycode = (uint16_t)(sc & 0x7Fu); *pressed = (sc & 0x80u) ? 0 : 1; return 1; } return 0; #elif defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64)) uint16_t code; uint32_t value; if (virtio_input_pop_event(&code, &value)) { *keycode = code; *pressed = (value == 0u) ? 0 : 1; return 1; } return 0; #else (void)keycode; (void)pressed; return 0; #endif } static void kbw_key_event(VM *vm) { uint16_t keycode; int pressed; if (sk_key_event_poll(&keycode, &pressed)) { vm_push(vm, (cell_t)keycode); vm_push(vm, (cell_t)pressed); vm_push(vm, -1); } else { vm_push(vm, 0); } } /* ALT+TAB ( -- ): FABRIC.md item 4.4y-revised. Programmatic equivalent of * the physical Alt+TAB interception (repl.c's sk_kbd_getc()) -- both call * console_fb_toggle_graphics(), so there is exactly one state-machine * transition, invoked two ways. */ static void kbw_alt_tab(VM *vm) { (void)vm; #if defined(__STARKERNEL__) console_fb_toggle_graphics(); #endif } void register_keyboard_words(VM *vm) { register_word(vm, "KBD-SCAN", kbw_scan); register_word(vm, "KBD-DEBUG", kbw_debug); register_word(vm, "VKBD-EVENT", kbw_vevent); register_word(vm, "VKBD-DEBUG", kbw_vdebug); register_word(vm, "KEY-EVENT", kbw_key_event); register_word(vm, "ALT+TAB", kbw_alt_tab); }