#[cfg(feature = "backend_session")]
use std::cell::RefCell;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
use std::sync::{atomic::AtomicBool, Arc};
use calloop::{EventSource, Interest, Poll, PostAction, Readiness, Token, TokenFactory};
use drm::control::{connector, crtc, Device as ControlDevice, Event, Mode, ResourceHandles};
use drm::{ClientCapability, Device as BasicDevice};
use nix::libc::dev_t;
use nix::sys::stat::fstat;
pub(super) mod atomic;
pub(super) mod legacy;
use super::surface::{atomic::AtomicDrmSurface, legacy::LegacyDrmSurface, DrmSurface, DrmSurfaceInternal};
use super::{error::Error, planes, Planes};
use atomic::AtomicDrmDevice;
use legacy::LegacyDrmDevice;
use slog::{error, info, o, trace, warn};
#[derive(Debug)]
pub struct DrmDevice<A: AsRawFd + 'static> {
pub(super) dev_id: dev_t,
pub(crate) internal: Arc<DrmDeviceInternal<A>>,
#[cfg(feature = "backend_session")]
pub(super) links: RefCell<Vec<crate::utils::signaling::SignalToken>>,
has_universal_planes: bool,
resources: ResourceHandles,
pub(super) logger: ::slog::Logger,
token: Token,
}
impl<A: AsRawFd + 'static> AsRawFd for DrmDevice<A> {
fn as_raw_fd(&self) -> RawFd {
match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
}
}
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDevice<A> {}
#[derive(Debug)]
pub struct FdWrapper<A: AsRawFd + 'static> {
fd: A,
pub(super) privileged: bool,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for FdWrapper<A> {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for FdWrapper<A> {}
impl<A: AsRawFd + 'static> ControlDevice for FdWrapper<A> {}
impl<A: AsRawFd + 'static> Drop for FdWrapper<A> {
fn drop(&mut self) {
info!(self.logger, "Dropping device: {:?}", self.dev_path());
if self.privileged {
if let Err(err) = self.release_master_lock() {
error!(self.logger, "Failed to drop drm master state. Error: {}", err);
}
}
}
}
#[derive(Debug)]
pub enum DrmDeviceInternal<A: AsRawFd + 'static> {
Atomic(AtomicDrmDevice<A>),
Legacy(LegacyDrmDevice<A>),
}
impl<A: AsRawFd + 'static> AsRawFd for DrmDeviceInternal<A> {
fn as_raw_fd(&self) -> RawFd {
match self {
DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
}
}
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> DrmDevice<A> {
pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
where
A: AsRawFd + 'static,
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm"));
info!(log, "DrmDevice initializing");
let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev;
let active = Arc::new(AtomicBool::new(true));
let dev = Arc::new({
let mut dev = FdWrapper {
fd,
privileged: false,
logger: log.clone(),
};
if dev.acquire_master_lock().is_err() {
warn!(log, "Unable to become drm master, assuming unprivileged mode");
} else {
dev.privileged = true;
}
dev
});
let has_universal_planes = dev
.set_client_capability(ClientCapability::UniversalPlanes, true)
.is_ok();
let resources = dev.resource_handles().map_err(|source| Error::Access {
errmsg: "Error loading resource handles",
dev: dev.dev_path(),
source,
})?;
let internal = Arc::new(DrmDevice::create_internal(
dev,
active,
disable_connectors,
log.clone(),
)?);
Ok(DrmDevice {
dev_id,
internal,
#[cfg(feature = "backend_session")]
links: RefCell::new(Vec::new()),
has_universal_planes,
resources,
logger: log,
token: Token::invalid(),
})
}
fn create_internal(
dev: Arc<FdWrapper<A>>,
active: Arc<AtomicBool>,
disable_connectors: bool,
log: ::slog::Logger,
) -> Result<DrmDeviceInternal<A>, Error> {
let force_legacy = std::env::var("SMITHAY_USE_LEGACY")
.map(|x| {
x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
})
.unwrap_or(false);
if force_legacy {
info!(log, "SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice.");
};
Ok(
if !force_legacy && dev.set_client_capability(ClientCapability::Atomic, true).is_ok() {
DrmDeviceInternal::Atomic(AtomicDrmDevice::new(dev, active, disable_connectors, log)?)
} else {
info!(log, "Falling back to LegacyDrmDevice");
DrmDeviceInternal::Legacy(LegacyDrmDevice::new(dev, active, disable_connectors, log)?)
},
)
}
pub fn is_atomic(&self) -> bool {
match *self.internal {
DrmDeviceInternal::Atomic(_) => true,
DrmDeviceInternal::Legacy(_) => false,
}
}
pub fn crtcs(&self) -> &[crtc::Handle] {
self.resources.crtcs()
}
pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> {
planes(self, crtc, self.has_universal_planes)
}
pub fn create_surface(
&self,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
) -> Result<DrmSurface<A>, Error> {
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(crtc));
}
let plane = planes(self, &crtc, self.has_universal_planes)?.primary;
let info = self.get_plane(plane).map_err(|source| Error::Access {
errmsg: "Failed to get plane info",
dev: self.dev_path(),
source,
})?;
let filter = info.possible_crtcs();
if !self.resources.filter_crtcs(filter).contains(&crtc) {
return Err(Error::PlaneNotCompatible(crtc, plane));
}
let active = match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.active.clone(),
DrmDeviceInternal::Legacy(dev) => dev.active.clone(),
};
let internal = if self.is_atomic() {
let mapping = match &*self.internal {
DrmDeviceInternal::Atomic(dev) => dev.prop_mapping.clone(),
_ => unreachable!(),
};
DrmSurfaceInternal::Atomic(AtomicDrmSurface::new(
self.internal.clone(),
active,
crtc,
plane,
mapping,
mode,
connectors,
self.logger.clone(),
)?)
} else {
DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(
self.internal.clone(),
active,
crtc,
mode,
connectors,
self.logger.clone(),
)?)
};
Ok(DrmSurface {
dev_id: self.dev_id,
crtc,
primary: plane,
internal: Arc::new(internal),
has_universal_planes: self.has_universal_planes,
#[cfg(feature = "backend_session")]
links: RefCell::new(Vec::new()),
})
}
pub fn device_id(&self) -> dev_t {
self.dev_id
}
}
pub trait DevPath {
fn dev_path(&self) -> Option<PathBuf>;
}
impl<A: AsRawFd> DevPath for A {
fn dev_path(&self) -> Option<PathBuf> {
use std::fs;
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
}
}
#[derive(Debug)]
pub enum DrmEvent {
VBlank(crtc::Handle),
Error(Error),
}
impl<A> EventSource for DrmDevice<A>
where
A: AsRawFd + 'static,
{
type Event = DrmEvent;
type Metadata = ();
type Ret = ();
fn process_events<F>(
&mut self,
_: Readiness,
token: Token,
mut callback: F,
) -> std::io::Result<PostAction>
where
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
if token != self.token {
return Ok(PostAction::Continue);
}
match self.receive_events() {
Ok(events) => {
for event in events {
if let Event::PageFlip(event) = event {
trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc);
callback(DrmEvent::VBlank(event.crtc), &mut ());
} else {
trace!(
self.logger,
"Got a non-page-flip event of device '{:?}'.",
self.dev_path()
);
}
}
}
Err(source) => {
callback(
DrmEvent::Error(Error::Access {
errmsg: "Error processing drm events",
dev: self.dev_path(),
source,
}),
&mut (),
);
}
}
Ok(PostAction::Continue)
}
fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> std::io::Result<()> {
self.token = factory.token();
poll.register(self.as_raw_fd(), Interest::READ, calloop::Mode::Level, self.token)
}
fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> std::io::Result<()> {
self.token = factory.token();
poll.reregister(self.as_raw_fd(), Interest::READ, calloop::Mode::Level, self.token)
}
fn unregister(&mut self, poll: &mut Poll) -> std::io::Result<()> {
self.token = Token::invalid();
poll.unregister(self.as_raw_fd())
}
}