opentitanlib/io/uart/
serial.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::cell::{Cell, RefCell};
6use std::io::{ErrorKind, Read, Write};
7use std::os::fd::{AsRawFd, BorrowedFd};
8use std::task::{Context, Poll, ready};
9use std::time::Duration;
10
11use anyhow::{Context as _, Result};
12use serialport::{ClearBuffer, SerialPort, TTYPort};
13use tokio::io::unix::AsyncFd;
14
15use super::{Parity, Uart, UartError};
16use crate::io::console::{ConsoleDevice, ConsoleExt};
17use crate::util;
18use crate::util::runtime::MultiWaker;
19
20/// Implementation of the `Uart` trait on top of a serial device, such as `/dev/ttyUSB0`.
21pub struct SerialPortUart {
22    port_name: String,
23    port: RefCell<AsyncFd<TTYPort>>,
24    pseudo_baud: Cell<u32>,
25    multi_waker: MultiWaker,
26}
27
28impl SerialPortUart {
29    // Not really forever, but close enough.  I'd rather use Duration::MAX, but
30    // it seems that the serialport library can compute an invalid `timeval` struct
31    // to pass to `poll`, which then leads to an `Invalid argument` error when
32    // trying to `read` or `write` without a timeout.  One hundred years should be
33    // longer than any invocation of this program.
34    const FOREVER: Duration = Duration::from_secs(100 * 365 * 86400);
35
36    /// Open the given serial device, such as `/dev/ttyUSB0`.
37    pub fn open(port_name: &str, baud: u32) -> Result<Self> {
38        let port = TTYPort::open(&serialport::new(port_name, baud).preserve_dtr_on_open())
39            .map_err(|e| UartError::OpenError(e.to_string()))?;
40        let _runtime_guard = crate::util::runtime().enter();
41        let port = AsyncFd::new(port)?;
42        flock_serial(port.get_ref(), port_name)?;
43        Ok(SerialPortUart {
44            port_name: port_name.to_string(),
45            port: RefCell::new(port),
46            pseudo_baud: Cell::new(0),
47            multi_waker: MultiWaker::new(),
48        })
49    }
50
51    /// Open a pseudo port (e.g. a verilator pts device).
52    pub fn open_pseudo(port_name: &str, baud: u32) -> Result<Self> {
53        // macOS sets the rate with an ioctl a pseudo-terminal rejects; 0 skips it.
54        let open_baud = if cfg!(target_os = "macos") { 0 } else { baud };
55        let port = TTYPort::open(&serialport::new(port_name, open_baud).preserve_dtr_on_open())
56            .map_err(|e| UartError::OpenError(e.to_string()))?;
57        let _runtime_guard = crate::util::runtime().enter();
58        let port = AsyncFd::new(port)?;
59        flock_serial(port.get_ref(), port_name)?;
60        Ok(SerialPortUart {
61            port_name: port_name.to_string(),
62            port: RefCell::new(port),
63            pseudo_baud: Cell::new(baud),
64            multi_waker: MultiWaker::new(),
65        })
66    }
67}
68
69impl ConsoleDevice for SerialPortUart {
70    fn poll_read(&self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
71        let mut port = self.port.borrow_mut();
72
73        loop {
74            let mut guard = ready!(
75                self.multi_waker
76                    .poll_with(cx, |cx| port.poll_read_ready_mut(cx))
77            )?;
78
79            match guard.try_io(|inner| {
80                inner.get_mut().set_timeout(Duration::ZERO)?;
81                let result = match inner.get_mut().read(buf) {
82                    Ok(n) => Ok(n),
83                    Err(ioerr) if ioerr.kind() == ErrorKind::TimedOut => {
84                        Err(std::io::Error::new(std::io::ErrorKind::WouldBlock, ioerr))
85                    }
86                    Err(ioerr) => Err(ioerr)?,
87                };
88                inner.get_mut().set_timeout(Self::FOREVER)?;
89                result
90            }) {
91                Ok(result) => return Poll::Ready(Ok(result?)),
92                Err(_would_block) => continue,
93            }
94        }
95    }
96
97    /// Writes data from `buf` to the UART.
98    fn write(&self, buf: &[u8]) -> Result<()> {
99        // Perform blocking write of all bytes in `buf` even if the mio library has put the
100        // file descriptor into non-blocking mode.
101        let mut port = self.port.borrow_mut();
102        let mut idx = 0;
103        while idx < buf.len() {
104            match port.get_mut().write(&buf[idx..]) {
105                Ok(n) => idx += n,
106                Err(ioerr) if ioerr.kind() == ErrorKind::TimedOut => {
107                    // Buffers are full, file descriptor is non-blocking.  Explicitly wait for
108                    // this one file descriptor to again become ready for writing.  Since this
109                    // is a UART, we know that it will become ready in bounded time.
110                    util::file::wait_timeout(
111                        // SAFETY: The file descriptor is owned by `port` and is valid.
112                        unsafe { BorrowedFd::borrow_raw(port.as_raw_fd()) },
113                        rustix::event::PollFlags::OUT,
114                        Duration::from_secs(5),
115                    )?;
116                }
117                Err(ioerr) => return Err(ioerr).context("UART communication error"),
118            }
119        }
120
121        Ok(())
122    }
123}
124
125impl Uart for SerialPortUart {
126    /// Returns the UART baudrate.  May return zero for virtual UARTs.
127    fn get_baudrate(&self) -> Result<u32> {
128        let pseudo = self.pseudo_baud.get();
129        if pseudo == 0 {
130            self.port
131                .borrow()
132                .get_ref()
133                .baud_rate()
134                .context("getting baudrate")
135        } else {
136            Ok(pseudo)
137        }
138    }
139
140    /// Sets the UART baudrate.  May do nothing for virtual UARTs.
141    fn set_baudrate(&self, baudrate: u32) -> Result<()> {
142        let pseudo = self.pseudo_baud.get();
143        if pseudo == 0 {
144            self.port
145                .borrow_mut()
146                .get_mut()
147                .set_baud_rate(baudrate)
148                .map_err(|_| UartError::InvalidSpeed(baudrate))?;
149        } else {
150            self.pseudo_baud.set(baudrate);
151        }
152        Ok(())
153    }
154
155    fn get_device_path(&self) -> Result<String> {
156        Ok(self.port_name.clone())
157    }
158
159    fn set_parity(&self, parity: Parity) -> Result<()> {
160        self.port.borrow_mut().get_mut().set_parity(parity)?;
161        Ok(())
162    }
163
164    /// Clears the UART RX buffer.
165    fn clear_rx_buffer(&self) -> Result<()> {
166        self.port.borrow_mut().get_mut().clear(ClearBuffer::Input)?;
167
168        // There might still be data in the device buffer, try to
169        // drain that as well.
170        //
171        // NOTE This code will only have an effect on backends that
172        // use SerialPortUart and do not override clear_rx_buffer,
173        // such as the chip_whisperer backend (which uses the SAM3x
174        // for UART). On backends such as hyperdebug which have a specific
175        // mechanism to clear the device buffers, following code will not
176        // doing anything useful.
177        const TIMEOUT: Duration = Duration::from_millis(5);
178        let mut buf = [0u8; 256];
179        while self.read_timeout(&mut buf, TIMEOUT)? > 0 {}
180        Ok(())
181    }
182
183    fn set_break(&self, enable: bool) -> Result<()> {
184        let mut port = self.port.borrow_mut();
185        if enable {
186            port.get_mut().set_break()?;
187        } else {
188            port.get_mut().clear_break()?;
189        }
190        Ok(())
191    }
192
193    fn borrow_fd(&self) -> Result<BorrowedFd<'_>> {
194        let port = self.port.borrow();
195        // SAFETY: `fd` is owned by `port` and is valid.
196        let fd = unsafe { BorrowedFd::borrow_raw(port.as_raw_fd()) };
197        Ok(fd)
198    }
199}
200
201/// Invoke Linux `flock()` on the given serial port, lock will be released when the file
202/// descriptor is closed (or when the process terminates).
203pub fn flock_serial(port: &TTYPort, port_name: &str) -> Result<()> {
204    // SAFETY: `fd` is owned by `port` and is valid.
205    let fd = unsafe { BorrowedFd::borrow_raw(port.as_raw_fd()) };
206    rustix::fs::flock(fd, rustix::fs::FlockOperation::NonBlockingLockExclusive)
207        .map_err(|_| UartError::OpenError(format!("Device {port_name} is locked")))?;
208    Ok(())
209}