From f2c4b6667b88f349cb154477aa1e30119630f5bf Mon Sep 17 00:00:00 2001 From: vasilito Date: Sun, 26 Jul 2026 20:54:38 +0900 Subject: [PATCH] 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 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, 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 for fence_eventfds and a BTreeSet for signalled_fences. All Mutex-protected. --- .../gpu/redox-drm/source/src/driver.rs | 40 ++- .../redox-drm/source/src/drivers/intel/mod.rs | 285 ++++++++++++++++-- 2 files changed, 291 insertions(+), 34 deletions(-) diff --git a/local/recipes/gpu/redox-drm/source/src/driver.rs b/local/recipes/gpu/redox-drm/source/src/driver.rs index b43d3767c0..7cf3d821ab 100644 --- a/local/recipes/gpu/redox-drm/source/src/driver.rs +++ b/local/recipes/gpu/redox-drm/source/src/driver.rs @@ -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 { + 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 { 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)] diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs index 4b201a8133..9c2b3cb614 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs @@ -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>, + bo_tiling: Mutex>, + bo_swizzle: Mutex>, + fence_eventfds: Mutex>, + signalled_fences: Mutex>, } 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 { - Ok(false) + fn i915_gem_busy(&self, handle: GemHandle) -> Result { + 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 { - 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 { + 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 { + 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 { - 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 = 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 {