opentitanlib/io/
fpga_backdoor.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 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
16/// FPGA Backdoor loader register offsets (byte-addressed) and field definitions.
17/// See hw/ip/bkdr_loader/doc/registers.md
18/// TODO: it would be nice to use Bazel to auto-generate a rust "header" for this IP instead.
19pub mod regs {
20
21    // STATUS register
22    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    // CONTROL register
27    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_AUTO_INCR_BIT: u32 = 3;
32    pub const CONTROL_TARGET_IDX_MASK: u32 = 0xff;
33    pub const CONTROL_TARGET_IDX_OFFSET: usize = 8;
34
35    // Other registers (all have one 32-bit `VAL` field)
36    pub const NUM_BKDR_TARGETS_REG_OFFSET: usize = 0x8;
37    pub const MISSION_MODE_SWITCH_DELAY_REG_OFFSET: usize = 0xc;
38    pub const USR_ACCESS_TIMESTAMP_REG_OFFSET: usize = 0x10;
39    pub const TARGET_INFO_0_REG_OFFSET: usize = 0x100;
40    pub const WIDTH_INFO_0_REG_OFFSET: usize = 0x200;
41    pub const DEPTH_INFO_0_REG_OFFSET: usize = 0x300;
42    pub const READ_DATA_0_REG_OFFSET: usize = 0x400;
43    pub const WRITE_DATA_0_REG_OFFSET: usize = 0x500;
44    pub const INDEX_REG_OFFSET: usize = 0x600;
45    pub const HASH_LAST_LOADED_0_REG_OFFSET: usize = 0x700;
46}
47
48pub mod consts {
49    // How long the reset strapping is applied for when entering the backdoor loader.
50    pub const RESET_PULSE_MS: u64 = 50;
51
52    // How long the backdoor loader TAP strapping is held after leaving reset.
53    pub const HOLD_TAP_STRAPS_MS: u64 = 50;
54
55    // Time to wait for a clear operation to finish.
56    pub const CLEAR_TIMEOUT_SECS: u64 = 5;
57
58    // How many JTAG cycles to wait for before considering the `CONTROL.DONE` transaction
59    // as being completed. We default to 10000, which is a conservative threshold.
60    pub const JTAG_DONE_CYCLES: u64 = 10000;
61
62    // FIXME: This should be refactored so that the ot_transport JSON5 file declares clock
63    // frequencies for each device which we can then query through the transport. This
64    // is hardcoded for now for convenience.
65    pub const CW340_MAIN_CLOCK_FREQ_HZ: u64 = 24 * 1000 * 1000; // 24 MHz
66
67    // Parameters - see hw/ip/bkdr_loader/doc/interfaces.md
68    pub const DATA_REGS_PER_WORD: usize = 8; // MaxWordWidthDiv32
69}
70
71use consts::*;
72
73/// Apply the bkdr_loader TAP strapping and reset to enter the backdoor loader.
74pub fn enter_backdoor_loader(transport: &TransportWrapper) -> Result<()> {
75    transport.capabilities()?.request(Capability::GPIO).ok()?;
76    let pinmux_tap_backdoor = transport.pin_strapping("PINMUX_TAP_FPGA_BACKDOOR")?;
77    let reset = transport.pin_strapping("RESET")?;
78
79    log::info!(
80        "Resetting with PINMUX_TAP_FPGA_BACKDOOR (== DFT) strapping applied to enter the backdoor loader"
81    );
82    pinmux_tap_backdoor.apply()?;
83    reset.apply()?;
84    std::thread::sleep(Duration::from_millis(RESET_PULSE_MS));
85    reset.remove()?;
86    std::thread::sleep(Duration::from_millis(HOLD_TAP_STRAPS_MS));
87    pinmux_tap_backdoor.remove()?;
88    log::info!("Reset complete, backdoor TAP strapping released");
89    Ok(())
90}
91
92/// A struct which represents a backdoor loader interface.
93///
94/// This struct represents an adaptor that has been configured to connect to a given JTAG chain,
95/// but has not yet been configured to access the backdoor TAP.
96pub struct BackdoorTap<'a> {
97    jtag: Box<dyn JtagChain + 'a>,
98    jtag_speed_khz: u64,
99}
100
101impl BackdoorTap<'_> {
102    /// Connect to the backdoor TAP, optionally enumerate information about all targets.
103    pub fn connect(self, enumerate: bool) -> Result<Backdoor> {
104        let openocd = self.jtag.connect(JtagTap::BackdoorTap)?.into_raw()?;
105        Backdoor::new(
106            OpenOcdDmi::new(openocd, "fpga_backdoor.tap")?,
107            self.jtag_speed_khz,
108            enumerate,
109        )
110    }
111}
112
113#[derive(Debug, Args, Clone)]
114pub struct BackdoorParams {
115    /// JTAG options to apply to the backdoor TAP.
116    #[command(flatten)]
117    pub jtag: JtagParams,
118}
119
120impl BackdoorParams {
121    pub fn create<'a>(&self, transport: &'a TransportWrapper) -> Result<BackdoorTap<'a>> {
122        Ok(BackdoorTap {
123            jtag: self.jtag.create(transport)?,
124            jtag_speed_khz: self.jtag.adapter_speed_khz,
125        })
126    }
127}
128
129/// Information about a specific backdoor target, e.g. OTP, ROM, FB0, SRAM.
130#[derive(Debug, Clone, Copy)]
131pub struct BackdoorTargetInfo {
132    /// The unique identifier of the backdoor target
133    pub id: u32,
134    /// The word width of the memory of the backdoor target.
135    pub width: u32,
136    /// The depth (number of words) of the memory of the backdoor target.
137    pub depth: u32,
138}
139
140impl BackdoorTargetInfo {
141    /// The target's unique identifier as a <= 4 character UTF-8 string.
142    pub fn id_str(&self) -> String {
143        let bytes = self.id.to_be_bytes();
144
145        String::from_utf8_lossy(&bytes).trim_end().to_owned()
146    }
147
148    // Convert a UTF-8 ID string into the unique u32 identifier format used by targets.
149    pub fn id_from_str(id: &str) -> Result<u32> {
150        let mut bytes = [32u8; 4];
151        let src = id.as_bytes();
152        let len = id.len().min(4);
153        bytes[..len].copy_from_slice(&src[..len]);
154
155        Ok(u32::from_be_bytes(bytes))
156    }
157}
158
159impl Serialize for BackdoorTargetInfo {
160    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161    where
162        S: Serializer,
163    {
164        let mut s = serializer.serialize_struct("BackdoorTargetInfo", 4)?;
165        s.serialize_field("id", &self.id)?;
166        s.serialize_field("id_str", &self.id_str())?;
167        s.serialize_field("width", &self.width)?;
168        s.serialize_field("depth", &self.depth)?;
169        s.end()
170    }
171}
172
173impl std::fmt::Display for BackdoorTargetInfo {
174    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
175        write!(f, "{} {} x {}", self.id_str(), self.width, self.depth)
176    }
177}
178
179impl Word {
180    /// Convert the word to a series of 32-bit chunks to be written to the data registers.
181    fn to_u32_chunks(&self) -> Result<[u32; DATA_REGS_PER_WORD]> {
182        ensure!(
183            self.bytes.len() <= DATA_REGS_PER_WORD * 4,
184            "Word '{}' with {} bytes will not fit into {} 32-bit registers.",
185            hex::encode(self.bytes.clone()),
186            self.bytes.len(),
187            DATA_REGS_PER_WORD
188        );
189        let mut chunks = [0u32; DATA_REGS_PER_WORD];
190
191        // Bytes are stored in Big Endian; when written to registers, the u32
192        // chunks are provided in LSB-first order (Little Endian).
193        for (i, &b) in self.bytes.iter().rev().enumerate() {
194            // Within u32 chunks, bytes are still given in MSB-first order (Big Endian).
195            let chunk_idx = i / 4;
196            let byte_pos = i % 4;
197            chunks[chunk_idx] |= (b as u32) << (byte_pos * 8);
198        }
199
200        Ok(chunks)
201    }
202
203    /// Convert the 32-bit chunks read from data registers into a word (MSB-first byte stream).
204    fn from_u32_chunks(chunks: &[u32; DATA_REGS_PER_WORD], bytes_per_word: usize) -> Self {
205        let num_chunks = bytes_per_word.div_ceil(size_of::<u32>());
206        let padding_bytes = (num_chunks * size_of::<u32>()) - bytes_per_word;
207
208        Self {
209            bytes: chunks
210                .iter()
211                .take(num_chunks)
212                .rev()
213                .flat_map(|chunk| chunk.to_be_bytes())
214                .skip(padding_bytes)
215                .collect(),
216        }
217    }
218}
219
220/// Handle for interacting with a given target via the backdoor loader.
221pub struct BackdoorTarget<'a> {
222    backdoor: &'a mut Backdoor,
223    index: u8,
224    /// Information about the target.
225    pub info: BackdoorTargetInfo,
226}
227
228impl<'a> BackdoorTarget<'a> {
229    /// Write a sequence of words at a given offset (word index) in the target's memory.
230    ///
231    /// The `write_all` parameter is used to control whether writes can be optimized by
232    /// maintaining shadow CSRs to determine when register contents have genuinely changed.
233    /// The `check_status` parameter is used to control whether the status bit is polled
234    /// after all words are written, to check for any errors.
235    pub fn write(
236        &mut self,
237        start: u32,
238        words: &[Word],
239        write_all: bool,
240        check_status: bool,
241    ) -> Result<()> {
242        ensure!(
243            start + words.len() as u32 <= self.info.depth,
244            "fpga bkdr_loader write of len {:#x} to word {:#x} of {} is out of bounds (depth: {:#x})",
245            words.len(),
246            start,
247            self.info.id_str(),
248            self.info.depth,
249        );
250        self.backdoor
251            .write_target(self.index, start, words, write_all, check_status)
252    }
253
254    /// Read a sequence of words at a given offset (word index) from the target's memory.
255    ///
256    /// The `check_status` parameter is used to control whether the status bit is polled
257    /// after all words are read, to check for any errors.
258    pub fn read(&mut self, start: u32, count: u32, check_status: bool) -> Result<Vec<Word>> {
259        ensure!(
260            start + count <= self.info.depth,
261            "fpga bkdr_loader read of len {:#x} to word {:#x} of {} is out of bounds (depth: {:#x})",
262            count,
263            start,
264            self.info.id_str(),
265            self.info.depth,
266        );
267        self.backdoor
268            .read_target(self.index, start, count, check_status)
269    }
270
271    /// Write a single word at a given word index in the target's memory, without disturbing any
272    /// auto-increment cursor. See [`Backdoor::write_target_word`].
273    pub fn write_word(&mut self, index: u32, word: &Word, check_status: bool) -> Result<()> {
274        ensure!(
275            index < self.info.depth,
276            "fpga bkdr_loader write to word {:#x} of {} is out of bounds (depth: {:#x})",
277            index,
278            self.info.id_str(),
279            self.info.depth,
280        );
281        self.backdoor
282            .write_target_word(self.index, index, word, check_status)
283    }
284
285    /// Read a single word at a given word index from the target's memory, without disturbing any
286    /// auto-increment cursor. See [`Backdoor::read_target_word`].
287    pub fn read_word(&mut self, index: u32, check_status: bool) -> Result<Word> {
288        ensure!(
289            index < self.info.depth,
290            "fpga bkdr_loader read from word {:#x} of {} is out of bounds (depth: {:#x})",
291            index,
292            self.info.id_str(),
293            self.info.depth,
294        );
295        self.backdoor
296            .read_target_word(self.index, index, check_status)
297    }
298
299    /// Clear the entire memory of the target with a given word.
300    ///
301    /// An optimized fast-path for clearing memories, primarily used to replicate existing
302    /// bitstream synthesis defaults. The `check_status` parameter is used to control
303    /// whether the status bit is polled after clearing, to check for any errors.
304    pub fn clear(&mut self, word: &Word, check_status: bool) -> Result<()> {
305        self.backdoor.clear_target(self.index, word, check_status)
306    }
307
308    /// Read this target's `HASH_LAST_LOADED` register: a non-resettable, software-managed
309    /// hash of the content that was last preloaded into this target, see
310    /// [`Backdoor::read_target_hash`].
311    pub fn read_hash(&mut self) -> Result<u32> {
312        self.backdoor.read_target_hash(self.index)
313    }
314
315    /// Write this target's `HASH_LAST_LOADED` register, see [`Backdoor::write_target_hash`].
316    pub fn write_hash(&mut self, hash: u32) -> Result<()> {
317        self.backdoor.write_target_hash(self.index, hash)
318    }
319}
320
321/// A struct which represents an active backdoor loader connection.
322pub struct Backdoor {
323    dmi: OpenOcdDmi,
324    jtag_speed_khz: u64,
325    targets: Vec<BackdoorTargetInfo>,
326}
327
328impl Backdoor {
329    /// Construct a [`Backdoor`] from a DMI connection to the backdoor TAP. Optionally
330    /// enumerate and discover information about all available targets.
331    pub fn new(dmi: OpenOcdDmi, jtag_speed_khz: u64, enumerate: bool) -> Result<Self> {
332        let mut fpga_backdoor = Self {
333            dmi,
334            jtag_speed_khz,
335            targets: Vec::new(),
336        };
337        if enumerate {
338            fpga_backdoor.enumerate()?;
339        }
340
341        Ok(fpga_backdoor)
342    }
343
344    /// Read from a DMI register with the given byte address offset.
345    /// DMI is a register interface; we must map the byte offsets to register (word) index.
346    fn dmi_read(&mut self, byte_addr: usize) -> Result<u32> {
347        self.dmi.dmi_read(byte_addr as u32 >> 2)
348    }
349
350    /// Write a value to a DMI register with the given byte address offset.
351    /// DMI is a register interface; we must map the byte offsets to register (word) index.
352    fn dmi_write(&mut self, byte_addr: usize, data: u32) -> Result<()> {
353        self.dmi.dmi_write(byte_addr as u32 >> 2, data)
354    }
355
356    // Enumerate the backdoor loader and retrieve information about available targets.
357    pub fn enumerate(&mut self) -> Result<()> {
358        self.targets.clear();
359
360        let num_targets = self
361            .dmi_read(regs::NUM_BKDR_TARGETS_REG_OFFSET)
362            .context("cannot read number of targets")? as usize;
363        log::info!("Number of FPGA bkdr_loader targets: {num_targets:?}");
364        for idx in 0..num_targets {
365            let addr_offset = idx * 4;
366            let target_info = BackdoorTargetInfo {
367                id: self
368                    .dmi_read(regs::TARGET_INFO_0_REG_OFFSET + addr_offset)
369                    .context("cannot read target info")?,
370                width: self
371                    .dmi_read(regs::WIDTH_INFO_0_REG_OFFSET + addr_offset)
372                    .context("cannot read width info")?,
373                depth: self
374                    .dmi_read(regs::DEPTH_INFO_0_REG_OFFSET + addr_offset)
375                    .context("cannot read depth info")?,
376            };
377            self.targets.push(target_info);
378        }
379
380        Ok(())
381    }
382
383    /// Communicate with the backdoor loader that we are finished using it.
384    ///
385    /// This transitions the bkdr_loader from it from its "Preload" state to "Mission mode",
386    /// causing it to re-route incoming JTAG back to the regular downstream interface.
387    pub fn set_done(mut self) -> Result<()> {
388        log::debug!("Finished using backdoor loader until next reset");
389
390        // We don't want the bkdr_loader to re-route JTAG mid-transaction, since that will
391        // cause us to see an unexpected response, as we will then be talking to an entirely
392        // different DMI / DTM (which can also put the RV_DM into a bad state). It will also
393        // potentially put the RV_dM debug infrastructure into a bad state. Configure the
394        // bkdr_loader to wait long enough so that we can finish our JTAG transaction.
395        // FIXME: These calculations are specific to the CW340.
396        let jtag_freq_hz: u64 = self.jtag_speed_khz * 1000;
397        let soc_clk_wait_cycles =
398            CW340_MAIN_CLOCK_FREQ_HZ.div_ceil(jtag_freq_hz) * JTAG_DONE_CYCLES;
399        let soc_clk_wait_cycles: u32 = soc_clk_wait_cycles.try_into().unwrap_or_else(|_| {
400            log::warn!(
401                "Configured JTAG speed ({} kHz) may overflow bkdr_loader wait time.",
402                self.jtag_speed_khz
403            );
404            log::warn!("Configuring maximum wait time.");
405            u32::MAX
406        });
407        self.dmi_write(
408            regs::MISSION_MODE_SWITCH_DELAY_REG_OFFSET,
409            soc_clk_wait_cycles,
410        )
411        .context("cannot write FPGA bkdr_loader mission_mode_switch_delay register")?;
412
413        if let Err(e) = self
414            .dmi_write(regs::CONTROL_REG_OFFSET, 0b1 << regs::CONTROL_DONE_BIT)
415            .context("cannot write done to FPGA bkdr_loader control reg")
416        {
417            log::error!("Error received when writing to `CONTROL.DONE`: {:?}", e);
418            log::error!("Trying to continue anyway...");
419        }
420
421        // Explicitly shut down all JTAG state before the transition happens, to avoid
422        // putting the next TAP(s) into some bad state across the transition.
423        drop(self);
424
425        // Wait until the transition to mission mode is complete and the system exits reset
426        // before continuing. For most sensible JTAG speeds this should be basically instant;
427        // for very slow speeds (e.g. <= 50 kHz) we need to add some special casing.
428        let done_wait_millis = (JTAG_DONE_CYCLES * 1000).div_ceil(jtag_freq_hz);
429        std::thread::sleep(Duration::from_millis(done_wait_millis));
430
431        Ok(())
432    }
433
434    /// Retrieve information about all of the targets available via the backdoor interface.
435    pub fn targets(&self) -> &[BackdoorTargetInfo] {
436        &self.targets
437    }
438
439    /// Borrow a target by its integer identifier. Only one BackdoorTarget can exist at a time.
440    pub fn target_by_id(&mut self, id: u32) -> Option<BackdoorTarget<'_>> {
441        let (index, info) = self.targets.iter().enumerate().find(|&(_, t)| t.id == id)?;
442        let (index, info) = (index as u8, *info);
443
444        Some(BackdoorTarget {
445            backdoor: self,
446            index,
447            info,
448        })
449    }
450
451    /// Borrow a target by its string identifier. Only one BackdoorTarget can exist at a time.
452    pub fn target_by_id_str(&mut self, id: &str) -> Result<Option<BackdoorTarget<'_>>> {
453        let encoded_id = BackdoorTargetInfo::id_from_str(id)?;
454
455        Ok(self.target_by_id(encoded_id))
456    }
457
458    /// Write a sequence of words at a given offset (word index) to a specified target's memory,
459    /// using the bkdr_loader's `AUTO_INCR` write mode.
460    ///
461    /// With `AUTO_INCR` set, writing the highest-indexed `WRITE_DATA` register needed for the
462    /// target's line width both commits a bkdr write at the current `INDEX` and advances `INDEX`
463    /// by one, so a full sequential range can be streamed without an `INDEX` write per word.
464    /// That top-word write must always happen (it's what fires the commit), but writes to any
465    /// lower-indexed `WRITE_DATA` registers can still be elided by the `write_all` parameter,
466    /// using shadow CSRs to determine when register contents have genuinely changed since the
467    /// previous word.
468    /// The `check_status` parameter is used to control whether the status bit is polled
469    /// after all words are written, to check for any errors; it also reads back `INDEX` to
470    /// verify the cursor advanced exactly once per word (i.e. no commit was lost).
471    pub fn write_target(
472        &mut self,
473        target_index: u8,
474        start: u32,
475        words: &[Word],
476        write_all: bool,
477        check_status: bool,
478    ) -> Result<()> {
479        ensure!(
480            usize::from(target_index) < self.targets.len(),
481            "Target index {} is out of range for {} targets",
482            target_index,
483            self.targets.len()
484        );
485        let info = self.targets[target_index as usize];
486        let width = info.width as usize;
487        let regs_used = width.div_ceil(u32::BITS as usize);
488        ensure!(
489            regs_used <= DATA_REGS_PER_WORD,
490            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
491            width,
492            regs_used,
493            DATA_REGS_PER_WORD
494        );
495
496        if words.is_empty() {
497            return Ok(());
498        }
499
500        // The top `WRITE_DATA` register (i.e. `bkdr_loader`'s `max_word_idx_tgt`) is the one
501        // whose write commits the line and advances `INDEX`; it must be written every time.
502        let top_reg_idx = regs_used - 1;
503
504        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
505        control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
506        control |= 0b1 << regs::CONTROL_AUTO_INCR_BIT;
507
508        // Cache previous written values in Shadow CSRs
509        let mut prev_regs = [0u32; DATA_REGS_PER_WORD];
510        let mut first = true;
511
512        // We batch together all the necessary writes - including the CONTROL setup and the
513        // single INDEX seed - so that we can perform a single batched write operation at the
514        // end, which is optimized for throughput. Batched writes execute strictly in order,
515        // so CONTROL (selecting the target and enabling AUTO_INCR) and the INDEX seed land
516        // before any data; with AUTO_INCR already set, the INDEX write cannot trigger a
517        // manual write. From the seed on, INDEX auto-increments in hardware.
518        let mut writes = vec![
519            ((regs::CONTROL_REG_OFFSET >> 2) as u32, control),
520            ((regs::INDEX_REG_OFFSET >> 2) as u32, start),
521        ];
522
523        for word in words {
524            let regs = word.to_u32_chunks()?;
525            for idx in 0..regs_used {
526                // Optimization - maintain shadow CSRs in software, and only write the
527                // data if there is a diff in that CSR from the previous contents. Vastly
528                // minimizes required operations for repetitive payloads. The top register
529                // is exempted since its write is what commits the line and advances INDEX.
530                if idx == top_reg_idx || write_all || first || regs[idx] != prev_regs[idx] {
531                    let addr_offset = idx * 4;
532                    writes.push((
533                        ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
534                        regs[idx],
535                    ));
536                    prev_regs[idx] = regs[idx];
537                }
538            }
539            first = false;
540        }
541
542        self.dmi
543            .batched_dmi_writes(&writes)
544            .context("failed to perform DMI writes")?;
545
546        if check_status {
547            // The auto-increment cursor must have advanced exactly once per word; a mismatch
548            // means a commit (top-word write) was lost somewhere in the stream, which would
549            // shift every subsequent word by one address.
550            let end_index = self
551                .dmi_read(regs::INDEX_REG_OFFSET)
552                .context("cannot read back index")?;
553            ensure!(
554                end_index == start + words.len() as u32,
555                "fpga bkdr_loader index is {:#x} after writing {:#x} words at {:#x} of target {} (expected {:#x})",
556                end_index,
557                words.len(),
558                start,
559                info.id_str(),
560                start + words.len() as u32
561            );
562
563            let status = self
564                .dmi_read(regs::STATUS_REG_OFFSET)
565                .context("cannot read status")?;
566            ensure!(
567                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
568                "fpga bkdr_loader reported an error writing to target {}",
569                info.id_str()
570            );
571        }
572
573        Ok(())
574    }
575
576    /// Write a single word at a given word index to a specified target's memory, using the
577    /// bkdr_loader's manual (non-`AUTO_INCR`) write mode: `WRITE_DATA` is loaded, then writing
578    /// `INDEX` itself commands the write to that exact address.
579    ///
580    /// Unlike [`Backdoor::write_target`], this does not move any auto-increment cursor and can
581    /// address any word directly, which is handy for one-off single-word pokes that don't want
582    /// to reason about a running `INDEX`. The `check_status` parameter is used to control whether
583    /// the status bit is polled afterwards, to check for any errors.
584    pub fn write_target_word(
585        &mut self,
586        target_index: u8,
587        index: u32,
588        word: &Word,
589        check_status: bool,
590    ) -> Result<()> {
591        ensure!(
592            usize::from(target_index) < self.targets.len(),
593            "Target index {} is out of range for {} targets",
594            target_index,
595            self.targets.len()
596        );
597        let info = self.targets[target_index as usize];
598        let width = info.width as usize;
599        let regs_used = width.div_ceil(u32::BITS as usize);
600        ensure!(
601            regs_used <= DATA_REGS_PER_WORD,
602            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
603            width,
604            regs_used,
605            DATA_REGS_PER_WORD
606        );
607
608        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
609        control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
610
611        // Batch everything into one round trip: CONTROL setup, the data registers, and the
612        // final INDEX write whose qe strobe (with AUTO_INCR clear) triggers the actual write.
613        let regs = word.to_u32_chunks()?;
614        let writes: Vec<(u32, u32)> =
615            std::iter::once(((regs::CONTROL_REG_OFFSET >> 2) as u32, control))
616                .chain(regs[..regs_used].iter().enumerate().map(|(idx, &reg)| {
617                    let addr_offset = idx * 4;
618                    (
619                        ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
620                        reg,
621                    )
622                }))
623                .chain(std::iter::once((
624                    (regs::INDEX_REG_OFFSET >> 2) as u32,
625                    index,
626                )))
627                .collect();
628
629        self.dmi
630            .batched_dmi_writes(&writes)
631            .context("failed to perform DMI writes")?;
632
633        if check_status {
634            let status = self
635                .dmi_read(regs::STATUS_REG_OFFSET)
636                .context("cannot read status")?;
637            ensure!(
638                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
639                "fpga bkdr_loader reported an error writing to target {}",
640                info.id_str()
641            );
642        }
643
644        Ok(())
645    }
646
647    /// Read a sequence of words at a given offset (word index) from a specified target's memory,
648    /// using the bkdr_loader's `AUTO_INCR` read mode.
649    ///
650    /// With `AUTO_INCR` set and `WRITE_ENA` clear, reading the highest-indexed `READ_DATA`
651    /// register needed for the target's line width advances `INDEX` by one (no bkdr write is
652    /// ever triggered on the read side), so a full sequential range can be streamed with a single
653    /// `INDEX` write up front rather than one per word. Because that top-word read is what
654    /// advances `INDEX`, each line's registers must be read in ascending order (topmost last),
655    /// reading it out of order would advance past data that hasn't been collected yet.
656    /// The `check_status` parameter is used to control whether the status bit is polled
657    /// after all words are read, to check for any errors; it also reads back `INDEX` to
658    /// verify the cursor advanced exactly once per word.
659    pub fn read_target(
660        &mut self,
661        target_index: u8,
662        start: u32,
663        count: u32,
664        check_status: bool,
665    ) -> Result<Vec<Word>> {
666        ensure!(
667            usize::from(target_index) < self.targets.len(),
668            "Target index {} is out of range for {} targets",
669            target_index,
670            self.targets.len()
671        );
672        let info = self.targets[target_index as usize];
673        let width = info.width as usize;
674        let bytes_per_word = width.div_ceil(u8::BITS as usize);
675        let regs_used = width.div_ceil(u32::BITS as usize);
676        ensure!(
677            regs_used <= DATA_REGS_PER_WORD,
678            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
679            width,
680            regs_used,
681            DATA_REGS_PER_WORD
682        );
683
684        if count == 0 {
685            return Ok(Vec::new());
686        }
687
688        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
689        control |= 0b1 << regs::CONTROL_AUTO_INCR_BIT;
690        // WRITE_ENA is left clear: with AUTO_INCR set, this selects the read-side trigger.
691        // Batch the CONTROL setup and the single INDEX seed into one round trip; the writes
692        // execute strictly in order, and with WRITE_ENA clear the INDEX write cannot trigger
693        // a write. From the seed on, INDEX auto-increments in hardware.
694        self.dmi
695            .batched_dmi_writes(&[
696                ((regs::CONTROL_REG_OFFSET >> 2) as u32, control),
697                ((regs::INDEX_REG_OFFSET >> 2) as u32, start),
698            ])
699            .context("cannot set up control and index registers")?;
700
701        // Reading the top register advances INDEX, so within each word the registers must be
702        // read in ascending order (topmost last). The flattened address sequence preserves
703        // that order, and batched reads keep operation order, across chunks too.
704        let addrs: Vec<u32> = (0..count)
705            .flat_map(|_| {
706                (0..regs_used).map(|idx| ((regs::READ_DATA_0_REG_OFFSET + idx * 4) >> 2) as u32)
707            })
708            .collect();
709        let values = self
710            .dmi
711            .batched_dmi_reads(&addrs)
712            .context("cannot read from read_data registers")?;
713
714        let words = values
715            .chunks_exact(regs_used)
716            .map(|chunk| {
717                let mut regs = [0u32; DATA_REGS_PER_WORD];
718                regs[..regs_used].copy_from_slice(chunk);
719                Word::from_u32_chunks(&regs, bytes_per_word)
720            })
721            .collect::<Vec<_>>();
722
723        if check_status {
724            // The auto-increment cursor must have advanced exactly once per word read; a
725            // mismatch means a top-word read strobe was lost or fired more than expected.
726            let end_index = self
727                .dmi_read(regs::INDEX_REG_OFFSET)
728                .context("cannot read back index")?;
729            ensure!(
730                end_index == start + count,
731                "fpga bkdr_loader index is {:#x} after reading {:#x} words at {:#x} of target {} (expected {:#x})",
732                end_index,
733                count,
734                start,
735                info.id_str(),
736                start + count
737            );
738
739            let status = self
740                .dmi_read(regs::STATUS_REG_OFFSET)
741                .context("cannot read status")?;
742            ensure!(
743                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
744                "fpga bkdr_loader reported an error reading from target {} starting at word {}",
745                info.id_str(),
746                start
747            );
748        }
749
750        Ok(words)
751    }
752
753    /// Read a single word at a given word index from a specified target's memory, using the
754    /// bkdr_loader's manual (non-`AUTO_INCR`) read mode: writing `INDEX` addresses the word, then
755    /// `READ_DATA` is read back.
756    ///
757    /// Unlike [`Backdoor::read_target`], this does not move any auto-increment cursor and can
758    /// address any word directly, which is handy for one-off single-word peeks. The
759    /// `check_status` parameter is used to control whether the status bit is polled afterwards,
760    /// to check for any errors.
761    pub fn read_target_word(
762        &mut self,
763        target_index: u8,
764        index: u32,
765        check_status: bool,
766    ) -> Result<Word> {
767        ensure!(
768            usize::from(target_index) < self.targets.len(),
769            "Target index {} is out of range for {} targets",
770            target_index,
771            self.targets.len()
772        );
773        let info = self.targets[target_index as usize];
774        let width = info.width as usize;
775        let bytes_per_word = width.div_ceil(u8::BITS as usize);
776        let regs_used = width.div_ceil(u32::BITS as usize);
777        ensure!(
778            regs_used <= DATA_REGS_PER_WORD,
779            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
780            width,
781            regs_used,
782            DATA_REGS_PER_WORD
783        );
784
785        // Batch the CONTROL setup (WRITE_ENA and AUTO_INCR both clear: manual read mode, no
786        // side effects on READ_DATA reads) and the INDEX write into one round trip.
787        let control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
788        self.dmi
789            .batched_dmi_writes(&[
790                ((regs::CONTROL_REG_OFFSET >> 2) as u32, control),
791                ((regs::INDEX_REG_OFFSET >> 2) as u32, index),
792            ])
793            .context("cannot set up control and index registers")?;
794
795        if check_status {
796            let status = self
797                .dmi_read(regs::STATUS_REG_OFFSET)
798                .context("cannot read status")?;
799            ensure!(
800                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
801                "fpga bkdr_loader reported an error reading from word idx {} of target {}",
802                index,
803                info.id_str()
804            );
805        }
806
807        let addrs: Vec<u32> = (0..regs_used)
808            .map(|idx| ((regs::READ_DATA_0_REG_OFFSET + idx * 4) >> 2) as u32)
809            .collect();
810        let values = self
811            .dmi
812            .batched_dmi_reads(&addrs)
813            .context("cannot read from read_data registers")?;
814
815        let mut regs = [0u32; DATA_REGS_PER_WORD];
816        regs[..regs_used].copy_from_slice(&values);
817
818        Ok(Word::from_u32_chunks(&regs, bytes_per_word))
819    }
820
821    /// Clear the entire memory of a specified target with a given word.
822    ///
823    /// An optimized fast-path for clearing memories, primarily used to replicate existing
824    /// bitstream synthesis defaults. The `check_status` parameter is used to control
825    /// whether the status bit is polled after clearing, to check for any errors.
826    pub fn clear_target(
827        &mut self,
828        target_index: u8,
829        word: &Word,
830        check_status: bool,
831    ) -> Result<()> {
832        ensure!(
833            usize::from(target_index) < self.targets.len(),
834            "Target index {} is out of range for {} targets",
835            target_index,
836            self.targets.len()
837        );
838        let info = self.targets[target_index as usize];
839
840        self.dmi
841            .batched_dmi_writes(
842                &word
843                    .to_u32_chunks()?
844                    .into_iter()
845                    .enumerate()
846                    .map(|(idx, reg)| {
847                        let addr_offset = idx * 4;
848                        (
849                            ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
850                            reg,
851                        )
852                    })
853                    .collect::<Vec<_>>(),
854            )
855            .context("failed to perform DMI writes")?;
856
857        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
858        control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
859        control |= 0b1 << regs::CONTROL_CLEAR_START_BIT;
860        self.dmi_write(regs::CONTROL_REG_OFFSET, control)
861            .context("cannot write to control register")?;
862
863        // Wait for the `CLEAR_IDLE` bit to appear set in the status register.
864        let timeout = Instant::now() + Duration::from_secs(CLEAR_TIMEOUT_SECS);
865        let mut status: u32;
866        loop {
867            status = self
868                .dmi_read(regs::STATUS_REG_OFFSET)
869                .context("cannot read status")?;
870            if status & (0b1 << regs::STATUS_CLEAR_IDLE_BIT) != 0 {
871                break;
872            }
873
874            if Instant::now() > timeout {
875                bail!(
876                    "Timed out after {} seconds waiting for {} clear to complete",
877                    CLEAR_TIMEOUT_SECS,
878                    info.id_str()
879                );
880            }
881        }
882
883        if check_status {
884            ensure!(
885                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
886                "fpga bkdr_loader reported an error writing to target {}",
887                info.id_str()
888            );
889        }
890
891        Ok(())
892    }
893
894    /// Read a specified target's `HASH_LAST_LOADED` register.
895    ///
896    /// This is a plain `rw` register with no side effects of its own: hardware only stores
897    /// whatever value software last wrote to it, and never clears it on the button/`rst_ni`
898    /// reset used to re-enter the backdoor loader. It exists so a caller can stash a hash
899    /// of a target's memory content across preloads, and skip re-writing that content if
900    /// the hash of what it's about to write hasn't changed.
901    pub fn read_target_hash(&mut self, target_index: u8) -> Result<u32> {
902        ensure!(
903            usize::from(target_index) < self.targets.len(),
904            "Target index {} is out of range for {} targets",
905            target_index,
906            self.targets.len()
907        );
908        self.dmi_read(regs::HASH_LAST_LOADED_0_REG_OFFSET + (target_index as usize) * 4)
909            .context("cannot read target hash register")
910    }
911
912    /// Write a specified target's `HASH_LAST_LOADED` register. See [`Backdoor::read_target_hash`].
913    pub fn write_target_hash(&mut self, target_index: u8, hash: u32) -> Result<()> {
914        ensure!(
915            usize::from(target_index) < self.targets.len(),
916            "Target index {} is out of range for {} targets",
917            target_index,
918            self.targets.len()
919        );
920        self.dmi_write(
921            regs::HASH_LAST_LOADED_0_REG_OFFSET + (target_index as usize) * 4,
922            hash,
923        )
924        .context("cannot write target hash register")
925    }
926
927    /// Read the FPGA's `USR_ACCESS_TIMESTAMP` register: the same value embedded in the
928    /// bitstream's `USR_ACCESS` primitive at build time (see `util::usr_access::usr_access_get`).
929    /// Unlike that value, this one is read directly from the FPGA fabric's configuration over
930    /// the backdoor TAP, so it identifies the bitstream currently loaded.
931    pub fn read_usr_access_timestamp(&mut self) -> Result<u32> {
932        self.dmi_read(regs::USR_ACCESS_TIMESTAMP_REG_OFFSET)
933            .context("cannot read USR_ACCESS_TIMESTAMP register")
934    }
935}
936
937#[cfg(test)]
938mod tests {
939    use super::*;
940
941    #[test]
942    fn identifer_str_encoding() {
943        let (width, depth) = (1, 1);
944        for (id, id_str) in [
945            (0x4f545020, "OTP"),
946            (0x5352414d, "SRAM"),
947            (0x46493031, "FI01"),
948        ] {
949            assert_eq!(BackdoorTargetInfo { id, width, depth }.id_str(), id_str);
950            assert_eq!(BackdoorTargetInfo::id_from_str(id_str).unwrap(), id);
951        }
952    }
953
954    #[test]
955    fn byte_u32_conversion() {
956        // Bytes stored in words are Big Endian (MSB first).
957        let word = Word::new(vec![
958            0x5a, 0xa5, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xbe, 0xef, 0xca, 0xfe,
959        ]);
960        // Register chunks are Little Endian, but bytes in each u32 are Big Endian.
961        // The 4th register should be half-used, the 4 remaining regs should be unused.
962        let mut expected = [0x0; DATA_REGS_PER_WORD];
963        expected[0] = 0xbeefcafe;
964        expected[1] = 0x89abcdef;
965        expected[2] = 0x01234567;
966        expected[3] = 0x00005aa5;
967
968        let chunks = word.to_u32_chunks().unwrap();
969        assert_eq!(chunks, expected);
970        assert_eq!(Word::from_u32_chunks(&chunks, word.bytes.len()), word);
971    }
972}