1use 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#[derive(Debug, Args, Clone, Default)]
30pub struct SramProgramParams {
31 #[arg(long, default_value = None)]
33 pub elf: Option<PathBuf>,
34
35 #[arg(long, conflicts_with = "elf", default_value = None)]
37 pub vmem: Option<PathBuf>,
38
39 #[arg(long, value_parser = <u32 as ParseInt>::from_str, conflicts_with="elf", default_value = None)]
41 pub load_addr: Option<u32>,
42
43 #[arg(long)]
45 pub skip_crc: bool,
46}
47
48#[derive(Debug, Clone)]
50pub enum SramProgramFile {
51 Vmem { path: PathBuf, load_addr: u32 },
52 Elf(PathBuf),
53}
54
55impl SramProgramParams {
56 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
88pub enum ExecutionMode {
90 Jump,
92 JumpAndHalt,
94 JumpAndWait(Duration),
96}
97
98#[derive(Debug, Deserialize, Serialize)]
100pub enum ExecutionError {
101 Unknown,
103 CrcMismatch,
105}
106
107#[derive(Debug, Deserialize, Serialize)]
109pub enum ExecutionResult {
110 HaltedAtStart,
112 Executing,
114 ExecutionDone(u32),
118 ExecutionError(ExecutionError),
120}
121
122#[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
144pub struct SramProgramInfo {
146 pub entry_point: u32,
148 pub crc32: u32,
150}
151
152const WORD_SIZE_BYTES: usize = std::mem::size_of::<u32>();
153
154pub 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 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
189pub 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 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 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 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 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 let mut entry_found = false;
280 for section in file.sections() {
281 if section.kind() != SectionKind::Text {
282 continue;
283 }
284
285 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
312pub 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
320pub fn prepare_epmp(jtag: &mut dyn Jtag) -> Result<()> {
357 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 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 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 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 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 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 let base = top_earlgrey::TOP_EARLGREY_MMIO_BASE_ADDR as u32;
385 let size = top_earlgrey::TOP_EARLGREY_MMIO_SIZE_BYTES as u32;
386 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
398pub 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
412pub 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 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 if skip_crc {
430 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 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 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 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
476pub 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 execute_sram_program(jtag, &prog_info, exec_mode, skip_crc)
486}