drm: Add atomic modesetting support
This commit is contained in:
parent
e8cb940aab
commit
365b7e6496
|
@ -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"]
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(¤t_conns);
|
||||||
|
|
||||||
|
for conn in removed {
|
||||||
|
if let Ok(info) = self.get_connector(*conn) {
|
||||||
|
info!(self.logger, "Removing connector: {:?}", info.interface());
|
||||||
|
} else {
|
||||||
|
info!(self.logger, "Removing unknown connector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for conn in added {
|
||||||
|
if let Ok(info) = self.get_connector(*conn) {
|
||||||
|
info!(self.logger, "Adding connector: {:?}", info.interface());
|
||||||
|
} else {
|
||||||
|
info!(self.logger, "Adding unknown connector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.mode != pending.mode {
|
||||||
|
info!(
|
||||||
|
self.logger,
|
||||||
|
"Setting new mode: {:?}",
|
||||||
|
pending.mode.as_ref().unwrap().name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(self.logger, "Testing screen config");
|
||||||
|
{
|
||||||
|
let req = self.build_request(
|
||||||
|
&pending.connectors,
|
||||||
|
&self.planes,
|
||||||
|
Some(framebuffer),
|
||||||
|
pending.mode,
|
||||||
|
pending.blob,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Err(err) = self
|
||||||
|
.atomic_commit(
|
||||||
|
&[AtomicCommitFlags::AllowModeset, AtomicCommitFlags::TestOnly],
|
||||||
|
req.clone(),
|
||||||
|
)
|
||||||
|
.compat()
|
||||||
|
.map_err(|_| Error::TestFailed(self.crtc))
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
self.logger,
|
||||||
|
"New screen configuration invalid!:\n\t{:#?}\n\t{}\n", req, err
|
||||||
|
);
|
||||||
|
info!(self.logger, "Reverting back to last know good state");
|
||||||
|
|
||||||
|
*pending = current.clone();
|
||||||
|
} else {
|
||||||
|
if current.mode != pending.mode {
|
||||||
|
if let Some(blob) = current.blob {
|
||||||
|
if let Err(err) = self.dev.destroy_property_blob(blob.into()) {
|
||||||
|
warn!(self.logger, "Failed to destory old mode property blob: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*current = pending.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = self.build_request(
|
||||||
|
¤t.connectors,
|
||||||
|
&self.planes,
|
||||||
|
Some(framebuffer),
|
||||||
|
current.mode,
|
||||||
|
current.blob,
|
||||||
|
)?;
|
||||||
|
debug!(self.logger, "Setting screen: {:#?}", req);
|
||||||
|
self.atomic_commit(
|
||||||
|
&[
|
||||||
|
AtomicCommitFlags::PageFlipEvent,
|
||||||
|
AtomicCommitFlags::AllowModeset,
|
||||||
|
AtomicCommitFlags::Nonblock,
|
||||||
|
],
|
||||||
|
req,
|
||||||
|
)
|
||||||
|
.compat()
|
||||||
|
.map_err(|source| Error::Access {
|
||||||
|
errmsg: "Error setting crtc",
|
||||||
|
dev: self.dev_path(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_flip(&self, framebuffer: framebuffer::Handle) -> Result<(), SwapBuffersError> {
|
||||||
|
let current = self.state.read().unwrap();
|
||||||
|
let req = self
|
||||||
|
.build_request(¤t.connectors, &self.planes, Some(framebuffer), None, None) //current.mode)
|
||||||
|
.map_err(|_| SwapBuffersError::ContextLost)?;
|
||||||
|
trace!(self.logger, "Queueing page flip: {:#?}", req);
|
||||||
|
self.atomic_commit(
|
||||||
|
&[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock],
|
||||||
|
req,
|
||||||
|
)
|
||||||
|
.map_err(|_| SwapBuffersError::ContextLost)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")]
|
||||||
|
|
Loading…
Reference in New Issue