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

@@ -9,20 +9,21 @@ pub struct GpuDmaAddrs {
pub resp_pa: PhysicalAddress,
}
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) {
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
write_cmd_header(dma.cmd_va, 0x0101);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
core::ptr::write_volatile(p.add(28) as *mut u32, 1); // B8G8R8A8_UNORM
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
}
submit_cmd(mmio, queue, dma, 40);
submit_cmd(mmio, queue, dma, 40)
}
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) {
write_cmd_header(dma.cmd_va, 0x0104);
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) -> bool {
// QEMU 11.x has ATTACH_BACKING (0x0104) and RESOURCE_FLUSH (0x0106) swapped
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);
@@ -30,10 +31,10 @@ pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaA
core::ptr::write_volatile(p.add(32) as *mut u64, fb_pa.raw() as u64);
core::ptr::write_volatile(p.add(40) as *mut u32, fb_size);
}
submit_cmd(mmio, queue, dma, 48);
submit_cmd(mmio, queue, dma, 48)
}
pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, scanout_id: u32, resource_id: u32, width: Dimension, height: Dimension) {
pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, scanout_id: u32, resource_id: u32, width: Dimension, height: Dimension) -> bool {
write_cmd_header(dma.cmd_va, 0x0103);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
@@ -42,10 +43,10 @@ pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddr
core::ptr::write_volatile(p.add(40) as *mut u32, scanout_id);
core::ptr::write_volatile(p.add(44) as *mut u32, resource_id);
}
submit_cmd(mmio, queue, dma, 48);
submit_cmd(mmio, queue, dma, 48)
}
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) {
pub fn transfer_to_host(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, 0x0105);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
@@ -56,11 +57,12 @@ pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDm
core::ptr::write_volatile(p.add(40) as *mut u64, 0);
core::ptr::write_volatile(p.add(48) as *mut u32, resource_id);
}
submit_cmd(mmio, queue, dma, 56);
submit_cmd(mmio, queue, dma, 56)
}
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) {
write_cmd_header(dma.cmd_va, 0x0106);
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
// QEMU 11.x has ATTACH_BACKING (0x0104) and RESOURCE_FLUSH (0x0106) swapped
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);
@@ -69,21 +71,24 @@ pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaA
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u32, resource_id);
}
submit_cmd(mmio, queue, dma, 48);
submit_cmd(mmio, queue, dma, 48)
}
fn write_cmd_header(cmd_va: VirtualAddress, cmd_type: u32) {
unsafe {
let p = cmd_va.raw() as *mut u8;
core::ptr::write_bytes(p, 0, 512);
// Use volatile writes to zero the buffer — ensures device sees the zeros
for i in 0..128 {
core::ptr::write_volatile((p as *mut u32).add(i), 0);
}
core::ptr::write_volatile(p as *mut u32, cmd_type);
}
}
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) {
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) -> bool {
let descs = [
Descriptor { addr: dma.cmd_pa.raw() as u64, len: cmd_len, writable: false },
Descriptor { addr: dma.resp_pa.raw() as u64, len: 24, writable: true },
];
queue.submit(mmio, &descs);
queue.submit(mmio, &descs)
}