440 lines
16 KiB
Zig
440 lines
16 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: Sovereign VirtIO Transport Layer
|
|
//!
|
|
//! Handles PCI Capability Discovery and provides a universal interface
|
|
//! for accessing both Legacy and Modern VirtIO devices.
|
|
//!
|
|
//! SAFETY: All hardware registers are accessed via volatile pointers.
|
|
//! Dynamically assigns BARs (Base Address Registers) if unassigned by firmware.
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const uart = @import("uart.zig");
|
|
|
|
// PCI Config Offsets
|
|
const PCI_COMMAND = 0x04;
|
|
const PCI_STATUS = 0x06;
|
|
const PCI_CAP_PTR = 0x34;
|
|
|
|
// Global Allocator for I/O and MMIO
|
|
var next_io_port: u32 = 0x1000;
|
|
const MMIO_ALLOC_ADDR: usize = 0x83000400;
|
|
|
|
fn get_mmio_alloc() *u64 {
|
|
return @ptrFromInt(MMIO_ALLOC_ADDR);
|
|
}
|
|
|
|
// VirtIO Capability Types
|
|
const VIRTIO_PCI_CAP_COMMON_CFG = 1;
|
|
const VIRTIO_PCI_CAP_NOTIFY_CFG = 2;
|
|
const VIRTIO_PCI_CAP_ISR_CFG = 3;
|
|
const VIRTIO_PCI_CAP_DEVICE_CFG = 4;
|
|
const VIRTIO_PCI_CAP_PCI_CFG = 5;
|
|
|
|
pub const VirtioTransport = struct {
|
|
base_addr: usize, // ECAM Base
|
|
is_modern: bool,
|
|
|
|
// Legacy Interface
|
|
legacy_bar: usize,
|
|
|
|
// Modern Interface (Mapped Addresses)
|
|
common_cfg: ?*volatile VirtioPciCommonCfg,
|
|
notify_cfg: ?usize, // Base of notification region
|
|
notify_off_multiplier: u32,
|
|
isr_cfg: ?*volatile u8,
|
|
device_cfg: ?*volatile u8,
|
|
|
|
pub fn init(ecam_base: usize) VirtioTransport {
|
|
return .{
|
|
.base_addr = ecam_base,
|
|
.is_modern = false,
|
|
.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 mmio_alloc = get_mmio_alloc();
|
|
if (mmio_alloc.* < 0x40000000) {
|
|
mmio_alloc.* = 0x40000000;
|
|
}
|
|
uart.print("[VirtIO-PCI] Probing capabilities...\n");
|
|
|
|
// 1. Enable Bus Master & Memory Space & IO Space
|
|
const cmd_ptr: *volatile u16 = @ptrFromInt(self.base_addr + PCI_COMMAND);
|
|
cmd_ptr.* |= 0x07; // IO | MEM | BUS_MASTER
|
|
|
|
// 2. Check for Capabilities
|
|
const status_ptr: *volatile u16 = @ptrFromInt(self.base_addr + PCI_STATUS);
|
|
|
|
uart.print(" [PCI BARs] ");
|
|
for (0..6) |i| {
|
|
const bar_val = @as(*volatile u32, @ptrFromInt(self.base_addr + 0x10 + (i * 4))).*;
|
|
uart.print("BAR");
|
|
uart.print_hex8(@intCast(i));
|
|
uart.print(":");
|
|
uart.print_hex(bar_val);
|
|
uart.print(" ");
|
|
}
|
|
uart.print("\n");
|
|
|
|
if ((status_ptr.* & 0x10) != 0) {
|
|
// Has Capabilities
|
|
var cap_offset = @as(*volatile u8, @ptrFromInt(self.base_addr + PCI_CAP_PTR)).*;
|
|
|
|
// 🔥 LOOP GUARD: Prevent infinite loops in capability chain
|
|
// Standard PCI config space is 256 bytes, max ~48 capabilities possible
|
|
// If we exceed this, the chain is circular or we're reading stale cached values
|
|
var loop_guard: usize = 0;
|
|
const MAX_CAPS: usize = 48;
|
|
|
|
while (cap_offset != 0) {
|
|
loop_guard += 1;
|
|
if (loop_guard > MAX_CAPS) {
|
|
uart.print("[VirtIO-PCI] WARN: Capability loop limit reached (");
|
|
uart.print_hex(loop_guard);
|
|
uart.print(" iterations). Breaking to prevent hang.\n");
|
|
break;
|
|
}
|
|
|
|
const cap_addr = self.base_addr + cap_offset;
|
|
const cap_id = @as(*volatile u8, @ptrFromInt(cap_addr)).*;
|
|
const cap_next = @as(*volatile u8, @ptrFromInt(cap_addr + 1)).*;
|
|
|
|
// uart.print(" ID: ");
|
|
// uart.print_hex(cap_id);
|
|
// uart.print(" Next: ");
|
|
// uart.print_hex(cap_next);
|
|
// uart.print("\n");
|
|
|
|
if (cap_id == 0x09) { // Vendor Specific (VirtIO)
|
|
const cap_type = @as(*volatile u8, @ptrFromInt(cap_addr + 3)).*;
|
|
const bar_idx = @as(*volatile u8, @ptrFromInt(cap_addr + 4)).*;
|
|
const offset = @as(*volatile u32, @ptrFromInt(cap_addr + 8)).*;
|
|
const length = @as(*volatile u32, @ptrFromInt(cap_addr + 12)).*;
|
|
|
|
uart.print(" [VirtIO Cap] Type:");
|
|
uart.print_hex(cap_type);
|
|
uart.print(" BAR:");
|
|
uart.print_hex(bar_idx);
|
|
uart.print(" Off:");
|
|
uart.print_hex(offset);
|
|
uart.print(" Len:");
|
|
uart.print_hex(length);
|
|
uart.print("\n");
|
|
|
|
if (bar_idx >= 6) {
|
|
uart.print("[VirtIO-PCI] Ignoring Invalid BAR Index in Cap\n");
|
|
cap_offset = cap_next;
|
|
continue;
|
|
}
|
|
|
|
// Resolve BAR Address
|
|
const bar_ptr = @as(*volatile u32, @ptrFromInt(self.base_addr + 0x10 + (@as(usize, bar_idx) * 4)));
|
|
const bar_val = bar_ptr.*;
|
|
|
|
// Check if BAR is assigned and is a Memory BAR (bit 0 == 0)
|
|
if ((bar_val & 0x1) == 0 and (bar_val & 0xFFFFFFF0) == 0) {
|
|
uart.print("[VirtIO-PCI] dev:");
|
|
uart.print_hex(self.base_addr);
|
|
uart.print(" ALLOC_VAL: ");
|
|
uart.print_hex(mmio_alloc.*);
|
|
uart.print(" Initializing BAR");
|
|
uart.print_hex8(@intCast(bar_idx));
|
|
uart.print(" at ");
|
|
uart.print_hex(mmio_alloc.*);
|
|
uart.print("\n");
|
|
|
|
bar_ptr.* = @intCast(mmio_alloc.* & 0xFFFFFFFF);
|
|
|
|
// Handle 64-bit BAR (Bit 2 of BAR value before write, or check type)
|
|
// If bit 2 is 1 (0b100), it's 64-bit.
|
|
if ((bar_val & 0x4) != 0) {
|
|
const high_ptr = @as(*volatile u32, @ptrFromInt(self.base_addr + 0x10 + (@as(usize, bar_idx) * 4) + 4));
|
|
high_ptr.* = @intCast(mmio_alloc.* >> 32);
|
|
}
|
|
|
|
const rb = bar_ptr.*;
|
|
uart.print("[VirtIO-PCI] dev:");
|
|
uart.print_hex(self.base_addr);
|
|
uart.print(" BAR Assigned. Readback: ");
|
|
uart.print_hex(rb);
|
|
uart.print("\n");
|
|
mmio_alloc.* += 0x10000; // Increment 64KB
|
|
}
|
|
|
|
// Refresh BAR resolution (Memory only for Modern)
|
|
const bar_base = bar_ptr.* & 0xFFFFFFF0;
|
|
|
|
if (cap_type == VIRTIO_PCI_CAP_COMMON_CFG) {
|
|
uart.print("[VirtIO-PCI] Found Modern Common Config\n");
|
|
uart.print(" BAR Base: ");
|
|
uart.print_hex(@as(u64, bar_base));
|
|
uart.print(" Offset: ");
|
|
uart.print_hex(@as(u64, offset));
|
|
uart.print("\n");
|
|
|
|
self.common_cfg = @ptrFromInt(bar_base + offset);
|
|
self.is_modern = true;
|
|
|
|
uart.print(" CommonCfg Ptr: ");
|
|
uart.print_hex(@intFromPtr(self.common_cfg.?));
|
|
uart.print("\n");
|
|
}
|
|
if (cap_type == VIRTIO_PCI_CAP_NOTIFY_CFG) {
|
|
uart.print("[VirtIO-PCI] Found Modern Notify Config\n");
|
|
self.notify_cfg = bar_base + offset;
|
|
self.notify_off_multiplier = @as(*volatile u32, @ptrFromInt(cap_addr + 16)).*;
|
|
}
|
|
if (cap_type == VIRTIO_PCI_CAP_ISR_CFG) {
|
|
uart.print("[VirtIO-PCI] Found Modern ISR Config\n");
|
|
self.isr_cfg = @ptrFromInt(bar_base + offset);
|
|
}
|
|
if (cap_type == VIRTIO_PCI_CAP_DEVICE_CFG) {
|
|
uart.print("[VirtIO-PCI] Found Modern Device Config\n");
|
|
uart.print(" BAR Base: ");
|
|
uart.print_hex(@as(u64, bar_base));
|
|
uart.print(" Offset: ");
|
|
uart.print_hex(@as(u64, offset));
|
|
uart.print("\n");
|
|
self.device_cfg = @ptrFromInt(bar_base + offset);
|
|
}
|
|
}
|
|
uart.print("[VirtIO-PCI] Next Cap...\n");
|
|
cap_offset = cap_next;
|
|
}
|
|
}
|
|
|
|
if (self.is_modern) {
|
|
uart.print("[VirtIO] Using Modern Interface\n");
|
|
return true;
|
|
}
|
|
|
|
// 3. Fallback to Legacy
|
|
uart.print("[VirtIO] Capabilities not found. Falling back to Legacy.\n");
|
|
const bar0_ptr = @as(*volatile u32, @ptrFromInt(self.base_addr + 0x10));
|
|
var bar0 = bar0_ptr.*;
|
|
|
|
// DEBUG: Print BAR0 value from PCI config
|
|
uart.print("[VirtIO] BAR0 from PCI config: ");
|
|
uart.print_hex(bar0);
|
|
uart.print("\n");
|
|
|
|
// Handle I/O vs Mem
|
|
const is_io = (bar0 & 0x1) == 1;
|
|
|
|
// QEMU RISC-V Constants
|
|
const PIO_BASE = 0x03000000;
|
|
const MMIO_BASE = 0x40000000;
|
|
|
|
if ((bar0 & 0xFFFFFFF0) == 0) {
|
|
if (is_io) {
|
|
// Assign I/O port address dynamically
|
|
const io_port: u32 = next_io_port | 0x1;
|
|
uart.print("[VirtIO] Legacy I/O BAR unassigned. Assigning ");
|
|
uart.print_hex(next_io_port);
|
|
uart.print("...\n");
|
|
|
|
bar0_ptr.* = io_port;
|
|
next_io_port += 0x100; // Increment 256 bytes (Safer alignment)
|
|
|
|
// Readback to verify QEMU accepted the assignment
|
|
const readback = bar0_ptr.*;
|
|
uart.print("[VirtIO] BAR0 readback after write: ");
|
|
uart.print_hex(readback);
|
|
uart.print("\n");
|
|
bar0 = readback;
|
|
} else {
|
|
uart.print("[VirtIO] Legacy Mem BAR unassigned. Assigning 0x40000000...\n");
|
|
bar0_ptr.* = MMIO_BASE;
|
|
bar0 = MMIO_BASE;
|
|
}
|
|
}
|
|
|
|
if (is_io) {
|
|
// IO Space translation
|
|
self.legacy_bar = PIO_BASE + (bar0 & 0xFFFFFFFC);
|
|
} else {
|
|
self.legacy_bar = bar0 & 0xFFFFFFF0;
|
|
}
|
|
|
|
// Re-enable I/O | MEM | BUS_MASTER after BAR assignment
|
|
const cmd_reg: *volatile u16 = @ptrFromInt(self.base_addr + PCI_COMMAND);
|
|
cmd_reg.* |= 0x07;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Unified Interface
|
|
pub fn reset(self: *VirtioTransport) void {
|
|
self.set_status(0);
|
|
}
|
|
|
|
pub fn get_status(self: *VirtioTransport) u8 {
|
|
if (self.is_modern) {
|
|
return self.common_cfg.?.device_status;
|
|
} else {
|
|
return @as(*volatile u8, @ptrFromInt(self.legacy_bar + 0x12)).*;
|
|
}
|
|
}
|
|
|
|
pub fn set_status(self: *VirtioTransport, status: u8) void {
|
|
if (self.is_modern) {
|
|
self.common_cfg.?.device_status = status;
|
|
} else {
|
|
@as(*volatile u8, @ptrFromInt(self.legacy_bar + 0x12)).* = 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 {
|
|
if (self.is_modern) {
|
|
self.common_cfg.?.queue_select = idx;
|
|
} else {
|
|
@as(*volatile u16, @ptrFromInt(self.legacy_bar + 0x0E)).* = idx;
|
|
}
|
|
}
|
|
|
|
pub fn get_queue_size(self: *VirtioTransport) u16 {
|
|
if (self.is_modern) {
|
|
return self.common_cfg.?.queue_size;
|
|
} else {
|
|
return @as(*volatile u16, @ptrFromInt(self.legacy_bar + 0x0C)).*;
|
|
}
|
|
}
|
|
|
|
pub fn set_queue_size(self: *VirtioTransport, size: u16) void {
|
|
// PCI legacy: queue size is read-only (device sets it)
|
|
// Modern: could set via common_cfg.queue_size
|
|
if (self.is_modern) {
|
|
if (self.common_cfg) |cfg| {
|
|
cfg.queue_size = size;
|
|
}
|
|
}
|
|
// Legacy PCI: queue size is fixed by device, no register to write
|
|
}
|
|
|
|
pub fn setup_legacy_queue(self: *VirtioTransport, pfn: u32) void {
|
|
// Only for legacy
|
|
@as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x08)).* = pfn;
|
|
}
|
|
|
|
pub fn setup_modern_queue(self: *VirtioTransport, desc: u64, avail: u64, used: u64) void {
|
|
if (self.common_cfg) |cfg| {
|
|
cfg.queue_desc = desc;
|
|
cfg.queue_avail = avail;
|
|
cfg.queue_used = used;
|
|
cfg.queue_enable = 1;
|
|
}
|
|
}
|
|
|
|
pub fn notify(self: *VirtioTransport, queue_idx: u16) void {
|
|
if (self.is_modern) {
|
|
if (self.common_cfg) |cfg| {
|
|
if (self.notify_cfg) |notify_base| {
|
|
cfg.queue_select = queue_idx;
|
|
const offset = @as(usize, cfg.queue_notify_off) * self.notify_off_multiplier;
|
|
const ptr: *volatile u16 = @ptrFromInt(notify_base + offset);
|
|
ptr.* = queue_idx;
|
|
}
|
|
}
|
|
} else {
|
|
const notify_ptr: *volatile u16 = @ptrFromInt(self.legacy_bar + 0x10);
|
|
notify_ptr.* = queue_idx;
|
|
}
|
|
}
|
|
|
|
// =========================================================
|
|
// Unified Accessor API (matches MMIO transport)
|
|
// =========================================================
|
|
|
|
pub fn get_device_features(self: *VirtioTransport) u64 {
|
|
if (self.is_modern) {
|
|
const cfg = self.common_cfg.?;
|
|
cfg.device_feature_select = 0;
|
|
io_barrier();
|
|
const low: u64 = cfg.device_feature;
|
|
cfg.device_feature_select = 1;
|
|
io_barrier();
|
|
const high: u64 = cfg.device_feature;
|
|
return (high << 32) | low;
|
|
} else {
|
|
// Legacy: features at offset 0x00 (32-bit only)
|
|
return @as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x00)).*;
|
|
}
|
|
}
|
|
|
|
pub fn set_driver_features(self: *VirtioTransport, features: u64) void {
|
|
if (self.is_modern) {
|
|
const cfg = self.common_cfg.?;
|
|
cfg.driver_feature_select = 0;
|
|
cfg.driver_feature = @truncate(features);
|
|
io_barrier();
|
|
cfg.driver_feature_select = 1;
|
|
cfg.driver_feature = @truncate(features >> 32);
|
|
io_barrier();
|
|
} else {
|
|
// Legacy: guest features at offset 0x04 (32-bit only)
|
|
@as(*volatile u32, @ptrFromInt(self.legacy_bar + 0x04)).* = @truncate(features);
|
|
}
|
|
}
|
|
|
|
pub fn get_device_config_byte(self: *VirtioTransport, offset: usize) u8 {
|
|
if (self.is_modern) {
|
|
if (self.device_cfg) |cfg| {
|
|
const ptr: [*]volatile u8 = @ptrCast(cfg);
|
|
return ptr[offset];
|
|
}
|
|
return 0;
|
|
} else {
|
|
// Legacy: device config starts at offset 20
|
|
return @as(*volatile u8, @ptrFromInt(self.legacy_bar + 20 + offset)).*;
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Arch-safe memory barrier for VirtIO I/O ordering.
|
|
pub inline fn io_barrier() void {
|
|
switch (builtin.cpu.arch) {
|
|
.riscv64 => asm volatile ("fence" ::: .{ .memory = true }),
|
|
.aarch64 => asm volatile ("dmb sy" ::: .{ .memory = true }),
|
|
else => @compileError("unsupported arch"),
|
|
}
|
|
}
|
|
|
|
// Modern Config Structure Layout
|
|
pub const VirtioPciCommonCfg = extern struct {
|
|
device_feature_select: u32,
|
|
device_feature: u32,
|
|
driver_feature_select: u32,
|
|
driver_feature: u32,
|
|
msix_config: u16,
|
|
num_queues: u16,
|
|
device_status: u8,
|
|
config_generation: u8,
|
|
|
|
// Queue configuration
|
|
queue_select: u16,
|
|
queue_size: u16,
|
|
queue_msix_vector: u16,
|
|
queue_enable: u16,
|
|
queue_notify_off: u16,
|
|
queue_desc: u64,
|
|
queue_avail: u64,
|
|
queue_used: u64,
|
|
};
|