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