diff --git a/Cargo.toml b/Cargo.toml index 1c34590..f70a2af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,10 @@ slog-term = "2.3" gl_generator = { version = "0.14", optional = true } [features] -default = ["backend_winit", "backend_drm_legacy", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "backend_session_logind", "renderer_glium", "xwayland", "wayland_frontend"] +default = ["backend_winit", "backend_drm_legacy", "backend_drm_atomic", "backend_drm_gbm", "backend_drm_egl", "backend_libinput", "backend_udev", "backend_session_logind", "renderer_glium", "xwayland", "wayland_frontend"] backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl", "use_system_lib"] backend_drm = ["drm", "failure"] +backend_drm_atomic = ["backend_drm"] backend_drm_legacy = ["backend_drm"] backend_drm_gbm = ["backend_drm", "gbm", "image"] backend_drm_egl = ["backend_drm", "backend_egl"] diff --git a/src/backend/drm/atomic/mod.rs b/src/backend/drm/atomic/mod.rs new file mode 100644 index 0000000..3bee9a1 --- /dev/null +++ b/src/backend/drm/atomic/mod.rs @@ -0,0 +1,388 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use drm::control::{atomic::AtomicModeReq, AtomicCommitFlags, Device as ControlDevice, Event}; +use drm::control::{ + connector, crtc, encoder, framebuffer, plane, property, PropertyValueSet, ResourceHandle, ResourceHandles, +}; +use drm::SystemError as DrmError; +use drm::{ClientCapability, Device as BasicDevice}; +use failure::{Fail, ResultExt}; +use nix::libc::dev_t; +use nix::sys::stat::fstat; + +use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice}; + +mod surface; +pub use self::surface::AtomicDrmSurface; +use self::surface::AtomicDrmSurfaceInternal; + +#[cfg(feature = "backend_session")] +pub mod session; + +pub struct AtomicDrmDevice { + dev: Rc>, + dev_id: dev_t, + active: Arc, + backends: Rc>>>>, + handler: Option>>>>, + logger: ::slog::Logger, +} + +type OldState = ( + Vec<(connector::Handle, PropertyValueSet)>, + Vec<(crtc::Handle, PropertyValueSet)>, + Vec<(framebuffer::Handle, PropertyValueSet)>, + Vec<(plane::Handle, PropertyValueSet)>, +); + +type Mapping = ( + HashMap>, + HashMap>, + HashMap>, + HashMap>, +); + +struct Dev { + fd: A, + privileged: bool, + active: Arc, + old_state: OldState, + prop_mapping: Mapping, + logger: ::slog::Logger, +} + +impl AsRawFd for Dev { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} +impl BasicDevice for Dev {} +impl ControlDevice for Dev {} +impl Drop for Dev { + fn drop(&mut self) { + info!(self.logger, "Dropping device: {:?}", self.dev_path()); + if self.active.load(Ordering::SeqCst) { + // Here we restore the card/tty's to it's previous state. + // In case e.g. getty was running on the tty sets the correct framebuffer again, + // so that getty will be visible. + + let mut req = AtomicModeReq::new(); + + fn add_multiple_props( + req: &mut AtomicModeReq, + old_state: &[(T, PropertyValueSet)], + ) { + for (handle, set) in old_state { + let (prop_handles, values) = set.as_props_and_values(); + for (&prop_handle, &val) in prop_handles.iter().zip(values.iter()) { + req.add_raw_property((*handle).into(), prop_handle, val); + } + } + }; + + add_multiple_props(&mut req, &self.old_state.0); + add_multiple_props(&mut req, &self.old_state.1); + add_multiple_props(&mut req, &self.old_state.2); + add_multiple_props(&mut req, &self.old_state.3); + + if let Err(err) = self.atomic_commit(&[AtomicCommitFlags::AllowModeset], req) { + error!(self.logger, "Failed to restore previous state. Error: {}", err); + } + } + if self.privileged { + if let Err(err) = self.release_master_lock() { + error!(self.logger, "Failed to drop drm master state. Error: {}", err); + } + } + } +} + +impl AtomicDrmDevice { + /// Create a new [`AtomicDrmDevice`] from an open drm node + /// + /// Returns an error if the file is no valid drm node or context creation was not + /// successful. + pub fn new(fd: A, logger: L) -> Result + where + L: Into>, + { + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); + info!(log, "AtomicDrmDevice initializing"); + + let dev_id = fstat(fd.as_raw_fd()) + .map_err(|source| Error::UnableToGetDeviceId(source))? + .st_rdev; + + let active = Arc::new(AtomicBool::new(true)); + let mut dev = Dev { + fd, + privileged: true, + active: active.clone(), + old_state: (Vec::new(), Vec::new(), Vec::new(), Vec::new()), + prop_mapping: (HashMap::new(), HashMap::new(), HashMap::new(), HashMap::new()), + logger: log.clone(), + }; + + // we want to modeset, so we better be the master, if we run via a tty session + if dev.acquire_master_lock().is_err() { + warn!(log, "Unable to become drm master, assuming unprivileged mode"); + dev.privileged = false; + }; + + // enable the features we need + dev.set_client_capability(ClientCapability::UniversalPlanes, true) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error enabling UniversalPlanes", + dev: dev.dev_path(), + source, + })?; + dev.set_client_capability(ClientCapability::Atomic, true) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error enabling AtomicModesetting", + dev: dev.dev_path(), + source, + })?; + + // enumerate (and save) the current device state + let res_handles = ControlDevice::resource_handles(&dev) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error loading drm resources", + dev: dev.dev_path(), + source, + })?; + + let plane_handles = dev.plane_handles().compat().map_err(|source| Error::Access { + errmsg: "Error loading planes", + dev: dev.dev_path(), + source, + })?; + let planes = plane_handles.planes(); + + fn add_props( + dev: &Dev, + handles: &[T], + state: &mut Vec<(T, PropertyValueSet)>, + ) -> Result<(), Error> + where + A: AsRawFd + 'static, + T: ResourceHandle, + { + let iter = handles.iter().map(|x| (x, dev.get_properties(*x))); + if let Some(len) = iter.size_hint().1 { + state.reserve_exact(len) + } + + iter.map(|(x, y)| (*x, y)) + .try_for_each(|(x, y)| match y { + Ok(y) => { + state.push((x, y)); + Ok(()) + } + Err(err) => Err(err), + }) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error reading properties", + dev: dev.dev_path(), + source, + }) + } + + fn map_props( + dev: &Dev, + handles: &[T], + mapping: &mut HashMap>, + ) -> Result<(), Error> + where + A: AsRawFd + 'static, + T: ResourceHandle + Eq + std::hash::Hash, + { + handles + .iter() + .map(|x| (x, dev.get_properties(*x))) + .try_for_each(|(handle, props)| { + let mut map = HashMap::new(); + //let prop_handles: PropertyValueSet = props.compat()?; + match props { + Ok(props) => { + let (prop_handles, _) = props.as_props_and_values(); + for prop in prop_handles { + if let Ok(info) = dev.get_property(*prop) { + let name = info.name().to_string_lossy().into_owned(); + map.insert(name, *prop); + } + } + mapping.insert(*handle, map); + Ok(()) + } + Err(err) => Err(err), + } + }) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error reading properties on {:?}", + dev: dev.dev_path(), + source, + }) + } + + let mut old_state = dev.old_state.clone(); + let mut mapping = dev.prop_mapping.clone(); + + add_props(&dev, res_handles.connectors(), &mut old_state.0)?; + add_props(&dev, res_handles.crtcs(), &mut old_state.1)?; + add_props(&dev, res_handles.framebuffers(), &mut old_state.2)?; + add_props(&dev, planes, &mut old_state.3)?; + + map_props(&dev, res_handles.connectors(), &mut mapping.0)?; + map_props(&dev, res_handles.crtcs(), &mut mapping.1)?; + map_props(&dev, res_handles.framebuffers(), &mut mapping.2)?; + map_props(&dev, planes, &mut mapping.3)?; + + dev.old_state = old_state; + dev.prop_mapping = mapping; + debug!(log, "Mapping: {:#?}", dev.prop_mapping); + + Ok(AtomicDrmDevice { + dev: Rc::new(dev), + dev_id, + active, + backends: Rc::new(RefCell::new(HashMap::new())), + handler: None, + logger: log.clone(), + }) + } +} + +impl AsRawFd for AtomicDrmDevice { + fn as_raw_fd(&self) -> RawFd { + self.dev.as_raw_fd() + } +} + +impl BasicDevice for AtomicDrmDevice {} +impl ControlDevice for AtomicDrmDevice {} + +impl Device for AtomicDrmDevice { + type Surface = AtomicDrmSurface; + + fn device_id(&self) -> dev_t { + self.dev_id + } + + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + self.handler = Some(RefCell::new(Box::new(handler))); + } + + fn clear_handler(&mut self) { + let _ = self.handler.take(); + } + + fn create_surface(&mut self, crtc: crtc::Handle) -> Result, Error> { + if self.backends.borrow().contains_key(&crtc) { + return Err(Error::CrtcAlreadyInUse(crtc)); + } + + if !self.active.load(Ordering::SeqCst) { + return Err(Error::DeviceInactive); + } + + let backend = Rc::new(AtomicDrmSurfaceInternal::new( + self.dev.clone(), + crtc, + self.logger.new(o!("crtc" => format!("{:?}", crtc))), + )?); + + self.backends.borrow_mut().insert(crtc, Rc::downgrade(&backend)); + Ok(AtomicDrmSurface(backend)) + } + + fn process_events(&mut self) { + match self.receive_events() { + Ok(events) => { + for event in events { + if let Event::PageFlip(event) = event { + trace!(self.logger, "Got event!"); + if self.active.load(Ordering::SeqCst) { + if self + .backends + .borrow() + .get(&event.crtc) + .iter() + .flat_map(|x| x.upgrade()) + .next() + .is_some() + { + trace!(self.logger, "Handling event for backend {:?}", event.crtc); + if let Some(handler) = self.handler.as_ref() { + handler.borrow_mut().vblank(event.crtc); + } + } else { + self.backends.borrow_mut().remove(&event.crtc); + } + } else { + debug!(self.logger, "Device not active. Ignoring PageFlip"); + } + } else { + trace!(self.logger, "Unrelated event"); + } + } + } + Err(source) => { + if let Some(handler) = self.handler.as_ref() { + handler.borrow_mut().error(Error::Access { + errmsg: "Error processing drm events", + dev: self.dev_path(), + source: source.compat(), + }); + } + } + } + } + + fn resource_handles(&self) -> Result { + ControlDevice::resource_handles(self) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error loading resource info", + dev: self.dev_path(), + source, + }) + } + + fn get_connector_info(&self, conn: connector::Handle) -> Result { + self.get_connector(conn) + } + fn get_crtc_info(&self, crtc: crtc::Handle) -> Result { + self.get_crtc(crtc) + } + fn get_encoder_info(&self, enc: encoder::Handle) -> Result { + self.get_encoder(enc) + } + fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result { + self.get_framebuffer(fb) + } + fn get_plane_info(&self, plane: plane::Handle) -> Result { + self.get_plane(plane) + } +} + +impl RawDevice for AtomicDrmDevice { + type Surface = AtomicDrmSurface; +} + +impl Drop for AtomicDrmDevice { + fn drop(&mut self) { + self.clear_handler(); + } +} diff --git a/src/backend/drm/atomic/session.rs b/src/backend/drm/atomic/session.rs new file mode 100644 index 0000000..377084b --- /dev/null +++ b/src/backend/drm/atomic/session.rs @@ -0,0 +1,89 @@ +use drm::control::crtc; +use drm::Device as BasicDevice; +use nix::libc::dev_t; +use nix::sys::stat; +use std::cell::RefCell; +use std::collections::HashMap; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::{Rc, Weak}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use super::{AtomicDrmDevice, AtomicDrmSurfaceInternal, Dev}; +use crate::backend::session::{AsSessionObserver, SessionObserver}; + +pub struct AtomicDrmDeviceObserver { + dev: Weak>, + dev_id: dev_t, + privileged: bool, + active: Arc, + backends: Weak>>>>, + logger: ::slog::Logger, +} + +impl AsSessionObserver> for AtomicDrmDevice { + fn observer(&mut self) -> AtomicDrmDeviceObserver { + AtomicDrmDeviceObserver { + dev: Rc::downgrade(&self.dev), + dev_id: self.dev_id, + active: self.active.clone(), + privileged: self.dev.privileged, + backends: Rc::downgrade(&self.backends), + logger: self.logger.clone(), + } + } +} + +impl SessionObserver for AtomicDrmDeviceObserver { + fn pause(&mut self, devnum: Option<(u32, u32)>) { + if let Some((major, minor)) = devnum { + if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { + return; + } + } + // TODO: Clear overlay planes (if we ever use them) + if let Some(backends) = self.backends.upgrade() { + for surface in backends.borrow().values().filter_map(Weak::upgrade) { + // other ttys that use no cursor, might not clear it themselves. + // This makes sure our cursor won't stay visible. + if let Err(err) = surface.clear_plane(surface.planes.cursor) { + warn!( + self.logger, + "Failed to clear cursor on {:?}: {}", surface.crtc, err + ); + } + } + } + + self.active.store(false, Ordering::SeqCst); + if self.privileged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.release_master_lock() { + error!(self.logger, "Failed to drop drm master state Error: {}", err); + } + } + } + } + + fn activate(&mut self, devnum: Option<(u32, u32, Option)>) { + if let Some((major, minor, fd)) = devnum { + if major as u64 != stat::major(self.dev_id) || minor as u64 != stat::minor(self.dev_id) { + return; + } else if let Some(fd) = fd { + info!(self.logger, "Replacing fd"); + if let Some(device) = self.dev.upgrade() { + ::nix::unistd::dup2(device.as_raw_fd(), fd) + .expect("Failed to replace file descriptor of drm device"); + } + } + } + if self.privileged { + if let Some(device) = self.dev.upgrade() { + if let Err(err) = device.acquire_master_lock() { + crit!(self.logger, "Failed to acquire drm master again. Error: {}", err); + } + } + } + self.active.store(true, Ordering::SeqCst); + } +} diff --git a/src/backend/drm/atomic/surface.rs b/src/backend/drm/atomic/surface.rs new file mode 100644 index 0000000..4dda5a5 --- /dev/null +++ b/src/backend/drm/atomic/surface.rs @@ -0,0 +1,836 @@ +use drm::buffer::Buffer; +use drm::control::atomic::AtomicModeReq; +use drm::control::Device as ControlDevice; +use drm::control::{connector, crtc, framebuffer, plane, property, AtomicCommitFlags, Mode, PlaneType}; +use drm::Device as BasicDevice; + +use std::cell::Cell; +use std::collections::HashSet; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::Rc; +use std::sync::RwLock; + +use failure::ResultExt as FailureResultExt; + +use super::Dev; +use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; +use crate::backend::graphics::CursorBackend; +use crate::backend::graphics::SwapBuffersError; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CursorState { + position: Cell>, + hotspot: Cell<(u32, u32)>, + framebuffer: Cell>, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct State { + pub mode: Option, + pub blob: Option>, + pub connectors: HashSet, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Planes { + pub primary: plane::Handle, + pub cursor: plane::Handle, +} + +pub(super) struct AtomicDrmSurfaceInternal { + pub(super) dev: Rc>, + pub(super) crtc: crtc::Handle, + pub(super) cursor: CursorState, + pub(super) planes: Planes, + pub(super) state: RwLock, + pub(super) pending: RwLock, + pub(super) logger: ::slog::Logger, +} + +impl AsRawFd for AtomicDrmSurfaceInternal { + fn as_raw_fd(&self) -> RawFd { + self.dev.as_raw_fd() + } +} + +impl BasicDevice for AtomicDrmSurfaceInternal {} +impl ControlDevice for AtomicDrmSurfaceInternal {} + +impl AtomicDrmSurfaceInternal { + pub(crate) fn new(dev: Rc>, crtc: crtc::Handle, logger: ::slog::Logger) -> Result { + let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { + errmsg: "Error loading crtc info", + dev: dev.dev_path(), + source, + })?; + + let mode = crtc_info.mode(); + let blob = match mode { + Some(mode) => Some( + dev.create_property_blob(mode) + .compat() + .map_err(|source| Error::Access { + errmsg: "Failed to create Property Blob for mode", + dev: dev.dev_path(), + source, + })?, + ), + None => None, + }; + + let res_handles = ControlDevice::resource_handles(&*dev) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error loading drm resources", + dev: dev.dev_path(), + source, + })?; + + let mut state = State { + mode, + blob, + connectors: HashSet::new(), + }; + + for conn in res_handles.connectors() { + let crtc_prop = dev + .prop_mapping + .0 + .get(&conn) + .expect("Unknown handle") + .get("CRTC_ID") + .ok_or_else(|| Error::UnknownProperty { + handle: (*conn).into(), + name: "CRTC_ID", + }) + .map(|x| *x)?; + if let (Ok(crtc_prop_info), Ok(props)) = (dev.get_property(crtc_prop), dev.get_properties(*conn)) + { + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + if id == crtc_prop { + if let property::Value::CRTC(Some(conn_crtc)) = + crtc_prop_info.value_type().convert_value(val) + { + if conn_crtc == crtc { + state.connectors.insert(*conn); + } + } + break; + } + } + } + } + + let (primary, cursor) = + AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes { + crtc, + dev: dev.dev_path(), + })?; + Ok(AtomicDrmSurfaceInternal { + dev, + crtc, + cursor: CursorState { + position: Cell::new(None), + framebuffer: Cell::new(None), + hotspot: Cell::new((0, 0)), + }, + planes: Planes { primary, cursor }, + state: RwLock::new(state.clone()), + pending: RwLock::new(state), + logger, + }) + } +} + +impl Surface for AtomicDrmSurfaceInternal { + type Error = Error; + type Connectors = HashSet; + + fn crtc(&self) -> crtc::Handle { + self.crtc + } + + fn current_connectors(&self) -> Self::Connectors { + self.state.read().unwrap().connectors.clone() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.pending.read().unwrap().connectors.clone() + } + + fn current_mode(&self) -> Option { + self.state.read().unwrap().mode.clone() + } + + fn pending_mode(&self) -> Option { + self.pending.read().unwrap().mode.clone() + } + + fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> { + let info = self + .get_connector(conn) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error loading connector info", + dev: self.dev_path(), + source, + })?; + + let mut pending = self.pending.write().unwrap(); + + // check if the connector can handle the current mode + if info.modes().contains(pending.mode.as_ref().unwrap()) { + let mut conns = pending.connectors.clone(); + conns.insert(conn); + + // check if config is supported + let req = self.build_request(&conns, &self.planes, None, pending.mode, pending.blob)?; + self.atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req, + ) + .compat() + .map_err(|_| Error::TestFailed(self.crtc))?; + + // seems to be, lets add the connector + pending.connectors = conns; + + Ok(()) + } else { + Err(Error::ModeNotSuitable(pending.mode.unwrap())) + } + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { + let mut pending = self.pending.write().unwrap(); + + // remove it temporary + let mut conns = pending.connectors.clone(); + conns.remove(&connector); + + // check if new config is supported (should be) + let req = self.build_request(&conns, &self.planes, None, pending.mode, pending.blob)?; + self.atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req, + ) + .compat() + .map_err(|_| Error::TestFailed(self.crtc))?; + + // seems to be, lets remove the connector + pending.connectors = conns; + + // try to disable it + let mut req = AtomicModeReq::new(); + + req.add_property( + connector, + self.conn_prop_handle(connector, "CRTC_ID")?, + property::Value::CRTC(None), + ); + + if let Err(err) = self + .atomic_commit(&[AtomicCommitFlags::TestOnly], req.clone()) + .compat() + .map_err(|_| Error::TestFailed(self.crtc)) + { + warn!( + self.logger, + "Could not disable connector ({:?}) (but rendering will be stopped): {}", connector, err + ); + Ok(()) + } else { + // should succeed, any error is serious + self.atomic_commit(&[AtomicCommitFlags::Nonblock], req.clone()) + .compat() + .map_err(|source| Error::Access { + errmsg: "Failed to commit disable connector", + dev: self.dev_path(), + source, + }) + } + } + + fn use_mode(&self, mode: Option) -> Result<(), Error> { + let mut pending = self.pending.write().unwrap(); + + // check if new config is supported + let new_blob = Some(match mode { + Some(mode) => self + .dev + .create_property_blob(mode) + .compat() + .map_err(|source| Error::Access { + errmsg: "Failed to create Property Blob for mode", + dev: self.dev_path(), + source, + })?, + None => property::Value::Unknown(0), + }); + + let req = self.build_request(&pending.connectors, &self.planes, None, mode, new_blob)?; + if let Err(err) = self + .atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req, + ) + .compat() + .map_err(|_| Error::TestFailed(self.crtc)) + { + let _ = self.dev.destroy_property_blob(new_blob.unwrap().into()); + return Err(err); + } + + // seems to be, lets change the mode + pending.mode = mode; + pending.blob = new_blob; + + Ok(()) + } +} + +impl RawSurface for AtomicDrmSurfaceInternal { + fn commit_pending(&self) -> bool { + *self.pending.read().unwrap() != *self.state.read().unwrap() + } + + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { + let mut current = self.state.write().unwrap(); + let mut pending = self.pending.write().unwrap(); + + debug!( + self.logger, + "Preparing Commit.\n\tCurrent: {:?}\n\tPending: {:?}\n", *current, *pending + ); + + { + let current_conns = current.connectors.clone(); + let pending_conns = pending.connectors.clone(); + let removed = current_conns.difference(&pending_conns); + let added = pending_conns.difference(¤t_conns); + + for conn in removed { + if let Ok(info) = self.get_connector(*conn) { + info!(self.logger, "Removing connector: {:?}", info.interface()); + } else { + info!(self.logger, "Removing unknown connector"); + } + } + + for conn in added { + if let Ok(info) = self.get_connector(*conn) { + info!(self.logger, "Adding connector: {:?}", info.interface()); + } else { + info!(self.logger, "Adding unknown connector"); + } + } + + if current.mode != pending.mode { + info!( + self.logger, + "Setting new mode: {:?}", + pending.mode.as_ref().unwrap().name() + ); + } + } + + trace!(self.logger, "Testing screen config"); + { + let req = self.build_request( + &pending.connectors, + &self.planes, + Some(framebuffer), + pending.mode, + pending.blob, + )?; + + if let Err(err) = self + .atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req.clone(), + ) + .compat() + .map_err(|_| Error::TestFailed(self.crtc)) + { + warn!( + self.logger, + "New screen configuration invalid!:\n\t{:#?}\n\t{}\n", req, err + ); + info!(self.logger, "Reverting back to last know good state"); + + *pending = current.clone(); + } else { + if current.mode != pending.mode { + if let Some(blob) = current.blob { + if let Err(err) = self.dev.destroy_property_blob(blob.into()) { + warn!(self.logger, "Failed to destory old mode property blob: {}", err); + } + } + } + *current = pending.clone(); + } + } + + let req = self.build_request( + ¤t.connectors, + &self.planes, + Some(framebuffer), + current.mode, + current.blob, + )?; + debug!(self.logger, "Setting screen: {:#?}", req); + self.atomic_commit( + &[ + AtomicCommitFlags::PageFlipEvent, + AtomicCommitFlags::AllowModeset, + AtomicCommitFlags::Nonblock, + ], + req, + ) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error setting crtc", + dev: self.dev_path(), + source, + })?; + + Ok(()) + } + + fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> { + let current = self.state.read().unwrap(); + let req = self + .build_request(¤t.connectors, &self.planes, Some(framebuffer), None, None) //current.mode) + .map_err(|_| SwapBuffersError::ContextLost)?; + trace!(self.logger, "Queueing page flip: {:#?}", req); + self.atomic_commit( + &[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock], + req, + ) + .map_err(|_| SwapBuffersError::ContextLost)?; + + Ok(()) + } +} + +impl CursorBackend for AtomicDrmSurfaceInternal { + type CursorFormat = dyn Buffer; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { + trace!(self.logger, "New cursor position ({},{}) pending", x, y); + self.cursor.position.set(Some((x, y))); + Ok(()) + } + + fn set_cursor_representation( + &self, + buffer: &Self::CursorFormat, + hotspot: (u32, u32), + ) -> Result<(), Error> { + trace!(self.logger, "Setting the new imported cursor"); + + if let Some(fb) = self.cursor.framebuffer.get().take() { + let _ = self.destroy_framebuffer(fb.handle()); + } + + self.cursor.framebuffer.set(Some( + self.get_framebuffer(self.add_planar_framebuffer(buffer, &[0; 4], 0).compat().map_err( + |source| Error::Access { + errmsg: "Failed to import cursor", + dev: self.dev_path(), + source, + }, + )?) + .compat() + .map_err(|source| Error::Access { + errmsg: "Failed to get framebuffer info", + dev: self.dev_path(), + source, + })?, + )); + + self.cursor.hotspot.set(hotspot); + + Ok(()) + } +} + +impl AtomicDrmSurfaceInternal { + fn conn_prop_handle( + &self, + handle: connector::Handle, + name: &'static str, + ) -> Result { + (*self.dev) + .prop_mapping + .0 + .get(&handle) + .expect("Unknown handle") + .get(name) + .ok_or_else(|| Error::UnknownProperty { + handle: handle.into(), + name, + }) + .map(|x| *x) + } + + fn crtc_prop_handle(&self, handle: crtc::Handle, name: &'static str) -> Result { + (*self.dev) + .prop_mapping + .1 + .get(&handle) + .expect("Unknown handle") + .get(name) + .ok_or_else(|| Error::UnknownProperty { + handle: handle.into(), + name, + }) + .map(|x| *x) + } + + #[allow(dead_code)] + fn fb_prop_handle( + &self, + handle: framebuffer::Handle, + name: &'static str, + ) -> Result { + (*self.dev) + .prop_mapping + .2 + .get(&handle) + .expect("Unknown handle") + .get(name) + .ok_or_else(|| Error::UnknownProperty { + handle: handle.into(), + name, + }) + .map(|x| *x) + } + + fn plane_prop_handle( + &self, + handle: plane::Handle, + name: &'static str, + ) -> Result { + (*self.dev) + .prop_mapping + .3 + .get(&handle) + .expect("Unknown handle") + .get(name) + .ok_or_else(|| Error::UnknownProperty { + handle: handle.into(), + name, + }) + .map(|x| *x) + } + + // If a mode is set a matching blob needs to be set (the inverse is not true) + fn build_request( + &self, + connectors: &HashSet, + planes: &Planes, + framebuffer: Option, + mode: Option, + blob: Option>, + ) -> Result { + let mut req = AtomicModeReq::new(); + + for conn in connectors.iter() { + req.add_property( + *conn, + self.conn_prop_handle(*conn, "CRTC_ID")?, + property::Value::CRTC(Some(self.crtc)), + ); + } + + if let Some(blob) = blob { + req.add_property(self.crtc, self.crtc_prop_handle(self.crtc, "MODE_ID")?, blob); + } + + req.add_property( + self.crtc, + self.crtc_prop_handle(self.crtc, "ACTIVE")?, + property::Value::Boolean(true), + ); + + if let Some(fb) = framebuffer { + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "FB_ID")?, + property::Value::Framebuffer(Some(fb)), + ); + } + + if let Some(mode) = mode { + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "CRTC_ID")?, + property::Value::CRTC(Some(self.crtc)), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "SRC_X")?, + property::Value::UnsignedRange(0), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "SRC_Y")?, + property::Value::UnsignedRange(0), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "SRC_W")?, + property::Value::UnsignedRange((mode.size().0 as u64) << 16), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "SRC_H")?, + property::Value::UnsignedRange((mode.size().1 as u64) << 16), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "CRTC_X")?, + property::Value::SignedRange(0), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "CRTC_Y")?, + property::Value::SignedRange(0), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "CRTC_W")?, + property::Value::UnsignedRange(mode.size().0 as u64), + ); + req.add_property( + planes.primary, + self.plane_prop_handle(planes.primary, "CRTC_H")?, + property::Value::UnsignedRange(mode.size().1 as u64), + ); + } + + let cursor_pos = self.cursor.position.get(); + let cursor_fb = self.cursor.framebuffer.get(); + + if let (Some(pos), Some(fb)) = (cursor_pos, cursor_fb) { + let hotspot = self.cursor.hotspot.get(); + + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "CRTC_ID")?, + property::Value::CRTC(Some(self.crtc)), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "SRC_X")?, + property::Value::UnsignedRange(0), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "SRC_Y")?, + property::Value::UnsignedRange(0), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "SRC_W")?, + property::Value::UnsignedRange((fb.size().0 as u64) << 16), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "SRC_H")?, + property::Value::UnsignedRange((fb.size().1 as u64) << 16), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "CRTC_X")?, + property::Value::SignedRange(pos.0 as i64 - (hotspot.0 as i64)), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "CRTC_Y")?, + property::Value::SignedRange(pos.1 as i64 - (hotspot.1 as i64)), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "CRTC_W")?, + property::Value::UnsignedRange(fb.size().0 as u64), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "CRTC_H")?, + property::Value::UnsignedRange(fb.size().1 as u64), + ); + req.add_property( + planes.cursor, + self.plane_prop_handle(planes.cursor, "FB_ID")?, + property::Value::Framebuffer(Some(fb.handle())), + ); + } + + Ok(req) + } + + fn find_planes(card: &Dev, crtc: crtc::Handle) -> Option<(plane::Handle, plane::Handle)> { + let res = card.resource_handles().expect("Could not list resources"); + let planes = card.plane_handles().expect("Could not list planes"); + let vec: Vec<(PlaneType, plane::Handle)> = planes + .planes() + .iter() + .map(|x| *x) + .filter(|plane| { + card.get_plane(*plane) + .map(|plane_info| { + let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs()); + compatible_crtcs.contains(&crtc) + }) + .unwrap_or(false) + }) + .filter_map(|plane| { + if let Ok(props) = card.get_properties(plane) { + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + if let Ok(info) = card.get_property(id) { + if info.name().to_str().map(|x| x == "type").unwrap_or(false) { + if val == (PlaneType::Primary as u32).into() { + return Some((PlaneType::Primary, plane)); + } + if val == (PlaneType::Cursor as u32).into() { + return Some((PlaneType::Cursor, plane)); + } + } + } + } + } + None + }) + .collect(); + + Some(( + vec.iter().find_map(|(plane_type, plane)| { + if *plane_type == PlaneType::Primary { + Some(*plane) + } else { + None + } + })?, + vec.iter().find_map(|(plane_type, plane)| { + if *plane_type == PlaneType::Cursor { + Some(*plane) + } else { + None + } + })?, + )) + } + + pub(crate) fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> { + let mut req = AtomicModeReq::new(); + + req.add_property( + plane, + self.plane_prop_handle(plane, "CRTC_ID")?, + property::Value::CRTC(None), + ); + + req.add_property( + plane, + self.plane_prop_handle(plane, "FB_ID")?, + property::Value::Framebuffer(None), + ); + + self.atomic_commit(&[AtomicCommitFlags::TestOnly], req.clone()) + .compat() + .map_err(|_| Error::TestFailed(self.crtc))?; + + self.atomic_commit(&[AtomicCommitFlags::Nonblock], req.clone()) + .compat() + .map_err(|source| Error::Access { + errmsg: "Failed to commit on clear_plane", + dev: self.dev_path(), + source, + }) + } +} + +/// Open raw crtc utilizing atomic mode-setting +pub struct AtomicDrmSurface(pub(super) Rc>); + +impl AsRawFd for AtomicDrmSurface { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl BasicDevice for AtomicDrmSurface {} +impl ControlDevice for AtomicDrmSurface {} + +impl CursorBackend for AtomicDrmSurface { + type CursorFormat = dyn Buffer; + type Error = Error; + + fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { + self.0.set_cursor_position(x, y) + } + + fn set_cursor_representation( + &self, + buffer: &Self::CursorFormat, + hotspot: (u32, u32), + ) -> Result<(), Error> { + self.0.set_cursor_representation(buffer, hotspot) + } +} + +impl Surface for AtomicDrmSurface { + type Error = Error; + type Connectors = HashSet; + + fn crtc(&self) -> crtc::Handle { + self.0.crtc() + } + + fn current_connectors(&self) -> Self::Connectors { + self.0.current_connectors() + } + + fn pending_connectors(&self) -> Self::Connectors { + self.0.pending_connectors() + } + + fn current_mode(&self) -> Option { + self.0.current_mode() + } + + fn pending_mode(&self) -> Option { + self.0.pending_mode() + } + + fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> { + self.0.add_connector(connector) + } + + fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { + self.0.remove_connector(connector) + } + + fn use_mode(&self, mode: Option) -> Result<(), Error> { + self.0.use_mode(mode) + } +} + +impl RawSurface for AtomicDrmSurface { + fn commit_pending(&self) -> bool { + self.0.commit_pending() + } + + fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), Error> { + self.0.commit(framebuffer) + } + + fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> { + RawSurface::page_flip(&*self.0, framebuffer) + } +} diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index 3263816..e9be94d 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -53,6 +53,8 @@ use calloop::{LoopHandle, Source}; use super::graphics::SwapBuffersError; +#[cfg(feature = "backend_drm_atomic")] +pub mod atomic; #[cfg(feature = "backend_drm")] pub mod common; #[cfg(feature = "backend_drm_egl")]