diff --git a/Cargo.lock b/Cargo.lock index d068933..7c4e616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,7 +82,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] -name = "intel_gpu_uapi" +name = "intel_gpu" version = "0.1.0" dependencies = [ "bindgen", diff --git a/Cargo.toml b/Cargo.toml index 9341d37..0583fff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "intel_gpu_uapi" +name = "intel_gpu" version = "0.1.0" edition = "2021" diff --git a/src/gpu/i915/mod.rs b/src/gpu/i915/mod.rs index 6e88114..290693b 100644 --- a/src/gpu/i915/mod.rs +++ b/src/gpu/i915/mod.rs @@ -1,14 +1,68 @@ use std::{ffi::OsString, os::fd::{AsRawFd, OwnedFd}}; +use crate::uapi::i915::DrmGemHandle; + use super::super::uapi; use std::path::Path; use std::fs::File; +pub use uapi::i915::GemIoctlError; + #[derive(Debug)] pub struct DrmDeviceNode { pub fd: OwnedFd, pub path: Option, } +#[derive(Debug)] +pub struct GemHandle<'a> { + pub handle: DrmGemHandle, + pub node: &'a DrmDeviceNode, +} + +/// An owned GEM handle that will be closed when dropped +impl GemHandle<'_> { + pub fn new(node: &DrmDeviceNode, size: u64) -> Option { + uapi::i915::make_gem(node.fd.as_raw_fd(), size).map(|handle| GemHandle { + handle: handle, + node: &node, + }) + } + + pub fn from_handle(node: &DrmDeviceNode, handle: DrmGemHandle) -> Result { + // Avoid invoking GemHandle::is_valid() here to prevent the drop() method from trying to drop an invalid handle + let is_valid = uapi::i915::gem_is_valid(node.fd.as_raw_fd(), handle); + + if is_valid.is_ok() && is_valid.unwrap() { + let res = GemHandle { + handle: handle, + node: &node, + }; + return Ok(res); + } else { + return Err(is_valid.err().unwrap_or(GemIoctlError::PermissionDenied)); + } + } + + pub fn close(&mut self) -> Result<(), i32> { + return uapi::i915::close_gem(self.node.fd.as_raw_fd(), self.handle); + } + + pub fn has_tiling(&self) -> Result { + uapi::i915::gem_has_tiling(self.node.fd.as_raw_fd(), self.handle) + } + + pub fn is_valid(&self) -> Result { + uapi::i915::gem_is_valid(self.node.fd.as_raw_fd(), self.handle) + } +} + +impl Drop for GemHandle<'_> { + fn drop(&mut self) { + // Ignoring the close failing as an invalid Gem handle's drop is a no-op + let _ = self.close(); + } +} + #[derive(Debug)] pub struct Device { pub node: DrmDeviceNode, diff --git a/src/uapi/i915/mod.rs b/src/uapi/i915/mod.rs index e53fcfb..67c0ca4 100644 --- a/src/uapi/i915/mod.rs +++ b/src/uapi/i915/mod.rs @@ -29,6 +29,11 @@ pub struct EngineInfo { pub capabilities: u64, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DrmGemHandle { + pub handle: u32, +} + fn engine_class_from_u16(engine_class: u16) -> EngineClass { match engine_class { 0 => EngineClass::Render, @@ -83,32 +88,39 @@ impl EngineInfo { } } +pub unsafe fn do_query(fd: RawFd, query_id: u32) -> Option<*mut T> { + let mut query_item = native::drm_i915_query_item { + query_id: query_id as u64, + length: 0, + data_ptr: core::ptr::null_mut::() as u64, + flags: 0, + }; + let mut query = native::drm_i915_query { + items_ptr: (&mut query_item) as *mut native::drm_i915_query_item as u64, + num_items: 1, + flags: 0, + }; + let res = libc::ioctl(fd.as_raw_fd(), native::DRM_IOCTL_I915_QUERY, &mut query); + if res != 0 { + return None; + } + let result_info = libc::malloc(query_item.length as usize) + as *mut T; + libc::memset(result_info as *mut c_void, 0, query_item.length as usize); + query_item.data_ptr = result_info as u64; + let res = libc::ioctl(fd.as_raw_fd(), native::DRM_IOCTL_I915_QUERY, &mut query); + if res != 0 { + return None; + } + Some(result_info) +} + pub fn get_engines(fd: RawFd) -> Option> { unsafe { - let mut query_item = native::drm_i915_query_item { - query_id: native::DRM_I915_QUERY_ENGINE_INFO as u64, - length: 0, - data_ptr: core::ptr::null_mut::() as u64, - flags: 0, - }; - let mut query = native::drm_i915_query { - items_ptr: (&mut query_item) as *mut native::drm_i915_query_item as u64, - num_items: 1, - flags: 0, - }; - let res = libc::ioctl(fd.as_raw_fd(), native::DRM_IOCTL_I915_QUERY, &mut query); - if res != 0 { - return None; - } - let engine_info = libc::malloc(query_item.length as usize) - as *mut native::drm_i915_query_engine_info; - libc::memset(engine_info as *mut c_void, 0, query_item.length as usize); - query_item.data_ptr = engine_info as u64; - let res = libc::ioctl(fd.as_raw_fd(), native::DRM_IOCTL_I915_QUERY, &mut query); - if res != 0 { - return None; - } - let engine_data = engine_info.as_mut().unwrap(); + let engine_data = + do_query::(fd, + native::DRM_I915_QUERY_ENGINE_INFO)? + .as_mut().unwrap(); let engines = engine_data.engines.as_mut_slice(engine_data.num_engines as usize); let mut engines_res: Vec = Vec::new(); for i in 0..engine_data.num_engines { @@ -134,6 +146,94 @@ pub fn get_param(fd: RawFd, param_id: i32) -> Option { } } +pub fn get_context_param(fd: RawFd, context_id: u32, param_id: u32) -> Option { + unsafe { + let mut param = native::drm_i915_gem_context_param { + ctx_id: context_id, + param: param_id as u64, + value: 0, + size: 0, + }; + let res = libc::ioctl(fd, native::DRM_IOCTL_I915_GEM_CONTEXT_GETPARAM, &mut param); + if res != 0 { + return None; + } + Some(param.value) + } +} + +pub fn make_gem(fd: RawFd, size: u64) -> Option { + unsafe { + let mut create = native::drm_i915_gem_create { + size: size, + handle: 0, + pad: 0, + }; + let res = libc::ioctl(fd, native::DRM_IOCTL_I915_GEM_CREATE, &mut create); + if res != 0 { + return None; + } + let handle = DrmGemHandle { + handle: create.handle, + }; + Some(handle) + } +} + +pub fn close_gem(fd: RawFd, handle: DrmGemHandle) -> Result<(), i32> { + unsafe { + let mut close = native::drm_gem_close { + handle: handle.handle, + pad: 0, + }; + let res = libc::ioctl(fd, native::DRM_IOCTL_GEM_CLOSE, &mut close); + if res != 0 { + return Err(res); + } + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GemIoctlError { + InvalidHandle, + PermissionDenied, + Unknown(i32), +} + +pub fn gem_has_tiling(fd: RawFd, handle: DrmGemHandle) -> Result { + unsafe { + let mut tiling = native::drm_i915_gem_set_tiling { + handle: handle.handle, + tiling_mode: 0, + stride: 0, + swizzle_mode: 0, + }; + let res = libc::ioctl(fd, native::DRM_IOCTL_I915_GEM_SET_TILING, &mut tiling); + match -res { + 0 => Ok(true), + libc::ENOENT => Err(GemIoctlError::InvalidHandle), + libc::EPERM => Err(GemIoctlError::PermissionDenied), + libc::EINVAL => Ok(false), + _ => Err(GemIoctlError::Unknown(res)), + } + } +} + +pub fn gem_is_valid(fd: RawFd, handle: DrmGemHandle) -> Result { + let res = gem_has_tiling(fd, handle); + if res.is_ok() && res.unwrap() { + return Ok(true); + } + + // For some reason the kernel returns -EPERM instead of -ENOENT when the handle is invalid... + if res.is_err() && res.unwrap_err() == GemIoctlError::PermissionDenied { + return Ok(false); + } + + return Err(res.unwrap_err()); +} + pub fn find_node() -> Option { let inodes = fs::read_dir("/dev/dri").ok()?; for inode in inodes { diff --git a/tests/tests_i915_gpu.rs b/tests/tests_i915_gpu.rs index 71bc193..c9cb93e 100644 --- a/tests/tests_i915_gpu.rs +++ b/tests/tests_i915_gpu.rs @@ -1,4 +1,4 @@ -use intel_gpu_uapi::*; +use intel_gpu::*; use gpu::i915; use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use std::fs::File; @@ -79,11 +79,37 @@ fn get_vendor_for_fd(fd: RawFd) -> String{ #[test] fn test_i915_gpu_get_param() { let device = i915::find_device().expect("Failed to find i915 device"); - let drm_version = &device.driver; - assert_eq!(drm_version.name.to_str().unwrap(), "i915"); - assert_eq!(drm_version.desc.to_str().unwrap(), "Intel Graphics"); let chipset_id = device.get_param(native::I915_PARAM_CHIPSET_ID).expect("Failed to get param"); let chip_id = format!("{:#04x}", chipset_id); let dev_id = get_vendor_for_fd(device.node.fd.as_raw_fd()); assert_eq!(dev_id, chip_id); +} + +#[test] +fn test_i915_gpu_gem_lifecycle() { + let device = i915::find_device().expect("Failed to find i915 device"); + let gem = i915::GemHandle::new(&device.node, 4096).expect("Failed to create gem handle"); + let tmp = gem.handle.clone(); + assert!(gem.is_valid().unwrap()); + drop(gem); + assert!(i915::GemHandle::from_handle(&device.node, tmp).is_err()); +} + +#[test] +fn test_i915_gpu_gem_from_handle() { + let device = i915::find_device().expect("Failed to find i915 device"); + let gem = i915::GemHandle::new(&device.node, 4096).expect("Failed to create gem handle"); + let tmp = gem.handle.clone(); + let gem_2 = i915::GemHandle::from_handle(&device.node, tmp).expect("Failed to create gem handle from handle"); + assert_eq!(gem.handle, gem_2.handle); + assert!(gem.is_valid().unwrap()); + drop(gem); + assert!(!gem_2.is_valid().unwrap()); +} + +#[test] +fn test_i915_gpu_gem_has_tiling() { + let device = i915::find_device().expect("Failed to find i915 device"); + let gem = i915::GemHandle::new(&device.node, 4096).expect("Failed to create gem handle"); + assert!(gem.has_tiling().unwrap()); } \ No newline at end of file diff --git a/tests/tests_raw.rs b/tests/tests_raw.rs index 28d80cb..89be3e3 100644 --- a/tests/tests_raw.rs +++ b/tests/tests_raw.rs @@ -1,4 +1,4 @@ -use intel_gpu_uapi::*; +use intel_gpu::*; use std::ffi::c_void; use std::ffi::CStr; use nix::{self}; diff --git a/tests/tests_uapi.rs b/tests/tests_uapi.rs index 9788952..7c199d9 100644 --- a/tests/tests_uapi.rs +++ b/tests/tests_uapi.rs @@ -1,6 +1,6 @@ #![allow(unused_imports)] -use intel_gpu_uapi::*; +use intel_gpu::*; use uapi::i915; use std::fs; use std::{fs::File, os::fd::{AsRawFd, RawFd}}; @@ -8,7 +8,9 @@ use std::{fs::File, os::fd::{AsRawFd, RawFd}}; #[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"); + 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] @@ -40,5 +42,39 @@ fn test_i915_uapi_native_engine_info() { #[test] fn test_i915_find_fd() { let fd = i915::find_fd().expect("Failed to find i915 fd"); - let _drm_version = uapi::get_drm_version(fd.as_raw_fd()).expect("Failed to get drm version"); + let drm_version = uapi::get_drm_version(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_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); +} + +#[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()); + i915::close_gem(node.fd.as_raw_fd(), gem).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"); } \ No newline at end of file