| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{anyhow, Result}; |
| use nix::unistd::isatty; |
| use raw_tty::TtyModeGuard; |
| use regex::Regex; |
| use serde_annotate::Annotate; |
| use std::any::Any; |
| use std::fs::File; |
| use std::os::unix::io::AsRawFd; |
| use std::time::Duration; |
| use structopt::StructOpt; |
| |
| use opentitanlib::app::command::CommandDispatch; |
| use opentitanlib::app::TransportWrapper; |
| use opentitanlib::io::uart::UartParams; |
| use opentitanlib::transport::Capability; |
| use opentitanlib::uart::console::{ExitStatus, UartConsole}; |
| |
| #[derive(Debug, StructOpt)] |
| pub struct Console { |
| #[structopt(flatten)] |
| params: UartParams, |
| |
| #[structopt(short, long, help = "Do not print console start end exit messages.")] |
| quiet: bool, |
| |
| #[structopt(short, long, help = "Log console output to a file")] |
| logfile: Option<String>, |
| |
| #[structopt(long, help = "Send a string into the console at startup.")] |
| send: Option<String>, |
| |
| #[structopt( |
| short, |
| long, parse(try_from_str=humantime::parse_duration), |
| help = "Duration of ROM detection timeout", |
| )] |
| timeout: Option<Duration>, |
| |
| #[structopt(long, help = "Print a timestamp on each line of console output.")] |
| timestamp: bool, |
| |
| #[structopt(long, help = "Exit with success if the specified regex is matched.")] |
| exit_success: Option<String>, |
| |
| #[structopt(long, help = "Exit with failure if the specified regex is matched.")] |
| exit_failure: Option<String>, |
| } |
| |
| impl CommandDispatch for Console { |
| fn run( |
| &self, |
| _context: &dyn Any, |
| transport: &TransportWrapper, |
| ) -> Result<Option<Box<dyn Annotate>>> { |
| // We need the UART for the console command to operate. |
| transport.capabilities()?.request(Capability::UART).ok()?; |
| let mut stdout = std::io::stdout(); |
| let mut stdin = std::io::stdin(); |
| |
| // Set up resources specified by the command line parameters. |
| let mut console = UartConsole { |
| logfile: self.logfile.as_ref().map(File::create).transpose()?, |
| timeout: self.timeout, |
| exit_success: self |
| .exit_success |
| .as_ref() |
| .map(|s| Regex::new(s.as_str())) |
| .transpose()?, |
| exit_failure: self |
| .exit_failure |
| .as_ref() |
| .map(|s| Regex::new(s.as_str())) |
| .transpose()?, |
| timestamp: self.timestamp, |
| newline: true, |
| ..Default::default() |
| }; |
| |
| if !self.quiet { |
| println!("Starting interactive console"); |
| println!("[CTRL+C] to exit.\n"); |
| } |
| let status = { |
| // Put the terminal into raw mode. The tty guard will restore the |
| // console settings when it goes out of scope. |
| let _stdin_guard = if isatty(stdin.as_raw_fd())? { |
| let mut guard = TtyModeGuard::new(stdin.as_raw_fd())?; |
| guard.set_raw_mode()?; |
| Some(guard) |
| } else { |
| None |
| }; |
| let _stdout_guard = if isatty(stdout.as_raw_fd())? { |
| let mut guard = TtyModeGuard::new(stdout.as_raw_fd())?; |
| guard.set_raw_mode()?; |
| Some(guard) |
| } else { |
| None |
| }; |
| let uart = self.params.create(transport)?; |
| if let Some(send) = self.send.as_ref() { |
| log::info!("Sending: {:?}", send); |
| uart.write(send.as_bytes())?; |
| } |
| console.interact(&*uart, Some(&mut stdin), Some(&mut stdout))? |
| }; |
| if !self.quiet { |
| println!("\n\nExiting interactive console."); |
| } |
| |
| match status { |
| ExitStatus::None | ExitStatus::CtrlC => Ok(None), |
| ExitStatus::Timeout => { |
| if console.exit_success.is_some() { |
| // If there was a console exit success condition, then a timeout |
| // represents an error. |
| Err(anyhow!("Console timeout exceeded")) |
| } else { |
| // If there was no console exit success condition, then a timeout |
| // is not an error. |
| Ok(None) |
| } |
| } |
| ExitStatus::ExitSuccess => { |
| log::info!( |
| "ExitSuccess({:?})", |
| console.captures(status).unwrap().get(0).unwrap().as_str() |
| ); |
| Ok(None) |
| } |
| ExitStatus::ExitFailure => { |
| log::info!( |
| "ExitFailure({:?})", |
| console.captures(status).unwrap().get(0).unwrap().as_str() |
| ); |
| Err(anyhow!("Matched exit_failure expression")) |
| } |
| } |
| } |
| } |