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::InputEventsRelayHandle;
6use crate::uinput::vfs::{CloseFreeSafe, NamespaceNode};
7
8use bit_vec::BitVec;
9use fidl_fuchsia_ui_test_input::{
10    self as futinput, CoordinateUnit, DisplayDimensions, KeyboardSimulateKeyEventRequest,
11    RegistryRegisterKeyboardAndGetDeviceInfoRequest,
12    RegistryRegisterTouchScreenAndGetDeviceInfoRequest,
13};
14
15use starnix_core::device::kobject::DeviceMetadata;
16use starnix_core::device::{DeviceMode, DeviceOps};
17use starnix_core::fileops_impl_seekless;
18use starnix_core::mm::MemoryAccessorExt;
19use starnix_core::task::{CurrentTask, Kernel};
20use starnix_core::vfs::{self, FileObject, FileOps, fileops_impl_noop_sync};
21use starnix_logging::log_warn;
22use starnix_modules_input_event_conversion::key_linux_to_fuchsia::LinuxKeyboardEventParser;
23use starnix_modules_input_event_conversion::touch_linux_to_fuchsia::LinuxTouchEventParser;
24use starnix_sync::{LockDepMutex, UinputDeviceStateLock};
25use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
26
27use starnix_uapi::errors::Errno;
28use starnix_uapi::open_flags::OpenFlags;
29use starnix_uapi::user_address::{MultiArchUserRef, UserRef};
30use starnix_uapi::{device_id, errno, error, uapi};
31use std::sync::Arc;
32use std::sync::atomic::{AtomicI32, Ordering};
33
34// Return the current uinput API version 5, it also told caller this uinput
35// supports UI_DEV_SETUP.
36const UINPUT_VERSION: u32 = 5;
37
38const DEFAULT_TOUCHSCREEN_WIDTH: i32 = 1280;
39const DEFAULT_TOUCHSCREEN_HEIGHT: i32 = 800;
40const DEFAULT_UINPUT_DEVICE_NAME: &str = "starnix_uinput";
41
42type InputEventPtr = MultiArchUserRef<uapi::input_event, uapi::arch32::input_event>;
43
44#[derive(Clone)]
45enum DeviceId {
46    Keyboard,
47    Touchscreen,
48}
49
50pub fn register_uinput_device(
51    kernel: &Kernel,
52    input_relay_handle: Arc<InputEventsRelayHandle>,
53) -> Result<(), Errno> {
54    let registry = &kernel.device_registry;
55    let misc_class = registry.objects.misc_class();
56    let device = UinputDevice::new(input_relay_handle);
57    registry.register_device(
58        kernel,
59        "uinput".into(),
60        DeviceMetadata::new("uinput".into(), device_id::DeviceId::UINPUT, DeviceMode::Char),
61        misc_class,
62        device,
63    )?;
64    Ok(())
65}
66#[derive(Clone)]
67struct UinputDevice {
68    input_relay_handle: Arc<InputEventsRelayHandle>,
69}
70
71impl UinputDevice {
72    pub fn new(input_relay_handle: Arc<InputEventsRelayHandle>) -> Self {
73        Self { input_relay_handle }
74    }
75}
76
77impl DeviceOps for UinputDevice {
78    fn open(
79        &self,
80        _current_task: &CurrentTask,
81        _id: device_id::DeviceId,
82        _node: &NamespaceNode,
83        _flags: OpenFlags,
84    ) -> Result<Box<dyn FileOps>, Errno> {
85        Ok(Box::new(UinputDeviceFile::new(self.input_relay_handle.clone())))
86    }
87}
88
89enum CreatedDevice {
90    None,
91    Keyboard(futinput::KeyboardSynchronousProxy, LinuxKeyboardEventParser),
92    // LinuxTouchEventParser need to boxed to avoid warning: large-enum-variant.
93    Touchscreen(futinput::TouchScreenSynchronousProxy, Box<LinuxTouchEventParser>),
94}
95
96#[derive(Clone, Copy)]
97struct Range {
98    min: i32,
99    max: i32,
100}
101struct UinputDeviceMutableState {
102    enabled_evbits: BitVec,
103    input_id: Option<uapi::input_id>,
104    name: Option<String>,
105    created_device: CreatedDevice,
106    x_range: Option<Range>,
107    y_range: Option<Range>,
108}
109
110impl UinputDeviceMutableState {
111    fn get_id_and_device_type(&self) -> Option<(uapi::input_id, DeviceId)> {
112        let input_id = match self.input_id {
113            Some(input_id) => input_id,
114            None => return None,
115        };
116        // Currently only support Keyboard and Touchscreen, if evbits contains
117        // EV_ABS, consider it is Touchscreen. This need to be revisit when we
118        // want to support more device types.
119        let device_type = match self.enabled_evbits.get(uapi::EV_ABS as usize) {
120            Some(true) => DeviceId::Touchscreen,
121            Some(false) | None => DeviceId::Keyboard,
122        };
123
124        Some((input_id, device_type))
125    }
126
127    fn teardown_device(&mut self) -> bool {
128        if matches!(self.created_device, CreatedDevice::None) {
129            return false;
130        }
131        self.created_device = CreatedDevice::None;
132        destroy_device();
133        true
134    }
135}
136
137struct UinputDeviceFile {
138    inner: LockDepMutex<UinputDeviceMutableState, UinputDeviceStateLock>,
139    input_relay_handle: Arc<InputEventsRelayHandle>,
140}
141
142impl UinputDeviceFile {
143    pub fn new(input_relay_handle: Arc<InputEventsRelayHandle>) -> Self {
144        Self {
145            inner: UinputDeviceMutableState {
146                enabled_evbits: BitVec::from_elem(uapi::EV_CNT as usize, false),
147                input_id: None,
148                name: None,
149                created_device: CreatedDevice::None,
150                x_range: None,
151                y_range: None,
152            }
153            .into(),
154            input_relay_handle,
155        }
156    }
157
158    /// UI_SET_EVBIT caller pass a u32 as the event type "EV_*" to set this
159    /// uinput device may handle events with the given event type.
160    fn ui_set_evbit(&self, arg: SyscallArg) -> Result<SyscallResult, Errno> {
161        let evbit: u32 = arg.into();
162        match evbit {
163            uapi::EV_KEY | uapi::EV_ABS => {
164                let mut inner = self.inner.lock();
165                if !matches!(inner.created_device, CreatedDevice::None) {
166                    return error!(EINVAL);
167                }
168                inner.enabled_evbits.set(evbit as usize, true);
169                Ok(SUCCESS)
170            }
171            _ => {
172                log_warn!("UI_SET_EVBIT with unsupported evbit {}", evbit);
173                error!(EPERM)
174            }
175        }
176    }
177
178    /// UI_ABS_SETUP caller pass in event codes and min and max value for
179    /// the event code.
180    fn ui_abs_setup(
181        &self,
182        current_task: &CurrentTask,
183        abs_setup: UserRef<starnix_uapi::uinput_abs_setup>,
184    ) -> Result<SyscallResult, Errno> {
185        let setup: starnix_uapi::uinput_abs_setup = current_task.read_object(abs_setup)?;
186        if setup.absinfo.minimum >= setup.absinfo.maximum {
187            return error!(EINVAL);
188        }
189        let code: u32 = setup.code.into();
190        match code {
191            uapi::ABS_MT_POSITION_X => {
192                let mut inner = self.inner.lock();
193                if !matches!(inner.created_device, CreatedDevice::None) {
194                    return error!(EINVAL);
195                }
196                inner.x_range =
197                    Some(Range { min: setup.absinfo.minimum, max: setup.absinfo.maximum });
198            }
199            uapi::ABS_MT_POSITION_Y => {
200                let mut inner = self.inner.lock();
201                if !matches!(inner.created_device, CreatedDevice::None) {
202                    return error!(EINVAL);
203                }
204                inner.y_range =
205                    Some(Range { min: setup.absinfo.minimum, max: setup.absinfo.maximum });
206            }
207            _ => {
208                log_warn!("UI_ABS_SETUP ignore event code {}", setup.code);
209            }
210        }
211        Ok(SUCCESS)
212    }
213
214    /// UI_GET_VERSION caller pass a address for u32 to `arg` to receive the
215    /// uinput version. ioctl returns SUCCESS(0) for success calls, and
216    /// EFAULT(14) for given address is null.
217    fn ui_get_version(
218        &self,
219        current_task: &CurrentTask,
220        user_version: UserRef<u32>,
221    ) -> Result<SyscallResult, Errno> {
222        let response: u32 = UINPUT_VERSION;
223        current_task.write_object(user_version, &response)?;
224        Ok(SUCCESS)
225    }
226
227    /// UI_DEV_SETUP set the name of device and input_id (bustype, vendor id,
228    /// product id, version) to the uinput device.
229    fn ui_dev_setup(
230        &self,
231        current_task: &CurrentTask,
232        user_uinput_setup: UserRef<uapi::uinput_setup>,
233    ) -> Result<SyscallResult, Errno> {
234        let uinput_setup = current_task.read_object(user_uinput_setup)?;
235        let mut inner = self.inner.lock();
236        if !matches!(inner.created_device, CreatedDevice::None) {
237            return error!(EINVAL);
238        }
239        inner.input_id = Some(uinput_setup.id);
240        // Parse name from null-terminated C string in uinput_setup.name
241        let name_bytes: &[u8] = zerocopy::IntoBytes::as_bytes(&uinput_setup.name);
242        let name_len = name_bytes.iter().position(|&c| c == 0).unwrap_or(name_bytes.len());
243        let name = std::str::from_utf8(&name_bytes[..name_len])
244            .ok()
245            .filter(|s| !s.is_empty())
246            .map(|s| s.to_string())
247            .unwrap_or_else(|| DEFAULT_UINPUT_DEVICE_NAME.to_string());
248        inner.name = Some(name);
249        Ok(SUCCESS)
250    }
251
252    fn ui_dev_create(&self, _current_task: &CurrentTask) -> Result<SyscallResult, Errno> {
253        {
254            let inner = self.inner.lock();
255            if !matches!(inner.created_device, CreatedDevice::None) {
256                return error!(EINVAL);
257            }
258            if inner.get_id_and_device_type().is_none() {
259                return error!(EINVAL);
260            }
261        }
262
263        // Only eng and userdebug builds include the `fuchsia.ui.test.input` service.
264        let registry = match fuchsia_component::client::connect_to_protocol_sync::<
265            futinput::RegistryMarker,
266        >() {
267            Ok(proxy) => Some(proxy),
268            Err(_) => {
269                log_warn!("Could not connect to fuchsia.ui.test.input/Registry");
270                None
271            }
272        };
273        self.ui_dev_create_inner(registry)
274    }
275
276    /// UI_DEV_CREATE calls create the uinput device with given information
277    /// from previous ioctl() calls.
278    fn ui_dev_create_inner(
279        &self,
280        // Takes `registry` arg so we can manually inject a mock registry in unit tests.
281        registry: Option<futinput::RegistrySynchronousProxy>,
282    ) -> Result<SyscallResult, Errno> {
283        let (input_id, device_type, name, x_range, y_range) = {
284            let inner = self.inner.lock();
285            if !matches!(inner.created_device, CreatedDevice::None) {
286                return error!(EINVAL);
287            }
288
289            let (input_id, device_type) = match inner.get_id_and_device_type() {
290                Some((id, dev)) => (id, dev),
291                None => return error!(EINVAL),
292            };
293            let name = inner.name.clone().unwrap_or_else(|| DEFAULT_UINPUT_DEVICE_NAME.to_string());
294            (input_id, device_type, name, inner.x_range, inner.y_range)
295        };
296
297        let proxy = match registry {
298            Some(proxy) => proxy,
299            None => {
300                log_warn!("No Registry available for Uinput.");
301                return error!(EPERM);
302            }
303        };
304
305        let (created_device, device_id) = match device_type {
306            DeviceId::Keyboard => {
307                let (key_client, key_server) =
308                    fidl::endpoints::create_sync_proxy::<futinput::KeyboardMarker>();
309
310                // Register a keyboard
311                let register_res = proxy.register_keyboard_and_get_device_info(
312                    RegistryRegisterKeyboardAndGetDeviceInfoRequest {
313                        device: Some(key_server),
314                        ..Default::default()
315                    },
316                    zx::MonotonicInstant::INFINITE,
317                );
318
319                match register_res {
320                    Ok(resp) => match resp.device_id {
321                        Some(device_id) => {
322                            let created_device = CreatedDevice::Keyboard(
323                                key_client,
324                                LinuxKeyboardEventParser::create(),
325                            );
326                            Ok((created_device, device_id))
327                        }
328                        None => {
329                            log_warn!(
330                                "register_keyboard_and_get_device_info response does not include a device_id"
331                            );
332                            error!(EPERM)
333                        }
334                    },
335                    Err(e) => {
336                        log_warn!("Uinput could not register Keyboard device to Registry: {:?}", e);
337                        error!(EPERM)
338                    }
339                }
340            }
341            DeviceId::Touchscreen => {
342                let (touch_client, touch_server) =
343                    fidl::endpoints::create_sync_proxy::<futinput::TouchScreenMarker>();
344
345                let (display_width, display_height) =
346                    (*self.input_relay_handle.display_size.lock())
347                        .filter(|&(w, h)| w > 0 && h > 0)
348                        .unwrap_or((DEFAULT_TOUCHSCREEN_WIDTH, DEFAULT_TOUCHSCREEN_HEIGHT));
349                let (min_x, max_x) = x_range.map(|r| (r.min, r.max)).unwrap_or((0, display_width));
350                let (min_y, max_y) = y_range.map(|r| (r.min, r.max)).unwrap_or((0, display_height));
351                if min_x >= max_x || min_y >= max_y {
352                    return error!(EINVAL);
353                }
354
355                let request = RegistryRegisterTouchScreenAndGetDeviceInfoRequest {
356                    device: Some(touch_server),
357                    coordinate_unit: Some(CoordinateUnit::RegisteredDimensions),
358                    display_dimensions: Some(DisplayDimensions {
359                        min_x: min_x.into(),
360                        max_x: max_x.into(),
361                        min_y: min_y.into(),
362                        max_y: max_y.into(),
363                    }),
364                    ..Default::default()
365                };
366
367                // Register a touchscreen
368                let register_res = proxy.register_touch_screen_and_get_device_info(
369                    request,
370                    zx::MonotonicInstant::INFINITE,
371                );
372
373                match register_res {
374                    Ok(resp) => match resp.device_id {
375                        Some(device_id) => {
376                            let created_device = CreatedDevice::Touchscreen(
377                                touch_client,
378                                Box::new(LinuxTouchEventParser::create()),
379                            );
380                            Ok((created_device, device_id))
381                        }
382                        None => {
383                            log_warn!(
384                                "register_touch_screen_and_get_device_info response does not include a device_id"
385                            );
386                            error!(EPERM)
387                        }
388                    },
389                    Err(e) => {
390                        log_warn!(
391                            "Uinput could not register TouchScreen device to Registry: {:?}",
392                            e
393                        );
394                        error!(EPERM)
395                    }
396                }
397            }
398        }?;
399
400        {
401            let mut inner = self.inner.lock();
402            if !matches!(inner.created_device, CreatedDevice::None) {
403                return error!(EINVAL);
404            }
405            inner.created_device = created_device;
406        }
407
408        // NOTE: The UI test registry often broadcasts `OnDeviceChanged(Added)` to
409        // `input_event_relay.rs` before `register_*_and_get_device_info` returns
410        // `device_id` here. When this happens, `register_and_add_device` initializes
411        // `/dev/input/eventX` and broadcasts the `KOBJECT_ADD` uevent with fallback IDs
412        // (`TOUCH_INPUT_ID` / `KEYBOARD_INPUT_ID` and `"starnix_touch"` / `"starnix_buttons"`).
413        //
414        // This is safe for synchronous callers that open the device node after `UI_DEV_CREATE`
415        // returns, because both paths share an `Arc<LockDepMutex<InputDeviceInfo, ...>>`,
416        // allowing `update_uinput_device` to overwrite those fallbacks before userspace opens
417        // the device node.
418        //
419        // However, there is a race window for asynchronous watchers (such as `ueventd`,
420        // `libinput`, or Android's `EventHub` reacting directly to the `KOBJECT_ADD` uevent
421        // or an inotify event on `/dev/input`): if they open `/dev/input/eventX` and query
422        // `EVIOCGID` or `EVIOCGNAME` before `update_uinput_device` executes below, they may
423        // observe the fallback IDs and names.
424        //
425        // TODO(https://fxbug.dev/502662433): Update `fuchsia.ui.test.input.Registry` so that
426        // caller-provided device metadata (e.g. name, input_id) can be passed during initial
427        // registration. This will allow `OnDeviceChanged(Added)` to immediately carry the
428        // correct metadata and eliminate the race window for asynchronous uevent watchers.
429        //
430        // Device metadata in `device_infos` remains alive until the device is removed via
431        // `OnDeviceChanged(Removed)`, preventing race conditions where an early `UI_DEV_DESTROY`
432        // could cause an in-flight addition to lose its metadata.
433        self.input_relay_handle.update_uinput_device(device_id, input_id, name);
434        new_device();
435
436        Ok(SUCCESS)
437    }
438
439    fn ui_dev_destroy(&self, _current_task: &CurrentTask) -> Result<SyscallResult, Errno> {
440        let mut inner = self.inner.lock();
441        if !inner.teardown_device() {
442            return error!(EPERM);
443        }
444        Ok(SUCCESS)
445    }
446}
447
448// TODO(b/312467059): Remove once ESC -> Power workaround can be remove.
449static COUNT_OF_UINPUT_DEVICE: AtomicI32 = AtomicI32::new(0);
450
451fn new_device() {
452    let _ = COUNT_OF_UINPUT_DEVICE.fetch_add(1, Ordering::SeqCst);
453}
454
455fn destroy_device() {
456    let _ = COUNT_OF_UINPUT_DEVICE.fetch_sub(1, Ordering::SeqCst);
457}
458
459pub fn uinput_running() -> bool {
460    COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst) > 0
461}
462
463/// `UinputDeviceFile` doesn't implement the `close` method.
464impl CloseFreeSafe for UinputDeviceFile {}
465
466impl Drop for UinputDeviceFile {
467    fn drop(&mut self) {
468        let inner = self.inner.get_mut();
469        inner.teardown_device();
470    }
471}
472
473impl FileOps for UinputDeviceFile {
474    fileops_impl_seekless!();
475    fileops_impl_noop_sync!();
476
477    fn ioctl(
478        &self,
479        _file: &FileObject,
480        current_task: &CurrentTask,
481        request: u32,
482        arg: SyscallArg,
483    ) -> Result<SyscallResult, Errno> {
484        match request {
485            uapi::UI_GET_VERSION => self.ui_get_version(current_task, arg.into()),
486            uapi::UI_SET_EVBIT => self.ui_set_evbit(arg),
487            uapi::UI_ABS_SETUP => self.ui_abs_setup(current_task, arg.into()),
488            // `fuchsia.ui.test.input.Registry` does not use some uinput ioctl
489            // request, just ignore the request and return SUCCESS, even args
490            // is invalid.
491            uapi::UI_SET_KEYBIT
492            | uapi::UI_SET_ABSBIT
493            | uapi::UI_SET_PHYS
494            | uapi::UI_SET_PROPBIT => Ok(SUCCESS),
495            uapi::UI_DEV_SETUP => self.ui_dev_setup(current_task, arg.into()),
496            uapi::UI_DEV_CREATE => self.ui_dev_create(current_task),
497            uapi::UI_DEV_DESTROY => self.ui_dev_destroy(current_task),
498            // default_ioctl() handles file system related requests and reject
499            // others.
500            _ => {
501                log_warn!("receive unknown ioctl request: {:?}", request);
502                error!(ENOTTY)
503            }
504        }
505    }
506
507    fn write(
508        &self,
509        _file: &vfs::FileObject,
510        current_task: &starnix_core::task::CurrentTask,
511        _offset: usize,
512        data: &mut dyn vfs::buffers::InputBuffer,
513    ) -> Result<usize, Errno> {
514        let content = data.read_all()?;
515        let event =
516            InputEventPtr::read_from_prefix(current_task, &content).map_err(|_| errno!(EINVAL))?;
517        let mut inner = self.inner.lock();
518
519        match &mut inner.created_device {
520            CreatedDevice::Keyboard(proxy, parser) => {
521                let input_report = parser.handle(event);
522                match input_report {
523                    Ok(Some(report)) => {
524                        if let Some(keyboard_report) = report.keyboard {
525                            let res = proxy.simulate_key_event(
526                                &KeyboardSimulateKeyEventRequest {
527                                    pressed_keys: keyboard_report.pressed_keys3,
528                                    ..Default::default()
529                                },
530                                zx::MonotonicInstant::INFINITE,
531                            );
532                            if res.is_err() {
533                                return error!(EIO);
534                            }
535                        }
536                    }
537                    Ok(None) => (),
538                    Err(e) => return Err(e),
539                }
540            }
541            CreatedDevice::Touchscreen(proxy, parser) => {
542                let input_report: Result<Option<futinput::TouchInputReport>, Errno> =
543                    parser.handle(event);
544                match input_report {
545                    Ok(Some(report)) => {
546                        let res =
547                            proxy.simulate_touch_event(&report, zx::MonotonicInstant::INFINITE);
548                        if res.is_err() {
549                            return error!(EIO);
550                        }
551                    }
552                    Ok(None) => (),
553                    Err(e) => return Err(e),
554                }
555            }
556            CreatedDevice::None => return error!(EINVAL),
557        }
558
559        Ok(content.len())
560    }
561
562    fn read(
563        &self,
564        _file: &vfs::FileObject,
565        _current_task: &starnix_core::task::CurrentTask,
566        _offset: usize,
567        _data: &mut dyn vfs::buffers::OutputBuffer,
568    ) -> Result<usize, Errno> {
569        log_warn!("uinput FD does not support read().");
570        error!(EINVAL)
571    }
572}
573
574#[cfg(test)]
575mod test {
576    use super::*;
577    use crate::{EventProxyMode, start_input_relays_for_test};
578    use futures::TryStreamExt;
579    use starnix_core::testing::{map_memory, spawn_kernel_and_run};
580    use starnix_core::vfs::FileHandle;
581    use starnix_uapi::user_address::UserAddress;
582    use std::sync::Arc;
583    use test_case::test_case;
584
585    static UINPUT_RUNNING_LOCK: starnix_sync::Mutex<()> = starnix_sync::Mutex::new(());
586
587    fn ensure_uinput_running_lock() -> starnix_sync::MutexGuard<'static, ()> {
588        UINPUT_RUNNING_LOCK.lock()
589    }
590
591    async fn new_kernel_objects(current_task: &CurrentTask) -> (Arc<UinputDeviceFile>, FileHandle) {
592        let (input_relay_handle, _, _, _, _, _, _, _, _, _, _, _) =
593            start_input_relays_for_test(current_task, EventProxyMode::None).await;
594        let dev = Arc::new(UinputDeviceFile::new(input_relay_handle));
595
596        let root_namespace_node = current_task
597            .lookup_path_from_root(".".into())
598            .expect("failed to get namespace node for root");
599
600        let file_object = FileObject::new(
601            current_task,
602            Box::new(dev.clone()),
603            // The input node doesn't really live at the root of the filesystem.
604            // But the test doesn't need to be 100% representative of production.
605            root_namespace_node,
606            OpenFlags::empty(),
607        )
608        .expect("FileObject::new failed");
609        (dev, file_object)
610    }
611
612    #[test_case(uapi::EV_KEY, vec![uapi::EV_KEY as usize] => Ok(SUCCESS))]
613    #[test_case(uapi::EV_ABS, vec![uapi::EV_ABS as usize] => Ok(SUCCESS))]
614    #[test_case(uapi::EV_REL, vec![] => error!(EPERM))]
615    #[::fuchsia::test]
616    async fn ui_set_evbit(bit: u32, expected_evbits: Vec<usize>) -> Result<SyscallResult, Errno> {
617        spawn_kernel_and_run(async move |current_task| {
618            let (dev, file_object) = new_kernel_objects(current_task).await;
619            let r = dev.ioctl(
620                &file_object,
621                current_task,
622                uapi::UI_SET_EVBIT,
623                SyscallArg::from(bit as u64),
624            );
625            for expected_evbit in expected_evbits {
626                assert!(dev.inner.lock().enabled_evbits.get(expected_evbit).unwrap());
627            }
628            r
629        })
630        .await
631    }
632
633    #[::fuchsia::test]
634    async fn ui_set_evbit_call_multi() {
635        spawn_kernel_and_run(async move |current_task| {
636            let (dev, file_object) = new_kernel_objects(current_task).await;
637            let r = dev.ioctl(
638                &file_object,
639                current_task,
640                uapi::UI_SET_EVBIT,
641                SyscallArg::from(uapi::EV_KEY as u64),
642            );
643            assert_eq!(r, Ok(SUCCESS));
644            let r = dev.ioctl(
645                &file_object,
646                current_task,
647                uapi::UI_SET_EVBIT,
648                SyscallArg::from(uapi::EV_ABS as u64),
649            );
650            assert_eq!(r, Ok(SUCCESS));
651            assert!(dev.inner.lock().enabled_evbits.get(uapi::EV_KEY as usize).unwrap());
652            assert!(dev.inner.lock().enabled_evbits.get(uapi::EV_ABS as usize).unwrap());
653        })
654        .await;
655    }
656
657    #[::fuchsia::test]
658    async fn ui_set_keybit() {
659        spawn_kernel_and_run(async move |current_task| {
660            let (dev, file_object) = new_kernel_objects(current_task).await;
661            let r = dev.ioctl(
662                &file_object,
663                current_task,
664                uapi::UI_SET_KEYBIT,
665                SyscallArg::from(uapi::BTN_TOUCH as u64),
666            );
667            assert_eq!(r, Ok(SUCCESS));
668
669            // also test call multi times.
670            let r = dev.ioctl(
671                &file_object,
672                current_task,
673                uapi::UI_SET_KEYBIT,
674                SyscallArg::from(uapi::KEY_SPACE as u64),
675            );
676            assert_eq!(r, Ok(SUCCESS));
677        })
678        .await;
679    }
680
681    #[::fuchsia::test]
682    async fn ui_set_absbit() {
683        spawn_kernel_and_run(async move |current_task| {
684            let (dev, file_object) = new_kernel_objects(current_task).await;
685            let r = dev.ioctl(
686                &file_object,
687                current_task,
688                uapi::UI_SET_ABSBIT,
689                SyscallArg::from(uapi::ABS_MT_SLOT as u64),
690            );
691            assert_eq!(r, Ok(SUCCESS));
692
693            // also test call multi times.
694            let r = dev.ioctl(
695                &file_object,
696                current_task,
697                uapi::UI_SET_ABSBIT,
698                SyscallArg::from(uapi::ABS_MT_TOUCH_MAJOR as u64),
699            );
700            assert_eq!(r, Ok(SUCCESS));
701        })
702        .await;
703    }
704
705    #[::fuchsia::test]
706    async fn ui_set_propbit() {
707        spawn_kernel_and_run(async move |current_task| {
708            let (dev, file_object) = new_kernel_objects(current_task).await;
709            let r = dev.ioctl(
710                &file_object,
711                current_task,
712                uapi::UI_SET_PROPBIT,
713                SyscallArg::from(uapi::INPUT_PROP_DIRECT as u64),
714            );
715            assert_eq!(r, Ok(SUCCESS));
716
717            // also test call multi times.
718            let r = dev.ioctl(
719                &file_object,
720                current_task,
721                uapi::UI_SET_PROPBIT,
722                SyscallArg::from(uapi::INPUT_PROP_DIRECT as u64),
723            );
724            assert_eq!(r, Ok(SUCCESS));
725        })
726        .await;
727    }
728
729    #[::fuchsia::test]
730    async fn ui_dev_destroy_uncreated() {
731        spawn_kernel_and_run(async move |current_task| {
732            let (dev, file_object) = new_kernel_objects(current_task).await;
733            let _guard = ensure_uinput_running_lock();
734            assert!(!uinput_running());
735            let r =
736                dev.ioctl(&file_object, current_task, uapi::UI_DEV_DESTROY, SyscallArg::from(0u64));
737            assert_eq!(r, error!(EPERM));
738            assert!(!uinput_running());
739        })
740        .await;
741    }
742
743    #[::fuchsia::test]
744    async fn ui_dev_create_updates_existing_device_info() {
745        spawn_kernel_and_run(async move |current_task| {
746            let (dev, _file_object) = new_kernel_objects(current_task).await;
747            let device_id = 42;
748            let initial_input_id = uapi::input_id { bustype: 1, vendor: 2, product: 3, version: 4 };
749            let initial_info =
750                crate::InputDeviceInfo::new(initial_input_id, "initial_name".to_string());
751            dev.input_relay_handle.device_infos.lock().insert(device_id, initial_info.clone());
752
753            let updated_input_id =
754                uapi::input_id { bustype: 10, vendor: 20, product: 30, version: 40 };
755            let updated_name = "updated_uinput_device".to_string();
756
757            // Exercise update_uinput_device without holding dev.inner.lock(), matching
758            // the updated lock ordering in ui_dev_create_inner where inner lock is dropped before
759            // updating uinput device metadata.
760            dev.input_relay_handle.update_uinput_device(
761                device_id,
762                updated_input_id,
763                updated_name.clone(),
764            );
765
766            // Verify that the existing InputDeviceInfoHandle was updated in-place without LockDep panic.
767            let locked_info = initial_info.lock();
768            assert_eq!(locked_info.input_id, updated_input_id);
769            assert_eq!(locked_info.name, updated_name);
770        })
771        .await;
772    }
773
774    #[::fuchsia::test]
775    async fn ui_dev_setup_name_parsing() {
776        spawn_kernel_and_run(async move |current_task| {
777            let (dev, file_object) = new_kernel_objects(current_task).await;
778
779            // 1. Normal null-terminated string
780            let mut setup = uapi::uinput_setup::default();
781            setup.id = uapi::input_id { bustype: 3, vendor: 0x1234, product: 0x5678, version: 1 };
782            let name_bytes = b"my_uinput_touch\0";
783            for (i, &b) in name_bytes.iter().enumerate() {
784                setup.name[i] = b as _;
785            }
786
787            let user_setup = map_memory(
788                current_task,
789                UserAddress::default(),
790                std::mem::size_of::<uapi::uinput_setup>() as u64,
791            );
792            current_task.write_object(UserRef::new(user_setup), &setup).expect("write_object");
793
794            let r = dev.ioctl(
795                &file_object,
796                current_task,
797                uapi::UI_DEV_SETUP,
798                SyscallArg::from(user_setup.ptr() as u64),
799            );
800            assert_eq!(r, Ok(SUCCESS));
801
802            {
803                let inner = dev.inner.lock();
804                assert_eq!(inner.input_id, Some(setup.id));
805                assert_eq!(inner.name, Some("my_uinput_touch".to_string()));
806            }
807
808            // 2. Non-null terminated (full buffer)
809            let mut setup_full = uapi::uinput_setup::default();
810            setup_full.id = setup.id;
811            setup_full.name.fill(b'a' as _);
812            current_task.write_object(UserRef::new(user_setup), &setup_full).expect("write_object");
813            let r = dev.ioctl(
814                &file_object,
815                current_task,
816                uapi::UI_DEV_SETUP,
817                SyscallArg::from(user_setup.ptr() as u64),
818            );
819            assert_eq!(r, Ok(SUCCESS));
820            {
821                let inner = dev.inner.lock();
822                assert_eq!(inner.name, Some("a".repeat(setup_full.name.len())));
823            }
824
825            // 3. Invalid UTF-8 bytes before null terminator fallback to "starnix_uinput"
826            let mut setup_invalid = uapi::uinput_setup::default();
827            setup_invalid.id = setup.id;
828            setup_invalid.name[0] = 0xff as u8 as _;
829            setup_invalid.name[1] = 0;
830            current_task
831                .write_object(UserRef::new(user_setup), &setup_invalid)
832                .expect("write_object");
833            let r = dev.ioctl(
834                &file_object,
835                current_task,
836                uapi::UI_DEV_SETUP,
837                SyscallArg::from(user_setup.ptr() as u64),
838            );
839            assert_eq!(r, Ok(SUCCESS));
840            {
841                let inner = dev.inner.lock();
842                assert_eq!(inner.name, Some(DEFAULT_UINPUT_DEVICE_NAME.to_string()));
843            }
844
845            // 4. Empty name (first byte is 0) fallback to "starnix_uinput"
846            let mut setup_empty = uapi::uinput_setup::default();
847            setup_empty.id = setup.id;
848            setup_empty.name[0] = 0;
849            current_task
850                .write_object(UserRef::new(user_setup), &setup_empty)
851                .expect("write_object");
852            let r = dev.ioctl(
853                &file_object,
854                current_task,
855                uapi::UI_DEV_SETUP,
856                SyscallArg::from(user_setup.ptr() as u64),
857            );
858            assert_eq!(r, Ok(SUCCESS));
859            {
860                let inner = dev.inner.lock();
861                assert_eq!(inner.name, Some(DEFAULT_UINPUT_DEVICE_NAME.to_string()));
862            }
863        })
864        .await;
865    }
866
867    fn spawn_mock_registry() -> (
868        futinput::RegistrySynchronousProxy,
869        std::thread::JoinHandle<()>,
870        Arc<std::sync::atomic::AtomicBool>,
871        Arc<starnix_sync::Mutex<Option<DisplayDimensions>>>,
872    ) {
873        let (client, server) = fidl::endpoints::create_endpoints::<futinput::RegistryMarker>();
874        let proxy = client.into_sync_proxy();
875        let received_request = Arc::new(std::sync::atomic::AtomicBool::new(false));
876        let received_flag = received_request.clone();
877        let captured_display_dimensions = Arc::new(starnix_sync::Mutex::new(None));
878        let dimensions_slot = captured_display_dimensions.clone();
879        let handle = std::thread::spawn(move || {
880            let mut executor = fuchsia_async::LocalExecutor::default();
881            executor.run_singlethreaded(async move {
882                let mut stream = server.into_stream();
883                while let Ok(Some(request)) = stream.try_next().await {
884                    received_flag.store(true, Ordering::SeqCst);
885                    match request {
886                        futinput::RegistryRequest::RegisterTouchScreenAndGetDeviceInfo {
887                            payload,
888                            responder,
889                        } => {
890                            assert_eq!(
891                                payload.coordinate_unit,
892                                Some(CoordinateUnit::RegisteredDimensions)
893                            );
894                            *dimensions_slot.lock() = payload.display_dimensions;
895                            let _ = responder.send(
896                                futinput::RegistryRegisterTouchScreenAndGetDeviceInfoResponse {
897                                    device_id: Some(101),
898                                    ..Default::default()
899                                },
900                            );
901                        }
902                        futinput::RegistryRequest::RegisterKeyboardAndGetDeviceInfo {
903                            responder,
904                            ..
905                        } => {
906                            let _ = responder.send(
907                                futinput::RegistryRegisterKeyboardAndGetDeviceInfoResponse {
908                                    device_id: Some(102),
909                                    ..Default::default()
910                                },
911                            );
912                        }
913                        _ => {}
914                    }
915                }
916            });
917        });
918        (proxy, handle, received_request, captured_display_dimensions)
919    }
920
921    #[::fuchsia::test]
922    async fn ui_dev_create_keyboard_and_destroy() {
923        spawn_kernel_and_run(async move |current_task| {
924            let (dev, file_object) = new_kernel_objects(current_task).await;
925            let _guard = ensure_uinput_running_lock();
926            assert!(!uinput_running());
927            let (proxy, join_handle, received, _) = spawn_mock_registry();
928
929            let input_id = uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 };
930            {
931                let mut inner = dev.inner.lock();
932                inner.input_id = Some(input_id);
933                inner.name = Some("test_keyboard".to_string());
934                inner.enabled_evbits.set(uapi::EV_KEY as usize, true);
935            }
936
937            let r = dev.ui_dev_create_inner(Some(proxy));
938            assert_eq!(r, Ok(SUCCESS));
939            assert!(uinput_running());
940            assert!(received.load(Ordering::SeqCst));
941
942            // Verify device was updated in relay handle
943            {
944                let device_infos = dev.input_relay_handle.device_infos.lock();
945                let info = device_infos.get(&102).expect("device info found").lock();
946                assert_eq!(info.name, "test_keyboard");
947                assert_eq!(info.input_id, input_id);
948            }
949
950            // Destroy device via ioctl
951            let r =
952                dev.ioctl(&file_object, current_task, uapi::UI_DEV_DESTROY, SyscallArg::from(0u64));
953            assert_eq!(r, Ok(SUCCESS));
954            assert!(!uinput_running());
955            // Device metadata remains in device_infos until removed via OnDeviceChanged(Removed)
956            assert!(dev.input_relay_handle.device_infos.lock().get(&102).is_some());
957
958            // A second UI_DEV_DESTROY returns EPERM
959            let r =
960                dev.ioctl(&file_object, current_task, uapi::UI_DEV_DESTROY, SyscallArg::from(0u64));
961            assert_eq!(r, error!(EPERM));
962
963            std::mem::drop(dev);
964            std::mem::drop(file_object);
965            let _ = join_handle.join();
966        })
967        .await;
968    }
969
970    #[::fuchsia::test]
971    async fn ui_dev_create_touchscreen_display_size_fallback_and_drop_cleanup() {
972        spawn_kernel_and_run(async move |current_task| {
973            let (input_relay_handle, _, _, _, _, _, _, _, _, _, _, _) =
974                start_input_relays_for_test(current_task, EventProxyMode::None).await;
975            let _guard = ensure_uinput_running_lock();
976            assert!(!uinput_running());
977
978            // Pre-set display_size to (0, 0) to ensure zero display sizes are filtered
979            // out and fall back to DEFAULT_TOUCHSCREEN_WIDTH and HEIGHT without panicking.
980            *input_relay_handle.display_size.lock() = Some((0, 0));
981
982            let dev = Arc::new(UinputDeviceFile::new(input_relay_handle.clone()));
983            let (proxy, join_handle, received, captured_dimensions) = spawn_mock_registry();
984
985            let input_id = uapi::input_id { bustype: 3, vendor: 5, product: 6, version: 7 };
986            {
987                let mut inner = dev.inner.lock();
988                inner.input_id = Some(input_id);
989                inner.name = Some("test_touchscreen".to_string());
990                inner.enabled_evbits.set(uapi::EV_ABS as usize, true);
991                // Leave x_range and y_range as None to trigger fallback to display_size
992            }
993
994            let r = dev.ui_dev_create_inner(Some(proxy));
995            assert_eq!(r, Ok(SUCCESS));
996            assert!(uinput_running());
997            assert!(received.load(Ordering::SeqCst));
998            assert_eq!(
999                *captured_dimensions.lock(),
1000                Some(DisplayDimensions {
1001                    min_x: 0,
1002                    max_x: DEFAULT_TOUCHSCREEN_WIDTH.into(),
1003                    min_y: 0,
1004                    max_y: DEFAULT_TOUCHSCREEN_HEIGHT.into(),
1005                })
1006            );
1007
1008            // Verify device info was inserted
1009            assert!(input_relay_handle.device_infos.lock().get(&101).is_some());
1010
1011            // Dropping dev should trigger Drop::drop and clean up the device and running count
1012            std::mem::drop(dev);
1013            assert!(!uinput_running());
1014            // Device metadata remains in device_infos until removed via OnDeviceChanged(Removed)
1015            assert!(input_relay_handle.device_infos.lock().get(&101).is_some());
1016
1017            let _ = join_handle.join();
1018        })
1019        .await;
1020    }
1021
1022    #[::fuchsia::test]
1023    async fn ui_dev_create_touchscreen_explicit_ranges() {
1024        spawn_kernel_and_run(async move |current_task| {
1025            let (dev, file_object) = new_kernel_objects(current_task).await;
1026            let _guard = ensure_uinput_running_lock();
1027            assert!(!uinput_running());
1028            let (proxy, join_handle, _, captured_dimensions) = spawn_mock_registry();
1029
1030            let input_id = uapi::input_id { bustype: 3, vendor: 10, product: 20, version: 30 };
1031            {
1032                let mut inner = dev.inner.lock();
1033                inner.input_id = Some(input_id);
1034                inner.name = Some("touch_ranges".to_string());
1035                inner.enabled_evbits.set(uapi::EV_ABS as usize, true);
1036                inner.x_range = Some(Range { min: 100, max: 2000 });
1037                inner.y_range = Some(Range { min: 50, max: 1000 });
1038            }
1039
1040            let r = dev.ui_dev_create_inner(Some(proxy));
1041            assert_eq!(r, Ok(SUCCESS));
1042            assert!(uinput_running());
1043            assert_eq!(
1044                *captured_dimensions.lock(),
1045                Some(DisplayDimensions { min_x: 100, max_x: 2000, min_y: 50, max_y: 1000 })
1046            );
1047            assert!(dev.input_relay_handle.device_infos.lock().get(&101).is_some());
1048
1049            // Destroy and verify clean
1050            let r = dev.ui_dev_destroy(current_task);
1051            assert_eq!(r, Ok(SUCCESS));
1052            assert!(!uinput_running());
1053            // Device metadata remains in device_infos until removed via OnDeviceChanged(Removed)
1054            assert!(dev.input_relay_handle.device_infos.lock().get(&101).is_some());
1055
1056            std::mem::drop(dev);
1057            std::mem::drop(file_object);
1058            let _ = join_handle.join();
1059        })
1060        .await;
1061    }
1062
1063    #[::fuchsia::test]
1064    async fn ui_dev_create_already_created_returns_einval() {
1065        spawn_kernel_and_run(async move |current_task| {
1066            let (dev, file_object) = new_kernel_objects(current_task).await;
1067            let _guard = ensure_uinput_running_lock();
1068            assert!(!uinput_running());
1069            let (proxy, join_handle, _, _) = spawn_mock_registry();
1070
1071            let input_id = uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 };
1072            {
1073                let mut inner = dev.inner.lock();
1074                inner.input_id = Some(input_id);
1075                inner.name = Some("test_keyboard".to_string());
1076                inner.enabled_evbits.set(uapi::EV_KEY as usize, true);
1077            }
1078
1079            let r = dev.ui_dev_create_inner(Some(proxy));
1080            assert_eq!(r, Ok(SUCCESS));
1081            assert!(uinput_running());
1082            assert_eq!(COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst), 1);
1083
1084            // Second UI_DEV_CREATE on same fd must return EINVAL and not increment device count
1085            let (proxy2, join_handle2, _, _) = spawn_mock_registry();
1086            let r = dev.ui_dev_create_inner(Some(proxy2));
1087            assert_eq!(r, error!(EINVAL));
1088            assert_eq!(COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst), 1);
1089
1090            // Destroy and verify clean
1091            let r = dev.ui_dev_destroy(current_task);
1092            assert_eq!(r, Ok(SUCCESS));
1093            assert!(!uinput_running());
1094            assert_eq!(COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst), 0);
1095
1096            std::mem::drop(dev);
1097            std::mem::drop(file_object);
1098            let _ = join_handle.join();
1099            let _ = join_handle2.join();
1100        })
1101        .await;
1102    }
1103
1104    #[::fuchsia::test]
1105    async fn ui_abs_setup_validates_ranges() {
1106        spawn_kernel_and_run(async move |current_task| {
1107            let (dev, file_object) = new_kernel_objects(current_task).await;
1108            let user_setup = map_memory(
1109                current_task,
1110                UserAddress::default(),
1111                std::mem::size_of::<starnix_uapi::uinput_abs_setup>() as u64,
1112            );
1113
1114            // Valid range
1115            let valid_setup = starnix_uapi::uinput_abs_setup {
1116                code: uapi::ABS_MT_POSITION_X as u16,
1117                absinfo: uapi::input_absinfo { minimum: 0, maximum: 1080, ..Default::default() },
1118                ..Default::default()
1119            };
1120            current_task.write_object(UserRef::new(user_setup), &valid_setup).expect("write");
1121            let r = dev.ioctl(
1122                &file_object,
1123                current_task,
1124                uapi::UI_ABS_SETUP,
1125                SyscallArg::from(user_setup.ptr() as u64),
1126            );
1127            assert_eq!(r, Ok(SUCCESS));
1128
1129            // Invalid range where minimum >= maximum
1130            let invalid_setup = starnix_uapi::uinput_abs_setup {
1131                code: uapi::ABS_MT_POSITION_X as u16,
1132                absinfo: uapi::input_absinfo { minimum: 100, maximum: 100, ..Default::default() },
1133                ..Default::default()
1134            };
1135            current_task.write_object(UserRef::new(user_setup), &invalid_setup).expect("write");
1136            let r = dev.ioctl(
1137                &file_object,
1138                current_task,
1139                uapi::UI_ABS_SETUP,
1140                SyscallArg::from(user_setup.ptr() as u64),
1141            );
1142            assert_eq!(r, error!(EINVAL));
1143        })
1144        .await;
1145    }
1146
1147    #[::fuchsia::test]
1148    async fn ui_dev_create_touchscreen_invalid_ranges_returns_einval() {
1149        spawn_kernel_and_run(async move |current_task| {
1150            let (dev, file_object) = new_kernel_objects(current_task).await;
1151            let _guard = ensure_uinput_running_lock();
1152            let (proxy, join_handle, _, _) = spawn_mock_registry();
1153
1154            let input_id = uapi::input_id { bustype: 3, vendor: 10, product: 20, version: 30 };
1155            {
1156                let mut inner = dev.inner.lock();
1157                inner.input_id = Some(input_id);
1158                inner.name = Some("touch_invalid_ranges".to_string());
1159                inner.enabled_evbits.set(uapi::EV_ABS as usize, true);
1160                // Invalid ranges min >= max
1161                inner.x_range = Some(Range { min: 2000, max: 100 });
1162                inner.y_range = Some(Range { min: 50, max: 1000 });
1163            }
1164
1165            let r = dev.ui_dev_create_inner(Some(proxy));
1166            assert_eq!(r, error!(EINVAL));
1167            assert!(!uinput_running());
1168
1169            std::mem::drop(dev);
1170            std::mem::drop(file_object);
1171            let _ = join_handle.join();
1172        })
1173        .await;
1174    }
1175
1176    #[::fuchsia::test]
1177    async fn ui_dev_create_failure_preserves_running_count_and_state() {
1178        spawn_kernel_and_run(async move |current_task| {
1179            let (dev, file_object) = new_kernel_objects(current_task).await;
1180            let _guard = ensure_uinput_running_lock();
1181            assert_eq!(COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst), 0);
1182            assert!(!uinput_running());
1183
1184            let input_id = uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 };
1185            {
1186                let mut inner = dev.inner.lock();
1187                inner.input_id = Some(input_id);
1188                inner.name = Some("test_keyboard".to_string());
1189                inner.enabled_evbits.set(uapi::EV_KEY as usize, true);
1190            }
1191
1192            // Attempting to create the device when no registry is available (None) must fail.
1193            let r = dev.ui_dev_create_inner(None);
1194            assert_eq!(r, error!(EPERM));
1195
1196            // Verify created_device remains None and COUNT_OF_UINPUT_DEVICE was not incremented.
1197            assert!(matches!(dev.inner.lock().created_device, CreatedDevice::None));
1198            assert_eq!(COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst), 0);
1199            assert!(!uinput_running());
1200
1201            // Dropping dev must not decrement COUNT_OF_UINPUT_DEVICE.
1202            std::mem::drop(dev);
1203            std::mem::drop(file_object);
1204            assert_eq!(COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst), 0);
1205            assert!(!uinput_running());
1206        })
1207        .await;
1208    }
1209
1210    #[::fuchsia::test]
1211    async fn ui_dev_create_before_setup_returns_einval() {
1212        spawn_kernel_and_run(async move |current_task| {
1213            let (dev, file_object) = new_kernel_objects(current_task).await;
1214            let _guard = ensure_uinput_running_lock();
1215            assert!(!uinput_running());
1216
1217            // Calling UI_DEV_CREATE before UI_DEV_SETUP without registry must return EINVAL (not EPERM).
1218            let r = dev.ui_dev_create_inner(None);
1219            assert_eq!(r, error!(EINVAL));
1220
1221            // Also verify calling via ioctl directly returns EINVAL without attempting registry connection.
1222            let r =
1223                dev.ioctl(&file_object, current_task, uapi::UI_DEV_CREATE, SyscallArg::from(0u64));
1224            assert_eq!(r, error!(EINVAL));
1225        })
1226        .await;
1227    }
1228
1229    #[::fuchsia::test]
1230    async fn ui_dev_create_touchscreen_single_axis_range_fallback() {
1231        spawn_kernel_and_run(async move |current_task| {
1232            let (dev, file_object) = new_kernel_objects(current_task).await;
1233            let _guard = ensure_uinput_running_lock();
1234            assert!(!uinput_running());
1235            let (proxy, join_handle, _, captured_dimensions) = spawn_mock_registry();
1236
1237            let input_id = uapi::input_id { bustype: 3, vendor: 10, product: 20, version: 30 };
1238            {
1239                let mut inner = dev.inner.lock();
1240                inner.input_id = Some(input_id);
1241                inner.name = Some("touch_single_axis".to_string());
1242                inner.enabled_evbits.set(uapi::EV_ABS as usize, true);
1243                // Only configure X axis, leave Y axis as None to fallback to display height
1244                inner.x_range = Some(Range { min: 100, max: 2000 });
1245            }
1246
1247            let r = dev.ui_dev_create_inner(Some(proxy));
1248            assert_eq!(r, Ok(SUCCESS));
1249            assert!(uinput_running());
1250            assert_eq!(
1251                *captured_dimensions.lock(),
1252                Some(DisplayDimensions { min_x: 100, max_x: 2000, min_y: 0, max_y: 1200 })
1253            );
1254
1255            std::mem::drop(dev);
1256            std::mem::drop(file_object);
1257            let _ = join_handle.join();
1258        })
1259        .await;
1260    }
1261
1262    #[::fuchsia::test]
1263    async fn ui_setup_after_create_returns_einval() {
1264        spawn_kernel_and_run(async move |current_task| {
1265            let (dev, file_object) = new_kernel_objects(current_task).await;
1266            let _guard = ensure_uinput_running_lock();
1267            let (proxy, join_handle, _, _) = spawn_mock_registry();
1268
1269            let input_id = uapi::input_id { bustype: 3, vendor: 10, product: 20, version: 30 };
1270            {
1271                let mut inner = dev.inner.lock();
1272                inner.input_id = Some(input_id);
1273                inner.name = Some("created_dev".to_string());
1274                inner.enabled_evbits.set(uapi::EV_KEY as usize, true);
1275            }
1276
1277            let r = dev.ui_dev_create_inner(Some(proxy));
1278            assert_eq!(r, Ok(SUCCESS));
1279
1280            // UI_SET_EVBIT after create must return EINVAL.
1281            let r = dev.ioctl(
1282                &file_object,
1283                current_task,
1284                uapi::UI_SET_EVBIT,
1285                SyscallArg::from(uapi::EV_KEY as u64),
1286            );
1287            assert_eq!(r, error!(EINVAL));
1288
1289            // UI_ABS_SETUP after create must return EINVAL.
1290            let user_setup = map_memory(
1291                current_task,
1292                UserAddress::default(),
1293                std::mem::size_of::<starnix_uapi::uinput_abs_setup>() as u64,
1294            );
1295            let valid_setup = starnix_uapi::uinput_abs_setup {
1296                code: uapi::ABS_MT_POSITION_X as u16,
1297                absinfo: uapi::input_absinfo { minimum: 0, maximum: 100, ..Default::default() },
1298                ..Default::default()
1299            };
1300            current_task.write_object(UserRef::new(user_setup), &valid_setup).expect("write");
1301            let r = dev.ioctl(
1302                &file_object,
1303                current_task,
1304                uapi::UI_ABS_SETUP,
1305                SyscallArg::from(user_setup.ptr() as u64),
1306            );
1307            assert_eq!(r, error!(EINVAL));
1308
1309            // UI_DEV_SETUP after create must return EINVAL.
1310            let user_dev_setup = map_memory(
1311                current_task,
1312                UserAddress::default(),
1313                std::mem::size_of::<uapi::uinput_setup>() as u64,
1314            );
1315            let dev_setup = uapi::uinput_setup {
1316                id: uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1317                ..Default::default()
1318            };
1319            current_task.write_object(UserRef::new(user_dev_setup), &dev_setup).expect("write");
1320            let r = dev.ioctl(
1321                &file_object,
1322                current_task,
1323                uapi::UI_DEV_SETUP,
1324                SyscallArg::from(user_dev_setup.ptr() as u64),
1325            );
1326            assert_eq!(r, error!(EINVAL));
1327
1328            std::mem::drop(dev);
1329            std::mem::drop(file_object);
1330            let _ = join_handle.join();
1331        })
1332        .await;
1333    }
1334}