Files
k-os/crates/drivers/framebuffer/virtio/src/lib.rs
Gabriel Kaszewski 25824a01fe fix: remove broken QEMU GPU swap hack, add graceful fallback
VirtIO GPU:
- Removed QEMU 11.x command type swap hack (0x0104↔0x0106).
  The hack was wrong — swapping types without swapping data formats
  meant handlers read wrong struct layouts. Commands returned OK
  but did nothing.
- Restored correct VirtIO spec command types.
- Removed broken test pattern (fill + flush that never displayed).
- GPU setup now reports success/failure without crashing.

Display server resilience:
- VirtioFramebuffer::new() → try_new() -> Option<Self>.
  Returns None if phys-info page is unmapped or has zero dimensions.
- bins/display-qemu: tries VirtioFramebuffer, falls back to
  MockFramebuffer. Display server runs headless instead of crashing.
- No more page faults when GPU is absent.

The GPU driver code is architecturally correct (proper VirtIO
commands, proper data formats) but doesn't produce visible output
on QEMU 11.x due to a suspected QEMU legacy MMIO bug where
transfer_to_host_2d succeeds but doesn't transfer data. The driver
is ready for when this is fixed or a different QEMU version is used.
2026-10-18 22:49:55 +02:00

108 lines
3.4 KiB
Rust

#![no_std]
use hw_virtio::mmio::MmioAccess;
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
use hw_virtio::gpu::{self, GpuDmaAddrs};
use port_framebuffer::Framebuffer;
use types::address::{PhysicalAddress, VirtualAddress};
use types::display::{Color, Dimension, ScreenCoord};
const GPU_MMIO_VA: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000;
const CMDBUF_VA: usize = 0x23000;
const PHYSINFO_VA: usize = 0x24000;
const FB_VA: usize = 0x100000;
const FB_BPP: usize = 4;
struct GpuMmio;
impl MmioAccess for GpuMmio {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((GPU_MMIO_VA + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((GPU_MMIO_VA + offset) as *mut u32, val) }
}
}
struct PhysInfo {
cmd_pa: PhysicalAddress,
resp_pa: PhysicalAddress,
width: u16,
height: u16,
}
fn read_phys_info() -> PhysInfo {
unsafe {
let p = PHYSINFO_VA as *const u64;
PhysInfo {
cmd_pa: PhysicalAddress::from_raw(core::ptr::read_volatile(p) as usize),
resp_pa: PhysicalAddress::from_raw(core::ptr::read_volatile(p.add(1)) as usize),
width: core::ptr::read_volatile(p.add(3)) as u16,
height: core::ptr::read_volatile(p.add(4)) as u16,
}
}
}
pub struct VirtioFramebuffer {
mmio: GpuMmio,
queue: VirtioQueue,
dma: GpuDmaAddrs,
width: Dimension,
height: Dimension,
stride: usize,
}
impl VirtioFramebuffer {
pub fn try_new() -> Option<Self> {
let phys = read_phys_info();
if phys.width == 0 || phys.height == 0 || phys.cmd_pa.raw() == 0 {
return None;
}
let width = Dimension::new(phys.width);
let height = Dimension::new(phys.height);
let stride = phys.width as usize * FB_BPP;
Some(Self {
mmio: GpuMmio,
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)),
dma: GpuDmaAddrs { cmd_pa: phys.cmd_pa, cmd_va: VirtualAddress::from_raw(CMDBUF_VA), resp_pa: phys.resp_pa },
width,
height,
stride,
})
}
}
impl Framebuffer for VirtioFramebuffer {
fn width(&self) -> Dimension { self.width }
fn height(&self) -> Dimension { self.height }
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
if x.raw() >= self.width.raw() || y.raw() >= self.height.raw() { return; }
let offset = (y.raw() as usize) * self.stride + (x.raw() as usize) * FB_BPP;
unsafe { core::ptr::write_volatile((FB_VA + offset) as *mut u32, color.raw()); }
}
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
for dy in 0..h.raw() {
for dx in 0..w.raw() {
self.write_pixel(ScreenCoord::new(x.raw() + dx), ScreenCoord::new(y.raw() + dy), color);
}
}
}
fn clear(&mut self, color: Color) {
let total = self.width.raw() as usize * self.height.raw() as usize;
for i in 0..total {
unsafe { core::ptr::write_volatile((FB_VA as *mut u32).add(i), color.raw()); }
}
}
fn flush(&mut self) {
let w = self.width.raw() as u32;
let h = self.height.raw() as u32;
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, 1, 0, 0, w, h);
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, 1, 0, 0, w, h);
}
}