opentitanlib/test_utils/
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};
6use clap::Args;
7use std::convert::From;
8use std::fs;
9use std::path::PathBuf;
10use std::str::FromStr;
11
12use crate::app::TransportWrapper;
13use crate::io::fpga_backdoor::{
14    Backdoor, BackdoorParams, BackdoorTarget, BackdoorTargetInfo, enter_backdoor_loader,
15};
16use crate::io::jtag::JtagParams;
17use crate::util::vmem::{Section, Vmem, Word};
18
19// Reset the SoC and enter the backdoor loader, initializing a connection via JTAG.
20pub fn enter_backdoor(transport: &TransportWrapper, jtag_params: &JtagParams) -> Result<Backdoor> {
21    enter_backdoor_loader(transport)?;
22    let backdoor_params = BackdoorParams {
23        jtag: jtag_params.clone(),
24    };
25    backdoor_params.create(transport)?.connect(true)
26}
27
28// Normalize a word to a given number of bits.
29fn normalize_word(word: &mut Word, bits_per_word: usize) {
30    let bytes_per_word = bits_per_word.div_ceil(8);
31    if word.bytes.len() > bytes_per_word {
32        let start = word.bytes.len() - bytes_per_word;
33        word.bytes.drain(..start);
34    } else if word.bytes.len() < bytes_per_word {
35        let mut padded = vec![0u8; bytes_per_word - word.bytes.len()];
36        padded.append(&mut word.bytes);
37        word.bytes = padded;
38    }
39
40    let extra_bits = bits_per_word % 8;
41    if extra_bits > 0 {
42        word.bytes[0] &= 0xFF >> (8 - extra_bits);
43    }
44}
45
46// Check that words read from target memory match input words.
47pub fn verify_readback(
48    input: &mut [Word],
49    readback: &mut [Word],
50    bits_per_word: usize,
51    mut offset: u32,
52) -> Result<()> {
53    for (write_word, read_word) in readback.iter_mut().zip(input) {
54        normalize_word(write_word, bits_per_word);
55        normalize_word(read_word, bits_per_word);
56        if write_word != read_word {
57            bail!(
58                "Read verification at word {} failed. Expected: {}, Got: {}",
59                offset,
60                hex::encode(write_word.bytes.clone()),
61                hex::encode(read_word.bytes.clone()),
62            );
63        }
64
65        offset += 1;
66    }
67
68    Ok(())
69}
70
71pub fn write_to_target(
72    target: &mut BackdoorTarget,
73    target_id: &str,
74    data: Vec<Section>,
75    verify: bool,
76) -> Result<()> {
77    // Perform the write(s)
78    log::info!("Writing to the {}...", target_id);
79    for mut section in data {
80        log::debug!(
81            "Writing section of size {} to word {} of target {}",
82            section.data.len(),
83            section.addr,
84            target_id
85        );
86
87        // Special case - if we're just trying to clear the entire target memory,
88        // instead use the fast clearing operation.
89        let first_word = &section.data.first().clone();
90        if section.addr == 0
91            && section.data.len() == target.info.depth as usize
92            && section.data.iter().all(|w| w == first_word.unwrap())
93        {
94            log::debug!(
95                "Clearing target {} with word {}",
96                target_id,
97                hex::encode(first_word.unwrap().bytes.clone())
98            );
99            target.clear(first_word.unwrap(), verify)?;
100        } else {
101            target.write(section.addr, &section.data, false, verify)?;
102        }
103
104        // If requested, read back the data and verify against written contents
105        if verify {
106            let mut readback = target.read(section.addr, section.data.len() as u32, false)?;
107            verify_readback(
108                &mut section.data,
109                &mut readback,
110                target.info.width as usize,
111                section.addr,
112            )?;
113        }
114    }
115
116    Ok(())
117}
118
119#[derive(Debug, Clone)]
120pub struct TargetWrite {
121    pub target: String,
122    pub path: PathBuf,
123    pub offset: Option<u32>,
124}
125
126impl FromStr for TargetWrite {
127    type Err = anyhow::Error;
128
129    fn from_str(s: &str) -> Result<Self, Self::Err> {
130        let (target_path, offset) = s
131            .split_once('@')
132            .map(|(x, y)| (x, y.parse::<u32>().ok()))
133            .unwrap_or((s, None));
134
135        let (target, path) = target_path
136            .split_once('=')
137            .context("expected input like TARGET=FILE[@OFFSET], but no '=' was seen")?;
138        if target.is_empty() {
139            bail!("target name cannot be empty");
140        }
141        if path.is_empty() {
142            bail!("file path cannot be empty");
143        }
144
145        Ok(TargetWrite {
146            target: target.to_string(),
147            path: PathBuf::from(path),
148            offset,
149        })
150    }
151}
152
153impl TargetWrite {
154    pub fn load_data(&self) -> Result<Vec<Section>> {
155        log::info!("Loading VMEM file: {}", self.path.display());
156        let vmem_content = fs::read_to_string(&self.path)?;
157        let mut vmem = Vmem::from_str(&vmem_content, None)?;
158        vmem.merge_sections(None);
159        let mut sections: Vec<Section> = vmem.sections().cloned().collect();
160
161        // If an offset is given, all sections must be offset by that amount.
162        if let Some(offset) = self.offset {
163            for section in &mut sections {
164                section.addr += offset;
165            }
166        }
167
168        Ok(sections)
169    }
170
171    pub fn backdoor_write(&self, backdoor: &mut Backdoor, verify: bool) -> Result<()> {
172        let mut target = backdoor
173            .target_by_id_str(&self.target)?
174            .context(format!("FPGA target '{}' not found", &self.target))?;
175
176        let data = self.load_data()?;
177        write_to_target(&mut target, &self.target, data, verify)
178    }
179}
180
181#[derive(Debug, Clone)]
182pub struct TargetClear {
183    pub target: String,
184    pub num_words: Option<u32>,
185    pub offset: Option<u32>,
186}
187
188impl FromStr for TargetClear {
189    type Err = anyhow::Error;
190
191    fn from_str(s: &str) -> Result<Self, Self::Err> {
192        let (target_words, offset) = s
193            .split_once('@')
194            .map(|(x, y)| (x, y.parse::<u32>().ok()))
195            .unwrap_or((s, None));
196
197        let (target, num_words) = target_words
198            .split_once('=')
199            .context("expected input like TARGET=NUM_WORDS[@OFFSET], but no '=' was seen")?;
200        if target.is_empty() {
201            bail!("target name cannot be empty");
202        }
203        if num_words.is_empty() {
204            bail!("num_words cannot be empty");
205        }
206        let num_words = if num_words.to_lowercase() == "all" {
207            None
208        } else {
209            Some(num_words.parse::<u32>()?)
210        };
211
212        Ok(TargetClear {
213            target: target.to_string(),
214            num_words,
215            offset,
216        })
217    }
218}
219
220impl TargetClear {
221    pub fn as_sections(&self, target_info: &BackdoorTargetInfo) -> Vec<Section> {
222        let num_bytes = target_info.width.div_ceil(8) as usize;
223        let remaining_words = target_info.depth - self.offset.unwrap_or(0);
224        let num_words = self.num_words.unwrap_or(remaining_words);
225        let data = vec![Word::new(vec![0x00; num_bytes]); num_words as usize];
226        vec![Section {
227            addr: self.offset.unwrap_or(0),
228            data,
229        }]
230    }
231
232    pub fn backdoor_write(&self, backdoor: &mut Backdoor, verify: bool) -> Result<()> {
233        let mut target = backdoor
234            .target_by_id_str(&self.target)?
235            .context(format!("FPGA target '{}' not found", &self.target))?;
236
237        let data = self.as_sections(&target.info);
238        write_to_target(&mut target, &self.target, data, verify)
239    }
240}
241
242// Write FPGA memories after loading the bitstream
243#[derive(Debug, Args)]
244pub struct LoadMemories {
245    /// Memories to be cleared / zeroed. All clears apply before writes.
246    #[arg(long = "clear-memory", value_name = "TARGET=NUM_WORDS[@OFFSET]")]
247    pub target_clears: Vec<TargetClear>,
248
249    /// Memories to be written, mapping VMEM files to FPGA target memories.
250    #[arg(long = "load-memory", value_name = "TARGET=FILE[@OFFSET]")]
251    pub target_writes: Vec<TargetWrite>,
252}
253
254impl LoadMemories {
255    pub fn init(&self, transport: &TransportWrapper, jtag_params: &JtagParams) -> Result<()> {
256        if self.target_clears.is_empty() && self.target_writes.is_empty() {
257            return Ok(());
258        }
259
260        let mut backdoor = enter_backdoor(transport, jtag_params)?;
261        self.target_clears
262            .iter()
263            .try_for_each(|t| t.backdoor_write(&mut backdoor, false))?;
264        self.target_writes
265            .iter()
266            .try_for_each(|t| t.backdoor_write(&mut backdoor, false))?;
267        backdoor.set_done()?;
268
269        Ok(())
270    }
271}