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