Commit Graph
9 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Opus 5.5 71e0be96b0 FABRIC-3.10.8 MC-3b: waking a worker core
Each worker core sets up its own IPI receiver before going online (amd64
LAPIC + vector 0x22 with its own stub, aarch64 banked GIC SGI 0, riscv64
sie.SSIE); core 0 sends by ICR / GICD_SGIR / SBI IPI. Park lets exactly
one interrupt in without a lost-wake window. Boot self-test: IPI round
trips 3/3 and 'woke x3, IPIs x3' on all three ISAs; worker vCPU threads
0.0% on the host. 3-ISA acceptance clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:31:01 -04:00
Robert Allan JamesandClaude Sonnet 5 350287850f arch/aarch64: fix VBAR_EL2 stale comment + hardcode HVC PSCI conduit
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
FABRIC-3.md §IV.3 item 7's code audit found two more findings; fixed both
per direct instruction naming them specifically.

Comments only, no behavior change:
interrupts.c's arch_interrupts_init() doc comment (and its matching
runtime EL2 console message) claimed VBAR_EL2 was "a known gap... not yet
wired." Checked against isr.S's aarch64_install_vectors() and found that
claim stale: the assembly already branches on aarch64_current_el() and
writes vbar_el2/vbar_el1 correctly, threading the same answer through
el2_mode_flag for the IRQ trampoline's ELR/SPSR selection too. There was
no gap to close -- only the comment was wrong. Also fixed a same-vintage
one-word staleness in arch.c's aarch64_install_vectors extern comment
("installs VBAR_EL1" -> EL-aware), caught while touching this.

