1use anyhow::{Context, Result, bail};
6use clap::Args;
7use crc::{CRC_32_ISO_HDLC, Crc};
8use std::convert::From;
9use std::fs;
10use std::path::PathBuf;
11use std::str::FromStr;
12use thiserror::Error;
13
14use crate::app::TransportWrapper;
15use crate::io::fpga_backdoor::{
16 Backdoor, BackdoorParams, BackdoorTarget, BackdoorTargetInfo, enter_backdoor_loader,
17};
18use crate::io::jtag::JtagParams;
19use crate::util::vmem::{Section, Vmem, Word};
20
21pub fn enter_backdoor(transport: &TransportWrapper, jtag_params: &JtagParams) -> Result<Backdoor> {
23 enter_backdoor_loader(transport)?;
24 let backdoor_params = BackdoorParams {
25 jtag: jtag_params.clone(),
26 };
27 backdoor_params.create(transport)?.connect(true)
28}
29
30fn normalize_word(word: &mut Word, bits_per_word: usize) {
32 let bytes_per_word = bits_per_word.div_ceil(8);
33 if word.bytes.len() > bytes_per_word {
34 let start = word.bytes.len() - bytes_per_word;
35 word.bytes.drain(..start);
36 } else if word.bytes.len() < bytes_per_word {
37 let mut padded = vec![0u8; bytes_per_word - word.bytes.len()];
38 padded.append(&mut word.bytes);
39 word.bytes = padded;
40 }
41
42 let extra_bits = bits_per_word % 8;
43 if extra_bits > 0 {
44 word.bytes[0] &= 0xFF >> (8 - extra_bits);
45 }
46}
47
48#[derive(Debug, Error)]
53#[error("Read verification at word {offset} failed. Expected: {expected}, Got: {actual}")]
54pub struct VerifyMismatch {
55 offset: u32,
56 expected: String,
57 actual: String,
58}
59
60pub fn verify_readback(
62 input: &mut [Word],
63 readback: &mut [Word],
64 bits_per_word: usize,
65 mut offset: u32,
66) -> Result<()> {
67 for (write_word, read_word) in readback.iter_mut().zip(input) {
68 normalize_word(write_word, bits_per_word);
69 normalize_word(read_word, bits_per_word);
70 if write_word != read_word {
71 return Err(VerifyMismatch {
72 offset,
73 expected: hex::encode(write_word.bytes.clone()),
74 actual: hex::encode(read_word.bytes.clone()),
75 }
76 .into());
77 }
78
79 offset += 1;
80 }
81
82 Ok(())
83}
84
85pub fn write_to_target(
86 target: &mut BackdoorTarget,
87 target_id: &str,
88 data: Vec<Section>,
89 verify: bool,
90 clearing: bool,
91) -> Result<()> {
92 target
96 .write_hash(0)
97 .context("failed to clear HASH_LAST_LOADED before write")?;
98
99 if clearing {
101 log::info!("Clearing the {}...", target_id);
102 } else {
103 log::info!("Writing to the {}...", target_id);
104 }
105 for mut section in data {
106 log::debug!(
107 "Writing section of size {} to word {} of target {}",
108 section.data.len(),
109 section.addr,
110 target_id
111 );
112
113 let first_word = §ion.data.first().clone();
116 if section.addr == 0
117 && section.data.len() == target.info.depth as usize
118 && section.data.iter().all(|w| w == first_word.unwrap())
119 {
120 log::debug!(
121 "Clearing target {} with word {}",
122 target_id,
123 hex::encode(first_word.unwrap().bytes.clone())
124 );
125 let start = std::time::Instant::now();
126 target.clear(first_word.unwrap(), true)?;
127 log::debug!(
128 "Cleared {} word(s) of target {} in {:?}",
129 section.data.len(),
130 target_id,
131 start.elapsed(),
132 );
133 } else {
134 let start = std::time::Instant::now();
135 target.write(section.addr, §ion.data, false, true)?;
136 log::debug!(
137 "Wrote {} word(s) to target {} in {:?}",
138 section.data.len(),
139 target_id,
140 start.elapsed(),
141 );
142 }
143
144 if verify {
146 let start = std::time::Instant::now();
147 let mut readback = target.read(section.addr, section.data.len() as u32, false)?;
148 verify_readback(
149 &mut section.data,
150 &mut readback,
151 target.info.width as usize,
152 section.addr,
153 )?;
154 log::debug!(
155 "Verified {} word(s) of target {} in {:?}",
156 section.data.len(),
157 target_id,
158 start.elapsed(),
159 );
160 }
161 }
162
163 Ok(())
164}
165
166#[derive(Debug, Clone)]
167pub struct TargetWrite {
168 pub target: String,
169 pub path: PathBuf,
170 pub offset: Option<u32>,
171}
172
173impl FromStr for TargetWrite {
174 type Err = anyhow::Error;
175
176 fn from_str(s: &str) -> Result<Self, Self::Err> {
177 let (target_path, offset) = s
178 .split_once('@')
179 .map(|(x, y)| (x, y.parse::<u32>().ok()))
180 .unwrap_or((s, None));
181
182 let (target, path) = target_path
183 .split_once('=')
184 .context("expected input like TARGET=FILE[@OFFSET], but no '=' was seen")?;
185 if target.is_empty() {
186 bail!("target name cannot be empty");
187 }
188 if path.is_empty() {
189 bail!("file path cannot be empty");
190 }
191
192 Ok(TargetWrite {
193 target: target.to_string(),
194 path: PathBuf::from(path),
195 offset,
196 })
197 }
198}
199
200impl TargetWrite {
201 pub fn load_data(&self) -> Result<Vec<Section>> {
202 log::info!("Loading VMEM file: {}", self.path.display());
203 let vmem_content = fs::read_to_string(&self.path)?;
204 let mut vmem = Vmem::from_str(&vmem_content, None)?;
205 vmem.merge_sections(None);
206 let mut sections: Vec<Section> = vmem.sections().cloned().collect();
207
208 if let Some(offset) = self.offset {
210 for section in &mut sections {
211 section.addr += offset;
212 }
213 }
214
215 Ok(sections)
216 }
217
218 fn file_hash(&self) -> Result<u32> {
223 let bytes = fs::read(&self.path)
224 .with_context(|| format!("failed to read VMEM file: {}", self.path.display()))?;
225 Ok(Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(&bytes))
226 }
227
228 pub fn backdoor_write(
238 &self,
239 backdoor: &mut Backdoor,
240 verify: bool,
241 check_hash: bool,
242 ) -> Result<()> {
243 let mut target = backdoor
244 .target_by_id_str(&self.target)?
245 .context(format!("FPGA target '{}' not found", &self.target))?;
246
247 let new_hash = if check_hash {
248 let new_hash = self.file_hash()?;
249 let old_hash = target.read_hash()?;
250 if old_hash == new_hash && old_hash != 0 {
254 log::info!(
255 "Skipping preload of target {}: content hash unchanged (0x{new_hash:08x})",
256 self.target,
257 );
258 return Ok(());
259 }
260 log::info!(
261 "Preloading target {}: content hash changed (old=0x{old_hash:08x}, new=0x{new_hash:08x})",
262 self.target,
263 );
264 Some(new_hash)
265 } else {
266 None
267 };
268
269 let data = self.load_data()?;
270 if let Err(err) = write_to_target(&mut target, &self.target, data, verify, false) {
271 if verify && err.downcast_ref::<VerifyMismatch>().is_some() {
273 target
278 .write_hash(0)
279 .context("failed to clear HASH_LAST_LOADED after integrity error")?;
280 return Err(err.context(format!(
281 "integrity error: write verification failed for target {} \
282 (HASH_LAST_LOADED has been cleared)",
283 self.target
284 )));
285 }
286 return Err(err);
287 }
288 if let Some(new_hash) = new_hash {
289 target.write_hash(new_hash)?;
290 }
291
292 Ok(())
293 }
294}
295
296#[derive(Debug, Clone)]
297pub struct TargetClear {
298 pub target: String,
299 pub num_words: Option<u32>,
300 pub offset: Option<u32>,
301}
302
303impl FromStr for TargetClear {
304 type Err = anyhow::Error;
305
306 fn from_str(s: &str) -> Result<Self, Self::Err> {
307 let (target_words, offset) = s
308 .split_once('@')
309 .map(|(x, y)| (x, y.parse::<u32>().ok()))
310 .unwrap_or((s, None));
311
312 let (target, num_words) = target_words
313 .split_once('=')
314 .context("expected input like TARGET=NUM_WORDS[@OFFSET], but no '=' was seen")?;
315 if target.is_empty() {
316 bail!("target name cannot be empty");
317 }
318 if num_words.is_empty() {
319 bail!("num_words cannot be empty");
320 }
321 let num_words = if num_words.to_lowercase() == "all" {
322 None
323 } else {
324 Some(num_words.parse::<u32>()?)
325 };
326
327 Ok(TargetClear {
328 target: target.to_string(),
329 num_words,
330 offset,
331 })
332 }
333}
334
335impl TargetClear {
336 pub fn as_sections(&self, target_info: &BackdoorTargetInfo) -> Vec<Section> {
337 let num_bytes = target_info.width.div_ceil(8) as usize;
338 let remaining_words = target_info.depth - self.offset.unwrap_or(0);
339 let num_words = self.num_words.unwrap_or(remaining_words);
340 let data = vec![Word::new(vec![0x00; num_bytes]); num_words as usize];
341 vec![Section {
342 addr: self.offset.unwrap_or(0),
343 data,
344 }]
345 }
346
347 pub fn backdoor_write(&self, backdoor: &mut Backdoor, verify: bool) -> Result<()> {
348 let mut target = backdoor
349 .target_by_id_str(&self.target)?
350 .context(format!("FPGA target '{}' not found", &self.target))?;
351
352 let data = self.as_sections(&target.info);
353 write_to_target(&mut target, &self.target, data, verify, true)
354 }
355}
356
357#[derive(Debug, Args)]
359pub struct LoadMemories {
360 #[arg(long = "clear-memory", value_name = "TARGET=NUM_WORDS[@OFFSET]")]
362 pub target_clears: Vec<TargetClear>,
363
364 #[arg(long = "load-memory", value_name = "TARGET=FILE[@OFFSET]")]
366 pub target_writes: Vec<TargetWrite>,
367
368 #[arg(long = "check-memory-hash", value_name = "TARGET")]
374 pub check_memory_hash: Vec<String>,
375}
376
377impl LoadMemories {
378 pub fn init(&self, transport: &TransportWrapper, jtag_params: &JtagParams) -> Result<()> {
379 if self.target_clears.is_empty() && self.target_writes.is_empty() {
380 return Ok(());
381 }
382
383 let mut backdoor = enter_backdoor(transport, jtag_params)?;
384 self.target_clears
385 .iter()
386 .try_for_each(|t| t.backdoor_write(&mut backdoor, false))?;
387 self.target_writes.iter().try_for_each(|t| {
388 let check_hash = self.check_memory_hash.contains(&t.target);
389 t.backdoor_write(&mut backdoor, false, check_hash)
390 })?;
391 backdoor.set_done()?;
392
393 Ok(())
394 }
395}
396
397#[cfg(test)]
401mod tests {
402 use super::*;
403 use std::io::{Seek, SeekFrom, Write};
404
405 #[test]
406 fn file_hash_matches_known_crc32_iso_hdlc_check_value() {
407 let mut file = tempfile::NamedTempFile::new().unwrap();
410 file.write_all(b"123456789").unwrap();
411
412 let target_write = TargetWrite {
413 target: "TEST".to_string(),
414 path: file.path().to_path_buf(),
415 offset: None,
416 };
417 assert_eq!(target_write.file_hash().unwrap(), 0xcbf43926);
418 }
419
420 #[test]
421 fn file_hash_changes_with_content() {
422 let mut file = tempfile::NamedTempFile::new().unwrap();
423 file.write_all(b"hello").unwrap();
424 let target_write = TargetWrite {
425 target: "TEST".to_string(),
426 path: file.path().to_path_buf(),
427 offset: None,
428 };
429 let hash_a = target_write.file_hash().unwrap();
430
431 file.as_file_mut().set_len(0).unwrap();
432 file.seek(SeekFrom::Start(0)).unwrap();
433 file.write_all(b"world").unwrap();
434 let hash_b = target_write.file_hash().unwrap();
435
436 assert_ne!(hash_a, hash_b);
437 }
438}