use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, PageFlipFlags};
use std::collections::HashSet;
use std::os::unix::io::AsRawFd;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
};
use crate::backend::drm::{
device::legacy::set_connector_state,
device::{DevPath, DrmDeviceInternal},
error::Error,
};
use slog::{debug, info, o, trace};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
pub mode: Mode,
pub connectors: HashSet<connector::Handle>,
}
impl State {
fn current_state<A: AsRawFd + ControlDevice>(fd: &A, crtc: crtc::Handle) -> Result<Self, Error> {
let crtc_info = fd.get_crtc(crtc).map_err(|source| Error::Access {
errmsg: "Error loading crtc info",
dev: fd.dev_path(),
source,
})?;
let current_mode = crtc_info.mode();
let mut current_connectors = HashSet::new();
let res_handles = fd.resource_handles().map_err(|source| Error::Access {
errmsg: "Error loading drm resources",
dev: fd.dev_path(),
source,
})?;
for &con in res_handles.connectors() {
let con_info = fd.get_connector(con).map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: fd.dev_path(),
source,
})?;
if let Some(enc) = con_info.current_encoder() {
let enc_info = fd.get_encoder(enc).map_err(|source| Error::Access {
errmsg: "Error loading encoder info",
dev: fd.dev_path(),
source,
})?;
if let Some(current_crtc) = enc_info.crtc() {
if crtc == current_crtc {
current_connectors.insert(con);
}
}
}
}
Ok(State {
mode: current_mode.unwrap_or_else(|| unsafe { std::mem::zeroed() }),
connectors: current_connectors,
})
}
}
#[derive(Debug)]
pub struct LegacyDrmSurface<A: AsRawFd + 'static> {
pub(super) fd: Arc<DrmDeviceInternal<A>>,
pub(super) active: Arc<AtomicBool>,
crtc: crtc::Handle,
state: RwLock<State>,
pending: RwLock<State>,
pub(crate) logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
pub fn new(
fd: Arc<DrmDeviceInternal<A>>,
active: Arc<AtomicBool>,
crtc: crtc::Handle,
mode: Mode,
connectors: &[connector::Handle],
logger: ::slog::Logger,
) -> Result<Self, Error> {
let logger = logger.new(o!("smithay_module" => "backend_drm_legacy", "drm_module" => "surface"));
info!(
logger,
"Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors
);
let state = State::current_state(&*fd, crtc)?;
let pending = State {
mode,
connectors: connectors.iter().copied().collect(),
};
let surface = LegacyDrmSurface {
fd,
active,
crtc,
state: RwLock::new(state),
pending: RwLock::new(pending),
logger,
};
Ok(surface)
}
pub fn current_connectors(&self) -> HashSet<connector::Handle> {
self.state.read().unwrap().connectors.clone()
}
pub fn pending_connectors(&self) -> HashSet<connector::Handle> {
self.pending.read().unwrap().connectors.clone()
}
pub fn current_mode(&self) -> Mode {
self.state.read().unwrap().mode
}
pub fn pending_mode(&self) -> Mode {
self.pending.read().unwrap().mode
}
pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap();
if self.check_connector(conn, &pending.mode)? {
pending.connectors.insert(conn);
}
Ok(())
}
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
let mut pending = self.pending.write().unwrap();
if pending.connectors.contains(&connector) && pending.connectors.len() == 1 {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
pending.connectors.remove(&connector);
Ok(())
}
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
if connectors.is_empty() {
return Err(Error::SurfaceWithoutConnectors(self.crtc));
}
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap();
if connectors
.iter()
.map(|conn| self.check_connector(*conn, &pending.mode))
.collect::<Result<Vec<bool>, _>>()?
.iter()
.all(|v| *v)
{
pending.connectors = connectors.iter().cloned().collect();
}
Ok(())
}
pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut pending = self.pending.write().unwrap();
for connector in &pending.connectors {
if !self
.fd
.get_connector(*connector)
.map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: self.fd.dev_path(),
source,
})?
.modes()
.contains(&mode)
{
return Err(Error::ModeNotSuitable(mode));
}
}
pending.mode = mode;
Ok(())
}
pub fn commit_pending(&self) -> bool {
*self.pending.read().unwrap() != *self.state.read().unwrap()
}
pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let mut current = self.state.write().unwrap();
let pending = self.pending.read().unwrap();
{
let removed = current.connectors.difference(&pending.connectors);
let added = pending.connectors.difference(¤t.connectors);
let mut conn_removed = false;
for conn in removed.clone() {
if let Ok(info) = self.fd.get_connector(*conn) {
info!(self.logger, "Removing connector: {:?}", info.interface());
} else {
info!(self.logger, "Removing unknown connector");
}
conn_removed = true;
}
set_connector_state(&*self.fd, removed.copied(), false)?;
if conn_removed {
self.fd
.set_crtc(self.crtc, None, (0, 0), &[], None)
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.fd.dev_path(),
source,
})?;
}
for conn in added.clone() {
if let Ok(info) = self.fd.get_connector(*conn) {
info!(self.logger, "Adding connector: {:?}", info.interface());
} else {
info!(self.logger, "Adding unknown connector");
}
}
set_connector_state(&*self.fd, added.copied(), true)?;
if current.mode != pending.mode {
info!(self.logger, "Setting new mode: {:?}", pending.mode.name());
}
}
debug!(self.logger, "Setting screen");
self.fd
.set_crtc(
self.crtc,
Some(framebuffer),
(0, 0),
&pending
.connectors
.iter()
.copied()
.collect::<Vec<connector::Handle>>(),
Some(pending.mode),
)
.map_err(|source| Error::Access {
errmsg: "Error setting crtc",
dev: self.fd.dev_path(),
source,
})?;
*current = pending.clone();
if event {
ControlDevice::page_flip(
&*self.fd,
self.crtc,
framebuffer,
&[PageFlipFlags::PageFlipEvent],
None,
)
.map_err(|source| Error::Access {
errmsg: "Failed to queue page flip",
dev: self.fd.dev_path(),
source,
})?;
}
Ok(())
}
pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
trace!(self.logger, "Queueing Page flip");
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
ControlDevice::page_flip(
&*self.fd,
self.crtc,
framebuffer,
if event {
&[PageFlipFlags::PageFlipEvent]
} else {
&[]
},
None,
)
.map_err(|source| Error::Access {
errmsg: "Failed to page flip",
dev: self.fd.dev_path(),
source,
})
}
pub fn test_buffer(&self, fb: framebuffer::Handle, mode: &Mode) -> Result<bool, Error> {
if !self.active.load(Ordering::SeqCst) {
return Err(Error::DeviceInactive);
}
let pending = self.pending.read().unwrap();
debug!(self.logger, "Setting screen for buffer *testing*");
Ok(self
.fd
.set_crtc(
self.crtc,
Some(fb),
(0, 0),
&pending
.connectors
.iter()
.copied()
.collect::<Vec<connector::Handle>>(),
Some(*mode),
)
.is_ok())
}
fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
let info = self.fd.get_connector(conn).map_err(|source| Error::Access {
errmsg: "Error loading connector info",
dev: self.fd.dev_path(),
source,
})?;
if info.modes().contains(mode) {
let encoders = info
.encoders()
.iter()
.flatten()
.map(|encoder| {
self.fd.get_encoder(*encoder).map_err(|source| Error::Access {
errmsg: "Error loading encoder info",
dev: self.fd.dev_path(),
source,
})
})
.collect::<Result<Vec<encoder::Info>, _>>()?;
let resource_handles = self.fd.resource_handles().map_err(|source| Error::Access {
errmsg: "Error loading resources",
dev: self.fd.dev_path(),
source,
})?;
if !encoders
.iter()
.map(|encoder| encoder.possible_crtcs())
.all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc))
{
Ok(false)
} else {
Ok(true)
}
} else {
Ok(false)
}
}
pub(crate) fn reset_state<B: AsRawFd + ControlDevice + 'static>(
&self,
fd: Option<&B>,
) -> Result<(), Error> {
*self.state.write().unwrap() = if let Some(fd) = fd {
State::current_state(fd, self.crtc)?
} else {
State::current_state(&*self.fd, self.crtc)?
};
Ok(())
}
}
impl<A: AsRawFd + 'static> Drop for LegacyDrmSurface<A> {
fn drop(&mut self) {
if !self.active.load(Ordering::SeqCst) {
return;
}
let current = self.state.read().unwrap();
if set_connector_state(&*self.fd, current.connectors.iter().copied(), false).is_ok() {
let _ = self.fd.set_crtc(self.crtc, None, (0, 0), &[], None);
}
}
}
#[cfg(test)]
mod test {
use super::LegacyDrmSurface;
use std::fs::File;
fn is_send<S: Send>() {}
#[test]
fn surface_is_send() {
is_send::<LegacyDrmSurface<File>>();
}
}