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