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)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<VirtioGpuDevice>,
|
||||
@@ -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<Option<DriverEvent>> {
|
||||
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<AtomicCommitResult> {
|
||||
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,
|
||||
|
||||
@@ -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<u32> {
|
||||
Ok(u32::from_le_bytes(array))
|
||||
}
|
||||
|
||||
fn read_u64(buf: &[u8], offset: usize) -> Result<u64> {
|
||||
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<T: Copy>(buf: &[u8]) -> Result<T> {
|
||||
if buf.len() < size_of::<T>() {
|
||||
return Err(Error::new(EINVAL));
|
||||
|
||||
Reference in New Issue
Block a user