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