blob: f676ab0c191c7c848f5b8fa5b044aa6f4b6342b5 [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 crate::otp::otp_img::OtpImg;
use crate::otp::vmem_serialize::*;
use crate::util::num_de::{self, DeferredValue};
use anyhow::{anyhow, bail, Result};
use serde::Deserialize;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fs;
use std::path::Path;
// FIXME: The OTP module is not being used yet. When we write the OTP
// configuration utility, remove the `dead_code`s and clean up the warnings.
#[derive(Deserialize, Debug)]
struct OtpMapConfig {
#[serde(deserialize_with = "num_de::deserialize")]
width: usize,
#[serde(deserialize_with = "num_de::deserialize")]
depth: usize,
}
#[derive(Deserialize, Debug)]
struct OtpMapKey {
name: String,
value: DeferredValue,
}
#[derive(Deserialize, Debug)]
struct OtpMapDigest {
name: String,
iv_value: DeferredValue,
cnst_value: DeferredValue,
}
#[derive(Deserialize, Debug)]
struct OtpMapScrambling {
#[serde(deserialize_with = "num_de::deserialize")]
key_size: usize,
#[serde(deserialize_with = "num_de::deserialize")]
iv_size: usize,
#[serde(deserialize_with = "num_de::deserialize")]
cnst_size: usize,
keys: Vec<OtpMapKey>,
digests: Vec<OtpMapDigest>,
}
#[derive(Deserialize, Debug)]
struct OtpMapItem {
name: String,
#[serde(deserialize_with = "num_de::deserialize")]
size: usize,
#[serde(default)]
#[allow(dead_code)]
isdigest: bool,
#[allow(dead_code)]
inv_default: Option<DeferredValue>,
}
#[derive(Deserialize, Debug)]
pub struct OtpMapPartition {
name: String,
#[allow(dead_code)]
secret: bool,
#[serde(default, deserialize_with = "num_de::deserialize")]
size: usize,
sw_digest: bool,
hw_digest: bool,
key_sel: String,
items: Vec<OtpMapItem>,
}
#[derive(Deserialize, Debug)]
pub struct OtpMap {
#[allow(dead_code)]
seed: String,
otp: OtpMapConfig,
scrambling: OtpMapScrambling,
partitions: Vec<OtpMapPartition>,
}
impl OtpMap {
pub fn new(in_file: &Path) -> Result<OtpMap> {
let json_text = fs::read_to_string(in_file)?;
let res: OtpMap = deser_hjson::from_str(&json_text)?;
Ok(res)
}
pub fn generate_keys(&self, img: &OtpImg) -> HashMap<String, Vec<u8>> {
let mut rng = img.get_rng();
let mut map = HashMap::new();
for key in &self.scrambling.keys {
let value = key.value.resolve(self.scrambling.key_size, &mut rng);
map.insert(key.name.clone(), value);
}
map
}
pub fn make_vmem(&mut self, img: &mut OtpImg) -> Result<VmemImage> {
// Seeded RNG needed for "<random>" values.
let mut rng = img.get_rng();
let mut vmem_partitions = Vec::<VmemPartition>::new();
for partition in &self.partitions {
let key_name = match partition.key_sel.as_str() {
"NoKey" => None,
key => Some(key.to_owned()),
};
let digest_type = if !partition.sw_digest && !partition.hw_digest {
DigestType::Unlocked
} else if partition.sw_digest && !partition.hw_digest {
DigestType::Software
} else if !partition.sw_digest && partition.hw_digest {
// Extra information needed to compute HW digests.
let iv_size = self.scrambling.iv_size;
let cnst_size = self.scrambling.cnst_size;
let digest_info = self
.scrambling
.digests
.iter_mut()
.find(|v| v.name == "CnstyDigest")
.ok_or(anyhow!("Couldn't find digest info"))?;
const IV_SIZE: usize = std::mem::size_of::<DigestIV>();
const CNST_SIZE: usize = std::mem::size_of::<DigestCnst>();
let iv_value: [u8; IV_SIZE] = digest_info
.iv_value
.resolve(iv_size, &mut rng)
.try_into()
.map_err(|_| anyhow!("Bad IV size {}", iv_size))?;
let cnst_value: [u8; CNST_SIZE] = digest_info
.cnst_value
.resolve(cnst_size, &mut rng)
.try_into()
.map_err(|_| anyhow!("Bad scrambling constant size {}", cnst_size))?;
DigestType::Hardware(
DigestIV::from_ne_bytes(iv_value),
DigestCnst::from_ne_bytes(cnst_value),
)
} else {
bail!("Invalid digest configuration");
};
let mut vmem_partition = VmemPartition::new(
partition.name.clone(),
partition.size,
digest_type,
key_name,
);
// Fetch the img definition for partition, this contains the associated values for
// paritition items.
let mut img_partition = img.get_partition(&partition.name);
let mut offset = 0usize;
// Resolve all values and convert to Vmem representation.
for item in &partition.items {
let img_item_value = match &mut img_partition {
Some(v) => {
let item_value = v.get_item(&item.name);
match item_value {
Some(v) => v.value.resolve(item.size, &mut rng),
None => vec![0u8; item.size],
}
}
None => vec![0u8; item.size],
};
let vmem_item = VmemItem::new(img_item_value, offset, item.name.clone());
offset += item.size;
vmem_partition.push_item(vmem_item);
}
if partition.size == 0 {
const SCRAMBLE_BLOCK_WIDTH: usize = 8;
const DIGEST_SIZE: usize = 8;
let mut size = SCRAMBLE_BLOCK_WIDTH
* ((offset + SCRAMBLE_BLOCK_WIDTH - 1) / SCRAMBLE_BLOCK_WIDTH);
if partition.hw_digest || partition.sw_digest {
size += DIGEST_SIZE;
}
vmem_partition.set_size(size);
}
vmem_partitions.push(vmem_partition);
}
Ok(VmemImage::new(
vmem_partitions,
self.otp.width,
self.otp.depth,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testdata;
use std::fs::read_to_string;
#[test]
fn test_mmap_deserialize() {
let _: OtpMap =
deser_hjson::from_str(&read_to_string(testdata!("otp_ctrl_mmap.hjson")).unwrap())
.unwrap();
}
#[test]
fn test_img_deserialize() {
let _: OtpImg =
deser_hjson::from_str(&read_to_string(testdata!("otp_ctrl_img_dev.hjson")).unwrap())
.unwrap();
}
}