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