1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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;
use directories::ProjectDirs;
use log::LevelFilter;
use serde_annotate::Annotate;
use std::env::ArgsOs;
use std::ffi::OsString;
use std::io::ErrorKind;
use std::iter::Iterator;
use std::path::PathBuf;
use std::str::FromStr;

use super::bootstrap::Bootstrap;
use super::load_bitstream::LoadBitstream;
use crate::app::TransportWrapper;
use crate::backend;
use crate::io::jtag::JtagParams;
// use opentitanlib::io::uart::UartParams;

#[derive(Debug, Args)]
pub struct InitializeTest {
    /// Filename of a default flagsfile.  Relative to $XDG_CONFIG_HOME/opentitantool.
    #[arg(long, value_parser = PathBuf::from_str, default_value = "config")]
    pub rcfile: PathBuf,

    #[arg(long, default_value = "off")]
    pub logging: LevelFilter,

    #[command(flatten)]
    pub backend_opts: backend::BackendOpts,

    // TODO: Bootstrap::options already has a uart_params (and a spi_params).
    // This probably needs some refactoring.
    //#[command(flatten)]
    //pub uart_params: UartParams,
    #[command(flatten)]
    pub load_bitstream: LoadBitstream,

    #[command(flatten)]
    pub bootstrap: Bootstrap,

    #[command(flatten)]
    pub jtag_params: JtagParams,
}

impl InitializeTest {
    pub fn init_logging(&self) {
        let level = self.logging;
        // The tests might use OpenOCD which uses util::printer so we will get
        // more useful logging if we log the target instead of the module path
        if level != LevelFilter::Off {
            env_logger::Builder::from_default_env()
                .format_target(true)
                .format_module_path(false)
                .format_timestamp_millis()
                .filter(None, level)
                .init();
        }
    }

    // Given some existing option configuration, maybe re-evaluate command
    // line options by reading an `rcfile`.
    pub fn parse_command_line(&self, mut args: ArgsOs) -> Result<Vec<OsString>> {
        // Initialize the logger if the user requested the non-defualt option.
        self.init_logging();
        if self.rcfile.as_os_str().is_empty() {
            // No rcfile to parse.
            return Ok(Vec::new());
        }

        // Construct the rcfile path based on the user's config directory
        // (ie: $HOME/.config/opentitantool/<filename>).
        let rcfile = if let Some(base) = ProjectDirs::from("org", "opentitan", "opentitantool") {
            base.config_dir().join(&self.rcfile)
        } else {
            self.rcfile.clone()
        };

        // argument[0] is the executable name.
        let mut arguments = vec![args.next().unwrap()];

        // Read in the rcfile and extend the argument list.
        match std::fs::read_to_string(&rcfile) {
            Ok(content) => {
                for line in content.split('\n') {
                    // Strip basic comments as shellwords won't handle comments.
                    let (line, _) = line.split_once('#').unwrap_or((line, ""));
                    arguments.extend(shellwords::split(line)?.iter().map(OsString::from));
                }
                Ok(())
            }
            Err(e) if e.kind() == ErrorKind::NotFound => {
                log::warn!("Could not read {:?}. Ignoring.", rcfile);
                Ok(())
            }
            Err(e) => Err(anyhow::Error::new(e).context(format!("Reading file {:?}", rcfile))),
        }?;

        // Extend the argument list with all remaining command line arguments.
        arguments.extend(args);
        Ok(arguments)
    }

    // Print the result of a command.
    // If there is an error and `RUST_BACKTRACE=1`, print a backtrace.
    pub fn print_result(operation: &str, result: Result<Option<Box<dyn Annotate>>>) -> Result<()> {
        match result {
            Ok(Some(value)) => {
                log::info!("{}: success.", operation);
                println!(
                    "\"{}\": {}",
                    operation,
                    serde_json::to_string_pretty(&value)?
                );
                Ok(())
            }
            Ok(None) => {
                log::info!("{}: success.", operation);
                println!("\"{}\": true", operation);
                Ok(())
            }
            Err(e) => {
                log::info!("{}: {:?}.", operation, e);
                println!("\"{}\": false", operation);
                Err(e)
            }
        }
    }

    pub fn init_target(&self) -> Result<TransportWrapper> {
        // Create the transport interface.
        let transport = backend::create(&self.backend_opts)?;

        // Set up the default pin configurations as specified in the transport's config file.
        transport.apply_default_configuration(None)?;

        // Create the UART first to initialize the desired parameters.
        let _uart = self.bootstrap.options.uart_params.create(&transport)?;

        // Load a bitstream.
        Self::print_result("load_bitstream", self.load_bitstream.init(&transport))?;

        // Bootstrap an rv32 test program.
        Self::print_result("bootstrap", self.bootstrap.init(&transport))?;
        Ok(transport)
    }
}