1use anyhow::{Context, Result, bail, ensure};
6use clap::Args;
7use serde::ser::{Serialize, SerializeStruct, Serializer};
8use std::time::{Duration, Instant};
9
10use crate::app::TransportWrapper;
11use crate::debug::dmi::{Dmi, OpenOcdDmi};
12use crate::io::jtag::{JtagChain, JtagParams, JtagTap};
13use crate::transport::Capability;
14use crate::util::vmem::Word;
15
16pub mod regs {
20
21 pub const STATUS_REG_OFFSET: usize = 0x0;
23 pub const STATUS_ERROR_BIT: u32 = 0;
24 pub const STATUS_CLEAR_IDLE_BIT: u32 = 1;
25
26 pub const CONTROL_REG_OFFSET: usize = 0x4;
28 pub const CONTROL_DONE_BIT: u32 = 0;
29 pub const CONTROL_WRITE_ENA_BIT: u32 = 1;
30 pub const CONTROL_CLEAR_START_BIT: u32 = 2;
31 pub const CONTROL_TARGET_IDX_MASK: u32 = 0xff;
32 pub const CONTROL_TARGET_IDX_OFFSET: usize = 8;
33
34 pub const NUM_BKDR_TARGETS_REG_OFFSET: usize = 0x8;
36 pub const MISSION_MODE_SWITCH_DELAY_REG_OFFSET: usize = 0xc;
37 pub const USR_ACCESS_TIMESTAMP_REG_OFFSET: usize = 0x10;
38 pub const TARGET_INFO_0_REG_OFFSET: usize = 0x100;
39 pub const WIDTH_INFO_0_REG_OFFSET: usize = 0x200;
40 pub const DEPTH_INFO_0_REG_OFFSET: usize = 0x300;
41 pub const READ_DATA_0_REG_OFFSET: usize = 0x400;
42 pub const WRITE_DATA_0_REG_OFFSET: usize = 0x500;
43 pub const INDEX_REG_OFFSET: usize = 0x600;
44}
45
46pub mod consts {
47 pub const RESET_PULSE_MS: u64 = 50;
49
50 pub const HOLD_TAP_STRAPS_MS: u64 = 50;
52
53 pub const CLEAR_TIMEOUT_SECS: u64 = 5;
55
56 pub const JTAG_DONE_CYCLES: u64 = 10000;
59
60 pub const CW340_MAIN_CLOCK_FREQ_HZ: u64 = 24 * 1000 * 1000; pub const DATA_REGS_PER_WORD: usize = 8; }
68
69use consts::*;
70
71pub fn enter_backdoor_loader(transport: &TransportWrapper) -> Result<()> {
73 transport.capabilities()?.request(Capability::GPIO).ok()?;
74 let pinmux_tap_backdoor = transport.pin_strapping("PINMUX_TAP_FPGA_BACKDOOR")?;
75 let reset = transport.pin_strapping("RESET")?;
76
77 pinmux_tap_backdoor.apply()?;
78 reset.apply()?;
79 std::thread::sleep(Duration::from_millis(RESET_PULSE_MS));
80 reset.remove()?;
81 std::thread::sleep(Duration::from_millis(HOLD_TAP_STRAPS_MS));
82 pinmux_tap_backdoor.remove()
83}
84
85pub struct BackdoorTap<'a> {
90 jtag: Box<dyn JtagChain + 'a>,
91 jtag_speed_khz: u64,
92}
93
94impl BackdoorTap<'_> {
95 pub fn connect(self, enumerate: bool) -> Result<Backdoor> {
97 let openocd = self.jtag.connect(JtagTap::BackdoorTap)?.into_raw()?;
98 Backdoor::new(
99 OpenOcdDmi::new(openocd, "fpga_backdoor.tap")?,
100 self.jtag_speed_khz,
101 enumerate,
102 )
103 }
104}
105
106#[derive(Debug, Args, Clone)]
107pub struct BackdoorParams {
108 #[command(flatten)]
110 pub jtag: JtagParams,
111}
112
113impl BackdoorParams {
114 pub fn create<'a>(&self, transport: &'a TransportWrapper) -> Result<BackdoorTap<'a>> {
115 Ok(BackdoorTap {
116 jtag: self.jtag.create(transport)?,
117 jtag_speed_khz: self.jtag.adapter_speed_khz,
118 })
119 }
120}
121
122#[derive(Debug, Clone, Copy)]
124pub struct BackdoorTargetInfo {
125 pub id: u32,
127 pub width: u32,
129 pub depth: u32,
131}
132
133impl BackdoorTargetInfo {
134 pub fn id_str(&self) -> String {
136 let bytes = self.id.to_be_bytes();
137
138 String::from_utf8_lossy(&bytes).trim_end().to_owned()
139 }
140
141 pub fn id_from_str(id: &str) -> Result<u32> {
143 let mut bytes = [32u8; 4];
144 let src = id.as_bytes();
145 let len = id.len().min(4);
146 bytes[..len].copy_from_slice(&src[..len]);
147
148 Ok(u32::from_be_bytes(bytes))
149 }
150}
151
152impl Serialize for BackdoorTargetInfo {
153 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: Serializer,
156 {
157 let mut s = serializer.serialize_struct("BackdoorTargetInfo", 4)?;
158 s.serialize_field("id", &self.id)?;
159 s.serialize_field("id_str", &self.id_str())?;
160 s.serialize_field("width", &self.width)?;
161 s.serialize_field("depth", &self.depth)?;
162 s.end()
163 }
164}
165
166impl std::fmt::Display for BackdoorTargetInfo {
167 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
168 write!(f, "{} {} x {}", self.id_str(), self.width, self.depth)
169 }
170}
171
172impl Word {
173 fn to_u32_chunks(&self) -> Result<[u32; DATA_REGS_PER_WORD]> {
175 ensure!(
176 self.bytes.len() <= DATA_REGS_PER_WORD * 4,
177 "Word '{}' with {} bytes will not fit into {} 32-bit registers.",
178 hex::encode(self.bytes.clone()),
179 self.bytes.len(),
180 DATA_REGS_PER_WORD
181 );
182 let mut chunks = [0u32; DATA_REGS_PER_WORD];
183
184 for (i, &b) in self.bytes.iter().rev().enumerate() {
187 let chunk_idx = i / 4;
189 let byte_pos = i % 4;
190 chunks[chunk_idx] |= (b as u32) << (byte_pos * 8);
191 }
192
193 Ok(chunks)
194 }
195
196 fn from_u32_chunks(chunks: &[u32; DATA_REGS_PER_WORD], bytes_per_word: usize) -> Self {
198 let num_chunks = bytes_per_word.div_ceil(size_of::<u32>());
199 let padding_bytes = (num_chunks * size_of::<u32>()) - bytes_per_word;
200
201 Self {
202 bytes: chunks
203 .iter()
204 .take(num_chunks)
205 .rev()
206 .flat_map(|chunk| chunk.to_be_bytes())
207 .skip(padding_bytes)
208 .collect(),
209 }
210 }
211}
212
213pub struct BackdoorTarget<'a> {
215 backdoor: &'a mut Backdoor,
216 index: u8,
217 pub info: BackdoorTargetInfo,
219}
220
221impl<'a> BackdoorTarget<'a> {
222 pub fn write(
229 &mut self,
230 start: u32,
231 words: &[Word],
232 write_all: bool,
233 check_status: bool,
234 ) -> Result<()> {
235 ensure!(
236 start + words.len() as u32 <= self.info.depth,
237 "fpga bkdr_loader write of len {:#x} to word {:#x} of {} is out of bounds (depth: {:#x})",
238 words.len(),
239 start,
240 self.info.id_str(),
241 self.info.depth,
242 );
243 self.backdoor
244 .write_target(self.index, start, words, write_all, check_status)
245 }
246
247 pub fn read(&mut self, start: u32, count: u32, check_status: bool) -> Result<Vec<Word>> {
252 ensure!(
253 start + count <= self.info.depth,
254 "fpga bkdr_loader read of len {:#x} to word {:#x} of {} is out of bounds (depth: {:#x})",
255 count,
256 start,
257 self.info.id_str(),
258 self.info.depth,
259 );
260 self.backdoor
261 .read_target(self.index, start, count, check_status)
262 }
263
264 pub fn clear(&mut self, word: &Word, check_status: bool) -> Result<()> {
270 self.backdoor.clear_target(self.index, word, check_status)
271 }
272}
273
274pub struct Backdoor {
276 dmi: OpenOcdDmi,
277 jtag_speed_khz: u64,
278 targets: Vec<BackdoorTargetInfo>,
279}
280
281impl Backdoor {
282 pub fn new(dmi: OpenOcdDmi, jtag_speed_khz: u64, enumerate: bool) -> Result<Self> {
285 let mut fpga_backdoor = Self {
286 dmi,
287 jtag_speed_khz,
288 targets: Vec::new(),
289 };
290 if enumerate {
291 fpga_backdoor.enumerate()?;
292 }
293
294 Ok(fpga_backdoor)
295 }
296
297 fn dmi_read(&mut self, byte_addr: usize) -> Result<u32> {
300 self.dmi.dmi_read(byte_addr as u32 >> 2)
301 }
302
303 fn dmi_write(&mut self, byte_addr: usize, data: u32) -> Result<()> {
306 self.dmi.dmi_write(byte_addr as u32 >> 2, data)
307 }
308
309 pub fn enumerate(&mut self) -> Result<()> {
311 self.targets.clear();
312
313 let num_targets = self
314 .dmi_read(regs::NUM_BKDR_TARGETS_REG_OFFSET)
315 .context("cannot read number of targets")? as usize;
316 log::info!("Number of FPGA bkdr_loader targets: {num_targets:?}");
317 for idx in 0..num_targets {
318 let addr_offset = idx * 4;
319 let target_info = BackdoorTargetInfo {
320 id: self
321 .dmi_read(regs::TARGET_INFO_0_REG_OFFSET + addr_offset)
322 .context("cannot read target info")?,
323 width: self
324 .dmi_read(regs::WIDTH_INFO_0_REG_OFFSET + addr_offset)
325 .context("cannot read width info")?,
326 depth: self
327 .dmi_read(regs::DEPTH_INFO_0_REG_OFFSET + addr_offset)
328 .context("cannot read depth info")?,
329 };
330 self.targets.push(target_info);
331 }
332
333 Ok(())
334 }
335
336 pub fn set_done(mut self) -> Result<()> {
341 log::debug!("Finished using backdoor loader until next reset");
342
343 let jtag_freq_hz: u64 = self.jtag_speed_khz * 1000;
350 let soc_clk_wait_cycles =
351 CW340_MAIN_CLOCK_FREQ_HZ.div_ceil(jtag_freq_hz) * JTAG_DONE_CYCLES;
352 let soc_clk_wait_cycles: u32 = soc_clk_wait_cycles.try_into().unwrap_or_else(|_| {
353 log::warn!(
354 "Configured JTAG speed ({} kHz) may overflow bkdr_loader wait time.",
355 self.jtag_speed_khz
356 );
357 log::warn!("Configuring maximum wait time.");
358 u32::MAX
359 });
360 self.dmi_write(
361 regs::MISSION_MODE_SWITCH_DELAY_REG_OFFSET,
362 soc_clk_wait_cycles,
363 )
364 .context("cannot write FPGA bkdr_loader mission_mode_switch_delay register")?;
365
366 self.dmi_write(regs::CONTROL_REG_OFFSET, 0b1 << regs::CONTROL_DONE_BIT)
367 .context("cannot write done to FPGA bkdr_loader control reg")?;
368
369 let done_wait_millis = (JTAG_DONE_CYCLES * 1000).div_ceil(jtag_freq_hz);
373 std::thread::sleep(Duration::from_millis(done_wait_millis));
374
375 Ok(())
376 }
377
378 pub fn targets(&self) -> &[BackdoorTargetInfo] {
380 &self.targets
381 }
382
383 pub fn target_by_id(&mut self, id: u32) -> Option<BackdoorTarget<'_>> {
385 let (index, info) = self.targets.iter().enumerate().find(|&(_, t)| t.id == id)?;
386 let (index, info) = (index as u8, *info);
387
388 Some(BackdoorTarget {
389 backdoor: self,
390 index,
391 info,
392 })
393 }
394
395 pub fn target_by_id_str(&mut self, id: &str) -> Result<Option<BackdoorTarget<'_>>> {
397 let encoded_id = BackdoorTargetInfo::id_from_str(id)?;
398
399 Ok(self.target_by_id(encoded_id))
400 }
401
402 pub fn write_target(
409 &mut self,
410 target_index: u8,
411 start: u32,
412 words: &[Word],
413 write_all: bool,
414 check_status: bool,
415 ) -> Result<()> {
416 ensure!(
417 usize::from(target_index) < self.targets.len(),
418 "Target index {} is out of range for {} targets",
419 target_index,
420 self.targets.len()
421 );
422 let info = self.targets[target_index as usize];
423 let width = info.width as usize;
424 let regs_used = width.div_ceil(u32::BITS as usize);
425 ensure!(
426 regs_used <= DATA_REGS_PER_WORD,
427 "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
428 width,
429 regs_used,
430 DATA_REGS_PER_WORD
431 );
432
433 let mut prev_regs = [0u32; DATA_REGS_PER_WORD];
435 let mut word_idx = start;
436 let mut first = true;
437
438 let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
439 control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
440 self.dmi_write(regs::CONTROL_REG_OFFSET, control)
441 .context("cannot write to control register")?;
442
443 let mut writes = Vec::new();
446
447 for word in words {
448 let regs = word.to_u32_chunks()?;
449 for idx in 0..regs_used {
450 if write_all || first || regs[idx] != prev_regs[idx] {
454 let addr_offset = idx * 4;
455 writes.push((
456 ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
457 regs[idx],
458 ));
459 prev_regs[idx] = regs[idx];
460 }
461 }
462 writes.push(((regs::INDEX_REG_OFFSET >> 2) as u32, word_idx));
463 first = false;
464 word_idx += 1;
465 }
466
467 self.dmi
468 .batched_dmi_writes(&writes)
469 .context("failed to perform DMI writes")?;
470
471 if check_status {
472 let status = self
473 .dmi_read(regs::STATUS_REG_OFFSET)
474 .context("cannot read status")?;
475 ensure!(
476 status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
477 "fpga bkdr_loader reported an error writing to target {}",
478 info.id_str()
479 );
480 }
481
482 Ok(())
483 }
484
485 pub fn read_target(
490 &mut self,
491 target_index: u8,
492 start: u32,
493 count: u32,
494 check_status: bool,
495 ) -> Result<Vec<Word>> {
496 ensure!(
497 usize::from(target_index) < self.targets.len(),
498 "Target index {} is out of range for {} targets",
499 target_index,
500 self.targets.len()
501 );
502 let info = self.targets[target_index as usize];
503 let width = info.width as usize;
504 let bytes_per_word = width.div_ceil(u8::BITS as usize);
505 let regs_used = width.div_ceil(u32::BITS as usize);
506 ensure!(
507 regs_used <= DATA_REGS_PER_WORD,
508 "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
509 width,
510 regs_used,
511 DATA_REGS_PER_WORD
512 );
513
514 let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
515 control &= !(0b1 << regs::CONTROL_WRITE_ENA_BIT);
516 self.dmi_write(regs::CONTROL_REG_OFFSET, control)
517 .context("cannot write to control register")?;
518
519 let mut words = Vec::new();
520
521 for word_idx in start..(start + count) {
522 self.dmi_write(regs::INDEX_REG_OFFSET, word_idx)
523 .context("cannot write word index")?;
524
525 if check_status {
526 let status = self
527 .dmi_read(regs::STATUS_REG_OFFSET)
528 .context("cannot read status")?;
529 ensure!(
530 status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
531 "fpga bkdr_loader reported an error reading from word idx {} of target {}",
532 word_idx,
533 info.id_str()
534 );
535 }
536
537 let mut regs = [0u32; DATA_REGS_PER_WORD];
538 for (idx, reg) in regs.iter_mut().enumerate().take(regs_used) {
539 let addr_offset = idx * 4;
540 *reg = self
541 .dmi_read(regs::READ_DATA_0_REG_OFFSET + addr_offset)
542 .context("cannot read from read_data register")?;
543 }
544
545 words.push(Word::from_u32_chunks(®s, bytes_per_word));
546 }
547
548 Ok(words)
549 }
550
551 pub fn clear_target(
557 &mut self,
558 target_index: u8,
559 word: &Word,
560 check_status: bool,
561 ) -> Result<()> {
562 ensure!(
563 usize::from(target_index) < self.targets.len(),
564 "Target index {} is out of range for {} targets",
565 target_index,
566 self.targets.len()
567 );
568 let info = self.targets[target_index as usize];
569
570 self.dmi
571 .batched_dmi_writes(
572 &word
573 .to_u32_chunks()?
574 .into_iter()
575 .enumerate()
576 .map(|(idx, reg)| {
577 let addr_offset = idx * 4;
578 (
579 ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
580 reg,
581 )
582 })
583 .collect::<Vec<_>>(),
584 )
585 .context("failed to perform DMI writes")?;
586
587 let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
588 control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
589 control |= 0b1 << regs::CONTROL_CLEAR_START_BIT;
590 self.dmi_write(regs::CONTROL_REG_OFFSET, control)
591 .context("cannot write to control register")?;
592
593 let timeout = Instant::now() + Duration::from_secs(CLEAR_TIMEOUT_SECS);
595 let mut status: u32;
596 loop {
597 status = self
598 .dmi_read(regs::STATUS_REG_OFFSET)
599 .context("cannot read status")?;
600 if status & (0b1 << regs::STATUS_CLEAR_IDLE_BIT) != 0 {
601 break;
602 }
603
604 if Instant::now() > timeout {
605 bail!(
606 "Timed out after {} seconds waiting for {} clear to complete",
607 CLEAR_TIMEOUT_SECS,
608 info.id_str()
609 );
610 }
611 }
612
613 if check_status {
614 ensure!(
615 status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
616 "fpga bkdr_loader reported an error writing to target {}",
617 info.id_str()
618 );
619 }
620
621 Ok(())
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628
629 #[test]
630 fn identifer_str_encoding() {
631 let (width, depth) = (1, 1);
632 for (id, id_str) in [
633 (0x4f545020, "OTP"),
634 (0x5352414d, "SRAM"),
635 (0x46493031, "FI01"),
636 ] {
637 assert_eq!(BackdoorTargetInfo { id, width, depth }.id_str(), id_str);
638 assert_eq!(BackdoorTargetInfo::id_from_str(id_str).unwrap(), id);
639 }
640 }
641
642 #[test]
643 fn byte_u32_conversion() {
644 let word = Word::new(vec![
646 0x5a, 0xa5, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xbe, 0xef, 0xca, 0xfe,
647 ]);
648 let mut expected = [0x0; DATA_REGS_PER_WORD];
651 expected[0] = 0xbeefcafe;
652 expected[1] = 0x89abcdef;
653 expected[2] = 0x01234567;
654 expected[3] = 0x00005aa5;
655
656 let chunks = word.to_u32_chunks().unwrap();
657 assert_eq!(chunks, expected);
658 assert_eq!(Word::from_u32_chunks(&chunks, word.bytes.len()), word);
659 }
660}