Real behavior change, gated to preserve existing QEMU behavior:
arch_cold_reset() hardcoded the PSCI conduit to HVC, a documented
QEMU-specific workaround (QEMU's AAVMF has no genuine EL3 to answer SMC).
Real Pi 5 hardware's ATF means EL3 exists there; confirmed further this
pass that bcm2712.dtsi's own /psci node declares method="smc" directly,
not just inferred from ATF's presence.

New aarch64_psci_conduit_init(dtb) in arch.c: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,psci-1.0") ->
fdt_find_prop_in_node(..., "method", ...) -- sets a module-static
s_psci_use_smc flag to 1 only when method reads exactly "smc"; every
other outcome (no DTB, no PSCI node, method="hvc", property absent)
leaves it at its default 0/HVC, preserving this system's existing QEMU
behavior exactly. arch_cold_reset() now branches on that flag between
smc #0/hvc #0. Called once from apic_init() (arch/aarch64/apic.c), the
one point in boot with boot_info->dtb already in hand -- collocated with
its consumer in arch.c rather than defined in apic.c, since
arch_cold_reset() has no boot_info of its own by the time it runs (called
from deep in VM execution, via BYE).

Three-arch QEMU acceptance run exercised both new guard branches: the
aarch64 boot log shows "PSCI: no DTB -- using HVC (QEMU virt-machine
default)" immediately before the existing GIC discovery lines, then
boots clean to zuse)ok> -- confirming the QEMU/UEFI path is unchanged.
The SMC success branch itself stays unverified until real Pi 5 hardware
runs BYE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:06:41 -04:00
Robert Allan JamesandClaude Sonnet 5 dc9445abec apic.c (aarch64): fix GIC base address for real Pi 5 hardware
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
FABRIC-3.md §IV.3 item 7's code audit found GICD_BASE_PA/GICC_BASE_PA
hardcoded to QEMU virt-machine constants (0x08000000/0x08010000),
self-documented as a deliberate exception because QEMU's aarch64 UEFI
firmware never forwards a DTB. That premise doesn't hold on the native
Pi 5 boot path (rpi5_native_boot.c), which does have a real DTB and calls
this same, unmodified apic_init() -- which ignored boot_info entirely and
always programmed the QEMU addresses. Real BCM2712 GIC-400 is at
0x10_7fff9000 (confirmed against bcm2712.dtsi's axi/gicv2 nodes), a
completely different region of the address space. Fixed per direct
instruction, naming this finding specifically.

apic_init() now calls gic_bases_from_dtb() first: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,gic-400") ->
fdt_find_prop_in_node(..., "reg", ...), reading the first two
2-address-cell/2-size-cell entries (GICD, then GICC -- the standard
arm,gic-400 binding order). Falls back to the QEMU constants on any
failure (no DTB, no matching node), so the existing QEMU/UEFI path is
unchanged. GICD_BASE_PA/GICC_BASE_PA became s_gicd_base/s_gicc_base
(module-static uintptr_t, no longer compile-time constants on this path)
-- every MMIO call site (apic_init, apic_spi_enable, apic_read_iar,
apic_eoi_intid) now reads through them.

Three-arch QEMU acceptance run exercised the guard itself, not just
compiled it: the aarch64 boot log shows "GICv2: no DTB GIC node -- using
QEMU virt-machine defaults" followed by the unchanged "distributor+CPU
interface enabled, PPI 30" line, then boots clean to zuse)ok>. The
success branch (a real DTB with a matching node) stays unverified until
real Pi 5 hardware -- this build never has a devicetree to exercise it
against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:54:39 -04:00
Robert Allan JamesandClaude Sonnet 5 b031b802e3 Rename FABRIC series: FABRIC.md->0, FABRIC-2.md->1, FABRIC-3.md->2, FABRIC-4.md unchanged
FABRIC.md -> FABRIC-0.md
FABRIC-2.md -> FABRIC-1.md
FABRIC-3.md -> FABRIC-2.md (the current/living document)
FABRIC-4.md unchanged (new #3 to follow separately)

Every cross-reference repo-wide updated to match, including doc-comment
citations inside kernel source (.c/.h) files -- done via an ordered
placeholder substitution (FABRIC-3.md->placeholder2, FABRIC-2.md->
placeholder1, FABRIC.md->placeholder0, then placeholders resolved to
final names) in a single pass per file to avoid double-shifting
already-renamed references.

One line in capsules/font.4th grew past the 64-char block-format limit
as a side effect of the longer filename; shortened it and reverified
with mkcapsule --lint (34/34 pass) before rebuilding.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground) after the fix; logs and DoE CSVs from this session's
verification runs included per this repo's own audit-artifact
convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:22:51 -04:00
Robert Allan JamesandClaude Sonnet 5 2a2d7c5e5e aarch64: GIC SPI wiring for virtio-input (item 4.3.5d)
Punch list §25 item 4.3.5d complete.

New apic_spi_enable(intid) generalizes item 0.6's PPI-only sequence to one
explicit SPI (IPRIORITYR/ISENABLER/ITARGETSR, ICFGR read-checked not
written). Verified via a software-pended SPI (GICD_ISPENDR, no device
needed) through the existing generic IRQ dispatch, which needed no changes.
Found and fixed a real bug during verification: PSTATE.I is still set at
apic_init()'s point in boot, so the first self-test run correctly latched
but never took the interrupt. Self-test code reverted after capturing
evidence -- interrupts.c has zero net diff, only apic_spi_enable() remains,
unused until 4.3.5e. Three-arch acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:15:23 -04:00
Robert Allan JamesandClaude Sonnet 5 3699be964d starkernel: converge the tick path and wire the adaptive heartbeat (item 0.8)
Introduces src/starkernel/heartbeat.c as the shared top/bottom-half
implementation of heartbeat_init/tick/service/ticks/trust/state, replacing
the per-architecture duplicates in amd64/riscv64/aarch64 timer.c. Each
arch's timer.c now contributes only heartbeat_read_counter() (rdtsc /
rdtime / CNTPCT_EL0). Per the GAP-A1 ruling the top half stays counter+
latch only; heartbeat_service() (called every REPL idle iteration,
unconditionally per FABRIC.md's fidelity note) does the window/variance/
trust work outside interrupt context. vm_tick()'s call sites are
unchanged -- the engine still runs on the virtual tick.

Per FABRIC.md §26 (ruled 2026-08-03): wires Loop #7's execution-derived
stable/volatile signal into the physical re-arm period. vm_runtime.c's
existing Loop #7 site now calls heartbeat_set_adaptive_period_ns() with
tick_target_ns ratio-rescaled onto a 10ms kernel base (not the hosted
10us HEARTBEAT_TICK_NS -- see §26.3 for the scale mismatch). Each
architecture's re-arm function (apic_timer_rearm() on amd64/aarch64,
riscv64_timer_rearm()) now converts heartbeat_next_period_ns() to its
own raw counter units instead of a fixed constant; amd64 gained a
rearm function it didn't previously need, since periodic-mode auto-reload
never required one before this item.

