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    // FIXME: This should be refactored so that the ot_transport JSON5 file declares clock
66    // frequencies for each device which we can then query through the transport. This
67    // is hardcoded for now for convenience.
68    pub const CW340_MAIN_CLOCK_FREQ_HZ: u64 = 24 * 1000 * 1000; // 24 MHz
69
70    // Parameters - see hw/ip/bkdr_loader/doc/interfaces.md
71    pub const DATA_REGS_PER_WORD: usize = 8; // MaxWordWidthDiv32
72}
73
74use consts::*;
75
76/// Apply the bkdr_loader TAP strapping and reset to enter the backdoor loader.
77pub fn enter_backdoor_loader(transport: &TransportWrapper) -> Result<()> {
78    transport.capabilities()?.request(Capability::GPIO).ok()?;
79    let pinmux_tap_backdoor = transport.pin_strapping("PINMUX_TAP_FPGA_BACKDOOR")?;
80    let reset = transport.pin_strapping("RESET")?;
81    // Hold the JTAG TAP in reset for as long as the main reset is asserted. This works around
82    // suspected corruption of the `dmi_jtag` clock domain crossing in `bkdr_loader`.
83    // See lowrisc/opentitan#30922 and lowrisc/opentitan#29555.
84    let trst = transport.optional_pin_strapping("TRST")?;
85
86    log::info!(
87        "Resetting with PINMUX_TAP_FPGA_BACKDOOR (== DFT) strapping applied to enter the backdoor loader"
88    );
89    pinmux_tap_backdoor.apply()?;
90    if let Some(trst) = &trst {
91        log::info!("Asserting TRST strapping");
92        trst.apply()?;
93    }
94    reset.apply()?;
95    std::thread::sleep(Duration::from_millis(RESET_PULSE_MS));
96    // Release in reverse order of assertion, so that the TCK side of the CDC never leaves reset
97    // while the `clk_i` side is still held.
98    reset.remove()?;
99    if let Some(trst) = &trst {
100        log::info!("Deasserting TRST strapping");
101        trst.remove()?;
102    }
103    std::thread::sleep(Duration::from_millis(HOLD_TAP_STRAPS_MS));
104    pinmux_tap_backdoor.remove()?;
105    log::info!("Reset complete, backdoor TAP strapping released");
106    Ok(())
107}
108
109/// A struct which represents a backdoor loader interface.
110///
111/// This struct represents an adaptor that has been configured to connect to a given JTAG chain,
112/// but has not yet been configured to access the backdoor TAP.
113pub struct BackdoorTap<'a> {
114    jtag: Box<dyn JtagChain + 'a>,
115    jtag_speed_khz: u64,
116}
117
118impl BackdoorTap<'_> {
119    /// Connect to the backdoor TAP, optionally enumerate information about all targets.
120    pub fn connect(self, enumerate: bool) -> Result<Backdoor> {
121        let openocd = self.jtag.connect(JtagTap::BackdoorTap)?.into_raw()?;
122        Backdoor::new(
123            OpenOcdDmi::new(openocd, "fpga_backdoor.tap")?,
124            self.jtag_speed_khz,
125            enumerate,
126        )
127    }
128}
129
130#[derive(Debug, Args, Clone)]
131pub struct BackdoorParams {
132    /// JTAG options to apply to the backdoor TAP.
133    #[command(flatten)]
134    pub jtag: JtagParams,
135}
136
137impl BackdoorParams {
138    pub fn create<'a>(&self, transport: &'a TransportWrapper) -> Result<BackdoorTap<'a>> {
139        Ok(BackdoorTap {
140            jtag: self.jtag.create(transport)?,
141            jtag_speed_khz: self.jtag.adapter_speed_khz,
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    targets: Vec<BackdoorTargetInfo>,
343}
344
345impl Backdoor {
346    /// Construct a [`Backdoor`] from a DMI connection to the backdoor TAP. Optionally
347    /// enumerate and discover information about all available targets.
348    pub fn new(dmi: OpenOcdDmi, jtag_speed_khz: u64, enumerate: bool) -> Result<Self> {
349        let mut fpga_backdoor = Self {
350            dmi,
351            jtag_speed_khz,
352            targets: Vec::new(),
353        };
354        if enumerate {
355            fpga_backdoor.enumerate()?;
356        }
357
358        Ok(fpga_backdoor)
359    }
360
361    /// Read from a DMI register with the given byte address offset.
362    /// DMI is a register interface; we must map the byte offsets to register (word) index.
363    fn dmi_read(&mut self, byte_addr: usize) -> Result<u32> {
364        self.dmi.dmi_read(byte_addr as u32 >> 2)
365    }
366
367    /// Write a value to a DMI register with the given byte address offset.
368    /// DMI is a register interface; we must map the byte offsets to register (word) index.
369    fn dmi_write(&mut self, byte_addr: usize, data: u32) -> Result<()> {
370        self.dmi.dmi_write(byte_addr as u32 >> 2, data)
371    }
372
373    // Enumerate the backdoor loader and retrieve information about available targets.
374    pub fn enumerate(&mut self) -> Result<()> {
375        self.targets.clear();
376
377        let num_targets = self
378            .dmi_read(regs::NUM_BKDR_TARGETS_REG_OFFSET)
379            .context("cannot read number of targets")? as usize;
380        log::info!("Number of FPGA bkdr_loader targets: {num_targets:?}");
381        for idx in 0..num_targets {
382            let addr_offset = idx * 4;
383            let target_info = BackdoorTargetInfo {
384                id: self
385                    .dmi_read(regs::TARGET_INFO_0_REG_OFFSET + addr_offset)
386                    .context("cannot read target info")?,
387                width: self
388                    .dmi_read(regs::WIDTH_INFO_0_REG_OFFSET + addr_offset)
389                    .context("cannot read width info")?,
390                depth: self
391                    .dmi_read(regs::DEPTH_INFO_0_REG_OFFSET + addr_offset)
392                    .context("cannot read depth info")?,
393            };
394            self.targets.push(target_info);
395        }
396
397        Ok(())
398    }
399
400    /// Communicate with the backdoor loader that we are finished using it.
401    ///
402    /// This transitions the bkdr_loader from it from its "Preload" state to "Mission mode",
403    /// causing it to re-route incoming JTAG back to the regular downstream interface.
404    pub fn set_done(mut self) -> Result<()> {
405        log::debug!("Finished using backdoor loader until next reset");
406
407        // We don't want the bkdr_loader to re-route JTAG mid-transaction, since that will
408        // cause us to see an unexpected response, as we will then be talking to an entirely
409        // different DMI / DTM (which can also put the RV_DM into a bad state). It will also
410        // potentially put the RV_dM debug infrastructure into a bad state. Configure the
411        // bkdr_loader to wait long enough so that we can finish our JTAG transaction.
412        // FIXME: These calculations are specific to the CW340.
413        let jtag_freq_hz: u64 = self.jtag_speed_khz * 1000;
414        let soc_clk_wait_cycles =
415            CW340_MAIN_CLOCK_FREQ_HZ.div_ceil(jtag_freq_hz) * JTAG_DONE_CYCLES;
416        let soc_clk_wait_cycles: u32 = soc_clk_wait_cycles.try_into().unwrap_or_else(|_| {
417            log::warn!(
418                "Configured JTAG speed ({} kHz) may overflow bkdr_loader wait time.",
419                self.jtag_speed_khz
420            );
421            log::warn!("Configuring maximum wait time.");
422            u32::MAX
423        });
424        self.dmi_write(
425            regs::MISSION_MODE_SWITCH_DELAY_REG_OFFSET,
426            soc_clk_wait_cycles,
427        )
428        .context("cannot write FPGA bkdr_loader mission_mode_switch_delay register")?;
429
430        if let Err(e) = self
431            .dmi_write(regs::CONTROL_REG_OFFSET, 0b1 << regs::CONTROL_DONE_BIT)
432            .context("cannot write done to FPGA bkdr_loader control reg")
433        {
434            log::error!("Error received when writing to `CONTROL.DONE`: {:?}", e);
435            log::error!("Trying to continue anyway...");
436        }
437
438        // Explicitly shut down all JTAG state before the transition happens, to avoid
439        // putting the next TAP(s) into some bad state across the transition.
440        drop(self);
441
442        // Wait until the transition to mission mode is complete and the system exits reset
443        // before continuing. For most sensible JTAG speeds this should be basically instant;
444        // for very slow speeds (e.g. <= 50 kHz) we need to add some special casing.
445        let done_wait_millis = (JTAG_DONE_CYCLES * 1000).div_ceil(jtag_freq_hz);
446        std::thread::sleep(Duration::from_millis(done_wait_millis));
447
448        Ok(())
449    }
450
451    /// Retrieve information about all of the targets available via the backdoor interface.
452    pub fn targets(&self) -> &[BackdoorTargetInfo] {
453        &self.targets
454    }
455
456    /// Borrow a target by its integer identifier. Only one BackdoorTarget can exist at a time.
457    pub fn target_by_id(&mut self, id: u32) -> Option<BackdoorTarget<'_>> {
458        let (index, info) = self.targets.iter().enumerate().find(|&(_, t)| t.id == id)?;
459        let (index, info) = (index as u8, *info);
460
461        Some(BackdoorTarget {
462            backdoor: self,
463            index,
464            info,
465        })
466    }
467
468    /// Borrow a target by its string identifier. Only one BackdoorTarget can exist at a time.
469    pub fn target_by_id_str(&mut self, id: &str) -> Result<Option<BackdoorTarget<'_>>> {
470        let encoded_id = BackdoorTargetInfo::id_from_str(id)?;
471
472        Ok(self.target_by_id(encoded_id))
473    }
474
475    /// Write a sequence of words at a given offset (word index) to a specified target's memory,
476    /// using the bkdr_loader's `AUTO_INCR` write mode.
477    ///
478    /// With `AUTO_INCR` set, writing the highest-indexed `WRITE_DATA` register needed for the
479    /// target's line width both commits a bkdr write at the current `INDEX` and advances `INDEX`
480    /// by one, so a full sequential range can be streamed without an `INDEX` write per word.
481    /// That top-word write must always happen (it's what fires the commit), but writes to any
482    /// lower-indexed `WRITE_DATA` registers can still be elided by the `write_all` parameter,
483    /// using shadow CSRs to determine when register contents have genuinely changed since the
484    /// previous word.
485    /// The `check_status` parameter is used to control whether the status bit is polled
486    /// after all words are written, to check for any errors; it also reads back `INDEX` to
487    /// verify the cursor advanced exactly once per word (i.e. no commit was lost).
488    pub fn write_target(
489        &mut self,
490        target_index: u8,
491        start: u32,
492        words: &[Word],
493        write_all: bool,
494        check_status: bool,
495    ) -> Result<()> {
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 regs_used = width.div_ceil(u32::BITS as usize);
505        ensure!(
506            regs_used <= DATA_REGS_PER_WORD,
507            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
508            width,
509            regs_used,
510            DATA_REGS_PER_WORD
511        );
512
513        if words.is_empty() {
514            return Ok(());
515        }
516
517        // The top `WRITE_DATA` register (i.e. `bkdr_loader`'s `max_word_idx_tgt`) is the one
518        // whose write commits the line and advances `INDEX`; it must be written every time.
519        let top_reg_idx = regs_used - 1;
520
521        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
522        control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
523        control |= 0b1 << regs::CONTROL_AUTO_INCR_BIT;
524
525        // Cache previous written values in Shadow CSRs
526        let mut prev_regs = [0u32; DATA_REGS_PER_WORD];
527        let mut first = true;
528
529        // We batch together all the necessary writes - including the CONTROL setup and the
530        // single INDEX seed - so that we can perform a single batched write operation at the
531        // end, which is optimized for throughput. Batched writes execute strictly in order,
532        // so CONTROL (selecting the target and enabling AUTO_INCR) and the INDEX seed land
533        // before any data; with AUTO_INCR already set, the INDEX write cannot trigger a
534        // manual write. From the seed on, INDEX auto-increments in hardware.
535        let mut writes = vec![
536            ((regs::CONTROL_REG_OFFSET >> 2) as u32, control),
537            ((regs::INDEX_REG_OFFSET >> 2) as u32, start),
538        ];
539
540        for word in words {
541            let regs = word.to_u32_chunks()?;
542            for idx in 0..regs_used {
543                // Optimization - maintain shadow CSRs in software, and only write the
544                // data if there is a diff in that CSR from the previous contents. Vastly
545                // minimizes required operations for repetitive payloads. The top register
546                // is exempted since its write is what commits the line and advances INDEX.
547                if idx == top_reg_idx || write_all || first || regs[idx] != prev_regs[idx] {
548                    let addr_offset = idx * 4;
549                    writes.push((
550                        ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
551                        regs[idx],
552                    ));
553                    prev_regs[idx] = regs[idx];
554                }
555            }
556            first = false;
557        }
558
559        self.dmi
560            .batched_dmi_writes(&writes)
561            .context("failed to perform DMI writes")?;
562
563        if check_status {
564            // The auto-increment cursor must have advanced exactly once per word; a mismatch
565            // means a commit (top-word write) was lost somewhere in the stream, which would
566            // shift every subsequent word by one address.
567            let end_index = self
568                .dmi_read(regs::INDEX_REG_OFFSET)
569                .context("cannot read back index")?;
570            ensure!(
571                end_index == start + words.len() as u32,
572                "fpga bkdr_loader index is {:#x} after writing {:#x} words at {:#x} of target {} (expected {:#x})",
573                end_index,
574                words.len(),
575                start,
576                info.id_str(),
577                start + words.len() as u32
578            );
579
580            let status = self
581                .dmi_read(regs::STATUS_REG_OFFSET)
582                .context("cannot read status")?;
583            ensure!(
584                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
585                "fpga bkdr_loader reported an error writing to target {}",
586                info.id_str()
587            );
588        }
589
590        Ok(())
591    }
592
593    /// Write a single word at a given word index to a specified target's memory, using the
594    /// bkdr_loader's manual (non-`AUTO_INCR`) write mode: `WRITE_DATA` is loaded, then writing
595    /// `INDEX` itself commands the write to that exact address.
596    ///
597    /// Unlike [`Backdoor::write_target`], this does not move any auto-increment cursor and can
598    /// address any word directly, which is handy for one-off single-word pokes that don't want
599    /// to reason about a running `INDEX`. The `check_status` parameter is used to control whether
600    /// the status bit is polled afterwards, to check for any errors.
601    pub fn write_target_word(
602        &mut self,
603        target_index: u8,
604        index: u32,
605        word: &Word,
606        check_status: bool,
607    ) -> Result<()> {
608        ensure!(
609            usize::from(target_index) < self.targets.len(),
610            "Target index {} is out of range for {} targets",
611            target_index,
612            self.targets.len()
613        );
614        let info = self.targets[target_index as usize];
615        let width = info.width as usize;
616        let regs_used = width.div_ceil(u32::BITS as usize);
617        ensure!(
618            regs_used <= DATA_REGS_PER_WORD,
619            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
620            width,
621            regs_used,
622            DATA_REGS_PER_WORD
623        );
624
625        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
626        control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
627
628        // Batch everything into one round trip: CONTROL setup, the data registers, and the
629        // final INDEX write whose qe strobe (with AUTO_INCR clear) triggers the actual write.
630        let regs = word.to_u32_chunks()?;
631        let writes: Vec<(u32, u32)> =
632            std::iter::once(((regs::CONTROL_REG_OFFSET >> 2) as u32, control))
633                .chain(regs[..regs_used].iter().enumerate().map(|(idx, &reg)| {
634                    let addr_offset = idx * 4;
635                    (
636                        ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
637                        reg,
638                    )
639                }))
640                .chain(std::iter::once((
641                    (regs::INDEX_REG_OFFSET >> 2) as u32,
642                    index,
643                )))
644                .collect();
645
646        self.dmi
647            .batched_dmi_writes(&writes)
648            .context("failed to perform DMI writes")?;
649
650        if check_status {
651            let status = self
652                .dmi_read(regs::STATUS_REG_OFFSET)
653                .context("cannot read status")?;
654            ensure!(
655                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
656                "fpga bkdr_loader reported an error writing to target {}",
657                info.id_str()
658            );
659        }
660
661        Ok(())
662    }
663
664    /// Read a sequence of words at a given offset (word index) from a specified target's memory,
665    /// using the bkdr_loader's `AUTO_INCR` read mode.
666    ///
667    /// With `AUTO_INCR` set and `WRITE_ENA` clear, reading the highest-indexed `READ_DATA`
668    /// register needed for the target's line width advances `INDEX` by one (no bkdr write is
669    /// ever triggered on the read side), so a full sequential range can be streamed with a single
670    /// `INDEX` write up front rather than one per word. Because that top-word read is what
671    /// advances `INDEX`, each line's registers must be read in ascending order (topmost last),
672    /// reading it out of order would advance past data that hasn't been collected yet.
673    /// The `check_status` parameter is used to control whether the status bit is polled
674    /// after all words are read, to check for any errors; it also reads back `INDEX` to
675    /// verify the cursor advanced exactly once per word.
676    pub fn read_target(
677        &mut self,
678        target_index: u8,
679        start: u32,
680        count: u32,
681        check_status: bool,
682    ) -> Result<Vec<Word>> {
683        ensure!(
684            usize::from(target_index) < self.targets.len(),
685            "Target index {} is out of range for {} targets",
686            target_index,
687            self.targets.len()
688        );
689        let info = self.targets[target_index as usize];
690        let width = info.width as usize;
691        let bytes_per_word = width.div_ceil(u8::BITS as usize);
692        let regs_used = width.div_ceil(u32::BITS as usize);
693        ensure!(
694            regs_used <= DATA_REGS_PER_WORD,
695            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
696            width,
697            regs_used,
698            DATA_REGS_PER_WORD
699        );
700
701        if count == 0 {
702            return Ok(Vec::new());
703        }
704
705        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
706        control |= 0b1 << regs::CONTROL_AUTO_INCR_BIT;
707        // WRITE_ENA is left clear: with AUTO_INCR set, this selects the read-side trigger.
708        // Batch the CONTROL setup and the single INDEX seed into one round trip; the writes
709        // execute strictly in order, and with WRITE_ENA clear the INDEX write cannot trigger
710        // a write. From the seed on, INDEX auto-increments in hardware.
711        self.dmi
712            .batched_dmi_writes(&[
713                ((regs::CONTROL_REG_OFFSET >> 2) as u32, control),
714                ((regs::INDEX_REG_OFFSET >> 2) as u32, start),
715            ])
716            .context("cannot set up control and index registers")?;
717
718        // Reading the top register advances INDEX, so within each word the registers must be
719        // read in ascending order (topmost last). The flattened address sequence preserves
720        // that order, and batched reads keep operation order, across chunks too.
721        let addrs: Vec<u32> = (0..count)
722            .flat_map(|_| {
723                (0..regs_used).map(|idx| ((regs::READ_DATA_0_REG_OFFSET + idx * 4) >> 2) as u32)
724            })
725            .collect();
726        let values = self
727            .dmi
728            .batched_dmi_reads(&addrs)
729            .context("cannot read from read_data registers")?;
730
731        let words = values
732            .chunks_exact(regs_used)
733            .map(|chunk| {
734                let mut regs = [0u32; DATA_REGS_PER_WORD];
735                regs[..regs_used].copy_from_slice(chunk);
736                Word::from_u32_chunks(&regs, bytes_per_word)
737            })
738            .collect::<Vec<_>>();
739
740        if check_status {
741            // The auto-increment cursor must have advanced exactly once per word read; a
742            // mismatch means a top-word read strobe was lost or fired more than expected.
743            let end_index = self
744                .dmi_read(regs::INDEX_REG_OFFSET)
745                .context("cannot read back index")?;
746            ensure!(
747                end_index == start + count,
748                "fpga bkdr_loader index is {:#x} after reading {:#x} words at {:#x} of target {} (expected {:#x})",
749                end_index,
750                count,
751                start,
752                info.id_str(),
753                start + count
754            );
755
756            let status = self
757                .dmi_read(regs::STATUS_REG_OFFSET)
758                .context("cannot read status")?;
759            ensure!(
760                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
761                "fpga bkdr_loader reported an error reading from target {} starting at word {}",
762                info.id_str(),
763                start
764            );
765        }
766
767        Ok(words)
768    }
769
770    /// Read a single word at a given word index from a specified target's memory, using the
771    /// bkdr_loader's manual (non-`AUTO_INCR`) read mode: writing `INDEX` addresses the word, then
772    /// `READ_DATA` is read back.
773    ///
774    /// Unlike [`Backdoor::read_target`], this does not move any auto-increment cursor and can
775    /// address any word directly, which is handy for one-off single-word peeks. The
776    /// `check_status` parameter is used to control whether the status bit is polled afterwards,
777    /// to check for any errors.
778    pub fn read_target_word(
779        &mut self,
780        target_index: u8,
781        index: u32,
782        check_status: bool,
783    ) -> Result<Word> {
784        ensure!(
785            usize::from(target_index) < self.targets.len(),
786            "Target index {} is out of range for {} targets",
787            target_index,
788            self.targets.len()
789        );
790        let info = self.targets[target_index as usize];
791        let width = info.width as usize;
792        let bytes_per_word = width.div_ceil(u8::BITS as usize);
793        let regs_used = width.div_ceil(u32::BITS as usize);
794        ensure!(
795            regs_used <= DATA_REGS_PER_WORD,
796            "Advertised target width {:#x} is too wide for the data registers (needs: {:#x}, has: {:#x})",
797            width,
798            regs_used,
799            DATA_REGS_PER_WORD
800        );
801
802        // Batch the CONTROL setup (WRITE_ENA and AUTO_INCR both clear: manual read mode, no
803        // side effects on READ_DATA reads) and the INDEX write into one round trip.
804        let control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
805        self.dmi
806            .batched_dmi_writes(&[
807                ((regs::CONTROL_REG_OFFSET >> 2) as u32, control),
808                ((regs::INDEX_REG_OFFSET >> 2) as u32, index),
809            ])
810            .context("cannot set up control and index registers")?;
811
812        if check_status {
813            let status = self
814                .dmi_read(regs::STATUS_REG_OFFSET)
815                .context("cannot read status")?;
816            ensure!(
817                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
818                "fpga bkdr_loader reported an error reading from word idx {} of target {}",
819                index,
820                info.id_str()
821            );
822        }
823
824        let addrs: Vec<u32> = (0..regs_used)
825            .map(|idx| ((regs::READ_DATA_0_REG_OFFSET + idx * 4) >> 2) as u32)
826            .collect();
827        let values = self
828            .dmi
829            .batched_dmi_reads(&addrs)
830            .context("cannot read from read_data registers")?;
831
832        let mut regs = [0u32; DATA_REGS_PER_WORD];
833        regs[..regs_used].copy_from_slice(&values);
834
835        Ok(Word::from_u32_chunks(&regs, bytes_per_word))
836    }
837
838    /// Clear the entire memory of a specified target with a given word.
839    ///
840    /// An optimized fast-path for clearing memories, primarily used to replicate existing
841    /// bitstream synthesis defaults. The `check_status` parameter is used to control
842    /// whether the status bit is polled after clearing, to check for any errors.
843    pub fn clear_target(
844        &mut self,
845        target_index: u8,
846        word: &Word,
847        check_status: bool,
848    ) -> Result<()> {
849        ensure!(
850            usize::from(target_index) < self.targets.len(),
851            "Target index {} is out of range for {} targets",
852            target_index,
853            self.targets.len()
854        );
855        let info = self.targets[target_index as usize];
856
857        self.dmi
858            .batched_dmi_writes(
859                &word
860                    .to_u32_chunks()?
861                    .into_iter()
862                    .enumerate()
863                    .map(|(idx, reg)| {
864                        let addr_offset = idx * 4;
865                        (
866                            ((regs::WRITE_DATA_0_REG_OFFSET + addr_offset) >> 2) as u32,
867                            reg,
868                        )
869                    })
870                    .collect::<Vec<_>>(),
871            )
872            .context("failed to perform DMI writes")?;
873
874        let mut control = (target_index as u32) << regs::CONTROL_TARGET_IDX_OFFSET;
875        control |= 0b1 << regs::CONTROL_WRITE_ENA_BIT;
876        control |= 0b1 << regs::CONTROL_CLEAR_START_BIT;
877        self.dmi_write(regs::CONTROL_REG_OFFSET, control)
878            .context("cannot write to control register")?;
879
880        // Wait for the `CLEAR_IDLE` bit to appear set in the status register.
881        let timeout = Instant::now() + Duration::from_secs(CLEAR_TIMEOUT_SECS);
882        let mut status: u32;
883        loop {
884            status = self
885                .dmi_read(regs::STATUS_REG_OFFSET)
886                .context("cannot read status")?;
887            if status & (0b1 << regs::STATUS_CLEAR_IDLE_BIT) != 0 {
888                break;
889            }
890
891            if Instant::now() > timeout {
892                bail!(
893                    "Timed out after {} seconds waiting for {} clear to complete",
894                    CLEAR_TIMEOUT_SECS,
895                    info.id_str()
896                );
897            }
898        }
899
900        if check_status {
901            ensure!(
902                status & (0b1 << regs::STATUS_ERROR_BIT) == 0,
903                "fpga bkdr_loader reported an error writing to target {}",
904                info.id_str()
905            );
906        }
907
908        Ok(())
909    }
910
911    /// Read a specified target's `HASH_LAST_LOADED` register.
912    ///
913    /// This is a plain `rw` register with no side effects of its own: hardware only stores
914    /// whatever value software last wrote to it, and never clears it on the button/`rst_ni`
915    /// reset used to re-enter the backdoor loader. It exists so a caller can stash a hash
916    /// of a target's memory content across preloads, and skip re-writing that content if
917    /// the hash of what it's about to write hasn't changed.
918    pub fn read_target_hash(&mut self, target_index: u8) -> Result<u32> {
919        ensure!(
920            usize::from(target_index) < self.targets.len(),
921            "Target index {} is out of range for {} targets",
922            target_index,
923            self.targets.len()
924        );
925        self.dmi_read(regs::HASH_LAST_LOADED_0_REG_OFFSET + (target_index as usize) * 4)
926            .context("cannot read target hash register")
927    }
928
929    /// Write a specified target's `HASH_LAST_LOADED` register. See [`Backdoor::read_target_hash`].
930    pub fn write_target_hash(&mut self, target_index: u8, hash: u32) -> Result<()> {
931        ensure!(
932            usize::from(target_index) < self.targets.len(),
933            "Target index {} is out of range for {} targets",
934            target_index,
935            self.targets.len()
936        );
937        self.dmi_write(
938            regs::HASH_LAST_LOADED_0_REG_OFFSET + (target_index as usize) * 4,
939            hash,
940        )
941        .context("cannot write target hash register")
942    }
943
944    /// Read the FPGA's `USR_ACCESS_TIMESTAMP` register: the same value embedded in the
945    /// bitstream's `USR_ACCESS` primitive at build time (see `util::usr_access::usr_access_get`).
946    /// Unlike that value, this one is read directly from the FPGA fabric's configuration over
947    /// the backdoor TAP, so it identifies the bitstream currently loaded.
948    pub fn read_usr_access_timestamp(&mut self) -> Result<u32> {
949        self.dmi_read(regs::USR_ACCESS_TIMESTAMP_REG_OFFSET)
950            .context("cannot read USR_ACCESS_TIMESTAMP register")
951    }
952}
953
954#[cfg(test)]
955mod tests {
956    use super::*;
957
958    #[test]
959    fn identifer_str_encoding() {
960        let (width, depth) = (1, 1);
961        for (id, id_str) in [
962            (0x4f545020, "OTP"),
963            (0x5352414d, "SRAM"),
964            (0x46493031, "FI01"),
965        ] {
966            assert_eq!(BackdoorTargetInfo { id, width, depth }.id_str(), id_str);
967            assert_eq!(BackdoorTargetInfo::id_from_str(id_str).unwrap(), id);
968        }
969    }
970
971    #[test]
972    fn byte_u32_conversion() {
973        // Bytes stored in words are Big Endian (MSB first).
974        let word = Word::new(vec![
975            0x5a, 0xa5, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xbe, 0xef, 0xca, 0xfe,
976        ]);
977        // Register chunks are Little Endian, but bytes in each u32 are Big Endian.
978        // The 4th register should be half-used, the 4 remaining regs should be unused.
979        let mut expected = [0x0; DATA_REGS_PER_WORD];
980        expected[0] = 0xbeefcafe;
981        expected[1] = 0x89abcdef;
982        expected[2] = 0x01234567;
983        expected[3] = 0x00005aa5;
984
985        let chunks = word.to_u32_chunks().unwrap();
986        assert_eq!(chunks, expected);
987        assert_eq!(Word::from_u32_chunks(&chunks, word.bytes.len()), word);
988    }
989}