drm: VIRGL blob resources, hardware cursor, atomic modeset

Implement VIRTGPU_RESOURCE_CREATE_BLOB:
- Define VirtioGpuResourceCreateBlob wire struct (commands.rs)
- Add VIRTIO_GPU_BLOB_MEM_*/FLAG_* constants
- Negotiate VIRTIO_GPU_F_RESOURCE_BLOB feature flag
- Add virgl_resource_create_blob() to GpuDriver trait
- Implement in VirtioDriver with virtio command dispatch
- Wire ioctl handler in scheme.rs (was EOPNOTSUPP stub)
- Add find_by_handle() to ResourceManager

Implement hardware cursor:
- Add VIRTIO_GPU_CMD_UPDATE_CURSOR/MOVE_CURSOR opcodes
- Define VirtioGpuCmdUpdateCursor/MoveCursor/CursorPos structs
- Add update_cursor()/move_cursor() to VirtioGpuDevice
- Override cursor_set/cursor_move on VirtioDriver
- CRTC-to-connector lookup for scanout index mapping

Implement atomic modeset:
- Override atomic_commit on VirtioDriver with full state
  validation via atomic_check(), then delegate to
  set_crtc + page_flip for each active CRTC
- Support TEST_ONLY flag (returns NoChange)

Mesa recipe: add iris,crocus to gallium-drivers
Config: enable mesa = {} in redbear-full.toml
This commit is contained in:
2026-06-02 15:17:35 +03:00
parent 7686729069
commit da023e71fa
5 changed files with 281 additions and 11 deletions
@@ -285,6 +285,12 @@ pub trait GpuDriver: Send + Sync {
) -> Result<()> {
Err(DriverError::Unsupported("virgl resource attach backing requires Mesa virgl + redox-drm GEM integration"))
}
fn virgl_resource_create_blob(
&self, _blob_mem: u32, _blob_flags: u32, _blob_id: u64, _size: u64,
) -> Result<(GemHandle, u32)> {
Err(DriverError::Unsupported("virgl blob resource creation not supported by this backend"))
}
}
#[cfg(test)]
@@ -292,6 +292,84 @@ pub struct VirtioGpuRespMapInfo {
pub padding: u32,
}
// Blob resource mem types (Linux virtio_gpu.h)
pub const VIRTIO_GPU_BLOB_MEM_GUEST: u32 = 0x0001;
pub const VIRTIO_GPU_BLOB_MEM_HOST3D: u32 = 0x0002;
pub const VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST: u32 = 0x0003;
// Blob resource flags
pub const VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE: u32 = 0x0001;
pub const VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE: u32 = 0x0002;
pub const VIRTIO_GPU_BLOB_FLAG_USE_CROSS_DEVICE: u32 = 0x0004;
pub const VIRTIO_GPU_BLOB_FLAG_USE_LINEAR: u32 = 0x0008;
// Cursor commands (virtio_gpu.h 0x03xx)
pub const VIRTIO_GPU_CMD_UPDATE_CURSOR: u32 = 0x0300;
pub const VIRTIO_GPU_CMD_MOVE_CURSOR: u32 = 0x0301;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuResourceCreateBlob {
pub hdr: VirtioGpuCtrlHeader,
pub resource_id: u32,
pub blob_mem: u32,
pub blob_flags: u32,
pub nr_entries: u32,
pub blob_id: u64,
pub size: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuResourceUnmapBlob {
pub hdr: VirtioGpuCtrlHeader,
pub resource_id: u32,
pub padding: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuSetScanoutBlob {
pub hdr: VirtioGpuCtrlHeader,
pub scanout_id: u32,
pub resource_id: u32,
pub width: u32,
pub height: u32,
pub format: u32,
pub padding: u32,
pub strides: [u32; 4],
pub offsets: [u32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuCmdUpdateCursor {
pub hdr: VirtioGpuCtrlHeader,
pub pos: VirtioGpuCursorPos,
pub resource_id: u32,
pub hot_x: u32,
pub hot_y: u32,
pub padding: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuCmdMoveCursor {
pub hdr: VirtioGpuCtrlHeader,
pub pos: VirtioGpuCursorPos,
pub resource_id: u32,
pub padding: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuCursorPos {
pub scanout_id: u32,
pub x: u32,
pub y: u32,
pub padding: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct VirtioGpuCmdGetEdid {
@@ -17,14 +17,15 @@ use crate::drivers::fence::FenceTimeline;
use crate::drivers::interrupt::InterruptHandle;
use crate::drivers::syncobj::SyncobjManager;
use crate::gem::{GemHandle, GemManager};
use crate::kms::atomic::{AtomicCommitResult, AtomicState, atomic_check};
use crate::kms::connector::{synthetic_edid, Connector};
use crate::kms::crtc::Crtc;
use crate::kms::{ConnectorInfo, ConnectorStatus, ConnectorType, ModeInfo};
use self::commands::{
append_bytes, bytes_of, read_struct, validate_response_type, response_type_name,
VirtioGpuBox, VirtioGpuCmdGetEdid,
VirtioGpuCmdSubmit, VirtioGpuConfig, VirtioGpuCtrlHeader, VirtioGpuCtxCreate,
VirtioGpuBox, VirtioGpuCmdGetEdid, VirtioGpuCmdMoveCursor, VirtioGpuCmdUpdateCursor,
VirtioGpuCmdSubmit, VirtioGpuConfig, VirtioGpuCtrlHeader, VirtioGpuCursorPos,
VirtioGpuCtxCreate,
VirtioGpuCtxDestroy, VirtioGpuDisplayOne, VirtioGpuGetCapset, VirtioGpuGetCapsetInfo,
VirtioGpuMemEntry, VirtioGpuRect, VirtioGpuResourceAttachBacking, VirtioGpuResourceCreate2d,
VirtioGpuResourceCreate3d, VirtioGpuResourceFlush, VirtioGpuResourceUnref, VirtioGpuRespCapset,
@@ -32,12 +33,14 @@ use self::commands::{
VirtioGpuTransferHost3d, VirtioGpuTransferToHost2d, VIRTIO_F_VERSION_1,
VIRTIO_GPU_CMD_CTX_CREATE, VIRTIO_GPU_CMD_CTX_DESTROY, VIRTIO_GPU_CMD_GET_CAPSET,
VIRTIO_GPU_CMD_GET_CAPSET_INFO, VIRTIO_GPU_CMD_GET_DISPLAY_INFO, VIRTIO_GPU_CMD_GET_EDID,
VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING, VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
VIRTIO_GPU_CMD_MOVE_CURSOR, VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB,
VIRTIO_GPU_CMD_RESOURCE_CREATE_3D, VIRTIO_GPU_CMD_RESOURCE_FLUSH,
VIRTIO_GPU_CMD_RESOURCE_UNREF, VIRTIO_GPU_CMD_SET_SCANOUT, VIRTIO_GPU_CMD_SUBMIT_3D,
VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_EVENT_DISPLAY, VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM,
VIRTIO_GPU_F_EDID, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_RESP_OK_CAPSET,
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_CMD_UPDATE_CURSOR,
VIRTIO_GPU_EVENT_DISPLAY, VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM,
VIRTIO_GPU_F_EDID, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_RESP_OK_CAPSET,
VIRTIO_GPU_RESP_OK_CAPSET_INFO, VIRTIO_GPU_RESP_OK_DISPLAY_INFO, VIRTIO_GPU_RESP_OK_EDID,
VIRTIO_GPU_RESP_OK_MAP_INFO, VIRTIO_GPU_RESP_OK_NODATA,
};
@@ -74,6 +77,7 @@ struct VirtioGpuDevice {
cursorq: Virtqueue,
supports_edid: bool,
has_virgl_3d: bool,
has_resource_blob: bool,
}
impl VirtioDriver {
@@ -109,7 +113,7 @@ impl VirtioDriver {
let mut transport = VirtioModernPciTransport::new(&info, &mut pci)?;
let negotiated = transport
.initialize_device(VIRTIO_F_VERSION_1 | VIRTIO_GPU_F_EDID | VIRTIO_GPU_F_VIRGL)?;
.initialize_device(VIRTIO_F_VERSION_1 | VIRTIO_GPU_F_EDID | VIRTIO_GPU_F_VIRGL | VIRTIO_GPU_F_RESOURCE_BLOB)?;
let ctrlq_cfg = transport.prepare_queue(CTRL_QUEUE_INDEX, DEFAULT_QUEUE_SIZE)?;
let ctrlq = Virtqueue::new(ctrlq_cfg.index, ctrlq_cfg.size, ctrlq_cfg.notify_off)?;
transport.activate_queue(
@@ -140,6 +144,7 @@ impl VirtioDriver {
cursorq,
supports_edid: (negotiated & VIRTIO_GPU_F_EDID) != 0,
has_virgl_3d: (negotiated & VIRTIO_GPU_F_VIRGL) != 0,
has_resource_blob: (negotiated & VIRTIO_GPU_F_RESOURCE_BLOB) != 0,
};
let (connectors, crtcs) = load_display_topology(&mut device)?;
@@ -865,6 +870,27 @@ impl GpuDriver for VirtioDriver {
device.resource_attach_backing(resource.resource_id, phys_addr, length)
}
fn virgl_resource_create_blob(
&self, blob_mem: u32, blob_flags: u32, blob_id: u64, size: u64,
) -> Result<(GemHandle, u32)> {
let mut device = self
.device
.lock()
.map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?;
if !device.has_resource_blob {
return Err(DriverError::Unsupported("virgl blob resource create"));
}
let gem_handle = self.gem_create(size, 0, 0)?;
let resource = self
.resources
.lock()
.map_err(|_| DriverError::Initialization("VirtIO resource state poisoned".into()))?
.find_by_handle(gem_handle)
.ok_or(DriverError::NotFound(format!("resource for GEM handle {}", gem_handle)))?;
device.resource_create_blob(resource.resource_id, blob_mem, blob_flags, blob_id, size)?;
Ok((gem_handle, resource.resource_id))
}
fn syncobj_create(&self) -> Result<SyncobjHandle> {
self.syncobjs
.lock()
@@ -905,6 +931,92 @@ impl GpuDriver for VirtioDriver {
.map_err(|_| DriverError::Initialization("VirtIO syncobj state poisoned".into()))?
.import_sync_file(fd)
}
fn cursor_set(&self, crtc_id: u32, fb_handle: u32, hot_x: u32, hot_y: u32) -> Result<()> {
let resource = self
.resources
.lock()
.map_err(|_| DriverError::Initialization("VirtIO resource state poisoned".into()))?
.find_by_handle(fb_handle)
.ok_or(DriverError::NotFound(format!(
"cursor resource for GEM handle {}", fb_handle
)))?;
let connector_id = self
.crtcs
.lock()
.map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))?
.iter()
.find(|crtc| crtc.id == crtc_id)
.and_then(|crtc| crtc.connectors.first().copied())
.ok_or(DriverError::NotFound(format!(
"no connector for CRTC {}", crtc_id
)))?;
let scanout_id = self.scanout_for_connector(connector_id)?;
let mut device = self
.device
.lock()
.map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?;
device.update_cursor(scanout_id, resource.resource_id, hot_x, hot_y)
}
fn cursor_move(&self, crtc_id: u32, x: i32, y: i32) -> Result<()> {
let connector_id = self
.crtcs
.lock()
.map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))?
.iter()
.find(|crtc| crtc.id == crtc_id)
.and_then(|crtc| crtc.connectors.first().copied())
.ok_or(DriverError::NotFound(format!(
"no connector for CRTC {}", crtc_id
)))?;
let scanout_id = self.scanout_for_connector(connector_id)?;
let mut device = self
.device
.lock()
.map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?;
device.move_cursor(scanout_id, x, y)
}
fn atomic_commit(&self, state: &AtomicState) -> Result<AtomicCommitResult> {
use crate::kms::atomic::AtomicCheckResult;
let connectors = self.detect_connectors();
match atomic_check(state, &connectors) {
AtomicCheckResult::Ok => {}
AtomicCheckResult::CrtcNotFound { crtc_id } => {
return Err(DriverError::NotFound(format!("CRTC {}", crtc_id)));
}
AtomicCheckResult::InvalidMode { crtc_id, reason } => {
return Err(DriverError::InvalidArgument(Box::leak(
format!("CRTC {} invalid mode: {}", crtc_id, reason).into_boxed_str(),
)));
}
AtomicCheckResult::ClockTooHigh { crtc_id, .. } => {
return Err(DriverError::InvalidArgument(Box::leak(
format!("CRTC {} pixel clock exceeds hardware limit", crtc_id).into_boxed_str(),
)));
}
AtomicCheckResult::ConnectorDisconnected { connector_id } => {
return Err(DriverError::Initialization(format!(
"connector {} disconnected", connector_id
)));
}
}
if state.test_only {
return Ok(AtomicCommitResult::NoChange);
}
let mut vblank_count = 0u64;
for crtc in &state.crtc_states {
if !crtc.active {
continue;
}
if let Some(ref mode) = crtc.mode {
self.set_crtc(crtc.crtc_id, crtc.fb_handle, &crtc.connectors, mode)?;
vblank_count = self.page_flip(crtc.crtc_id, crtc.fb_handle, 0)?;
}
}
Ok(AtomicCommitResult::Committed { vblank_count })
}
}
impl VirtioGpuDevice {
@@ -1052,6 +1164,40 @@ impl VirtioGpuDevice {
self.submit_nodata(&request)
}
fn update_cursor(&mut self, scanout_id: u32, resource_id: u32, hot_x: u32, hot_y: u32) -> Result<()> {
let pos = VirtioGpuCursorPos {
scanout_id,
x: 0,
y: 0,
padding: 0,
};
let request = VirtioGpuCmdUpdateCursor {
hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_UPDATE_CURSOR),
pos,
resource_id,
hot_x,
hot_y,
padding: 0,
};
self.submit_nodata(&request)
}
fn move_cursor(&mut self, scanout_id: u32, x: i32, y: i32) -> Result<()> {
let pos = VirtioGpuCursorPos {
scanout_id,
x: x.max(0) as u32,
y: y.max(0) as u32,
padding: 0,
};
let request = VirtioGpuCmdMoveCursor {
hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_MOVE_CURSOR),
pos,
resource_id: 0,
padding: 0,
};
self.submit_nodata(&request)
}
fn set_scanout(
&mut self,
scanout_id: u32,
@@ -1088,6 +1234,21 @@ impl VirtioGpuDevice {
self.submit_nodata(&request)
}
fn resource_create_blob(
&mut self, resource_id: u32, blob_mem: u32, blob_flags: u32, blob_id: u64, size: u64,
) -> Result<()> {
let request = VirtioGpuResourceCreateBlob {
hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB),
resource_id,
blob_mem,
blob_flags,
nr_entries: 0,
blob_id,
size,
};
self.submit_nodata(&request)
}
fn ctx_create(&mut self, ctx_id: u32, debug_name: &str, context_init: u32) -> Result<()> {
let mut request = VirtioGpuCtxCreate {
hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_CTX_CREATE),
@@ -80,6 +80,10 @@ impl ResourceManager {
})
}
pub fn find_by_handle(&self, handle: GemHandle) -> Option<VirtioResource> {
self.resources.get(&handle).copied()
}
pub fn allocate_resource_id(&mut self, handle: GemHandle) -> Result<u32> {
if !self.resources.contains_key(&handle) {
return Err(DriverError::NotFound(format!(
@@ -508,6 +508,20 @@ struct VirtgpuMapWire {
pad: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct VirtgpuResourceCreateBlobWire {
blob_mem: u32,
blob_flags: u32,
bo_handle: u32,
res_handle: u32,
size: u64,
pad: u32,
cmd_size: u32,
cmd: u64,
blob_id: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct VirtgpuContextInitWire {
@@ -2475,10 +2489,17 @@ impl DrmScheme {
}
DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB => {
warn!(
"redox-drm: VIRTGPU_RESOURCE_CREATE_BLOB rejected — virgl blob resources are not implemented"
);
return Err(Error::new(EOPNOTSUPP));
let req = decode_wire::<VirtgpuResourceCreateBlobWire>(payload)?;
let (bo_handle, res_handle) = self
.driver
.virgl_resource_create_blob(
req.blob_mem, req.blob_flags, req.blob_id, req.size,
)
.map_err(driver_to_syscall)?;
let mut resp = req;
resp.bo_handle = bo_handle;
resp.res_handle = res_handle;
bytes_of(&resp)
}
DRM_IOCTL_REDOX_AMD_SDMA_SUBMIT => {