Skip to main content

starnix_core/device/
serial.rs

1// Copyright 2025 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::device::kobject::DeviceMetadata;
6use crate::device::terminal::{Terminal, TtyState};
7use crate::device::{DeviceMode, DeviceOps};
8use crate::fs::devpts::{TtyFile, new_pts_fs_with_state};
9use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
10use crate::task::{CurrentTask, EventHandler, Kernel, ThreadLockupDetector, Waiter};
11use crate::vfs::{FileOps, FsString, NamespaceNode, VecInputBuffer, VecOutputBuffer};
12use anyhow::Error;
13use fidl::endpoints::ClientEnd;
14use fidl_fuchsia_hardware_serial as fserial;
15use starnix_sync::{FileOpsCore, Locked, Unlocked};
16use starnix_uapi::device_id::{DeviceId, TTY_MAJOR};
17use starnix_uapi::errors::Errno;
18use starnix_uapi::from_status_like_fdio;
19use starnix_uapi::open_flags::OpenFlags;
20use starnix_uapi::vfs::FdEvents;
21use std::sync::Arc;
22
23struct ForwardTask {
24    terminal: Arc<Terminal>,
25    serial_proxy: Arc<fserial::DeviceSynchronousProxy>,
26}
27
28impl ForwardTask {
29    fn new(terminal: Arc<Terminal>, serial_proxy: Arc<fserial::DeviceSynchronousProxy>) -> Self {
30        terminal.main_open();
31        Self { terminal, serial_proxy }
32    }
33
34    fn spawn_reader(&self, kernel: &Kernel) {
35        let terminal = self.terminal.clone();
36        let serial_proxy = self.serial_proxy.clone();
37        let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| {
38            let _result = move || -> Result<(), Error> {
39                let waiter = Waiter::new();
40                loop {
41                    // Register edge triggered waiter for POLLOUT on terminal main side
42                    // This is waiting for the terminal to flag it can receive data
43                    terminal.main_wait_async(&waiter, FdEvents::POLLOUT, EventHandler::None);
44
45                    // Only await the event if it is not already asserted
46                    if !terminal.main_query_events().contains(FdEvents::POLLOUT) {
47                        waiter.wait(locked, current_task)?;
48                    }
49
50                    let data = {
51                        let _waiting_guard = ThreadLockupDetector::pause_tracking();
52                        serial_proxy
53                            .read(zx::MonotonicInstant::INFINITE)?
54                            .map_err(|e: i32| from_status_like_fdio!(zx::Status::from_raw(e)))?
55                    };
56                    terminal.main_write(locked, &mut VecInputBuffer::from(data))?;
57                }
58            }();
59        };
60        let req = SpawnRequestBuilder::new()
61            .with_debug_name("serial-reader")
62            .with_sync_closure(closure)
63            .build();
64        kernel.kthreads.spawner().spawn_from_request(req);
65    }
66
67    fn spawn_writer(&self, kernel: &Kernel) {
68        let terminal = self.terminal.clone();
69        let serial_proxy = self.serial_proxy.clone();
70        let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| {
71            let _result = move || -> Result<(), Error> {
72                let waiter = Waiter::new();
73                loop {
74                    // Register edge triggered waiter for POLLIN on terminal main side
75                    // This is waiting for the terminal to flag it has data to send
76                    terminal.main_wait_async(&waiter, FdEvents::POLLIN, EventHandler::None);
77
78                    // Only await the event if it is not already asserted
79                    if !terminal.main_query_events().contains(FdEvents::POLLIN) {
80                        waiter.wait(locked, current_task)?;
81                    }
82
83                    let size = terminal.read().get_available_read_size(true);
84                    let mut buffer = VecOutputBuffer::new(size);
85                    terminal.main_read(locked, &mut buffer)?;
86                    serial_proxy
87                        .write(buffer.data(), zx::MonotonicInstant::INFINITE)?
88                        .map_err(|e: i32| from_status_like_fdio!(zx::Status::from_raw(e)))?;
89                }
90            }();
91        };
92        let req = SpawnRequestBuilder::new()
93            .with_debug_name("serial-writer")
94            .with_sync_closure(closure)
95            .build();
96        kernel.kthreads.spawner().spawn_from_request(req);
97    }
98}
99
100impl Drop for ForwardTask {
101    fn drop(&mut self) {
102        self.terminal.main_close();
103        // TODO: How do we terminate the spawned threads?
104    }
105}
106
107pub struct SerialDevice {
108    terminal: Arc<Terminal>,
109    _forward_task: ForwardTask,
110}
111
112impl SerialDevice {
113    /// Create a serial device attached to the given fuchsia.hardware.serial endpoint.
114    ///
115    /// To register the device, call `register_serial_device`.
116    pub fn new(
117        locked: &mut Locked<Unlocked>,
118        current_task: &CurrentTask,
119        serial_device: ClientEnd<fserial::DeviceMarker>,
120    ) -> Result<Arc<Self>, Errno> {
121        let kernel = current_task.kernel();
122
123        let state = Arc::new(TtyState::default());
124        let fs = new_pts_fs_with_state(locked, kernel, Default::default(), state.clone())?;
125        let terminal = state.get_next_terminal(fs.root().clone(), current_task.current_fscred())?;
126
127        let serial_proxy = Arc::new(serial_device.into_sync_proxy());
128        let forward_task = ForwardTask::new(terminal.clone(), serial_proxy);
129        forward_task.spawn_reader(kernel);
130        forward_task.spawn_writer(kernel);
131
132        Ok(Arc::new(Self { terminal, _forward_task: forward_task }))
133    }
134}
135
136impl DeviceOps for Arc<SerialDevice> {
137    fn open(
138        &self,
139        _locked: &mut Locked<FileOpsCore>,
140        _current_task: &CurrentTask,
141        _id: DeviceId,
142        _node: &NamespaceNode,
143        _flags: OpenFlags,
144    ) -> Result<Box<dyn FileOps>, Errno> {
145        Ok(Box::new(TtyFile::new(self.terminal.clone())))
146    }
147}
148
149/// Register the given serial device.
150///
151/// The `index` should be the numerical value associated with the device. For example, if you want
152/// to register /dev/ttyS<n>, then `index` should be `n`.
153pub fn register_serial_device(
154    locked: &mut Locked<Unlocked>,
155    system_task: &CurrentTask,
156    index: u32,
157    serial_device: Arc<SerialDevice>,
158) -> Result<(), Errno> {
159    // See https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
160    //  64 = /dev/ttyS0    First UART serial port
161    const SERIAL_MINOR_BASE: u32 = 64;
162
163    let name = FsString::from(format!("ttyS{}", index));
164
165    let kernel = system_task.kernel();
166    let registry = &kernel.device_registry;
167    registry.register_device(
168        locked,
169        system_task,
170        name.as_ref(),
171        DeviceMetadata::new(
172            name.clone(),
173            DeviceId::new(TTY_MAJOR, SERIAL_MINOR_BASE + index),
174            DeviceMode::Char,
175        ),
176        registry.objects.tty_class(),
177        serial_device,
178    )?;
179    Ok(())
180}