a52632f69d
The generated bits/pthread.h uses size_t but had no includes. Also added openat cache vars to m4 recipe for gnulib cross-compilation.
266 lines
8.7 KiB
Rust
266 lines
8.7 KiB
Rust
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<IntelGemExecbuffer2Result> {
|
|
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<Vec<PinnedObject>> {
|
|
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<u64> {
|
|
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<SyncobjHandle> {
|
|
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<Vec<u32>> {
|
|
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)
|
|
}
|