Verified: all three architectures build with no new warnings and boot
cleanly to ok> with dict_hash=0x3d4e1daf289da94f, unchanged from the
pre-change baseline -- no regression. Verified NOT achieved: live re-arm
period variation under load. A temporary diagnostic (added and reverted)
confirmed Loop #7 never actually fired during a live QEMU session -- a
synthetic word-execution loop drove ~6,500 executions, past the 1000-tick
inference frequency, without tripping vm_tick_inference_engine()'s
pre-existing !vm->rolling_window.is_warm gate. That gate predates this
item and was not investigated -- out of scope. FABRIC.md's Done-when is
amended to record this honestly rather than claim it.

Punch list §25 item 0.8 complete (per amended, weaker acceptance -- see
the item's own annotation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 00:01:48 -04:00
Robert Allan JamesandClaude Sonnet 5 43aa0a2a36 aarch64: arm the ARM Generic Timer -- CNTP/CNTHP TVAL+CTL, per-tick re-arm
Punch list §25 item 0.7 complete. This is what finally makes items 0.5 and
0.6 provably work end to end.

apic_timer_start(): writes CNTP_TVAL_EL0 (or CNTHP_TVAL_EL2 at EL2 --
aarch64_current_el(), same EL-aware discipline as 0.4-0.6) to
s_timer_period_tsc, then CTL.ENABLE=1/IMASK=0, followed by an ISB. The ISB
is not decorative: confirmed against Linux's own arch_timer_reg_write_cp15()
(arch/arm64/include/asm/arch_timer.h) that only the *control* register write
needs synchronising before the enable/mask state is guaranteed visible to
the interrupt pipeline -- TVAL/CVAL writes do not carry the same
requirement, which is why apic_timer_rearm() omits it.

TVAL is architecturally 32-bit but MSR-to-system-register is always a
64-bit instruction form -- passing a uint32_t operand directly failed to
build (-Wasm-operand-widths). Fixed by truncating to 32 bits ourselves then
zero-extending back to 64 for the operand, which supplies explicit,
provably-correct zeros in the RES0 upper field rather than depending on
unverified hardware behaviour -- Linux's own driver never exercises this
path (it always uses the 64-bit CVAL form instead), so there was no local
source to confirm the alternative against.

apic_timer_rearm() (new): re-writes TVAL only, no ISB needed. TVAL is
relative to "now," not an absolute deadline like riscv64's SBI interface
(item 0.3), so there is no drift-correction bookkeeping -- each write means
"N ticks from this instant." Wired into aarch64_irq_handler() and called
*first*, before heartbeat_tick(), matching riscv64_timer_rearm()'s ordering
discipline exactly: the ARM Generic Timer does not auto-reload, so a return
path that skips this leaves the interrupt condition latched, which the GIC
would redeliver the instant it's EOI'd -- a real storm, the same class of
failure item 0.6's verification investigated (and that time found absent,
because nothing was armed yet).

Verified: builds clean; every generated instruction checked against
disassembly, not just reviewed by eye (both EL branches, correct TVAL/CTL
register names, single shared ISB in apic_timer_start(), no ISB in
apic_timer_rearm()). Boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged.

