rumpk/hal/virtio_mmio.zig

269 lines
9.5 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 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));
}