opentitanlib/test_utils/
load_sram_program.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 std::fs;
6use std::path::PathBuf;
7use std::time::Duration;
8
9use anyhow::{Context, Result, ensure};
10use bindgen::sram_program::{
11    SRAM_MAGIC_SP_CRC_ERROR, SRAM_MAGIC_SP_CRC_SKIPPED, SRAM_MAGIC_SP_EXECUTION_DONE,
12};
13use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
14use clap::Args;
15use crc::Crc;
16use object::{Object, ObjectSection, ObjectSegment, SectionKind};
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20use ot_hal::top::earlgrey as top_earlgrey;
21use ot_hal::util::multibits::MultiBitBool4;
22
23use crate::impl_serializable_error;
24use crate::io::jtag::{Jtag, RiscvCsr, RiscvGpr, RiscvReg};
25use crate::util::parse_int::ParseInt;
26use crate::util::vmem::Vmem;
27
28/// Command-line parameters.
29#[derive(Debug, Args, Clone, Default)]
30pub struct SramProgramParams {
31    /// Path to the ELF file to load.
32    #[arg(long, default_value = None)]
33    pub elf: Option<PathBuf>,
34
35    /// Path to the VMEM file to load.
36    #[arg(long, conflicts_with = "elf", default_value = None)]
37    pub vmem: Option<PathBuf>,
38
39    /// Address where to load the VMEM file.
40    #[arg(long, value_parser = <u32 as ParseInt>::from_str, conflicts_with="elf", default_value = None)]
41    pub load_addr: Option<u32>,
42
43    /// load the VMEM file.
44    #[arg(long)]
45    pub skip_crc: bool,
46}
47
48/// Describe a file to load to SRAM.
49#[derive(Debug, Clone)]
50pub enum SramProgramFile {
51    Vmem { path: PathBuf, load_addr: u32 },
52    Elf(PathBuf),
53}
54
55impl SramProgramParams {
56    // Convert the command line parameters into a nicer structure.
57    pub fn get_file(&self) -> SramProgramFile {
58        if let Some(path) = &self.vmem {
59            SramProgramFile::Vmem {
60                path: path.clone(),
61                load_addr: self
62                    .load_addr
63                    .expect("you must provide a load address for a VMEM file"),
64            }
65        } else {
66            SramProgramFile::Elf(
67                self.elf
68                    .as_ref()
69                    .expect("you must provide either an ELF file or a VMEM file")
70                    .clone(),
71            )
72        }
73    }
74
75    pub fn load(&self, jtag: &mut dyn Jtag) -> Result<SramProgramInfo> {
76        load_sram_program(jtag, &self.get_file())
77    }
78
79    pub fn load_and_execute(
80        &self,
81        jtag: &mut dyn Jtag,
82        exec_mode: ExecutionMode,
83    ) -> Result<ExecutionResult> {
84        load_and_execute_sram_program(jtag, &self.get_file(), exec_mode, self.skip_crc)
85    }
86}
87
88/// Execution mode for a SRAM program.
89pub enum ExecutionMode {
90    /// Jump to the loading address and let the program run forever.
91    Jump,
92    /// Jump at the loading address and immediately halt execution.
93    JumpAndHalt,
94    /// Jump at the loading address and wait for the core to halt or timeout.
95    JumpAndWait(Duration),
96}
97
98/// Detail of execution error of a SRAM program.
99#[derive(Debug, Deserialize, Serialize)]
100pub enum ExecutionError {
101    /// Unknown error.
102    Unknown,
103    /// The SRAM program loader reported a CRC self-check error.
104    CrcMismatch,
105}
106
107/// Result of execution of a SRAM program.
108#[derive(Debug, Deserialize, Serialize)]
109pub enum ExecutionResult {
110    /// (JumpAndHalt only) Execution is halted at the beginning.
111    HaltedAtStart,
112    /// (Jump only) Execution is ongoing.
113    Executing,
114    /// (JumpAndWait only) Execution successfully stopped.
115    ///
116    /// The content of register `a0` is returned.
117    ExecutionDone(u32),
118    /// (JumpAndWait only) Execution did not finish it time or an error occurred.
119    ExecutionError(ExecutionError),
120}
121
122/// Errors related to loading an SRAM program.
123#[derive(Error, Debug, Deserialize, Serialize)]
124pub enum LoadSramProgramError {
125    #[error("SRAM ELF programs must be 32-bit binaries")]
126    Not32Bit,
127    #[error(
128        "SRAM program contains segments whose address or size is not a multiple of the word size"
129    )]
130    SegmentNotWordAligned,
131    #[error("SRAM program must be compiled with the `-nmagic` flag")]
132    NotCompiledWithNmagic,
133    #[error("SRAM program's segments must be consecutive")]
134    GapBetweenSegments,
135    #[error("Data readback from the SRAM mismatches from the data loaded")]
136    ReadbackMismatch,
137    #[error("SRAM program entry point is not contained in any text section")]
138    EntryPointNotFound,
139    #[error("Generic error {0}")]
140    Generic(String),
141}
142impl_serializable_error!(LoadSramProgramError);
143
144/// Information about the loaded SRAM program
145pub struct SramProgramInfo {
146    /// Address of the entry point.
147    pub entry_point: u32,
148    /// CRC32 of the entire data.
149    pub crc32: u32,
150}
151
152const WORD_SIZE_BYTES: usize = std::mem::size_of::<u32>();
153
154/// Load a program into SRAM using JTAG (VMEM files).
155pub fn load_vmem_sram_program(
156    jtag: &mut dyn Jtag,
157    vmem_filename: &PathBuf,
158    load_addr: u32,
159) -> Result<SramProgramInfo> {
160    log::info!("Loading VMEM file {}", vmem_filename.display());
161    let vmem_content = fs::read_to_string(vmem_filename)?;
162    let mut vmem = Vmem::from_str(&vmem_content, Some(WORD_SIZE_BYTES))?;
163    vmem.merge_sections(Some(WORD_SIZE_BYTES));
164
165    log::info!("Uploading program to SRAM at {:x}", load_addr);
166    let crc = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
167    let mut digest = crc.digest();
168    for section in vmem.sections() {
169        log::info!(
170            "Load {} words at address {:x}",
171            section.data.len(),
172            load_addr + section.addr
173        );
174        let words: Vec<u32> = section.clone().try_into()?;
175        jtag.write_memory32(load_addr + section.addr, &words)?;
176        // Update CRC
177        let mut data8: Vec<u8> = vec![];
178        for elem in &words {
179            data8.write_u32::<LittleEndian>(*elem).unwrap();
180        }
181        digest.update(&data8);
182    }
183    Ok(SramProgramInfo {
184        entry_point: load_addr,
185        crc32: digest.finalize(),
186    })
187}
188
189/// Load a program into SRAM using JTAG (ELF files).
190pub fn load_elf_sram_program(
191    jtag: &mut dyn Jtag,
192    elf_filename: &PathBuf,
193) -> Result<SramProgramInfo> {
194    log::info!("Loading ELF file {}", elf_filename.display());
195    let file_data = std::fs::read(elf_filename)
196        .with_context(|| format!("Could not read ELF file {}.", elf_filename.display()))?;
197    let file = object::File::parse(&*file_data)
198        .with_context(|| format!("Could not parse ELF file {}", elf_filename.display()))?;
199    ensure!(!file.is_64(), LoadSramProgramError::Not32Bit);
200    log::info!("Uploading program to SRAM");
201
202    // By default, linkers produces ELF files where all segments are aligned to the page size,
203    // so the operating system can use mmap to load the program into memory (known as demand
204    // paging).
205    //
206    // Here is an example:
207    //
208    // Section Headers:
209    //   [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
210    //   [ 0]                   NULL            00000000 000000 000000 00      0   0  0
211    //   [ 1] .text             PROGBITS        10001fc8 000fc8 0064ea 00  AX  0   0  4
212    //   [ 2] .rodata           PROGBITS        100084b8 0074b8 0016de 00   A  0   0  8
213    //   [ 3] .data             PROGBITS        10009b98 008b98 000084 00  WA  0   0  4
214    //   [ 4] .sdata            PROGBITS        10009c1c 008c1c 000000 00   W  0   0  4
215    //   [ 5] .bss              NOBITS          10009c1c 008c1c 001f6c 00  WA  0   0  4
216    //
217    // Program Headers:
218    //   Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
219    //   LOAD           0x000000 0x10001000 0x10001000 0x08c1c 0x0ab88 RWE 0x1000
220    //   GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
221    //
222    // Note that the segment starts at 0x10001000 but .text starts at 0x10001fc8, so loading
223    // the segment would actually overwrite the beginning of the SRAM (static critical data).
224    // Also note that there is a 6-byte gap between the end of .text and the beginning of .rodata
225    // because .rodata needs a bigger alignment.
226    //
227    // Demand paging has no use in embedded environment, and as shown above, if we load the
228    // program using the segments we could overwrite data unintentionally. Furthermore there will
229    // be an inconsistency between data loaded this way and data loaded via VMEM because the gap
230    // at the beginning is ignored by objcopy and is not covered by the CRC.
231    //
232    // Fortunately there is a flag, confusingly named as `nmagic`, that changes the behaviour and
233    // disables this excessive alignment. The code below has a sanity check to ensure that the
234    // program is indeed compiled with `nmagic` enabeld by making sure tha the alignment does not
235    // exceed 8.
236    let crc = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
237    let mut digest = crc.digest();
238    let mut last_address: Option<u32> = None;
239    for segment in file.segments() {
240        let address = segment.address();
241        let data = segment.data()?;
242
243        if data.is_empty() {
244            continue;
245        }
246
247        // It is much faster to load data word by word instead of bytes by bytes.
248        // The linker script always ensures that we the address and size are multiple of 4.
249        ensure!(
250            address % WORD_SIZE_BYTES as u64 == 0 && data.len() % WORD_SIZE_BYTES == 0,
251            LoadSramProgramError::SegmentNotWordAligned
252        );
253        ensure!(
254            segment.align() <= 256,
255            LoadSramProgramError::NotCompiledWithNmagic
256        );
257        // A sanity check to ensure that there are no gaps between segments.
258        if let Some(last_addr) = last_address {
259            let gap_size = address as i32 - last_addr as i32;
260            ensure!(gap_size == 0, LoadSramProgramError::GapBetweenSegments);
261        }
262        // Write segment's data.
263        log::info!(
264            "Load segment: {} bytes at address {:x}",
265            data.len(),
266            address
267        );
268        let data32: Vec<u32> = data.chunks(4).map(LittleEndian::read_u32).collect();
269        jtag.write_memory32(address as u32, &data32)?;
270        digest.update(data);
271
272        last_address = Some((address + data.len() as u64) as u32);
273    }
274
275    // We verify (read back and compare) the data from the section that contains the entry point.
276    // The rationale is that if the CRC code is corrupted, it could execute the SRAM program even though
277    // it should not. By verifying just the tiny bit of code that checks the CRC, we can ensure that the
278    // entire program is validated.
279    let mut entry_found = false;
280    for section in file.sections() {
281        if section.kind() != SectionKind::Text {
282            continue;
283        }
284
285        // If this section contains the entry point, read back the data and compare.
286        if (section.address()..(section.address() + section.size())).contains(&file.entry()) {
287            entry_found = true;
288
289            let data32: Vec<u32> = section
290                .data()?
291                .chunks(4)
292                .map(LittleEndian::read_u32)
293                .collect();
294            println!("{:?}", data32);
295            let mut read_data32 = vec![0u32; data32.len()];
296            log::info!("Read back data to verify");
297            jtag.read_memory32(section.address() as u32, &mut read_data32)?;
298            ensure!(
299                data32 == read_data32,
300                LoadSramProgramError::ReadbackMismatch
301            );
302        }
303    }
304    ensure!(entry_found, LoadSramProgramError::EntryPointNotFound);
305
306    Ok(SramProgramInfo {
307        entry_point: file.entry() as u32,
308        crc32: digest.finalize(),
309    })
310}
311
312/// Load a program into SRAM using JTAG. Returns the address of the entry point.
313pub fn load_sram_program(jtag: &mut dyn Jtag, file: &SramProgramFile) -> Result<SramProgramInfo> {
314    match file {
315        SramProgramFile::Vmem { path, load_addr } => load_vmem_sram_program(jtag, path, *load_addr),
316        SramProgramFile::Elf(path) => load_elf_sram_program(jtag, path),
317    }
318}
319
320/// Set up the ePMP to enable read/write/execute from SRAM and read/write access
321/// to the full MMIO region. Specifically, this function will:
322/// 1. set the PMP entry 15 to NAPOT to cover the SRAM as RWX
323/// 2. set the PMP entry 11 to TOR to cover the MMIO region as RW.
324///
325/// This follows the memory layout used by the ROM [0].
326///
327/// The Ibex core is initialized with a default ePMP configuration [3]
328/// when it starts. This configuration has no PMP entry for the RAM, only
329/// partial access to the MMIO region (e.g., RV_PLIC access is denied), and
330/// mseccfg.mmwp is set to 1 so accesses that don't match a PMP entry will
331/// be denied.
332///
333/// Before transferring the SRAM program to the device, we must configure the
334/// PMP unit to enable reading, writing, and executing from SRAM, and reading
335/// and writing to the entire MMIO region. Due to implementation details of
336/// OpenTitan's hardware debug module, it is important that the RV_ROM remains
337/// accessible at all times [1]. It uses entry 13 of the PMP on boot so we want
338/// to preserve that. However, we can safely modify the other PMP configuration
339/// registers.
340///
341/// In more detail, the problem is that our debug module implements the
342/// "Access Register" abstract command by assembling instructions in the
343/// program buffer and then executing the buffer. If one of those
344/// instructions clobbers the PMP configuration register that allows
345/// execution from the program buffer (PMP entry 13),
346/// subsequent instruction fetches will generate exceptions.
347///
348/// Debug module concepts like abstract commands and the program buffer are
349/// defined in "RISC-V External Debug Support Version 0.13.2" [2]. OpenTitan's
350/// (vendored-in) implementation lives in hw/vendor/pulp_riscv_dbg.
351///
352/// [0]: https://opentitan.org/book/sw/device/silicon_creator/rom/doc/memory_protection.html
353/// [1]: https://github.com/lowRISC/opentitan/issues/14978
354/// [2]: https://riscv.org/wp-content/uploads/2019/03/riscv-debug-release.pdf
355/// [3]: https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/rtl/ibex_pmp_reset_pkg.sv
356pub fn prepare_epmp(jtag: &mut dyn Jtag) -> Result<()> {
357    // Setup ePMP for SRAM execution.
358    log::info!("Configure ePMP for SRAM execution.");
359    let pmpcfg3 = jtag.read_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPCFG3))?;
360    log::info!("Old value of pmpcfg3: {:x}", pmpcfg3);
361    // Write "L NAPOT X W R" to pmpcfg3 in region 15.
362    let pmpcfg3 = (pmpcfg3 & 0x00ffffffu32) | 0x9f000000;
363    log::info!("New value of pmpcfg3: {:x}", pmpcfg3);
364    jtag.write_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPCFG3), pmpcfg3)?;
365    // Write pmpaddr15 to map the SRAM range.
366    // hex((0x10000000 >> 2) | ((0x20000 - 1) >> 3)) = 0x4003fff
367    let base = top_earlgrey::SRAM_CTRL_MAIN_RAM_BASE_ADDR as u32;
368    let size = top_earlgrey::SRAM_CTRL_MAIN_RAM_SIZE_BYTES as u32;
369    // Make sure that this is a power of two.
370    assert!(size & (size - 1) == 0);
371    let pmpaddr15 = (base >> 2) | ((size - 1) >> 3);
372    log::info!("New value of pmpaddr15: {:x}", pmpaddr15);
373    jtag.write_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPADDR15), pmpaddr15)?;
374
375    // Setup ePMP for R/W access to MMIO region.
376    log::info!("Configure ePMP for MMIO access.");
377    let pmpcfg2 = jtag.read_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPCFG2))?;
378    log::info!("Old value of pmpcfg2: {:x}", pmpcfg2);
379    // Write "L TOR X W R" to pmpcfg2 in region 11.
380    let pmpcfg2 = (pmpcfg2 & 0x00ffffffu32) | 0x8f000000;
381    log::info!("New value of pmpcfg2: {:x}", pmpcfg2);
382    jtag.write_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPCFG2), pmpcfg2)?;
383    // Write pmpaddr10 and pmpaddr11 to map the MMIO range.
384    let base = top_earlgrey::TOP_EARLGREY_MMIO_BASE_ADDR as u32;
385    let size = top_earlgrey::TOP_EARLGREY_MMIO_SIZE_BYTES as u32;
386    // make sure that this is a power of two
387    assert!(size & (size - 1) == 0);
388    let pmpaddr10 = base >> 2;
389    let pmpaddr11 = (base + size) >> 2;
390    log::info!("New value of pmpaddr10: {:x}", pmpaddr10);
391    log::info!("New value of pmpaddr11: {:x}", pmpaddr11);
392    jtag.write_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPADDR10), pmpaddr10)?;
393    jtag.write_riscv_reg(&RiscvReg::Csr(RiscvCsr::PMPADDR11), pmpaddr11)?;
394
395    Ok(())
396}
397
398/// Set up the sram_ctrl to execute code.
399pub fn prepare_sram_ctrl(jtag: &mut dyn Jtag) -> Result<()> {
400    const SRAM_CTRL_EXEC_REG_OFFSET: u32 = (top_earlgrey::SRAM_CTRL_MAIN_REGS_BASE_ADDR as u32)
401        + ot_bindgen_dif::SRAM_CTRL_EXEC_REG_OFFSET;
402    log::info!("Enabling execution from SRAM.");
403    let mut sram_ctrl_exec = [0];
404    jtag.read_memory32(SRAM_CTRL_EXEC_REG_OFFSET, &mut sram_ctrl_exec)?;
405    log::info!("Old value of sram_exec_en: {:x}", sram_ctrl_exec[0]);
406    sram_ctrl_exec[0] = u8::from(MultiBitBool4::True) as u32;
407    jtag.write_memory32(SRAM_CTRL_EXEC_REG_OFFSET, &sram_ctrl_exec)?;
408    log::info!("New value of sram_exec_en: {:x}", sram_ctrl_exec[0]);
409    Ok(())
410}
411
412/// Execute an already loaded SRAM program. It takes care of setting up the ePMP.
413pub fn execute_sram_program(
414    jtag: &mut dyn Jtag,
415    prog_info: &SramProgramInfo,
416    exec_mode: ExecutionMode,
417    skip_crc: bool,
418) -> Result<ExecutionResult> {
419    prepare_epmp(jtag)?;
420    prepare_sram_ctrl(jtag)?;
421
422    // To avoid unexpected behaviors, we always make sure that the return address
423    // points to an invalid address.
424    let ret_addr = 0xdeadbeefu32;
425    log::info!("set RA to {:x}", ret_addr);
426    jtag.write_riscv_reg(&RiscvReg::Gpr(RiscvGpr::RA), ret_addr)?;
427
428    // Potentially skip CRC check.
429    if skip_crc {
430        // The SRAM program loader will skip the CRC32 check if a0 is a magic value.
431        log::info!(
432            "skip CRC by setting A0 to {:x} (crc32)",
433            SRAM_MAGIC_SP_CRC_SKIPPED
434        );
435        jtag.write_riscv_reg(&RiscvReg::Gpr(RiscvGpr::A0), SRAM_MAGIC_SP_CRC_SKIPPED)?;
436    } else {
437        // The SRAM program loader expects the CRC32 value in a0
438        log::info!("set A0 to {:x} (crc32)", prog_info.crc32);
439        jtag.write_riscv_reg(&RiscvReg::Gpr(RiscvGpr::A0), prog_info.crc32)?;
440    }
441
442    // OpenOCD takes care of invalidating the cache when resuming execution
443    match exec_mode {
444        ExecutionMode::Jump => {
445            log::info!("resume execution at {:x}", prog_info.entry_point);
446            jtag.resume_at(prog_info.entry_point)?;
447            Ok(ExecutionResult::Executing)
448        }
449        ExecutionMode::JumpAndHalt => {
450            log::info!("set DPC to {:x}", prog_info.entry_point);
451            jtag.write_riscv_reg(&RiscvReg::Csr(RiscvCsr::DPC), prog_info.entry_point)?;
452            Ok(ExecutionResult::HaltedAtStart)
453        }
454        ExecutionMode::JumpAndWait(tmo) => {
455            log::info!("resume execution at {:x}", prog_info.entry_point);
456            jtag.resume_at(prog_info.entry_point)?;
457            log::info!("wait for execution to stop");
458            jtag.wait_halt(tmo)?;
459            jtag.halt()?;
460            // The SRAM's crt has a protocol to notify us that execution returned: it sets
461            // the stack pointer to a certain value.
462            let sp = jtag.read_riscv_reg(&RiscvReg::Gpr(RiscvGpr::SP))?;
463            log::info!("after timeout, sp = {:x}", sp);
464            match sp {
465                SRAM_MAGIC_SP_EXECUTION_DONE => Ok(ExecutionResult::ExecutionDone(sp)),
466                SRAM_MAGIC_SP_CRC_SKIPPED => Ok(ExecutionResult::ExecutionDone(sp)),
467                SRAM_MAGIC_SP_CRC_ERROR => {
468                    Ok(ExecutionResult::ExecutionError(ExecutionError::CrcMismatch))
469                }
470                _ => Ok(ExecutionResult::ExecutionError(ExecutionError::Unknown)),
471            }
472        }
473    }
474}
475
476/// Loads and execute a SRAM program. It takes care of setting up the ePMP.
477pub fn load_and_execute_sram_program(
478    jtag: &mut dyn Jtag,
479    file: &SramProgramFile,
480    exec_mode: ExecutionMode,
481    skip_crc: bool,
482) -> Result<ExecutionResult> {
483    let prog_info = load_sram_program(jtag, file)?;
484    // Never skip CRC check outside of a test.
485    execute_sram_program(jtag, &prog_info, exec_mode, skip_crc)
486}