riscv64: virtio-keyboard-pci, interrupt-driven keyboard input (item 4.3.5c)
Punch list §25 item 4.3.5c complete. Amended from a nonexistent MMIO transport to PCI (matching the board's actual virtio-blk-pci precedent). New virtio-input driver: eventq with pre-posted buffers, PLIC source computed at runtime from PCI slot/pin (derived live from this host's QEMU riscv64 DTB), mandatory ISR-status read, PCI interrupt-disable-bit check. New VKBD-EVENT/VKBD-DEBUG FORTH words. Verified with a real QEMU sendkey keypress: exact KEY_A/press match, two real interrupts serviced, zero exceptions. Three-arch acceptance boot clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5f4df673c1
commit
8251aebcf8
@@ -24,6 +24,14 @@ extern void riscv64_install_vectors(void);
|
||||
* convention as riscv64_install_vectors above. */
|
||||
extern void riscv64_timer_rearm(void);
|
||||
|
||||
/* Defined in virtio_input.c (item 4.3.5c). g_virtio_input_plic_source is 0
|
||||
* (plic_claim()'s own "nothing pending" sentinel) until
|
||||
* virtio_input_find_keyboard() finds a real device and computes its
|
||||
* routed source -- so this branch is inert on any boot where the device
|
||||
* isn't present, same as the generic dispatch was inert before 4.3.5c. */
|
||||
extern uint32_t g_virtio_input_plic_source;
|
||||
extern void virtio_input_isr(void);
|
||||
|
||||
/* scause cause codes for supervisor-mode interrupts (RISC-V Privileged Spec
|
||||
* §4.1.9, Table "Supervisor cause register values"). */
|
||||
#define SCAUSE_INTERRUPT_BIT (1ULL << 63)
|
||||
@@ -51,14 +59,12 @@ volatile uint32_t g_plic_claim_count = 0;
|
||||
* permanently and silently. Ordering it ahead of @c heartbeat_tick() means a
|
||||
* fault in the bookkeeping cannot also cost the next tick.
|
||||
*
|
||||
* Supervisor external (cause 9, item 4.3.5b): claim/dispatch/complete
|
||||
* through the PLIC. No source is enabled by default (see plic.c), so this
|
||||
* branch is normally never reached; a future consumer enabling its own
|
||||
* source is what makes it fire. Dispatch here is deliberately generic --
|
||||
* this item is pure PLIC substrate, not a device driver -- a future
|
||||
* consumer (4.3.5c) is expected to extend this with real per-source
|
||||
* handling, the same way i8042's keyboard branch sits in amd64's
|
||||
* isr_common_handler().
|
||||
* Supervisor external (cause 9): claim/dispatch/complete through the PLIC
|
||||
* (substrate, item 4.3.5b). Source 10 (UART) stays unclaimed by anything
|
||||
* here -- 4.3.5b's own synthetic-interrupt self-test that used it was run
|
||||
* once and reverted, not a standing consumer. virtio-input (item 4.3.5c)
|
||||
* is the first real per-source dispatch, the same shape as i8042's
|
||||
* keyboard branch in amd64's isr_common_handler().
|
||||
*
|
||||
* Any other cause is ignored rather than fatal: an unexpected-but-harmless
|
||||
* asynchronous interrupt should not take the kernel down.
|
||||
@@ -76,6 +82,9 @@ void riscv64_interrupt_handler(uint64_t scause)
|
||||
uint32_t irq = plic_claim();
|
||||
if (irq != 0) {
|
||||
g_plic_claim_count++;
|
||||
if (irq == g_virtio_input_plic_source) {
|
||||
virtio_input_isr();
|
||||
}
|
||||
plic_complete(irq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
|
||||
#include "starkernel/repl.h"
|
||||
#include "starkernel/pci.h"
|
||||
#include "starkernel/virtio_blk.h"
|
||||
#include "starkernel/virtio_input.h"
|
||||
#include "block_subsystem.h"
|
||||
#include "vm.h" /* DictEntry, vm_find_word, ACL_MODE_STRICT */
|
||||
#include "version.h"
|
||||
@@ -584,6 +585,14 @@ static void kernel_main_deep(BootInfo *boot_info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* item 4.3.5c: virtio-keyboard-pci, riscv64 only today. Unconditional
|
||||
* call site, same as virtio_blk_find_artemis() above -- the function
|
||||
* itself no-ops with a console message on architectures/boards where
|
||||
* the device isn't present or interrupt routing isn't implemented yet
|
||||
* (see virtio_input.c's enable_interrupt_route()), so dictionary/boot
|
||||
* sequence parity across all three architectures is unaffected. */
|
||||
(void)virtio_input_find_keyboard();
|
||||
|
||||
/* Copy capsule directory header to heap (has pointer field needing update) */
|
||||
CapsuleDirHeader *live_dir = (CapsuleDirHeader *)kmalloc(sizeof(CapsuleDirHeader));
|
||||
if (!live_dir) {
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
* virtio_input.c — Virtio 1.0 input device driver for StarKernel (item 4.3.5c)
|
||||
*
|
||||
* Modern virtio 1.0 interface only (device ID 0x1052). No legacy fallback
|
||||
* exists for virtio-input (unlike virtio-blk's 0x1001) -- input postdates
|
||||
* the legacy 0.9.5 spec.
|
||||
*
|
||||
* Split virtqueue (eventq, index 0) with pre-posted device-writable
|
||||
* 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.
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* 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).
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "virtio_input.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "starkernel/pci.h"
|
||||
#include "starkernel/virtio_input.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "console.h"
|
||||
|
||||
#if defined(ARCH_RISCV64)
|
||||
#include "starkernel/plic.h"
|
||||
#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
|
||||
* here (virtio_blk.c defines the constant but never reads it -- blk polls
|
||||
* and never needed to; this is the first interrupt-driven virtio-pci
|
||||
* device in this tree).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define VIRTIO_PCI_CAP_VENDOR_ID 0x09u
|
||||
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1u
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2u
|
||||
#define VIRTIO_PCI_CAP_ISR_CFG 3u
|
||||
|
||||
#define VCAP_OFF_CAP_VNDR 0u
|
||||
#define VCAP_OFF_CAP_NEXT 1u
|
||||
#define VCAP_OFF_CFG_TYPE 3u
|
||||
#define VCAP_OFF_BAR 4u
|
||||
#define VCAP_OFF_OFFSET 8u
|
||||
#define VCAP_OFF_LENGTH 12u
|
||||
#define VCAP_OFF_NOTIFY_MULT 16u
|
||||
|
||||
/* virtio common config (MMIO layout, virtio 1.0 §4.1.4.3) -- not packed,
|
||||
* same rationale as virtio_blk.c's VirtioCommonCfg (riscv64 strict
|
||||
* alignment; QEMU's common-cfg MMIO handler mishandles byte reads of the
|
||||
* 16-bit fields otherwise). */
|
||||
typedef struct {
|
||||
volatile uint32_t device_feature_select;
|
||||
volatile uint32_t device_feature;
|
||||
volatile uint32_t driver_feature_select;
|
||||
volatile uint32_t driver_feature;
|
||||
volatile uint16_t config_msix_vector;
|
||||
volatile uint16_t num_queues;
|
||||
volatile uint8_t device_status;
|
||||
volatile uint8_t config_generation;
|
||||
volatile uint16_t queue_select;
|
||||
volatile uint16_t queue_size;
|
||||
volatile uint16_t queue_msix_vector;
|
||||
volatile uint16_t queue_enable;
|
||||
volatile uint16_t queue_notify_off;
|
||||
volatile uint64_t queue_desc;
|
||||
volatile uint64_t queue_driver;
|
||||
volatile uint64_t queue_device;
|
||||
volatile uint16_t queue_notify_data;
|
||||
volatile uint16_t queue_reset;
|
||||
} VirtioCommonCfg;
|
||||
|
||||
#define VIRTIO_STATUS_ACKNOWLEDGE 0x01u
|
||||
#define VIRTIO_STATUS_DRIVER 0x02u
|
||||
#define VIRTIO_STATUS_DRIVER_OK 0x04u
|
||||
#define VIRTIO_STATUS_FEATURES_OK 0x08u
|
||||
#define VIRTIO_STATUS_FAILED 0x80u
|
||||
|
||||
#define VIRTIO_F_VERSION_1 (1ULL << 32)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Split virtqueue structures (virtio 1.0 §2.6) -- duplicated shape from
|
||||
* virtio_blk.c, sized for the event queue instead.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define EVENTQ_SIZE 8u /* power of 2; headroom for several queued events */
|
||||
|
||||
#define VRING_DESC_F_WRITE 2u
|
||||
|
||||
typedef struct {
|
||||
uint64_t addr;
|
||||
uint32_t len;
|
||||
uint16_t flags;
|
||||
uint16_t next;
|
||||
} VirtqDesc;
|
||||
|
||||
typedef struct {
|
||||
uint16_t flags;
|
||||
uint16_t idx;
|
||||
uint16_t ring[EVENTQ_SIZE];
|
||||
uint16_t used_event;
|
||||
} VirtqAvail;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
uint32_t len;
|
||||
} VirtqUsedElem;
|
||||
|
||||
typedef struct {
|
||||
uint16_t flags;
|
||||
uint16_t idx;
|
||||
VirtqUsedElem ring[EVENTQ_SIZE];
|
||||
uint16_t avail_event;
|
||||
} VirtqUsed;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Diagnostic ring -- consumed by keyboard_words.c's VKBD-EVENT, same
|
||||
* relationship KBD-SCAN has to i8042's ring (raw hardware-boundary peek,
|
||||
* no scancode-to-ASCII layout, no REPL wiring -- that's 4.3.5f/M8).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define DIAG_RING_SIZE 16u
|
||||
|
||||
typedef struct {
|
||||
uint16_t code;
|
||||
uint32_t value;
|
||||
} DiagEvent;
|
||||
|
||||
static DiagEvent g_diag_ring[DIAG_RING_SIZE];
|
||||
static volatile uint32_t g_diag_head = 0;
|
||||
static volatile uint32_t g_diag_tail = 0;
|
||||
|
||||
static void diag_push(uint16_t code, uint32_t value)
|
||||
{
|
||||
uint32_t next = (g_diag_head + 1u) % DIAG_RING_SIZE;
|
||||
if (next == g_diag_tail) return; /* full: drop, same as i8042's bounded ring */
|
||||
g_diag_ring[g_diag_head].code = code;
|
||||
g_diag_ring[g_diag_head].value = value;
|
||||
g_diag_head = next;
|
||||
}
|
||||
|
||||
int virtio_input_pop_event(uint16_t *code, uint32_t *value)
|
||||
{
|
||||
if (g_diag_tail == g_diag_head) return 0;
|
||||
if (code) *code = g_diag_ring[g_diag_tail].code;
|
||||
if (value) *value = g_diag_ring[g_diag_tail].value;
|
||||
g_diag_tail = (g_diag_tail + 1u) % DIAG_RING_SIZE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Standing diagnostic, same shape as g_plic_claim_count/g_i8042_isr_count. */
|
||||
volatile uint32_t g_virtio_input_isr_count = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Driver state
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
VirtioCommonCfg *common;
|
||||
volatile uint16_t *notify;
|
||||
uint32_t notify_off_mult;
|
||||
uint16_t queue_notify_off;
|
||||
volatile uint8_t *isr_status; /* VIRTIO_PCI_CAP_ISR_CFG -- read to
|
||||
* deassert the (level-triggered) line */
|
||||
|
||||
VirtqDesc *desc;
|
||||
VirtqAvail *avail;
|
||||
VirtqUsed *used;
|
||||
|
||||
VirtioInputEvent *bufs; /* EVENTQ_SIZE event buffers, device-writable */
|
||||
uint16_t avail_idx;
|
||||
uint16_t last_used_idx;
|
||||
} VirtInputState;
|
||||
|
||||
static VirtInputState g_vinput;
|
||||
static int g_vinput_ready = 0;
|
||||
|
||||
/* PLIC source (riscv64) this device's INTx routes to, computed at init.
|
||||
* 0 = invalid/not found -- matches plic_claim()'s own "0 = nothing
|
||||
* pending" sentinel, so an unfound device can never accidentally match a
|
||||
* real claim. Checked by arch/riscv64/interrupts.c's dispatch. */
|
||||
uint32_t g_virtio_input_plic_source = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capability walker -- duplicated from virtio_blk.c, see file header.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static void *walk_virtio_caps(const PciDevice *d, uint8_t cap_type,
|
||||
uint32_t *extra_out) {
|
||||
uint8_t cap_ptr = pci_read8(d, (uint16_t)PCI_CFG_CAP_PTR) & 0xFCu;
|
||||
if (!cap_ptr) return NULL;
|
||||
|
||||
int limit = 48;
|
||||
while (cap_ptr && limit--) {
|
||||
uint8_t vndr = pci_read8(d, cap_ptr + (uint16_t)VCAP_OFF_CAP_VNDR);
|
||||
uint8_t next = pci_read8(d, cap_ptr + (uint16_t)VCAP_OFF_CAP_NEXT);
|
||||
uint8_t ctype = pci_read8(d, cap_ptr + (uint16_t)VCAP_OFF_CFG_TYPE);
|
||||
|
||||
if (vndr == (uint8_t)VIRTIO_PCI_CAP_VENDOR_ID && ctype == cap_type) {
|
||||
uint8_t bar = pci_read8 (d, cap_ptr + (uint16_t)VCAP_OFF_BAR);
|
||||
uint32_t offset = pci_read32(d, cap_ptr + (uint16_t)VCAP_OFF_OFFSET);
|
||||
uint32_t length = pci_read32(d, cap_ptr + (uint16_t)VCAP_OFF_LENGTH);
|
||||
|
||||
if (bar > 5u) { cap_ptr = next & 0xFCu; continue; }
|
||||
|
||||
uint64_t bar_base = pci_bar(d, (int)bar);
|
||||
if (!bar_base) { cap_ptr = next & 0xFCu; continue; }
|
||||
|
||||
if (pci_map_bar(bar_base, (uint64_t)length + offset) != 0) {
|
||||
cap_ptr = next & 0xFCu; continue;
|
||||
}
|
||||
|
||||
if (extra_out && cap_type == VIRTIO_PCI_CAP_NOTIFY_CFG) {
|
||||
*extra_out = pci_read32(d, cap_ptr + (uint16_t)VCAP_OFF_NOTIFY_MULT);
|
||||
}
|
||||
|
||||
return (void *)(uintptr_t)(bar_base + offset);
|
||||
}
|
||||
|
||||
cap_ptr = next & 0xFCu;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void wmb(void) {
|
||||
/* Compiler barrier only, same as virtio_blk.c -- reported not fixed
|
||||
* there (FABRIC.md item 4.3.5e note); this item is the first place an
|
||||
* interrupt-driven used-ring (device writes concurrently with the
|
||||
* driver's re-post loop) leans on the ordering harder than blk's
|
||||
* synchronous polled loop ever did. */
|
||||
__asm__ volatile("" : : : "memory");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 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.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#if defined(ARCH_RISCV64)
|
||||
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 source = 32u + ((slot + (uint32_t)pin - 1u) % 4u);
|
||||
|
||||
plic_set_priority(source, 1u);
|
||||
plic_enable(source);
|
||||
|
||||
g_virtio_input_plic_source = source;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int enable_interrupt_route(const PciDevice *pci)
|
||||
{
|
||||
(void)pci;
|
||||
console_println("virtio-input: interrupt routing not implemented on this arch");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Device initialisation -- status/feature negotiation mirrors
|
||||
* vblk_init_device() (virtio_blk.c:361-499); differences noted inline.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static int vinput_init_device(const PciDevice *pci) {
|
||||
VirtInputState *s = &g_vinput;
|
||||
|
||||
pci_enable(pci);
|
||||
|
||||
/* pci_enable() only sets IO/MEM/BUS_MASTER -- it never clears PCI
|
||||
* COMMAND bit 10 (Interrupt Disable). If firmware left it set, INTx
|
||||
* never asserts and every step below "succeeds" while producing zero
|
||||
* real interrupts (FABRIC.md item 4.3.5c/4.3.5e note). Check and clear
|
||||
* explicitly. */
|
||||
{
|
||||
uint16_t cmd = pci_read16(pci, (uint16_t)PCI_CFG_COMMAND);
|
||||
if (cmd & (uint16_t)PCI_CMD_INTX_DISABLE) {
|
||||
pci_write16(pci, (uint16_t)PCI_CFG_COMMAND,
|
||||
cmd & (uint16_t)~PCI_CMD_INTX_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t notify_mult = 0;
|
||||
VirtioCommonCfg *common = (VirtioCommonCfg *)
|
||||
walk_virtio_caps(pci, VIRTIO_PCI_CAP_COMMON_CFG, NULL);
|
||||
volatile uint16_t *notify = (volatile uint16_t *)
|
||||
walk_virtio_caps(pci, VIRTIO_PCI_CAP_NOTIFY_CFG, ¬ify_mult);
|
||||
volatile uint8_t *isr_status = (volatile uint8_t *)
|
||||
walk_virtio_caps(pci, VIRTIO_PCI_CAP_ISR_CFG, NULL);
|
||||
|
||||
if (!common || !notify || !isr_status) {
|
||||
console_println("virtio-input: cap walk failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
s->common = common;
|
||||
s->notify = notify;
|
||||
s->notify_off_mult = notify_mult;
|
||||
s->isr_status = isr_status;
|
||||
|
||||
common->device_status = 0;
|
||||
wmb();
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_ACKNOWLEDGE;
|
||||
wmb();
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER);
|
||||
wmb();
|
||||
|
||||
common->driver_feature_select = 1;
|
||||
wmb();
|
||||
common->driver_feature = (uint32_t)(VIRTIO_F_VERSION_1 >> 32);
|
||||
common->driver_feature_select = 0;
|
||||
wmb();
|
||||
common->driver_feature = 0;
|
||||
wmb();
|
||||
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE |
|
||||
VIRTIO_STATUS_DRIVER |
|
||||
VIRTIO_STATUS_FEATURES_OK);
|
||||
wmb();
|
||||
if (!(common->device_status & (uint8_t)VIRTIO_STATUS_FEATURES_OK)) {
|
||||
console_println("virtio-input: FEATURES_OK rejected");
|
||||
common->device_status = (uint8_t)VIRTIO_STATUS_FAILED;
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Queue 0 = eventq (device-to-driver key events). Queue 1 = statusq
|
||||
* (driver-to-device, e.g. LED state) -- not needed to read keypresses;
|
||||
* left unconfigured, consistent with this item's scope (input, not
|
||||
* output reports). Read num_queues back rather than assume 2. */
|
||||
if (common->num_queues < 1u) {
|
||||
console_println("virtio-input: no queues reported");
|
||||
return -2;
|
||||
}
|
||||
|
||||
common->queue_select = 0;
|
||||
wmb();
|
||||
uint16_t max_size = common->queue_size;
|
||||
if (max_size == 0u) {
|
||||
console_println("virtio-input: bad eventq size");
|
||||
return -2;
|
||||
}
|
||||
uint16_t qsize = (max_size < (uint16_t)EVENTQ_SIZE)
|
||||
? max_size : (uint16_t)EVENTQ_SIZE;
|
||||
common->queue_size = qsize;
|
||||
s->queue_notify_off = common->queue_notify_off;
|
||||
wmb();
|
||||
|
||||
common->config_msix_vector = 0xFFFFu;
|
||||
common->queue_msix_vector = 0xFFFFu;
|
||||
wmb();
|
||||
|
||||
size_t desc_bytes = (size_t)qsize * sizeof(VirtqDesc);
|
||||
size_t avail_bytes = sizeof(uint16_t) * 2u +
|
||||
(size_t)qsize * sizeof(uint16_t) + sizeof(uint16_t);
|
||||
size_t used_bytes = sizeof(uint16_t) * 2u +
|
||||
(size_t)qsize * sizeof(VirtqUsedElem) + sizeof(uint16_t);
|
||||
|
||||
s->desc = (VirtqDesc *) kmalloc_aligned(desc_bytes, 64);
|
||||
s->avail = (VirtqAvail *)kmalloc_aligned(avail_bytes, 2);
|
||||
s->used = (VirtqUsed *) kmalloc_aligned(used_bytes, 4);
|
||||
s->bufs = (VirtioInputEvent *)
|
||||
kmalloc_aligned((size_t)qsize * sizeof(VirtioInputEvent), 8);
|
||||
if (!s->desc || !s->avail || !s->used || !s->bufs) {
|
||||
console_println("virtio-input: alloc failed");
|
||||
return -2;
|
||||
}
|
||||
memset(s->desc, 0, desc_bytes);
|
||||
memset(s->avail, 0, avail_bytes);
|
||||
memset(s->used, 0, used_bytes);
|
||||
memset(s->bufs, 0, (size_t)qsize * sizeof(VirtioInputEvent));
|
||||
|
||||
s->avail_idx = 0;
|
||||
s->last_used_idx = 0;
|
||||
|
||||
common->queue_desc = (uint64_t)(uintptr_t)s->desc;
|
||||
common->queue_driver = (uint64_t)(uintptr_t)s->avail;
|
||||
common->queue_device = (uint64_t)(uintptr_t)s->used;
|
||||
wmb();
|
||||
common->queue_enable = 1;
|
||||
wmb();
|
||||
|
||||
/* Pre-post every buffer, device-writable, single descriptor each
|
||||
* (events are fixed 8-byte structs, no chaining needed) -- unlike
|
||||
* vblk_io()'s per-request chain-of-three, this posts the whole queue
|
||||
* up front and never waits synchronously; the device fills them
|
||||
* asynchronously as real input events arrive. */
|
||||
uint16_t i;
|
||||
for (i = 0; i < qsize; i++) {
|
||||
s->desc[i].addr = (uint64_t)(uintptr_t)&s->bufs[i];
|
||||
s->desc[i].len = (uint32_t)sizeof(VirtioInputEvent);
|
||||
s->desc[i].flags = (uint16_t)VRING_DESC_F_WRITE;
|
||||
s->desc[i].next = 0;
|
||||
|
||||
s->avail->ring[i] = i;
|
||||
}
|
||||
wmb();
|
||||
s->avail->idx = qsize;
|
||||
s->avail_idx = qsize;
|
||||
wmb();
|
||||
|
||||
uint16_t notify_idx = (uint16_t)(s->queue_notify_off *
|
||||
(s->notify_off_mult & 0xFFFFu));
|
||||
volatile uint16_t *doorbell = s->notify + notify_idx;
|
||||
*doorbell = 0;
|
||||
wmb();
|
||||
|
||||
if (enable_interrupt_route(pci) != 0) {
|
||||
console_println("virtio-input: interrupt route unavailable, device idle");
|
||||
return -2;
|
||||
}
|
||||
|
||||
common->device_status = (uint8_t)(VIRTIO_STATUS_ACKNOWLEDGE |
|
||||
VIRTIO_STATUS_DRIVER |
|
||||
VIRTIO_STATUS_FEATURES_OK |
|
||||
VIRTIO_STATUS_DRIVER_OK);
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtio_input_find_keyboard(void) {
|
||||
PciDevice pci;
|
||||
int found = pci_find_first(VIRTIO_INPUT_PCI_VENDOR_ID,
|
||||
VIRTIO_INPUT_PCI_DEVICE_ID, &pci);
|
||||
if (found != 0) {
|
||||
console_println("virtio-input: no device on PCI bus 0");
|
||||
return -1;
|
||||
}
|
||||
|
||||
console_println("virtio-input: found device");
|
||||
|
||||
int rc = vinput_init_device(&pci);
|
||||
if (rc != 0) return rc;
|
||||
|
||||
g_vinput_ready = 1;
|
||||
console_println("virtio-input: driver ready");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Interrupt path -- called from arch/riscv64/interrupts.c on a matching
|
||||
* PLIC claim (g_virtio_input_plic_source).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
void virtio_input_isr(void) {
|
||||
VirtInputState *s = &g_vinput;
|
||||
if (!g_vinput_ready) return;
|
||||
|
||||
g_virtio_input_isr_count++;
|
||||
|
||||
/* Mandatory: reading ISR status is what deasserts the (level-triggered,
|
||||
* per FABRIC.md §27.5.2's decoded interrupt-map flags) line. Skipping
|
||||
* this leaves the PLIC source's condition latched -- storm or hang,
|
||||
* not a subtle bug (same finding 4.3.5e's plan flags). */
|
||||
(void)*s->isr_status;
|
||||
|
||||
while (s->used->idx != s->last_used_idx) {
|
||||
uint16_t used_slot = s->last_used_idx % EVENTQ_SIZE;
|
||||
uint32_t desc_id = s->used->ring[used_slot].id;
|
||||
VirtioInputEvent *ev = &s->bufs[desc_id];
|
||||
|
||||
if (ev->type == (uint16_t)VIRTIO_INPUT_EV_KEY) {
|
||||
diag_push(ev->code, ev->value);
|
||||
}
|
||||
/* EV_SYN and other event types are received but not surfaced to
|
||||
* the diagnostic ring -- this item decodes EV_KEY specifically,
|
||||
* per its own scope; a full input-event model is later/REPL work. */
|
||||
|
||||
/* Re-post this buffer immediately -- same descriptor, still
|
||||
* device-writable, not re-initialised (already zeroed content
|
||||
* doesn't matter, the device overwrites it in full each time). */
|
||||
uint16_t avail_slot = s->avail_idx % EVENTQ_SIZE;
|
||||
s->avail->ring[avail_slot] = (uint16_t)desc_id;
|
||||
wmb();
|
||||
s->avail_idx++;
|
||||
s->avail->idx = s->avail_idx;
|
||||
wmb();
|
||||
|
||||
s->last_used_idx++;
|
||||
}
|
||||
|
||||
uint16_t notify_idx = (uint16_t)(s->queue_notify_off *
|
||||
(s->notify_off_mult & 0xFFFFu));
|
||||
volatile uint16_t *doorbell = s->notify + notify_idx;
|
||||
*doorbell = 0;
|
||||
wmb();
|
||||
}
|
||||
@@ -32,6 +32,14 @@
|
||||
* @par KBD-DEBUG ( -- isr_count spurious_count )
|
||||
* Standing diagnostic: count of real keyboard IRQs serviced and count of
|
||||
* 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.
|
||||
*
|
||||
* @par VKBD-DEBUG ( -- isr_count )
|
||||
* Standing diagnostic: count of virtio-input ISR invocations since boot.
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include "starkernel/i8042.h"
|
||||
#endif
|
||||
|
||||
#if defined(__STARKERNEL__) && defined(ARCH_RISCV64)
|
||||
#include "starkernel/virtio_input.h"
|
||||
#endif
|
||||
|
||||
/* KBD-SCAN ( -- c -1 | 0 ) */
|
||||
static void kbw_scan(VM *vm)
|
||||
{
|
||||
@@ -50,8 +54,46 @@ 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. */
|
||||
static void kbw_vevent(VM *vm)
|
||||
{
|
||||
#if defined(__STARKERNEL__) && defined(ARCH_RISCV64)
|
||||
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)
|
||||
vm_push(vm, (cell_t)g_virtio_input_isr_count);
|
||||
#else
|
||||
vm_push(vm, 0);
|
||||
#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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user