diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD index 3e859987e3215..2b37d45fe6711 100644 --- a/sw/host/opentitanlib/BUILD +++ b/sw/host/opentitanlib/BUILD @@ -92,6 +92,7 @@ rust_library( "src/image/manifest_ext.rs", "src/image/mod.rs", "src/io/console.rs", + "src/io/console/broadcast.rs", "src/io/console/ext.rs", "src/io/eeprom.rs", "src/io/emu.rs", diff --git a/sw/host/opentitanlib/src/io/console.rs b/sw/host/opentitanlib/src/io/console.rs index dd145b48a9c90..79c76369f2939 100644 --- a/sw/host/opentitanlib/src/io/console.rs +++ b/sw/host/opentitanlib/src/io/console.rs @@ -9,9 +9,10 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::impl_serializable_error; -use crate::transport::TransportError; +mod broadcast; mod ext; +pub use broadcast::Broadcaster; pub use ext::ConsoleExt; /// Errors related to the console interface. @@ -31,8 +32,26 @@ pub trait ConsoleDevice { /// Writes data from `buf` to the UART. fn write(&self, buf: &[u8]) -> Result<()>; +} + +impl ConsoleDevice for &T { + fn poll_read(&self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + T::poll_read(self, cx, buf) + } + + /// Writes data from `buf` to the UART. + fn write(&self, buf: &[u8]) -> Result<()> { + T::write(self, buf) + } +} - fn set_break(&self, _enable: bool) -> Result<()> { - Err(TransportError::UnsupportedOperation.into()) +impl ConsoleDevice for std::rc::Rc { + fn poll_read(&self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + T::poll_read(self, cx, buf) + } + + /// Writes data from `buf` to the UART. + fn write(&self, buf: &[u8]) -> Result<()> { + T::write(self, buf) } } diff --git a/sw/host/opentitanlib/src/io/console/broadcast.rs b/sw/host/opentitanlib/src/io/console/broadcast.rs new file mode 100644 index 0000000000000..89d57344dca19 --- /dev/null +++ b/sw/host/opentitanlib/src/io/console/broadcast.rs @@ -0,0 +1,200 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, ready}; +use std::time::Duration; + +use anyhow::Result; + +use super::{ConsoleDevice, ConsoleExt}; +use crate::io::uart::{FlowControl, Parity, Uart}; + +/// Broadcast UART recevied data to multiple users. +/// +/// Normally, if there are multiple users of `UART`, they share the same buffer (most commonly the kernel buffer). +/// This means that only one user can read a specific piece of data at a time. `Broadcaster` ensures that all clone +/// of it can receive all data. +pub struct Broadcaster { + inner: Arc>>, + index: usize, +} + +impl Clone for Broadcaster { + fn clone(&self) -> Self { + let mut inner = self.inner.lock().unwrap(); + + let pos = inner.reader_pos[self.index]; + let index = if let Some(index) = inner.reader_pos.iter().position(|x| x.is_none()) { + inner.reader_pos[index] = pos; + index + } else { + let index = inner.reader_pos.len(); + inner.reader_pos.push(pos); + index + }; + + Self { + inner: self.inner.clone(), + index, + } + } +} + +impl Drop for Broadcaster { + fn drop(&mut self) { + let mut inner = self.inner.lock().unwrap(); + + // Remove the reader position for this clone. + // As this array can be sparse, remove all trailing sparse elements as a compactation step. + inner.reader_pos[self.index] = None; + while let Some(None) = inner.reader_pos.last() { + inner.reader_pos.pop(); + } + + // Dropping a broadcaster instance may cause the buffer to be shrinkable. + if inner.count() != 0 { + inner.shrink(); + } + } +} + +struct BroadcasterInner { + /// Data received. Dequeing of a specific byte is not possible until all readers have consumed it. + buffer: VecDeque, + /// Reader positions. Each clone of broadcaster occupies a specific index. + reader_pos: Vec>, + /// Inner instance to read from. + inner: T, +} + +impl BroadcasterInner { + fn count(&self) -> usize { + self.reader_pos.iter().filter(|x| x.is_some()).count() + } + + fn shrink(&mut self) { + // Now go through all reader_pos to see if we can drop some buffer now. + let min_pos = self.reader_pos.iter().filter_map(|x| *x).min().unwrap(); + self.buffer.drain(..min_pos); + + self.reader_pos + .iter_mut() + .filter_map(|x| x.as_mut()) + .for_each(|x| *x -= min_pos); + } +} + +impl Broadcaster { + pub fn new(inner: T) -> Broadcaster { + Self { + inner: Arc::new(Mutex::new(BroadcasterInner { + buffer: VecDeque::new(), + reader_pos: vec![Some(0)], + inner, + })), + index: 0, + } + } +} + +impl ConsoleDevice for Broadcaster { + fn poll_read(&self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + let mut inner = self.inner.lock().unwrap(); + + let current_pos = inner.reader_pos[self.index].unwrap(); + if current_pos < inner.buffer.len() { + // Still more data to read from the buffer. + // Do some reading first. + let (front, back) = inner.buffer.as_slices(); + + let front_skip = std::cmp::min(current_pos, front.len()); + let front_copy = std::cmp::min(front.len() - front_skip, buf.len()); + buf[..front_copy].copy_from_slice(&front[front_skip..][..front_copy]); + + let back_skip = current_pos.saturating_sub(front_skip); + let back_copy = + std::cmp::min(back.len() - back_skip, buf.len().saturating_sub(front_copy)); + buf[front_copy..][..back_copy].copy_from_slice(&back[back_skip..][..back_copy]); + + let copy_len = front_copy + back_copy; + *inner.reader_pos[self.index].as_mut().unwrap() += copy_len; + + inner.shrink(); + return Poll::Ready(Ok(copy_len)); + } + + let len = ready!(inner.inner.poll_read(cx, buf))?; + + // We've read some more data. If there're other readers, we need to push to the buffer. + let total_readers = inner.count(); + if total_readers != 1 { + inner.buffer.extend(&buf[..len]); + *inner.reader_pos[self.index].as_mut().unwrap() += len; + } + + Poll::Ready(Ok(len)) + } + + fn write(&self, buf: &[u8]) -> Result<()> { + self.inner.lock().unwrap().inner.write(buf) + } +} + +impl Uart for Broadcaster { + fn get_baudrate(&self) -> Result { + self.inner.lock().unwrap().inner.get_baudrate() + } + + fn set_baudrate(&self, baudrate: u32) -> Result<()> { + self.inner.lock().unwrap().inner.set_baudrate(baudrate) + } + + fn get_flow_control(&self) -> Result { + self.inner.lock().unwrap().inner.get_flow_control() + } + + fn set_flow_control(&self, flow_control: bool) -> Result<()> { + self.inner + .lock() + .unwrap() + .inner + .set_flow_control(flow_control) + } + + fn get_device_path(&self) -> Result { + self.inner.lock().unwrap().inner.get_device_path() + } + + fn clear_rx_buffer(&self) -> Result<()> { + // If we're the only user, clear the inner buffer. + { + let inner = self.inner.lock().unwrap(); + if inner.count() == 1 { + inner.inner.clear_rx_buffer()?; + return Ok(()); + } + } + + // If we're not the only user, then we cannot clear RX buffer from `inner` + // as it disrupts other readers. Just do a read w/ timeout to clear out. + const TIMEOUT: Duration = Duration::from_millis(5); + let mut buf = [0u8; 256]; + while self.read_timeout(&mut buf, TIMEOUT)? > 0 {} + Ok(()) + } + + fn set_parity(&self, parity: Parity) -> Result<()> { + self.inner.lock().unwrap().inner.set_parity(parity) + } + + fn get_parity(&self) -> Result { + self.inner.lock().unwrap().inner.get_parity() + } + + fn set_break(&self, enable: bool) -> Result<()> { + self.inner.lock().unwrap().inner.set_break(enable) + } +} diff --git a/sw/host/opentitanlib/src/io/uart/flow.rs b/sw/host/opentitanlib/src/io/uart/flow.rs index ce747588e6f66..3e1551a269d04 100644 --- a/sw/host/opentitanlib/src/io/uart/flow.rs +++ b/sw/host/opentitanlib/src/io/uart/flow.rs @@ -117,10 +117,6 @@ impl ConsoleDevice for SoftwareFlowControl { } Ok(()) } - - fn set_break(&self, enable: bool) -> Result<()> { - self.inner.set_break(enable) - } } impl Uart for SoftwareFlowControl { @@ -162,4 +158,8 @@ impl Uart for SoftwareFlowControl { // Clear the host input buffer. self.inner.clear_rx_buffer() } + + fn set_break(&self, enable: bool) -> Result<()> { + self.inner.set_break(enable) + } } diff --git a/sw/host/opentitanlib/src/io/uart/mod.rs b/sw/host/opentitanlib/src/io/uart/mod.rs index 6520f86bc2c8d..1c7619392f42b 100644 --- a/sw/host/opentitanlib/src/io/uart/mod.rs +++ b/sw/host/opentitanlib/src/io/uart/mod.rs @@ -102,6 +102,86 @@ pub trait Uart: ConsoleDevice { fn get_parity(&self) -> Result { Err(TransportError::UnsupportedOperation.into()) } + + fn set_break(&self, _enable: bool) -> Result<()> { + Err(TransportError::UnsupportedOperation.into()) + } +} + +impl Uart for &T { + fn get_baudrate(&self) -> Result { + T::get_baudrate(self) + } + + fn set_baudrate(&self, baudrate: u32) -> Result<()> { + T::set_baudrate(self, baudrate) + } + + fn get_flow_control(&self) -> Result { + T::get_flow_control(self) + } + + fn set_flow_control(&self, flow_control: bool) -> Result<()> { + T::set_flow_control(self, flow_control) + } + + fn get_device_path(&self) -> Result { + T::get_device_path(self) + } + + fn clear_rx_buffer(&self) -> Result<()> { + T::clear_rx_buffer(self) + } + + fn set_parity(&self, parity: Parity) -> Result<()> { + T::set_parity(self, parity) + } + + fn get_parity(&self) -> Result { + T::get_parity(self) + } + + fn set_break(&self, enable: bool) -> Result<()> { + T::set_break(self, enable) + } +} + +impl Uart for Rc { + fn get_baudrate(&self) -> Result { + T::get_baudrate(self) + } + + fn set_baudrate(&self, baudrate: u32) -> Result<()> { + T::set_baudrate(self, baudrate) + } + + fn get_flow_control(&self) -> Result { + T::get_flow_control(self) + } + + fn set_flow_control(&self, flow_control: bool) -> Result<()> { + T::set_flow_control(self, flow_control) + } + + fn get_device_path(&self) -> Result { + T::get_device_path(self) + } + + fn clear_rx_buffer(&self) -> Result<()> { + T::clear_rx_buffer(self) + } + + fn set_parity(&self, parity: Parity) -> Result<()> { + T::set_parity(self, parity) + } + + fn get_parity(&self) -> Result { + T::get_parity(self) + } + + fn set_break(&self, enable: bool) -> Result<()> { + T::set_break(self, enable) + } } impl Read for &dyn Uart { diff --git a/sw/host/opentitanlib/src/io/uart/serial.rs b/sw/host/opentitanlib/src/io/uart/serial.rs index 624a771c6b444..66300adc24c34 100644 --- a/sw/host/opentitanlib/src/io/uart/serial.rs +++ b/sw/host/opentitanlib/src/io/uart/serial.rs @@ -118,16 +118,6 @@ impl ConsoleDevice for SerialPortUart { Ok(()) } - - fn set_break(&self, enable: bool) -> Result<()> { - let mut port = self.port.borrow_mut(); - if enable { - port.get_mut().set_break()?; - } else { - port.get_mut().clear_break()?; - } - Ok(()) - } } impl Uart for SerialPortUart { @@ -187,6 +177,16 @@ impl Uart for SerialPortUart { while self.read_timeout(&mut buf, TIMEOUT)? > 0 {} Ok(()) } + + fn set_break(&self, enable: bool) -> Result<()> { + let mut port = self.port.borrow_mut(); + if enable { + port.get_mut().set_break()?; + } else { + port.get_mut().clear_break()?; + } + Ok(()) + } } /// Invoke Linux `flock()` on the given serial port, lock will be released when the file diff --git a/sw/host/opentitanlib/src/test_utils/rpc.rs b/sw/host/opentitanlib/src/test_utils/rpc.rs index 3ff54bbfc292d..14d9efce9d860 100644 --- a/sw/host/opentitanlib/src/test_utils/rpc.rs +++ b/sw/host/opentitanlib/src/test_utils/rpc.rs @@ -6,7 +6,6 @@ use crc::{CRC_32_ISO_HDLC, Crc}; use regex::Regex; use serde::Serialize; use serde::de::DeserializeOwned; -use std::io::Write; use std::time::Duration; use crate::io::console::{ConsoleDevice, ConsoleError}; @@ -70,14 +69,7 @@ where Some(Regex::new(r"RESP_OK:(.*) CRC:([0-9]+)\n")?), Some(Regex::new(r"RESP_ERR:(.*) CRC:([0-9]+)\n")?), ); - let mut stdout = std::io::stdout(); - let out = if !quiet { - let w: &mut dyn Write = &mut stdout; - Some(w) - } else { - None - }; - let result = console.interact(device, out)?; + let result = console.interact(device, quiet)?; println!(); match result { ExitStatus::ExitSuccess => { diff --git a/sw/host/opentitanlib/src/uart/console.rs b/sw/host/opentitanlib/src/uart/console.rs index 2687782935e1c..2b195a1f7a8a1 100644 --- a/sw/host/opentitanlib/src/uart/console.rs +++ b/sw/host/opentitanlib/src/uart/console.rs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use std::fs::File; use std::io::Write; use std::time::{Duration, SystemTime}; @@ -12,7 +11,6 @@ use regex::{Captures, Regex}; use crate::io::console::{ConsoleDevice, ConsoleError}; pub struct UartConsole { - pub logfile: Option, timeout: Option, exit_success: Option, exit_failure: Option, @@ -37,7 +35,6 @@ impl UartConsole { exit_failure: Option, ) -> Self { Self { - logfile: None, timeout, exit_success, exit_failure, @@ -48,27 +45,23 @@ impl UartConsole { } // Runs an interactive console until CTRL_C is received. - pub fn interact(&mut self, device: &T, stdout: Option<&mut dyn Write>) -> Result + pub fn interact(&mut self, device: &T, quiet: bool) -> Result where T: ConsoleDevice + ?Sized, { - crate::util::runtime::block_on(self.interact_async(device, stdout)) + crate::util::runtime::block_on(self.interact_async(device, quiet)) } // Runs an interactive console until CTRL_C is received. Uses `mio` library to simultaneously // wait for data from UART or from stdin, without need for timeouts and repeated calls. - pub async fn interact_async( - &mut self, - device: &T, - mut stdout: Option<&mut dyn Write>, - ) -> Result + pub async fn interact_async(&mut self, device: &T, quiet: bool) -> Result where T: ConsoleDevice + ?Sized, { let timeout = self.timeout; let rx = async { loop { - self.uart_read(device, &mut stdout).await?; + self.uart_read(device, quiet).await?; if self .exit_success .as_ref() @@ -118,40 +111,35 @@ impl UartConsole { } // Read from the console device and process the data read. - async fn uart_read(&mut self, device: &T, stdout: &mut Option<&mut dyn Write>) -> Result<()> + async fn uart_read(&mut self, device: &T, quiet: bool) -> Result<()> where T: ConsoleDevice + ?Sized, { - let mut buf = [0u8; 1024]; - let effective_buf = if self.uses_regex() { - // Read one byte at a time when matching, to avoid the risk of consuming output past a - // match. - &mut buf[..1] - } else { - &mut buf - }; - let len = std::future::poll_fn(|cx| device.poll_read(cx, effective_buf)).await?; - for i in 0..len { + let mut ch = 0; + + // Read one byte at a time to avoid the risk of consuming output past a match. + let len = + std::future::poll_fn(|cx| device.poll_read(cx, std::slice::from_mut(&mut ch))).await?; + + if len == 0 { + return Ok(()); + } + + if !quiet { + let mut stdout = std::io::stdout().lock(); + if self.timestamp && self.newline { let t = humantime::format_rfc3339_millis(SystemTime::now()); - stdout.as_mut().map_or(Ok(()), |out| { - out.write_fmt(format_args!("[{} console]", t)) - })?; - self.newline = false; + stdout.write_fmt(format_args!("[{} console]", t))?; } - self.newline = buf[i] == b'\n'; - stdout - .as_mut() - .map_or(Ok(()), |out| out.write_all(&buf[i..i + 1]))?; + self.newline = ch == b'\n'; + + stdout.write_all(std::slice::from_ref(&ch))?; + stdout.flush()?; } - stdout.as_mut().map_or(Ok(()), |out| out.flush())?; - // If we're logging, save it to the logfile. - self.logfile - .as_mut() - .map_or(Ok(()), |f| f.write_all(&buf[..len]))?; if self.uses_regex() { - self.append_buffer(&buf[..len]); + self.append_buffer(std::slice::from_ref(&ch)); } Ok(()) } @@ -178,8 +166,7 @@ impl UartConsole { T: ConsoleDevice + ?Sized, { let mut console = UartConsole::new(Some(timeout), Some(Regex::new(rx)?), None); - let mut stdout = std::io::stdout(); - let result = console.interact(device, Some(&mut stdout))?; + let result = console.interact(device, false)?; println!(); match result { ExitStatus::ExitSuccess => { diff --git a/sw/host/opentitanlib/src/util/rom_detect.rs b/sw/host/opentitanlib/src/util/rom_detect.rs index ff9d112c1ba16..269e34c87031b 100644 --- a/sw/host/opentitanlib/src/util/rom_detect.rs +++ b/sw/host/opentitanlib/src/util/rom_detect.rs @@ -30,7 +30,7 @@ impl RomDetect { pub fn detect(&mut self, uart: &dyn Uart) -> Result { let t0 = Instant::now(); - let rc = self.console.interact(uart, None)?; + let rc = self.console.interact(uart, true)?; let t1 = Instant::now(); log::debug!("detect exit={:?}, duration={:?}", rc, t1 - t0); if let Some(cap) = self.console.captures(ExitStatus::ExitSuccess) { diff --git a/sw/host/opentitantool/src/command/console.rs b/sw/host/opentitantool/src/command/console.rs index 4b8cc8df1bfc8..0b9f22c909a38 100644 --- a/sw/host/opentitantool/src/command/console.rs +++ b/sw/host/opentitantool/src/command/console.rs @@ -7,14 +7,15 @@ use clap::Args; use regex::Regex; use std::any::Any; use std::fs::File; +use std::io::Write; use std::marker::Unpin; use std::time::Duration; use tokio::io::{AsyncRead, AsyncReadExt}; use opentitanlib::app::TransportWrapper; use opentitanlib::app::command::CommandDispatch; -use opentitanlib::io::console::ConsoleDevice; -use opentitanlib::io::uart::UartParams; +use opentitanlib::io::console::Broadcaster; +use opentitanlib::io::uart::{Uart, UartParams}; use opentitanlib::transport::Capability; use opentitanlib::uart::console::{ExitStatus, UartConsole}; use opentitanlib::util::raw_tty::RawTty; @@ -59,13 +60,14 @@ const CTRL_B: u8 = 2; const CTRL_C: u8 = 3; /// Takes input from an input stream and send it to a console device. Breaks are handled. -async fn process_input(device: &T, stdin: &mut R) -> Result<()> +async fn process_input(device: &T, stdin: &mut R) -> Result<()> where - T: ConsoleDevice + ?Sized, + T: Uart + ?Sized, + R: AsyncRead + Unpin, { let mut break_en = false; + let mut buf = [0u8; 256]; loop { - let mut buf = [0u8; 256]; let len = stdin.read(&mut buf).await?; if len == 1 { if buf[0] == CTRL_C { @@ -85,9 +87,21 @@ where continue; } } - if len > 0 { - device.write(&buf[..len])?; - } + device.write(&buf[..len])?; + } +} + +/// Takes input from a console and write to an output. +async fn pipe_output(device: &T, w: &mut W) -> Result<()> +where + T: Uart + ?Sized, + W: Write, +{ + let mut buf = [0u8; 256]; + + loop { + let len = std::future::poll_fn(|cx| device.poll_read(cx, &mut buf)).await?; + w.write_all(&buf[..len])?; } } @@ -100,6 +114,14 @@ impl CommandDispatch for Console { // We need the UART for the console command to operate. transport.capabilities()?.request(Capability::UART).ok()?; + let uart = self.params.create(transport)?; + if let Some(send) = self.send.as_ref() { + log::info!("Sending: {:?}", send); + uart.write(send.as_bytes())?; + } + + let mut logfile = self.logfile.as_ref().map(File::create).transpose()?; + // Set up resources specified by the command line parameters. let mut console = UartConsole::new( self.timeout, @@ -112,48 +134,54 @@ impl CommandDispatch for Console { .map(|s| Regex::new(s.as_str())) .transpose()?, ); - console.logfile = self.logfile.as_ref().map(File::create).transpose()?; console.timestamp = self.timestamp; - let status = { - // Put the terminal into raw mode. The tty guard will restore the - // console settings when it goes out of scope. - let mut stdin = if self.non_interactive { - None - } else { - Some(RawTty::new(tokio::io::stdin())?) - }; - let mut stdout = std::io::stdout(); - - let uart = self.params.create(transport)?; - if let Some(send) = self.send.as_ref() { - log::info!("Sending: {:?}", send); - uart.write(send.as_bytes())?; - } - if !self.non_interactive { - eprint!("Starting interactive console\r\n"); - eprint!("[CTRL+C] to exit.\r\n\r\n"); - } + // Put the terminal into raw mode. The tty guard will restore the + // console settings when it goes out of scope. + let mut stdin = if self.non_interactive { + None + } else { + Some(RawTty::new(tokio::io::stdin())?) + }; + + if !self.non_interactive { + eprint!("Starting interactive console\r\n"); + eprint!("[CTRL+C] to exit.\r\n\r\n"); + } - transport.relinquish_exclusive_access(|| { - opentitanlib::util::runtime::block_on(async { - let tx = async { - if let Some(stdin) = stdin.as_mut() { - process_input(&*uart, stdin).await - } else { - std::future::pending().await - } - }; - - let rx = console.interact_async(&*uart, Some(&mut stdout)); - - Result::<_>::Ok(tokio::select! { - v = tx => Err(v?), - v = rx => Ok(v?), - }) + let status = transport.relinquish_exclusive_access(|| { + opentitanlib::util::runtime::block_on(async { + let uart_rx = Broadcaster::new(uart.clone()); + + let tx = async { + if let Some(stdin) = stdin.as_mut() { + process_input(&*uart, stdin).await + } else { + std::future::pending().await + } + }; + + let log_to_file = async { + if let Some(file) = logfile.as_mut() { + pipe_output(&uart_rx.clone(), file).await + } else { + std::future::pending().await + } + }; + + let rx = async { console.interact_async(&uart_rx, false).await }; + + Result::<_>::Ok(tokio::select! { + v = tx => Err(v?), + v = log_to_file => Err(v?), + v = rx => Ok(v?), }) - })?? - }; + }) + })??; + + // Bring us out from raw mode early. + drop(stdin); + if !self.non_interactive { eprintln!("\n\nExiting interactive console."); } diff --git a/sw/host/ot_transports/hyperdebug/src/uart.rs b/sw/host/ot_transports/hyperdebug/src/uart.rs index 504dd3ac08c95..4770cc05ffba3 100644 --- a/sw/host/ot_transports/hyperdebug/src/uart.rs +++ b/sw/host/ot_transports/hyperdebug/src/uart.rs @@ -75,20 +75,6 @@ impl ConsoleDevice for HyperdebugUart { fn write(&self, buf: &[u8]) -> Result<()> { self.serial_port.write(buf) } - - fn set_break(&self, enable: bool) -> Result<()> { - let usb_handle = self.inner.usb_device.borrow(); - usb_handle - .write_control( - rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface), - ControlRequest::Break as u8, - if enable { 0xFFFF } else { 0 }, - self.usb_interface as u16, - &[], - ) - .context("Setting break condition")?; - Ok(()) - } } impl Uart for HyperdebugUart { @@ -189,4 +175,18 @@ impl Uart for HyperdebugUart { _ => Err(UartError::ReadError("Unknown parity value".to_string()).into()), } } + + fn set_break(&self, enable: bool) -> Result<()> { + let usb_handle = self.inner.usb_device.borrow(); + usb_handle + .write_control( + rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface), + ControlRequest::Break as u8, + if enable { 0xFFFF } else { 0 }, + self.usb_interface as u16, + &[], + ) + .context("Setting break condition")?; + Ok(()) + } } diff --git a/sw/host/ot_transports/proxy/src/uart.rs b/sw/host/ot_transports/proxy/src/uart.rs index 1fb6800184f62..dd5054460fc8a 100644 --- a/sw/host/ot_transports/proxy/src/uart.rs +++ b/sw/host/ot_transports/proxy/src/uart.rs @@ -70,13 +70,6 @@ impl ConsoleDevice for ProxyUart { _ => bail!(ProxyError::UnexpectedReply()), } } - - fn set_break(&self, enable: bool) -> Result<()> { - match self.execute_command(UartRequest::SetBreak(enable))? { - UartResponse::SetBreak => Ok(()), - _ => bail!(ProxyError::UnexpectedReply()), - } - } } impl Uart for ProxyUart { @@ -130,4 +123,11 @@ impl Uart for ProxyUart { _ => bail!(ProxyError::UnexpectedReply()), } } + + fn set_break(&self, enable: bool) -> Result<()> { + match self.execute_command(UartRequest::SetBreak(enable))? { + UartResponse::SetBreak => Ok(()), + _ => bail!(ProxyError::UnexpectedReply()), + } + } } diff --git a/sw/host/tests/chip/jtag/src/openocd_test.rs b/sw/host/tests/chip/jtag/src/openocd_test.rs index c1d48140f4deb..d26515667db7f 100644 --- a/sw/host/tests/chip/jtag/src/openocd_test.rs +++ b/sw/host/tests/chip/jtag/src/openocd_test.rs @@ -42,7 +42,7 @@ fn test_openocd(opts: &Opts, transport: &TransportWrapper) -> Result<()> { const CONSOLE_TIMEOUT: Duration = Duration::from_secs(5); let mut console = UartConsole::new(Some(CONSOLE_TIMEOUT), Some(Regex::new(r"PASS!")?), None); - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; log::info!("result: {:?}", result); // diff --git a/sw/host/tests/chip/jtag/src/sram_load.rs b/sw/host/tests/chip/jtag/src/sram_load.rs index 5ca410bcb0bf6..7dcf10a8f28a0 100644 --- a/sw/host/tests/chip/jtag/src/sram_load.rs +++ b/sw/host/tests/chip/jtag/src/sram_load.rs @@ -54,7 +54,7 @@ fn test_sram_load(opts: &Opts, transport: &TransportWrapper) -> Result<()> { )?), None, ); - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; log::info!("result: {:?}", result); jtag.halt()?; jtag.disconnect()?; diff --git a/sw/host/tests/chip/power_virus/src/main.rs b/sw/host/tests/chip/power_virus/src/main.rs index 05a6a2e4e6e3a..5d3b0dc8d3aa3 100644 --- a/sw/host/tests/chip/power_virus/src/main.rs +++ b/sw/host/tests/chip/power_virus/src/main.rs @@ -31,8 +31,7 @@ fn power_virus_systemtest(opts: &Opts, transport: &TransportWrapper) -> Result<( Some(Regex::new(r"PASS.*\n")?), Some(Regex::new(r"(FAIL|FAULT).*\n")?), ); - let mut stdout = std::io::stdout(); - let result = console.interact(&*uart, Some(&mut stdout))?; + let result = console.interact(&*uart, false)?; match result { ExitStatus::Timeout => Err(anyhow!("Console timeout exceeded")), ExitStatus::ExitSuccess => { diff --git a/sw/host/tests/chip/rv_core_ibex_epmp/src/main.rs b/sw/host/tests/chip/rv_core_ibex_epmp/src/main.rs index cf74608b08fad..25c0dd12b4b79 100644 --- a/sw/host/tests/chip/rv_core_ibex_epmp/src/main.rs +++ b/sw/host/tests/chip/rv_core_ibex_epmp/src/main.rs @@ -69,8 +69,7 @@ fn ibex_epmp_test(opts: &Opts, transport: &TransportWrapper) -> Result<()> { Some(Regex::new(r"(FAIL|FAULT).*\n")?), ); - let mut stdout = std::io::stdout(); - let result = console.interact(&*uart, Some(&mut stdout))?; + let result = console.interact(&*uart, false)?; match result { ExitStatus::Timeout => Err(anyhow!("Console timeout exceeded")), ExitStatus::ExitSuccess => { diff --git a/sw/host/tests/chip/spi_device/src/spi_device_flash_smoketest.rs b/sw/host/tests/chip/spi_device/src/spi_device_flash_smoketest.rs index 677852b65a0b7..328383c0c21b9 100644 --- a/sw/host/tests/chip/spi_device/src/spi_device_flash_smoketest.rs +++ b/sw/host/tests/chip/spi_device/src/spi_device_flash_smoketest.rs @@ -93,7 +93,7 @@ fn main() -> Result<()> { ); // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; diff --git a/sw/host/tests/chip/spi_device/src/spi_device_sleep_test.rs b/sw/host/tests/chip/spi_device/src/spi_device_sleep_test.rs index b426128de3e80..7020d1e1e005d 100644 --- a/sw/host/tests/chip/spi_device/src/spi_device_sleep_test.rs +++ b/sw/host/tests/chip/spi_device/src/spi_device_sleep_test.rs @@ -104,7 +104,7 @@ fn main() -> Result<()> { ); // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; diff --git a/sw/host/tests/chip/spi_device/src/spi_device_tpm_test.rs b/sw/host/tests/chip/spi_device/src/spi_device_tpm_test.rs index e5b3ff1dfa685..95f17e50628a7 100644 --- a/sw/host/tests/chip/spi_device/src/spi_device_tpm_test.rs +++ b/sw/host/tests/chip/spi_device/src/spi_device_tpm_test.rs @@ -69,7 +69,7 @@ fn main() -> Result<()> { ); // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; diff --git a/sw/host/tests/chip/spi_device/src/spi_host_config_test.rs b/sw/host/tests/chip/spi_device/src/spi_host_config_test.rs index cbd878e7d5721..3e11997075467 100644 --- a/sw/host/tests/chip/spi_device/src/spi_host_config_test.rs +++ b/sw/host/tests/chip/spi_device/src/spi_host_config_test.rs @@ -153,7 +153,7 @@ fn main() -> Result<()> { ); // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; diff --git a/sw/host/tests/chip/spi_device_ottf_console/src/main.rs b/sw/host/tests/chip/spi_device_ottf_console/src/main.rs index e8b31c40e428b..b5f83865b7339 100644 --- a/sw/host/tests/chip/spi_device_ottf_console/src/main.rs +++ b/sw/host/tests/chip/spi_device_ottf_console/src/main.rs @@ -43,7 +43,6 @@ fn spi_device_console_test(opts: &Opts, transport: &TransportWrapper) -> Result< Some(Regex::new(r"PASS.*\n")?), Some(Regex::new(r"(FAIL|FAULT).*\n")?), ); - let mut stdout = std::io::stdout(); let spi = transport.spi(&opts.console_spi)?; let spi_console_device = SpiConsoleDevice::new(&*spi, None)?; @@ -93,7 +92,7 @@ fn spi_device_console_test(opts: &Opts, transport: &TransportWrapper) -> Result< spi_console_device.write(&data)?; } - let result = console.interact(&spi_console_device, Some(&mut stdout))?; + let result = console.interact(&spi_console_device, false)?; match result { ExitStatus::Timeout => Err(anyhow!("Console timeout exceeded")), ExitStatus::ExitSuccess => { diff --git a/sw/host/tests/manuf/individualize_sw_cfg_functest/src/main.rs b/sw/host/tests/manuf/individualize_sw_cfg_functest/src/main.rs index 49f66d8990d23..5e1d7df7ba312 100644 --- a/sw/host/tests/manuf/individualize_sw_cfg_functest/src/main.rs +++ b/sw/host/tests/manuf/individualize_sw_cfg_functest/src/main.rs @@ -60,8 +60,7 @@ fn individualize_sw_cfg(opts: &Opts, transport: &TransportWrapper) -> Result<()> Some(Regex::new(r"PASS.*\n")?), Some(Regex::new(r"(FAIL|FAULT).*\n")?), ); - let mut stdout = std::io::stdout(); - let result = console.interact(&*uart, Some(&mut stdout))?; + let result = console.interact(&*uart, false)?; match result { ExitStatus::Timeout => Err(anyhow!("Console timeout exceeded")), ExitStatus::ExitSuccess => { diff --git a/sw/host/tests/manuf/manuf_cp_ast_test_execution/src/main.rs b/sw/host/tests/manuf/manuf_cp_ast_test_execution/src/main.rs index 17f268952fc88..e890cc6a6cdd9 100644 --- a/sw/host/tests/manuf/manuf_cp_ast_test_execution/src/main.rs +++ b/sw/host/tests/manuf/manuf_cp_ast_test_execution/src/main.rs @@ -94,8 +94,7 @@ fn manuf_cp_ast_text_execution_write_otp(opts: &Opts, transport: &TransportWrapp Some(Regex::new(r"PASS.*\n")?), Some(Regex::new(r"(FAIL|FAULT).*\n")?), ); - let mut stdout = std::io::stdout(); - let result = console.interact(&*uart, Some(&mut stdout))?; + let result = console.interact(&*uart, false)?; match result { ExitStatus::Timeout => Err(anyhow!("Console timeout exceeded")), ExitStatus::ExitSuccess => { diff --git a/sw/host/tests/manuf/manuf_cp_device_info_flash_wr/src/main.rs b/sw/host/tests/manuf/manuf_cp_device_info_flash_wr/src/main.rs index 11fb8ac2e4848..92499e5caa9c9 100644 --- a/sw/host/tests/manuf/manuf_cp_device_info_flash_wr/src/main.rs +++ b/sw/host/tests/manuf/manuf_cp_device_info_flash_wr/src/main.rs @@ -110,8 +110,7 @@ fn manuf_cp_device_info_flash_wr(opts: &Opts, transport: &TransportWrapper) -> R Some(Regex::new(r"PASS.*\n")?), Some(Regex::new(r"(FAIL|FAULT).*\n")?), ); - let mut stdout = std::io::stdout(); - let result = console.interact(&*uart, Some(&mut stdout))?; + let result = console.interact(&*uart, false)?; match result { ExitStatus::Timeout => Err(anyhow!("Console timeout exceeded")), ExitStatus::ExitSuccess => { diff --git a/sw/host/tests/rom/e2e_bootstrap_disabled/src/main.rs b/sw/host/tests/rom/e2e_bootstrap_disabled/src/main.rs index 0beeee4fe14ff..6d42d8696fd01 100644 --- a/sw/host/tests/rom/e2e_bootstrap_disabled/src/main.rs +++ b/sw/host/tests/rom/e2e_bootstrap_disabled/src/main.rs @@ -41,7 +41,7 @@ fn test_bootstrap_disabled_requested(opts: &Opts, transport: &TransportWrapper) transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; @@ -69,7 +69,7 @@ fn test_bootstrap_disabled_not_requested(opts: &Opts, transport: &TransportWrapp transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; diff --git a/sw/host/tests/rom/e2e_bootstrap_entry/src/main.rs b/sw/host/tests/rom/e2e_bootstrap_entry/src/main.rs index 7706a65cb4ebf..1854bfea658b9 100644 --- a/sw/host/tests/rom/e2e_bootstrap_entry/src/main.rs +++ b/sw/host/tests/rom/e2e_bootstrap_entry/src/main.rs @@ -108,7 +108,7 @@ fn test_bootstrap_enabled_requested(opts: &Opts, transport: &TransportWrapper) - transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; @@ -133,7 +133,7 @@ fn test_bootstrap_enabled_not_requested(opts: &Opts, transport: &TransportWrappe transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; // Now watch the console for the exit conditions. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); }; @@ -314,7 +314,7 @@ fn test_bootstrap_shutdown( let bad_erase = [cmd, 0xff, 0xff, 0xff]; spi.run_transaction(&mut [Transfer::Write(&bad_erase)])?; // We should see the expected BFVs. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -340,7 +340,7 @@ fn test_bootstrap_phase1_reset(opts: &Opts, transport: &TransportWrapper) -> Res // Discard buffered messages before interacting with the console. uart.clear_rx_buffer()?; SpiFlash::chip_reset(&*spi)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::Timeout { bail!("FAIL: {:?}", result); } @@ -369,7 +369,7 @@ fn test_bootstrap_phase1_page_program(opts: &Opts, transport: &TransportWrapper) transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; // We should see the expected BFV. - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -407,7 +407,7 @@ fn test_bootstrap_phase1_erase( // Remove strapping so that chip fails to boot instead of going into bootstrap. transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -419,7 +419,7 @@ fn test_bootstrap_phase1_erase( uart.clear_rx_buffer()?; transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -447,7 +447,7 @@ fn test_bootstrap_phase1_read(opts: &Opts, transport: &TransportWrapper) -> Resu // Remove strapping so that chip fails to boot instead of going into bootstrap. transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -481,7 +481,7 @@ fn test_bootstrap_phase2_reset(opts: &Opts, transport: &TransportWrapper) -> Res // Discard buffered messages before interacting with the console. uart.clear_rx_buffer()?; SpiFlash::chip_reset(&*spi)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -516,7 +516,7 @@ fn test_bootstrap_phase2_page_program(opts: &Opts, transport: &TransportWrapper) // Remove strapping so that chip fails to boot instead of going into bootstrap. transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -556,7 +556,7 @@ fn test_bootstrap_phase2_erase( // Remove strapping so that chip fails to boot instead of going into bootstrap. transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -590,7 +590,7 @@ fn test_bootstrap_phase2_read(opts: &Opts, transport: &TransportWrapper) -> Resu // Remove strapping so that chip fails to boot instead of going into bootstrap. transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; transport.reset_target(opts.init.bootstrap.options.reset_delay, true)?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::ExitSuccess { bail!("FAIL: {:?}", result); } @@ -615,7 +615,7 @@ fn test_bootstrap_watchdog_check(opts: &Opts, transport: &TransportWrapper) -> R // anything over UART until the console times out. uart.clear_rx_buffer()?; transport.pin_strapping("ROM_BOOTSTRAP")?.remove()?; - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; if result != ExitStatus::Timeout { bail!("FAIL: {:?}", result); }; diff --git a/sw/host/tests/rom/e2e_bootstrap_rma/src/main.rs b/sw/host/tests/rom/e2e_bootstrap_rma/src/main.rs index 548c1d635f4ee..d516f5655d309 100644 --- a/sw/host/tests/rom/e2e_bootstrap_rma/src/main.rs +++ b/sw/host/tests/rom/e2e_bootstrap_rma/src/main.rs @@ -8,7 +8,6 @@ //! 1. Checks that the ROM times out and resets under the `RMA_BOOTSTRAP` strapping. //! 2. Triggers a LC transition from `PROD` to `RMA` and checks for success. -use std::io; use std::iter; use std::time::Duration; @@ -98,9 +97,8 @@ fn test_no_rma_command(opts: &Opts, transport: &TransportWrapper) -> anyhow::Res Some(exit_failure), ); - let mut stdout = io::stdout(); let result = console - .interact(&*uart, Some(&mut stdout)) + .interact(&*uart, false) .context("failed to interact with console")?; match result { @@ -269,9 +267,8 @@ fn test_rma_command(opts: &Opts, transport: &TransportWrapper) -> anyhow::Result Some(exit_failure), ); - let mut stdout = io::stdout(); let result = console - .interact(&*uart, Some(&mut stdout)) + .interact(&*uart, false) .context("failed to interact with console")?; match result { diff --git a/sw/host/tests/rom/e2e_openocd_debug_test/src/debug_test.rs b/sw/host/tests/rom/e2e_openocd_debug_test/src/debug_test.rs index 2bc5b2fab2e90..54ac249ba61ce 100644 --- a/sw/host/tests/rom/e2e_openocd_debug_test/src/debug_test.rs +++ b/sw/host/tests/rom/e2e_openocd_debug_test/src/debug_test.rs @@ -343,7 +343,7 @@ fn debug_test(opts: &Opts, transport: &TransportWrapper) -> Result<()> { Some(Regex::new(r"OK!GDB-OK(?s:.*)BFV:0142500d")?), None, ); - let result = console.interact(&*uart, Some(&mut std::io::stdout()))?; + let result = console.interact(&*uart, false)?; assert_eq!(result, ExitStatus::ExitSuccess); dbg.disconnect()?;