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        let port = TTYPort::open(&serialport::new(port_name, baud).preserve_dtr_on_open())
54            .map_err(|e| UartError::OpenError(e.to_string()))?;
55        let _runtime_guard = crate::util::runtime().enter();
56        let port = AsyncFd::new(port)?;
57        flock_serial(port.get_ref(), port_name)?;
58        Ok(SerialPortUart {
59            port_name: port_name.to_string(),
60            port: RefCell::new(port),
61            pseudo_baud: Cell::new(baud),
62            multi_waker: MultiWaker::new(),
63        })
64    }
65}
66
67impl ConsoleDevice for SerialPortUart {
68    fn poll_read(&self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
69        let mut port = self.port.borrow_mut();
70
71        loop {
72            let mut guard = ready!(
73                self.multi_waker
74                    .poll_with(cx, |cx| port.poll_read_ready_mut(cx))
75            )?;
76
77            match guard.try_io(|inner| {
78                inner.get_mut().set_timeout(Duration::ZERO)?;
79                let result = match inner.get_mut().read(buf) {
80                    Ok(n) => Ok(n),
81                    Err(ioerr) if ioerr.kind() == ErrorKind::TimedOut => {
82                        Err(std::io::Error::new(std::io::ErrorKind::WouldBlock, ioerr))
83                    }
84                    Err(ioerr) => Err(ioerr)?,
85                };
86                inner.get_mut().set_timeout(Self::FOREVER)?;
87                result
88            }) {
89                Ok(result) => return Poll::Ready(Ok(result?)),
90                Err(_would_block) => continue,
91            }
92        }
93    }
94
95    /// Writes data from `buf` to the UART.
96    fn write(&self, buf: &[u8]) -> Result<()> {
97        // Perform blocking write of all bytes in `buf` even if the mio library has put the
98        // file descriptor into non-blocking mode.
99        let mut port = self.port.borrow_mut();
100        let mut idx = 0;
101        while idx < buf.len() {
102            match port.get_mut().write(&buf[idx..]) {
103                Ok(n) => idx += n,
104                Err(ioerr) if ioerr.kind() == ErrorKind::TimedOut => {
105                    // Buffers are full, file descriptor is non-blocking.  Explicitly wait for
106                    // this one file descriptor to again become ready for writing.  Since this
107                    // is a UART, we know that it will become ready in bounded time.
108                    util::file::wait_timeout(
109                        // SAFETY: The file descriptor is owned by `port` and is valid.
110                        unsafe { BorrowedFd::borrow_raw(port.as_raw_fd()) },
111                        rustix::event::PollFlags::OUT,
112                        Duration::from_secs(5),
113                    )?;
114                }
115                Err(ioerr) => return Err(ioerr).context("UART communication error"),
116            }
117        }
118
119        Ok(())
120    }
121
122    fn set_break(&self, enable: bool) -> Result<()> {
123        let mut port = self.port.borrow_mut();
124        if enable {
125            port.get_mut().set_break()?;
126        } else {
127            port.get_mut().clear_break()?;
128        }
129        Ok(())
130    }
131}
132
133impl Uart for SerialPortUart {
134    /// Returns the UART baudrate.  May return zero for virtual UARTs.
135    fn get_baudrate(&self) -> Result<u32> {
136        let pseudo = self.pseudo_baud.get();
137        if pseudo == 0 {
138            self.port
139                .borrow()
140                .get_ref()
141                .baud_rate()
142                .context("getting baudrate")
143        } else {
144            Ok(pseudo)
145        }
146    }
147
148    /// Sets the UART baudrate.  May do nothing for virtual UARTs.
149    fn set_baudrate(&self, baudrate: u32) -> Result<()> {
150        let pseudo = self.pseudo_baud.get();
151        if pseudo == 0 {
152            self.port
153                .borrow_mut()
154                .get_mut()
155                .set_baud_rate(baudrate)
156                .map_err(|_| UartError::InvalidSpeed(baudrate))?;
157        } else {
158            self.pseudo_baud.set(baudrate);
159        }
160        Ok(())
161    }
162
163    fn get_device_path(&self) -> Result<String> {
164        Ok(self.port_name.clone())
165    }
166
167    fn set_parity(&self, parity: Parity) -> Result<()> {
168        self.port.borrow_mut().get_mut().set_parity(parity)?;
169        Ok(())
170    }
171
172    /// Clears the UART RX buffer.
173    fn clear_rx_buffer(&self) -> Result<()> {
174        self.port.borrow_mut().get_mut().clear(ClearBuffer::Input)?;
175
176        // There might still be data in the device buffer, try to
177        // drain that as well.
178        //
179        // NOTE This code will only have an effect on backends that
180        // use SerialPortUart and do not override clear_rx_buffer,
181        // such as the chip_whisperer backend (which uses the SAM3x
182        // for UART). On backends such as hyperdebug which have a specific
183        // mechanism to clear the device buffers, following code will not
184        // doing anything useful.
185        const TIMEOUT: Duration = Duration::from_millis(5);
186        let mut buf = [0u8; 256];
187        while self.read_timeout(&mut buf, TIMEOUT)? > 0 {}
188        Ok(())
189    }
190}
191
192/// Invoke Linux `flock()` on the given serial port, lock will be released when the file
193/// descriptor is closed (or when the process terminates).
194pub fn flock_serial(port: &TTYPort, port_name: &str) -> Result<()> {
195    // SAFETY: `fd` is owned by `port` and is valid.
196    let fd = unsafe { BorrowedFd::borrow_raw(port.as_raw_fd()) };
197    rustix::fs::flock(fd, rustix::fs::FlockOperation::NonBlockingLockExclusive)
198        .map_err(|_| UartError::OpenError(format!("Device {port_name} is locked")))?;
199    Ok(())
200}