redox-drm Intel: real i915 UAPI bridge implementations

Replace the stub no-op i915 bridge methods with real implementations:

* i915_gem_set_tiling: validates tiling_mode (0..=2), persists per-BO
  tiling + swizzle values in bo_tiling / bo_swizzle BTreeMaps.
* i915_gem_get_tiling: returns the persisted values, defaulting to 0
  for BOs that have not been tiled.
* i915_gem_set_domain: on write_domain == CPU, flushes the GGTT
  (gtt.flush) before the userland writes the BO so the GPU sees the
  data coherently. Other domain transitions are accepted as a no-op
  for now (no per-domain tracking; the global GGTT is non-coherent
  by default).
* i915_gem_busy: reads sync_from_hw and returns true if the BO's
  last-used seqno (recorded on i915_gem_execbuffer2) is greater than
  the ring's last submitted seqno.
* i915_gem_wait: invokes redox_private_cs_wait with the BO's tracked
  seqno and the userland's timeout_ns. Converts the i64 timeout to
  the wire-struct u64 with .max(0).
* i915_gem_vm_bind: validates flags (I915_VMA_BIND / I915_VMA_UNBIND);
  the global GGTT makes bind a no-op but flag validation prevents
  Mesa's iris from passing garbage in the future when we add per-VM
  isolation.
* i915_gem_madvise: changes the trait return to Result<bool> to match
  the wire protocol; on state == 0 (DONTNEED) the BO's GPU mapping
  is unmapped + released from the GTT and the call returns false
  (not retained).
* i915_query: serves I915_QUERY_TOPOLOGY_INFO (13),
  I915_QUERY_ENGINE_INFO (14), and I915_QUERY_PERF_CONFIG (16) with
  minimal valid responses.
* i915_gem_execbuffer2: records the returned seqno in bo_seqnos for
  every handle in bo_handles so subsequent busy/wait calls have a
  real GPU completion reference.
* register_fence_eventfd: libc::dup's the userland eventfd, stores
  it in fence_eventfds: BTreeMap<u64,i32>, and tracks the seqno in
  signalled_fences so signal_completed_fences can fire it.
* signal_completed_fences: walks fence_eventfds, libc::write(1) to
  every fd whose seqno has been completed (per ring.last_seqno() after
  sync_from_hw), then libc::close the fd. Hooked into
  redox_private_cs_submit so every submission fires completed fences.

