use drmkit_core::Device;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum PanelSelfRefresh {
#[default]
Unknown,
None,
Possible,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct PrimeCaps {
pub can_import: bool,
pub can_export: bool,
}
const PRIME_CAP_IMPORT: u64 = 0x1;
const PRIME_CAP_EXPORT: u64 = 0x2;
#[must_use]
pub const fn decode_prime_caps(cap: u64) -> PrimeCaps {
PrimeCaps {
can_import: cap & PRIME_CAP_IMPORT != 0,
can_export: cap & PRIME_CAP_EXPORT != 0,
}
}
const CONNECTOR_EDP: u32 = 14;
const CONNECTOR_DSI: u32 = 16;
#[must_use]
pub const fn connector_type_self_refreshes(connector_type: u32) -> bool {
matches!(connector_type, CONNECTOR_EDP | CONNECTOR_DSI)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[expect(
clippy::struct_excessive_bools,
reason = "the shape is the kernel's capability list, not a design choice"
)]
#[non_exhaustive]
pub struct DriverProfile {
pub name: String,
pub addfb2_modifiers: bool,
pub async_page_flip: bool,
pub timestamp_monotonic: bool,
pub prime_import: bool,
pub prime_export: bool,
pub cursor_width: u64,
pub cursor_height: u64,
pub fb_damage_clips: bool,
pub vrr_capable: bool,
pub psr: PanelSelfRefresh,
}
impl DriverProfile {
pub fn probe(device: &Device) -> Result<Self, drmkit_core::CoreError> {
use drm::control::Device as _;
let driver = drm::Device::get_driver(device).map_err(|e| errno(&e))?;
let cap = |c: drm::DriverCapability| drm::Device::get_driver_capability(device, c).ok();
let prime = decode_prime_caps(cap(drm::DriverCapability::Prime).unwrap_or(0));
let _ = device.enable_universal_planes();
let _ = device.enable_atomic();
let mut fb_damage_clips = false;
if let Ok(planes) = device.plane_handles() {
for handle in planes {
if object_has_property(device, handle.into(), ObjectType::Plane, "FB_DAMAGE_CLIPS")
{
fb_damage_clips = true;
break;
}
}
}
let resources = device.resource_handles().ok();
let crtcs = resources.as_ref().map_or(&[][..], |r| r.crtcs());
let connectors = resources.as_ref().map_or(&[][..], |r| r.connectors());
let mut vrr_capable = false;
for crtc in crtcs {
if object_has_property(device, (*crtc).into(), ObjectType::Crtc, "VRR_ENABLED") {
vrr_capable = true;
break;
}
}
let mut psr = PanelSelfRefresh::Unknown;
for handle in connectors {
let Ok(info) = device.get_connector(*handle, false) else {
continue;
};
if info.state() != drm::control::connector::State::Connected {
continue;
}
let internal = connector_type_self_refreshes(connector_type_code(info.interface()));
psr = if internal {
PanelSelfRefresh::Possible
} else if psr == PanelSelfRefresh::Unknown {
PanelSelfRefresh::None
} else {
psr
};
if internal {
break;
}
}
Ok(Self {
name: driver.name().to_string_lossy().into_owned(),
addfb2_modifiers: cap(drm::DriverCapability::AddFB2Modifiers).unwrap_or(0) != 0,
async_page_flip: cap(drm::DriverCapability::ASyncPageFlip).unwrap_or(0) != 0,
timestamp_monotonic: cap(drm::DriverCapability::MonotonicTimestamp).unwrap_or(0) != 0,
prime_import: prime.can_import,
prime_export: prime.can_export,
cursor_width: nonzero_or(cap(drm::DriverCapability::CursorWidth), 64),
cursor_height: nonzero_or(cap(drm::DriverCapability::CursorHeight), 64),
fb_damage_clips,
vrr_capable,
psr,
})
}
}
use drmkit_core::{CoreError, ObjectType, PropertyStore};
pub(crate) fn errno(error: &std::io::Error) -> CoreError {
CoreError::Io(rustix::io::Errno::from_io_error(error).unwrap_or(rustix::io::Errno::IO))
}
pub(crate) const fn nonzero_or(reported: Option<u64>, fallback: u64) -> u64 {
match reported {
Some(v) if v != 0 => v,
_ => fallback,
}
}
fn object_has_property(device: &Device, id: u32, kind: ObjectType, name: &str) -> bool {
let mut store = PropertyStore::new();
store.cache_properties(device, id, kind).is_ok() && store.property_id(id, name).is_ok()
}
fn connector_type_code(interface: drm::control::connector::Interface) -> u32 {
use drm::control::connector::Interface;
match interface {
Interface::EmbeddedDisplayPort => CONNECTOR_EDP,
Interface::DSI => CONNECTOR_DSI,
_ => 0,
}
}