diff --git a/Cargo.toml b/Cargo.toml index 1242a90..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"] @@ -63,5 +64,5 @@ wayland_frontend = ["wayland-server", "wayland-commons", "wayland-protocols"] xwayland = ["wayland_frontend"] [[example]] -name = "raw_drm" +name = "raw_legacy_drm" required-features = ["backend_drm_legacy"] diff --git a/anvil/Cargo.toml b/anvil/Cargo.toml index 4d42e65..fc92bf5 100644 --- a/anvil/Cargo.toml +++ b/anvil/Cargo.toml @@ -29,5 +29,5 @@ gl_generator = "0.14" default = [ "winit", "egl", "udev", "logind" ] egl = [ "smithay/use_system_lib" ] winit = [ "smithay/backend_winit" ] -udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm_legacy", "smithay/backend_drm_gbm", "smithay/backend_drm_egl", "smithay/backend_session", "input" ] +udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm_atomic", "smithay/backend_drm_legacy", "smithay/backend_drm_gbm", "smithay/backend_drm_egl", "smithay/backend_session", "input" ] logind = [ "smithay/backend_session_logind" ] diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 5fc1c81..03917cc 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -19,6 +19,8 @@ use smithay::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend}; use smithay::{ backend::{ drm::{ + atomic::AtomicDrmDevice, + common::fallback::FallbackDevice, device_bind, egl::{EglDevice, EglSurface}, gbm::{egl::Gbm as EglGbmBackend, GbmDevice}, @@ -68,6 +70,7 @@ use crate::shell::{init_shell, MyWindowMap, Roles}; use crate::AnvilState; use smithay::backend::drm::gbm::GbmSurface; +#[derive(Clone)] pub struct SessionFd(RawFd); impl AsRawFd for SessionFd { fn as_raw_fd(&self) -> RawFd { @@ -75,9 +78,12 @@ impl AsRawFd for SessionFd { } } -type RenderDevice = - EglDevice>, GbmDevice>>; -type RenderSurface = EglSurface>>; +type RenderDevice = EglDevice< + EglGbmBackend, LegacyDrmDevice>>, + GbmDevice, LegacyDrmDevice>>, +>; +type RenderSurface = + EglSurface, LegacyDrmDevice>>>; pub fn run_udev(mut display: Display, mut event_loop: EventLoop, log: Logger) -> Result<(), ()> { let name = display.add_socket_auto().unwrap().into_string().unwrap(); @@ -411,9 +417,29 @@ impl UdevHandler for UdevHandlerImpl OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, ) .ok() - .and_then(|fd| LegacyDrmDevice::new(SessionFd(fd), self.logger.clone()).ok()) - .and_then(|drm| GbmDevice::new(drm, self.logger.clone()).ok()) - .and_then(|gbm| EglDevice::new(gbm, self.logger.clone()).ok()) + .and_then( + |fd| match FallbackDevice::new(SessionFd(fd), self.logger.clone()) { + Ok(drm) => Some(drm), + Err(err) => { + error!(self.logger, "Skipping drm device, because of error: {}", err); + None + } + }, + ) + .and_then(|drm| match GbmDevice::new(drm, self.logger.clone()) { + Ok(gbm) => Some(gbm), + Err(err) => { + error!(self.logger, "Skipping gbm device, because of error: {}", err); + None + } + }) + .and_then(|gbm| match EglDevice::new(gbm, self.logger.clone()) { + Ok(egl) => Some(egl), + Err(err) => { + error!(self.logger, "Skipping egl device, because of error: {}", err); + None + } + }) { // init hardware acceleration on the primary gpu. #[cfg(feature = "egl")] diff --git a/examples/raw_atomic_drm.rs b/examples/raw_atomic_drm.rs new file mode 100644 index 0000000..9877376 --- /dev/null +++ b/examples/raw_atomic_drm.rs @@ -0,0 +1,183 @@ +#![warn(rust_2018_idioms)] + +#[macro_use] +extern crate slog; + +use slog::Drain; +use smithay::{ + backend::drm::{ + atomic::{AtomicDrmDevice, AtomicDrmSurface}, + common::Error, + device_bind, Device, DeviceHandler, RawSurface, Surface, + }, + reexports::{ + calloop::EventLoop, + drm::{ + buffer::format::PixelFormat, + control::{ + connector::State as ConnectorState, crtc, dumbbuffer::DumbBuffer, framebuffer, property, + Device as ControlDevice, ResourceHandle, + }, + }, + }, +}; +use std::{ + fs::{File, OpenOptions}, + io::Error as IoError, + rc::Rc, + sync::Mutex, +}; + +fn get_property_by_name<'a, D: ControlDevice, T: ResourceHandle>( + dev: &'a D, + handle: T, + name: &'static str, +) -> Option<(property::ValueType, property::RawValue)> { + let props = dev.get_properties(handle).expect("Could not get props"); + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + let info = dev.get_property(id).unwrap(); + if info.name().to_str().map(|x| x == name).unwrap_or(false) { + let val_ty = info.value_type(); + return Some((val_ty, val)); + } + } + None +} + +fn main() { + let log = slog::Logger::root(Mutex::new(slog_term::term_full().fuse()).fuse(), o!()); + + /* + * Initialize the drm backend + */ + + // "Find" a suitable drm device + let mut options = OpenOptions::new(); + options.read(true); + options.write(true); + let mut device = AtomicDrmDevice::new(options.open("/dev/dri/card0").unwrap(), log.clone()).unwrap(); + + // Get a set of all modesetting resource handles (excluding planes): + let res_handles = Device::resource_handles(&device).unwrap(); + + // Use first connected connector + let connector_info = res_handles + .connectors() + .iter() + .map(|conn| device.get_connector_info(*conn).unwrap()) + .find(|conn| conn.state() == ConnectorState::Connected) + .unwrap(); + + // use the connected crtc if any + let (val_ty, raw) = get_property_by_name(&device, connector_info.handle(), "CRTC_ID").unwrap(); + let crtc = match val_ty.convert_value(raw) { + property::Value::CRTC(Some(handle)) => handle, + property::Value::CRTC(None) => { + // Use the first encoder + let encoder = connector_info + .encoders() + .iter() + .filter_map(|&e| e) + .next() + .unwrap(); + let encoder_info = device.get_encoder_info(encoder).unwrap(); + + *res_handles + .filter_crtcs(encoder_info.possible_crtcs()) + .iter() + .next() + .unwrap() + } + _ => unreachable!("CRTC_ID does not return another property type"), + }; + + // Assuming we found a good connector and loaded the info into `connector_info` + let mode = connector_info.modes()[0]; // Use first mode (usually highest resoltion, but in reality you should filter and sort and check and match with other connectors, if you use more then one.) + + // Initialize the hardware backend + let surface = Rc::new(device.create_surface(crtc).unwrap()); + surface.set_connectors(&[connector_info.handle()]).unwrap(); + surface.use_mode(Some(mode)).unwrap(); + + for conn in surface.current_connectors().into_iter() { + if conn != connector_info.handle() { + surface.remove_connector(conn).unwrap(); + } + } + surface.add_connector(connector_info.handle()).unwrap(); + + /* + * Lets create buffers and framebuffers. + * We use drm-rs DumbBuffers, because they always work and require little to no setup. + * But they are very slow, this is just for demonstration purposes. + */ + let (w, h) = mode.size(); + let front_buffer = device + .create_dumb_buffer((w as u32, h as u32), PixelFormat::XRGB8888) + .unwrap(); + let front_framebuffer = device.add_framebuffer(&front_buffer).unwrap(); + let back_buffer = device + .create_dumb_buffer((w as u32, h as u32), PixelFormat::XRGB8888) + .unwrap(); + let back_framebuffer = device.add_framebuffer(&back_buffer).unwrap(); + + device.set_handler(DrmHandlerImpl { + current: front_framebuffer, + front: (front_buffer, front_framebuffer), + back: (back_buffer, back_framebuffer), + surface: surface.clone(), + }); + + /* + * Register the DrmDevice on the EventLoop + */ + let mut event_loop = EventLoop::<()>::new().unwrap(); + let _source = device_bind(&event_loop.handle(), device) + .map_err(|err| -> IoError { err.into() }) + .unwrap(); + + // Start rendering + if surface.commit_pending() { + surface.commit(front_framebuffer).unwrap(); + } + + // Run + event_loop.run(None, &mut (), |_| {}).unwrap(); +} + +pub struct DrmHandlerImpl { + front: (DumbBuffer, framebuffer::Handle), + back: (DumbBuffer, framebuffer::Handle), + current: framebuffer::Handle, + surface: Rc>, +} + +impl DeviceHandler for DrmHandlerImpl { + type Device = AtomicDrmDevice; + + fn vblank(&mut self, _crtc: crtc::Handle) { + { + // Swap and map buffer + let mut mapping = if self.current == self.front.1 { + self.current = self.back.1; + self.surface.map_dumb_buffer(&mut self.back.0).unwrap() + } else { + self.current = self.front.1; + self.surface.map_dumb_buffer(&mut self.front.0).unwrap() + }; + + // now we could render to the mapping via software rendering. + // this example just sets some grey color + + for x in mapping.as_mut() { + *x = 128; + } + } + RawSurface::page_flip(&*self.surface, self.current).unwrap(); + } + + fn error(&mut self, error: Error) { + panic!("{:?}", error); + } +} diff --git a/examples/raw_drm.rs b/examples/raw_legacy_drm.rs similarity index 92% rename from examples/raw_drm.rs rename to examples/raw_legacy_drm.rs index a540bac..6afa248 100644 --- a/examples/raw_drm.rs +++ b/examples/raw_legacy_drm.rs @@ -6,8 +6,9 @@ extern crate slog; use slog::Drain; use smithay::{ backend::drm::{ + common::Error, device_bind, - legacy::{Error, LegacyDrmDevice, LegacyDrmSurface}, + legacy::{LegacyDrmDevice, LegacyDrmSurface}, Device, DeviceHandler, RawSurface, Surface, }, reexports::{ @@ -80,13 +81,7 @@ fn main() { let surface = Rc::new(device.create_surface(crtc).unwrap()); surface.use_mode(Some(mode)).unwrap(); - for conn in surface.current_connectors().into_iter() { - if conn != connector_info.handle() { - surface.remove_connector(conn).unwrap(); - } - } - surface.add_connector(connector_info.handle()).unwrap(); - + surface.set_connectors(&[connector_info.handle()]).unwrap(); /* * Lets create buffers and framebuffers. * We use drm-rs DumbBuffers, because they always work and require little to no setup. @@ -121,7 +116,6 @@ fn main() { if surface.commit_pending() { surface.commit(front_framebuffer).unwrap(); } - RawSurface::page_flip(&*surface, front_framebuffer).unwrap(); // Run event_loop.run(None, &mut (), |_| {}).unwrap(); diff --git a/src/backend/drm/atomic/mod.rs b/src/backend/drm/atomic/mod.rs new file mode 100644 index 0000000..73653d6 --- /dev/null +++ b/src/backend/drm/atomic/mod.rs @@ -0,0 +1,406 @@ +//! +//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface) +//! implementations using the atomic mode-setting infrastructure. +//! +//! Usually this implementation will wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice). +//! Take a look at `anvil`s source code for an example of this. +//! +//! For an example how to use this standalone, take a look at the raw_atomic_drm example. +//! + +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; + +/// Open raw drm device utilizing atomic mode-setting +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 Dev { + // Add all properties of given handles to a given drm resource type to state. + // You may use this to snapshot the current state of the drm device (fully or partially). + fn add_props(&self, handles: &[T], state: &mut Vec<(T, PropertyValueSet)>) -> Result<(), Error> + where + A: AsRawFd + 'static, + T: ResourceHandle, + { + let iter = handles.iter().map(|x| (x, self.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: self.dev_path(), + source, + }) + } + + /// Create a mapping of property names and handles for given handles of a given drm resource type. + /// You may use this to easily lookup properties by name instead of going through this procedure manually. + fn map_props( + &self, + handles: &[T], + mapping: &mut HashMap>, + ) -> Result<(), Error> + where + A: AsRawFd + 'static, + T: ResourceHandle + Eq + std::hash::Hash, + { + handles + .iter() + .map(|x| (x, self.get_properties(*x))) + .try_for_each(|(handle, props)| { + let mut map = HashMap::new(); + match props { + Ok(props) => { + let (prop_handles, _) = props.as_props_and_values(); + for prop in prop_handles { + if let Ok(info) = self.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: self.dev_path(), + source, + }) + } +} + +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(Error::UnableToGetDeviceId)?.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(); + + let mut old_state = dev.old_state.clone(); + let mut mapping = dev.prop_mapping.clone(); + + dev.add_props(res_handles.connectors(), &mut old_state.0)?; + dev.add_props(res_handles.crtcs(), &mut old_state.1)?; + dev.add_props(res_handles.framebuffers(), &mut old_state.2)?; + dev.add_props(planes, &mut old_state.3)?; + + dev.map_props(res_handles.connectors(), &mut mapping.0)?; + dev.map_props(res_handles.crtcs(), &mut mapping.1)?; + dev.map_props(res_handles.framebuffers(), &mut mapping.2)?; + dev.map_props(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 a page-flip event for crtc ({:?})", event.crtc); + 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", + self.dev_path() + ); + } + } else { + trace!( + self.logger, + "Got a non-page-flip event of device '{:?}'.", + self.dev_path() + ); + } + } + } + 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..b58dbe0 --- /dev/null +++ b/src/backend/drm/atomic/session.rs @@ -0,0 +1,99 @@ +//! +//! Support to register an open [`AtomicDrmDevice`](AtomicDrmDevice) +//! to an open [`Session`](::backend::session::Session). +//! + +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}; + +/// [`SessionObserver`](SessionObserver) +/// linked to the [`AtomicDrmDevice`](AtomicDrmDevice) +/// it was created from. +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..57c6246 --- /dev/null +++ b/src/backend/drm/atomic/surface.rs @@ -0,0 +1,874 @@ +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 + } + + fn pending_mode(&self) -> Option { + self.pending.read().unwrap().mode + } + + 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()) { + // check if config is supported + let req = self.build_request( + &mut [conn].iter(), + &mut [].iter(), + &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.insert(conn); + + Ok(()) + } else { + Err(Error::ModeNotSuitable(pending.mode.unwrap())) + } + } + + fn remove_connector(&self, conn: connector::Handle) -> Result<(), Error> { + let mut pending = self.pending.write().unwrap(); + + // check if new config is supported (should be) + let req = self.build_request( + &mut [].iter(), + &mut [conn].iter(), + &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.remove(&conn); + + Ok(()) + } + + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + let current = self.state.write().unwrap(); + let mut pending = self.pending.write().unwrap(); + + let conns = connectors.iter().cloned().collect::>(); + let mut added = conns.difference(¤t.connectors); + let mut removed = current.connectors.difference(&conns); + + let req = self.build_request( + &mut added, + &mut removed, + &self.planes, + None, + pending.mode, + pending.blob, + )?; + + self.atomic_commit( + &[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly], + req, + ) + .map_err(|_| Error::TestFailed(self.crtc))?; + + pending.connectors = conns; + + Ok(()) + } + + 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( + &mut pending.connectors.iter(), + &mut [].iter(), + &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 mut removed = current_conns.difference(&pending_conns); + let mut added = pending_conns.difference(¤t_conns); + + for conn in removed.clone() { + 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.clone() { + 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 = { + let req = self.build_request( + &mut added, + &mut removed, + &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(); + + self.build_request( + &mut [].iter(), + &mut [].iter(), + &self.planes, + Some(framebuffer), + current.mode, + current.blob, + )? + } 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(); + + // new config + req + } + }; + + 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 req = self + .build_request( + &mut [].iter(), + &mut [].iter(), + &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, + new_connectors: &mut dyn Iterator, + removed_connectors: &mut dyn Iterator, + planes: &Planes, + framebuffer: Option, + mode: Option, + blob: Option>, + ) -> Result { + let mut req = AtomicModeReq::new(); + + for conn in new_connectors { + req.add_property( + *conn, + self.conn_prop_handle(*conn, "CRTC_ID")?, + property::Value::CRTC(Some(self.crtc)), + ); + } + + for conn in removed_connectors { + req.add_property( + *conn, + self.conn_prop_handle(*conn, "CRTC_ID")?, + property::Value::CRTC(None), + ); + } + + 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() + .copied() + .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) + .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 set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> { + self.0.set_connectors(connectors) + } + + 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/common/fallback.rs b/src/backend/drm/common/fallback.rs new file mode 100644 index 0000000..69679a9 --- /dev/null +++ b/src/backend/drm/common/fallback.rs @@ -0,0 +1,347 @@ +//! +//! Types to make fallback device initialization easier +//! + +#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] +use crate::backend::drm::{atomic::AtomicDrmDevice, legacy::LegacyDrmDevice}; +use crate::backend::drm::{common::Error, Device, DeviceHandler, RawDevice, RawSurface, Surface}; +use crate::backend::egl::Error as EGLError; +#[cfg(feature = "use_system_lib")] +use crate::backend::egl::{display::EGLBufferReader, EGLGraphicsBackend}; +#[cfg(feature = "renderer_gl")] +use crate::backend::graphics::gl::GLGraphicsBackend; +#[cfg(feature = "renderer_gl")] +use crate::backend::graphics::PixelFormat; +use crate::backend::graphics::{CursorBackend, SwapBuffersError}; +use crate::backend::session::{AsSessionObserver, SessionObserver}; + +use drm::{ + control::{connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Mode, ResourceHandles}, + Device as BasicDevice, SystemError as DrmError, +}; +#[cfg(feature = "renderer_gl")] +use nix::libc::c_void; +use nix::libc::dev_t; +use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(feature = "use_system_lib")] +use wayland_server::Display; + +/// [`Device`](::backend::drm::Device) Wrapper to assist fallback +/// in case initialization of the preferred device type fails. +pub enum FallbackDevice { + /// Variant for successful initialization of the preferred device + Preference(D1), + /// Variant for the fallback device + Fallback(D2), +} + +struct FallbackDeviceHandlerD1( + Box> + 'static>, +) +where + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + 'static, + S2: Surface + 'static, + D1: Device + 'static, + D2: Device + 'static; + +impl DeviceHandler for FallbackDeviceHandlerD1 +where + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + 'static, + S2: Surface + 'static, + D1: Device + 'static, + D2: Device + 'static, +{ + type Device = D1; + + fn vblank(&mut self, crtc: crtc::Handle) { + self.0.vblank(crtc) + } + fn error(&mut self, error: E) { + self.0.error(error); + } +} + +struct FallbackDeviceHandlerD2( + Box> + 'static>, +) +where + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + 'static, + S2: Surface + 'static, + D1: Device + 'static, + D2: Device + 'static; + +impl DeviceHandler for FallbackDeviceHandlerD2 +where + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + 'static, + S2: Surface + 'static, + D1: Device + 'static, + D2: Device + 'static, +{ + type Device = D2; + + fn vblank(&mut self, crtc: crtc::Handle) { + self.0.vblank(crtc) + } + fn error(&mut self, error: E) { + self.0.error(error); + } +} + +/// [`SessionObserver`](::backend::session::SessionObserver) Wrapper to assist fallback +/// in case initialization of the preferred device type fails. +pub enum FallbackDeviceObserver { + /// Variant for successful initialization of the preferred device + Preference(O1), + /// Variant for the fallback device + Fallback(O2), +} + +impl AsSessionObserver> for FallbackDevice +where + O1: SessionObserver + 'static, + O2: SessionObserver + 'static, + D1: Device + AsSessionObserver + 'static, + D2: Device + AsSessionObserver + 'static, +{ + fn observer(&mut self) -> FallbackDeviceObserver { + match self { + FallbackDevice::Preference(dev) => FallbackDeviceObserver::Preference(dev.observer()), + FallbackDevice::Fallback(dev) => FallbackDeviceObserver::Fallback(dev.observer()), + } + } +} + +impl SessionObserver + for FallbackDeviceObserver +{ + fn pause(&mut self, device: Option<(u32, u32)>) { + match self { + FallbackDeviceObserver::Preference(dev) => dev.pause(device), + FallbackDeviceObserver::Fallback(dev) => dev.pause(device), + } + } + + fn activate(&mut self, device: Option<(u32, u32, Option)>) { + match self { + FallbackDeviceObserver::Preference(dev) => dev.activate(device), + FallbackDeviceObserver::Fallback(dev) => dev.activate(device), + } + } +} + +/// [`Surface`](::backend::drm::Surface) Wrapper to assist fallback +/// in case initialization of the preferred device type fails. +pub enum FallbackSurface { + /// Variant for successful initialization of the preferred device + Preference(S1), + /// Variant for the fallback device + Fallback(S2), +} + +#[cfg(all(feature = "backend_drm_atomic", feature = "backend_drm_legacy"))] +impl FallbackDevice, LegacyDrmDevice> { + /// Try to initialize an [`AtomicDrmDevice`](::backend::drm:;atomic::AtomicDrmDevice) + /// and fall back to a [`LegacyDrmDevice`] if atomic-modesetting is not supported. + pub fn new(fd: A, logger: L) -> Result + where + L: Into>, + { + let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm_fallback")); + info!(log, "Trying to initialize AtomicDrmDevice"); + + match AtomicDrmDevice::new(fd.clone(), log.clone()) { + Ok(dev) => Ok(FallbackDevice::Preference(dev)), + Err(err) => { + error!(log, "Failed to initialize preferred AtomicDrmDevice: {}", err); + info!(log, "Falling back to fallback LegacyyDrmDevice"); + Ok(FallbackDevice::Fallback(LegacyDrmDevice::new(fd, log)?)) + } + } + } +} + +macro_rules! fallback_device_impl { + ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { + fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { + match self { + FallbackDevice::Preference(dev) => dev.$func_name($($arg_name),*), + FallbackDevice::Fallback(dev) => dev.$func_name($($arg_name),*), + } + } + }; + ($func_name:ident, $self:ty, $return:ty) => { + fallback_device_impl!($func_name, $self, $return,); + }; + ($func_name:ident, $self:ty) => { + fallback_device_impl!($func_name, $self, ()); + }; +} + +macro_rules! fallback_surface_impl { + ($func_name:ident, $self:ty, $return:ty, $($arg_name:ident : $arg_ty:ty),*) => { + fn $func_name(self: $self, $($arg_name : $arg_ty),*) -> $return { + match self { + FallbackSurface::Preference(dev) => dev.$func_name($($arg_name),*), + FallbackSurface::Fallback(dev) => dev.$func_name($($arg_name),*), + } + } + }; + ($func_name:ident, $self:ty, $return:ty) => { + fallback_surface_impl!($func_name, $self, $return,); + }; + ($func_name:ident, $self:ty) => { + fallback_surface_impl!($func_name, $self, ()); + }; +} + +impl AsRawFd for FallbackDevice { + fallback_device_impl!(as_raw_fd, &Self, RawFd); +} +impl BasicDevice for FallbackDevice {} +impl ControlDevice for FallbackDevice {} + +impl Device for FallbackDevice +where + // Connectors and Error need to match for both Surfaces + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + 'static, + S2: Surface + 'static, + D1: Device + 'static, + D2: Device + 'static, +{ + type Surface = FallbackSurface; + + fallback_device_impl!(device_id, &Self, dev_t); + fn set_handler(&mut self, handler: impl DeviceHandler + 'static) { + match self { + FallbackDevice::Preference(dev) => dev.set_handler(FallbackDeviceHandlerD1(Box::new(handler))), + FallbackDevice::Fallback(dev) => dev.set_handler(FallbackDeviceHandlerD2(Box::new(handler))), + } + } + fallback_device_impl!(clear_handler, &mut Self); + fn create_surface(&mut self, crtc: crtc::Handle) -> Result { + match self { + FallbackDevice::Preference(dev) => Ok(FallbackSurface::Preference(dev.create_surface(crtc)?)), + FallbackDevice::Fallback(dev) => Ok(FallbackSurface::Fallback(dev.create_surface(crtc)?)), + } + } + fallback_device_impl!(process_events, &mut Self); + fallback_device_impl!(resource_handles, &Self, Result); + fallback_device_impl!(get_connector_info, &Self, Result, conn: connector::Handle); + fallback_device_impl!(get_crtc_info, &Self, Result, crtc: crtc::Handle); + fallback_device_impl!(get_encoder_info, &Self, Result, enc: encoder::Handle); + fallback_device_impl!(get_framebuffer_info, &Self, Result, fb: framebuffer::Handle); + fallback_device_impl!(get_plane_info, &Self, Result, plane : plane::Handle); +} + +// Impl RawDevice where underlying types implement RawDevice +impl RawDevice for FallbackDevice +where + // Connectors and Error need to match for both Surfaces + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: RawSurface + Surface + 'static, + S2: RawSurface + Surface + 'static, + D1: RawDevice + 'static, + D2: RawDevice + 'static, +{ + type Surface = FallbackSurface; +} + +#[cfg(feature = "use_system_lib")] +impl EGLGraphicsBackend + for FallbackDevice +{ + fallback_device_impl!(bind_wl_display, &Self, Result, display : &Display); +} + +impl Surface for FallbackSurface +where + // Connectors and Error need to match for both Surfaces + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + 'static, + S2: Surface + 'static, +{ + type Error = E; + type Connectors = C; + + fallback_surface_impl!(crtc, &Self, crtc::Handle); + fallback_surface_impl!(current_connectors, &Self, C); + fallback_surface_impl!(pending_connectors, &Self, C); + fallback_surface_impl!(add_connector, &Self, Result<(), E>, conn: connector::Handle); + fallback_surface_impl!(remove_connector, &Self, Result<(), E>, conn: connector::Handle); + fallback_surface_impl!(set_connectors, &Self, Result<(), E>, conns: &[connector::Handle]); + fallback_surface_impl!(current_mode, &Self, Option); + fallback_surface_impl!(pending_mode, &Self, Option); + fallback_surface_impl!(use_mode, &Self, Result<(), E>, mode: Option); +} + +impl RawSurface for FallbackSurface +where + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: RawSurface + Surface + 'static, + S2: RawSurface + Surface + 'static, +{ + fallback_surface_impl!(commit_pending, &Self, bool); + fallback_surface_impl!(commit, &Self, Result<(), E>, fb: framebuffer::Handle); + fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> { + match self { + FallbackSurface::Preference(dev) => RawSurface::page_flip(dev, framebuffer), + FallbackSurface::Fallback(dev) => RawSurface::page_flip(dev, framebuffer), + } + } +} + +impl AsRawFd for FallbackSurface { + fallback_surface_impl!(as_raw_fd, &Self, RawFd); +} +impl BasicDevice for FallbackSurface {} +impl ControlDevice for FallbackSurface {} + +impl CursorBackend for FallbackSurface +where + E1: std::error::Error + Send + 'static, + E2: 'static, + CF: ?Sized, + C: IntoIterator + 'static, + S1: Surface + CursorBackend + 'static, + S2: Surface + CursorBackend + 'static, +{ + type CursorFormat = CF; + type Error = E2; + + fallback_surface_impl!(set_cursor_position, &Self, Result<(), E2>, x: u32, y: u32); + fallback_surface_impl!(set_cursor_representation, &Self, Result<(), E2>, buffer: &Self::CursorFormat, hotspot: (u32, u32)); +} + +#[cfg(feature = "renderer_gl")] +impl GLGraphicsBackend for FallbackSurface +where + E: std::error::Error + Send + 'static, + C: IntoIterator + 'static, + S1: Surface + GLGraphicsBackend + 'static, + S2: Surface + GLGraphicsBackend + 'static, +{ + fallback_surface_impl!(swap_buffers, &Self, Result<(), SwapBuffersError>); + fallback_surface_impl!(get_proc_address, &Self, *const c_void, symbol: &str); + fallback_surface_impl!(get_framebuffer_dimensions, &Self, (u32, u32)); + fallback_surface_impl!(is_current, &Self, bool); + unsafe fn make_current(&self) -> Result<(), SwapBuffersError> { + match self { + FallbackSurface::Preference(dev) => dev.make_current(), + FallbackSurface::Fallback(dev) => dev.make_current(), + } + } + fallback_surface_impl!(get_pixel_format, &Self, PixelFormat); +} diff --git a/src/backend/drm/common/mod.rs b/src/backend/drm/common/mod.rs new file mode 100644 index 0000000..83ef7c2 --- /dev/null +++ b/src/backend/drm/common/mod.rs @@ -0,0 +1,70 @@ +//! +//! Module for common/shared types of the various [`Device`](::backend::drm::Device) +//! and [`Surface`](::backend::drm::Surface) implementations of the `backend::drm` module. +//! + +use drm::control::{connector, crtc, Mode, RawResourceHandle}; + +use std::path::PathBuf; + +pub mod fallback; + +/// Errors thrown by the [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice), +/// [`AtomicDrmDevice`](::backend::drm::atomic::AtomicDrmDevice) +/// and their surfaces: [`LegacyDrmSurface`](::backend::drm::legacy::LegacyDrmSurface) +/// and [`AtomicDrmSurface`](::backend::drm::atomic::AtomicDrmSurface). +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// Unable to acquire DRM master + #[error("Failed to aquire DRM master")] + DrmMasterFailed, + /// The `DrmDevice` encountered an access error + #[error("DRM access error: {errmsg} on device `{dev:?}`")] + Access { + /// Error message associated to the access error + errmsg: &'static str, + /// Device on which the error was generated + dev: Option, + /// Underlying device error + source: failure::Compat, + }, + /// Unable to determine device id of drm device + #[error("Unable to determine device id of drm device")] + UnableToGetDeviceId(#[source] nix::Error), + /// Device is currently paused + #[error("Device is currently paused, operation rejected")] + DeviceInactive, + /// Mode is not compatible with all given connectors + #[error("Mode `{0:?}` is not compatible with all given connectors")] + ModeNotSuitable(Mode), + /// The given crtc is already in use by another backend + #[error("Crtc `{0:?}` is already in use by another backend")] + CrtcAlreadyInUse(crtc::Handle), + /// No encoder was found for a given connector on the set crtc + #[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")] + NoSuitableEncoder { + /// Connector + connector: connector::Handle, + /// CRTC + crtc: crtc::Handle, + }, + /// No matching primary and cursor plane could be found for the given crtc + #[error("No matching primary and cursor plane could be found for crtc {crtc:?} on {dev:?}")] + NoSuitablePlanes { + /// CRTC + crtc: crtc::Handle, + /// Device on which the error was generated + dev: Option, + }, + /// The DrmDevice is missing a required property + #[error("The DrmDevice is missing a required property '{name}' for handle ({handle:?})")] + UnknownProperty { + /// Property handle + handle: RawResourceHandle, + /// Property name + name: &'static str, + }, + /// Atomic Test failed for new properties + #[error("Atomic Test failed for new properties on crtc ({0:?})")] + TestFailed(crtc::Handle), +} diff --git a/src/backend/drm/egl/surface.rs b/src/backend/drm/egl/surface.rs index 1e1f97c..e9107a0 100644 --- a/src/backend/drm/egl/surface.rs +++ b/src/backend/drm/egl/surface.rs @@ -49,6 +49,10 @@ where .map_err(Error::Underlying) } + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + self.surface.set_connectors(connectors).map_err(Error::Underlying) + } + fn current_mode(&self) -> Option { self.surface.current_mode() } @@ -62,25 +66,22 @@ where } } -impl<'a, N> CursorBackend<'a> for EglSurface +impl CursorBackend for EglSurface where - N: NativeSurface + Surface + CursorBackend<'a>, + N: NativeSurface + Surface + CursorBackend, { - type CursorFormat = >::CursorFormat; - type Error = >::Error; + type CursorFormat = ::CursorFormat; + type Error = ::Error; fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), Self::Error> { self.surface.set_cursor_position(x, y) } - fn set_cursor_representation<'b>( - &'b self, - buffer: Self::CursorFormat, + fn set_cursor_representation( + &self, + buffer: &Self::CursorFormat, hotspot: (u32, u32), - ) -> ::std::result::Result<(), Self::Error> - where - 'a: 'b, - { + ) -> ::std::result::Result<(), Self::Error> { self.surface.set_cursor_representation(buffer, hotspot) } } diff --git a/src/backend/drm/gbm/session.rs b/src/backend/drm/gbm/session.rs index 5da767c..fbb93af 100644 --- a/src/backend/drm/gbm/session.rs +++ b/src/backend/drm/gbm/session.rs @@ -56,13 +56,23 @@ impl< for (crtc, backend) in backends.borrow().iter() { if let Some(backend) = backend.upgrade() { // restart rendering loop, if it was previously running - if let Some(Err(err)) = backend - .current_frame_buffer - .get() - .map(|fb| backend.crtc.page_flip(fb)) - { - warn!(self.logger, "Failed to restart rendering loop. Error: {}", err); + if let Some(fb) = backend.current_frame_buffer.get() { + if backend.crtc.page_flip(fb).is_err() { + // Try more! + if let Err(err) = backend.recreate() { + error!( + self.logger, + "Failed to re-create gbm surface, is the device gone?\n\t{}", err + ); + } + if let Err(err) = unsafe { backend.page_flip() } { + warn!(self.logger, "Failed to restart rendering loop. Error: {}", err); + // TODO bubble this up the user somehow + // maybe expose a "running" state from a surface? + } + } } + // reset cursor { use ::drm::control::Device; diff --git a/src/backend/drm/gbm/surface.rs b/src/backend/drm/gbm/surface.rs index 79c85ec..30cbbb2 100644 --- a/src/backend/drm/gbm/surface.rs +++ b/src/backend/drm/gbm/surface.rs @@ -6,11 +6,8 @@ use gbm::{self, BufferObject, BufferObjectFlags, Format as GbmFormat, SurfaceBuf use image::{ImageBuffer, Rgba}; use std::cell::{Cell, RefCell}; -use std::os::unix::io::AsRawFd; use std::rc::Rc; -#[cfg(feature = "backend_drm_legacy")] -use crate::backend::drm::legacy::LegacyDrmDevice; use crate::backend::graphics::CursorBackend; use crate::backend::graphics::SwapBuffersError; @@ -78,24 +75,31 @@ impl GbmSurfaceInternal { if self.recreated.get() { debug!(self.logger, "Commiting new state"); - self.crtc.commit(fb).map_err(|_| SwapBuffersError::ContextLost)?; + if let Err(err) = self.crtc.commit(fb) { + error!(self.logger, "Error commiting crtc: {}", err); + return Err(SwapBuffersError::ContextLost); + } self.recreated.set(false); + } else { + trace!(self.logger, "Queueing Page flip"); + RawSurface::page_flip(&self.crtc, fb)?; } - trace!(self.logger, "Queueing Page flip"); - RawSurface::page_flip(&self.crtc, fb)?; - self.current_frame_buffer.set(Some(fb)); Ok(()) } pub fn recreate(&self) -> Result<(), Error<<::Surface as Surface>::Error>> { - let (w, h) = self.pending_mode().ok_or(Error::NoModeSet)?.size(); + let (w, h) = self + .pending_mode() + .or_else(|| self.current_mode()) + .ok_or(Error::NoModeSet)? + .size(); // Recreate the surface and the related resources to match the new // resolution. - debug!(self.logger, "(Re-)Initializing surface for mode: {}:{}", w, h); + debug!(self.logger, "(Re-)Initializing surface (with mode: {}:{})", w, h); let surface = self .dev .borrow_mut() @@ -159,6 +163,10 @@ impl Surface for GbmSurfaceInternal { self.crtc.remove_connector(connector).map_err(Error::Underlying) } + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + self.crtc.set_connectors(connectors).map_err(Error::Underlying) + } + fn current_mode(&self) -> Option { self.crtc.current_mode() } @@ -172,47 +180,24 @@ impl Surface for GbmSurfaceInternal { } } -// FIXME: -// -// Option 1: When there is GAT support, impl `GraphicsBackend` for `LegacyDrmBackend` -// using a new generic `B: Buffer` and use this: -/* -impl<'a, D: RawDevice + 'static> CursorBackend<'a> for GbmSurfaceInternal +#[cfg(feature = "backend_drm")] +impl CursorBackend for GbmSurfaceInternal where - ::Surface: CursorBackend<'a>, - <::Surface as CursorBackend<'a>>::CursorFormat: Buffer, - <::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send + ::Surface: CursorBackend, + <::Surface as CursorBackend>::Error: ::std::error::Error + Send, { -*/ -// -// Option 2: When equality checks in where clauses are supported, we could at least do this: -/* -impl<'a, D: RawDevice + 'static> GraphicsBackend<'a> for GbmSurfaceInternal -where - ::Surface: CursorBackend<'a>, - <::Surface as CursorBackend<'a>>::CursorFormat=&'a Buffer, - <::Surface as CursorBackend<'a>>::Error: ::std::error::Error + Send -{ -*/ -// But for now got to do this: - -#[cfg(feature = "backend_drm_legacy")] -impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurfaceInternal> { - type CursorFormat = &'a ImageBuffer, Vec>; - type Error = Error<< as Device>::Surface as Surface>::Error>; + type CursorFormat = ImageBuffer, Vec>; + type Error = Error<<::Surface as CursorBackend>::Error>; fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { self.crtc.set_cursor_position(x, y).map_err(Error::Underlying) } - fn set_cursor_representation<'b>( - &'b self, + fn set_cursor_representation( + &self, buffer: &ImageBuffer, Vec>, hotspot: (u32, u32), - ) -> Result<(), Self::Error> - where - 'a: 'b, - { + ) -> Result<(), Self::Error> { let (w, h) = buffer.dimensions(); debug!(self.logger, "Importing cursor"); @@ -336,6 +321,10 @@ impl Surface for GbmSurface { self.0.remove_connector(connector) } + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + self.0.set_connectors(connectors) + } + fn current_mode(&self) -> Option { self.0.current_mode() } @@ -349,23 +338,24 @@ impl Surface for GbmSurface { } } -#[cfg(feature = "backend_drm_legacy")] -impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for GbmSurface> { - type CursorFormat = &'a ImageBuffer, Vec>; - type Error = ::Error; +#[cfg(feature = "backend_drm")] +impl CursorBackend for GbmSurface +where + ::Surface: CursorBackend, + <::Surface as CursorBackend>::Error: ::std::error::Error + Send, +{ + type CursorFormat = ImageBuffer, Vec>; + type Error = Error<<::Surface as CursorBackend>::Error>; fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Self::Error> { self.0.set_cursor_position(x, y) } - fn set_cursor_representation<'b>( - &'b self, + fn set_cursor_representation( + &self, buffer: &ImageBuffer, Vec>, hotspot: (u32, u32), - ) -> Result<(), Self::Error> - where - 'a: 'b, - { + ) -> Result<(), Self::Error> { self.0.set_cursor_representation(buffer, hotspot) } } diff --git a/src/backend/drm/legacy/mod.rs b/src/backend/drm/legacy/mod.rs index 9d5d502..19597f3 100644 --- a/src/backend/drm/legacy/mod.rs +++ b/src/backend/drm/legacy/mod.rs @@ -5,13 +5,13 @@ //! Usually this implementation will be wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice). //! Take a look at `anvil`s source code for an example of this. //! -//! For an example how to use this standalone, take a look at the `raw_drm` example. +//! For an example how to use this standalone, take a look at the `raw_legacy_drm` example. //! -use super::{DevPath, Device, DeviceHandler, RawDevice}; +use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice}; use drm::control::{ - connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Event, Mode, ResourceHandles, + connector, crtc, encoder, framebuffer, plane, Device as ControlDevice, Event, ResourceHandles, }; use drm::{Device as BasicDevice, SystemError as DrmError}; use nix::libc::dev_t; @@ -20,7 +20,6 @@ use nix::sys::stat::fstat; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::os::unix::io::{AsRawFd, RawFd}; -use std::path::PathBuf; use std::rc::{Rc, Weak}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; @@ -34,45 +33,6 @@ use self::surface::{LegacyDrmSurfaceInternal, State}; #[cfg(feature = "backend_session")] pub mod session; -/// Errors thrown by the [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice) -/// and [`LegacyDrmSurface`](::backend::drm::legacy::LegacyDrmSurface). -#[derive(thiserror::Error, Debug)] -pub enum Error { - /// Unable to acquire DRM master - #[error("Failed to aquire DRM master")] - DrmMasterFailed, - /// The `DrmDevice` encountered an access error - #[error("DRM access error: {errmsg} on device `{dev:?}`")] - Access { - /// Error message associated to the access error - errmsg: &'static str, - /// Device on which the error was generated - dev: Option, - /// Underlying device error - source: failure::Compat, - }, - /// Unable to determine device id of drm device - #[error("Unable to determine device id of drm device")] - UnableToGetDeviceId(#[source] nix::Error), - /// Device is currently paused - #[error("Device is currently paused, operation rejected")] - DeviceInactive, - /// Mode is not compatible with all given connectors - #[error("Mode `{0:?}` is not compatible with all given connectors")] - ModeNotSuitable(Mode), - /// The given crtc is already in use by another backend - #[error("Crtc `{0:?}` is already in use by another backend")] - CrtcAlreadyInUse(crtc::Handle), - /// No encoder was found for a given connector on the set crtc - #[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")] - NoSuitableEncoder { - /// Connector - connector: connector::Handle, - /// CRTC - crtc: crtc::Handle, - }, -} - /// Open raw drm device utilizing legacy mode-setting pub struct LegacyDrmDevice { dev: Rc>, @@ -85,7 +45,7 @@ pub struct LegacyDrmDevice { pub(in crate::backend::drm) struct Dev { fd: A, - priviledged: bool, + privileged: bool, active: Arc, old_state: HashMap)>, logger: ::slog::Logger, @@ -117,7 +77,7 @@ impl Drop for Dev { } } } - if self.priviledged { + if self.privileged { if let Err(err) = self.release_master_lock() { error!(self.logger, "Failed to drop drm master state. Error: {}", err); } @@ -135,7 +95,7 @@ impl LegacyDrmDevice { L: Into>, { let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_drm")); - info!(log, "DrmDevice initializing"); + info!(log, "LegacyDrmDevice initializing"); let dev_id = fstat(dev.as_raw_fd()) .map_err(Error::UnableToGetDeviceId)? @@ -144,7 +104,7 @@ impl LegacyDrmDevice { let active = Arc::new(AtomicBool::new(true)); let mut dev = Dev { fd: dev, - priviledged: true, + privileged: true, old_state: HashMap::new(), active: active.clone(), logger: log.clone(), @@ -152,8 +112,8 @@ impl LegacyDrmDevice { // 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 unpriviledged mode"); - dev.priviledged = false; + warn!(log, "Unable to become drm master, assuming unprivileged mode"); + dev.privileged = false; }; // enumerate (and save) the current device state @@ -308,7 +268,11 @@ impl Device for LegacyDrmDevice { } else { self.backends.borrow_mut().remove(&event.crtc); } + } else { + debug!(self.logger, "Device not active. Ignoring PageFlip"); } + } else { + trace!(self.logger, "Unrelated event"); } } } @@ -334,22 +298,19 @@ impl Device for LegacyDrmDevice { }) } - fn get_connector_info(&self, conn: connector::Handle) -> std::result::Result { + fn get_connector_info(&self, conn: connector::Handle) -> Result { self.get_connector(conn) } - fn get_crtc_info(&self, crtc: crtc::Handle) -> std::result::Result { + fn get_crtc_info(&self, crtc: crtc::Handle) -> Result { self.get_crtc(crtc) } - fn get_encoder_info(&self, enc: encoder::Handle) -> std::result::Result { + fn get_encoder_info(&self, enc: encoder::Handle) -> Result { self.get_encoder(enc) } - fn get_framebuffer_info( - &self, - fb: framebuffer::Handle, - ) -> std::result::Result { + fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result { self.get_framebuffer(fb) } - fn get_plane_info(&self, plane: plane::Handle) -> std::result::Result { + fn get_plane_info(&self, plane: plane::Handle) -> Result { self.get_plane(plane) } } diff --git a/src/backend/drm/legacy/session.rs b/src/backend/drm/legacy/session.rs index 1f2c2d6..eb5c86e 100644 --- a/src/backend/drm/legacy/session.rs +++ b/src/backend/drm/legacy/session.rs @@ -23,7 +23,7 @@ use crate::backend::session::{AsSessionObserver, SessionObserver}; pub struct LegacyDrmDeviceObserver { dev: Weak>, dev_id: dev_t, - priviledged: bool, + privileged: bool, active: Arc, backends: Weak>>>>, logger: ::slog::Logger, @@ -35,7 +35,7 @@ impl AsSessionObserver> for Leg dev: Rc::downgrade(&self.dev), dev_id: self.dev_id, active: self.active.clone(), - priviledged: self.dev.priviledged, + privileged: self.dev.privileged, backends: Rc::downgrade(&self.backends), logger: self.logger.clone(), } @@ -62,7 +62,7 @@ impl SessionObserver for LegacyDrmDeviceObserver { } } self.active.store(false, Ordering::SeqCst); - if self.priviledged { + 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); @@ -84,7 +84,7 @@ impl SessionObserver for LegacyDrmDeviceObserver { } } self.active.store(true, Ordering::SeqCst); - if self.priviledged { + 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); diff --git a/src/backend/drm/legacy/surface.rs b/src/backend/drm/legacy/surface.rs index 53c178c..6bdfa1a 100644 --- a/src/backend/drm/legacy/surface.rs +++ b/src/backend/drm/legacy/surface.rs @@ -10,13 +10,13 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::Rc; use std::sync::RwLock; -use crate::backend::drm::{DevPath, RawSurface, Surface}; +use crate::backend::drm::{common::Error, DevPath, RawSurface, Surface}; use crate::backend::graphics::CursorBackend; use crate::backend::graphics::SwapBuffersError; -use super::{Dev, Error}; +use super::Dev; -use failure::ResultExt; +use failure::{Fail, ResultExt}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct State { @@ -41,8 +41,8 @@ impl AsRawFd for LegacyDrmSurfaceInternal { impl BasicDevice for LegacyDrmSurfaceInternal {} impl ControlDevice for LegacyDrmSurfaceInternal {} -impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurfaceInternal { - type CursorFormat = &'a dyn Buffer; +impl CursorBackend for LegacyDrmSurfaceInternal { + type CursorFormat = dyn Buffer; type Error = Error; fn set_cursor_position(&self, x: u32, y: u32) -> Result<(), Error> { @@ -56,14 +56,11 @@ impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurfaceInternal }) } - fn set_cursor_representation<'b>( - &'b self, - buffer: Self::CursorFormat, + fn set_cursor_representation( + &self, + buffer: &Self::CursorFormat, hotspot: (u32, u32), - ) -> Result<(), Error> - where - 'a: 'b, - { + ) -> Result<(), Error> { trace!(self.logger, "Setting the new imported cursor"); if self @@ -108,58 +105,13 @@ impl Surface for LegacyDrmSurfaceInternal { } 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()) { - // check if there is a valid encoder - let encoders = info - .encoders() - .iter() - .filter(|enc| enc.is_some()) - .map(|enc| enc.unwrap()) - .map(|encoder| { - self.get_encoder(encoder) - .compat() - .map_err(|source| Error::Access { - errmsg: "Error loading encoder info", - dev: self.dev_path(), - source, - }) - }) - .collect::, _>>()?; - - // and if any encoder supports the selected crtc - let resource_handles = self.resource_handles().compat().map_err(|source| Error::Access { - errmsg: "Error loading resources", - dev: self.dev_path(), - source, - })?; - if !encoders - .iter() - .map(|encoder| encoder.possible_crtcs()) - .all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc)) - { - return Err(Error::NoSuitableEncoder { - connector: info.handle(), - crtc: self.crtc, - }); - } - + if self.check_connector(conn, pending.mode.as_ref().unwrap())? { pending.connectors.insert(conn); - Ok(()) - } else { - Err(Error::ModeNotSuitable(pending.mode.unwrap())) } + + Ok(()) } fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> { @@ -167,6 +119,22 @@ impl Surface for LegacyDrmSurfaceInternal { Ok(()) } + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + let mut pending = self.pending.write().unwrap(); + + if connectors + .iter() + .map(|conn| self.check_connector(*conn, pending.mode.as_ref().unwrap())) + .collect::, _>>()? + .iter() + .all(|v| *v) + { + pending.connectors = connectors.iter().cloned().collect(); + } + + Ok(()) + } + fn use_mode(&self, mode: Option) -> Result<(), Error> { let mut pending = self.pending.write().unwrap(); @@ -208,12 +176,27 @@ impl RawSurface for LegacyDrmSurfaceInternal { let removed = current.connectors.difference(&pending.connectors); let added = pending.connectors.difference(¤t.connectors); + let mut conn_removed = false; 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"); } + // if the connector was mapped to our crtc, we need to ack the disconnect. + // the graphics pipeline will not be freed otherwise + conn_removed = true; + } + + if conn_removed { + // We need to do a null commit to free graphics pipelines + self.set_crtc(self.crtc, None, (0, 0), &[], None) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error setting crtc", + dev: self.dev_path(), + source, + })?; } for conn in added { @@ -254,7 +237,18 @@ impl RawSurface for LegacyDrmSurfaceInternal { *current = pending.clone(); - Ok(()) + ControlDevice::page_flip( + self, + self.crtc, + framebuffer, + &[PageFlipFlags::PageFlipEvent], + None, + ) + .map_err(|source| Error::Access { + errmsg: "Failed to queue page flip", + dev: self.dev_path(), + source: source.compat(), + }) } fn page_flip(&self, framebuffer: framebuffer::Handle) -> ::std::result::Result<(), SwapBuffersError> { @@ -267,11 +261,61 @@ impl RawSurface for LegacyDrmSurfaceInternal { &[PageFlipFlags::PageFlipEvent], None, ) - .map_err(|x| dbg!(x)) .map_err(|_| SwapBuffersError::ContextLost) } } +impl LegacyDrmSurfaceInternal { + fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result { + let info = self + .get_connector(conn) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error loading connector info", + dev: self.dev_path(), + source, + })?; + + // check if the connector can handle the current mode + if info.modes().contains(mode) { + // check if there is a valid encoder + let encoders = info + .encoders() + .iter() + .filter(|enc| enc.is_some()) + .map(|enc| enc.unwrap()) + .map(|encoder| { + self.get_encoder(encoder) + .compat() + .map_err(|source| Error::Access { + errmsg: "Error loading encoder info", + dev: self.dev_path(), + source, + }) + }) + .collect::, _>>()?; + + // and if any encoder supports the selected crtc + let resource_handles = self.resource_handles().compat().map_err(|source| Error::Access { + errmsg: "Error loading resources", + dev: self.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) + } + } +} + impl Drop for LegacyDrmSurfaceInternal { fn drop(&mut self) { // ignore failure at this point @@ -291,22 +335,19 @@ impl AsRawFd for LegacyDrmSurface { impl BasicDevice for LegacyDrmSurface {} impl ControlDevice for LegacyDrmSurface {} -impl<'a, A: AsRawFd + 'static> CursorBackend<'a> for LegacyDrmSurface { - type CursorFormat = &'a dyn Buffer; +impl CursorBackend for LegacyDrmSurface { + 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<'b>( - &'b self, - buffer: Self::CursorFormat, + fn set_cursor_representation( + &self, + buffer: &Self::CursorFormat, hotspot: (u32, u32), - ) -> Result<(), Error> - where - 'a: 'b, - { + ) -> Result<(), Error> { self.0.set_cursor_representation(buffer, hotspot) } } @@ -343,6 +384,10 @@ impl Surface for LegacyDrmSurface { self.0.remove_connector(connector) } + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error> { + self.0.set_connectors(connectors) + } + fn use_mode(&self, mode: Option) -> Result<(), Error> { self.0.use_mode(mode) } diff --git a/src/backend/drm/mod.rs b/src/backend/drm/mod.rs index c3204d6..1e05df0 100644 --- a/src/backend/drm/mod.rs +++ b/src/backend/drm/mod.rs @@ -53,6 +53,10 @@ 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")] pub mod egl; #[cfg(feature = "backend_drm_gbm")] @@ -99,7 +103,7 @@ pub trait Device: AsRawFd + DevPath { /// The number of crtc's represent the number of independant output devices the hardware may handle. fn create_surface( &mut self, - ctrc: crtc::Handle, + crtc: crtc::Handle, ) -> Result::Error>; /// Processes any open events of the underlying file descriptor. @@ -168,6 +172,13 @@ pub trait Surface { /// Tries to mark a [`connector`](drm::control::connector) /// for removal on the next commit. fn remove_connector(&self, connector: connector::Handle) -> Result<(), Self::Error>; + /// Tries to replace the current connector set with the newly provided one on the next commit. + /// + /// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc) + /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) + /// or is not compatible with the currently pending + /// [`Mode`](drm::control::Mode). + fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Self::Error>; /// Returns the currently active [`Mode`](drm::control::Mode) /// of the underlying [`crtc`](drm::control::crtc) /// if any. @@ -203,7 +214,10 @@ pub trait RawSurface: Surface + ControlDevice + BasicDevice { /// potentially causing some flickering. Check before performing this /// operation if a commit really is necessary using [`commit_pending`](RawSurface::commit_pending). /// - /// This operation is blocking until the crtc is in the desired state. + /// This operation is not necessarily blocking until the crtc is in the desired state, + /// but will trigger a `vblank` event once done. + /// Make sure to [set a `DeviceHandler`](Device::set_handler) and + /// [register the belonging `Device`](device_bind) before to receive the event in time. fn commit(&self, framebuffer: framebuffer::Handle) -> Result<(), ::Error>; /// Page-flip the underlying [`crtc`](drm::control::crtc) /// to a new given [`framebuffer`]. diff --git a/src/backend/graphics/cursor.rs b/src/backend/graphics/cursor.rs index 7be3bc0..f1ff46c 100644 --- a/src/backend/graphics/cursor.rs +++ b/src/backend/graphics/cursor.rs @@ -4,18 +4,18 @@ /// where possible. This may however be quite restrictive in terms of supported formats. /// /// For those reasons you may always choose to render your cursor(s) (partially) in software instead. -pub trait CursorBackend<'a> { +pub trait CursorBackend { /// Format representing the image drawn for the cursor. - type CursorFormat: 'a; + type CursorFormat: ?Sized; /// Error the underlying backend throws if operations fail - type Error; + type Error: 'static; /// Sets the cursor position and therefore updates the drawn cursors position. /// Useful as well for e.g. pointer wrapping. /// /// Not guaranteed to be supported on every backend. The result usually - /// depends on the backend, the cursor might be "owned" by another more priviledged + /// depends on the backend, the cursor might be "owned" by another more privileged /// compositor (running nested). /// /// In these cases setting the position is actually not required, as movement is done @@ -29,11 +29,9 @@ pub trait CursorBackend<'a> { /// The format is entirely dictated by the concrete implementation and might range /// from raw image buffers over a fixed list of possible cursor types to simply the /// void type () to represent no possible customization of the cursor itself. - fn set_cursor_representation<'b>( - &'b self, - cursor: Self::CursorFormat, + fn set_cursor_representation( + &self, + cursor: &Self::CursorFormat, hotspot: (u32, u32), - ) -> Result<(), Self::Error> - where - 'a: 'b; + ) -> Result<(), Self::Error>; } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 4c4e6ea..c1960bb 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -247,8 +247,8 @@ impl WinitGraphicsBackend { } } -impl<'a> CursorBackend<'a> for WinitGraphicsBackend { - type CursorFormat = &'a CursorIcon; +impl CursorBackend for WinitGraphicsBackend { + type CursorFormat = CursorIcon; type Error = (); fn set_cursor_position(&self, x: u32, y: u32) -> ::std::result::Result<(), ()> { @@ -261,14 +261,11 @@ impl<'a> CursorBackend<'a> for WinitGraphicsBackend { }) } - fn set_cursor_representation<'b>( - &'b self, - cursor: Self::CursorFormat, + fn set_cursor_representation( + &self, + cursor: &Self::CursorFormat, _hotspot: (u32, u32), - ) -> ::std::result::Result<(), ()> - where - 'a: 'b, - { + ) -> ::std::result::Result<(), ()> { // Cannot log this one, as `CursorFormat` is not `Debug` and should not be debug!(self.logger, "Changing cursor representation"); self.window.window().set_cursor_icon(*cursor);