Skip to main content

starnix_modules_serial/
lib.rs

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