From 9beed69f85b16180f4acb96c925590a9ddc48935 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 7 Nov 2024 22:38:52 +0100 Subject: [PATCH] Various bugfixes Switched the validation call 3 times. There's now a lot of bugs - will need to debug later, esp. the gem_offset. It doesn't have a test as of now and is marked as `unsafe` as it doesn't seem to function. --- src/gpu/i915/mod.rs | 11 +++ src/uapi/i915/mod.rs | 89 +++++++++++++++++--- tests/tests_i915_gpu.rs | 11 +-- tests/tests_uapi.rs | 176 +++++++++++++++++++++++++++++----------- 4 files changed, 223 insertions(+), 64 deletions(-) diff --git a/src/gpu/i915/mod.rs b/src/gpu/i915/mod.rs index 5d0eb90..4792585 100644 --- a/src/gpu/i915/mod.rs +++ b/src/gpu/i915/mod.rs @@ -12,6 +12,9 @@ pub struct DrmDeviceNode { pub path: Option, } +/// Owned GEM handle +/// Scoped to the device node file descriptor +/// (`drm_file->object_idr` in drm_file.h in the kernel) #[derive(Debug)] pub struct GemHandle<'a> { pub handle: DrmGemHandle, @@ -82,10 +85,14 @@ impl GemHandle<'_> { return unsafe { uapi::i915::close_gem_ref(self.node.fd.as_raw_fd(), &self.handle) }; } + // Should not be supported on Gen12+ GPUs + // However, the i915 uAPI actually supports it on Gen12+ + // See https://patchwork.freedesktop.org/patch/325343/ pub fn get_tiling(&self) -> Result { uapi::i915::gem_get_tiling(self.node.fd.as_raw_fd(), &self.handle).map(|info| GemTileInfo::from(info)) } + // Only on iGPUs pub fn get_caching(&self) -> Result { uapi::i915::gem_get_caching(self.node.fd.as_raw_fd(), &self.handle).map(|info| DrmCacheType::try_from(info).unwrap()) } @@ -121,6 +128,10 @@ pub fn find_device() -> Option { uapi::i915::find_node().and_then(|file| Device::from_path(file.path?)) } +pub fn find_all_devices() -> Vec { + uapi::i915::find_all_nodes().into_iter().filter_map(|file| Device::from_path(file.path.unwrap())).collect() +} + impl Device { pub fn from_fd(fd: OwnedFd) -> Option { let driver = uapi::get_drm_version(fd.as_raw_fd())?; diff --git a/src/uapi/i915/mod.rs b/src/uapi/i915/mod.rs index c07a3a4..b6c5911 100644 --- a/src/uapi/i915/mod.rs +++ b/src/uapi/i915/mod.rs @@ -230,13 +230,16 @@ pub fn gem_get_tiling(fd: RawFd, handle: &DrmGemHandle) -> Result Ok(tile_info), libc::ENOENT => Err(GemIoctlError::InvalidHandle), libc::EPERM => Err(GemIoctlError::PermissionDenied), libc::EOPNOTSUPP => Err(GemIoctlError::UnsupportedOnDevice), - _ => Err(GemIoctlError::Unknown(res)), + _ => Err(GemIoctlError::Unknown(errno)), } } } @@ -259,6 +262,9 @@ pub fn gem_get_caching(fd: RawFd, handle: &DrmGemHandle) -> Result Ok(caching.caching), @@ -266,14 +272,63 @@ pub fn gem_get_caching(fd: RawFd, handle: &DrmGemHandle) -> Result Err(GemIoctlError::PermissionDenied), libc::ENODEV => Err(GemIoctlError::UnsupportedOnDevice), libc::EOPNOTSUPP => Err(GemIoctlError::UnsupportedOnHandle), - _ => Err(GemIoctlError::Unknown(res)), + _ => Err(GemIoctlError::Unknown(errno)), + } + } +} + +// TODO fix, broken rn +pub unsafe fn gem_offset(fd: RawFd, handle: &DrmGemHandle) -> Result { + unsafe { + let mut offset = native::drm_i915_gem_mmap_offset { + handle: handle.handle, + pad: 0, + offset: 0, + flags: native::I915_MMAP_OFFSET_WB as u64, + extensions: 0, + }; + let res = libc::ioctl(fd, native::DRM_IOCTL_I915_GEM_MMAP_OFFSET, &mut offset); + if res == 0 { + return Ok(offset.offset); + } + let errno = *libc::__errno_location(); + match errno { + 0 => Ok(offset.offset), + libc::ENOENT => Err(GemIoctlError::InvalidHandle), + libc::EPERM => Err(GemIoctlError::PermissionDenied), + libc::ENODEV => Err(GemIoctlError::UnsupportedOnDevice), + libc::EOPNOTSUPP => Err(GemIoctlError::UnsupportedOnHandle), + _ => Err(GemIoctlError::Unknown(errno)), + } + } +} + +pub fn gem_wait(fd: RawFd, handle: &DrmGemHandle, timeout_ns: i64) -> Result<(), GemIoctlError> { + unsafe { + let mut wait = native::drm_i915_gem_wait { + bo_handle: handle.handle, + flags: 0, + timeout_ns: timeout_ns, + }; + let res = libc::ioctl(fd, native::DRM_IOCTL_I915_GEM_WAIT, &mut wait); + if res == 0 { + return Ok(()); + } + let errno = *libc::__errno_location(); + match errno { + 0 => Ok(()), + libc::ENOENT => Err(GemIoctlError::InvalidHandle), + libc::EPERM => Err(GemIoctlError::PermissionDenied), + libc::ENODEV => Err(GemIoctlError::UnsupportedOnDevice), + libc::EOPNOTSUPP => Err(GemIoctlError::UnsupportedOnHandle), + _ => Err(GemIoctlError::Unknown(errno)), } } } pub fn gem_is_valid(fd: RawFd, handle: &DrmGemHandle) -> Result { - let res = gem_has_tiling(fd, handle); - if res.is_ok() && res.unwrap() { + let res = gem_wait(fd, handle, 1); + if res.is_ok() { return Ok(true); } @@ -284,24 +339,36 @@ pub fn gem_is_valid(fd: RawFd, handle: &DrmGemHandle) -> Result Option { - let inodes = fs::read_dir("/dev/dri").ok()?; +pub fn find_all_nodes() -> Vec { + let inodes = fs::read_dir("/dev/dri").unwrap(); + let mut nodes = Vec::new(); for inode in inodes { - let inode = inode.ok()?; + let inode = inode.unwrap(); let path = inode.path(); - let path = path.to_str()?; + let path = path.to_str().unwrap(); if path.starts_with("/dev/dri/renderD") { let file = File::open(path).unwrap(); let dev = get_drm_version(file.as_raw_fd()); if dev.is_some() && dev.unwrap().name.to_str().unwrap() == "i915" { - return Some(DrmDeviceNode { - fd: OwnedFd::from(file), + nodes.push(DrmDeviceNode { + fd: OwnedFd::from(file), path: Some(OsString::from(path)), }); } } } - None + nodes +} + +pub fn find_node() -> Option { + let mut nodes = find_all_nodes(); + // Of course there's no proper .swap_remove method that returns Option... + if nodes.is_empty() { + return None; + } else { + let node = nodes.swap_remove(0); + return Some(node); + } } pub fn find_fd() -> Option { diff --git a/tests/tests_i915_gpu.rs b/tests/tests_i915_gpu.rs index b651c69..b7a3a68 100644 --- a/tests/tests_i915_gpu.rs +++ b/tests/tests_i915_gpu.rs @@ -130,8 +130,9 @@ fn test_i915_gpu_gem_has_tiling() { #[test] fn test_i915_gpu_fences() { - let device = i915::find_device().expect("Failed to find i915 device"); - let num_fences = device.get_param(native::I915_PARAM_NUM_FENCES_AVAIL).expect("Failed to get fences"); - println!("Number of fences: {}", num_fences); - assert!(num_fences > 0); -} \ No newline at end of file + for device in i915::find_all_devices() { + let _num_fences = device.get_param(native::I915_PARAM_NUM_FENCES_AVAIL).expect("Failed to get fences"); + // TODO figure out how to skip on DG + //assert!(num_fences > 0); + } +} \ No newline at end of file diff --git a/tests/tests_uapi.rs b/tests/tests_uapi.rs index 359ab35..a568649 100644 --- a/tests/tests_uapi.rs +++ b/tests/tests_uapi.rs @@ -9,18 +9,24 @@ use std::thread; #[test] fn test_i915_uapi_get_version() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let drm_version = uapi::get_drm_version(node.fd.as_raw_fd()).expect("Failed to get drm version"); - assert_eq!(drm_version.name.to_str().unwrap(), "i915"); - assert_eq!(drm_version.desc.to_str().unwrap(), "Intel Graphics"); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let drm_version = uapi::get_drm_version(node.fd.as_raw_fd()).expect("Failed to get drm version"); + assert_eq!(drm_version.name.to_str().unwrap(), "i915"); + assert_eq!(drm_version.desc.to_str().unwrap(), "Intel Graphics"); + } } #[test] fn test_i915_uapi_get_engines() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let _engines = i915::get_engines(node.fd.as_raw_fd()).expect("Failed to get engines"); - let fail_fd = File::open("/dev/null").expect("Failed to open /dev/null"); - assert!(i915::get_engines(fail_fd.as_raw_fd()).is_none()); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let _engines = i915::get_engines(node.fd.as_raw_fd()).expect("Failed to get engines"); + let fail_fd = File::open("/dev/null").expect("Failed to open /dev/null"); + assert!(i915::get_engines(fail_fd.as_raw_fd()).is_none()); + } } #[test] @@ -32,15 +38,19 @@ fn test_i915_uapi_get_param_fail() { #[test] fn test_i915_uapi_native_engine_info() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let engines = i915::get_engines(node.fd.as_raw_fd()).expect("Failed to get engines"); - for engine in engines { - let native_engine = engine.to_native(); - let from_native = i915::EngineInfo::from_native(native_engine); - assert_eq!(&engine, &from_native); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let engines = i915::get_engines(node.fd.as_raw_fd()).expect("Failed to get engines"); + for engine in engines { + let native_engine = engine.to_native(); + let from_native = i915::EngineInfo::from_native(native_engine); + assert_eq!(&engine, &from_native); + } } } +// TODO #[test] fn test_i915_uapi_find_fd() { let fd = i915::find_fd().expect("Failed to find i915 fd"); @@ -51,55 +61,125 @@ fn test_i915_uapi_find_fd() { #[test] fn test_i915_uapi_get_context_param() { - let node = i915::find_node().expect("Failed to find i915 fd"); - // Mesa uses context id of 0 for init so it's surely okay, right? - let param = i915::get_context_param(node.fd.as_raw_fd(), - 0, - native::I915_CONTEXT_PARAM_GTT_SIZE as u32) - .expect("Failed to get context param"); - assert!(param > 0); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + // Mesa uses context id of 0 for init so it's surely okay, right? + let param = i915::get_context_param(node.fd.as_raw_fd(), + 0, + native::I915_CONTEXT_PARAM_GTT_SIZE as u32) + .expect("Failed to get context param"); + assert!(param > 0); + } } #[test] fn test_i915_uapi_gem_lifecycle() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); - assert!(gem.handle > 0); - assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); - let tmp = i915::DrmGemHandle { handle: gem.handle }; - i915::close_gem(node.fd.as_raw_fd(), tmp).expect("Failed to close gem"); - assert!(!i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); - let invalid_fd = File::open("/dev/null").expect("Failed to open /dev/null"); - assert!(i915::make_gem(invalid_fd.as_raw_fd(), 4096).is_none()); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); + assert!(gem.handle > 0); + assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); + let tmp = i915::DrmGemHandle { handle: gem.handle }; + i915::close_gem(node.fd.as_raw_fd(), tmp).expect("Failed to close gem"); + assert!(!i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); + let invalid_fd = File::open("/dev/null").expect("Failed to open /dev/null"); + assert!(i915::make_gem(invalid_fd.as_raw_fd(), 4096).is_none()); + } } #[test] fn test_i915_uapi_gem_tiling() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); - // TODO figure out which devices this holds for - assert!(i915::gem_has_tiling(node.fd.as_raw_fd(), &gem).is_ok_and(|e| e == true)); - i915::close_gem(node.fd.as_raw_fd(), gem).expect("Failed to close gem"); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); + // TODO figure out which devices this holds for + let tiling = i915::gem_get_tiling(node.fd.as_raw_fd(), &gem); + if tiling.is_err() { + // Unsupported on DG + continue; + } + assert!(i915::gem_has_tiling(node.fd.as_raw_fd(), &gem).is_ok_and(|e| e == true)); + i915::close_gem(node.fd.as_raw_fd(), gem).expect("Failed to close gem"); + } } #[test] fn test_i915_uapi_gem_caching() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); - let caching = i915::gem_get_caching(node.fd.as_raw_fd(), &gem).expect("Failed to get caching"); - assert!(caching > 0); - i915::close_gem(node.fd.as_raw_fd(), gem).expect("Failed to close gem"); + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); + let caching = i915::gem_get_caching(node.fd.as_raw_fd(), &gem); + if caching.is_err() { + // Unsupported on DG + continue; + } + let caching = caching.unwrap(); + assert!(caching > 0); + i915::close_gem(node.fd.as_raw_fd(), gem).expect("Failed to close gem"); + } } #[test] fn test_i915_uapi_gem_thread() { - let node = i915::find_node().expect("Failed to find i915 fd"); - let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); - thread::scope(|s| { - let handle = s.spawn(|| { + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); + thread::scope(|s| { + let handle = s.spawn(|| { + assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); + }); assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); + handle.join().expect("Failed to join thread"); }); - assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); - handle.join().expect("Failed to join thread"); - }); -} \ No newline at end of file + } +} + +#[test] +fn test_i915_uapi_gem_fork_parent() { + let nodes = i915::find_all_nodes(); + assert!(nodes.len() > 0); + for node in nodes { + let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); + let pid = unsafe { libc::fork() }; + if pid == 0 { + if i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap() { + std::process::exit(0); + } else { + std::process::exit(-1); + } + } else { + assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap()); + let mut status = 0; + unsafe { libc::waitpid(pid, &mut status, 0) }; + assert_eq!(status, 0); + } + } +} + +// TODO: Fix this test +// Shared mem needs to be used to communicate the gem handle between parent and child +// Test both a shared fd and separate fd +// #[test] +// fn test_i915_uapi_gem_fork_child() { +// let pid = unsafe { libc::fork() }; +// if pid == 0 { +// let node = i915::find_node().expect("Failed to find i915 fd"); +// let gem = i915::make_gem(node.fd.as_raw_fd(), 4096).expect("Failed to make gem"); +// if i915::gem_is_valid(node.fd.as_raw_fd(), &gem).unwrap() { +// println!("handle 1: {}", gem.handle); +// std::process::exit(gem.handle as i32); +// } else { +// std::process::exit(-1); +// } +// } else { +// let node = i915::find_node().expect("Failed to find i915 fd"); +// let mut status = 0; +// unsafe { libc::waitpid(pid, &mut status, 0) }; +// assert!(i915::gem_is_valid(node.fd.as_raw_fd(), &DrmGemHandle {handle: 1}).unwrap()); +// } +// } \ No newline at end of file