Rate measured directly against real wall-clock time via QEMU's own -d int
trap trace (same method as riscv64's item 0.3), two independent windows:
1,090 interrupts over 11.05 s (98.679 Hz) and 4,031 over 40.88 s (98.614 Hz)
-- consistent across both, so this is a real, small, systematic bias
(~1.3-1.4% slow), not measurement noise from polling granularity, which
would have shrunk with the longer window and did not. Attributed to genuine
per-interrupt service latency: TVAL is rewritten mid-ISR, so the trampoline
save/restore, GICC_IAR read, EL branch and GICC_EOIR write all lengthen the
effective period slightly versus the nominal 10 ms, inherent to any
relative-countdown re-arm scheme. Reported as measured, not smoothed over.
The interrupt sustained continuously across both windows with no stall and
no storm, which is the primary evidence re-arm-every-tick is correct;
the small rate bias is overhead, not a defect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 22:14:52 -04:00
Robert Allan JamesandClaude Sonnet 5 cabb0e8bd4 aarch64: minimal GICv2 driver -- distributor, CPU interface, timer PPI
Punch list §25 item 0.6 complete.

Ruling applied (AskUserQuestion, this session): DTB is confirmed unreachable
on this system's aarch64 firmware too (qemu-efi-aarch64 2025.11-3ubuntu7,
same finding as riscv64's item 0.3), so GICD/GICC base addresses and the
timer PPI are named QEMU-virt constants with a recorded caveat, not
DTB-discovered as the item originally asked.

Nothing here was recalled from memory. Base addresses (GICD 0x08000000,
GICC 0x08010000) and the timer PPI (30, non-secure EL1 physical) were read
out of QEMU 10.2.1's own internal devicetree via
`qemu-system-aarch64 -machine virt,dumpdtb=...`, decoded with this tree's own
fdt.c reader rather than a new tool -- correct for this exact QEMU version,
not assumed stable across others. Bonus finding from the same dump: PPI 26
for the EL2 hypervisor timer, which item 0.7 will need for its EL2 path.
Register offsets within each block (GICD_CTLR, GICC_IAR, etc.) are GICv2
architectural constants, not board-specific, and were cross-checked against
Linux's own arm-gic.h driver header rather than recalled either.

Acceptance amended before implementing (§25.0 "when an item is genuinely
wrong"): the original text required observing a delivered-and-acknowledged
timer interrupt, which cannot happen within this item's own scope --
apic_timer_start() (item 0.7) is still the no-op stub, so nothing arms the
timer. This is the same defect the earlier review's C2 fix already applied to
items 0.2 and 0.5; it was missed here. Acceptance is now: GIC initialises
without fault, the IAR/EOIR path is wired into aarch64_irq_handler() and
ready, boots with no regression -- item 0.7's tick-advance is what proves
delivery, exactly as 0.5 already defers to 0.7.

EL-aware (B3, same discipline as items 0.4/0.5): apic_init() selects PPI 30
or 26 from aarch64_current_el(), decided once and cached, not re-derived per
interrupt.

aarch64_irq_handler() now does real work: reads GICC_IAR (the GICv2
acknowledgement step), dispatches to heartbeat_tick() when the INTID matches
the timer PPI, and always completes with GICC_EOIR (INTID 1023 = spurious
handled per the GICv2 spec, not as a special case of "unrecognised"). This
mirrors exactly how riscv64's item 0.2 built full cause-dispatch logic before
its timer was armed in 0.3.

Investigated and resolved a real scare during verification: QEMU's `-d int`
trace showed 1,728 "Taking exception 5 [IRQ]" events by the time boot reached
the prompt, which looked exactly like an interrupt storm (hypothesis: EDK2
firmware leaves CNTP_CTL_EL0 enabled with a stale comparator, and enabling
the GIC path exposes it before item 0.7 reprograms the timer). A direct
one-shot probe inside aarch64_irq_handler() itself -- ground truth for
whether this code path runs at all -- fired zero times across a clean,
bounded boot. The trace events were almost certainly from EDK2 firmware's
own internal timer usage during its own boot phase, before control passes to
this kernel; the earlier conclusion was drawn from the external trace alone
without checking that distinction, and the probe (not the trace) is what
settled it. Probe code fully reverted; not part of the commit.

Verified: builds clean, boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged from the item 0.1-0.5 baseline, EL banner and
IDT-installed lines still print in order, GIC init line confirms PPI 30
selected. Only aarch64-scoped files touched; amd64/riscv64 not rebuilt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 19:48:56 -04:00
Robert Allan James a5ed8c3d87 Initial commit — LithosAnanke kernel 2026-08-01 07:49:56 -04:00