riscv64: PLIC base address DTB-discovered, QEMU-virt constant as fallback (§V.3 item 3)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

plic_init() now takes boot_info->dtb (threaded through apic_init()) and
tries fdt_find_node_by_compatible(dtb, "sifive,plic-1.0.0") ->
fdt_find_prop_in_node(..., "reg", ...) before falling back to the
QEMU-virt-specific constant it previously hardcoded unconditionally.
Reuses the node-scoped DTB lookup primitive built for the aarch64 GIC
base fix unchanged. s_plic_base is now a runtime uintptr_t, same shape
as apic.c's s_gicd_base/s_gicc_base.

This system's QEMU/UEFI riscv64 firmware does not forward a DTB to the
guest (timer.c's own timebase-frequency read falls back too, confirmed
in this boot's own log), so only the no-DTB fallback branch is exercised
here -- the success branch (a real DTB with a matching PLIC node) stays
unverified until real Milk-V Mars hardware. FABRIC-3.md's first-drafted
claim that the success branch would run (based on a stale comment in
plic.c's own pre-fix header) was checked against the actual log and
corrected before this commit.

3-arch acceptance: amd64/aarch64 don't compile these files, so their
runs are non-regression on untouched files only. riscv64's own boot log
confirms the fallback path prints exactly as designed and boot reaches
zuse)ok> unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
Robert Allan James
2026-09-05 00:56:25 -04:00
co-authored by Claude Sonnet 5
parent 90ee8deb6d
commit 9b6de5d6c7
12 changed files with 27711 additions and 39 deletions
+29 -14
View File
@@ -708,26 +708,41 @@ Traced against real code before writing this, same discipline as §IV.3: `pci_in
session's own hypervisor-detection check) — no further work needed there; it was built DTB- session's own hypervisor-detection check) — no further work needed there; it was built DTB-
first from the start, unlike aarch64's timer which needed a new ACPI-based check today. first from the start, unlike aarch64's timer which needed a new ACPI-based check today.
**One real, already-flagged risk found while tracing this**: `arch/riscv64/apic.c`'s own doc **One real, already-flagged risk found while tracing this — FIXED 2026-09-05, see item 3
comment says the PLIC base address is "a constant, not discovered from `boot_info->dtb`" — below.** `arch/riscv64/apic.c`'s own doc comment said the PLIC base address was "a constant,
and `arch/riscv64/plic.c`'s own doc comment (predating this document) already warned not discovered from `boot_info->dtb`" — and `arch/riscv64/plic.c`'s own doc comment
`PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-specific... not assumed stable across" other (predating this document) already warned `PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-
configurations. That warning becomes concrete now: the JH7110's real PLIC address on the Mars specific... not assumed stable across" other configurations. That warning was concrete: the
is not confirmed to match QEMU-virt's, and the interrupt controller will not work correctly if JH7110's real PLIC address on the Mars was not confirmed to match QEMU-virt's, and the
it doesn't. This is a real punch-list item, not a hypothetical. interrupt controller would not have worked correctly if it didn't.
1. **Entry stub**: new native riscv64 entry point at `0x40000000` (§V.1), receiving `a0`=hart 1. **Entry stub**: new native riscv64 entry point at `0x40000000` (§V.1), receiving `a0`=hart
ID, `a1`=DTB pointer directly (now-confirmed SBI convention) — no UEFI, no PE loader. ID, `a1`=DTB pointer directly (now-confirmed SBI convention) — no UEFI, no PE loader.
2. **DTB → `BootInfo` constructor**: same shape as aarch64's (§IV.3 item 2) — `dtb`=real 2. **DTB → `BootInfo` constructor**: same shape as aarch64's (§IV.3 item 2) — `dtb`=real
pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from
`/chosen`/`bootargs`. `/chosen`/`bootargs`.
3. **PLIC base address: make it DTB-discovered**, not the current QEMU-virt-specific 3. **PLIC base address: make it DTB-discovered — DONE 2026-09-05.** `plic_init()`
constant — the one concrete, already-flagged risk above. `fdt.c`'s node-scoped lookup (`arch/riscv64/plic.c`) now takes `boot_info->dtb`, passed through from `apic_init()`
extension (§IV.3 item 4, **DONE 2026-09-04**, shared with the Pi 5's UART/mailbox (`arch/riscv64/apic.c`), and tries `fdt_find_node_by_compatible(dtb, "sifive,plic-1.0.0")`
addresses) is the primitive this calls `fdt_find_prop_in_node(..., "reg", ...)` before falling back to the QEMU-virt constant —
(`fdt_find_node_by_compatible(fdt, "sifive,plic-1.0.0")``fdt_find_prop_in_node(..., "reg", ...)`, exactly the primitive built for the aarch64 GIC base fix (§IV.3 item 7), reused unchanged.
plausible compatible string, not yet confirmed against the Mars's real DTB) — the actual `s_plic_base` is now a runtime `uintptr_t`, not a `#define`, same shape as apic.c's
PLIC-init call site update is still open, only the primitive it needs now exists. `s_gicd_base`/`s_gicc_base`. **Correction, same day**: this document's own text at this
point originally predicted the success branch would run under QEMU, on the strength of a
stale claim in `plic.c`'s own pre-fix header ("riscv64 DTB access does work, unlike
aarch64") — checked against the actual boot log rather than trusted, and it doesn't hold
for this system's QEMU/UEFI riscv64 firmware specifically: `timer.c`'s own
`timebase-frequency` read falls back too (`"Timer: RISC-V time CSR @ 10000000 Hz
(FALLBACK, no devicetree)"`), and the new PLIC line confirms the same thing —
`"PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000)"`. So this
fix is evidentially in the same position as the GIC fix: the guard (no-DTB fallback) is
exercised and correct, the success branch (a real DTB with a matching PLIC node) remains
unverified until real hardware. The stale claim that misled this paragraph's first draft
lived only in `plic.c`'s old header and was already replaced by this fix's own comment —
not re-introduced. The Mars's real JH7110 `reg` value stays unconfirmed either way — the
compatible string is architectural (SiFive PLIC-1.0.0 is a standard binding), not
board-specific, but no
hardware exists yet to check the Mars's own DTB against it.
4. **Framebuffer for HDMI output**: JH7110's display path is genuinely unresearched this 4. **Framebuffer for HDMI output**: JH7110's display path is genuinely unresearched this
pass — unlike the Pi 5's mailbox interface (well-documented, reused across many Pi bare- pass — unlike the Pi 5's mailbox interface (well-documented, reused across many Pi bare-
metal projects), no equivalent research done yet for JH7110's own display controller. metal projects), no equivalent research done yet for JH7110's own display controller.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-05T04:15:27Z --> <!-- Generated by mkcapsule --manifest 2026-09-05T04:53:23Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
+11 -1
View File
@@ -14,6 +14,11 @@
* at all before this item. Pure substrate: this item wires the mechanism * at all before this item. Pure substrate: this item wires the mechanism
* (claim/dispatch/complete) with no permanent source enabled by default; * (claim/dispatch/complete) with no permanent source enabled by default;
* a real consumer (4.3.5b, virtio-keyboard) enables its own source later. * a real consumer (4.3.5b, virtio-keyboard) enables its own source later.
*
* @c plic_init() takes the boot DTB as of FABRIC-3.md §V.3 item 3's fix,
* 2026-09-05: the base address was previously a QEMU-virt-specific
* hardcoded constant, unconditionally wrong on the Milk-V Mars's real
* JH7110 PLIC. See @c plic.c's file header for the discovery mechanism.
*/ */
#ifndef STARKERNEL_PLIC_H #ifndef STARKERNEL_PLIC_H
@@ -28,9 +33,14 @@
* Also sets sie.SEIE. Does not touch sstatus.SIE -- arch_enable_interrupts() * Also sets sie.SEIE. Does not touch sstatus.SIE -- arch_enable_interrupts()
* still owns that, same as the timer. * still owns that, same as the timer.
* *
* @param dtb Candidate devicetree blob (@c BootInfo->dtb); NULL-safe. When
* a real @c "sifive,plic-1.0.0" node is found, the PLIC base
* address is read from its @c reg property; otherwise falls
* back to the QEMU-virt-machine constant, unchanged from this
* function's previous unconditional behaviour.
* @return 0 on success. * @return 0 on success.
*/ */
int plic_init(void); int plic_init(const void *dtb);
/** Set a source's interrupt priority (1-7; 0 means "never interrupt"). */ /** Set a source's interrupt priority (1-7; 0 means "never interrupt"). */
void plic_set_priority(uint32_t irq, uint32_t priority); void plic_set_priority(uint32_t irq, uint32_t priority);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -160,15 +160,15 @@ void riscv64_timer_rearm(void)
* context's threshold, and enables @c sie.SEIE. No source is enabled here; * 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). * that is each future consumer's own job (4.3.5c for virtio-keyboard).
* *
* @param boot_info Kernel boot information; unused (PLIC base is a verified * @param boot_info Kernel boot information; @c dtb is passed through to
* constant, not discovered from @c boot_info->dtb -- see * @c plic_init() for real-hardware PLIC base discovery as
* plic.c's file header for why). * of the 2026-09-05 fix (FABRIC-3.md §V.3 item 3) -- see
* plic.c's file header for the discovery mechanism.
* @return 0 always. * @return 0 always.
*/ */
int apic_init(BootInfo *boot_info) int apic_init(BootInfo *boot_info)
{ {
(void)boot_info; plic_init(boot_info ? boot_info->dtb : (void *) 0);
plic_init();
return 0; return 0;
} }
+95 -18
View File
@@ -12,8 +12,8 @@
* architectural, not board-specific, same status as the GICD/GICC register * architectural, not board-specific, same status as the GICD/GICC register
* *offsets* item 0.6 cross-checked against the Linux arm-gic header. * *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 * PLIC_BASE_QEMU_DEFAULT and PLIC_CONTEXT_S are QEMU-virt-specific and were
* hardcoded from memory: verified 2026-08-08 via * NOT hardcoded from memory: verified 2026-08-08 via
* `qemu-system-riscv64 -machine virt,dumpdtb=...`, decoded with a script * `qemu-system-riscv64 -machine virt,dumpdtb=...`, decoded with a script
* using this tree's own fdt.c parsing logic (not dtc, which is not * using this tree's own fdt.c parsing logic (not dtc, which is not
* installed). The real devicetree shows: * installed). The real devicetree shows:
@@ -28,34 +28,111 @@
* Confirmed for this exact QEMU 10.2.1 build; not assumed stable across * 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. * versions, same caveat item 0.6 recorded for its own dumped values.
* *
* No live in-kernel node-scoped devicetree lookup here even though * DTB-discovered base address, as of FABRIC-3.md §V.3 item 3's fix,
* riscv64 DTB access does work (unlike aarch64): fdt.c's fdt_find_prop() * 2026-09-05: the node-scoped ("reg" is not globally unique the way
* is a flat, whole-tree search by property *name*, which is exactly right * "timebase-frequency" is) devicetree lookup this file's own comment
* for a globally-unique name like "timebase-frequency" (already used by * previously flagged as "a bigger addition than this item's stated scope"
* timer.c) but wrong for "reg" -- that name recurs on nearly every node, * was subsequently built anyway -- `fdt_find_node_by_compatible()` +
* so a flat search would return the wrong node's base address. Finding * `fdt_find_prop_in_node()` (`fdt.c`/`fdt.h`), added for the aarch64 GIC
* PLIC's own reg specifically needs node-scoped (by-compatible) lookup, * base fix (FABRIC-3.md §IV.3 item 7) and reused here unchanged.
* which does not exist in fdt.c and is a bigger addition than this item's * `plic_base_from_dtb()` below tries `"sifive,plic-1.0.0"` first; on
* stated scope (PLIC programming, not a general devicetree query engine). * failure (no DTB, e.g. this system's QEMU/UEFI firmware -- same
* `fdt_valid()` failure mode apic.c's GIC discovery already documents) it
* falls back to the QEMU constant above, unchanged from this function's
* previous unconditional behaviour. Not yet confirmed against the Milk-V
* Mars's real DTB -- the compatible string is architectural (SiFive
* PLIC-1.0.0 is the standard binding), not board-specific, but the actual
* JH7110 `reg` value is unverified until real hardware arrives (2026-09-17).
*/ */
#include <stdint.h> #include <stdint.h>
#include "starkernel/plic.h" #include "starkernel/plic.h"
#include "starkernel/fdt.h"
#include "console.h" #include "console.h"
#define PLIC_BASE 0x0C000000UL #define PLIC_BASE_QEMU_DEFAULT 0x0C000000UL
#define PLIC_CONTEXT_S 1u /* S-mode, hart 0 -- verified, see file header */ #define PLIC_CONTEXT_S 1u /* S-mode, hart 0 -- verified, see file header */
#define PLIC_PRIORITY(irq) (PLIC_BASE + 4u * (irq)) /* Runtime PLIC base address -- set once by plic_init(), defaulting to the
#define PLIC_ENABLE(ctx) (PLIC_BASE + 0x2000u + 0x80u * (ctx)) * QEMU constant above until/unless plic_base_from_dtb() overrides it.
#define PLIC_THRESHOLD(ctx) (PLIC_BASE + 0x200000u + 0x1000u * (ctx)) * uintptr_t, not a #define, precisely because this is no longer a
#define PLIC_CLAIM(ctx) (PLIC_BASE + 0x200004u + 0x1000u * (ctx)) * compile-time constant on this path (same shape as aarch64 apic.c's
* s_gicd_base/s_gicc_base). */
static uintptr_t s_plic_base = PLIC_BASE_QEMU_DEFAULT;
#define PLIC_PRIORITY(irq) (s_plic_base + 4u * (irq))
#define PLIC_ENABLE(ctx) (s_plic_base + 0x2000u + 0x80u * (ctx))
#define PLIC_THRESHOLD(ctx) (s_plic_base + 0x200000u + 0x1000u * (ctx))
#define PLIC_CLAIM(ctx) (s_plic_base + 0x200004u + 0x1000u * (ctx))
static inline volatile uint32_t *reg32(uintptr_t addr) { static inline volatile uint32_t *reg32(uintptr_t addr) {
return (volatile uint32_t *)addr; return (volatile uint32_t *)addr;
} }
int plic_init(void) { /**
* @brief Read one big-endian 32-bit cell from a devicetree property blob.
*
* Duplicated from aarch64 apic.c's identical helper rather than shared --
* same "a few lines is simpler than a new shared dependency" precedent
* that file's own comment already set for this exact situation.
*/
static uint32_t be32_cell(const unsigned char *b) {
return ((uint32_t) b[0] << 24) | ((uint32_t) b[1] << 16) |
((uint32_t) b[2] << 8) | (uint32_t) b[3];
}
/**
* @brief Read one two-cell (64-bit) big-endian address from a `reg` entry.
*
* QEMU-virt's `plic` node's own dumped `reg = <0x0 0xc000000 0x0 0x600000>`
* (see file header) is address(2 cells) + size(2 cells) = 4 cells total,
* same shape as aarch64's `gicv2` node -- reads only the two address
* cells, ignoring the size pair that follows.
*/
static uint64_t be64_addr_cell_pair(const unsigned char *b) {
return ((uint64_t) be32_cell(b) << 32) | (uint64_t) be32_cell(b + 4);
}
/**
* @brief Try to discover the real PLIC base address from the DTB.
*
* Looks up the `"sifive,plic-1.0.0"` node and reads its `reg` property's
* first (address) cell pair. Returns 0 (leaving @p base_out untouched) if
* @p dtb is not a valid FDT, no matching node exists, or `reg` is shorter
* than one address-cell pair -- the caller keeps the QEMU constant default
* in that case, exactly as before this function existed.
*
* @param dtb Candidate devicetree blob (@c BootInfo->dtb); NULL-safe.
* @param base_out Receives the PLIC base address on success.
* @return 1 on success, 0 if discovery failed for any reason.
*/
static int plic_base_from_dtb(const void *dtb, uintptr_t *base_out) {
const void *node;
const unsigned char *reg;
uint32_t len;
if (!fdt_valid(dtb)) return 0;
node = fdt_find_node_by_compatible(dtb, "sifive,plic-1.0.0");
if (!node) return 0;
reg = (const unsigned char *) fdt_find_prop_in_node(dtb, node, "reg", &len);
if (!reg || len < 8u) return 0;
*base_out = (uintptr_t) be64_addr_cell_pair(reg);
return 1;
}
int plic_init(const void *dtb) {
if (plic_base_from_dtb(dtb, &s_plic_base)) {
console_puts("PLIC: base discovered from DTB (0x");
for (int s = 60; s >= 0; s -= 4)
console_putc("0123456789abcdef"[(s_plic_base >> s) & 0xF]);
console_println(")");
} else {
console_println("PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000)");
}
/* Threshold 0 = maximally permissive. Safe: nothing is enabled at any /* Threshold 0 = maximally permissive. Safe: nothing is enabled at any
* source yet, so nothing can actually reach claim() until a caller * source yet, so nothing can actually reach claim() until a caller
* explicitly enables it. */ * explicitly enables it. */
@@ -63,7 +140,7 @@ int plic_init(void) {
__asm__ volatile ("csrs sie, %0" :: "r"(1UL << 9) : "memory"); /* SEIE */ __asm__ volatile ("csrs sie, %0" :: "r"(1UL << 9) : "memory"); /* SEIE */
console_println("PLIC: init (base=0x0c000000, S-mode context 1, threshold=0)"); console_println("PLIC: init (S-mode context 1, threshold=0)");
return 0; return 0;
} }