Add some ioctls for evdev
This commit is contained in:
parent
a176e4c3c6
commit
4f71f4bbe6
|
|
@ -151,6 +151,11 @@ impl RelCodeSet {
|
|||
let index = rel_code as usize;
|
||||
self.0.get(index).map(|b| *b).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the bitmap as a byte slice.
|
||||
pub fn as_raw_slice(&self) -> &[u8] {
|
||||
self.0.as_raw_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of [`KeyCode`] represented as a bitmap.
|
||||
|
|
@ -196,6 +201,11 @@ impl KeyCodeSet {
|
|||
assert!(range.is_empty() || range.end <= KEY_COUNT);
|
||||
range.into_iter().any(|i| *self.0.get(i).unwrap())
|
||||
}
|
||||
|
||||
/// Returns the bitmap as a byte slice.
|
||||
pub fn as_raw_slice(&self) -> &[u8] {
|
||||
self.0.as_raw_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// Common keyboard and mouse keys.
|
||||
|
|
|
|||
|
|
@ -225,6 +225,21 @@ impl InputCapability {
|
|||
pub fn clear_supported_relative_axis(&mut self, rel_code: RelCode) {
|
||||
self.supported_relative_axes.clear(rel_code);
|
||||
}
|
||||
|
||||
/// Returns the supported event types as a bitmap.
|
||||
pub fn event_types_bits(&self) -> u32 {
|
||||
self.supported_event_types.bits()
|
||||
}
|
||||
|
||||
/// Returns the supported key code bitmap as bytes.
|
||||
pub fn supported_keys_bitmap(&self) -> &[u8] {
|
||||
self.supported_keys.as_raw_slice()
|
||||
}
|
||||
|
||||
/// Returns the supported relative axes bitmap as bytes.
|
||||
pub fn supported_relative_axes_bitmap(&self) -> &[u8] {
|
||||
self.supported_relative_axes.as_raw_slice()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InputDevice: Send + Sync + Any + Debug {
|
||||
|
|
|
|||
|
|
@ -27,14 +27,36 @@ use crate::{
|
|||
},
|
||||
prelude::*,
|
||||
process::signal::{PollHandle, Pollable, Pollee},
|
||||
syscall::ClockId,
|
||||
util::{
|
||||
ioctl::RawIoctl,
|
||||
ioctl::{dispatch_ioctl, RawIoctl},
|
||||
ring_buffer::{RbConsumer, RbProducer, RingBuffer},
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) const EVDEV_BUFFER_SIZE: usize = 64;
|
||||
|
||||
mod ioctl_defs {
|
||||
use aster_input::input_dev::InputId;
|
||||
|
||||
use crate::util::ioctl::{ioc, InData, IoctlEnum, OutData};
|
||||
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/linux/input.h>
|
||||
|
||||
pub(super) type GetDriverVer = ioc!(EVIOCGVERSION, b'E', 0x01, OutData<i32>);
|
||||
pub(super) type GetInputId = ioc!(EVIOCGID, b'E', 0x02, OutData<InputId>);
|
||||
pub(super) type GetDeviceName = ioc!(EVIOCGNAME, b'E', 0x06, OutData<[u8]>);
|
||||
pub(super) type GetDevicePhys = ioc!(EVIOCGPHYS, b'E', 0x07, OutData<[u8]>);
|
||||
pub(super) type GetDeviceUniq = ioc!(EVIOCGUNIQ, b'E', 0x08, OutData<[u8]>);
|
||||
pub(super) type GetKeyState = ioc!(EVIOCGKEY, b'E', 0x18, OutData<[u8]>);
|
||||
pub(super) type GetLedState = ioc!(EVIOCGLED, b'E', 0x19, OutData<[u8]>);
|
||||
pub(super) type GetSwState = ioc!(EVIOCGSW, b'E', 0x1B, OutData<[u8]>);
|
||||
pub(super) type SetClockId = ioc!(EVIOCSCLOCKID, b'E', 0xA0, InData<i32>);
|
||||
|
||||
/// The `EVIOCGBIT` ioctl enum.
|
||||
pub(super) type GetEventBits = IoctlEnum<b'E', 0x20, 0x1F, OutData<[u8]>>;
|
||||
}
|
||||
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17.9/source/include/uapi/linux/input.h#L28>
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Pod)]
|
||||
|
|
@ -61,7 +83,7 @@ impl EvdevEvent {
|
|||
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17.9/source/drivers/input/evdev.c#L181-L189>
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, TryFromInt)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromInt)]
|
||||
enum EvdevClock {
|
||||
Realtime = 0,
|
||||
Monotonic = 1,
|
||||
|
|
@ -79,6 +101,22 @@ define_atomic_version_of_integer_like_type!(EvdevClock, try_from = true, {
|
|||
struct AtomicEvdevClock(AtomicU8);
|
||||
});
|
||||
|
||||
impl TryFrom<ClockId> for EvdevClock {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(clock_id: ClockId) -> Result<Self> {
|
||||
match clock_id {
|
||||
ClockId::CLOCK_REALTIME => Ok(EvdevClock::Realtime),
|
||||
ClockId::CLOCK_MONOTONIC => Ok(EvdevClock::Monotonic),
|
||||
ClockId::CLOCK_BOOTTIME => Ok(EvdevClock::Boottime),
|
||||
_ => return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"the clock is not a valid clock for evdev files"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An opened file from an evdev device ([`EvdevDevice`]).
|
||||
pub(super) struct EvdevFile {
|
||||
/// Inner data (shared with the device).
|
||||
|
|
@ -131,6 +169,29 @@ impl EvdevFileInner {
|
|||
self.pollee.invalidate();
|
||||
}
|
||||
|
||||
pub(self) fn clear_with_consumer_producer_locked(
|
||||
&self,
|
||||
consumer: &mut RbConsumer<EvdevEvent>,
|
||||
producer: &mut RbProducer<EvdevEvent>,
|
||||
) {
|
||||
// Note that the following operations are race-free because both the consumer and the
|
||||
// producer locks have been held.
|
||||
|
||||
if consumer.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
consumer.clear();
|
||||
|
||||
let time = self.read_clock();
|
||||
let dropped_event =
|
||||
EvdevEvent::from_event_and_time(&InputEvent::from_sync_event(SynEvent::Dropped), time);
|
||||
producer.push(dropped_event).unwrap();
|
||||
|
||||
self.packet_count.store(1, Ordering::Relaxed);
|
||||
self.pollee.notify(IoEvents::IN);
|
||||
}
|
||||
|
||||
/// Checks if buffer has complete event packets.
|
||||
pub(self) fn has_complete_packets(&self) -> bool {
|
||||
self.packet_count.load(Ordering::Relaxed) > 0
|
||||
|
|
@ -214,6 +275,30 @@ impl EvdevFile {
|
|||
IoEvents::empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade_device(&self) -> Result<Arc<EvdevDevice>> {
|
||||
self.evdev.upgrade().ok_or_else(|| {
|
||||
Error::with_message(Errno::ENODEV, "the evdev device has been disconnected")
|
||||
})
|
||||
}
|
||||
|
||||
fn set_clock(&self, clock: EvdevClock) -> Result<()> {
|
||||
// Lock the mutex in advance to avoid race conditions.
|
||||
let mut consumer = self.inner.consumer.lock();
|
||||
|
||||
if self.inner.clock_id.load(Ordering::Relaxed) == clock {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let device = self.upgrade_device()?;
|
||||
device.with_producer_locked(&self.inner, |producer| {
|
||||
self.inner.clock_id.store(clock, Ordering::Relaxed);
|
||||
self.inner
|
||||
.clear_with_consumer_producer_locked(&mut consumer, producer);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the event is a `SYN_REPORT` event.
|
||||
|
|
@ -226,6 +311,36 @@ pub(super) fn is_syn_dropped_event(event: &EvdevEvent) -> bool {
|
|||
event.type_ == EventTypes::SYN.as_index() && event.code == SynEvent::Dropped as u16
|
||||
}
|
||||
|
||||
fn write_bytes_and_zeros_to_userspace(writer: &mut VmWriter, bytes: &[u8]) -> Result<()> {
|
||||
let mut reader = VmReader::from(bytes);
|
||||
writer.write_fallible(&mut reader)?;
|
||||
writer.fill_zeros(writer.avail())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_bit(evdev: &Arc<EvdevDevice>, event_type: u8, writer: &mut VmWriter) -> Result<()> {
|
||||
let capability = evdev.device.capability();
|
||||
|
||||
match event_type as u16 {
|
||||
0 => {
|
||||
let event_types_bytes = capability.event_types_bits().to_ne_bytes();
|
||||
write_bytes_and_zeros_to_userspace(writer, &event_types_bytes)?;
|
||||
}
|
||||
t if t == EventTypes::KEY.as_index() => {
|
||||
let bitmap = capability.supported_keys_bitmap();
|
||||
write_bytes_and_zeros_to_userspace(writer, bitmap)?;
|
||||
}
|
||||
t if t == EventTypes::REL.as_index() => {
|
||||
let bitmap = capability.supported_relative_axes_bitmap();
|
||||
write_bytes_and_zeros_to_userspace(writer, bitmap)?;
|
||||
}
|
||||
_ => {
|
||||
return_errno_with_message!(Errno::EINVAL, "the event type is not supported yet");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Pollable for EvdevFile {
|
||||
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
|
||||
self.inner
|
||||
|
|
@ -292,17 +407,88 @@ impl FileIo for EvdevFile {
|
|||
false
|
||||
}
|
||||
|
||||
fn ioctl(&self, _raw_ioctl: RawIoctl) -> Result<i32> {
|
||||
// TODO: Support ioctl operations for evdev files.
|
||||
fn ioctl(&self, raw_ioctl: RawIoctl) -> Result<i32> {
|
||||
use ioctl_defs::*;
|
||||
|
||||
// Most ioctl implementations return `ENOTTY` for invalid ioctl commands, representing "The
|
||||
// specified operation does not apply". However, according to the Linux implementation,
|
||||
// evdev files return `EINVAL` in this case.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17.8/source/drivers/input/evdev.c#L1251>
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"the ioctl command is not supported by evdev files"
|
||||
);
|
||||
dispatch_ioctl!(match raw_ioctl {
|
||||
cmd @ GetDriverVer => {
|
||||
const EVDEV_DRIVER_VERSION: i32 = 0x010001;
|
||||
cmd.write(&EVDEV_DRIVER_VERSION)?;
|
||||
}
|
||||
cmd @ GetInputId => {
|
||||
let evdev = self.upgrade_device()?;
|
||||
let input_id = evdev.device.id();
|
||||
cmd.write(&input_id)?;
|
||||
}
|
||||
cmd @ SetClockId => {
|
||||
let clock_id_raw: i32 = cmd.read()?;
|
||||
let clock_id = ClockId::try_from(clock_id_raw)?;
|
||||
let evdev_clock = EvdevClock::try_from(clock_id)?;
|
||||
self.set_clock(evdev_clock)?;
|
||||
}
|
||||
cmd @ GetDeviceName => {
|
||||
let evdev = self.upgrade_device()?;
|
||||
cmd.with_writer(|mut writer| {
|
||||
write_bytes_and_zeros_to_userspace(&mut writer, evdev.device.name().as_bytes())
|
||||
})?;
|
||||
}
|
||||
cmd @ GetDevicePhys => {
|
||||
let evdev = self.upgrade_device()?;
|
||||
cmd.with_writer(|mut writer| {
|
||||
write_bytes_and_zeros_to_userspace(&mut writer, evdev.device.phys().as_bytes())
|
||||
})?;
|
||||
}
|
||||
cmd @ GetDeviceUniq => {
|
||||
let evdev = self.upgrade_device()?;
|
||||
cmd.with_writer(|mut writer| {
|
||||
write_bytes_and_zeros_to_userspace(&mut writer, evdev.device.uniq().as_bytes())
|
||||
})?;
|
||||
}
|
||||
cmd @ GetEventBits => {
|
||||
let evdev = self.upgrade_device()?;
|
||||
let event_type = cmd.discriminant();
|
||||
cmd.base_ioctl()
|
||||
.with_writer(|mut writer| handle_get_bit(&evdev, event_type, &mut writer))?;
|
||||
}
|
||||
cmd @ GetKeyState => {
|
||||
// TODO: We need to track whether the key is currently pressed and report that state
|
||||
// here. If we report states, we need to flush the queue to avoid interfering with the
|
||||
// user space's state tracking. See the Linux implementation at:
|
||||
// <https://elixir.bootlin.com/linux/v6.15/source/drivers/input/evdev.c#L872-L876>.
|
||||
//
|
||||
// Currently, no key state tracking is supported. So we report zeros as key states here.
|
||||
cmd.with_writer(|mut writer| {
|
||||
writer.fill_zeros(writer.avail())?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
cmd @ GetLedState => {
|
||||
// No LED events are supported. So we can report zeros as LED states here.
|
||||
cmd.with_writer(|mut writer| {
|
||||
writer.fill_zeros(writer.avail())?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
cmd @ GetSwState => {
|
||||
// No switch events are supported. So we can report zeros as switch states here.
|
||||
cmd.with_writer(|mut writer| {
|
||||
writer.fill_zeros(writer.avail())?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
_ => {
|
||||
// Most ioctl implementations return `ENOTTY` for invalid ioctl commands, representing "The
|
||||
// specified operation does not apply". However, according to the Linux implementation,
|
||||
// evdev files return `EINVAL` in this case.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17.8/source/drivers/input/evdev.c#L1251>
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"the ioctl command is not supported by evdev files"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,6 +108,18 @@ impl EvdevDevice {
|
|||
opened_files.swap_remove(pos);
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/// Distributes events to all opened evdev files.
|
||||
fn pass_events(&self, events: &[InputEvent]) {
|
||||
// No need to disable IRQs because this method can only be called in the interrupt context.
|
||||
|
|
|
|||
|
|
@ -364,7 +364,6 @@ impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool>
|
|||
/// Obtains a [`VmWriter`] that can write the dynamically-sized ioctl argument to userspace.
|
||||
///
|
||||
/// The size of the ioctl argument is specified in [`VmWriter::avail`].
|
||||
#[expect(dead_code)]
|
||||
pub fn with_writer<F, R>(&self, f: F) -> Result<R>
|
||||
where
|
||||
F: for<'a> FnOnce(VmWriter<'a>) -> Result<R>,
|
||||
|
|
|
|||
Loading…
Reference in New Issue