Add evdev module

This commit is contained in:
Cautreoxit 2025-11-06 20:00:00 +08:00 committed by Tate, Hongliang Tian
parent 2034055f90
commit 869e04f6bb
4 changed files with 643 additions and 2 deletions

View File

@ -4,6 +4,7 @@
#![expect(dead_code)]
use alloc::format;
use core::ops::Range;
use device_id::{DeviceId, MajorId};
@ -173,9 +174,14 @@ impl<'a> DevtmpfsName<'a> {
pub(super) fn init_in_first_process(fs_resolver: &FsResolver) -> Result<()> {
for device in collect_all() {
let name = device.devtmpfs_name().dev_name().to_string();
let devtmpfs_name = device.devtmpfs_name();
let path = if let Some(class_name) = devtmpfs_name.class_name() {
format!("{}/{}", class_name, devtmpfs_name.dev_name())
} else {
devtmpfs_name.dev_name().to_string()
};
let device = Arc::new(CharFile::new(device));
add_node(device, &name, fs_resolver)?;
add_node(device, &path, fs_resolver)?;
}
Ok(())

View File

@ -0,0 +1,282 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::sync::Weak;
use core::{
fmt::Debug,
sync::atomic::{AtomicUsize, Ordering},
time::Duration,
};
use aster_input::{
event_type_codes::{EventTypes, SynEvent},
input_dev::InputEvent,
};
use atomic_integer_wrapper::define_atomic_version_of_integer_like_type;
use ostd::{
mm::{VmReader, VmWriter},
sync::Mutex,
Pod,
};
use super::EvdevDevice;
use crate::{
events::IoEvents,
fs::{
inode_handle::FileIo,
utils::{InodeIo, IoctlCmd, StatusFlags},
},
prelude::*,
process::signal::{PollHandle, Pollable, Pollee},
syscall::ClockId,
util::ring_buffer::{RbConsumer, RbProducer, RingBuffer},
};
pub(super) const EVDEV_BUFFER_SIZE: usize = 64;
impl From<ClockId> for i32 {
fn from(clock_id: ClockId) -> Self {
clock_id as i32
}
}
define_atomic_version_of_integer_like_type!(ClockId, try_from = true, {
#[derive(Debug)]
pub(super) struct AtomicClockId(core::sync::atomic::AtomicI32);
});
// Compatible with Linux's event format.
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod)]
pub struct EvdevEvent {
pub sec: u64,
pub usec: u64,
pub type_: u16,
pub code: u16,
pub value: i32,
}
impl EvdevEvent {
pub fn from_event_and_time(event: &InputEvent, time: Duration) -> Self {
let (type_, code, value) = event.to_raw();
Self {
sec: time.as_secs(),
usec: time.subsec_micros() as u64,
type_,
code,
value,
}
}
}
/// An opened file from an evdev device (`EvdevDevice`).
pub struct EvdevFile {
/// Consumer for reading events.
consumer: Mutex<RbConsumer<EvdevEvent>>,
/// Clock ID for this opened evdev file.
clock_id: AtomicClockId,
/// Number of events available.
event_count: AtomicUsize,
/// Number of complete event packets available (ended with SYN_REPORT).
packet_count: AtomicUsize,
/// Pollee for event notification.
pollee: Pollee,
/// Weak reference to the evdev device that owns this evdev file.
evdev: Weak<EvdevDevice>,
}
impl EvdevFile {
pub(super) fn new(
buffer_size: usize,
evdev: Weak<EvdevDevice>,
) -> (Self, RbProducer<EvdevEvent>) {
let (producer, consumer) = RingBuffer::new(buffer_size).split();
let evdev_file = Self {
consumer: Mutex::new(consumer),
// Default to be CLOCK_MONOTONIC
clock_id: AtomicClockId::new(ClockId::CLOCK_MONOTONIC),
event_count: AtomicUsize::new(0),
packet_count: AtomicUsize::new(0),
pollee: Pollee::new(),
evdev,
};
(evdev_file, producer)
}
/// Returns the clock ID for this opened evdev file.
pub(super) fn clock_id(&self) -> ClockId {
self.clock_id.load(Ordering::Relaxed)
}
/// Checks if the EvdevEvent is a `SYN_REPORT` event.
fn is_syn_report_event(&self, event: &EvdevEvent) -> bool {
event.type_ == EventTypes::SYN.as_index() && event.code == SynEvent::Report as u16
}
/// Checks if the EvdevEvent is a `SYN_DROPPED` event.
fn is_syn_dropped_event(&self, event: &EvdevEvent) -> bool {
event.type_ == EventTypes::SYN.as_index() && event.code == SynEvent::Dropped as u16
}
/// Checks if buffer has complete event packets.
pub fn has_complete_packets(&self) -> bool {
self.packet_count.load(Ordering::Relaxed) > 0
}
/// Increments event count.
pub fn increment_event_count(&self) {
self.event_count.fetch_add(1, Ordering::Relaxed);
self.pollee.notify(IoEvents::IN);
}
/// Decrements event count.
pub fn decrement_event_count(&self) {
self.event_count.fetch_sub(1, Ordering::Relaxed);
if self.event_count.load(Ordering::Relaxed) == 0 {
self.pollee.invalidate();
}
}
/// Increments packet count.
pub fn increment_packet_count(&self) {
self.packet_count.fetch_add(1, Ordering::Relaxed);
self.pollee.notify(IoEvents::IN);
}
/// Decrements packet count.
pub fn decrement_packet_count(&self) {
self.packet_count.fetch_sub(1, Ordering::Relaxed);
if self.packet_count.load(Ordering::Relaxed) == 0 {
self.pollee.invalidate();
}
}
/// Processes events and writes them to the writer.
/// Returns the total number of bytes written, or EAGAIN if no events available.
fn process_events(&self, max_events: usize, writer: &mut VmWriter) -> Result<usize> {
const EVENT_SIZE: usize = core::mem::size_of::<EvdevEvent>();
let mut consumer = self.consumer.lock();
let mut event_count = 0;
for _ in 0..max_events {
let Some(event) = consumer.pop() else {
break;
};
// Check if this is a SYN_REPORT or SYN_DROPPED event.
let is_syn_report = self.is_syn_report_event(&event);
let is_syn_dropped = self.is_syn_dropped_event(&event);
// Write event directly to writer.
writer.write_val(&event)?;
event_count += 1;
self.decrement_event_count();
if is_syn_report || is_syn_dropped {
self.decrement_packet_count();
}
}
if event_count == 0 {
return Err(Error::with_message(Errno::EAGAIN, "No events available"));
}
Ok(event_count * EVENT_SIZE)
}
}
impl Pollable for EvdevFile {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.pollee.poll_with(mask, poller, || {
let has_complete_packets = self.has_complete_packets();
let mut events = IoEvents::empty();
if has_complete_packets && mask.contains(IoEvents::IN) {
events |= IoEvents::IN;
}
events
})
}
}
impl InodeIo for EvdevFile {
fn read_at(
&self,
_offset: usize,
writer: &mut VmWriter,
status_flags: StatusFlags,
) -> Result<usize> {
let requested_bytes = writer.avail();
let max_events = requested_bytes / core::mem::size_of::<EvdevEvent>();
if max_events == 0 {
return Ok(0);
}
let is_nonblocking = status_flags.contains(StatusFlags::O_NONBLOCK);
match self.process_events(max_events, writer) {
Ok(bytes) => Ok(bytes),
Err(e) if e.error() == Errno::EAGAIN => {
if is_nonblocking {
Err(e)
} else {
self.wait_events(IoEvents::IN, None, || {
self.process_events(max_events, writer)
})
}
}
Err(e) => Err(e),
}
}
fn write_at(
&self,
_offset: usize,
_reader: &mut VmReader,
_status_flags: StatusFlags,
) -> Result<usize> {
// TODO: support write operation on evdev devices.
Err(Error::with_message(
Errno::ENOSYS,
"WRITE operation not supported on evdev devices",
))
}
}
impl FileIo for EvdevFile {
fn check_seekable(&self) -> Result<()> {
Ok(())
}
fn is_offset_aware(&self) -> bool {
false
}
fn ioctl(&self, _cmd: IoctlCmd, _arg: usize) -> Result<i32> {
// TODO: support ioctl operation on evdev devices.
Err(Error::with_message(
Errno::EINVAL,
"IOCTL operation not supported on evdev devices",
))
}
}
impl Debug for EvdevFile {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("EvdevFile")
.field("event_count", &self.event_count.load(Ordering::Relaxed))
.field("clock_id", &self.clock_id())
.finish()
}
}
impl Drop for EvdevFile {
fn drop(&mut self) {
if let Some(evdev) = self.evdev.upgrade() {
evdev.detach_closed_files();
}
}
}

