opentitanlib/bootstrap/
legacy.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use sha2::{Digest, Sha256};
7use std::time::Duration;
8use thiserror::Error;
9use zerocopy::{Immutable, IntoBytes};
10
11use crate::app::TransportWrapper;
12use crate::bootstrap::{Bootstrap, BootstrapOptions, UpdateProtocol};
13use crate::impl_serializable_error;
14use crate::io::spi::Transfer;
15use crate::transport::{Capability, ProgressIndicator};
16
17#[derive(Immutable, IntoBytes, Debug, Default)]
18#[repr(C)]
19struct FrameHeader {
20    hash: [u8; Frame::HASH_LEN],
21    frame_num: u32,
22    flash_offset: u32,
23}
24
25#[derive(Immutable, IntoBytes, Debug)]
26#[repr(C)]
27struct Frame {
28    header: FrameHeader,
29    data: [u8; Frame::DATA_LEN],
30}
31
32impl Default for Frame {
33    fn default() -> Self {
34        Frame {
35            header: Default::default(),
36            data: [0xff; Frame::DATA_LEN],
37        }
38    }
39}
40
41impl Frame {
42    const EOF: u32 = 0x8000_0000;
43    const FLASH_SECTOR_SIZE: usize = 2048;
44    const FLASH_SECTOR_MASK: usize = Self::FLASH_SECTOR_SIZE - 1;
45    const FLASH_BUFFER_SIZE: usize = 128;
46    const FLASH_BUFFER_MASK: usize = Self::FLASH_BUFFER_SIZE - 1;
47    const DATA_LEN: usize = 2048 - std::mem::size_of::<FrameHeader>();
48    const HASH_LEN: usize = 32;
49    const FLASH_BASE_ADDRESS: usize = 65536 * 8;
50
51    /// Computes the hash in the header.
52    fn header_hash(&self) -> [u8; Frame::HASH_LEN] {
53        let frame = self.as_bytes();
54        let sha = Sha256::digest(&frame[Frame::HASH_LEN..]);
55        sha.into()
56    }
57
58    /// Computes the hash over the entire frame.
59    fn frame_hash(&self) -> [u8; Frame::HASH_LEN] {
60        let mut digest = Sha256::digest(self.as_bytes());
61        // Touch up zeroes into ones, as that is what the old chips are doing.
62        for b in &mut digest {
63            if *b == 0 {
64                *b = 1;
65            }
66        }
67        digest.into()
68    }
69
70    /// Creates a sequence of frames based on a `payload` binary.
71    fn from_payload(payload: &[u8]) -> Vec<Frame> {
72        let mut frames = Vec::new();
73
74        let max_addr = (payload.chunks(4).rposition(|c| c != [0xff; 4]).unwrap_or(0) + 1) * 4;
75
76        let mut frame_num = 0;
77        let mut addr = 0;
78        while addr < max_addr {
79            // Try skipping over 0xffffffff words.
80            let nonempty_addr = addr
81                + payload[addr..]
82                    .chunks(4)
83                    .position(|c| c != [0xff; 4])
84                    .unwrap()
85                    * 4;
86            let skip_addr = nonempty_addr & !Self::FLASH_SECTOR_MASK;
87            if skip_addr > addr && (addr == 0 || addr & Self::FLASH_BUFFER_MASK != 0) {
88                // Can only skip from the start or if the last addr wasn't an exact multiple of
89                // 128 (per H1D boot rom).
90                addr = skip_addr;
91            }
92
93            let mut frame = Frame {
94                header: FrameHeader {
95                    frame_num,
96                    flash_offset: (addr + Self::FLASH_BASE_ADDRESS) as u32,
97                    ..Default::default()
98                },
99                ..Default::default()
100            };
101            let slice_size = Self::DATA_LEN.min(payload.len() - addr);
102            frame.data[..slice_size].copy_from_slice(&payload[addr..addr + slice_size]);
103            frames.push(frame);
104
105            addr += Self::DATA_LEN;
106            frame_num += 1;
107        }
108        if let Some(f) = frames.last_mut() {
109            f.header.frame_num |= Self::EOF;
110        }
111        frames
112            .iter_mut()
113            .for_each(|f| f.header.hash = f.header_hash());
114        frames
115    }
116}
117
118/// Implements the bootstrap protocol of previous Google Titan family chips.
119pub struct Legacy {
120    /// Controls the time that CS is deasserted between frames.  With Dauntless, 20ms has been
121    /// observed to be "too long", in that after the final frame was transmitted, the bootloader
122    /// could have already stopped listening to SPI and started booting the newly flashed code,
123    /// before OpenTitan tool could read the ACK of the final transaction.  1ms is what the
124    /// spiflash.cc tool from cr50-utils uses.
125    pub inter_frame_delay: Duration,
126}
127
128impl Legacy {
129    const INTER_FRAME_DELAY: Duration = Duration::from_millis(1);
130    const MAX_CONSECUTIVE_ERRORS: u32 = 100;
131
132    /// Creates a new `Primitive` protocol updater from `options`.
133    pub fn new(options: &BootstrapOptions) -> Self {
134        Self {
135            inter_frame_delay: options.inter_frame_delay.unwrap_or(Self::INTER_FRAME_DELAY),
136        }
137    }
138}
139
140#[derive(Debug, Error, serde::Serialize, serde::Deserialize)]
141pub enum LegacyBootstrapError {
142    #[error("Boot rom not ready")]
143    NotReady,
144    #[error("Unknown boot rom error: {0}")]
145    Unknown(u8),
146    #[error("Boot rom error: NOREQUEST")]
147    NoRequest,
148    #[error("Boot rom error: NOMAGIC")]
149    NoMagic,
150    #[error("Boot rom error: TOOBIG")]
151    TooBig,
152    #[error("Boot rom error: TOOHIGH")]
153    TooHigh,
154    #[error("Boot rom error: NOALIGN")]
155    NoAlign,
156    #[error("Boot rom error: NOROUND")]
157    NoRound,
158    #[error("Boot rom error: BADKEY")]
159    BadKey,
160    #[error("Boot rom error: BADSTART")]
161    BadStart,
162    #[error("Boot rom error: NOWIPE")]
163    NoWipe,
164    #[error("Boot rom error: NOWIPE0")]
165    NoWipe0,
166    #[error("Boot rom error: NOWIPE1")]
167    NoWipe1,
168    #[error("Boot rom error: NOTEMPTY")]
169    NotEmpty,
170    #[error("Boot rom error: NOWRITE")]
171    NoWrite,
172    #[error("Boot rom error: BADADR")]
173    BadAdr,
174    #[error("Boot rom error: OVERFLOW")]
175    Overflow,
176    #[error("Repeated errors communicating with boot rom")]
177    RepeatedErrors,
178}
179impl_serializable_error!(LegacyBootstrapError);
180
181impl From<u8> for LegacyBootstrapError {
182    fn from(value: u8) -> LegacyBootstrapError {
183        match value {
184            // All zeroes or all ones means that the bootloader is not yet ready to respond.
185            0 | 255 => LegacyBootstrapError::NotReady,
186            // Other values represent particular errors.
187            1 => LegacyBootstrapError::NoRequest,
188            2 => LegacyBootstrapError::NoMagic,
189            3 => LegacyBootstrapError::TooBig,
190            4 => LegacyBootstrapError::TooHigh,
191            5 => LegacyBootstrapError::NoAlign,
192            6 => LegacyBootstrapError::NoRound,
193            7 => LegacyBootstrapError::BadKey,
194            8 => LegacyBootstrapError::BadStart,
195            10 => LegacyBootstrapError::NoWipe,
196            11 => LegacyBootstrapError::NoWipe0,
197            12 => LegacyBootstrapError::NoWipe1,
198            13 => LegacyBootstrapError::NotEmpty,
199            14 => LegacyBootstrapError::NoWrite,
200            15 => LegacyBootstrapError::BadAdr,
201            16 => LegacyBootstrapError::Overflow,
202            n => LegacyBootstrapError::Unknown(n),
203        }
204    }
205}
206
207impl UpdateProtocol for Legacy {
208    fn verify_capabilities(
209        &self,
210        _container: &Bootstrap,
211        transport: &TransportWrapper,
212    ) -> Result<()> {
213        transport
214            .capabilities()?
215            .request(Capability::GPIO | Capability::SPI)
216            .ok()?;
217        Ok(())
218    }
219
220    fn uses_common_bootstrap_reset(&self) -> bool {
221        true
222    }
223
224    /// Performs the update protocol using the `transport` with the firmware `payload`.
225    fn update(
226        &self,
227        container: &Bootstrap,
228        transport: &TransportWrapper,
229        payload: &[u8],
230        progress: &dyn ProgressIndicator,
231    ) -> Result<()> {
232        let spi = container.spi_params.create(transport, "BOOTSTRAP")?;
233
234        let frames = Frame::from_payload(payload);
235
236        // All frames up to but not including this index have been ack'ed by the bootloader.
237        // Once this reaches frames.len(), the operation was successful.
238        let mut first_unacked_index = 0;
239
240        // Counts the number of repeated failures in getting a frame ack.
241        let mut consecutive_errors = 0;
242
243        // Set if the past transaction ack'ed the block sent before that one, while transmitting
244        // the next.  If so, we heuristically assume that the block just sent will be ack'ed in
245        // the upcoming bidirectional transaction.
246        let mut optimistic = false;
247
248        // Set if the past transaction ack'ed the block sent before that one, while
249        // re-transmitting the same block.  If so, we do not care about the ack in the upcoming
250        // bidirectional transaction, as it turns out it was not necessary to perform the most
251        // recent re-transmission.
252        let mut becoming_optimistic = false;
253
254        progress.new_stage("", payload.len());
255        loop {
256            if consecutive_errors > Self::MAX_CONSECUTIVE_ERRORS {
257                return Err(LegacyBootstrapError::RepeatedErrors.into());
258            }
259
260            let second_unacked_index = (first_unacked_index + 1).min(frames.len() - 1);
261            let transmit_index = if optimistic {
262                second_unacked_index
263            } else {
264                first_unacked_index
265            };
266            let frame = &frames[transmit_index];
267            eprint!("{}.", transmit_index);
268            std::thread::sleep(self.inter_frame_delay);
269
270            // Write the frame and read back the ack of a previously transmitted frame.
271            progress.progress(frame.header.flash_offset as usize - Frame::FLASH_BASE_ADDRESS);
272            let mut response = [0u8; std::mem::size_of::<Frame>()];
273            spi.run_transaction(&mut [Transfer::Both(frame.as_bytes(), &mut response)])?;
274
275            if becoming_optimistic {
276                optimistic = true;
277                becoming_optimistic = false;
278                continue;
279            }
280
281            if response[..Frame::HASH_LEN]
282                .iter()
283                .all(|&x| x == response[0])
284            {
285                // A response consisteing of all identical bytes is a status code.
286                match LegacyBootstrapError::from(response[0]) {
287                    LegacyBootstrapError::NotReady => {
288                        consecutive_errors += 1;
289                        continue; // Retry sending same frame.
290                    }
291                    error => return Err(error.into()),
292                }
293            }
294
295            if response[..Frame::HASH_LEN] == frames[first_unacked_index].frame_hash() {
296                first_unacked_index += 1;
297            } else if response[..Frame::HASH_LEN] == frames[second_unacked_index].frame_hash() {
298                first_unacked_index = second_unacked_index + 1;
299            } else {
300                consecutive_errors += 1;
301                optimistic = false;
302                continue;
303            }
304
305            consecutive_errors = 0;
306            if first_unacked_index == frames.len() {
307                // All frames acked, we are done.
308                break;
309            }
310            if !optimistic {
311                // Heuristic, become optimistic after second or later frame is ack'ed.
312                becoming_optimistic = first_unacked_index > 1;
313            }
314        }
315        progress.progress(payload.len());
316        eprintln!("success");
317        Ok(())
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    const SIMPLE_BIN: &[u8; 2048] = include_bytes!("simple.bin");
326
327    #[test]
328    fn test_small_binary() -> Result<()> {
329        let frames = Frame::from_payload(SIMPLE_BIN);
330
331        assert_eq!(frames[0].header.frame_num, 0);
332        assert_eq!(frames[0].header.flash_offset, 0x80000);
333        assert_eq!(
334            hex::encode(frames[0].header.hash),
335            "4e31bfd8b3be32358f2235c0f241f3970de575fc6aca0564aa6bf30adaf33910"
336        );
337
338        assert_eq!(frames[1].header.frame_num, 0x8000_0001);
339        assert_eq!(frames[1].header.flash_offset, 0x807d8);
340        assert_eq!(
341            hex::encode(frames[1].header.hash),
342            "ff584c07bbb0a039934a660bd49b7812af8ee847d1e675d9aba71c11fab3cfcb"
343        );
344        Ok(())
345    }
346}