From 27ca1c329638dc22a003c6f157a7de9dd9b3b47f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:04:42 +0200 Subject: [PATCH] graphics/virtio-gpud: Handle VIRTIO_GPU_EVENT_DISPLAY This doesn't yet provide a way for individual consumers to resize their own framebuffer in response to the display changing size. --- graphics/virtio-gpud/src/main.rs | 30 +++++++++++++- graphics/virtio-gpud/src/scheme.rs | 64 +++++++++++++++++------------- virtio-core/src/transport.rs | 14 +++++++ 3 files changed, 78 insertions(+), 30 deletions(-) diff --git a/graphics/virtio-gpud/src/main.rs b/graphics/virtio-gpud/src/main.rs index 7f95d9d572..ff04932dff 100644 --- a/graphics/virtio-gpud/src/main.rs +++ b/graphics/virtio-gpud/src/main.rs @@ -31,7 +31,7 @@ use virtio_core::MSIX_PRIMARY_VECTOR; mod scheme; -// const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0; +const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0; const VIRTIO_GPU_MAX_SCANOUTS: usize = 16; #[repr(C)] @@ -457,6 +457,8 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { .transport .setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + device.transport.setup_config_notify(MSIX_PRIMARY_VECTOR); + device.transport.run_device(); deamon.ready().unwrap(); @@ -471,6 +473,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { enum Source { Input, Scheme, + Interrupt, } } @@ -490,8 +493,15 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { event::EventFlags::READ, ) .unwrap(); + event_queue + .subscribe( + device.irq_handle.as_raw_fd() as usize, + Source::Interrupt, + event::EventFlags::READ, + ) + .unwrap(); - let all = [Source::Input, Source::Scheme]; + let all = [Source::Input, Source::Scheme, Source::Interrupt]; for event in all .into_iter() .chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data)) @@ -510,6 +520,22 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { .tick() .expect("virtio-gpud: failed to process scheme events"); } + Source::Interrupt => loop { + let before_gen = device.transport.config_generation(); + + let events = config.events_read.get(); + + if events & VIRTIO_GPU_EVENT_DISPLAY != 0 { + futures::executor::block_on(scheme.adapter_mut().update_displays(config)) + .unwrap(); + config.events_clear.set(VIRTIO_GPU_EVENT_DISPLAY); + } + + let after_gen = device.transport.config_generation(); + if before_gen == after_gen { + break; + } + }, } } diff --git a/graphics/virtio-gpud/src/scheme.rs b/graphics/virtio-gpud/src/scheme.rs index b7707ab5f1..c6656c5398 100644 --- a/graphics/virtio-gpud/src/scheme.rs +++ b/graphics/virtio-gpud/src/scheme.rs @@ -64,6 +64,40 @@ pub struct VirtGpuAdapter<'a> { } impl VirtGpuAdapter<'_> { + pub async fn update_displays(&mut self, config: &mut GpuConfig) -> Result<(), Error> { + let mut display_info = self.get_display_info().await?; + let raw_displays = &mut display_info.display_info[..config.num_scanouts() as usize]; + + self.displays.resize( + raw_displays.len(), + Display { + width: 0, + height: 0, + active_resource: None, + }, + ); + for (i, info) in raw_displays.iter().enumerate() { + log::info!( + "virtio-gpu: display {i} ({}x{}px)", + info.rect.width, + info.rect.height + ); + + if info.rect.width == 0 || info.rect.height == 0 { + // QEMU gives all displays other than the first a zero width and height, but trying + // to attach a zero sized framebuffer to the display will result an error, so + // default to 640x480px. + self.displays[i].width = 640; + self.displays[i].height = 480; + } else { + self.displays[i].width = info.rect.width; + self.displays[i].height = info.rect.height; + } + } + + Ok(()) + } + async fn send_request(&self, request: Dma) -> Result, Error> { let header = Dma::new(ControlHeader::default())?; let command = ChainBuilder::new() @@ -350,7 +384,7 @@ pub struct GpuScheme {} impl<'a> GpuScheme { pub async fn new( - config: &'a mut GpuConfig, + config: &mut GpuConfig, control_queue: Arc>, cursor_queue: Arc>, transport: Arc, @@ -362,33 +396,7 @@ impl<'a> GpuScheme { displays: vec![], }; - let mut display_info = adapter.get_display_info().await?; - let raw_displays = &mut display_info.display_info[..config.num_scanouts() as usize]; - - for info in raw_displays.iter() { - log::info!( - "virtio-gpu: opening display ({}x{}px)", - info.rect.width, - info.rect.height - ); - - if info.rect.width == 0 || info.rect.height == 0 { - // QEMU gives all displays other than the first a zero width and height, but trying - // to attach a zero sized framebuffer to the display will result an error, so - // default to 640x480px. - adapter.displays.push(Display { - width: 640, - height: 480, - active_resource: None, - }); - } else { - adapter.displays.push(Display { - width: info.rect.width, - height: info.rect.height, - active_resource: None, - }); - } - } + adapter.update_displays(config).await?; let scheme = GraphicsScheme::new(adapter, "display.virtio-gpu".to_owned()); let handle = DisplayHandle::new("virtio-gpu").unwrap(); diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index b2782bdc5c..5f3ecaa91f 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -492,6 +492,12 @@ pub trait Transport: Sync + Send { self.insert_status(DeviceStatusFlags::DRIVER_OK); } + /// Request to be notified on configuration changes on the given MSI-X vector. + fn setup_config_notify(&self, vector: u16); + + /// Each time the device configuration changes this number will be updated. + fn config_generation(&self) -> u32; + /// Creates a new queue. /// /// ## Panics @@ -601,6 +607,14 @@ impl Transport for StandardTransport<'_> { assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK); } + fn setup_config_notify(&self, vector: u16) { + self.common.lock().unwrap().config_msix_vector.set(vector); + } + + fn config_generation(&self) -> u32 { + u32::from(self.common.lock().unwrap().config_generation.get()) + } + fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result, Error> { let mut common = self.common.lock().unwrap();