311 lines
9.8 KiB
Zig
311 lines
9.8 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 Layer 0: VirtIO-Block Driver
|
|
//!
|
|
//! Provides raw sector access for the unikernel storage system.
|
|
//!
|
|
//! SAFETY: Synchronous driver. Blocks current fiber until QEMU completes
|
|
//! the request. Uses bounce-buffers to guarantee alignment.
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const uart = @import("uart.zig");
|
|
|
|
// Comptime transport switch: PCI on RISC-V, MMIO on ARM64
|
|
const transport_mod = if (builtin.cpu.arch == .aarch64)
|
|
@import("virtio_mmio.zig")
|
|
else
|
|
@import("virtio_pci.zig");
|
|
|
|
// External C/Zig stubs
|
|
extern fn malloc(size: usize) ?*anyopaque;
|
|
|
|
var global_blk: ?VirtioBlkDriver = null;
|
|
|
|
export fn virtio_blk_read(sector: u64, buf: [*]u8) void {
|
|
if (global_blk) |*d| {
|
|
d.read(sector, buf[0..512]) catch {
|
|
uart.print("[VirtIO-Blk] READ ERROR\n");
|
|
};
|
|
}
|
|
}
|
|
|
|
export fn virtio_blk_write(sector: u64, buf: [*]const u8) void {
|
|
if (global_blk) |*d| {
|
|
d.write(sector, buf[0..512]) catch {
|
|
uart.print("[VirtIO-Blk] WRITE ERROR\n");
|
|
};
|
|
}
|
|
}
|
|
|
|
pub fn init() void {
|
|
if (VirtioBlkDriver.probe()) |_| {
|
|
uart.print("[Rumpk L0] Storage initialized (The Ledger).\n");
|
|
} else {
|
|
uart.print("[Rumpk L0] No Storage Device Found.\n");
|
|
}
|
|
}
|
|
|
|
pub const VirtioBlkDriver = struct {
|
|
transport: transport_mod.VirtioTransport,
|
|
v_desc: [*]volatile VirtioDesc,
|
|
v_avail: *volatile VirtioAvail,
|
|
v_used: *volatile VirtioUsed,
|
|
queue_size: u16,
|
|
|
|
pub fn probe() ?VirtioBlkDriver {
|
|
if (builtin.cpu.arch == .aarch64) {
|
|
return probe_mmio();
|
|
} else {
|
|
return probe_pci();
|
|
}
|
|
}
|
|
|
|
fn probe_mmio() ?VirtioBlkDriver {
|
|
const mmio = @import("virtio_mmio.zig");
|
|
const base = mmio.find_device(2) orelse { // device_id=2 is block
|
|
uart.print("[VirtIO] No VirtIO-Block MMIO device found\n");
|
|
return null;
|
|
};
|
|
uart.print("[VirtIO] Found VirtIO-Block at MMIO 0x");
|
|
uart.print_hex(base);
|
|
uart.print("\n");
|
|
var self = VirtioBlkDriver{
|
|
.transport = transport_mod.VirtioTransport.init(base),
|
|
.v_desc = undefined,
|
|
.v_avail = undefined,
|
|
.v_used = undefined,
|
|
.queue_size = 0,
|
|
};
|
|
if (self.init_device()) {
|
|
return self;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn probe_pci() ?VirtioBlkDriver {
|
|
const PCI_ECAM_BASE: usize = 0x30000000;
|
|
const bus: u8 = 0;
|
|
const func: u8 = 0;
|
|
|
|
// Scan slots 1 to 8
|
|
var i: u8 = 1;
|
|
while (i <= 8) : (i += 1) {
|
|
const addr = PCI_ECAM_BASE | (@as(usize, bus) << 20) | (@as(usize, i) << 15) | (@as(usize, func) << 12);
|
|
const ptr: *volatile u32 = @ptrFromInt(addr);
|
|
const id = ptr.*;
|
|
|
|
if (id == 0x10011af4 or id == 0x10421af4) {
|
|
uart.print("[VirtIO] Found VirtIO-Block device at PCI 00:0");
|
|
uart.print_hex(i);
|
|
uart.print(".0\n");
|
|
var self = VirtioBlkDriver{
|
|
.transport = transport_mod.VirtioTransport.init(addr),
|
|
.v_desc = undefined,
|
|
.v_avail = undefined,
|
|
.v_used = undefined,
|
|
.queue_size = 0,
|
|
};
|
|
if (self.init_device()) {
|
|
return self;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn init_device(self: *VirtioBlkDriver) bool {
|
|
if (!self.transport.probe()) return false;
|
|
|
|
self.transport.reset();
|
|
self.transport.add_status(1); // ACKNOWLEDGE
|
|
self.transport.add_status(2); // DRIVER
|
|
|
|
// Feature negotiation
|
|
const dev_features = self.transport.get_device_features();
|
|
_ = dev_features;
|
|
// Accept no special features for block — just basic operation
|
|
self.transport.set_driver_features(0);
|
|
transport_mod.io_barrier();
|
|
// FEATURES_OK only on modern (v2) transport
|
|
if (self.transport.is_modern) {
|
|
self.transport.add_status(8); // FEATURES_OK
|
|
transport_mod.io_barrier();
|
|
}
|
|
|
|
self.transport.select_queue(0);
|
|
const max_count = self.transport.get_queue_size();
|
|
// Cap queue size for memory efficiency
|
|
const MAX_BLK_QUEUE: u16 = 128;
|
|
const count = if (max_count > MAX_BLK_QUEUE) MAX_BLK_QUEUE else max_count;
|
|
|
|
// [Desc] [Avail] [Used] (Simplified layout)
|
|
const total = (count * 16) + (6 + count * 2) + 4096 + (6 + count * 8);
|
|
const raw_ptr = malloc(total + 4096) orelse return false;
|
|
const aligned = (@intFromPtr(raw_ptr) + 4095) & ~@as(usize, 4095);
|
|
|
|
// Zero out queue memory to ensure clean state
|
|
const byte_ptr: [*]u8 = @ptrFromInt(aligned);
|
|
for (0..total) |i| {
|
|
byte_ptr[i] = 0;
|
|
}
|
|
|
|
self.v_desc = @ptrFromInt(aligned);
|
|
self.v_avail = @ptrFromInt(aligned + (count * 16));
|
|
self.v_used = @ptrFromInt(aligned + (count * 16) + (6 + count * 2) + 4096);
|
|
self.queue_size = count;
|
|
|
|
// Ensure avail/used rings start clean
|
|
self.v_avail.flags = 0;
|
|
self.v_avail.idx = 0;
|
|
self.v_used.flags = 0;
|
|
|
|
if (self.transport.is_modern) {
|
|
self.transport.setup_modern_queue(aligned, aligned + (count * 16), @intFromPtr(self.v_used));
|
|
} else {
|
|
self.transport.set_queue_size(count);
|
|
self.transport.setup_legacy_queue(@intCast(aligned >> 12));
|
|
}
|
|
|
|
self.transport.add_status(4); // DRIVER_OK
|
|
global_blk = self.*;
|
|
|
|
uart.print("[VirtIO-Blk] Device Ready. Queue Size: ");
|
|
uart.print_hex(count);
|
|
uart.print(" HeaderSize: ");
|
|
uart.print_hex(@sizeOf(VirtioBlkHeader));
|
|
uart.print("\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
pub fn read(self: *VirtioBlkDriver, sector: u64, buf: []u8) !void {
|
|
const header = VirtioBlkHeader{
|
|
.type = 0, // READ
|
|
.reserved = 0,
|
|
.sector = sector,
|
|
};
|
|
var status: u8 = 0xFF;
|
|
|
|
// Simple synchronous request: Use descriptors 0, 1, 2
|
|
// Desc 0: Header (Read-only for device)
|
|
self.v_desc[0].addr = @intFromPtr(&header);
|
|
self.v_desc[0].len = @sizeOf(VirtioBlkHeader);
|
|
self.v_desc[0].flags = 1; // NEXT
|
|
self.v_desc[0].next = 1;
|
|
|
|
// Desc 1: Data Buffer (Write-only for device)
|
|
self.v_desc[1].addr = @intFromPtr(buf.ptr);
|
|
self.v_desc[1].len = 512;
|
|
self.v_desc[1].flags = 1 | 2; // NEXT | WRITE
|
|
self.v_desc[1].next = 2;
|
|
|
|
// Desc 2: Status Byte (Write-only for device)
|
|
self.v_desc[2].addr = @intFromPtr(&status);
|
|
self.v_desc[2].len = 1;
|
|
self.v_desc[2].flags = 2; // WRITE
|
|
self.v_desc[2].next = 0;
|
|
|
|
// Submit to Avail Ring
|
|
const ring = @as([*]volatile u16, @ptrFromInt(@intFromPtr(self.v_avail) + 4));
|
|
ring[self.v_avail.idx % self.queue_size] = 0; // Head of chain
|
|
const expected_used = self.v_used.idx +% 1;
|
|
transport_mod.io_barrier();
|
|
self.v_avail.idx +%= 1;
|
|
transport_mod.io_barrier();
|
|
|
|
self.transport.notify(0);
|
|
|
|
// Wait for device (Polling — wait until used ring advances)
|
|
var timeout: usize = 0;
|
|
while (self.v_used.idx != expected_used) {
|
|
transport_mod.io_barrier();
|
|
timeout += 1;
|
|
if (timeout > 100_000_000) {
|
|
uart.print("[VirtIO-Blk] READ TIMEOUT! used.idx=");
|
|
uart.print_hex(self.v_used.idx);
|
|
uart.print(" expected=");
|
|
uart.print_hex(expected_used);
|
|
uart.print("\n");
|
|
return error.DiskError;
|
|
}
|
|
}
|
|
|
|
if (status != 0) return error.DiskError;
|
|
}
|
|
|
|
pub fn write(self: *VirtioBlkDriver, sector: u64, buf: []const u8) !void {
|
|
const header = VirtioBlkHeader{
|
|
.type = 1, // WRITE
|
|
.reserved = 0,
|
|
.sector = sector,
|
|
};
|
|
var status: u8 = 0xFF;
|
|
|
|
self.v_desc[3].addr = @intFromPtr(&header);
|
|
self.v_desc[3].len = @sizeOf(VirtioBlkHeader);
|
|
self.v_desc[3].flags = 1;
|
|
self.v_desc[3].next = 4;
|
|
|
|
self.v_desc[4].addr = @intFromPtr(buf.ptr);
|
|
self.v_desc[4].len = 512;
|
|
self.v_desc[4].flags = 1; // Note: Write for disk is READ for device
|
|
self.v_desc[4].next = 5;
|
|
|
|
self.v_desc[5].addr = @intFromPtr(&status);
|
|
self.v_desc[5].len = 1;
|
|
self.v_desc[5].flags = 2;
|
|
self.v_desc[5].next = 0;
|
|
|
|
const ring = @as([*]volatile u16, @ptrFromInt(@intFromPtr(self.v_avail) + 4));
|
|
ring[self.v_avail.idx % self.queue_size] = 3;
|
|
const expected_used = self.v_used.idx +% 1;
|
|
transport_mod.io_barrier();
|
|
self.v_avail.idx +%= 1;
|
|
transport_mod.io_barrier();
|
|
|
|
self.transport.notify(0);
|
|
|
|
// Wait for device (Polling — wait until used ring advances)
|
|
var timeout: usize = 0;
|
|
while (self.v_used.idx != expected_used) {
|
|
transport_mod.io_barrier();
|
|
timeout += 1;
|
|
if (timeout > 100_000_000) {
|
|
uart.print("[VirtIO-Blk] WRITE TIMEOUT!\n");
|
|
return error.DiskError;
|
|
}
|
|
}
|
|
|
|
if (status != 0) return error.DiskError;
|
|
}
|
|
|
|
const VirtioDesc = struct {
|
|
addr: u64,
|
|
len: u32,
|
|
flags: u16,
|
|
next: u16,
|
|
};
|
|
|
|
const VirtioAvail = extern struct {
|
|
flags: u16,
|
|
idx: u16,
|
|
};
|
|
|
|
const VirtioUsed = extern struct {
|
|
flags: u16,
|
|
idx: u16,
|
|
};
|
|
|
|
const VirtioBlkHeader = extern struct {
|
|
type: u32,
|
|
reserved: u32,
|
|
sector: u64,
|
|
};
|
|
};
|