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