Flash 199 lines
// Interrupt handling — GIC (Generic Interrupt Controller) for Raspberry Pi 4
const LINEAR_MAP_BASE u64 = 0xFFFF000000000000
const GIC_BASE u64 = 0xFF840000 + LINEAR_MAP_BASE
const GICD_BASE u64 = GIC_BASE + 0x1000
const GICC_BASE u64 = GIC_BASE + 0x2000
const GICD_ISENABLER_BASE u64 = GICD_BASE + 0x100
const GICD_ITARGETSR_BASE u64 = GICD_BASE + 0x800
const GICC_CTLR u64 = GICC_BASE + 0x00
const GICC_PMR u64 = GICC_BASE + 0x04
const GICC_IAR u64 = GICC_BASE + 0x0C
const GICC_EOIR u64 = GICC_BASE + 0x10
const DistributorEnableRegs = extern struct { bitmap [32]u32 }
const DistributorTargetRegs = extern struct { set [255]u32 }
fn enableRegs() *mut volatile DistributorEnableRegs {
return #as(*mut volatile DistributorEnableRegs, #ptrFromInt(GICD_ISENABLER_BASE))
}
fn targetRegs() *mut volatile DistributorTargetRegs {
return #as(*mut volatile DistributorTargetRegs, #ptrFromInt(GICD_ITARGETSR_BASE))
}
fn iarReg() *mut volatile u32 {
return #as(*mut volatile u32, #ptrFromInt(GICC_IAR))
}
fn eoirReg() *mut volatile u32 {
return #as(*mut volatile u32, #ptrFromInt(GICC_EOIR))
}
// IRQ numbers
const NS_PHYS_TIMER_IRQ u32 = 30
const VC_TIMER_IRQ_1 u32 = 97
const VC_AUX_IRQ u32 = 125
const MU i32 = 0
extern fn main_output(interface i32, str [*:0]u8) void
extern fn main_output_u64(interface i32, n u64) void
extern fn main_output_char(interface i32, ch u8) void
extern fn main_output_process(interface i32, p *mut anyopaque) void
extern fn mini_uart_recv() u8
extern fn mini_uart_rx_pending() bool
extern fn handle_sys_timer_1() void
extern fn handle_generic_timer() void
extern fn timer_tick() void
extern fn get_core() u32
extern var current *mut anyopaque
const console = #import("console")
// Named module (the same instance board.zig exposes as board.usb): the
// timer-tick enumeration service below polls the DWC2 core.
const usb = #import("rpi4b_usb")
// -Dtrace profiler seam. The empty stub keeps handle_irq's signature and
// `frame` argument identical in a non-trace build (the call inlines to
// nothing and emits no code), so the default kernel image is byte-for-byte
// unchanged; only under -Dtrace does the sampler get pulled in.
const build_options = #import("build_options")
const KeRegs = #import("task_layout").KeRegs
const trace_sampler = if (build_options.trace)
#import("sampler")
else
struct {
pub fn trace_sample(_ *mut KeRegs) void {}
}
const entry_error_messages = [_][*:0]u8{
"SYNC_INVALID_EL1t",
"IRQ_INVALID_EL1t",
"FIQ_INVALID_EL1t",
"SERROR_INVALID_EL1t",
"SYNC_INVALID_EL1h",
"IRQ_INVALID_EL1h",
"FIQ_INVALID_EL1h",
"SERROR_INVALID_EL1h",
"SYNC_INVALID_EL0_64",
"IRQ_INVALID_EL0_64",
"FIQ_INVALID_EL0_64",
"SERROR_INVALID_EL0_64",
"SYNC_INVALID_EL0_32",
"IRQ_INVALID_EL0_32",
"FIQ_INVALID_EL0_32",
"SERROR_INVALID_EL0_32",
"SYNC_ERROR",
"SYSCALL_ERROR",
"DATA_ABORT_ERROR"
}
export fn show_invalid_entry_message(typ u32, esr u64, address u64) void {
main_output(MU, "ERROR CAUGHT: ")
if (typ < entry_error_messages.len) {
main_output(MU, entry_error_messages[typ])
} else {
main_output(MU, "UNKNOWN_ENTRY")
}
main_output(MU, ", ESR: ")
main_output_u64(MU, esr)
main_output(MU, ", Address: ")
main_output_u64(MU, address)
main_output(MU, "\n")
}
export fn enable_gic_distributor(intid u32) void {
const n usize = #intCast(intid / 32)
const shift u5 = #intCast(intid % 32)
enableRegs().bitmap[n] |= (#as(u32, 1) << shift)
}
export fn assign_interrupt_core(intid u32, core u32) void {
const n usize = #intCast(intid / 4)
const byte_offset u32 = intid % 4
const shift u5 = #intCast(byte_offset * 8 + core)
targetRegs().set[n] |= (#as(u32, 1) << shift)
}
export fn enable_interrupt_gic(intid u32, core u32) void {
enable_gic_distributor(intid)
assign_interrupt_core(intid, core)
}
export fn handle_irq(frame *mut KeRegs) void {
// Sample before dispatch so a tick that reschedules cannot skip it.
// No-op (and no codegen) unless built with -Dtrace.
trace_sampler.trace_sample(frame)
const iar u32 = iarReg().*
// GICv2 GICC_IAR INTID is bits[9:0]; mask is 0x3FF. A 0x2FF mask
// silently clears bit 8 and drops IRQ IDs 256..511.
const intid u32 = iar & 0x3FF
switch intid {
VC_TIMER_IRQ_1 => {
handle_sys_timer_1()
eoirReg().* = iar
},
VC_AUX_IRQ => {
// Drain the entire RX FIFO in one IRQ slot. mini-UART FIFO
// is 8 bytes on BCM2711; popping just one per IRQ would
// lose bytes under sustained typing bursts since the level-
// triggered AUX line refires only once per CPU-mask/unmask
// round-trip. console_push ring-buffers + wakes the
// sys_readConsole waiter.
while (mini_uart_rx_pending()) {
console.console_push(mini_uart_recv())
}
eoirReg().* = iar
},
NS_PHYS_TIMER_IRQ => {
handle_generic_timer()
eoirReg().* = iar
if (get_core() == 0) {
// USB service backstop. Enumeration never depends
// on this 1 Hz tick — it could not meet the host's ~20 ms
// post-reset SETUP window anyway; the usb.zig connection
// manager keeps the gadget detached until the PID-0 idle
// loop polls at µs rate. This backstop only matters when the
// system goes busy AFTER attach: it keeps USBRST/ENUMDONE
// state moving so the connection manager can self-heal once
// the system is idle again. Gated on !enumerated(): once the
// data path is up, IRQ-context polling would race
// serviceTxRing against a syscall-context cdc_tx mid-FIFO-
// write (preempt_disable does not mask IRQs). Runs BEFORE
// timer_tick so a tick-triggered reschedule cannot skip it.
if (!usb.enumerated()) {
usb.poll()
}
timer_tick()
}
},
else => main_output(MU, "unknown pending irq\n"),
}
}
/// CPU-side GICv2 (GIC-400) bring-up for the calling core — the rpi4b analog of
/// virt's GICv3 board_irq_init. FlashOS runs at non-secure EL1, so these MMIO
/// accesses hit the NS-banked CPU interface: GICC_CTLR bit 0 = EnableGrp1, and
/// GICC_PMR is the NS priority mask.
/// 1. GICC_PMR = 0xF0 — accept any priority. FlashOS uses a single
/// interrupt priority, so this can never narrow
/// delivery below the working set.
/// 2. GICC_CTLR |= 1 — OR-in the Group-1 enable. Read-modify-write, NOT a
/// plain assign: GICv2's GICC_CTLR packs firmware-owned
/// bits (EOImode / CBPR / bypass, plus GIC-400 group /
/// security bits) that a blind write would clobber, and
/// an OR can only ever SET the enable — it cannot
/// disable a CPU interface firmware already brought up,
/// so it is regression-safe whatever the firmware left.
/// On the current Pi boot path firmware leaves the interface enabled, so this is
/// a self-healing no-op that removes the silent dependency on that firmware
/// state. NOTE: QEMU raspi4b pre-enables the CPU interface, so the watchdog
/// cannot tell a correct enable from a wrong one here — real-Pi boot acceptance
/// stays authoritative for this path.
pub fn board_irq_init() void {
const gicc_pmr = #as(*mut volatile u32, #ptrFromInt(GICC_PMR))
const gicc_ctlr = #as(*mut volatile u32, #ptrFromInt(GICC_CTLR))
gicc_pmr.* = 0xF0
gicc_ctlr.* |= 0x1
}