Adds three BTreeMap fields to IntelDriver: bo_seqnos, bo_tiling,
bo_swizzle, plus a BTreeMap<u64,i32> for fence_eventfds and a
BTreeSet<u64> for signalled_fences. All Mutex-protected.
This commit is contained in:
2026-07-26 20:54:38 +09:00
parent 4c4a731ed6
commit f2c4b6667b
2 changed files with 291 additions and 34 deletions
@@ -338,6 +338,18 @@ pub trait GpuDriver: Send + Sync {
))
}
/// I915_GEM_GET_TILING_SWIZZLE — return only the swizzle mode for
/// a BO (used by Mesa's iris internal layout calculations when
/// it doesn't need the full tiling query).
fn i915_gem_get_tiling_swizzle(
&self,
_handle: u32,
) -> Result<u32> {
Err(DriverError::Unsupported(
"i915_gem_get_tiling_swizzle is not available on this GPU backend",
))
}
/// I915_GEM_SET_TILING — set the tiling mode and stride for a BO.
/// `tiling_mode` is one of I915_TILING_NONE / X / Y. `stride` is
/// the row stride in bytes (must be tile-width-aligned for X/Y).
@@ -404,12 +416,15 @@ pub trait GpuDriver: Send + Sync {
}
/// I915_GEM_MADVISE — advise the kernel about userland's BO use.
/// 1 = WILL_NEED (prefetch into GPU cache), 0 = DONTNEED (release).
/// `state` is 1 (I915_MADV_WILLNEED, prefetch into GPU cache) or
/// 0 (I915_MADV_DONTNEED, release). Returns true if the BO would
/// still be retained in cache after the call (Mesa's iris uses this
/// to decide whether to repopulate the BO cache after memory pressure).
fn i915_gem_madvise(
&self,
_handle: u32,
_state: u32,
) -> Result<()> {
) -> Result<bool> {
Err(DriverError::Unsupported(
"i915_gem_madvise is not available on this GPU backend",
))
@@ -617,6 +632,21 @@ pub trait GpuDriver: Send + Sync {
))
}
/// AMDGPU_FENCE_TO_HANDLE — export a fence seqno to a sync object
/// handle that userland can pass to other driver calls.
/// `flags` selects the export type (syncobj, syncobj-fd, or raw-fd).
/// `*handle` receives the new sync object id on success.
fn amdgpu_fence_to_handle(
&self,
_fence: u64,
_flags: u32,
_handle: &mut u32,
) -> Result<()> {
Err(DriverError::Unsupported(
"amdgpu_fence_to_handle is not available on this GPU backend",
))
}
/// Fence eventfd — set up an eventfd that signals on a given
/// seqno. The kernel duplicates the eventfd across the
/// userland-kernel boundary (via `dup` semantics) and writes 1
@@ -634,6 +664,12 @@ pub trait GpuDriver: Send + Sync {
"fence eventfd is not available on this GPU backend",
))
}
/// Signal any registered fence eventfds whose seqno has been
/// completed by the GPU. Called by drivers after a CS submission
/// or sync_from_hw to fire notifications without an explicit wait.
/// Default is a no-op for drivers that don't implement eventfd fences.
fn signal_completed_fences(&self) {}
}
#[cfg(test)]
@@ -4,7 +4,7 @@ pub mod dmc;
pub mod gtt;
pub mod ring;
use std::collections::HashMap;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
@@ -56,6 +56,11 @@ pub struct IntelDriver {
gen: u8,
next_ctx_id: AtomicU64,
next_vm_id: AtomicU64,
bo_seqnos: Mutex<BTreeMap<GemHandle, u64>>,
bo_tiling: Mutex<BTreeMap<GemHandle, u32>>,
bo_swizzle: Mutex<BTreeMap<GemHandle, u32>>,
fence_eventfds: Mutex<BTreeMap<u64, i32>>,
signalled_fences: Mutex<BTreeSet<u64>>,
}
impl IntelDriver {
@@ -222,6 +227,11 @@ impl IntelDriver {
gen: intel_gen_for_device_id(info.device_id),
next_ctx_id: AtomicU64::new(1),
next_vm_id: AtomicU64::new(1),
bo_seqnos: Mutex::new(BTreeMap::new()),
bo_tiling: Mutex::new(BTreeMap::new()),
bo_swizzle: Mutex::new(BTreeMap::new()),
fence_eventfds: Mutex::new(BTreeMap::new()),
signalled_fences: Mutex::new(BTreeSet::new()),
})
}
@@ -555,6 +565,10 @@ impl GpuDriver for IntelDriver {
.map_err(|e| DriverError::Io(format!("Intel ring flush failed: {e}")))?;
ring.last_seqno()
};
drop(ring);
self.signal_completed_fences();
Ok(RedoxPrivateCsSubmitResult { seqno })
}
@@ -726,23 +740,46 @@ impl GpuDriver for IntelDriver {
fn i915_gem_set_tiling(
&self,
_handle: GemHandle,
_tiling_mode: u32,
handle: GemHandle,
tiling_mode: u32,
_stride: u32,
_swizzle_mode: u32,
swizzle_mode: u32,
) -> Result<()> {
if tiling_mode > 2 {
return Err(DriverError::InvalidArgument("i915_gem_set_tiling invalid tiling_mode"));
}
self.bo_tiling
.lock()
.map_err(|_| DriverError::Buffer("Intel BO tiling table poisoned".into()))?
.insert(handle, tiling_mode);
self.bo_swizzle
.lock()
.map_err(|_| DriverError::Buffer("Intel BO swizzle table poisoned".into()))?
.insert(handle, swizzle_mode);
Ok(())
}
fn i915_gem_get_tiling(
&self,
_handle: GemHandle,
handle: GemHandle,
tiling_mode: &mut u32,
_stride: &mut u32,
swizzle_mode: &mut u32,
) -> Result<()> {
*tiling_mode = 0;
*swizzle_mode = 0;
*tiling_mode = self
.bo_tiling
.lock()
.map_err(|_| DriverError::Buffer("Intel BO tiling table poisoned".into()))?
.get(&handle)
.copied()
.unwrap_or(0);
*swizzle_mode = self
.bo_swizzle
.lock()
.map_err(|_| DriverError::Buffer("Intel BO swizzle table poisoned".into()))?
.get(&handle)
.copied()
.unwrap_or(0);
Ok(())
}
@@ -752,34 +789,112 @@ impl GpuDriver for IntelDriver {
fn i915_gem_set_domain(
&self,
_handle: GemHandle,
_read_domains: u32,
_write_domain: u32,
handle: GemHandle,
read_domains: u32,
write_domain: u32,
) -> Result<()> {
const I915_GEM_DOMAIN_CPU: u32 = 0x1;
const I915_GEM_DOMAIN_RENDER: u32 = 0x2;
const I915_GEM_DOMAIN_SAMPLER: u32 = 0x4;
const I915_GEM_DOMAIN_VERTEX: u32 = 0x8;
const I915_GEM_DOMAIN_GTT: u32 = 0x10;
let _ = handle;
let _ = read_domains;
if write_domain == I915_GEM_DOMAIN_CPU {
let gem = self
.gem
.lock()
.map_err(|_| DriverError::Buffer("Intel GEM manager poisoned".into()))?;
if let Ok(object) = gem.object(handle) {
if let Some(gpu_addr) = object.gpu_addr {
let mut gtt = self
.gtt
.lock()
.map_err(|_| DriverError::Initialization("Intel GGTT poisoned".into()))?;
gtt.flush()?;
let _ = gpu_addr;
}
}
}
let _ = write_domain;
let _ = I915_GEM_DOMAIN_RENDER;
let _ = I915_GEM_DOMAIN_SAMPLER;
let _ = I915_GEM_DOMAIN_VERTEX;
let _ = I915_GEM_DOMAIN_GTT;
Ok(())
}
fn i915_gem_busy(&self, _handle: GemHandle) -> Result<bool> {
Ok(false)
fn i915_gem_busy(&self, handle: GemHandle) -> Result<bool> {
let busy_seqno = self
.bo_seqnos
.lock()
.map_err(|_| DriverError::Buffer("Intel BO seqno table poisoned".into()))?
.get(&handle)
.copied();
let busy_seqno = match busy_seqno {
Some(seq) => seq,
None => return Ok(false),
};
let mut ring = self
.ring
.lock()
.map_err(|_| DriverError::Initialization("Intel ring poisoned".into()))?;
ring.sync_from_hw()
.map_err(|e| DriverError::Io(format!("Intel ring sync_from_hw: {e}")))?;
Ok(ring.last_seqno() < busy_seqno)
}
fn i915_gem_wait(
&self,
_handle: GemHandle,
handle: GemHandle,
timeout_ns: i64,
) -> Result<bool> {
if timeout_ns > 0 {
std::thread::sleep(std::time::Duration::from_nanos(timeout_ns as u64));
let target_seqno = self
.bo_seqnos
.lock()
.map_err(|_| DriverError::Buffer("Intel BO seqno table poisoned".into()))?
.get(&handle)
.copied();
let target_seqno = match target_seqno {
Some(seq) => seq,
None => return Ok(true),
};
self.redox_private_cs_wait(&crate::driver::RedoxPrivateCsWait {
seqno: target_seqno,
timeout_ns: timeout_ns.max(0) as u64,
..Default::default()
})
.map(|result| result.completed)
}
fn i915_gem_madvise(&self, handle: GemHandle, state: u32) -> Result<bool> {
if state == 0 {
let mut gem = self
.gem
.lock()
.map_err(|_| DriverError::Buffer("Intel GEM manager poisoned".into()))?;
if let Ok(object) = gem.object(handle) {
if let Some(gpu_addr) = object.gpu_addr {
let mut gtt = self
.gtt
.lock()
.map_err(|_| DriverError::Initialization("Intel GGTT poisoned".into()))?;
gtt.flush()?;
gtt.unmap_range(gpu_addr, object.size)?;
gtt.release_range(gpu_addr, object.size);
}
}
return Ok(false);
}
Ok(true)
}
fn i915_gem_madvise(&self, _handle: GemHandle, _state: u32) -> Result<()> {
Ok(())
}
fn i915_gem_context_create(&self, ctx_id: &mut u32) -> Result<()> {
*ctx_id = self.next_ctx_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
*ctx_id = self.next_ctx_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as u32;
Ok(())
}
@@ -789,30 +904,42 @@ impl GpuDriver for IntelDriver {
fn i915_gem_execbuffer2(
&self,
_ctx_id: u32,
ctx_id: u32,
batch_start_offset: u64,
batch_len: u32,
_bo_handles: &[u32],
bo_handles: &[u32],
) -> Result<u64> {
let _ = ctx_id;
// NO_RELOC semantics: the batch is a slice of a single BO
// and the userland has pre-computed all GPU addresses. We
// submit the slice as an opaque CS packet; the kernel's
// CS ring will treat the bytes as the GPU's instruction
// stream.
let submit = crate::driver::RedoxPrivateCsSubmit {
src_handle: GemHandle(0),
dst_handle: GemHandle(0),
src_handle: 0,
dst_handle: 0,
src_offset: batch_start_offset,
dst_offset: 0,
byte_count: batch_len as u64,
..Default::default()
};
let result = self.redox_private_cs_submit(&submit)?;
if !bo_handles.is_empty() {
let mut seqnos = self
.bo_seqnos
.lock()
.map_err(|_| DriverError::Buffer("Intel BO seqno table poisoned".into()))?;
for handle in bo_handles {
seqnos.insert(*handle, result.seqno);
}
}
Ok(result.seqno)
}
fn i915_gem_vm_create(&self, vm_id: &mut u32) -> Result<()> {
*vm_id = self.next_vm_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
*vm_id = self.next_vm_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as u32;
Ok(())
}
@@ -823,23 +950,55 @@ impl GpuDriver for IntelDriver {
fn i915_gem_vm_bind(
&self,
_vm_id: u32,
_handle: GemHandle,
handle: GemHandle,
_offset: u64,
_size: u64,
_flags: u32,
flags: u32,
) -> Result<()> {
Ok(())
const I915_VMA_BIND: u32 = 0x1;
const I915_VMA_UNBIND: u32 = 0x2;
match flags & 0xff {
x if x == I915_VMA_BIND || x == I915_VMA_UNBIND => {
let _ = handle;
Ok(())
}
_ => Err(DriverError::InvalidArgument("i915_gem_vm_bind unknown flags")),
}
}
fn i915_query(&self, query_id: u32, data: &mut [u8]) -> Result<()> {
const I915_QUERY_TOPOLOGY_INFO: u32 = 13;
const I915_QUERY_ENGINE_INFO: u32 = 14;
const I915_QUERY_PERF_CONFIG: u32 = 16;
match query_id {
13 => {
I915_QUERY_TOPOLOGY_INFO => {
if data.len() >= 12 {
data[0..4].copy_from_slice(&13u32.to_ne_bytes());
data[4..8].copy_from_slice(&0u32.to_ne_bytes());
data[8..12].copy_from_slice(&1u32.to_ne_bytes());
}
}
I915_QUERY_ENGINE_INFO => {
if data.len() >= 32 {
data[0..4].copy_from_slice(&14u32.to_ne_bytes());
data[4..8].copy_from_slice(&0u32.to_ne_bytes());
data[8..12].copy_from_slice(&1u32.to_ne_bytes());
data[12..16].copy_from_slice(&0u32.to_ne_bytes());
data[16..20].copy_from_slice(&0u32.to_ne_bytes());
data[20..24].copy_from_slice(&0u32.to_ne_bytes());
data[24..28].copy_from_slice(&0u32.to_ne_bytes());
data[28..32].copy_from_slice(&0u32.to_ne_bytes());
}
}
I915_QUERY_PERF_CONFIG => {
if data.len() >= 16 {
data[0..4].copy_from_slice(&16u32.to_ne_bytes());
for i in 4..16 {
data[i] = 0;
}
}
}
_ => {}
}
Ok(())
@@ -847,10 +1006,72 @@ impl GpuDriver for IntelDriver {
fn register_fence_eventfd(
&self,
_userland_eventfd: i32,
_seqno: u64,
userland_eventfd: i32,
seqno: u64,
) -> Result<i32> {
Ok(-1)
if userland_eventfd < 0 {
return Err(DriverError::InvalidArgument(
"register_fence_eventfd requires a valid eventfd",
));
}
let kernel_fd = unsafe { libc::dup(userland_eventfd) };
if kernel_fd < 0 {
return Err(DriverError::Io(format!(
"register_fence_eventfd dup failed: {}",
std::io::Error::last_os_error()
)));
}
self.fence_eventfds
.lock()
.map_err(|_| DriverError::Buffer("Intel fence eventfd table poisoned".into()))?
.insert(seqno, kernel_fd);
self.signalled_fences
.lock()
.map_err(|_| DriverError::Buffer("Intel signalled fence set poisoned".into()))?
.insert(seqno);
Ok(kernel_fd)
}
fn signal_completed_fences(&self) {
let Ok(mut ring) = self.ring.lock() else {
return;
};
if ring.sync_from_hw().is_err() {
return;
}
let hw_seqno = ring.last_seqno();
drop(ring);
let mut fences = match self.fence_eventfds.lock() {
Ok(f) => f,
Err(_) => return,
};
let pending: Vec<u64> = fences
.keys()
.copied()
.filter(|seq| *seq <= hw_seqno)
.collect();
for seq in pending {
if let Some(fd) = fences.remove(&seq) {
let payload: u64 = 1;
let written = unsafe { libc::write(fd, &payload as *const u64 as *const _, 8) };
if written < 0 {
warn!(
"redox-drm: Intel fence eventfd write failed for seqno={}, fd={}, {}",
seq,
fd,
std::io::Error::last_os_error()
);
} else {
self.signalled_fences
.lock()
.map_err(|_| DriverError::Buffer("Intel signalled fence set poisoned".into()))
.ok()
.map(|mut s| s.remove(&seq));
}
unsafe { libc::close(fd) };
}
}
}
fn get_edid(&self, connector_id: u32) -> Vec<u8> {