Refactor udev backend

- Do not open devices for `UdevHandler` anymore
- `UdevBackend` does not require `LoopHandle` or `Session` anymore
- Type of the created device can be choosed freely by the handler
- `UdevBackendObserver` is not needed anymore
This commit is contained in:
Victor Brekenfeld 2018-11-21 09:39:09 +01:00
parent 505791e336
commit b537237a74
3 changed files with 71 additions and 358 deletions

View file

@ -39,7 +39,6 @@ env:
- FEATURES="backend_libinput"
- FEATURES="backend_udev"
- FEATURES="backend_session"
- FEATURES="backend_session_udev"
- FEATURES="backend_session_logind"
- FEATURES="renderer_glium"
- FEATURES="xwayland"

View file

@ -43,9 +43,8 @@ backend_drm = ["drm", "backend_egl"]
backend_egl = ["gl_generator"]
backend_libinput = ["input"]
backend_session = []
backend_session_udev = ["udev", "backend_session"]
backend_udev = ["udev"]
backend_session_logind = ["dbus", "systemd", "backend_session"]
backend_udev = ["udev", "backend_drm", "backend_session_udev"]
renderer_gl = ["gl_generator"]
renderer_glium = ["renderer_gl", "glium"]
xwayland = []

View file

@ -9,258 +9,91 @@
//! See also `examples/udev.rs` for pure hardware backed example of a compositor utilizing this
//! backend.
use backend::{
drm::{drm_device_bind, DrmDevice, DrmHandler},
session::{AsSessionObserver, Session, SessionObserver},
};
use drm::{control::Device as ControlDevice, Device as BasicDevice};
use nix::{fcntl, sys::stat::dev_t};
use nix::sys::stat::{dev_t, stat};
use std::{
cell::RefCell,
collections::HashMap,
collections::HashSet,
ffi::OsString,
io::Error as IoError,
mem::drop,
os::unix::io::{AsRawFd, RawFd},
path::{Path, PathBuf},
rc::{Rc, Weak},
};
use udev::{Context, Enumerator, Event, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult};
use udev::{Context, Enumerator, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult};
use wayland_server::calloop::{
generic::{EventedRawFd, Generic},
generic::{EventedFd, Generic},
mio::Ready,
LoopHandle, Source,
LoopHandle, Source, InsertError,
};
/// Udev's `DrmDevice` type based on the underlying session
pub struct SessionFdDrmDevice(RawFd);
impl AsRawFd for SessionFdDrmDevice {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl BasicDevice for SessionFdDrmDevice {}
impl ControlDevice for SessionFdDrmDevice {}
/// Graphical backend that monitors available DRM devices.
/// Backend to monitor available drm devices.
///
/// Provides a way to automatically initialize a `DrmDevice` for available GPUs and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for GPUs and
/// Provides a way to automatically scan for available gpus and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for gpus and
/// attached monitors.
pub struct UdevBackend<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> {
_handler: ::std::marker::PhantomData<H>,
devices: Rc<
RefCell<
HashMap<
dev_t,
(
Source<Generic<EventedRawFd>>,
Rc<RefCell<DrmDevice<SessionFdDrmDevice>>>,
),
>,
>,
>,
pub struct UdevBackend<T: UdevHandler + 'static> {
devices: HashSet<dev_t>,
monitor: MonitorSocket,
session: S,
handler: T,
logger: ::slog::Logger,
handle: LoopHandle<Data>,
}
impl<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> UdevBackend<H, S, T, Data>
{
impl<T: UdevHandler + 'static> AsRawFd for UdevBackend<T> {
fn as_raw_fd(&self) -> RawFd {
self.monitor.as_raw_fd()
}
}
impl<T: UdevHandler + 'static> UdevBackend<T> {
/// Creates a new `UdevBackend` and adds it to the given `EventLoop`'s state.
///
/// ## Arguments
/// `evlh` - An event loop to use for binding `DrmDevices`
/// `context` - An initialized udev context
/// `session` - A session used to open and close devices as they become available
/// `handler` - User-provided handler to respond to any detected changes
/// `seat` -
/// `logger` - slog Logger to be used by the backend and its `DrmDevices`.
pub fn new<L>(
handle: LoopHandle<Data>,
pub fn new<L, S: AsRef<str>>(
context: &Context,
mut session: S,
mut handler: T,
seat: S,
logger: L,
) -> Result<UdevBackend<H, S, T, Data>>
) -> UdevResult<UdevBackend<T>>
where
L: Into<Option<::slog::Logger>>,
{
let logger = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));
let seat = session.seat();
let devices = all_gpus(context, seat)
.chain_err(|| ErrorKind::FailedToScan)?
let log = ::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));
let devices = all_gpus(context, seat)?
.into_iter()
// Create devices
.flat_map(|path| {
match DrmDevice::new(
{
match session.open(
&path,
fcntl::OFlag::O_RDWR
| fcntl::OFlag::O_CLOEXEC
| fcntl::OFlag::O_NOCTTY
| fcntl::OFlag::O_NONBLOCK,
) {
Ok(fd) => SessionFdDrmDevice(fd),
Err(err) => {
warn!(
logger,
"Unable to open drm device {:?}, Error: {:?}. Skipping", path, err
);
return None;
}
}
},
logger.clone(),
) {
// Call the handler, which might add it to the runloop
Ok(mut device) => {
let devnum = device.device_id();
let fd = device.as_raw_fd();
match handler.device_added(&mut device) {
Some(drm_handler) => match drm_device_bind(&handle, device, drm_handler) {
Ok((event_source, device)) => Some((devnum, (event_source, device))),
Err((err, mut device)) => {
warn!(logger, "Failed to bind device. Error: {:?}.", err);
handler.device_removed(&mut device);
drop(device);
if let Err(err) = session.close(fd) {
warn!(
logger,
"Failed to close dropped device. Error: {:?}. Ignoring", err
);
};
None
}
},
None => {
drop(device); //drops master
if let Err(err) = session.close(fd) {
warn!(logger, "Failed to close device. Error: {:?}. Ignoring", err);
}
None
}
}
}
Err(err) => {
warn!(
logger,
"Failed to initialize device {:?}. Error: {:?}. Skipping", path, err
);
None
}
.flat_map(|path| match stat(&path) {
Ok(stat) => {
handler.device_added(stat.st_rdev, path);
Some(stat.st_rdev)
},
Err(err) => {
warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err);
None
}
}).collect::<HashMap<dev_t, _>>();
})
.collect();
let mut builder = MonitorBuilder::new(context).chain_err(|| ErrorKind::FailedToInitMonitor)?;
builder
.match_subsystem("drm")
.chain_err(|| ErrorKind::FailedToInitMonitor)?;
let monitor = builder.listen().chain_err(|| ErrorKind::FailedToInitMonitor)?;
let mut builder = MonitorBuilder::new(context)?;
builder.match_subsystem("drm")?;
let monitor = builder.listen()?;
Ok(UdevBackend {
_handler: ::std::marker::PhantomData,
devices: Rc::new(RefCell::new(devices)),
devices,
monitor,
session,
handler,
logger,
handle,
logger: log,
})
}
/// Closes the udev backend and frees all remaining open devices.
pub fn close(&mut self) {
let mut devices = self.devices.borrow_mut();
for (_, (event_source, device)) in devices.drain() {
event_source.remove();
let mut device = Rc::try_unwrap(device)
.unwrap_or_else(|_| unreachable!())
.into_inner();
self.handler.device_removed(&mut device);
let fd = device.as_raw_fd();
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(self.logger, "Failed to close device. Error: {:?}. Ignoring", err);
};
}
info!(self.logger, "All devices closed");
}
}
impl<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> Drop for UdevBackend<H, S, T, Data>
impl<T: UdevHandler + 'static> Drop for UdevBackend<T>
{
fn drop(&mut self) {
self.close();
}
}
/// `SessionObserver` linked to the `UdevBackend` it was created from.
pub struct UdevBackendObserver {
devices: Weak<
RefCell<
HashMap<
dev_t,
(
Source<Generic<EventedRawFd>>,
Rc<RefCell<DrmDevice<SessionFdDrmDevice>>>,
),
>,
>,
>,
logger: ::slog::Logger,
}
impl<
H: DrmHandler<SessionFdDrmDevice> + 'static,
S: Session + 'static,
T: UdevHandler<H> + 'static,
Data: 'static,
> AsSessionObserver<UdevBackendObserver> for UdevBackend<H, S, T, Data>
{
fn observer(&mut self) -> UdevBackendObserver {
UdevBackendObserver {
devices: Rc::downgrade(&self.devices),
logger: self.logger.clone(),
}
}
}
impl SessionObserver for UdevBackendObserver {
fn pause(&mut self, devnum: Option<(u32, u32)>) {
if let Some(devices) = self.devices.upgrade() {
for &mut (_, ref device) in devices.borrow_mut().values_mut() {
info!(self.logger, "changed successful");
device.borrow_mut().observer().pause(devnum);
}
}
}
fn activate(&mut self, devnum: Option<(u32, u32, Option<RawFd>)>) {
if let Some(devices) = self.devices.upgrade() {
for &mut (_, ref device) in devices.borrow_mut().values_mut() {
info!(self.logger, "changed successful");
device.borrow_mut().observer().activate(devnum);
}
for device in &self.devices {
self.handler.device_removed(*device);
}
}
}
@ -269,122 +102,39 @@ impl SessionObserver for UdevBackendObserver {
///
/// Allows the backend to receive kernel events and thus to drive the `UdevHandler`.
/// No runtime functionality can be provided without using this function.
pub fn udev_backend_bind<H, S, T, Data>(
mut udev: UdevBackend<H, S, T, Data>,
) -> ::std::result::Result<Source<Generic<EventedRawFd>>, IoError>
where
H: DrmHandler<SessionFdDrmDevice> + 'static,
T: UdevHandler<H> + 'static,
S: Session + 'static,
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>(
handle: &LoopHandle<Data>,
udev: UdevBackend<T>,
) -> Result<Source<Generic<EventedFd<UdevBackend<T>>>>, InsertError<Generic<EventedFd<UdevBackend<T>>>>>
{
let fd = udev.monitor.as_raw_fd();
let handle = udev.handle.clone();
let mut source = Generic::from_raw_fd(fd);
let mut source = Generic::from_fd_source(udev);
source.set_interest(Ready::readable());
handle
.insert_source(source, move |_, _| {
udev.process_events();
}).map_err(Into::into)
handle.insert_source(source, |evt, _| {
evt.source.borrow_mut().0.process_events();
})
}
impl<H, S, T, Data> UdevBackend<H, S, T, Data>
where
H: DrmHandler<SessionFdDrmDevice> + 'static,
T: UdevHandler<H> + 'static,
S: Session + 'static,
Data: 'static,
{
impl<T: UdevHandler + 'static> UdevBackend<T> {
fn process_events(&mut self) {
let events = self.monitor.clone().collect::<Vec<Event>>();
for event in events {
let monitor = self.monitor.clone();
for event in monitor {
match event.event_type() {
// New device
EventType::Add => {
info!(self.logger, "Device Added");
if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) {
let mut device = {
match DrmDevice::new(
{
let logger = self.logger.clone();
match self.session.open(
path,
fcntl::OFlag::O_RDWR
| fcntl::OFlag::O_CLOEXEC
| fcntl::OFlag::O_NOCTTY
| fcntl::OFlag::O_NONBLOCK,
) {
Ok(fd) => SessionFdDrmDevice(fd),
Err(err) => {
warn!(
logger,
"Unable to open drm device {:?}, Error: {:?}. Skipping",
path,
err
);
continue;
}
}
},
self.logger.clone(),
) {
Ok(dev) => dev,
Err(err) => {
warn!(
self.logger,
"Failed to initialize device {:?}. Error: {}. Skipping", path, err
);
continue;
}
}
};
let fd = device.as_raw_fd();
match self.handler.device_added(&mut device) {
Some(drm_handler) => match drm_device_bind(&self.handle, device, drm_handler) {
Ok(fd_event_source) => {
self.devices.borrow_mut().insert(devnum, fd_event_source);
}
Err((err, mut device)) => {
warn!(self.logger, "Failed to bind device. Error: {:?}.", err);
self.handler.device_removed(&mut device);
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(
self.logger,
"Failed to close dropped device. Error: {:?}. Ignoring", err
);
};
}
},
None => {
self.handler.device_removed(&mut device);
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(self.logger, "Failed to close unused device. Error: {:?}", err);
}
}
};
if self.devices.insert(devnum) {
self.handler.device_added(devnum, path.to_path_buf());
}
}
}
// Device removed
EventType::Remove => {
info!(self.logger, "Device Remove");
if let Some(devnum) = event.devnum() {
if let Some((fd_event_source, device)) = self.devices.borrow_mut().remove(&devnum) {
fd_event_source.remove();
let mut device = Rc::try_unwrap(device)
.unwrap_or_else(|_| unreachable!())
.into_inner();
self.handler.device_removed(&mut device);
let fd = device.as_raw_fd();
drop(device);
if let Err(err) = self.session.close(fd) {
warn!(
self.logger,
"Failed to close device {:?}. Error: {:?}. Ignoring",
event.sysname(),
err
);
};
if self.devices.remove(&devnum) {
self.handler.device_removed(devnum);
}
}
}
@ -393,9 +143,8 @@ where
info!(self.logger, "Device Changed");
if let Some(devnum) = event.devnum() {
info!(self.logger, "Devnum: {:b}", devnum);
if let Some(&(_, ref device)) = self.devices.borrow_mut().get(&devnum) {
let handler = &mut self.handler;
handler.device_changed(&mut device.borrow_mut());
if self.devices.contains(&devnum) {
self.handler.device_changed(devnum);
} else {
info!(self.logger, "changed, but device not tracked by backend");
};
@ -409,36 +158,21 @@ where
}
}
/// Handler for the `UdevBackend`, allows to open, close and update DRM devices as they change during runtime.
pub trait UdevHandler<H: DrmHandler<SessionFdDrmDevice> + 'static> {
/// Called on initialization for every known device and when a new device is detected.
///
/// Returning a `DrmHandler` will initialize the device, returning `None` will ignore the device.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn device_added(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>) -> Option<H>;
/// Handler for the `UdevBackend`, allows to open, close and update drm devices as they change during runtime.
pub trait UdevHandler {
/// Called when a new device is detected.
fn device_added(&mut self, device: dev_t, path: PathBuf);
/// Called when an open device is changed.
///
/// This usually indicates that some connectors did become available or were unplugged. The handler
/// should scan again for connected monitors and mode switch accordingly.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn device_changed(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>);
fn device_changed(&mut self, device: dev_t);
/// Called when a device was removed.
///
/// The device will not accept any operations anymore and its file descriptor will be closed once
/// this function returns, any open references/tokens to this device need to be released.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn device_removed(&mut self, device: &mut DrmDevice<SessionFdDrmDevice>);
/// Called when the udev context has encountered and error.
///
/// ## Panics
/// Panics if you try to borrow the token of the belonging `UdevBackend` using this `StateProxy`.
fn error(&mut self, error: IoError);
/// The corresponding `UdevRawFd` will never return a valid `RawFd` anymore
/// and its file descriptor will be closed once this function returns,
/// any open references/tokens to this device need to be released.
fn device_removed(&mut self, device: dev_t);
}
/// Returns the path of the primary GPU device if any
@ -489,22 +223,3 @@ pub fn all_gpus<S: AsRef<str>>(context: &Context, seat: S) -> UdevResult<Vec<Pat
}).flat_map(|device| device.devnode().map(PathBuf::from))
.collect())
}
error_chain! {
errors {
#[doc = "Failed to scan for devices"]
FailedToScan {
description("Failed to scan for devices"),
}
#[doc = "Failed to initialize udev monitor"]
FailedToInitMonitor {
description("Failed to initialize udev monitor"),
}
#[doc = "Failed to identify devices"]
FailedToIdentifyDevices {
description("Failed to identify devices"),
}
}
}