// 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 HAL: VirtIO MMIO Transport Layer (ARM64)
//!
//! Provides the same VirtioTransport API as virtio_pci.zig but for MMIO-based
//! VirtIO devices as found on QEMU aarch64 virt machine.
//!
//! QEMU virt MMIO layout: 32 slots starting at 0x0a000000, stride 0x200.
//! Each slot triggers GIC SPI (IRQ 48 + slot_index).
//!
//! Supports both legacy (v1) and modern (v2) MMIO transport.
//!
//! SAFETY: All hardware registers accessed via volatile pointers.

const std = @import("std");
const builtin = @import("builtin");
const uart = @import("uart.zig");

// =========================================================
// VirtIO MMIO Register Offsets (spec §4.2.2)
// =========================================================

const VIRTIO_MMIO_MAGIC_VALUE = 0x000;
const VIRTIO_MMIO_VERSION = 0x004;
const VIRTIO_MMIO_DEVICE_ID = 0x008;
const VIRTIO_MMIO_VENDOR_ID = 0x00C;
const VIRTIO_MMIO_DEVICE_FEATURES = 0x010;
const VIRTIO_MMIO_DEVICE_FEATURES_SEL = 0x014;
const VIRTIO_MMIO_DRIVER_FEATURES = 0x020;
const VIRTIO_MMIO_DRIVER_FEATURES_SEL = 0x024;
const VIRTIO_MMIO_QUEUE_SEL = 0x030;
const VIRTIO_MMIO_QUEUE_NUM_MAX = 0x034;
const VIRTIO_MMIO_QUEUE_NUM = 0x038;
const VIRTIO_MMIO_QUEUE_ALIGN = 0x03C;
const VIRTIO_MMIO_QUEUE_PFN = 0x040;
const VIRTIO_MMIO_QUEUE_READY = 0x044;
const VIRTIO_MMIO_QUEUE_NOTIFY = 0x050;
const VIRTIO_MMIO_INTERRUPT_STATUS = 0x060;
const VIRTIO_MMIO_INTERRUPT_ACK = 0x064;
const VIRTIO_MMIO_STATUS = 0x070;
const VIRTIO_MMIO_QUEUE_DESC_LOW = 0x080;
const VIRTIO_MMIO_QUEUE_DESC_HIGH = 0x084;
const VIRTIO_MMIO_QUEUE_AVAIL_LOW = 0x090;
const VIRTIO_MMIO_QUEUE_AVAIL_HIGH = 0x094;
const VIRTIO_MMIO_QUEUE_USED_LOW = 0x0A0;
const VIRTIO_MMIO_QUEUE_USED_HIGH = 0x0A4;
const VIRTIO_MMIO_CONFIG = 0x100; // Device-specific config starts here

// VirtIO magic value: "virt" in little-endian
const VIRTIO_MAGIC: u32 = 0x74726976;

// =========================================================
// QEMU virt MMIO Topology
// =========================================================

const MMIO_BASE: usize = 0x0a000000;
const MMIO_STRIDE: usize = 0x200;
const MMIO_SLOT_COUNT: usize = 32;
const MMIO_IRQ_BASE: u32 = 48; // GIC SPI base for VirtIO MMIO

// =========================================================
// MMIO Read/Write Helpers
// =========================================================

fn mmio_read(base: usize, offset: usize) u32 {
    const ptr: *volatile u32 = @ptrFromInt(base + offset);
    return ptr.*;
}

fn mmio_write(base: usize, offset: usize, val: u32) void {
    const ptr: *volatile u32 = @ptrFromInt(base + offset);
    ptr.* = val;
}

fn mmio_read_u8(base: usize, offset: usize) u8 {
    const ptr: *volatile u8 = @ptrFromInt(base + offset);
    return ptr.*;
}

// =========================================================
// Arch-safe memory barrier
// =========================================================

pub inline fn io_barrier() void {
    switch (builtin.cpu.arch) {
        .aarch64 => asm volatile ("dmb sy" ::: .{ .memory = true }),
        .riscv64 => asm volatile ("fence" ::: .{ .memory = true }),
        else => @compileError("unsupported arch"),
    }
}

// =========================================================
// VirtIO MMIO Transport (same API surface as PCI transport)
// =========================================================

