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
123impl Uart for SerialPortUart {
124    /// Returns the UART baudrate.  May return zero for virtual UARTs.
125    fn get_baudrate(&self) -> Result<u32> {
126        let pseudo = self.pseudo_baud.get();
127        if pseudo == 0 {
128            self.port
129                .borrow()
130                .get_ref()
131                .baud_rate()
132                .context("getting baudrate")
133        } else {
134            Ok(pseudo)
135        }
136    }
137
138    /// Sets the UART baudrate.  May do nothing for virtual UARTs.
139    fn set_baudrate(&self, baudrate: u32) -> Result<()> {
140        let pseudo = self.pseudo_baud.get();
141        if pseudo == 0 {
142            self.port
143                .borrow_mut()
144                .get_mut()
145                .set_baud_rate(baudrate)
146                .map_err(|_| UartError::InvalidSpeed(baudrate))?;
147        } else {
148            self.pseudo_baud.set(baudrate);
149        }
150        Ok(())
151    }
152
153    fn get_device_path(&self) -> Result<String> {
154        Ok(self.port_name.clone())
155    }
156
157    fn set_parity(&self, parity: Parity) -> Result<()> {
158        self.port.borrow_mut().get_mut().set_parity(parity)?;
159        Ok(())
160    }
161
162    /// Clears the UART RX buffer.
163    fn clear_rx_buffer(&self) -> Result<()> {
164        self.port.borrow_mut().get_mut().clear(ClearBuffer::Input)?;
165
166        // There might still be data in the device buffer, try to
167        // drain that as well.
168        //
169        // NOTE This code will only have an effect on backends that
170        // use SerialPortUart and do not override clear_rx_buffer,
171        // such as the chip_whisperer backend (which uses the SAM3x
172        // for UART). On backends such as hyperdebug which have a specific
173        // mechanism to clear the device buffers, following code will not
174        // doing anything useful.
175        const TIMEOUT: Duration = Duration::from_millis(5);
176        let mut buf = [0u8; 256];
177        while self.read_timeout(&mut buf, TIMEOUT)? > 0 {}
178        Ok(())
179    }
180
181    fn set_break(&self, enable: bool) -> Result<()> {
182        let mut port = self.port.borrow_mut();
183        if enable {
184            port.get_mut().set_break()?;
185        } else {
186            port.get_mut().clear_break()?;
187        }
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}