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