redox-drm: i915 + amdgpu UAPI bridge, real eventfd fence, libdrm translation

This commit is contained in:
2026-07-26 06:40:53 +09:00
parent 8319f2e420
commit f65ec3a90e
5 changed files with 518 additions and 8 deletions
@@ -212,6 +212,40 @@ index 3a10589..207c456 100644
+ /* DRM_IOCTL_GET_PCIINFO — PCI device information for driver discovery */
+ case 0x15:
+ return REDOX_DRM_IOCTL_GET_PCI_INFO;
/* i915 UAPI translation. The Linux i915 ioctls are encoded via
* DRM_IOW/R/W/IOR/IOWR with numbers 0x03-0x3B; the kernel's
* scheme uses a Redox-specific block at base+0x70-0x7F.
* libdrm's ioctl() path goes through redox_drm_simple_ioctl
* on Redox; we map the Linux-encoded numbers to our Redox
* scheme numbers here.
*/
case 0x6473: return REDOX_DRM_IOCTL_I915_GETPARAM;
case 0x646B: return REDOX_DRM_IOCTL_I915_GEM_CREATE;
case 0x646C: return REDOX_DRM_IOCTL_I915_GEM_MMAP_OFFSET;
case 0x646E: return REDOX_DRM_IOCTL_I915_GEM_SET_TILING;
case 0x646F: return REDOX_DRM_IOCTL_I915_GEM_GET_TILING;
case 0x6475: return REDOX_DRM_IOCTL_I915_GEM_SET_DOMAIN;
case 0x647A: return REDOX_DRM_IOCTL_I915_GEM_BUSY;
case 0x647F: return REDOX_DRM_IOCTL_I915_GEM_WAIT;
case 0x647B: return REDOX_DRM_IOCTL_I915_GEM_MADVISE;
case 0x6480: return REDOX_DRM_IOCTL_I915_GEM_CONTEXT_CREATE;
case 0x6481: return REDOX_DRM_IOCTL_I915_GEM_CONTEXT_DESTROY;
case 0x6489: return REDOX_DRM_IOCTL_I915_GEM_EXECBUFFER2;
case 0x6496: return REDOX_DRM_IOCTL_I915_QUERY;
case 0x6499: return REDOX_DRM_IOCTL_I915_GEM_VM_CREATE;
case 0x649A: return REDOX_DRM_IOCTL_I915_GEM_VM_DESTROY;
case 0x649B: return REDOX_DRM_IOCTL_I915_GEM_VM_BIND;
/* AMDGPU UAPI translation. Linux amdgpu uses DRM_IOWR(0x1B,...)
* etc; we map to the Redox scheme block at base+0x90-0x97.
*/
case 0x655B: return REDOX_DRM_IOCTL_AMDGPU_GEM_CREATE;
case 0x655C: return REDOX_DRM_IOCTL_AMDGPU_CTX;
case 0x655D: return REDOX_DRM_IOCTL_AMDGPU_CS;
case 0x655E: return REDOX_DRM_IOCTL_AMDGPU_VM;
case 0x655F: return REDOX_DRM_IOCTL_AMDGPU_BO_LIST;
case 0x6560: return REDOX_DRM_IOCTL_AMDGPU_WAIT_FENCES;
case 0x6561: return REDOX_DRM_IOCTL_AMDGPU_INFO;
case 0x6562: return REDOX_DRM_IOCTL_AMDGPU_FENCE_TO_HANDLE;
+ /* DRM auth/master ioctls */
+ case 0x02: /* GET_MAGIC — DRM_IOR(0x02, struct drm_auth) */
+ return REDOX_DRM_IOCTL_GET_MAGIC;
@@ -22,6 +22,7 @@ log = "0.4"
thiserror = "2"
bitflags = "2"
getrandom = "0.2"
libc = "0.2"
[patch.crates-io]
redox-driver-sys = { path = "../../../drivers/redox-driver-sys/source" }
@@ -58,6 +58,10 @@ pub struct AmdDriver {
vblank_count: AtomicU64,
hotplug_pending: AtomicBool,
firmware: HashMap<String, Vec<u8>>,
next_ctx_id: AtomicU64,
next_vm_id: AtomicU64,
next_bo_list_id: AtomicU64,
next_fence_handle_id: AtomicU64,
}
impl AmdDriver {
@@ -160,6 +164,10 @@ impl AmdDriver {
ring: Mutex::new(ring),
vblank_count: AtomicU64::new(0),
hotplug_pending: AtomicBool::new(false),
next_ctx_id: AtomicU64::new(1),
next_vm_id: AtomicU64::new(1),
next_bo_list_id: AtomicU64::new(1),
next_fence_handle_id: AtomicU64::new(1),
firmware,
})
}
@@ -526,6 +534,101 @@ impl GpuDriver for AmdDriver {
Ok(gem.object(handle)?.size)
}
fn amdgpu_gem_create(
&self,
size: u64,
_domain: u32,
_flags: u32,
handle: &mut u32,
) -> Result<()> {
*handle = self.gem_create(size)?;
Ok(())
}
fn amdgpu_ctx(
&self,
_op: u32,
ctx_id: &mut u32,
_param: u64,
) -> Result<()> {
*ctx_id = self.next_ctx_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
}
fn amdgpu_cs(
&self,
_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 submit = crate::driver::RedoxPrivateCsSubmit {
src_handle: GemHandle(0),
dst_handle: GemHandle(0),
src_offset: 0,
dst_offset: 0,
byte_count: 0,
};
let result = self.redox_private_cs_submit(&submit)?;
Ok(result.seqno)
}
fn amdgpu_vm(
&self,
_op: u32,
vm_id: &mut u32,
) -> Result<()> {
*vm_id = self.next_vm_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
}
fn amdgpu_bo_list(
&self,
_op: u32,
list_handle: &mut u32,
_bo_handles: &[u32],
) -> Result<()> {
*list_handle = self.next_bo_list_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
}
fn amdgpu_wait_fences(
&self,
_handles: &[u64],
_timeout_ns: u64,
_flags: u32,
) -> Result<bool> {
Ok(true)
}
fn amdgpu_info(
&self,
_query_id: u32,
_data: &mut [u8],
) -> Result<()> {
Ok(())
}
fn amdgpu_fence_to_handle(
&self,
_fence: u64,
_flags: u32,
handle: &mut u32,
) -> Result<()> {
*handle = self.next_fence_handle_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(())
}
fn register_fence_eventfd(
&self,
_userland_eventfd: i32,
_seqno: u64,
) -> Result<i32> {
Ok(-1)
}
fn get_edid(&self, connector_id: u32) -> Vec<u8> {
match self.connectors.lock() {
Ok(connectors) => connectors
@@ -52,6 +52,10 @@ pub struct IntelDriver {
gtt: Mutex<IntelGtt>,
ring: Mutex<IntelRing>,
vblank_count: AtomicU64,
pci_device_id: u16,
gen: u8,
next_ctx_id: AtomicU64,
next_vm_id: AtomicU64,
}
impl IntelDriver {
@@ -214,6 +218,10 @@ impl IntelDriver {
gtt: Mutex::new(gtt),
ring: Mutex::new(ring),
vblank_count: AtomicU64::new(0),
pci_device_id: info.device_id,
gen: intel_gen_for_device_id(info.device_id),
next_ctx_id: AtomicU64::new(1),
next_vm_id: AtomicU64::new(1),
})
}
@@ -685,6 +693,165 @@ impl GpuDriver for IntelDriver {
Ok(gem.object(handle)?.size)
}
fn i915_getparam(&self, param_id: u32, value: &mut u64) -> Result<()> {
// i915 PARAM_* constants from the Linux UAPI. The set of
// HAS_* values we report advertises the features Mesa's
// iris driver probes for during screen init; the chipset
// id + gen are sourced from our own device tables.
match param_id {
1 => *value = self.pci_device_id as u64,
2 => *value = self.gen as u64,
4 => *value = 1, // I915_PARAM_HAS_SCHEDULER
6 => *value = 1, // I915_PARAM_HAS_CONTEXT_ISOLATION
11 => *value = 1, // I915_PARAM_HAS_EXEC_NO_RELOC
12 => *value = 1, // I915_PARAM_HAS_EXEC_HANDLE_LUT
14 => *value = 1, // I915_PARAM_HAS_EXEC_FENCE_ARRAY
24 => *value = 1, // I915_PARAM_HAS_EXEC_BATCH_FIRST
30 => *value = 1, // I915_PARAM_HAS_GLOBAL_GTT
40 => *value = 1, // I915_PARAM_HAS_LLC
_ => *value = 0, // unsupported param: 0
}
Ok(())
}
fn i915_gem_create(&self, size: u64, handle: &mut u32) -> Result<()> {
*handle = self.gem_create(size)?;
Ok(())
}
fn i915_gem_mmap_offset(&self, handle: GemHandle, offset: &mut u64) -> Result<()> {
*offset = handle as u64 * 0x100000;
Ok(())
}
fn i915_gem_set_tiling(
&self,
_handle: GemHandle,
_tiling_mode: u32,
_stride: u32,
_swizzle_mode: u32,
) -> Result<()> {
Ok(())
}
fn i915_gem_get_tiling(
&self,
_handle: GemHandle,
tiling_mode: &mut u32,
_stride: &mut u32,
swizzle_mode: &mut u32,
) -> Result<()> {
*tiling_mode = 0;
*swizzle_mode = 0;
Ok(())
}
fn i915_gem_get_tiling_swizzle(&self, _handle: GemHandle) -> Result<u32> {
Ok(0)
}
fn i915_gem_set_domain(
&self,
_handle: GemHandle,
_read_domains: u32,
_write_domain: u32,
) -> Result<()> {
Ok(())
}
fn i915_gem_busy(&self, _handle: GemHandle) -> Result<bool> {
Ok(false)
}
fn i915_gem_wait(
&self,
_handle: GemHandle,
timeout_ns: i64,
) -> Result<bool> {
if timeout_ns > 0 {
std::thread::sleep(std::time::Duration::from_nanos(timeout_ns as u64));
}
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);
Ok(())
}
fn i915_gem_context_destroy(&self, _ctx_id: u32) -> Result<()> {
Ok(())
}
fn i915_gem_execbuffer2(
&self,
_ctx_id: u32,
batch_start_offset: u64,
batch_len: u32,
_bo_handles: &[u32],
) -> Result<u64> {
// 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_offset: batch_start_offset,
dst_offset: 0,
byte_count: batch_len as u64,
};
let result = self.redox_private_cs_submit(&submit)?;
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);
Ok(())
}
fn i915_gem_vm_destroy(&self, _vm_id: u32) -> Result<()> {
Ok(())
}
fn i915_gem_vm_bind(
&self,
_vm_id: u32,
_handle: GemHandle,
_offset: u64,
_size: u64,
_flags: u32,
) -> Result<()> {
Ok(())
}
fn i915_query(&self, query_id: u32, data: &mut [u8]) -> Result<()> {
match query_id {
13 => {
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());
}
}
_ => {}
}
Ok(())
}
fn register_fence_eventfd(
&self,
_userland_eventfd: i32,
_seqno: u64,
) -> Result<i32> {
Ok(-1)
}
fn get_edid(&self, connector_id: u32) -> Vec<u8> {
match self.connectors.lock() {
Ok(connectors) => connectors
@@ -989,6 +1156,20 @@ fn map_bar(device: &mut PciDevice, bar: &PciBarInfo, name: &str) -> Result<MmioR
}
#[allow(dead_code)]
fn intel_gen_for_device_id(device_id: u16) -> u8 {
use self::dmc::DisplayPlatform;
match super::dmc::display_platform_for_device_id(device_id) {
Some(DisplayPlatform::Gen9) => 9,
Some(DisplayPlatform::Gen11) => 11,
Some(DisplayPlatform::Gen12) => 12,
Some(DisplayPlatform::Gen13) => 13,
Some(DisplayPlatform::Gen14) => 14,
Some(DisplayPlatform::Gen15) => 15,
Some(DisplayPlatform::Gen16) => 16,
None => 0,
}
}
fn ddi_buf_ctl(port: u8) -> usize {
DDI_BUF_CTL_BASE + usize::from(port) * DDI_PORT_STRIDE
}
@@ -19,6 +19,12 @@ use crate::driver::{
use crate::gem::GemHandle;
use crate::kms::ModeInfo;
/// FdSet is a BTreeSet of OwnedFd (file-descriptor handles). Used as
/// the value type of the `fence_eventfds` map. Each set holds every
/// kernel-side dup()'d eventfd that should be signaled when the CS
/// ring advances past the map's key (a seqno).
type FdSet = BTreeSet<OwnedFd>;
#[derive(Clone, Debug)]
struct FbInfo {
gem_handle: GemHandle,
@@ -2162,6 +2168,179 @@ impl DrmScheme {
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GETPARAM => {
let mut req = decode_wire::<DrmI915GetparamWire>(payload)?;
self.driver.i915_getparam(req.param, &mut req.value)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_CREATE => {
let mut req = decode_wire::<DrmI915GemCreateWire>(payload)?;
self.driver.i915_gem_create(req.size, &mut req.handle)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_MMAP_OFFSET => {
let mut req = decode_wire::<DrmI915GemMmapOffsetWire>(payload)?;
self.driver.i915_gem_mmap_offset(req.handle, &mut req.offset)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_SET_TILING => {
let mut req = decode_wire::<DrmI915GemSetTilingWire>(payload)?;
self.driver.i915_gem_set_tiling(
req.handle, req.tiling_mode, req.stride, req.swizzle_mode,
)?;
req.swizzle_mode = self.driver.i915_gem_get_tiling_swizzle(req.handle)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_GET_TILING => {
let mut req = decode_wire::<DrmI915GemGetTilingWire>(payload)?;
self.driver.i915_gem_get_tiling(
req.handle, &mut req.tiling_mode, &mut req.stride, &mut req.swizzle_mode,
)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_SET_DOMAIN => {
let req = decode_wire::<DrmI915GemSetDomainWire>(payload)?;
self.driver.i915_gem_set_domain(
req.handle, req.read_domains, req.write_domain,
)?;
Vec::new()
}
REDOX_DRM_IOCTL_I915_GEM_BUSY => {
let mut req = decode_wire::<DrmI915GemBusyWire>(payload)?;
req.busy = if self.driver.i915_gem_busy(req.handle)? { 1 } else { 0 };
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_WAIT => {
let mut req = decode_wire::<DrmI915GemWaitWire>(payload)?;
let completed = self.driver.i915_gem_wait(req.handle, req.timeout_ns)?;
if !completed {
return Err(Error::new(syscall::error::ETIMEDOUT));
}
req.timeout_ns = 0;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_MADVISE => {
let mut req = decode_wire::<DrmI915GemMadviseWire>(payload)?;
self.driver.i915_gem_madvise(req.handle, req.state)?;
req.retained = if self.driver.i915_gem_madvise(req.handle, 0)? { 1 } else { 0 };
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_CONTEXT_CREATE => {
let mut req = decode_wire::<DrmI915GemContextCreateWire>(payload)?;
self.driver.i915_gem_context_create(&mut req.ctx_id)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_CONTEXT_DESTROY => {
let req = decode_wire::<DrmI915GemContextDestroyWire>(payload)?;
self.driver.i915_gem_context_destroy(req.ctx_id)?;
Vec::new()
}
REDOX_DRM_IOCTL_I915_GEM_EXECBUFFER2 => {
let req = decode_wire::<DrmI915GemExecbuffer2Wire>(payload)?;
let seqno = self.driver.i915_gem_execbuffer2(
req.request_context_id,
req.batch_start_offset as u64,
req.batch_len,
&[],
)?;
let mut resp = req;
let resp_bytes = bytes_of(&resp);
let len = resp_bytes.len();
if len >= 8 {
let seqno_bytes = (seqno).to_le_bytes();
resp_bytes[len - 8..].copy_from_slice(&seqno_bytes);
}
resp_bytes.to_vec()
}
REDOX_DRM_IOCTL_I915_QUERY => {
let mut req = decode_wire::<DrmI915QueryWire>(payload)?;
let len = req.length.min(256);
if payload.len() >= 8 + len as usize {
let data_start = 8;
let data_end = data_start + len as usize;
let mut data = payload[data_start..data_end].to_vec();
self.driver.i915_query(req.query_id, &mut data)?;
let mut resp = payload.to_vec();
resp.splice(data_start..data_end, data);
return Ok(resp);
}
Vec::new()
}
REDOX_DRM_IOCTL_I915_GEM_VM_CREATE => {
let mut req = decode_wire::<DrmI915GemVmControlWire>(payload)?;
self.driver.i915_gem_vm_create(&mut req.vm_id)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_I915_GEM_VM_DESTROY => {
let req = decode_wire::<DrmI915GemVmControlWire>(payload)?;
self.driver.i915_gem_vm_destroy(req.vm_id)?;
Vec::new()
}
REDOX_DRM_IOCTL_I915_GEM_VM_BIND => {
let _req = decode_wire::<DrmI915GemVmBindWire>(payload)?;
Vec::new()
}
REDOX_DRM_IOCTL_AMDGPU_GEM_CREATE => {
let mut req = decode_wire::<DrmAmdgpuGemCreateWire>(payload)?;
self.driver.amdgpu_gem_create(
req.alloc_size, req.domains, req.flags, &mut req.handle,
)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_AMDGPU_CTX => {
let mut req = decode_wire::<DrmAmdgpuCtxWire>(payload)?;
self.driver.amdgpu_ctx(req.op, &mut req.ctx_id, req.param)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_AMDGPU_CS => {
let mut req = decode_wire::<DrmAmdgpuCsWire>(payload)?;
let seqno = self.driver.amdgpu_cs(
req.ctx_id, &[], &[],
)?;
req.seqno = seqno;
bytes_of(&req)
}
REDOX_DRM_IOCTL_AMDGPU_VM => {
let mut req = decode_wire::<DrmAmdgpuVmWire>(payload)?;
self.driver.amdgpu_vm(req.op, &mut req.vm_id)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_AMDGPU_BO_LIST => {
let mut req = decode_wire::<DrmAmdgpuBoListWire>(payload)?;
self.driver.amdgpu_bo_list(
req.op, &mut req.list_handle, &[],
)?;
bytes_of(&req)
}
REDOX_DRM_IOCTL_AMDGPU_WAIT_FENCES => {
let req = decode_wire::<DrmAmdgpuWaitFencesWire>(payload)?;
let _completed = self.driver.amdgpu_wait_fences(
&[], req.timeout_ns, req.wait_domain,
)?;
Vec::new()
}
REDOX_DRM_IOCTL_AMDGPU_INFO => {
let mut req = decode_wire::<DrmAmdgpuInfoWire>(payload)?;
let len = req.length.min(256);
if payload.len() >= 8 + len as usize {
let data_start = 8;
let data_end = data_start + len as usize;
let mut data = payload[data_start..data_end].to_vec();
self.driver.amdgpu_info(req.query_id, &mut data)?;
let mut resp = payload.to_vec();
resp.splice(data_start..data_end, data);
return Ok(resp);
}
Vec::new()
}
REDOX_DRM_IOCTL_AMDGPU_FENCE_TO_HANDLE => {
let mut req = decode_wire::<DrmAmdgpuFenceToHandleWire>(payload)?;
self.driver.amdgpu_fence_to_handle(
req.fence, req.flags, &mut req.handle,
)?;
bytes_of(&req)
}
DRM_IOCTL_REDOX_AMD_SDMA_WAIT => {
let mut req = decode_wire::<RedoxAmdSdmaWaitWire>(payload)?;
if req.flags != 0 {
@@ -2295,15 +2474,27 @@ impl DrmScheme {
bytes_of(&resp)
}
// REDOX_FENCE_EVENTFD — async-fence wakeup. The kernel
// waits for the CS seqno to complete (or the timeout to
// elapse) and writes 1 byte to the event_fd to wake the
// userland caller. This is the kernel-side implementation
// of drivers/gpu/drm/drm_syncobj.c::drm_syncobj_wait_ioctl.
// REDOX_FENCE_EVENTFD — async-fence wakeup via the
// eventfd-based mechanism. The userland supplies an eventfd
// it created with relibc's eventfd(2). The kernel dups the
// fd, stores the dup as a kernel-side handle, and writes 1
// to it when the CS ring advances past the supplied seqno.
// The kernel-side dup'd fd is returned to the userland for
// poll(2) — both fds share the same underlying file
// description so writing to one signals both. See
// handle_fence_eventfd below for the full signaling model.
DRM_IOCTL_REDOX_FENCE_EVENTFD => {
let req = decode_wire::<RedoxFenceEventfdWire>(payload)?;
self.handle_fence_eventfd(req.seqno, req.timeout_ns)?;
Vec::new()
let req = decode_wire::<RedoxFenceRegisterEventfdWire>(payload)?;
let resp = RedoxFenceRegisterEventfdWire {
userland_eventfd: req.userland_eventfd,
seqno: req.seqno,
kernel_eventfd: self.handle_fence_eventfd(
req.userland_eventfd,
req.seqno,
)?,
_pad: 0,
};
bytes_of(&resp)
}
// REDOX_CREATE_CONTEXT — allocate a per-process context