[opentitantool] Minor refactors for transport interfaces
1. Define SPI Errors next to the SPI trait definition.
2. Define GPIO Erros next to the GPIO trait definition.
3. Express GPIO pins as strings. Add the backend's default naming scheme
for its pins. A future PR will allow change this a bit more to permit
user-defined pin-mappings.
4. Add some convenience methods to the GPIO trait to support bootstrap in
a generic way.
5. Change the `gpio` command to use positional arguments where appropriate.
Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/Cargo.toml b/sw/host/opentitanlib/Cargo.toml
index 13bbc04..722280a 100644
--- a/sw/host/opentitanlib/Cargo.toml
+++ b/sw/host/opentitanlib/Cargo.toml
@@ -20,6 +20,7 @@
safe-ftdi = { git = "https://github.com/cr1901/safe-ftdi" }
num_enum = "0.5.2"
byteorder = "1.4.3"
+structopt = "0.3"
serde = { version="1", features=["serde_derive"] }
serde_json = "1"
diff --git a/sw/host/opentitanlib/src/io/gpio.rs b/sw/host/opentitanlib/src/io/gpio.rs
index 292649a..7e3760f 100644
--- a/sw/host/opentitanlib/src/io/gpio.rs
+++ b/sw/host/opentitanlib/src/io/gpio.rs
@@ -3,12 +3,55 @@
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
+use structopt::clap::arg_enum;
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum GpioError {
+ #[error("Invalid GPIO pin name {0}")]
+ InvalidPinName(String),
+ #[error("Invalid GPIO pin number {0}")]
+ InvalidPinNumber(u8),
+ #[error("Invalid GPIO pin direction: {0}")]
+ InvalidPinDirection(u8),
+ #[error("Invalid strap configuration: {0:?}")]
+ InvalidStrapConfig(StrapConfig),
+}
+
+arg_enum! {
+ /// Direction for I/O pins.
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ pub enum PinDirection {
+ Input,
+ Output,
+ }
+}
+
+/// Settings for pin straps. Not all backends support all settings.
+#[derive(Clone, Copy, Debug)]
+pub enum StrapConfig {
+ None,
+ RomBootstrap,
+ Custom(u8),
+}
/// A trait which represents a GPIO interface.
pub trait Gpio {
/// Reads the value of the the GPIO pin `id`.
- fn read(&self, id: u32) -> Result<bool>;
+ fn read(&self, id: &str) -> Result<bool>;
/// Sets the value of the GPIO pin `id` to `value`.
- fn write(&self, id: u32, value: bool) -> Result<()>;
+ fn write(&self, id: &str, value: bool) -> Result<()>;
+
+ /// Sets the `direction` of GPIO `id` as input or output.
+ fn set_direction(&self, id: &str, direction: PinDirection) -> Result<()>;
+
+ /// Drive the reset pin. The `reset` parameter represents whether or not the caller
+ /// wants to drive the chip into reset, _not_ the logic-level of the reset pin.
+ fn drive_reset(&self, reset: bool) -> Result<()>;
+
+ /// Set the requested strap value to the strapping pins. Note: not all backends
+ /// support all settings. An unsupported StrapConfig will result in an
+ /// `InvalidStrapConfig` error.
+ fn set_strap_pins(&self, strap: StrapConfig) -> Result<()>;
}
diff --git a/sw/host/opentitanlib/src/io/spi.rs b/sw/host/opentitanlib/src/io/spi.rs
index 00849b2..3240ac9 100644
--- a/sw/host/opentitanlib/src/io/spi.rs
+++ b/sw/host/opentitanlib/src/io/spi.rs
@@ -3,6 +3,16 @@
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
+use thiserror::Error;
+
+/// Errors related to the SPI interface and SPI transactions.
+#[derive(Error, Debug)]
+pub enum SpiError {
+ #[error("Invalid word size: {0}")]
+ InvalidWordSize(u32),
+ #[error("Invalid speed: {0}")]
+ InvalidSpeed(u32),
+}
/// Represents the SPI transfer mode.
/// See https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Clock_polarity_and_phase
diff --git a/sw/host/opentitanlib/src/transport/ultradebug/gpio.rs b/sw/host/opentitanlib/src/transport/ultradebug/gpio.rs
index ebfd383..096e70b 100644
--- a/sw/host/opentitanlib/src/transport/ultradebug/gpio.rs
+++ b/sw/host/opentitanlib/src/transport/ultradebug/gpio.rs
@@ -2,14 +2,18 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
-use anyhow::Result;
+use anyhow::{ensure, Result};
+use lazy_static::lazy_static;
use safe_ftdi as ftdi;
use std::cell::RefCell;
+use std::collections::HashMap;
use std::rc::Rc;
-use crate::io::gpio::Gpio;
+use crate::collection;
+use crate::io::gpio::{Gpio, GpioError, PinDirection, StrapConfig};
use crate::transport::ultradebug::mpsse;
use crate::transport::ultradebug::Ultradebug;
+use crate::util::parse_int::ParseInt;
/// Represents the Ultradebug GPIO pins.
pub struct UltradebugGpio {
@@ -18,30 +22,87 @@
impl UltradebugGpio {
/// Request the upstream SPI bus to tristate so ultradebug may drive the SPI bus (platforms-ultradebug only).
- pub const PIN_SPI_ZB: u32 = 4;
+ pub const PIN_SPI_ZB: u8 = 4;
/// Reset the chip attached to ultradebug.
- pub const PIN_RESET_B: u32 = 5;
+ pub const PIN_RESET_B: u8 = 5;
/// Request bootstrap mode on the chip attached to ultradebug.
- pub const PIN_BOOTSTRAP: u32 = 6;
+ pub const PIN_BOOTSTRAP: u8 = 6;
/// Reset the target system attached to ultradebug (platforms-ultradebug only).
- pub const PIN_TGT_RESET: u32 = 7;
+ pub const PIN_TGT_RESET: u8 = 7;
+
+ const LAST_PIN_NUM: u8 = 7;
pub fn open(ultradebug: &Ultradebug) -> Result<Self> {
Ok(UltradebugGpio {
device: ultradebug.mpsse(ftdi::Interface::B)?,
})
}
+
+ /// Given an ultradebug pin name, return its pin number.
+ pub fn pin_name_to_number(&self, pinname: &str) -> Result<u8> {
+ // If the pinname is an integer, use it; otherwise try to see if it
+ // is a symbolic name of a pin.
+ if let Ok(pinnum) = u8::from_str(pinname) {
+ ensure!(
+ pinnum <= UltradebugGpio::LAST_PIN_NUM,
+ GpioError::InvalidPinNumber(pinnum)
+ );
+ return Ok(pinnum);
+ }
+ let pinname = pinname.to_uppercase();
+ let pn = pinname.as_str();
+ PIN_NAMES
+ .get(pn)
+ .copied()
+ .ok_or_else(|| GpioError::InvalidPinName(pinname).into())
+ }
}
impl Gpio for UltradebugGpio {
/// Reads the value of the the GPIO pin `id`.
- fn read(&self, id: u32) -> Result<bool> {
+ fn read(&self, id: &str) -> Result<bool> {
+ let id = self.pin_name_to_number(id)?;
let bits = self.device.borrow_mut().gpio_get()?;
Ok(bits & (1 << id) != 0)
}
/// Sets the value of the GPIO pin `id` to `value`.
- fn write(&self, id: u32, value: bool) -> Result<()> {
+ fn write(&self, id: &str, value: bool) -> Result<()> {
+ let id = self.pin_name_to_number(id)?;
self.device.borrow_mut().gpio_set(id, value)
}
+
+ /// Sets the `direction` of GPIO `id` as input or output.
+ fn set_direction(&self, id: &str, direction: PinDirection) -> Result<()> {
+ let id = self.pin_name_to_number(id)?;
+ self.device
+ .borrow_mut()
+ .gpio_set_direction(id, direction == PinDirection::Output)
+ }
+
+ /// Drive the reset pin. The `reset` parameter represents whether or not the caller
+ /// wants to drive the chip into reset, _not_ the logic-level of the reset pin.
+ fn drive_reset(&self, reset: bool) -> Result<()> {
+ self.write("RESET_B", !reset)
+ }
+
+ /// Set the requested strap value to the strapping pins. Note: not all backends
+ /// support all settings. An unsupported StrapConfig will result in an
+ /// `InvalidStrapConfig` error.
+ fn set_strap_pins(&self, strap: StrapConfig) -> Result<()> {
+ match strap {
+ StrapConfig::None => self.write("BOOTSTRAP", false),
+ StrapConfig::RomBootstrap => self.write("BOOTSTRAP", true),
+ _ => Err(GpioError::InvalidStrapConfig(strap).into()),
+ }
+ }
+}
+
+lazy_static! {
+ static ref PIN_NAMES: HashMap<&'static str, u8> = collection! {
+ "SPI_ZB" => 4,
+ "RESET_B" => 5,
+ "BOOTSTRAP" => 6,
+ "TGT_RESET" => 7,
+ };
}
diff --git a/sw/host/opentitanlib/src/transport/ultradebug/mpsse.rs b/sw/host/opentitanlib/src/transport/ultradebug/mpsse.rs
index f4933d1..fd287c1 100644
--- a/sw/host/opentitanlib/src/transport/ultradebug/mpsse.rs
+++ b/sw/host/opentitanlib/src/transport/ultradebug/mpsse.rs
@@ -6,9 +6,10 @@
use bitflags::bitflags;
use log;
use safe_ftdi as ftdi;
+use std::time::{Duration, Instant};
use thiserror::Error;
-use std::time::{Duration, Instant};
+use crate::io::gpio::GpioError;
pub const MPSSE_WRCLK_FALLING: u8 = 0x01;
pub const MPSSE_RDCLK_FALLING: u8 = 0x04;
@@ -189,10 +190,6 @@
MpsseTimeout,
#[error("unknown MPSSE error: {0:02x} {1:02x}")]
MpsseUnknown(u8, u8),
- #[error("Invalid GPIO pin: {0}")]
- InvalidGpioPin(u32),
- #[error("Invalid GPIO direction: {0}")]
- InvalidGpioDirection(u32),
#[error("Invalid data length: {0}")]
InvalidDataLength(usize),
}
@@ -327,10 +324,10 @@
}
/// Set the GPIO state of an individual pin on the FTDI device.
- pub fn gpio_set(&mut self, pin: u32, high: bool) -> Result<()> {
- let dir = GpioDirection::from_bits(1 << pin).ok_or(Error::InvalidGpioPin(pin))?;
+ pub fn gpio_set(&mut self, pin: u8, high: bool) -> Result<()> {
+ let dir = GpioDirection::from_bits(1 << pin).ok_or(GpioError::InvalidPinNumber(pin))?;
if (dir & self.gpio_direction).bits() == 0 {
- return Err(Error::InvalidGpioDirection(pin).into());
+ return Err(GpioError::InvalidPinDirection(pin).into());
}
if high {
self.gpio_value |= 1 << pin;
@@ -346,6 +343,20 @@
self.execute(&mut [command])
}
+ /// Set the direction of an individual pin on the FTDI device.
+ pub fn gpio_set_direction(&mut self, pin: u8, output: bool) -> Result<()> {
+ let direction =
+ GpioDirection::from_bits(1 << pin).ok_or(GpioError::InvalidPinNumber(pin))?;
+ if output {
+ self.gpio_direction |= direction;
+ } else {
+ self.gpio_direction &= direction;
+ }
+ // Perform a read to immediately synchronize the direction to the device.
+ let _ = self.gpio_get()?;
+ Ok(())
+ }
+
/// Set the clock frequency for serial opertions on the FTDI device.
pub fn set_clock_frequency(&mut self, frequency: u32) -> Result<()> {
let base = self.max_clock_frequency;
diff --git a/sw/host/opentitanlib/src/transport/ultradebug/spi.rs b/sw/host/opentitanlib/src/transport/ultradebug/spi.rs
index 0b0ef9c..9a1c0bb 100644
--- a/sw/host/opentitanlib/src/transport/ultradebug/spi.rs
+++ b/sw/host/opentitanlib/src/transport/ultradebug/spi.rs
@@ -7,20 +7,11 @@
use safe_ftdi as ftdi;
use std::cell::RefCell;
use std::rc::Rc;
-use thiserror::Error;
-use crate::io::spi::{ClockPolarity, Target, Transfer, TransferMode};
+use crate::io::spi::{ClockPolarity, SpiError, Target, Transfer, TransferMode};
use crate::transport::ultradebug::mpsse;
use crate::transport::ultradebug::Ultradebug;
-#[derive(Error, Debug)]
-pub enum Error {
- #[error("Invalid word size: {0}")]
- InvalidWordSize(u32),
- #[error("Invalid speed: {0}")]
- InvalidSpeed(u32),
-}
-
/// Represents the Ultradebug SPI device.
pub struct UltradebugSpi {
pub device: Rc<RefCell<mpsse::Context>>,
@@ -29,11 +20,11 @@
}
impl UltradebugSpi {
- pub const PIN_CLOCK: u32 = 0;
- pub const PIN_MOSI: u32 = 1;
- pub const PIN_MISO: u32 = 2;
- pub const PIN_CHIP_SELECT: u32 = 3;
- pub const PIN_SPI_ZB: u32 = 4;
+ pub const PIN_CLOCK: u8 = 0;
+ pub const PIN_MOSI: u8 = 1;
+ pub const PIN_MISO: u8 = 2;
+ pub const PIN_CHIP_SELECT: u8 = 3;
+ pub const PIN_SPI_ZB: u8 = 4;
pub fn open(ultradebug: &Ultradebug) -> Result<Self> {
let mpsse = ultradebug.mpsse(ftdi::Interface::B)?;
// Note: platforms ultradebugs tristate their SPI lines
@@ -68,7 +59,7 @@
fn set_bits_per_word(&mut self, bits_per_word: u32) -> Result<()> {
match bits_per_word {
8 => Ok(()),
- _ => Err(Error::InvalidWordSize(bits_per_word).into()),
+ _ => Err(SpiError::InvalidWordSize(bits_per_word).into()),
}
}
diff --git a/sw/host/opentitanlib/src/util/mod.rs b/sw/host/opentitanlib/src/util/mod.rs
index e6c4ba9..4babbbb 100644
--- a/sw/host/opentitanlib/src/util/mod.rs
+++ b/sw/host/opentitanlib/src/util/mod.rs
@@ -5,3 +5,19 @@
pub mod bitfield;
pub mod file;
pub mod parse_int;
+
+/// The `collection` macro provides syntax for hash and set literals.
+#[macro_export]
+macro_rules! collection {
+ // map-like
+ ($($k:expr => $v:expr),* $(,)?) => {{
+ use std::iter::{Iterator, IntoIterator};
+ Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]))
+ }};
+
+ // set-like
+ ($($v:expr),* $(,)?) => {{
+ use std::iter::{Iterator, IntoIterator};
+ Iterator::collect(IntoIterator::into_iter([$($v),*]))
+ }};
+}
diff --git a/sw/host/opentitantool/src/command/gpio.rs b/sw/host/opentitantool/src/command/gpio.rs
index 3adaeb1..ce0652f 100644
--- a/sw/host/opentitantool/src/command/gpio.rs
+++ b/sw/host/opentitantool/src/command/gpio.rs
@@ -7,18 +7,19 @@
use structopt::StructOpt;
use opentitanlib::app::command::CommandDispatch;
+use opentitanlib::io::gpio::PinDirection;
use opentitanlib::transport::{Capability, Transport};
#[derive(Debug, StructOpt)]
/// Reads a GPIO pin.
pub struct GpioRead {
- #[structopt(short, long, help = "The GPIO pin to read")]
- pub pin: u32,
+ #[structopt(name = "PIN", help = "The GPIO pin to read")]
+ pub pin: String,
}
#[derive(serde::Serialize)]
pub struct GpioReadResult {
- pub pin: u32,
+ pub pin: String,
pub value: bool,
}
@@ -26,9 +27,9 @@
fn run(&self, transport: &mut dyn Transport) -> Result<Option<Box<dyn Serialize>>> {
transport.capabilities().request(Capability::GPIO).ok()?;
let gpio = transport.gpio()?;
- let value = gpio.read(self.pin)?;
+ let value = gpio.read(&self.pin)?;
Ok(Some(Box::new(GpioReadResult {
- pin: self.pin,
+ pin: self.pin.clone(),
value,
})))
}
@@ -37,10 +38,14 @@
#[derive(Debug, StructOpt)]
/// Writes a GPIO pin.
pub struct GpioWrite {
- #[structopt(short, long, help = "The GPIO pin to write")]
- pub pin: u32,
- #[structopt(short, long, help = "The value to write to the pin")]
- pub value: u8,
+ #[structopt(name = "PIN", help = "The GPIO pin to write")]
+ pub pin: String,
+ #[structopt(
+ name = "VALUE",
+ parse(try_from_str),
+ help = "The value to write to the pin"
+ )]
+ pub value: bool,
}
impl CommandDispatch for GpioWrite {
@@ -48,8 +53,30 @@
transport.capabilities().request(Capability::GPIO).ok()?;
let gpio = transport.gpio()?;
- gpio.write(self.pin, self.value != 0)?;
- let _value = gpio.read(self.pin)?;
+ gpio.write(&self.pin, self.value)?;
+ Ok(None)
+ }
+}
+
+#[derive(Debug, StructOpt)]
+/// Set the I/O direction of a GPIO pin.
+pub struct GpioSetDirection {
+ #[structopt(name = "PIN", help = "The GPIO pin to modify")]
+ pub pin: String,
+ #[structopt(
+ name = "DIRECTION",
+ possible_values = &PinDirection::variants(),
+ case_insensitive=true,
+ help = "The I/O direction of the pin"
+ )]
+ pub direction: PinDirection,
+}
+
+impl CommandDispatch for GpioSetDirection {
+ fn run(&self, transport: &mut dyn Transport) -> Result<Option<Box<dyn Serialize>>> {
+ transport.capabilities().request(Capability::GPIO).ok()?;
+ let gpio = transport.gpio()?;
+ gpio.set_direction(&self.pin, self.direction)?;
Ok(None)
}
}
@@ -59,4 +86,5 @@
pub enum GpioCommand {
Read(GpioRead),
Write(GpioWrite),
+ SetDirection(GpioSetDirection),
}