| // Copyright 2025 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::device::kobject::DeviceMetadata; |
| use crate::device::terminal::{Terminal, TtyState}; |
| use crate::device::{DeviceMode, DeviceOps}; |
| use crate::fs::devpts::{TtyFile, new_pts_fs_with_state}; |
| use crate::task::dynamic_thread_spawner::SpawnRequestBuilder; |
| use crate::task::{CurrentTask, EventHandler, Kernel, Waiter}; |
| use crate::vfs::{FileOps, FsString, NamespaceNode, VecInputBuffer, VecOutputBuffer}; |
| use anyhow::Error; |
| use fidl::endpoints::ClientEnd; |
| use fidl_fuchsia_hardware_serial as fserial; |
| use starnix_sync::{FileOpsCore, Locked, Unlocked}; |
| use starnix_uapi::device_type::{DeviceType, TTY_MAJOR}; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::from_status_like_fdio; |
| use starnix_uapi::open_flags::OpenFlags; |
| use starnix_uapi::vfs::FdEvents; |
| use std::sync::Arc; |
| |
| struct ForwardTask { |
| terminal: Arc<Terminal>, |
| serial_proxy: Arc<fserial::DeviceSynchronousProxy>, |
| } |
| |
| impl ForwardTask { |
| fn new(terminal: Arc<Terminal>, serial_proxy: Arc<fserial::DeviceSynchronousProxy>) -> Self { |
| terminal.main_open(); |
| Self { terminal, serial_proxy } |
| } |
| |
| fn spawn_reader(&self, kernel: &Kernel) { |
| let terminal = self.terminal.clone(); |
| let serial_proxy = self.serial_proxy.clone(); |
| let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| { |
| let _result = move || -> Result<(), Error> { |
| let waiter = Waiter::new(); |
| loop { |
| // Register edge triggered waiter for POLLOUT on terminal main side |
| // This is waiting for the terminal to flag it can receive data |
| terminal.main_wait_async(&waiter, FdEvents::POLLOUT, EventHandler::None); |
| |
| // Only await the event if it is not already asserted |
| if !terminal.main_query_events().contains(FdEvents::POLLOUT) { |
| waiter.wait(locked, current_task)?; |
| } |
| |
| let data = serial_proxy |
| .read(zx::MonotonicInstant::INFINITE)? |
| .map_err(|e: i32| from_status_like_fdio!(zx::Status::from_raw(e)))?; |
| terminal.main_write(locked, &mut VecInputBuffer::from(data))?; |
| } |
| }(); |
| }; |
| let req = SpawnRequestBuilder::new() |
| .with_debug_name("serial-reader") |
| .with_sync_closure(closure) |
| .build(); |
| kernel.kthreads.spawner().spawn_from_request(req); |
| } |
| |
| fn spawn_writer(&self, kernel: &Kernel) { |
| let terminal = self.terminal.clone(); |
| let serial_proxy = self.serial_proxy.clone(); |
| let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| { |
| let _result = move || -> Result<(), Error> { |
| let waiter = Waiter::new(); |
| loop { |
| // Register edge triggered waiter for POLLIN on terminal main side |
| // This is waiting for the terminal to flag it has data to send |
| terminal.main_wait_async(&waiter, FdEvents::POLLIN, EventHandler::None); |
| |
| // Only await the event if it is not already asserted |
| if !terminal.main_query_events().contains(FdEvents::POLLIN) { |
| waiter.wait(locked, current_task)?; |
| } |
| |
| let size = terminal.read().get_available_read_size(true); |
| let mut buffer = VecOutputBuffer::new(size); |
| terminal.main_read(locked, &mut buffer)?; |
| serial_proxy |
| .write(buffer.data(), zx::MonotonicInstant::INFINITE)? |
| .map_err(|e: i32| from_status_like_fdio!(zx::Status::from_raw(e)))?; |
| } |
| }(); |
| }; |
| let req = SpawnRequestBuilder::new() |
| .with_debug_name("serial-writer") |
| .with_sync_closure(closure) |
| .build(); |
| kernel.kthreads.spawner().spawn_from_request(req); |
| } |
| } |
| |
| impl Drop for ForwardTask { |
| fn drop(&mut self) { |
| self.terminal.main_close(); |
| // TODO: How do we terminate the spawned threads? |
| } |
| } |
| |
| pub struct SerialDevice { |
| terminal: Arc<Terminal>, |
| _forward_task: ForwardTask, |
| } |
| |
| impl SerialDevice { |
| /// Create a serial device attached to the given fuchsia.hardware.serial endpoint. |
| /// |
| /// To register the device, call `register_serial_device`. |
| pub fn new( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| serial_device: ClientEnd<fserial::DeviceMarker>, |
| ) -> Result<Arc<Self>, Errno> { |
| let kernel = current_task.kernel(); |
| |
| let state = Arc::new(TtyState::default()); |
| let fs = new_pts_fs_with_state(locked, kernel, Default::default(), state.clone())?; |
| let terminal = state.get_next_terminal(fs.root().clone(), current_task.current_fscred())?; |
| |
| let serial_proxy = Arc::new(serial_device.into_sync_proxy()); |
| let forward_task = ForwardTask::new(terminal.clone(), serial_proxy); |
| forward_task.spawn_reader(kernel); |
| forward_task.spawn_writer(kernel); |
| |
| Ok(Arc::new(Self { terminal, _forward_task: forward_task })) |
| } |
| } |
| |
| impl DeviceOps for Arc<SerialDevice> { |
| fn open( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _current_task: &CurrentTask, |
| _id: DeviceType, |
| _node: &NamespaceNode, |
| _flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| Ok(Box::new(TtyFile::new(self.terminal.clone()))) |
| } |
| } |
| |
| /// Register the given serial device. |
| /// |
| /// The `index` should be the numerical value associated with the device. For example, if you want |
| /// to register /dev/ttyS<n>, then `index` should be `n`. |
| pub fn register_serial_device( |
| locked: &mut Locked<Unlocked>, |
| system_task: &CurrentTask, |
| index: u32, |
| serial_device: Arc<SerialDevice>, |
| ) -> Result<(), Errno> { |
| // See https://www.kernel.org/doc/Documentation/admin-guide/devices.txt |
| // 64 = /dev/ttyS0 First UART serial port |
| const SERIAL_MINOR_BASE: u32 = 64; |
| |
| let name = FsString::from(format!("ttyS{}", index)); |
| |
| let kernel = system_task.kernel(); |
| let registry = &kernel.device_registry; |
| registry.register_device( |
| locked, |
| system_task, |
| name.as_ref(), |
| DeviceMetadata::new( |
| name.clone(), |
| DeviceType::new(TTY_MAJOR, SERIAL_MINOR_BASE + index), |
| DeviceMode::Char, |
| ), |
| registry.objects.tty_class(), |
| serial_device, |
| )?; |
| Ok(()) |
| } |