2025-11-06 12:00:00 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
|
|
//! Event device (evdev) support.
|
|
|
|
|
//!
|
|
|
|
|
//! Character device with major number 13. The minor numbers are dynamically allocated.
|
|
|
|
|
//! Devices appear as `/dev/input/eventX` where X is the minor number.
|
|
|
|
|
//!
|
|
|
|
|
//! Reference: <https://elixir.bootlin.com/linux/v6.17/source/include/uapi/linux/major.h>
|
|
|
|
|
|
|
|
|
|
mod file;
|
|
|
|
|
|
2025-11-25 09:04:24 +00:00
|
|
|
use alloc::{format, string::String, sync::Arc, vec::Vec};
|
2025-11-06 12:00:00 +00:00
|
|
|
use core::{
|
|
|
|
|
fmt::Debug,
|
|
|
|
|
sync::atomic::{AtomicU32, Ordering},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use aster_input::{
|
2025-11-25 08:38:07 +00:00
|
|
|
event_type_codes::SynEvent,
|
2025-11-06 12:00:00 +00:00
|
|
|
input_dev::{InputDevice, InputEvent},
|
|
|
|
|
input_handler::{ConnectError, InputHandler, InputHandlerClass},
|
|
|
|
|
};
|
|
|
|
|
use device_id::{DeviceId, MajorId, MinorId};
|
2025-11-25 08:38:07 +00:00
|
|
|
use file::{
|
2025-12-08 12:53:18 +00:00
|
|
|
EVDEV_BUFFER_SIZE, EvdevEvent, EvdevFile, EvdevFileInner, is_syn_dropped_event,
|
|
|
|
|
is_syn_report_event,
|
2025-11-25 08:38:07 +00:00
|
|
|
};
|
2025-11-06 12:00:00 +00:00
|
|
|
use ostd::sync::SpinLock;
|
|
|
|
|
use spin::Once;
|
|
|
|
|
|
2025-12-08 12:53:18 +00:00
|
|
|
use super::registry::char::{MajorIdOwner, acquire_major, register, unregister};
|
2025-11-06 12:00:00 +00:00
|
|
|
use crate::{
|
2025-11-23 15:18:59 +00:00
|
|
|
fs::{
|
|
|
|
|
device::{Device, DeviceType},
|
|
|
|
|
inode_handle::FileIo,
|
|
|
|
|
},
|
|
|
|
|
prelude::*,
|
|
|
|
|
util::ring_buffer::RbProducer,
|
2025-11-06 12:00:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Major device number for evdev devices.
|
|
|
|
|
const EVDEV_MAJOR_ID: u16 = 13;
|
|
|
|
|
|
|
|
|
|
/// Global minor number allocator for evdev devices.
|
|
|
|
|
static EVDEV_MINOR_COUNTER: AtomicU32 = AtomicU32::new(0);
|
|
|
|
|
|
|
|
|
|
/// Global registry of evdev devices.
|
2025-11-25 09:08:55 +00:00
|
|
|
static EVDEV_DEVICES: Mutex<BTreeMap<MinorId, Arc<EvdevDevice>>> = Mutex::new(BTreeMap::new());
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
struct EvdevDevice {
|
2025-11-06 12:00:00 +00:00
|
|
|
/// Input device associated with this evdev.
|
|
|
|
|
device: Arc<dyn InputDevice>,
|
|
|
|
|
/// List of opened evdev files with their producers.
|
|
|
|
|
///
|
|
|
|
|
/// # Deadlock Prevention
|
|
|
|
|
///
|
|
|
|
|
/// This lock is acquired in both the task and interrupt contexts.
|
|
|
|
|
/// We must make sure that this lock is taken with the local IRQs disabled.
|
|
|
|
|
/// Otherwise, we would be vulnerable to deadlock.
|
2025-11-25 09:04:24 +00:00
|
|
|
opened_files: SpinLock<Vec<(Arc<EvdevFileInner>, RbProducer<EvdevEvent>)>>,
|
2025-11-06 12:00:00 +00:00
|
|
|
/// Device ID.
|
|
|
|
|
id: DeviceId,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Debug for EvdevDevice {
|
|
|
|
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
2025-11-25 09:04:24 +00:00
|
|
|
let device_name = self.device.name();
|
|
|
|
|
let opened_count = self.opened_files.disable_irq().lock().len();
|
|
|
|
|
let id_minor = self.id.minor();
|
2025-11-06 12:00:00 +00:00
|
|
|
f.debug_struct("EvdevDevice")
|
2025-11-25 09:04:24 +00:00
|
|
|
.field("device_name", &device_name)
|
|
|
|
|
.field("opened_count", &opened_count)
|
|
|
|
|
.field("id_minor", &id_minor)
|
|
|
|
|
.finish_non_exhaustive()
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EvdevDevice {
|
|
|
|
|
pub(self) fn new(minor: u32, device: Arc<dyn InputDevice>) -> Self {
|
2025-11-25 09:18:31 +00:00
|
|
|
let major = MajorId::new(EVDEV_MAJOR_ID);
|
2025-11-06 12:00:00 +00:00
|
|
|
let minor_id = MinorId::new(minor);
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
device,
|
|
|
|
|
opened_files: SpinLock::new(Vec::new()),
|
|
|
|
|
id: DeviceId::new(major, minor_id),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Checks if this evdev device is associated with the given input device.
|
|
|
|
|
pub(self) fn matches_input_device(&self, input_device: &Arc<dyn InputDevice>) -> bool {
|
|
|
|
|
Arc::ptr_eq(&self.device, input_device)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds an opened evdev file to this evdev device.
|
2025-11-25 09:04:24 +00:00
|
|
|
fn attach_file(&self, file: Arc<EvdevFileInner>, producer: RbProducer<EvdevEvent>) {
|
2025-11-06 12:00:00 +00:00
|
|
|
let mut opened_files = self.opened_files.disable_irq().lock();
|
|
|
|
|
opened_files.push((file, producer));
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 09:04:24 +00:00
|
|
|
/// Removes the closed evdev file from this evdev device.
|
|
|
|
|
pub(self) fn detach_closed_file(&self, file: &Arc<EvdevFileInner>) {
|
2025-11-06 12:00:00 +00:00
|
|
|
let mut opened_files = self.opened_files.disable_irq().lock();
|
2025-11-25 09:04:24 +00:00
|
|
|
let pos = opened_files
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|(f, _)| Arc::ptr_eq(f, file))
|
|
|
|
|
.unwrap();
|
|
|
|
|
opened_files.swap_remove(pos);
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-07 07:02:46 +00:00
|
|
|
pub(self) fn with_producer_locked<F>(&self, file: &Arc<EvdevFileInner>, f: F)
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&mut RbProducer<EvdevEvent>),
|
|
|
|
|
{
|
|
|
|
|
let mut opened_files = self.opened_files.disable_irq().lock();
|
|
|
|
|
let pos = opened_files
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|(f, _)| Arc::ptr_eq(f, file))
|
|
|
|
|
.unwrap();
|
|
|
|
|
f(&mut opened_files[pos].1)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 12:00:00 +00:00
|
|
|
/// Distributes events to all opened evdev files.
|
2025-11-25 09:18:31 +00:00
|
|
|
fn pass_events(&self, events: &[InputEvent]) {
|
2025-11-25 08:38:07 +00:00
|
|
|
// No need to disable IRQs because this method can only be called in the interrupt context.
|
2025-11-06 12:00:00 +00:00
|
|
|
let mut opened_files = self.opened_files.lock();
|
|
|
|
|
|
|
|
|
|
// Send events to all opened evdev files using their producers.
|
2025-11-25 09:04:24 +00:00
|
|
|
for (file, producer) in opened_files.iter_mut() {
|
2025-11-06 12:00:00 +00:00
|
|
|
for event in events {
|
2025-11-24 15:52:36 +00:00
|
|
|
// Read the current time according to the opened evdev file's clock type.
|
|
|
|
|
let time = file.read_clock();
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 08:38:07 +00:00
|
|
|
// When the buffer is full and a new event arrives, Linux drops all unconsumed
|
|
|
|
|
// events and queues a `SYN_DROPPED` event with the new one [1].
|
|
|
|
|
//
|
|
|
|
|
// We follow the Linux implementation to try to drop unconsumed events. However, if
|
|
|
|
|
// there is a concurrent consumer, `try_clear_with_producer_locked` may not be able
|
|
|
|
|
// to make progress because we're in the interrupt context. So we will always push
|
|
|
|
|
// a `SYN_DROPPED` event when the buffer is almost full to indicate that events are
|
|
|
|
|
// about to be dropped. This should match the correct semantics of the
|
|
|
|
|
// `SYN_DROPPED` event [2].
|
|
|
|
|
//
|
|
|
|
|
// [1]: https://elixir.bootlin.com/linux/v6.17.9/source/drivers/input/evdev.c#L221-L225
|
|
|
|
|
// [2]: https://elixir.bootlin.com/linux/v6.17.9/source/Documentation/input/event-codes.rst#L113-L118
|
|
|
|
|
if producer.free_len() <= 1 {
|
|
|
|
|
file.try_clear_with_producer_locked();
|
|
|
|
|
|
2025-11-06 12:00:00 +00:00
|
|
|
let dropped_event = EvdevEvent::from_event_and_time(
|
|
|
|
|
&InputEvent::from_sync_event(SynEvent::Dropped),
|
|
|
|
|
time,
|
|
|
|
|
);
|
2025-11-25 08:38:07 +00:00
|
|
|
// This fails if the buffer is full and `try_clear_with_producer_locked` cannot
|
|
|
|
|
// make progress. A `SYN_DROPPED` event must have already been pushed.
|
|
|
|
|
if producer.push(dropped_event).is_some() {
|
2025-11-06 12:00:00 +00:00
|
|
|
file.increment_packet_count();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-25 08:38:07 +00:00
|
|
|
|
|
|
|
|
let timed_event = EvdevEvent::from_event_and_time(event, time);
|
|
|
|
|
if is_syn_dropped_event(&timed_event) {
|
|
|
|
|
// This is a bug in the device driver. We ignore the event to prevent bugs in
|
|
|
|
|
// the device drivers from breaking the invariant of the packet count.
|
|
|
|
|
log::warn!(
|
|
|
|
|
"Received dropped event from evdev device '{}'",
|
|
|
|
|
self.device.name()
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if producer.push(timed_event).is_some() && is_syn_report_event(&timed_event) {
|
|
|
|
|
file.increment_packet_count();
|
|
|
|
|
}
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates a new opened evdev file for this evdev device.
|
2025-11-24 03:50:10 +00:00
|
|
|
fn create_file(self: &Arc<Self>, buffer_size: usize) -> Result<Box<EvdevFile>> {
|
2025-11-06 12:00:00 +00:00
|
|
|
// Create the evdev file and get the producer.
|
|
|
|
|
let (file, producer) = EvdevFile::new(buffer_size, Arc::downgrade(self));
|
|
|
|
|
|
|
|
|
|
// Attach the opened evdev file to this device.
|
2025-11-25 09:04:24 +00:00
|
|
|
self.attach_file(file.inner().clone(), producer);
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-24 03:50:10 +00:00
|
|
|
Ok(Box::new(file))
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
impl InputHandler for EvdevDevice {
|
|
|
|
|
fn handle_events(&self, events: &[InputEvent]) {
|
|
|
|
|
self.pass_events(events);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-23 15:18:59 +00:00
|
|
|
impl Device for EvdevDevice {
|
|
|
|
|
fn type_(&self) -> DeviceType {
|
|
|
|
|
DeviceType::Char
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn id(&self) -> DeviceId {
|
|
|
|
|
self.id
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-23 15:18:59 +00:00
|
|
|
fn devtmpfs_path(&self) -> Option<String> {
|
|
|
|
|
Some(format!("input/event{}", self.id.minor().get()))
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-24 03:50:10 +00:00
|
|
|
fn open(&self) -> Result<Box<dyn FileIo>> {
|
2025-11-24 12:42:57 +00:00
|
|
|
// Get the device from the registry.
|
2025-11-06 12:00:00 +00:00
|
|
|
let devices = EVDEV_DEVICES.lock();
|
|
|
|
|
let Some(evdev) = devices.get(&self.id.minor()) else {
|
2025-11-24 12:42:57 +00:00
|
|
|
return_errno_with_message!(
|
2025-11-06 12:00:00 +00:00
|
|
|
Errno::ENODEV,
|
2025-11-24 12:42:57 +00:00
|
|
|
"the evdev device does not exist in the registry"
|
|
|
|
|
);
|
2025-11-06 12:00:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create a new opened evdev file for this evdev device.
|
|
|
|
|
let file = evdev.create_file(EVDEV_BUFFER_SIZE)?;
|
2025-11-24 03:50:10 +00:00
|
|
|
Ok(file as Box<dyn FileIo>)
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
/// The evdev handler class that creates device nodes for input devices.
|
2025-11-06 12:00:00 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct EvdevHandlerClass;
|
|
|
|
|
|
|
|
|
|
impl InputHandlerClass for EvdevHandlerClass {
|
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
|
"evdev"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn connect(
|
|
|
|
|
&self,
|
|
|
|
|
dev: Arc<dyn InputDevice>,
|
|
|
|
|
) -> core::result::Result<Arc<dyn InputHandler>, ConnectError> {
|
|
|
|
|
// Allocate a new minor number.
|
|
|
|
|
let minor = EVDEV_MINOR_COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
let minor_id = MinorId::new(minor);
|
|
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
// Create an evdev device.
|
2025-11-06 12:00:00 +00:00
|
|
|
let evdev = Arc::new(EvdevDevice::new(minor, dev.clone()));
|
|
|
|
|
|
|
|
|
|
// Register the device with the char device subsystem.
|
|
|
|
|
register(evdev.clone()).map_err(|_| ConnectError::InternalError)?;
|
|
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
// Add to our registry for looking up during disconnection.
|
2025-11-06 12:00:00 +00:00
|
|
|
EVDEV_DEVICES.lock().insert(minor_id, evdev.clone());
|
|
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
// Return the device as a handler instance.
|
|
|
|
|
Ok(evdev as _)
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn disconnect(&self, dev: &Arc<dyn InputDevice>) {
|
|
|
|
|
let mut devices = EVDEV_DEVICES.lock();
|
|
|
|
|
let device_name = dev.name();
|
|
|
|
|
|
|
|
|
|
// Find the device by checking if it matches the input device.
|
|
|
|
|
let mut found_minor = None;
|
|
|
|
|
for (minor, evdev) in devices.iter() {
|
|
|
|
|
if evdev.matches_input_device(dev) {
|
|
|
|
|
found_minor = Some(*minor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
let Some(minor) = found_minor else {
|
2025-11-06 12:00:00 +00:00
|
|
|
log::warn!(
|
|
|
|
|
"Attempted to disconnect device '{}' but it did not connect to evdev",
|
|
|
|
|
device_name
|
|
|
|
|
);
|
2025-11-25 09:18:31 +00:00
|
|
|
return;
|
|
|
|
|
};
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
let evdev = devices.remove(&minor).unwrap();
|
|
|
|
|
let device_id = evdev.id();
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
// Unregister from the char device subsystem.
|
|
|
|
|
if let Err(err) = unregister(device_id) {
|
|
|
|
|
log::warn!(
|
|
|
|
|
"Failed to unregister evdev device '{}' (minor: {}): {:?}",
|
|
|
|
|
device_name,
|
|
|
|
|
minor.get(),
|
|
|
|
|
err
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
// TODO: Implement device node deletion when the functionality is available.
|
|
|
|
|
log::info!(
|
|
|
|
|
"Disconnected evdev device '{}' (minor: {}), device node /dev/input/event{} still exists",
|
|
|
|
|
device_name,
|
|
|
|
|
minor.get(),
|
|
|
|
|
minor.get()
|
|
|
|
|
);
|
2025-11-06 12:00:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) fn init_in_first_kthread() {
|
2025-11-25 09:18:31 +00:00
|
|
|
use aster_input::input_handler::RegisteredInputHandlerClass;
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
static EVDEV_MAJOR: Once<MajorIdOwner> = Once::new();
|
|
|
|
|
EVDEV_MAJOR.call_once(|| acquire_major(MajorId::new(EVDEV_MAJOR_ID)).unwrap());
|
2025-11-06 12:00:00 +00:00
|
|
|
|
2025-11-25 09:18:31 +00:00
|
|
|
static REGISTERED_EVDDEV_CLASS: Once<RegisteredInputHandlerClass> = Once::new();
|
2025-11-06 12:00:00 +00:00
|
|
|
let handler_class = Arc::new(EvdevHandlerClass);
|
|
|
|
|
let handle = aster_input::register_handler_class(handler_class);
|
|
|
|
|
REGISTERED_EVDDEV_CLASS.call_once(|| handle);
|
|
|
|
|
|
|
|
|
|
log::info!("Evdev device support initialized");
|
|
|
|
|
}
|