Repository: chipsalliance/caliptra-sw
Pull request #310 (2023-05-24 17:27:27)

[FEAT] Log the values from the fuse in to DCCM
This PR logs the values in the Fuse Logs in to DCCM. All the fuses whose values impact the image validation are logged. This includes :- 
1. Vendor public key index fuse
2. vendor public key revocation
3. FMC SVN From Manifest
4. FMC Min SVN
5. FMC SVN from Fuse
6. RT SVN From Manifest
7. RT Min SVN From Manifest




Patch:
diff --git a/common/src/fuse.rs b/common/src/fuse.rs
new file mode 100644
index 00000000..3841343e
--- /dev/null
+++ b/common/src/fuse.rs
@@ -0,0 +1,57 @@
+/*++
+Licensed under the Apache-2.0 license.
+
+File Name:
+
+    fuse.rs
+
+Abstract:
+
+    Fuse-related Types.
+
+--*/
+
+use zerocopy::{AsBytes, FromBytes};
+
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+
+pub enum FuseLogEntryId {
+    Invalid = 0,
+    VendorPubKeyIndex = 1,      // 4 bytes  (From Manifest)
+    VendorPubKeyRevocation = 2, // 4 bytes  (From Fuse)
+    ManifestFmcSvn = 3,         // 4 bytes
+    ManifestFmcMinSvn = 4,      // 4 bytes
+    FuseFmcSvn = 5,             // 4 bytes
+    ManifestRtSvn = 6,          // 4 bytes
+    ManifestRtMinSvn = 7,       // 4 bytes
+    FuseRtSvn = 8,              // 4 bytes
+}
+
+impl From<u32> for FuseLogEntryId {
+    fn from(id: u32) -> FuseLogEntryId {
+        match id {
+            1 => FuseLogEntryId::VendorPubKeyIndex,
+            2 => FuseLogEntryId::VendorPubKeyRevocation,
+            3 => FuseLogEntryId::ManifestFmcSvn,
+            4 => FuseLogEntryId::ManifestFmcMinSvn,
+            5 => FuseLogEntryId::FuseFmcSvn,
+            6 => FuseLogEntryId::ManifestRtSvn,
+            7 => FuseLogEntryId::ManifestRtMinSvn,
+            8 => FuseLogEntryId::FuseRtSvn,
+            _ => FuseLogEntryId::Invalid,
+        }
+    }
+}
+
+/// Fuse log entry
+#[repr(C)]
+#[derive(AsBytes, FromBytes, Default, Debug)]
+pub struct FuseLogEntry {
+    /// Entry identifier
+    pub entry_id: u32,
+
+    pub log_data: [u32; 1],
+
+    pub reserved: [u32; 2],
+}
diff --git a/common/src/lib.rs b/common/src/lib.rs
index 3d921038..77f6573a 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -8,6 +8,7 @@ pub mod hand_off;
 #[macro_use]
 pub mod printer;
 pub mod boot_status;
+pub mod fuse;
 pub mod pcr;
 
 ///merge imports
@@ -17,5 +18,6 @@ pub use hand_off::{
 };
 
 pub use boot_status::RomBootStatus;
+pub use fuse::{FuseLogEntry, FuseLogEntryId};
 pub use pcr::{PcrLogEntry, PcrLogEntryId};
 pub use printer::MutablePrinter;
