rumpk/hal/virtio_pci.zig

294 lines
10 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 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;
pub var next_mmio_addr: u32 = 0x40000000;
// 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,
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,
};
}
pub fn probe(self: *VirtioTransport) bool {
if (next_mmio_addr == 0) {
next_mmio_addr = 0x40000000;
uart.print("[VirtIO-PCI] WARNING: next_mmio_addr was ZERO! Restored to 0x40000000\n");
}
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);
if ((status_ptr.* & 0x10) != 0) {
// Has Capabilities
var cap_offset = @as(*volatile u8, @ptrFromInt(self.base_addr + PCI_CAP_PTR)).*;
while (cap_offset != 0) {
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)).*;
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] Initializing Unassigned Memory BAR ");
uart.print_hex(@as(u64, bar_idx));
uart.print(" at ");
uart.print_hex(next_mmio_addr);
uart.print("\n");
bar_ptr.* = next_mmio_addr;
const rb = bar_ptr.*;
uart.print("[VirtIO-PCI] BAR Assigned. Readback: ");
uart.print_hex(rb);
uart.print("\n");
next_mmio_addr += 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");
self.common_cfg = @ptrFromInt(bar_base + offset);
self.is_modern = true;
}
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);
}
}
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 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;
}
}
};
// 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,
};