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
+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;
* 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
* constant, not discovered from @c boot_info->dtb -- see
* plic.c's file header for why).
* @param boot_info Kernel boot information; @c dtb is passed through to
* @c plic_init() for real-hardware PLIC base discovery as
* 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.
*/
int apic_init(BootInfo *boot_info)
{
(void)boot_info;
plic_init();
plic_init(boot_info ? boot_info->dtb : (void *) 0);
return 0;
}
+95 -18
View File
@@ -12,8 +12,8 @@
* 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
* PLIC_BASE_QEMU_DEFAULT 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:
@@ -28,34 +28,111 @@
* 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).
* DTB-discovered base address, as of FABRIC-3.md §V.3 item 3's fix,
* 2026-09-05: the node-scoped ("reg" is not globally unique the way
* "timebase-frequency" is) devicetree lookup this file's own comment
* previously flagged as "a bigger addition than this item's stated scope"
* was subsequently built anyway -- `fdt_find_node_by_compatible()` +
* `fdt_find_prop_in_node()` (`fdt.c`/`fdt.h`), added for the aarch64 GIC
* base fix (FABRIC-3.md §IV.3 item 7) and reused here unchanged.
* `plic_base_from_dtb()` below tries `"sifive,plic-1.0.0"` first; on
* 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 "starkernel/plic.h"
#include "starkernel/fdt.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_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))
/* Runtime PLIC base address -- set once by plic_init(), defaulting to the
* QEMU constant above until/unless plic_base_from_dtb() overrides it.
* uintptr_t, not a #define, precisely because this is no longer a
* 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) {
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
* source yet, so nothing can actually reach claim() until a caller
* explicitly enables it. */
@@ -63,7 +140,7 @@ int plic_init(void) {
__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;
}