diff --git a/drivers/src/fuse_bank.rs b/drivers/src/fuse_bank.rs
index 7676ad02..10e98fb7 100644
--- a/drivers/src/fuse_bank.rs
+++ b/drivers/src/fuse_bank.rs
@@ -27,7 +27,7 @@ pub enum X509KeyIdAlgo {
 }
 
 bitflags::bitflags! {
-    #[derive(Default, Copy, Clone)]
+    #[derive(Default, Copy, Clone, Debug)]
     pub struct VendorPubKeyRevocation : u32 {
         const KEY0 = 0b0001;
         const KEY1 = 0b0010;
diff --git a/error/src/lib.rs b/error/src/lib.rs
index 51490d97..978e94ae 100644
--- a/error/src/lib.rs
+++ b/error/src/lib.rs
@@ -315,6 +315,11 @@ impl CaliptraError {
     pub const ROM_GLOBAL_PCR_LOG_UNSUPPORTED_DATA_LENGTH: CaliptraError =
         CaliptraError::new_const(0x01040005);
 
+    pub const ROM_GLOBAL_FUSE_LOG_INVALID_ENTRY_ID: CaliptraError =
+        CaliptraError::new_const(0x01040006);
+    pub const ROM_GLOBAL_FUSE_LOG_UNSUPPORTED_DATA_LENGTH: CaliptraError =
+        CaliptraError::new_const(0x01040007);
+
     /// ROM KAT Errors
     pub const ROM_KAT_SHA256_DIGEST_FAILURE: CaliptraError = CaliptraError::new_const(0x90010001);
     pub const ROM_KAT_SHA256_DIGEST_MISMATCH: CaliptraError = CaliptraError::new_const(0x90010002);
diff --git a/image/verify/src/lib.rs b/image/verify/src/lib.rs
index 050d36fe..ad3a4d92 100644
--- a/image/verify/src/lib.rs
+++ b/image/verify/src/lib.rs
@@ -23,6 +23,14 @@ pub use verifier::ImageVerifier;
 
 /// Image Verifification Executable Info
 #[derive(Default, Debug)]
+pub struct ImageSvnLogInfo {
+    pub manifest_svn: u32,
+    pub manifest_min_svn: u32,
+    pub fuse_svn: u32,
+}
+
+/// Image Verification Executable Info
+#[derive(Default, Debug)]
 pub struct ImageVerificationExeInfo {
     /// Load address
     pub load_addr: u32,
@@ -43,6 +51,22 @@ pub struct ImageVerificationExeInfo {
     pub digest: ImageDigest,
 }
 
+/// Information To Be Logged For The Verified Image
+#[derive(Default, Debug)]
+pub struct ImageVerificationLogInfo {
+    // Vendor Public Key Index To Log
+    pub vendor_ecc_pub_key_idx: u32,
+
+    /// Vendor Public Key Revocation Fuse    
+    pub fuse_vendor_pub_key_revocation: VendorPubKeyRevocation,
+
+    /// First Mutable code's logging information
+    pub fmc_log_info: ImageSvnLogInfo,
+
+    /// Runtime Mutable code's logging information
+    pub rt_log_info: ImageSvnLogInfo,
+}
+
 /// Verified image information
 #[derive(Default, Debug)]
 pub struct ImageVerificationInfo {
@@ -60,6 +84,9 @@ pub struct ImageVerificationInfo {
 
     /// Runtime
     pub runtime: ImageVerificationExeInfo,
+
+    /// Information Returned To Be Logged
+    pub log_info: ImageVerificationLogInfo,
 }
 
 /// Image Verification Environment
diff --git a/image/verify/src/verifier.rs b/image/verify/src/verifier.rs
index 5f98b7d1..0778adc7 100644
--- a/image/verify/src/verifier.rs
+++ b/image/verify/src/verifier.rs
@@ -25,6 +25,7 @@ const ZERO_DIGEST: ImageDigest = [0u32; SHA384_DIGEST_WORD_SIZE];
 struct HeaderInfo<'a> {
     vendor_ecc_pub_key_idx: u32,
     vendor_lms_pub_key_idx: u32,
+    vendor_pub_key_revocation: VendorPubKeyRevocation,
     vendor_info: (&'a ImageEccPubKey, &'a ImageEccSignature),
     vendor_lms_info: (&'a ImageLmsPublicKey, &'a ImageLmsSignature),
     owner_info: Option<(&'a ImageEccPubKey, &'a ImageEccSignature)>,
@@ -101,10 +102,10 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
         let image_info = okref(&image_info)?;
 
         // Verify FMC
-        let fmc_info = self.verify_fmc(image_info.fmc, reason)?;
+        let (fmc_info, fmc_log_info) = self.verify_fmc(image_info.fmc, reason)?;
 
         // Verify Runtime
-        let runtime_info = self.verify_runtime(image_info.runtime)?;
+        let (runtime_info, rt_log_info) = self.verify_runtime(image_info.runtime)?;
 
         let info = ImageVerificationInfo {
             vendor_ecc_pub_key_idx: header_info.vendor_ecc_pub_key_idx,
@@ -112,6 +113,12 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             owner_pub_keys_digest: header_info.owner_pub_keys_digest,
             fmc: fmc_info,
             runtime: runtime_info,
+            log_info: ImageVerificationLogInfo {
+                vendor_ecc_pub_key_idx: header_info.vendor_ecc_pub_key_idx,
+                fuse_vendor_pub_key_revocation: header_info.vendor_pub_key_revocation,
+                fmc_log_info,
+                rt_log_info,
+            },
         };
 
         Ok(info)
@@ -130,7 +137,8 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
         let owner_pk_digest = self.verify_owner_pk_digest(reason)?;
 
         // Verify Vendor Key Index
-        let vendor_ecc_pub_key_idx = self.verify_vendor_ecc_pk_idx(preamble, reason)?;
+        let (vendor_ecc_pub_key_idx, vendor_pub_key_revocation) =
+            self.verify_vendor_ecc_pk_idx(preamble, reason)?;
 
         if vendor_ecc_pub_key_idx >= VENDOR_ECC_KEY_COUNT {
             Err(CaliptraError::IMAGE_VERIFIER_ERR_UPDATE_RESET_VEN_PUB_KEY_IDX_OUT_OF_BOUNDS)?;
@@ -174,6 +182,7 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             vendor_lms_info,
             owner_pub_keys_digest,
             owner_info,
+            vendor_pub_key_revocation,
         };
 
         Ok(info)
@@ -184,7 +193,7 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
         &mut self,
         preamble: &ImagePreamble,
         reason: ResetReason,
-    ) -> CaliptraResult<u32> {
+    ) -> CaliptraResult<(u32, VendorPubKeyRevocation)> {
         const SECOND_LAST_KEY_IDX: u32 = VENDOR_ECC_KEY_COUNT - 2;
         const LAST_KEY_IDX: u32 = VENDOR_ECC_KEY_COUNT - 1;
 
@@ -211,7 +220,7 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             }
         }
 
-        Ok(key_idx)
+        Ok((key_idx, revocation))
     }
 
     /// Verify vendor public key digest
@@ -448,7 +457,7 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
         &mut self,
         verify_info: &ImageTocEntry,
         reason: ResetReason,
-    ) -> CaliptraResult<ImageVerificationExeInfo> {
+    ) -> CaliptraResult<(ImageVerificationExeInfo, ImageSvnLogInfo)> {
         let range = verify_info.image_range();
 
         let actual = self
@@ -506,14 +515,20 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             size: verify_info.size,
         };
 
-        Ok(info)
+        let log_info: ImageSvnLogInfo = ImageSvnLogInfo {
+            manifest_svn: verify_info.svn,
+            manifest_min_svn: verify_info.min_svn,
+            fuse_svn: self.env.fmc_svn(),
+        };
+
+        Ok((info, log_info))
     }
 
     /// Verify Runtime
     fn verify_runtime(
         &mut self,
         verify_info: &ImageTocEntry,
-    ) -> CaliptraResult<ImageVerificationExeInfo> {
+    ) -> CaliptraResult<(ImageVerificationExeInfo, ImageSvnLogInfo)> {
         let range = verify_info.image_range();
 
         let actual = self
@@ -567,7 +582,13 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             size: verify_info.size,
         };
 
