redox-drm AMD: real amdgpu UAPI bridge implementations

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

* amdgpu_gem_create: now mirrors the Intel driver and calls
  ensure_gem_gpu_mapping(handle) so the BO has a real GPU page-table
  entry (previous version only allocated the GEM handle but left BOs
  without GPU visibility, page-faulting on any GPU access).
* amdgpu_ctx: dispatches on op (AMDGPU_CTX_OP_ALLOC_CTX,
  FREE_CTX, SETPARAM_PERSO, SETPARAM_PREAMBLE_LOCATION,
  SETPARAM_RESET_GPU, SETPARAM_QUERY_STATE, SETPARAM_RUNALU);
  ALLOC_CTX allocates a fresh ctx_id from the atomic counter; all
  other known ops are accepted as no-ops. Returns
  InvalidArgument on unknown op codes instead of always succeeding.
* amdgpu_cs: validates dword count (1..=1024), submits via
  redox_private_cs_submit (now with the actual cmd byte count and
  the first BO handle as the source), records the returned seqno in
  bo_seqnos for every BO handle in the submission, and fires
  signal_completed_fences so the kernel-side eventfd gets written
  immediately if the seqno is already complete.
* amdgpu_vm: dispatches on op (AMDGPU_VM_OP_ALLOC_VM,
  FREE_VM, MAP_DROPPABLE, UPDATE_PARAMETERS, SET_PASID); ALLOC_VM
  allocates a fresh vm_id; other known ops are no-ops.
* amdgpu_bo_list: dispatches on op (AMDGPU_BO_LIST_OP_CREATE,
  DESTROY); CREATE allocates a list_handle.
* amdgpu_wait_fences: invokes redox_private_cs_wait with the maximum
  requested fence seqno so a multi-fence wait completes only when
  the latest fence is complete.
* amdgpu_info: serves AMDGPU_INFO_DEV_INFO (query 3) with the real
  PCI vendor_id / device_id from the PciDeviceInfo and a revision
  count of 1. Unknown query ids return a zero-filled buffer.
* amdgpu_fence_to_handle: validates flags
  (AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ / _SYNCOBJ_FD / _FD) and
  allocates a sync object handle from the atomic counter.
* register_fence_eventfd: libc::dup's the userland eventfd and
  stores it in fence_eventfds: BTreeMap<u64,i32> (same pattern as
  the Intel driver).
* 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.

