aarch64: virtio-keyboard-pci, interrupt-driven keyboard input (item 4.3.5e)

Punch list §25 item 4.3.5e complete.

Extended virtio_input.c with a GIC-routed interrupt path alongside 4.3.5c's
PLIC one -- same capability walk, feature negotiation, eventq handling
(confirming §27.5.1's prediction that these items would share most of the
driver). aarch64_irq_handler() dispatches to virtio_input_isr() before its
EOIR write, same claim-dispatch-complete ordering riscv64 uses. Verified
with a real QEMU sendkey keypress: exact KEY_A/press match, two real
interrupts serviced, identical result to riscv64. Found (not fixed) an
unrelated pre-existing bug: BYE's cold-reset path faults on aarch64,
discovered incidentally since nobody had exercised it from a monitored
session before. Three-arch acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-08 12:29:27 -04:00
co-authored by Claude Sonnet 5
parent 2a2d7c5e5e
commit a373c9124a
18 changed files with 72523 additions and 78 deletions
+19
View File
@@ -33,6 +33,14 @@ extern uint32_t apic_timer_ppi(void);
extern void apic_eoi_intid(uint32_t intid);
extern void apic_timer_rearm(void);
/* Defined in virtio_input.c (item 4.3.5e). g_virtio_input_gic_intid is 0
* (not a valid SPI INTID -- SPIs start at 32) until
* virtio_input_find_keyboard() finds a real device and computes its routed
* INTID, so this branch is inert on any boot where the device isn't
* present. Same extern-in-place convention as the rest of this file. */
extern uint32_t g_virtio_input_gic_intid;
extern void virtio_input_isr(void);
/* INTID 1023 = "spurious" (GICv2 spec): the CPU interface has nothing
* pending, typically because another interrupt at the same or higher
* priority raced this one to acknowledgement. Architecturally defined,
@@ -64,6 +72,13 @@ extern void apic_timer_rearm(void);
* the rest of the dispatch means a fault in @c heartbeat_tick() cannot
* also cost the next tick — same discipline as @c riscv64_timer_rearm()
* (item 0.3).
*
* virtio-input (item 4.3.5e, @c g_virtio_input_gic_intid): dispatched
* **before** the @c GICC_EOIR write below, not after — the ISR itself is
* what deasserts the device's (level-triggered) interrupt line by reading
* its ISR-status capability; EOI'ing first would let the GIC immediately
* re-signal a still-asserted line. Same claim-dispatch-complete ordering
* @c riscv64_interrupt_handler() uses for its own PLIC-routed devices.
*/
void aarch64_irq_handler(void)
{
@@ -74,6 +89,10 @@ void aarch64_irq_handler(void)
heartbeat_tick();
}
if (intid == g_virtio_input_gic_intid) {
virtio_input_isr();
}
if (intid != GIC_INTID_SPURIOUS) {
apic_eoi_intid(intid);
}
+56 -17
View File
@@ -1,5 +1,6 @@
/*
* virtio_input.c — Virtio 1.0 input device driver for StarKernel (item 4.3.5c)
* virtio_input.c — Virtio 1.0 input device driver for StarKernel
* (riscv64: item 4.3.5c; aarch64: item 4.3.5e)
*
* Modern virtio 1.0 interface only (device ID 0x1052). No legacy fallback
* exists for virtio-input (unlike virtio-blk's 0x1001) -- input postdates
@@ -9,20 +10,24 @@
* buffers: unlike virtio_blk.c's synchronous request/response pattern, the
* driver posts empty VirtioInputEvent buffers up front and the device
* fills+posts them to the used ring asynchronously, signalled by the PLIC
* (see virtio_input_isr(), called from arch/riscv64/interrupts.c on a
* matching claim) -- no polling anywhere in this path.
* (riscv64) or GIC (aarch64) -- see virtio_input_isr(), called from
* arch/riscv64/interrupts.c or arch/aarch64/interrupts.c on a matching
* claim -- no polling anywhere in this path.
*
* The PCI capability walker below is a deliberate duplicate of
* virtio_blk.c's static walk_virtio_caps(), not a shared/promoted version
* -- decided at this item rather than refactoring the working, tested
* virtio_blk.c (FABRIC.md item 4.3.5c note, carries forward to 4.3.5e).
* -- decided at item 4.3.5c rather than refactoring the working, tested
* virtio_blk.c, carried forward here.
*
* Interrupt routing (PLIC source computation/enable) is riscv64-specific
* and arch-guarded below; the capability walk, feature negotiation, and
* queue/event handling are arch-agnostic and compiled for all three
* targets (same idiom as pci.c itself: config-space access already
* differs per arch internally, this file adds one more arch-guarded
* piece on top rather than a second copy of the whole file).
* Interrupt routing (source computation/enable) is arch-guarded below,
* riscv64 (PLIC) and aarch64 (GIC) each with their own slot/pin-to-source
* formula (FABRIC.md §27.5.1 aarch64, §27.5.2 riscv64, both derived live
* from this host's own QEMU DTB, not assumed identical to each other). The
* capability walk, feature negotiation, and queue/event handling are
* arch-agnostic and compiled for all three targets (same idiom as pci.c
* itself: config-space access already differs per arch internally, this
* file adds one more arch-guarded piece on top rather than a second copy
* of the whole file).
*/
#ifndef __STARKERNEL__
@@ -42,6 +47,12 @@
#include "starkernel/plic.h"
#endif
#if defined(ARCH_AARCH64)
/* Defined in arch/aarch64/apic.c (item 4.3.5d). Extern-in-place, same
* convention that file's own cross-file declarations already use. */
extern void apic_spi_enable(uint32_t intid);
#endif
/* -------------------------------------------------------------------------
* Virtio 1.0 PCI capability structures -- duplicated from virtio_blk.c
* (see file header for why), plus VIRTIO_PCI_CAP_ISR_CFG actually used
@@ -200,6 +211,12 @@ static int g_vinput_ready = 0;
* real claim. Checked by arch/riscv64/interrupts.c's dispatch. */
uint32_t g_virtio_input_plic_source = 0;
/* GIC INTID (aarch64) this device's INTx routes to, computed at init. 0 is
* not a valid SPI INTID (SPIs start at 32), so it is likewise a safe
* "invalid/not found" sentinel. Checked by arch/aarch64/interrupts.c's
* dispatch. */
uint32_t g_virtio_input_gic_intid = 0;
/* -------------------------------------------------------------------------
* Capability walker -- duplicated from virtio_blk.c, see file header.
* ------------------------------------------------------------------------- */
@@ -251,11 +268,15 @@ static inline void wmb(void) {
}
/* -------------------------------------------------------------------------
* Interrupt routing -- riscv64 (PLIC). aarch64 (GIC, item 4.3.5e) is not
* yet implemented; the #else path reports rather than silently no-ops, so
* a future arch attempting to use this file without adding its own
* routing fails loudly instead of "working" with a permanently-pending,
* never-enabled source.
* Interrupt routing -- riscv64 (PLIC, item 4.3.5c) and aarch64 (GIC, item
* 4.3.5e). Both compute their target source from the same PCI slot/pin
* swizzle shape (§27.5.1 aarch64, §27.5.2 riscv64) but with different base
* offsets and controller calls -- derived independently from each board's
* own DTB, not assumed identical. The #else path (amd64, which never
* attaches this device) reports rather than silently no-ops, so a future
* arch attempting to use this file without adding its own routing fails
* loudly instead of "working" with a permanently-pending, never-enabled
* source.
* ------------------------------------------------------------------------- */
#if defined(ARCH_RISCV64)
@@ -275,6 +296,23 @@ static int enable_interrupt_route(const PciDevice *pci)
g_virtio_input_plic_source = source;
return 0;
}
#elif defined(ARCH_AARCH64)
static int enable_interrupt_route(const PciDevice *pci)
{
uint8_t pin = pci_read8(pci, (uint16_t)PCI_CFG_INT_PIN);
if (pin < 1u || pin > 4u) {
console_println("virtio-input: bad/absent INT_PIN");
return -1;
}
uint32_t slot = pci->device;
uint32_t spi = 3u + ((slot + (uint32_t)pin - 1u) % 4u);
uint32_t intid = 32u + spi;
apic_spi_enable(intid);
g_virtio_input_gic_intid = intid;
return 0;
}
#else
static int enable_interrupt_route(const PciDevice *pci)
{
@@ -466,7 +504,8 @@ int virtio_input_find_keyboard(void) {
/* -------------------------------------------------------------------------
* Interrupt path -- called from arch/riscv64/interrupts.c on a matching
* PLIC claim (g_virtio_input_plic_source).
* PLIC claim (g_virtio_input_plic_source) or arch/aarch64/interrupts.c on
* a matching GIC IRQ (g_virtio_input_gic_intid).
* ------------------------------------------------------------------------- */
void virtio_input_isr(void) {
+4 -3
View File
@@ -34,9 +34,10 @@
* LAPIC spurious-vector interrupts, both since boot.
*
* @par VKBD-EVENT ( -- code value -1 | 0 )
* Item 4.3.5c. Pop one decoded EV_KEY event (Linux input-event code/value
* pair) off the virtio-input interrupt-fed ring buffer. Kernel-only,
* riscv64-only today (virtio-keyboard-pci); no-op elsewhere.
* Items 4.3.5c (riscv64) / 4.3.5e (aarch64). Pop one decoded EV_KEY event
* (Linux input-event code/value pair) off the virtio-input interrupt-fed
* ring buffer. Kernel-only, riscv64/aarch64 (virtio-keyboard-pci); no-op
* on amd64 (i8042 hardware instead, see KBD-SCAN).
*
* @par VKBD-DEBUG ( -- isr_count )
* Standing diagnostic: count of virtio-input ISR invocations since boot.
+11 -10
View File
@@ -17,7 +17,7 @@
#include "starkernel/i8042.h"
#endif
#if defined(__STARKERNEL__) && defined(ARCH_RISCV64)
#if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
#include "starkernel/virtio_input.h"
#endif
@@ -54,16 +54,17 @@ static void kbw_debug(VM *vm)
#endif
}
/* VKBD-EVENT ( -- code value -1 | 0 ): item 4.3.5c, riscv64 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. */
/* 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)
#if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
uint16_t code;
uint32_t value;
if (virtio_input_pop_event(&code, &value)) {
@@ -83,7 +84,7 @@ static void kbw_vevent(VM *vm)
extern volatile uint32_t g_virtio_input_isr_count;
static void kbw_vdebug(VM *vm)
{
#if defined(__STARKERNEL__) && defined(ARCH_RISCV64)
#if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
vm_push(vm, (cell_t)g_virtio_input_isr_count);
#else
vm_push(vm, 0);