-        Ok(info)
+        let log_info: ImageSvnLogInfo = ImageSvnLogInfo {
+            manifest_svn: verify_info.svn,
+            manifest_min_svn: verify_info.min_svn,
+            fuse_svn: self.env.runtime_svn(),
+        };
+
+        Ok((info, log_info))
     }
 
     /// Calculates a digest of the vendor key that signed the image.
@@ -837,6 +858,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&ecc_pubkey, &ecc_sig)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let result = verifier.verify_header(&header, &header_info);
         assert_eq!(
@@ -859,6 +881,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&owner_ecc_pubkey, &owner_ecc_sig)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let result = verifier.verify_header(&header, &header_info);
         assert_eq!(
@@ -887,6 +910,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&owner_ecc_pubkey, &owner_ecc_sig)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let result = verifier.verify_header(&header, &header_info);
         assert_eq!(
@@ -911,6 +935,7 @@ mod tests {
         let header_info: HeaderInfo = HeaderInfo {
             vendor_ecc_pub_key_idx: 0,
             vendor_lms_pub_key_idx: 0,
+            vendor_pub_key_revocation: Default::default(),
             vendor_info: (&VENDOR_ECC_PUBKEY, &VENDOR_ECC_SIG),
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&owner_ecc_pubkey, &owner_ecc_sig)),
@@ -941,6 +966,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&owner_ecc_pubkey, &owner_ecc_sig)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let result = verifier.verify_header(&header, &header_info);
         assert_eq!(
@@ -967,6 +993,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&owner_ecc_pubkey, &owner_ecc_sig)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let result = verifier.verify_header(&header, &header_info);
         assert_eq!(
@@ -992,6 +1019,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&OWNER_ECC_PUBKEY, &owner_ecc_sig)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let result = verifier.verify_header(&header, &header_info);
         assert_eq!(
@@ -1020,6 +1048,7 @@ mod tests {
             vendor_lms_info: (&vendor_lms_pubkey(), &vendor_lms_sig()),
             owner_info: Some((&OWNER_ECC_PUBKEY, &OWNER_ECC_SIG)),
             owner_pub_keys_digest: ImageDigest::default(),
+            vendor_pub_key_revocation: Default::default(),
         };
         let toc_info = verifier.verify_header(&header, &header_info).unwrap();
         assert_eq!(toc_info.len, 100);
@@ -1293,7 +1322,7 @@ mod tests {
 
         let result = verifier.verify_fmc(&verify_info, ResetReason::ColdReset);
         assert!(result.is_ok());
-        let info = result.unwrap();
+        let (info, _log_info) = result.unwrap();
         assert_eq!(info.load_addr, 0x40000000);
         assert_eq!(info.entry_point, 0x40000000);
         assert_eq!(info.svn, 1);
@@ -1328,7 +1357,7 @@ mod tests {
         };
         let result = verifier.verify_runtime(&verify_info);
         assert!(result.is_ok());
-        let info = result.unwrap();
+        let (info, _log_info) = result.unwrap();
         assert_eq!(info.load_addr, 0x40000000);
         assert_eq!(info.entry_point, 0x40000000);
         assert_eq!(info.svn, 1);
diff --git a/rom/dev/src/flow/cold_reset/fmc_alias.rs b/rom/dev/src/flow/cold_reset/fmc_alias.rs
index 921a12fe..9e035cab 100644
--- a/rom/dev/src/flow/cold_reset/fmc_alias.rs
+++ b/rom/dev/src/flow/cold_reset/fmc_alias.rs
@@ -18,11 +18,13 @@ use super::dice::{DiceInput, DiceLayer, DiceOutput};
 use super::x509::X509;
 use crate::flow::cold_reset::{copy_tbs, TbsType};
 use crate::flow::cold_reset::{KEY_ID_CDI, KEY_ID_FMC_PRIV_KEY};
+use crate::fuse::log_fuse_data;
 use crate::print::HexBytes;
 use crate::rom_env::RomEnv;
 use crate::verifier::RomImageVerificationEnv;
 use crate::{cprint, cprintln, pcr};
 use caliptra_common::dice;
+use caliptra_common::fuse::FuseLogEntryId;
 use caliptra_common::RomBootStatus::*;
 use caliptra_drivers::{
     okref, report_boot_status, Array4x12, CaliptraResult, ColdResetEntry4, ColdResetEntry48,
@@ -31,7 +33,7 @@ use caliptra_drivers::{
 };
 use caliptra_error::CaliptraError;
 use caliptra_image_types::{ImageManifest, IMAGE_BYTE_SIZE};
-use caliptra_image_verify::{ImageVerificationInfo, ImageVerifier};
+use caliptra_image_verify::{ImageVerificationInfo, ImageVerificationLogInfo, ImageVerifier};
 use caliptra_x509::{FmcAliasCertTbs, FmcAliasCertTbsParams, NotAfter, NotBefore};
 use core::mem::ManuallyDrop;
 use zerocopy::{AsBytes, FromBytes};
@@ -75,6 +77,8 @@ impl DiceLayer for FmcAliasLayer {
         let info = Self::verify_image(&mut venv, manifest, txn.dlen());
         let info = okref(&info)?;
 
+        Self::update_fuse_log(&info.log_info)?;
+
         // populate data vault
         Self::populate_data_vault(venv.data_vault, info);
 
@@ -239,12 +243,63 @@ impl FmcAliasLayer {
 
         cprintln!(
             "[afmc] Image verified using Vendor ECC Key Index {}",
-            info.vendor_ecc_pub_key_idx
+            info.vendor_ecc_pub_key_idx,
         );
         report_boot_status(FmcAliasImageVerificationComplete.into());
         Ok(info)
     }
 
+    fn update_fuse_log(log_info: &ImageVerificationLogInfo) -> CaliptraResult<()> {
+        // Log VendorPubKeyIndex
+        log_fuse_data(
+            FuseLogEntryId::VendorPubKeyIndex,
+            log_info.vendor_ecc_pub_key_idx.as_bytes(),
+        )?;
+
+        // Log VendorPubKeyRevocation
+        log_fuse_data(
+            FuseLogEntryId::VendorPubKeyRevocation,
+            log_info.fuse_vendor_pub_key_revocation.bits().as_bytes(),
+        )?;
+
+        // Log ManifestFmcSvn
+        log_fuse_data(
+            FuseLogEntryId::ManifestFmcSvn,
+            log_info.fmc_log_info.manifest_svn.as_bytes(),
+        )?;
+
+        // Log ManifestFmcMinSvn
+        log_fuse_data(
+            FuseLogEntryId::ManifestFmcMinSvn,
+            log_info.fmc_log_info.manifest_min_svn.as_bytes(),
+        )?;
+
+        // Log FuseFmcSvn
+        log_fuse_data(
+            FuseLogEntryId::FuseFmcSvn,
+            log_info.fmc_log_info.fuse_svn.as_bytes(),
+        )?;
+
+        // Log ManifestRtSvn
+        log_fuse_data(
+            FuseLogEntryId::ManifestRtSvn,
+            log_info.rt_log_info.manifest_svn.as_bytes(),
+        )?;
+
+        // Log ManifestRtMinSvn
+        log_fuse_data(
+            FuseLogEntryId::ManifestRtMinSvn,
+            log_info.rt_log_info.manifest_min_svn.as_bytes(),
+        )?;
+
+        // Log FuseRtSvn
+        log_fuse_data(
+            FuseLogEntryId::FuseRtSvn,
+            log_info.rt_log_info.fuse_svn.as_bytes(),
+        )?;
+        Ok(())
+    }
+
     /// Load the image to ICCM & DCCM
     ///
     /// # Arguments
diff --git a/rom/dev/src/fuse.rs b/rom/dev/src/fuse.rs
new file mode 100644
index 00000000..b54ce10a
--- /dev/null
+++ b/rom/dev/src/fuse.rs
@@ -0,0 +1,55 @@
+/*++
+Licensed under the Apache-2.0 license.
+
+File Name:
+
+    fuse.rs
+
+Abstract:
+
+    The file contains Fuse-related Implementations.
+
+--*/
+use caliptra_common::{FuseLogEntry, FuseLogEntryId};
+use caliptra_drivers::{CaliptraError, CaliptraResult};
+use core::mem::size_of;
+use zerocopy::AsBytes;
+
+const FUSE_LOG_SIZE: usize = 1024;
+
+extern "C" {
+    static mut FUSE_LOG_ORG: [FuseLogEntry; FUSE_LOG_SIZE / size_of::<FuseLogEntry>()];
+}
+
+/// Log Fuse data
+///
+/// # Arguments
+/// * `entry_id` - log entry ID
+/// * `data` -  data To log to the fuse log
+///
+/// # Return Value
+/// * `Ok(())` - Success
+/// * `Err(GlobalErr::FuseLogInvalidEntryId)` - Invalid Fuse log entry ID
+/// * `Err(GlobalErr::FuseLogUpsupportedDataLength)` - Unsupported data length
+///
+pub fn log_fuse_data(entry_id: FuseLogEntryId, data: &[u8]) -> CaliptraResult<()> {
+    if entry_id == FuseLogEntryId::Invalid {
+        return Err(CaliptraError::ROM_GLOBAL_FUSE_LOG_INVALID_ENTRY_ID);
+    }
+
+    if data.len() > 4 {
+        return Err(CaliptraError::ROM_GLOBAL_FUSE_LOG_UNSUPPORTED_DATA_LENGTH);
+    }
+
+    // Create a FUSE log entry
+    let mut log_entry = FuseLogEntry {
+        entry_id: entry_id as u32,
+        ..Default::default()
+    };
+    log_entry.log_data.as_bytes_mut()[..data.len()].copy_from_slice(data);
+
+    let dst = unsafe { &mut FUSE_LOG_ORG[..] };
+    dst[entry_id as usize - 1] = log_entry;
+
+    Ok(())
+}
diff --git a/rom/dev/src/main.rs b/rom/dev/src/main.rs
index 6bbcc6bf..86d99945 100644
--- a/rom/dev/src/main.rs
+++ b/rom/dev/src/main.rs
@@ -26,6 +26,7 @@ core::arch::global_asm!(include_str!("start.S"));
 mod exception;
 mod fht;
 mod flow;
+mod fuse;
 mod kat;
 mod lock;
 mod pcr;
diff --git a/rom/dev/src/rom.ld b/rom/dev/src/rom.ld
index fa202ecb..4756a613 100644
--- a/rom/dev/src/rom.ld
+++ b/rom/dev/src/rom.ld
@@ -26,6 +26,7 @@ FHT_ORG          = 0x50002000;
 LDEVID_TBS_ORG   = 0x50003000;
 FMCALIAS_TBS_ORG = 0x50003400;
 PCR_LOG_ORG      = 0x50003800;
+FUSE_LOG_ORG     = 0x50003C00;
 DATA_ORG         = 0x50004000;
 STACK_ORG        = 0x5001C000;
 ESTACK_ORG       = 0x5001F800;
@@ -46,7 +47,8 @@ MAN2_SIZE         = 4K;
 FHT_SIZE          = 4K;
 LDEVID_TBS_SIZE   = 1K;
 FMCALIAS_TBS_SIZE = 1K;
-PCR_LOG_SIZE      = 2K;
+PCR_LOG_SIZE      = 1K;
+FUSE_LOG_SIZE     = 1K;
 DATA_SIZE         = 96K;
 STACK_SIZE        = 14K;
 ESTACK_SIZE       = 1K;
@@ -63,6 +65,7 @@ MEMORY
 	LDEVID_TBS   (rw) : ORIGIN = LDEVID_TBS_ORG,   LENGTH = LDEVID_TBS_SIZE
 	FMCALIAS_TBS (rw) : ORIGIN = FMCALIAS_TBS_ORG, LENGTH = FMCALIAS_TBS_SIZE
 	PCR_LOG      (rw) : ORIGIN = PCR_LOG_ORG,      LENGTH = PCR_LOG_SIZE
+	FUSE_LOG     (rw) : ORIGIN = FUSE_LOG_ORG,     LENGTH = FUSE_LOG_SIZE
 	DATA         (rw) : ORIGIN = DATA_ORG,         LENGTH = DATA_SIZE
 	STACK        (rw) : ORIGIN = STACK_ORG,        LENGTH = STACK_SIZE
 	ESTACK       (rw) : ORIGIN = ESTACK_ORG,       LENGTH = ESTACK_SIZE


Test patch:
diff --git a/rom/dev/tests/test_fmcalias_derivation.rs b/rom/dev/tests/test_fmcalias_derivation.rs
index a3e8ab18..a4efbe02 100644
--- a/rom/dev/tests/test_fmcalias_derivation.rs
+++ b/rom/dev/tests/test_fmcalias_derivation.rs
@@ -1,8 +1,8 @@
 // Licensed under the Apache-2.0 license
 
 use caliptra_builder::{FwId, ImageOptions, APP_WITH_UART, ROM_WITH_UART};
-use caliptra_common::PcrLogEntry;
-use caliptra_common::PcrLogEntryId::*;
+use caliptra_common::{FuseLogEntry, FuseLogEntryId};
+use caliptra_common::{PcrLogEntry, PcrLogEntryId};
 use caliptra_hw_model::{BootParams, Fuses, HwModel, InitParams, ModelError, SecurityState};
 use caliptra_image_fake_keys::VENDOR_CONFIG_KEY_1;
 use caliptra_image_gen::ImageGenerator;
@@ -122,7 +122,7 @@ fn test_pcr_log() {
     // Check PCR entry for DeviceLifecycle.
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, DeviceLifecycle as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::DeviceLifecycle as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     let device_lifecycle = hw
         .soc_ifc()
@@ -135,7 +135,7 @@ fn test_pcr_log() {
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, DebugLocked as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::DebugLocked as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     let debug_locked = hw.soc_ifc().cptra_security_state().read().debug_locked();
     assert_eq!((pcr_log_entry.pcr_data[0] as u8) != 0, debug_locked);
@@ -144,7 +144,7 @@ fn test_pcr_log() {
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, AntiRollbackDisabled as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::AntiRollbackDisabled as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     let anti_rollback_disable = hw.soc_ifc().fuse_anti_rollback_disable().read().dis();
     assert_eq!(
@@ -156,7 +156,7 @@ fn test_pcr_log() {
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, VendorPubKeyHash as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::VendorPubKeyHash as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     helpers::change_dword_endianess(vendor_pubkey_digest.as_bytes_mut());
     assert_eq!(pcr_log_entry.pcr_data, vendor_pubkey_digest);
@@ -165,7 +165,7 @@ fn test_pcr_log() {
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, OwnerPubKeyHash as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::OwnerPubKeyHash as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     helpers::change_dword_endianess(owner_pubkey_digest.as_bytes_mut());
     assert_eq!(pcr_log_entry.pcr_data, owner_pubkey_digest);
@@ -174,7 +174,7 @@ fn test_pcr_log() {
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, VendorPubKeyIndex as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::VendorPubKeyIndex as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     assert_eq!(
         pcr_log_entry.pcr_data[0] as u8,
@@ -185,14 +185,146 @@ fn test_pcr_log() {
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, FmcTci as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::FmcTci as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
 
     // Check PCR entry for FmcSvn.
     pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
     let pcr_log_entry =
         PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, FmcSvn as u16);
+    assert_eq!(pcr_log_entry.id, PcrLogEntryId::FmcSvn as u16);
     assert_eq!(pcr_log_entry.pcr_id, 0);
     assert_eq!(pcr_log_entry.pcr_data[0] as u8, FMC_SVN as u8);
 }
+
+#[test]
+fn test_fuse_log() {
+    const FMC_SVN: u32 = 4;
+    const FMC_MIN_SVN: u32 = 2;
+
+    let fuses = Fuses {
+        anti_rollback_disable: true,
+        fmc_key_manifest_svn: 0x0F,  // Value of FMC_SVN
+        runtime_svn: [0xF, 0, 0, 0], // Value of RT_SVN
+        ..Default::default()
+    };
+
+    pub const TEST_FMC_WITH_UART: FwId = FwId {
+        crate_name: "caliptra-rom-test-fmc",
+        bin_name: "caliptra-rom-test-fmc",
+        features: &["emu"],
+    };
+
+    let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
+    let mut hw = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            security_state: SecurityState::from(fuses.life_cycle as u32),
+            ..Default::default()
+        },
+        fuses,
+        fw_image: None,
+    })
+    .unwrap();
+
+    let image_options = ImageOptions {
+        vendor_config: VENDOR_CONFIG_KEY_1,
+        fmc_svn: FMC_SVN,
+        fmc_min_svn: FMC_MIN_SVN,
+        app_svn: FMC_SVN,
+        app_min_svn: FMC_MIN_SVN,
+        ..Default::default()
+    };
+    let image_bundle =
+        caliptra_builder::build_and_sign_image(&TEST_FMC_WITH_UART, &APP_WITH_UART, image_options)
+            .unwrap();
+
+    assert!(hw
+        .upload_firmware(&image_bundle.to_bytes().unwrap())
+        .is_ok());
+
+    hw.step_until_output_contains("[exit] Launching FMC")
+        .unwrap();
+
+    let result = hw.mailbox_execute(0x1000_0002, &[]);
+    assert!(result.is_ok());
+
+    let fuse_entry_arr = result.unwrap().unwrap();
+
+    let mut fuse_log_entry_offset = 0;
+
+    // Check entry for VendorPubKeyIndex.
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+
+    assert_eq!(
+        fuse_log_entry.entry_id,
+        FuseLogEntryId::VendorPubKeyIndex as u32
+    );
+
+    assert_eq!(fuse_log_entry.log_data[0], VENDOR_CONFIG_KEY_1.ecc_key_idx);
+
+    // Validate that the ID is VendorPubKeyRevocation
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(
+        fuse_log_entry.entry_id,
+        FuseLogEntryId::VendorPubKeyRevocation as u32
+    );
+    assert_eq!(fuse_log_entry.log_data[0], 0,);
+
+    // Validate the ManifestFmcSvn
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(
+        fuse_log_entry.entry_id,
+        FuseLogEntryId::ManifestFmcSvn as u32
+    );
+    assert_eq!(fuse_log_entry.log_data[0], FMC_SVN);
+
+    // Validate the ManifestFmcMinSvn
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(
+        fuse_log_entry.entry_id,
+        FuseLogEntryId::ManifestFmcMinSvn as u32
+    );
+    assert_eq!(fuse_log_entry.log_data[0], FMC_MIN_SVN);
+
+    // Validate the FuseFmcSvn
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(fuse_log_entry.entry_id, FuseLogEntryId::FuseFmcSvn as u32);
+    assert_eq!(fuse_log_entry.log_data[0], FMC_SVN);
+
+    // Validate the ManifestRtSvn
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(
+        fuse_log_entry.entry_id,
+        FuseLogEntryId::ManifestRtSvn as u32
+    );
+    assert_eq!(fuse_log_entry.log_data[0], FMC_SVN);
+
+    // Validate the ManifestRtMinSvn
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(
+        fuse_log_entry.entry_id,
+        FuseLogEntryId::ManifestRtMinSvn as u32
+    );
+    assert_eq!(fuse_log_entry.log_data[0], FMC_MIN_SVN);
+
+    // Validate the FuseRtSvn
+    fuse_log_entry_offset += core::mem::size_of::<FuseLogEntry>();
+    let fuse_log_entry =
+        FuseLogEntry::read_from_prefix(fuse_entry_arr[fuse_log_entry_offset..].as_bytes()).unwrap();
+    assert_eq!(fuse_log_entry.entry_id, FuseLogEntryId::FuseRtSvn as u32);
+    assert_eq!(fuse_log_entry.log_data[0], FMC_SVN);
+}
diff --git a/rom/dev/tools/test-fmc/src/fmc.ld b/rom/dev/tools/test-fmc/src/fmc.ld
index e77b5acc..ef1bbd4a 100644
--- a/rom/dev/tools/test-fmc/src/fmc.ld
+++ b/rom/dev/tools/test-fmc/src/fmc.ld
@@ -25,6 +25,7 @@ FHT_ORG          = 0x50002000;
 LDEVID_TBS_ORG   = 0x50003000;
 FMCALIAS_TBS_ORG = 0x50003400;
 PCR_LOG_ORG      = 0x50003800;
+FUSE_LOG_ORG     = 0x50003C00;
 DATA_ORG         = 0x50004000;
 STACK_ORG        = 0x5001C000;
 ESTACK_ORG       = 0x5001F800;
@@ -38,7 +39,8 @@ MAN2_SIZE         = 4K;
 FHT_SIZE          = 4K;
 LDEVID_TBS_SIZE   = 1K;
 FMCALIAS_TBS_SIZE = 1K;
-PCR_LOG_SIZE      = 2K;
+PCR_LOG_SIZE      = 1K;
+FUSE_LOG_SIZE     = 1K;
 DATA_SIZE         = 96K;
 STACK_SIZE        = 14K;
 ESTACK_SIZE       = 1K;
@@ -54,6 +56,7 @@ MEMORY
 	LDEVID_TBS   (rw) : ORIGIN = LDEVID_TBS_ORG,   LENGTH = LDEVID_TBS_SIZE
 	FMCALIAS_TBS (rw) : ORIGIN = FMCALIAS_TBS_ORG, LENGTH = FMCALIAS_TBS_SIZE
 	PCR_LOG      (rw) : ORIGIN = PCR_LOG_ORG,      LENGTH = PCR_LOG_SIZE
+	FUSE_LOG     (rw) : ORIGIN = FUSE_LOG_ORG,     LENGTH = FUSE_LOG_SIZE
 	DATA         (rw) : ORIGIN = DATA_ORG,         LENGTH = DATA_SIZE
 	STACK        (rw) : ORIGIN = STACK_ORG,        LENGTH = STACK_SIZE
 	ESTACK       (rw) : ORIGIN = ESTACK_ORG,       LENGTH = ESTACK_SIZE
diff --git a/rom/dev/tools/test-fmc/src/main.rs b/rom/dev/tools/test-fmc/src/main.rs
index df6fdf73..621bab5f 100644
--- a/rom/dev/tools/test-fmc/src/main.rs
+++ b/rom/dev/tools/test-fmc/src/main.rs
@@ -18,9 +18,11 @@ extern "C" {
     static mut LDEVID_TBS_ORG: u8;
     static mut FMCALIAS_TBS_ORG: u8;
     static mut PCR_LOG_ORG: u8;
+    static mut FUSE_LOG_ORG: u8;
 }
 
 use caliptra_common::FirmwareHandoffTable;
+use caliptra_common::{FuseLogEntry, FuseLogEntryId};
 use caliptra_common::{PcrLogEntry, PcrLogEntryId};
 use caliptra_drivers::DataVault;
 use caliptra_drivers::Mailbox;
@@ -214,9 +216,11 @@ fn process_mailbox_commands() {
         0x1000_0001 => {
             // TODO: Generate certs
         }
-        _ => {
-            // No-op
+
+        0x1000_0002 => {
+            read_fuse_log(&mbox);
         }
+        _ => {}
     }
 }
 
@@ -256,3 +260,55 @@ fn read_pcr_log(mbox: &caliptra_registers::mbox::RegisterBlock<RealMmioMut>) {
     });
     mbox.status().write(|w| w.status(|w| w.data_ready()));
 }
+
+fn read_fuse_log(mbox: &caliptra_registers::mbox::RegisterBlock<RealMmioMut>) {
+    let mut fuse_entry_count = 0;
+    loop {
+        let fuse_entry = get_fuse_entry(fuse_entry_count);
+        if FuseLogEntryId::from(fuse_entry.entry_id) == FuseLogEntryId::Invalid {
+            break;
+        }
+
+        fuse_entry_count += 1;
+        let fuse_entry_bytes = fuse_entry.as_bytes();
+        let word_size = core::mem::size_of::<u32>();
+        let remainder = fuse_entry_bytes.len() % word_size;
+        let n = fuse_entry_bytes.len() - remainder;
+
+        for idx in (0..n).step_by(word_size) {
+            mbox.datain().write(|_| {
+                u32::from_le_bytes(fuse_entry_bytes[idx..idx + word_size].try_into().unwrap())
+            });
+        }
+
+        if remainder > 0 {
+            let mut last_word = fuse_entry_bytes[n] as u32;
+            for idx in 1..remainder {
+                last_word |= (fuse_entry_bytes[n + idx] as u32) << (idx << 3);
+            }
+            mbox.datain().write(|_| last_word);
+        }
+    }
+
+    mbox.dlen().write(|_| {
+        (core::mem::size_of::<FuseLogEntry>() * fuse_entry_count)
+            .try_into()
+            .unwrap()
+    });
+    mbox.status().write(|w| w.status(|w| w.data_ready()));
+}
+
+fn get_fuse_entry(entry_index: usize) -> FuseLogEntry {
+    // Copy the Fuse log entry from DCCM
+    let mut fuse_entry: [u8; core::mem::size_of::<FuseLogEntry>()] =
+        [0u8; core::mem::size_of::<FuseLogEntry>()];
+
+    let src = unsafe {
+        let offset = core::mem::size_of::<FuseLogEntry>() * entry_index;
+        let ptr = (&mut FUSE_LOG_ORG as *mut u8).add(offset);
+        core::slice::from_raw_parts_mut(ptr, core::mem::size_of::<FuseLogEntry>())
+    };
+
+    fuse_entry.copy_from_slice(src);
+    FuseLogEntry::read_from_prefix(fuse_entry.as_bytes()).unwrap()
+}