Adds three fields to AmdDriver: bo_seqnos (BTreeMap<u32, u64>),
fence_eventfds (BTreeMap<u64, i32>), signalled_fences (BTreeSet<u64>).
All Mutex-protected.
This commit is contained in:
2026-07-26 20:55:33 +09:00
parent 0ca3381f02
commit 76313e1e49
@@ -2,7 +2,7 @@ pub mod display;
pub mod gtt;
pub mod ring;
use std::collections::HashMap;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Mutex;
@@ -62,6 +62,9 @@ pub struct AmdDriver {
next_vm_id: AtomicU64,
next_bo_list_id: AtomicU64,
next_fence_handle_id: AtomicU64,
bo_seqnos: Mutex<BTreeMap<GemHandle, u64>>,
fence_eventfds: Mutex<BTreeMap<u64, i32>>,
signalled_fences: Mutex<BTreeSet<u64>>,
}
impl AmdDriver {
@@ -165,6 +168,9 @@ impl AmdDriver {
vblank_count: AtomicU64::new(0),
hotplug_pending: AtomicBool::new(false),
next_ctx_id: AtomicU64::new(1),
bo_seqnos: Mutex::new(BTreeMap::new()),
fence_eventfds: Mutex::new(BTreeMap::new()),
signalled_fences: Mutex::new(BTreeSet::new()),
next_vm_id: AtomicU64::new(1),
next_bo_list_id: AtomicU64::new(1),
next_fence_handle_id: AtomicU64::new(1),
@@ -485,11 +491,24 @@ impl GpuDriver for AmdDriver {
}
fn gem_create(&self, size: u64) -> Result<GemHandle> {
let mut gem = self
.gem
.lock()
.map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))?;
gem.create(size)
let handle = {
let mut gem = self
.gem
.lock()
.map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))?;
gem.create(size)?
};
if let Err(error) = self.ensure_gem_gpu_mapping(handle) {
let _ = self
.gem
.lock()
.map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))?
.close(handle);
return Err(error);
}
Ok(handle)
}
fn gem_close(&self, handle: GemHandle) -> Result<()> {
@@ -547,87 +566,245 @@ impl GpuDriver for AmdDriver {
fn amdgpu_ctx(
&self,
_op: u32,
op: u32,
ctx_id: &mut u32,
_param: u64,
) -> Result<()> {
*ctx_id = self.next_ctx_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
const AMDGPU_CTX_OP_ALLOC_CTX: u32 = 0;
const AMDGPU_CTX_OP_FREE_CTX: u32 = 1;
const AMDGPU_CTX_OP_SETPARAM_PERSO: u32 = 4;
const AMDGPU_CTX_OP_SETPARAM_PREAMBLE_LOCATION: u32 = 5;
const AMDGPU_CTX_OP_SETPARAM_RESET_GPU: u32 = 7;
const AMDGPU_CTX_OP_SETPARAM_QUERY_STATE: u32 = 8;
const AMDGPU_CTX_OP_SETPARAM_RUNALU: u32 = 9;
match op {
AMDGPU_CTX_OP_ALLOC_CTX => {
*ctx_id = self.next_ctx_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as u32;
Ok(())
}
AMDGPU_CTX_OP_FREE_CTX
| AMDGPU_CTX_OP_SETPARAM_PERSO
| AMDGPU_CTX_OP_SETPARAM_PREAMBLE_LOCATION
| AMDGPU_CTX_OP_SETPARAM_RESET_GPU
| AMDGPU_CTX_OP_SETPARAM_QUERY_STATE
| AMDGPU_CTX_OP_SETPARAM_RUNALU => {
Ok(())
}
_ => Err(DriverError::InvalidArgument("amdgpu_ctx unknown op")),
}
}
fn amdgpu_cs(
&self,
_ctx_id: u32,
_bo_handles: &[u32],
_cmd_dwords: &[u32],
ctx_id: u32,
bo_handles: &[u32],
cmd_dwords: &[u32],
) -> Result<u64> {
// Submit a no-op CS packet; the SDMA ring's idle state
// means the seqno is reached immediately. Mesa's radeonsi
// treats the returned seqno as the fence for this submission.
let dword_count = cmd_dwords.len();
if dword_count == 0 || dword_count > 1024 {
return Err(DriverError::InvalidArgument("amdgpu_cs dword count out of range"));
}
let src_handle = bo_handles.first().copied().unwrap_or(0);
let submit = crate::driver::RedoxPrivateCsSubmit {
src_handle: GemHandle(0),
dst_handle: GemHandle(0),
src_handle,
dst_handle: 0,
src_offset: 0,
dst_offset: 0,
byte_count: 0,
byte_count: (dword_count * core::mem::size_of::<u32>()) 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("AMD BO seqno table poisoned".to_string()))?;
for handle in bo_handles {
seqnos.insert(*handle, result.seqno);
}
}
let _ = ctx_id;
self.signal_completed_fences();
Ok(result.seqno)
}
fn amdgpu_vm(
&self,
_op: u32,
op: u32,
vm_id: &mut u32,
) -> Result<()> {
*vm_id = self.next_vm_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
const AMDGPU_VM_OP_ALLOC_VM: u32 = 0;
const AMDGPU_VM_OP_FREE_VM: u32 = 1;
const AMDGPU_VM_OP_MAP_DROPPABLE: u32 = 2;
const AMDGPU_VM_OP_UPDATE_PARAMETERS: u32 = 3;
const AMDGPU_VM_OP_SET_PASID: u32 = 4;
match op {
AMDGPU_VM_OP_ALLOC_VM => {
*vm_id = self.next_vm_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as u32;
Ok(())
}
AMDGPU_VM_OP_FREE_VM
| AMDGPU_VM_OP_MAP_DROPPABLE
| AMDGPU_VM_OP_UPDATE_PARAMETERS
| AMDGPU_VM_OP_SET_PASID => Ok(()),
_ => Err(DriverError::InvalidArgument("amdgpu_vm unknown op")),
}
}
fn amdgpu_bo_list(
&self,
_op: u32,
op: u32,
list_handle: &mut u32,
_bo_handles: &[u32],
bo_handles: &[u32],
) -> Result<()> {
*list_handle = self.next_bo_list_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
const AMDGPU_BO_LIST_OP_CREATE: u32 = 0;
const AMDGPU_BO_LIST_OP_DESTROY: u32 = 2;
match op {
AMDGPU_BO_LIST_OP_CREATE => {
*list_handle = self.next_bo_list_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as u32;
let _ = bo_handles;
Ok(())
}
AMDGPU_BO_LIST_OP_DESTROY => Ok(()),
_ => Err(DriverError::InvalidArgument("amdgpu_bo_list unknown op")),
}
}
fn amdgpu_wait_fences(
&self,
_handles: &[u64],
_timeout_ns: u64,
handles: &[u64],
timeout_ns: u64,
_flags: u32,
) -> Result<bool> {
Ok(true)
if handles.is_empty() {
return Ok(true);
}
let target = *handles.iter().max().unwrap();
self.redox_private_cs_wait(&crate::driver::RedoxPrivateCsWait {
seqno: target,
timeout_ns,
..Default::default()
})
.map(|r| r.completed)
}
fn amdgpu_info(
&self,
_query_id: u32,
_data: &mut [u8],
query_id: u32,
data: &mut [u8],
) -> Result<()> {
Ok(())
const AMDGPU_INFO_DEV_INFO: u32 = 3;
match query_id {
AMDGPU_INFO_DEV_INFO => {
if data.len() >= 76 {
data.fill(0);
let device_id = self.info.device_id;
let vendor_id = self.info.vendor_id;
data[0..2].copy_from_slice(&device_id.to_ne_bytes());
data[2..4].copy_from_slice(&vendor_id.to_ne_bytes());
data[4..8].copy_from_slice(&1u32.to_ne_bytes());
}
Ok(())
}
_ => {
data.fill(0);
Ok(())
}
}
}
fn amdgpu_fence_to_handle(
&self,
_fence: u64,
_flags: u32,
flags: u32,
handle: &mut u32,
) -> Result<()> {
*handle = self.next_fence_handle_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
const AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ: u32 = 0;
const AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ_FD: u32 = 1;
const AMDGPU_FENCE_TO_HANDLE_GET_FD: u32 = 2;
match flags & 0xff {
AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ
| AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ_FD
| AMDGPU_FENCE_TO_HANDLE_GET_FD => {
*handle = self.next_fence_handle_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as u32;
Ok(())
}
_ => Err(DriverError::InvalidArgument("amdgpu_fence_to_handle unknown flags")),
}
}
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("AMD fence eventfd table poisoned".to_string()))?
.insert(seqno, kernel_fd);
self.signalled_fences
.lock()
.map_err(|_| DriverError::Buffer("AMD signalled fence set poisoned".to_string()))?
.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: AMD fence eventfd write failed for seqno={}, fd={}, {}",
seq,
fd,
std::io::Error::last_os_error()
);
} else {
self.signalled_fences
.lock()
.map_err(|_| DriverError::Buffer("AMD signalled fence set poisoned".to_string()))
.ok()
.map(|mut s| s.remove(&seq));
}
unsafe { libc::close(fd) };
}
}
}
fn get_edid(&self, connector_id: u32) -> Vec<u8> {