drm: Add atomic modesetting support

This commit is contained in:
Victor Brekenfeld 2020-04-19 01:39:42 +02:00
parent e8cb940aab
commit 365b7e6496
5 changed files with 1317 additions and 1 deletions

View File

@ -45,9 +45,10 @@ slog-term = "2.3"
gl_generator = { version = "0.14", optional = true } gl_generator = { version = "0.14", optional = true }
[features] [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_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl", "use_system_lib"]
backend_drm = ["drm", "failure"] backend_drm = ["drm", "failure"]
backend_drm_atomic = ["backend_drm"]
backend_drm_legacy = ["backend_drm"] backend_drm_legacy = ["backend_drm"]
backend_drm_gbm = ["backend_drm", "gbm", "image"] backend_drm_gbm = ["backend_drm", "gbm", "image"]
backend_drm_egl = ["backend_drm", "backend_egl"] backend_drm_egl = ["backend_drm", "backend_egl"]

View File

@ -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<A: AsRawFd + 'static> {
dev: Rc<Dev<A>>,
dev_id: dev_t,
active: Arc<AtomicBool>,
backends: Rc<RefCell<HashMap<crtc::Handle, Weak<AtomicDrmSurfaceInternal<A>>>>>,
handler: Option<RefCell<Box<dyn DeviceHandler<Device = AtomicDrmDevice<A>>>>>,
logger: ::slog::Logger,
}
type OldState = (
Vec<(connector::Handle, PropertyValueSet)>,
Vec<(crtc::Handle, PropertyValueSet)>,
Vec<(framebuffer::Handle, PropertyValueSet)>,
Vec<(plane::Handle, PropertyValueSet)>,
);
type Mapping = (
HashMap<connector::Handle, HashMap<String, property::Handle>>,
HashMap<crtc::Handle, HashMap<String, property::Handle>>,
HashMap<framebuffer::Handle, HashMap<String, property::Handle>>,
HashMap<plane::Handle, HashMap<String, property::Handle>>,
);
struct Dev<A: AsRawFd + 'static> {
fd: A,
privileged: bool,
active: Arc<AtomicBool>,
old_state: OldState,
prop_mapping: Mapping,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for Dev<A> {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for Dev<A> {}
impl<A: AsRawFd + 'static> ControlDevice for Dev<A> {}
impl<A: AsRawFd + 'static> Drop for Dev<A> {
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<T: ResourceHandle>(
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<A: AsRawFd + 'static> AtomicDrmDevice<A> {
/// 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<L>(fd: A, logger: L) -> Result<Self, Error>
where
L: Into<Option<::slog::Logger>>,
{
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<A, T>(
dev: &Dev<A>,
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<A, T>(
dev: &Dev<A>,
handles: &[T],
mapping: &mut HashMap<T, HashMap<String, property::Handle>>,
) -> 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<A: AsRawFd + 'static> AsRawFd for AtomicDrmDevice<A> {
fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for AtomicDrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for AtomicDrmDevice<A> {}
impl<A: AsRawFd + 'static> Device for AtomicDrmDevice<A> {
type Surface = AtomicDrmSurface<A>;
fn device_id(&self) -> dev_t {
self.dev_id
}
fn set_handler(&mut self, handler: impl DeviceHandler<Device = Self> + '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<AtomicDrmSurface<A>, 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<ResourceHandles, Error> {
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<connector::Info, DrmError> {
self.get_connector(conn)
}
fn get_crtc_info(&self, crtc: crtc::Handle) -> Result<crtc::Info, DrmError> {
self.get_crtc(crtc)
}
fn get_encoder_info(&self, enc: encoder::Handle) -> Result<encoder::Info, DrmError> {
self.get_encoder(enc)
}
fn get_framebuffer_info(&self, fb: framebuffer::Handle) -> Result<framebuffer::Info, DrmError> {
self.get_framebuffer(fb)
}
fn get_plane_info(&self, plane: plane::Handle) -> Result<plane::Info, DrmError> {
self.get_plane(plane)
}
}
impl<A: AsRawFd + 'static> RawDevice for AtomicDrmDevice<A> {
type Surface = AtomicDrmSurface<A>;
}
impl<A: AsRawFd + 'static> Drop for AtomicDrmDevice<A> {
fn drop(&mut self) {
self.clear_handler();
}
}

View File

@ -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<A: AsRawFd + 'static> {
dev: Weak<Dev<A>>,
dev_id: dev_t,
privileged: bool,
active: Arc<AtomicBool>,
backends: Weak<RefCell<HashMap<crtc::Handle, Weak<AtomicDrmSurfaceInternal<A>>>>>,
logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsSessionObserver<AtomicDrmDeviceObserver<A>> for AtomicDrmDevice<A> {
fn observer(&mut self) -> AtomicDrmDeviceObserver<A> {
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<A: AsRawFd + 'static> SessionObserver for AtomicDrmDeviceObserver<A> {
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<RawFd>)>) {
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);
}
}

