├── drm-ffi ├── LICENSE ├── drm-sys │ ├── LICENSE │ ├── README.md │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── build.rs ├── Cargo.toml └── src │ ├── utils.rs │ ├── gem.rs │ ├── lib.rs │ ├── syncobj.rs │ ├── ioctl.rs │ └── mode.rs ├── .gitignore ├── examples ├── images │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png ├── list_modes.rs ├── basic.rs ├── properties.rs ├── ffi.rs ├── utils │ └── mod.rs ├── syncobj.rs ├── legacy_modeset.rs ├── resources.rs ├── planes.rs ├── atomic_modeset.rs └── kms_interactive.rs ├── .github └── workflows │ ├── fetch_headers.sh │ └── ci.yml ├── src ├── util.rs ├── node │ ├── constants.rs │ └── mod.rs ├── control │ ├── syncobj.rs │ ├── dumbbuffer.rs │ ├── atomic.rs │ ├── crtc.rs │ ├── plane.rs │ ├── framebuffer.rs │ ├── encoder.rs │ ├── connector.rs │ └── property.rs ├── buffer │ └── mod.rs └── lib.rs ├── LICENSE ├── Cargo.toml └── README.md /drm-ffi/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /drm-ffi/drm-sys/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | -------------------------------------------------------------------------------- /examples/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smithay/drm-rs/HEAD/examples/images/1.png -------------------------------------------------------------------------------- /examples/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smithay/drm-rs/HEAD/examples/images/2.png -------------------------------------------------------------------------------- /examples/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smithay/drm-rs/HEAD/examples/images/3.png -------------------------------------------------------------------------------- /examples/images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smithay/drm-rs/HEAD/examples/images/4.png -------------------------------------------------------------------------------- /.github/workflows/fetch_headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir drm 4 | wget -O drm/drm.h https://github.com/torvalds/linux/raw/master/include/uapi/drm/drm.h 5 | wget -O drm/drm_mode.h https://github.com/torvalds/linux/raw/master/include/uapi/drm/drm_mode.h 6 | echo "LIBDRM_INCLUDE_PATH=${PWD}/drm" >> $GITHUB_ENV 7 | echo "BINDGEN_EXTRA_CLANG_ARGS=-D __user= ${BINDGEN_EXTRA_CLANG_ARGS}" >> $GITHUB_ENV 8 | -------------------------------------------------------------------------------- /drm-ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drm-ffi" 3 | description = "Safe, low-level bindings to the Direct Rendering Manager API" 4 | repository = "https://github.com/Smithay/drm-rs" 5 | version = "0.9.0" 6 | license = "MIT" 7 | authors = ["Tyler Slabinski "] 8 | rust-version = "1.70" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | drm-sys = { path = "drm-sys", version = "0.8.0" } 13 | rustix = { version = "1.0.7" } 14 | 15 | [features] 16 | use_bindgen = ["drm-sys/use_bindgen"] 17 | -------------------------------------------------------------------------------- /drm-ffi/drm-sys/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ```toml 4 | [dependencies] 5 | drm-sys = "..." 6 | ``` 7 | 8 | # Platforms 9 | 10 | The following platforms have prebuilt bindings available: 11 | 12 | * Linux 13 | * \*BSD 14 | 15 | The bindings are not architecture dependant, but CI testing only happens for: 16 | 17 | * arm 18 | * armv7 19 | * aarch64 20 | * riscv64gc 21 | * i686 22 | * x86\_64 23 | 24 | If bindings for your target platform are not available, you can attempt to 25 | generate them by enabling the `use_bindgen` feature: 26 | 27 | ```toml 28 | [dependencies.drm-sys] 29 | version = "..." 30 | features = ["use_bindgen"] 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/list_modes.rs: -------------------------------------------------------------------------------- 1 | /// Check the `util` module to see how the `Card` structure is implemented. 2 | pub mod utils; 3 | use crate::utils::*; 4 | 5 | pub fn main() { 6 | let card = Card::open_global(); 7 | 8 | let resources = card.resource_handles().unwrap(); 9 | for connector in resources.connectors().iter() { 10 | let info = card.get_connector(*connector, false).unwrap(); 11 | println!("Connector {:?}: {:?}", info.interface(), info.state()); 12 | if info.state() == drm::control::connector::State::Connected { 13 | println!("\t Modes:\n{:#?}", info.modes()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Utilities used internally by this crate. 2 | 3 | use crate::control::{from_u32, RawResourceHandle}; 4 | 5 | pub unsafe fn transmute_vec(from: Vec) -> Vec { 6 | let mut from = std::mem::ManuallyDrop::new(from); 7 | 8 | Vec::from_raw_parts(from.as_mut_ptr() as *mut U, from.len(), from.capacity()) 9 | } 10 | 11 | pub unsafe fn transmute_vec_from_u32>(raw: Vec) -> Vec { 12 | if cfg!(debug_assertions) { 13 | raw.into_iter() 14 | .map(|handle| from_u32(handle).unwrap()) 15 | .collect() 16 | } else { 17 | transmute_vec(raw) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /drm-ffi/drm-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drm-sys" 3 | description = "Bindings to the Direct Rendering Manager API" 4 | repository = "https://github.com/Smithay/drm-rs" 5 | version = "0.8.0" 6 | authors = ["Tyler Slabinski "] 7 | license = "MIT" 8 | build = "build.rs" 9 | rust-version = "1.70" 10 | edition = "2021" 11 | 12 | [features] 13 | default = [] 14 | use_bindgen = ["bindgen", "pkg-config"] 15 | update_bindings = ["use_bindgen"] 16 | 17 | [build-dependencies] 18 | bindgen = { version = "0.70", optional = true } 19 | pkg-config = { version = "0.3.19", optional = true } 20 | 21 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] 22 | linux-raw-sys = { version = "0.9", default-features = false, features = ["general", "no_std"] } 23 | 24 | [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] 25 | libc = "0.2" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /drm-ffi/drm-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | 6 | #[cfg(any(target_os = "android", target_os = "linux"))] 7 | mod platform { 8 | pub use linux_raw_sys::general::__kernel_size_t; 9 | pub type drm_handle_t = core::ffi::c_uint; 10 | pub const DRM_RDWR: u32 = linux_raw_sys::general::O_RDWR; 11 | pub const DRM_CLOEXEC: u32 = linux_raw_sys::general::O_CLOEXEC; 12 | } 13 | 14 | #[cfg(not(any(target_os = "android", target_os = "linux")))] 15 | mod platform { 16 | pub type __kernel_size_t = libc::size_t; 17 | pub type drm_handle_t = core::ffi::c_ulong; 18 | pub const DRM_RDWR: u32 = libc::O_RDWR as u32; 19 | pub const DRM_CLOEXEC: u32 = libc::O_CLOEXEC as u32; 20 | } 21 | 22 | pub use platform::*; 23 | 24 | #[cfg(feature = "use_bindgen")] 25 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 26 | 27 | #[cfg(not(feature = "use_bindgen"))] 28 | include!("bindings.rs"); 29 | 30 | pub const DRM_PLANE_TYPE_OVERLAY: u32 = 0; 31 | pub const DRM_PLANE_TYPE_PRIMARY: u32 = 1; 32 | pub const DRM_PLANE_TYPE_CURSOR: u32 = 2; 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drm" 3 | description = "Safe, low-level bindings to the Direct Rendering Manager API" 4 | repository = "https://github.com/Smithay/drm-rs" 5 | version = "0.14.1" 6 | license = "MIT" 7 | authors = ["Tyler Slabinski ", "Victoria Brekenfeld "] 8 | exclude = [".gitignore", ".github"] 9 | rust-version = "1.70" 10 | edition = "2021" 11 | 12 | [dependencies] 13 | bitflags = "2" 14 | bytemuck = { version = "1.14", features = ["extern_crate_alloc", "derive"] } 15 | bytemuck_derive = { version = "1.5" } 16 | drm-ffi = { path = "drm-ffi", version = "0.9.0" } 17 | drm-fourcc = "^2.2.0" 18 | rustix = { version = "1.0.7", features = ["mm", "fs"] } 19 | 20 | [target.'cfg(any(target_os = "freebsd", target_os = "dragonfly"))'.dependencies] 21 | libc = "0.2" 22 | 23 | [dev-dependencies] 24 | image = { version = "0.24", default-features = false, features = ["png"] } 25 | rustix = { version = "1.0.7", features = ["event", "mm"] } 26 | rustyline = "13" 27 | 28 | [features] 29 | use_bindgen = ["drm-ffi/use_bindgen"] 30 | 31 | [workspace] 32 | members = [ 33 | "drm-ffi", 34 | "drm-ffi/drm-sys", 35 | ] 36 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | /// Check the `util` module to see how the `Card` structure is implemented. 2 | pub mod utils; 3 | use crate::utils::*; 4 | 5 | pub fn main() { 6 | let card = Card::open_global(); 7 | 8 | // Attempt to acquire and release master lock 9 | println!("Get Master lock: {:?}", card.acquire_master_lock()); 10 | println!("Release Master lock: {:?}", card.release_master_lock()); 11 | 12 | // Get the Bus ID of the device 13 | println!("Getting Bus ID: {:?}", card.get_bus_id().unwrap()); 14 | 15 | // Figure out driver in use 16 | println!("Getting driver info"); 17 | let driver = card.get_driver().unwrap(); 18 | println!("\tName: {:?}", driver.name()); 19 | println!("\tDate: {:?}", driver.date()); 20 | println!("\tDesc: {:?}", driver.description()); 21 | 22 | // Enable all possible client capabilities 23 | println!("Setting client capabilities"); 24 | for &cap in capabilities::CLIENT_CAP_ENUMS { 25 | println!("\t{:?}: {:?}", cap, card.set_client_capability(cap, true)); 26 | } 27 | 28 | // Get driver capabilities 29 | println!("Getting driver capabilities"); 30 | for &cap in capabilities::DRIVER_CAP_ENUMS { 31 | println!("\t{:?}: {:?}", cap, card.get_driver_capability(cap)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/node/constants.rs: -------------------------------------------------------------------------------- 1 | //! OS-Specific DRM constants. 2 | 3 | /// DRM major value. 4 | #[cfg(target_os = "dragonfly")] 5 | pub const DRM_MAJOR: u32 = 145; 6 | 7 | /// DRM major value. 8 | #[cfg(target_os = "netbsd")] 9 | pub const DRM_MAJOR: u32 = 34; 10 | 11 | /// DRM major value. 12 | #[cfg(all(target_os = "openbsd", target_arch = "x86"))] 13 | pub const DRM_MAJOR: u32 = 88; 14 | 15 | /// DRM major value. 16 | #[cfg(all(target_os = "openbsd", not(target_arch = "x86")))] 17 | pub const DRM_MAJOR: u32 = 87; 18 | 19 | /// DRM major value. 20 | #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd")))] 21 | pub const DRM_MAJOR: u32 = 226; 22 | 23 | /// Primary DRM node prefix. 24 | #[cfg(not(target_os = "openbsd"))] 25 | pub const PRIMARY_NAME: &str = "card"; 26 | 27 | /// Primary DRM node prefix. 28 | #[cfg(target_os = "openbsd")] 29 | pub const PRIMARY_NAME: &str = "drm"; 30 | 31 | /// Control DRM node prefix. 32 | #[cfg(not(target_os = "openbsd"))] 33 | pub const CONTROL_NAME: &str = "controlD"; 34 | 35 | /// Control DRM node prefix. 36 | #[cfg(target_os = "openbsd")] 37 | pub const CONTROL_NAME: &str = "drmC"; 38 | 39 | /// Render DRM node prefix. 40 | #[cfg(not(target_os = "openbsd"))] 41 | pub const RENDER_NAME: &str = "renderD"; 42 | 43 | /// Render DRM node prefix. 44 | #[cfg(target_os = "openbsd")] 45 | pub const RENDER_NAME: &str = "drmR"; 46 | -------------------------------------------------------------------------------- /drm-ffi/src/utils.rs: -------------------------------------------------------------------------------- 1 | /// Takes an `Option<&mut Vec>` style buffer and gets its pointer. 2 | macro_rules! map_ptr { 3 | ($buffer:expr) => { 4 | match $buffer { 5 | Some(b) => b.as_ptr() as _, 6 | None => 0 as _, 7 | } 8 | }; 9 | } 10 | 11 | /// Takes an `Option<&mut Vec>` style buffer and gets its allocated length. 12 | macro_rules! map_len { 13 | ($buffer:expr) => { 14 | match $buffer { 15 | Some(b) => b.capacity() as _, 16 | None => 0, 17 | } 18 | }; 19 | } 20 | 21 | /// Takes an `Option<&mut Vec>` style buffer and reserves space. 22 | macro_rules! map_reserve { 23 | ($buffer:expr, $size:expr) => { 24 | match $buffer { 25 | Some(ref mut b) => crate::utils::map_reserve_inner(b, $size), 26 | _ => (), 27 | } 28 | }; 29 | } 30 | 31 | pub(crate) fn map_reserve_inner(b: &mut Vec, size: usize) { 32 | let old_len = b.len(); 33 | if size <= old_len { 34 | return; 35 | } 36 | b.reserve_exact(size - old_len); 37 | 38 | // `memset` to 0, at least so Valgrind doesn't complain 39 | unsafe { 40 | let ptr = b.as_mut_ptr().add(old_len) as *mut u8; 41 | ptr.write_bytes(0, (size - old_len) * std::mem::size_of::()); 42 | } 43 | } 44 | 45 | /// Takes an `Option<&mut Vec>` style buffer and shrinks it. 46 | macro_rules! map_set { 47 | ($buffer:expr, $min:expr) => { 48 | match $buffer { 49 | Some(ref mut b) => unsafe { b.set_len($min) }, 50 | _ => (), 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /examples/properties.rs: -------------------------------------------------------------------------------- 1 | /// Check the `util` module to see how the `Card` structure is implemented. 2 | pub mod utils; 3 | use crate::utils::*; 4 | 5 | fn print_properties(card: &Card, handle: T) { 6 | let props = card.get_properties(handle).unwrap(); 7 | 8 | for (&id, &val) in props.iter() { 9 | println!("Property: {id:?}"); 10 | let info = card.get_property(id).unwrap(); 11 | println!("{:?}", info.name()); 12 | println!("{:#?}", info.value_type()); 13 | println!("Mutable: {}", info.mutable()); 14 | println!("Atomic: {}", info.atomic()); 15 | println!("Value: {:?}", info.value_type().convert_value(val)); 16 | println!(); 17 | } 18 | } 19 | 20 | pub fn main() { 21 | let card = Card::open_global(); 22 | 23 | // Enable all possible client capabilities 24 | for &cap in capabilities::CLIENT_CAP_ENUMS { 25 | if let Err(e) = card.set_client_capability(cap, true) { 26 | eprintln!("Unable to activate capability {cap:?}: {e}"); 27 | return; 28 | } 29 | } 30 | 31 | let resources = card.resource_handles().unwrap(); 32 | let plane_res = card.plane_handles().unwrap(); 33 | 34 | for &handle in resources.connectors() { 35 | print_properties(&card, handle); 36 | } 37 | 38 | for &handle in resources.framebuffers() { 39 | print_properties(&card, handle); 40 | } 41 | 42 | for &handle in resources.crtcs() { 43 | print_properties(&card, handle); 44 | } 45 | 46 | for handle in plane_res { 47 | print_properties(&card, handle); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /drm-ffi/src/gem.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Bindings to the Graphics Execution Manager 3 | //! 4 | 5 | use crate::ioctl; 6 | use drm_sys::*; 7 | 8 | use std::{ 9 | io, 10 | os::unix::io::{AsRawFd, BorrowedFd}, 11 | }; 12 | 13 | /// Open a GEM object given it's 32-bit name, returning the handle. 14 | pub fn open(fd: BorrowedFd<'_>, name: u32) -> io::Result { 15 | let mut gem = drm_gem_open { 16 | name, 17 | ..Default::default() 18 | }; 19 | 20 | unsafe { 21 | ioctl::gem::open(fd, &mut gem)?; 22 | } 23 | 24 | Ok(gem) 25 | } 26 | 27 | /// Closes a GEM object given it's handle. 28 | pub fn close(fd: BorrowedFd<'_>, handle: u32) -> io::Result { 29 | let gem = drm_gem_close { 30 | handle, 31 | ..Default::default() 32 | }; 33 | 34 | unsafe { 35 | ioctl::gem::close(fd, &gem)?; 36 | } 37 | 38 | Ok(gem) 39 | } 40 | 41 | /// Converts a GEM object's handle to a PRIME file descriptor. 42 | pub fn handle_to_fd(fd: BorrowedFd<'_>, handle: u32, flags: u32) -> io::Result { 43 | let mut prime = drm_prime_handle { 44 | handle, 45 | flags, 46 | ..Default::default() 47 | }; 48 | 49 | unsafe { 50 | ioctl::gem::prime_handle_to_fd(fd, &mut prime)?; 51 | } 52 | 53 | Ok(prime) 54 | } 55 | 56 | /// Converts a PRIME file descriptor to a GEM object's handle. 57 | pub fn fd_to_handle(fd: BorrowedFd<'_>, primefd: BorrowedFd<'_>) -> io::Result { 58 | let mut prime = drm_prime_handle { 59 | fd: primefd.as_raw_fd(), 60 | ..Default::default() 61 | }; 62 | 63 | unsafe { 64 | ioctl::gem::prime_fd_to_handle(fd, &mut prime)?; 65 | } 66 | 67 | Ok(prime) 68 | } 69 | -------------------------------------------------------------------------------- /src/control/syncobj.rs: -------------------------------------------------------------------------------- 1 | //! # SyncObj 2 | //! 3 | //! A SyncObj is a binding point for the DRM subsystem to attach single-use fences which are 4 | //! signalled when a device task completes. They are typically provided as optional arguments to 5 | //! device-specific command submission IOCTLs. In practice, they are used to implement Vulkan 6 | //! fence objects. 7 | //! 8 | //! After a submission IOCTL sets a fence into a SyncObj, it may be exported as a sync file 9 | //! descriptor. This sync file may be epoll()'d for EPOLLIN to implement asynchronous waiting on 10 | //! multiple events. This file descriptor is also compatible with [`tokio::io::unix::AsyncFd`] for 11 | //! Rust async/await integration. 12 | //! 13 | //! [`tokio::io::unix::AsyncFd`]: 14 | 15 | use crate::control; 16 | 17 | /// A handle to a specific syncobj 18 | #[repr(transparent)] 19 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 20 | pub struct Handle(control::RawResourceHandle); 21 | 22 | // Safety: Handle is repr(transparent) over NonZeroU32 23 | unsafe impl bytemuck::ZeroableInOption for Handle {} 24 | unsafe impl bytemuck::PodInOption for Handle {} 25 | unsafe impl bytemuck::NoUninit for Handle {} 26 | 27 | impl From for control::RawResourceHandle { 28 | fn from(handle: Handle) -> Self { 29 | handle.0 30 | } 31 | } 32 | 33 | impl From for u32 { 34 | fn from(handle: Handle) -> Self { 35 | handle.0.into() 36 | } 37 | } 38 | 39 | impl From for Handle { 40 | fn from(handle: control::RawResourceHandle) -> Self { 41 | Handle(handle) 42 | } 43 | } 44 | 45 | impl std::fmt::Debug for Handle { 46 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 47 | f.debug_tuple("syncobj::Handle").field(&self.0).finish() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/ffi.rs: -------------------------------------------------------------------------------- 1 | use drm_ffi as ffi; 2 | 3 | use std::fs::{File, OpenOptions}; 4 | use std::os::unix::io::{AsFd, BorrowedFd}; 5 | 6 | #[derive(Debug)] 7 | // This is our customized struct that implements the traits in drm. 8 | struct Card(File); 9 | 10 | // Need to implement AsRawFd before we can implement drm::Device 11 | impl AsFd for Card { 12 | fn as_fd(&self) -> BorrowedFd<'_> { 13 | self.0.as_fd() 14 | } 15 | } 16 | 17 | impl Card { 18 | fn open(path: &str) -> Self { 19 | let mut options = OpenOptions::new(); 20 | options.read(true); 21 | options.write(true); 22 | Card(options.open(path).unwrap()) 23 | } 24 | 25 | fn open_global() -> Self { 26 | Self::open("/dev/dri/card0") 27 | } 28 | } 29 | 30 | fn print_busid(fd: BorrowedFd<'_>) { 31 | let mut buffer = Vec::new(); 32 | let busid = ffi::get_bus_id(fd, Some(&mut buffer)); 33 | println!("{busid:#?}"); 34 | } 35 | 36 | fn print_client(fd: BorrowedFd<'_>) { 37 | let client = ffi::get_client(fd, 0); 38 | println!("{client:#?}"); 39 | } 40 | 41 | fn print_version(fd: BorrowedFd<'_>) { 42 | let mut name = Vec::new(); 43 | let mut date = Vec::new(); 44 | let mut desc = Vec::new(); 45 | 46 | let version = ffi::get_version(fd, Some(&mut name), Some(&mut date), Some(&mut desc)); 47 | 48 | println!("{version:#?}"); 49 | } 50 | 51 | fn print_capabilities(fd: BorrowedFd<'_>) { 52 | for cty in 1.. { 53 | let cap = ffi::get_capability(fd, cty); 54 | match cap { 55 | Ok(_) => println!("{cap:#?}"), 56 | Err(_) => break, 57 | } 58 | } 59 | } 60 | 61 | fn print_token(fd: BorrowedFd<'_>) { 62 | let token = ffi::auth::get_magic_token(fd); 63 | println!("{token:#?}"); 64 | } 65 | 66 | fn main() { 67 | let card = Card::open_global(); 68 | let fd = card.as_fd(); 69 | 70 | print_busid(fd); 71 | print_client(fd); 72 | print_version(fd); 73 | print_capabilities(fd); 74 | print_token(fd); 75 | //print_stats(fd); 76 | } 77 | -------------------------------------------------------------------------------- /examples/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | pub use drm::control::Device as ControlDevice; 4 | pub use drm::Device; 5 | use std::io; 6 | 7 | #[derive(Debug)] 8 | /// A simple wrapper for a device node. 9 | pub struct Card(std::fs::File); 10 | 11 | /// Implementing `AsFd` is a prerequisite to implementing the traits found 12 | /// in this crate. Here, we are just calling `as_fd()` on the inner File. 13 | impl std::os::unix::io::AsFd for Card { 14 | fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { 15 | self.0.as_fd() 16 | } 17 | } 18 | 19 | /// With `AsFd` implemented, we can now implement `drm::Device`. 20 | impl Device for Card {} 21 | impl ControlDevice for Card {} 22 | 23 | /// Simple helper methods for opening a `Card`. 24 | impl Card { 25 | pub fn open(path: &str) -> io::Result { 26 | let mut options = std::fs::OpenOptions::new(); 27 | options.read(true); 28 | options.write(true); 29 | Ok(Card(options.open(path)?)) 30 | } 31 | 32 | pub fn open_global() -> Self { 33 | Self::open("/dev/dri/card0").unwrap() 34 | } 35 | } 36 | 37 | pub mod capabilities { 38 | use drm::ClientCapability as CC; 39 | pub const CLIENT_CAP_ENUMS: &[CC] = &[CC::Stereo3D, CC::UniversalPlanes, CC::Atomic]; 40 | 41 | use drm::DriverCapability as DC; 42 | pub const DRIVER_CAP_ENUMS: &[DC] = &[ 43 | DC::DumbBuffer, 44 | DC::VBlankHighCRTC, 45 | DC::DumbPreferredDepth, 46 | DC::DumbPreferShadow, 47 | DC::Prime, 48 | DC::MonotonicTimestamp, 49 | DC::ASyncPageFlip, 50 | DC::CursorWidth, 51 | DC::CursorHeight, 52 | DC::AddFB2Modifiers, 53 | DC::PageFlipTarget, 54 | DC::CRTCInVBlankEvent, 55 | DC::SyncObj, 56 | DC::TimelineSyncObj, 57 | ]; 58 | } 59 | 60 | pub mod images { 61 | use image; 62 | 63 | pub fn load_image(name: &str) -> image::RgbaImage { 64 | let path = format!("examples/images/{name}"); 65 | image::open(path).unwrap().to_rgba8() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/control/dumbbuffer.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # DumbBuffer 3 | //! 4 | //! Memory-supported, slow, but easy & cross-platform buffer implementation 5 | //! 6 | 7 | use crate::buffer; 8 | 9 | use std::borrow::{Borrow, BorrowMut}; 10 | use std::ops::{Deref, DerefMut}; 11 | 12 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 13 | /// Slow, but generic [`buffer::Buffer`] implementation 14 | pub struct DumbBuffer { 15 | pub(crate) size: (u32, u32), 16 | pub(crate) length: usize, 17 | pub(crate) format: buffer::DrmFourcc, 18 | pub(crate) pitch: u32, 19 | pub(crate) handle: buffer::Handle, 20 | } 21 | 22 | /// Mapping of a [`DumbBuffer`] 23 | pub struct DumbMapping<'a> { 24 | pub(crate) _phantom: core::marker::PhantomData<&'a ()>, 25 | pub(crate) map: &'a mut [u8], 26 | } 27 | 28 | impl AsRef<[u8]> for DumbMapping<'_> { 29 | fn as_ref(&self) -> &[u8] { 30 | self.map 31 | } 32 | } 33 | 34 | impl AsMut<[u8]> for DumbMapping<'_> { 35 | fn as_mut(&mut self) -> &mut [u8] { 36 | self.map 37 | } 38 | } 39 | 40 | impl Borrow<[u8]> for DumbMapping<'_> { 41 | fn borrow(&self) -> &[u8] { 42 | self.map 43 | } 44 | } 45 | 46 | impl BorrowMut<[u8]> for DumbMapping<'_> { 47 | fn borrow_mut(&mut self) -> &mut [u8] { 48 | self.map 49 | } 50 | } 51 | 52 | impl Deref for DumbMapping<'_> { 53 | type Target = [u8]; 54 | 55 | fn deref(&self) -> &Self::Target { 56 | self.map 57 | } 58 | } 59 | 60 | impl DerefMut for DumbMapping<'_> { 61 | fn deref_mut(&mut self) -> &mut Self::Target { 62 | self.map 63 | } 64 | } 65 | 66 | impl Drop for DumbMapping<'_> { 67 | fn drop(&mut self) { 68 | unsafe { 69 | rustix::mm::munmap(self.map.as_mut_ptr() as *mut _, self.map.len()) 70 | .expect("Unmap failed"); 71 | } 72 | } 73 | } 74 | 75 | impl buffer::Buffer for DumbBuffer { 76 | fn size(&self) -> (u32, u32) { 77 | self.size 78 | } 79 | fn format(&self) -> buffer::DrmFourcc { 80 | self.format 81 | } 82 | fn pitch(&self) -> u32 { 83 | self.pitch 84 | } 85 | fn handle(&self) -> buffer::Handle { 86 | self.handle 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/syncobj.rs: -------------------------------------------------------------------------------- 1 | /// Check the `util` module to see how the `Card` structure is implemented. 2 | pub mod utils; 3 | 4 | use crate::utils::*; 5 | use rustix::event::PollFlags; 6 | use std::{ 7 | io, 8 | os::unix::io::{AsFd, OwnedFd}, 9 | }; 10 | 11 | impl Card { 12 | fn simulate_command_submission(&self) -> io::Result { 13 | // Create a temporary syncobj to receive the command fence. 14 | let syncobj = self.create_syncobj(false)?; 15 | 16 | let sync_file = { 17 | // Fake a command submission by signalling the syncobj immediately. The kernel 18 | // attaches a null fence object which is always signalled. Other than this, there 19 | // isn't a good way to create and signal a fence object from user-mode, so an actual 20 | // device is required to test this properly. 21 | // 22 | // For a real device, the syncobj handle should be passed to a command submission 23 | // which is expected to set a fence to be signalled upon completion. 24 | self.syncobj_signal(&[syncobj])?; 25 | 26 | // Export fence set by previous ioctl to file descriptor. 27 | self.syncobj_to_fd(syncobj, true) 28 | }; 29 | 30 | // The sync file descriptor constitutes ownership of the fence, so the syncobj can be 31 | // safely destroyed. 32 | self.destroy_syncobj(syncobj)?; 33 | 34 | sync_file 35 | } 36 | } 37 | 38 | fn main() { 39 | let card = Card::open_global(); 40 | let sync_file = card.simulate_command_submission().unwrap(); 41 | let fd = sync_file.as_fd(); 42 | 43 | // Poll for readability. The DRM fence object will directly wake the thread when signalled. 44 | // 45 | // Alternatively, Tokio's AsyncFd may be used like so: 46 | // 47 | // use tokio::io::{Interest, unix::AsyncFd}; 48 | // let afd = AsyncFd::with_interest(sync_file, Interest::READABLE).unwrap(); 49 | // let future = async move { afd.readable().await.unwrap().retain_ready() }; 50 | // future.await; 51 | let mut poll_fds = [rustix::event::PollFd::new(&fd, PollFlags::IN)]; 52 | rustix::event::poll(&mut poll_fds, None).unwrap(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/legacy_modeset.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | use crate::utils::*; 3 | 4 | use drm::control::Device as ControlDevice; 5 | 6 | use drm::buffer::DrmFourcc; 7 | 8 | use drm::control::{connector, crtc}; 9 | 10 | pub fn main() { 11 | let card = Card::open_global(); 12 | 13 | // Load the information. 14 | let res = card 15 | .resource_handles() 16 | .expect("Could not load normal resource ids."); 17 | let coninfo: Vec = res 18 | .connectors() 19 | .iter() 20 | .flat_map(|con| card.get_connector(*con, true)) 21 | .collect(); 22 | let crtcinfo: Vec = res 23 | .crtcs() 24 | .iter() 25 | .flat_map(|crtc| card.get_crtc(*crtc)) 26 | .collect(); 27 | 28 | // Filter each connector until we find one that's connected. 29 | let con = coninfo 30 | .iter() 31 | .find(|&i| i.state() == connector::State::Connected) 32 | .expect("No connected connectors"); 33 | 34 | // Get the first (usually best) mode 35 | let &mode = con.modes().first().expect("No modes found on connector"); 36 | 37 | let (disp_width, disp_height) = mode.size(); 38 | 39 | // Find a crtc and FB 40 | let crtc = crtcinfo.first().expect("No crtcs found"); 41 | 42 | // Select the pixel format 43 | let fmt = DrmFourcc::Xrgb8888; 44 | 45 | // Create a DB 46 | // If buffer resolution is larger than display resolution, an ENOSPC (not enough video memory) 47 | // error may occur 48 | let mut db = card 49 | .create_dumb_buffer((disp_width.into(), disp_height.into()), fmt, 32) 50 | .expect("Could not create dumb buffer"); 51 | 52 | // Map it and grey it out. 53 | { 54 | let mut map = card 55 | .map_dumb_buffer(&mut db) 56 | .expect("Could not map dumbbuffer"); 57 | for b in map.as_mut() { 58 | *b = 128; 59 | } 60 | } 61 | 62 | // Create an FB: 63 | let fb = card 64 | .add_framebuffer(&db, 24, 32) 65 | .expect("Could not create FB"); 66 | 67 | println!("{mode:#?}"); 68 | println!("{fb:#?}"); 69 | println!("{db:#?}"); 70 | 71 | // Set the crtc 72 | // On many setups, this requires root access. 73 | card.set_crtc(crtc.handle(), Some(fb), (0, 0), &[con.handle()], Some(mode)) 74 | .expect("Could not set CRTC"); 75 | 76 | let five_seconds = ::std::time::Duration::from_millis(5000); 77 | ::std::thread::sleep(five_seconds); 78 | 79 | card.destroy_framebuffer(fb).unwrap(); 80 | card.destroy_dumb_buffer(db).unwrap(); 81 | } 82 | -------------------------------------------------------------------------------- /src/control/atomic.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for atomic modesetting. 2 | 3 | use crate::control; 4 | 5 | /// Helper struct to construct atomic commit requests 6 | #[derive(Debug, Clone, Default)] 7 | pub struct AtomicModeReq { 8 | pub(super) objects: Vec, 9 | pub(super) count_props_per_object: Vec, 10 | pub(super) props: Vec, 11 | pub(super) values: Vec, 12 | } 13 | 14 | impl AtomicModeReq { 15 | /// Create a new and empty atomic commit request 16 | pub fn new() -> AtomicModeReq { 17 | Self::default() 18 | } 19 | 20 | /// Add a property and value pair for a given raw resource to the request 21 | pub fn add_raw_property( 22 | &mut self, 23 | obj_id: control::RawResourceHandle, 24 | prop_id: control::property::Handle, 25 | value: control::property::RawValue, 26 | ) { 27 | // add object if missing (also to count_props_per_object) 28 | let (idx, prop_count) = match self.objects.binary_search(&obj_id) { 29 | Ok(idx) => (idx, self.count_props_per_object[idx]), 30 | Err(new_idx) => { 31 | self.objects.insert(new_idx, obj_id); 32 | self.count_props_per_object.insert(new_idx, 0); 33 | (new_idx, 0) 34 | } 35 | }; 36 | 37 | // get start of our objects props 38 | let prop_slice_start = self.count_props_per_object.iter().take(idx).sum::() as usize; 39 | // get end 40 | let prop_slice_end = prop_slice_start + prop_count as usize; 41 | 42 | // search for existing prop entry 43 | match self.props[prop_slice_start..prop_slice_end] 44 | .binary_search_by_key(&Into::::into(prop_id), |x| (*x).into()) 45 | { 46 | // prop exists, override 47 | Ok(prop_idx) => { 48 | self.values[prop_slice_start + prop_idx] = value; 49 | } 50 | Err(prop_idx) => { 51 | // increase prop count 52 | self.count_props_per_object[idx] += 1; 53 | // insert prop, insert value 54 | self.props.insert(prop_slice_start + prop_idx, prop_id); 55 | self.values.insert(prop_slice_start + prop_idx, value); 56 | } 57 | } 58 | } 59 | 60 | /// Add a property and value pair for a given handle to the request 61 | pub fn add_property( 62 | &mut self, 63 | handle: H, 64 | property: control::property::Handle, 65 | value: control::property::Value, 66 | ) where 67 | H: control::ResourceHandle, 68 | { 69 | self.add_raw_property(handle.into(), property, value.into()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/resources.rs: -------------------------------------------------------------------------------- 1 | /// Check the `util` module to see how the `Card` structure is implemented. 2 | pub mod utils; 3 | use crate::utils::*; 4 | 5 | pub fn main() { 6 | let card = Card::open_global(); 7 | 8 | // Enable all possible client capabilities 9 | for &cap in capabilities::CLIENT_CAP_ENUMS { 10 | if let Err(e) = card.set_client_capability(cap, true) { 11 | eprintln!("Unable to activate capability {cap:?}: {e}"); 12 | return; 13 | } 14 | } 15 | 16 | let resources = card.resource_handles().unwrap(); 17 | let plane_res = card.plane_handles().unwrap(); 18 | 19 | // Print out all card resource handles 20 | println!("Connectors:\t{:?}", resources.connectors()); 21 | println!("Encoders:\t{:?}", resources.encoders()); 22 | println!("CRTCs:\t\t{:?}", resources.crtcs()); 23 | println!("Framebuffers:\t{:?}", resources.framebuffers()); 24 | println!("Planes:\t\t{plane_res:?}"); 25 | 26 | for &handle in resources.connectors() { 27 | let info = card.get_connector(handle, false).unwrap(); 28 | println!("Connector: {handle:?}"); 29 | println!("\t{:?}-{}", info.interface(), info.interface_id()); 30 | println!("\t{:?}", info.state()); 31 | println!("\t{:?}", info.size()); 32 | println!("\t{:?}", info.encoders()); 33 | println!("\t{:?}", info.current_encoder()); 34 | 35 | for mode in card.get_modes(handle).unwrap() { 36 | println!("{mode:?}"); 37 | } 38 | } 39 | println!("\n"); 40 | 41 | for &handle in resources.encoders() { 42 | let info = card.get_encoder(handle).unwrap(); 43 | println!("Encoder: {handle:?}"); 44 | println!("\t{:?}", info.kind()); 45 | println!("\t{:?}", info.crtc()); 46 | } 47 | println!("\n"); 48 | 49 | for &handle in resources.crtcs() { 50 | let info = card.get_crtc(handle).unwrap(); 51 | println!("CRTC: {handle:?}"); 52 | println!("\tPosition: {:?}", info.position()); 53 | println!("\tMode: {:?}", info.mode()); 54 | println!("\tFramebuffer: {:?}", info.framebuffer()); 55 | println!("\tGamma Length: {:?}", info.gamma_length()); 56 | } 57 | println!("\n"); 58 | 59 | for &handle in resources.framebuffers() { 60 | let info = card.get_framebuffer(handle).unwrap(); 61 | println!("Framebuffer: {handle:?}"); 62 | println!("\tSize: {:?}", info.size()); 63 | println!("\tPitch: {:?}", info.pitch()); 64 | println!("\tBPP: {:?}", info.bpp()); 65 | println!("\tDepth: {:?}", info.depth()); 66 | } 67 | 68 | println!("\n"); 69 | 70 | for handle in plane_res { 71 | let info = card.get_plane(handle).unwrap(); 72 | println!("Plane: {handle:?}"); 73 | println!("\tCRTC: {:?}", info.crtc()); 74 | println!("\tFramebuffer: {:?}", info.framebuffer()); 75 | println!("\tFormats: {:?}", info.formats()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/control/crtc.rs: -------------------------------------------------------------------------------- 1 | //! # CRTC 2 | //! 3 | //! A CRTC is a display controller provided by your device. It's primary job is 4 | //! to take pixel data and send it to a connector with the proper resolution and 5 | //! frequencies. 6 | //! 7 | //! Specific CRTCs can only be attached to connectors that have an encoder it 8 | //! supports. For example, you can have a CRTC that can not output to analog 9 | //! connectors. These are built in hardware limitations. 10 | //! 11 | //! Each CRTC has a built in plane, which can have a framebuffer attached to it, 12 | //! but they can also use pixel data from other planes to perform hardware 13 | //! compositing. 14 | 15 | use crate::control; 16 | use drm_ffi as ffi; 17 | 18 | /// A handle to a specific CRTC 19 | #[repr(transparent)] 20 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 21 | pub struct Handle(control::RawResourceHandle); 22 | 23 | // Safety: Handle is repr(transparent) over NonZeroU32 24 | unsafe impl bytemuck::ZeroableInOption for Handle {} 25 | unsafe impl bytemuck::PodInOption for Handle {} 26 | 27 | impl From for control::RawResourceHandle { 28 | fn from(handle: Handle) -> Self { 29 | handle.0 30 | } 31 | } 32 | 33 | impl From for u32 { 34 | fn from(handle: Handle) -> Self { 35 | handle.0.into() 36 | } 37 | } 38 | 39 | impl From for Handle { 40 | fn from(handle: control::RawResourceHandle) -> Self { 41 | Handle(handle) 42 | } 43 | } 44 | 45 | impl control::ResourceHandle for Handle { 46 | const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_CRTC; 47 | } 48 | 49 | impl std::fmt::Debug for Handle { 50 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 51 | f.debug_tuple("crtc::Handle").field(&self.0).finish() 52 | } 53 | } 54 | 55 | /// Information about a specific CRTC 56 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 57 | pub struct Info { 58 | pub(crate) handle: Handle, 59 | pub(crate) position: (u32, u32), 60 | pub(crate) mode: Option, 61 | pub(crate) fb: Option, 62 | pub(crate) gamma_length: u32, 63 | } 64 | 65 | impl std::fmt::Display for Info { 66 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 67 | write!(f, "CRTC {}", self.handle.0) 68 | } 69 | } 70 | 71 | impl Info { 72 | /// Returns the handle to this CRTC. 73 | pub fn handle(&self) -> Handle { 74 | self.handle 75 | } 76 | 77 | /// Returns the position of the CRTC. 78 | pub fn position(&self) -> (u32, u32) { 79 | self.position 80 | } 81 | 82 | /// Returns the current mode of the CRTC. 83 | pub fn mode(&self) -> Option { 84 | self.mode 85 | } 86 | 87 | /// Returns the framebuffer currently attached to this CRTC. 88 | pub fn framebuffer(&self) -> Option { 89 | self.fb 90 | } 91 | 92 | /// Returns the size of the gamma LUT. 93 | pub fn gamma_length(&self) -> u32 { 94 | self.gamma_length 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drm-rs 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/drm.svg)](https://crates.io/crates/drm) 4 | [![docs.rs](https://docs.rs/drm/badge.svg)](https://docs.rs/drm) 5 | [![Build Status](https://github.com/Smithay/drm-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/Smithay/drm-rs/actions/workflows/ci.yml) 6 | 7 | A safe interface to the Direct Rendering Manager. 8 | 9 | ## Direct Rendering Manager 10 | 11 | The Direct Rendering Manager is a subsystem found on multiple Unix-based 12 | operating systems that provides a userspace API to graphics hardware. 13 | See the [Wikipedia article](https://en.wikipedia.org/wiki/Direct_Rendering_Manager) 14 | for more details. 15 | 16 | ## Usage 17 | 18 | ### Basic 19 | 20 | The DRM is accessed using [ioctls](https://en.wikipedia.org/wiki/Ioctl) 21 | on a file representing a graphics card. These can normally be 22 | found in `/dev/dri`, but can also be opened in other ways (ex. udev). 23 | 24 | This crate does not provide a method of opening these files. Instead, the 25 | user program must provide a way to access the file descriptor representing the 26 | device through the [AsFd](https://doc.rust-lang.org/std/os/fd/trait.AsFd.html) 27 | trait. Here is a basic example using `File` as a backend: 28 | 29 | ```rust 30 | /// A simple wrapper for a device node. 31 | pub struct Card(std::fs::File); 32 | 33 | /// Implementing [`AsFd`] is a prerequisite to implementing the traits found 34 | /// in this crate. Here, we are just calling [`File::as_fd()`] on the inner 35 | /// [`File`]. 36 | impl AsFd for Card { 37 | fn as_fd(&self) -> BorrowedFd<'_> { 38 | self.0.as_fd() 39 | } 40 | } 41 | 42 | /// Simple helper methods for opening a `Card`. 43 | impl Card { 44 | pub fn open(path: &str) -> Self { 45 | let mut options = std::fs::OpenOptions::new(); 46 | options.read(true); 47 | options.write(true); 48 | Card(options.open(path).unwrap()) 49 | } 50 | } 51 | ``` 52 | 53 | Finally, you can implement `drm::Device` to gain access to the basic DRM 54 | functionality: 55 | 56 | ```rust 57 | impl drm::Device for Card {} 58 | 59 | fn main() { 60 | let gpu = Card::open("/dev/dri/card0"); 61 | println!("{:#?}", gpu.get_driver().unwrap()); 62 | } 63 | ``` 64 | 65 | ### Control (modesetting) 66 | 67 | See [`drm::control::Device`](https://docs.rs/drm/*/drm/control/trait.Device.html) 68 | as well as our mode-setting examples: [`atomic_modeset`](https://github.com/Smithay/drm-rs/blob/develop/examples/atomic_modeset.rs) 69 | and [`legacy_modeset`](https://github.com/Smithay/drm-rs/blob/develop/examples/legacy_modeset.rs) 70 | 71 | ### Rendering 72 | 73 | Rendering is done by [creating](https://docs.rs/drm/*/drm/control/trait.Device.html#method.add_framebuffer) and 74 | [attaching](https://docs.rs/drm/*/drm/control/trait.Device.html#method.page_flip) [framebuffers](https://docs.rs/drm/*/drm/control/framebuffer/index.html) 75 | to [crtcs](https://docs.rs/drm/*/drm/control/crtc/index.html). 76 | 77 | A framebuffer is created from anything implementing [`Buffer`](https://docs.rs/drm/*/drm/buffer/trait.Buffer.html) like the always 78 | available, but very limited, [`DumbBuffer`](https://docs.rs/drm/*/drm/control/dumbbuffer/struct.DumbBuffer.html). 79 | 80 | For faster hardware-backed buffers, checkout [gbm.rs](https://github.com/Smithay/gbm.rs). 81 | -------------------------------------------------------------------------------- /src/control/plane.rs: -------------------------------------------------------------------------------- 1 | //! # Plane 2 | //! 3 | //! Attachment point for a Framebuffer. 4 | //! 5 | //! A Plane is a resource that can have a framebuffer attached to it, either for 6 | //! hardware compositing or displaying directly to a screen. There are three 7 | //! types of planes available for use: 8 | //! 9 | //! * Primary - A CRTC's built-in plane. When attaching a framebuffer to a CRTC, 10 | //! it is actually being attached to this kind of plane. 11 | //! 12 | //! * Overlay - Can be overlaid on top of a primary plane, utilizing extremely 13 | //! fast hardware compositing. 14 | //! 15 | //! * Cursor - Similar to an overlay plane, these are typically used to display 16 | //! cursor type objects. 17 | 18 | use crate::control; 19 | use drm_ffi as ffi; 20 | 21 | /// A handle to a plane 22 | #[repr(transparent)] 23 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 24 | pub struct Handle(control::RawResourceHandle); 25 | 26 | // Safety: Handle is repr(transparent) over NonZeroU32 27 | unsafe impl bytemuck::ZeroableInOption for Handle {} 28 | unsafe impl bytemuck::PodInOption for Handle {} 29 | 30 | impl From for control::RawResourceHandle { 31 | fn from(handle: Handle) -> Self { 32 | handle.0 33 | } 34 | } 35 | 36 | impl From for u32 { 37 | fn from(handle: Handle) -> Self { 38 | handle.0.into() 39 | } 40 | } 41 | 42 | impl From for Handle { 43 | fn from(handle: control::RawResourceHandle) -> Self { 44 | Handle(handle) 45 | } 46 | } 47 | 48 | impl control::ResourceHandle for Handle { 49 | const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_PLANE; 50 | } 51 | 52 | impl std::fmt::Debug for Handle { 53 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 54 | f.debug_tuple("plane::Handle").field(&self.0).finish() 55 | } 56 | } 57 | 58 | /// Information about a plane 59 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 60 | pub struct Info { 61 | pub(crate) handle: Handle, 62 | pub(crate) crtc: Option, 63 | pub(crate) fb: Option, 64 | pub(crate) pos_crtcs: u32, 65 | pub(crate) formats: Vec, 66 | } 67 | 68 | impl std::fmt::Display for Info { 69 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 | write!(f, "Plane {}", self.handle.0) 71 | } 72 | } 73 | 74 | impl Info { 75 | /// Returns the handle to this plane. 76 | pub fn handle(&self) -> Handle { 77 | self.handle 78 | } 79 | 80 | /// Returns the CRTC this plane is attached to. 81 | pub fn crtc(&self) -> Option { 82 | self.crtc 83 | } 84 | 85 | /// Returns a filter for supported crtcs of this plane. 86 | /// 87 | /// Use with [`control::ResourceHandles::filter_crtcs`] 88 | /// to receive a list of crtcs. 89 | pub fn possible_crtcs(&self) -> control::CrtcListFilter { 90 | control::CrtcListFilter(self.pos_crtcs) 91 | } 92 | 93 | /// Returns the framebuffer this plane is attached to. 94 | pub fn framebuffer(&self) -> Option { 95 | self.fb 96 | } 97 | 98 | /// Returns the formats this plane supports. 99 | pub fn formats(&self) -> &[u32] { 100 | &self.formats 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/control/framebuffer.rs: -------------------------------------------------------------------------------- 1 | //! # Framebuffer 2 | //! 3 | //! Process specific GPU buffers that can be attached to a plane. 4 | 5 | use crate::buffer; 6 | use crate::control; 7 | use drm_ffi as ffi; 8 | use drm_fourcc::{DrmFourcc, DrmModifier}; 9 | 10 | /// A handle to a framebuffer 11 | #[repr(transparent)] 12 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 13 | pub struct Handle(control::RawResourceHandle); 14 | 15 | // Safety: Handle is repr(transparent) over NonZeroU32 16 | unsafe impl bytemuck::ZeroableInOption for Handle {} 17 | unsafe impl bytemuck::PodInOption for Handle {} 18 | 19 | impl From for control::RawResourceHandle { 20 | fn from(handle: Handle) -> Self { 21 | handle.0 22 | } 23 | } 24 | 25 | impl From for u32 { 26 | fn from(handle: Handle) -> Self { 27 | handle.0.into() 28 | } 29 | } 30 | 31 | impl From for Handle { 32 | fn from(handle: control::RawResourceHandle) -> Self { 33 | Handle(handle) 34 | } 35 | } 36 | 37 | impl control::ResourceHandle for Handle { 38 | const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_FB; 39 | } 40 | 41 | impl std::fmt::Debug for Handle { 42 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 43 | f.debug_tuple("framebuffer::Handle").field(&self.0).finish() 44 | } 45 | } 46 | 47 | /// Information about a framebuffer 48 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 49 | pub struct Info { 50 | pub(crate) handle: Handle, 51 | pub(crate) size: (u32, u32), 52 | pub(crate) pitch: u32, 53 | pub(crate) bpp: u32, 54 | pub(crate) depth: u32, 55 | pub(crate) buffer: Option, 56 | } 57 | 58 | impl std::fmt::Display for Info { 59 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 60 | write!(f, "Framebuffer {}", self.handle.0) 61 | } 62 | } 63 | 64 | impl Info { 65 | /// Returns the handle to this framebuffer. 66 | pub fn handle(&self) -> Handle { 67 | self.handle 68 | } 69 | 70 | /// Returns the size of this framebuffer. 71 | pub fn size(&self) -> (u32, u32) { 72 | self.size 73 | } 74 | 75 | /// Returns the pitch of this framebuffer. 76 | pub fn pitch(&self) -> u32 { 77 | self.pitch 78 | } 79 | 80 | /// Returns the bits-per-pixel of this framebuffer. 81 | pub fn bpp(&self) -> u32 { 82 | self.bpp 83 | } 84 | 85 | /// Returns the depth of this framebuffer. 86 | pub fn depth(&self) -> u32 { 87 | self.depth 88 | } 89 | 90 | /// Returns the buffer handle of this framebuffer. 91 | pub fn buffer(&self) -> Option { 92 | self.buffer 93 | } 94 | } 95 | 96 | /// Information about a framebuffer (with modifiers) 97 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 98 | pub struct PlanarInfo { 99 | pub(crate) handle: Handle, 100 | pub(crate) size: (u32, u32), 101 | pub(crate) pixel_format: DrmFourcc, 102 | pub(crate) flags: control::FbCmd2Flags, 103 | pub(crate) buffers: [Option; 4], 104 | pub(crate) pitches: [u32; 4], 105 | pub(crate) offsets: [u32; 4], 106 | pub(crate) modifier: Option, 107 | } 108 | 109 | impl PlanarInfo { 110 | /// Returns the handle to this framebuffer. 111 | pub fn handle(&self) -> Handle { 112 | self.handle 113 | } 114 | 115 | /// Returns the size of this framebuffer. 116 | pub fn size(&self) -> (u32, u32) { 117 | self.size 118 | } 119 | 120 | /// Returns the pixel format of this framebuffer. 121 | pub fn pixel_format(&self) -> DrmFourcc { 122 | self.pixel_format 123 | } 124 | 125 | /// Returns the flags of this framebuffer. 126 | pub fn flags(&self) -> control::FbCmd2Flags { 127 | self.flags 128 | } 129 | 130 | /// Returns the buffer handles of this framebuffer. 131 | pub fn buffers(&self) -> [Option; 4] { 132 | self.buffers 133 | } 134 | 135 | /// Returns the pitches of this framebuffer. 136 | pub fn pitches(&self) -> [u32; 4] { 137 | self.pitches 138 | } 139 | 140 | /// Returns the offsets of this framebuffer. 141 | pub fn offsets(&self) -> [u32; 4] { 142 | self.offsets 143 | } 144 | 145 | /// Returns the modifier of this framebuffer. 146 | pub fn modifier(&self) -> Option { 147 | self.modifier 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/control/encoder.rs: -------------------------------------------------------------------------------- 1 | //! # Encoder 2 | //! 3 | //! An encoder is a bridge between a CRTC and a connector that takes the pixel 4 | //! data of the CRTC and encodes it into a format the connector understands. 5 | 6 | use crate::control; 7 | use drm_ffi as ffi; 8 | 9 | /// A handle to an encoder 10 | #[repr(transparent)] 11 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 12 | pub struct Handle(control::RawResourceHandle); 13 | 14 | // Safety: Handle is repr(transparent) over NonZeroU32 15 | unsafe impl bytemuck::ZeroableInOption for Handle {} 16 | unsafe impl bytemuck::PodInOption for Handle {} 17 | 18 | impl From for control::RawResourceHandle { 19 | fn from(handle: Handle) -> Self { 20 | handle.0 21 | } 22 | } 23 | 24 | impl From for u32 { 25 | fn from(handle: Handle) -> Self { 26 | handle.0.into() 27 | } 28 | } 29 | 30 | impl From for Handle { 31 | fn from(handle: control::RawResourceHandle) -> Self { 32 | Handle(handle) 33 | } 34 | } 35 | 36 | impl control::ResourceHandle for Handle { 37 | const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_ENCODER; 38 | } 39 | 40 | impl std::fmt::Debug for Handle { 41 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 42 | f.debug_tuple("encoder::Handle").field(&self.0).finish() 43 | } 44 | } 45 | 46 | /// Information about an encoder 47 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 48 | pub struct Info { 49 | pub(crate) handle: Handle, 50 | pub(crate) enc_type: Kind, 51 | pub(crate) crtc: Option, 52 | pub(crate) pos_crtcs: u32, 53 | pub(crate) pos_clones: u32, 54 | } 55 | 56 | impl std::fmt::Display for Info { 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 58 | write!(f, "Encoder {}", self.handle.0) 59 | } 60 | } 61 | 62 | impl Info { 63 | /// Returns the handle to this encoder. 64 | pub fn handle(&self) -> Handle { 65 | self.handle 66 | } 67 | 68 | /// Returns the `Kind` of encoder this is. 69 | pub fn kind(&self) -> Kind { 70 | self.enc_type 71 | } 72 | 73 | /// Returns a handle to the CRTC this encoder is attached to. 74 | pub fn crtc(&self) -> Option { 75 | self.crtc 76 | } 77 | 78 | /// Returns a filter for the possible CRTCs that can use this encoder. 79 | /// 80 | /// Use with [`control::ResourceHandles::filter_crtcs`] 81 | /// to receive a list of crtcs. 82 | pub fn possible_crtcs(&self) -> control::CrtcListFilter { 83 | control::CrtcListFilter(self.pos_crtcs) 84 | } 85 | 86 | /// Returns a filter for the possible encoders that clones this one. 87 | pub fn possible_clones(&self) { 88 | unimplemented!() 89 | } 90 | } 91 | 92 | /// The type of encoder. 93 | #[allow(missing_docs)] 94 | #[allow(clippy::upper_case_acronyms)] 95 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 96 | pub enum Kind { 97 | None, 98 | DAC, 99 | TMDS, 100 | LVDS, 101 | TVDAC, 102 | Virtual, 103 | DSI, 104 | DPMST, 105 | DPI, 106 | } 107 | 108 | impl From for Kind { 109 | fn from(n: u32) -> Self { 110 | match n { 111 | ffi::DRM_MODE_ENCODER_NONE => Kind::None, 112 | ffi::DRM_MODE_ENCODER_DAC => Kind::DAC, 113 | ffi::DRM_MODE_ENCODER_TMDS => Kind::TMDS, 114 | ffi::DRM_MODE_ENCODER_LVDS => Kind::LVDS, 115 | ffi::DRM_MODE_ENCODER_TVDAC => Kind::TVDAC, 116 | ffi::DRM_MODE_ENCODER_VIRTUAL => Kind::Virtual, 117 | ffi::DRM_MODE_ENCODER_DSI => Kind::DSI, 118 | ffi::DRM_MODE_ENCODER_DPMST => Kind::DPMST, 119 | ffi::DRM_MODE_ENCODER_DPI => Kind::DPI, 120 | _ => Kind::None, 121 | } 122 | } 123 | } 124 | 125 | impl From for u32 { 126 | fn from(kind: Kind) -> Self { 127 | match kind { 128 | Kind::None => ffi::DRM_MODE_ENCODER_NONE, 129 | Kind::DAC => ffi::DRM_MODE_ENCODER_DAC, 130 | Kind::TMDS => ffi::DRM_MODE_ENCODER_TMDS, 131 | Kind::LVDS => ffi::DRM_MODE_ENCODER_LVDS, 132 | Kind::TVDAC => ffi::DRM_MODE_ENCODER_TVDAC, 133 | Kind::Virtual => ffi::DRM_MODE_ENCODER_VIRTUAL, 134 | Kind::DSI => ffi::DRM_MODE_ENCODER_DSI, 135 | Kind::DPMST => ffi::DRM_MODE_ENCODER_DPMST, 136 | Kind::DPI => ffi::DRM_MODE_ENCODER_DPI, 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/buffer/mod.rs: -------------------------------------------------------------------------------- 1 | //! Memory management and buffer functionality that the DRM subsystem exposes. 2 | //! 3 | //! # Summary 4 | //! 5 | //! The DRM subsystem exposes functionality for managing memory on modern GPU 6 | //! devices using a system called the Graphics Execution Manager (GEM). This 7 | //! system manages GPU buffers and exposes them to userspace using 32-bit 8 | //! handles. These handles are automatically reference counted in the kernel. 9 | //! 10 | //! GEM provides a small API for sharing buffers between processes. However, it 11 | //! does not provide any generic API for creating these. Instead, each driver 12 | //! provides its own method of creating these buffers. The `libgbm` library 13 | //! (part of the mesa project) provides a driver agnostic method of creating 14 | //! these buffers. 15 | //! 16 | //! There are two methods of sharing a GEM handle between processes: 17 | //! 18 | //! 1. Using `Flink` to globally publish a handle using a 32-bit 'name'. This 19 | //! requires either holding the DRM Master lock or having the process' 20 | //! [`AuthToken`](struct@crate::AuthToken) authenticated. However, any process can 21 | //! open these handles if they know (or even guess) the global name. 22 | //! 23 | //! 2. Converting the GEM handle into a PRIME file descriptor, and passing it 24 | //! like a regular one. This allows better control and security, and is the 25 | //! recommended method of sharing buffers. 26 | 27 | use crate::control; 28 | pub use drm_fourcc::{DrmFourcc, DrmModifier, DrmVendor, UnrecognizedFourcc, UnrecognizedVendor}; 29 | 30 | /// A handle to a GEM buffer 31 | /// 32 | /// # Notes 33 | /// 34 | /// There are no guarantees that this handle is valid. It is up to the user 35 | /// to make sure this handle does not outlive the underlying buffer, and to 36 | /// prevent buffers from leaking by properly closing them after they are done. 37 | #[repr(transparent)] 38 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 39 | pub struct Handle(control::RawResourceHandle); 40 | 41 | // Safety: Handle is repr(transparent) over NonZeroU32 42 | unsafe impl bytemuck::ZeroableInOption for Handle {} 43 | unsafe impl bytemuck::PodInOption for Handle {} 44 | 45 | impl From for control::RawResourceHandle { 46 | fn from(handle: Handle) -> Self { 47 | handle.0 48 | } 49 | } 50 | 51 | impl From for u32 { 52 | fn from(handle: Handle) -> Self { 53 | handle.0.into() 54 | } 55 | } 56 | 57 | impl From for Handle { 58 | fn from(handle: control::RawResourceHandle) -> Self { 59 | Handle(handle) 60 | } 61 | } 62 | 63 | impl std::fmt::Debug for Handle { 64 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 65 | f.debug_tuple("buffer::Handle").field(&self.0).finish() 66 | } 67 | } 68 | 69 | /// The name of a GEM buffer. 70 | /// 71 | /// # Notes 72 | /// 73 | /// There are no guarantees that this name is valid. It is up to the user 74 | /// to make sure this name does not outlive the underlying buffer, and to 75 | /// prevent buffers from leaking by properly closing them after they are done. 76 | #[repr(transparent)] 77 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 78 | pub struct Name(u32); 79 | 80 | impl From for u32 { 81 | fn from(name: Name) -> u32 { 82 | name.0 83 | } 84 | } 85 | 86 | impl std::fmt::Debug for Name { 87 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 88 | f.debug_tuple("buffer::Name").field(&self.0).finish() 89 | } 90 | } 91 | 92 | /// Common functionality of all regular buffers. 93 | pub trait Buffer { 94 | /// The width and height of the buffer. 95 | fn size(&self) -> (u32, u32); 96 | /// The format of the buffer. 97 | fn format(&self) -> DrmFourcc; 98 | /// The pitch of the buffer. 99 | fn pitch(&self) -> u32; 100 | /// The handle to the buffer. 101 | fn handle(&self) -> Handle; 102 | } 103 | 104 | /// Planar buffers are buffers where each channel/plane is in its own buffer. 105 | /// 106 | /// Each plane has their own handle, pitch, and offsets. 107 | pub trait PlanarBuffer { 108 | /// The width and height of the buffer. 109 | fn size(&self) -> (u32, u32); 110 | /// The format of the buffer. 111 | fn format(&self) -> DrmFourcc; 112 | /// The modifier of the buffer. 113 | fn modifier(&self) -> Option; 114 | /// The pitches of the buffer. 115 | fn pitches(&self) -> [u32; 4]; 116 | /// The handles to the buffer. 117 | fn handles(&self) -> [Option; 4]; 118 | /// The offsets of the buffer. 119 | fn offsets(&self) -> [u32; 4]; 120 | } 121 | -------------------------------------------------------------------------------- /examples/planes.rs: -------------------------------------------------------------------------------- 1 | use drm::control::property::Value; 2 | use std::{collections::HashMap, fs, io}; 3 | 4 | pub mod utils; 5 | use crate::utils::*; 6 | 7 | fn convert_value((type_, value): &(drm::control::property::ValueType, u64)) -> Value<'_> { 8 | type_.convert_value(*value) 9 | } 10 | 11 | // 16.16 fixed point 12 | fn convert_value_fixed((type_, value): &(drm::control::property::ValueType, u64)) -> Option { 13 | let val = type_.convert_value(*value).as_unsigned_range()? as u32; 14 | Some(val as f64 / 0xffff as f64) 15 | } 16 | 17 | fn display_plane( 18 | card: &Card, 19 | plane_handle: drm::control::plane::Handle, 20 | plane: drm::control::plane::Info, 21 | ) -> io::Result<()> { 22 | let props = card.get_properties(plane_handle)?; 23 | let mut prop_map = HashMap::new(); 24 | for (handle, value) in &props { 25 | let info = card.get_property(*handle)?; 26 | let name = info.name().to_str().unwrap().to_owned(); 27 | prop_map.insert(name, (info.value_type().clone(), *value)); 28 | } 29 | let Value::Enum(Some(type_)) = convert_value(&prop_map["type"]) else { 30 | panic!("failed to convert plane type enum"); 31 | }; 32 | println!(" Type: {}", type_.name().to_str().unwrap()); 33 | println!( 34 | " CRTC_XYWH: ({}, {}) {}x{}", 35 | convert_value(&prop_map["CRTC_X"]) 36 | .as_signed_range() 37 | .unwrap(), 38 | convert_value(&prop_map["CRTC_Y"]) 39 | .as_signed_range() 40 | .unwrap(), 41 | convert_value(&prop_map["CRTC_W"]) 42 | .as_unsigned_range() 43 | .unwrap(), 44 | convert_value(&prop_map["CRTC_H"]) 45 | .as_unsigned_range() 46 | .unwrap() 47 | ); 48 | println!( 49 | " SRC_XYWH: ({:.1}, {:.1}) {:.1}x{:.1}", 50 | convert_value_fixed(&prop_map["SRC_X"]).unwrap(), 51 | convert_value_fixed(&prop_map["SRC_Y"]).unwrap(), 52 | convert_value_fixed(&prop_map["SRC_W"]).unwrap(), 53 | convert_value_fixed(&prop_map["SRC_H"]).unwrap() 54 | ); 55 | if let Some(framebuffer_handle) = plane.framebuffer() { 56 | println!(" {:?}", framebuffer_handle); 57 | let framebuffer = card.get_planar_framebuffer(framebuffer_handle).unwrap(); 58 | println!(" Format: {:?}", framebuffer.pixel_format()); 59 | if let Some(modifier) = framebuffer.modifier() { 60 | println!(" Modifier: {:?}", modifier); 61 | } 62 | println!( 63 | " Size: {}x{}", 64 | framebuffer.size().0, 65 | framebuffer.size().1 66 | ); 67 | 68 | println!(" Planes"); 69 | for i in 0..4 { 70 | println!( 71 | " Plane {} (offset: {}, pitch: {})", 72 | i, 73 | framebuffer.offsets()[i], 74 | framebuffer.pitches()[i] 75 | ); 76 | } 77 | } 78 | Ok(()) 79 | } 80 | 81 | fn display_card(card: &Card) -> io::Result<()> { 82 | for &cap in capabilities::CLIENT_CAP_ENUMS { 83 | card.set_client_capability(cap, true)?; 84 | } 85 | 86 | let resources = card.resource_handles()?; 87 | 88 | let mut planes = Vec::new(); 89 | for connector_handle in resources.connectors() { 90 | let connector = card.get_connector(*connector_handle, false)?; 91 | if connector.state() != drm::control::connector::State::Connected { 92 | continue; 93 | } 94 | 95 | println!(" {:?}", connector_handle); 96 | println!( 97 | " {:?}{}", 98 | connector.interface(), 99 | connector.interface_id() 100 | ); 101 | if let Some(encoder_handle) = connector.current_encoder() { 102 | let encoder = card.get_encoder(encoder_handle)?; 103 | println!(" {:?}", encoder_handle); 104 | println!(" Kind: {:?}", encoder.kind()); 105 | if let Some(crtc_handle) = encoder.crtc() { 106 | println!(" {:?}", crtc_handle); 107 | let crtc = card.get_crtc(crtc_handle)?; 108 | if let Some(mode) = crtc.mode() { 109 | println!(" {:?}", mode); 110 | } 111 | for plane_handle in card.plane_handles()? { 112 | let plane = card.get_plane(plane_handle)?; 113 | if plane.crtc() != Some(crtc_handle) { 114 | continue; 115 | } 116 | planes.push(plane_handle); 117 | println!(" {:?}", plane_handle); 118 | display_plane(card, plane_handle, plane)?; 119 | } 120 | } 121 | } 122 | } 123 | 124 | println!("Planes not associated with connector"); 125 | for plane_handle in card.plane_handles()? { 126 | if !planes.contains(&plane_handle) { 127 | let plane = card.get_plane(plane_handle)?; 128 | println!(" {:?}", plane_handle); 129 | display_plane(card, plane_handle, plane)?; 130 | } 131 | } 132 | 133 | Ok(()) 134 | } 135 | 136 | fn main() -> io::Result<()> { 137 | for i in fs::read_dir("/dev/dri")? { 138 | let i = i?; 139 | if i.file_name().to_str().unwrap().starts_with("card") { 140 | if let Ok(card) = Card::open(i.path().to_str().unwrap()) { 141 | let driver = card.get_driver()?; 142 | println!( 143 | "{} ({}, {}, version {}.{}.{})", 144 | i.path().display(), 145 | driver.name.to_str().unwrap(), 146 | driver.desc.to_str().unwrap(), 147 | driver.version.0, 148 | driver.version.1, 149 | driver.version.2 150 | ); 151 | display_card(&card)?; 152 | } 153 | } 154 | } 155 | Ok(()) 156 | } 157 | -------------------------------------------------------------------------------- /drm-ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Foreign function interface 3 | //! 4 | 5 | #![warn(missing_docs)] 6 | #![allow(unused_doc_comments)] 7 | 8 | pub use drm_sys::{self, *}; 9 | 10 | #[macro_use] 11 | pub(crate) mod utils; 12 | 13 | pub mod gem; 14 | mod ioctl; 15 | pub mod mode; 16 | pub mod syncobj; 17 | 18 | use std::{ 19 | ffi::{c_int, c_ulong}, 20 | io, 21 | os::unix::io::BorrowedFd, 22 | }; 23 | 24 | /// 25 | /// Bindings to the methods of authentication the DRM provides. 26 | /// 27 | pub mod auth { 28 | use crate::ioctl; 29 | use drm_sys::*; 30 | 31 | use std::{io, os::unix::io::BorrowedFd}; 32 | 33 | /// Get the 'Magic Authentication Token' for this file descriptor. 34 | pub fn get_magic_token(fd: BorrowedFd<'_>) -> io::Result { 35 | unsafe { ioctl::get_token(fd) } 36 | } 37 | 38 | /// Authorize another process' 'Magic Authentication Token'. 39 | pub fn auth_magic_token(fd: BorrowedFd<'_>, auth: u32) -> io::Result { 40 | let token = drm_auth { magic: auth }; 41 | 42 | unsafe { 43 | ioctl::auth_token(fd, &token)?; 44 | } 45 | 46 | Ok(token) 47 | } 48 | 49 | /// Acquire the 'Master DRM Lock' for this file descriptor. 50 | pub fn acquire_master(fd: BorrowedFd<'_>) -> io::Result<()> { 51 | unsafe { ioctl::acquire_master(fd) } 52 | } 53 | 54 | /// Release the 'Master DRM Lock' for this file descriptor. 55 | pub fn release_master(fd: BorrowedFd<'_>) -> io::Result<()> { 56 | unsafe { ioctl::release_master(fd) } 57 | } 58 | } 59 | 60 | /// Load this device's Bus ID into a buffer. 61 | pub fn get_bus_id(fd: BorrowedFd<'_>, mut buf: Option<&mut Vec>) -> io::Result { 62 | let mut sizes = drm_unique::default(); 63 | unsafe { 64 | ioctl::get_bus_id(fd, &mut sizes)?; 65 | } 66 | 67 | if buf.is_none() { 68 | return Ok(sizes); 69 | } 70 | 71 | map_reserve!(buf, sizes.unique_len as usize); 72 | 73 | let mut busid = drm_unique { 74 | unique_len: sizes.unique_len, 75 | unique: map_ptr!(&buf), 76 | }; 77 | 78 | unsafe { 79 | ioctl::get_bus_id(fd, &mut busid)?; 80 | } 81 | 82 | map_set!(buf, busid.unique_len as usize); 83 | 84 | Ok(busid) 85 | } 86 | 87 | /// Get a device's IRQ. 88 | pub fn get_interrupt_from_bus_id( 89 | fd: BorrowedFd<'_>, 90 | bus: c_int, 91 | dev: c_int, 92 | func: c_int, 93 | ) -> io::Result { 94 | let mut irq = drm_irq_busid { 95 | busnum: bus, 96 | devnum: dev, 97 | funcnum: func, 98 | ..Default::default() 99 | }; 100 | 101 | unsafe { 102 | ioctl::get_irq_from_bus_id(fd, &mut irq)?; 103 | } 104 | 105 | Ok(irq) 106 | } 107 | 108 | /// Get client information given a client's ID. 109 | pub fn get_client(fd: BorrowedFd<'_>, idx: c_int) -> io::Result { 110 | let mut client = drm_client { 111 | idx, 112 | ..Default::default() 113 | }; 114 | 115 | unsafe { 116 | ioctl::get_client(fd, &mut client)?; 117 | } 118 | 119 | Ok(client) 120 | } 121 | 122 | /// Check if a capability is set. 123 | pub fn get_capability(fd: BorrowedFd<'_>, cty: u64) -> io::Result { 124 | let mut cap = drm_get_cap { 125 | capability: cty, 126 | ..Default::default() 127 | }; 128 | 129 | unsafe { 130 | ioctl::get_cap(fd, &mut cap)?; 131 | } 132 | 133 | Ok(cap) 134 | } 135 | 136 | /// Attempt to enable/disable a client's capability. 137 | pub fn set_capability(fd: BorrowedFd<'_>, cty: u64, val: bool) -> io::Result { 138 | let cap = drm_set_client_cap { 139 | capability: cty, 140 | value: val as u64, 141 | }; 142 | 143 | unsafe { 144 | ioctl::set_cap(fd, &cap)?; 145 | } 146 | 147 | Ok(cap) 148 | } 149 | 150 | /// Sets the requested interface version 151 | pub fn set_version(fd: BorrowedFd<'_>, version: &mut drm_set_version) -> io::Result<()> { 152 | unsafe { ioctl::set_version(fd, version) } 153 | } 154 | 155 | /// Gets the driver version for this device. 156 | pub fn get_version( 157 | fd: BorrowedFd<'_>, 158 | mut name_buf: Option<&mut Vec>, 159 | mut date_buf: Option<&mut Vec>, 160 | mut desc_buf: Option<&mut Vec>, 161 | ) -> io::Result { 162 | let mut sizes = drm_version::default(); 163 | unsafe { ioctl::get_version(fd, &mut sizes) }?; 164 | 165 | map_reserve!(name_buf, sizes.name_len as usize); 166 | map_reserve!(date_buf, sizes.date_len as usize); 167 | map_reserve!(desc_buf, sizes.desc_len as usize); 168 | 169 | let mut version = drm_version { 170 | name_len: map_len!(&name_buf), 171 | name: map_ptr!(&name_buf), 172 | date_len: map_len!(&date_buf), 173 | date: map_ptr!(&date_buf), 174 | desc_len: map_len!(&desc_buf), 175 | desc: map_ptr!(&desc_buf), 176 | ..Default::default() 177 | }; 178 | 179 | unsafe { ioctl::get_version(fd, &mut version) }?; 180 | 181 | map_set!(name_buf, version.name_len as usize); 182 | map_set!(date_buf, version.date_len as usize); 183 | map_set!(desc_buf, version.desc_len as usize); 184 | 185 | Ok(version) 186 | } 187 | 188 | /// Waits for a vblank. 189 | pub fn wait_vblank( 190 | fd: BorrowedFd<'_>, 191 | type_: u32, 192 | sequence: u32, 193 | signal: usize, 194 | ) -> io::Result { 195 | // We can't assume the kernel will completely fill the reply in the union 196 | // with valid data (it won't populate the timestamp if the event flag is 197 | // set, for example), so use `default` to ensure the structure is completely 198 | // initialized with zeros 199 | let mut wait_vblank = drm_wait_vblank::default(); 200 | wait_vblank.request = drm_wait_vblank_request { 201 | type_, 202 | sequence, 203 | signal: signal as c_ulong, 204 | }; 205 | 206 | unsafe { 207 | ioctl::wait_vblank(fd, &mut wait_vblank)?; 208 | }; 209 | 210 | Ok(unsafe { wait_vblank.reply }) 211 | } 212 | -------------------------------------------------------------------------------- /drm-ffi/drm-sys/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "use_bindgen")] 2 | mod use_bindgen { 3 | use bindgen::{ 4 | callbacks::{DeriveTrait, ImplementsTrait, ParseCallbacks}, 5 | Builder, CodegenConfig, 6 | }; 7 | use std::env::var; 8 | use std::path::PathBuf; 9 | 10 | #[derive(Debug)] 11 | struct Callbacks; 12 | 13 | impl ParseCallbacks for Callbacks { 14 | // These types are integers, and implement `Copy`, `Eq`, etc. 15 | // So structs including them can derive those. 16 | fn blocklisted_type_implements_trait( 17 | &self, 18 | name: &str, 19 | _derive_trait: DeriveTrait, 20 | ) -> Option { 21 | if name == "__kernel_size_t" || name == "drm_handle_t" { 22 | Some(ImplementsTrait::Yes) 23 | } else { 24 | None 25 | } 26 | } 27 | } 28 | 29 | fn create_builder(contents: &str) -> Builder { 30 | println!("{contents}"); 31 | 32 | let pkgconf = pkg_config::Config::new(); 33 | let include_paths = if let Ok(value) = var("LIBDRM_INCLUDE_PATH") { 34 | vec![PathBuf::from(value)] 35 | } else { 36 | pkgconf.probe("libdrm").unwrap().include_paths 37 | }; 38 | 39 | let config = CodegenConfig::all(); 40 | 41 | Builder::default() 42 | .clang_args( 43 | include_paths 44 | .into_iter() 45 | .map(|path| "-I".to_string() + &path.into_os_string().into_string().unwrap()), 46 | ) 47 | .header_contents("bindings.h", contents) 48 | .ctypes_prefix("core::ffi") 49 | .with_codegen_config(config) 50 | .prepend_enum_name(false) 51 | .layout_tests(false) 52 | .generate_comments(false) 53 | .derive_copy(true) 54 | .derive_debug(true) 55 | .derive_default(true) 56 | .derive_hash(true) 57 | .derive_eq(true) 58 | .allowlist_recursively(true) 59 | .blocklist_type("__kernel_.*") 60 | .blocklist_type("drm_handle_t") 61 | .parse_callbacks(Box::new(Callbacks)) 62 | .use_core() 63 | } 64 | 65 | const TMP_BIND_PREFIX: &str = "__BINDGEN_TMP_"; 66 | const TMP_BIND_PREFIX_REG: &str = "_BINDGEN_TMP_.*"; 67 | 68 | const INCLUDES: &[&str] = &["drm.h", "drm_mode.h"]; 69 | 70 | const MACROS: &[&str] = &["DRM_MODE_PROP_SIGNED_RANGE", "DRM_MODE_PROP_OBJECT"]; 71 | 72 | // Applies a formatting function over a slice of strings, 73 | // concatenating them on separate lines into a single String 74 | fn apply_formatting(iter: I, f: F) -> String 75 | where 76 | I: Iterator, 77 | I::Item: AsRef, 78 | F: Fn(&str) -> String, 79 | { 80 | iter.fold(String::new(), |acc, x| acc + &f(x.as_ref()) + "\n") 81 | } 82 | 83 | // Create a name for a temporary value 84 | fn tmp_val(name: &str) -> String { 85 | format!("{TMP_BIND_PREFIX}{name}") 86 | } 87 | 88 | // Create a C include directive 89 | fn include(header: &str) -> String { 90 | format!("#include <{header}>") 91 | } 92 | 93 | // Create a C constant 94 | fn decl_const(ty: &str, name: &str, value: &str) -> String { 95 | format!("const {ty} {name} = {value};") 96 | } 97 | 98 | // Create a C macro definition 99 | fn define_macro(name: &str, val: &str) -> String { 100 | format!("#define {name} {val}") 101 | } 102 | 103 | // Create a C undefinition 104 | fn undefine_macro(name: &str) -> String { 105 | format!("#undef {name}") 106 | } 107 | 108 | // Rebind a C macro as a constant 109 | // Required for some macros that won't get generated 110 | fn rebind_macro(name: &str) -> String { 111 | let tmp_name = tmp_val(name); 112 | format!( 113 | "{}\n{}\n{}\n{}", 114 | decl_const("unsigned int", &tmp_name, name), 115 | undefine_macro(name), 116 | decl_const("unsigned int", name, &tmp_name), 117 | define_macro(name, name) 118 | ) 119 | } 120 | 121 | // Fully create the header 122 | fn create_header() -> String { 123 | apply_formatting(INCLUDES.iter(), include) + &apply_formatting(MACROS.iter(), rebind_macro) 124 | } 125 | 126 | pub fn generate_bindings() { 127 | let bindings = create_builder(&create_header()) 128 | .blocklist_type(TMP_BIND_PREFIX_REG) 129 | .blocklist_type("drm_control_DRM_ADD_COMMAND") 130 | .allowlist_type("_?DRM_.*|drm_.*|hdr_.*") 131 | .allowlist_var("_?DRM_.*|drm_.*") 132 | .constified_enum_module("drm_control_.*") 133 | .constified_enum_module("drm_buf_desc_.*") 134 | .constified_enum_module("drm_map_type") 135 | .constified_enum_module("drm_map_flags") 136 | .constified_enum_module("drm_stat_type") 137 | .constified_enum_module("drm_lock_flags") 138 | .constified_enum_module("drm_dma_flags") 139 | .constified_enum_module("drm_ctx_flags") 140 | .constified_enum_module("drm_drawable_info_type_t") 141 | .constified_enum_module("drm_vblank_seq_type") 142 | .constified_enum_module("drm_mode_subconnector") 143 | .generate() 144 | .expect("Unable to generate libdrm bindings"); 145 | 146 | let out_path = var("OUT_DIR").unwrap(); 147 | let bind_file = PathBuf::from(out_path).join("bindings.rs"); 148 | 149 | println!("cargo:rerun-if-changed={}", bind_file.display()); 150 | 151 | bindings 152 | .write_to_file(bind_file) 153 | .expect("Could not write bindings"); 154 | } 155 | 156 | #[cfg(feature = "update_bindings")] 157 | pub fn update_bindings() { 158 | use std::fs; 159 | 160 | let out_path = var("OUT_DIR").unwrap(); 161 | let bind_file = PathBuf::from(out_path).join("bindings.rs"); 162 | let dest_file = PathBuf::from("src/bindings.rs"); 163 | 164 | println!("cargo:rerun-if-changed={}", dest_file.display()); 165 | 166 | fs::copy(bind_file, &dest_file).unwrap(); 167 | } 168 | } 169 | 170 | #[cfg(feature = "use_bindgen")] 171 | pub fn main() { 172 | use_bindgen::generate_bindings(); 173 | 174 | #[cfg(feature = "update_bindings")] 175 | use_bindgen::update_bindings(); 176 | } 177 | 178 | #[cfg(not(feature = "use_bindgen"))] 179 | pub fn main() {} 180 | -------------------------------------------------------------------------------- /examples/atomic_modeset.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | use crate::utils::*; 3 | 4 | use drm::control::Device as ControlDevice; 5 | use drm::Device as BasicDevice; 6 | 7 | use drm::buffer::DrmFourcc; 8 | 9 | use drm::control::{self, atomic, connector, crtc, property, AtomicCommitFlags}; 10 | 11 | pub fn main() { 12 | let card = Card::open_global(); 13 | 14 | card.set_client_capability(drm::ClientCapability::UniversalPlanes, true) 15 | .expect("Unable to request UniversalPlanes capability"); 16 | card.set_client_capability(drm::ClientCapability::Atomic, true) 17 | .expect("Unable to request Atomic capability"); 18 | 19 | // Load the information. 20 | let res = card 21 | .resource_handles() 22 | .expect("Could not load normal resource ids."); 23 | let coninfo: Vec = res 24 | .connectors() 25 | .iter() 26 | .flat_map(|con| card.get_connector(*con, true)) 27 | .collect(); 28 | let crtcinfo: Vec = res 29 | .crtcs() 30 | .iter() 31 | .flat_map(|crtc| card.get_crtc(*crtc)) 32 | .collect(); 33 | 34 | // Filter each connector until we find one that's connected. 35 | let con = coninfo 36 | .iter() 37 | .find(|&i| i.state() == connector::State::Connected) 38 | .expect("No connected connectors"); 39 | 40 | // Get the first (usually best) mode 41 | let &mode = con.modes().first().expect("No modes found on connector"); 42 | 43 | let (disp_width, disp_height) = mode.size(); 44 | 45 | // Find a crtc and FB 46 | let crtc = crtcinfo.first().expect("No crtcs found"); 47 | 48 | // Select the pixel format 49 | let fmt = DrmFourcc::Xrgb8888; 50 | 51 | // Create a DB 52 | // If buffer resolution is above display resolution, a ENOSPC (not enough GPU memory) error may 53 | // occur 54 | let mut db = card 55 | .create_dumb_buffer((disp_width.into(), disp_height.into()), fmt, 32) 56 | .expect("Could not create dumb buffer"); 57 | 58 | // Map it and grey it out. 59 | { 60 | let mut map = card 61 | .map_dumb_buffer(&mut db) 62 | .expect("Could not map dumbbuffer"); 63 | for b in map.as_mut() { 64 | *b = 128; 65 | } 66 | } 67 | 68 | // Create an FB: 69 | let fb = card 70 | .add_framebuffer(&db, 24, 32) 71 | .expect("Could not create FB"); 72 | 73 | let planes = card.plane_handles().expect("Could not list planes"); 74 | let (better_planes, compatible_planes): ( 75 | Vec, 76 | Vec, 77 | ) = planes 78 | .iter() 79 | .filter(|&&plane| { 80 | card.get_plane(plane) 81 | .map(|plane_info| { 82 | let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs()); 83 | compatible_crtcs.contains(&crtc.handle()) 84 | }) 85 | .unwrap_or(false) 86 | }) 87 | .partition(|&&plane| { 88 | if let Ok(props) = card.get_properties(plane) { 89 | for (&id, &val) in props.iter() { 90 | if let Ok(info) = card.get_property(id) { 91 | if info.name().to_str().map(|x| x == "type").unwrap_or(false) { 92 | return val == (drm::control::PlaneType::Primary as u32).into(); 93 | } 94 | } 95 | } 96 | } 97 | false 98 | }); 99 | let plane = *better_planes.first().unwrap_or(&compatible_planes[0]); 100 | 101 | println!("{mode:#?}"); 102 | println!("{fb:#?}"); 103 | println!("{db:#?}"); 104 | println!("{plane:#?}"); 105 | 106 | let con_props = card 107 | .get_properties(con.handle()) 108 | .expect("Could not get props of connector") 109 | .as_hashmap(&card) 110 | .expect("Could not get a prop from connector"); 111 | let crtc_props = card 112 | .get_properties(crtc.handle()) 113 | .expect("Could not get props of crtc") 114 | .as_hashmap(&card) 115 | .expect("Could not get a prop from crtc"); 116 | let plane_props = card 117 | .get_properties(plane) 118 | .expect("Could not get props of plane") 119 | .as_hashmap(&card) 120 | .expect("Could not get a prop from plane"); 121 | 122 | let mut atomic_req = atomic::AtomicModeReq::new(); 123 | atomic_req.add_property( 124 | con.handle(), 125 | con_props["CRTC_ID"].handle(), 126 | property::Value::CRTC(Some(crtc.handle())), 127 | ); 128 | let blob = card 129 | .create_property_blob(&mode) 130 | .expect("Failed to create blob"); 131 | atomic_req.add_property(crtc.handle(), crtc_props["MODE_ID"].handle(), blob); 132 | atomic_req.add_property( 133 | crtc.handle(), 134 | crtc_props["ACTIVE"].handle(), 135 | property::Value::Boolean(true), 136 | ); 137 | atomic_req.add_property( 138 | plane, 139 | plane_props["FB_ID"].handle(), 140 | property::Value::Framebuffer(Some(fb)), 141 | ); 142 | atomic_req.add_property( 143 | plane, 144 | plane_props["CRTC_ID"].handle(), 145 | property::Value::CRTC(Some(crtc.handle())), 146 | ); 147 | atomic_req.add_property( 148 | plane, 149 | plane_props["SRC_X"].handle(), 150 | property::Value::UnsignedRange(0), 151 | ); 152 | atomic_req.add_property( 153 | plane, 154 | plane_props["SRC_Y"].handle(), 155 | property::Value::UnsignedRange(0), 156 | ); 157 | atomic_req.add_property( 158 | plane, 159 | plane_props["SRC_W"].handle(), 160 | property::Value::UnsignedRange((mode.size().0 as u64) << 16), 161 | ); 162 | atomic_req.add_property( 163 | plane, 164 | plane_props["SRC_H"].handle(), 165 | property::Value::UnsignedRange((mode.size().1 as u64) << 16), 166 | ); 167 | atomic_req.add_property( 168 | plane, 169 | plane_props["CRTC_X"].handle(), 170 | property::Value::SignedRange(0), 171 | ); 172 | atomic_req.add_property( 173 | plane, 174 | plane_props["CRTC_Y"].handle(), 175 | property::Value::SignedRange(0), 176 | ); 177 | atomic_req.add_property( 178 | plane, 179 | plane_props["CRTC_W"].handle(), 180 | property::Value::UnsignedRange(mode.size().0 as u64), 181 | ); 182 | atomic_req.add_property( 183 | plane, 184 | plane_props["CRTC_H"].handle(), 185 | property::Value::UnsignedRange(mode.size().1 as u64), 186 | ); 187 | 188 | // Set the crtc 189 | // On many setups, this requires root access. 190 | card.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, atomic_req) 191 | .expect("Failed to set mode"); 192 | 193 | let five_seconds = ::std::time::Duration::from_millis(5000); 194 | ::std::thread::sleep(five_seconds); 195 | 196 | card.destroy_framebuffer(fb).unwrap(); 197 | card.destroy_dumb_buffer(db).unwrap(); 198 | } 199 | -------------------------------------------------------------------------------- /drm-ffi/src/syncobj.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Bindings for DRM sync objects 3 | //! 4 | 5 | use crate::ioctl; 6 | use drm_sys::*; 7 | 8 | use std::{ 9 | io, 10 | os::unix::io::{AsRawFd, BorrowedFd}, 11 | }; 12 | 13 | /// Creates a syncobj. 14 | pub fn create(fd: BorrowedFd<'_>, signaled: bool) -> io::Result { 15 | let mut args = drm_syncobj_create { 16 | handle: 0, 17 | flags: if signaled { 18 | DRM_SYNCOBJ_CREATE_SIGNALED 19 | } else { 20 | 0 21 | }, 22 | }; 23 | 24 | unsafe { 25 | ioctl::syncobj::create(fd, &mut args)?; 26 | } 27 | 28 | Ok(args) 29 | } 30 | 31 | /// Destroys a syncobj. 32 | pub fn destroy(fd: BorrowedFd<'_>, handle: u32) -> io::Result { 33 | let mut args = drm_syncobj_destroy { handle, pad: 0 }; 34 | 35 | unsafe { 36 | ioctl::syncobj::destroy(fd, &mut args)?; 37 | } 38 | 39 | Ok(args) 40 | } 41 | 42 | /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. 43 | pub fn handle_to_fd( 44 | fd: BorrowedFd<'_>, 45 | handle: u32, 46 | export_sync_file: bool, 47 | ) -> io::Result { 48 | let mut args = drm_syncobj_handle { 49 | handle, 50 | flags: if export_sync_file { 51 | DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE 52 | } else { 53 | 0 54 | }, 55 | fd: 0, 56 | pad: 0, 57 | point: 0, // TODO: Add support for TIMELINE sync files 58 | }; 59 | 60 | unsafe { 61 | ioctl::syncobj::handle_to_fd(fd, &mut args)?; 62 | } 63 | 64 | Ok(args) 65 | } 66 | 67 | /// Imports a file descriptor exported by [`handle_to_fd`] back into a process-local handle. 68 | pub fn fd_to_handle( 69 | fd: BorrowedFd<'_>, 70 | syncobj_fd: BorrowedFd<'_>, 71 | import_sync_file: bool, 72 | ) -> io::Result { 73 | let mut args = drm_syncobj_handle { 74 | handle: 0, 75 | flags: if import_sync_file { 76 | DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE 77 | } else { 78 | 0 79 | }, 80 | fd: syncobj_fd.as_raw_fd(), 81 | pad: 0, 82 | point: 0, // TODO: Add support for TIMELINE sync files 83 | }; 84 | 85 | unsafe { 86 | ioctl::syncobj::fd_to_handle(fd, &mut args)?; 87 | } 88 | 89 | Ok(args) 90 | } 91 | 92 | /// Waits for one or more syncobjs to become signalled. 93 | pub fn wait( 94 | fd: BorrowedFd<'_>, 95 | handles: &[u32], 96 | timeout_nsec: i64, 97 | wait_all: bool, 98 | wait_for_submit: bool, 99 | ) -> io::Result { 100 | let mut args = drm_syncobj_wait { 101 | handles: handles.as_ptr() as _, 102 | timeout_nsec, 103 | count_handles: handles.len() as _, 104 | flags: if wait_all { 105 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL 106 | } else { 107 | 0 108 | } | if wait_for_submit { 109 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT 110 | } else { 111 | 0 112 | }, 113 | first_signaled: 0, 114 | pad: 0, 115 | deadline_nsec: 0, 116 | }; 117 | 118 | unsafe { 119 | ioctl::syncobj::wait(fd, &mut args)?; 120 | } 121 | 122 | Ok(args) 123 | } 124 | 125 | /// Resets (un-signals) one or more syncobjs. 126 | pub fn reset(fd: BorrowedFd<'_>, handles: &[u32]) -> io::Result { 127 | let mut args = drm_syncobj_array { 128 | handles: handles.as_ptr() as _, 129 | count_handles: handles.len() as _, 130 | pad: 0, 131 | }; 132 | 133 | unsafe { 134 | ioctl::syncobj::reset(fd, &mut args)?; 135 | } 136 | 137 | Ok(args) 138 | } 139 | 140 | /// Signals one or more syncobjs. 141 | pub fn signal(fd: BorrowedFd<'_>, handles: &[u32]) -> io::Result { 142 | let mut args = drm_syncobj_array { 143 | handles: handles.as_ptr() as _, 144 | count_handles: handles.len() as _, 145 | pad: 0, 146 | }; 147 | 148 | unsafe { 149 | ioctl::syncobj::signal(fd, &mut args)?; 150 | } 151 | 152 | Ok(args) 153 | } 154 | 155 | /// Waits for one or more specific timeline syncobj points. 156 | pub fn timeline_wait( 157 | fd: BorrowedFd<'_>, 158 | handles: &[u32], 159 | points: &[u64], 160 | timeout_nsec: i64, 161 | wait_all: bool, 162 | wait_for_submit: bool, 163 | wait_available: bool, 164 | ) -> io::Result { 165 | debug_assert_eq!(handles.len(), points.len()); 166 | 167 | let mut args = drm_syncobj_timeline_wait { 168 | handles: handles.as_ptr() as _, 169 | points: points.as_ptr() as _, 170 | timeout_nsec, 171 | count_handles: handles.len() as _, 172 | flags: if wait_all { 173 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL 174 | } else { 175 | 0 176 | } | if wait_for_submit { 177 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT 178 | } else { 179 | 0 180 | } | if wait_available { 181 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE 182 | } else { 183 | 0 184 | }, 185 | first_signaled: 0, 186 | pad: 0, 187 | deadline_nsec: 0, 188 | }; 189 | 190 | unsafe { 191 | ioctl::syncobj::timeline_wait(fd, &mut args)?; 192 | } 193 | 194 | Ok(args) 195 | } 196 | 197 | /// Queries for state of one or more timeline syncobjs. 198 | pub fn query( 199 | fd: BorrowedFd<'_>, 200 | handles: &[u32], 201 | points: &mut [u64], 202 | last_submitted: bool, 203 | ) -> io::Result { 204 | debug_assert_eq!(handles.len(), points.len()); 205 | 206 | let mut args = drm_syncobj_timeline_array { 207 | handles: handles.as_ptr() as _, 208 | points: points.as_mut_ptr() as _, 209 | count_handles: handles.len() as _, 210 | flags: if last_submitted { 211 | DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED 212 | } else { 213 | 0 214 | }, 215 | }; 216 | 217 | unsafe { 218 | ioctl::syncobj::query(fd, &mut args)?; 219 | } 220 | 221 | Ok(args) 222 | } 223 | 224 | /// Transfers one timeline syncobj point to another. 225 | pub fn transfer( 226 | fd: BorrowedFd<'_>, 227 | src_handle: u32, 228 | dst_handle: u32, 229 | src_point: u64, 230 | dst_point: u64, 231 | ) -> io::Result { 232 | let mut args = drm_syncobj_transfer { 233 | src_handle, 234 | dst_handle, 235 | src_point, 236 | dst_point, 237 | flags: 0, 238 | pad: 0, 239 | }; 240 | 241 | unsafe { 242 | ioctl::syncobj::transfer(fd, &mut args)?; 243 | } 244 | 245 | Ok(args) 246 | } 247 | 248 | /// Signals one or more specific timeline syncobj points. 249 | pub fn timeline_signal( 250 | fd: BorrowedFd<'_>, 251 | handles: &[u32], 252 | points: &[u64], 253 | ) -> io::Result { 254 | debug_assert_eq!(handles.len(), points.len()); 255 | 256 | let mut args = drm_syncobj_timeline_array { 257 | handles: handles.as_ptr() as _, 258 | points: points.as_ptr() as _, 259 | count_handles: handles.len() as _, 260 | flags: 0, 261 | }; 262 | 263 | unsafe { 264 | ioctl::syncobj::timeline_signal(fd, &mut args)?; 265 | } 266 | 267 | Ok(args) 268 | } 269 | 270 | /// Register an eventfd to be signalled by a syncobj. 271 | pub fn eventfd( 272 | fd: BorrowedFd<'_>, 273 | handle: u32, 274 | point: u64, 275 | eventfd: BorrowedFd<'_>, 276 | wait_available: bool, 277 | ) -> io::Result { 278 | let flags = if wait_available { 279 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE 280 | } else { 281 | 0 282 | }; 283 | let mut args = drm_syncobj_eventfd { 284 | handle, 285 | point, 286 | flags, 287 | fd: eventfd.as_raw_fd(), 288 | pad: 0, 289 | }; 290 | 291 | unsafe { 292 | ioctl::syncobj::eventfd(fd, &mut args)?; 293 | } 294 | 295 | Ok(args) 296 | } 297 | -------------------------------------------------------------------------------- /examples/kms_interactive.rs: -------------------------------------------------------------------------------- 1 | /// Check the `util` module to see how the `Card` structure is implemented. 2 | pub mod utils; 3 | use crate::utils::*; 4 | use drm::control::{from_u32, RawResourceHandle}; 5 | 6 | pub fn main() { 7 | let card = Card::open_global(); 8 | 9 | // Enable all possible client capabilities 10 | for &cap in capabilities::CLIENT_CAP_ENUMS { 11 | if let Err(e) = card.set_client_capability(cap, true) { 12 | eprintln!("Unable to activate capability {cap:?}: {e}"); 13 | return; 14 | } 15 | } 16 | 17 | run_repl(&card); 18 | } 19 | 20 | fn run_repl(card: &Card) { 21 | // Load a set of numbered images 22 | let images = [ 23 | images::load_image("1.png"), 24 | images::load_image("2.png"), 25 | images::load_image("3.png"), 26 | images::load_image("4.png"), 27 | ]; 28 | 29 | for image in &images { 30 | // Create the Dumbbuffer 31 | let fmt = drm::buffer::DrmFourcc::Xrgb8888; 32 | let mut db = card 33 | .create_dumb_buffer(image.dimensions(), fmt, 32) 34 | .unwrap(); 35 | 36 | // Create a Framebuffer to represent it 37 | let _fb = card.add_framebuffer(&db, 24, 32).unwrap(); 38 | 39 | // Load the image into the buffer 40 | { 41 | let mut mapping = card.map_dumb_buffer(&mut db).unwrap(); 42 | let buffer = mapping.as_mut(); 43 | for (img_px, map_px) in image.pixels().zip(buffer.chunks_exact_mut(4)) { 44 | // Assuming little endian, it's BGRA 45 | map_px[0] = img_px[0]; // Blue 46 | map_px[1] = img_px[1]; // Green 47 | map_px[2] = img_px[2]; // Red 48 | map_px[3] = img_px[3]; // Alpha 49 | } 50 | }; 51 | } 52 | 53 | // Using rustyline to create the interactive prompt. 54 | let editor_config = rustyline::config::Builder::new() 55 | .max_history_size(256) 56 | .unwrap() 57 | .completion_type(rustyline::config::CompletionType::List) 58 | .edit_mode(rustyline::config::EditMode::Vi) 59 | .auto_add_history(true) 60 | .build(); 61 | let mut kms_editor = rustyline::Editor::<(), _>::with_config(editor_config).unwrap(); 62 | let mut atomic_editor = rustyline::Editor::<(), _>::with_config(editor_config).unwrap(); 63 | 64 | for line in kms_editor.iter("KMS>> ").map(|x| x.unwrap()) { 65 | let args: Vec<_> = line.split_whitespace().collect(); 66 | match &args[..] { 67 | ["CreateAtomicSet"] => { 68 | for line in atomic_editor.iter("Atomic>> ").map(|x| x.unwrap()) { 69 | let args: Vec<_> = line.split_whitespace().collect(); 70 | match &args[..] { 71 | ["Quit"] => break, 72 | args => println!("{args:?}"), 73 | } 74 | } 75 | } 76 | // Destroying a framebuffer 77 | ["DestroyFramebuffer", handle] => { 78 | let handle: u32 = str::parse(handle).unwrap(); 79 | let handle: drm::control::framebuffer::Handle = from_u32(handle).unwrap(); 80 | if let Err(err) = card.destroy_framebuffer(handle) { 81 | println!("Unable to destroy framebuffer ({handle:?}): {err}"); 82 | } 83 | } 84 | // Print out all resources 85 | ["GetResources"] => { 86 | let resources = card.resource_handles().unwrap(); 87 | println!("\tConnectors: {:?}", resources.connectors()); 88 | println!("\tEncoders: {:?}", resources.encoders()); 89 | println!("\tCRTCS: {:?}", resources.crtcs()); 90 | println!("\tFramebuffers: {:?}", resources.framebuffers()); 91 | let planes = card.plane_handles().unwrap(); 92 | println!("\tPlanes: {planes:?}"); 93 | } 94 | // Print out the values of a specific property 95 | ["GetProperty", handle] => { 96 | let handle: u32 = str::parse(handle).unwrap(); 97 | let handle: drm::control::property::Handle = from_u32(handle).unwrap(); 98 | let property = card.get_property(handle).unwrap(); 99 | println!("\tName: {:?}", property.name()); 100 | println!("\tMutable: {:?}", property.mutable()); 101 | println!("\tAtomic: {:?}", property.atomic()); 102 | println!("\tValue: {:#?}", property.value_type()); 103 | } 104 | // Get the property-value pairs of a single resource 105 | ["GetProperties", handle] => match HandleWithProperties::from_str(card, handle) { 106 | Ok(handle) => { 107 | let props = match handle { 108 | HandleWithProperties::Connector(handle) => { 109 | card.get_properties(handle).unwrap() 110 | } 111 | HandleWithProperties::CRTC(handle) => card.get_properties(handle).unwrap(), 112 | HandleWithProperties::Plane(handle) => card.get_properties(handle).unwrap(), 113 | }; 114 | for (id, val) in props.iter() { 115 | println!("\tProperty: {id:?}\tValue: {val:?}"); 116 | } 117 | } 118 | Err(_) => println!("Unknown handle or handle has no properties"), 119 | }, 120 | // Set a property's value on a resource 121 | ["SetProperty", handle, property, value] => { 122 | let property: u32 = str::parse(property).unwrap(); 123 | let property: drm::control::property::Handle = from_u32(property).unwrap(); 124 | let value: u64 = str::parse(value).unwrap(); 125 | 126 | match HandleWithProperties::from_str(card, handle) { 127 | Ok(handle) => { 128 | match handle { 129 | HandleWithProperties::Connector(handle) => { 130 | println!("\t{:?}", card.set_property(handle, property, value)); 131 | } 132 | HandleWithProperties::CRTC(handle) => { 133 | println!("\t{:?}", card.set_property(handle, property, value)); 134 | } 135 | HandleWithProperties::Plane(handle) => { 136 | println!("\t{:?}", card.set_property(handle, property, value)); 137 | } 138 | }; 139 | } 140 | Err(_) => println!("Unknown handle or handle has no properties"), 141 | }; 142 | } 143 | ["GetModes", handle] => match HandleWithProperties::from_str(card, handle) { 144 | Ok(HandleWithProperties::Connector(handle)) => { 145 | let modes = card.get_modes(handle).unwrap(); 146 | for mode in modes { 147 | println!("\tName:\t{:?}", mode.name()); 148 | println!("\t\tSize:\t{:?}", mode.size()); 149 | println!("\t\tRefresh:\t{:?}", mode.vrefresh()); 150 | } 151 | } 152 | _ => println!("Unknown handle or handle is not a connector"), 153 | }, 154 | ["help"] => { 155 | println!("CreateAtomicSet"); 156 | println!("DestroyFramebuffer "); 157 | println!("GetResources"); 158 | println!("GetProperty "); 159 | println!("GetProperties "); 160 | println!("SetProperty "); 161 | println!("GetModes "); 162 | } 163 | ["quit"] => break, 164 | [] => (), 165 | _ => { 166 | println!("Unknown command"); 167 | } 168 | } 169 | } 170 | } 171 | 172 | #[allow(clippy::upper_case_acronyms)] 173 | enum HandleWithProperties { 174 | Connector(drm::control::connector::Handle), 175 | CRTC(drm::control::crtc::Handle), 176 | Plane(drm::control::plane::Handle), 177 | } 178 | 179 | impl HandleWithProperties { 180 | // This is a helper command that will take a string of a number and lookup 181 | // the corresponding resource. 182 | fn from_str(card: &Card, handle: &str) -> Result { 183 | let handle: u32 = str::parse(handle).unwrap(); 184 | let handle = RawResourceHandle::new(handle).unwrap(); 185 | 186 | let rhandles = card.resource_handles().unwrap(); 187 | for connector in rhandles.connectors().iter().map(|h| (*h).into()) { 188 | if handle == connector { 189 | return Ok(HandleWithProperties::Connector(handle.into())); 190 | } 191 | } 192 | 193 | for crtc in rhandles.crtcs().iter().map(|h| (*h).into()) { 194 | if handle == crtc { 195 | return Ok(HandleWithProperties::CRTC(handle.into())); 196 | } 197 | } 198 | 199 | let phandles = card.plane_handles().unwrap(); 200 | for plane in phandles.iter().map(|h| (*h).into()) { 201 | if handle == plane { 202 | return Ok(HandleWithProperties::Plane(handle.into())); 203 | } 204 | } 205 | 206 | Err(()) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - develop 7 | tags: 8 | - 'v[0-9]+.[0-9]+.[0-9]+' 9 | pull_request: 10 | 11 | jobs: 12 | format: 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Cargo cache 17 | uses: actions/cache@v3 18 | with: 19 | path: | 20 | ~/.cargo/registry 21 | ~/.cargo/git 22 | key: ${{ runner.os }}-cargo-rust_stable-${{ hashFiles('**/Cargo.toml') }} 23 | - name: Format 24 | run: cargo fmt --all -- --check 25 | 26 | doc: 27 | runs-on: ubuntu-22.04 28 | steps: 29 | - uses: actions/checkout@v3 30 | - name: Cargo cache 31 | uses: actions/cache@v3 32 | with: 33 | path: | 34 | ~/.cargo/registry 35 | ~/.cargo/git 36 | key: ${{ runner.os }}-cargo-rust_nightly-${{ hashFiles('**/Cargo.toml') }} 37 | - name: Documentation 38 | run: cargo doc 39 | 40 | check: 41 | runs-on: ubuntu-22.04 42 | steps: 43 | - uses: actions/checkout@v3 44 | - run: | 45 | sudo apt-get update -y 46 | sudo apt-get install -y libdrm-dev 47 | - name: Fetch drm headers 48 | run: ./.github/workflows/fetch_headers.sh 49 | - uses: dtolnay/rust-toolchain@1.70.0 50 | with: 51 | components: clippy 52 | # TODO: Remove once our MSRV is past 1.84 for the new resolver 53 | - uses: dtolnay/rust-toolchain@1.84.0 54 | - name: Downgrade msrv aware 55 | run: env CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback cargo +1.84 generate-lockfile 56 | - name: Cargo cache 57 | uses: actions/cache@v3 58 | with: 59 | path: | 60 | ~/.cargo/registry 61 | ~/.cargo/git 62 | key: ${{ runner.os }}-cargo-rust_1.70.0-${{ hashFiles('**/Cargo.toml') }} 63 | - name: Build cache 64 | uses: actions/cache@v3 65 | with: 66 | path: target 67 | key: ${{ runner.os }}-build-rust_1.70.0-check-${{ hashFiles('**/Cargo.toml') }} 68 | - name: Clippy 69 | run: cargo +1.70.0 clippy --all --all-features --all-targets -- -D warnings -A clippy::redundant_static_lifetimes 70 | 71 | check-minimal: 72 | runs-on: ubuntu-22.04 73 | steps: 74 | - uses: actions/checkout@v3 75 | - run: | 76 | sudo apt-get update -y 77 | sudo apt-get install -y libdrm-dev 78 | - name: Fetch drm headers 79 | run: ./.github/workflows/fetch_headers.sh 80 | - uses: dtolnay/rust-toolchain@nightly 81 | - name: Cargo cache 82 | uses: actions/cache@v3 83 | with: 84 | path: | 85 | ~/.cargo/registry 86 | ~/.cargo/git 87 | key: ${{ runner.os }}-cargo-rust_nightly-${{ hashFiles('**/Cargo.toml') }} 88 | - name: Build cache 89 | uses: actions/cache@v3 90 | with: 91 | path: target 92 | key: ${{ runner.os }}-build-rust_nightly-check-minimal-${{ hashFiles('**/Cargo.toml') }} 93 | - name: Check 94 | run: cargo check --all --all-features --all-targets -Z minimal-versions 95 | 96 | test: 97 | needs: 98 | - format 99 | - doc 100 | - check 101 | strategy: 102 | fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} 103 | matrix: 104 | include: 105 | # Generate bindings 106 | - task: bindings 107 | os: ubuntu-22.04 108 | rust: stable 109 | target: i686-unknown-linux-gnu 110 | - task: bindings 111 | os: ubuntu-22.04 112 | rust: stable 113 | target: x86_64-unknown-linux-gnu 114 | - task: bindings 115 | os: ubuntu-22.04 116 | rust: stable 117 | target: arm-unknown-linux-gnueabihf 118 | - task: bindings 119 | os: ubuntu-22.04 120 | rust: stable 121 | target: armv7-unknown-linux-gnueabihf 122 | - task: bindings 123 | os: ubuntu-22.04 124 | rust: stable 125 | target: aarch64-unknown-linux-gnu 126 | - task: bindings 127 | os: ubuntu-22.04 128 | rust: stable 129 | target: riscv64gc-unknown-linux-gnu 130 | # Test channels 131 | - task: channels 132 | os: ubuntu-22.04 133 | rust: stable 134 | target: x86_64-unknown-linux-gnu 135 | - task: channels 136 | os: ubuntu-22.04 137 | rust: beta 138 | target: x86_64-unknown-linux-gnu 139 | - task: channels 140 | os: ubuntu-22.04 141 | rust: nightly 142 | target: x86_64-unknown-linux-gnu 143 | runs-on: ${{ matrix.os }} 144 | steps: 145 | - uses: actions/checkout@v3 146 | - name: Setup linux cross toolchain 147 | if: contains(matrix.target, '-linux-') && !startsWith(matrix.target, 'x86_64-') 148 | run: | 149 | GCC_TARGET=$(printf "${{ matrix.target }}" | sed 's/-unknown-/-/;s/arm[^-]*/arm/;s/riscv[^-]*/riscv64/') 150 | ENV_TARGET=$(printf "${{ matrix.target }}" | tr '-' '_' | tr '[[:lower:]]' '[[:upper:]]') 151 | sudo apt-get update -y 152 | sudo apt-get install -y gcc-${GCC_TARGET} 153 | echo "CARGO_TARGET_${ENV_TARGET}_LINKER=${GCC_TARGET}-gcc" >> $GITHUB_ENV 154 | echo "BINDGEN_EXTRA_CLANG_ARGS=--sysroot=/usr/${GCC_TARGET}" >> $GITHUB_ENV 155 | - name: Fetch drm headers 156 | run: ./.github/workflows/fetch_headers.sh 157 | - name: Setup Rust 158 | uses: dtolnay/rust-toolchain@master 159 | with: 160 | toolchain: ${{ matrix.rust }} 161 | targets: ${{ matrix.target }} 162 | - name: Cargo cache 163 | uses: actions/cache@v3 164 | with: 165 | path: | 166 | ~/.cargo/registry 167 | ~/.cargo/git 168 | key: ${{ runner.os }}-cargo-rust_${{ matrix.rust }}-${{ hashFiles('**/Cargo.toml') }} 169 | - name: Build cache 170 | uses: actions/cache@v3 171 | with: 172 | path: target 173 | key: ${{ runner.os }}-build-rust_${{ matrix.rust }}-target_${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }} 174 | - name: Build sys 175 | env: 176 | RUST_LOG: bindgen=warn,bindgen::ir=error,bindgen::codegen=error 177 | run: cargo build --manifest-path drm-ffi/drm-sys/Cargo.toml --target ${{ matrix.target }} --features update_bindings 178 | - name: Copy bindings 179 | run: cp drm-ffi/drm-sys/src/bindings.rs bindings-${{ matrix.target }}.rs 180 | - name: Upload bindings 181 | if: matrix.task == 'bindings' 182 | uses: actions/upload-artifact@v4 183 | with: 184 | name: bindings-${{ matrix.target }}-${{ matrix.rust }} 185 | path: bindings-*.rs 186 | - name: Build 187 | run: cargo build --target ${{ matrix.target }} 188 | - name: Test 189 | if: contains(matrix.target, '-linux-') && (startsWith(matrix.target, 'x86_64-') || startsWith(matrix.target, 'i686-')) 190 | timeout-minutes: 12 191 | env: 192 | RUST_BACKTRACE: full 193 | run: cargo test --all --target ${{ matrix.target }} 194 | 195 | compare-bindings: 196 | needs: 197 | - test 198 | runs-on: ubuntu-22.04 199 | steps: 200 | - name: download bindings 201 | uses: actions/download-artifact@v4 202 | with: 203 | pattern: bindings-* 204 | merge-multiple: true 205 | - name: compare 206 | run: | 207 | code=0 208 | for i in bindings-*.rs 209 | do 210 | if cmp -s bindings-x86_64-unknown-linux-gnu.rs ${i} 211 | then 212 | echo ${i} matches x86_64 bindings 213 | else 214 | echo ${i} does not match x86_64 bindings 215 | diff bindings-x86_64-unknown-linux-gnu.rs ${i} 216 | code=1 217 | fi 218 | done 219 | exit ${code} 220 | 221 | update-bindings: 222 | if: ${{ github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/tags/') }} 223 | needs: 224 | - compare-bindings 225 | runs-on: ubuntu-22.04 226 | steps: 227 | - uses: actions/checkout@v3 228 | - name: download bindings 229 | uses: actions/download-artifact@v4 230 | with: 231 | pattern: bindings-* 232 | merge-multiple: true 233 | - name: Copy x86_64 bindings 234 | run: | 235 | cp bindings-x86_64-unknown-linux-gnu.rs drm-ffi/drm-sys/src/bindings.rs 236 | rm bindings-*.rs 237 | - name: Create pull request 238 | uses: peter-evans/create-pull-request@v3 239 | with: 240 | base: ${{ github.head_ref }} 241 | commit-message: Updated bindings 242 | branch: update-bindings 243 | delete-branch: true 244 | title: Update bindings 245 | body: | 246 | Bindings should be updated to be consistent with latest changes 247 | 248 | publish: 249 | if: github.repository == 'Smithay/drm-rs' && startsWith(github.ref, 'refs/tags/v') 250 | needs: 251 | - format 252 | - doc 253 | - check 254 | - check-minimal 255 | - test 256 | runs-on: ubuntu-22.04 257 | steps: 258 | - uses: actions/checkout@v3 259 | - name: Publish crates 260 | uses: katyo/publish-crates@v1 261 | with: 262 | registry-token: ${{ secrets.CRATES_TOKEN }} 263 | args: --no-verify 264 | #dry-run: true 265 | -------------------------------------------------------------------------------- /drm-ffi/src/ioctl.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_uint, io, os::unix::io::BorrowedFd}; 2 | 3 | use drm_sys::*; 4 | use rustix::ioctl::{ioctl, opcode, Getter, NoArg, Opcode, Setter, Updater}; 5 | 6 | macro_rules! ioctl_readwrite { 7 | ($name:ident, $ioty:expr, $nr:expr, $ty:ty) => { 8 | pub unsafe fn $name(fd: BorrowedFd, data: &mut $ty) -> io::Result<()> { 9 | const OPCODE: Opcode = opcode::read_write::<$ty>($ioty, $nr); 10 | Ok(ioctl(fd, Updater::::new(data))?) 11 | } 12 | }; 13 | } 14 | 15 | macro_rules! ioctl_read { 16 | ($name:ident, $ioty:expr, $nr:expr, $ty:ty) => { 17 | pub unsafe fn $name(fd: BorrowedFd) -> io::Result<$ty> { 18 | const OPCODE: Opcode = opcode::read::<$ty>($ioty, $nr); 19 | Ok(ioctl(fd, Getter::::new())?) 20 | } 21 | }; 22 | } 23 | 24 | macro_rules! ioctl_write_ptr { 25 | ($name:ident, $ioty:expr, $nr:expr, $ty:ty) => { 26 | pub unsafe fn $name(fd: BorrowedFd, data: &$ty) -> io::Result<()> { 27 | const OPCODE: Opcode = opcode::write::<$ty>($ioty, $nr); 28 | Ok(ioctl(fd, Setter::::new(*data))?) 29 | } 30 | }; 31 | } 32 | 33 | macro_rules! ioctl_none { 34 | ($name:ident, $ioty:expr, $nr:expr) => { 35 | pub unsafe fn $name(fd: BorrowedFd) -> io::Result<()> { 36 | const OPCODE: Opcode = opcode::none($ioty, $nr); 37 | Ok(ioctl(fd, NoArg::::new())?) 38 | } 39 | }; 40 | } 41 | 42 | /// Gets the bus ID of the device 43 | /// 44 | /// # Locks DRM mutex: Yes 45 | /// # Permissions: None 46 | /// # Nodes: Primary 47 | ioctl_readwrite!(get_bus_id, DRM_IOCTL_BASE, 0x01, drm_unique); 48 | 49 | /// Get information about the client 50 | /// 51 | /// # Locks DRM mutex: No 52 | /// # Permissions: None 53 | /// # Nodes: Primary 54 | ioctl_readwrite!(get_client, DRM_IOCTL_BASE, 0x05, drm_client); 55 | 56 | /// Get capabilities of the device. 57 | /// 58 | /// # Locks DRM mutex: No 59 | /// # Permissions: None 60 | /// # Nodes: Primary, Render 61 | ioctl_readwrite!(get_cap, DRM_IOCTL_BASE, 0x0c, drm_get_cap); 62 | 63 | /// Tells the device we understand a capability 64 | /// 65 | /// # Locks DRM mutex: Yes 66 | /// # Permissions: None 67 | /// # Nodes: Primary 68 | ioctl_write_ptr!(set_cap, DRM_IOCTL_BASE, 0x0d, drm_set_client_cap); 69 | 70 | /// Sets the requested interface version 71 | /// 72 | /// # Locks DRM mutex: Yes 73 | /// # Permissions: Master 74 | /// # Nodes: Primary, control 75 | ioctl_readwrite!(set_version, DRM_IOCTL_BASE, 0x07, drm_set_version); 76 | 77 | /// Gets the current interface version 78 | /// 79 | /// # Locks DRM mutex: No 80 | /// # Permissions: None 81 | /// # Nodes: All 82 | ioctl_readwrite!(get_version, DRM_IOCTL_BASE, 0x00, drm_version); 83 | 84 | /// Generates the client's authentication token 85 | /// 86 | /// # Locks DRM mutex: No 87 | /// # Permissions: None 88 | /// # Nodes: Primary 89 | ioctl_read!(get_token, DRM_IOCTL_BASE, 0x02, drm_auth); 90 | 91 | /// Authenticates a client via their authentication token 92 | /// 93 | /// # Locks DRM mutex: No 94 | /// # Permissions: Auth, Master 95 | /// # Nodes: Primary 96 | ioctl_write_ptr!(auth_token, DRM_IOCTL_BASE, 0x11, drm_auth); 97 | 98 | /// Acquires the DRM Master lock 99 | /// 100 | /// # Locks DRM mutex: No 101 | /// # Permissions: Root 102 | /// # Nodes: Primary 103 | ioctl_none!(acquire_master, DRM_IOCTL_BASE, 0x1e); 104 | 105 | /// Drops the DRM Master lock 106 | /// 107 | /// # Locks DRM mutex: No 108 | /// # Permissions: Root 109 | /// # Nodes: Primary 110 | ioctl_none!(release_master, DRM_IOCTL_BASE, 0x1f); 111 | 112 | /// Gets the IRQ number 113 | /// 114 | /// # Locks DRM mutex: No 115 | /// # Permissions: None 116 | /// # Nodes: Primary 117 | ioctl_readwrite!(get_irq_from_bus_id, DRM_IOCTL_BASE, 0x03, drm_irq_busid); 118 | 119 | /// Enable the vblank interrupt and sleep until the requested sequence occurs 120 | /// 121 | /// # Locks DRM mutex: No 122 | /// # Permissions: None 123 | /// # Nodes: Primary 124 | ioctl_readwrite!(wait_vblank, DRM_IOCTL_BASE, 0x3a, drm_wait_vblank); 125 | 126 | pub(crate) mod mode { 127 | use super::*; 128 | 129 | /// Modesetting resources 130 | ioctl_readwrite!(get_resources, DRM_IOCTL_BASE, 0xA0, drm_mode_card_res); 131 | 132 | ioctl_readwrite!( 133 | get_plane_resources, 134 | DRM_IOCTL_BASE, 135 | 0xB5, 136 | drm_mode_get_plane_res 137 | ); 138 | 139 | /// Connector related functions 140 | ioctl_readwrite!(get_connector, DRM_IOCTL_BASE, 0xA7, drm_mode_get_connector); 141 | 142 | /// Encoder related functions 143 | ioctl_readwrite!(get_encoder, DRM_IOCTL_BASE, 0xA6, drm_mode_get_encoder); 144 | 145 | /// CRTC related functions 146 | ioctl_readwrite!(get_crtc, DRM_IOCTL_BASE, 0xA1, drm_mode_crtc); 147 | ioctl_readwrite!(set_crtc, DRM_IOCTL_BASE, 0xA2, drm_mode_crtc); 148 | 149 | /// Gamma related functions 150 | ioctl_readwrite!(get_gamma, DRM_IOCTL_BASE, 0xA4, drm_mode_crtc_lut); 151 | ioctl_readwrite!(set_gamma, DRM_IOCTL_BASE, 0xA5, drm_mode_crtc_lut); 152 | 153 | // TODO: Figure out GAMMA LUT arrays 154 | 155 | /// FB related functions 156 | ioctl_readwrite!(get_fb, DRM_IOCTL_BASE, 0xAD, drm_mode_fb_cmd); 157 | ioctl_readwrite!(get_fb2, DRM_IOCTL_BASE, 0xCE, drm_mode_fb_cmd2); 158 | ioctl_readwrite!(add_fb, DRM_IOCTL_BASE, 0xAE, drm_mode_fb_cmd); 159 | ioctl_readwrite!(add_fb2, DRM_IOCTL_BASE, 0xB8, drm_mode_fb_cmd2); 160 | ioctl_readwrite!(rm_fb, DRM_IOCTL_BASE, 0xAF, c_uint); 161 | 162 | /// Plane related functions 163 | ioctl_readwrite!(get_plane, DRM_IOCTL_BASE, 0xB6, drm_mode_get_plane); 164 | 165 | ioctl_readwrite!(set_plane, DRM_IOCTL_BASE, 0xB7, drm_mode_set_plane); 166 | 167 | /// Dumbbuffer related functions 168 | ioctl_readwrite!(create_dumb, DRM_IOCTL_BASE, 0xB2, drm_mode_create_dumb); 169 | 170 | ioctl_readwrite!(map_dumb, DRM_IOCTL_BASE, 0xB3, drm_mode_map_dumb); 171 | 172 | ioctl_readwrite!(destroy_dumb, DRM_IOCTL_BASE, 0xB4, drm_mode_destroy_dumb); 173 | 174 | /// Cursor related functions 175 | ioctl_readwrite!(cursor, DRM_IOCTL_BASE, 0xA3, drm_mode_cursor); 176 | ioctl_readwrite!(cursor2, DRM_IOCTL_BASE, 0xBB, drm_mode_cursor2); 177 | 178 | /// Property related functions 179 | ioctl_readwrite!(get_property, DRM_IOCTL_BASE, 0xAA, drm_mode_get_property); 180 | 181 | ioctl_readwrite!( 182 | connector_set_property, 183 | DRM_IOCTL_BASE, 184 | 0xAB, 185 | drm_mode_connector_set_property 186 | ); 187 | 188 | ioctl_readwrite!( 189 | obj_get_properties, 190 | DRM_IOCTL_BASE, 191 | 0xB9, 192 | drm_mode_obj_get_properties 193 | ); 194 | 195 | ioctl_readwrite!( 196 | obj_set_property, 197 | DRM_IOCTL_BASE, 198 | 0xBA, 199 | drm_mode_obj_set_property 200 | ); 201 | 202 | /// Property blobs 203 | ioctl_readwrite!(get_blob, DRM_IOCTL_BASE, 0xAC, drm_mode_get_blob); 204 | 205 | // TODO: Property blobs probably require a large buffer 206 | 207 | ioctl_readwrite!(create_blob, DRM_IOCTL_BASE, 0xBD, drm_mode_create_blob); 208 | 209 | ioctl_readwrite!(destroy_blob, DRM_IOCTL_BASE, 0xBE, drm_mode_destroy_blob); 210 | 211 | /// Atomic modesetting related functions 212 | ioctl_readwrite!( 213 | crtc_page_flip, 214 | DRM_IOCTL_BASE, 215 | 0xB0, 216 | drm_mode_crtc_page_flip 217 | ); 218 | 219 | ioctl_readwrite!(dirty_fb, DRM_IOCTL_BASE, 0xB1, drm_mode_fb_dirty_cmd); 220 | 221 | ioctl_readwrite!(atomic, DRM_IOCTL_BASE, 0xBC, drm_mode_atomic); 222 | 223 | ioctl_readwrite!(create_lease, DRM_IOCTL_BASE, 0xC6, drm_mode_create_lease); 224 | ioctl_readwrite!(list_lessees, DRM_IOCTL_BASE, 0xC7, drm_mode_list_lessees); 225 | ioctl_readwrite!(get_lease, DRM_IOCTL_BASE, 0xC8, drm_mode_get_lease); 226 | ioctl_readwrite!(revoke_lease, DRM_IOCTL_BASE, 0xC9, drm_mode_revoke_lease); 227 | } 228 | 229 | pub(crate) mod gem { 230 | use super::*; 231 | 232 | /// GEM related functions 233 | ioctl_readwrite!(open, DRM_IOCTL_BASE, 0x0b, drm_gem_open); 234 | ioctl_write_ptr!(close, DRM_IOCTL_BASE, 0x09, drm_gem_close); 235 | 236 | /// Converts a buffer handle into a dma-buf file descriptor. 237 | ioctl_readwrite!(prime_handle_to_fd, DRM_IOCTL_BASE, 0x2d, drm_prime_handle); 238 | 239 | /// Converts a dma-buf file descriptor into a buffer handle. 240 | ioctl_readwrite!(prime_fd_to_handle, DRM_IOCTL_BASE, 0x2e, drm_prime_handle); 241 | } 242 | 243 | pub(crate) mod syncobj { 244 | use super::*; 245 | 246 | /// Creates a syncobj. 247 | ioctl_readwrite!(create, DRM_IOCTL_BASE, 0xBF, drm_syncobj_create); 248 | /// Destroys a syncobj. 249 | ioctl_readwrite!(destroy, DRM_IOCTL_BASE, 0xC0, drm_syncobj_destroy); 250 | /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. 251 | ioctl_readwrite!(handle_to_fd, DRM_IOCTL_BASE, 0xC1, drm_syncobj_handle); 252 | /// Imports a file descriptor exported by [`handle_to_fd`] back into a process-local handle. 253 | ioctl_readwrite!(fd_to_handle, DRM_IOCTL_BASE, 0xC2, drm_syncobj_handle); 254 | /// Waits for one or more syncobjs to become signalled. 255 | ioctl_readwrite!(wait, DRM_IOCTL_BASE, 0xC3, drm_syncobj_wait); 256 | /// Resets (un-signals) one or more syncobjs. 257 | ioctl_readwrite!(reset, DRM_IOCTL_BASE, 0xC4, drm_syncobj_array); 258 | /// Signals one or more syncobjs. 259 | ioctl_readwrite!(signal, DRM_IOCTL_BASE, 0xC5, drm_syncobj_array); 260 | 261 | /// Waits for one or more specific timeline syncobj points. 262 | ioctl_readwrite!( 263 | timeline_wait, 264 | DRM_IOCTL_BASE, 265 | 0xCA, 266 | drm_syncobj_timeline_wait 267 | ); 268 | /// Queries for state of one or more timeline syncobjs. 269 | ioctl_readwrite!(query, DRM_IOCTL_BASE, 0xCB, drm_syncobj_timeline_array); 270 | /// Transfers one timeline syncobj point to another. 271 | ioctl_readwrite!(transfer, DRM_IOCTL_BASE, 0xCC, drm_syncobj_transfer); 272 | /// Signals one or more specific timeline syncobj points. 273 | ioctl_readwrite!( 274 | timeline_signal, 275 | DRM_IOCTL_BASE, 276 | 0xCD, 277 | drm_syncobj_timeline_array 278 | ); 279 | /// Register an eventfd to be signalled by a syncobj. 280 | ioctl_readwrite!(eventfd, DRM_IOCTL_BASE, 0xCF, drm_syncobj_eventfd); 281 | } 282 | -------------------------------------------------------------------------------- /src/control/connector.rs: -------------------------------------------------------------------------------- 1 | //! # Connector 2 | //! 3 | //! Represents the physical output, such as a DisplayPort or VGA connector. 4 | //! 5 | //! A Connector is the physical connection between the display controller and 6 | //! a display. These objects keep track of connection information and state, 7 | //! including the modes that the current display supports. 8 | 9 | use crate::control; 10 | use drm_ffi as ffi; 11 | 12 | /// A handle to a connector 13 | #[repr(transparent)] 14 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 15 | pub struct Handle(control::RawResourceHandle); 16 | 17 | // Safety: Handle is repr(transparent) over NonZeroU32 18 | unsafe impl bytemuck::ZeroableInOption for Handle {} 19 | unsafe impl bytemuck::PodInOption for Handle {} 20 | 21 | impl From for control::RawResourceHandle { 22 | fn from(handle: Handle) -> Self { 23 | handle.0 24 | } 25 | } 26 | 27 | impl From for u32 { 28 | fn from(handle: Handle) -> Self { 29 | handle.0.into() 30 | } 31 | } 32 | 33 | impl From for Handle { 34 | fn from(handle: control::RawResourceHandle) -> Self { 35 | Handle(handle) 36 | } 37 | } 38 | 39 | impl control::ResourceHandle for Handle { 40 | const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_CONNECTOR; 41 | } 42 | 43 | impl std::fmt::Debug for Handle { 44 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 45 | f.debug_tuple("connector::Handle").field(&self.0).finish() 46 | } 47 | } 48 | 49 | /// Information about a connector 50 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 51 | pub struct Info { 52 | pub(crate) handle: Handle, 53 | pub(crate) interface: Interface, 54 | pub(crate) interface_id: u32, 55 | pub(crate) connection: State, 56 | pub(crate) size: Option<(u32, u32)>, 57 | pub(crate) modes: Vec, 58 | pub(crate) encoders: Vec, 59 | pub(crate) curr_enc: Option, 60 | pub(crate) subpixel: SubPixel, 61 | } 62 | 63 | impl std::fmt::Display for Info { 64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 | write!(f, "{}-{}", self.interface.as_str(), self.interface_id) 66 | } 67 | } 68 | 69 | impl Info { 70 | /// Returns the handle to this connector. 71 | pub fn handle(&self) -> Handle { 72 | self.handle 73 | } 74 | 75 | /// Returns the type of `Interface` of this connector. 76 | pub fn interface(&self) -> Interface { 77 | self.interface 78 | } 79 | 80 | /// Returns the interface ID of this connector. 81 | /// 82 | /// When multiple connectors have the same `Interface`, they will have 83 | /// different interface IDs. 84 | pub fn interface_id(&self) -> u32 { 85 | self.interface_id 86 | } 87 | 88 | /// Returns the `State` of this connector. 89 | pub fn state(&self) -> State { 90 | self.connection 91 | } 92 | 93 | /// Returns the size of the display (in millimeters) if connected. 94 | pub fn size(&self) -> Option<(u32, u32)> { 95 | self.size 96 | } 97 | 98 | /// Returns a list of encoders that can be possibly used by this connector. 99 | pub fn encoders(&self) -> &[control::encoder::Handle] { 100 | &self.encoders 101 | } 102 | 103 | /// Returns a list of modes this connector reports as supported. 104 | pub fn modes(&self) -> &[control::Mode] { 105 | &self.modes 106 | } 107 | 108 | /// Returns the current encoder attached to this connector. 109 | pub fn current_encoder(&self) -> Option { 110 | self.curr_enc 111 | } 112 | 113 | /// Subpixel order of the connected sink 114 | pub fn subpixel(&self) -> SubPixel { 115 | self.subpixel 116 | } 117 | } 118 | 119 | /// A physical interface type. 120 | #[allow(missing_docs)] 121 | #[allow(clippy::upper_case_acronyms)] 122 | #[non_exhaustive] 123 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 124 | pub enum Interface { 125 | Unknown, 126 | VGA, 127 | DVII, 128 | DVID, 129 | DVIA, 130 | Composite, 131 | SVideo, 132 | LVDS, 133 | Component, 134 | NinePinDIN, 135 | DisplayPort, 136 | HDMIA, 137 | HDMIB, 138 | TV, 139 | EmbeddedDisplayPort, 140 | Virtual, 141 | DSI, 142 | DPI, 143 | Writeback, 144 | SPI, 145 | USB, 146 | } 147 | 148 | impl Interface { 149 | /// Get interface name as string 150 | pub fn as_str(&self) -> &'static str { 151 | // source: https://github.com/torvalds/linux/blob/489fa31ea873282b41046d412ec741f93946fc2d/drivers/gpu/drm/drm_connector.c#L89 152 | match self { 153 | Interface::Unknown => "Unknown", 154 | Interface::VGA => "VGA", 155 | Interface::DVII => "DVI-I", 156 | Interface::DVID => "DVI-D", 157 | Interface::DVIA => "DVI-A", 158 | Interface::Composite => "Composite", 159 | Interface::SVideo => "SVIDEO", 160 | Interface::LVDS => "LVDS", 161 | Interface::Component => "Component", 162 | Interface::NinePinDIN => "DIN", 163 | Interface::DisplayPort => "DP", 164 | Interface::HDMIA => "HDMI-A", 165 | Interface::HDMIB => "HDMI-B", 166 | Interface::TV => "TV", 167 | Interface::EmbeddedDisplayPort => "eDP", 168 | Interface::Virtual => "Virtual", 169 | Interface::DSI => "DSI", 170 | Interface::DPI => "DPI", 171 | Interface::Writeback => "Writeback", 172 | Interface::SPI => "SPI", 173 | Interface::USB => "USB", 174 | } 175 | } 176 | } 177 | 178 | impl From for Interface { 179 | fn from(n: u32) -> Self { 180 | match n { 181 | ffi::DRM_MODE_CONNECTOR_Unknown => Interface::Unknown, 182 | ffi::DRM_MODE_CONNECTOR_VGA => Interface::VGA, 183 | ffi::DRM_MODE_CONNECTOR_DVII => Interface::DVII, 184 | ffi::DRM_MODE_CONNECTOR_DVID => Interface::DVID, 185 | ffi::DRM_MODE_CONNECTOR_DVIA => Interface::DVIA, 186 | ffi::DRM_MODE_CONNECTOR_Composite => Interface::Composite, 187 | ffi::DRM_MODE_CONNECTOR_SVIDEO => Interface::SVideo, 188 | ffi::DRM_MODE_CONNECTOR_LVDS => Interface::LVDS, 189 | ffi::DRM_MODE_CONNECTOR_Component => Interface::Component, 190 | ffi::DRM_MODE_CONNECTOR_9PinDIN => Interface::NinePinDIN, 191 | ffi::DRM_MODE_CONNECTOR_DisplayPort => Interface::DisplayPort, 192 | ffi::DRM_MODE_CONNECTOR_HDMIA => Interface::HDMIA, 193 | ffi::DRM_MODE_CONNECTOR_HDMIB => Interface::HDMIB, 194 | ffi::DRM_MODE_CONNECTOR_TV => Interface::TV, 195 | ffi::DRM_MODE_CONNECTOR_eDP => Interface::EmbeddedDisplayPort, 196 | ffi::DRM_MODE_CONNECTOR_VIRTUAL => Interface::Virtual, 197 | ffi::DRM_MODE_CONNECTOR_DSI => Interface::DSI, 198 | ffi::DRM_MODE_CONNECTOR_DPI => Interface::DPI, 199 | ffi::DRM_MODE_CONNECTOR_WRITEBACK => Interface::Writeback, 200 | ffi::DRM_MODE_CONNECTOR_SPI => Interface::SPI, 201 | ffi::DRM_MODE_CONNECTOR_USB => Interface::USB, 202 | _ => Interface::Unknown, 203 | } 204 | } 205 | } 206 | 207 | impl From for u32 { 208 | fn from(interface: Interface) -> Self { 209 | match interface { 210 | Interface::Unknown => ffi::DRM_MODE_CONNECTOR_Unknown, 211 | Interface::VGA => ffi::DRM_MODE_CONNECTOR_VGA, 212 | Interface::DVII => ffi::DRM_MODE_CONNECTOR_DVII, 213 | Interface::DVID => ffi::DRM_MODE_CONNECTOR_DVID, 214 | Interface::DVIA => ffi::DRM_MODE_CONNECTOR_DVIA, 215 | Interface::Composite => ffi::DRM_MODE_CONNECTOR_Composite, 216 | Interface::SVideo => ffi::DRM_MODE_CONNECTOR_SVIDEO, 217 | Interface::LVDS => ffi::DRM_MODE_CONNECTOR_LVDS, 218 | Interface::Component => ffi::DRM_MODE_CONNECTOR_Component, 219 | Interface::NinePinDIN => ffi::DRM_MODE_CONNECTOR_9PinDIN, 220 | Interface::DisplayPort => ffi::DRM_MODE_CONNECTOR_DisplayPort, 221 | Interface::HDMIA => ffi::DRM_MODE_CONNECTOR_HDMIA, 222 | Interface::HDMIB => ffi::DRM_MODE_CONNECTOR_HDMIB, 223 | Interface::TV => ffi::DRM_MODE_CONNECTOR_TV, 224 | Interface::EmbeddedDisplayPort => ffi::DRM_MODE_CONNECTOR_eDP, 225 | Interface::Virtual => ffi::DRM_MODE_CONNECTOR_VIRTUAL, 226 | Interface::DSI => ffi::DRM_MODE_CONNECTOR_DSI, 227 | Interface::DPI => ffi::DRM_MODE_CONNECTOR_DPI, 228 | Interface::Writeback => ffi::DRM_MODE_CONNECTOR_WRITEBACK, 229 | Interface::SPI => ffi::DRM_MODE_CONNECTOR_SPI, 230 | Interface::USB => ffi::DRM_MODE_CONNECTOR_USB, 231 | } 232 | } 233 | } 234 | 235 | /// The state of a connector. 236 | #[allow(missing_docs)] 237 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 238 | pub enum State { 239 | Connected, 240 | Disconnected, 241 | Unknown, 242 | } 243 | 244 | impl From for State { 245 | fn from(n: u32) -> Self { 246 | // These variables are not defined in drm_mode.h for some reason. 247 | // They were copied from libdrm's xf86DrmMode.h 248 | match n { 249 | 1 => State::Connected, 250 | 2 => State::Disconnected, 251 | _ => State::Unknown, 252 | } 253 | } 254 | } 255 | 256 | impl From for u32 { 257 | fn from(state: State) -> Self { 258 | // These variables are not defined in drm_mode.h for some reason. 259 | // They were copied from libdrm's xf86DrmMode.h 260 | match state { 261 | State::Connected => 1, 262 | State::Disconnected => 2, 263 | State::Unknown => 3, 264 | } 265 | } 266 | } 267 | 268 | /// Subpixel order of the connected sink 269 | #[non_exhaustive] 270 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 271 | pub enum SubPixel { 272 | /// Unknown geometry 273 | Unknown, 274 | /// Horizontal RGB 275 | HorizontalRgb, 276 | /// Horizontal BGR 277 | HorizontalBgr, 278 | /// Vertical RGB 279 | VerticalRgb, 280 | /// Vertical BGR 281 | VerticalBgr, 282 | /// No geometry 283 | None, 284 | /// Encountered value not supported by drm-rs 285 | NotImplemented, 286 | } 287 | 288 | impl SubPixel { 289 | pub(super) fn from_raw(n: u32) -> Self { 290 | // These values are not defined in drm_mode.h 291 | // They were copied from linux's drm_connector.h 292 | // Don't mistake those for ones used in xf86DrmMode.h (libdrm offsets those by 1) 293 | match n { 294 | 0 => Self::Unknown, 295 | 1 => Self::HorizontalRgb, 296 | 2 => Self::HorizontalBgr, 297 | 3 => Self::VerticalRgb, 298 | 4 => Self::VerticalBgr, 299 | 5 => Self::None, 300 | _ => Self::NotImplemented, 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/control/property.rs: -------------------------------------------------------------------------------- 1 | //! # Property 2 | //! 3 | //! A property of a modesetting resource. 4 | //! 5 | //! All modesetting resources have a set of properties that have values that 6 | //! can be modified. These properties are modesetting resources themselves, and 7 | //! may even have their own set of properties. 8 | //! 9 | //! Properties may have mutable values attached to them. These can be changed by 10 | //! either changing the state of a resource (thereby affecting the property), 11 | //! directly changing the property value itself, or by batching property changes 12 | //! together and executing them all atomically. 13 | 14 | use crate::control::{RawResourceHandle, ResourceHandle}; 15 | use drm_ffi as ffi; 16 | 17 | /// A raw property value that does not have a specific property type 18 | pub type RawValue = u64; 19 | 20 | /// A handle to a property 21 | #[repr(transparent)] 22 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 23 | pub struct Handle(RawResourceHandle); 24 | 25 | // Safety: Handle is repr(transparent) over NonZeroU32 26 | unsafe impl bytemuck::ZeroableInOption for Handle {} 27 | unsafe impl bytemuck::PodInOption for Handle {} 28 | 29 | impl From for RawResourceHandle { 30 | fn from(handle: Handle) -> Self { 31 | handle.0 32 | } 33 | } 34 | 35 | impl From for u32 { 36 | fn from(handle: Handle) -> Self { 37 | handle.0.into() 38 | } 39 | } 40 | 41 | impl From for Handle { 42 | fn from(handle: RawResourceHandle) -> Self { 43 | Handle(handle) 44 | } 45 | } 46 | 47 | impl ResourceHandle for Handle { 48 | const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_PROPERTY; 49 | } 50 | 51 | impl std::fmt::Debug for Handle { 52 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 53 | f.debug_tuple("property::Handle").field(&self.0).finish() 54 | } 55 | } 56 | 57 | /// Information about a property 58 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 59 | pub struct Info { 60 | pub(crate) handle: Handle, 61 | pub(crate) val_type: ValueType, 62 | pub(crate) mutable: bool, 63 | pub(crate) atomic: bool, 64 | pub(crate) info: ffi::drm_mode_get_property, 65 | } 66 | 67 | impl Info { 68 | /// Returns the handle to this property. 69 | pub fn handle(&self) -> Handle { 70 | self.handle 71 | } 72 | 73 | /// Returns the name of this property. 74 | pub fn name(&self) -> &std::ffi::CStr { 75 | unsafe { std::ffi::CStr::from_ptr(&self.info.name[0] as _) } 76 | } 77 | 78 | /// Returns the ValueType of this property. 79 | pub fn value_type(&self) -> &ValueType { 80 | &self.val_type 81 | } 82 | 83 | /// Returns whether this property is mutable. 84 | pub fn mutable(&self) -> bool { 85 | self.mutable 86 | } 87 | 88 | /// Returns whether this property can be atomically updated. 89 | pub fn atomic(&self) -> bool { 90 | self.atomic 91 | } 92 | } 93 | 94 | /// Describes the types of value that a property uses. 95 | #[allow(clippy::upper_case_acronyms)] 96 | #[allow(clippy::large_enum_variant)] 97 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 98 | pub enum ValueType { 99 | /// A catch-all for any unknown types 100 | Unknown, 101 | /// A True or False type 102 | Boolean, 103 | /// An unsigned integer that has a min and max value 104 | UnsignedRange(u64, u64), 105 | /// A signed integer that has a min and max value 106 | SignedRange(i64, i64), 107 | /// A set of values that are mutually exclusive 108 | Enum(EnumValues), 109 | /// A set of values that can be combined 110 | Bitmask, 111 | /// A chunk of binary data that must be acquired 112 | Blob, 113 | /// A non-specific DRM object 114 | Object, 115 | /// A CRTC object 116 | CRTC, 117 | /// A Connector object 118 | Connector, 119 | /// An Encoder object 120 | Encoder, 121 | /// A Framebuffer object 122 | Framebuffer, 123 | /// A Plane object 124 | Plane, 125 | /// A Property object 126 | Property, 127 | } 128 | 129 | impl ValueType { 130 | /// Given a [`RawValue`], convert it into a specific [`Value`] 131 | pub fn convert_value(&self, value: RawValue) -> Value<'_> { 132 | match self { 133 | ValueType::Unknown => Value::Unknown(value), 134 | ValueType::Boolean => Value::Boolean(value != 0), 135 | ValueType::UnsignedRange(_, _) => Value::UnsignedRange(value), 136 | ValueType::SignedRange(_, _) => Value::SignedRange(value as i64), 137 | ValueType::Enum(values) => Value::Enum(values.get_value_from_raw_value(value)), 138 | ValueType::Bitmask => Value::Bitmask(value), 139 | ValueType::Blob => Value::Blob(value), 140 | ValueType::Object => Value::Object(bytemuck::cast(value as u32)), 141 | ValueType::CRTC => Value::CRTC(bytemuck::cast(value as u32)), 142 | ValueType::Connector => Value::Connector(bytemuck::cast(value as u32)), 143 | ValueType::Encoder => Value::Encoder(bytemuck::cast(value as u32)), 144 | ValueType::Framebuffer => Value::Framebuffer(bytemuck::cast(value as u32)), 145 | ValueType::Plane => Value::Plane(bytemuck::cast(value as u32)), 146 | ValueType::Property => Value::Property(bytemuck::cast(value as u32)), 147 | } 148 | } 149 | } 150 | 151 | /// The value of a property, in a typed format 152 | #[allow(missing_docs)] 153 | #[allow(clippy::upper_case_acronyms)] 154 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 155 | pub enum Value<'a> { 156 | /// Unknown value 157 | Unknown(RawValue), 158 | /// Boolean value 159 | Boolean(bool), 160 | /// Unsigned range value 161 | UnsignedRange(u64), 162 | /// Signed range value 163 | SignedRange(i64), 164 | /// Enum Value 165 | Enum(Option<&'a EnumValue>), 166 | /// Bitmask value 167 | Bitmask(u64), 168 | /// Opaque (blob) value 169 | Blob(u64), 170 | /// Unknown object value 171 | Object(Option), 172 | /// Crtc object value 173 | CRTC(Option), 174 | /// Connector object value 175 | Connector(Option), 176 | /// Encoder object value 177 | Encoder(Option), 178 | /// Framebuffer object value 179 | Framebuffer(Option), 180 | /// Plane object value 181 | Plane(Option), 182 | /// Property object value 183 | Property(Option), 184 | } 185 | 186 | impl<'a> From> for RawValue { 187 | fn from(value: Value<'a>) -> Self { 188 | match value { 189 | Value::Unknown(x) => x, 190 | Value::Boolean(true) => 1, 191 | Value::Boolean(false) => 0, 192 | Value::UnsignedRange(x) => x, 193 | Value::SignedRange(x) => x as u64, 194 | Value::Enum(val) => val.map_or(0, EnumValue::value), 195 | Value::Bitmask(x) => x, 196 | Value::Blob(x) => x, 197 | Value::Object(x) => bytemuck::cast::<_, u32>(x) as u64, 198 | Value::CRTC(x) => bytemuck::cast::<_, u32>(x) as u64, 199 | Value::Connector(x) => bytemuck::cast::<_, u32>(x) as u64, 200 | Value::Encoder(x) => bytemuck::cast::<_, u32>(x) as u64, 201 | Value::Framebuffer(x) => bytemuck::cast::<_, u32>(x) as u64, 202 | Value::Plane(x) => bytemuck::cast::<_, u32>(x) as u64, 203 | Value::Property(x) => bytemuck::cast::<_, u32>(x) as u64, 204 | } 205 | } 206 | } 207 | 208 | macro_rules! match_variant { 209 | ($this:ident, $variant:ident) => { 210 | if let Self::$variant(v) = *$this { 211 | Some(v) 212 | } else { 213 | None 214 | } 215 | }; 216 | } 217 | 218 | impl<'a> Value<'a> { 219 | /// Boolean value 220 | pub fn as_boolean(&self) -> Option { 221 | match_variant!(self, Boolean) 222 | } 223 | 224 | /// Unsigned range value 225 | pub fn as_unsigned_range(&self) -> Option { 226 | match_variant!(self, UnsignedRange) 227 | } 228 | 229 | /// Signed range value 230 | pub fn as_signed_range(&self) -> Option { 231 | match_variant!(self, SignedRange) 232 | } 233 | 234 | /// Enum Value 235 | pub fn as_enum(&self) -> Option<&'a EnumValue> { 236 | match_variant!(self, Enum).flatten() 237 | } 238 | 239 | /// Bitmask value 240 | pub fn as_bitmask(&self) -> Option { 241 | match_variant!(self, Bitmask) 242 | } 243 | 244 | /// Opaque (blob) value 245 | pub fn as_blob(&self) -> Option { 246 | match_variant!(self, Blob) 247 | } 248 | 249 | /// Unknown object value 250 | pub fn as_object(&self) -> Option { 251 | match_variant!(self, Object).flatten() 252 | } 253 | 254 | /// Crtc object value 255 | pub fn as_crtc(&self) -> Option { 256 | match_variant!(self, CRTC).flatten() 257 | } 258 | 259 | /// Connector object value 260 | pub fn as_connector(&self) -> Option { 261 | match_variant!(self, Connector).flatten() 262 | } 263 | 264 | /// Encoder object value 265 | pub fn as_encoder(&self) -> Option { 266 | match_variant!(self, Encoder).flatten() 267 | } 268 | 269 | /// Framebuffer object value 270 | pub fn as_framebuffer(&self) -> Option { 271 | match_variant!(self, Framebuffer).flatten() 272 | } 273 | 274 | /// Plane object value 275 | pub fn as_plane(&self) -> Option { 276 | match_variant!(self, Plane).flatten() 277 | } 278 | 279 | /// Property object value 280 | pub fn as_property(&self) -> Option { 281 | match_variant!(self, Property).flatten() 282 | } 283 | } 284 | 285 | /// A single value of [`ValueType::Enum`] type 286 | #[repr(transparent)] 287 | #[derive(Copy, Clone, Hash, PartialEq, Eq, bytemuck::TransparentWrapper)] 288 | pub struct EnumValue(ffi::drm_mode_property_enum); 289 | 290 | impl EnumValue { 291 | /// Returns the [`RawValue`] of this value 292 | pub fn value(&self) -> RawValue { 293 | self.0.value 294 | } 295 | 296 | /// Returns the name of this value 297 | pub fn name(&self) -> &std::ffi::CStr { 298 | unsafe { std::ffi::CStr::from_ptr(&self.0.name[0] as _) } 299 | } 300 | } 301 | 302 | impl From for EnumValue { 303 | fn from(inner: ffi::drm_mode_property_enum) -> Self { 304 | EnumValue(inner) 305 | } 306 | } 307 | 308 | impl std::fmt::Debug for EnumValue { 309 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 310 | f.debug_struct("EnumValue") 311 | .field("value", &self.value()) 312 | .field("name", &self.name()) 313 | .finish() 314 | } 315 | } 316 | 317 | /// A set of [`EnumValue`]s for a single property 318 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 319 | pub struct EnumValues { 320 | pub(crate) values: Vec, 321 | pub(crate) enums: Vec, 322 | } 323 | 324 | impl EnumValues { 325 | /// Returns a tuple containing slices to the [`RawValue`]s and the [`EnumValue`]s 326 | pub fn values(&self) -> (&[RawValue], &[EnumValue]) { 327 | (&self.values, &self.enums) 328 | } 329 | 330 | /// Returns an [`EnumValue`] for a [`RawValue`], or [`None`] if `value` is 331 | /// not part of this [`EnumValues`]. 332 | pub fn get_value_from_raw_value(&self, value: RawValue) -> Option<&EnumValue> { 333 | let (values, enums) = self.values(); 334 | let index = if values.get(value as usize) == Some(&value) { 335 | // Early-out: indices match values 336 | value as usize 337 | } else { 338 | values.iter().position(|&v| v == value)? 339 | }; 340 | Some(&enums[index]) 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /src/node/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module for abstractions on drm device nodes. 2 | 3 | pub mod constants; 4 | 5 | use std::error::Error; 6 | use std::fmt::{self, Debug, Display, Formatter}; 7 | use std::io; 8 | use std::os::unix::io::AsFd; 9 | use std::path::{Path, PathBuf}; 10 | 11 | use rustix::fs::{fstat, major, minor, stat, Dev as dev_t, Stat}; 12 | 13 | use crate::node::constants::*; 14 | 15 | /// A node which refers to a DRM device. 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 17 | pub struct DrmNode { 18 | dev: dev_t, 19 | ty: NodeType, 20 | } 21 | 22 | impl DrmNode { 23 | /// Creates a DRM node from an open drm device. 24 | pub fn from_file(file: A) -> Result { 25 | let stat = fstat(file).map_err(Into::::into)?; 26 | DrmNode::from_stat(stat) 27 | } 28 | 29 | /// Creates a DRM node from path. 30 | pub fn from_path>(path: A) -> Result { 31 | let stat = stat(path.as_ref()).map_err(Into::::into)?; 32 | DrmNode::from_stat(stat) 33 | } 34 | 35 | /// Creates a DRM node from a file stat. 36 | pub fn from_stat(stat: Stat) -> Result { 37 | let dev = stat.st_rdev; 38 | DrmNode::from_dev_id(dev) 39 | } 40 | 41 | /// Creates a DRM node from a [`dev_t`]. 42 | pub fn from_dev_id(dev: dev_t) -> Result { 43 | if !is_device_drm(dev) { 44 | return Err(CreateDrmNodeError::NotDrmNode); 45 | } 46 | 47 | let ty = NodeType::from_dev_id(dev)?; 48 | 49 | Ok(DrmNode { dev, ty }) 50 | } 51 | 52 | /// Returns the type of the DRM node. 53 | pub fn ty(&self) -> NodeType { 54 | self.ty 55 | } 56 | 57 | /// Returns the device_id of the underlying DRM node. 58 | pub fn dev_id(&self) -> dev_t { 59 | self.dev 60 | } 61 | 62 | /// Returns the path of the open device if possible. 63 | pub fn dev_path(&self) -> Option { 64 | node_path(self, self.ty).ok() 65 | } 66 | 67 | /// Returns the path of the specified node type matching the device, if available. 68 | pub fn dev_path_with_type(&self, ty: NodeType) -> Option { 69 | node_path(self, ty).ok() 70 | } 71 | 72 | /// Returns a new node of the specified node type matching the device, if available. 73 | pub fn node_with_type(&self, ty: NodeType) -> Option> { 74 | self.dev_path_with_type(ty).map(DrmNode::from_path) 75 | } 76 | 77 | /// Returns the major device number of the DRM device. 78 | pub fn major(&self) -> u32 { 79 | major(self.dev_id()) 80 | } 81 | 82 | /// Returns the minor device number of the DRM device. 83 | pub fn minor(&self) -> u32 { 84 | minor(self.dev_id()) 85 | } 86 | 87 | /// Returns whether the DRM device has render nodes. 88 | pub fn has_render(&self) -> bool { 89 | #[cfg(target_os = "linux")] 90 | { 91 | node_path(self, NodeType::Render).is_ok() 92 | } 93 | 94 | // TODO: More robust checks on non-linux. 95 | 96 | #[cfg(target_os = "freebsd")] 97 | { 98 | false 99 | } 100 | 101 | #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] 102 | { 103 | false 104 | } 105 | } 106 | } 107 | 108 | impl Display for DrmNode { 109 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 110 | write!(f, "{}{}", self.ty.minor_name_prefix(), minor(self.dev_id())) 111 | } 112 | } 113 | 114 | /// A type of node 115 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 116 | pub enum NodeType { 117 | /// A primary node may be used to allocate buffers. 118 | /// 119 | /// If no other node is present, this may be used to post a buffer to an output with mode-setting. 120 | Primary, 121 | 122 | /// A control node may be used for mode-setting. 123 | /// 124 | /// This is almost never used since no DRM API for control nodes is available yet. 125 | Control, 126 | 127 | /// A render node may be used by a client to allocate buffers. 128 | /// 129 | /// Mode-setting is not possible with a render node. 130 | Render, 131 | } 132 | 133 | impl NodeType { 134 | /// Bit-offset of [`NodeType`] inside [`minor()`] 135 | const MINOR_OFFSET: u32 = 6; 136 | /// Mask of [`NodeType`] inside [`minor()`] 137 | #[cfg(not(target_os = "linux"))] 138 | const MINOR_MASK: u32 = 0b11 << Self::MINOR_OFFSET; 139 | 140 | /// Returns a string representing the prefix of a minor device's name. 141 | /// 142 | /// For example, on Linux with a primary node, the returned string would be `card`. 143 | pub fn minor_name_prefix(&self) -> &'static str { 144 | match self { 145 | NodeType::Primary => PRIMARY_NAME, 146 | NodeType::Control => CONTROL_NAME, 147 | NodeType::Render => RENDER_NAME, 148 | } 149 | } 150 | 151 | fn from_dev_id(dev: dev_t) -> Result { 152 | // The type of the DRM node is determined by the minor number ranges: 153 | // 0 - 63 -> Primary 154 | // 64 - 127 -> Control 155 | // 128 - 255 -> Render 156 | Ok(match minor(dev) >> Self::MINOR_OFFSET { 157 | 0 => Self::Primary, 158 | 1 => Self::Control, 159 | 2 => Self::Render, 160 | _ => return Err(CreateDrmNodeError::NotDrmNode), 161 | }) 162 | } 163 | 164 | #[cfg(not(target_os = "linux"))] 165 | fn minor_index(&self) -> u32 { 166 | match self { 167 | NodeType::Primary => 0, 168 | NodeType::Control => 1, 169 | NodeType::Render => 2, 170 | } 171 | } 172 | 173 | /// Returns the value to place at [`Self::MINOR_MASK`] 174 | #[cfg(not(target_os = "linux"))] 175 | fn minor_base(&self) -> u32 { 176 | self.minor_index() << Self::MINOR_OFFSET 177 | } 178 | } 179 | 180 | impl Display for NodeType { 181 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 182 | Debug::fmt(self, f) 183 | } 184 | } 185 | 186 | /// An error that may occur when creating a [`DrmNode`] from a file descriptor. 187 | #[derive(Debug)] 188 | pub enum CreateDrmNodeError { 189 | /// Some underlying IO error occured while trying to create a DRM node. 190 | Io(io::Error), 191 | 192 | /// The provided file descriptor does not refer to a DRM node. 193 | NotDrmNode, 194 | } 195 | 196 | impl Display for CreateDrmNodeError { 197 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 198 | match self { 199 | Self::Io(err) => Display::fmt(err, f), 200 | Self::NotDrmNode => { 201 | f.write_str("the provided file descriptor does not refer to a DRM node") 202 | } 203 | } 204 | } 205 | } 206 | 207 | impl Error for CreateDrmNodeError { 208 | fn source(&self) -> Option<&(dyn Error + 'static)> { 209 | match self { 210 | Self::Io(err) => Some(err), 211 | Self::NotDrmNode => None, 212 | } 213 | } 214 | } 215 | 216 | impl From for CreateDrmNodeError { 217 | #[inline] 218 | fn from(err: io::Error) -> Self { 219 | CreateDrmNodeError::Io(err) 220 | } 221 | } 222 | 223 | #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] 224 | fn devname(dev: dev_t) -> Option { 225 | use std::os::raw::c_char; 226 | 227 | // Matching value of SPECNAMELEN in FreeBSD 13+ 228 | let mut dev_name = vec![0u8; 255]; 229 | 230 | let buf: *mut c_char = unsafe { 231 | libc::devname_r( 232 | dev, 233 | libc::S_IFCHR, // Must be S_IFCHR or S_IFBLK 234 | dev_name.as_mut_ptr() as *mut c_char, 235 | dev_name.len() as _, 236 | ) 237 | }; 238 | 239 | // Buffer was too small (weird issue with the size of buffer) or the device could not be named. 240 | if buf.is_null() { 241 | return None; 242 | } 243 | 244 | // SAFETY: The buffer written to by devname_r is guaranteed to be NUL terminated. 245 | unsafe { dev_name.set_len(libc::strlen(buf)) }; 246 | 247 | Some(String::from_utf8(dev_name).expect("Returned device name is not valid utf8")) 248 | } 249 | 250 | /// Returns if the given device by major:minor pair is a DRM device. 251 | #[cfg(target_os = "linux")] 252 | pub fn is_device_drm(dev: dev_t) -> bool { 253 | // We `stat` the path rather than comparing the major to support dynamic device numbers: 254 | // https://gitlab.freedesktop.org/mesa/drm/-/commit/f8392583418aef5e27bfed9989aeb601e20cc96d 255 | let path = format!("/sys/dev/char/{}:{}/device/drm", major(dev), minor(dev)); 256 | stat(path.as_str()).is_ok() 257 | } 258 | 259 | /// Returns if the given device by major:minor pair is a DRM device. 260 | #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] 261 | pub fn is_device_drm(dev: dev_t) -> bool { 262 | devname(dev).map_or(false, |dev_name| { 263 | dev_name.starts_with("drm/") 264 | || dev_name.starts_with("dri/card") 265 | || dev_name.starts_with("dri/control") 266 | || dev_name.starts_with("dri/renderD") 267 | }) 268 | } 269 | 270 | /// Returns if the given device by major:minor pair is a DRM device. 271 | #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly")))] 272 | pub fn is_device_drm(dev: dev_t) -> bool { 273 | major(dev) == DRM_MAJOR 274 | } 275 | 276 | /// Returns the path of a specific type of node from the same DRM device as another path of the same node. 277 | pub fn path_to_type>(path: P, ty: NodeType) -> io::Result { 278 | let stat = stat(path.as_ref()).map_err(Into::::into)?; 279 | dev_path(stat.st_rdev, ty) 280 | } 281 | 282 | /// Returns the path of a specific type of node from the same DRM device as an existing [`DrmNode`]. 283 | pub fn node_path(node: &DrmNode, ty: NodeType) -> io::Result { 284 | dev_path(node.dev, ty) 285 | } 286 | 287 | /// Returns the path of a specific type of node from the DRM device described by major and minor device numbers. 288 | #[cfg(target_os = "linux")] 289 | pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result { 290 | use std::fs; 291 | use std::io::ErrorKind; 292 | 293 | if !is_device_drm(dev) { 294 | return Err(io::Error::new( 295 | ErrorKind::NotFound, 296 | format!("{}:{} is no DRM device", major(dev), minor(dev)), 297 | )); 298 | } 299 | 300 | let read = fs::read_dir(format!( 301 | "/sys/dev/char/{}:{}/device/drm", 302 | major(dev), 303 | minor(dev) 304 | ))?; 305 | 306 | for entry in read.flatten() { 307 | let name = entry.file_name(); 308 | let name = name.to_string_lossy(); 309 | 310 | // Only 1 primary, control and render node may exist simultaneously, so the 311 | // first occurrence is good enough. 312 | if name.starts_with(ty.minor_name_prefix()) { 313 | let path = Path::new("/dev/dri").join(&*name); 314 | if path.exists() { 315 | return Ok(path); 316 | } 317 | } 318 | } 319 | 320 | Err(io::Error::new( 321 | ErrorKind::NotFound, 322 | format!( 323 | "Could not find node of type {} from DRM device {}:{}", 324 | ty, 325 | major(dev), 326 | minor(dev) 327 | ), 328 | )) 329 | } 330 | 331 | /// Returns the path of a specific type of node from the DRM device described by major and minor device numbers. 332 | #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] 333 | pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result { 334 | // Based on libdrm `drmGetMinorNameForFD`. Should be updated if the code 335 | // there is replaced with anything more sensible... 336 | 337 | use std::io::ErrorKind; 338 | 339 | if !is_device_drm(dev) { 340 | return Err(io::Error::new( 341 | ErrorKind::NotFound, 342 | format!("{}:{} is no DRM device", major(dev), minor(dev)), 343 | )); 344 | } 345 | 346 | if let Some(dev_name) = devname(dev) { 347 | let suffix = dev_name.trim_start_matches(|c: char| !c.is_numeric()); 348 | if let Ok(old_id) = suffix.parse::() { 349 | let id = old_id & !NodeType::MINOR_MASK | ty.minor_base(); 350 | let path = PathBuf::from(format!("/dev/dri/{}{}", ty.minor_name_prefix(), id)); 351 | if path.exists() { 352 | return Ok(path); 353 | } 354 | } 355 | } 356 | 357 | Err(io::Error::new( 358 | ErrorKind::NotFound, 359 | format!( 360 | "Could not find node of type {} from DRM device {}:{}", 361 | ty, 362 | major(dev), 363 | minor(dev) 364 | ), 365 | )) 366 | } 367 | 368 | /// Returns the path of a specific type of node from the DRM device described by major and minor device numbers. 369 | #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly")))] 370 | pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result { 371 | use std::io::ErrorKind; 372 | 373 | if !is_device_drm(dev) { 374 | return Err(io::Error::new( 375 | ErrorKind::NotFound, 376 | format!("{}:{} is no DRM device", major(dev), minor(dev)), 377 | )); 378 | } 379 | 380 | let old_id = minor(dev); 381 | let id = old_id & !NodeType::MINOR_MASK | ty.minor_base(); 382 | let path = PathBuf::from(format!("/dev/dri/{}{}", ty.minor_name_prefix(), id)); 383 | if path.exists() { 384 | return Ok(path); 385 | } 386 | 387 | Err(io::Error::new( 388 | ErrorKind::NotFound, 389 | format!( 390 | "Could not find node of type {} for DRM device {}:{}", 391 | ty, 392 | major(dev), 393 | minor(dev) 394 | ), 395 | )) 396 | } 397 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A safe interface to the Direct Rendering Manager subsystem found in various 2 | //! operating systems. 3 | //! 4 | //! # Summary 5 | //! 6 | //! The Direct Rendering Manager (DRM) is subsystem found in various operating 7 | //! systems that exposes graphical functionality to userspace processes. It can 8 | //! be used to send data and commands to a GPU driver that implements the 9 | //! interface. 10 | //! 11 | //! Userspace processes can access the DRM by opening a 'device node' (usually 12 | //! found in `/dev/dri/*`) and using various `ioctl` commands on the open file 13 | //! descriptor. Most processes use the libdrm library (part of the mesa project) 14 | //! to execute these commands. This crate takes a more direct approach, 15 | //! bypassing libdrm and executing the commands directly and doing minimal 16 | //! abstraction to keep the interface safe. 17 | //! 18 | //! While the DRM subsystem exposes many powerful GPU interfaces, it is not 19 | //! recommended for rendering or GPGPU operations. There are many standards made 20 | //! for these use cases, and they are far more fitting for those sort of tasks. 21 | //! 22 | //! ## Usage 23 | //! 24 | //! To begin using this crate, the [`Device`] trait must be 25 | //! implemented. See the trait's [example section](trait@Device#example) for 26 | //! details on how to implement it. 27 | //! 28 | 29 | #![warn(missing_docs)] 30 | 31 | pub(crate) mod util; 32 | 33 | pub mod buffer; 34 | pub mod control; 35 | pub mod node; 36 | 37 | use std::ffi::{OsStr, OsString}; 38 | use std::time::Duration; 39 | use std::{ 40 | io, 41 | os::unix::{ffi::OsStringExt, io::AsFd}, 42 | }; 43 | 44 | use rustix::io::Errno; 45 | 46 | use crate::util::*; 47 | 48 | pub use drm_ffi::{DRM_CLOEXEC as CLOEXEC, DRM_RDWR as RDWR}; 49 | 50 | /// This trait should be implemented by any object that acts as a DRM device. It 51 | /// is a prerequisite for using any DRM functionality. 52 | /// 53 | /// This crate does not provide a concrete device object due to the various ways 54 | /// it can be implemented. The user of this crate is expected to implement it 55 | /// themselves and derive this trait as necessary. The example below 56 | /// demonstrates how to do this using a small wrapper. 57 | /// 58 | /// # Example 59 | /// 60 | /// ``` 61 | /// use drm::Device; 62 | /// 63 | /// use std::fs::File; 64 | /// use std::fs::OpenOptions; 65 | /// 66 | /// use std::os::unix::io::AsFd; 67 | /// use std::os::unix::io::BorrowedFd; 68 | /// 69 | /// #[derive(Debug)] 70 | /// /// A simple wrapper for a device node. 71 | /// struct Card(File); 72 | /// 73 | /// /// Implementing [`AsFd`] is a prerequisite to implementing the traits found 74 | /// /// in this crate. Here, we are just calling [`File::as_fd()`] on the inner 75 | /// /// [`File`]. 76 | /// impl AsFd for Card { 77 | /// fn as_fd(&self) -> BorrowedFd<'_> { 78 | /// self.0.as_fd() 79 | /// } 80 | /// } 81 | /// 82 | /// /// With [`AsFd`] implemented, we can now implement [`drm::Device`]. 83 | /// impl Device for Card {} 84 | /// 85 | /// impl Card { 86 | /// /// Simple helper method for opening a [`Card`]. 87 | /// fn open() -> Self { 88 | /// let mut options = OpenOptions::new(); 89 | /// options.read(true); 90 | /// options.write(true); 91 | /// 92 | /// // The normal location of the primary device node on Linux 93 | /// Card(options.open("/dev/dri/card0").unwrap()) 94 | /// } 95 | /// } 96 | /// ``` 97 | pub trait Device: AsFd { 98 | /// Acquires the DRM Master lock for this process. 99 | /// 100 | /// # Notes 101 | /// 102 | /// Acquiring the DRM Master is done automatically when the primary device 103 | /// node is opened. If you opened the primary device node and did not 104 | /// acquire the lock, another process likely has the lock. 105 | /// 106 | /// This function is only available to processes with CAP_SYS_ADMIN 107 | /// privileges (usually as root) 108 | fn acquire_master_lock(&self) -> io::Result<()> { 109 | drm_ffi::auth::acquire_master(self.as_fd())?; 110 | Ok(()) 111 | } 112 | 113 | /// Releases the DRM Master lock for another process to use. 114 | fn release_master_lock(&self) -> io::Result<()> { 115 | drm_ffi::auth::release_master(self.as_fd())?; 116 | Ok(()) 117 | } 118 | 119 | /// Generates an [`AuthToken`] for this process. 120 | #[deprecated(note = "Consider opening a render node instead.")] 121 | fn generate_auth_token(&self) -> io::Result { 122 | let token = drm_ffi::auth::get_magic_token(self.as_fd())?; 123 | Ok(AuthToken(token.magic)) 124 | } 125 | 126 | /// Authenticates an [`AuthToken`] from another process. 127 | fn authenticate_auth_token(&self, token: AuthToken) -> io::Result<()> { 128 | drm_ffi::auth::auth_magic_token(self.as_fd(), token.0)?; 129 | Ok(()) 130 | } 131 | 132 | /// Requests the driver to expose or hide certain capabilities. See 133 | /// [`ClientCapability`] for more information. 134 | fn set_client_capability(&self, cap: ClientCapability, enable: bool) -> io::Result<()> { 135 | drm_ffi::set_capability(self.as_fd(), cap as u64, enable)?; 136 | Ok(()) 137 | } 138 | 139 | /// Gets the bus ID of this device. 140 | fn get_bus_id(&self) -> io::Result { 141 | let mut buffer = Vec::new(); 142 | let _ = drm_ffi::get_bus_id(self.as_fd(), Some(&mut buffer))?; 143 | let bus_id = OsString::from_vec(buffer); 144 | 145 | Ok(bus_id) 146 | } 147 | 148 | /// Check to see if our [`AuthToken`] has been authenticated 149 | /// by the DRM Master 150 | fn authenticated(&self) -> io::Result { 151 | let client = drm_ffi::get_client(self.as_fd(), 0)?; 152 | Ok(client.auth == 1) 153 | } 154 | 155 | /// Gets the value of a capability. 156 | fn get_driver_capability(&self, cap: DriverCapability) -> io::Result { 157 | let cap = drm_ffi::get_capability(self.as_fd(), cap as u64)?; 158 | Ok(cap.value) 159 | } 160 | 161 | /// # Possible errors: 162 | /// - `EFAULT`: Kernel could not copy fields into userspace 163 | #[allow(missing_docs)] 164 | fn get_driver(&self) -> io::Result { 165 | let mut name = Vec::new(); 166 | let mut date = Vec::new(); 167 | let mut desc = Vec::new(); 168 | 169 | let v = drm_ffi::get_version( 170 | self.as_fd(), 171 | Some(&mut name), 172 | Some(&mut date), 173 | Some(&mut desc), 174 | )?; 175 | 176 | let version = (v.version_major, v.version_minor, v.version_patchlevel); 177 | let name = OsString::from_vec(unsafe { transmute_vec(name) }); 178 | let date = OsString::from_vec(unsafe { transmute_vec(date) }); 179 | let desc = OsString::from_vec(unsafe { transmute_vec(desc) }); 180 | 181 | let driver = Driver { 182 | version, 183 | name, 184 | date, 185 | desc, 186 | }; 187 | 188 | Ok(driver) 189 | } 190 | 191 | /// Waits for a vblank. 192 | fn wait_vblank( 193 | &self, 194 | target_sequence: VblankWaitTarget, 195 | flags: VblankWaitFlags, 196 | high_crtc: u32, 197 | user_data: usize, 198 | ) -> io::Result { 199 | use drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_HIGH_CRTC_MASK; 200 | use drm_ffi::_DRM_VBLANK_HIGH_CRTC_SHIFT; 201 | 202 | let high_crtc_mask = _DRM_VBLANK_HIGH_CRTC_MASK >> _DRM_VBLANK_HIGH_CRTC_SHIFT; 203 | if (high_crtc & !high_crtc_mask) != 0 { 204 | return Err(Errno::INVAL.into()); 205 | } 206 | 207 | let (sequence, wait_type) = match target_sequence { 208 | VblankWaitTarget::Absolute(n) => { 209 | (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_ABSOLUTE) 210 | } 211 | VblankWaitTarget::Relative(n) => { 212 | (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_RELATIVE) 213 | } 214 | }; 215 | 216 | let type_ = wait_type | (high_crtc << _DRM_VBLANK_HIGH_CRTC_SHIFT) | flags.bits(); 217 | let reply = drm_ffi::wait_vblank(self.as_fd(), type_, sequence, user_data)?; 218 | 219 | let time = match (reply.tval_sec, reply.tval_usec) { 220 | (0, 0) => None, 221 | (sec, usec) => Some(Duration::new(sec as u64, (usec * 1000) as u32)), 222 | }; 223 | 224 | Ok(VblankWaitReply { 225 | frame: reply.sequence, 226 | time, 227 | }) 228 | } 229 | } 230 | 231 | /// An authentication token, unique to the file descriptor of the device. 232 | /// 233 | /// This token can be sent to another process that owns the DRM Master lock to 234 | /// allow unprivileged use of the device, such as rendering. 235 | /// 236 | /// # Deprecation Notes 237 | /// 238 | /// This method of authentication is somewhat deprecated. Accessing unprivileged 239 | /// functionality is best done by opening a render node. However, some other 240 | /// processes may still use this method of authentication. Therefore, we still 241 | /// provide functionality for generating and authenticating these tokens. 242 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 243 | pub struct AuthToken(u32); 244 | 245 | /// Driver version of a device. 246 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 247 | pub struct Driver { 248 | /// Version of the driver in `(major, minor, patchlevel)` format 249 | pub version: (i32, i32, i32), 250 | /// Name of the driver 251 | pub name: OsString, 252 | /// Date driver was published 253 | pub date: OsString, 254 | /// Driver description 255 | pub desc: OsString, 256 | } 257 | 258 | impl Driver { 259 | /// Name of driver 260 | pub fn name(&self) -> &OsStr { 261 | self.name.as_ref() 262 | } 263 | 264 | /// Date driver was published 265 | pub fn date(&self) -> &OsStr { 266 | self.date.as_ref() 267 | } 268 | 269 | /// Driver description 270 | pub fn description(&self) -> &OsStr { 271 | self.desc.as_ref() 272 | } 273 | } 274 | 275 | /// Used to check which capabilities your graphics driver has. 276 | #[allow(clippy::upper_case_acronyms)] 277 | #[repr(u64)] 278 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 279 | pub enum DriverCapability { 280 | /// DumbBuffer support for scanout 281 | DumbBuffer = drm_ffi::DRM_CAP_DUMB_BUFFER as u64, 282 | /// Unknown 283 | VBlankHighCRTC = drm_ffi::DRM_CAP_VBLANK_HIGH_CRTC as u64, 284 | /// Preferred depth to use for dumb buffers 285 | DumbPreferredDepth = drm_ffi::DRM_CAP_DUMB_PREFERRED_DEPTH as u64, 286 | /// Unknown 287 | DumbPreferShadow = drm_ffi::DRM_CAP_DUMB_PREFER_SHADOW as u64, 288 | /// PRIME handles are supported 289 | Prime = drm_ffi::DRM_CAP_PRIME as u64, 290 | /// Unknown 291 | MonotonicTimestamp = drm_ffi::DRM_CAP_TIMESTAMP_MONOTONIC as u64, 292 | /// Asynchronous page flipping support 293 | ASyncPageFlip = drm_ffi::DRM_CAP_ASYNC_PAGE_FLIP as u64, 294 | /// Asynchronous page flipping support for atomic API 295 | AtomicASyncPageFlip = drm_ffi::DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP as u64, 296 | /// Width of cursor buffers 297 | CursorWidth = drm_ffi::DRM_CAP_CURSOR_WIDTH as u64, 298 | /// Height of cursor buffers 299 | CursorHeight = drm_ffi::DRM_CAP_CURSOR_HEIGHT as u64, 300 | /// Create framebuffers with modifiers 301 | AddFB2Modifiers = drm_ffi::DRM_CAP_ADDFB2_MODIFIERS as u64, 302 | /// Unknown 303 | PageFlipTarget = drm_ffi::DRM_CAP_PAGE_FLIP_TARGET as u64, 304 | /// Uses the CRTC's ID in vblank events 305 | CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64, 306 | /// SyncObj support 307 | SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64, 308 | /// Timeline SyncObj support 309 | TimelineSyncObj = drm_ffi::DRM_CAP_SYNCOBJ_TIMELINE as u64, 310 | } 311 | 312 | /// Used to enable/disable capabilities for the process. 313 | #[repr(u64)] 314 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 315 | pub enum ClientCapability { 316 | /// The driver provides 3D screen control 317 | Stereo3D = drm_ffi::DRM_CLIENT_CAP_STEREO_3D as u64, 318 | /// The driver provides more plane types for modesetting 319 | UniversalPlanes = drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64, 320 | /// The driver provides atomic modesetting 321 | Atomic = drm_ffi::DRM_CLIENT_CAP_ATOMIC as u64, 322 | /// If set to 1, the DRM core will provide aspect ratio information in modes. 323 | AspectRatio = drm_ffi::DRM_CLIENT_CAP_ASPECT_RATIO as u64, 324 | /// If set to 1, the DRM core will expose special connectors to be used for 325 | /// writing back to memory the scene setup in the commit. 326 | /// 327 | /// The client must enable [`Self::Atomic`] first. 328 | WritebackConnectors = drm_ffi::DRM_CLIENT_CAP_WRITEBACK_CONNECTORS as u64, 329 | /// Drivers for para-virtualized hardware have additional restrictions for cursor planes e.g. 330 | /// they need cursor planes to act like one would expect from a mouse 331 | /// cursor and have correctly set hotspot properties. 332 | /// If this client cap is not set the DRM core will hide cursor plane on 333 | /// those virtualized drivers because not setting it implies that the 334 | /// client is not capable of dealing with those extra restrictions. 335 | /// Clients which do set cursor hotspot and treat the cursor plane 336 | /// like a mouse cursor should set this property. 337 | /// 338 | /// The client must enable [`Self::Atomic`] first. 339 | CursorPlaneHotspot = drm_ffi::DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT as u64, 340 | } 341 | 342 | /// Used to specify a vblank sequence to wait for 343 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 344 | pub enum VblankWaitTarget { 345 | /// Wait for a specific vblank sequence number 346 | Absolute(u32), 347 | /// Wait for a given number of vblanks 348 | Relative(u32), 349 | } 350 | 351 | bitflags::bitflags! { 352 | /// Flags to alter the behaviour when waiting for a vblank 353 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 354 | pub struct VblankWaitFlags : u32 { 355 | /// Send event instead of blocking 356 | const EVENT = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_EVENT; 357 | /// If missed, wait for next vblank 358 | const NEXT_ON_MISS = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_NEXTONMISS; 359 | } 360 | } 361 | 362 | /// Data returned from a vblank wait 363 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 364 | pub struct VblankWaitReply { 365 | frame: u32, 366 | time: Option, 367 | } 368 | 369 | impl VblankWaitReply { 370 | /// Sequence of the frame 371 | pub fn frame(&self) -> u32 { 372 | self.frame 373 | } 374 | 375 | /// Time at which the vblank occurred. [`None`] if an asynchronous event was 376 | /// requested 377 | pub fn time(&self) -> Option { 378 | self.time 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /drm-ffi/src/mode.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Bindings to the DRM's modesetting capabilities. 3 | //! 4 | 5 | #![allow(clippy::too_many_arguments)] 6 | 7 | use crate::ioctl; 8 | use drm_sys::*; 9 | 10 | use std::{io, os::unix::io::BorrowedFd}; 11 | 12 | /// Enumerate most card resources. 13 | pub fn get_resources( 14 | fd: BorrowedFd<'_>, 15 | mut fbs: Option<&mut Vec>, 16 | mut crtcs: Option<&mut Vec>, 17 | mut connectors: Option<&mut Vec>, 18 | mut encoders: Option<&mut Vec>, 19 | ) -> io::Result { 20 | let mut sizes = drm_mode_card_res::default(); 21 | unsafe { 22 | ioctl::mode::get_resources(fd, &mut sizes)?; 23 | } 24 | 25 | map_reserve!(fbs, sizes.count_fbs as usize); 26 | map_reserve!(crtcs, sizes.count_crtcs as usize); 27 | map_reserve!(connectors, sizes.count_connectors as usize); 28 | map_reserve!(encoders, sizes.count_encoders as usize); 29 | 30 | let mut res = drm_mode_card_res { 31 | fb_id_ptr: map_ptr!(&fbs), 32 | crtc_id_ptr: map_ptr!(&crtcs), 33 | connector_id_ptr: map_ptr!(&connectors), 34 | encoder_id_ptr: map_ptr!(&encoders), 35 | count_fbs: map_len!(&fbs), 36 | count_crtcs: map_len!(&crtcs), 37 | count_connectors: map_len!(&connectors), 38 | count_encoders: map_len!(&encoders), 39 | ..Default::default() 40 | }; 41 | 42 | unsafe { 43 | ioctl::mode::get_resources(fd, &mut res)?; 44 | } 45 | 46 | map_set!(fbs, res.count_fbs as usize); 47 | map_set!(crtcs, res.count_crtcs as usize); 48 | map_set!(connectors, res.count_connectors as usize); 49 | map_set!(encoders, res.count_encoders as usize); 50 | 51 | Ok(res) 52 | } 53 | 54 | /// Enumerate plane resources. 55 | pub fn get_plane_resources( 56 | fd: BorrowedFd<'_>, 57 | mut planes: Option<&mut Vec>, 58 | ) -> io::Result { 59 | let mut sizes = drm_mode_get_plane_res::default(); 60 | unsafe { 61 | ioctl::mode::get_plane_resources(fd, &mut sizes)?; 62 | } 63 | 64 | if planes.is_none() { 65 | return Ok(sizes); 66 | } 67 | 68 | map_reserve!(planes, sizes.count_planes as usize); 69 | 70 | let mut res = drm_mode_get_plane_res { 71 | plane_id_ptr: map_ptr!(&planes), 72 | count_planes: sizes.count_planes, 73 | }; 74 | 75 | unsafe { 76 | ioctl::mode::get_plane_resources(fd, &mut res)?; 77 | } 78 | 79 | map_set!(planes, res.count_planes as usize); 80 | 81 | Ok(res) 82 | } 83 | 84 | /// Get info about a framebuffer. 85 | pub fn get_framebuffer(fd: BorrowedFd<'_>, fb_id: u32) -> io::Result { 86 | let mut info = drm_mode_fb_cmd { 87 | fb_id, 88 | ..Default::default() 89 | }; 90 | 91 | unsafe { 92 | ioctl::mode::get_fb(fd, &mut info)?; 93 | } 94 | 95 | Ok(info) 96 | } 97 | 98 | /// Add a new framebuffer. 99 | pub fn add_fb( 100 | fd: BorrowedFd<'_>, 101 | width: u32, 102 | height: u32, 103 | pitch: u32, 104 | bpp: u32, 105 | depth: u32, 106 | handle: u32, 107 | ) -> io::Result { 108 | let mut fb = drm_mode_fb_cmd { 109 | width, 110 | height, 111 | pitch, 112 | bpp, 113 | depth, 114 | handle, 115 | ..Default::default() 116 | }; 117 | 118 | unsafe { 119 | ioctl::mode::add_fb(fd, &mut fb)?; 120 | } 121 | 122 | Ok(fb) 123 | } 124 | 125 | /// Get info about a framebuffer (with modifiers). 126 | pub fn get_framebuffer2(fd: BorrowedFd<'_>, fb_id: u32) -> io::Result { 127 | let mut info = drm_mode_fb_cmd2 { 128 | fb_id, 129 | ..Default::default() 130 | }; 131 | 132 | unsafe { 133 | ioctl::mode::get_fb2(fd, &mut info)?; 134 | } 135 | 136 | Ok(info) 137 | } 138 | 139 | /// Add a new framebuffer (with modifiers) 140 | pub fn add_fb2( 141 | fd: BorrowedFd<'_>, 142 | width: u32, 143 | height: u32, 144 | fmt: u32, 145 | handles: &[u32; 4], 146 | pitches: &[u32; 4], 147 | offsets: &[u32; 4], 148 | modifier: &[u64; 4], 149 | flags: u32, 150 | ) -> io::Result { 151 | let mut fb = drm_mode_fb_cmd2 { 152 | width, 153 | height, 154 | pixel_format: fmt, 155 | flags, 156 | handles: *handles, 157 | pitches: *pitches, 158 | offsets: *offsets, 159 | modifier: *modifier, 160 | ..Default::default() 161 | }; 162 | 163 | unsafe { 164 | ioctl::mode::add_fb2(fd, &mut fb)?; 165 | } 166 | 167 | Ok(fb) 168 | } 169 | 170 | /// Remove a framebuffer. 171 | pub fn rm_fb(fd: BorrowedFd<'_>, mut id: u32) -> io::Result<()> { 172 | unsafe { 173 | ioctl::mode::rm_fb(fd, &mut id)?; 174 | } 175 | 176 | Ok(()) 177 | } 178 | 179 | /// Mark a framebuffer as dirty. 180 | pub fn dirty_fb( 181 | fd: BorrowedFd<'_>, 182 | fb_id: u32, 183 | clips: &[drm_clip_rect], 184 | ) -> io::Result { 185 | let mut dirty = drm_mode_fb_dirty_cmd { 186 | fb_id, 187 | num_clips: clips.len() as _, 188 | clips_ptr: clips.as_ptr() as _, 189 | ..Default::default() 190 | }; 191 | 192 | unsafe { 193 | ioctl::mode::dirty_fb(fd, &mut dirty)?; 194 | } 195 | 196 | Ok(dirty) 197 | } 198 | 199 | /// Get info about a CRTC 200 | pub fn get_crtc(fd: BorrowedFd<'_>, crtc_id: u32) -> io::Result { 201 | let mut info = drm_mode_crtc { 202 | crtc_id, 203 | ..Default::default() 204 | }; 205 | 206 | unsafe { 207 | ioctl::mode::get_crtc(fd, &mut info)?; 208 | } 209 | 210 | Ok(info) 211 | } 212 | 213 | /// Set CRTC state 214 | pub fn set_crtc( 215 | fd: BorrowedFd<'_>, 216 | crtc_id: u32, 217 | fb_id: u32, 218 | x: u32, 219 | y: u32, 220 | conns: &[u32], 221 | mode: Option, 222 | ) -> io::Result { 223 | let mut crtc = drm_mode_crtc { 224 | set_connectors_ptr: conns.as_ptr() as _, 225 | count_connectors: conns.len() as _, 226 | crtc_id, 227 | fb_id, 228 | x, 229 | y, 230 | mode_valid: match mode { 231 | Some(_) => 1, 232 | None => 0, 233 | }, 234 | mode: mode.unwrap_or_default(), 235 | ..Default::default() 236 | }; 237 | 238 | unsafe { 239 | ioctl::mode::set_crtc(fd, &mut crtc)?; 240 | } 241 | 242 | Ok(crtc) 243 | } 244 | 245 | /// Get CRTC gamma ramp 246 | pub fn get_gamma( 247 | fd: BorrowedFd<'_>, 248 | crtc_id: u32, 249 | size: usize, 250 | red: &mut [u16], 251 | green: &mut [u16], 252 | blue: &mut [u16], 253 | ) -> io::Result { 254 | let mut lut = drm_mode_crtc_lut { 255 | crtc_id, 256 | gamma_size: size as _, 257 | red: red.as_mut_ptr() as _, 258 | green: green.as_mut_ptr() as _, 259 | blue: blue.as_mut_ptr() as _, 260 | }; 261 | 262 | unsafe { 263 | ioctl::mode::get_gamma(fd, &mut lut)?; 264 | } 265 | 266 | Ok(lut) 267 | } 268 | 269 | /// Set CRTC gamma ramp 270 | pub fn set_gamma( 271 | fd: BorrowedFd<'_>, 272 | crtc_id: u32, 273 | size: usize, 274 | red: &[u16], 275 | green: &[u16], 276 | blue: &[u16], 277 | ) -> io::Result { 278 | let mut lut = drm_mode_crtc_lut { 279 | crtc_id, 280 | gamma_size: size as _, 281 | red: red.as_ptr() as _, 282 | green: green.as_ptr() as _, 283 | blue: blue.as_ptr() as _, 284 | }; 285 | 286 | unsafe { 287 | ioctl::mode::set_gamma(fd, &mut lut)?; 288 | } 289 | 290 | Ok(lut) 291 | } 292 | 293 | /// Set cursor state 294 | /// 295 | /// The buffer must be allocated using the buffer manager of the driver (GEM or TTM). It is not 296 | /// allowed to be a dumb buffer. 297 | #[deprecated = "use a cursor plane instead"] 298 | pub fn set_cursor( 299 | fd: BorrowedFd<'_>, 300 | crtc_id: u32, 301 | buf_id: u32, 302 | width: u32, 303 | height: u32, 304 | ) -> io::Result { 305 | let mut cursor = drm_mode_cursor { 306 | flags: DRM_MODE_CURSOR_BO, 307 | crtc_id, 308 | width, 309 | height, 310 | handle: buf_id, 311 | ..Default::default() 312 | }; 313 | 314 | unsafe { 315 | ioctl::mode::cursor(fd, &mut cursor)?; 316 | } 317 | 318 | Ok(cursor) 319 | } 320 | 321 | /// Set cursor state (with hotspot position) 322 | /// 323 | /// The buffer must be allocated using the buffer manager of the driver (GEM or TTM). It is not 324 | /// allowed to be a dumb buffer. 325 | /// 326 | /// The hotspot position is used to coordinate the guest and host cursor location in case of 327 | /// virtualization. 328 | #[deprecated = "use a cursor plane instead"] 329 | pub fn set_cursor2( 330 | fd: BorrowedFd<'_>, 331 | crtc_id: u32, 332 | buf_id: u32, 333 | width: u32, 334 | height: u32, 335 | hot_x: i32, 336 | hot_y: i32, 337 | ) -> io::Result { 338 | let mut cursor = drm_mode_cursor2 { 339 | flags: DRM_MODE_CURSOR_BO, 340 | crtc_id, 341 | width, 342 | height, 343 | handle: buf_id, 344 | hot_x, 345 | hot_y, 346 | ..Default::default() 347 | }; 348 | 349 | unsafe { 350 | ioctl::mode::cursor2(fd, &mut cursor)?; 351 | } 352 | 353 | Ok(cursor) 354 | } 355 | 356 | /// Move cursor 357 | #[deprecated = "use a cursor plane instead"] 358 | pub fn move_cursor( 359 | fd: BorrowedFd<'_>, 360 | crtc_id: u32, 361 | x: i32, 362 | y: i32, 363 | ) -> io::Result { 364 | let mut cursor = drm_mode_cursor { 365 | flags: DRM_MODE_CURSOR_MOVE, 366 | crtc_id, 367 | x, 368 | y, 369 | ..Default::default() 370 | }; 371 | 372 | unsafe { 373 | ioctl::mode::cursor(fd, &mut cursor)?; 374 | } 375 | 376 | Ok(cursor) 377 | } 378 | 379 | /// Get info about a connector 380 | pub fn get_connector( 381 | fd: BorrowedFd<'_>, 382 | connector_id: u32, 383 | mut props: Option<&mut Vec>, 384 | mut prop_values: Option<&mut Vec>, 385 | mut modes: Option<&mut Vec>, 386 | mut encoders: Option<&mut Vec>, 387 | force_probe: bool, 388 | ) -> io::Result { 389 | assert_eq!(props.is_some(), prop_values.is_some()); 390 | 391 | let tmp_mode = drm_mode_modeinfo::default(); 392 | let mut sizes = drm_mode_get_connector { 393 | connector_id, 394 | modes_ptr: if force_probe { 395 | 0 396 | } else { 397 | &tmp_mode as *const _ as _ 398 | }, 399 | count_modes: u32::from(!force_probe), 400 | ..Default::default() 401 | }; 402 | 403 | unsafe { 404 | ioctl::mode::get_connector(fd, &mut sizes)?; 405 | } 406 | 407 | let info = loop { 408 | map_reserve!(props, sizes.count_props as usize); 409 | map_reserve!(prop_values, sizes.count_props as usize); 410 | map_reserve!(modes, sizes.count_modes as usize); 411 | map_reserve!(encoders, sizes.count_encoders as usize); 412 | 413 | let mut info = drm_mode_get_connector { 414 | connector_id, 415 | encoders_ptr: map_ptr!(&encoders), 416 | modes_ptr: match &mut modes { 417 | Some(b) => b.as_mut_ptr() as _, 418 | None => { 419 | if force_probe { 420 | 0 as _ 421 | } else { 422 | &tmp_mode as *const _ as _ 423 | } 424 | } 425 | }, 426 | props_ptr: map_ptr!(&props), 427 | prop_values_ptr: map_ptr!(&prop_values), 428 | count_modes: match &modes { 429 | Some(b) => b.capacity() as _, 430 | None => u32::from(!force_probe), 431 | }, 432 | count_props: map_len!(&props), 433 | count_encoders: map_len!(&encoders), 434 | ..Default::default() 435 | }; 436 | 437 | unsafe { 438 | ioctl::mode::get_connector(fd, &mut info)?; 439 | } 440 | 441 | if info.count_modes == sizes.count_modes 442 | && info.count_encoders == sizes.count_encoders 443 | && info.count_props == sizes.count_props 444 | { 445 | break info; 446 | } else { 447 | sizes = info; 448 | } 449 | }; 450 | 451 | map_set!(modes, info.count_modes as usize); 452 | map_set!(props, info.count_props as usize); 453 | map_set!(prop_values, info.count_props as usize); 454 | map_set!(encoders, info.count_encoders as usize); 455 | 456 | Ok(info) 457 | } 458 | 459 | /// Get info about an encoder 460 | pub fn get_encoder(fd: BorrowedFd<'_>, encoder_id: u32) -> io::Result { 461 | let mut info = drm_mode_get_encoder { 462 | encoder_id, 463 | ..Default::default() 464 | }; 465 | 466 | unsafe { 467 | ioctl::mode::get_encoder(fd, &mut info)?; 468 | } 469 | 470 | Ok(info) 471 | } 472 | 473 | /// Get info about a plane. 474 | pub fn get_plane( 475 | fd: BorrowedFd<'_>, 476 | plane_id: u32, 477 | mut formats: Option<&mut Vec>, 478 | ) -> io::Result { 479 | let mut sizes = drm_mode_get_plane { 480 | plane_id, 481 | ..Default::default() 482 | }; 483 | 484 | unsafe { 485 | ioctl::mode::get_plane(fd, &mut sizes)?; 486 | } 487 | 488 | if formats.is_none() { 489 | return Ok(sizes); 490 | } 491 | 492 | map_reserve!(formats, sizes.count_format_types as usize); 493 | 494 | let mut info = drm_mode_get_plane { 495 | plane_id, 496 | count_format_types: sizes.count_format_types, 497 | format_type_ptr: map_ptr!(&formats), 498 | ..Default::default() 499 | }; 500 | 501 | unsafe { 502 | ioctl::mode::get_plane(fd, &mut info)?; 503 | } 504 | 505 | map_set!(formats, info.count_format_types as usize); 506 | 507 | Ok(info) 508 | } 509 | 510 | /// Set plane state. 511 | pub fn set_plane( 512 | fd: BorrowedFd<'_>, 513 | plane_id: u32, 514 | crtc_id: u32, 515 | fb_id: u32, 516 | flags: u32, 517 | crtc_x: i32, 518 | crtc_y: i32, 519 | crtc_w: u32, 520 | crtc_h: u32, 521 | src_x: u32, 522 | src_y: u32, 523 | src_w: u32, 524 | src_h: u32, 525 | ) -> io::Result { 526 | let mut plane = drm_mode_set_plane { 527 | plane_id, 528 | crtc_id, 529 | fb_id, 530 | flags, 531 | crtc_x, 532 | crtc_y, 533 | crtc_w, 534 | crtc_h, 535 | src_x, 536 | src_y, 537 | src_h, 538 | src_w, 539 | }; 540 | 541 | unsafe { 542 | ioctl::mode::set_plane(fd, &mut plane)?; 543 | } 544 | 545 | Ok(plane) 546 | } 547 | 548 | /// Get property 549 | pub fn get_property( 550 | fd: BorrowedFd<'_>, 551 | prop_id: u32, 552 | mut values: Option<&mut Vec>, 553 | mut enums: Option<&mut Vec>, 554 | ) -> io::Result { 555 | let mut prop = drm_mode_get_property { 556 | prop_id, 557 | ..Default::default() 558 | }; 559 | 560 | unsafe { 561 | ioctl::mode::get_property(fd, &mut prop)?; 562 | } 563 | 564 | // There is no need to call get_property() twice if there is nothing else to retrieve. 565 | if prop.count_values == 0 && prop.count_enum_blobs == 0 { 566 | return Ok(prop); 567 | } 568 | 569 | map_reserve!(values, prop.count_values as usize); 570 | map_reserve!(enums, prop.count_enum_blobs as usize); 571 | 572 | prop.values_ptr = map_ptr!(&values); 573 | prop.enum_blob_ptr = map_ptr!(&enums); 574 | 575 | unsafe { 576 | ioctl::mode::get_property(fd, &mut prop)?; 577 | } 578 | 579 | map_set!(values, prop.count_values as usize); 580 | map_set!(enums, prop.count_enum_blobs as usize); 581 | 582 | Ok(prop) 583 | } 584 | 585 | /// Set property 586 | pub fn set_connector_property( 587 | fd: BorrowedFd<'_>, 588 | connector_id: u32, 589 | prop_id: u32, 590 | value: u64, 591 | ) -> io::Result { 592 | let mut prop = drm_mode_connector_set_property { 593 | value, 594 | prop_id, 595 | connector_id, 596 | }; 597 | 598 | unsafe { 599 | ioctl::mode::connector_set_property(fd, &mut prop)?; 600 | } 601 | 602 | Ok(prop) 603 | } 604 | 605 | /// Get the value of a property blob 606 | pub fn get_property_blob( 607 | fd: BorrowedFd<'_>, 608 | blob_id: u32, 609 | mut data: Option<&mut Vec>, 610 | ) -> io::Result { 611 | let mut sizes = drm_mode_get_blob { 612 | blob_id, 613 | ..Default::default() 614 | }; 615 | 616 | unsafe { 617 | ioctl::mode::get_blob(fd, &mut sizes)?; 618 | } 619 | 620 | if data.is_none() { 621 | return Ok(sizes); 622 | } 623 | 624 | map_reserve!(data, sizes.length as usize); 625 | 626 | let mut blob = drm_mode_get_blob { 627 | blob_id, 628 | length: sizes.length, 629 | data: map_ptr!(&data), 630 | }; 631 | 632 | unsafe { 633 | ioctl::mode::get_blob(fd, &mut blob)?; 634 | } 635 | 636 | map_set!(data, blob.length as usize); 637 | 638 | Ok(blob) 639 | } 640 | 641 | /// Create a property blob 642 | pub fn create_property_blob( 643 | fd: BorrowedFd<'_>, 644 | data: &mut [u8], 645 | ) -> io::Result { 646 | let mut blob = drm_mode_create_blob { 647 | data: data.as_mut_ptr() as _, 648 | length: data.len() as _, 649 | ..Default::default() 650 | }; 651 | 652 | unsafe { 653 | ioctl::mode::create_blob(fd, &mut blob)?; 654 | } 655 | 656 | Ok(blob) 657 | } 658 | 659 | /// Destroy a property blob 660 | pub fn destroy_property_blob(fd: BorrowedFd<'_>, id: u32) -> io::Result { 661 | let mut blob = drm_mode_destroy_blob { blob_id: id }; 662 | 663 | unsafe { 664 | ioctl::mode::destroy_blob(fd, &mut blob)?; 665 | } 666 | 667 | Ok(blob) 668 | } 669 | 670 | /// Get properties from an object 671 | pub fn get_properties( 672 | fd: BorrowedFd<'_>, 673 | obj_id: u32, 674 | obj_type: u32, 675 | mut props: Option<&mut Vec>, 676 | mut values: Option<&mut Vec>, 677 | ) -> io::Result { 678 | assert_eq!(props.is_some(), values.is_some()); 679 | 680 | let mut sizes = drm_mode_obj_get_properties { 681 | obj_id, 682 | obj_type, 683 | ..Default::default() 684 | }; 685 | 686 | unsafe { 687 | ioctl::mode::obj_get_properties(fd, &mut sizes)?; 688 | } 689 | 690 | map_reserve!(props, sizes.count_props as usize); 691 | map_reserve!(values, sizes.count_props as usize); 692 | 693 | let mut info = drm_mode_obj_get_properties { 694 | props_ptr: map_ptr!(&props), 695 | prop_values_ptr: map_ptr!(&values), 696 | count_props: map_len!(&props), 697 | obj_id, 698 | obj_type, 699 | }; 700 | 701 | unsafe { 702 | ioctl::mode::obj_get_properties(fd, &mut info)?; 703 | } 704 | 705 | map_set!(props, info.count_props as usize); 706 | map_set!(values, info.count_props as usize); 707 | 708 | Ok(info) 709 | } 710 | 711 | /// Set the properties of an object 712 | pub fn set_property( 713 | fd: BorrowedFd<'_>, 714 | prop_id: u32, 715 | obj_id: u32, 716 | obj_type: u32, 717 | value: u64, 718 | ) -> io::Result<()> { 719 | let mut prop = drm_mode_obj_set_property { 720 | value, 721 | prop_id, 722 | obj_id, 723 | obj_type, 724 | }; 725 | 726 | unsafe { 727 | ioctl::mode::obj_set_property(fd, &mut prop)?; 728 | } 729 | 730 | Ok(()) 731 | } 732 | 733 | /// Schedule a page flip 734 | pub fn page_flip( 735 | fd: BorrowedFd<'_>, 736 | crtc_id: u32, 737 | fb_id: u32, 738 | flags: u32, 739 | sequence: u32, 740 | ) -> io::Result<()> { 741 | let mut flip = drm_mode_crtc_page_flip { 742 | crtc_id, 743 | fb_id, 744 | flags, 745 | // Same struct as drm_mode_crtc_page_flip_target 746 | reserved: sequence, 747 | user_data: crtc_id as _, 748 | }; 749 | 750 | unsafe { 751 | ioctl::mode::crtc_page_flip(fd, &mut flip)?; 752 | } 753 | 754 | Ok(()) 755 | } 756 | 757 | /// Atomically set properties 758 | pub fn atomic_commit( 759 | fd: BorrowedFd<'_>, 760 | flags: u32, 761 | objs: &mut [u32], 762 | prop_counts: &mut [u32], 763 | props: &mut [u32], 764 | values: &mut [u64], 765 | ) -> io::Result<()> { 766 | let mut atomic = drm_mode_atomic { 767 | flags, 768 | count_objs: objs.len() as _, 769 | objs_ptr: objs.as_mut_ptr() as _, 770 | count_props_ptr: prop_counts.as_mut_ptr() as _, 771 | props_ptr: props.as_mut_ptr() as _, 772 | prop_values_ptr: values.as_mut_ptr() as _, 773 | ..Default::default() 774 | }; 775 | 776 | unsafe { 777 | ioctl::mode::atomic(fd, &mut atomic)?; 778 | } 779 | 780 | Ok(()) 781 | } 782 | 783 | /// Create a drm lease 784 | pub fn create_lease( 785 | fd: BorrowedFd<'_>, 786 | objects: &[u32], 787 | flags: u32, 788 | ) -> io::Result { 789 | let mut data = drm_mode_create_lease { 790 | object_ids: objects.as_ptr() as _, 791 | object_count: objects.len() as u32, 792 | flags, 793 | ..Default::default() 794 | }; 795 | 796 | unsafe { 797 | ioctl::mode::create_lease(fd, &mut data)?; 798 | } 799 | 800 | Ok(data) 801 | } 802 | 803 | /// List all active drm leases 804 | pub fn list_lessees( 805 | fd: BorrowedFd<'_>, 806 | mut lessees: Option<&mut Vec>, 807 | ) -> io::Result { 808 | let mut sizes = drm_mode_list_lessees::default(); 809 | 810 | unsafe { 811 | ioctl::mode::list_lessees(fd, &mut sizes)?; 812 | }; 813 | 814 | map_reserve!(lessees, sizes.count_lessees as usize); 815 | 816 | let mut data = drm_mode_list_lessees { 817 | lessees_ptr: map_ptr!(&lessees), 818 | count_lessees: map_len!(&lessees), 819 | ..Default::default() 820 | }; 821 | 822 | unsafe { 823 | ioctl::mode::list_lessees(fd, &mut data)?; 824 | }; 825 | 826 | map_set!(lessees, data.count_lessees as usize); 827 | 828 | Ok(data) 829 | } 830 | 831 | /// Get leased objects for a lease file descriptor 832 | pub fn get_lease( 833 | fd: BorrowedFd<'_>, 834 | mut objects: Option<&mut Vec>, 835 | ) -> io::Result { 836 | let mut sizes = drm_mode_get_lease::default(); 837 | 838 | unsafe { 839 | ioctl::mode::get_lease(fd, &mut sizes)?; 840 | } 841 | 842 | map_reserve!(objects, sizes.count_objects as usize); 843 | 844 | let mut data = drm_mode_get_lease { 845 | count_objects: map_len!(&objects), 846 | objects_ptr: map_ptr!(&objects), 847 | ..Default::default() 848 | }; 849 | 850 | unsafe { 851 | ioctl::mode::get_lease(fd, &mut data)?; 852 | } 853 | 854 | map_set!(objects, data.count_objects as usize); 855 | 856 | Ok(data) 857 | } 858 | 859 | /// Revoke previously issued lease 860 | pub fn revoke_lease(fd: BorrowedFd<'_>, lessee_id: u32) -> io::Result<()> { 861 | let mut data = drm_mode_revoke_lease { lessee_id }; 862 | 863 | unsafe { 864 | ioctl::mode::revoke_lease(fd, &mut data)?; 865 | } 866 | 867 | Ok(()) 868 | } 869 | 870 | /// 871 | /// Dumbbuffers are basic buffers that can be used for scanout. 872 | /// 873 | pub mod dumbbuffer { 874 | use crate::ioctl; 875 | use drm_sys::*; 876 | 877 | use std::{io, os::unix::io::BorrowedFd}; 878 | 879 | /// Create a dumb buffer 880 | pub fn create( 881 | fd: BorrowedFd<'_>, 882 | width: u32, 883 | height: u32, 884 | bpp: u32, 885 | flags: u32, 886 | ) -> io::Result { 887 | let mut db = drm_mode_create_dumb { 888 | height, 889 | width, 890 | bpp, 891 | flags, 892 | ..Default::default() 893 | }; 894 | 895 | unsafe { 896 | ioctl::mode::create_dumb(fd, &mut db)?; 897 | } 898 | 899 | Ok(db) 900 | } 901 | 902 | /// Destroy a dumb buffer 903 | pub fn destroy(fd: BorrowedFd<'_>, handle: u32) -> io::Result { 904 | let mut db = drm_mode_destroy_dumb { handle }; 905 | 906 | unsafe { 907 | ioctl::mode::destroy_dumb(fd, &mut db)?; 908 | } 909 | 910 | Ok(db) 911 | } 912 | 913 | /// Map a dump buffer and prep it for an mmap 914 | pub fn map( 915 | fd: BorrowedFd<'_>, 916 | handle: u32, 917 | pad: u32, 918 | offset: u64, 919 | ) -> io::Result { 920 | let mut map = drm_mode_map_dumb { 921 | handle, 922 | pad, 923 | offset, 924 | }; 925 | 926 | unsafe { 927 | ioctl::mode::map_dumb(fd, &mut map)?; 928 | } 929 | 930 | Ok(map) 931 | } 932 | } 933 | --------------------------------------------------------------------------------