riscv64: PLIC bring-up, external-interrupt substrate (item 4.3.5b)
Punch list §25 item 4.3.5b complete. sie.SEIE enabled, PLIC threshold/claim/complete wired into the trap handler. Verified with a UART-loopback synthetic interrupt (PLIC has no software set-pending register, unlike GICv2): claim_count=1, last_irq=10, IIR confirms genuine receive-data cause, byte matched exactly. Self-test code run once for evidence then fully reverted, per Captain Bob's ruling; only the permanent substrate remains, no source enabled by default. Three-arch acceptance boot clean, zero exceptions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
36965cf812
commit
5f4df673c1
@@ -6,14 +6,17 @@
|
||||
|
||||
/**
|
||||
* apic.c (riscv64) - APIC stub. No Local APIC on RISC-V; interrupt
|
||||
* controller is PLIC (Platform-Level Interrupt Controller). These stubs
|
||||
* satisfy the common kernel interface; PLIC wiring is a later milestone.
|
||||
* controller is PLIC (Platform-Level Interrupt Controller). apic_init()
|
||||
* calls into plic.c for the real PLIC bring-up (item 4.3.5b); apic_eoi()
|
||||
* stays a no-op since PLIC completion happens per-source in the trap
|
||||
* handler (claim/complete), not via a single shared EOI register.
|
||||
*/
|
||||
|
||||
#include "apic.h"
|
||||
#include "uefi.h"
|
||||
#include "console.h"
|
||||
#include "timer.h"
|
||||
#include "starkernel/plic.h"
|
||||
#include <stdint.h>
|
||||
|
||||
static uint64_t s_timer_period_tsc = 0;
|
||||
@@ -149,20 +152,23 @@ void riscv64_timer_rearm(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Initialise the interrupt controller (RISC-V PLIC stub).
|
||||
* @brief Initialise the interrupt controller: PLIC bring-up (item 4.3.5b).
|
||||
*
|
||||
* On x86-64 this function configures the Local APIC. On RISC-V the
|
||||
* Platform-Level Interrupt Controller (PLIC) handles external interrupt
|
||||
* routing; PLIC bring-up is deferred to a later milestone. This stub
|
||||
* satisfies the common @c apic_init() call site in @c kernel_main()
|
||||
* and always returns success.
|
||||
* routing -- @c plic_init() (arch/riscv64/plic.c) maps it, sets the S-mode
|
||||
* context's threshold, and enables @c sie.SEIE. No source is enabled here;
|
||||
* that is each future consumer's own job (4.3.5c for virtio-keyboard).
|
||||
*
|
||||
* @param boot_info Kernel boot information (ACPI/PLIC base address); unused.
|
||||
* @param boot_info Kernel boot information; unused (PLIC base is a verified
|
||||
* constant, not discovered from @c boot_info->dtb -- see
|
||||
* plic.c's file header for why).
|
||||
* @return 0 always.
|
||||
*/
|
||||
int apic_init(BootInfo *boot_info)
|
||||
{
|
||||
(void)boot_info;
|
||||
plic_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "arch.h"
|
||||
#include "console.h"
|
||||
#include "starkernel/timer.h"
|
||||
#include "starkernel/plic.h"
|
||||
|
||||
volatile const char *g_sk_fault_word = (void *)0;
|
||||
|
||||
@@ -24,10 +25,18 @@ extern void riscv64_install_vectors(void);
|
||||
extern void riscv64_timer_rearm(void);
|
||||
|
||||
/* scause cause codes for supervisor-mode interrupts (RISC-V Privileged Spec
|
||||
* §4.1.9, Table "Supervisor cause register values"). Only the timer is used;
|
||||
* software (1) and external (9) interrupts are not enabled. */
|
||||
* §4.1.9, Table "Supervisor cause register values"). */
|
||||
#define SCAUSE_INTERRUPT_BIT (1ULL << 63)
|
||||
#define SCAUSE_S_TIMER 5ULL
|
||||
#define SCAUSE_S_EXTERNAL 9ULL
|
||||
|
||||
/* Count of real PLIC claims serviced since boot (item 4.3.5b). Cheap
|
||||
* standing diagnostic, same shape as amd64's g_i8042_isr_count/
|
||||
* g_spurious_count -- confirms the claim/dispatch/complete path is alive
|
||||
* without needing a live debugger. Nothing enables any PLIC source by
|
||||
* default, so this stays 0 unless a future consumer (or a live test)
|
||||
* explicitly calls plic_enable(). */
|
||||
volatile uint32_t g_plic_claim_count = 0;
|
||||
|
||||
/**
|
||||
* @brief Dispatch an asynchronous supervisor interrupt.
|
||||
@@ -42,9 +51,17 @@ extern void riscv64_timer_rearm(void);
|
||||
* 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().
|
||||
*
|
||||
* Any other cause is ignored rather than fatal: an unexpected-but-harmless
|
||||
* asynchronous interrupt should not take the kernel down, and none are
|
||||
* currently enabled to arrive.
|
||||
* asynchronous interrupt should not take the kernel down.
|
||||
*
|
||||
* @param scause Raw @c scause value, interrupt bit still set.
|
||||
*/
|
||||
@@ -55,6 +72,12 @@ void riscv64_interrupt_handler(uint64_t scause)
|
||||
if (cause == SCAUSE_S_TIMER) {
|
||||
riscv64_timer_rearm();
|
||||
heartbeat_tick();
|
||||
} else if (cause == SCAUSE_S_EXTERNAL) {
|
||||
uint32_t irq = plic_claim();
|
||||
if (irq != 0) {
|
||||
g_plic_claim_count++;
|
||||
plic_complete(irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* plic.c (riscv64) - Platform-Level Interrupt Controller driver
|
||||
*
|
||||
* Item 4.3.5b. Register layout (priority/pending/enable/threshold/claim-
|
||||
* complete) is the standard SiFive PLIC-1.0.0 layout QEMU implements --
|
||||
* architectural, not board-specific, same status as the GICD/GICC register
|
||||
* *offsets* item 0.6 cross-checked against the Linux arm-gic header.
|
||||
*
|
||||
* PLIC_BASE and PLIC_CONTEXT_S are QEMU-virt-specific and were NOT
|
||||
* hardcoded from memory: verified 2026-08-08 via
|
||||
* `qemu-system-riscv64 -machine virt,dumpdtb=...`, decoded with a script
|
||||
* using this tree's own fdt.c parsing logic (not dtc, which is not
|
||||
* installed). The real devicetree shows:
|
||||
* soc/plic@c000000: compatible = "sifive,plic-1.0.0","riscv,plic0",
|
||||
* reg = <0x0 0xc000000 0x0 0x600000>, riscv,ndev = 95
|
||||
* soc/plic@c000000: interrupts-extended = <&cpu0-intc 11 &cpu0-intc 9>
|
||||
* -- two contexts for hart 0: context 0 feeds cause 11 (MEIE, M-mode
|
||||
* external), context 1 feeds cause 9 (SEIE, S-mode external). Context
|
||||
* index is position in this list, confirmed rather than assumed.
|
||||
* soc/serial@10000000 (ns16550a): interrupts = <10>, interrupt-parent =
|
||||
* &plic -- PLIC source 10, matching console.c's existing NS16550_BASE.
|
||||
* Confirmed for this exact QEMU 10.2.1 build; not assumed stable across
|
||||
* versions, same caveat item 0.6 recorded for its own dumped values.
|
||||
*
|
||||
* No live in-kernel node-scoped devicetree lookup here even though
|
||||
* riscv64 DTB access does work (unlike aarch64): fdt.c's fdt_find_prop()
|
||||
* is a flat, whole-tree search by property *name*, which is exactly right
|
||||
* for a globally-unique name like "timebase-frequency" (already used by
|
||||
* timer.c) but wrong for "reg" -- that name recurs on nearly every node,
|
||||
* so a flat search would return the wrong node's base address. Finding
|
||||
* PLIC's own reg specifically needs node-scoped (by-compatible) lookup,
|
||||
* which does not exist in fdt.c and is a bigger addition than this item's
|
||||
* stated scope (PLIC programming, not a general devicetree query engine).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/plic.h"
|
||||
#include "console.h"
|
||||
|
||||
#define PLIC_BASE 0x0C000000UL
|
||||
#define PLIC_CONTEXT_S 1u /* S-mode, hart 0 -- verified, see file header */
|
||||
|
||||
#define PLIC_PRIORITY(irq) (PLIC_BASE + 4u * (irq))
|
||||
#define PLIC_ENABLE(ctx) (PLIC_BASE + 0x2000u + 0x80u * (ctx))
|
||||
#define PLIC_THRESHOLD(ctx) (PLIC_BASE + 0x200000u + 0x1000u * (ctx))
|
||||
#define PLIC_CLAIM(ctx) (PLIC_BASE + 0x200004u + 0x1000u * (ctx))
|
||||
|
||||
static inline volatile uint32_t *reg32(uintptr_t addr) {
|
||||
return (volatile uint32_t *)addr;
|
||||
}
|
||||
|
||||
int plic_init(void) {
|
||||
/* Threshold 0 = maximally permissive. Safe: nothing is enabled at any
|
||||
* source yet, so nothing can actually reach claim() until a caller
|
||||
* explicitly enables it. */
|
||||
*reg32(PLIC_THRESHOLD(PLIC_CONTEXT_S)) = 0;
|
||||
|
||||
__asm__ volatile ("csrs sie, %0" :: "r"(1UL << 9) : "memory"); /* SEIE */
|
||||
|
||||
console_println("PLIC: init (base=0x0c000000, S-mode context 1, threshold=0)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void plic_set_priority(uint32_t irq, uint32_t priority) {
|
||||
*reg32(PLIC_PRIORITY(irq)) = priority;
|
||||
}
|
||||
|
||||
void plic_enable(uint32_t irq) {
|
||||
volatile uint32_t *reg = reg32(PLIC_ENABLE(PLIC_CONTEXT_S) + 4u * (irq / 32u));
|
||||
*reg |= (1u << (irq % 32u));
|
||||
}
|
||||
|
||||
void plic_disable(uint32_t irq) {
|
||||
volatile uint32_t *reg = reg32(PLIC_ENABLE(PLIC_CONTEXT_S) + 4u * (irq / 32u));
|
||||
*reg &= ~(1u << (irq % 32u));
|
||||
}
|
||||
|
||||
uint32_t plic_claim(void) {
|
||||
return *reg32(PLIC_CLAIM(PLIC_CONTEXT_S));
|
||||
}
|
||||
|
||||
void plic_complete(uint32_t irq) {
|
||||
*reg32(PLIC_CLAIM(PLIC_CONTEXT_S)) = irq;
|
||||
}
|
||||
Reference in New Issue
Block a user