blob: c44d329c72bf2a269ba19a767e907262ae513fda [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 sha2::{Digest, Sha256};
use std::time::Duration;
use thiserror::Error;
use zerocopy::AsBytes;
use crate::app::TransportWrapper;
use crate::bootstrap::{Bootstrap, BootstrapOptions, UpdateProtocol};
use crate::impl_serializable_error;
use crate::io::spi::Transfer;
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 = 2048 - std::mem::size_of::<FrameHeader>();
const HASH_LEN: usize = 32;
const FLASH_BASE_ADDRESS: usize = 65536 * 8;
/// Computes the hash in the header.
fn header_hash(&self) -> [u8; Frame::HASH_LEN] {
let frame = self.as_bytes();
let sha = Sha256::digest(&frame[Frame::HASH_LEN..]);
sha.into()
}
/// Computes the hash over the entire frame.
fn frame_hash(&self) -> [u8; Frame::HASH_LEN] {
let mut digest = Sha256::digest(self.as_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.into()
}
/// Creates a sequence of frames based on a `payload` binary.
fn from_payload(payload: &[u8]) -> Vec<Frame> {
let mut frames = Vec::new();
let max_addr = (payload.chunks(4).rposition(|c| c != [0xff; 4]).unwrap_or(0) + 1) * 4;
let mut frame_num = 0;
let mut addr = 0;
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 + Self::FLASH_BASE_ADDRESS) 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;
}
if let Some(f) = frames.last_mut() {
f.header.frame_num |= Self::EOF;
}
frames
.iter_mut()
.for_each(|f| f.header.hash = f.header_hash());
frames
}
}
/// Implements the bootstrap protocol of previous Google Titan family chips.
pub struct Legacy {
/// Controls the time that CS is deasserted between frames. With Dauntless, 20ms has been
/// observed to be "too long", in that after the final frame was transmitted, the bootloader
/// could have already stopped listening to SPI and started booting the newly flashed code,
/// before OpenTitan tool could read the ACK of the final transaction. 1ms is what the
/// spiflash.cc tool from cr50-utils uses.
pub inter_frame_delay: Duration,
}
impl Legacy {
const INTER_FRAME_DELAY: Duration = Duration::from_millis(1);
const MAX_CONSECUTIVE_ERRORS: u32 = 100;
/// Creates a new `Primitive` protocol updater from `options`.
pub fn new(options: &BootstrapOptions) -> Self {
Self {
inter_frame_delay: options.inter_frame_delay.unwrap_or(Self::INTER_FRAME_DELAY),
}
}
}
#[derive(Debug, Error, serde::Serialize, serde::Deserialize)]
pub enum LegacyBootstrapError {
#[error("Boot rom not ready")]
NotReady,
#[error("Unknown boot rom error: {0}")]
Unknown(u8),
#[error("Boot rom error: NOREQUEST")]
NoRequest,
#[error("Boot rom error: NOMAGIC")]
NoMagic,
#[error("Boot rom error: TOOBIG")]
TooBig,
#[error("Boot rom error: TOOHIGH")]
TooHigh,
#[error("Boot rom error: NOALIGN")]
NoAlign,
#[error("Boot rom error: NOROUND")]
NoRound,
#[error("Boot rom error: BADKEY")]
BadKey,
#[error("Boot rom error: BADSTART")]
BadStart,
#[error("Boot rom error: NOWIPE")]
NoWipe,
#[error("Boot rom error: NOWIPE0")]
NoWipe0,
#[error("Boot rom error: NOWIPE1")]
NoWipe1,
#[error("Boot rom error: NOTEMPTY")]
NotEmpty,
#[error("Boot rom error: NOWRITE")]
NoWrite,
#[error("Boot rom error: BADADR")]
BadAdr,
#[error("Boot rom error: OVERFLOW")]
Overflow,
#[error("Repeated errors communicating with boot rom")]
RepeatedErrors,
}
impl_serializable_error!(LegacyBootstrapError);
impl From<u8> for LegacyBootstrapError {
fn from(value: u8) -> LegacyBootstrapError {
match value {
// All zeroes or all ones means that the bootloader is not yet ready to respond.
0 | 255 => LegacyBootstrapError::NotReady,
// Other values represent particular errors.
1 => LegacyBootstrapError::NoRequest,
2 => LegacyBootstrapError::NoMagic,
3 => LegacyBootstrapError::TooBig,
4 => LegacyBootstrapError::TooHigh,
5 => LegacyBootstrapError::NoAlign,
6 => LegacyBootstrapError::NoRound,
7 => LegacyBootstrapError::BadKey,
8 => LegacyBootstrapError::BadStart,
10 => LegacyBootstrapError::NoWipe,
11 => LegacyBootstrapError::NoWipe0,
12 => LegacyBootstrapError::NoWipe1,
13 => LegacyBootstrapError::NotEmpty,
14 => LegacyBootstrapError::NoWrite,
15 => LegacyBootstrapError::BadAdr,
16 => LegacyBootstrapError::Overflow,
n => LegacyBootstrapError::Unknown(n),
}
}
}
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,
container: &Bootstrap,
transport: &TransportWrapper,
payload: &[u8],
progress: &dyn Fn(u32, u32),
) -> Result<()> {
let spi = container.spi_params.create(transport, "BOOTSTRAP")?;
let frames = Frame::from_payload(payload);
// All frames up to but not including this index have been ack'ed by the bootloader.
// Once this reaches frames.len(), the operation was successful.
let mut first_unacked_index = 0;
// Counts the number of repeated failures in getting a frame ack.
let mut consecutive_errors = 0;
// Set if the past transaction ack'ed the block sent before that one, while transmitting
// the next. If so, we heuristically assume that the block just sent will be ack'ed in
// the upcoming bidirectional transaction.
let mut optimistic = false;
// Set if the past transaction ack'ed the block sent before that one, while
// re-transmitting the same block. If so, we do not care about the ack in the upcoming
// bidirectional transaction, as it turns out it was not necessary to perform the most
// recent re-transmission.
let mut becoming_optimistic = false;
loop {
if consecutive_errors > Self::MAX_CONSECUTIVE_ERRORS {
return Err(LegacyBootstrapError::RepeatedErrors.into());
}
let second_unacked_index = (first_unacked_index + 1).min(frames.len() - 1);
let transmit_index = if optimistic {
second_unacked_index
} else {
first_unacked_index
};
let frame = &frames[transmit_index];
eprint!("{}.", transmit_index);
std::thread::sleep(self.inter_frame_delay);
// Write the frame and read back the ack of a previously transmitted frame.
progress(frame.header.flash_offset, frame.data.len() as u32);
let mut response = [0u8; std::mem::size_of::<Frame>()];
spi.run_transaction(&mut [Transfer::Both(frame.as_bytes(), &mut response)])?;
if becoming_optimistic {
optimistic = true;
becoming_optimistic = false;
continue;
}
if response[..Frame::HASH_LEN]
.iter()
.all(|&x| x == response[0])
{
// A response consisteing of all identical bytes is a status code.
match LegacyBootstrapError::from(response[0]) {
LegacyBootstrapError::NotReady => {
consecutive_errors += 1;
continue; // Retry sending same frame.
}
error => return Err(error.into()),
}
}
if response[..Frame::HASH_LEN] == frames[first_unacked_index].frame_hash() {
first_unacked_index += 1;
} else if response[..Frame::HASH_LEN] == frames[second_unacked_index].frame_hash() {
first_unacked_index = second_unacked_index + 1;
} else {
consecutive_errors += 1;
optimistic = false;
continue;
}
consecutive_errors = 0;
if first_unacked_index == frames.len() {
// All frames acked, we are done.
break;
}
if !optimistic {
// Heuristic, become optimistic after second or later frame is ack'ed.
becoming_optimistic = first_unacked_index > 1;
}
}
eprintln!("success");
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
const SIMPLE_BIN: &[u8; 2048] = include_bytes!("simple.bin");
#[test]
fn test_small_binary() -> Result<()> {
let frames = Frame::from_payload(SIMPLE_BIN);
assert_eq!(frames[0].header.frame_num, 0);
assert_eq!(frames[0].header.flash_offset, 0x80000);
assert_eq!(
hex::encode(frames[0].header.hash),
"4e31bfd8b3be32358f2235c0f241f3970de575fc6aca0564aa6bf30adaf33910"
);
assert_eq!(frames[1].header.frame_num, 0x8000_0001);
assert_eq!(frames[1].header.flash_offset, 0x807d8);
assert_eq!(
hex::encode(frames[1].header.hash),
"ff584c07bbb0a039934a660bd49b7812af8ee847d1e675d9aba71c11fab3cfcb"
);
Ok(())
}
}