[opentitantool] Add support for 'emulator' commands
Introduction of new command 'emulator' to opentitantool.
Now all backends supporting 'EMULATOR' capability can be controlled using emulator sub-commands.
Short description of 'emulator' sub commands:
'state' - Return current state of Emulator in human readable form.
'stop' - Stop Emulator sub-process
'start' - Start Emulator sub-process with provided arguments.
Example:
```
$ opentitantool --interface=ti50Emulator state
$ opentitantool --interface=ti50Emulator start \
--apps-list=test_app --flash-list=/data/bank1.img,/data/bank2.img ...
$ opentitantool --interface=ti50Emulator stop
```
Signed-off-by: Michał Mazurek <maz@semihalf.com>
diff --git a/sw/host/opentitanlib/src/io/emu.rs b/sw/host/opentitanlib/src/io/emu.rs
index 523b9d6..fbe9cbd 100644
--- a/sw/host/opentitanlib/src/io/emu.rs
+++ b/sw/host/opentitanlib/src/io/emu.rs
@@ -6,6 +6,8 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
+use std::fmt;
+use std::path::PathBuf;
use thiserror::Error;
/// Error related to the `Emulator` trait.
@@ -13,7 +15,7 @@
pub enum EmuError {
#[error("Invalid argument name: {0}")]
InvalidArgumetName(String),
- #[error("Argument nmae: {0} has invalid value: {1}")]
+ #[error("Argument name: {0} has invalid value: {1}")]
InvalidArgumentValue(String, String),
#[error("Start failed with cause: {0}")]
StartFailureCause(String),
@@ -30,9 +32,33 @@
pub enum EmuValue {
Empty,
String(String),
- FilePath(String),
+ FilePath(PathBuf),
StringList(Vec<String>),
- FilePathList(Vec<String>),
+ FilePathList(Vec<PathBuf>),
+}
+
+impl From<String> for EmuValue {
+ fn from(s: String) -> Self {
+ EmuValue::String(s)
+ }
+}
+
+impl From<Vec<String>> for EmuValue {
+ fn from(str_array: Vec<String>) -> Self {
+ EmuValue::StringList(str_array)
+ }
+}
+
+impl From<PathBuf> for EmuValue {
+ fn from(p: PathBuf) -> Self {
+ EmuValue::FilePath(p)
+ }
+}
+
+impl From<Vec<PathBuf>> for EmuValue {
+ fn from(path_array: Vec<PathBuf>) -> Self {
+ EmuValue::FilePathList(path_array)
+ }
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
@@ -43,6 +69,17 @@
Error,
}
+impl fmt::Display for EmuState {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ EmuState::Off => write!(f, "Off"),
+ EmuState::On => write!(f, "On"),
+ EmuState::Busy => write!(f, "Busy"),
+ EmuState::Error => write!(f, "Error"),
+ }
+ }
+}
+
/// A trait which represents a `Emulator` instance
pub trait Emulator {
/// Simple function with return `EmuState` representing current state of Emulator instance.
diff --git a/sw/host/opentitantool/BUILD b/sw/host/opentitantool/BUILD
index 4aef06a..df50d82 100644
--- a/sw/host/opentitantool/BUILD
+++ b/sw/host/opentitantool/BUILD
@@ -11,6 +11,7 @@
srcs = [
"src/command/bootstrap.rs",
"src/command/console.rs",
+ "src/command/emulator.rs",
"src/command/gpio.rs",
"src/command/hello.rs",
"src/command/i2c.rs",
diff --git a/sw/host/opentitantool/src/command/emulator.rs b/sw/host/opentitantool/src/command/emulator.rs
new file mode 100644
index 0000000..1b4d253
--- /dev/null
+++ b/sw/host/opentitantool/src/command/emulator.rs
@@ -0,0 +1,135 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use anyhow::Result;
+use erased_serde::Serialize;
+use std::any::Any;
+use std::collections::HashMap;
+use std::path::PathBuf;
+use structopt::StructOpt;
+
+use opentitanlib::app::command::CommandDispatch;
+use opentitanlib::app::TransportWrapper;
+use opentitanlib::io::emu::{EmuState, EmuValue};
+use opentitanlib::transport::Capability;
+
+#[derive(Debug, StructOpt)]
+/// Get State of Emulator instance
+pub struct EmuGetState {}
+
+#[derive(serde::Serialize)]
+pub struct EmuGetStateResult {
+ pub state: EmuState,
+}
+
+impl CommandDispatch for EmuGetState {
+ fn run(
+ &self,
+ _context: &dyn Any,
+ transport: &TransportWrapper,
+ ) -> Result<Option<Box<dyn Serialize>>> {
+ transport
+ .capabilities()?
+ .request(Capability::EMULATOR)
+ .ok()?;
+ let emulator = transport.emulator()?;
+ let state = emulator.get_state()?;
+ Ok(Some(Box::new(EmuGetStateResult { state })))
+ }
+}
+
+#[derive(Debug, StructOpt)]
+/// Start Emulator instance
+pub struct EmuStart {
+ #[structopt(
+ long,
+ help = "Reset all presistent storage (For example: flash, otp, eeprom) to factory state"
+ )]
+ pub factory_reset: bool,
+ #[structopt(long, help = "Emulator executable file name")]
+ pub emulator_exec: Option<String>,
+ #[structopt(
+ long,
+ help = "List of application names that will be provided in flash images"
+ )]
+ pub apps_list: Option<Vec<String>>,
+ #[structopt(
+ long,
+ parse(from_os_str),
+ help = "List of file paths representing Flash images"
+ )]
+ pub flash_list: Option<Vec<PathBuf>>,
+ #[structopt(
+ long,
+ parse(from_os_str),
+ help = "Path to file representing Emulator version state"
+ )]
+ pub version_init_state: Option<PathBuf>,
+ #[structopt(
+ long,
+ parse(from_os_str),
+ help = "Path to file representing PMU initial state"
+ )]
+ pub pmu_init_state: Option<PathBuf>,
+}
+
+fn pack_args(
+ args_map: &mut HashMap<String, EmuValue>,
+ key: &str,
+ value: &Option<impl Into<EmuValue> + Clone>,
+) {
+ if let Some(value) = value {
+ args_map.insert(key.to_string(), value.clone().into());
+ }
+}
+
+impl CommandDispatch for EmuStart {
+ fn run(
+ &self,
+ _context: &dyn Any,
+ transport: &TransportWrapper,
+ ) -> Result<Option<Box<dyn Serialize>>> {
+ transport
+ .capabilities()?
+ .request(Capability::EMULATOR)
+ .ok()?;
+ let emulator = transport.emulator()?;
+ let mut args: HashMap<String, EmuValue> = HashMap::new();
+ pack_args(&mut args, "exec", &self.emulator_exec);
+ pack_args(&mut args, "app", &self.apps_list);
+ pack_args(&mut args, "flash", &self.flash_list);
+ pack_args(&mut args, "version_init_state", &self.version_init_state);
+ pack_args(&mut args, "pmu_init_state", &self.pmu_init_state);
+ emulator.start(self.factory_reset, &args)?;
+ Ok(None)
+ }
+}
+
+#[derive(Debug, StructOpt)]
+/// Stop Emulator instance
+pub struct EmuStop {}
+
+impl CommandDispatch for EmuStop {
+ fn run(
+ &self,
+ _context: &dyn Any,
+ transport: &TransportWrapper,
+ ) -> Result<Option<Box<dyn Serialize>>> {
+ transport
+ .capabilities()?
+ .request(Capability::EMULATOR)
+ .ok()?;
+ let emulator = transport.emulator()?;
+ emulator.stop()?;
+ Ok(None)
+ }
+}
+
+#[derive(Debug, StructOpt, CommandDispatch)]
+/// Commands for interacting with Emulator instance
+pub enum EmuCommand {
+ State(EmuGetState),
+ Start(EmuStart),
+ Stop(EmuStop),
+}
diff --git a/sw/host/opentitantool/src/command/mod.rs b/sw/host/opentitantool/src/command/mod.rs
index b0d09e5..fdad781 100644
--- a/sw/host/opentitantool/src/command/mod.rs
+++ b/sw/host/opentitantool/src/command/mod.rs
@@ -4,6 +4,7 @@
pub mod bootstrap;
pub mod console;
+pub mod emulator;
pub mod gpio;
pub mod hello;
pub mod i2c;
diff --git a/sw/host/opentitantool/src/main.rs b/sw/host/opentitantool/src/main.rs
index d19c6d7..66e895f 100644
--- a/sw/host/opentitantool/src/main.rs
+++ b/sw/host/opentitantool/src/main.rs
@@ -26,6 +26,7 @@
Console(command::console::Console),
Gpio(command::gpio::GpioCommand),
+ Emulator(command::emulator::EmuCommand),
I2c(command::i2c::I2cCommand),
Image(command::image::Image),