[mask_rom] Alert Handler Driver

The alert handler driver allows assigning individual alerts to alert
classes and configuring the escalations for each alert class.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/device/lib/base/macros.h b/sw/device/lib/base/macros.h
index 5abe723..8989077 100644
--- a/sw/device/lib/base/macros.h
+++ b/sw/device/lib/base/macros.h
@@ -15,6 +15,11 @@
  */
 
 /**
+ * A annotation that a switch/case fallthrough is the intended behavior.
+ */
+#define FALLTHROUGH_INTENDED __attribute__((fallthrough))
+
+/**
  * A variable-argument macro that expands to the number of arguments passed into
  * it, between 0 and 31 arguments.
  *
diff --git a/sw/device/silicon_creator/lib/drivers/alert.c b/sw/device/silicon_creator/lib/drivers/alert.c
new file mode 100644
index 0000000..0ff4881
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/alert.c
@@ -0,0 +1,216 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/alert.h"
+
+#include "sw/device/lib/base/macros.h"
+#include "sw/device/silicon_creator/lib/base/abs_mmio.h"
+#include "sw/device/silicon_creator/lib/error.h"
+
+#include "alert_handler_regs.h"
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+enum {
+  kBase = TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR,
+};
+
+rom_error_t alert_configure(size_t index, alert_class_t cls,
+                            alert_enable_t enabled) {
+  if (index >= ALERT_HANDLER_ALERT_CLASS_MULTIREG_COUNT) {
+    return kErrorAlertBadIndex;
+  }
+  index *= 4;
+
+  switch (cls) {
+    case kAlertClassA:
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_CLASS_0_REG_OFFSET + index,
+                       ALERT_HANDLER_ALERT_CLASS_0_CLASS_A_0_VALUE_CLASSA);
+      break;
+    case kAlertClassB:
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_CLASS_0_REG_OFFSET + index,
+                       ALERT_HANDLER_ALERT_CLASS_0_CLASS_A_0_VALUE_CLASSB);
+      break;
+    case kAlertClassC:
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_CLASS_0_REG_OFFSET + index,
+                       ALERT_HANDLER_ALERT_CLASS_0_CLASS_A_0_VALUE_CLASSC);
+      break;
+    case kAlertClassD:
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_CLASS_0_REG_OFFSET + index,
+                       ALERT_HANDLER_ALERT_CLASS_0_CLASS_A_0_VALUE_CLASSD);
+      break;
+    case kAlertClassX:
+      return kErrorOk;
+    default:
+      return kErrorAlertBadClass;
+  }
+
+  switch (enabled) {
+    case kAlertEnableNone:
+      break;
+    case kAlertEnableLocked:
+      // Enable, then lock.
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_EN_0_REG_OFFSET + index, 1);
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_REGWEN_0_REG_OFFSET + index,
+                       0);
+      break;
+    case kAlertEnableEnabled:
+      abs_mmio_write32(kBase + ALERT_HANDLER_ALERT_EN_0_REG_OFFSET + index, 1);
+      break;
+    default:
+      return kErrorAlertBadEnable;
+  }
+
+  return kErrorOk;
+}
+
+rom_error_t alert_local_configure(size_t index, alert_class_t cls,
+                                  alert_enable_t enabled) {
+  if (index >= ALERT_HANDLER_LOC_ALERT_CLASS_MULTIREG_COUNT) {
+    return kErrorAlertBadIndex;
+  }
+  index *= 4;
+
+  switch (cls) {
+    case kAlertClassA:
+      abs_mmio_write32(
+          kBase + ALERT_HANDLER_LOC_ALERT_CLASS_0_REG_OFFSET + index,
+          ALERT_HANDLER_LOC_ALERT_CLASS_0_CLASS_LA_0_VALUE_CLASSA);
+      break;
+    case kAlertClassB:
+      abs_mmio_write32(
+          kBase + ALERT_HANDLER_LOC_ALERT_CLASS_0_REG_OFFSET + index,
+          ALERT_HANDLER_LOC_ALERT_CLASS_0_CLASS_LA_0_VALUE_CLASSB);
+      break;
+    case kAlertClassC:
+      abs_mmio_write32(
+          kBase + ALERT_HANDLER_LOC_ALERT_CLASS_0_REG_OFFSET + index,
+          ALERT_HANDLER_LOC_ALERT_CLASS_0_CLASS_LA_0_VALUE_CLASSC);
+      break;
+    case kAlertClassD:
+      abs_mmio_write32(
+          kBase + ALERT_HANDLER_LOC_ALERT_CLASS_0_REG_OFFSET + index,
+          ALERT_HANDLER_LOC_ALERT_CLASS_0_CLASS_LA_0_VALUE_CLASSD);
+      break;
+    case kAlertClassX:
+      return kErrorOk;
+    default:
+      return kErrorAlertBadClass;
+  }
+
+  switch (enabled) {
+    case kAlertEnableNone:
+      break;
+    case kAlertEnableLocked:
+      // Enable, then lock.
+      abs_mmio_write32(kBase + ALERT_HANDLER_LOC_ALERT_EN_0_REG_OFFSET + index,
+                       1);
+      abs_mmio_write32(
+          kBase + ALERT_HANDLER_LOC_ALERT_REGWEN_0_REG_OFFSET + index, 0);
+      break;
+    case kAlertEnableEnabled:
+      abs_mmio_write32(kBase + ALERT_HANDLER_LOC_ALERT_EN_0_REG_OFFSET + index,
+                       1);
+      break;
+    default:
+      return kErrorAlertBadEnable;
+  }
+
+  return kErrorOk;
+}
+
+rom_error_t alert_class_configure(alert_class_t cls,
+                                  const alert_class_config_t *config) {
+  uint32_t offset = 0;
+  uint32_t reg = 0;
+
+  // Each escalation signal should be asserted in its corresponding phase.
+  reg = bitfield_field32_write(reg, ALERT_HANDLER_CLASSA_CTRL_MAP_E0_FIELD, 0);
+  reg = bitfield_field32_write(reg, ALERT_HANDLER_CLASSA_CTRL_MAP_E1_FIELD, 1);
+  reg = bitfield_field32_write(reg, ALERT_HANDLER_CLASSA_CTRL_MAP_E2_FIELD, 2);
+  reg = bitfield_field32_write(reg, ALERT_HANDLER_CLASSA_CTRL_MAP_E3_FIELD, 3);
+
+  // All of the alert class register blocks are identical but at different
+  // offsets.  We'll treat everything like Class A, but add in the offset
+  // to the other classes.
+  switch (cls) {
+    case kAlertClassA:
+      offset = ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET -
+               ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET;
+      break;
+    case kAlertClassB:
+      offset = ALERT_HANDLER_CLASSB_CTRL_REG_OFFSET -
+               ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET;
+      break;
+    case kAlertClassC:
+      offset = ALERT_HANDLER_CLASSC_CTRL_REG_OFFSET -
+               ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET;
+      break;
+    case kAlertClassD:
+      offset = ALERT_HANDLER_CLASSD_CTRL_REG_OFFSET -
+               ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET;
+      break;
+    case kAlertClassX:
+    default:
+      return kErrorAlertBadClass;
+  }
+  switch (config->enabled) {
+    case kAlertEnableLocked:
+      reg = bitfield_bit32_write(reg, ALERT_HANDLER_CLASSA_CTRL_LOCK_BIT, true);
+      FALLTHROUGH_INTENDED;
+    case kAlertEnableEnabled:
+      reg = bitfield_bit32_write(reg, ALERT_HANDLER_CLASSA_CTRL_EN_BIT, true);
+      FALLTHROUGH_INTENDED;
+    case kAlertEnableNone:
+      break;
+    default:
+      return kErrorAlertBadEnable;
+  }
+  switch (config->escalation) {
+    case kAlertEscalatePhase3:
+      reg =
+          bitfield_bit32_write(reg, ALERT_HANDLER_CLASSA_CTRL_EN_E3_BIT, true);
+      FALLTHROUGH_INTENDED;
+    case kAlertEscalatePhase2:
+      reg =
+          bitfield_bit32_write(reg, ALERT_HANDLER_CLASSA_CTRL_EN_E2_BIT, true);
+      FALLTHROUGH_INTENDED;
+    case kAlertEscalatePhase1:
+      reg =
+          bitfield_bit32_write(reg, ALERT_HANDLER_CLASSA_CTRL_EN_E1_BIT, true);
+      FALLTHROUGH_INTENDED;
+    case kAlertEscalatePhase0:
+      reg =
+          bitfield_bit32_write(reg, ALERT_HANDLER_CLASSA_CTRL_EN_E0_BIT, true);
+      FALLTHROUGH_INTENDED;
+    case kAlertEscalateNone:
+      break;
+    default:
+      return kErrorAlertBadEscalation;
+  }
+
+  abs_mmio_write32(kBase + ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET + offset, reg);
+  abs_mmio_write32(
+      kBase + ALERT_HANDLER_CLASSA_ACCUM_THRESH_REG_OFFSET + offset,
+      config->accum_threshold);
+  abs_mmio_write32(kBase + ALERT_HANDLER_CLASSA_TIMEOUT_CYC_REG_OFFSET + offset,
+                   config->timeout_cycles);
+  for (size_t i = 0; i < 4; ++i) {
+    abs_mmio_write32(
+        kBase + ALERT_HANDLER_CLASSA_PHASE0_CYC_REG_OFFSET + offset + i * 4,
+        config->phase_cycles[i]);
+  }
+  if (config->enabled == kAlertEnableLocked) {
+    // Lock the alert configuration if it is configured to be locked.
+    abs_mmio_write32(kBase + ALERT_HANDLER_CLASSA_REGWEN_REG_OFFSET + offset,
+                     0);
+  }
+  return kErrorOk;
+}
+
+rom_error_t alert_ping_enable(void) {
+  // Enable the ping timer, then lock it.
+  abs_mmio_write32(kBase + ALERT_HANDLER_PING_TIMER_EN_REG_OFFSET, 1);
+  abs_mmio_write32(kBase + ALERT_HANDLER_PING_TIMER_REGWEN_REG_OFFSET, 0);
+  return kErrorOk;
+}
diff --git a/sw/device/silicon_creator/lib/drivers/alert.h b/sw/device/silicon_creator/lib/drivers/alert.h
new file mode 100644
index 0000000..a6d43ea
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/alert.h
@@ -0,0 +1,123 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_ALERT_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_ALERT_H_
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sw/device/silicon_creator/lib/error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ALERT_CLASSES 4
+
+// Note: the AlertClass values need to map to a byte.
+/**
+ * Alert Classification Values as stored in OTP.
+ */
+typedef enum AlertClass {
+  /***
+   * Alert class X is a special class which means "not configured"
+   */
+  kAlertClassX,
+  kAlertClassA,
+  kAlertClassB,
+  kAlertClassC,
+  kAlertClassD,
+} alert_class_t;
+
+// Note: the AlertEnable values need to map to a byte.
+/**
+ * Alert Enable Values as stored in OTP.
+ */
+typedef enum AlertEnable {
+  kAlertEnableNone,
+  kAlertEnableEnabled,
+  kAlertEnableLocked,
+} alert_enable_t;
+
+/**
+ * Alert Escalation Policy as stored in OTP.  Note that each phase implies the
+ * prior phases are also enabled.
+ */
+typedef enum AlertEscalate {
+  kAlertEscalateNone,
+  kAlertEscalatePhase0,
+  kAlertEscalatePhase1,
+  kAlertEscalatePhase2,
+  kAlertEscalatePhase3,
+} alert_escalate_t;
+
+/**
+ * Alert class configuration struct.
+ */
+typedef struct AlertClassConfig {
+  /**
+   * Whether or not this alert class enabled.
+   */
+  alert_enable_t enabled;
+  /**
+   * The escalation configuration for this alert class.
+   */
+  alert_escalate_t escalation;
+  /**
+   * The accumlation threshold for this alert class.
+   */
+  uint32_t accum_threshold;
+  /**
+   * The timeout cycles for this alert class.
+   */
+  uint32_t timeout_cycles;
+  /**
+   * The phase cycles for this alert class.
+   */
+  uint32_t phase_cycles[4];
+} alert_class_config_t;
+
+/**
+ * Configure a single alert.
+ *
+ * Configures and optionally lock an alert's class configuration.
+ *
+ * @param index: The alert number.
+ * @param cls: Class of the alert.
+ * @param enabled: Whether or not to enable and/or lock the alert.
+ */
+rom_error_t alert_configure(size_t index, alert_class_t cls,
+                            alert_enable_t enabled);
+
+/**
+ * Configure a single local alert.
+ *
+ * Configures and optionally lock a local alert's class configuration.
+ *
+ * @param index: The local alert number.
+ * @param cls: Class of the alert.
+ * @param enabled: Whether or not to enable and/or lock the alert.
+ */
+rom_error_t alert_local_configure(size_t index, alert_class_t cls,
+                                  alert_enable_t enabled);
+/**
+ * Configure an alert class.
+ *
+ * Configures an alert class to the specified config.
+ *
+ * @param cls: Class of the alert (alert class X is not permitted here).
+ * @param config: The alert class' configuration.
+ */
+rom_error_t alert_class_configure(alert_class_t cls,
+                                  const alert_class_config_t *config);
+
+/**
+ * Enable the ping timer mechanism.
+ */
+rom_error_t alert_ping_enable(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_ALERT_H_
diff --git a/sw/device/silicon_creator/lib/drivers/alert_functest.c b/sw/device/silicon_creator/lib/drivers/alert_functest.c
new file mode 100644
index 0000000..337a418
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/alert_functest.c
@@ -0,0 +1,103 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/runtime/print.h"
+#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/rstmgr.h"
+#include "sw/device/silicon_creator/lib/error.h"
+#include "sw/device/silicon_creator/lib/test_main.h"
+
+#include "alert_handler_regs.h"
+#include "flash_ctrl_regs.h"
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "otp_ctrl_regs.h"
+#include "rstmgr_regs.h"
+
+// The reset reason value is really a bitfield. The power-on-reset indicator
+// is defined by rstmgr_regs.h.
+#define RESET_REASON_POR (1 << RSTMGR_RESET_INFO_POR_BIT)
+// FIXME: I don't know where the HW_REQ field of the reset reason register
+// is defined.  I observe a value of 4 for alerts.
+#define RESET_REASON_ALERT \
+  ((4 << RSTMGR_RESET_INFO_HW_REQ_OFFSET) | RESET_REASON_POR)
+
+enum {
+  kAlertBase = TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR,
+  kOtpBase = TOP_EARLGREY_OTP_CTRL_BASE_ADDR,
+  kFlashBase = TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR,
+};
+
+rom_error_t alert_no_escalate_test(void) {
+  // Configure class B alerts for phase 0 only and disable NMI signalling.
+  alert_class_config_t config = {
+      .enabled = kAlertEnableEnabled,
+      .escalation = kAlertEscalatePhase0,
+      .accum_threshold = 0,
+      .timeout_cycles = 0,
+      .phase_cycles = {1, 10, 100, 1000},
+  };
+  LOG_INFO("Configure OtpCtrlFatalMacroError as class B");
+  RETURN_IF_ERROR(alert_configure(kTopEarlgreyAlertIdOtpCtrlFatalMacroError,
+                                  kAlertClassB, kAlertEnableLocked));
+  LOG_INFO("Configure class B alerts");
+  RETURN_IF_ERROR(alert_class_configure(kAlertClassB, &config));
+  LOG_INFO("Generate alert via test regs");
+  abs_mmio_write32(kOtpBase + OTP_CTRL_ALERT_TEST_REG_OFFSET, 1);
+  uint32_t count =
+      abs_mmio_read32(kAlertBase + ALERT_HANDLER_CLASSB_ACCUM_CNT_REG_OFFSET);
+  return count == 1 ? kErrorOk : kErrorUnknown;
+}
+
+rom_error_t alert_escalate_test(void) {
+  // Configure class A alerts for full escalation.
+  alert_class_config_t config = {
+      .enabled = kAlertEnableEnabled,
+      .escalation = kAlertEscalatePhase3,
+      .accum_threshold = 0,
+      .timeout_cycles = 0,
+      .phase_cycles = {1, 10, 100, 1000},
+  };
+
+  LOG_INFO("Configure FlashCtrlFatalIntgErr as class A");
+  RETURN_IF_ERROR(alert_configure(kTopEarlgreyAlertIdFlashCtrlFatalIntgErr,
+                                  kAlertClassA, kAlertEnableEnabled));
+  LOG_INFO("Configure class A alerts");
+  RETURN_IF_ERROR(alert_class_configure(kAlertClassA, &config));
+  LOG_INFO("Generate alert via test regs");
+  abs_mmio_write32(kFlashBase + FLASH_CTRL_ALERT_TEST_REG_OFFSET, 8);
+  return kErrorUnknown;
+}
+
+const test_config_t kTestConfig;
+
+bool test_main(void) {
+  rom_error_t result = kErrorOk;
+  uint32_t reason = rstmgr_reason_get();
+  rstmgr_alert_info_enable();
+
+  LOG_INFO("reset_info = %08x", reason);
+  if (reason == RESET_REASON_POR) {
+    EXECUTE_TEST(result, alert_no_escalate_test);
+    EXECUTE_TEST(result, alert_escalate_test);
+    // We should never get here - the escalate test should cause a reset
+    // and we should see a reset reason of 0x21.
+    LOG_ERROR("Test failure: should have reset before this line.");
+    result = kErrorUnknown;
+  } else if (reason == RESET_REASON_ALERT) {
+    LOG_INFO("Detected reset after escalation test");
+  } else {
+    LOG_ERROR("Unknown reset reason");
+    result = kErrorUnknown;
+  }
+  return result == kErrorOk;
+}
diff --git a/sw/device/silicon_creator/lib/drivers/alert_unittest.cc b/sw/device/silicon_creator/lib/drivers/alert_unittest.cc
new file mode 100644
index 0000000..97bcd0b
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/alert_unittest.cc
@@ -0,0 +1,265 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/alert.h"
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/silicon_creator/lib/base/mock_abs_mmio.h"
+
+#include "alert_handler_regs.h"  // Generated.
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+namespace alert_unittest {
+namespace {
+using testing::Each;
+using testing::Eq;
+using testing::Test;
+
+class AlertTest : public mask_rom_test::MaskRomTest {
+ protected:
+  uint32_t base_ = TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR;
+  mask_rom_test::MockAbsMmio mmio_;
+};
+
+class InitTest : public AlertTest {};
+
+TEST_F(InitTest, AlertConfigureAlertBadIndex) {
+  EXPECT_EQ(alert_configure(ALERT_HANDLER_ALERT_CLASS_MULTIREG_COUNT,
+                            kAlertClassA, kAlertEnableNone),
+            kErrorAlertBadIndex);
+}
+
+TEST_F(InitTest, AlertConfigureAlertBadClass) {
+  EXPECT_EQ(alert_configure(0, (alert_class_t)-1, kAlertEnableNone),
+            kErrorAlertBadClass);
+}
+
+TEST_F(InitTest, AlertConfigureAlertBadEnable) {
+  // We expect the alert to get configured as class A, but then to
+  // experience an error when evaluating the enable parameter.
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_CLASS_0_REG_OFFSET, 0);
+  EXPECT_EQ(alert_configure(0, kAlertClassA, (alert_enable_t)-1),
+            kErrorAlertBadEnable);
+}
+
+TEST_F(InitTest, AlertConfigureAlertClassXNoOperation) {
+  EXPECT_EQ(alert_configure(0, kAlertClassX, kAlertEnableNone), kErrorOk);
+}
+
+TEST_F(InitTest, LocalAlertConfigureAlertClassXNoOperation) {
+  EXPECT_EQ(alert_local_configure(0, kAlertClassX, kAlertEnableNone), kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigure0AsClassA) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_CLASS_0_REG_OFFSET, 0);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_EN_0_REG_OFFSET, 1);
+  EXPECT_EQ(alert_configure(0, kAlertClassA, kAlertEnableEnabled), kErrorOk);
+}
+
+TEST_F(InitTest, LocalAlertConfigure0AsClassA) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_CLASS_0_REG_OFFSET,
+                     0);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_EN_0_REG_OFFSET, 1);
+  EXPECT_EQ(alert_local_configure(0, kAlertClassA, kAlertEnableEnabled),
+            kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigure1AsClassB) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_CLASS_1_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_EN_1_REG_OFFSET, 1);
+  EXPECT_EQ(alert_configure(1, kAlertClassB, kAlertEnableEnabled), kErrorOk);
+}
+
+TEST_F(InitTest, LocalAlertConfigure1AsClassB) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_CLASS_1_REG_OFFSET,
+                     1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_EN_1_REG_OFFSET, 1);
+  EXPECT_EQ(alert_local_configure(1, kAlertClassB, kAlertEnableEnabled),
+            kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigure2AsClassC) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_CLASS_2_REG_OFFSET, 2);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_EN_2_REG_OFFSET, 1);
+  EXPECT_EQ(alert_configure(2, kAlertClassC, kAlertEnableEnabled), kErrorOk);
+}
+
+TEST_F(InitTest, LocalAlertConfigure2AsClassC) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_CLASS_2_REG_OFFSET,
+                     2);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_EN_2_REG_OFFSET, 1);
+  EXPECT_EQ(alert_local_configure(2, kAlertClassC, kAlertEnableEnabled),
+            kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigure3AsClassDLocked) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_CLASS_3_REG_OFFSET, 3);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_EN_3_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_ALERT_REGWEN_3_REG_OFFSET, 0);
+  EXPECT_EQ(alert_configure(3, kAlertClassD, kAlertEnableLocked), kErrorOk);
+}
+
+TEST_F(InitTest, LocalAlertConfigure3AsClassDLocked) {
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_CLASS_3_REG_OFFSET,
+                     3);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_EN_3_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_LOC_ALERT_REGWEN_3_REG_OFFSET,
+                     0);
+  EXPECT_EQ(alert_local_configure(3, kAlertClassD, kAlertEnableLocked),
+            kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigureClassXBadClass) {
+  alert_class_config_t config{};
+  EXPECT_EQ(alert_class_configure(kAlertClassX, &config), kErrorAlertBadClass);
+}
+
+TEST_F(InitTest, AlertConfigureClassA) {
+  alert_class_config_t config = {
+      .enabled = kAlertEnableLocked,
+      .escalation = kAlertEscalatePhase3,
+      .accum_threshold = 1,
+      .timeout_cycles = 2,
+      .phase_cycles = {1, 10, 100, 1000},
+  };
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_CTRL_REG_OFFSET,
+                     {
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_LOCK_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E3_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E2_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E1_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E0_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E0_OFFSET, 0},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E1_OFFSET, 1},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E2_OFFSET, 2},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E3_OFFSET, 3},
+                     });
+  EXPECT_ABS_WRITE32(mmio_,
+                     base_ + ALERT_HANDLER_CLASSA_ACCUM_THRESH_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_TIMEOUT_CYC_REG_OFFSET,
+                     2);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_PHASE0_CYC_REG_OFFSET,
+                     1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_PHASE1_CYC_REG_OFFSET,
+                     10);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_PHASE2_CYC_REG_OFFSET,
+                     100);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_PHASE3_CYC_REG_OFFSET,
+                     1000);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSA_REGWEN_REG_OFFSET, 0);
+  EXPECT_EQ(alert_class_configure(kAlertClassA, &config), kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigureClassB) {
+  alert_class_config_t config = {
+      .enabled = kAlertEnableEnabled,
+      .escalation = kAlertEscalatePhase2,
+      .accum_threshold = 1,
+      .timeout_cycles = 2,
+      .phase_cycles = {1, 10, 100, 1000},
+  };
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSB_CTRL_REG_OFFSET,
+                     {
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_LOCK_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E3_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E2_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E1_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E0_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E0_OFFSET, 0},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E1_OFFSET, 1},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E2_OFFSET, 2},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E3_OFFSET, 3},
+                     });
+  EXPECT_ABS_WRITE32(mmio_,
+                     base_ + ALERT_HANDLER_CLASSB_ACCUM_THRESH_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSB_TIMEOUT_CYC_REG_OFFSET,
+                     2);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSB_PHASE0_CYC_REG_OFFSET,
+                     1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSB_PHASE1_CYC_REG_OFFSET,
+                     10);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSB_PHASE2_CYC_REG_OFFSET,
+                     100);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSB_PHASE3_CYC_REG_OFFSET,
+                     1000);
+  EXPECT_EQ(alert_class_configure(kAlertClassB, &config), kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigureClassC) {
+  alert_class_config_t config = {
+      .enabled = kAlertEnableEnabled,
+      .escalation = kAlertEscalatePhase1,
+      .accum_threshold = 1,
+      .timeout_cycles = 2,
+      .phase_cycles = {1, 10, 100, 1000},
+  };
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSC_CTRL_REG_OFFSET,
+                     {
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_LOCK_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E3_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E2_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E1_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E0_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E0_OFFSET, 0},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E1_OFFSET, 1},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E2_OFFSET, 2},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E3_OFFSET, 3},
+                     });
+  EXPECT_ABS_WRITE32(mmio_,
+                     base_ + ALERT_HANDLER_CLASSC_ACCUM_THRESH_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSC_TIMEOUT_CYC_REG_OFFSET,
+                     2);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSC_PHASE0_CYC_REG_OFFSET,
+                     1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSC_PHASE1_CYC_REG_OFFSET,
+                     10);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSC_PHASE2_CYC_REG_OFFSET,
+                     100);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSC_PHASE3_CYC_REG_OFFSET,
+                     1000);
+  EXPECT_EQ(alert_class_configure(kAlertClassC, &config), kErrorOk);
+}
+
+TEST_F(InitTest, AlertConfigureClassD) {
+  alert_class_config_t config = {
+      .enabled = kAlertEnableEnabled,
+      .escalation = kAlertEscalateNone,
+      .accum_threshold = 1,
+      .timeout_cycles = 2,
+      .phase_cycles = {1, 10, 100, 1000},
+  };
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSD_CTRL_REG_OFFSET,
+                     {
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_BIT, true},
+                         {ALERT_HANDLER_CLASSA_CTRL_LOCK_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E3_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E2_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E1_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_EN_E0_BIT, false},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E0_OFFSET, 0},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E1_OFFSET, 1},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E2_OFFSET, 2},
+                         {ALERT_HANDLER_CLASSA_CTRL_MAP_E3_OFFSET, 3},
+                     });
+  EXPECT_ABS_WRITE32(mmio_,
+                     base_ + ALERT_HANDLER_CLASSD_ACCUM_THRESH_REG_OFFSET, 1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSD_TIMEOUT_CYC_REG_OFFSET,
+                     2);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSD_PHASE0_CYC_REG_OFFSET,
+                     1);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSD_PHASE1_CYC_REG_OFFSET,
+                     10);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSD_PHASE2_CYC_REG_OFFSET,
+                     100);
+  EXPECT_ABS_WRITE32(mmio_, base_ + ALERT_HANDLER_CLASSD_PHASE3_CYC_REG_OFFSET,
+                     1000);
+  EXPECT_EQ(alert_class_configure(kAlertClassD, &config), kErrorOk);
+}
+
+}  // namespace
+}  // namespace alert_unittest
diff --git a/sw/device/silicon_creator/lib/drivers/meson.build b/sw/device/silicon_creator/lib/drivers/meson.build
index 318e65a..86ee4cd 100644
--- a/sw/device/silicon_creator/lib/drivers/meson.build
+++ b/sw/device/silicon_creator/lib/drivers/meson.build
@@ -192,3 +192,57 @@
   ),
   suite: 'mask_rom',
 )
