[opentitanlib] Add OTP hex file generation.
Signed-off-by: Jon Flatley <jflat@google.com>
diff --git a/sw/host/opentitanlib/src/lib.rs b/sw/host/opentitanlib/src/lib.rs
index 38e9c55..71ddeab 100644
--- a/sw/host/opentitanlib/src/lib.rs
+++ b/sw/host/opentitanlib/src/lib.rs
@@ -5,6 +5,7 @@
pub mod app;
pub mod bootstrap;
pub mod io;
+pub mod otp;
pub mod spiflash;
pub mod transport;
pub mod util;
diff --git a/sw/host/opentitanlib/src/otp/lc_state.rs b/sw/host/opentitanlib/src/otp/lc_state.rs
new file mode 100644
index 0000000..813d8da
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/lc_state.rs
@@ -0,0 +1,110 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use anyhow::{bail, Result};
+use serde::Deserialize;
+use std::fs;
+use std::path::Path;
+
+/// SECDED matrix used for ECC in OTP.
+#[derive(Deserialize, Debug)]
+pub struct LcSecded {
+ /// The number of bits of data covered by ECC.
+ data_width: usize,
+ /// The number of ECC bits.
+ ecc_width: usize,
+ /// ECC matrix used for computing ECC bits.
+ ecc_matrix: Vec<Vec<u8>>,
+}
+
+/// The internal representation of lc_ctrl_state, used in OTP operations.
+#[derive(Deserialize, Debug)]
+pub struct LcState {
+ secded: LcSecded,
+}
+
+impl LcSecded {
+ pub fn new(in_file: &Path) -> Result<LcSecded> {
+ let json_text = fs::read_to_string(in_file)?;
+ let res: LcState = deser_hjson::from_str(&json_text)?;
+ if res.secded.ecc_matrix.len() != res.secded.ecc_width {
+ bail!("Bad ecc matrix length {}", res.secded.ecc_matrix.len());
+ }
+ Ok(res.secded)
+ }
+
+ fn bit_index(data: &[u8], index: usize) -> bool {
+ let byte = index / 8;
+ let bit = index % 8;
+ data[byte] & (1 << bit) != 0
+ }
+
+ pub fn ecc_encode(&self, mut data: Vec<u8>) -> Result<Vec<u8>> {
+ if data.len() * 8 != self.data_width {
+ bail!("Bad data length for ecc {}", data.len() * 8);
+ }
+ let data_len = data.len();
+ data.resize(data_len + self.ecc_byte_len(), 0);
+ for (i, matrix) in self.ecc_matrix.iter().enumerate() {
+ let mut bit = false;
+ for j in matrix {
+ bit ^= Self::bit_index(&data, *j as usize);
+ }
+ if bit {
+ let byte = i / 8 + data_len;
+ let bit = i % 8;
+ data[byte] |= 1 << bit;
+ }
+ }
+
+ Ok(data)
+ }
+
+ pub fn ecc_byte_len(&self) -> usize {
+ if self.ecc_width == 0 {
+ 0
+ } else {
+ (self.ecc_width - 1) / 8 + 1
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::Result;
+ use deser_hjson::from_str;
+ use std::fs::read_to_string;
+
+ #[test]
+ fn test_lc_state_deserialize() -> Result<()> {
+ let _: LcState = from_str(&read_to_string("tests/lc_ctrl_state.hjson")?)?;
+ Ok(())
+ }
+
+ #[test]
+ fn test_ecc_encode() {
+ let secded = LcSecded {
+ data_width: 16,
+ ecc_width: 6,
+ ecc_matrix: vec![
+ vec![0, 1, 3, 4, 6, 8, 10, 11, 13, 15], // ECC bit 0
+ vec![0, 2, 3, 5, 6, 9, 10, 12, 13], // ECC bit 1
+ vec![1, 2, 3, 7, 8, 9, 10, 14, 15], // ECC bit 2
+ vec![4, 5, 6, 7, 8, 9, 10], // ECC bit 3
+ vec![11, 12, 13, 14, 15], // ECC bit 4
+ vec![
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ ], // Parity bit
+ ],
+ };
+
+ let zero: Vec<u8> = vec![0, 0];
+ let a5a5: Vec<u8> = vec![0xa5, 0xa5];
+ let fcc5: Vec<u8> = vec![0xfc, 0xc5];
+ assert_eq!(vec![0u8, 0, 0], secded.ecc_encode(zero).unwrap());
+ assert_eq!(vec![0xa5u8, 0xa5, 0x27], secded.ecc_encode(a5a5).unwrap());
+ assert_eq!(vec![0x0fcu8, 0xc5, 0x06], secded.ecc_encode(fcc5).unwrap())
+ }
+}
diff --git a/sw/host/opentitanlib/src/otp/mod.rs b/sw/host/opentitanlib/src/otp/mod.rs
new file mode 100644
index 0000000..8465483
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/mod.rs
@@ -0,0 +1,37 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+pub mod lc_state;
+pub mod otp;
+pub mod otp_img;
+pub mod otp_mmap;
+pub mod vmem_serialize;
+
+mod num_de;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::Result;
+ use std::path::Path;
+
+ #[test]
+ fn test_vmem_serialize() -> Result<()> {
+ let mut otp_mmap = otp_mmap::OtpMap::new(Path::new("tests/otp_ctrl_mmap.hjson"))?;
+ let mut otp_img = otp_img::OtpImg::new(Path::new("tests/otp_ctrl_img_dev.hjson"))?;
+ let lc_state = lc_state::LcSecded::new(Path::new("tests/lc_ctrl_state.hjson"))?;
+ let vmem = otp_mmap.make_vmem(&mut otp_img)?;
+ let keys = otp_mmap.generate_keys(&otp_img);
+ let result = vmem.generate(keys, &lc_state)?;
+ let expected = std::fs::read_to_string(Path::new("tests/output.vmem"))?;
+ let expected = expected
+ .split("\n")
+ .filter(|s| !s.is_empty())
+ .collect::<Vec<&str>>();
+
+ assert_eq!(result, expected);
+
+ Ok(())
+ }
+}
diff --git a/sw/host/opentitanlib/src/otp/num_de.rs b/sw/host/opentitanlib/src/otp/num_de.rs
new file mode 100644
index 0000000..d621e9e
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/num_de.rs
@@ -0,0 +1,228 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+/// Deserialization utilities for certain values in OTP HJSON files.
+///
+/// The OTP HJSON files have some strange values:
+///
+/// Integers, sometimes wrapped in strings, with inconsistent formatting and meta values, such as:
+/// - value: "0x739"
+/// - key_size: "16"
+/// - seed: "10556718629619452145"
+/// - seed: 01931961561863975174 // This is a decimal integer, not octal.
+/// - value: "<random>"
+///
+/// Additionally, some values have sizes defined within the config files themselves, such as the
+/// keys. This module exists to handle these peculiar cases.
+use anyhow::Result;
+use rand::RngCore;
+use serde::de::{self, Deserializer, Unexpected};
+use serde::ser::Serializer;
+use serde::{Deserialize, Serialize};
+use std::any::type_name;
+use std::fmt;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+use crate::util::parse_int::{ParseInt, ParseIntError};
+
+pub fn _serialize<S, T>(_r: T, _ser: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+ T: Serialize + Copy,
+{
+ unimplemented!();
+}
+
+/// Deserialize numeric types from HJSON config files.
+pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
+where
+ D: Deserializer<'de>,
+ T: ParseInt,
+{
+ struct Visitor<U>(PhantomData<U>);
+
+ impl<'a, U: ParseInt> de::Visitor<'a> for Visitor<U> {
+ type Value = U;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_fmt(format_args!("a string that parses to {}", type_name::<U>()))
+ }
+
+ fn visit_string<E>(self, mut name: String) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ if name.starts_with("false") {
+ name = "0".to_owned()
+ } else if name.starts_with("true") {
+ name = "1".to_owned()
+ }
+
+ let trimmed = if name.starts_with("0x") {
+ &name
+ } else {
+ let trimmed = name[0..name.len() - 1].trim_start_matches('0');
+ &name[name.len() - trimmed.len() - 1..]
+ };
+
+ match U::from_str(trimmed) {
+ Ok(value) => Ok(value),
+ Err(_) => Err(de::Error::invalid_value(Unexpected::Str(trimmed), &self)),
+ }
+ }
+
+ fn visit_str<E>(self, name: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ self.visit_string(name.to_owned())
+ }
+
+ fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ if v {
+ self.visit_str("1")
+ } else {
+ self.visit_str("0")
+ }
+ }
+ }
+
+ deserializer.deserialize_string(Visitor {
+ 0: PhantomData::<T>,
+ })
+}
+
+/// Placeholder type for values that cannot be resolved during deserialization.
+#[derive(Debug, PartialEq, Clone)]
+enum DeferredInit {
+ Initialized(Vec<u8>),
+ Random,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct DeferredValue(#[serde(with = "self")] DeferredInit);
+
+impl DeferredValue {
+ pub fn resolve(&self, size: usize, rng: &mut dyn RngCore) -> Vec<u8> {
+ match self.0.clone() {
+ DeferredInit::Initialized(mut vec) => {
+ vec.resize(size, 0);
+ vec
+ }
+ DeferredInit::Random => {
+ let mut vec = vec![0u8; size];
+ rng.fill_bytes(&mut vec);
+ vec
+ }
+ }
+ }
+
+ pub fn is_initialized(&self) -> bool {
+ matches!(self.0, DeferredInit::Initialized(_))
+ }
+}
+
+impl ParseInt for DeferredInit {
+ type FromStrRadixErr = ParseIntError;
+
+ fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError> {
+ Ok(DeferredInit::Initialized(Vec::<u8>::from_str_radix(
+ src, radix,
+ )?))
+ }
+
+ fn from_str(src: &str) -> Result<Self, ParseIntError> {
+ if src == "<random>" {
+ Ok(DeferredInit::Random)
+ } else {
+ Ok(DeferredInit::Initialized(Vec::<u8>::from_str(src)?))
+ }
+ }
+}
+
+impl Deref for DeferredValue {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ match &self.0 {
+ DeferredInit::Initialized(val) => val,
+ _ => panic!("Value has not been initialized"),
+ }
+ }
+}
+
+/// Wrapper type to force deserialization assuming octal encoding.
+#[derive(Deserialize, Debug)]
+pub struct OctEncoded<T: ParseInt>(#[serde(with = "self")] T);
+
+/// Wrapper type to force deserialization assuming decimal encoding.
+#[derive(Deserialize, Debug)]
+pub struct DecEncoded<T: ParseInt>(#[serde(with = "self")] T);
+
+/// Wrapper type to force deserialization assuming hexadecimal encoding.
+#[derive(Deserialize, Debug)]
+pub struct HexEncoded<T: ParseInt>(#[serde(with = "self")] T);
+
+macro_rules! impl_parse_int_enc {
+ ($ty:ident, $radix:expr) => {
+ impl<T: ParseInt> std::ops::Deref for $ty<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+
+ impl<T: ParseInt> ParseInt for $ty<T> {
+ type FromStrRadixErr = T::FromStrRadixErr;
+
+ fn from_str_radix(src: &str, radix: u32) -> Result<Self, T::FromStrRadixErr> {
+ Ok(Self(T::from_str_radix(src, radix)?))
+ }
+
+ fn from_str(src: &str) -> Result<Self, ParseIntError> {
+ Self::from_str_radix(src, $radix).map_err(|e| e.into())
+ }
+ }
+ };
+}
+
+impl_parse_int_enc!(OctEncoded, 8);
+impl_parse_int_enc!(DecEncoded, 10);
+impl_parse_int_enc!(HexEncoded, 16);
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use serde::Deserialize;
+
+ #[test]
+ fn de_u8() -> Result<()> {
+ #[derive(Debug, Deserialize)]
+ struct TestData {
+ #[serde(with = "super")]
+ oct: OctEncoded<u8>,
+ #[serde(with = "super")]
+ dec: DecEncoded<u8>,
+ #[serde(with = "super")]
+ hex: HexEncoded<u8>,
+ }
+
+ let data: TestData = deser_hjson::from_str(stringify!(
+ {
+ oct: "77",
+ dec: "77",
+ hex: "77"
+ }))?;
+
+ assert_eq!(*data.oct, 63);
+ assert_eq!(*data.dec, 77);
+ assert_eq!(*data.hex, 119);
+ Ok(())
+ }
+}
diff --git a/sw/host/opentitanlib/src/otp/otp.rs b/sw/host/opentitanlib/src/otp/otp.rs
new file mode 100644
index 0000000..c11fea0
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/otp.rs
@@ -0,0 +1,44 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::util::parse_int::{ParseInt, ParseIntError};
+
+use anyhow::Result;
+
+impl ParseInt for Vec<u8> {
+ type FromStrRadixErr = ParseIntError;
+
+ fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError> {
+ let mut bytes = vec![];
+ for digit_bytes in src.as_bytes().rchunks(2) {
+ let digits = std::str::from_utf8(digit_bytes).unwrap();
+ bytes.push(u8::from_str_radix(digits, radix)?);
+ }
+ Ok(bytes)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::convert::TryInto;
+ #[test]
+ fn byte_field_test() {
+ assert_eq!(Vec::from_str("0x1"), Ok(vec![0x1]));
+ assert_eq!(
+ Vec::from_str("0x4b4b4b4b4b4ba5a5"),
+ Ok(vec![0xa5, 0xa5, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b])
+ );
+ assert_eq!(
+ u64::from_ne_bytes(
+ Vec::from_str("0x4b4b4b4b4b4ba5a5")
+ .unwrap()
+ .try_into()
+ .unwrap()
+ ),
+ u64::from_str("0x4b4b4b4b4b4ba5a5").unwrap()
+ );
+ assert!(Vec::from_str("-1").is_err());
+ }
+}
diff --git a/sw/host/opentitanlib/src/otp/otp_img.rs b/sw/host/opentitanlib/src/otp/otp_img.rs
new file mode 100644
index 0000000..fe05775
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/otp_img.rs
@@ -0,0 +1,60 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use serde::Deserialize;
+
+use std::path::Path;
+
+use crate::otp::num_de::{DecEncoded, DeferredValue};
+use anyhow::Result;
+use rand::rngs::StdRng;
+use rand::SeedableRng;
+
+const OTP_IMG_SEED_DIVERSIFIER: u64 = 1941661965323525198146u128 as u64;
+
+#[derive(Deserialize, Debug)]
+pub struct OtpImgItem {
+ pub name: String,
+ pub value: DeferredValue,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OtpImgPartition {
+ pub name: String,
+ pub items: Option<Vec<OtpImgItem>>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OtpImg {
+ pub seed: DecEncoded<u64>,
+ pub partitions: Vec<OtpImgPartition>,
+}
+
+impl OtpImgPartition {
+ pub fn get_item(&mut self, name: &str) -> Option<&mut OtpImgItem> {
+ self.items
+ .as_mut()
+ .and_then(|items| items.iter_mut().find(|i| i.name == name))
+ }
+}
+
+impl OtpImg {
+ pub fn new(in_file: &Path) -> Result<OtpImg> {
+ let json_text = std::fs::read_to_string(in_file)?;
+ let res: OtpImg = deser_hjson::from_str(&json_text)?;
+ Ok(res)
+ }
+
+ pub fn get_partition(&mut self, name: &str) -> Option<&mut OtpImgPartition> {
+ self.partitions.iter_mut().find(|p| p.name == name)
+ }
+
+ pub fn partition(&self) -> &[OtpImgPartition] {
+ &self.partitions
+ }
+
+ pub fn get_rng(&self) -> StdRng {
+ StdRng::seed_from_u64(OTP_IMG_SEED_DIVERSIFIER + *self.seed)
+ }
+}
diff --git a/sw/host/opentitanlib/src/otp/otp_mmap.rs b/sw/host/opentitanlib/src/otp/otp_mmap.rs
new file mode 100644
index 0000000..e6960c3
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/otp_mmap.rs
@@ -0,0 +1,207 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::otp::num_de::{self, DeferredValue};
+use crate::otp::otp_img::OtpImg;
+use crate::otp::vmem_serialize::*;
+
+use anyhow::{anyhow, bail, Result};
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::fs;
+use std::path::Path;
+
+#[derive(Deserialize, Debug)]
+struct OtpMapConfig {
+ #[serde(with = "num_de")]
+ width: usize,
+ #[serde(with = "num_de")]
+ 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(with = "num_de")]
+ key_size: usize,
+ #[serde(with = "num_de")]
+ iv_size: usize,
+ #[serde(with = "num_de")]
+ cnst_size: usize,
+ keys: Vec<OtpMapKey>,
+ digests: Vec<OtpMapDigest>,
+}
+
+#[derive(Deserialize, Debug)]
+struct OtpMapItem {
+ name: String,
+ #[serde(with = "num_de")]
+ size: usize,
+ #[serde(default)]
+ isdigest: bool,
+ inv_default: Option<DeferredValue>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OtpMapPartition {
+ name: String,
+ secret: bool,
+ #[serde(default, with = "num_de")]
+ size: usize,
+ sw_digest: bool,
+ hw_digest: bool,
+ key_sel: String,
+ items: Vec<OtpMapItem>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OtpMap {
+ 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 std::fs::read_to_string;
+
+ #[test]
+ fn test_mmap_deserialize() {
+ let _: OtpMap =
+ deser_hjson::from_str(&read_to_string("tests/otp_ctrl_mmap.hjson").unwrap()).unwrap();
+ }
+
+ #[test]
+ fn test_img_deserialize() {
+ let _: OtpImg =
+ deser_hjson::from_str(&read_to_string("tests/otp_ctrl_img_dev.hjson").unwrap())
+ .unwrap();
+ }
+}
diff --git a/sw/host/opentitanlib/src/otp/vmem_serialize.rs b/sw/host/opentitanlib/src/otp/vmem_serialize.rs
new file mode 100644
index 0000000..077b46a
--- /dev/null
+++ b/sw/host/opentitanlib/src/otp/vmem_serialize.rs
@@ -0,0 +1,279 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::otp::lc_state::LcSecded;
+use crate::util::present::Present;
+
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::fmt::Write;
+
+use anyhow::{anyhow, bail, ensure, Result};
+
+use zerocopy::AsBytes;
+
+enum ItemType {
+ Bytes(Vec<u8>),
+ Unvalued(usize),
+}
+
+/// The hex representation of an OTP item.
+pub struct VmemItem {
+ value: ItemType,
+ offset: usize,
+ name: String,
+}
+
+impl VmemItem {
+ pub fn new(bytes: Vec<u8>, offset: usize, name: String) -> VmemItem {
+ VmemItem {
+ value: ItemType::Bytes(bytes),
+ offset,
+ name,
+ }
+ }
+
+ pub fn new_unvalued(size: usize, offset: usize, name: String) -> VmemItem {
+ VmemItem {
+ value: ItemType::Unvalued(size),
+ offset,
+ name,
+ }
+ }
+
+ pub fn size(&self) -> usize {
+ match &self.value {
+ ItemType::Bytes(b) => b.len(),
+ ItemType::Unvalued(size) => *size,
+ }
+ }
+}
+
+pub type DigestIV = u64;
+pub type DigestCnst = u128;
+
+/// Digest information for an OTP partition.
+#[derive(PartialEq)]
+pub enum DigestType {
+ Unlocked,
+ Software,
+ Hardware(DigestIV, DigestCnst),
+}
+
+/// The hex representation of an OTP partition.
+pub struct VmemPartition {
+ /// Items associated with this partition.
+ items: Vec<VmemItem>,
+ /// The name of this partition.
+ /// Used in annotations.
+ name: String,
+ /// The type of digest used for this partition.
+ /// For software digests, the value of the digest is provided and appended to the list of
+ /// items. For hardware digests, we must compute the digest value and append to the list of
+ /// items.
+ digest_type: DigestType,
+ /// Partition size.
+ size: usize,
+ /// The key name for this parition.
+ /// If specified, the serializer will attempt to scramble this parition using the key named in
+ /// this field.
+ key_name: Option<String>,
+}
+
+impl VmemPartition {
+ pub fn new(
+ name: String,
+ size: usize,
+ digest_type: DigestType,
+ key_name: Option<String>,
+ ) -> VmemPartition {
+ VmemPartition {
+ items: Vec::new(),
+ name,
+ digest_type,
+ size,
+ key_name,
+ }
+ }
+
+ /// Set the size of the partition.
+ ///
+ /// For partitions that don't specify their size, this is used to set the size of the partition
+ /// including the digest.
+ pub fn set_size(&mut self, size: usize) {
+ self.size = size;
+ }
+
+ /// Add an item to this partition.
+ pub fn push_item(&mut self, item: VmemItem) {
+ self.items.push(item);
+ }
+
+ /// Produces a tuple containing OTP HEX lines with annotations.
+ fn write_to_buffer(&self, keys: &HashMap<String, Vec<u8>>) -> Result<(Vec<u8>, Vec<String>)> {
+ if self.size % 8 != 0 {
+ bail!("Partition {} must be 64-bit alligned", self.name);
+ }
+
+ let mut defined = vec![false; self.size];
+ let mut annotations: Vec<String> = vec!["unallocated".to_owned(); self.size];
+
+ let mut data_bytes: Vec<u8> = vec![0; self.size];
+
+ for item in &self.items {
+ let end = item.offset + item.size();
+ annotations[item.offset..end].fill(format!("{}: {}", self.name, item.name).to_string());
+ let defined = &mut defined[item.offset..end];
+ if let Some(collision) = defined.iter().position(|defined| *defined) {
+ bail!(
+ "Unexpected item collision with item {} at 0x{:x}",
+ item.name,
+ collision
+ );
+ }
+ defined.fill(true);
+ if let ItemType::Bytes(bytes) = &item.value {
+ data_bytes[item.offset..end].copy_from_slice(bytes);
+ }
+ }
+
+ let mut data_blocks = Vec::<u64>::new();
+ let mut data_blocks_defined = Vec::<bool>::new();
+ for (k, chunk) in data_bytes.chunks(8).enumerate() {
+ data_blocks.push(u64::from_le_bytes(chunk.try_into().unwrap()));
+ let byte_offset = k * 8;
+ data_blocks_defined.push(
+ defined[byte_offset..byte_offset + 8]
+ .iter()
+ .fold(false, |a, &b| a || b),
+ );
+ }
+
+ if let Some(key_name) = &self.key_name {
+ let key = keys
+ .get(key_name)
+ .ok_or_else(|| anyhow!("Key not found {}", key_name))?;
+
+ let cipher = Present::try_new(key.clone())?;
+
+ for i in 0..data_blocks.len() {
+ if data_blocks_defined[i] {
+ data_blocks[i] = cipher.encrypt_block(data_blocks[i]);
+ }
+ }
+ }
+
+ if let DigestType::Hardware(iv, fin_const) = self.digest_type {
+ ensure!(
+ matches!(data_blocks.last(), None | Some(0)),
+ "Digest of partition {} cannot be overridden manually",
+ self.name
+ );
+ let last = data_blocks.len() - 1;
+ data_blocks[last] = present_digest_64(&data_blocks[0..last], iv, fin_const);
+ }
+
+ let data = data_blocks.as_bytes().to_vec();
+
+ if data.len() != self.size {
+ Err(anyhow!("Partition {} size mismatch", self.name))
+ } else {
+ Ok((data, annotations))
+ }
+ }
+}
+
+pub struct VmemImage {
+ partitions: Vec<VmemPartition>,
+ width: usize,
+ depth: usize,
+}
+
+impl VmemImage {
+ pub fn new(partitions: Vec<VmemPartition>, width: usize, depth: usize) -> VmemImage {
+ VmemImage {
+ partitions,
+ width,
+ depth,
+ }
+ }
+ pub fn generate(
+ &self,
+ keys: HashMap<String, Vec<u8>>,
+ secded: &LcSecded,
+ ) -> Result<Vec<String>> {
+ let mut data: Vec<u8> = vec![0; self.width * self.depth];
+ let mut annotations: Vec<String> = vec![Default::default(); data.len()];
+ let mut offset = 0;
+ for partition in &self.partitions {
+ let (part_data, part_annotation) = partition.write_to_buffer(&keys)?;
+ let end = offset + partition.size;
+ if end > data.len() {
+ bail!(
+ "Partition {} out of bounds, ends at 0x{:x}",
+ partition.name,
+ end
+ );
+ }
+ data[offset..end].clone_from_slice(&part_data);
+ annotations[offset..end].clone_from_slice(&part_annotation);
+ offset += partition.size;
+ }
+
+ let width_ecc = self.width + secded.ecc_byte_len();
+ let num_words = data.len() / self.width;
+
+ let mut output = vec![format!(
+ "// OTP memory hexfile with {} x {}bit layout",
+ self.depth,
+ width_ecc * 8
+ )];
+
+ for i in 0..num_words {
+ let mut word = Vec::<u8>::new();
+ let mut word_annotation = Vec::<String>::new();
+ for j in 0..self.width {
+ let idx = i * self.width + j;
+ word.push(data[idx]);
+ if !word_annotation.contains(&annotations[idx]) {
+ word_annotation.push(annotations[idx].clone());
+ }
+ }
+ let word_with_ecc = secded.ecc_encode(word)?;
+ let mut word_str = String::new();
+ for byte in word_with_ecc.iter().rev() {
+ write!(word_str, "{:02x}", byte)?;
+ }
+ output.push(format!(
+ "{} // {:06x}: {}",
+ word_str,
+ i * self.width,
+ word_annotation.join(", ")
+ ));
+ }
+
+ Ok(output)
+ }
+}
+
+fn present_digest_64(message: &[u64], iv: DigestIV, fin_const: DigestCnst) -> u64 {
+ let mut state = iv;
+ for i in (0..message.len() + 2).step_by(2) {
+ let b128: [u8; 16] = if i + 1 < message.len() {
+ (message[i] as u128) << 64 | message[i + 1] as u128
+ } else if i < message.len() {
+ (message[i] as u128) << 64 | message[i] as u128
+ } else {
+ fin_const
+ }
+ .as_bytes()
+ .try_into()
+ .unwrap();
+
+ let cipher = Present::new_128(&b128);
+ state ^= cipher.encrypt_block(state);
+ }
+
+ state
+}
diff --git a/sw/host/opentitanlib/src/util/mod.rs b/sw/host/opentitanlib/src/util/mod.rs
index 4d655de..1c3c7bf 100644
--- a/sw/host/opentitanlib/src/util/mod.rs
+++ b/sw/host/opentitanlib/src/util/mod.rs
@@ -7,8 +7,8 @@
pub mod file;
pub mod image;
pub mod parse_int;
-pub mod usb;
pub mod present;
+pub mod usb;
pub mod voltage;
/// The `collection` macro provides syntax for hash and set literals.
diff --git a/sw/host/opentitanlib/src/util/parse_int.rs b/sw/host/opentitanlib/src/util/parse_int.rs
index 1308013..6da3d48 100644
--- a/sw/host/opentitanlib/src/util/parse_int.rs
+++ b/sw/host/opentitanlib/src/util/parse_int.rs
@@ -73,6 +73,8 @@
impl_parse_int!(u32);
impl_parse_int!(i64);
impl_parse_int!(u64);
+impl_parse_int!(i128);
+impl_parse_int!(u128);
impl_parse_int!(isize);
impl_parse_int!(usize);