docs: Add more explanations to the legacy-drm-code

This commit is contained in:
Victor Brekenfeld 2020-06-05 20:31:14 +02:00
parent fa42a0a223
commit 7e8f6b2955
3 changed files with 61 additions and 6 deletions

View File

@ -2,11 +2,20 @@
//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface) //! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface)
//! implementations using the legacy mode-setting infrastructure. //! implementations using the legacy mode-setting infrastructure.
//! //!
//! Legacy mode-setting refers to the now outdated, but still supported direct manager api
//! of the linux kernel. Adaptations of this api can be found in BSD kernels.
//!
//! The newer and objectively better api is known as atomic-modesetting (or nuclear-page-flip),
//! however this api is not supported by every driver, so this is provided for backwards compatibility.
//! Currenly there are no features in smithay, that are exclusive to the atomic api.
//!
//! Usually this implementation will be wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice). //! 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. //! 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_legacy_drm` example. //! For an example how to use this standalone, take a look at the `raw_legacy_drm` example.
//! //!
//! For detailed overview of these abstractions take a look at the module documentation of backend::drm.
//!
use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice}; use super::{common::Error, DevPath, Device, DeviceHandler, RawDevice};
@ -66,6 +75,8 @@ impl<A: AsRawFd + 'static> Drop for Dev<A> {
// Here we restore the tty to it's previous state. // Here we restore the tty to it's previous state.
// In case e.g. getty was running on the tty sets the correct framebuffer again, // In case e.g. getty was running on the tty sets the correct framebuffer again,
// so that getty will be visible. // so that getty will be visible.
// We do exit correctly, if this fails, but the user will be presented with
// a black screen, if no display handler takes control again.
let old_state = self.old_state.clone(); let old_state = self.old_state.clone();
for (handle, (info, connectors)) in old_state { for (handle, (info, connectors)) in old_state {
if let Err(err) = self.set_crtc( if let Err(err) = self.set_crtc(
@ -88,7 +99,7 @@ impl<A: AsRawFd + 'static> Drop for Dev<A> {
} }
impl<A: AsRawFd + 'static> LegacyDrmDevice<A> { impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
/// Create a new [`LegacyDrmDevice`] from an open drm node /// Create a new [`LegacyDrmDevice`] from an open drm node.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -115,6 +126,8 @@ impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
.map_err(Error::UnableToGetDeviceId)? .map_err(Error::UnableToGetDeviceId)?
.st_rdev; .st_rdev;
// we wrap some of the internal state in another struct to share with
// the surfaces and event loop handlers.
let active = Arc::new(AtomicBool::new(true)); let active = Arc::new(AtomicBool::new(true));
let mut dev = Dev { let mut dev = Dev {
fd: dev, fd: dev,
@ -124,13 +137,17 @@ impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
logger: log.clone(), logger: log.clone(),
}; };
// we want to modeset, so we better be the master, if we run via a tty session // We want to modeset, so we better be the master, if we run via a tty session.
// This is only needed on older kernels. Newer kernels grant this permission,
// if no other process is already the *master*. So we skip over this error.
if dev.acquire_master_lock().is_err() { if dev.acquire_master_lock().is_err() {
warn!(log, "Unable to become drm master, assuming unprivileged mode"); warn!(log, "Unable to become drm master, assuming unprivileged mode");
dev.privileged = false; dev.privileged = false;
}; };
// enumerate (and save) the current device state // Enumerate (and save) the current device state.
// We need to keep the previous device configuration to restore the state later,
// so we query everything, that we can set.
let res_handles = ControlDevice::resource_handles(&dev) let res_handles = ControlDevice::resource_handles(&dev)
.compat() .compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
@ -165,11 +182,18 @@ impl<A: AsRawFd + 'static> LegacyDrmDevice<A> {
} }
} }
// If the user does not explicitly requests us to skip this,
// we clear out the complete connector<->crtc mapping on device creation.
//
// The reason is, that certain operations may be racy otherwise, as surfaces can
// exist on different threads. As a result, we cannot enumerate the current state
// on surface creation (it might be changed on another thread during the enumeration).
// An easy workaround is to set a known state on device creation.
if disable_connectors { if disable_connectors {
dev.set_connector_state(res_handles.connectors().iter().copied(), false)?; dev.set_connector_state(res_handles.connectors().iter().copied(), false)?;
for crtc in res_handles.crtcs() { for crtc in res_handles.crtcs() {
// null commit // null commit (necessary to trigger removal on the kernel side with the legacy api.)
dev.set_crtc(*crtc, None, (0, 0), &[], None) dev.set_crtc(*crtc, None, (0, 0), &[], None)
.compat() .compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
@ -199,6 +223,7 @@ impl<A: AsRawFd + 'static> Dev<A> {
connectors: impl Iterator<Item = connector::Handle>, connectors: impl Iterator<Item = connector::Handle>,
enabled: bool, enabled: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
// for every connector...
for conn in connectors { for conn in connectors {
let info = self let info = self
.get_connector(conn) .get_connector(conn)
@ -208,7 +233,9 @@ impl<A: AsRawFd + 'static> Dev<A> {
dev: self.dev_path(), dev: self.dev_path(),
source, source,
})?; })?;
// that is currently connected ...
if info.state() == connector::State::Connected { if info.state() == connector::State::Connected {
// get a list of it's properties.
let props = self let props = self
.get_properties(conn) .get_properties(conn)
.compat() .compat()
@ -218,7 +245,9 @@ impl<A: AsRawFd + 'static> Dev<A> {
source, source,
})?; })?;
let (handles, _) = props.as_props_and_values(); let (handles, _) = props.as_props_and_values();
// for every handle ...
for handle in handles { for handle in handles {
// get information of that property
let info = self let info = self
.get_property(*handle) .get_property(*handle)
.compat() .compat()
@ -227,7 +256,9 @@ impl<A: AsRawFd + 'static> Dev<A> {
dev: self.dev_path(), dev: self.dev_path(),
source, source,
})?; })?;
// to find out, if we got the handle of the "DPMS" property ...
if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) { if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) {
// so we can use that to turn on / off the connector
self.set_property( self.set_property(
conn, conn,
*handle, *handle,

View File

@ -72,6 +72,8 @@ impl<A: AsRawFd + 'static> LegacyDrmDeviceObserver<A> {
for surface in backends.borrow().values().filter_map(Weak::upgrade) { for surface in backends.borrow().values().filter_map(Weak::upgrade) {
// other ttys that use no cursor, might not clear it themselves. // other ttys that use no cursor, might not clear it themselves.
// This makes sure our cursor won't stay visible. // This makes sure our cursor won't stay visible.
//
// This usually happens with getty, and a cursor on top of a kernel console looks very weird.
let _ = (*device).set_cursor( let _ = (*device).set_cursor(
surface.crtc, surface.crtc,
Option::<&drm::control::dumbbuffer::DumbBuffer>::None, Option::<&drm::control::dumbbuffer::DumbBuffer>::None,
@ -119,6 +121,11 @@ impl<A: AsRawFd + 'static> LegacyDrmDeviceObserver<A> {
} }
impl<A: AsRawFd + 'static> LegacyDrmDeviceObserver<A> { impl<A: AsRawFd + 'static> LegacyDrmDeviceObserver<A> {
// reset state enumerates the actual current state of the drm device
// and applies that to the `current_state` as saved in the surfaces.
//
// This re-sync is necessary after a tty swap, as the pipeline might
// be left in a different state.
fn reset_state(&mut self) -> Result<(), Error> { fn reset_state(&mut self) -> Result<(), Error> {
// lets enumerate it the current state // lets enumerate it the current state
if let Some(dev) = self.dev.upgrade() { if let Some(dev) = self.dev.upgrade() {

View File

@ -70,10 +70,13 @@ impl<A: AsRawFd + 'static> CursorBackend for LegacyDrmSurfaceInternal<A> {
trace!(self.logger, "Setting the new imported cursor"); trace!(self.logger, "Setting the new imported cursor");
// set_cursor2 allows us to set the hotspot, but is not supported by every implementation.
if self if self
.set_cursor2(self.crtc, Some(buffer), (hotspot.0 as i32, hotspot.1 as i32)) .set_cursor2(self.crtc, Some(buffer), (hotspot.0 as i32, hotspot.1 as i32))
.is_err() .is_err()
{ {
// the cursor will be slightly misplaced, when using the function for hotspots other then (0, 0),
// but that is still better then no cursor.
self.set_cursor(self.crtc, Some(buffer)) self.set_cursor(self.crtc, Some(buffer))
.compat() .compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
@ -221,7 +224,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
self.dev.set_connector_state(removed.copied(), false)?; self.dev.set_connector_state(removed.copied(), false)?;
if conn_removed { if conn_removed {
// We need to do a null commit to free graphics pipelines // null commit (necessary to trigger removal on the kernel side with the legacy api.)
self.set_crtc(self.crtc, None, (0, 0), &[], None) self.set_crtc(self.crtc, None, (0, 0), &[], None)
.compat() .compat()
.map_err(|source| Error::Access { .map_err(|source| Error::Access {
@ -246,6 +249,7 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
} }
debug!(self.logger, "Setting screen"); debug!(self.logger, "Setting screen");
// do a modeset and attach the given framebuffer
self.set_crtc( self.set_crtc(
self.crtc, self.crtc,
Some(framebuffer), Some(framebuffer),
@ -266,6 +270,11 @@ impl<A: AsRawFd + 'static> RawSurface for LegacyDrmSurfaceInternal<A> {
*current = pending.clone(); *current = pending.clone();
// set crtc does not trigger page_flip events, so we immediately queue a flip
// with the same framebuffer.
// this will result in wasting a frame, because this flip will need to wait
// for `set_crtc`, but is necessary to drive the event loop and thus provide
// a more consistent api.
ControlDevice::page_flip( ControlDevice::page_flip(
self, self,
self.crtc, self.crtc,
@ -316,7 +325,8 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
"Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors "Initializing drm surface with mode {:?} and connectors {:?}", mode, connectors
); );
// Try to enumarate the current state to set the initial state variable correctly // Try to enumarate the current state to set the initial state variable correctly.
// We need an accurate state to handle `commit_pending`.
let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access { let crtc_info = dev.get_crtc(crtc).compat().map_err(|source| Error::Access {
errmsg: "Error loading crtc info", errmsg: "Error loading crtc info",
dev: dev.dev_path(), dev: dev.dev_path(),
@ -377,6 +387,13 @@ impl<A: AsRawFd + 'static> LegacyDrmSurfaceInternal<A> {
Ok(surface) Ok(surface)
} }
// we use this function to verify, if a certain connector/mode combination
// is valid on our crtc. We do this with the most basic information we have:
// - is there a matching encoder
// - does the connector support the provided Mode.
//
// Better would be some kind of test commit to ask the driver,
// but that only exists for the atomic api.
fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> { fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
let info = self let info = self
.get_connector(conn) .get_connector(conn)