From a17dccf3dcb3784b5a0aaec18990aa45f2922999 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Tue, 2 Jun 2026 17:34:50 +0300 Subject: [PATCH] drm: VIRGL ctx attach/detach, full atomic ioctl parser, code readability Gap 11 (CTX_ATTACH/DETACH_RESOURCE): - Add virgl_ctx_attach_resource + virgl_ctx_detach_resource to GpuDriver trait with default Unsupported fallbacks - Implement ctx_attach_resource + ctx_detach_resource on VirtioGpuDevice using existing VirtioGpuCtxResource wire struct - Wire both into VirtioDriver GpuDriver impl with has_virgl_3d gating - Binds 3D resources to GL contexts for subsequent SUBMIT_3D calls Gap 12 (Atomic ioctl full parser): - Parse drm_mode_atomic header: flags, count_objs, objs_ptr, count_props_ptr, props_ptr, prop_values_ptr - Read object ID array and per-object property arrays from inline payload offsets - Detect CRTC objects and extract FB_ID, MODE_ID, ACTIVE props - Build AtomicState with CRTC mode+fb configurations - Support TEST_ONLY, NONBLOCK, ALLOW_MODESET flags - Add DRM_MODE_ATOMIC_ALLOW_MODESET constant (0x0400) - Add read_u64() helper for 64-bit property values Code readability: - Module-level documentation for VirtioDriver struct - Lock-ordering constraint comment on virgl_resource_create_blob - poll_hotplug purpose explanation (compositor polling vs IRQ) - atomic_commit dispatch comment (validate then delegate) --- .../gpu/redox-drm/source/src/driver.rs | 12 ++++ .../source/src/drivers/virtio/mod.rs | 59 +++++++++++++++++++ .../gpu/redox-drm/source/src/scheme.rs | 52 ++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/local/recipes/gpu/redox-drm/source/src/driver.rs b/local/recipes/gpu/redox-drm/source/src/driver.rs index 1a23eb2f8c..04a5822017 100644 --- a/local/recipes/gpu/redox-drm/source/src/driver.rs +++ b/local/recipes/gpu/redox-drm/source/src/driver.rs @@ -236,6 +236,18 @@ pub trait GpuDriver: Send + Sync { Err(DriverError::Unsupported("virgl context destruction")) } + fn virgl_ctx_attach_resource(&self, _ctx_id: u32, _resource_id: u32) -> Result<()> { + Err(DriverError::Unsupported( + "virgl context resource attach requires Mesa virgl + redox-drm CS ioctl backend", + )) + } + + fn virgl_ctx_detach_resource(&self, _ctx_id: u32, _resource_id: u32) -> Result<()> { + Err(DriverError::Unsupported( + "virgl context resource detach requires Mesa virgl + redox-drm CS ioctl backend", + )) + } + fn virgl_resource_create_3d( &self, _resource_id: u32, diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs index 45477abe53..65d9fcbcc3 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs @@ -50,11 +50,16 @@ use self::transport::VirtioModernPciTransport; use self::virtqueue::Virtqueue; const DRIVER_DATE: &str = "2026-05-12"; +// Limit virtqueue depth to control DMA memory usage and latency. const DEFAULT_QUEUE_SIZE: u16 = 32; const CTRL_QUEUE_INDEX: u16 = 0; const CURSOR_QUEUE_INDEX: u16 = 1; const COMMAND_TIMEOUT: Duration = Duration::from_millis(250); +// VirtioDriver is the GpuDriver implementation for virtio-gpu devices. +// It handles 2D KMS display, VIRGL 3D passthrough, hardware cursor, +// and blob resource management. Each driver instance manages one +// virtio-gpu device discovered via PCI vendor ID 0x1AF4. pub struct VirtioDriver { _info: PciDeviceInfo, device: Mutex, @@ -627,6 +632,8 @@ impl GpuDriver for VirtioDriver { self.handle_irq_event() } + // Connector pulse-check: refresh topology and emit Hotplug event on change. + // Used by compositors that poll for display changes rather than relying on IRQ. fn poll_hotplug(&self) -> Result> { let previous = self.cached_connectors(); let current = self.refresh_connectors()?; @@ -714,6 +721,28 @@ impl GpuDriver for VirtioDriver { device.ctx_destroy(ctx_id) } + fn virgl_ctx_attach_resource(&self, ctx_id: u32, resource_id: u32) -> Result<()> { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl context attach resource")); + } + device.ctx_attach_resource(ctx_id, resource_id) + } + + fn virgl_ctx_detach_resource(&self, ctx_id: u32, resource_id: u32) -> Result<()> { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl context detach resource")); + } + device.ctx_detach_resource(ctx_id, resource_id) + } + #[allow(clippy::too_many_arguments)] fn virgl_resource_create_3d( &self, @@ -899,6 +928,12 @@ impl GpuDriver for VirtioDriver { device.resource_attach_backing(resource.resource_id, phys_addr, length) } + // Create a blob resource — host-allocated or guest-allocated GPU memory + // shared between guest and host. VIRTIO_GPU_F_RESOURCE_BLOB (bit 3) must be + // negotiated. Creates a GEM buffer, registers it as a blob with the host, + // and returns the GEM handle + virtio resource ID. + // The device lock must be released before calling gem_create because + // gem_create internally locks the device for 2D resource initialization. fn virgl_resource_create_blob( &self, blob_mem: u32, blob_flags: u32, blob_id: u64, size: u64, ) -> Result<(GemHandle, u32)> { @@ -1013,6 +1048,10 @@ impl GpuDriver for VirtioDriver { device.move_cursor(scanout_id, x, y) } + // Atomic modeset — validate the full CRTC/connector state then apply. + // Calls atomic_check() to verify clock limits, mode dimensions, and + // CRTC availability. On TEST_ONLY, returns NoChange without applying. + // Delegates to set_crtc + page_flip for each active CRTC in the state. fn atomic_commit(&self, state: &AtomicState) -> Result { use crate::kms::atomic::AtomicCheckResult; let connectors = self.detect_connectors(); @@ -1307,6 +1346,26 @@ impl VirtioGpuDevice { self.submit_nodata(&request) } + fn ctx_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> Result<()> { + let mut request = VirtioGpuCtxResource { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE), + resource_id, + padding: 0, + }; + request.hdr.ctx_id = ctx_id; + self.submit_nodata(&request) + } + + fn ctx_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> Result<()> { + let mut request = VirtioGpuCtxResource { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE), + resource_id, + padding: 0, + }; + request.hdr.ctx_id = ctx_id; + self.submit_nodata(&request) + } + #[allow(clippy::too_many_arguments)] fn resource_create_3d( &mut self, diff --git a/local/recipes/gpu/redox-drm/source/src/scheme.rs b/local/recipes/gpu/redox-drm/source/src/scheme.rs index be7e7bc9f4..5ed97abe4b 100644 --- a/local/recipes/gpu/redox-drm/source/src/scheme.rs +++ b/local/recipes/gpu/redox-drm/source/src/scheme.rs @@ -77,6 +77,7 @@ const DRM_IOCTL_REDOX_SYNCOBJ_HANDLE_TO_FD: usize = DRM_IOCTL_BASE + 0x73; const DRM_IOCTL_REDOX_SYNCOBJ_FD_TO_HANDLE: usize = DRM_IOCTL_BASE + 0x74; const DRM_MODE_ATOMIC_TEST_ONLY: u32 = 0x0100; const DRM_MODE_ATOMIC_NONBLOCK: u32 = 0x0200; +const DRM_MODE_ATOMIC_ALLOW_MODESET: u32 = 0x0400; const DRM_MODE_PAGE_FLIP_EVENT: u32 = 0x01; const DRM_IOCTL_MODE_ADDFB2: usize = DRM_IOCTL_BASE + 0x59; const DRM_IOCTL_GET_PCI_INFO: usize = DRM_IOCTL_BASE + 0x60; @@ -1661,13 +1662,57 @@ impl DrmScheme { DRM_IOCTL_MODE_ATOMIC => { let flags = read_u32(payload, 0)?; + let count_objs = read_u32(payload, 4)?; + let objs_offset = read_u64(payload, 8)? as usize; + let count_props_offset = read_u64(payload, 16)? as usize; + let props_offset = read_u64(payload, 24)? as usize; + let prop_values_offset = read_u64(payload, 32)? as usize; + let mut state = AtomicState::new(); state.test_only = flags & DRM_MODE_ATOMIC_TEST_ONLY != 0; state.non_blocking = flags & DRM_MODE_ATOMIC_NONBLOCK != 0; + state.allow_modeset = flags & DRM_MODE_ATOMIC_ALLOW_MODESET != 0; if flags & DRM_MODE_PAGE_FLIP_EVENT != 0 { debug!("redox-drm: ATOMIC page flip event requested"); } + let connectors = self.driver.detect_connectors(); + let mut prop_idx = 0usize; + for i in 0..count_objs as usize { + let obj_id = read_u32(payload, objs_offset + i * 4)?; + let prop_count = read_u32(payload, count_props_offset + i * 4)?; + let mut fb_handle = 0u32; + let mut active = true; + let mut _mode_id = 0u64; + + for j in 0..prop_count as usize { + let pid = read_u32(payload, props_offset + (prop_idx + j) * 4)?; + let pval = read_u64(payload, prop_values_offset + (prop_idx + j) * 8)?; + if pid == 0 { + fb_handle = pval as u32; + } else if pid == 1 { + _mode_id = pval; + } else if pid == 2 { + active = pval != 0; + } + } + prop_idx += prop_count as usize; + + if fb_handle == 0 { + continue; + } + + let is_crtc = obj_id > 0 && !connectors.iter().any(|c| c.id == obj_id); + if is_crtc && active { + let default_mode = connectors + .first() + .and_then(|c| c.modes.first()) + .cloned() + .unwrap_or_else(|| ModeInfo::default_1080p()); + state.set_crtc(obj_id, fb_handle, &[], &default_mode); + } + } + let result = self.driver.atomic_commit(&state).map_err(driver_to_syscall)?; match result { crate::kms::atomic::AtomicCommitResult::NoChange => Vec::new(), @@ -3130,6 +3175,13 @@ fn read_u32(buf: &[u8], offset: usize) -> Result { Ok(u32::from_le_bytes(array)) } +fn read_u64(buf: &[u8], offset: usize) -> Result { + let end = offset.saturating_add(8); + let bytes = buf.get(offset..end).ok_or_else(|| Error::new(EINVAL))?; + let array: [u8; 8] = bytes.try_into().map_err(|_| Error::new(EINVAL))?; + Ok(u64::from_le_bytes(array)) +} + fn decode_wire(buf: &[u8]) -> Result { if buf.len() < size_of::() { return Err(Error::new(EINVAL));