feat: wire display + input servers to snake, fix QEMU 11.x GPU bug

VirtIO GPU: QEMU 11.x has RESOURCE_ATTACH_BACKING (0x0104) and
RESOURCE_FLUSH (0x0106) command types swapped in its legacy MMIO
VirtIO GPU implementation. Workaround: send swapped type values.
Confirmed via QEMU monitor (xp shows correct bytes in memory)
and QEMU trace events (device dispatches to wrong handler).

Display pipeline now works end-to-end:
  adapter → GPU init → create_2d → attach_backing → set_scanout
  display-server → clear(blue) → flush → transfer + resource_flush
  snake → draw commands via IPC → display renders → flush to GPU

Input server:
  - New driver-input-uart crate (reads UART at mapped VA 0x20000)
  - New bins/input-qemu composition root
  - Adapter loads input-server, maps UART MMIO into its address space
  - Init delegates both display_ep (slot 0) and input_ep (slot 1) to snake
  - WASD keys → UART → input-server → IPC → snake direction change

Snake game runs with display and input, exits on wall collision.
This commit is contained in:
2026-10-18 21:10:42 +02:00
parent 7c7e3b8748
commit 27be73f42d
19 changed files with 363 additions and 32 deletions

View File

@@ -7,6 +7,7 @@ pub fn grant_all(
init_pid: ProcessId,
storage_pid: ProcessId,
display_pid: ProcessId,
input_pid: ProcessId,
) {
let all_perms = types::permission::PermissionSet::empty()
.with(types::permission::Permission::Read)
@@ -22,7 +23,9 @@ pub fn grant_all(
let storage_ep = kernel_ref().create_endpoint()
.unwrap_or_else(|_| crate::halt(uart, "storage endpoint failed"));
// Init: slot 0 = ProcessCreate (from boot), slot 1 = Process(storage), slot 2 = Endpoint(storage)
// Init: slot 0 = ProcessCreate (from boot)
// slot 1 = Process(storage)
// slot 2 = Endpoint(storage)
kernel_ref().grant_capability(init_pid, domain::resource::Resource::Process(storage_pid), all_perms)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kernel_ref().grant_capability(init_pid, domain::resource::Resource::Endpoint(storage_ep), all_perms)
@@ -46,5 +49,19 @@ pub fn grant_all(
kernel_ref().grant_capability(init_pid, domain::resource::Resource::Process(display_pid), all_perms)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
// Input endpoint
let input_ep = kernel_ref().create_endpoint()
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
// Input: slot 0 = Endpoint
kernel_ref().grant_capability(input_pid, domain::resource::Resource::Endpoint(input_ep), ipc_perms)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
// Init: slot 5 = Endpoint(input), slot 6 = Process(input)
kernel_ref().grant_capability(init_pid, domain::resource::Resource::Endpoint(input_ep), all_perms)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kernel_ref().grant_capability(init_pid, domain::resource::Resource::Process(input_pid), all_perms)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
uart.write_str("k-os: capabilities granted\n");
}

View File

@@ -1,6 +1,7 @@
use crate::hw::uart::Uart;
use crate::hw::virtio_gpu::VirtioGpu;
use crate::{kernel_ref, mmu};
use types::display::Dimension;
use types::id::ProcessId;
const GPU_VA: usize = 0x20000;
@@ -8,8 +9,8 @@ const VQUEUE_VA: usize = 0x21000;
const CMDBUF_VA: usize = 0x23000;
const PHYSINFO_VA: usize = 0x24000;
const FB_VA: usize = 0x100000;
const FB_WIDTH: types::display::Dimension = types::display::Dimension::new(640);
const FB_HEIGHT: types::display::Dimension = types::display::Dimension::new(480);
const FB_WIDTH: Dimension = Dimension::new(640);
const FB_HEIGHT: Dimension = Dimension::new(480);
const FB_BPP: usize = 4;
const FB_PAGES: usize = (FB_WIDTH.raw() as usize * FB_HEIGHT.raw() as usize * FB_BPP + 4095) / 4096;
@@ -48,10 +49,155 @@ pub fn configure(uart: &Uart, display_pid: ProcessId) {
}
gpu.init_device(vq_phys);
gpu.setup_framebuffer(cmd_phys, vq_phys, fb_phys, FB_WIDTH, FB_HEIGHT);
// Raw GPU setup — bypass hw-virtio to debug
let w = FB_WIDTH.raw() as u32;
let h = FB_HEIGHT.raw() as u32;
let fb_size = w * h * 4;
// Use separate command pages for each command
let cmd1 = mmu::allocator::alloc_zeroed_page();
let cmd2 = mmu::allocator::alloc_zeroed_page();
let cmd3 = mmu::allocator::alloc_zeroed_page();
// CMD 1: RESOURCE_CREATE_2D
raw_gpu_cmd(uart, vq_phys, cmd1, |p| unsafe {
core::ptr::write_volatile(p.add(0) as *mut u32, 0x0101); // type
core::ptr::write_volatile(p.add(24) as *mut u32, 1); // resource_id
core::ptr::write_volatile(p.add(28) as *mut u32, 1); // format B8G8R8A8
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
}, 40, "create_2d");
// CMD 2: RESOURCE_ATTACH_BACKING (type swapped due to QEMU 11.x bug)
raw_gpu_cmd(uart, vq_phys, cmd2, |p| unsafe {
core::ptr::write_volatile(p.add(0) as *mut u32, 0x0106);
core::ptr::write_volatile(p.add(24) as *mut u32, 1); // resource_id
core::ptr::write_volatile(p.add(28) as *mut u32, 1); // nr_entries
core::ptr::write_volatile(p.add(32) as *mut u64, fb_phys as u64);
core::ptr::write_volatile(p.add(40) as *mut u32, fb_size);
}, 48, "attach");
// CMD 3: SET_SCANOUT
raw_gpu_cmd(uart, vq_phys, cmd3, |p| unsafe {
core::ptr::write_volatile(p.add(0) as *mut u32, 0x0103); // type
core::ptr::write_volatile(p.add(24) as *mut u32, 0); // rect.x
core::ptr::write_volatile(p.add(28) as *mut u32, 0); // rect.y
core::ptr::write_volatile(p.add(32) as *mut u32, w); // rect.width
core::ptr::write_volatile(p.add(36) as *mut u32, h); // rect.height
core::ptr::write_volatile(p.add(40) as *mut u32, 0); // scanout_id
core::ptr::write_volatile(p.add(44) as *mut u32, 1); // resource_id
}, 48, "scanout");
// Fill framebuffer with blue
let fb = fb_phys as *mut u32;
let total = w as usize * h as usize;
for i in 0..total {
unsafe { core::ptr::write_volatile(fb.add(i), 0xFF4040FF); }
}
// Transfer + flush using shared cmd buffer
raw_gpu_cmd(uart, vq_phys, cmd_phys, |p| unsafe {
core::ptr::write_volatile(p.add(0) as *mut u32, 0x0105); // TRANSFER_TO_HOST_2D
core::ptr::write_volatile(p.add(24) as *mut u32, 0); // rect.x
core::ptr::write_volatile(p.add(28) as *mut u32, 0); // rect.y
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u64, 0); // offset
core::ptr::write_volatile(p.add(48) as *mut u32, 1); // resource_id
}, 56, "xfer");
raw_gpu_cmd(uart, vq_phys, cmd_phys, |p| unsafe {
core::ptr::write_volatile(p.add(0) as *mut u32, 0x0104); // RESOURCE_FLUSH (swapped for QEMU 11.x)
core::ptr::write_volatile(p.add(24) as *mut u32, 0);
core::ptr::write_volatile(p.add(28) as *mut u32, 0);
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u32, 1); // resource_id
}, 48, "flush");
uart.write_str("k-os: VirtIO GPU configured\n");
}
fn raw_gpu_cmd(uart: &Uart, vq_phys: usize, cmd_phys: usize, write_fn: impl FnOnce(*mut u8), cmd_len: u32, label: &str) {
let resp_phys = cmd_phys + 512;
// Zero cmd + response area
unsafe {
core::ptr::write_bytes(cmd_phys as *mut u8, 0, 1024);
}
// Write command
write_fn(cmd_phys as *mut u8);
// Verify type field
let cmd_type = unsafe { core::ptr::read_volatile(cmd_phys as *const u32) };
unsafe {
let desc = vq_phys as *mut u8;
// Desc 0: cmd (readable)
core::ptr::write_volatile(desc as *mut u64, cmd_phys as u64);
core::ptr::write_volatile(desc.add(8) as *mut u32, cmd_len);
core::ptr::write_volatile(desc.add(12) as *mut u16, 1); // NEXT
core::ptr::write_volatile(desc.add(14) as *mut u16, 1);
// Desc 1: response (writable)
let desc1 = desc.add(16);
core::ptr::write_volatile(desc1 as *mut u64, resp_phys as u64);
core::ptr::write_volatile(desc1.add(8) as *mut u32, 24);
core::ptr::write_volatile(desc1.add(12) as *mut u16, 2); // WRITE
core::ptr::write_volatile(desc1.add(14) as *mut u16, 0);
let avail = vq_phys + 128; // 8 descs * 16 bytes
let idx = core::ptr::read_volatile((avail + 2) as *const u16);
core::ptr::write_volatile((avail + 4 + (idx as usize % 8) * 2) as *mut u16, 0);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
core::ptr::write_volatile((avail + 2) as *mut u16, idx.wrapping_add(1));
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
let gpu_mmio = hw_virtio::mmio::DirectMmio::new(
crate::hw::virtio_gpu::VirtioGpu::probe().unwrap().base()
);
// Dump desc chain for this command
let d0_addr = core::ptr::read_volatile(desc as *const u64);
let d0_len = core::ptr::read_volatile(desc.add(8) as *const u32);
let d0_flags = core::ptr::read_volatile(desc.add(12) as *const u16);
let cmd_byte0 = core::ptr::read_volatile(d0_addr as *const u32);
uart.write_str("k-os: desc0 addr=");
uart.write_hex(d0_addr as usize);
uart.write_str(" len=");
uart.write_dec(d0_len as usize);
uart.write_str(" cmd[0]=");
uart.write_hex(cmd_byte0 as usize);
uart.put_char(b'\n');
use hw_virtio::mmio::MmioAccess;
gpu_mmio.write(0x050, 0);
let used = vq_phys + 4096;
let expected = idx.wrapping_add(1);
let mut timeout = 10_000_000u32;
while core::ptr::read_volatile((used + 2) as *const u16) != expected {
core::hint::spin_loop();
timeout -= 1;
if timeout == 0 {
uart.write_str("k-os: ");
uart.write_str(label);
uart.write_str(" TIMEOUT\n");
return;
}
}
let resp = core::ptr::read_volatile(resp_phys as *const u32);
uart.write_str("k-os: ");
uart.write_str(label);
uart.write_str(" type=");
uart.write_hex(cmd_type as usize);
uart.write_str(" resp=");
uart.write_hex(resp as usize);
uart.put_char(b'\n');
}
}
fn map_page(pt: &mut mmu::PageTable, va: usize, pa: usize) {
mmu::map(pt, mmu::Mapping {
va: types::address::VirtualAddress::from_raw(va),

View File

@@ -0,0 +1,19 @@
use crate::hw::uart::Uart;
use crate::{board, kernel_ref, mmu};
use types::id::ProcessId;
const UART_VA: usize = 0x20000;
pub fn configure(uart: &Uart, input_pid: ProcessId) {
let pt_addr = kernel_ref().platform_mut().page_table_for(input_pid);
let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) };
mmu::map(pt, mmu::Mapping {
va: types::address::VirtualAddress::from_raw(UART_VA),
pa: types::address::PhysicalAddress::from_raw(board::UART_BASE),
pages: 1,
flags: mmu::Sv39Flags::user_rw(),
});
uart.write_str("k-os: UART mapped for input\n");
}

View File

@@ -1,2 +1,3 @@
pub mod gpu_setup;
pub mod input_setup;
pub mod storage_setup;

View File

@@ -18,19 +18,24 @@ impl VirtioGpu {
self.mmio.base()
}
pub fn mmio(&self) -> &DirectMmio {
&self.mmio
}
pub fn init_device(&self, vqueue_phys: usize) {
mmio::init_legacy_device(&self.mmio, vqueue_phys, QUEUE_SIZE as u32);
}
pub fn setup_framebuffer(&self, cmd_phys: usize, vq_phys: usize, fb_phys: usize, width: Dimension, height: Dimension) {
pub fn setup_framebuffer(&self, cmd_phys: usize, vq_phys: usize, fb_phys: usize, width: Dimension, height: Dimension) -> bool {
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_phys));
let dma = GpuDmaAddrs {
cmd_pa: PhysicalAddress::from_raw(cmd_phys),
cmd_va: VirtualAddress::from_raw(cmd_phys),
resp_pa: PhysicalAddress::from_raw(cmd_phys + 512),
};
gpu::create_2d(&self.mmio, &queue, &dma, 1, width, height);
gpu::attach_backing(&self.mmio, &queue, &dma, 1, PhysicalAddress::from_raw(fb_phys), width.raw() as u32 * height.raw() as u32 * 4);
gpu::set_scanout(&self.mmio, &queue, &dma, 0, 1, width, height);
let ok1 = gpu::create_2d(&self.mmio, &queue, &dma, 1, width, height);
let ok2 = gpu::attach_backing(&self.mmio, &queue, &dma, 1, PhysicalAddress::from_raw(fb_phys), width.raw() as u32 * height.raw() as u32 * 4);
let ok3 = gpu::set_scanout(&self.mmio, &queue, &dma, 0, 1, width, height);
ok1 && ok2 && ok3
}
}

View File

@@ -48,12 +48,14 @@ extern "C" fn kmain() -> ! {
let (init, _) = boot::loader::load_init(&uart, &mut blk, kernel_pt);
let storage = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"storage.elf", "storage");
let display = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"display.elf", "display");
let input = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"input.elf", "input");
let virtio_base = blk.base();
drop(blk);
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base);
devices::gpu_setup::configure(&uart, display.pid);
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid);
devices::input_setup::configure(&uart, input.pid);
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
uart.write_str("k-os: entering userspace\n");
boot::loader::enter_userspace(init.pid, init.entry, SHARED_BUF_VA);