diff --git a/kernel/src/device/char.rs b/kernel/src/device/char.rs index a4968b24b..cdd28a13d 100644 --- a/kernel/src/device/char.rs +++ b/kernel/src/device/char.rs @@ -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(()) diff --git a/kernel/src/device/evdev/file.rs b/kernel/src/device/evdev/file.rs new file mode 100644 index 000000000..af5b0f448 --- /dev/null +++ b/kernel/src/device/evdev/file.rs @@ -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 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>, + /// 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, +} + +impl EvdevFile { + pub(super) fn new( + buffer_size: usize, + evdev: Weak, + ) -> (Self, RbProducer) { + 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 { + const EVENT_SIZE: usize = core::mem::size_of::(); + + 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 { + let requested_bytes = writer.avail(); + let max_events = requested_bytes / core::mem::size_of::(); + + 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 { + // 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 { + // 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(); + } + } +} diff --git a/kernel/src/device/evdev/mod.rs b/kernel/src/device/evdev/mod.rs new file mode 100644 index 000000000..4941d793c --- /dev/null +++ b/kernel/src/device/evdev/mod.rs @@ -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: + +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>> = + SpinLock::new(BTreeMap::new()); + +pub struct EvdevDevice { + /// Input device associated with this evdev. + device: Arc, + /// 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, RbProducer)>>, + /// 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) -> 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) -> bool { + Arc::ptr_eq(&self.device, input_device) + } + + /// Adds an opened evdev file to this evdev device. + fn attach_file(&self, file: Weak, producer: RbProducer) { + 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, buffer_size: usize) -> Result> { + // 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> { + // Get the Arc 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) + } +} + +/// 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, + ) -> core::result::Result, 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) { + 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, +} + +impl EvdevHandler { + fn new(evdev: Arc) -> Self { + Self { evdev } + } +} + +impl InputHandler for EvdevHandler { + fn handle_events(&self, events: &[InputEvent]) { + self.evdev.pass_events(events); + } +} + +static EVDEV_MAJOR: Once = 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"); +} diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index 4ed7ea546..d6c0bc897 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -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.