riscv64: PLIC base address DTB-discovered, QEMU-virt constant as fallback (§V.3 item 3)
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:
co-authored by
Claude Sonnet 5
parent
90ee8deb6d
commit
9b6de5d6c7
+29
-14
@@ -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-
|
||||
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
|
||||
comment says the PLIC base address is "a constant, not discovered from `boot_info->dtb`" —
|
||||
and `arch/riscv64/plic.c`'s own doc comment (predating this document) already warned
|
||||
`PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-specific... not assumed stable across" other
|
||||
configurations. That warning becomes concrete now: the JH7110's real PLIC address on the Mars
|
||||
is not confirmed to match QEMU-virt's, and the interrupt controller will not work correctly if
|
||||
it doesn't. This is a real punch-list item, not a hypothetical.
|
||||
**One real, already-flagged risk found while tracing this — FIXED 2026-09-05, see item 3
|
||||
below.** `arch/riscv64/apic.c`'s own doc comment said the PLIC base address was "a constant,
|
||||
not discovered from `boot_info->dtb`" — and `arch/riscv64/plic.c`'s own doc comment
|
||||
(predating this document) already warned `PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-
|
||||
specific... not assumed stable across" other configurations. That warning was concrete: the
|
||||
JH7110's real PLIC address on the Mars was not confirmed to match QEMU-virt's, and the
|
||||
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
|
||||
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
|
||||
pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from
|
||||
`/chosen`/`bootargs`.
|
||||
3. **PLIC base address: make it DTB-discovered**, not the current QEMU-virt-specific
|
||||
constant — the one concrete, already-flagged risk above. `fdt.c`'s node-scoped lookup
|
||||
extension (§IV.3 item 4, **DONE 2026-09-04**, shared with the Pi 5's UART/mailbox
|
||||
addresses) is the primitive this calls
|
||||
(`fdt_find_node_by_compatible(fdt, "sifive,plic-1.0.0")` → `fdt_find_prop_in_node(..., "reg", ...)`,
|
||||
plausible compatible string, not yet confirmed against the Mars's real DTB) — the actual
|
||||
PLIC-init call site update is still open, only the primitive it needs now exists.
|
||||
3. **PLIC base address: make it DTB-discovered — DONE 2026-09-05.** `plic_init()`
|
||||
(`arch/riscv64/plic.c`) now takes `boot_info->dtb`, passed through from `apic_init()`
|
||||
(`arch/riscv64/apic.c`), 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 constant —
|
||||
exactly the primitive built for the aarch64 GIC base fix (§IV.3 item 7), reused unchanged.
|
||||
`s_plic_base` is now a runtime `uintptr_t`, not a `#define`, same shape as apic.c's
|
||||
`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
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
Binary file not shown.
@@ -14,6 +14,11 @@
|
||||
* at all before this item. Pure substrate: this item wires the mechanism
|
||||
* (claim/dispatch/complete) with no permanent source enabled by default;
|
||||
* 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
|
||||
@@ -28,9 +33,14 @@
|
||||
* Also sets sie.SEIE. Does not touch sstatus.SIE -- arch_enable_interrupts()
|
||||
* 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.
|
||||
*/
|
||||
int plic_init(void);
|
||||
int plic_init(const void *dtb);
|
||||
|
||||
/** Set a source's interrupt priority (1-7; 0 means "never interrupt"). */
|
||||
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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user