| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{bail, Context, Result}; |
| use nix::fcntl::{flock, FlockArg}; |
| use nix::sys::signal; |
| use nix::unistd::Pid; |
| use serialport::ClearBuffer; |
| //use serialport::{FlowControl, SerialPort}; |
| use serialport::{SerialPort, TTYPort}; |
| use std::cell::{Cell, RefCell}; |
| use std::collections::VecDeque; |
| use std::fs::OpenOptions; |
| use std::io::{ErrorKind, Read, Write}; |
| use std::os::unix::io::AsRawFd; |
| use std::time::Duration; |
| |
| //use crate::io::uart::{Uart, UartError}; |
| use crate::io::uart::{FlowControl, Uart, UartError}; |
| use crate::transport::TransportError; |
| |
| /// Implementation of the `Uart` trait on top of a serial device, such as `/dev/ttyUSB0`. |
| pub struct SerialPortUart { |
| flow_control: Cell<FlowControl>, |
| port: RefCell<TTYPort>, |
| rxbuf: RefCell<VecDeque<u8>>, |
| /// Lock field, will remove lock file via the `Drop` trait. |
| _lock: SerialPortExclusiveLock, |
| } |
| |
| impl SerialPortUart { |
| // Not really forever, but close enough. I'd rather use Duration::MAX, but |
| // it seems that the serialport library can compute an invalid `timeval` struct |
| // to pass to `poll`, which then leads to an `Invalid argument` error when |
| // trying to `read` or `write` without a timeout. One hundred years should be |
| // longer than any invocation of this program. |
| const FOREVER: Duration = Duration::from_secs(100 * 365 * 86400); |
| |
| /// Open the given serial device, such as `/dev/ttyUSB0`. |
| pub fn open(port_name: &str) -> Result<Self> { |
| let lock = SerialPortExclusiveLock::lock(port_name)?; |
| let port = TTYPort::open(&serialport::new(port_name, 115200)) |
| .map_err(|e| UartError::OpenError(e.to_string()))?; |
| flock_serial(&port, port_name)?; |
| Ok(SerialPortUart { |
| flow_control: Cell::new(FlowControl::None), |
| port: RefCell::new(port), |
| rxbuf: RefCell::default(), |
| _lock: lock, |
| }) |
| } |
| |
| fn read_worker(&self, timeout: Duration) -> Result<()> { |
| let mut buf = [0u8; 256]; |
| let mut port = self.port.borrow_mut(); |
| |
| port.set_timeout(timeout).context("UART read error")?; |
| let result = port.read(&mut buf); |
| let len = match result { |
| Ok(n) => n, |
| Err(ioerr) if ioerr.kind() == ErrorKind::TimedOut => 0, |
| Err(e) => return Err(e.into()), |
| }; |
| for &ch in &buf[..len] { |
| if self.flow_control.get() != FlowControl::None { |
| if ch == FlowControl::Resume as u8 { |
| log::debug!("Got RESUME"); |
| self.flow_control.set(FlowControl::Resume); |
| continue; |
| } else if ch == FlowControl::Pause as u8 { |
| log::debug!("Got PAUSE"); |
| self.flow_control.set(FlowControl::Pause); |
| continue; |
| } |
| } |
| self.rxbuf.borrow_mut().push_back(ch); |
| } |
| port.set_timeout(Self::FOREVER).context("UART read error")?; |
| Ok(()) |
| } |
| |
| fn read_buffer(&self, buf: &mut [u8]) -> Result<usize> { |
| let mut rxbuf = self.rxbuf.borrow_mut(); |
| let mut i = 0; |
| for byte in buf.iter_mut() { |
| let Some(rx) = rxbuf.pop_front() else { |
| break; |
| }; |
| *byte = rx; |
| i += 1; |
| } |
| Ok(i) |
| } |
| } |
| |
| impl Uart for SerialPortUart { |
| /// Returns the UART baudrate. May return zero for virtual UARTs. |
| fn get_baudrate(&self) -> Result<u32> { |
| self.port.borrow().baud_rate().context("getting baudrate") |
| } |
| |
| /// Sets the UART baudrate. May do nothing for virtual UARTs. |
| fn set_baudrate(&self, baudrate: u32) -> Result<()> { |
| self.port |
| .borrow_mut() |
| .set_baud_rate(baudrate) |
| .map_err(|_| UartError::InvalidSpeed(baudrate))?; |
| Ok(()) |
| } |
| |
| fn set_flow_control(&self, flow_control: bool) -> Result<()> { |
| self.flow_control.set(match flow_control { |
| false => FlowControl::None, |
| // When flow-control is enabled, assume we're haven't |
| // already been put into a pause state. |
| true => FlowControl::Resume, |
| }); |
| Ok(()) |
| } |
| |
| /// Reads UART receive data into `buf`, returning the number of bytes read. |
| /// The `timeout` may be used to specify a duration to wait for data. |
| fn read_timeout(&self, buf: &mut [u8], timeout: Duration) -> Result<usize> { |
| if self.rxbuf.borrow().is_empty() { |
| self.read_worker(timeout)?; |
| } |
| self.read_buffer(buf) |
| } |
| |
| /// Reads UART receive data into `buf`, returning the number of bytes read. |
| /// This function _may_ block. |
| fn read(&self, buf: &mut [u8]) -> Result<usize> { |
| self.read_timeout(buf, Self::FOREVER) |
| } |
| |
| /// Writes data from `buf` to the UART. |
| fn write(&self, buf: &[u8]) -> Result<()> { |
| // The constant of 10 is approximately 10 uart bit times per byte. |
| let pacing = Duration::from_nanos(10 * 1_000_000_000u64 / (self.get_baudrate()? as u64)); |
| log::debug!("pacing = {:?}", pacing); |
| |
| for b in buf.iter() { |
| // If flow control is enabled, read data from the input stream and |
| // process the flow control chars. |
| while self.flow_control.get() != FlowControl::None { |
| self.read_worker(Duration::ZERO)?; |
| // If we're ok to send, then break out of the flow-control loop and send the data. |
| if self.flow_control.get() == FlowControl::Resume { |
| break; |
| } |
| } |
| self.port |
| .borrow_mut() |
| .write_all(std::slice::from_ref(b)) |
| .context("UART write error")?; |
| // Sleep one uart character time after writing to the uart to pace characters into the |
| // usb-serial device so that we don't fill any device-internal buffers. The CW310 (for |
| // example) appears to have a large internal buffer that will keep transmitting to OT |
| // even if an XOFF is sent. |
| std::thread::sleep(pacing); |
| } |
| Ok(()) |
| } |
| |
| /// Clears the UART RX buffer. |
| fn clear_rx_buffer(&self) -> Result<()> { |
| self.rxbuf.borrow_mut().clear(); |
| self.port.borrow_mut().clear(ClearBuffer::Input)?; |
| Ok(()) |
| } |
| } |
| |
| const PID_FILE_LEN: usize = 11; |
| |
| /// Struct for managing a lock file in `/var/lock` corresponding to a particular serial port. The |
| /// `Drop` trait of this struct will delete the lock file, so the `SerialPortExclusiveLock` |
| /// instance should be kept alive for as long as the serial port handle it is guarding. Should |
| /// this process terminate without `drop()` getting a chance to run, other processes will |
| /// recognize that the lock file is stale, as they verify whether a process with the given PID is |
| /// still running. |
| pub struct SerialPortExclusiveLock { |
| lockfilename: String, |
| } |
| |
| impl SerialPortExclusiveLock { |
| pub fn lock(port_name: &str) -> Result<Self> { |
| let start_of_last = match port_name.rfind('/') { |
| Some(n) => n + 1, |
| None => 0, |
| }; |
| let lockfilename = format!("/var/lock/LCK..{}", &port_name[start_of_last..]); |
| if let Ok(mut lockfile) = OpenOptions::new().read(true).open(&lockfilename) { |
| // The following code attempts to parse a PID from the lock file, and send a "no-op" |
| // signal to the process identified by it. If successful, that means that the process |
| // is still running (no actual signal will be delivered), and we should refrain from |
| // also opening the same port. On any parsing error or failure to deliver the signal, |
| // we proceed to overwrite the lock file with our own PID. |
| match (|| -> Result<()> { |
| let mut buf = [0u8; PID_FILE_LEN]; |
| match lockfile.read(&mut buf) { |
| Ok(PID_FILE_LEN) => { |
| let line = std::str::from_utf8(&buf)?; |
| let pid = line.trim().parse()?; |
| signal::kill(Pid::from_raw(pid), None)?; |
| Ok(()) // This will result in "Device is locked" error. |
| } |
| _ => bail!(""), |
| } |
| })() { |
| Ok(()) => bail!(TransportError::OpenError( |
| port_name.to_string(), |
| "Device is locked".to_string() |
| )), |
| Err(_) => { |
| log::info!("Lockfile is stale. Overriding it..."); |
| std::fs::remove_file(&lockfilename)?; |
| } |
| } |
| } |
| let mut lockfile = OpenOptions::new() |
| .write(true) |
| .create_new(true) |
| .open(&lockfilename)?; |
| if PID_FILE_LEN != lockfile.write(format!("{:10}\n", nix::unistd::getpid()).as_bytes())? { |
| bail!(TransportError::OpenError( |
| port_name.to_string(), |
| "Error writing lockfile".to_string() |
| )); |
| } |
| Ok(Self { lockfilename }) |
| } |
| } |
| |
| impl Drop for SerialPortExclusiveLock { |
| fn drop(&mut self) { |
| let _ = std::fs::remove_file(&self.lockfilename); |
| } |
| } |
| |
| /// Invoke Linux `flock()` on the given serial port, lock will be released when the file |
| /// descriptor is closed (or when the process terminates). |
| pub fn flock_serial(port: &TTYPort, port_name: &str) -> Result<()> { |
| flock(port.as_raw_fd(), FlockArg::LockExclusiveNonblock).map_err(|_| { |
| TransportError::OpenError(port_name.to_string(), "Device is locked".to_string()) |
| })?; |
| Ok(()) |
| } |