blob: d6f8641c3e27e47e6485f503030725b50e8b1cd5 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use thiserror::Error;
use chrono::{Datelike, Timelike, Utc};
use crc::Crc;
const WRITE_USR_ACCESS: [u8; 4] = [0x30, 0x01, 0xa0, 0x01];
const WRITE_CRC_REG: [u8; 4] = [0x30, 0x00, 0x00, 0x01];
const NOOP: [u8; 4] = [0x20, 0x00, 0x00, 0x00];
#[derive(Error, Debug, Serialize, Deserialize)]
pub enum Error {
#[error("Command {0:#x?} not found in bitstream")]
CommandNotFound(u32),
}
fn find_cmd(bitstream: &[u8], cmd: &[u8; 4]) -> Result<usize> {
Ok(bitstream
.chunks(4)
.enumerate()
.find(|(_, op)| *op == cmd)
.ok_or_else(|| Error::CommandNotFound(u32::from_be_bytes(*cmd)))?
.0
* 4)
}
/// Searches for the following pattern in the bitstream
///
/// 0x30000001 (write the following word to the CRC register and check)
/// 0xXXXXXXXX (value to write to the CRC register)
/// 0x20000000 (NOOP)
/// 0x20000000 (NOOP)
///
/// and replaces it with
///
/// 0x20000000 (NOOP)
/// 0x20000000 (NOOP)
/// 0x20000000 (NOOP)
/// 0x20000000 (NOOP)
///
/// to remove the CRC check during FPGA configuration.
fn remove_crc(bitstream: &mut [u8]) {
let mut start = 0;
while start < bitstream.len() {
match find_cmd(&bitstream[start..], &WRITE_CRC_REG) {
Ok(mut i) => {
i += start;
if (i + 16) <= bitstream.len()
&& bitstream[i + 8..i + 12] == NOOP
&& bitstream[i + 12..i + 16] == NOOP
{
log::info!("Replaced WRITE_CRC_REG command at {:#x} with NOOP", i);
bitstream[i..i + 4].copy_from_slice(&NOOP);
bitstream[i + 4..i + 8].copy_from_slice(&NOOP);
}
start = i + 16;
}
_ => return,
}
}
}
pub fn usr_access_get(bitstream: &[u8]) -> Result<u32> {
let i = find_cmd(bitstream, &WRITE_USR_ACCESS)?;
if (i + 8) > bitstream.len() {
return Err(Error::CommandNotFound(u32::from_be_bytes(WRITE_USR_ACCESS)).into());
}
let operand = &bitstream[i + 4..i + 8];
let usr_access = u32::from_be_bytes(operand.try_into()?);
log::info!("Bitstream file USR_ACCESS value: {:#x}", usr_access);
Ok(usr_access)
}
/// Returns a 32-bit timestamp suitable to be used as a USR_ACCESS value:
///
/// |-------+-------+-------+-------------------------+-------+--------+--------|
/// | Bits: | 31:27 | 26:23 | 22:17 | 16:12 | 11:6 | 5:0 |
/// |-------+-------+-------+-------------------------+-------+--------+--------|
/// | Data: | Day | Month | Year (since 2000, 0-63) | Hour | Minute | Second |
/// |-------+-------+-------+-------------------------+-------+--------+--------|
///
/// From
/// https://www.xilinx.com/content/dam/xilinx/support/documents/application_notes/xapp1232-bitstream-id-with-usr_access.pdf
pub fn usr_access_timestamp() -> u32 {
let now = Utc::now();
now.day() << 27
| now.month() << 23
| now.year_ce().1.checked_sub(2000u32).unwrap() << 17
| now.hour() << 12
| now.minute() << 6
| now.second()
}
/// Returns the crc32 hash of the bitstream to be used as a USR_ACCESS value
pub fn usr_access_crc32(bitstream: &mut [u8]) -> Result<u32> {
usr_access_set(bitstream, 0)?; // Clear usr_access before hashing
Ok(Crc::<u32>::new(&crc::CRC_32_ISO_HDLC).checksum(bitstream))
}
pub fn usr_access_set(bitstream: &mut [u8], val: u32) -> Result<()> {
let i = find_cmd(bitstream, &WRITE_USR_ACCESS)?;
log::info!(
"Bitstream file old USR_ACCESS value: {:#x}",
u32::from_be_bytes(bitstream[i + 4..i + 8].try_into()?)
);
bitstream[i + 4..i + 8].copy_from_slice(&val.to_be_bytes());
log::info!("Bitstream file new USR_ACCESS value: {:#x}", val);
remove_crc(bitstream);
Ok(())
}