// SPDX-License-Identifier: LCL-1.0 // Copyright (c) 2026 Markus Maiwald // Stewardship: Self Sovereign Society Foundation // // This file is part of the Nexus Commonwealth. // See legal/LICENSE_COMMONWEALTH.md for license terms. //! Rumpk Layer 0: GICv2 Driver (ARM64) //! //! Minimal Generic Interrupt Controller v2 for QEMU virt machine. //! Handles interrupt enable, claim, and complete for timer and device IRQs. //! //! SAFETY: All register accesses use volatile pointers to MMIO regions. // ========================================================= // GICv2 MMIO Base Addresses (QEMU virt machine) // ========================================================= const GICD_BASE: usize = 0x08000000; // Distributor const GICC_BASE: usize = 0x08010000; // CPU Interface // ========================================================= // Distributor Registers (GICD) // ========================================================= const GICD_CTLR: usize = 0x000; // Control const GICD_TYPER: usize = 0x004; // Type (read-only) const GICD_ISENABLER: usize = 0x100; // Set-Enable (banked per 32 IRQs) const GICD_ICENABLER: usize = 0x180; // Clear-Enable const GICD_ISPENDR: usize = 0x200; // Set-Pending const GICD_ICPENDR: usize = 0x280; // Clear-Pending const GICD_IPRIORITYR: usize = 0x400; // Priority (byte-accessible) const GICD_ITARGETSR: usize = 0x800; // Target (byte-accessible) const GICD_ICFGR: usize = 0xC00; // Configuration // ========================================================= // CPU Interface Registers (GICC) // ========================================================= const GICC_CTLR: usize = 0x000; // Control const GICC_PMR: usize = 0x004; // Priority Mask const GICC_IAR: usize = 0x00C; // Interrupt Acknowledge const GICC_EOIR: usize = 0x010; // End of Interrupt // ========================================================= // IRQ Numbers (QEMU virt) // ========================================================= /// Non-Secure Physical Timer PPI pub const TIMER_IRQ: u32 = 30; /// UART PL011 (SPI #1 = IRQ 33) pub const UART_IRQ: u32 = 33; /// VirtIO MMIO IRQ base (SPI #16 = IRQ 48) /// QEMU virt assigns SPIs 48..79 to MMIO slots 0..31 pub const VIRTIO_MMIO_IRQ_BASE: u32 = 48; // Spurious interrupt ID const SPURIOUS_IRQ: u32 = 1023; // ========================================================= // MMIO Helpers // ========================================================= fn gicd_read(offset: usize) u32 { const ptr: *volatile u32 = @ptrFromInt(GICD_BASE + offset); return ptr.*; } fn gicd_write(offset: usize, val: u32) void { const ptr: *volatile u32 = @ptrFromInt(GICD_BASE + offset); ptr.* = val; } fn gicc_read(offset: usize) u32 { const ptr: *volatile u32 = @ptrFromInt(GICC_BASE + offset); return ptr.*; } fn gicc_write(offset: usize, val: u32) void { const ptr: *volatile u32 = @ptrFromInt(GICC_BASE + offset); ptr.* = val; } // ========================================================= // Public API // ========================================================= /// Initialize GICv2 distributor and CPU interface. pub fn gic_init() void { // 1. Disable distributor during setup gicd_write(GICD_CTLR, 0); // 2. Set all SPIs to lowest priority (0xFF) and target CPU 0 // PPIs (0-31) are banked per-CPU, handled separately const typer = gicd_read(GICD_TYPER); const it_lines = (typer & 0x1F) + 1; // Number of 32-IRQ groups var i: usize = 1; // Skip group 0 (SGIs/PPIs - banked) while (i < it_lines) : (i += 1) { // Disable all SPIs gicd_write(GICD_ICENABLER + i * 4, 0xFFFFFFFF); // Set priority to 0xA0 (low but not lowest) var j: usize = 0; while (j < 8) : (j += 1) { gicd_write(GICD_IPRIORITYR + (i * 32 + j * 4), 0xA0A0A0A0); } // Target CPU 0 for all SPIs j = 0; while (j < 8) : (j += 1) { gicd_write(GICD_ITARGETSR + (i * 32 + j * 4), 0x01010101); } } // 3. Configure PPI priorities (group 0, banked) // Timer IRQ 30: priority 0x20 (high) const timer_prio_reg = GICD_IPRIORITYR + (TIMER_IRQ / 4) * 4; const timer_prio_shift: u5 = @intCast((TIMER_IRQ % 4) * 8); var prio_val = gicd_read(timer_prio_reg); prio_val &= ~(@as(u32, 0xFF) << timer_prio_shift); prio_val |= @as(u32, 0x20) << timer_prio_shift; gicd_write(timer_prio_reg, prio_val); // 4. Enable distributor (Group 0 + Group 1) gicd_write(GICD_CTLR, 0x3); // 5. Configure CPU interface gicc_write(GICC_PMR, 0xFF); // Accept all priorities gicc_write(GICC_CTLR, 0x1); // Enable CPU interface } /// Enable a specific interrupt in the distributor. pub fn gic_enable_irq(irq: u32) void { const reg = GICD_ISENABLER + (irq / 32) * 4; const bit: u5 = @intCast(irq % 32); gicd_write(reg, @as(u32, 1) << bit); } /// Disable a specific interrupt in the distributor. pub fn gic_disable_irq(irq: u32) void { const reg = GICD_ICENABLER + (irq / 32) * 4; const bit: u5 = @intCast(irq % 32); gicd_write(reg, @as(u32, 1) << bit); } /// Acknowledge an interrupt (read IAR). Returns IRQ number or SPURIOUS_IRQ. pub fn gic_claim() u32 { return gicc_read(GICC_IAR) & 0x3FF; } /// Signal end of interrupt processing. pub fn gic_complete(irq: u32) void { gicc_write(GICC_EOIR, irq); } /// Check if a claimed IRQ is spurious. pub fn is_spurious(irq: u32) bool { return irq >= SPURIOUS_IRQ; } /// Enable the NS Physical Timer interrupt (IRQ 30). pub fn gic_enable_timer_irq() void { gic_enable_irq(TIMER_IRQ); } /// Enable a VirtIO MMIO slot interrupt in the GIC. pub fn gic_enable_virtio_mmio_irq(slot: u32) void { gic_enable_irq(VIRTIO_MMIO_IRQ_BASE + slot); }