[sw/silicon_creator] Use OTP library in shutdown module

1. Follow the redaction policy in #7353 (no redaction in RMA and
   test unlocked lifecycle states).
2. Use the OTP library to read from OTP.

The OTP and lifecycle libraries use sec_mmio underneath the hood.
Since sec_mmio can trigger a call to shutdown_finalize we need to
avoid calling it from shutdown_finalize. To acheive this a new
shutdown_redact_policy function has been added that determines
the redaction policy to apply without using either library.

This change required an increase in the number of registers that
`sec_mmio_ctx` has capacity to track. I've bumped it up to 200 for
now.

Signed-off-by: Michael Munday <mike.munday@lowrisc.org>
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio.c b/sw/device/silicon_creator/lib/base/sec_mmio.c
index 486d42d..c9192b6 100644
--- a/sw/device/silicon_creator/lib/base/sec_mmio.c
+++ b/sw/device/silicon_creator/lib/base/sec_mmio.c
@@ -4,6 +4,8 @@
 
 #include "sw/device/silicon_creator/lib/base/sec_mmio.h"
 
+#include <assert.h>
+
 #include "sw/device/lib/base/memory.h"
 #include "sw/device/silicon_creator/lib/base/abs_mmio.h"
 
@@ -14,12 +16,17 @@
 // FIXME: Replace for shutdown module handler.
 static sec_mmio_shutdown_handler sec_mmio_shutdown_cb;
 
-// Value with good hamming weight used to mask the stored expected value.
-static const uint32_t kSecMmioMaskVal = 0x21692436u;
+enum {
+  // Value with good hamming weight used to mask the stored expected value.
+  kSecMmioMaskVal = 0x21692436u,
 
-// This must be set to a prime number greater than the number of items in
-// `sec_mmio_ctx.addrs`. Used to generate random read order permutations.
-static const uint32_t kSecMmioRndStep = 103u;
+  // This must be set to a prime number greater than the number of items in
+  // `sec_mmio_ctx.addrs`. Used to generate random read order permutations.
+  kSecMmioRndStep = 211u,
+};
+
+static_assert((uint32_t)kSecMmioRndStep > (uint32_t)kSecMmioRegFileSize,
+              "kSecMmioRndStep not large enough");
 
 /**
  * Updates or inserts the register entry pointed to by MMIO `addr` with the
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio.h b/sw/device/silicon_creator/lib/base/sec_mmio.h
index 18d648d..e63f039 100644
--- a/sw/device/silicon_creator/lib/base/sec_mmio.h
+++ b/sw/device/silicon_creator/lib/base/sec_mmio.h
@@ -61,7 +61,7 @@
    * This value must be less than the `kSecMmioRndStep` in sec_mmio.c.
    */
   // TODO(#6609): Update size of expectations table.
-  kSecMmioRegFileSize = 100,
+  kSecMmioRegFileSize = 200,
 };
 
 /**
@@ -109,12 +109,12 @@
  * to both boot stages.
  */
 OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, values, 0);
-OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, addrs, 400);
-OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, last_index, 800);
-OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, write_count, 804);
-OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, expected_write_count, 808);
-OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, check_count, 812);
-OT_ASSERT_SIZE(sec_mmio_ctx_t, 816);  // Checked by linker script.
+OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, addrs, 800);
+OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, last_index, 1600);
+OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, write_count, 1604);
+OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, expected_write_count, 1608);
+OT_ASSERT_MEMBER_OFFSET(sec_mmio_ctx_t, check_count, 1612);
+OT_ASSERT_SIZE(sec_mmio_ctx_t, 1616);  // Checked by linker script.
 
 /**
  * Shutdown module callback handler.
diff --git a/sw/device/silicon_creator/lib/base/static_critical.ld b/sw/device/silicon_creator/lib/base/static_critical.ld
index 03e7dc3..80b169d 100644
--- a/sw/device/silicon_creator/lib/base/static_critical.ld
+++ b/sw/device/silicon_creator/lib/base/static_critical.ld
@@ -11,4 +11,4 @@
 ASSERT(. == ORIGIN(ram_main), "Error: .static_critical section not at the base address of main RAM.");
 ASSERT(. - ADDR(.static_critical) == 0, "Error: .static_critical.sec_mmio_ctx section offset has changed.");
 KEEP(*(.static_critical.sec_mmio_ctx))
-ASSERT(. - ADDR(.static_critical) == 816, "Error: .static_critical.sec_mmio_ctx section size has changed");
+ASSERT(. - ADDR(.static_critical) == 1616, "Error: .static_critical.sec_mmio_ctx section size has changed");
diff --git a/sw/device/silicon_creator/lib/meson.build b/sw/device/silicon_creator/lib/meson.build
index cec69ec..4a4449f 100644
--- a/sw/device/silicon_creator/lib/meson.build
+++ b/sw/device/silicon_creator/lib/meson.build
@@ -69,6 +69,7 @@
     sources: [
       hw_ip_alert_handler_reg_h,
       hw_ip_otp_ctrl_reg_h,
+      hw_ip_lc_ctrl_reg_h,
       hw_ip_keymgr_reg_h,
       hw_ip_sram_ctrl_reg_h,
       hw_ip_flash_ctrl_reg_h,
@@ -120,6 +121,7 @@
     'sw_silicon_creator_lib_shutdown_unittest',
     sources: [
       hw_ip_alert_handler_reg_h,
+      hw_ip_lc_ctrl_reg_h,
       hw_ip_otp_ctrl_reg_h,
       hw_ip_keymgr_reg_h,
       hw_ip_sram_ctrl_reg_h,
diff --git a/sw/device/silicon_creator/lib/shutdown.c b/sw/device/silicon_creator/lib/shutdown.c
index 170aec2..d4546bb 100644
--- a/sw/device/silicon_creator/lib/shutdown.c
+++ b/sw/device/silicon_creator/lib/shutdown.c
@@ -14,11 +14,13 @@
 #include "sw/device/silicon_creator/lib/base/abs_mmio.h"
 #include "sw/device/silicon_creator/lib/drivers/alert.h"
 #include "sw/device/silicon_creator/lib/drivers/lifecycle.h"
+#include "sw/device/silicon_creator/lib/drivers/otp.h"
 
 #include "alert_handler_regs.h"
 #include "flash_ctrl_regs.h"
 #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
 #include "keymgr_regs.h"
+#include "lc_ctrl_regs.h"
 #include "otp_ctrl_regs.h"
 #include "sram_ctrl_regs.h"
 
@@ -46,16 +48,6 @@
   static ALWAYS_INLINE modifiers_ void name_
 #endif
 
-// TODO: use the real OTP driver after it's converted to abs_mmio.
-#ifdef OT_OFF_TARGET_TEST
-extern uint32_t otp_read32(uint32_t address);
-#else
-inline uint32_t otp_read32(uint32_t address) {
-  return abs_mmio_read32(TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR +
-                         OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + address);
-}
-#endif
-
 // Convert the alert class to an index.
 // This is required because I expect to change the constant definitions in
 // alert_class_t to have reasonable hamming distances.
@@ -174,7 +166,13 @@
   return error;
 }
 
-uint32_t shutdown_redact(rom_error_t reason, shutdown_error_redact_t severity) {
+/**
+ * Implementation of `shutdown_redact` that is guaranteed to be inlined.
+ *
+ * This function must be inlined because it is called from `shutdown_finalize`.
+ */
+static ALWAYS_INLINE uint32_t
+shutdown_redact_inline(rom_error_t reason, shutdown_error_redact_t severity) {
   uint32_t redacted = (uint32_t)reason;
   if (reason == kErrorOk) {
     return 0;
@@ -196,6 +194,70 @@
   return redacted;
 }
 
+uint32_t shutdown_redact(rom_error_t reason, shutdown_error_redact_t severity) {
+  return shutdown_redact_inline(reason, severity);
+}
+
+/**
+ * Implementation of `shutdown_redact_policy` that is guaranteed to be inlined.
+ *
+ * This function must be inlined because it is called from `shutdown_finalize`.
+ */
+static ALWAYS_INLINE shutdown_error_redact_t
+shutdown_redact_policy_inline(void) {
+  // Determine the error code redaction policy to apply according to the
+  // lifecycle state and OTP configuration.
+  //
+  // Note that we cannot use the lifecycle or OTP libraries since an error
+  // may trigger a call to `shutdown_finalize`.
+  lifecycle_state_t lc_state = (lifecycle_state_t)bitfield_field32_read(
+      abs_mmio_read32(TOP_EARLGREY_LC_CTRL_BASE_ADDR +
+                      LC_CTRL_LC_STATE_REG_OFFSET),
+      LC_CTRL_LC_STATE_STATE_FIELD);
+  switch (lc_state) {
+    case kLcStateRaw:
+    case kLcStateTestUnlocked0:
+    case kLcStateTestUnlocked1:
+    case kLcStateTestUnlocked2:
+    case kLcStateTestUnlocked3:
+    case kLcStateTestUnlocked4:
+    case kLcStateTestUnlocked5:
+    case kLcStateTestUnlocked6:
+    case kLcStateTestUnlocked7:
+    case kLcStateRma:
+      // No error redaction in RAW, TEST_UNLOCKED and RMA states.
+      return kShutdownErrorRedactNone;
+    case kLcStateProd:
+    case kLcStateProdEnd:
+    case kLcStateDev:
+      // In production states use the redaction level specified in OTP.
+      return (shutdown_error_redact_t)abs_mmio_read32(
+          TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR +
+          OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET +
+          OTP_CTRL_PARAM_ROM_ERROR_REPORTING_OFFSET);
+    default:
+      // Redact everything if in an unexpected lifecycle state.
+      return kShutdownErrorRedactAll;
+  }
+}
+
+shutdown_error_redact_t shutdown_redact_policy(void) {
+  return shutdown_redact_policy_inline();
+}
+
+SHUTDOWN_FUNC(NO_MODIFIERS, shutdown_report_error(rom_error_t reason)) {
+  // Call the inline variant of `shutdown_redact_policy` because we want to
+  // guarantee that we won't jump to a different function.
+  shutdown_error_redact_t policy = shutdown_redact_policy_inline();
+
+  // Call the inline variant of `shutdown_redact` because we want to guarantee
+  // that we won't jump to a different function.
+  uint32_t redacted_error = shutdown_redact_inline(reason, policy);
+
+  // TODO(lowRISC/opentitan#8236): base_printf is in the .text section.
+  base_printf("boot_fault: 0x%08x\n", redacted_error);
+}
+
 SHUTDOWN_FUNC(NO_MODIFIERS, shutdown_software_escalate(void)) {
   // TODO(lowRISC/opentitan#7148): Use a software alert when available.
   // For now, signal a fatal_intg_error from SRAM.
@@ -265,12 +327,7 @@
 __attribute__((section(".shutdown")))
 #endif
 void shutdown_finalize(rom_error_t reason) {
-  uint32_t redacted_error = shutdown_redact(
-      reason, otp_read32(OTP_CTRL_PARAM_ROM_ERROR_REPORTING_OFFSET));
-
-  // TODO(lowRISC/opentitan#8236): base_printf is in the .text section.
-  base_printf("boot_fault: 0x%08x\n", redacted_error);
-
+  shutdown_report_error(reason);
   shutdown_software_escalate();
   shutdown_keymgr_kill();
   shutdown_flash_kill();
diff --git a/sw/device/silicon_creator/lib/shutdown.h b/sw/device/silicon_creator/lib/shutdown.h
index d79c309..044ff64 100644
--- a/sw/device/silicon_creator/lib/shutdown.h
+++ b/sw/device/silicon_creator/lib/shutdown.h
@@ -7,6 +7,7 @@
 #include <stdint.h>
 #include <stdnoreturn.h>
 
+#include "sw/device/lib/base/macros.h"
 #include "sw/device/silicon_creator/lib/drivers/lifecycle.h"
 #include "sw/device/silicon_creator/lib/error.h"
 #ifdef __cplusplus
@@ -19,6 +20,8 @@
  * Reads the shutdown policy from OTP, and initializes the alert handler.
  *
  * @param lc_state: Lifecycle state of the chip.
+ * @param[out] redaction Redaction level initialized according to the lifecycle
+ * state and OTP configuration.
  * @return: Any error encountered during initialization.
  */
 rom_error_t shutdown_init(lifecycle_state_t lc_state);
@@ -42,6 +45,15 @@
 } shutdown_error_redact_t;
 
 /**
+ * Calculate the error redaction level required given the current lifecycle
+ * state and OTP configuration.
+ *
+ * @return Redaction level to apply to error codes.
+ */
+OT_WARN_UNUSED_RESULT
+shutdown_error_redact_t shutdown_redact_policy(void);
+
+/**
  * Redact an error code.
  *
  * @param reason: The error code to be redacted.
@@ -53,7 +65,7 @@
 /**
  * Perform a shutdown in the Mask ROM in response to an exceptional condition.
  *
- * @param reason: A reason for entering the shutdown state.
+ * @param reason A reason for entering the shutdown state.
  */
 #ifndef OT_OFF_TARGET_TEST
 // If this is a test, we'll omit `noreturn` so we can call this function
diff --git a/sw/device/silicon_creator/lib/shutdown_unittest.cc b/sw/device/silicon_creator/lib/shutdown_unittest.cc
index 5d94cb0..102bdf2 100644
--- a/sw/device/silicon_creator/lib/shutdown_unittest.cc
+++ b/sw/device/silicon_creator/lib/shutdown_unittest.cc
@@ -16,6 +16,7 @@
 #include "alert_handler_regs.h"
 #include "flash_ctrl_regs.h"
 #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "lc_ctrl_regs.h"
 #include "otp_ctrl_regs.h"
 
 // FIXME: I can't get ARRAYSIZE from `memory.h` because the definitions of
@@ -40,6 +41,7 @@
 // Create a mock for shutdown functions.
 class MockShutdown : public ::mask_rom_test::GlobalMock<MockShutdown> {
  public:
+  MOCK_METHOD(void, shutdown_report_error, (rom_error_t));
   MOCK_METHOD(void, shutdown_software_escalate, ());
   MOCK_METHOD(void, shutdown_keymgr_kill, ());
   MOCK_METHOD(void, shutdown_flash_kill, ());
@@ -52,7 +54,9 @@
 }  // namespace internal
 using MockShutdown = testing::StrictMock<internal::MockShutdown>;
 extern "C" {
-
+void shutdown_report_error(rom_error_t error) {
+  return MockShutdown::Instance().shutdown_report_error(error);
+}
 void shutdown_software_escalate(void) {
   return MockShutdown::Instance().shutdown_software_escalate();
 }
@@ -488,6 +492,55 @@
   EXPECT_EQ(shutdown_init(kLcStateRma), kErrorOk);
 }
 
+TEST_F(ShutdownTest, RedactPolicyManufacturing) {
+  // Devices in manufacturing or RMA states should not redact errors regardless
+  // of the redaction level set by OTP.
+  constexpr auto kManufacturingStates = std::array<lifecycle_state_t, 10>{
+      kLcStateRaw,           kLcStateTestUnlocked0,
+      kLcStateTestUnlocked1, kLcStateTestUnlocked2,
+      kLcStateTestUnlocked3, kLcStateTestUnlocked4,
+      kLcStateTestUnlocked5, kLcStateTestUnlocked6,
+      kLcStateTestUnlocked7, kLcStateRma};
+  for (const auto state : kManufacturingStates) {
+    EXPECT_ABS_READ32(
+        TOP_EARLGREY_LC_CTRL_BASE_ADDR + LC_CTRL_LC_STATE_REG_OFFSET,
+        static_cast<uint32_t>(state));
+    EXPECT_EQ(shutdown_redact_policy(), kShutdownErrorRedactNone);
+  }
+}
+
+TEST_F(ShutdownTest, RedactPolicyProduction) {
+  // Production states should read redaction level from OTP.
+  constexpr auto kProductionStates = std::array<lifecycle_state_t, 3>{
+      kLcStateProd, kLcStateProdEnd, kLcStateDev};
+  for (const auto state : kProductionStates) {
+    EXPECT_ABS_READ32(
+        TOP_EARLGREY_LC_CTRL_BASE_ADDR + LC_CTRL_LC_STATE_REG_OFFSET,
+        static_cast<uint32_t>(state));
+    EXPECT_ABS_READ32(TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR +
+                          OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET +
+                          OTP_CTRL_PARAM_ROM_ERROR_REPORTING_OFFSET,
+                      static_cast<uint32_t>(kShutdownErrorRedactModule));
+    EXPECT_EQ(shutdown_redact_policy(), kShutdownErrorRedactModule);
+  }
+}
+
+TEST_F(ShutdownTest, RedactPolicyInvalid) {
+  // Invalid states should result in the highest redaction level regardless of
+  // the redaction level set by OTP.
+  constexpr auto kInvalidStates = std::array<lifecycle_state_t, 11>{
+      kLcStateTestLocked0, kLcStateTestLocked1, kLcStateTestLocked2,
+      kLcStateTestLocked3, kLcStateTestLocked4, kLcStateTestLocked5,
+      kLcStateTestLocked6, kLcStateScrap,       kLcStatePostTransition,
+      kLcStateEscalate,    kLcStateInvalid};
+  for (const auto state : kInvalidStates) {
+    EXPECT_ABS_READ32(
+        TOP_EARLGREY_LC_CTRL_BASE_ADDR + LC_CTRL_LC_STATE_REG_OFFSET,
+        static_cast<uint32_t>(state));
+    EXPECT_EQ(shutdown_redact_policy(), kShutdownErrorRedactAll);
+  }
+}
+
 TEST(ShutdownModule, RedactErrors) {
   EXPECT_EQ(shutdown_redact(kErrorOk, kShutdownErrorRedactNone), 0);
   EXPECT_EQ(shutdown_redact(kErrorOk, kShutdownErrorRedactError), 0);
@@ -506,6 +559,7 @@
 
 TEST_F(ShutdownTest, ShutdownFinalize) {
   SetupOtpReads();
+  EXPECT_CALL(shutdown_, shutdown_report_error(kErrorUnknown));
   EXPECT_CALL(shutdown_, shutdown_software_escalate());
   EXPECT_CALL(shutdown_, shutdown_keymgr_kill());
   EXPECT_CALL(shutdown_, shutdown_flash_kill());