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-
|
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,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. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user