View File

@ -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<Option<(u32, u32)>>,
hotspot: Cell<(u32, u32)>,
framebuffer: Cell<Option<framebuffer::Info>>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
pub mode: Option<Mode>,
pub blob: Option<property::Value<'static>>,
pub connectors: HashSet<connector::Handle>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Planes {
pub primary: plane::Handle,
pub cursor: plane::Handle,
}
pub(super) struct AtomicDrmSurfaceInternal<A: AsRawFd + 'static> {
pub(super) dev: Rc<Dev<A>>,
pub(super) crtc: crtc::Handle,
pub(super) cursor: CursorState,
pub(super) planes: Planes,
pub(super) state: RwLock<State>,
pub(super) pending: RwLock<State>,
pub(super) logger: ::slog::Logger,
}
impl<A: AsRawFd + 'static> AsRawFd for AtomicDrmSurfaceInternal<A> {
fn as_raw_fd(&self) -> RawFd {
self.dev.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for AtomicDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for AtomicDrmSurfaceInternal<A> {}
impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
pub(crate) fn new(dev: Rc<Dev<A>>, crtc: crtc::Handle, logger: ::slog::Logger) -> Result<Self, Error> {
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<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
type Error = Error;
type Connectors = HashSet<connector::Handle>;
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<Mode> {
self.state.read().unwrap().mode.clone()
}
fn pending_mode(&self) -> Option<Mode> {
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<Mode>) -> 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<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
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(&current_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(
&current.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(&current.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<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
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<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
fn conn_prop_handle(
&self,
handle: connector::Handle,
name: &'static str,
) -> Result<property::Handle, Error> {
(*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<property::Handle, Error> {
(*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<property::Handle, Error> {
(*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<property::Handle, Error> {
(*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<connector::Handle>,
planes: &Planes,
framebuffer: Option<framebuffer::Handle>,
mode: Option<Mode>,
blob: Option<property::Value<'static>>,
) -> Result<AtomicModeReq, Error> {
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<A>, 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<A: AsRawFd + 'static>(pub(super) Rc<AtomicDrmSurfaceInternal<A>>);
impl<A: AsRawFd + 'static> AsRawFd for AtomicDrmSurface<A> {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl<A: AsRawFd + 'static> BasicDevice for AtomicDrmSurface<A> {}
impl<A: AsRawFd + 'static> ControlDevice for AtomicDrmSurface<A> {}
impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurface<A> {
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<A: AsRawFd + 'static> Surface for AtomicDrmSurface<A> {
type Error = Error;
type Connectors = HashSet<connector::Handle>;
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<Mode> {
self.0.current_mode()
}
fn pending_mode(&self) -> Option<Mode> {
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<Mode>) -> Result<(), Error> {
self.0.use_mode(mode)
}
}
impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurface<A> {
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)
}
}

View File

@ -53,6 +53,8 @@ use calloop::{LoopHandle, Source};
use super::graphics::SwapBuffersError; use super::graphics::SwapBuffersError;
#[cfg(feature = "backend_drm_atomic")]
pub mod atomic;
#[cfg(feature = "backend_drm")] #[cfg(feature = "backend_drm")]
pub mod common; pub mod common;
#[cfg(feature = "backend_drm_egl")] #[cfg(feature = "backend_drm_egl")]