Skip to main content

starnix_modules_input/
uinput.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::uinput::vfs::{CloseFreeSafe, NamespaceNode};
6use crate::{InputEventsRelayHandle, InputFile, OpenedFiles};
7use bit_vec::BitVec;
8use fidl_fuchsia_ui_test_input::{
9    self as futinput, CoordinateUnit, DisplayDimensions, KeyboardSimulateKeyEventRequest,
10    RegistryRegisterKeyboardAndGetDeviceInfoRequest,
11    RegistryRegisterTouchScreenAndGetDeviceInfoRequest,
12};
13use fuchsia_inspect;
14use starnix_core::device::kobject::{Device, DeviceMetadata};
15use starnix_core::device::{DeviceMode, DeviceOps};
16use starnix_core::fileops_impl_seekless;
17use starnix_core::mm::MemoryAccessorExt;
18use starnix_core::task::CurrentTask;
19use starnix_core::vfs::{
20    self, FileObject, FileOps, FsString, default_ioctl, fileops_impl_noop_sync,
21};
22use starnix_logging::log_warn;
23use starnix_modules_input_event_conversion::key_linux_to_fuchsia::LinuxKeyboardEventParser;
24use starnix_modules_input_event_conversion::touch_linux_to_fuchsia::LinuxTouchEventParser;
25use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
26use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
27use starnix_uapi::device_id::INPUT_MAJOR;
28use starnix_uapi::errors::Errno;
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::user_address::{MultiArchUserRef, UserRef};
31use starnix_uapi::{device_id, errno, error, uapi};
32use std::sync::Arc;
33use std::sync::atomic::{AtomicI32, Ordering};
34
35// Return the current uinput API version 5, it also told caller this uinput
36// supports UI_DEV_SETUP.
37const UINPUT_VERSION: u32 = 5;
38
39type InputEventPtr = MultiArchUserRef<uapi::input_event, uapi::arch32::input_event>;
40
41#[derive(Clone)]
42enum DeviceId {
43    Keyboard,
44    Touchscreen(i32, i32),
45}
46
47pub fn register_uinput_device(
48    locked: &mut Locked<Unlocked>,
49    system_task: &CurrentTask,
50    input_event_relay_handle: Arc<InputEventsRelayHandle>,
51) -> Result<(), Errno> {
52    let kernel = system_task.kernel();
53    let registry = &kernel.device_registry;
54    let misc_class = registry.objects.misc_class();
55    let inspect_node = Arc::new(kernel.inspect_node.create_child("uinput"));
56    let device = UinputDevice::new(input_event_relay_handle, inspect_node);
57    registry.register_device(
58        locked,
59        system_task,
60        "uinput".into(),
61        DeviceMetadata::new("uinput".into(), device_id::DeviceId::UINPUT, DeviceMode::Char),
62        misc_class,
63        device,
64    )?;
65    Ok(())
66}
67
68fn add_and_register_input_device<L>(
69    locked: &mut Locked<L>,
70    system_task: &CurrentTask,
71    dev_ops: impl DeviceOps,
72    device_id: u32,
73) -> Result<Device, Errno>
74where
75    L: LockEqualOrBefore<FileOpsCore>,
76{
77    let kernel = system_task.kernel();
78    let registry = &kernel.device_registry;
79
80    let input_class = registry.objects.input_class();
81
82    registry.register_device(
83        locked,
84        system_task,
85        FsString::from(format!("event{}", device_id)).as_ref(),
86        DeviceMetadata::new(
87            format!("input/event{}", device_id).into(),
88            starnix_uapi::device_id::DeviceId::new(INPUT_MAJOR, device_id),
89            DeviceMode::Char,
90        ),
91        input_class,
92        dev_ops,
93    )
94}
95
96#[derive(Clone)]
97struct UinputDevice {
98    input_event_relay: Arc<InputEventsRelayHandle>,
99    inspect_node: Arc<fuchsia_inspect::Node>,
100}
101
102impl UinputDevice {
103    pub fn new(
104        input_event_relay: Arc<InputEventsRelayHandle>,
105        inspect_node: Arc<fuchsia_inspect::Node>,
106    ) -> Self {
107        Self { input_event_relay, inspect_node }
108    }
109}
110
111impl DeviceOps for UinputDevice {
112    fn open(
113        &self,
114        _locked: &mut Locked<FileOpsCore>,
115        _current_task: &CurrentTask,
116        _id: device_id::DeviceId,
117        _node: &NamespaceNode,
118        _flags: OpenFlags,
119    ) -> Result<Box<dyn FileOps>, Errno> {
120        Ok(Box::new(UinputDeviceFile::new(
121            self.input_event_relay.clone(),
122            self.inspect_node.clone(),
123        )))
124    }
125}
126
127enum CreatedDevice {
128    None,
129    Keyboard(futinput::KeyboardSynchronousProxy, LinuxKeyboardEventParser),
130    // LinuxTouchEventParser need to boxed to avoid warning: large-enum-variant.
131    Touchscreen(futinput::TouchScreenSynchronousProxy, Box<LinuxTouchEventParser>),
132}
133
134struct Range {
135    min: i32,
136    max: i32,
137}
138struct UinputDeviceMutableState {
139    enabled_evbits: BitVec,
140    input_id: Option<uapi::input_id>,
141    created_device: CreatedDevice,
142    k_device: Option<Device>,
143    device_id: Option<u32>,
144    x_range: Option<Range>,
145    y_range: Option<Range>,
146}
147
148impl UinputDeviceMutableState {
149    fn get_id_and_device_type(&self) -> Option<(uapi::input_id, DeviceId)> {
150        let input_id = match self.input_id {
151            Some(input_id) => input_id,
152            None => return None,
153        };
154        // Currently only support Keyboard and Touchscreen, if evbits contains
155        // EV_ABS, consider it is Touchscreen. This need to be revisit when we
156        // want to support more device types.
157        let device_type = match self.enabled_evbits.clone().get(uapi::EV_ABS as usize) {
158            Some(true) => {
159                // TODO(b/304595635): Check if screen size is required.
160                let mut touchscreen_width = 1000;
161                let mut touchscreen_height = 1000;
162
163                if self.x_range.is_some() && self.y_range.is_some() {
164                    let x_range = self.x_range.as_ref().unwrap();
165                    let y_range = self.y_range.as_ref().unwrap();
166                    touchscreen_width = x_range.max - x_range.min;
167                    touchscreen_height = y_range.max - y_range.min;
168                }
169                DeviceId::Touchscreen(touchscreen_width, touchscreen_height)
170            }
171            Some(false) | None => DeviceId::Keyboard,
172        };
173
174        Some((input_id, device_type))
175    }
176}
177
178struct UinputDeviceFile {
179    input_event_relay: Arc<InputEventsRelayHandle>,
180    inner: Mutex<UinputDeviceMutableState>,
181    inspect_node: Arc<fuchsia_inspect::Node>,
182}
183
184impl UinputDeviceFile {
185    pub fn new(
186        input_event_relay: Arc<InputEventsRelayHandle>,
187        inspect_node: Arc<fuchsia_inspect::Node>,
188    ) -> Self {
189        Self {
190            input_event_relay,
191            inner: Mutex::new(UinputDeviceMutableState {
192                enabled_evbits: BitVec::from_elem(uapi::EV_CNT as usize, false),
193                input_id: None,
194                created_device: CreatedDevice::None,
195                k_device: None,
196                device_id: None,
197                x_range: None,
198                y_range: None,
199            }),
200            inspect_node,
201        }
202    }
203
204    /// UI_SET_EVBIT caller pass a u32 as the event type "EV_*" to set this
205    /// uinput device may handle events with the given event type.
206    fn ui_set_evbit(&self, arg: SyscallArg) -> Result<SyscallResult, Errno> {
207        let evbit: u32 = arg.into();
208        match evbit {
209            uapi::EV_KEY | uapi::EV_ABS => {
210                self.inner.lock().enabled_evbits.set(evbit as usize, true);
211                Ok(SUCCESS)
212            }
213            _ => {
214                log_warn!("UI_SET_EVBIT with unsupported evbit {}", evbit);
215                error!(EPERM)
216            }
217        }
218    }
219
220    /// UI_ABS_SETUP caller pass in event codes and min and max value for
221    /// the event code.
222    fn ui_abs_setup(
223        &self,
224        current_task: &CurrentTask,
225        abs_setup: UserRef<starnix_uapi::uinput_abs_setup>,
226    ) -> Result<SyscallResult, Errno> {
227        let setup: starnix_uapi::uinput_abs_setup = current_task.read_object(abs_setup)?;
228        let code: u32 = setup.code.into();
229        match code {
230            uapi::ABS_MT_POSITION_X => {
231                self.inner.lock().x_range =
232                    Some(Range { min: setup.absinfo.minimum, max: setup.absinfo.maximum });
233            }
234            uapi::ABS_MT_POSITION_Y => {
235                self.inner.lock().y_range =
236                    Some(Range { min: setup.absinfo.minimum, max: setup.absinfo.maximum });
237            }
238            _ => {
239                log_warn!("UI_ABS_SETUP ignore event code {}", setup.code);
240            }
241        }
242        Ok(SUCCESS)
243    }
244
245    /// UI_GET_VERSION caller pass a address for u32 to `arg` to receive the
246    /// uinput version. ioctl returns SUCCESS(0) for success calls, and
247    /// EFAULT(14) for given address is null.
248    fn ui_get_version(
249        &self,
250        current_task: &CurrentTask,
251        user_version: UserRef<u32>,
252    ) -> Result<SyscallResult, Errno> {
253        let response: u32 = UINPUT_VERSION;
254        current_task.write_object(user_version, &response)?;
255        Ok(SUCCESS)
256    }
257
258    /// UI_DEV_SETUP set the name of device and input_id (bustype, vendor id,
259    /// product id, version) to the uinput device.
260    fn ui_dev_setup(
261        &self,
262        current_task: &CurrentTask,
263        user_uinput_setup: UserRef<uapi::uinput_setup>,
264    ) -> Result<SyscallResult, Errno> {
265        let uinput_setup = current_task.read_object(user_uinput_setup)?;
266        self.inner.lock().input_id = Some(uinput_setup.id);
267        Ok(SUCCESS)
268    }
269
270    fn ui_dev_create<L>(
271        &self,
272        locked: &mut Locked<L>,
273        current_task: &CurrentTask,
274    ) -> Result<SyscallResult, Errno>
275    where
276        L: LockEqualOrBefore<FileOpsCore>,
277    {
278        // Only eng and userdebug builds include the `fuchsia.ui.test.input` service.
279        let registry = match fuchsia_component::client::connect_to_protocol_sync::<
280            futinput::RegistryMarker,
281        >() {
282            Ok(proxy) => Some(proxy),
283            Err(_) => {
284                log_warn!("Could not connect to fuchsia.ui.test.input/Registry");
285                None
286            }
287        };
288        self.ui_dev_create_inner(locked, current_task, registry)
289    }
290
291    /// UI_DEV_CREATE calls create the uinput device with given information
292    /// from previous ioctl() calls.
293    fn ui_dev_create_inner<L>(
294        &self,
295        locked: &mut Locked<L>,
296        current_task: &CurrentTask,
297        // Takes `registry` arg so we can manually inject a mock registry in unit tests.
298        registry: Option<futinput::RegistrySynchronousProxy>,
299    ) -> Result<SyscallResult, Errno>
300    where
301        L: LockEqualOrBefore<FileOpsCore>,
302    {
303        match registry {
304            Some(proxy) => {
305                let mut inner = self.inner.lock();
306                let (input_id, device_type) = match inner.get_id_and_device_type() {
307                    Some((id, dev)) => (id, dev),
308                    None => return error!(EINVAL),
309                };
310
311                let open_files: OpenedFiles = Default::default();
312
313                let (registered_device_id, inspect_status) = match device_type {
314                    DeviceId::Keyboard => {
315                        let (key_client, key_server) =
316                            fidl::endpoints::create_sync_proxy::<futinput::KeyboardMarker>();
317                        inner.created_device =
318                            CreatedDevice::Keyboard(key_client, LinuxKeyboardEventParser::create());
319
320                        // Register a keyboard
321                        let register_res = proxy.register_keyboard_and_get_device_info(
322                            RegistryRegisterKeyboardAndGetDeviceInfoRequest {
323                                device: Some(key_server),
324                                ..Default::default()
325                            },
326                            zx::MonotonicInstant::INFINITE,
327                        );
328
329                        match register_res {
330                            Ok(resp) => match resp.device_id {
331                                Some(device_id) => {
332                                    inner.device_id = Some(device_id);
333                                    let node = self
334                                        .inspect_node
335                                        .create_child(format!("uinput_device_{}", device_id));
336                                    let inspect_status = crate::InputDeviceStatus::new(node);
337                                    self.input_event_relay.add_keyboard_device(
338                                        device_id,
339                                        open_files.clone(),
340                                        Some(inspect_status.clone()),
341                                    );
342                                    (device_id, inspect_status)
343                                }
344                                None => {
345                                    log_warn!(
346                                        "register_keyboard_and_get_device_info response does not include a device_id"
347                                    );
348                                    return error!(EPERM);
349                                }
350                            },
351                            Err(e) => {
352                                log_warn!(
353                                    "Uinput could not register Keyboard device to Registry: {:?}",
354                                    e
355                                );
356                                return error!(EPERM);
357                            }
358                        }
359                    }
360                    DeviceId::Touchscreen(_width, _height) => {
361                        let (touch_client, touch_server) =
362                            fidl::endpoints::create_sync_proxy::<futinput::TouchScreenMarker>();
363                        inner.created_device = CreatedDevice::Touchscreen(
364                            touch_client,
365                            Box::new(LinuxTouchEventParser::create()),
366                        );
367
368                        let mut request = RegistryRegisterTouchScreenAndGetDeviceInfoRequest {
369                            device: Some(touch_server),
370                            coordinate_unit: Some(CoordinateUnit::PhysicalPixels),
371                            ..Default::default()
372                        };
373
374                        if inner.x_range.is_some() && inner.y_range.is_some() {
375                            request.coordinate_unit = Some(CoordinateUnit::RegisteredDimensions);
376                            let x_range = inner.x_range.as_ref().unwrap();
377                            let y_range = inner.y_range.as_ref().unwrap();
378                            request.display_dimensions = Some(DisplayDimensions {
379                                min_x: x_range.min.into(),
380                                max_x: x_range.max.into(),
381                                min_y: y_range.min.into(),
382                                max_y: y_range.max.into(),
383                            });
384                        }
385
386                        // Register a touchscreen
387                        let register_res = proxy.register_touch_screen_and_get_device_info(
388                            request,
389                            zx::Instant::INFINITE,
390                        );
391
392                        match register_res {
393                            Ok(resp) => match resp.device_id {
394                                Some(device_id) => {
395                                    inner.device_id = Some(device_id);
396                                    let node = self
397                                        .inspect_node
398                                        .create_child(format!("uinput_device_{}", device_id));
399                                    let inspect_status = crate::InputDeviceStatus::new(node);
400                                    self.input_event_relay.add_touch_device(
401                                        device_id,
402                                        open_files.clone(),
403                                        Some(inspect_status.clone()),
404                                    );
405                                    (device_id, inspect_status)
406                                }
407                                None => {
408                                    log_warn!(
409                                        "register_touch_screen_and_get_device_info response does not include a device_id"
410                                    );
411                                    return error!(EPERM);
412                                }
413                            },
414                            Err(e) => {
415                                log_warn!(
416                                    "Uinput could not register Keyboard device to Registry: {:?}",
417                                    e
418                                );
419                                return error!(EPERM);
420                            }
421                        }
422                    }
423                };
424
425                let device = add_and_register_input_device(
426                    locked,
427                    current_task,
428                    VirtualDevice { input_id, devt: device_type, open_files, inspect_status },
429                    registered_device_id,
430                )?;
431                inner.k_device = Some(device);
432
433                new_device();
434
435                Ok(SUCCESS)
436            }
437            None => {
438                log_warn!("No Registry available for Uinput.");
439                error!(EPERM)
440            }
441        }
442    }
443
444    fn ui_dev_destroy<L>(
445        &self,
446        locked: &mut Locked<L>,
447        current_task: &CurrentTask,
448    ) -> Result<SyscallResult, Errno>
449    where
450        L: LockEqualOrBefore<FileOpsCore>,
451    {
452        let mut inner = self.inner.lock();
453        match inner.device_id {
454            Some(device_id) => {
455                self.input_event_relay.remove_device(device_id);
456            }
457            None => {
458                // This is possible if caller does not call create device but calls destroy.
459                // No cleanup is needed for input event relay in this case.
460            }
461        }
462
463        match inner.k_device.clone() {
464            Some(device) => {
465                let kernel = current_task.kernel();
466                kernel.device_registry.remove_device(locked, current_task, device);
467            }
468            None => {
469                log_warn!("UI_DEV_DESTROY kHandle not found");
470                return error!(EPERM);
471            }
472        }
473        inner.k_device = None;
474        inner.created_device = CreatedDevice::None;
475
476        destroy_device();
477
478        Ok(SUCCESS)
479    }
480}
481
482// TODO(b/312467059): Remove once ESC -> Power workaround can be remove.
483static COUNT_OF_UINPUT_DEVICE: AtomicI32 = AtomicI32::new(0);
484
485fn new_device() {
486    let _ = COUNT_OF_UINPUT_DEVICE.fetch_add(1, Ordering::SeqCst);
487}
488
489fn destroy_device() {
490    let _ = COUNT_OF_UINPUT_DEVICE.fetch_sub(1, Ordering::SeqCst);
491}
492
493pub fn uinput_running() -> bool {
494    COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst) > 0
495}
496
497/// `UinputDeviceFile` doesn't implement the `close` method.
498impl CloseFreeSafe for UinputDeviceFile {}
499impl FileOps for UinputDeviceFile {
500    fileops_impl_seekless!();
501    fileops_impl_noop_sync!();
502
503    fn ioctl(
504        &self,
505        locked: &mut Locked<Unlocked>,
506        file: &FileObject,
507        current_task: &CurrentTask,
508        request: u32,
509        arg: SyscallArg,
510    ) -> Result<SyscallResult, Errno> {
511        match request {
512            uapi::UI_GET_VERSION => self.ui_get_version(current_task, arg.into()),
513            uapi::UI_SET_EVBIT => self.ui_set_evbit(arg),
514            uapi::UI_ABS_SETUP => self.ui_abs_setup(current_task, arg.into()),
515            // `fuchsia.ui.test.input.Registry` does not use some uinput ioctl
516            // request, just ignore the request and return SUCCESS, even args
517            // is invalid.
518            uapi::UI_SET_KEYBIT
519            | uapi::UI_SET_ABSBIT
520            | uapi::UI_SET_PHYS
521            | uapi::UI_SET_PROPBIT => Ok(SUCCESS),
522            uapi::UI_DEV_SETUP => self.ui_dev_setup(current_task, arg.into()),
523            uapi::UI_DEV_CREATE => self.ui_dev_create(locked, current_task),
524            uapi::UI_DEV_DESTROY => self.ui_dev_destroy(locked, current_task),
525            // default_ioctl() handles file system related requests and reject
526            // others.
527            _ => {
528                log_warn!("receive unknown ioctl request: {:?}", request);
529                default_ioctl(file, locked, current_task, request, arg)
530            }
531        }
532    }
533
534    fn write(
535        &self,
536        _locked: &mut Locked<FileOpsCore>,
537        _file: &vfs::FileObject,
538        current_task: &starnix_core::task::CurrentTask,
539        _offset: usize,
540        data: &mut dyn vfs::buffers::InputBuffer,
541    ) -> Result<usize, Errno> {
542        let content = data.read_all()?;
543        let event =
544            InputEventPtr::read_from_prefix(current_task, &content).map_err(|_| errno!(EINVAL))?;
545        let mut inner = self.inner.lock();
546
547        match &mut inner.created_device {
548            CreatedDevice::Keyboard(proxy, parser) => {
549                let input_report = parser.handle(event);
550                match input_report {
551                    Ok(Some(report)) => {
552                        if let Some(keyboard_report) = report.keyboard {
553                            let res = proxy.simulate_key_event(
554                                &KeyboardSimulateKeyEventRequest {
555                                    report: Some(keyboard_report),
556                                    ..Default::default()
557                                },
558                                zx::MonotonicInstant::INFINITE,
559                            );
560                            if res.is_err() {
561                                return error!(EIO);
562                            }
563                        }
564                    }
565                    Ok(None) => (),
566                    Err(e) => return Err(e),
567                }
568            }
569            CreatedDevice::Touchscreen(proxy, parser) => {
570                let input_report = parser.handle(event);
571                match input_report {
572                    Ok(Some(report)) => {
573                        if let Some(touch_report) = report.touch {
574                            let res = proxy.simulate_touch_event(
575                                &touch_report,
576                                zx::MonotonicInstant::INFINITE,
577                            );
578                            if res.is_err() {
579                                return error!(EIO);
580                            }
581                        }
582                    }
583                    Ok(None) => (),
584                    Err(e) => return Err(e),
585                }
586            }
587            CreatedDevice::None => return error!(EINVAL),
588        }
589
590        Ok(content.len())
591    }
592
593    fn read(
594        &self,
595        _locked: &mut Locked<FileOpsCore>,
596        _file: &vfs::FileObject,
597        _current_task: &starnix_core::task::CurrentTask,
598        _offset: usize,
599        _data: &mut dyn vfs::buffers::OutputBuffer,
600    ) -> Result<usize, Errno> {
601        log_warn!("uinput FD does not support read().");
602        error!(EINVAL)
603    }
604}
605
606#[derive(Clone)]
607pub struct VirtualDevice {
608    input_id: uapi::input_id,
609    devt: DeviceId,
610    open_files: OpenedFiles,
611    inspect_status: Arc<crate::InputDeviceStatus>,
612}
613
614impl DeviceOps for VirtualDevice {
615    fn open(
616        &self,
617        _locked: &mut Locked<FileOpsCore>,
618        _current_task: &CurrentTask,
619        _id: device_id::DeviceId,
620        _node: &NamespaceNode,
621        _flags: OpenFlags,
622    ) -> Result<Box<dyn FileOps>, Errno> {
623        let mut file_nodes = self.inspect_status.file_nodes.lock();
624        let child_node =
625            self.inspect_status.node.create_child(format!("file_{}", file_nodes.len()));
626        let input_file = match &self.devt {
627            DeviceId::Keyboard => Arc::new(InputFile::new_keyboard(self.input_id, &child_node)),
628            DeviceId::Touchscreen(width, height) => Arc::new(InputFile::new_touch(
629                self.input_id,
630                width.clone(),
631                height.clone(),
632                &child_node,
633            )),
634        };
635
636        file_nodes.push(child_node);
637        input_file.init_inspect_status();
638        self.open_files.lock().push(Arc::downgrade(&input_file));
639
640        Ok(Box::new(crate::input_file::ArcInputFile(input_file)))
641    }
642}
643
644#[cfg(test)]
645mod test {
646    use super::*;
647    use crate::{EventProxyMode, start_input_relays_for_test};
648    use starnix_core::task::Kernel;
649    #[allow(deprecated, reason = "pre-existing usage")]
650    use starnix_core::testing::{AutoReleasableTask, create_kernel_task_and_unlocked};
651    use starnix_core::vfs::FileHandle;
652    use std::sync::Arc;
653    use test_case::test_case;
654
655    async fn new_kernel_objects() -> (
656        Arc<UinputDeviceFile>,
657        Arc<Kernel>,
658        AutoReleasableTask,
659        FileHandle,
660        &'static mut Locked<Unlocked>,
661    ) {
662        #[allow(deprecated, reason = "pre-existing usage")]
663        let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
664        let (input_relay_handle, _, _, _, _, _, _, _, _, _, _, _) =
665            start_input_relays_for_test(locked, &current_task, EventProxyMode::None).await;
666        let inspector = fuchsia_inspect::Inspector::default();
667        let dev = Arc::new(UinputDeviceFile::new(
668            input_relay_handle,
669            Arc::new(inspector.root().create_child("uinput")),
670        ));
671
672        let root_namespace_node = current_task
673            .lookup_path_from_root(locked, ".".into())
674            .expect("failed to get namespace node for root");
675
676        let file_object = FileObject::new(
677            locked,
678            &current_task,
679            Box::new(dev.clone()),
680            // The input node doesn't really live at the root of the filesystem.
681            // But the test doesn't need to be 100% representative of production.
682            root_namespace_node,
683            OpenFlags::empty(),
684        )
685        .expect("FileObject::new failed");
686        (dev, kernel, current_task, file_object, locked)
687    }
688
689    #[test_case(uapi::EV_KEY, vec![uapi::EV_KEY as usize] => Ok(SUCCESS))]
690    #[test_case(uapi::EV_ABS, vec![uapi::EV_ABS as usize] => Ok(SUCCESS))]
691    #[test_case(uapi::EV_REL, vec![] => error!(EPERM))]
692    #[::fuchsia::test]
693    async fn ui_set_evbit(bit: u32, expected_evbits: Vec<usize>) -> Result<SyscallResult, Errno> {
694        let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await;
695        let locked = locked.cast_locked::<Unlocked>();
696        let r = dev.ioctl(
697            locked,
698            &file_object,
699            &current_task,
700            uapi::UI_SET_EVBIT,
701            SyscallArg::from(bit as u64),
702        );
703        for expected_evbit in expected_evbits {
704            assert!(dev.inner.lock().enabled_evbits.get(expected_evbit).unwrap());
705        }
706        r
707    }
708
709    #[::fuchsia::test]
710    async fn ui_set_evbit_call_multi() {
711        let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await;
712        let locked = locked.cast_locked::<Unlocked>();
713        let r = dev.ioctl(
714            locked,
715            &file_object,
716            &current_task,
717            uapi::UI_SET_EVBIT,
718            SyscallArg::from(uapi::EV_KEY as u64),
719        );
720        assert_eq!(r, Ok(SUCCESS));
721        let r = dev.ioctl(
722            locked,
723            &file_object,
724            &current_task,
725            uapi::UI_SET_EVBIT,
726            SyscallArg::from(uapi::EV_ABS as u64),
727        );
728        assert_eq!(r, Ok(SUCCESS));
729        assert!(dev.inner.lock().enabled_evbits.get(uapi::EV_KEY as usize).unwrap());
730        assert!(dev.inner.lock().enabled_evbits.get(uapi::EV_ABS as usize).unwrap());
731    }
732
733    #[::fuchsia::test]
734    async fn ui_set_keybit() {
735        let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await;
736        let locked = locked.cast_locked::<Unlocked>();
737        let r = dev.ioctl(
738            locked,
739            &file_object,
740            &current_task,
741            uapi::UI_SET_KEYBIT,
742            SyscallArg::from(uapi::BTN_TOUCH as u64),
743        );
744        assert_eq!(r, Ok(SUCCESS));
745
746        // also test call multi times.
747        let r = dev.ioctl(
748            locked,
749            &file_object,
750            &current_task,
751            uapi::UI_SET_KEYBIT,
752            SyscallArg::from(uapi::KEY_SPACE as u64),
753        );
754        assert_eq!(r, Ok(SUCCESS));
755    }
756
757    #[::fuchsia::test]
758    async fn ui_set_absbit() {
759        let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await;
760        let locked = locked.cast_locked::<Unlocked>();
761        let r = dev.ioctl(
762            locked,
763            &file_object,
764            &current_task,
765            uapi::UI_SET_ABSBIT,
766            SyscallArg::from(uapi::ABS_MT_SLOT as u64),
767        );
768        assert_eq!(r, Ok(SUCCESS));
769
770        // also test call multi times.
771        let r = dev.ioctl(
772            locked,
773            &file_object,
774            &current_task,
775            uapi::UI_SET_ABSBIT,
776            SyscallArg::from(uapi::ABS_MT_TOUCH_MAJOR as u64),
777        );
778        assert_eq!(r, Ok(SUCCESS));
779    }
780
781    #[::fuchsia::test]
782    async fn ui_set_propbit() {
783        let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await;
784        let locked = locked.cast_locked::<Unlocked>();
785        let r = dev.ioctl(
786            locked,
787            &file_object,
788            &current_task,
789            uapi::UI_SET_PROPBIT,
790            SyscallArg::from(uapi::INPUT_PROP_DIRECT as u64),
791        );
792        assert_eq!(r, Ok(SUCCESS));
793
794        // also test call multi times.
795        let r = dev.ioctl(
796            locked,
797            &file_object,
798            &current_task,
799            uapi::UI_SET_PROPBIT,
800            SyscallArg::from(uapi::INPUT_PROP_DIRECT as u64),
801        );
802        assert_eq!(r, Ok(SUCCESS));
803    }
804}