rumpk/hal/gic.zig

170 lines
5.6 KiB
Zig

// 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);
}