Skip to main content

logicaffeine_compile/codegen_sva/hart/
image.rs

1//! The boot image the board's flash holds after its bitstream: a header the boot ROM checks, then a DEFLATE-compressed
2//! body of sections — the shim, the compiler (a static riscv64 Linux ELF), the bundle of LOGOS sources it may open,
3//! and, when a run is given them, the compiler's arguments. The boot ROM refuses an image whose RTL fingerprint is not
4//! the bitstream's, digests the compressed stream as it inflates it, and refuses one whose digest does not match.
5
6use super::soc::{IMAGE_HEADER_SIZE, IMAGE_MAGIC, IMAGE_VERSION, SECTION_ARGS, SECTION_BUNDLE, SECTION_FIRMWARE, SECTION_SHIM};
7use crate::crypto_accel::sha256;
8
9/// What a boot image carries.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct BootImage {
12    /// The fingerprint of the RTL the image was built for (the bitstream's `SYS_RTL_MD5`).
13    pub rtl_md5: [u8; 16],
14    /// The shim, as a raw binary for `SHIM_BASE`.
15    pub shim: Vec<u8>,
16    /// The compiler: a static riscv64 Linux ELF linked at `FIRMWARE_BASE`.
17    pub firmware: Vec<u8>,
18    /// The files the compiler may open, by path.
19    pub bundle: Vec<(String, Vec<u8>)>,
20    /// The compiler's arguments after its name, if the image runs it with any.
21    pub args: Vec<String>,
22}
23
24/// The bundle's bytes: a u32 count, then per file its u32 name length, the name, its u64 length and its bytes.
25pub fn bundle_bytes(files: &[(String, Vec<u8>)]) -> Vec<u8> {
26    let mut out = (files.len() as u32).to_le_bytes().to_vec();
27    for (name, data) in files {
28        out.extend_from_slice(&(name.len() as u32).to_le_bytes());
29        out.extend_from_slice(name.as_bytes());
30        out.extend_from_slice(&(data.len() as u64).to_le_bytes());
31        out.extend_from_slice(data);
32    }
33    out
34}
35
36/// The arguments' bytes: each NUL-terminated.
37fn args_bytes(args: &[String]) -> Vec<u8> {
38    let mut out = Vec::new();
39    for a in args {
40        out.extend_from_slice(a.as_bytes());
41        out.push(0);
42    }
43    out
44}
45
46/// The inflated body: a u32 section count and a reserved u32, the table of (u32 kind, u32 flags, u64 offset,
47/// u64 length), then each section at an 8-byte-aligned offset from the body's start.
48pub fn body(image: &BootImage) -> Vec<u8> {
49    let mut sections: Vec<(u32, Vec<u8>)> = vec![
50        (SECTION_SHIM, image.shim.clone()),
51        (SECTION_FIRMWARE, image.firmware.clone()),
52        (SECTION_BUNDLE, bundle_bytes(&image.bundle)),
53    ];
54    if !image.args.is_empty() {
55        sections.push((SECTION_ARGS, args_bytes(&image.args)));
56    }
57    let table_end = 8 + 24 * sections.len();
58    let mut offset = (table_end + 7) & !7;
59    let mut table = Vec::new();
60    let mut placed = Vec::new();
61    for (kind, data) in &sections {
62        table.extend_from_slice(&kind.to_le_bytes());
63        table.extend_from_slice(&0u32.to_le_bytes());
64        table.extend_from_slice(&(offset as u64).to_le_bytes());
65        table.extend_from_slice(&(data.len() as u64).to_le_bytes());
66        placed.push(offset);
67        offset = (offset + data.len() + 7) & !7;
68    }
69    let mut out = vec![0u8; offset];
70    out[0..4].copy_from_slice(&(sections.len() as u32).to_le_bytes());
71    out[8..table_end].copy_from_slice(&table);
72    for ((_, data), at) in sections.iter().zip(placed) {
73        out[at..at + data.len()].copy_from_slice(data);
74    }
75    out
76}
77
78/// The body's compression: raw DEFLATE at the strongest level, the stream the boot ROM inflates.
79pub fn pack(bytes: &[u8]) -> Vec<u8> {
80    miniz_oxide::deflate::compress_to_vec(bytes, 10)
81}
82
83/// The image as the flash holds it at `IMAGE_OFFSET`: the 128-byte header, then the compressed body.
84pub fn build(image: &BootImage) -> Vec<u8> {
85    let body = body(image);
86    let packed = pack(&body);
87    let mut header = vec![0u8; IMAGE_HEADER_SIZE as usize];
88    header[0..8].copy_from_slice(&IMAGE_MAGIC.to_le_bytes());
89    header[8..12].copy_from_slice(&IMAGE_VERSION.to_le_bytes());
90    header[12..16].copy_from_slice(&(IMAGE_HEADER_SIZE as u32).to_le_bytes());
91    header[0x10..0x20].copy_from_slice(&image.rtl_md5);
92    header[0x20..0x28].copy_from_slice(&(body.len() as u64).to_le_bytes());
93    header[0x28..0x30].copy_from_slice(&(packed.len() as u64).to_le_bytes());
94    header[0x30..0x50].copy_from_slice(&sha256(&packed));
95    header.extend_from_slice(&packed);
96    header
97}
98
99/// An image read back: its fingerprint and its inflated body, every check the boot ROM makes applied.
100pub fn open(flash_image: &[u8]) -> Result<([u8; 16], Vec<u8>), String> {
101    let h = flash_image.get(..IMAGE_HEADER_SIZE as usize).ok_or("the image is shorter than its header")?;
102    let word = |o: usize| u64::from_le_bytes(h[o..o + 8].try_into().unwrap());
103    if word(0) != IMAGE_MAGIC {
104        return Err("no boot image here".into());
105    }
106    if u32::from_le_bytes(h[8..12].try_into().unwrap()) != IMAGE_VERSION {
107        return Err("the boot image's version is not this one".into());
108    }
109    let (body_len, packed_len) = (word(0x20) as usize, word(0x28) as usize);
110    let packed = flash_image
111        .get(IMAGE_HEADER_SIZE as usize..IMAGE_HEADER_SIZE as usize + packed_len)
112        .ok_or("the image is shorter than its body")?;
113    if sha256(packed)[..] != h[0x30..0x50] {
114        return Err("the boot image's digest does not match".into());
115    }
116    let body = miniz_oxide::inflate::decompress_to_vec(packed).map_err(|e| format!("the body does not inflate: {e:?}"))?;
117    if body.len() != body_len {
118        return Err(format!("the body inflates to {} bytes, not {body_len}", body.len()));
119    }
120    Ok((h[0x10..0x20].try_into().unwrap(), body))
121}