feat: playable snake game via UART terminal rendering
Snake renders using ANSI escape codes via debug_putchar syscall, bypassing the VirtIO GPU (which has a QEMU 11.x legacy MMIO bug where transfer_to_host_2d succeeds but doesn't transfer data). Full microkernel stack running end-to-end: adapter → init → storage-server IPC → load snake from disk → input-server IPC (UART keyboard) → snake game logic → debug_putchar syscall → UART → terminal display Controls: WASD to steer, runs at 0.2s tick rate. Green O=head, o=body, red @=food, #=border.
This commit is contained in:
@@ -49,155 +49,30 @@ 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
|
||||
// Fill framebuffer with WHITE and flush — visible test pattern
|
||||
let fb = fb_phys as *mut u32;
|
||||
let total = w as usize * h as usize;
|
||||
let total = FB_WIDTH.raw() as usize * FB_HEIGHT.raw() as usize;
|
||||
for i in 0..total {
|
||||
unsafe { core::ptr::write_volatile(fb.add(i), 0xFF4040FF); }
|
||||
unsafe { core::ptr::write_volatile(fb.add(i), 0xFFFFFFFF); }
|
||||
}
|
||||
|
||||
// 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");
|
||||
let queue = hw_virtio::queue::VirtioQueue::new(
|
||||
hw_virtio::queue::QueueAddrs::from_base(vq_phys),
|
||||
);
|
||||
let dma = hw_virtio::gpu::GpuDmaAddrs {
|
||||
cmd_pa: types::address::PhysicalAddress::from_raw(cmd_phys),
|
||||
cmd_va: types::address::VirtualAddress::from_raw(cmd_phys),
|
||||
resp_pa: types::address::PhysicalAddress::from_raw(cmd_phys + 512),
|
||||
};
|
||||
let mmio = hw_virtio::mmio::DirectMmio::new(gpu.base());
|
||||
hw_virtio::gpu::transfer_to_host(&mmio, &queue, &dma, 1, 0, 0, FB_WIDTH.raw() as u32, FB_HEIGHT.raw() as u32);
|
||||
hw_virtio::gpu::resource_flush(&mmio, &queue, &dma, 1, 0, 0, FB_WIDTH.raw() as u32, FB_HEIGHT.raw() as u32);
|
||||
|
||||
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),
|
||||
|
||||
Reference in New Issue
Block a user