+
+# Mask ROM alert handler driver
+sw_silicon_creator_lib_driver_alert = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_driver_alert',
+    sources: [
+      hw_ip_alert_handler_reg_h,
+      'alert.c',
+    ],
+    dependencies: [
+      sw_silicon_creator_lib_base_abs_mmio,
+    ],
+  ),
+)
+
+test('sw_silicon_creator_lib_driver_alert_unittest', executable(
+    'sw_silicon_creator_lib_driver_alert_unittest',
+    sources: [
+      'alert_unittest.cc',
+      hw_ip_alert_handler_reg_h,
+      'alert.c',
+    ],
+    dependencies: [
+      sw_vendor_gtest,
+      sw_silicon_creator_lib_base_mock_abs_mmio,
+    ],
+    native: true,
+    c_args: ['-DMOCK_ABS_MMIO'],
+    cpp_args: ['-DMOCK_ABS_MMIO'],
+  ),
+  suite: 'mask_rom',
+)
+
+sw_silicon_creator_lib_driver_alert_functest = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_driver_alert_functest',
+    sources: [
+      hw_ip_rstmgr_reg_h,
+      hw_ip_otp_ctrl_reg_h,
+      hw_ip_alert_handler_reg_h,
+      hw_ip_flash_ctrl_reg_h,
+      'alert_functest.c'
+    ],
+    dependencies: [
+      sw_silicon_creator_lib_driver_alert,
+      sw_silicon_creator_lib_driver_rstmgr,
+    ],
+  ),
+)
+mask_rom_tests += {
+  'sw_silicon_creator_lib_driver_alert_functest': {
+    'library': sw_silicon_creator_lib_driver_alert_functest,
+  }
+}
diff --git a/sw/device/silicon_creator/lib/drivers/mock_alert.h b/sw/device/silicon_creator/lib/drivers/mock_alert.h
new file mode 100644
index 0000000..897346a
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/mock_alert.h
@@ -0,0 +1,57 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_MOCK_ALERT_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_MOCK_ALERT_H_
+
+#include "sw/device/lib/testing/mask_rom_test.h"
+#include "sw/device/silicon_creator/lib/drivers/alert.h"
+
+namespace mask_rom_test {
+namespace internal {
+
+/**
+ * Mock class for alert.c.
+ */
+class MockAlert {
+ public:
+  MOCK_METHOD(rom_error_t, alert_configure,
+              (size_t, alert_class_t, alert_enable_t));
+  MOCK_METHOD(rom_error_t, alert_local_configure,
+              (size_t, alert_class_t, alert_enable_t));
+  MOCK_METHOD(rom_error_t, alert_class_configure,
+              (alert_class_t, const alert_class_config_t *));
+  MOCK_METHOD(rom_error_t, alert_ping_enable, ());
+  virtual ~MockAlert() {}
+};
+
+}  // namespace internal
+
+using MockAlert = GlobalMock<testing::StrictMock<internal::MockAlert>>;
+
+extern "C" {
+
+rom_error_t alert_configure(size_t index, alert_class_t cls,
+                            alert_enable_t enabled) {
+  return MockAlert::Instance().alert_configure(index, cls, enabled);
+}
+
+rom_error_t alert_local_configure(size_t index, alert_class_t cls,
+                                  alert_enable_t enabled) {
+  return MockAlert::Instance().alert_local_configure(index, cls, enabled);
+}
+
+rom_error_t alert_class_configure(alert_class_t cls,
+                                  const alert_class_config_t *config) {
+  return MockAlert::Instance().alert_class_configure(cls, config);
+}
+
+rom_error_t alert_ping_enable(void) {
+  return MockAlert::Instance().alert_ping_enable();
+}
+
+}  // extern "C"
+}  // namespace mask_rom_test
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_MOCK_ALERT_H_
diff --git a/sw/device/silicon_creator/lib/error.h b/sw/device/silicon_creator/lib/error.h
index 1db127a..222a6d6 100644
--- a/sw/device/silicon_creator/lib/error.h
+++ b/sw/device/silicon_creator/lib/error.h
@@ -21,12 +21,13 @@
  */
 enum module_ {
   kModuleUnknown = 0,
-  kModuleUart = 0x4155,         // ASCII "UA".
-  kModuleHmac = 0x4d48,         // ASCII "HM".
-  kModuleSigverify = 0x5653,    // ASCII "SV".
-  kModuleKeymgr = 0x4d4b,       // ASCII "KM".
-  kModuleManifest = 0x414d,     // ASCII "MA".
-  kModuleRomextimage = 0x4552,  // ASCII "RE".
+  kModuleAlertHandler = 0x4148,  // ASCII "AH".
+  kModuleUart = 0x4155,          // ASCII "UA".
+  kModuleHmac = 0x4d48,          // ASCII "HM".
+  kModuleSigverify = 0x5653,     // ASCII "SV".
+  kModuleKeymgr = 0x4d4b,        // ASCII "KM".
+  kModuleManifest = 0x414d,      // ASCII "MA".
+  kModuleRomextimage = 0x4552,   // ASCII "RE".
 };
 
 /**
@@ -54,6 +55,10 @@
   X(kErrorManifestInternal,           ERROR_(1, kModuleManifest, kInternal)), \
   X(kErrorRomextimageInvalidArgument, ERROR_(1, kModuleRomextimage, kInvalidArgument)), \
   X(kErrorRomextimageInternal,        ERROR_(2, kModuleRomextimage, kInternal)), \
+  X(kErrorAlertBadIndex,              ERROR_(1, kModuleAlertHandler, kInvalidArgument)), \
+  X(kErrorAlertBadClass,              ERROR_(2, kModuleAlertHandler, kInvalidArgument)), \
+  X(kErrorAlertBadEnable,             ERROR_(3, kModuleAlertHandler, kInvalidArgument)), \
+  X(kErrorAlertBadEscalation,         ERROR_(4, kModuleAlertHandler, kInvalidArgument)), \
   X(kErrorUnknown, 0xFFFFFFFF)
 // clang-format on
 
@@ -82,5 +87,4 @@
 #ifdef __cplusplus
 }
 #endif
-
 #endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_ERROR_H_