opentitanlib/util/
usr_access.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::Result;
6use serde::{Deserialize, Serialize};
7use std::convert::TryInto;
8use thiserror::Error;
9
10use chrono::{Datelike, Timelike, Utc};
11use crc::Crc;
12
13use crate::util::bitfield::BitField;
14
15const SYNC_WORD: [u8; 4] = [0xAA, 0x99, 0x55, 0x66];
16const REG_ADDR_USR_ACCESS: u32 = 0x0d;
17const REG_ADDR_CRC: u32 = 0x00;
18const NOOP: [u8; 4] = [0x20, 0x00, 0x00, 0x00];
19const TYPE_FIELD: BitField = BitField {
20    offset: 29,
21    size: 3,
22};
23const OP_FIELD: BitField = BitField {
24    offset: 27,
25    size: 2,
26};
27const TYPE1_ADDR_FIELD: BitField = BitField {
28    offset: 13,
29    size: 14,
30};
31const TYPE1_WORDS_FIELD: BitField = BitField {
32    offset: 0,
33    size: 11,
34};
35const TYPE2_WORDS_FIELD: BitField = BitField {
36    offset: 0,
37    size: 27,
38};
39
40#[derive(Error, Debug, Serialize, Deserialize)]
41pub enum Error {
42    #[error("Command {0:#x?} not found in bitstream")]
43    CommandNotFound(u32),
44}
45
46#[derive(PartialEq)]
47enum BitstreamOp {
48    Nop = 0,
49    Read = 1,
50    Write = 2,
51    Reserved,
52}
53
54struct BitstreamTypeOnePacketHeader {
55    /// The 32-bit word making up the header of the Type 1 packet.
56    value: u32,
57    /// The byte offset in the bitstream where this word begins.
58    offset: usize,
59}
60
61impl BitstreamTypeOnePacketHeader {
62    fn op(&self) -> BitstreamOp {
63        match OP_FIELD.extract(self.value) {
64            0 => BitstreamOp::Nop,
65            1 => BitstreamOp::Read,
66            2 => BitstreamOp::Write,
67            _ => BitstreamOp::Reserved,
68        }
69    }
70
71    fn address(&self) -> u32 {
72        TYPE1_ADDR_FIELD.extract(self.value)
73    }
74
75    fn data_size(&self) -> usize {
76        usize::try_from(4 * TYPE1_WORDS_FIELD.extract(self.value)).unwrap()
77    }
78}
79
80/// Iterator struct that outputs a sequence of Type 1 packet headers contained in the bitstream.
81struct BitstreamTypeOneHeaders<'a> {
82    bitstream: &'a [u8],
83    offset: usize,
84    have_sync: bool,
85}
86
87impl<'a> BitstreamTypeOneHeaders<'a> {
88    fn from_bitstream(bitstream: &[u8]) -> BitstreamTypeOneHeaders<'_> {
89        BitstreamTypeOneHeaders {
90            bitstream,
91            offset: 0,
92            have_sync: false,
93        }
94    }
95
96    /// Finds the index in the `bitstream` slice that represents the beginning of the next sync
97    /// word. The search begins from the current offset, and if a sync word is found, returns with
98    /// the position of that sync word (indexed from the beginning of the bitstream).
99    fn find_next_sync(&self) -> Option<usize> {
100        self.bitstream[self.offset..]
101            .windows(4)
102            .position(|word| *word == SYNC_WORD)
103            .map(|offset| self.offset + offset)
104    }
105}
106
107impl<'a> std::iter::Iterator for BitstreamTypeOneHeaders<'a> {
108    type Item = BitstreamTypeOnePacketHeader;
109
110    /// Conditionally returns a BitstreamTypeOnePacketHeader representing the next Type 1 packet
111    /// header in the bitstream, starting from the last Type 1 packet header found. Does not wrap
112    /// around to the beginning of the bitstream again.
113    fn next(&mut self) -> Option<Self::Item> {
114        while self.offset + 4 < self.bitstream.len() {
115            if !self.have_sync {
116                if let Some(sync_offset) = self.find_next_sync() {
117                    self.offset = sync_offset + 4;
118                    self.have_sync = true;
119                } else {
120                    self.offset = self.bitstream.len();
121                    return None;
122                }
123            }
124            let header = &self.bitstream[self.offset..self.offset + 4];
125            let header = u32::from_be_bytes(header.try_into().ok()?);
126            let header = match TYPE_FIELD.extract(header) {
127                1 => {
128                    let x = BitstreamTypeOnePacketHeader {
129                        value: header,
130                        offset: self.offset,
131                    };
132                    self.offset += 4 + x.data_size();
133                    Some(x)
134                }
135                2 => {
136                    let data_bytes = 4 * TYPE2_WORDS_FIELD.extract(header);
137                    self.offset += usize::try_from(4 + data_bytes).unwrap();
138                    None
139                }
140                _ => {
141                    log::info!("Bitstream lost sync at {:#x}", self.offset);
142                    self.have_sync = false;
143                    None
144                }
145            };
146            if header.is_some() {
147                return header;
148            }
149        }
150        None
151    }
152}
153
154/// Assembles a Type 1 header word from the `op` and `address` fields, assuming a single word for
155/// the length of the value.
156fn cmd_from_parts(op: BitstreamOp, address: u32) -> u32 {
157    TYPE_FIELD.emplace(1)
158        | OP_FIELD.emplace(op as u32)
159        | TYPE1_ADDR_FIELD.emplace(address)
160        | TYPE1_WORDS_FIELD.emplace(1)
161}
162
163/// Searches for the following pattern in the bitstream
164///
165/// 0x30000001 (write the following word to the CRC register and check)
166/// 0xXXXXXXXX (value to write to the CRC register)
167/// 0x20000000 (NOOP)
168/// 0x20000000 (NOOP)
169///
170/// and replaces it with
171///
172/// 0x20000000 (NOOP)
173/// 0x20000000 (NOOP)
174/// 0x20000000 (NOOP)
175/// 0x20000000 (NOOP)
176///
177/// to remove the CRC check during FPGA configuration.
178fn remove_crc(bitstream: &mut [u8]) {
179    let crc_headers: Vec<BitstreamTypeOnePacketHeader> =
180        BitstreamTypeOneHeaders::from_bitstream(bitstream)
181            .filter(|x| (x.op() == BitstreamOp::Write) && (x.address() == REG_ADDR_CRC))
182            .collect();
183    for header in crc_headers.iter() {
184        log::info!(
185            "Replaced WRITE_CRC_REG command at {:#x} with NOOP",
186            header.offset
187        );
188        bitstream[header.offset..header.offset + 4].copy_from_slice(&NOOP);
189        bitstream[header.offset + 4..header.offset + 8].copy_from_slice(&NOOP);
190    }
191}
192
193pub fn usr_access_get(bitstream: &[u8]) -> Result<u32> {
194    if let Some(header) = BitstreamTypeOneHeaders::from_bitstream(bitstream)
195        .find(|x| (x.op() == BitstreamOp::Write) && (x.address() == REG_ADDR_USR_ACCESS))
196    {
197        let operand = &bitstream[header.offset + 4..header.offset + 8];
198        let usr_access = u32::from_be_bytes(operand.try_into()?);
199        log::info!("Bitstream file USR_ACCESS value: {:#x}", usr_access);
200        return Ok(usr_access);
201    }
202    Err(Error::CommandNotFound(cmd_from_parts(BitstreamOp::Write, REG_ADDR_USR_ACCESS)).into())
203}
204
205/// Returns a 32-bit timestamp suitable to be used as a USR_ACCESS value:
206///
207/// |-------+-------+-------+-------------------------+-------+--------+--------|
208/// | Bits: | 31:27 | 26:23 | 22:17                   | 16:12 | 11:6   | 5:0    |
209/// |-------+-------+-------+-------------------------+-------+--------+--------|
210/// | Data: | Day   | Month | Year (since 2000, 0-63) | Hour  | Minute | Second |
211/// |-------+-------+-------+-------------------------+-------+--------+--------|
212///
213/// From
214/// <https://www.xilinx.com/content/dam/xilinx/support/documents/application_notes/xapp1232-bitstream-id-with-usr_access.pdf>
215pub fn usr_access_timestamp() -> u32 {
216    let now = Utc::now();
217    now.day() << 27
218        | now.month() << 23
219        | now.year_ce().1.checked_sub(2000u32).unwrap() << 17
220        | now.hour() << 12
221        | now.minute() << 6
222        | now.second()
223}
224
225/// Returns the crc32 hash of the bitstream to be used as a USR_ACCESS value
226pub fn usr_access_crc32(bitstream: &mut [u8]) -> Result<u32> {
227    usr_access_set(bitstream, 0)?; // Clear usr_access before hashing
228    Ok(Crc::<u32>::new(&crc::CRC_32_ISO_HDLC).checksum(bitstream))
229}
230
231pub fn usr_access_set(bitstream: &mut [u8], val: u32) -> Result<()> {
232    if let Some(header) = BitstreamTypeOneHeaders::from_bitstream(bitstream)
233        .find(|x| (x.op() == BitstreamOp::Write) && (x.address() == REG_ADDR_USR_ACCESS))
234    {
235        let operand = &mut bitstream[header.offset + 4..header.offset + 8];
236        let usr_access = u32::from_be_bytes(operand.try_into()?);
237        log::info!("Bitstream file old USR_ACCESS value: {:#x}", usr_access);
238        operand.copy_from_slice(&val.to_be_bytes());
239        log::info!("Bitstream file new USR_ACCESS value: {:#x}", val);
240        remove_crc(bitstream);
241        return Ok(());
242    }
243    Err(Error::CommandNotFound(cmd_from_parts(BitstreamOp::Write, REG_ADDR_USR_ACCESS)).into())
244}