#![no_std]
#![feature(ptr_metadata)]
#![feature(ascii_char)]
extern crate alloc;
pub mod event;
pub mod ioctl;
pub mod modeset;
pub mod result;
use core::iter::{self, zip};
use core::ptr::null_mut;
use alloc::sync::Arc;
use alloc::vec::Vec;
use linux_io::fd::ioctl::IoctlReq;
use modeset::{EncoderState, ModeInfo, ModeProp};
use result::{Error, InitError};
#[repr(transparent)]
#[derive(Debug)]
pub struct Card {
f: Arc<linux_io::File<ioctl::DrmCardDevice>>,
}
impl Card {
pub fn open(path: &core::ffi::CStr) -> Result<Self, InitError> {
let f = linux_io::File::open(path, linux_io::OpenOptions::read_write())?;
Self::from_file(f)
}
pub fn from_file<D>(f: linux_io::File<D>) -> Result<Self, InitError> {
let f: linux_io::File<ioctl::DrmCardDevice> = unsafe { f.to_device(ioctl::DrmCardDevice) };
let ret = Self { f: Arc::new(f) };
let mut v = ioctl::DrmVersion::zeroed();
ret.ioctl(ioctl::DRM_IOCTL_VERSION, &mut v)?;
Ok(ret)
}
pub unsafe fn from_file_unchecked<D>(f: linux_io::File<D>) -> Self {
let f: linux_io::File<ioctl::DrmCardDevice> = unsafe { f.to_device(ioctl::DrmCardDevice) };
Self { f: Arc::new(f) }
}
pub fn fd(&self) -> linux_unsafe::int {
self.f.fd()
}
pub fn api_version(&self) -> Result<ApiVersion, Error> {
let mut v = ioctl::DrmVersion::zeroed();
self.ioctl(ioctl::DRM_IOCTL_VERSION, &mut v)?;
Ok(ApiVersion {
major: v.version_major as i64,
minor: v.version_minor as i64,
patch: v.version_patchlevel as i64,
})
}
pub fn read_driver_name<'a>(&self, into: &'a mut [u8]) -> Result<&'a mut [u8], Error> {
let mut v = ioctl::DrmVersion::zeroed();
let ptr = into.as_mut_ptr();
v.name_len = into.len();
v.name = ptr as *mut _;
self.ioctl(ioctl::DRM_IOCTL_VERSION, &mut v)?;
Ok(&mut into[..v.name_len])
}
pub fn driver_name(&self) -> Result<Vec<u8>, Error> {
let mut v = ioctl::DrmVersion::zeroed();
self.ioctl(ioctl::DRM_IOCTL_VERSION, &mut v)?;
let len = v.name_len;
let mut ret = vec_with_capacity(len)?;
v = ioctl::DrmVersion::zeroed();
v.name_len = len;
v.name = ret.as_mut_ptr() as *mut _;
self.ioctl(ioctl::DRM_IOCTL_VERSION, &mut v)?;
unsafe { ret.set_len(v.name_len) };
Ok(ret)
}
#[inline(always)]
pub fn get_device_cap(&self, capability: DeviceCap) -> Result<u64, Error> {
self.get_device_cap_raw(capability.into())
}
#[inline]
pub fn get_device_cap_raw(&self, capability: ioctl::DrmCap) -> Result<u64, Error> {
let mut s = ioctl::DrmGetCap {
capability,
value: 0,
};
self.ioctl(ioctl::DRM_IOCTL_GET_CAP, &mut s)?;
Ok(s.value)
}
#[inline(always)]
pub fn set_client_cap(&self, capability: ClientCap, value: u64) -> Result<(), Error> {
self.set_client_cap_raw(capability.into(), value)
}
#[inline]
pub fn set_client_cap_raw(
&self,
capability: ioctl::DrmClientCap,
value: u64,
) -> Result<(), Error> {
let s = ioctl::DrmSetClientCap { capability, value };
self.ioctl(ioctl::DRM_IOCTL_SET_CLIENT_CAP, &s)?;
Ok(())
}
#[inline]
pub fn become_master(&mut self) -> Result<(), Error> {
self.ioctl(ioctl::DRM_IOCTL_SET_MASTER, ())?;
Ok(())
}
#[inline]
pub fn drop_master(&mut self) -> Result<(), Error> {
self.ioctl(ioctl::DRM_IOCTL_DROP_MASTER, ())?;
Ok(())
}
pub fn property_meta(&self, prop_id: u32) -> Result<modeset::ObjectPropMeta, Error> {
let mut tmp = ioctl::DrmModeGetProperty::zeroed();
tmp.prop_id = prop_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETPROPERTY, &mut tmp)?;
if !tmp.name.is_ascii() {
return Err(Error::NotSupported);
}
Ok(modeset::ObjectPropMeta::new(tmp, &self))
}
pub fn object_properties(
&self,
obj_id: impl Into<modeset::ObjectId>,
) -> Result<Vec<modeset::ModeProp>, Error> {
fn real_object_properties(
card: &Card,
obj_id: modeset::ObjectId,
) -> Result<Vec<modeset::ModeProp>, Error> {
let (type_id, raw_id) = obj_id.as_raw_type_and_id();
let mut tmp = ioctl::DrmModeObjGetProperties::zeroed();
tmp.obj_type = type_id;
tmp.obj_id = raw_id;
card.ioctl(ioctl::DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &mut tmp)?;
loop {
let prop_count = tmp.count_props as usize;
let mut prop_ids = vec_with_capacity::<u32>(prop_count)?;
let mut prop_values = vec_with_capacity::<u64>(prop_count)?;
tmp.props_ptr = prop_ids.as_mut_ptr() as u64;
tmp.prop_values_ptr = prop_values.as_mut_ptr() as u64;
card.ioctl(ioctl::DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &mut tmp)?;
let new_prop_count = tmp.count_props as usize;
if new_prop_count != prop_count {
continue;
}
unsafe {
prop_ids.set_len(prop_count);
prop_values.set_len(prop_count);
};
return Ok(iter::zip(prop_ids.into_iter(), prop_values.into_iter())
.map(|(id, val)| modeset::ModeProp {
prop_id: id,
value: val,
})
.collect());
}
}
real_object_properties(self, obj_id.into())
}
pub fn each_object_property_meta(
&self,
obj_id: impl Into<modeset::ObjectId>,
mut f: impl FnMut(modeset::ObjectPropMeta, u64),
) -> Result<(), Error> {
let obj_id = obj_id.into();
let (type_id, raw_id) = obj_id.as_raw_type_and_id();
let mut tmp = ioctl::DrmModeObjGetProperties::zeroed();
tmp.obj_type = type_id;
tmp.obj_id = raw_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &mut tmp)?;
if tmp.count_props == 0 {
return Ok(());
}
let (prop_ids, prop_values) = loop {
let prop_count = tmp.count_props as usize;
let mut prop_ids = vec_with_capacity::<u32>(prop_count)?;
let mut prop_values = vec_with_capacity::<u64>(prop_count)?;
tmp.props_ptr = prop_ids.as_mut_ptr() as u64;
tmp.prop_values_ptr = prop_values.as_mut_ptr() as u64;
self.ioctl(ioctl::DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &mut tmp)?;
let new_prop_count = tmp.count_props as usize;
if new_prop_count != prop_count {
continue;
}
unsafe {
prop_ids.set_len(prop_count);
prop_values.set_len(prop_count);
};
break (prop_ids, prop_values);
};
for (prop_id, value) in zip(prop_ids, prop_values) {
let mut raw = ioctl::DrmModeGetProperty::zeroed();
raw.prop_id = prop_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETPROPERTY, &mut raw)?;
if !raw.name.is_ascii() {
continue;
}
f(modeset::ObjectPropMeta::new(raw, &self), value);
}
Ok(())
}
pub fn resources(&self) -> Result<modeset::CardResources, Error> {
let mut ret = loop {
let mut r = ioctl::DrmModeCardRes::zeroed();
self.ioctl(ioctl::DRM_IOCTL_MODE_GETRESOURCES, &mut r)?;
let fb_count = r.count_fbs as usize;
let connector_count = r.count_connectors as usize;
let crtc_count = r.count_crtcs as usize;
let encoder_count = r.count_encoders as usize;
let mut fb_ids = vec_with_capacity::<u32>(fb_count)?;
let mut connector_ids = vec_with_capacity::<u32>(connector_count)?;
let mut crtc_ids = vec_with_capacity::<u32>(crtc_count)?;
let mut encoder_ids = vec_with_capacity::<u32>(encoder_count)?;
r = ioctl::DrmModeCardRes::zeroed();
r.count_fbs = fb_count as u32;
r.fb_id_ptr = fb_ids.as_mut_ptr() as u64;
r.count_connectors = connector_count as u32;
r.connector_id_ptr = connector_ids.as_mut_ptr() as u64;
r.count_crtcs = crtc_count as u32;
r.crtc_id_ptr = crtc_ids.as_mut_ptr() as u64;
r.count_encoders = encoder_count as u32;
r.encoder_id_ptr = encoder_ids.as_mut_ptr() as u64;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETRESOURCES, &mut r)?;
if r.count_fbs as usize != fb_count {
continue;
}
if r.count_connectors as usize != connector_count {
continue;
}
if r.count_crtcs as usize != crtc_count {
continue;
}
if r.count_encoders as usize != encoder_count {
continue;
}
unsafe {
fb_ids.set_len(fb_count);
connector_ids.set_len(connector_count);
crtc_ids.set_len(crtc_count);
encoder_ids.set_len(encoder_count);
};
break modeset::CardResources {
fb_ids,
connector_ids,
crtc_ids,
encoder_ids,
plane_ids: Vec::new(),
min_width: r.min_width,
max_width: r.max_width,
min_height: r.min_height,
max_height: r.max_height,
};
};
loop {
let mut tmp = ioctl::DrmModeGetPlaneRes::zeroed();
self.ioctl(ioctl::DRM_IOCTL_MODE_GETPLANERESOURCES, &mut tmp)?;
let plane_count = tmp.count_planes as usize;
let mut plane_ids = vec_with_capacity::<u32>(plane_count)?;
tmp.plane_id_ptr = plane_ids.as_mut_ptr() as u64;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETPLANERESOURCES, &mut tmp)?;
if tmp.count_planes as usize != plane_count {
continue;
}
unsafe {
plane_ids.set_len(plane_count);
};
ret.plane_ids = plane_ids;
return Ok(ret);
}
}
pub fn connector_state(&self, connector_id: u32) -> Result<modeset::ConnectorState, Error> {
loop {
let mut tmp = ioctl::DrmModeGetConnector::zeroed();
tmp.connector_id = connector_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETCONNECTOR, &mut tmp)?;
let mode_count = tmp.count_modes;
let encoder_count = tmp.count_encoders;
let prop_count = tmp.count_props;
let mut modes = vec_with_capacity::<ioctl::DrmModeInfo>(mode_count as usize)?;
let mut ret_modes = vec_with_capacity::<ModeInfo>(mode_count as usize)?;
let mut encoder_ids = vec_with_capacity::<u32>(encoder_count as usize)?;
let mut prop_ids = vec_with_capacity::<u32>(prop_count as usize)?;
let mut prop_values = vec_with_capacity::<u64>(prop_count as usize)?;
let mut ret_props = vec_with_capacity::<ModeProp>(prop_count as usize)?;
tmp = ioctl::DrmModeGetConnector::zeroed();
tmp.connector_id = connector_id;
tmp.count_modes = mode_count;
tmp.modes_ptr = modes.as_mut_ptr() as u64;
tmp.count_encoders = encoder_count;
tmp.encoders_ptr = encoder_ids.as_mut_ptr() as u64;
tmp.count_props = prop_count;
tmp.props_ptr = prop_ids.as_mut_ptr() as u64;
tmp.prop_values_ptr = prop_values.as_mut_ptr() as u64;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETCONNECTOR, &mut tmp)?;
if tmp.count_modes != mode_count
|| tmp.count_props != prop_count
|| tmp.count_encoders != encoder_count
{
continue;
}
unsafe {
modes.set_len(mode_count as usize);
encoder_ids.set_len(encoder_count as usize);
prop_ids.set_len(prop_count as usize);
prop_values.set_len(prop_count as usize);
}
ret_modes.extend(modes.into_iter().map(|raw| {
let r: modeset::ModeInfo = raw.into();
r
}));
ret_props.extend(
core::iter::zip(prop_ids.iter().copied(), prop_values.iter().copied())
.map(|(prop_id, value)| ModeProp { prop_id, value }),
);
return Ok(modeset::ConnectorState {
id: tmp.connector_id,
current_encoder_id: tmp.encoder_id,
connector_type: tmp.connector_type.into(),
connector_type_id: tmp.connector_type_id,
connection_state: tmp.connection.into(),
width_mm: tmp.mm_width,
height_mm: tmp.mm_height,
subpixel_type: tmp.subpixel.into(),
modes: ret_modes,
props: ret_props,
available_encoder_ids: encoder_ids,
});
}
}
pub fn encoder_state(&self, encoder_id: u32) -> Result<modeset::EncoderState, Error> {
let mut tmp = ioctl::DrmModeGetEncoder::zeroed();
tmp.encoder_id = encoder_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETENCODER, &mut tmp)?;
Ok(EncoderState {
encoder_id: tmp.encoder_id,
encoder_type: tmp.encoder_type,
current_crtc_id: tmp.crtc_id,
possible_crtcs: tmp.possible_crtcs,
possible_clones: tmp.possible_clones,
})
}
pub fn crtc_state(&self, crtc_id: u32) -> Result<modeset::CrtcState, Error> {
let mut tmp = ioctl::DrmModeCrtc::zeroed();
tmp.crtc_id = crtc_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETCRTC, &mut tmp)?;
Ok(tmp.into())
}
pub fn plane_state(&self, plane_id: u32) -> Result<modeset::PlaneState, Error> {
let mut tmp = ioctl::DrmModeGetPlane::zeroed();
tmp.plane_id = plane_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_GETPLANE, &mut tmp)?;
Ok(modeset::PlaneState {
id: tmp.plane_id,
crtc_id: tmp.crtc_id,
fb_id: tmp.fb_id,
possible_crtcs: tmp.possible_crtcs,
gamma_size: tmp.gamma_size,
})
}
pub fn atomic_commit(
&mut self,
req: modeset::AtomicRequest,
flags: modeset::AtomicCommitFlags,
user_data: u64,
) -> Result<(), Error> {
let mut tmp = ioctl::DrmModeAtomic::zeroed();
let mut raw_parts = req.for_ioctl_req();
tmp.count_objs = raw_parts.obj_ids.len() as u32;
tmp.objs_ptr = raw_parts.obj_ids.as_mut_ptr() as u64;
tmp.count_props_ptr = raw_parts.obj_prop_counts.as_mut_ptr() as u64;
tmp.props_ptr = raw_parts.prop_ids.as_mut_ptr() as u64;
tmp.prop_values_ptr = raw_parts.prop_values.as_mut_ptr() as u64;
tmp.flags = flags.0;
tmp.user_data = user_data;
self.ioctl(ioctl::DRM_IOCTL_MODE_ATOMIC, &mut tmp)?;
Ok(())
}
pub fn reset_crtc(&mut self, crtc_id: u32) -> Result<modeset::CrtcState, Error> {
let mut tmp = ioctl::DrmModeCrtc::zeroed();
tmp.crtc_id = crtc_id;
self.ioctl(ioctl::DRM_IOCTL_MODE_SETCRTC, &mut tmp)?;
Ok(tmp.into())
}
pub fn set_crtc_dumb_buffer(
&mut self,
crtc_id: u32,
buf: &modeset::DumbBuffer,
mode: &ModeInfo,
conn_ids: &[u32],
) -> Result<modeset::CrtcState, Error> {
let mut tmp = ioctl::DrmModeCrtc::zeroed();
tmp.crtc_id = crtc_id;
if conn_ids.len() > (u32::MAX as usize) {
return Err(Error::Invalid);
}
tmp.count_connectors = conn_ids.len() as u32;
tmp.set_connectors_ptr = conn_ids.as_ptr() as u64;
tmp.fb_id = buf.fb_id;
tmp.mode = mode.into();
tmp.mode_valid = 1;
self.ioctl(ioctl::DRM_IOCTL_MODE_SETCRTC, &mut tmp)?;
Ok(tmp.into())
}
pub fn crtc_page_flip_dumb_buffer(
&mut self,
crtd_id: u32,
buf: &modeset::DumbBuffer,
flags: modeset::PageFlipFlags,
) -> Result<(), Error> {
let mut tmp = ioctl::DrmModeCrtcPageFlip::zeroed();
tmp.crtc_id = crtd_id;
tmp.fb_id = buf.fb_id;
tmp.flags = flags.into();
self.ioctl(ioctl::DRM_IOCTL_MODE_PAGE_FLIP, &mut tmp)?;
Ok(())
}
pub fn create_dumb_buffer(
&self,
req: modeset::DumbBufferRequest,
) -> Result<modeset::DumbBuffer, Error> {
let mut buf_req = ioctl::DrmModeCreateDumb::zeroed();
buf_req.width = req.width;
buf_req.height = req.height;
buf_req.bpp = req.bpp;
self.ioctl(ioctl::DRM_IOCTL_MODE_CREATE_DUMB, &mut buf_req)?;
let mut fb_req = ioctl::DrmModeFbCmd::zeroed();
fb_req.width = buf_req.width;
fb_req.height = buf_req.height;
fb_req.bpp = buf_req.bpp;
fb_req.depth = req.depth;
fb_req.pitch = buf_req.pitch;
fb_req.handle = buf_req.handle;
self.ioctl(ioctl::DRM_IOCTL_MODE_ADDFB, &mut fb_req)?;
let mut map_req = ioctl::DrmModeMapDumb::zeroed();
map_req.handle = buf_req.handle;
self.ioctl(ioctl::DRM_IOCTL_MODE_MAP_DUMB, &mut map_req)?;
let buf_ptr = unsafe {
self.f.mmap_raw(
map_req.offset as i64,
buf_req.size as usize,
null_mut(),
0b11, 0x01, )?
};
Ok(modeset::DumbBuffer {
width: buf_req.width,
height: buf_req.height,
bpp: buf_req.bpp,
pitch: buf_req.pitch,
ptr: buf_ptr as *mut u8,
len: buf_req.size as usize,
fb_id: fb_req.fb_id,
buffer_handle: buf_req.handle,
file: Arc::downgrade(&self.f),
})
}
pub fn read_events_raw<'a>(
&self,
buf: &'a mut [u8],
) -> Result<impl Iterator<Item = &'a event::raw::DrmEvent> + 'a, Error> {
let len = self.f.read(buf)?;
let buf = &buf[0..len];
Ok(event::raw::events_from_bytes(buf))
}
pub fn read_events<'a>(
&self,
buf: &'a mut [u8],
) -> Result<impl Iterator<Item = event::DrmEvent> + 'a, Error> {
let raws = self.read_events_raw(buf)?;
Ok(raws.map(|raw| event::DrmEvent::from_raw(raw)))
}
#[inline]
pub fn close(self) -> linux_io::result::Result<()> {
let f = self.take_file()?;
f.close()
}
pub fn take_file(self) -> linux_io::result::Result<linux_io::File<ioctl::DrmCardDevice>> {
Arc::into_inner(self.f).ok_or(linux_io::result::EBUSY)
}
#[inline(always)]
pub fn borrow_file(&self) -> &linux_io::File<ioctl::DrmCardDevice> {
self.f.as_ref()
}
#[inline(always)]
pub fn ioctl<'a, Req: IoctlReq<'a, ioctl::DrmCardDevice> + Copy>(
&'a self,
request: Req,
arg: Req::ExtArg,
) -> linux_io::result::Result<Req::Result> {
drm_ioctl(&self.f, request, arg)
}
}
pub(crate) fn drm_ioctl<'a, Req: IoctlReq<'a, ioctl::DrmCardDevice> + Copy>(
f: &'a linux_io::File<ioctl::DrmCardDevice>,
request: Req,
arg: Req::ExtArg,
) -> linux_io::result::Result<Req::Result> {
let arg_ptr = &arg as *const _;
loop {
let arg = unsafe { core::ptr::read(arg_ptr) };
let ret = f.ioctl(request, arg);
if !matches!(ret, Err(linux_io::result::EINTR)) {
return ret;
}
}
}
impl<D> TryFrom<linux_io::File<D>> for Card {
type Error = InitError;
#[inline(always)]
fn try_from(value: linux_io::File<D>) -> Result<Self, InitError> {
Card::from_file(value)
}
}
#[derive(Debug)]
pub struct ApiVersion {
pub major: i64,
pub minor: i64,
pub patch: i64,
}
impl core::fmt::Display for ApiVersion {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("{}.{}.{}", self.major, self.minor, self.patch))
}
}
#[repr(u64)]
#[non_exhaustive]
pub enum DeviceCap {
DumbBuffer = ioctl::DRM_CAP_DUMB_BUFFER.0,
VBlankHighCrtc = ioctl::DRM_CAP_VBLANK_HIGH_CRTC.0,
DumbPreferredDepth = ioctl::DRM_CAP_DUMB_PREFERRED_DEPTH.0,
DumbPreferShadow = ioctl::DRM_CAP_DUMB_PREFER_SHADOW.0,
Prime = ioctl::DRM_CAP_PRIME.0,
TimestampMonotonic = ioctl::DRM_CAP_TIMESTAMP_MONOTONIC.0,
AsyncPageFlip = ioctl::DRM_CAP_ASYNC_PAGE_FLIP.0,
CursorWidth = ioctl::DRM_CAP_CURSOR_WIDTH.0,
CursorHeight = ioctl::DRM_CAP_CURSOR_HEIGHT.0,
Addfb2Modifiers = ioctl::DRM_CAP_ADDFB2_MODIFIERS.0,
PageFlipTarget = ioctl::DRM_CAP_PAGE_FLIP_TARGET.0,
CrtcInVblankEvent = ioctl::DRM_CAP_CRTC_IN_VBLANK_EVENT.0,
Syncobj = ioctl::DRM_CAP_SYNCOBJ.0,
SyncobjTimeline = ioctl::DRM_CAP_SYNCOBJ_TIMELINE.0,
}
impl From<DeviceCap> for ioctl::DrmCap {
#[inline(always)]
fn from(value: DeviceCap) -> Self {
ioctl::DrmCap(value as u64)
}
}
#[repr(u64)]
#[non_exhaustive]
pub enum ClientCap {
Stereo3d = ioctl::DRM_CLIENT_CAP_STEREO_3D.0,
UniversalPlanes = ioctl::DRM_CLIENT_CAP_UNIVERSAL_PLANES.0,
Atomic = ioctl::DRM_CLIENT_CAP_ATOMIC.0,
AspectRatio = ioctl::DRM_CLIENT_CAP_ASPECT_RATIO.0,
WritebackConnectors = ioctl::DRM_CLIENT_CAP_WRITEBACK_CONNECTORS.0,
}
impl From<ClientCap> for ioctl::DrmClientCap {
#[inline(always)]
fn from(value: ClientCap) -> Self {
ioctl::DrmClientCap(value as u64)
}
}
pub(crate) fn vec_with_capacity<T>(
capacity: usize,
) -> Result<Vec<T>, alloc::collections::TryReserveError> {
let mut ret = Vec::<T>::new();
ret.try_reserve_exact(capacity)?;
Ok(ret)
}