feat(rumpk): Phase 3.5c VirtIO-GPU Retina Driver (WIP)

- Vision: Updated NexShell section with VirtIO-GPU transport detail
- Canvas: Implemented framebuffer.zig (800x600x32bpp in BSS)
- Retina: Implemented gpu.zig VirtIO-GPU MMIO driver
  - Device probing across MMIO slots 0x10001000-0x10008000
  - Support for VirtIO MMIO v1 (legacy) and v2 (modern)
  - Queue setup with PFN for legacy devices
  - 2D Resource creation, backing attachment, scanout setup
- Integration: UI fiber now calls virtio_gpu_flush() after render
- Status: GPU detected at 0x10008000 (DevID=16), queue initialized
- Remaining: Debug command/response polling (hangs on first command)
This commit is contained in:
Markus Maiwald 2025-12-31 09:14:46 +01:00
parent 8aa50eb3ef
commit f6a49db00f
5 changed files with 616 additions and 12 deletions

View File

@ -109,6 +109,18 @@ zig build-obj \
mv ui.o "$BUILD_DIR/ui.o" mv ui.o "$BUILD_DIR/ui.o"
echo "$BUILD_DIR/ui.o" echo "$BUILD_DIR/ui.o"
# Compile GPU Driver (Retina)
echo "[1.3/8] Compiling GPU Driver (VirtIO-GPU)..."
zig build-obj \
-target $ZIG_TARGET \
$ZIG_OBJ_FLAGS \
-O ReleaseFast \
"$RUMPK_DIR/hal/gpu.zig" \
--name gpu
mv gpu.o "$BUILD_DIR/gpu.o"
echo "$BUILD_DIR/gpu.o"
# ========================================================= # =========================================================
# Step 2: Compile context switch assembly # Step 2: Compile context switch assembly
# ========================================================= # =========================================================
@ -545,6 +557,7 @@ $LINKER \
"$BUILD_DIR/loader.o" \ "$BUILD_DIR/loader.o" \
"$BUILD_DIR/nexshell.o" \ "$BUILD_DIR/nexshell.o" \
"$BUILD_DIR/ui.o" \ "$BUILD_DIR/ui.o" \
"$BUILD_DIR/gpu.o" \
"$BUILD_DIR/microui.o" \ "$BUILD_DIR/microui.o" \
$NIM_OBJS \ $NIM_OBJS \
-o "$BUILD_DIR/rumpk-$ARCH.elf" -o "$BUILD_DIR/rumpk-$ARCH.elf"

View File

@ -210,6 +210,16 @@ proc kmain() {.exportc, cdecl.} =
ion_pool_init() ion_pool_init()
hal_io_init() hal_io_init()
# 1.5 The Retina (VirtIO-GPU)
proc virtio_gpu_init(base: uint64) {.importc, cdecl.}
# On QEMU virt machine, virtio-mmio devices are at 0x10001000-0x10008000
# GPU could be at any slot.
kprintln("[Kernel] Scanning for VirtIO-GPU...")
for i in 1..8:
let base_addr = 0x10000000'u64 + (uint64(i) * 0x1000'u64)
virtio_gpu_init(base_addr)
# 2. Channel Infrastructure # 2. Channel Infrastructure
kprintln("[Kernel] Mapping Sovereign Channels...") kprintln("[Kernel] Mapping Sovereign Channels...")

View File

