docs: Add more explanations to the legacy-drm-code
This commit is contained in:
parent
fa42a0a223
commit
7e8f6b2955
|
@ -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,
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue