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