blob: aea57a75eff0b6bc2a12ef7dad398626c22e8eec [file] [log] [blame] [edit]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fuchsia_inspect::{NumericProperty, Property};
use starnix_core::fileops_impl_nonseekable;
use starnix_core::mm::{MemoryAccessor, MemoryAccessorExt};
use starnix_core::task::{CurrentTask, EventHandler, WaitCanceler, WaitQueue, Waiter};
use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
use starnix_core::vfs::{CloseFreeSafe, FileObject, FileOps, fileops_impl_noop_sync};
use starnix_logging::{log_info, trace_duration, trace_flow_begin, trace_flow_end, track_stub};
use starnix_sync::{FileOpsCore, Locked, Mutex, Unlocked};
use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
use starnix_types::time::duration_from_timeval;
use starnix_uapi::errors::Errno;
use starnix_uapi::user_address::{ArchSpecific, MultiArchUserRef, UserAddress, UserRef};
use starnix_uapi::vfs::FdEvents;
use starnix_uapi::{
ABS_CNT, ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_SLOT, ABS_MT_TRACKING_ID, BTN_MISC,
BTN_TOUCH, EV_CNT, FF_CNT, INPUT_PROP_CNT, INPUT_PROP_DIRECT, KEY_CNT, KEY_POWER, KEY_SLEEP,
KEY_VOLUMEDOWN, LED_CNT, MSC_CNT, REL_CNT, REL_WHEEL, SW_CNT, errno, error, uapi,
};
use std::collections::VecDeque;
use std::sync::Arc;
use zerocopy::IntoBytes as _; // for `as_bytes()`
uapi::check_arch_independent_layout! {
input_id {}
input_absinfo {}
}
type InputEventPtr = MultiArchUserRef<uapi::input_event, uapi::arch32::input_event>;
pub struct InputFileStatus {
/// The number of FIDL events received by this file from Fuchsia input system.
///
/// We expect:
/// fidl_events_received_count = fidl_events_ignored_count +
/// fidl_events_unexpected_count +
/// fidl_events_converted_count
/// otherwise starnix ignored events unexpectedly.
///
/// fidl_events_unexpected_count should be 0, if not it hints issues from upstream of ui stack.
pub fidl_events_received_count: fuchsia_inspect::UintProperty,
/// The number of FIDL events ignored to this module’s representation of TouchEvent.
pub fidl_events_ignored_count: fuchsia_inspect::UintProperty,
/// The unexpected number of FIDL events reached to this module should be filtered out
/// earlier in the UI stack.
/// It maybe unexpected format or unexpected order.
pub fidl_events_unexpected_count: fuchsia_inspect::UintProperty,
/// The number of FIDL events converted to this module’s representation of TouchEvent.
pub fidl_events_converted_count: fuchsia_inspect::UintProperty,
/// The number of uapi::input_events generated from TouchEvents.
pub uapi_events_generated_count: fuchsia_inspect::UintProperty,
/// The event time of the last generated uapi::input_event.
pub last_generated_uapi_event_timestamp_ns: fuchsia_inspect::IntProperty,
/// The number of uapi::input_events read from this input file by external process.
pub uapi_events_read_count: fuchsia_inspect::UintProperty,
/// The event time of the last uapi::input_event read by external process.
pub last_read_uapi_event_timestamp_ns: fuchsia_inspect::IntProperty,
}
impl InputFileStatus {
fn new(node: &fuchsia_inspect::Node) -> Self {
let fidl_events_received_count = node.create_uint("fidl_events_received_count", 0);
let fidl_events_ignored_count = node.create_uint("fidl_events_ignored_count", 0);
let fidl_events_unexpected_count = node.create_uint("fidl_events_unexpected_count", 0);
let fidl_events_converted_count = node.create_uint("fidl_events_converted_count", 0);
let uapi_events_generated_count = node.create_uint("uapi_events_generated_count", 0);
let last_generated_uapi_event_timestamp_ns =
node.create_int("last_generated_uapi_event_timestamp_ns", 0);
let uapi_events_read_count = node.create_uint("uapi_events_read_count", 0);
let last_read_uapi_event_timestamp_ns =
node.create_int("last_read_uapi_event_timestamp_ns", 0);
Self {
fidl_events_received_count,
fidl_events_ignored_count,
fidl_events_unexpected_count,
fidl_events_converted_count,
uapi_events_generated_count,
last_generated_uapi_event_timestamp_ns,
uapi_events_read_count,
last_read_uapi_event_timestamp_ns,
}
}
pub fn count_received_events(&self, count: u64) {
self.fidl_events_received_count.add(count);
}
pub fn count_ignored_events(&self, count: u64) {
self.fidl_events_ignored_count.add(count);
}
pub fn count_unexpected_events(&self, count: u64) {
self.fidl_events_unexpected_count.add(count);
}
pub fn count_converted_events(&self, count: u64) {
self.fidl_events_converted_count.add(count);
}
pub fn count_generated_events(&self, count: u64, event_time_ns: i64) {
self.uapi_events_generated_count.add(count);
self.last_generated_uapi_event_timestamp_ns.set(event_time_ns);
}
pub fn count_read_events(&self, count: u64, event_time_ns: i64) {
self.uapi_events_read_count.add(count);
self.last_read_uapi_event_timestamp_ns.set(event_time_ns);
}
}
pub struct InputFile {
driver_version: u32,
input_id: uapi::input_id,
supported_event_types: BitSet<{ min_bytes(EV_CNT) }>,
supported_keys: BitSet<{ min_bytes(KEY_CNT) }>,
supported_position_attributes: BitSet<{ min_bytes(ABS_CNT) }>, // ABSolute position
supported_motion_attributes: BitSet<{ min_bytes(REL_CNT) }>, // RELative motion
supported_switches: BitSet<{ min_bytes(SW_CNT) }>,
supported_leds: BitSet<{ min_bytes(LED_CNT) }>,
supported_haptics: BitSet<{ min_bytes(FF_CNT) }>, // 'F'orce 'F'eedback
supported_misc_features: BitSet<{ min_bytes(MSC_CNT) }>,
properties: BitSet<{ min_bytes(INPUT_PROP_CNT) }>,
mt_slot_axis_info: uapi::input_absinfo,
mt_tracking_id_axis_info: uapi::input_absinfo,
x_axis_info: uapi::input_absinfo,
y_axis_info: uapi::input_absinfo,
pub inner: Mutex<InputFileMutableState>,
// InputFile will be initialized with an InputFileStatus that holds Inspect data
// `None` for Uinput InputFiles
pub inspect_status: Option<Arc<InputFileStatus>>,
// A descriptive device name. Should contain only alphanumerics and `_`.
device_name: String,
}
pub struct LinuxEventWithTraceId {
pub event: uapi::input_event,
pub trace_id: Option<fuchsia_trace::Id>,
}
impl LinuxEventWithTraceId {
pub fn new(event: uapi::input_event) -> Self {
match event.type_ as u32 {
uapi::EV_SYN => {
let trace_id = fuchsia_trace::Id::random();
trace_duration!("input", "linux_event_create");
trace_flow_begin!("input", "linux_event", trace_id);
LinuxEventWithTraceId { event: event, trace_id: Some(trace_id) }
}
// EV_SYN marks the end of a complete input event. Other event types are its properties,
// so they don't initiate a trace.
_ => LinuxEventWithTraceId { event: event, trace_id: None },
}
}
}
// Mutable state of `InputFile`
pub struct InputFileMutableState {
pub events: VecDeque<LinuxEventWithTraceId>,
pub waiters: WaitQueue,
}
/// Returns the minimum number of bytes required to store `n_bits` bits.
const fn min_bytes(n_bits: u32) -> usize {
((n_bits as usize) + 7) / 8
}
/// Returns appropriate `INPUT_PROP`-erties for a keyboard device.
fn keyboard_properties() -> BitSet<{ min_bytes(INPUT_PROP_CNT) }> {
let mut attrs = BitSet::new();
attrs.set(INPUT_PROP_DIRECT);
attrs
}
/// Returns appropriate `KEY`-board related flags for a touchscreen device.
fn touch_key_attributes() -> BitSet<{ min_bytes(KEY_CNT) }> {
let mut attrs = BitSet::new();
attrs.set(BTN_TOUCH);
attrs.set(BTN_MISC); // Include BTN_MISC as a catchall key event.
attrs.set(KEY_SLEEP);
attrs
}
/// Returns appropriate `ABS`-olute position related flags for a touchscreen device.
fn touch_position_attributes() -> BitSet<{ min_bytes(ABS_CNT) }> {
let mut attrs = BitSet::new();
attrs.set(ABS_MT_SLOT);
attrs.set(ABS_MT_TRACKING_ID);
attrs.set(ABS_MT_POSITION_X);
attrs.set(ABS_MT_POSITION_Y);
attrs
}
/// Returns appropriate `INPUT_PROP`-erties for a touchscreen device.
fn touch_properties() -> BitSet<{ min_bytes(INPUT_PROP_CNT) }> {
let mut attrs = BitSet::new();
attrs.set(INPUT_PROP_DIRECT);
attrs
}
/// Returns appropriate `KEY`-board related flags for a keyboard device.
fn keyboard_key_attributes() -> BitSet<{ min_bytes(KEY_CNT) }> {
let mut attrs = BitSet::new();
attrs.set(BTN_MISC);
attrs.set(KEY_POWER);
attrs.set(KEY_VOLUMEDOWN);
attrs
}
/// Returns appropriate `ABS`-olute position related flags for a keyboard device.
fn keyboard_position_attributes() -> BitSet<{ min_bytes(ABS_CNT) }> {
BitSet::new()
}
fn mouse_wheel_attributes() -> BitSet<{ min_bytes(REL_CNT) }> {
let mut attrs = BitSet::new();
attrs.set(REL_WHEEL);
attrs
}
/// Makes a device name string from a name and device ID details.
///
/// For practical reasons the device name should contain alphanumerics and `_`.
fn get_device_name(name: &str, input_id: &uapi::input_id) -> String {
format!("{}_{:04x}_{:04x}_v{}", name, input_id.vendor, input_id.product, input_id.version)
}
impl InputFile {
// Per https://www.linuxjournal.com/article/6429, the driver version is 32-bits wide,
// and interpreted as:
// * [31-16]: version
// * [15-08]: minor
// * [07-00]: patch level
const DRIVER_VERSION: u32 = 0;
/// Creates an `InputFile` instance suitable for emulating a touchscreen.
///
/// # Parameters
/// - `input_id`: device's bustype, vendor id, product id, and version.
/// - `width`: width of screen.
/// - `height`: height of screen.
/// - `inspect_status`: The inspect status for the parent device of "touch_input_file".
pub fn new_touch(
input_id: uapi::input_id,
width: i32,
height: i32,
node: Option<&fuchsia_inspect::Node>,
) -> Self {
let device_name = get_device_name("starnix_touch", &input_id);
// Fuchsia scales the position reported by the touch sensor to fit view coordinates.
// Hence, the range of touch positions is exactly the same as the range of view
// coordinates.
Self {
driver_version: Self::DRIVER_VERSION,
input_id,
supported_event_types: BitSet::list([uapi::EV_ABS]),
supported_keys: touch_key_attributes(),
supported_position_attributes: touch_position_attributes(),
supported_motion_attributes: BitSet::new(), // None supported, not a mouse.
supported_switches: BitSet::new(), // None supported
supported_leds: BitSet::new(), // None supported
supported_haptics: BitSet::new(), // None supported
supported_misc_features: BitSet::new(), // None supported
properties: touch_properties(),
mt_slot_axis_info: uapi::input_absinfo {
minimum: 0,
maximum: 10,
..uapi::input_absinfo::default()
},
mt_tracking_id_axis_info: uapi::input_absinfo {
minimum: 0,
maximum: i32::MAX,
..uapi::input_absinfo::default()
},
x_axis_info: uapi::input_absinfo {
minimum: 0,
maximum: i32::from(width),
// TODO(https://fxbug.dev/42075436): `value` field should contain the most recent
// X position.
..uapi::input_absinfo::default()
},
y_axis_info: uapi::input_absinfo {
minimum: 0,
maximum: i32::from(height),
// TODO(https://fxbug.dev/42075436): `value` field should contain the most recent
// Y position.
..uapi::input_absinfo::default()
},
inner: Mutex::new(InputFileMutableState {
events: VecDeque::new(),
waiters: WaitQueue::default(),
}),
inspect_status: node.map(|n| Arc::new(InputFileStatus::new(n))),
device_name,
}
}
/// Creates an `InputFile` instance suitable for emulating a keyboard.
///
/// # Parameters
/// - `input_id`: device's bustype, vendor id, product id, and version.
/// - `inspect_status`: The inspect status for the parent device of "keyboard_input_file".
pub fn new_keyboard(input_id: uapi::input_id, node: Option<&fuchsia_inspect::Node>) -> Self {
let device_name = get_device_name("starnix_buttons", &input_id);
Self {
driver_version: Self::DRIVER_VERSION,
input_id,
supported_event_types: BitSet::list([uapi::EV_KEY]),
supported_keys: keyboard_key_attributes(),
supported_position_attributes: keyboard_position_attributes(),
supported_motion_attributes: BitSet::new(), // None supported, not a mouse.
supported_switches: BitSet::new(), // None supported
supported_leds: BitSet::new(), // None supported
supported_haptics: BitSet::new(), // None supported
supported_misc_features: BitSet::new(), // None supported
properties: keyboard_properties(),
mt_slot_axis_info: uapi::input_absinfo::default(),
mt_tracking_id_axis_info: uapi::input_absinfo::default(),
x_axis_info: uapi::input_absinfo::default(),
y_axis_info: uapi::input_absinfo::default(),
inner: Mutex::new(InputFileMutableState {
events: VecDeque::new(),
waiters: WaitQueue::default(),
}),
inspect_status: node.map(|n| Arc::new(InputFileStatus::new(n))),
device_name,
}
}
/// Creates an `InputFile` instance suitable for emulating a mouse wheel.
///
/// # Parameters
/// - `input_id`: device's bustype, vendor id, product id, and version.
/// - `inspect_status`: The inspect status for the parent device of "mouse_input_file".
pub fn new_mouse(input_id: uapi::input_id, node: Option<&fuchsia_inspect::Node>) -> Self {
let device_name = get_device_name("starnix_mouse", &input_id);
Self {
driver_version: Self::DRIVER_VERSION,
input_id,
supported_event_types: BitSet::list([uapi::EV_REL]),
supported_keys: BitSet::new(), // None supported, scroll only
supported_position_attributes: BitSet::new(), // None supported, scroll only
supported_motion_attributes: mouse_wheel_attributes(),
supported_switches: BitSet::new(), // None supported
supported_leds: BitSet::new(), // None supported
supported_haptics: BitSet::new(), // None supported
supported_misc_features: BitSet::new(), // None supported
properties: BitSet::new(), // None supported, scroll only
mt_slot_axis_info: uapi::input_absinfo::default(),
mt_tracking_id_axis_info: uapi::input_absinfo::default(),
x_axis_info: uapi::input_absinfo::default(),
y_axis_info: uapi::input_absinfo::default(),
inner: Mutex::new(InputFileMutableState {
events: VecDeque::new(),
waiters: WaitQueue::default(),
}),
inspect_status: node.map(|n| Arc::new(InputFileStatus::new(n))),
device_name,
}
}
}
// The bit-mask that removes the variable parts of the EVIOCGNAME ioctl
// request.
const EVIOCGNAME_MASK: u32 = 0b11_00_0000_0000_0000_1111_1111_1111_1111;
/// `InputFile` doesn't implement the `close` method.
impl CloseFreeSafe for InputFile {}
impl FileOps for InputFile {
fileops_impl_nonseekable!();
fileops_impl_noop_sync!();
fn ioctl(
&self,
_locked: &mut Locked<Unlocked>,
_file: &FileObject,
current_task: &CurrentTask,
request: u32,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
match request {
uapi::EVIOCGVERSION => {
current_task.write_object(UserRef::new(user_addr), &self.driver_version)?;
Ok(SUCCESS)
}
uapi::EVIOCGID => {
current_task.write_object(UserRef::new(user_addr), &self.input_id)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_0 => {
current_task
.write_object(UserRef::new(user_addr), &self.supported_event_types.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_KEY => {
current_task.write_object(UserRef::new(user_addr), &self.supported_keys.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_ABS => {
current_task.write_object(
UserRef::new(user_addr),
&self.supported_position_attributes.bytes,
)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_REL => {
current_task.write_object(
UserRef::new(user_addr),
&self.supported_motion_attributes.bytes,
)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_SW => {
current_task
.write_object(UserRef::new(user_addr), &self.supported_switches.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_LED => {
current_task.write_object(UserRef::new(user_addr), &self.supported_leds.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_FF => {
current_task
.write_object(UserRef::new(user_addr), &self.supported_haptics.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGBIT_EV_MSC => {
current_task
.write_object(UserRef::new(user_addr), &self.supported_misc_features.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGPROP => {
current_task.write_object(UserRef::new(user_addr), &self.properties.bytes)?;
Ok(SUCCESS)
}
uapi::EVIOCGABS_MT_SLOT => {
current_task.write_object(UserRef::new(user_addr), &self.mt_slot_axis_info)?;
Ok(SUCCESS)
}
uapi::EVIOCGABS_MT_TRACKING_ID => {
current_task
.write_object(UserRef::new(user_addr), &self.mt_tracking_id_axis_info)?;
Ok(SUCCESS)
}
uapi::EVIOCGABS_MT_POSITION_X => {
current_task.write_object(UserRef::new(user_addr), &self.x_axis_info)?;
Ok(SUCCESS)
}
uapi::EVIOCGABS_MT_POSITION_Y => {
current_task.write_object(UserRef::new(user_addr), &self.y_axis_info)?;
Ok(SUCCESS)
}
request_with_params => {
// Remove the variable part of the request with params, so
// we can identify it.
match request_with_params & EVIOCGNAME_MASK {
uapi::EVIOCGNAME_0 => {
// Request to report the device name.
//
// An EVIOCGNAME request comes with the response buffer size encoded in
// bits 29..16 of the request's `u32` code. This is in contrast to
// most other ioctl request codes in this file, which are fully known
// at compile time, so we need to decode it a bit differently from
// other ioctl codes.
//
// See [here][hh] the macros that do this.
//
// [hh]: https://cs.opensource.google/fuchsia/fuchsia/+/main:third_party/android/platform/bionic/libc/kernel/uapi/linux/input.h;l=82;drc=0f0c18f695543b15b852f68f297744d03d642a26
let device_name = &self.device_name;
// The lowest 14 bits of the top 16 bits are the unsigned buffer
// length in bytes. While we don't use multibyte characters,
// make sure that all sizes below are expressed in terms of
// bytes, not characters.
let buffer_bytes_count =
((request_with_params >> 16) & ((1 << 14) - 1)) as usize;
// Zero out the entire user buffer in case the user reads too much.
// Probably not needed, but I don't think it hurts.
current_task.zero(user_addr, buffer_bytes_count)?;
let device_name_as_bytes = device_name.as_bytes();
// Copy all bytes from device name if the buffer is large enough.
// If not, copy one less than the buffer size, to leave space
// for the final NUL.
let to_copy_bytes_count =
std::cmp::min(device_name_as_bytes.len(), buffer_bytes_count - 1);
current_task.write_memory(
user_addr,
&device_name_as_bytes[..to_copy_bytes_count],
)?;
// EVIOCGNAME ioctl returns the number of bytes written.
// Do not forget the trailing NUL.
Ok((to_copy_bytes_count + 1).into())
}
_ => {
track_stub!(
TODO("https://fxbug.dev/322873200"),
"input ioctl",
request_with_params
);
error!(EOPNOTSUPP)
}
}
}
}
}
fn read(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
trace_duration!("input", "InputFile::read");
debug_assert!(offset == 0);
let mut inner = self.inner.lock();
let num_events = inner.events.len();
if num_events == 0 {
// TODO(https://fxbug.dev/42075445): `EAGAIN` is only permitted if the file is opened
// with `O_NONBLOCK`. Figure out what to do if the file is opened without that flag.
log_info!("read() returning EAGAIN");
return error!(EAGAIN);
}
let input_event_size = InputEventPtr::size_of_object_for(current_task);
// The limit of the buffer is determined by taking the available bytes
// and using integer division on the size of uapi::input_event in bytes.
// This is how many events we can write at a time, up to the amount of
// events queued to be written.
let limit = std::cmp::min(data.available() / input_event_size, num_events);
if num_events > limit {
log_info!(
"There was only space in the given buffer to read {} of the {} queued events. Sending a notification to prompt another read.",
limit,
num_events
);
inner.waiters.notify_fd_events(FdEvents::POLLIN);
}
let events: Vec<LinuxEventWithTraceId> = inner.events.drain(..limit).collect::<Vec<_>>();
let last_event_timeval = events.last().expect("events is nonempty").event.time;
let last_event_time_ns = duration_from_timeval::<zx::MonotonicTimeline>(last_event_timeval)
.unwrap()
.into_nanos();
self.inspect_status
.clone()
.map(|status| status.count_read_events(events.len() as u64, last_event_time_ns));
for event in &events {
if let Some(trace_id) = event.trace_id {
trace_duration!("input", "linux_event_read");
trace_flow_end!("input", "linux_event", trace_id);
}
}
if current_task.is_arch32() {
let events: Result<Vec<uapi::arch32::input_event>, _> =
events.iter().map(|e| uapi::arch32::input_event::try_from(e.event)).collect();
let events = events.map_err(|_| errno!(EINVAL))?;
data.write_all(events.as_bytes())
} else {
let events: Vec<uapi::input_event> = events.iter().map(|e| e.event).collect();
data.write_all(events.as_bytes())
}
}
fn write(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
_data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
debug_assert!(offset == 0);
track_stub!(TODO("https://fxbug.dev/322874385"), "write() on input device");
error!(EOPNOTSUPP)
}
fn wait_async(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
waiter: &Waiter,
events: FdEvents,
handler: EventHandler,
) -> Option<WaitCanceler> {
Some(self.inner.lock().waiters.wait_async_fd_events(waiter, events, handler))
}
fn query_events(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
) -> Result<FdEvents, Errno> {
Ok(if self.inner.lock().events.is_empty() { FdEvents::empty() } else { FdEvents::POLLIN })
}
}
pub struct BitSet<const NUM_BYTES: usize> {
bytes: [u8; NUM_BYTES],
}
impl<const NUM_BYTES: usize> BitSet<{ NUM_BYTES }> {
pub const fn new() -> Self {
Self { bytes: [0; NUM_BYTES] }
}
pub const fn list<const N: usize>(bits: [u32; N]) -> Self {
let mut bitset = Self::new();
let mut i = 0;
while i < bits.len() {
bitset.set(bits[i]);
i += 1;
}
bitset
}
pub const fn set(&mut self, bitnum: u32) {
let bitnum = bitnum as usize;
let byte = bitnum / 8;
let bit = bitnum % 8;
self.bytes[byte] |= 1 << bit;
}
}