pub const VirtioTransport = struct {
    base_addr: usize,
    is_modern: bool,
    version: u32,

    // Legacy compatibility fields (match PCI transport shape)
    legacy_bar: usize,

    // Modern interface placeholders (unused for MMIO but present for API compat)
    common_cfg: ?*volatile anyopaque,
    notify_cfg: ?usize,
    notify_off_multiplier: u32,
    isr_cfg: ?*volatile u8,
    device_cfg: ?*volatile u8,

    pub fn init(mmio_base: usize) VirtioTransport {
        return .{
            .base_addr = mmio_base,
            .is_modern = false,
            .version = 0,
            .legacy_bar = 0,
            .common_cfg = null,
            .notify_cfg = null,
            .notify_off_multiplier = 0,
            .isr_cfg = null,
            .device_cfg = null,
        };
    }

    pub fn probe(self: *VirtioTransport) bool {
        const magic = mmio_read(self.base_addr, VIRTIO_MMIO_MAGIC_VALUE);
        if (magic != VIRTIO_MAGIC) return false;

        self.version = mmio_read(self.base_addr, VIRTIO_MMIO_VERSION);
        if (self.version != 1 and self.version != 2) return false;

        const device_id = mmio_read(self.base_addr, VIRTIO_MMIO_DEVICE_ID);
        if (device_id == 0) return false; // No device at this slot

        self.is_modern = (self.version == 2);

        uart.print("[VirtIO-MMIO] Probed 0x");
        uart.print_hex(self.base_addr);
        uart.print(" Ver=");
        uart.print_hex(self.version);
        uart.print(" DevID=");
        uart.print_hex(device_id);
        uart.print("\n");

        return true;
    }

    pub fn reset(self: *VirtioTransport) void {
        self.set_status(0);
        // After reset, wait for device to reinitialize (spec §2.1.1)
        io_barrier();
    }

    pub fn get_status(self: *VirtioTransport) u8 {
        return @truncate(mmio_read(self.base_addr, VIRTIO_MMIO_STATUS));
    }

    pub fn set_status(self: *VirtioTransport, status: u8) void {
        mmio_write(self.base_addr, VIRTIO_MMIO_STATUS, @as(u32, status));
    }

    pub fn add_status(self: *VirtioTransport, status: u8) void {
        self.set_status(self.get_status() | status);
    }

    pub fn select_queue(self: *VirtioTransport, idx: u16) void {
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_SEL, @as(u32, idx));
    }

    pub fn get_queue_size(self: *VirtioTransport) u16 {
        return @truncate(mmio_read(self.base_addr, VIRTIO_MMIO_QUEUE_NUM_MAX));
    }

    pub fn set_queue_size(self: *VirtioTransport, size: u16) void {
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_NUM, @as(u32, size));
    }

    pub fn setup_legacy_queue(self: *VirtioTransport, pfn: u32) void {
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_ALIGN, 4096);
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_PFN, pfn);
    }

    pub fn setup_modern_queue(self: *VirtioTransport, desc: u64, avail: u64, used: u64) void {
        // Set queue size first
        const max_size = mmio_read(self.base_addr, VIRTIO_MMIO_QUEUE_NUM_MAX);
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_NUM, max_size);

        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_DESC_LOW, @truncate(desc));
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_DESC_HIGH, @truncate(desc >> 32));
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_AVAIL_LOW, @truncate(avail));
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, @truncate(avail >> 32));
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_USED_LOW, @truncate(used));
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_USED_HIGH, @truncate(used >> 32));

        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_READY, 1);
    }

    pub fn notify(self: *VirtioTransport, queue_idx: u16) void {
        mmio_write(self.base_addr, VIRTIO_MMIO_QUEUE_NOTIFY, @as(u32, queue_idx));
    }

    // =========================================================
    // Unified Accessor API (matches PCI transport extensions)
    // =========================================================

    pub fn get_device_features(self: *VirtioTransport) u64 {
        mmio_write(self.base_addr, VIRTIO_MMIO_DEVICE_FEATURES_SEL, 0);
        io_barrier();
        const low: u64 = mmio_read(self.base_addr, VIRTIO_MMIO_DEVICE_FEATURES);

        mmio_write(self.base_addr, VIRTIO_MMIO_DEVICE_FEATURES_SEL, 1);
        io_barrier();
        const high: u64 = mmio_read(self.base_addr, VIRTIO_MMIO_DEVICE_FEATURES);

        return (high << 32) | low;
    }

    pub fn set_driver_features(self: *VirtioTransport, features: u64) void {
        mmio_write(self.base_addr, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 0);
        mmio_write(self.base_addr, VIRTIO_MMIO_DRIVER_FEATURES, @truncate(features));
        io_barrier();
        mmio_write(self.base_addr, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 1);
        mmio_write(self.base_addr, VIRTIO_MMIO_DRIVER_FEATURES, @truncate(features >> 32));
        io_barrier();
    }

    pub fn get_device_config_byte(self: *VirtioTransport, offset: usize) u8 {
        return mmio_read_u8(self.base_addr, VIRTIO_MMIO_CONFIG + offset);
    }

    pub fn ack_interrupt(self: *VirtioTransport) u32 {
        const status = mmio_read(self.base_addr, VIRTIO_MMIO_INTERRUPT_STATUS);
        mmio_write(self.base_addr, VIRTIO_MMIO_INTERRUPT_ACK, status);
        return status;
    }
};

// =========================================================
// Device Discovery
// =========================================================

/// Scan MMIO slots for a VirtIO device with the given device ID.
/// Returns MMIO base address or null if not found.
pub fn find_device(device_id: u32) ?usize {
    var slot: usize = 0;
    while (slot < MMIO_SLOT_COUNT) : (slot += 1) {
        const base = MMIO_BASE + (slot * MMIO_STRIDE);
        const magic = mmio_read(base, VIRTIO_MMIO_MAGIC_VALUE);
        if (magic != VIRTIO_MAGIC) continue;

        const dev_id = mmio_read(base, VIRTIO_MMIO_DEVICE_ID);
        if (dev_id == device_id) {
            return base;
        }
    }
    return null;
}

/// Get the GIC SPI number for a given MMIO slot base address.
pub fn slot_irq(base: usize) u32 {
    const slot = (base - MMIO_BASE) / MMIO_STRIDE;
    return MMIO_IRQ_BASE + @as(u32, @intCast(slot));
}