View File

@ -0,0 +1,351 @@
// 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;
use alloc::{
format,
string::String,
sync::{Arc, Weak},
vec::Vec,
};
use core::{
fmt::Debug,
sync::atomic::{AtomicU32, Ordering},
time::Duration,
};
use aster_input::{
event_type_codes::{EventTypes, SynEvent},
input_dev::{InputDevice, InputEvent},
input_handler::{ConnectError, InputHandler, InputHandlerClass},
};
use aster_time::read_monotonic_time;
use device_id::{DeviceId, MajorId, MinorId};
use file::{EvdevEvent, EvdevFile, EVDEV_BUFFER_SIZE};
use ostd::sync::SpinLock;
use spin::Once;
use super::char::{acquire_major, register, unregister, CharDevice, MajorIdOwner};
use crate::{
device::char::DevtmpfsName,
fs::inode_handle::FileIo,
prelude::*,
syscall::ClockId,
time::clocks::{
BootTimeClock, MonotonicClock, MonotonicCoarseClock, MonotonicRawClock, RealTimeClock,
RealTimeCoarseClock,
},
util::ring_buffer::RbProducer,
};
/// 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.
static EVDEV_DEVICES: SpinLock<BTreeMap<MinorId, Arc<EvdevDevice>>> =
SpinLock::new(BTreeMap::new());
pub struct EvdevDevice {
/// 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.
opened_files: SpinLock<Vec<(Weak<EvdevFile>, RbProducer<EvdevEvent>)>>,
/// Device node name (e.g., "event0").
node_name: String,
/// Device ID.
id: DeviceId,
}
impl Debug for EvdevDevice {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let opened_count = self
.opened_files
.lock()
.iter()
.filter(|(file, _)| file.strong_count() > 0)
.count();
f.debug_struct("EvdevDevice")
.field("minor", &self.id.minor())
.field("device_name", &self.device.name())
.field("opened_files", &opened_count)
.finish()
}
}
impl EvdevDevice {
pub(self) fn new(minor: u32, device: Arc<dyn InputDevice>) -> Self {
let node_name = format!("event{}", minor);
let major = EVDEV_MAJOR.get().unwrap().get();
let minor_id = MinorId::new(minor);
Self {
device,
opened_files: SpinLock::new(Vec::new()),
node_name,
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.
fn attach_file(&self, file: Weak<EvdevFile>, producer: RbProducer<EvdevEvent>) {
let mut opened_files = self.opened_files.disable_irq().lock();
opened_files.push((file, producer));
}
/// Removes closed evdev files from this evdev device.
pub(self) fn detach_closed_files(&self) {
let mut opened_files = self.opened_files.disable_irq().lock();
opened_files.retain(|(file, _)| file.strong_count() > 0);
}
/// Distributes events to all opened evdev files.
pub(self) fn pass_events(&self, events: &[InputEvent]) {
let mut opened_files = self.opened_files.lock();
// Send events to all opened evdev files using their producers.
for (file_weak, producer) in opened_files.iter_mut() {
let Some(file) = file_weak.upgrade() else {
continue;
};
for event in events {
// Get time according to the opened evdev file's clock type.
let time = self.get_time_for_file(&file);
let timed_event = EvdevEvent::from_event_and_time(event, time);
// Try to push event to the buffer.
if let Some(()) = producer.push(timed_event) {
file.increment_event_count();
// Check if this is a SYN_REPORT event
if self.is_syn_report_event(event) {
file.increment_packet_count();
}
} else {
// TODO: In Linux's implementation, when the buffer is full, evdev will pop the two
// oldest events to ensure that both the SYN_DROPPED event and the current event can
// be pushed into the buffer. However, since we are using `RingBuffer`, evdev cannot
// pop events. Considering the convenience of lock-free programming with `RingBuffer`
// and the rarity of this error condition, we choose to retain the use of `RingBuffer`
// and instead attempt to push both the SYN_DROPPED event and the current event.
let dropped_event = EvdevEvent::from_event_and_time(
&InputEvent::from_sync_event(SynEvent::Dropped),
time,
);
// Try to push SYN_DROPPED event.
if let Some(()) = producer.push(dropped_event) {
file.increment_event_count();
file.increment_packet_count();
// Try to push the original event.
if let Some(()) = producer.push(timed_event) {
file.increment_event_count();
// Check if this is a SYN_REPORT event.
if self.is_syn_report_event(event) {
file.increment_packet_count();
}
}
} else {
// Failed to push SYN_DROPPED event, emit a warning.
log::warn!(
"Failed to push SYN_DROPPED event to evdev file buffer (buffer full)"
);
}
}
}
}
}
/// Checks if the event is a SYN_REPORT event (marks end of packet).
fn is_syn_report_event(&self, event: &InputEvent) -> bool {
let (type_, code, _) = event.to_raw();
type_ == EventTypes::SYN.as_index() && code == SynEvent::Report as u16
}
/// Gets time according to the opened evdev file's clock ID.
fn get_time_for_file(&self, file: &EvdevFile) -> Duration {
let clock_id = file.clock_id();
match clock_id {
ClockId::CLOCK_REALTIME => RealTimeClock::get().read_time(),
ClockId::CLOCK_MONOTONIC => MonotonicClock::get().read_time(),
ClockId::CLOCK_MONOTONIC_RAW => MonotonicRawClock::get().read_time(),
ClockId::CLOCK_REALTIME_COARSE => RealTimeCoarseClock::get().read_time(),
ClockId::CLOCK_MONOTONIC_COARSE => MonotonicCoarseClock::get().read_time(),
ClockId::CLOCK_BOOTTIME => BootTimeClock::get().read_time(),
// For process/thread clocks, fallback to monotonic time.
ClockId::CLOCK_PROCESS_CPUTIME_ID | ClockId::CLOCK_THREAD_CPUTIME_ID => {
read_monotonic_time()
}
}
}
/// Creates a new opened evdev file for this evdev device.
pub(self) fn create_file(self: &Arc<Self>, buffer_size: usize) -> Result<Arc<EvdevFile>> {
// Create the evdev file and get the producer.
let (file, producer) = EvdevFile::new(buffer_size, Arc::downgrade(self));
let file = Arc::new(file);
let file_weak = Arc::downgrade(&file);
// Attach the opened evdev file to this device.
self.attach_file(file_weak, producer);
Ok(file)
}
}
impl CharDevice for EvdevDevice {
fn devtmpfs_name(&self) -> DevtmpfsName<'_> {
DevtmpfsName::new(&self.node_name, Some("input"))
}
fn id(&self) -> DeviceId {
self.id
}
fn open(&self) -> Result<Arc<dyn FileIo>> {
// Get the Arc<EvdevDevice> from the registry.
let devices = EVDEV_DEVICES.lock();
let Some(evdev) = devices.get(&self.id.minor()) else {
return Err(Error::with_message(
Errno::ENODEV,
"evdev device not found in registry",
));
};
// Create a new opened evdev file for this evdev device.
let file = evdev.create_file(EVDEV_BUFFER_SIZE)?;
Ok(file as Arc<dyn FileIo>)
}
}
/// Evdev handler class that creates device nodes for input devices.
#[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);
// Create evdev device.
let evdev = Arc::new(EvdevDevice::new(minor, dev.clone()));
// Register the device with the char device subsystem.
register(evdev.clone()).map_err(|_| ConnectError::InternalError)?;
// Add to our registry for lookup during disconnect.
EVDEV_DEVICES.lock().insert(minor_id, evdev.clone());
// Create handler instance for this device.
let handler = EvdevHandler::new(evdev);
Ok(Arc::new(handler))
}
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;
}
}
if let Some(minor) = found_minor {
let evdev = devices.remove(&minor).unwrap();
let device_id = evdev.id();
// Unregister from the char device subsystem.
if let Err(e) = unregister(device_id) {
log::warn!(
"Failed to unregister evdev device '{}' (minor: {}): {:?}",
device_name,
minor.get(),
e
);
}
// 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()
);
} else {
log::warn!(
"Attempted to disconnect device '{}' but it did not connect to evdev",
device_name
);
}
}
}
/// Evdev handler instance for a specific device.
#[derive(Debug)]
pub struct EvdevHandler {
evdev: Arc<EvdevDevice>,
}
impl EvdevHandler {
fn new(evdev: Arc<EvdevDevice>) -> Self {
Self { evdev }
}
}
impl InputHandler for EvdevHandler {
fn handle_events(&self, events: &[InputEvent]) {
self.evdev.pass_events(events);
}
}
static EVDEV_MAJOR: Once<MajorIdOwner> = Once::new();
pub(super) fn init_in_first_kthread() {
EVDEV_MAJOR.call_once(|| acquire_major(MajorId::new(EVDEV_MAJOR_ID)).unwrap());
static REGISTERED_EVDDEV_CLASS: spin::Once<
aster_input::input_handler::RegisteredInputHandlerClass,
> = spin::Once::new();
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");
}

View File

@ -2,6 +2,7 @@
mod char;
mod disk;
mod evdev;
mod mem;
pub mod misc;
mod pty;
@ -28,6 +29,7 @@ pub fn init_in_first_kthread() {
disk::init_in_first_kthread();
mem::init_in_first_kthread();
misc::init_in_first_kthread();
evdev::init_in_first_kthread();
}
/// Init the device node in fs, must be called after mounting rootfs.