Release session devices during teardown and tolerate removed panel outputs

This commit is contained in:
Samuel Onoja committed 2026-10-14 19:02:54 +01:00
1 parent 90564e6564
commit 53ba599918
4 files changed
+113 -8

No files matched your search

+8 -5
View File
@@ -17,6 +17,7 @@ mod planes;
mod recovery;
mod redraw;
mod scheduling;
mod session_device;
mod topology;
mod transaction;
use diagnostics::{Diagnostics, PrimaryOutcome};
@@ -47,10 +48,9 @@ use smithay::reexports::drm::control::{
Device as ControlDevice, Mode as DrmMode, ModeTypeFlags, connector, crtc, property,
};
use smithay::reexports::input::{Device as LibinputDevice, Libinput};
use smithay::reexports::rustix::fs::OFlags;
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind;
use smithay::reexports::wayland_server::backend::GlobalId;
use smithay::utils::{DeviceFd, Monotonic, Time, Transform};
use smithay::utils::{Monotonic, Time, Transform};
use smithay::wayland::dmabuf::DmabufFeedbackBuilder;
use smithay::wayland::presentation::Refresh;
use topology::{Topology, reconcile_outputs};
@@ -257,6 +257,8 @@ struct DirectDevice {
renderer: GlesRenderer,
render_node: DrmNode,
outputs: HashMap<crtc::Handle, DirectOutput>,
// Last: release libseat's original FD after renderer/DRM resources.
_session_device: session_device::SessionDevice<LibSeatSession>,
}
struct DirectOutput {
@@ -788,15 +790,15 @@ pub(crate) fn validate_live_outputs(state: &Ferese, profiles: &[OutputProfile])
}
fn open_device(state: &mut Ferese, path: &Path) -> Result<DrmNode, Box<dyn Error>> {
let mut session = state
let session = state
.direct_backend
.as_ref()
.expect("direct backend state is initialized before the DRM device")
.session
.clone();
let node = DrmNode::from_path(path)?;
let fd = session.open(path, OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK)?;
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
let session_device = session_device::SessionDevice::open(session, path)?;
let fd = DrmDeviceFd::new(session_device.device_fd()?);
let (drm, notifier) = DrmDevice::new(fd.clone(), true)?;
let gbm = GbmDevice::new(fd)?;
// SAFETY: GBM owns a valid DRM descriptor for the lifetime of the EGL display.
@@ -984,6 +986,7 @@ fn open_device(state: &mut Ferese, path: &Path) -> Result<DrmNode, Box<dyn Error
.insert(
node,
DirectDevice {
_session_device: session_device,
drm,
gbm,
renderer,
@@ -0,0 +1,87 @@
use smithay::{backend::session::Session, reexports::rustix::fs::OFlags, utils::DeviceFd};
use std::{io, os::fd::OwnedFd, path::Path};
pub(super) struct SessionDevice<S: Session> {
session: S,
fd: Option<OwnedFd>,
}
impl<S: Session> SessionDevice<S> {
pub fn open(mut session: S, path: &Path) -> Result<Self, S::Error> {
let fd = session.open(path, OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK)?;
Ok(Self { session, fd: Some(fd) })
}
pub fn device_fd(&self) -> io::Result<DeviceFd> {
self.fd.as_ref().unwrap().try_clone().map(DeviceFd::from)
}
}
impl<S: Session> Drop for SessionDevice<S> {
fn drop(&mut self) {
if let Err(error) = self.session.close(self.fd.take().unwrap()) {
tracing::warn!(?error, "could not release DRM device through the session manager");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{cell::Cell, fs::File, os::fd::AsRawFd, rc::Rc};
#[derive(Clone, Default)]
struct TestSession(Rc<Cell<Option<i32>>>);
impl Session for TestSession {
type Error = ();
fn open(&mut self, _: &Path, _: OFlags) -> Result<OwnedFd, ()> {
if self.0.get().is_some() {
return Err(());
}
let fd: OwnedFd = File::open("/dev/null").unwrap().into();
self.0.set(Some(fd.as_raw_fd()));
Ok(fd)
}
fn close(&mut self, fd: OwnedFd) -> Result<(), ()> {
assert_eq!(self.0.replace(None), Some(fd.as_raw_fd()));
Ok(())
}
fn change_vt(&mut self, _: i32) -> Result<(), ()> {
Ok(())
}
fn is_active(&self) -> bool {
true
}
fn seat(&self) -> String {
"seat0".into()
}
}
#[test]
fn teardown_releases_original_session_lease_before_reopening() {
let session = TestSession::default();
for _ in 0..3 {
let lease = SessionDevice::open(session.clone(), Path::new("/dev/null")).unwrap();
let rendering = lease.device_fd().unwrap();
assert_ne!(Some(rendering.as_raw_fd()), session.0.get());
drop(rendering);
assert!(session.0.get().is_some());
drop(lease);
assert!(session.0.get().is_none());
}
}
#[test]
fn failed_renderer_initialization_releases_lease() {
fn initialize(session: TestSession) -> Result<(), ()> {
let lease = SessionDevice::open(session, Path::new("/dev/null"))?;
let _rendering = lease.device_fd().unwrap();
Err(())
}
let session = TestSession::default();
assert!(initialize(session.clone()).is_err());
assert!(session.0.get().is_none());
assert!(SessionDevice::open(session, Path::new("/dev/null")).is_ok());
}
}
+1 -1
View File
@@ -260,7 +260,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) {
None => match open_device(state, current_path.unwrap()) {
Ok(node) => node,
Err(error) => {
tracing::warn!(%error, ?path, "DRM device unavailable; will retry");
tracing::warn!(%error, ?path, device_path = ?current_path, "DRM device unavailable; will retry");
slot_mut(state, &path).failed(Instant::now());
continue;
}