blob: 12980e146d6f1724d3976433c631000c11291995 [file] [log] [blame]
// 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 humantime::parse_duration;
use serde::{Deserialize, Serialize};
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::impl_serializable_error;
use crate::io::gpio::GpioPin;
use crate::io::spi::SpiParams;
use crate::io::uart::UartParams;
use crate::transport::Capability;
mod eeprom;
mod legacy;
mod primitive;
mod rescue;
pub use legacy::LegacyBootstrapError;
pub use rescue::RescueError;
#[derive(Debug, Error, Serialize, Deserialize)]
pub enum BootstrapError {
#[error("Invalid hash length: {0}")]
InvalidHashLength(usize),
}
impl_serializable_error!(BootstrapError);
arg_enum! {
/// `BootstrapProtocol` describes the supported types of bootstrap.
/// 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.
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum BootstrapProtocol {
Primitive,
Legacy,
Eeprom,
Rescue,
Emulator,
}
}
// Implementations of bootstrap need to implement the `UpdateProtocol` trait.
trait UpdateProtocol {
/// 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],
progress: &dyn Fn(u32, u32),
) -> Result<()>;
}
/// Options which control bootstrap behavior.
/// The meaning of each of these values depends on the specific bootstrap protocol being used.
#[derive(Clone, Debug, StructOpt, Serialize, Deserialize)]
pub struct BootstrapOptions {
#[structopt(flatten)]
pub uart_params: UartParams,
#[structopt(flatten)]
pub spi_params: SpiParams,
#[structopt(
short,
long,
possible_values = &BootstrapProtocol::variants(),
case_insensitive = true,
default_value = "eeprom",
help = "Bootstrap protocol to use"
)]
pub protocol: BootstrapProtocol,
#[structopt(
long,
help = "Whether to reset target and clear UART RX buffer after bootstrap. For CW310 only."
)]
pub clear_uart: Option<bool>,
#[structopt(long, parse(try_from_str=parse_duration), default_value = "100ms", help = "Duration of the reset delay")]
pub reset_delay: Duration,
#[structopt(long, parse(try_from_str=parse_duration), help = "Duration of the inter-frame delay")]
pub inter_frame_delay: Option<Duration>,
#[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<'a> {
pub protocol: BootstrapProtocol,
pub clear_uart_rx: bool,
pub uart_params: &'a UartParams,
pub spi_params: &'a SpiParams,
reset_pin: Rc<dyn GpioPin>,
reset_delay: Duration,
}
impl<'a> Bootstrap<'a> {
/// 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<()> {
Self::update_with_progress(transport, options, payload, |_, _| {})
}
/// Perform the update, sending the firmware `payload` to a SPI or UART target depending on
/// given `options`, which specifies protocol and port to use. The `progress` callback will
/// be called with the flash address and length of each chunk sent to the target device.
pub fn update_with_progress(
transport: &TransportWrapper,
options: &BootstrapOptions,
payload: &[u8],
progress: impl Fn(u32, u32),
) -> Result<()> {
if transport
.capabilities()?
.request(Capability::PROXY)
.ok()
.is_ok()
{
// The transport happens to be connection to a remove opentitan session. Pass
// payload along with all relevant command line arguments to the remote session, and
// it will run the actual bootstrapping logic.
transport.proxy_ops()?.bootstrap(options, payload)?;
return Ok(());
}
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 => Box::new(eeprom::Eeprom::new()),
BootstrapProtocol::Emulator => {
// Not intended to be implemented by this struct.
unimplemented!();
}
};
Bootstrap {
protocol: options.protocol,
clear_uart_rx: options.clear_uart.unwrap_or(false),
uart_params: &options.uart_params,
spi_params: &options.spi_params,
reset_pin: transport.gpio_pin("RESET")?,
reset_delay: options.reset_delay,
}
.do_update(updater, transport, payload, &progress)
}
fn do_update(
&self,
updater: Box<dyn UpdateProtocol>,
transport: &TransportWrapper,
payload: &[u8],
progress: &dyn Fn(u32, u32),
) -> Result<()> {
updater.verify_capabilities(self, transport)?;
let perform_bootstrap_reset = updater.uses_common_bootstrap_reset();
if perform_bootstrap_reset {
log::info!("Asserting bootstrap pins...");
transport.apply_pin_strapping("ROM_BOOTSTRAP")?;
transport.reset_target(self.reset_delay, self.clear_uart_rx)?;
log::info!("Performing bootstrap...");
}
let result = updater.update(self, transport, payload, progress);
if perform_bootstrap_reset {
log::info!("Releasing bootstrap pins...");
transport.remove_pin_strapping("ROM_BOOTSTRAP")?;
}
// Don't clear the UART RX buffer after bootstrap to preserve the bootstrap output.
transport.reset_target(self.reset_delay, false)?;
result
}
}