1use 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#[derive(Debug, Args, Clone, Default)]
31pub struct SramProgramParams {
32 #[arg(long, default_value = None)]
34 pub elf: Option<PathBuf>,
35
36 #[arg(long, conflicts_with = "elf", default_value = None)]
38 pub vmem: Option<PathBuf>,
39
40 #[arg(long, value_parser = <u32 as ParseInt>::from_str, conflicts_with="elf", default_value = None)]
42 pub load_addr: Option<u32>,
43
44 #[arg(long)]
46 pub skip_crc: bool,
47}
48
49#[derive(Debug, Clone)]
51pub enum SramProgramFile {
52 Vmem { path: PathBuf, load_addr: u32 },
53 Elf(PathBuf),
54}
55
56impl SramProgramParams {
57 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
89pub enum ExecutionMode {
91 Jump,
93 JumpAndHalt,
95 JumpAndWait(Duration),
97}
98
99#[derive(Debug, Deserialize, Serialize)]
101pub enum ExecutionError {
102 Unknown,
104 CrcMismatch,
106}
107
108#[derive(Debug, Deserialize, Serialize)]
110pub enum ExecutionResult {
111 HaltedAtStart,
113 Executing,
115 ExecutionDone(u32),
119 ExecutionError(ExecutionError),
121}
122
123#[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
145pub struct SramProgramInfo {
147 pub entry_point: u32,
149 pub crc32: u32,
151}
152
153pub 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, §ion.data)?;
173 let mut data8: Vec<u8> = vec![];
175 for elem in §ion.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
186pub 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 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 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 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 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 let mut entry_found = false;
278 for section in file.sections() {
279 if section.kind() != SectionKind::Text {
280 continue;
281 }
282
283 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
310pub 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
318pub fn prepare_epmp(jtag: &mut dyn Jtag) -> Result<()> {
355 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 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 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 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 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 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 let base = top_earlgrey::TOP_EARLGREY_MMIO_BASE_ADDR as u32;
383 let size = top_earlgrey::TOP_EARLGREY_MMIO_SIZE_BYTES as u32;
384 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
396pub 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
410pub 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 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 if skip_crc {
428 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 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 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 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
474pub 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 execute_sram_program(jtag, &prog_info, exec_mode, skip_crc)
484}