[opentitantool] Add non-SPI rescue protocol for bootstrapping
Ti50 firmware supports a serial protocol for flashing a replacement
image, which we would like opentitantool to be able to use. In order
to add this protocol a few changes to generic code has been made:
1. Refactoring of bootstrap/mod.rs to remove assumption of SPI and
bootstrap pin being used for all protocols.
2. Minor modifications to io/uart.rs trait and implementations, to
ensure uniform behavior of read_timeout(), and to simplify callers use
of write().
Signed-off-by: Jes B. Klinke <jbk@chromium.org>
Change-Id: I2a612abb2f2de0b2f1f72cdc7bf94c0a51ec99de
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index bb1e86a..907799e 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -16,6 +16,7 @@
"src/bootstrap/legacy.rs",
"src/bootstrap/mod.rs",
"src/bootstrap/primitive.rs",
+ "src/bootstrap/rescue.rs",
"src/crypto/mod.rs",
"src/crypto/sha256.rs",
"src/io/gpio.rs",
diff --git a/sw/host/opentitanlib/Cargo.toml b/sw/host/opentitanlib/Cargo.toml
index c975a69..71dd937 100644
--- a/sw/host/opentitanlib/Cargo.toml
+++ b/sw/host/opentitanlib/Cargo.toml
@@ -33,6 +33,7 @@
num-bigint-dig = "0.7.0"
num-traits = "0.2.14"
sha2 = "0.10.1"
+humantime = "2.1.0"
serde = { version="1", features=["serde_derive"] }
serde_json = "1"
diff --git a/sw/host/opentitanlib/src/bootstrap/legacy.rs b/sw/host/opentitanlib/src/bootstrap/legacy.rs
index 1d9d5ff..d857bd9 100644
--- a/sw/host/opentitanlib/src/bootstrap/legacy.rs
+++ b/sw/host/opentitanlib/src/bootstrap/legacy.rs
@@ -8,8 +8,10 @@
use thiserror::Error;
use zerocopy::AsBytes;
-use crate::bootstrap::{BootstrapOptions, UpdateProtocol};
-use crate::io::spi::{Target, Transfer};
+use crate::app::TransportWrapper;
+use crate::bootstrap::{Bootstrap, BootstrapOptions, UpdateProtocol};
+use crate::io::spi::Transfer;
+use crate::transport::Capability;
#[derive(AsBytes, Debug, Default)]
#[repr(C)]
@@ -205,8 +207,31 @@
}
impl UpdateProtocol for Legacy {
+ fn verify_capabilities(
+ &self,
+ _container: &Bootstrap,
+ transport: &TransportWrapper,
+ ) -> Result<()> {
+ transport
+ .capabilities()
+ .request(Capability::GPIO | Capability::SPI)
+ .ok()?;
+ Ok(())
+ }
+
+ fn uses_common_bootstrap_reset(&self) -> bool {
+ true
+ }
+
/// Performs the update protocol using the `transport` with the firmware `payload`.
- fn update(&self, spi: &dyn Target, payload: &[u8]) -> Result<()> {
+ fn update(
+ &self,
+ container: &Bootstrap,
+ transport: &TransportWrapper,
+ payload: &[u8],
+ ) -> Result<()> {
+ let spi = container.spi_params.create(transport)?;
+
let frames = Frame::from_payload(payload);
// All frames up to but not including this index have been ack'ed by the bootloader.
diff --git a/sw/host/opentitanlib/src/bootstrap/mod.rs b/sw/host/opentitanlib/src/bootstrap/mod.rs
index beb6883..b29ba99 100644
--- a/sw/host/opentitanlib/src/bootstrap/mod.rs
+++ b/sw/host/opentitanlib/src/bootstrap/mod.rs
@@ -3,16 +3,22 @@
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
+use humantime::parse_duration;
use serde::Deserialize;
+use std::rc::Rc;
use std::time::Duration;
use structopt::clap::arg_enum;
+use structopt::StructOpt;
use thiserror::Error;
+use crate::app::TransportWrapper;
use crate::io::gpio::GpioPin;
-use crate::io::spi::Target;
+use crate::io::spi::SpiParams;
+use crate::io::uart::UartParams;
mod legacy;
mod primitive;
+mod rescue;
#[derive(Debug, Error)]
pub enum BootstrapError {
@@ -22,9 +28,10 @@
arg_enum! {
/// `BootstrapProtocol` describes the supported types of bootstrap.
- /// The `Primitive` protocol is used by OpenTitan during development.
- /// The `Legacy` protocol is used by previous generations of Google Titan-class chips.
- /// The `Eeprom` protocol is planned to be implemented for OpenTitan.
+ /// The `Primitive` SPI protocol is used by OpenTitan during development.
+ /// The `Legacy` SPI protocol is used by previous generations of Google Titan-class chips.
+ /// The `Eeprom` SPI protocol is planned to be implemented for OpenTitan.
+ /// The `Rescue` UART protocol is used by Google Ti50 firmware.
/// The 'Emulator' value indicates that this tool has a direct way
/// of communicating with the OpenTitan emulator, to replace the
/// contents of the emulated flash storage.
@@ -33,42 +40,81 @@
Primitive,
Legacy,
Eeprom,
+ Rescue,
Emulator,
}
}
// Implementations of bootstrap need to implement the `UpdateProtocol` trait.
trait UpdateProtocol {
- fn update(&self, spi: &dyn Target, payload: &[u8]) -> Result<()>;
+ /// Called before any action is taken, to allow the protocol to verify that the transport
+ /// supports SPI/UART or whatever it needs.
+ fn verify_capabilities(
+ &self,
+ container: &Bootstrap,
+ transport: &TransportWrapper,
+ ) -> Result<()>;
+ /// Indicates whether the caller should assert the bootstrap pin and reset the chip, before
+ /// invoking update().
+ fn uses_common_bootstrap_reset(&self) -> bool;
+ /// Invoked to perform the actual transfer of an executable image to the OpenTitan chip.
+ fn update(
+ &self,
+ container: &Bootstrap,
+ transport: &TransportWrapper,
+ payload: &[u8],
+ ) -> Result<()>;
}
/// Options which control bootstrap behavior.
/// The meaning of each of these values depends on the specific bootstrap protocol being used.
-#[derive(Debug, Default)]
+#[derive(Debug, StructOpt)]
pub struct BootstrapOptions {
- /// How long to hold the reset pin during the reset sequence.
+ #[structopt(flatten)]
+ pub uart_params: UartParams,
+ #[structopt(flatten)]
+ pub spi_params: SpiParams,
+ #[structopt(
+ short,
+ long,
+ possible_values = &BootstrapProtocol::variants(),
+ case_insensitive = true,
+ default_value = "primitive",
+ help = "Bootstrap protocol to use"
+ )]
+ pub protocol: BootstrapProtocol,
+ #[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the reset delay")]
pub reset_delay: Option<Duration>,
- /// How long to delay between sending bootstrap frames.
+ #[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the inter-frame delay")]
pub inter_frame_delay: Option<Duration>,
- /// How long to delay during a flash erase operation.
+ #[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the flash-erase delay")]
pub flash_erase_delay: Option<Duration>,
}
/// Bootstrap wraps and drives the various bootstrap protocols.
-pub struct Bootstrap {
+pub struct Bootstrap<'a> {
pub protocol: BootstrapProtocol,
+ pub uart_params: &'a UartParams,
+ pub spi_params: &'a SpiParams,
+ reset_pin: Rc<dyn GpioPin>,
+ bootstrap_pin: Rc<dyn GpioPin>,
reset_delay: Duration,
- updater: Box<dyn UpdateProtocol>,
}
-impl Bootstrap {
+impl<'a> Bootstrap<'a> {
const RESET_DELAY: Duration = Duration::from_millis(200);
- /// Consrtuct a `Bootstrap` struct configured to use `protocol` and `options`.
- pub fn new(protocol: BootstrapProtocol, options: BootstrapOptions) -> Result<Self> {
- let updater: Box<dyn UpdateProtocol> = match protocol {
+ /// Perform the update, sending the firmware `payload` to a SPI or UART target depending on
+ /// given `options`, which specifies protocol and port to use.
+ pub fn update(
+ transport: &TransportWrapper,
+ options: &BootstrapOptions,
+ payload: &[u8],
+ ) -> Result<()> {
+ let updater: Box<dyn UpdateProtocol> = match options.protocol {
BootstrapProtocol::Primitive => Box::new(primitive::Primitive::new(&options)),
BootstrapProtocol::Legacy => Box::new(legacy::Legacy::new(&options)),
+ BootstrapProtocol::Rescue => Box::new(rescue::Rescue::new(&options)),
BootstrapProtocol::Eeprom => {
unimplemented!();
}
@@ -77,36 +123,44 @@
unimplemented!();
}
};
- Ok(Bootstrap {
- protocol,
+ Bootstrap {
+ protocol: options.protocol,
+ uart_params: &options.uart_params,
+ spi_params: &options.spi_params,
+ reset_pin: transport.gpio_pin("RESET")?,
+ bootstrap_pin: transport.gpio_pin("BOOTSTRAP")?,
reset_delay: options.reset_delay.unwrap_or(Self::RESET_DELAY),
- updater: updater,
- })
+ }
+ .do_update(updater, transport, payload)
}
- /// Perform the update, sending the firmware `payload` to the `spi` target,
- /// using `gpio` to sequence the reset and bootstrap pins.
- pub fn update(
+ fn do_update(
&self,
- spi: &dyn Target,
- reset: &dyn GpioPin,
- bootstrap: &dyn GpioPin,
+ updater: Box<dyn UpdateProtocol>,
+ transport: &TransportWrapper,
payload: &[u8],
) -> Result<()> {
- log::info!("Asserting bootstrap pins...");
- bootstrap.write(true)?;
+ updater.verify_capabilities(&self, transport)?;
+ let perform_bootstrap_reset = updater.uses_common_bootstrap_reset();
- log::info!("Restting the target...");
- reset.write(false)?; // Low active
- std::thread::sleep(self.reset_delay);
- reset.write(true)?; // Release reset
- std::thread::sleep(self.reset_delay);
+ if perform_bootstrap_reset {
+ log::info!("Asserting bootstrap pins...");
+ self.bootstrap_pin.write(true)?;
- log::info!("Performing bootstrap...");
- self.updater.update(spi, payload)?;
+ log::info!("Reseting the target...");
+ self.reset_pin.write(false)?; // Low active
+ std::thread::sleep(self.reset_delay);
+ self.reset_pin.write(true)?; // Release reset
+ std::thread::sleep(self.reset_delay);
- log::info!("Releasing bootstrap pins...");
- bootstrap.write(false)?;
- Ok(())
+ log::info!("Performing bootstrap...");
+ }
+ let result = updater.update(&self, transport, payload);
+
+ if perform_bootstrap_reset {
+ log::info!("Releasing bootstrap pins...");
+ self.bootstrap_pin.write(false)?;
+ }
+ result
}
}
diff --git a/sw/host/opentitanlib/src/bootstrap/primitive.rs b/sw/host/opentitanlib/src/bootstrap/primitive.rs
index 4c7d4e2..37ed60e 100644
--- a/sw/host/opentitanlib/src/bootstrap/primitive.rs
+++ b/sw/host/opentitanlib/src/bootstrap/primitive.rs
@@ -7,8 +7,10 @@
use std::time::Duration;
use zerocopy::AsBytes;
-use crate::bootstrap::{BootstrapOptions, UpdateProtocol};
-use crate::io::spi::{Target, Transfer};
+use crate::app::TransportWrapper;
+use crate::bootstrap::{Bootstrap, BootstrapOptions, UpdateProtocol};
+use crate::io::spi::Transfer;
+use crate::transport::Capability;
#[derive(AsBytes, Debug, Default)]
#[repr(C)]
@@ -105,8 +107,31 @@
}
impl UpdateProtocol for Primitive {
+ fn verify_capabilities(
+ &self,
+ _container: &Bootstrap,
+ transport: &TransportWrapper,
+ ) -> Result<()> {
+ transport
+ .capabilities()
+ .request(Capability::GPIO | Capability::SPI)
+ .ok()?;
+ Ok(())
+ }
+
+ fn uses_common_bootstrap_reset(&self) -> bool {
+ true
+ }
+
/// Performs the update protocol using the `transport` with the firmware `payload`.
- fn update(&self, spi: &dyn Target, payload: &[u8]) -> Result<()> {
+ fn update(
+ &self,
+ container: &Bootstrap,
+ transport: &TransportWrapper,
+ payload: &[u8],
+ ) -> Result<()> {
+ let spi = container.spi_params.create(transport)?;
+
let frames = Frame::from_payload(payload);
let mut i = 0;
diff --git a/sw/host/opentitanlib/src/bootstrap/rescue.rs b/sw/host/opentitanlib/src/bootstrap/rescue.rs
new file mode 100644
index 0000000..d6585c2
--- /dev/null
+++ b/sw/host/opentitanlib/src/bootstrap/rescue.rs
@@ -0,0 +1,356 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use anyhow::{bail, ensure, Result};
+use mundane::hash::{Digest, Hasher, Sha256};
+use std::time::{Duration, Instant};
+use thiserror::Error;
+use zerocopy::AsBytes;
+
+use crate::app::TransportWrapper;
+use crate::bootstrap::{Bootstrap, BootstrapOptions, UpdateProtocol};
+use crate::io::uart::Uart;
+use crate::transport::Capability;
+
+#[derive(AsBytes, Debug, Default)]
+#[repr(C)]
+struct FrameHeader {
+ hash: [u8; Frame::HASH_LEN],
+ frame_num: u32,
+ flash_offset: u32,
+}
+
+#[derive(AsBytes, Debug)]
+#[repr(C)]
+struct Frame {
+ header: FrameHeader,
+ data: [u8; Frame::DATA_LEN],
+}
+
+impl Default for Frame {
+ fn default() -> Self {
+ Frame {
+ header: Default::default(),
+ data: [0xff; Frame::DATA_LEN],
+ }
+ }
+}
+
+impl Frame {
+ const EOF: u32 = 0x8000_0000;
+ const FLASH_SECTOR_SIZE: usize = 2048;
+ const FLASH_SECTOR_MASK: usize = Self::FLASH_SECTOR_SIZE - 1;
+ const FLASH_BUFFER_SIZE: usize = 128;
+ const FLASH_BUFFER_MASK: usize = Self::FLASH_BUFFER_SIZE - 1;
+ const DATA_LEN: usize = 1024 - std::mem::size_of::<FrameHeader>();
+ const HASH_LEN: usize = 32;
+ const MAGIC_HEADER: [u8; 4] = [0xfd, 0xff, 0xff, 0xff];
+
+ /// Computes the hash in the header.
+ fn header_hash(&self) -> [u8; Frame::HASH_LEN] {
+ let frame = self.as_bytes();
+ let sha = Sha256::hash(&frame[Frame::HASH_LEN..]);
+ sha.bytes()
+ }
+
+ /// Computes the hash over the entire frame.
+ fn frame_hash(&self) -> [u8; Frame::HASH_LEN] {
+ let sha = Sha256::hash(self.as_bytes());
+ let mut digest = sha.bytes();
+ // Touch up zeroes into ones, as that is what the old chips are doing.
+ for b in &mut digest {
+ if *b == 0 {
+ *b = 1;
+ }
+ }
+ digest
+ }
+
+ /// Creates a sequence of frames based on a `payload` binary.
+ fn from_payload(payload: &[u8]) -> Result<Vec<Frame>> {
+ // The given payload will contain up to four sections concatenated together:
+ // RO_A, RW_A optionally follwed by RO_B, RW_B
+ // Each section starts with a magic number on at least a 256 byte boundary.
+
+ // This rescue protocol uses the RW_A section only, which will start at the second
+ // occurrance of the magic value, and end at the third occurrence or at the end of the
+ // file.
+
+ ensure!(
+ payload.starts_with(&Self::MAGIC_HEADER),
+ RescueError::ImageFormatError
+ );
+
+ // Find second occurrence of magic value.
+ let min_addr = match payload[256..]
+ .chunks(256)
+ .position(|c| &c[0..4] == &Self::MAGIC_HEADER)
+ {
+ Some(n) => (n + 1) * 256,
+ None => bail!(RescueError::ImageFormatError),
+ };
+
+ // Find third occurrence of magic value.
+ let max_addr = match payload[min_addr + 256..]
+ .chunks(256)
+ .position(|c| &c[0..4] == &Self::MAGIC_HEADER)
+ {
+ Some(n) => (n + 1) * 256 + min_addr,
+ None => payload.len(),
+ };
+
+ // Trim trailing 0xff bytes.
+ let max_addr = (payload[..max_addr]
+ .chunks(4)
+ .rposition(|c| c != &[0xff; 4])
+ .unwrap_or(0)
+ + 1)
+ * 4;
+
+ let mut frames = Vec::new();
+ let mut frame_num = 0;
+ let mut addr = min_addr;
+ while addr < max_addr {
+ // Try skipping over 0xffffffff words.
+ let nonempty_addr = addr
+ + payload[addr..]
+ .chunks(4)
+ .position(|c| c != &[0xff; 4])
+ .unwrap()
+ * 4;
+ let skip_addr = nonempty_addr & !Self::FLASH_SECTOR_MASK;
+ if skip_addr > addr && (addr == 0 || addr & Self::FLASH_BUFFER_MASK != 0) {
+ // Can only skip from the start or if the last addr wasn't an exact multiple of
+ // 128 (per H1D boot rom).
+ addr = skip_addr;
+ }
+
+ let mut frame = Frame {
+ header: FrameHeader {
+ frame_num,
+ flash_offset: addr as u32,
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+ let slice_size = Self::DATA_LEN.min(payload.len() - addr);
+ frame.data[..slice_size].copy_from_slice(&payload[addr..addr + slice_size]);
+ frames.push(frame);
+
+ addr += Self::DATA_LEN;
+ frame_num += 1;
+ }
+ frames.last_mut().map(|f| f.header.frame_num |= Self::EOF);
+ frames
+ .iter_mut()
+ .for_each(|f| f.header.hash = f.header_hash());
+ Ok(frames)
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum RescueError {
+ #[error("Unrecognized image file format")]
+ ImageFormatError,
+ #[error("Synchronization error communicating with boot rom")]
+ SyncError,
+ #[error("Repeated errors communicating with boot rom")]
+ RepeatedErrors,
+}
+
+/// Implements the UART rescue protocol of Google Ti50 firmware.
+pub struct Rescue {}
+
+impl Rescue {
+ /// Abort if a block has not been accepted after this number of retries.
+ const MAX_CONSECUTIVE_ERRORS: u32 = 50;
+ /// Take some measure to regain protocol synchronization, in case of this number of retries
+ /// of the same block.
+ const RESYNC_AFTER_CONSECUTIVE_ERRORS: u32 = 3;
+
+ /// Creates a new `Rescue` protocol updater from `options`.
+ pub fn new(_options: &BootstrapOptions) -> Self {
+ Self {}
+ }
+
+ /// Waits for some time for a character, returns None on timeout.
+ fn read_char(&self, uart: &dyn Uart) -> Option<char> {
+ let mut buf = [0u8; 1];
+ match uart.read_timeout(&mut buf, Duration::from_millis(100)) {
+ Ok(1) => Some(buf[0] as char),
+ Ok(_) => None,
+ _ => None,
+ }
+ }
+
+ /// Waits some time for data, returning true if the given string was seen in full, or false
+ /// as soon as a non-matching character is received or on timeout.
+ fn expect_string(&self, uart: &dyn Uart, s: &str) -> bool {
+ for expected_ch in s.chars() {
+ match self.read_char(uart) {
+ Some(ch) if ch == expected_ch => (),
+ _ => return false,
+ }
+ }
+ true
+ }
+
+ /// Reads and discards any characters in the receive buffer, waiting a little while for any
+ /// more which will also be discarded.
+ fn flush_rx(&self, uart: &dyn Uart) {
+ let mut response = [0u8; Frame::HASH_LEN];
+ loop {
+ match uart.read_timeout(&mut response, Duration::from_millis(500)) {
+ Ok(0) | Err(_) => break,
+ Ok(_) => continue,
+ }
+ }
+ }
+
+ /// As the 1024 byte blocks sent to the chip have no discernible header, the sender and
+ /// receiver could be "out of sync". This is resolved by sending one byte at a time, and
+ /// observing when the chip sends a response (which will be a rejection due to checksum).
+ fn synchronize(&self, uart: &dyn Uart) -> Result<()> {
+ // Most likely, only a few "extra" bytes have been sent during initial negotiation.
+ // Send almost a complete block in one go, and then send each of the last 16 bytes one
+ // at a time, slowly enough to detect a response before sending the next byte.
+ uart.write(&[0u8; 1008])?;
+ let mut response = [0u8; 1];
+ let limit = match uart.read_timeout(&mut response, Duration::from_millis(50)) {
+ Ok(0) | Err(_) => 16,
+ Ok(_) => {
+ // A response at this point must mean that more than 16 bytes had already been
+ // sent before entering this method. This will be resolved by doing another
+ // slower round of 1024 bytes with delay in between every one.
+ self.flush_rx(uart);
+ 1024
+ }
+ };
+ for _ in 0..limit {
+ uart.write(&[0u8; 1])?;
+ match uart.read_timeout(&mut response, Duration::from_millis(50)) {
+ Ok(0) | Err(_) => (),
+ Ok(_) => {
+ self.flush_rx(uart);
+ return Ok(());
+ }
+ }
+ }
+ Err(RescueError::SyncError.into())
+ }
+
+ /// Reset the chip and send the magic 'r' character at the opportune moment during boot in
+ /// order to enter rescue more, repeat if necessary.
+ fn enter_rescue_mode(&self, container: &Bootstrap, uart: &dyn Uart) -> Result<()> {
+ // Attempt getting the attention of the bootloader.
+ let timeout = Duration::from_millis(2000);
+ for _ in 0..Self::MAX_CONSECUTIVE_ERRORS {
+ eprint!("Resetting...");
+ container.reset_pin.write(false)?; // Low active
+ std::thread::sleep(container.reset_delay);
+ container.reset_pin.write(true)?; // Release reset
+ let stopwatch = Instant::now();
+ while stopwatch.elapsed() < timeout {
+ if !self.expect_string(uart, "Bldr |") {
+ continue;
+ }
+ uart.write(&['r' as u8])?;
+ eprint!("a.");
+ while stopwatch.elapsed() < timeout {
+ if !self.expect_string(uart, "oops?|") {
+ continue;
+ }
+ uart.write(&['r' as u8])?;
+ eprint!("b.");
+ if self.expect_string(uart, "escue") {
+ eprintln!("c: Entered rescue mode!");
+ self.synchronize(uart)?;
+ return Ok(());
+ }
+ }
+ }
+ eprintln!(" Failed to enter rescue mode.");
+ }
+ bail!(RescueError::RepeatedErrors);
+ }
+}
+
+impl UpdateProtocol for Rescue {
+ fn verify_capabilities(
+ &self,
+ _container: &Bootstrap,
+ transport: &TransportWrapper,
+ ) -> Result<()> {
+ transport
+ .capabilities()
+ .request(Capability::GPIO | Capability::UART)
+ .ok()?;
+ Ok(())
+ }
+
+ /// Returns false, in order to as the containing Bootstrap struct to not perform standard
+ /// BOOTSTRAP/RESET sequence.
+ fn uses_common_bootstrap_reset(&self) -> bool {
+ false
+ }
+
+ /// Performs the update protocol using the `transport` with the firmware `payload`.
+ fn update(
+ &self,
+ container: &Bootstrap,
+ transport: &TransportWrapper,
+ payload: &[u8],
+ ) -> Result<()> {
+ let frames = Frame::from_payload(payload)?;
+ let uart = container.uart_params.create(transport)?;
+
+ self.enter_rescue_mode(container, &*uart)?;
+
+ // Send frames one at a time.
+ 'next_block: for (idx, frame) in frames.iter().enumerate() {
+ for consecutive_errors in 0..Self::MAX_CONSECUTIVE_ERRORS {
+ eprint!("{}.", idx);
+ uart.write(frame.as_bytes())?;
+ let mut response = [0u8; Frame::HASH_LEN];
+ let mut index = 0;
+ while index < Frame::HASH_LEN {
+ let timeout = if index == 0 {
+ Duration::from_millis(1000)
+ } else {
+ Duration::from_millis(10)
+ };
+ match uart.read_timeout(&mut response[index..], timeout) {
+ Ok(0) | Err(_) => break,
+ Ok(n) => index += n,
+ }
+ }
+ if index < Frame::HASH_LEN {
+ eprint!("sync.");
+ self.synchronize(&*uart)?;
+ continue;
+ }
+ if response[4..].chunks(4).all(|x| x == &response[..4]) {
+ eprint!("sync.");
+ self.synchronize(&*uart)?;
+ } else if response == frame.frame_hash() {
+ continue 'next_block;
+ } else {
+ self.flush_rx(&*uart);
+ if consecutive_errors >= Self::RESYNC_AFTER_CONSECUTIVE_ERRORS {
+ eprint!("sync.");
+ self.synchronize(&*uart)?;
+ }
+ }
+ }
+ bail!(RescueError::RepeatedErrors);
+ }
+
+ // Reset, in order to leave rescue mode.
+ container.reset_pin.write(false)?; // Low active
+ std::thread::sleep(container.reset_delay);
+ container.reset_pin.write(true)?; // Release reset
+ eprintln!("Success!");
+ Ok(())
+ }
+}
diff --git a/sw/host/opentitanlib/src/io/uart.rs b/sw/host/opentitanlib/src/io/uart.rs
index 0eea670..7728be0 100644
--- a/sw/host/opentitanlib/src/io/uart.rs
+++ b/sw/host/opentitanlib/src/io/uart.rs
@@ -45,5 +45,5 @@
fn read_timeout(&self, buf: &mut [u8], timeout: Duration) -> Result<usize>;
/// Writes data from `buf` to the UART.
- fn write(&self, buf: &[u8]) -> Result<usize>;
+ fn write(&self, buf: &[u8]) -> Result<()>;
}
diff --git a/sw/host/opentitanlib/src/transport/cw310/uart.rs b/sw/host/opentitanlib/src/transport/cw310/uart.rs
index 7f33680..1038e4e 100644
--- a/sw/host/opentitanlib/src/transport/cw310/uart.rs
+++ b/sw/host/opentitanlib/src/transport/cw310/uart.rs
@@ -79,7 +79,11 @@
}
/// Writes data from `buf` to the UART.
- fn write(&self, buf: &[u8]) -> Result<usize> {
- Ok(self.port.borrow_mut().write(buf)?)
+ fn write(&self, mut buf: &[u8]) -> Result<()> {
+ while buf.len() > 0 {
+ let written = self.port.borrow_mut().write(buf)?;
+ buf = &buf[written..];
+ }
+ Ok(())
}
}
diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs b/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs
index 1157e57..2e13d82 100644
--- a/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs
+++ b/sw/host/opentitanlib/src/transport/hyperdebug/uart.rs
@@ -43,8 +43,12 @@
Ok(self.port.borrow_mut().read(buf)?)
}
- fn write(&self, buf: &[u8]) -> Result<usize> {
- Ok(self.port.borrow_mut().write(buf)?)
+ fn write(&self, mut buf: &[u8]) -> Result<()> {
+ while buf.len() > 0 {
+ let written = self.port.borrow_mut().write(buf)?;
+ buf = &buf[written..];
+ }
+ Ok(())
}
fn get_baudrate(&self) -> u32 {
diff --git a/sw/host/opentitanlib/src/transport/ultradebug/uart.rs b/sw/host/opentitanlib/src/transport/ultradebug/uart.rs
index c6475c4..7af655b 100644
--- a/sw/host/opentitanlib/src/transport/ultradebug/uart.rs
+++ b/sw/host/opentitanlib/src/transport/ultradebug/uart.rs
@@ -5,7 +5,9 @@
use anyhow::Result;
use safe_ftdi as ftdi;
use std::cell::RefCell;
-use std::time::Duration;
+use std::cmp;
+use std::thread;
+use std::time::{Duration, Instant};
use crate::io::uart::Uart;
use crate::transport::ultradebug::Ultradebug;
@@ -48,11 +50,21 @@
Ok(())
}
- fn read_timeout(&self, buf: &mut [u8], _timeout: Duration) -> Result<usize> {
- // Note: my recollection is that there is no way to set a read timeout
- // for the UART. If there are no characters ready, the FTDI device
- // simply returns a zero-length read.
- self.read(buf)
+ fn read_timeout(&self, buf: &mut [u8], timeout: Duration) -> Result<usize> {
+ let now = Instant::now();
+ let count = self.read(buf)?;
+ if count > 0 {
+ return Ok(count);
+ }
+ let short_delay = cmp::min(timeout.mul_f32(0.1), Duration::from_millis(20));
+ while now.elapsed() < timeout {
+ thread::sleep(short_delay);
+ let count = self.read(buf)?;
+ if count > 0 {
+ return Ok(count);
+ }
+ }
+ Ok(0)
}
fn read(&self, buf: &mut [u8]) -> Result<usize> {
@@ -60,13 +72,12 @@
Ok(n as usize)
}
- fn write(&self, buf: &[u8]) -> Result<usize> {
- let mut total = 0usize;
+ fn write(&self, mut buf: &[u8]) -> Result<()> {
let inner = self.inner.borrow();
- while total < buf.len() {
- let n = inner.device.write_data(&buf[total..])?;
- total += n as usize;
+ while buf.len() > 0 {
+ let n = inner.device.write_data(buf)?;
+ buf = &buf[n as usize..];
}
- Ok(total)
+ Ok(())
}
}
diff --git a/sw/host/opentitanlib/src/transport/verilator/uart.rs b/sw/host/opentitanlib/src/transport/verilator/uart.rs
index f74cc28..3759ca7 100644
--- a/sw/host/opentitanlib/src/transport/verilator/uart.rs
+++ b/sw/host/opentitanlib/src/transport/verilator/uart.rs
@@ -47,8 +47,7 @@
Ok(self.file.borrow_mut().read(buf)?)
}
- fn write(&self, buf: &[u8]) -> Result<usize> {
- self.file.borrow_mut().write_all(buf)?;
- Ok(buf.len())
+ fn write(&self, buf: &[u8]) -> Result<()> {
+ Ok(self.file.borrow_mut().write_all(buf)?)
}
}
diff --git a/sw/host/opentitantool/Cargo.toml b/sw/host/opentitantool/Cargo.toml
index aa6ea01..44f0b80 100644
--- a/sw/host/opentitantool/Cargo.toml
+++ b/sw/host/opentitantool/Cargo.toml
@@ -24,7 +24,6 @@
regex = "1"
nix = "0.17.0"
indicatif = "0.16.2"
-humantime = "2.1.0"
directories = "4.0.1"
shellwords = "1.1.0"
diff --git a/sw/host/opentitantool/src/command/bootstrap.rs b/sw/host/opentitantool/src/command/bootstrap.rs
index 2b0874c..aa552d5 100644
--- a/sw/host/opentitantool/src/command/bootstrap.rs
+++ b/sw/host/opentitantool/src/command/bootstrap.rs
@@ -4,18 +4,14 @@
use anyhow::{ensure, Result};
use erased_serde::Serialize;
-use humantime::parse_duration;
use std::any::Any;
use std::path::PathBuf;
-use std::time::Duration;
use structopt::StructOpt;
use opentitanlib::app::command::CommandDispatch;
use opentitanlib::app::TransportWrapper;
use opentitanlib::bootstrap::{Bootstrap, BootstrapOptions, BootstrapProtocol};
-use opentitanlib::io::spi::SpiParams;
use opentitanlib::transport;
-use opentitanlib::transport::Capability;
use opentitanlib::util::image::ImageAssembler;
use opentitanlib::util::parse_int::ParseInt;
@@ -23,22 +19,7 @@
#[derive(Debug, StructOpt)]
pub struct BootstrapCommand {
#[structopt(flatten)]
- params: SpiParams,
- #[structopt(
- short,
- long,
- possible_values = &BootstrapProtocol::variants(),
- case_insensitive = true,
- default_value = "primitive",
- help = "Bootstrap protocol to use"
- )]
- protocol: BootstrapProtocol,
- #[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the reset delay")]
- reset_delay: Option<Duration>,
- #[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the inter-frame delay")]
- inter_frame_delay: Option<Duration>,
- #[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the flash-erase delay")]
- flash_erase_delay: Option<Duration>,
+ bootstrap_options: BootstrapOptions,
#[structopt(
long,
parse(try_from_str=usize::from_str),
@@ -97,27 +78,11 @@
!self.filename.is_empty(),
"You must supply at least one filename"
);
- if self.protocol == BootstrapProtocol::Emulator {
+ if self.bootstrap_options.protocol == BootstrapProtocol::Emulator {
return self.bootstrap_using_direct_emulator_integration(transport);
}
- transport
- .capabilities()
- .request(Capability::GPIO | Capability::SPI)
- .ok()?;
-
- let options = BootstrapOptions {
- reset_delay: self.reset_delay,
- inter_frame_delay: self.inter_frame_delay,
- flash_erase_delay: self.flash_erase_delay,
- };
- let bootstrap = Bootstrap::new(self.protocol, options)?;
-
- let spi = self.params.create(transport)?;
- let reset_pin = transport.gpio_pin("RESET")?;
- let bootstrap_pin = transport.gpio_pin("BOOTSTRAP")?;
- let payload = self.payload()?;
- bootstrap.update(&*spi, &*reset_pin, &*bootstrap_pin, &payload)?;
+ Bootstrap::update(&transport, &self.bootstrap_options, &self.payload()?)?;
Ok(None)
}
}
diff --git a/third_party/cargo/crates.bzl b/third_party/cargo/crates.bzl
index bf6dd5f..95a633a 100644
--- a/third_party/cargo/crates.bzl
+++ b/third_party/cargo/crates.bzl
@@ -18,6 +18,7 @@
"deser-hjson": "@raze__deser_hjson__1_0_2//:deser_hjson",
"erased-serde": "@raze__erased_serde__0_3_16//:erased_serde",
"hex": "@raze__hex__0_4_3//:hex",
+ "humantime": "@raze__humantime__2_1_0//:humantime",
"lazy_static": "@raze__lazy_static__1_4_0//:lazy_static",
"log": "@raze__log__0_4_14//:log",
"nix": "@raze__nix__0_17_0//:nix",
@@ -48,7 +49,6 @@
"env_logger": "@raze__env_logger__0_8_4//:env_logger",
"erased-serde": "@raze__erased_serde__0_3_16//:erased_serde",
"hex": "@raze__hex__0_4_3//:hex",
- "humantime": "@raze__humantime__2_1_0//:humantime",
"indicatif": "@raze__indicatif__0_16_2//:indicatif",
"log": "@raze__log__0_4_14//:log",
"nix": "@raze__nix__0_17_0//:nix",