use log::{debug, info, warn}; use super::fence::FenceTimeline; use super::gtt::IntelGtt; use super::ring::{IntelRing, RingType}; use super::syncobj::SyncobjManager; use crate::driver::{ ContextHandle, DriverError, IntelGemExecObject2, IntelGemExecbuffer2, IntelGemExecbuffer2Result, IntelGemRelocationEntry, Result, SyncobjHandle, EXEC_OBJECT_PINNED, EXEC_OBJECT_WRITE, I915_EXEC_BLT, I915_EXEC_BSD, I915_EXEC_RENDER, I915_EXEC_VEBOX, }; use crate::gem::GemManager; const MAX_EXEC_OBJECTS: usize = 256; struct PinnedObject { handle: u32, gpu_offset: u64, size: u64, is_write: bool, } pub struct ExecbufferContext<'a> { gtt: &'a mut IntelGtt, gem: &'a mut GemManager, render_ring: &'a mut IntelRing, blitter_ring: Option<&'a mut IntelRing>, fence_timeline: &'a FenceTimeline, syncobj_mgr: &'a mut SyncobjManager, } impl<'a> ExecbufferContext<'a> { pub fn new( gtt: &'a mut IntelGtt, gem: &'a mut GemManager, render_ring: &'a mut IntelRing, blitter_ring: Option<&'a mut IntelRing>, fence_timeline: &'a FenceTimeline, syncobj_mgr: &'a mut SyncobjManager, ) -> Self { Self { gtt, gem, render_ring, blitter_ring, fence_timeline, syncobj_mgr } } pub fn execute( &mut self, exec: &IntelGemExecbuffer2, objects: &[IntelGemExecObject2], relocs: &[IntelGemRelocationEntry], batch_data: &[u8], ) -> Result { if exec.buffer_count as usize > MAX_EXEC_OBJECTS { return Err(DriverError::InvalidArgument("execbuffer buffer_count exceeds limit")); } if exec.buffer_count as usize != objects.len() { return Err(DriverError::InvalidArgument( "execbuffer buffer_count does not match objects array length", )); } if batch_data.is_empty() && exec.batch_len == 0 { return Err(DriverError::InvalidArgument("execbuffer batch is empty")); } let mut pinned = self.pin_objects(objects)?; self.apply_relocations(&pinned, objects, relocs, batch_data)?; let ring = self.select_ring(exec.flags)?; ring.submit_batch(&batch_data_to_u32s(batch_data)?)?; let seqno = ring.last_seqno(); let ring_type = ring.ring_type(); drop(ring); let fence_handle = self.allocate_fence(seqno)?; debug!( "redox-drm-intel: execbuffer2 submitted — {} objects, {} batch bytes, ring {:?}, seqno {}, fence {}", objects.len(), batch_data.len(), ring_type, seqno, fence_handle ); self.update_pinned_objects(&pinned, objects, batch_data)?; Ok(IntelGemExecbuffer2Result { seqno, fence_handle, }) } fn pin_objects(&mut self, objects: &[IntelGemExecObject2]) -> Result> { let mut pinned = Vec::with_capacity(objects.len()); for obj in objects { let gpu_offset = if obj.flags & EXEC_OBJECT_PINNED != 0 { obj.offset } else { let (phys_addr, size) = { let gem_obj = self.gem.object(obj.handle)?; (gem_obj.phys_addr as u64, gem_obj.size) }; let gpu_addr = self.gtt.alloc_range(size)?; if let Err(e) = self.gtt.map_range(gpu_addr, phys_addr, size, 1 << 1) { let _ = self.gtt.release_range(gpu_addr, size); return Err(e); } gpu_addr }; let size = self.gem.object(obj.handle)?.size; pinned.push(PinnedObject { handle: obj.handle, gpu_offset, size, is_write: obj.flags & EXEC_OBJECT_WRITE != 0, }); } Ok(pinned) } fn apply_relocations( &self, pinned: &[PinnedObject], objects: &[IntelGemExecObject2], relocs: &[IntelGemRelocationEntry], batch_data: &[u8], ) -> Result<()> { if relocs.is_empty() { return Ok(()); } let mut patched = batch_data.to_vec(); for reloc in relocs { let target_offset = self.resolve_gpu_offset(pinned, objects, reloc.target_handle)?; let patched_addr = target_offset.wrapping_add(u64::from(reloc.delta)); let patch_offset = reloc.offset as usize; let end = patch_offset.checked_add(4).ok_or_else(|| { DriverError::InvalidArgument("relocation offset overflows batch buffer") })?; if end > patched.len() { return Err(DriverError::InvalidArgument( "relocation offset exceeds batch buffer length", )); } let current = u32::from_le_bytes( patched[patch_offset..end] .try_into() .unwrap_or([0u8; 4]), ); let expected = (reloc.presumed_offset as u32) .wrapping_add(reloc.delta); if reloc.presumed_offset != 0 && current != expected { info!( "redox-drm-intel: relocation mismatch at offset {} — expected {:#010x}, found {:#010x}; re-patching", patch_offset, expected, current ); } let value = (patched_addr as u32).wrapping_add(reloc.delta); patched[patch_offset..end].copy_from_slice(&value.to_le_bytes()); debug!( "redox-drm-intel: relocation {:?} → target_handle={}, gpu_offset={:#010x}, delta={}, patched_addr={:#010x}", reloc, reloc.target_handle, target_offset, reloc.delta, patched_addr ); } Ok(()) } fn resolve_gpu_offset( &self, pinned: &[PinnedObject], objects: &[IntelGemExecObject2], target_handle: u32, ) -> Result { if let Some(p) = pinned.iter().find(|p| p.handle == target_handle) { return Ok(p.gpu_offset); } let obj = objects .iter() .find(|o| o.handle == target_handle) .ok_or_else(|| { DriverError::NotFound(format!( "relocation target handle {} not found in exec object list", target_handle )) })?; if obj.flags & EXEC_OBJECT_PINNED != 0 { return Ok(obj.offset); } Err(DriverError::InvalidArgument( "relocation target not pinned and not found in resolved list", )) } fn select_ring(&mut self, flags: u64) -> Result<&mut IntelRing> { let engine_mask = flags & 0x3F; if engine_mask & I915_EXEC_RENDER != 0 { Ok(self.render_ring) } else if engine_mask & I915_EXEC_BLT != 0 { self.blitter_ring .as_deref_mut() .ok_or_else(|| DriverError::Unsupported("blitter ring not initialized")) } else if engine_mask & I915_EXEC_BSD != 0 { Err(DriverError::Unsupported("BSD/video ring not supported")) } else if engine_mask & I915_EXEC_VEBOX != 0 { Err(DriverError::Unsupported("VEBOX ring not supported")) } else { Ok(self.render_ring) } } fn allocate_fence(&mut self, seqno: u64) -> Result { let handle = self.syncobj_mgr.create(0)?; self.syncobj_mgr.signal(&[handle])?; debug!( "redox-drm-intel: fence seqno {} mapped to syncobj handle {}", seqno, handle ); Ok(handle) } fn update_pinned_objects( &self, pinned: &[PinnedObject], objects: &[IntelGemExecObject2], _batch_data: &[u8], ) -> Result<()> { for p in pinned { if let Some(obj) = objects.iter().find(|o| o.handle == p.handle) { if p.is_write { if let Ok(gem_obj) = self.gem.object(p.handle) { debug!( "redox-drm-intel: write-domain object handle {} size {} preserved after execbuffer", p.handle, gem_obj.size ); } } } } Ok(()) } } fn batch_data_to_u32s(data: &[u8]) -> Result> { let dword_count = data.len() / 4; let mut result = Vec::with_capacity(dword_count); for chunk in data.chunks_exact(4) { let arr: [u8; 4] = chunk.try_into().map_err(|_| { DriverError::InvalidArgument("batch data not 4-byte aligned") })?; result.push(u32::from_le_bytes(arr)); } Ok(result) }