| // Copyright 2026 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. | |
| #[cfg(test)] | |
| use crate::direct_input::DirectInputDevice; | |
| use crate::direct_input::DirectInputManager; | |
| use fidl::endpoints::{ClientEnd, RequestStream as _}; | |
| use fidl_fuchsia_input_injection::{ | |
| InputDeviceRegistryRegisterAndGetDeviceInfoResponse, InputDeviceRegistryRequest, | |
| InputDeviceRegistryRequestStream, | |
| }; | |
| use fidl_fuchsia_input_report as fir; | |
| use futures::StreamExt; | |
| use starnix_core::task::Kernel; | |
| use starnix_logging::log_warn; | |
| use starnix_uapi::errors::Errno; | |
| use std::sync::Arc; | |
| use zx; | |
| /// Server implementing `fuchsia.input.injection.InputDeviceRegistry`. | |
| /// | |
| /// Enables test frameworks (such as `input-helper`) to dynamically inject input | |
| /// devices and report streams into Starnix without hardcoded device configs, | |
| /// delegating device creation, minor allocation, reader loops, and ownership gating | |
| /// to [`DirectInputManager`]. | |
| pub struct InputDeviceRegistry { | |
| direct_input: Arc<DirectInputManager>, | |
| } | |
| impl std::fmt::Debug for InputDeviceRegistry { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| f.debug_struct("InputDeviceRegistry").field("direct_input", &self.direct_input).finish() | |
| } | |
| } | |
| impl InputDeviceRegistry { | |
| /// Creates a new `InputDeviceRegistry` backed by `direct_input`. | |
| pub fn new(direct_input: Arc<DirectInputManager>) -> Arc<Self> { | |
| Arc::new(Self { direct_input }) | |
| } | |
| /// Serves incoming requests from an `InputDeviceRegistryRequestStream`. | |
| pub async fn serve_stream( | |
| self: Arc<Self>, | |
| mut stream: InputDeviceRegistryRequestStream, | |
| ) -> Result<(), fidl::Error> { | |
| while let Some(request) = stream.next().await { | |
| match request? { | |
| InputDeviceRegistryRequest::Register { device, control_handle: _ } => { | |
| if let Err(e) = self.register_device(device).await { | |
| log_warn!("InputDeviceRegistry::Register failed: {:?}", e); | |
| } | |
| } | |
| InputDeviceRegistryRequest::RegisterAndGetDeviceInfo { device, responder } => { | |
| match self.register_device(device).await { | |
| Ok(device_id) => { | |
| let _ = responder.send( | |
| InputDeviceRegistryRegisterAndGetDeviceInfoResponse { | |
| device_id: Some(device_id), | |
| ..Default::default() | |
| }, | |
| ); | |
| } | |
| Err(e) => { | |
| log_warn!( | |
| "InputDeviceRegistry::RegisterAndGetDeviceInfo failed: {:?}", | |
| e | |
| ); | |
| let _ = responder.send( | |
| InputDeviceRegistryRegisterAndGetDeviceInfoResponse::default(), | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// Registers an input device client by delegating to [`DirectInputManager::add_device`]. | |
| pub async fn register_device( | |
| &self, | |
| device_client_end: ClientEnd<fir::InputDeviceMarker>, | |
| ) -> Result<u32, Errno> { | |
| self.direct_input.add_device(device_client_end.into_proxy(), None).await | |
| } | |
| /// Removes and unregisters an injected device. | |
| #[cfg(test)] | |
| pub fn remove_device(&self, device_id: u32) { | |
| self.direct_input.remove_device(device_id); | |
| } | |
| /// Returns an injected device if it is currently registered. | |
| #[cfg(test)] | |
| pub(crate) fn device(&self, device_id: u32) -> Option<DirectInputDevice> { | |
| self.direct_input.device(device_id) | |
| } | |
| } | |
| /// Serves `fuchsia.input.injection.InputDeviceRegistry` on the given request stream | |
| /// using the kernel's active `DirectInputManager`. | |
| pub fn serve_input_device_registry(kernel: &Kernel, stream: InputDeviceRegistryRequestStream) { | |
| let Some(direct_input) = kernel.expando.peek::<Arc<DirectInputManager>>() else { | |
| log_warn!("DirectInputManager not available; rejecting InputDeviceRegistry request"); | |
| stream.control_handle().shutdown_with_epitaph(zx::Status::NOT_SUPPORTED); | |
| return; | |
| }; | |
| let registry = InputDeviceRegistry::new((*direct_input).clone()); | |
| kernel.kthreads.spawn_future( | |
| move || async move { | |
| if let Err(e) = registry.serve_stream(stream).await { | |
| log_warn!("InputDeviceRegistry serve error: {e:?}"); | |
| } | |
| }, | |
| "input_device_registry", | |
| ); | |
| } | |
| #[cfg(test)] | |
| #[allow(clippy::disallowed_types)] | |
| mod tests { | |
| use super::*; | |
| use assert_matches::assert_matches; | |
| use fidl::endpoints::{self, RequestStream, ServerEnd}; | |
| use fidl_fuchsia_input_injection::{self as finputinjection, InputDeviceRegistryMarker}; | |
| use fuchsia_async as fasync; | |
| use starnix_core::device::DeviceMode; | |
| use starnix_core::testing::spawn_kernel_and_run; | |
| use starnix_core::vfs::{FileHandle, VecOutputBuffer}; | |
| use starnix_sync::Mutex; | |
| use starnix_uapi::device_id::{DeviceId as StarnixDeviceId, INPUT_MAJOR}; | |
| use starnix_uapi::errno; | |
| use starnix_uapi::errors::{EAGAIN, Errno}; | |
| use zerocopy::FromBytes; | |
| const INPUT_EVENT_SIZE: usize = std::mem::size_of::<starnix_uapi::input_event>(); | |
| fn read_uapi_events( | |
| file: &FileHandle, | |
| current_task: &starnix_core::task::CurrentTask, | |
| ) -> Vec<starnix_uapi::input_event> { | |
| std::iter::from_fn(|| { | |
| let mut event_bytes = VecOutputBuffer::new(INPUT_EVENT_SIZE); | |
| match file.read(current_task, &mut event_bytes) { | |
| Ok(INPUT_EVENT_SIZE) => Some( | |
| starnix_uapi::input_event::read_from_bytes(Vec::from(event_bytes).as_slice()) | |
| .map_err(|_| anyhow::anyhow!("failed to read input_event from buffer")), | |
| ), | |
| Ok(other_size) => Some(Err(anyhow::anyhow!( | |
| "got {} bytes (expected {})", | |
| other_size, | |
| INPUT_EVENT_SIZE | |
| ))), | |
| Err(Errno { code: EAGAIN, .. }) => None, | |
| Err(other_error) => Some(Err(anyhow::anyhow!("read failed: {:?}", other_error))), | |
| } | |
| }) | |
| .enumerate() | |
| .map(|(i, read_res)| match read_res { | |
| Ok(event) => event, | |
| Err(e) => panic!("unexpected result {:?} on iteration {}", e, i), | |
| }) | |
| .collect() | |
| } | |
| fn make_test_descriptor() -> fir::DeviceDescriptor { | |
| fir::DeviceDescriptor { | |
| device_information: Some(fir::DeviceInformation { | |
| vendor_id: Some(0x18d1), | |
| product_id: Some(0x2345), | |
| version: Some(1), | |
| product_name: Some("Test Touch".to_string()), | |
| ..Default::default() | |
| }), | |
| touch: Some(fir::TouchDescriptor { | |
| input: Some(fir::TouchInputDescriptor { | |
| contacts: Some(vec![fir::ContactInputDescriptor { | |
| position_x: Some(fir::Axis { | |
| range: fir::Range { min: 0, max: 1080 }, | |
| unit: fir::Unit { type_: fir::UnitType::Meters, exponent: -3 }, | |
| }), | |
| position_y: Some(fir::Axis { | |
| range: fir::Range { min: 0, max: 2400 }, | |
| unit: fir::Unit { type_: fir::UnitType::Meters, exponent: -3 }, | |
| }), | |
| ..Default::default() | |
| }]), | |
| max_contacts: Some(10), | |
| ..Default::default() | |
| }), | |
| ..Default::default() | |
| }), | |
| ..Default::default() | |
| } | |
| } | |
| fn make_keyboard_descriptor() -> fir::DeviceDescriptor { | |
| fir::DeviceDescriptor { | |
| device_information: Some(fir::DeviceInformation { | |
| vendor_id: Some(0x18d1), | |
| product_id: Some(0x1234), | |
| version: Some(1), | |
| product_name: Some("Test Keyboard".to_string()), | |
| ..Default::default() | |
| }), | |
| keyboard: Some(fir::KeyboardDescriptor { | |
| input: Some(fir::KeyboardInputDescriptor::default()), | |
| ..Default::default() | |
| }), | |
| ..Default::default() | |
| } | |
| } | |
| #[::fuchsia::test] | |
| async fn test_registration_and_event_dispatch_v2() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let (reader_sender, reader_receiver) = | |
| futures::channel::oneshot::channel::<fir::InputReportsReaderV2ControlHandle>(); | |
| let mut reader_sender_opt = Some(reader_sender); | |
| let (ack_sender, ack_receiver) = futures::channel::oneshot::channel::<()>(); | |
| let mut ack_sender_opt = Some(ack_sender); | |
| // Mock InputDevice server | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| let mut inner_tasks = Vec::new(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| let reader_stream = reader.into_stream(); | |
| let control_handle = reader_stream.control_handle(); | |
| if let Some(sender) = reader_sender_opt.take() { | |
| let _ = sender.send(control_handle); | |
| } | |
| responder.send(1).unwrap(); | |
| let ack_sender_opt_inner = ack_sender_opt.take(); | |
| inner_tasks.push(fasync::Task::local(async move { | |
| let mut ack_sender_opt_inner = ack_sender_opt_inner; | |
| let mut reader_stream = reader_stream; | |
| while let Some(req) = reader_stream.next().await { | |
| match req.unwrap() { | |
| fir::InputReportsReaderV2Request::AcknowledgeReports { | |
| .. | |
| } => { | |
| if let Some(sender) = ack_sender_opt_inner.take() { | |
| let _ = sender.send(()); | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let device_id = registry.register_device(device_client).await.unwrap(); | |
| let injected = registry.device(device_id).expect("device should be registered"); | |
| let file = injected.input_device.open_test(¤t_task).unwrap(); | |
| let control_handle = reader_receiver.await.unwrap(); | |
| // Inject a touch report | |
| let touch_report = fir::InputReport { | |
| event_time: Some(100_000_000), | |
| touch: Some(fir::TouchInputReport { | |
| contacts: Some(vec![fir::ContactInputReport { | |
| contact_id: Some(1), | |
| position_x: Some(500), | |
| position_y: Some(600), | |
| pressure: Some(50), | |
| ..Default::default() | |
| }]), | |
| ..Default::default() | |
| }), | |
| ..Default::default() | |
| }; | |
| control_handle.send_on_input_reports(vec![touch_report], 1).unwrap(); | |
| // Deterministic sync: wait until reader acknowledges report | |
| ack_receiver.await.unwrap(); | |
| let events = read_uapi_events(&file, ¤t_task); | |
| assert!(!events.is_empty()); | |
| assert!(events.iter().any(|e| e.type_ == starnix_uapi::EV_ABS as u16 | |
| && e.code == starnix_uapi::ABS_MT_POSITION_X as u16 | |
| && e.value == 500)); | |
| assert!(events.iter().any(|e| e.type_ == starnix_uapi::EV_ABS as u16 | |
| && e.code == starnix_uapi::ABS_MT_POSITION_Y as u16 | |
| && e.value == 600)); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_pre_open_buffering() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let (reader_sender, reader_receiver) = | |
| futures::channel::oneshot::channel::<fir::InputReportsReaderV2ControlHandle>(); | |
| let mut reader_sender_opt = Some(reader_sender); | |
| let (ack_sender, ack_receiver) = futures::channel::oneshot::channel::<()>(); | |
| let mut ack_sender_opt = Some(ack_sender); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| let mut inner_tasks = Vec::new(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| let reader_stream = reader.into_stream(); | |
| let control_handle = reader_stream.control_handle(); | |
| if let Some(sender) = reader_sender_opt.take() { | |
| let _ = sender.send(control_handle); | |
| } | |
| responder.send(1).unwrap(); | |
| let ack_inner = ack_sender_opt.take(); | |
| inner_tasks.push(fasync::Task::local(async move { | |
| let mut ack_inner = ack_inner; | |
| let mut reader_stream = reader_stream; | |
| while let Some(req) = reader_stream.next().await { | |
| match req.unwrap() { | |
| fir::InputReportsReaderV2Request::AcknowledgeReports { | |
| .. | |
| } => { | |
| if let Some(sender) = ack_inner.take() { | |
| let _ = sender.send(()); | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let device_id = registry.register_device(device_client).await.unwrap(); | |
| let injected = registry.device(device_id).expect("device should be registered"); | |
| let control_handle = reader_receiver.await.unwrap(); | |
| // Inject a touch report BEFORE opening the file | |
| let touch_report = fir::InputReport { | |
| event_time: Some(150_000_000), | |
| touch: Some(fir::TouchInputReport { | |
| contacts: Some(vec![fir::ContactInputReport { | |
| contact_id: Some(1), | |
| position_x: Some(700), | |
| position_y: Some(800), | |
| pressure: Some(40), | |
| ..Default::default() | |
| }]), | |
| ..Default::default() | |
| }), | |
| ..Default::default() | |
| }; | |
| control_handle.send_on_input_reports(vec![touch_report], 1).unwrap(); | |
| // Deterministic sync: wait until reader acknowledges report | |
| ack_receiver.await.unwrap(); | |
| // Now open the device *after* the event has already been dispatched | |
| let file = injected.input_device.open_test(¤t_task).unwrap(); | |
| let events = read_uapi_events(&file, ¤t_task); | |
| assert!(!events.is_empty()); | |
| assert!(events.iter().any(|e| e.type_ == starnix_uapi::EV_ABS as u16 | |
| && e.code == starnix_uapi::ABS_MT_POSITION_X as u16 | |
| && e.value == 700)); | |
| assert!(events.iter().any(|e| e.type_ == starnix_uapi::EV_ABS as u16 | |
| && e.code == starnix_uapi::ABS_MT_POSITION_Y as u16 | |
| && e.value == 800)); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_injected_device_minor_in_direct_input_minor_range() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { responder, .. } => { | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let device_id = registry.register_device(device_client).await.unwrap(); | |
| let dev = registry.device(device_id).expect("device should be registered"); | |
| assert!(crate::DIRECT_INPUT_MINOR_RANGE.contains(&dev.minor)); | |
| assert_eq!(dev.minor, crate::DIRECT_INPUT_MINOR_RANGE.start); | |
| assert!( | |
| kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, dev.minor), DeviceMode::Char) | |
| .is_ok() | |
| ); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_reports_not_delivered_when_ownership_inactive() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| // Explicitly inactive ownership | |
| direct_input.set_ownership_active(false); | |
| let registry = InputDeviceRegistry::new(direct_input.clone()); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let (reader_sender, reader_receiver) = | |
| futures::channel::oneshot::channel::<fir::InputReportsReaderV2ControlHandle>(); | |
| let mut reader_sender_opt = Some(reader_sender); | |
| let (ack_sender, mut ack_receiver) = futures::channel::mpsc::channel::<()>(16); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| let mut inner_tasks = Vec::new(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| let reader_stream = reader.into_stream(); | |
| let control_handle = reader_stream.control_handle(); | |
| if let Some(sender) = reader_sender_opt.take() { | |
| let _ = sender.send(control_handle); | |
| } | |
| responder.send(1).unwrap(); | |
| let mut ack_sender_clone = ack_sender.clone(); | |
| inner_tasks.push(fasync::Task::local(async move { | |
| let mut reader_stream = reader_stream; | |
| while let Some(req) = reader_stream.next().await { | |
| match req.unwrap() { | |
| fir::InputReportsReaderV2Request::AcknowledgeReports { | |
| .. | |
| } => { | |
| let _ = ack_sender_clone.try_send(()); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let device_id = registry.register_device(device_client).await.unwrap(); | |
| let injected = registry.device(device_id).expect("device should be registered"); | |
| let file = injected.input_device.open_test(¤t_task).unwrap(); | |
| // While ownership is inactive, reader task is not spawned and no reports are delivered. | |
| let events = read_uapi_events(&file, ¤t_task); | |
| assert!(events.is_empty(), "reports must not be delivered when ownership is inactive"); | |
| // Now activate ownership, which resumes readers and spawns the reader task | |
| direct_input.set_ownership_active(true); | |
| let control_handle = reader_receiver.await.unwrap(); | |
| // Inject a touch report | |
| let touch_report = fir::InputReport { | |
| event_time: Some(200_000_000), | |
| touch: Some(fir::TouchInputReport { | |
| contacts: Some(vec![fir::ContactInputReport { | |
| contact_id: Some(1), | |
| position_x: Some(400), | |
| position_y: Some(500), | |
| pressure: Some(30), | |
| ..Default::default() | |
| }]), | |
| ..Default::default() | |
| }), | |
| ..Default::default() | |
| }; | |
| control_handle.send_on_input_reports(vec![touch_report], 1).unwrap(); | |
| // Wait for reader acknowledgment | |
| ack_receiver.next().await.unwrap(); | |
| let events_after = read_uapi_events(&file, ¤t_task); | |
| assert!( | |
| !events_after.is_empty(), | |
| "reports must be delivered after ownership is activated" | |
| ); | |
| assert!(events_after.iter().any(|e| e.type_ == starnix_uapi::EV_ABS as u16 | |
| && e.code == starnix_uapi::ABS_MT_POSITION_X as u16 | |
| && e.value == 400)); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_rollback_on_closed_channel() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let (closed_client, closed_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| drop(closed_server); | |
| let result = registry.register_device(closed_client).await; | |
| assert!(result.is_err()); | |
| assert_eq!(result.unwrap_err(), errno!(EIO)); | |
| assert!(registry.device(1).is_none()); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_register_and_get_device_info_fidl() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (registry_client, registry_server) = | |
| endpoints::create_endpoints::<InputDeviceRegistryMarker>(); | |
| let reg_clone = registry.clone(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let _ = reg_clone.serve_stream(registry_server.into_stream()).await; | |
| })); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let (_reader_sender, _reader_receiver) = | |
| futures::channel::oneshot::channel::<ServerEnd<fir::InputReportsReaderV2Marker>>(); | |
| let mut reader_sender_opt = Some(_reader_sender); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| if let Some(sender) = reader_sender_opt.take() { | |
| let _ = sender.send(reader); | |
| } | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let proxy = registry_client.into_proxy(); | |
| let res = proxy.register_and_get_device_info(device_client).await.unwrap(); | |
| assert!(res.device_id.is_some()); | |
| let id = res.device_id.unwrap(); | |
| assert!(registry.device(id).is_some()); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_register_one_way_fidl() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (registry_client, registry_server) = | |
| endpoints::create_endpoints::<InputDeviceRegistryMarker>(); | |
| let reg_clone = registry.clone(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let _ = reg_clone.serve_stream(registry_server.into_stream()).await; | |
| })); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let (_reader_sender, _reader_receiver) = | |
| futures::channel::oneshot::channel::<ServerEnd<fir::InputReportsReaderV2Marker>>(); | |
| let mut reader_sender_opt = Some(_reader_sender); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| if let Some(sender) = reader_sender_opt.take() { | |
| let _ = sender.send(reader); | |
| } | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let proxy = registry_client.into_proxy(); | |
| proxy.register(device_client).unwrap(); | |
| // Wait deterministically for registration to complete | |
| while registry.device(1).is_none() { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| assert!(registry.device(1).is_some()); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_disconnect_teardown() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let (reader_sender, reader_receiver) = | |
| futures::channel::oneshot::channel::<ServerEnd<fir::InputReportsReaderV2Marker>>(); | |
| let mut reader_sender_opt = Some(reader_sender); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| if let Some(sender) = reader_sender_opt.take() { | |
| let _ = sender.send(reader); | |
| } | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let device_id = registry.register_device(device_client).await.unwrap(); | |
| assert!(registry.device(device_id).is_some()); | |
| // Receive reader server end and immediately drop it to simulate client disconnect. | |
| let reader_server = reader_receiver.await.unwrap(); | |
| drop(reader_server); | |
| // Wait deterministically for disconnect handler to trigger | |
| while registry.device(device_id).is_some() { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| assert!(registry.device(device_id).is_none()); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_multi_device_registration_and_minor_allocation() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input.clone()); | |
| let mut _tasks = Vec::new(); | |
| let readers = Arc::new(Mutex::new(Vec::new())); | |
| // Device 1: Touch | |
| let (device_client_1, device_server_1) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let readers_1 = readers.clone(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server_1.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| readers_1.lock().push(reader); | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| // Device 2: Keyboard | |
| let (device_client_2, device_server_2) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let readers_2 = readers.clone(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server_2.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_keyboard_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| readers_2.lock().push(reader); | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let (res_1, res_2) = futures::join!( | |
| registry.register_device(device_client_1), | |
| registry.register_device(device_client_2), | |
| ); | |
| let id_1 = res_1.unwrap(); | |
| let id_2 = res_2.unwrap(); | |
| assert_ne!(id_1, id_2); | |
| let dev_1 = registry.device(id_1).expect("device 1 registered"); | |
| let dev_2 = registry.device(id_2).expect("device 2 registered"); | |
| assert_ne!(dev_1.minor, dev_2.minor); | |
| assert!(crate::DIRECT_INPUT_MINOR_RANGE.contains(&dev_1.minor)); | |
| assert!(crate::DIRECT_INPUT_MINOR_RANGE.contains(&dev_2.minor)); | |
| assert_eq!(direct_input.num_devices(), 2); | |
| registry.remove_device(id_1); | |
| assert!(registry.device(id_1).is_none()); | |
| assert!(registry.device(id_2).is_some()); | |
| assert_eq!(direct_input.num_devices(), 1); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_registry_drop_unregisters_devices() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input.clone()); | |
| let mut _tasks = Vec::new(); | |
| let readers = Arc::new(Mutex::new(Vec::new())); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let readers_clone = readers.clone(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| readers_clone.lock().push(reader); | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let id = registry.register_device(device_client).await.unwrap(); | |
| let dev = registry.device(id).expect("device should be registered"); | |
| let minor = dev.minor; | |
| assert!( | |
| kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_ok() | |
| ); | |
| // Dropping the registry and direct_input must cleanly unregister devices | |
| drop(registry); | |
| drop(direct_input); | |
| while kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_ok() | |
| { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_immediate_reader_termination_does_not_leak() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input.clone()); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| let server_task = fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { | |
| reader, | |
| responder, | |
| .. | |
| } => { | |
| // Immediately drop reader endpoint so stream terminates | |
| drop(reader); | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| }); | |
| let id = registry.register_device(device_client).await.unwrap(); | |
| let minor = registry.device(id).expect("device should be registered").minor; | |
| assert!( | |
| kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_ok(), | |
| "char device should be present in device registry" | |
| ); | |
| // Wait deterministically for reader loop termination and remove_device to complete. | |
| while registry.device(id).is_some() { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| assert!(registry.device(id).is_none()); | |
| assert_eq!(direct_input.num_devices(), 0); | |
| // Assert that the mock InputDevice server's request stream ends (channel closed). | |
| server_task.await; | |
| // Wait for the async kthread unregistration to complete and remove the char device. | |
| while kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_ok() | |
| { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| assert!( | |
| kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_err(), | |
| "char device must be unregistered" | |
| ); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_register_and_get_device_info_failure_does_not_close_stream() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input); | |
| let mut _tasks = Vec::new(); | |
| let (registry_client, registry_server) = | |
| endpoints::create_endpoints::<InputDeviceRegistryMarker>(); | |
| let registry_clone = registry.clone(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let _ = registry_clone.serve_stream(registry_server.into_stream()).await; | |
| })); | |
| let registry_proxy = registry_client.into_proxy(); | |
| // 1. Send invalid device client whose channel is immediately closed. | |
| let (closed_client, closed_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| drop(closed_server); | |
| let res = registry_proxy | |
| .register_and_get_device_info(closed_client) | |
| .await | |
| .expect("request should not fail FIDL call"); | |
| assert_eq!(res.device_id, None); | |
| // 2. Stream should remain open; a valid registration succeeds. | |
| let (valid_client, valid_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = valid_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { responder, .. } => { | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let res2 = registry_proxy | |
| .register_and_get_device_info(valid_client) | |
| .await | |
| .expect("stream should still be open and return Ok"); | |
| assert!(res2.device_id.is_some()); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_remove_device_aborts_reader_task() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| direct_input.set_ownership_active(true); | |
| let registry = InputDeviceRegistry::new(direct_input.clone()); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| fir::InputDeviceRequest::GetInputReportsReaderV2 { responder, .. } => { | |
| responder.send(16).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let id = registry.register_device(device_client).await.unwrap(); | |
| assert!(registry.device(id).is_some()); | |
| registry.remove_device(id); | |
| assert!(registry.device(id).is_none()); | |
| assert_eq!(direct_input.num_devices(), 0); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_disconnect_while_ownership_inactive_removes_device() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| // Ownership starts inactive (!is_ownership_active() and is_paused: true). | |
| let direct_input = DirectInputManager::new(Arc::downgrade(&kernel), None, None); | |
| assert!(!direct_input.is_ownership_active()); | |
| let registry = InputDeviceRegistry::new(direct_input.clone()); | |
| let mut _tasks = Vec::new(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| if let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let device_id = registry.register_device(device_client).await.unwrap(); | |
| let dev = registry.device(device_id).expect("device must be registered"); | |
| let minor = dev.minor; | |
| assert_eq!(direct_input.num_devices(), 1); | |
| // Assert that the char device is registered in device_registry. | |
| assert!( | |
| kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_ok(), | |
| "char device should be present in device registry" | |
| ); | |
| // Wait for disconnect handler to trigger and remove the device while inactive. | |
| while registry.device(device_id).is_some() { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| assert!(registry.device(device_id).is_none()); | |
| assert_eq!(direct_input.num_devices(), 0); | |
| // Wait for the async kthread unregistration to complete and remove the char device. | |
| while kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_ok() | |
| { | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(2), | |
| )) | |
| .await; | |
| } | |
| assert!( | |
| kernel | |
| .device_registry | |
| .get_device(StarnixDeviceId::new(INPUT_MAJOR, minor), DeviceMode::Char) | |
| .is_err(), | |
| "char device must be unregistered" | |
| ); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_serve_input_device_registry_without_manager_closes_stream() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| // Verify that no DirectInputManager exists in expando initially. | |
| assert!(kernel.expando.peek::<Arc<DirectInputManager>>().is_none()); | |
| let (client, server) = | |
| endpoints::create_endpoints::<finputinjection::InputDeviceRegistryMarker>(); | |
| serve_input_device_registry(&kernel, server.into_stream()); | |
| // Since no manager exists, the stream must be closed with NOT_SUPPORTED epitaph. | |
| let proxy = client.into_proxy(); | |
| let mut event_stream = proxy.take_event_stream(); | |
| let event = event_stream.next().await; | |
| assert_matches!( | |
| event, | |
| Some(Err(fidl::Error::ClientChannelClosed { epitaph, .. })) if epitaph == zx::Status::NOT_SUPPORTED | |
| ); | |
| // Serving the registry without a manager must NOT lazily initialize one. | |
| assert!(kernel.expando.peek::<Arc<DirectInputManager>>().is_none()); | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_framebuffer_and_registry_share_same_direct_input_manager() { | |
| spawn_kernel_and_run(async move |current_task| { | |
| let kernel = current_task.kernel().clone(); | |
| // Simulate framebuffer module initializing the DirectInputManager via get_or_init_direct_input. | |
| let fb_manager = crate::get_or_init_direct_input(&kernel); | |
| // Repeated calls return the identical instance. | |
| let fb_manager_second = crate::get_or_init_direct_input(&kernel); | |
| assert!(Arc::ptr_eq(&fb_manager, &fb_manager_second)); | |
| // Peek into expando to ensure the registry path accesses the exact same manager. | |
| let peeked = kernel | |
| .expando | |
| .peek::<Arc<DirectInputManager>>() | |
| .expect("DirectInputManager should be present in expando"); | |
| assert!(Arc::ptr_eq(&fb_manager, &peeked)); | |
| // Serve the registry and verify it operates on the shared manager. | |
| let (client, server) = | |
| endpoints::create_endpoints::<finputinjection::InputDeviceRegistryMarker>(); | |
| serve_input_device_registry(&kernel, server.into_stream()); | |
| let mut _tasks = Vec::new(); | |
| let proxy = client.into_proxy(); | |
| let (device_client, device_server) = | |
| endpoints::create_endpoints::<fir::InputDeviceMarker>(); | |
| _tasks.push(fasync::Task::local(async move { | |
| let mut stream = device_server.into_stream(); | |
| while let Some(request) = stream.next().await { | |
| match request.unwrap() { | |
| fir::InputDeviceRequest::GetDescriptor { responder } => { | |
| responder.send(&make_test_descriptor()).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| })); | |
| let response = proxy.register_and_get_device_info(device_client).await.unwrap(); | |
| let device_id = response.device_id.unwrap(); | |
| // The registered device must exist in the shared fb_manager! | |
| assert!(fb_manager.device(device_id).is_some()); | |
| assert_eq!(fb_manager.num_devices(), 1); | |
| // Clean up resources before executor terminates so no receivers outlive it. | |
| drop(proxy); | |
| drop(_tasks); | |
| fasync::Timer::new(fasync::MonotonicInstant::after( | |
| zx::MonotonicDuration::from_millis(50), | |
| )) | |
| .await; | |
| drop(fb_manager); | |
| drop(fb_manager_second); | |
| drop(peeked); | |
| kernel.expando.remove::<Arc<DirectInputManager>>(); | |
| }) | |
| .await; | |
| } | |
| } |