diff --git a/build.sh b/build.sh index b456994..d63d4bd 100755 --- a/build.sh +++ b/build.sh @@ -109,6 +109,18 @@ zig build-obj \ mv ui.o "$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 # ========================================================= @@ -545,6 +557,7 @@ $LINKER \ "$BUILD_DIR/loader.o" \ "$BUILD_DIR/nexshell.o" \ "$BUILD_DIR/ui.o" \ + "$BUILD_DIR/gpu.o" \ "$BUILD_DIR/microui.o" \ $NIM_OBJS \ -o "$BUILD_DIR/rumpk-$ARCH.elf" diff --git a/core/kernel.nim b/core/kernel.nim index c4e22cc..d88b54b 100644 --- a/core/kernel.nim +++ b/core/kernel.nim @@ -210,6 +210,16 @@ proc kmain() {.exportc, cdecl.} = ion_pool_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 kprintln("[Kernel] Mapping Sovereign Channels...") diff --git a/hal/framebuffer.zig b/hal/framebuffer.zig index 91b9ce5..6f488f3 100644 --- a/hal/framebuffer.zig +++ b/hal/framebuffer.zig @@ -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"); +// 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 { x: i32, y: i32, @@ -7,21 +26,97 @@ pub const Rect = struct { 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 { - // 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 { - // TODO: Set scissoring - _ = r; + clip_x = r.x; + 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 { - // TODO: Plot to framebuffer - // For now, valid compilation is the goal. - _ = x; - _ = y; - _ = w; - _ = h; - _ = color; +pub fn put_pixel(x: i32, y: i32, color: u32) void { + if (x < 0 or y < 0) return; + if (x >= @as(i32, @intCast(WIDTH)) or y >= @as(i32, @intCast(HEIGHT))) return; + + // Clip check + if (x < clip_x or x >= clip_x + clip_w) return; + if (y < clip_y or y >= clip_y + clip_h) return; + + 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); + } } diff --git a/hal/gpu.zig b/hal/gpu.zig new file mode 100644 index 0000000..c44e9e2 --- /dev/null +++ b/hal/gpu.zig @@ -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(); +} diff --git a/hal/ui.zig b/hal/ui.zig index e009e1b..58b6bd3 100644 --- a/hal/ui.zig +++ b/hal/ui.zig @@ -1,7 +1,8 @@ const std = @import("std"); const fb = @import("framebuffer.zig"); -// Import the C Logic directly +// GPU Driver (extern) +extern fn virtio_gpu_flush() void; pub const c = @cImport({ @cInclude("microui.h"); }); @@ -68,6 +69,9 @@ pub fn render() void { else => {}, } } + + // PUSH TO HOST (The Retina) + virtio_gpu_flush(); } // --- HELPER: Draw Rect ---