opentitanlib/test_utils/
init.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 anyhow::Result;
6use clap::Args;
7use directories::ProjectDirs;
8use log::LevelFilter;
9use serde_annotate::Annotate;
10use std::env::ArgsOs;
11use std::ffi::OsString;
12use std::io::ErrorKind;
13use std::iter::Iterator;
14use std::path::PathBuf;
15use std::str::FromStr;
16
17use super::bootstrap::Bootstrap;
18use super::load_bitstream::LoadBitstream;
19use crate::app::TransportWrapper;
20use crate::backend;
21use crate::io::jtag::JtagParams;
22// use opentitanlib::io::uart::UartParams;
23
24#[derive(Debug, Args)]
25pub struct InitializeTest {
26    /// Filename of a default flagsfile.  Relative to $XDG_CONFIG_HOME/opentitantool.
27    #[arg(long, value_parser = PathBuf::from_str, default_value = "config")]
28    pub rcfile: PathBuf,
29
30    #[arg(long, default_value = "off")]
31    pub logging: LevelFilter,
32
33    #[command(flatten)]
34    pub backend_opts: backend::BackendOpts,
35
36    // TODO: Bootstrap::options already has a uart_params (and a spi_params).
37    // This probably needs some refactoring.
38    //#[command(flatten)]
39    //pub uart_params: UartParams,
40    #[command(flatten)]
41    pub load_bitstream: LoadBitstream,
42
43    #[command(flatten)]
44    pub bootstrap: Bootstrap,
45
46    #[command(flatten)]
47    pub jtag_params: JtagParams,
48}
49
50impl InitializeTest {
51    pub fn init_logging(&self) {
52        let level = self.logging;
53        // The tests might use OpenOCD which uses util::printer so we will get
54        // more useful logging if we log the target instead of the module path
55        if level != LevelFilter::Off {
56            env_logger::Builder::from_default_env()
57                .format_target(true)
58                .format_module_path(false)
59                .format_timestamp_millis()
60                .filter(None, level)
61                .init();
62        }
63    }
64
65    // Given some existing option configuration, maybe re-evaluate command
66    // line options by reading an `rcfile`.
67    pub fn parse_command_line(&self, mut args: ArgsOs) -> Result<Vec<OsString>> {
68        // Initialize the logger if the user requested the non-defualt option.
69        self.init_logging();
70        if self.rcfile.as_os_str().is_empty() {
71            // No rcfile to parse.
72            return Ok(Vec::new());
73        }
74
75        // Construct the rcfile path based on the user's config directory
76        // (ie: $HOME/.config/opentitantool/<filename>).
77        let rcfile = if let Some(base) = ProjectDirs::from("org", "opentitan", "opentitantool") {
78            base.config_dir().join(&self.rcfile)
79        } else {
80            self.rcfile.clone()
81        };
82
83        // argument[0] is the executable name.
84        let mut arguments = vec![args.next().unwrap()];
85
86        // Read in the rcfile and extend the argument list.
87        match std::fs::read_to_string(&rcfile) {
88            Ok(content) => {
89                for line in content.split('\n') {
90                    // Strip basic comments as shellwords won't handle comments.
91                    let (line, _) = line.split_once('#').unwrap_or((line, ""));
92                    arguments.extend(shellwords::split(line)?.iter().map(OsString::from));
93                }
94                Ok(())
95            }
96            Err(e) if e.kind() == ErrorKind::NotFound => {
97                log::warn!("Could not read {:?}. Ignoring.", rcfile);
98                Ok(())
99            }
100            Err(e) => Err(anyhow::Error::new(e).context(format!("Reading file {:?}", rcfile))),
101        }?;
102
103        // Extend the argument list with all remaining command line arguments.
104        arguments.extend(args);
105        Ok(arguments)
106    }
107
108    // Print the result of a command.
109    // If there is an error and `RUST_BACKTRACE=1`, print a backtrace.
110    pub fn print_result(operation: &str, result: Result<Option<Box<dyn Annotate>>>) -> Result<()> {
111        match result {
112            Ok(Some(value)) => {
113                log::info!("{}: success.", operation);
114                println!(
115                    "\"{}\": {}",
116                    operation,
117                    serde_json::to_string_pretty(&value)?
118                );
119                Ok(())
120            }
121            Ok(None) => {
122                log::info!("{}: success.", operation);
123                println!("\"{}\": true", operation);
124                Ok(())
125            }
126            Err(e) => {
127                log::info!("{}: {:?}.", operation, e);
128                println!("\"{}\": false", operation);
129                Err(e)
130            }
131        }
132    }
133
134    pub fn init_target(&self) -> Result<TransportWrapper> {
135        // Create the transport interface.
136        let transport = backend::create(&self.backend_opts)?;
137
138        // Set up the default pin configurations as specified in the transport's config file.
139        transport.apply_default_configuration(None)?;
140
141        // Create the UART first to initialize the desired parameters.
142        let _uart = self.bootstrap.options.uart_params.create(&transport)?;
143
144        // Load a bitstream.
145        Self::print_result("load_bitstream", self.load_bitstream.init(&transport))?;
146
147        // Bootstrap an rv32 test program.
148        Self::print_result("bootstrap", self.bootstrap.init(&transport))?;
149        Ok(transport)
150    }
151}