@ -1,5 +1,24 @@
// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
// Phase 3.5c: The Canvas - Static Framebuffer in BSS
// This is our "Video RAM" living in kernel memory.
const std = @import("std"); const std = @import("std");
// Resolution: 800x600 @ 32bpp (ARGB)
pub const WIDTH: usize = 800;
pub const HEIGHT: usize = 600;
pub const STRIDE: usize = WIDTH;
// The Physical Backing Store (1.9MB in BSS)
// Zero-initialized at boot.
var fb_memory: [WIDTH * HEIGHT]u32 = [_]u32{0} ** (WIDTH * HEIGHT);
// Clip rectangle (for MU_COMMAND_CLIP)
var clip_x: i32 = 0;
var clip_y: i32 = 0;
var clip_w: i32 = WIDTH;
var clip_h: i32 = HEIGHT;
pub const Rect = struct { pub const Rect = struct {
x: i32, x: i32,
y: i32, y: i32,
@ -7,21 +26,97 @@ pub const Rect = struct {
h: i32, h: i32,
}; };
pub fn get_buffer() [*]u32 {
return &fb_memory;
}
pub fn get_buffer_phys() usize {
// Identity mapping for now
return @intFromPtr(&fb_memory);
}
pub fn get_size() usize {
return WIDTH * HEIGHT * @sizeOf(u32);
}
pub fn init() void { pub fn init() void {
// TODO: Init VirtIO GPU or PL111 // Clear to dark blue (Nexus brand)
clear(0xFF101030);
}
pub fn clear(color: u32) void {
@memset(&fb_memory, color);
} }
pub fn set_clip(r: Rect) void { pub fn set_clip(r: Rect) void {
// TODO: Set scissoring clip_x = r.x;
_ = r; clip_y = r.y;
clip_w = r.w;
clip_h = r.h;
} }
pub fn fill_rect(x: i32, y: i32, w: i32, h: i32, color: u32) void { pub fn put_pixel(x: i32, y: i32, color: u32) void {
// TODO: Plot to framebuffer if (x < 0 or y < 0) return;
// For now, valid compilation is the goal. if (x >= @as(i32, @intCast(WIDTH)) or y >= @as(i32, @intCast(HEIGHT))) return;
_ = x;
_ = y; // Clip check
_ = w; if (x < clip_x or x >= clip_x + clip_w) return;
_ = h; if (y < clip_y or y >= clip_y + clip_h) return;
_ = color;
const ux: usize = @intCast(x);
const uy: usize = @intCast(y);
fb_memory[uy * WIDTH + ux] = color;
}
// Optimized Rect Fill (The Workhorse for microui)
pub fn fill_rect(x: i32, y: i32, w: i32, h: i32, color: u32) void {
// Clipping to screen
var rx = x;
var ry = y;
var rw = w;
var rh = h;
if (rx < 0) {
rw += rx;
rx = 0;
}
if (ry < 0) {
rh += ry;
ry = 0;
}
if (rx + rw > @as(i32, @intCast(WIDTH))) {
rw = @as(i32, @intCast(WIDTH)) - rx;
}
if (ry + rh > @as(i32, @intCast(HEIGHT))) {
rh = @as(i32, @intCast(HEIGHT)) - ry;
}
if (rw <= 0 or rh <= 0) return;
// Clip to clip rect
if (rx < clip_x) {
rw -= (clip_x - rx);
rx = clip_x;
}
if (ry < clip_y) {
rh -= (clip_y - ry);
ry = clip_y;
}
if (rx + rw > clip_x + clip_w) {
rw = clip_x + clip_w - rx;
}
if (ry + rh > clip_y + clip_h) {
rh = clip_y + clip_h - ry;
}
if (rw <= 0 or rh <= 0) return;
const start_row: usize = @intCast(ry);
const end_row: usize = start_row + @as(usize, @intCast(rh));
const start_col: usize = @intCast(rx);
const cols: usize = @intCast(rw);
var row = start_row;
while (row < end_row) : (row += 1) {
const offset = row * WIDTH + start_col;
@memset(fb_memory[offset .. offset + cols], color);
}
} }

482
hal/gpu.zig Normal file
View File

@ -0,0 +1,482 @@
// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
// Phase 3.5c: The Retina - VirtIO-GPU Driver
// Blasts pixels from RAM canvas to host display.
const std = @import("std");
const fb = @import("framebuffer.zig");
const uart = @import("uart.zig");
// =========================================================
// VirtIO-GPU Constants
// =========================================================
const VIRTIO_GPU_CMD_GET_DISPLAY_INFO: u32 = 0x0100;
const VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: u32 = 0x0101;
const VIRTIO_GPU_CMD_RESOURCE_UNREF: u32 = 0x0102;
const VIRTIO_GPU_CMD_SET_SCANOUT: u32 = 0x0103;
const VIRTIO_GPU_CMD_RESOURCE_FLUSH: u32 = 0x0104;
const VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: u32 = 0x0105;
const VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: u32 = 0x0106;
const VIRTIO_GPU_RESP_OK_NODATA: u32 = 0x1100;
const VIRTIO_GPU_RESP_OK_DISPLAY_INFO: u32 = 0x1101;
const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1;
const RESOURCE_ID: u32 = 1;
// =========================================================
// VirtIO-GPU Structures (packed for wire format)
// =========================================================
const VirtioGpuCtrlHdr = extern struct {
type: u32,
flags: u32,
fence_id: u64,
ctx_id: u32,
padding: u32,
};
const VirtioGpuResourceCreate2D = extern struct {
hdr: VirtioGpuCtrlHdr,
resource_id: u32,
format: u32,
width: u32,
height: u32,
};
const VirtioGpuMemEntry = extern struct {
addr: u64,
length: u32,
padding: u32,
};
const VirtioGpuResourceAttachBacking = extern struct {
hdr: VirtioGpuCtrlHdr,
resource_id: u32,
nr_entries: u32,
// Followed by VirtioGpuMemEntry array
};
const VirtioGpuSetScanout = extern struct {
hdr: VirtioGpuCtrlHdr,
r_x: u32,
r_y: u32,
r_width: u32,
r_height: u32,
scanout_id: u32,
resource_id: u32,
};
const VirtioGpuTransferToHost2D = extern struct {
hdr: VirtioGpuCtrlHdr,
r_x: u32,
r_y: u32,
r_width: u32,
r_height: u32,
offset: u64,
resource_id: u32,
padding: u32,
};
const VirtioGpuResourceFlush = extern struct {
hdr: VirtioGpuCtrlHdr,
r_x: u32,
r_y: u32,
r_width: u32,
r_height: u32,
resource_id: u32,
padding: u32,
};
// =========================================================
// Driver State
// =========================================================
var initialized: bool = false;
// MMIO base for VirtIO-GPU (discovered at runtime or hardcoded for virt machine)
// On QEMU virt, virtio-gpu-device is at 0x10008000 (device 1 after net)
// Actually it depends on dtb, for now we'll use a probe or hardcode.
var mmio_base: usize = 0;
// Control queue (Queue 0)
const QUEUE_SIZE = 16;
const VirtioDesc = extern struct {
addr: u64,
len: u32,
flags: u16,
next: u16,
};
const VirtioAvail = extern struct {
flags: u16,
idx: u16,
ring: [QUEUE_SIZE]u16,
};
const VirtioUsed = extern struct {
flags: u16,
idx: u16,
ring: [QUEUE_SIZE]extern struct { id: u32, len: u32 },
};
var desc_table: [QUEUE_SIZE]VirtioDesc = undefined;
var avail_ring: VirtioAvail = undefined;
var used_ring: VirtioUsed = undefined;
var desc_idx: u16 = 0;
var last_used_idx: u16 = 0;
// Command/Response buffers (static)
var cmd_buf: [512]u8 align(4096) = undefined;
var resp_buf: [256]u8 align(4096) = undefined;
// =========================================================
// MMIO Helpers
// =========================================================
fn mmio_read(offset: usize) u32 {
const ptr: *volatile u32 = @ptrFromInt(mmio_base + offset);
return ptr.*;
}
fn mmio_write(offset: usize, val: u32) void {
const ptr: *volatile u32 = @ptrFromInt(mmio_base + offset);
ptr.* = val;
}
// VirtIO MMIO offsets
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_DRIVER_FEATURES = 0x020;
const VIRTIO_MMIO_QUEUE_SEL = 0x030;
const VIRTIO_MMIO_QUEUE_NUM_MAX = 0x034;
const VIRTIO_MMIO_QUEUE_NUM = 0x038;
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_STATUS_ACKNOWLEDGE = 1;
const VIRTIO_STATUS_DRIVER = 2;
const VIRTIO_STATUS_DRIVER_OK = 4;
const VIRTIO_STATUS_FEATURES_OK = 8;
const VRING_DESC_F_NEXT: u16 = 1;
const VRING_DESC_F_WRITE: u16 = 2;
// =========================================================
// Queue Operations
// =========================================================
fn queue_init() void {
// Select queue 0 (controlq)
mmio_write(VIRTIO_MMIO_QUEUE_SEL, 0);
const max = mmio_read(VIRTIO_MMIO_QUEUE_NUM_MAX);
if (max == 0) {
uart.print("[GPU] Queue 0 not available!\n");
return;
}
mmio_write(VIRTIO_MMIO_QUEUE_NUM, QUEUE_SIZE);
// Legacy (v1) uses a single contiguous page for desc+avail+used
// and QUEUE_PFN register instead of separate addresses.
// For simplicity, we use a static aligned buffer.
const version = mmio_read(VIRTIO_MMIO_VERSION);
if (version == 1) {
// Legacy VirtIO MMIO v1
// Queue address = page frame number (page-aligned address / page_size)
// We need to provide a contiguous buffer for desc, avail, used.
// For now, use our static arrays but provide the desc address as PFN.
const page_addr = @intFromPtr(&desc_table) & 0xFFFFF000; // Page aligned
const pfn = page_addr / 4096; // Page frame number
// QUEUE_ALIGN register at 0x3c (page size, usually 4096)
// QUEUE_PFN register at 0x40
const VIRTIO_MMIO_QUEUE_ALIGN: usize = 0x03c;
const VIRTIO_MMIO_QUEUE_PFN: usize = 0x040;
mmio_write(VIRTIO_MMIO_QUEUE_ALIGN, 4096);
mmio_write(VIRTIO_MMIO_QUEUE_PFN, @truncate(pfn));
uart.print("[GPU] Legacy queue at PFN 0x");
uart.print_hex(pfn);
uart.print("\n");
} else {
// Modern VirtIO MMIO v2
const desc_addr = @intFromPtr(&desc_table);
const avail_addr = @intFromPtr(&avail_ring);
const used_addr = @intFromPtr(&used_ring);
mmio_write(VIRTIO_MMIO_QUEUE_DESC_LOW, @truncate(desc_addr));
mmio_write(VIRTIO_MMIO_QUEUE_DESC_HIGH, @truncate(desc_addr >> 32));
mmio_write(VIRTIO_MMIO_QUEUE_AVAIL_LOW, @truncate(avail_addr));
mmio_write(VIRTIO_MMIO_QUEUE_AVAIL_HIGH, @truncate(avail_addr >> 32));
mmio_write(VIRTIO_MMIO_QUEUE_USED_LOW, @truncate(used_addr));
mmio_write(VIRTIO_MMIO_QUEUE_USED_HIGH, @truncate(used_addr >> 32));
mmio_write(VIRTIO_MMIO_QUEUE_READY, 1);
}
avail_ring.idx = 0;
last_used_idx = 0;
desc_idx = 0;
}
fn send_command(cmd_ptr: [*]const u8, cmd_len: usize) void {
// Descriptor 0: Command (device read)
desc_table[0] = .{
.addr = @intFromPtr(cmd_ptr),
.len = @intCast(cmd_len),
.flags = VRING_DESC_F_NEXT,
.next = 1,
};
// Descriptor 1: Response (device write)
desc_table[1] = .{
.addr = @intFromPtr(&resp_buf),
.len = @sizeOf(@TypeOf(resp_buf)),
.flags = VRING_DESC_F_WRITE,
.next = 0,
};
// Add to available ring
avail_ring.ring[avail_ring.idx % QUEUE_SIZE] = 0;
asm volatile ("fence" ::: .{ .memory = true });
avail_ring.idx +%= 1;
asm volatile ("fence" ::: .{ .memory = true });
// Notify device
mmio_write(VIRTIO_MMIO_QUEUE_NOTIFY, 0);
// Wait for response (polling)
while (last_used_idx == used_ring.idx) {
asm volatile ("" ::: .{ .memory = true });
}
last_used_idx = used_ring.idx;
}
// =========================================================
// GPU Commands
// =========================================================
fn cmd_resource_create_2d() void {
const cmd = @as(*VirtioGpuResourceCreate2D, @ptrCast(@alignCast(&cmd_buf)));
cmd.* = .{
.hdr = .{
.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
.flags = 0,
.fence_id = 0,
.ctx_id = 0,
.padding = 0,
},
.resource_id = RESOURCE_ID,
.format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM,
.width = fb.WIDTH,
.height = fb.HEIGHT,
};
send_command(&cmd_buf, @sizeOf(VirtioGpuResourceCreate2D));
}
fn cmd_attach_backing() void {
// We need to send the header + 1 mem entry
const AttachCmd = extern struct {
hdr: VirtioGpuCtrlHdr,
resource_id: u32,
nr_entries: u32,
entry: VirtioGpuMemEntry,
};
const cmd = @as(*AttachCmd, @ptrCast(@alignCast(&cmd_buf)));
cmd.* = .{
.hdr = .{
.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
.flags = 0,
.fence_id = 0,
.ctx_id = 0,
.padding = 0,
},
.resource_id = RESOURCE_ID,
.nr_entries = 1,
.entry = .{
.addr = fb.get_buffer_phys(),
.length = @intCast(fb.get_size()),
.padding = 0,
},
};
send_command(&cmd_buf, @sizeOf(AttachCmd));
}
fn cmd_set_scanout() void {
const cmd = @as(*VirtioGpuSetScanout, @ptrCast(@alignCast(&cmd_buf)));
cmd.* = .{
.hdr = .{
.type = VIRTIO_GPU_CMD_SET_SCANOUT,
.flags = 0,
.fence_id = 0,
.ctx_id = 0,
.padding = 0,
},
.r_x = 0,
.r_y = 0,
.r_width = fb.WIDTH,
.r_height = fb.HEIGHT,
.scanout_id = 0,
.resource_id = RESOURCE_ID,
};
send_command(&cmd_buf, @sizeOf(VirtioGpuSetScanout));
}
fn cmd_transfer_2d() void {
const cmd = @as(*VirtioGpuTransferToHost2D, @ptrCast(@alignCast(&cmd_buf)));
cmd.* = .{
.hdr = .{
.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
.flags = 0,
.fence_id = 0,
.ctx_id = 0,
.padding = 0,
},
.r_x = 0,
.r_y = 0,
.r_width = fb.WIDTH,
.r_height = fb.HEIGHT,
.offset = 0,
.resource_id = RESOURCE_ID,
.padding = 0,
};
send_command(&cmd_buf, @sizeOf(VirtioGpuTransferToHost2D));
}
fn cmd_resource_flush() void {
const cmd = @as(*VirtioGpuResourceFlush, @ptrCast(@alignCast(&cmd_buf)));
cmd.* = .{
.hdr = .{
.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH,
.flags = 0,
.fence_id = 0,
.ctx_id = 0,
.padding = 0,
},
.r_x = 0,
.r_y = 0,
.r_width = fb.WIDTH,
.r_height = fb.HEIGHT,
.resource_id = RESOURCE_ID,
.padding = 0,
};
send_command(&cmd_buf, @sizeOf(VirtioGpuResourceFlush));
}
// =========================================================
// Public API
// =========================================================
pub fn probe(base: usize) bool {
mmio_base = base;
const magic = mmio_read(VIRTIO_MMIO_MAGIC_VALUE);
const version = mmio_read(VIRTIO_MMIO_VERSION);
const device_id = mmio_read(VIRTIO_MMIO_DEVICE_ID);
// Debug: Print what we found
uart.print("[GPU Probe] 0x");
uart.print_hex(base);
uart.print(" Magic=0x");
uart.print_hex(magic);
uart.print(" Ver=");
uart.print_hex(version);
uart.print(" DevID=");
uart.print_hex(device_id);
uart.print("\n");
// Magic = "virt" (0x74726976), Version = 1 or 2, Device ID = 16 (GPU)
if (magic != 0x74726976) return false;
if (version != 1 and version != 2) return false;
if (device_id != 16) return false;
uart.print("[GPU] VirtIO-GPU found at 0x");
uart.print_hex(base);
uart.print("\n");
return true;
}
pub fn init(base: usize) void {
if (!probe(base)) {
uart.print("[GPU] No VirtIO-GPU device found.\n");
return;
}
// Reset
mmio_write(VIRTIO_MMIO_STATUS, 0);
// Acknowledge
mmio_write(VIRTIO_MMIO_STATUS, VIRTIO_STATUS_ACKNOWLEDGE);
// Driver
mmio_write(VIRTIO_MMIO_STATUS, VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER);
// Features (we don't need any special features for basic 2D)
mmio_write(VIRTIO_MMIO_DRIVER_FEATURES, 0);
// Features OK
mmio_write(VIRTIO_MMIO_STATUS, VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK);
// Setup queue
queue_init();
// Driver OK
mmio_write(VIRTIO_MMIO_STATUS, VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK | VIRTIO_STATUS_DRIVER_OK);
// Initialize framebuffer
fb.init();
// GPU Setup sequence
uart.print("[GPU] Creating 2D Resource...\n");
cmd_resource_create_2d();
uart.print("[GPU] Attaching Backing...\n");
cmd_attach_backing();
uart.print("[GPU] Setting Scanout...\n");
cmd_set_scanout();
initialized = true;
uart.print("[GPU] VirtIO-GPU initialized. Resolution: 800x600\n");
// Draw initial test pattern
fb.fill_rect(100, 100, 200, 50, 0xFF00FF00); // Neon green bar
flush();
}
pub fn flush() void {
if (!initialized) return;
cmd_transfer_2d();
cmd_resource_flush();
}
// Export for Nim
export fn virtio_gpu_init(base: usize) void {
init(base);
}
export fn virtio_gpu_flush() void {
flush();
}

View File

@ -1,7 +1,8 @@
const std = @import("std"); const std = @import("std");
const fb = @import("framebuffer.zig"); const fb = @import("framebuffer.zig");
// Import the C Logic directly // GPU Driver (extern)
extern fn virtio_gpu_flush() void;
pub const c = @cImport({ pub const c = @cImport({
@cInclude("microui.h"); @cInclude("microui.h");
}); });
@ -68,6 +69,9 @@ pub fn render() void {
else => {}, else => {},
} }
} }
// PUSH TO HOST (The Retina)
virtio_gpu_flush();
} }
// --- HELPER: Draw Rect --- // --- HELPER: Draw Rect ---