use std::ffi::{c_int, c_void};
use std::io;
use std::os::fd::{AsFd, AsRawFd};
type Req = *mut c_void;
struct Lib {
alloc: unsafe extern "C" fn() -> Req,
add: unsafe extern "C" fn(Req, u32, u32, u64) -> c_int,
commit: unsafe extern "C" fn(c_int, Req, u32, *mut c_void) -> c_int,
free: unsafe extern "C" fn(Req),
}
impl Lib {
fn load() -> io::Result<Lib> {
let handle = unsafe { libc::dlopen(c"libdrm.so.2".as_ptr(), libc::RTLD_NOW | libc::RTLD_GLOBAL) };
if handle.is_null() {
return Err(io::Error::other("libdrm.so.2 not found"));
}
let sym = |name: &std::ffi::CStr| {
let p = unsafe { libc::dlsym(handle, name.as_ptr()) };
if p.is_null() { Err(io::Error::other(format!("libdrm.so.2 lacks {name:?}"))) } else { Ok(p) }
};
unsafe {
Ok(Lib {
alloc: std::mem::transmute::<*mut c_void, unsafe extern "C" fn() -> Req>(sym(c"drmModeAtomicAlloc")?),
add: std::mem::transmute::<*mut c_void, unsafe extern "C" fn(Req, u32, u32, u64) -> c_int>(
sym(c"drmModeAtomicAddProperty")?,
),
commit: std::mem::transmute::<*mut c_void, unsafe extern "C" fn(c_int, Req, u32, *mut c_void) -> c_int>(
sym(c"drmModeAtomicCommit")?,
),
free: std::mem::transmute::<*mut c_void, unsafe extern "C" fn(Req)>(sym(c"drmModeAtomicFree")?),
})
}
}
}
#[repr(C)]
struct RawReq {
cursor: u32,
size_items: u32,
items: *const RawItem,
}
#[repr(C)]
struct RawItem {
object: u32,
property: u32,
value: u64,
}
fn probe_item_size(lib: &Lib) -> Option<usize> {
unsafe {
let req = (lib.alloc)();
if req.is_null() {
return None;
}
let entries = [(0x5a5a_0001u32, 0x5a5a_0002u32, 0x5a5a_0003_5a5a_0004u64), (0x6b6b_0001, 0x6b6b_0002, 0x6b6b_0003_6b6b_0004)];
let added = entries.iter().all(|&(o, p, v)| (lib.add)(req, o, p, v) >= 0);
let raw = &*req.cast::<RawReq>();
let size = (added && raw.cursor == 2 && raw.size_items >= 2 && !raw.items.is_null())
.then(|| {
[24usize, 16].into_iter().find(|&stride| {
entries.iter().enumerate().all(|(i, &(o, p, v))| {
let item = &*raw.items.cast::<u8>().add(i * stride).cast::<RawItem>();
(item.object, item.property, item.value) == (o, p, v)
})
})
})
.flatten();
(lib.free)(req);
size
}
}
pub struct RequestSlot {
lib: Lib,
slot: Box<Req>,
item_size: Option<usize>,
}
impl RequestSlot {
pub fn new() -> io::Result<RequestSlot> {
let lib = Lib::load()?;
let req = unsafe { (lib.alloc)() };
if req.is_null() {
return Err(io::Error::other("drmModeAtomicAlloc failed"));
}
let item_size = probe_item_size(&lib);
Ok(RequestSlot { lib, slot: Box::new(req), item_size })
}
pub fn mpv_ptr(&mut self) -> *mut *mut c_void {
&mut *self.slot
}
pub fn add(&mut self, object: u32, property: u32, value: u64) -> io::Result<()> {
if unsafe { (self.lib.add)(*self.slot, object, property, value) } < 0 {
return Err(io::Error::other("drmModeAtomicAddProperty failed"));
}
Ok(())
}
pub fn find(&self, object: u32, property: u32) -> Option<u64> {
let stride = self.item_size?;
unsafe {
let req = &*(*self.slot).cast::<RawReq>();
(0..req.cursor as usize).rev().find_map(|i| {
let item = &*req.items.cast::<u8>().add(i * stride).cast::<RawItem>();
(item.object == object && item.property == property).then_some(item.value)
})
}
}
pub fn commit(&mut self, card: &impl AsFd) -> io::Result<()> {
let fresh = unsafe { (self.lib.alloc)() };
if fresh.is_null() {
return Err(io::Error::other("drmModeAtomicAlloc failed"));
}
let req = std::mem::replace(&mut *self.slot, fresh);
let ret = unsafe { (self.lib.commit)(card.as_fd().as_raw_fd(), req, 0, std::ptr::null_mut()) };
let error = io::Error::last_os_error();
unsafe { (self.lib.free)(req) };
if ret != 0 { Err(error) } else { Ok(()) }
}
}
impl Drop for RequestSlot {
fn drop(&mut self) {
unsafe { (self.lib.free)(*self.slot) };
}
}
#[cfg(test)]
mod tests {
#[test]
fn slot_replaces_request_on_commit() {
let Ok(mut slot) = super::RequestSlot::new() else { return }; let first = unsafe { *slot.mpv_ptr() };
slot.add(1, 2, 3).unwrap();
let null = std::fs::File::open("/dev/null").unwrap();
assert!(slot.commit(&null).is_err());
let second = unsafe { *slot.mpv_ptr() };
assert!(!second.is_null());
slot.add(1, 2, 4).unwrap();
let _ = first;
}
#[test]
fn find_returns_the_last_value_added() {
let Ok(mut slot) = super::RequestSlot::new() else { return };
assert!(slot.item_size.is_some(), "libdrm request layout not recognised");
assert_eq!(slot.find(7, 8), None);
slot.add(7, 8, 100).unwrap();
slot.add(7, 9, 200).unwrap();
slot.add(7, 8, 300).unwrap();
assert_eq!(slot.find(7, 8), Some(300));
assert_eq!(slot.find(7, 9), Some(200));
assert_eq!(slot.find(9, 9), None);
}
}