kms: lock during screen conf changes

This commit is contained in:
Victoria Brekenfeld 2025-07-16 21:13:50 +02:00 committed by Victoria Brekenfeld
parent bb7a6a7113
commit e34a289c01
10 changed files with 747 additions and 382 deletions

View file

@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
backend::render::{output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR},
backend::{
kms::render::gles::GbmGlowBackend,
render::{init_shaders, output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR},
},
config::{AdaptiveSync, EdidProduct, OutputConfig, OutputState, ScreenFilter},
shell::Shell,
utils::{env::dev_list_var, prelude::*},
@ -20,10 +23,11 @@ use smithay::{
drm::{
compositor::{FrameError, FrameFlags},
exporter::gbm::GbmFramebufferExporter,
output::DrmOutputManager,
output::{DrmOutputManager, LockedDrmOutputManager},
DrmDevice, DrmDeviceFd, DrmEvent, DrmNode, NodeType,
},
egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay},
renderer::glow::GlowRenderer,
session::Session,
},
desktop::utils::OutputPresentationFeedback,
@ -43,6 +47,7 @@ use smithay::{
use tracing::{error, info, warn};
use std::{
borrow::BorrowMut,
cell::RefCell,
collections::{HashMap, HashSet},
fmt,
@ -60,6 +65,18 @@ pub struct EGLInternals {
pub context: EGLContext,
}
pub type LockedGbmDrmOutputManager<'a> = LockedDrmOutputManager<
'a,
GbmAllocator<DrmDeviceFd>,
GbmFramebufferExporter<DrmDeviceFd>,
Option<(
OutputPresentationFeedback,
Receiver<(Frame, Vec<Rectangle<i32, BufferCoords>>)>,
Duration,
)>,
DrmDeviceFd,
>;
pub type GbmDrmOutputManager = DrmOutputManager<
GbmAllocator<DrmDeviceFd>,
GbmFramebufferExporter<DrmDeviceFd>,
@ -111,6 +128,22 @@ impl fmt::Debug for Device {
}
}
pub struct LockedDevice<'a> {
pub dev_node: &'a DrmNode,
pub render_node: &'a DrmNode,
pub egl: &'a mut Option<EGLInternals>,
pub outputs: &'a HashMap<connector::Handle, Output>,
pub surfaces: &'a mut HashMap<crtc::Handle, Surface>,
pub drm: LockedGbmDrmOutputManager<'a>,
pub gbm: &'a GbmDevice<DrmDeviceFd>,
pub leased_connectors: &'a mut Vec<(connector::Handle, crtc::Handle)>,
pub leasing_global: &'a mut Option<DrmLeaseState>,
pub active_leases: &'a mut Vec<DrmLease>,
pub active_buffers: &'a HashSet<Weak<WlBuffer>>,
}
pub fn init_egl(gbm: &GbmDevice<DrmDeviceFd>) -> Result<EGLInternals> {
let path = gbm.dev_path();
@ -496,13 +529,13 @@ impl State {
.write()
.unwrap()
.take_if(|node| node == &device.render_node);
}
};
self.common
.output_configuration_state
.remove_heads(outputs_removed.iter());
backend.refresh_used_devices()?;
if self.backend.kms().session.is_active() {
if backend.session.is_active() {
for output in outputs_removed {
self.common.remove_output(&output);
}
@ -570,107 +603,24 @@ impl Device {
Ok(OutputChanges { added, removed })
}
pub fn connector_added(
&mut self,
primary_node: Arc<RwLock<Option<DrmNode>>>,
conn: connector::Handle,
maybe_crtc: Option<crtc::Handle>,
position: (u32, u32),
evlh: &LoopHandle<'static, State>,
screen_filter: ScreenFilter,
shell: Arc<parking_lot::RwLock<Shell>>,
startup_done: Arc<AtomicBool>,
) -> Result<(Output, bool)> {
let output = self
.outputs
.get(&conn)
.cloned()
.map(|output| Ok(output))
.unwrap_or_else(|| create_output_for_conn(self.drm.device_mut(), conn))
.context("Failed to create `Output`")?;
let non_desktop =
match drm_helpers::get_property_val(self.drm.device_mut(), conn, "non-desktop") {
Ok((val_type, value)) => val_type.convert_value(value).as_boolean().unwrap(),
Err(err) => {
warn!(
?err,
"Failed to determine if connector is meant desktop usage, assuming so."
);
false
}
};
if non_desktop {
if let Some(crtc) = maybe_crtc {
self.leased_connectors.push((conn, crtc));
info!(
"Connector {} is non-desktop, setting up for leasing",
output.name()
);
if let Some(lease_state) = self.leasing_global.as_mut() {
let physical = output.physical_properties();
lease_state.add_connector::<State>(
conn,
output.name(),
format!("{} {}", physical.make, physical.model),
);
}
} else {
warn!(
"Connector {} is non-desktop, but we don't have a free crtc: not leasing",
output.name()
);
}
Ok((output, false))
} else {
output
.user_data()
.insert_if_missing(|| RefCell::new(OutputConfig::default()));
populate_modes(self.drm.device_mut(), &output, conn, position)
.with_context(|| "Failed to enumerate connector modes")?;
let has_surface = if let Some(crtc) = maybe_crtc {
match Surface::new(
&output,
crtc,
conn,
primary_node,
self.dev_node,
self.render_node,
evlh,
screen_filter,
shell,
startup_done,
) {
Ok(data) => {
self.surfaces.insert(crtc, data);
true
}
Err(err) => {
error!(?crtc, "Failed to initialize surface: {}", err);
false
}
}
} else {
false
};
if !has_surface {
output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut()
.enabled = OutputState::Disabled;
}
Ok((output, true))
pub fn lock(&mut self) -> LockedDevice<'_> {
LockedDevice {
dev_node: &self.dev_node,
render_node: &self.render_node,
egl: &mut self.egl,
outputs: &self.outputs,
surfaces: &mut self.surfaces,
drm: self.drm.lock(),
gbm: &self.gbm,
leased_connectors: &mut self.leased_connectors,
leasing_global: &mut self.leasing_global,
active_leases: &mut self.active_leases,
active_buffers: &mut self.active_buffers,
}
}
}
impl<'a> LockedDevice<'a> {
fn allow_frame_flags(
&mut self,
flag: bool,
@ -692,9 +642,7 @@ impl Device {
.map(|(crtc, surface)| (*crtc, surface.output.clone()))
.collect::<HashMap<_, _>>();
let mut drm = self.drm.lock();
let map = drm.compositors();
for (crtc, compositor) in map.iter() {
for (crtc, compositor) in self.drm.compositors().iter() {
let elements = match output_map.get(crtc) {
Some(output) => output_elements(
Some(&self.render_node),
@ -753,9 +701,7 @@ impl Device {
shell,
)?;
let mut drm = self.drm.lock();
let maps = drm.compositors();
for (crtc, comp) in maps {
for (crtc, comp) in self.drm.compositors() {
let Some(surface) = self.surfaces.get_mut(crtc) else {
continue;
};
@ -777,12 +723,364 @@ impl Device {
Ok(())
}
}
pub fn in_use(&self, primary: Option<&DrmNode>) -> bool {
pub trait MaybeLockedDevice {
fn dev_node(&self) -> DrmNode;
fn render_node(&self) -> DrmNode;
fn output(&self, conn: &connector::Handle) -> Option<&Output>;
fn drm_device_mut(&mut self) -> &mut DrmDevice;
fn gbm(&self) -> &GbmDevice<DrmDeviceFd>;
fn egl(&mut self) -> &mut Option<EGLInternals>;
fn insert_surface(&mut self, crtc: crtc::Handle, surface: Surface);
fn in_use(&self, primary: Option<&DrmNode>) -> bool;
fn add_leased_connector(
&mut self,
crtc: crtc::Handle,
conn: connector::Handle,
output: &Output,
);
fn connector_added(
&mut self,
primary_node: Arc<RwLock<Option<DrmNode>>>,
conn: connector::Handle,
maybe_crtc: Option<crtc::Handle>,
position: (u32, u32),
evlh: &LoopHandle<'static, State>,
screen_filter: ScreenFilter,
shell: Arc<parking_lot::RwLock<Shell>>,
startup_done: Arc<AtomicBool>,
) -> Result<(Output, bool)> {
let output = self
.output(&conn)
.cloned()
.map(|output| Ok(output))
.unwrap_or_else(|| create_output_for_conn(self.drm_device_mut(), conn))
.context("Failed to create `Output`")?;
let non_desktop =
match drm_helpers::get_property_val(self.drm_device_mut(), conn, "non-desktop") {
Ok((val_type, value)) => val_type.convert_value(value).as_boolean().unwrap(),
Err(err) => {
warn!(
?err,
"Failed to determine if connector is meant desktop usage, assuming so."
);
false
}
};
if non_desktop {
if let Some(crtc) = maybe_crtc {
self.add_leased_connector(crtc, conn, &output);
} else {
warn!(
"Connector {} is non-desktop, but we don't have a free crtc: not leasing",
output.name()
);
}
Ok((output, false))
} else {
output
.user_data()
.insert_if_missing(|| RefCell::new(OutputConfig::default()));
populate_modes(self.drm_device_mut(), &output, conn, position)
.with_context(|| "Failed to enumerate connector modes")?;
let has_surface = if let Some(crtc) = maybe_crtc {
match Surface::new(
&output,
crtc,
conn,
primary_node,
self.dev_node(),
self.render_node(),
evlh,
screen_filter,
shell,
startup_done,
) {
Ok(data) => {
self.insert_surface(crtc, data);
true
}
Err(err) => {
error!(?crtc, "Failed to initialize surface: {}", err);
false
}
}
} else {
false
};
if !has_surface {
output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut()
.enabled = OutputState::Disabled;
}
Ok((output, true))
}
}
fn update_egl(
&mut self,
primary_node: Option<&DrmNode>,
api: &mut GbmGlowBackend<DrmDeviceFd>,
) -> Result<bool> {
if self.in_use(primary_node) {
if self.egl().is_none() {
let egl = init_egl(self.gbm()).context("Failed to create EGL context")?;
let mut renderer = unsafe {
GlowRenderer::new(
EGLContext::new_shared_with_priority(
&egl.display,
&egl.context,
ContextPriority::High,
)
.context("Failed to create shared EGL context")?,
)
.context("Failed to create GL renderer")?
};
init_shaders(renderer.borrow_mut()).context("Failed to compile shaders")?;
api.add_node(
self.render_node(),
GbmAllocator::new(
self.gbm().clone(),
// SCANOUT because stride bugs
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
),
renderer,
);
*self.egl() = Some(egl);
}
Ok(true)
} else {
if self.egl().is_some() {
let _ = self.egl().take();
api.remove_node(&self.render_node());
}
Ok(false)
}
}
fn update_surface_nodes<'b>(
&mut self,
used_devices: &HashSet<DrmNode>,
others: impl Iterator<Item = &'b Self>,
) -> Result<()>
where
Self: 'b;
}
impl MaybeLockedDevice for Device {
fn dev_node(&self) -> DrmNode {
self.dev_node
}
fn render_node(&self) -> DrmNode {
self.render_node
}
fn output(&self, conn: &connector::Handle) -> Option<&Output> {
self.outputs.get(conn)
}
fn drm_device_mut(&mut self) -> &mut DrmDevice {
self.drm.device_mut()
}
fn gbm(&self) -> &GbmDevice<DrmDeviceFd> {
&self.gbm
}
fn egl(&mut self) -> &mut Option<EGLInternals> {
&mut self.egl
}
fn insert_surface(&mut self, crtc: crtc::Handle, surface: Surface) {
self.surfaces.insert(crtc, surface);
}
fn in_use(&self, primary: Option<&DrmNode>) -> bool {
Some(&self.render_node) == primary
|| !self.surfaces.is_empty()
|| !self.active_buffers.is_empty()
}
fn add_leased_connector(
&mut self,
crtc: crtc::Handle,
conn: connector::Handle,
output: &Output,
) {
self.leased_connectors.push((conn, crtc));
info!(
"Connector {} is non-desktop, setting up for leasing",
output.name()
);
if let Some(lease_state) = self.leasing_global.as_mut() {
let physical = output.physical_properties();
lease_state.add_connector::<State>(
conn,
output.name(),
format!("{} {}", physical.make, physical.model),
);
}
}
fn update_surface_nodes<'b>(
&mut self,
used_devices: &HashSet<DrmNode>,
mut others: impl Iterator<Item = &'b Device>,
) -> Result<()> {
for surface in self.surfaces.values_mut() {
let known_nodes = surface.known_nodes().clone();
for gone_device in known_nodes.difference(&used_devices) {
surface.remove_node(*gone_device);
}
for new_device in used_devices.difference(&known_nodes) {
let (render_node, egl, gbm) = if self.render_node == *new_device {
// we need to make sure to do partial borrows here, as device.surfaces is borrowed mutable
(
self.render_node,
self.egl.as_ref().unwrap(),
self.gbm.clone(),
)
} else {
let device = others.find(|d| d.render_node == *new_device).unwrap();
(
device.render_node,
device.egl.as_ref().unwrap(),
device.gbm.clone(),
)
};
surface.add_node(
render_node,
GbmAllocator::new(gbm, GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT),
EGLContext::new_shared_with_priority(
&egl.display,
&egl.context,
ContextPriority::High,
)
.context("Failed to create shared EGL context")?,
);
}
}
Ok(())
}
}
impl<'a> MaybeLockedDevice for LockedDevice<'a> {
fn dev_node(&self) -> DrmNode {
*self.dev_node
}
fn render_node(&self) -> DrmNode {
*self.render_node
}
fn output(&self, conn: &connector::Handle) -> Option<&Output> {
self.outputs.get(conn)
}
fn drm_device_mut(&mut self) -> &mut DrmDevice {
self.drm.device_mut()
}
fn gbm(&self) -> &GbmDevice<DrmDeviceFd> {
self.gbm
}
fn egl(&mut self) -> &mut Option<EGLInternals> {
self.egl
}
fn insert_surface(&mut self, crtc: crtc::Handle, surface: Surface) {
self.surfaces.insert(crtc, surface);
}
fn in_use(&self, primary: Option<&DrmNode>) -> bool {
Some(self.render_node) == primary
|| !self.surfaces.is_empty()
|| !self.active_buffers.is_empty()
}
fn add_leased_connector(
&mut self,
crtc: crtc::Handle,
conn: connector::Handle,
output: &Output,
) {
self.leased_connectors.push((conn, crtc));
info!(
"Connector {} is non-desktop, setting up for leasing",
output.name()
);
if let Some(lease_state) = self.leasing_global.as_mut() {
let physical = output.physical_properties();
lease_state.add_connector::<State>(
conn,
output.name(),
format!("{} {}", physical.make, physical.model),
);
}
}
fn update_surface_nodes<'b>(
&mut self,
used_devices: &HashSet<DrmNode>,
mut others: impl Iterator<Item = &'b LockedDevice<'a>>,
) -> Result<()>
where
'a: 'b,
{
for surface in self.surfaces.values_mut() {
let known_nodes = surface.known_nodes().clone();
for gone_device in known_nodes.difference(&used_devices) {
surface.remove_node(*gone_device);
}
for new_device in used_devices.difference(&known_nodes) {
let (render_node, egl, gbm) = if *self.render_node == *new_device {
// we need to make sure to do partial borrows here, as device.surfaces is borrowed mutable
(
self.render_node,
self.egl.as_ref().unwrap(),
self.gbm.clone(),
)
} else {
let device = others.find(|d| d.render_node == new_device).unwrap();
(
device.render_node,
device.egl.as_ref().unwrap(),
device.gbm.clone(),
)
};
surface.add_node(
*render_node,
GbmAllocator::new(gbm, GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT),
EGLContext::new_shared_with_priority(
&egl.display,
&egl.context,
ContextPriority::High,
)
.context("Failed to create shared EGL context")?,
);
}
}
Ok(())
}
}
fn create_output_for_conn(drm: &mut DrmDevice, conn: connector::Handle) -> Result<Output> {