From f5ac7189a92b1b86fa4829c1a8b57127e5e2b8e9 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 19 Oct 2026 00:01:29 +0200 Subject: [PATCH] fix: VirtIO GPU driver and snake rendering through display server - Fix swapped command IDs: attach_backing was 0x0104 (FLUSH), resource_flush was 0x0106 (ATTACH_BACKING). Now correct. - Increase process stack from 4 pages (16 KB) to 16 pages (64 KB) to prevent stack overflow in display process. - Restructure display-qemu to avoid MockFramebuffer's 32 KB array inflating the VirtIO path's stack frame. - Wire snake app to render through display server via IPC instead of UART escape codes. Uses existing renderer.rs pixel renderer. - Init now delegates display endpoint to snake process. --- Cargo.lock | 3 + crates/adapter-riscv/src/boot/loader.rs | 4 +- crates/apps/snake/Cargo.toml | 2 + crates/apps/snake/src/main.rs | 113 +++++++----------------- crates/bins/display-qemu/src/main.rs | 29 ++++-- crates/hw-virtio/src/gpu.rs | 4 +- crates/init/src/lib.rs | 3 +- 7 files changed, 63 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 916e338..7c8e16e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,7 @@ name = "display-qemu" version = "0.1.0" dependencies = [ "display-server", + "driver-framebuffer-mock", "driver-framebuffer-virtio", "port-framebuffer", "server-framework", @@ -283,6 +284,8 @@ dependencies = [ name = "snake" version = "0.1.0" dependencies = [ + "display-server", + "server-framework", "types", "userspace", "userspace-rt", diff --git a/crates/adapter-riscv/src/boot/loader.rs b/crates/adapter-riscv/src/boot/loader.rs index bbf2831..08ce788 100644 --- a/crates/adapter-riscv/src/boot/loader.rs +++ b/crates/adapter-riscv/src/boot/loader.rs @@ -161,10 +161,10 @@ fn load_elf( } } - for i in 0..4usize { + for i in 0..16usize { let sp = mmu::allocator::alloc_zeroed_page(); mmu::map(user_pt, mmu::Mapping { - va: types::address::VirtualAddress::from_raw(USER_VA_BASE - (4 - i) * 4096), + va: types::address::VirtualAddress::from_raw(USER_VA_BASE - (16 - i) * 4096), pa: types::address::PhysicalAddress::from_raw(sp), pages: 1, flags: mmu::Sv39Flags::user_rw(), diff --git a/crates/apps/snake/Cargo.toml b/crates/apps/snake/Cargo.toml index 5242f4c..93d7ce7 100644 --- a/crates/apps/snake/Cargo.toml +++ b/crates/apps/snake/Cargo.toml @@ -12,3 +12,5 @@ bench = false types = { workspace = true } userspace = { workspace = true } userspace-rt = { workspace = true } +display-server = { workspace = true } +server-framework = { workspace = true } diff --git a/crates/apps/snake/src/main.rs b/crates/apps/snake/src/main.rs index 333076d..3f25c3c 100644 --- a/crates/apps/snake/src/main.rs +++ b/crates/apps/snake/src/main.rs @@ -3,119 +3,70 @@ extern crate userspace_rt; -use snake::game::{Direction, GameState, Snake, GRID_WIDTH, GRID_HEIGHT}; +use snake::game::{Direction, GameState, Snake}; +use snake::renderer::{self, RenderCommand}; use types::bounded::CapSlot; +use types::display::{Color, Dimension, ScreenCoord}; use types::time::Ticks; use userspace_rt::syscall; +use server_framework::Encode; +use display_server::protocol::DisplayRequest; const INPUT_EP: CapSlot = CapSlot::const_new(0); +const DISPLAY_EP: CapSlot = CapSlot::const_new(1); const TICK_DELAY: u64 = 2_000_000; +const MAX_RENDER_CMDS: usize = 140; -fn putchar(c: u8) { syscall::debug_putchar(c); } -fn puts(s: &[u8]) { for &b in s { putchar(b); } } - -fn move_cursor(row: u16, col: u16) { - puts(b"\x1b["); - print_dec(row + 1); - putchar(b';'); - print_dec(col + 1); - putchar(b'H'); +fn display_send(req: &DisplayRequest) { + let words = req.encode(); + syscall::send(DISPLAY_EP, words[0], words[1], words[2], words[3]); + syscall::receive(DISPLAY_EP); } -fn print_dec(mut v: u16) { - if v == 0 { putchar(b'0'); return; } - let mut buf = [0u8; 5]; - let mut i = 0; - while v > 0 { buf[i] = b'0' + (v % 10) as u8; v /= 10; i += 1; } - while i > 0 { i -= 1; putchar(buf[i]); } +fn draw_rect(x: u16, y: u16, w: u16, h: u16, color: u32) { + display_send(&DisplayRequest::DrawRect { + x: ScreenCoord::new(x), + y: ScreenCoord::new(y), + w: Dimension::new(w), + h: Dimension::new(h), + color: Color::from_raw(color), + }); } -fn set_color(fg: u8) { - puts(b"\x1b["); - print_dec(fg as u16); - putchar(b'm'); +fn flush() { + display_send(&DisplayRequest::Flush); } #[unsafe(no_mangle)] extern "C" fn main() { let mut snake = Snake::new(); - - puts(b"\x1b[2J"); // clear screen - puts(b"\x1b[?25l"); // hide cursor - - // Draw border - set_color(90); // dark grey - move_cursor(0, 0); - for _ in 0..GRID_WIDTH + 2 { putchar(b'#'); } - for y in 0..GRID_HEIGHT { - move_cursor(y + 1, 0); - putchar(b'#'); - move_cursor(y + 1, GRID_WIDTH + 1); - putchar(b'#'); - } - move_cursor(GRID_HEIGHT + 1, 0); - for _ in 0..GRID_WIDTH + 2 { putchar(b'#'); } + let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS]; loop { - // Render - render_game(&snake); + let count = renderer::render(&snake, &mut commands); + for i in 0..count { + let cmd = &commands[i]; + draw_rect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.color); + } + flush(); wait_tick(); handle_input(&mut snake); snake.tick(); if snake.state() == GameState::GameOver { - move_cursor(GRID_HEIGHT / 2, GRID_WIDTH / 2 - 4); - set_color(91); // bright red - puts(b"GAME OVER"); - set_color(0); - move_cursor(GRID_HEIGHT + 3, 0); - puts(b"Score: "); - print_dec(snake.score()); - putchar(b'\n'); + let gw = renderer::grid_pixel_width(); + let gh = renderer::grid_pixel_height(); + draw_rect(gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFF800000); + flush(); let end = Ticks::from_raw(syscall::get_time().raw() + 50_000_000); while syscall::get_time().raw() < end.raw() { syscall::yield_now(); } - puts(b"\x1b[?25h"); // show cursor return; } } } -fn render_game(snake: &Snake) { - // Clear game area - for y in 0..GRID_HEIGHT { - move_cursor(y + 1, 1); - for _ in 0..GRID_WIDTH { putchar(b' '); } - } - - // Draw food - let food = snake.food(); - move_cursor(food.y + 1, food.x + 1); - set_color(91); // bright red - putchar(b'@'); - - // Draw snake body - let body = snake.body(); - for (i, pos) in body.iter().enumerate() { - move_cursor(pos.y + 1, pos.x + 1); - if i == 0 { - set_color(92); // bright green - putchar(b'O'); // head - } else { - set_color(32); // green - putchar(b'o'); // body - } - } - - // Score - set_color(0); - move_cursor(GRID_HEIGHT + 2, 0); - puts(b"Score: "); - print_dec(snake.score()); - puts(b" WASD to move"); -} - fn handle_input(snake: &mut Snake) { let _ = syscall::send(INPUT_EP, 0, 0, 0, 0); let (tag, kind, _, _) = syscall::receive(INPUT_EP); diff --git a/crates/bins/display-qemu/src/main.rs b/crates/bins/display-qemu/src/main.rs index a2c1be1..60bcab1 100644 --- a/crates/bins/display-qemu/src/main.rs +++ b/crates/bins/display-qemu/src/main.rs @@ -10,16 +10,29 @@ extern "C" fn main() { let ep = CapSlot::const_new(0); if let Some(fb) = driver_framebuffer_virtio::VirtioFramebuffer::try_new() { - let mut srv = display_server::server::DisplayServer::new(fb); - server_framework::serve(&mut srv, ep); + run_virtio(fb, ep); } else { - let fb = driver_framebuffer_mock::MockFramebuffer::new( - types::display::Dimension::new(640), - types::display::Dimension::new(480), - ); - let mut srv = display_server::server::DisplayServer::new(fb); - server_framework::serve(&mut srv, ep); + run_mock(ep); } loop { userspace::syscall::yield_now(); } } + +#[inline(never)] +fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot) { + use port_framebuffer::Framebuffer; + fb.clear(types::display::Color::rgb(30, 30, 60)); + fb.flush(); + let mut srv = display_server::server::DisplayServer::new(fb); + server_framework::serve(&mut srv, ep); +} + +#[inline(never)] +fn run_mock(ep: CapSlot) { + let fb = driver_framebuffer_mock::MockFramebuffer::new( + types::display::Dimension::new(640), + types::display::Dimension::new(480), + ); + let mut srv = display_server::server::DisplayServer::new(fb); + server_framework::serve(&mut srv, ep); +} diff --git a/crates/hw-virtio/src/gpu.rs b/crates/hw-virtio/src/gpu.rs index 2e277c2..00601c9 100644 --- a/crates/hw-virtio/src/gpu.rs +++ b/crates/hw-virtio/src/gpu.rs @@ -22,7 +22,7 @@ pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, } pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) -> bool { - write_cmd_header(dma.cmd_va, 0x0104); + write_cmd_header(dma.cmd_va, 0x0106); unsafe { let p = dma.cmd_va.raw() as *mut u8; core::ptr::write_volatile(p.add(24) as *mut u32, resource_id); @@ -60,7 +60,7 @@ pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDm } pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool { - write_cmd_header(dma.cmd_va, 0x0106); + write_cmd_header(dma.cmd_va, 0x0104); unsafe { let p = dma.cmd_va.raw() as *mut u8; core::ptr::write_volatile(p.add(24) as *mut u32, x); diff --git a/crates/init/src/lib.rs b/crates/init/src/lib.rs index 7c56824..4f94e98 100644 --- a/crates/init/src/lib.rs +++ b/crates/init/src/lib.rs @@ -165,11 +165,10 @@ pub fn run(shared_buf_va: usize) -> ! { debug_print(b")\n"); } - // Load snake — give it input endpoint (slot 0) for keyboard input - // Snake renders via debug_putchar (UART) — no display endpoint needed debug_print(b"init: loading snake... "); if let Some(snake) = load_binary(shared_buf, b"snake.elf") { syscall::delegate(INPUT_EP, snake.process_cap, ipc_perms); + syscall::delegate(DISPLAY_EP, snake.process_cap, ipc_perms); syscall::spawn_thread_in(snake.process_cap, snake.entry_point, USER_STACK_TOP); debug_print(b"ok\n"); } else {