blob: a665f9153eaee1ab5a377255c1e5f833ea2773de [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//! Useful modules for OpenTitanTool application development.
pub mod command;
pub mod config;
use crate::io::emu::Emulator;
use crate::io::gpio::{GpioMonitoring, GpioPin, PinMode, PullMode};
use crate::io::i2c::Bus;
use crate::io::spi::Target;
use crate::io::uart::Uart;
use crate::transport::{
Capability, Progress, ProxyOps, Transport, TransportError, TransportInterfaceType,
};
use anyhow::Result;
use std::time::Duration;
use indicatif::{ProgressBar, ProgressStyle};
use serde_annotate::Annotate;
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use std::vec::Vec;
const DEFAULT_TEMPLATE: &str = "[{elapsed_precise}] [{wide_bar}] {bytes}/{total_bytes} ({eta})";
/// Helper function to create a progress bar in the same form for each of
/// the commands which will use it.
pub fn progress_bar(total: u64) -> ProgressBar {
let progress = ProgressBar::new(total);
progress.set_style(ProgressStyle::default_bar().template(DEFAULT_TEMPLATE));
progress
}
/// Helper struct for displaying progress bars for operations which may have multiple stages
/// (e.g. erasing then writing), or whose byte size may not be known until the operation is
/// underway.
pub struct StagedProgressBar {
pub current_progress_bar: Rc<RefCell<Option<indicatif::ProgressBar>>>,
}
impl Default for StagedProgressBar {
fn default() -> Self {
Self::new()
}
}
impl StagedProgressBar {
const STAGE_TEMPLATE: &str =
"{msg}: [{elapsed_precise}] [{wide_bar}] {bytes}/{total_bytes} ({eta})";
pub fn new() -> Self {
Self {
current_progress_bar: Rc::new(RefCell::new(None)),
}
}
/// Construct boxed progress function, suitable for passing to certain methods that perform
/// I/O or otherwise may take a long time to complete.
pub fn pfunc(&self) -> Box<dyn Fn(Progress)> {
let func_rc = self.current_progress_bar.clone();
Box::new(move |pos| match pos {
// A new stage has started (possibly the first), record the number of bytes and its
// name.
Progress::Stage { name, total } => {
let progress = progress_bar(total as u64);
if !name.is_empty() {
progress.set_style(ProgressStyle::default_bar().template(Self::STAGE_TEMPLATE));
}
func_rc.borrow_mut().replace(progress.with_message(name));
}
// Progress has been made on the current state (possibly completed).
Progress::Progress { pos } => {
let bar = func_rc.as_ref().borrow();
let bar = bar.as_ref().unwrap();
if pos as u64 == bar.length() {
bar.finish();
return;
}
bar.set_position(pos as u64);
}
})
}
}
#[derive(Default, Debug)]
pub struct PinConfiguration {
/// The input/output mode of the GPIO pin.
pub mode: Option<PinMode>,
/// The default/initial level of the pin (true means high), has effect only in `PushPull` or
/// `OpenDrain` modes.
pub level: Option<bool>,
/// Whether the pin has pullup/down resistor enabled.
pub pull_mode: Option<PullMode>,
/// The default/initial analog level of the pin in Volts, has effect only in `AnalogOutput`
/// mode.
pub volts: Option<f32>,
}
fn merge_field<T>(f1: &mut Option<T>, f2: Option<T>) -> Result<(), ()>
where
T: PartialEq<T> + Copy,
{
match (&*f1, f2) {
(Some(v1), Some(v2)) if *v1 != v2 => return Err(()),
(None, _) => *f1 = f2,
_ => (),
}
Ok(())
}
impl PinConfiguration {
/// Sometimes one configuration file specifies OpenDrain while leaving out the level, and
/// another file specifies high level, while leaving out the mode. This method will merge
/// declarations from multiple files, as long as they are not conflicting (e.g. both PushPull
/// and OpenDrain, or both high and low level.)
fn merge(&mut self, other: &PinConfiguration) -> Result<(), ()> {
merge_field(&mut self.mode, other.mode)?;
merge_field(&mut self.level, other.level)?;
merge_field(&mut self.pull_mode, other.pull_mode)?;
merge_field(&mut self.volts, other.volts)?;
Ok(())
}
}
#[derive(Default, Debug)]
pub struct SpiConfiguration {
pub bits_per_sec: Option<u32>,
}
impl SpiConfiguration {
fn merge(&mut self, other: &SpiConfiguration) -> Result<(), ()> {
merge_field(&mut self.bits_per_sec, other.bits_per_sec)?;
Ok(())
}
}
pub struct TransportWrapperBuilder {
transport: RefCell<Box<dyn Transport>>,
pin_alias_map: HashMap<String, String>,
uart_map: HashMap<String, String>,
spi_map: HashMap<String, String>,
i2c_map: HashMap<String, String>,
pin_conf_list: Vec<(String, PinConfiguration)>,
spi_conf_list: Vec<(String, SpiConfiguration)>,
strapping_conf_map: HashMap<String, Vec<(String, PinConfiguration)>>,
}
// This is the structure to be passed to each Command implementation,
// replacing the "bare" Transport argument. The fields other than
// transport will have been computed from a number ConfigurationFiles.
pub struct TransportWrapper {
transport: RefCell<Box<dyn Transport>>,
pin_map: HashMap<String, String>,
uart_map: HashMap<String, String>,
spi_map: HashMap<String, String>,
i2c_map: HashMap<String, String>,
pin_conf_map: HashMap<String, PinConfiguration>,
spi_conf_map: HashMap<String, SpiConfiguration>,
strapping_conf_map: HashMap<String, HashMap<String, PinConfiguration>>,
}
impl TransportWrapperBuilder {
pub fn new(transport: Box<dyn crate::transport::Transport>) -> Self {
Self {
transport: RefCell::new(transport),
pin_alias_map: HashMap::new(),
uart_map: HashMap::new(),
spi_map: HashMap::new(),
i2c_map: HashMap::new(),
pin_conf_list: Vec::new(),
spi_conf_list: Vec::new(),
strapping_conf_map: HashMap::new(),
}
}
fn record_pin_conf(
pin_conf_list: &mut Vec<(String, PinConfiguration)>,
pin_conf: &config::PinConfiguration,
) {
if (None, None, None, None)
== (
pin_conf.mode,
pin_conf.pull_mode,
pin_conf.level,
pin_conf.volts,
)
{
return;
}
let mut conf_entry: PinConfiguration = PinConfiguration::default();
if let Some(pin_mode) = pin_conf.mode {
conf_entry.mode = Some(pin_mode);
}
if let Some(pull_mode) = pin_conf.pull_mode {
conf_entry.pull_mode = Some(pull_mode);
}
if let Some(level) = pin_conf.level {
conf_entry.level = Some(level);
}
if let Some(volts) = pin_conf.volts {
conf_entry.volts = Some(volts);
}
pin_conf_list.push((pin_conf.name.to_string(), conf_entry))
}
fn record_spi_conf(
spi_conf_list: &mut Vec<(String, SpiConfiguration)>,
spi_conf: &config::SpiConfiguration,
) {
if spi_conf.bits_per_sec.is_none() {
return;
}
let mut conf_entry: SpiConfiguration = SpiConfiguration::default();
if let Some(bits_per_sec) = spi_conf.bits_per_sec {
conf_entry.bits_per_sec = Some(bits_per_sec);
}
spi_conf_list.push((spi_conf.name.to_string(), conf_entry))
}
pub fn add_configuration_file(&mut self, file: config::ConfigurationFile) -> Result<()> {
// Merge content of configuration file into pin_map and other members.
for pin_conf in file.pins {
if let Some(alias_of) = &pin_conf.alias_of {
self.pin_alias_map
.insert(pin_conf.name.to_uppercase(), alias_of.clone());
}
// Record default input / open drain / push pull configuration to the pin.
Self::record_pin_conf(&mut self.pin_conf_list, &pin_conf);
}
for strapping_conf in file.strappings {
let strapping_pin_map = self
.strapping_conf_map
.entry(strapping_conf.name.to_uppercase())
.or_default();
for pin_conf in strapping_conf.pins {
Self::record_pin_conf(strapping_pin_map, &pin_conf);
}
}
for spi_conf in file.spi {
if let Some(alias_of) = &spi_conf.alias_of {
self.spi_map
.insert(spi_conf.name.to_uppercase(), alias_of.clone());
}
Self::record_spi_conf(&mut self.spi_conf_list, &spi_conf);
}
for uart_conf in file.uarts {
if let Some(alias_of) = &uart_conf.alias_of {
self.uart_map
.insert(uart_conf.name.to_uppercase(), alias_of.clone());
}
// TODO(#8769): Record baud / parity configration for later
// use when opening uart.
}
Ok(())
}
fn consolidate_pin_conf_map(
pin_alias_map: &HashMap<String, String>,
pin_conf_list: &Vec<(String, PinConfiguration)>,
) -> Result<HashMap<String, PinConfiguration>> {
let mut result_pin_conf_map: HashMap<String, PinConfiguration> = HashMap::new();
for (name, conf) in pin_conf_list {
result_pin_conf_map
.entry(map_name(pin_alias_map, name))
.or_default()
.merge(conf)
.map_err(|_| {
TransportError::InconsistentConf(TransportInterfaceType::Gpio, name.to_string())
})?;
}
Ok(result_pin_conf_map)
}
fn consolidate_spi_conf_map(
spi_alias_map: &HashMap<String, String>,
spi_conf_list: &Vec<(String, SpiConfiguration)>,
) -> Result<HashMap<String, SpiConfiguration>> {
let mut result_spi_conf_map: HashMap<String, SpiConfiguration> = HashMap::new();
for (name, conf) in spi_conf_list {
result_spi_conf_map
.entry(map_name(spi_alias_map, name))
.or_default()
.merge(conf)
.map_err(|_| {
TransportError::InconsistentConf(TransportInterfaceType::Spi, name.to_string())
})?;
}
Ok(result_spi_conf_map)
}
pub fn build(self) -> Result<TransportWrapper> {
let pin_conf_map =
Self::consolidate_pin_conf_map(&self.pin_alias_map, &self.pin_conf_list)?;
let mut strapping_conf_map: HashMap<String, HashMap<String, PinConfiguration>> =
HashMap::new();
for (strapping_name, pin_conf_map) in self.strapping_conf_map {
strapping_conf_map.insert(
strapping_name,
Self::consolidate_pin_conf_map(&self.pin_alias_map, &pin_conf_map)?,
);
}
let spi_conf_map = Self::consolidate_spi_conf_map(&self.spi_map, &self.spi_conf_list)?;
Ok(TransportWrapper {
transport: self.transport,
pin_map: self.pin_alias_map,
uart_map: self.uart_map,
spi_map: self.spi_map,
i2c_map: self.i2c_map,
pin_conf_map,
spi_conf_map,
strapping_conf_map,
})
}
}
impl TransportWrapper {
/// Returns a `Capabilities` object to check the capabilities of this
/// transport object.
pub fn capabilities(&self) -> Result<crate::transport::Capabilities> {
self.transport.borrow().capabilities()
}
/// Returns a SPI [`Target`] implementation.
pub fn spi(&self, name: &str) -> Result<Rc<dyn Target>> {
self.transport
.borrow()
.spi(map_name(&self.spi_map, name).as_str())
}
/// Returns a I2C [`Bus`] implementation.
pub fn i2c(&self, name: &str) -> Result<Rc<dyn Bus>> {
self.transport
.borrow()
.i2c(map_name(&self.i2c_map, name).as_str())
}
/// Returns a [`Uart`] implementation.
pub fn uart(&self, name: &str) -> Result<Rc<dyn Uart>> {
self.transport
.borrow()
.uart(map_name(&self.uart_map, name).as_str())
}
/// Returns a [`GpioPin`] implementation.
pub fn gpio_pin(&self, name: &str) -> Result<Rc<dyn GpioPin>> {
let resolved_pin_name = map_name(&self.pin_map, name);
if resolved_pin_name == "NULL" {
return Ok(Rc::new(NullPin::new(name)));
}
self.transport.borrow().gpio_pin(resolved_pin_name.as_str())
}
/// Convenience method, returns a number of [`GpioPin`] implementations.
pub fn gpio_pins(&self, names: &[String]) -> Result<Vec<Rc<dyn GpioPin>>> {
let mut result = Vec::new();
for name in names {
result.push(self.gpio_pin(name)?);
}
Ok(result)
}
/// Returns a [`GpioMonitoring`] implementation.
pub fn gpio_monitoring(&self) -> Result<Rc<dyn GpioMonitoring>> {
self.transport.borrow().gpio_monitoring()
}
/// Returns a [`Emulator`] implementation.
pub fn emulator(&self) -> Result<Rc<dyn Emulator>> {
self.transport.borrow().emulator()
}
/// Methods available only on Proxy implementation.
pub fn proxy_ops(&self) -> Result<Rc<dyn ProxyOps>> {
self.transport.borrow().proxy_ops()
}
/// Invoke non-standard functionality of some Transport implementations.
pub fn dispatch(&self, action: &dyn Any) -> Result<Option<Box<dyn Annotate>>> {
self.transport.borrow().dispatch(action)
}
/// Apply given configuration to a single pins.
fn apply_pin_configuration(&self, name: &str, conf: &PinConfiguration) -> Result<()> {
let pin = self.gpio_pin(name)?;
pin.set(conf.mode, conf.level, conf.pull_mode, conf.volts)
}
/// Apply given configuration to a all the given pins.
fn apply_pin_configurations(&self, conf_map: &HashMap<String, PinConfiguration>) -> Result<()> {
for (name, conf) in conf_map {
self.apply_pin_configuration(name, conf)?
}
Ok(())
}
fn apply_spi_configurations(&self, conf_map: &HashMap<String, SpiConfiguration>) -> Result<()> {
for (name, conf) in conf_map {
let spi = self.spi(name)?;
if let Some(bits_per_sec) = conf.bits_per_sec {
spi.set_max_speed(bits_per_sec)?;
}
}
Ok(())
}
/// Configure all pins as input/output, pullup, etc. as declared in configuration files.
/// Also configure SPI port mode/speed, and other similar settings.
pub fn apply_default_configuration(&self) -> Result<()> {
self.transport.borrow().apply_default_configuration()?;
self.apply_pin_configurations(&self.pin_conf_map)?;
self.apply_spi_configurations(&self.spi_conf_map)?;
Ok(())
}
/// Configure a specific set of pins as strong/weak pullup/pulldown as declared in
/// configuration files under a given strapping name.
pub fn apply_pin_strapping(&self, strapping_name: &str) -> Result<()> {
let mut success = false;
if self.capabilities()?.request(Capability::PROXY).ok().is_ok() {
// The transport happens to be connection to a remote opentitan session. Pass
// request to the remote server.
if let Err(e) = self.proxy_ops()?.apply_pin_strapping(strapping_name) {
match e.downcast_ref::<TransportError>() {
Some(TransportError::InvalidStrappingName(_)) => (),
_ => return Err(e),
}
} else {
// Remote server recognized name of the strapping, based on its configuration.
// Make a note of that, and do not report error even if local configuration does
// not mention this trapping.
success = true;
}
}
if let Some(strapping_conf_map) = self.strapping_conf_map.get(strapping_name) {
// Local configuration contains this strapping, make a note of that and do not report
// error even if remote server did not recognize this strapping.
success = true;
self.apply_pin_configurations(strapping_conf_map)?;
}
if success {
Ok(())
} else {
Err(TransportError::InvalidStrappingName(strapping_name.to_string()).into())
}
}
/// Return the set of pins affected by the given strapping to their "default" (un-strapped)
/// configuration, that is, to the level declared in the "pins" section of configuration
/// files, outside of any "strappings" section.
pub fn remove_pin_strapping(&self, strapping_name: &str) -> Result<()> {
let mut success = false;
if self.capabilities()?.request(Capability::PROXY).ok().is_ok() {
// The transport happens to be connection to a remote opentitan session. Pass
// request to the remote server.
if let Err(e) = self.proxy_ops()?.remove_pin_strapping(strapping_name) {
match e.downcast_ref::<TransportError>() {
Some(TransportError::InvalidStrappingName(_)) => (),
_ => return Err(e),
}
} else {
// Remote server recognized name of the strapping, based on its configuration.
// Make a note of that, and do not report error even if local configuration does
// not mention this trapping.
success = true;
}
}
if let Some(strapping_conf_map) = self.strapping_conf_map.get(strapping_name) {
// Local configuration contains this strapping, make a note of that and do not report
// error even if remote server did not recognize this strapping.
success = true;
for pin_name in strapping_conf_map.keys() {
if let Some(default_pin_conf) = self.pin_conf_map.get(pin_name) {
self.apply_pin_configuration(pin_name, default_pin_conf)?;
}
}
}
if success {
Ok(())
} else {
Err(TransportError::InvalidStrappingName(strapping_name.to_string()).into())
}
}
pub fn reset_target(&self, reset_delay: Duration, clear_uart_rx: bool) -> Result<()> {
log::info!("Asserting the reset signal");
self.apply_pin_strapping("RESET")?;
std::thread::sleep(reset_delay);
if clear_uart_rx {
log::info!("Clearing the UART RX buffer");
self.uart("console")?.clear_rx_buffer()?;
}
log::info!("Deasserting the reset signal");
self.remove_pin_strapping("RESET")?;
std::thread::sleep(reset_delay);
Ok(())
}
}
/// Given an pin/uart/spi/i2c port name, if the name is a known alias, return the underlying
/// name/number, otherwise return the string as is.
fn map_name(map: &HashMap<String, String>, name: &str) -> String {
let name = name.to_uppercase();
match map.get(&name) {
Some(v) => {
if v.eq(&name) {
name
} else {
map_name(map, v)
}
}
None => name,
}
}
/// Certain transports may want to declare that they do not support a particular pin and that it
/// should be ignored, even though its name is mentioned in e.g. generic strapping configurations.
/// (Absent any such declaration, it would result in an error to mention strappings of a pin that
/// is not in fact supported.)
struct NullPin {
name: String,
has_warned: Cell<bool>,
}
impl NullPin {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
has_warned: Cell::new(false),
}
}
/// Emit a warning the first this pin is accessed.
fn warn(&self) {
if !self.has_warned.get() {
log::warn!("Accessed NULL pin {}", self.name);
self.has_warned.set(true);
}
}
}
impl GpioPin for NullPin {
fn read(&self) -> Result<bool> {
self.warn();
Ok(false)
}
fn write(&self, _value: bool) -> Result<()> {
self.warn();
Ok(())
}
fn set_mode(&self, _mode: PinMode) -> Result<()> {
self.warn();
Ok(())
}
fn set_pull_mode(&self, _mode: PullMode) -> Result<()> {
self.warn();
Ok(())
}
}