From 5e1d4080183b6f17d6019bac9adac46558470810 Mon Sep 17 00:00:00 2001 From: "Jes B. Klinke" Date: Fri, 22 Nov 2024 09:56:02 -0800 Subject: [PATCH] [opentitantool] Add ability to access underlying serial device Some transports/debuggers expose the serial ports in a way that can be accessed by other programs, without necessarily going through opentitanlib. This CL introduces a new `uart` top level command and `os-device` under it, (in hindsigt, `console` and `rescue` probably beloned under the same top-level, in order to share some flag processing.) The idea is that `opentitantool` can be queried, using a set of configuration file with aliases and command line usb serial number, and will print the path of the underlying OS device corresponding to the given port of the given debugger: ``` opentitantool --conf ... --usb-serial ... uart --uart=EC os-device { path: "/dev/ttyUSB6" } ``` This can be used in cases where processing of output from the OpenTitan chip takes place in external scripts or programs. Such cases currently will have to use an `opentitantool console` process to pass data between the serial device and its stdin/stdout pipes to the actual script doing the actual processing, which is wasteful. Change-Id: Id4dfff38fbc74ec65366d3a7e49a6844ae52c8ee Signed-off-by: Jes B. Klinke --- sw/host/opentitanlib/src/io/uart.rs | 6 ++ sw/host/opentitanlib/src/proxy/handler.rs | 4 ++ sw/host/opentitanlib/src/proxy/protocol.rs | 2 + .../opentitanlib/src/transport/common/uart.rs | 7 +++ .../src/transport/hyperdebug/uart.rs | 4 ++ .../opentitanlib/src/transport/proxy/uart.rs | 7 +++ sw/host/opentitantool/BUILD | 1 + sw/host/opentitantool/src/command/mod.rs | 1 + sw/host/opentitantool/src/command/uart.rs | 63 +++++++++++++++++++ sw/host/opentitantool/src/main.rs | 1 + 10 files changed, 96 insertions(+) create mode 100644 sw/host/opentitantool/src/command/uart.rs diff --git a/sw/host/opentitanlib/src/io/uart.rs b/sw/host/opentitanlib/src/io/uart.rs index b9b830d5a26e2..ba319865ab0d7 100644 --- a/sw/host/opentitanlib/src/io/uart.rs +++ b/sw/host/opentitanlib/src/io/uart.rs @@ -72,6 +72,12 @@ pub trait Uart { Ok(()) } + /// Returns `"/dev/ttyUSBn"` or similar OS device path usable by external programs for + /// directly accessing the serial port. + fn get_device_path(&self) -> Result { + Err(TransportError::UnsupportedOperation.into()) + } + /// Reads UART receive data into `buf`, returning the number of bytes read. /// This function _may_ block. fn read(&self, buf: &mut [u8]) -> Result; diff --git a/sw/host/opentitanlib/src/proxy/handler.rs b/sw/host/opentitanlib/src/proxy/handler.rs index 6301b57484c9d..a52b29c338312 100644 --- a/sw/host/opentitanlib/src/proxy/handler.rs +++ b/sw/host/opentitanlib/src/proxy/handler.rs @@ -269,6 +269,10 @@ impl<'a> TransportCommandHandler<'a> { instance.set_parity(*parity)?; Ok(Response::Uart(UartResponse::SetParity)) } + UartRequest::GetDevicePath => { + let path = instance.get_device_path()?; + Ok(Response::Uart(UartResponse::GetDevicePath { path })) + } UartRequest::Read { timeout_millis, len, diff --git a/sw/host/opentitanlib/src/proxy/protocol.rs b/sw/host/opentitanlib/src/proxy/protocol.rs index f1f164a9a6175..da47687351f83 100644 --- a/sw/host/opentitanlib/src/proxy/protocol.rs +++ b/sw/host/opentitanlib/src/proxy/protocol.rs @@ -183,6 +183,7 @@ pub enum UartRequest { rate: u32, }, SetParity(Parity), + GetDevicePath, Read { timeout_millis: Option, len: u32, @@ -199,6 +200,7 @@ pub enum UartResponse { GetBaudrate { rate: u32 }, SetBaudrate, SetParity, + GetDevicePath { path: String }, Read { data: Vec }, Write, SupportsNonblockingRead { has_support: bool }, diff --git a/sw/host/opentitanlib/src/transport/common/uart.rs b/sw/host/opentitanlib/src/transport/common/uart.rs index b58e375753fc4..fcd4ba6dd5e6a 100644 --- a/sw/host/opentitanlib/src/transport/common/uart.rs +++ b/sw/host/opentitanlib/src/transport/common/uart.rs @@ -18,6 +18,7 @@ use crate::util; /// Implementation of the `Uart` trait on top of a serial device, such as `/dev/ttyUSB0`. pub struct SerialPortUart { + port_name: String, flow_control: Cell, port: RefCell, rxbuf: RefCell>, @@ -38,6 +39,7 @@ impl SerialPortUart { .map_err(|e| UartError::OpenError(e.to_string()))?; flock_serial(&port, port_name)?; Ok(SerialPortUart { + port_name: port_name.to_string(), flow_control: Cell::new(FlowControl::None), port: RefCell::new(port), rxbuf: RefCell::default(), @@ -51,6 +53,7 @@ impl SerialPortUart { .map_err(|e| UartError::OpenError(e.to_string()))?; flock_serial(&port, port_name)?; Ok(SerialPortUart { + port_name: port_name.to_string(), flow_control: Cell::new(FlowControl::None), port: RefCell::new(port), rxbuf: RefCell::default(), @@ -136,6 +139,10 @@ impl Uart for SerialPortUart { Ok(()) } + fn get_device_path(&self) -> Result { + Ok(self.port_name.clone()) + } + /// 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 { diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs b/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs index 0c2403b596841..647094529bedd 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs @@ -93,6 +93,10 @@ impl Uart for HyperdebugUart { self.serial_port.set_flow_control(flow_control) } + fn get_device_path(&self) -> Result { + self.serial_port.get_device_path() + } + fn read(&self, buf: &mut [u8]) -> Result { self.serial_port.read(buf) } diff --git a/sw/host/opentitanlib/src/transport/proxy/uart.rs b/sw/host/opentitanlib/src/transport/proxy/uart.rs index 0cb1448b0a688..8dd2c155957ea 100644 --- a/sw/host/opentitanlib/src/transport/proxy/uart.rs +++ b/sw/host/opentitanlib/src/transport/proxy/uart.rs @@ -87,6 +87,13 @@ impl Uart for ProxyUart { } } + fn get_device_path(&self) -> Result { + match self.execute_command(UartRequest::GetDevicePath)? { + UartResponse::GetDevicePath { path } => Ok(path), + _ => bail!(ProxyError::UnexpectedReply()), + } + } + /// Reads UART receive data into `buf`, returning the number of bytes read. /// This function _may_ block. fn read(&self, buf: &mut [u8]) -> Result { diff --git a/sw/host/opentitantool/BUILD b/sw/host/opentitantool/BUILD index 94a8115b955aa..79aa0e38f4ec1 100644 --- a/sw/host/opentitantool/BUILD +++ b/sw/host/opentitantool/BUILD @@ -35,6 +35,7 @@ rust_binary( "src/command/status_cmd.rs", "src/command/tpm.rs", "src/command/transport.rs", + "src/command/uart.rs", "src/command/update_usr_access.rs", "src/command/version.rs", "src/command/xmodem.rs", diff --git a/sw/host/opentitantool/src/command/mod.rs b/sw/host/opentitantool/src/command/mod.rs index 8452c37d96c49..06a594c59cfad 100644 --- a/sw/host/opentitantool/src/command/mod.rs +++ b/sw/host/opentitantool/src/command/mod.rs @@ -26,6 +26,7 @@ pub mod spx; pub mod status_cmd; pub mod tpm; pub mod transport; +pub mod uart; pub mod update_usr_access; pub mod version; pub mod xmodem; diff --git a/sw/host/opentitantool/src/command/uart.rs b/sw/host/opentitantool/src/command/uart.rs new file mode 100644 index 0000000000000..05076886d2244 --- /dev/null +++ b/sw/host/opentitantool/src/command/uart.rs @@ -0,0 +1,63 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use clap::{Args, Subcommand}; +use serde_annotate::Annotate; +use std::any::Any; + +use opentitanlib::app::command::CommandDispatch; +use opentitanlib::app::TransportWrapper; +use opentitanlib::io::uart::UartParams; + +/// Outputs `"/dev/ttyUSBn"` or similar OS device path usable by external programs for directly +/// accessing the given serial port of the debugger. +#[derive(Debug, Args)] +pub struct UartOsDevice {} + +#[derive(Debug, serde::Serialize)] +pub struct UartOsDeviceResponse { + path: String, +} + +impl CommandDispatch for UartOsDevice { + fn run( + &self, + context: &dyn Any, + transport: &TransportWrapper, + ) -> Result>> { + let context = context.downcast_ref::().unwrap(); + let uart = context.params.create(transport)?; + Ok(Some(Box::new(UartOsDeviceResponse { + path: uart.get_device_path()?, + }))) + } +} + +/// Commands for interacting with a UART. +#[derive(Debug, Subcommand, CommandDispatch)] +pub enum InternalUartCommand { + OsDevice(UartOsDevice), +} + +#[derive(Debug, Args)] +pub struct UartCommand { + #[command(flatten)] + params: UartParams, + + #[command(subcommand)] + command: InternalUartCommand, +} + +impl CommandDispatch for UartCommand { + fn run( + &self, + _context: &dyn Any, + transport: &TransportWrapper, + ) -> Result>> { + // None of the UART commands care about the prior context, but they do + // care about the `UartParams` parameter in the current node. + self.command.run(self, transport) + } +} diff --git a/sw/host/opentitantool/src/main.rs b/sw/host/opentitantool/src/main.rs index 65350f4c3b073..30c5b7452c5e2 100644 --- a/sw/host/opentitantool/src/main.rs +++ b/sw/host/opentitantool/src/main.rs @@ -60,6 +60,7 @@ enum RootCommandHierarchy { Spx(command::spx::Spx), #[command(subcommand)] Transport(command::transport::TransportCommand), + Uart(command::uart::UartCommand), Version(command::version::Version), #[command(subcommand)] Status(command::status_cmd::StatusCommand),