From: Emmanuel Blot <eblot@rivosinc.com>
Signed-off-by: Lex Bailey <lex.bailey@lowrisc.org>
Includes existing MIT licenced code (already published elsewhere)
---
hw/opentitan/Kconfig | 2 +
hw/opentitan/meson.build | 1 +
hw/opentitan/ot_alert.c | 668 ++++++++++++++++++++++++++++++++
hw/opentitan/trace-events | 5 +
hw/riscv/Kconfig | 1 +
hw/riscv/ibex_common.c | 2 +-
hw/riscv/ot_earlgrey.c | 11 +-
include/hw/opentitan/ot_alert.h | 23 ++
include/hw/riscv/ibex_irq.h | 6 +-
9 files changed, 712 insertions(+), 7 deletions(-)
create mode 100644 hw/opentitan/ot_alert.c
create mode 100644 include/hw/opentitan/ot_alert.h
diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig
index 6bd1855a84..6e9aa833b4 100644
--- a/hw/opentitan/Kconfig
+++ b/hw/opentitan/Kconfig
@@ -1,2 +1,4 @@
# OpenTitan devices
+config OT_ALERT
+ bool
diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build
index 6bd1855a84..d24aae88fd 100644
--- a/hw/opentitan/meson.build
+++ b/hw/opentitan/meson.build
@@ -1,2 +1,3 @@
# OpenTitan devices
+system_ss.add(when: 'CONFIG_OT_ALERT', if_true: files('ot_alert.c'))
diff --git a/hw/opentitan/ot_alert.c b/hw/opentitan/ot_alert.c
new file mode 100644
index 0000000000..b864fda0fa
--- /dev/null
+++ b/hw/opentitan/ot_alert.c
@@ -0,0 +1,668 @@
+/*
+ * QEMU OpenTitan Alert handler device
+ *
+ * Copyright (c) 2023-2025 Rivos, Inc.
+ *
+ * Author(s):
+ * Emmanuel Blot <eblot@rivosinc.com>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/timer.h"
+#include "qemu/typedefs.h"
+#include "qapi/error.h"
+#include "hw/opentitan/ot_alert.h"
+#include "hw/core/qdev-properties-system.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/riscv/ibex_common.h"
+#include "hw/riscv/ibex_irq.h"
+#include "hw/core/sysbus.h"
+#include "trace.h"
+
+#define PARAM_N_ALERTS 65u
+#define PARAM_N_LPG 24u
+#define PARAM_N_LPG_WIDTH 5u
+#define PARAM_ESC_CNT_DW 32u
+#define PARAM_ACCU_CNT_DW 16u
+#define PARAM_N_CLASSES 4u
+#define PARAM_N_ESC_SEV 4u
+#define PARAM_N_PHASES 4u
+#define PARAM_N_LOC_ALERT 7u
+#define PARAM_PING_CNT_DW 16u
+#define PARAM_PHASE_DW 2u
+#define PARAM_CLASS_DW 2u
+
+/* clang-format off */
+REG32(INTR_STATE, 0x0u)
+ SHARED_FIELD(INTR_STATE_CLASSA, 0u, 1u)
+ SHARED_FIELD(INTR_STATE_CLASSB, 1u, 1u)
+ SHARED_FIELD(INTR_STATE_CLASSC, 2u, 1u)
+ SHARED_FIELD(INTR_STATE_CLASSD, 3u, 1u)
+REG32(INTR_ENABLE, 0x4u)
+REG32(INTR_TEST, 0x8u)
+REG32(PING_TIMER_REGWEN, 0xcu)
+ FIELD(PING_TIMER_REGWEN, EN, 0u, 1u)
+REG32(PING_TIMEOUT_CYC_SHADOWED, 0x10u)
+ FIELD(PING_TIMEOUT_CYC_SHADOWED, VAL, 0u, 16u)
+REG32(PING_TIMER_EN_SHADOWED, 0x14u)
+ FIELD(PING_TIMER_EN_SHADOWED, EN, 0u, 1u)
+REG32(ALERT_REGWEN, 0x18u)
+ SHARED_FIELD(ALERT_REGWEN_EN, 0u, 1u)
+REG32(ALERT_EN_SHADOWED, 0x11cu)
+ SHARED_FIELD(ALERT_EN_SHADOWED_EN, 0u, 1u)
+REG32(ALERT_CLASS_SHADOWED, 0x220u)
+ SHARED_FIELD(ALERT_CLASS_SHADOWED_EN, 0u, 2u)
+REG32(ALERT_CAUSE, 0x324u)
+ SHARED_FIELD(ALERT_CAUSE_EN, 0u, 1u)
+REG32(LOC_ALERT_REGWEN, 0x428u)
+ SHARED_FIELD(LOC_ALERT_REGWEN_EN, 0u, 1u)
+REG32(LOC_ALERT_EN_SHADOWED, 0x444u)
+ SHARED_FIELD(LOC_ALERT_EN_SHADOWED_EN, 0u, 1u)
+REG32(LOC_ALERT_CLASS_SHADOWED, 0x460u)
+ SHARED_FIELD(LOC_ALERT_CLASS_SHADOWED_EN, 0u, 2u)
+REG32(LOC_ALERT_CAUSE, 0x47cu)
+ SHARED_FIELD(LOC_ALERT_CAUSE_EN, 0u, 1u)
+REG32(CLASS_REGWEN, 0x498u)
+ FIELD(CLASS_REGWEN, EN, 0u, 1u)
+REG32(CLASS_CTRL_SHADOWED, 0x49cu)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_EN, 0u, 1u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_LOCK, 1u, 1u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_EN_E0, 2u, 1u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_EN_E1, 3u, 1u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_EN_E2, 4u, 1u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_EN_E3, 5u, 1u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_MAP_E0, 6u, 2u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_MAP_E1, 8u, 2u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_MAP_E2, 10u, 2u)
+ SHARED_FIELD(CLASS_CTRL_SHADOWED_MAP_E3, 12u, 2u)
+REG32(CLASS_CLR_REGWEN, 0x4a0u)
+ SHARED_FIELD(CLASS_CLR_REGWEN_EN, 0u, 1u)
+REG32(CLASS_CLR_SHADOWED, 0x4a4u)
+ SHARED_FIELD(CLASS_CLR_SHADOWED_EN, 0u, 1u)
+REG32(CLASS_ACCUM_CNT, 0x4a8u)
+ SHARED_FIELD(CLASS_ACCUM_CNT, 0u, 16u)
+REG32(CLASS_ACCUM_THRESH_SHADOWED, 0x4acu)
+ SHARED_FIELD(CLASS_ACCUM_THRESH_SHADOWED, 0u, 16u)
+REG32(CLASS_TIMEOUT_CYC_SHADOWED, 0x4b0u)
+REG32(CLASS_CRASHDUMP_TRIGGER_SHADOWED, 0x4b4u)
+ SHARED_FIELD(CLASS_CRASHDUMP_TRIGGER_SHADOWED, 0u, 2u)
+REG32(CLASS_PHASE0_CYC_SHADOWED, 0x4b8u)
+REG32(CLASS_PHASE1_CYC_SHADOWED, 0x4bcu)
+REG32(CLASS_PHASE2_CYC_SHADOWED, 0x4c0u)
+REG32(CLASS_PHASE3_CYC_SHADOWED, 0x4c4u)
+REG32(CLASS_ESC_CNT, 0x4c8u)
+REG32(CLASS_STATE, 0x4ccu)
+ FIELD(CLASS_STATE, VAL, 0u, 3u)
+/* clang-format on */
+
+enum {
+ ALERT_ID_ALERT_PINGFAIL,
+ ALERT_ID_ESC_PINGFAIL,
+ ALERT_ID_ALERT_INTEGFAIL,
+ ALERT_ID_ESC_INTEGFAIL,
+ ALERT_ID_BUS_INTEGFAIL,
+ ALERT_ID_SHADOW_REG_UPDATE_ERROR,
+ ALERT_ID_SHADOW_REG_STORAGE_ERROR,
+};
+
+enum {
+ ALERT_CLASSA,
+ ALERT_CLASSB,
+ ALERT_CLASSC,
+ ALERT_CLASSD,
+};
+
+enum {
+ STATE_IDLE,
+ STATE_TIMEOUT,
+ STATE_FSMERROR,
+ STATE_TERMINAL,
+ STATE_PHASE0,
+ STATE_PHASE1,
+ STATE_PHASE2,
+ STATE_PHASE3,
+};
+
+#define INTR_MASK ((1u << PARAM_N_CLASSES) - 1u)
+#define CLASS_CTRL_SHADOWED_MASK \
+ (CLASS_CTRL_SHADOWED_EN_MASK | CLASS_CTRL_SHADOWED_LOCK_MASK | \
+ CLASS_CTRL_SHADOWED_EN_E0_MASK | CLASS_CTRL_SHADOWED_EN_E1_MASK | \
+ CLASS_CTRL_SHADOWED_EN_E2_MASK | CLASS_CTRL_SHADOWED_EN_E3_MASK | \
+ CLASS_CTRL_SHADOWED_MAP_E0_MASK | CLASS_CTRL_SHADOWED_MAP_E1_MASK | \
+ CLASS_CTRL_SHADOWED_MAP_E2_MASK | CLASS_CTRL_SHADOWED_MAP_E3_MASK)
+
+#define R32_OFF(_r_) ((_r_) / sizeof(uint32_t))
+
+#define R_LAST_REG R32_OFF(0x574u)
+#define REGS_COUNT (R_LAST_REG + 1u)
+#define REGS_SIZE (REGS_COUNT * sizeof(uint32_t))
+
+#define ALERT_SLOT_SIZE R32_OFF(sizeof(struct alerts))
+#define LOC_ALERT_SLOT_SIZE R32_OFF(sizeof(struct loc_alerts))
+#define CLASS_SLOT_SIZE R32_OFF(sizeof(struct classes))
+#define CASE_RANGE(_reg_, _cnt_) (_reg_)...((_reg_) + (_cnt_) - (1u))
+#define CASE_STRIDE(_reg_, _cls_) ((_reg_) + (_cls_) * (CLASS_SLOT_SIZE))
+#define SLOT_OFFSET(_reg_, _base_, _kind_) \
+ (((_reg_) - (_base_)) / _kind_##_SLOT_SIZE)
+#define ALERT_SLOT(_reg_) SLOT_OFFSET(_reg_, R_ALERT_REGWEN, ALERT)
+#define LOC_ALERT_SLOT(_reg_) SLOT_OFFSET(_reg_, R_LOC_ALERT_REGWEN, LOC_ALERT)
+#define CLASS_SLOT(_reg_) SLOT_OFFSET(_reg_, R_CLASS_REGWEN, CLASS)
+
+#define CHECK_REGWEN(_reg_, _cond_) \
+ ot_alert_check_regwen(__func__, (_reg_), (_cond_))
+
+struct intr {
+ uint32_t state;
+ uint32_t enable;
+ uint32_t test;
+};
+
+struct ping {
+ uint32_t timer_regwen;
+ uint32_t timeout_cyc_shadowed;
+ uint32_t timer_en_shadowed;
+};
+
+struct alerts {
+ uint32_t regwen;
+ uint32_t en_shadowed;
+ uint32_t class_shadowed;
+ uint32_t cause;
+};
+
+struct loc_alerts {
+ uint32_t regwen;
+ uint32_t en_shadowed;
+ uint32_t class_shadowed;
+ uint32_t cause;
+};
+
+struct classes {
+ uint32_t regwen;
+ uint32_t ctrl_shadowed;
+ uint32_t clr_regwen;
+ uint32_t clr_shadowed;
+ uint32_t accum_cnt;
+ uint32_t accum_thresh_shadowed;
+ uint32_t timeout_cyc_shadowed;
+ uint32_t crashdump_trigger_shadowed;
+ uint32_t phase0_cyc_shadowed;
+ uint32_t phase1_cyc_shadowed;
+ uint32_t phase2_cyc_shadowed;
+ uint32_t phase3_cyc_shadowed;
+ uint32_t esc_cnt;
+ uint32_t state;
+};
+
+typedef struct OtAlertRegs {
+ struct intr intr;
+ struct ping ping;
+ struct alerts alerts[PARAM_N_ALERTS];
+ struct loc_alerts loc_alerts[PARAM_N_LOC_ALERT];
+ struct classes classes[PARAM_N_CLASSES];
+} OtAlertRegs;
+
+struct OtAlertState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ IbexIRQ irqs[PARAM_N_CLASSES];
+
+ OtAlertRegs *regs;
+};
+
+struct OtAlertClass {
+ SysBusDeviceClass parent_class;
+ ResettablePhases parent_phases;
+};
+
+static inline bool
+ot_alert_check_regwen(const char *func, unsigned reg, bool cond)
+{
+ if (!cond) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: reg 0x%04x is write-protected\n",
+ func, (unsigned)(reg * sizeof(uint32_t)));
+ return false;
+ }
+ return true;
+}
+
+static void ot_alert_update_irqs(OtAlertState *s)
+{
+ uint32_t level = s->regs->intr.state & s->regs->intr.enable;
+
+ for (unsigned ix = 0; ix < ARRAY_SIZE(s->irqs); ix++) {
+ ibex_irq_set(&s->irqs[ix], (int)((level >> ix) & 0x1));
+ }
+}
+
+static uint64_t ot_alert_regs_read(void *opaque, hwaddr addr, unsigned size)
+{
+ OtAlertState *s = opaque;
+ OtAlertRegs *regs = s->regs;
+ uint32_t val32;
+
+ hwaddr reg = R32_OFF(addr);
+
+ switch (reg) {
+ case R_INTR_STATE:
+ val32 = regs->intr.state;
+ break;
+ case R_INTR_ENABLE:
+ val32 = regs->intr.enable;
+ break;
+ case R_PING_TIMER_REGWEN:
+ val32 = regs->ping.timer_regwen;
+ break;
+ case R_PING_TIMEOUT_CYC_SHADOWED:
+ val32 = regs->ping.timeout_cyc_shadowed;
+ break;
+ case R_PING_TIMER_EN_SHADOWED:
+ val32 = regs->ping.timer_en_shadowed;
+ break;
+ case R_INTR_TEST:
+ qemu_log_mask(LOG_GUEST_ERROR, "W/O register 0x02%" HWADDR_PRIx "\n",
+ addr);
+ val32 = 0;
+ break;
+ case CASE_RANGE(R_ALERT_REGWEN, PARAM_N_ALERTS):
+ val32 = regs->alerts[ALERT_SLOT(reg)].regwen;
+ break;
+ case CASE_RANGE(R_ALERT_EN_SHADOWED, PARAM_N_ALERTS):
+ val32 = regs->alerts[ALERT_SLOT(reg)].en_shadowed;
+ break;
+ case CASE_RANGE(R_ALERT_CLASS_SHADOWED, PARAM_N_ALERTS):
+ val32 = regs->alerts[ALERT_SLOT(reg)].class_shadowed;
+ break;
+ case CASE_RANGE(R_ALERT_CAUSE, PARAM_N_ALERTS):
+ val32 = regs->alerts[ALERT_SLOT(reg)].cause;
+ break;
+ case CASE_RANGE(R_LOC_ALERT_REGWEN, PARAM_N_LOC_ALERT):
+ val32 = regs->loc_alerts[LOC_ALERT_SLOT(reg)].regwen;
+ break;
+ case CASE_RANGE(R_LOC_ALERT_EN_SHADOWED, PARAM_N_LOC_ALERT):
+ val32 = regs->loc_alerts[LOC_ALERT_SLOT(reg)].en_shadowed;
+ break;
+ case CASE_RANGE(R_LOC_ALERT_CLASS_SHADOWED, PARAM_N_LOC_ALERT):
+ val32 = regs->loc_alerts[LOC_ALERT_SLOT(reg)].class_shadowed;
+ break;
+ case CASE_RANGE(R_LOC_ALERT_CAUSE, PARAM_N_LOC_ALERT):
+ val32 = regs->loc_alerts[LOC_ALERT_SLOT(reg)].cause;
+ break;
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].regwen;
+ break;
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].ctrl_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].clr_regwen;
+ break;
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].clr_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].accum_cnt;
+ break;
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].accum_thresh_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].timeout_cyc_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].crashdump_trigger_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].phase0_cyc_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].phase1_cyc_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].phase2_cyc_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].phase3_cyc_shadowed;
+ break;
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSD):
+ val32 = regs->classes[CLASS_SLOT(reg)].esc_cnt;
+ break;
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSD):
+ val32 =
+ regs->classes[reg - CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSA)].state;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ val32 = 0;
+ break;
+ }
+
+ uint64_t pc = ibex_get_current_pc();
+ trace_ot_alert_io_read_out((unsigned)addr, (uint64_t)val32, pc);
+
+ return (uint64_t)val32;
+};
+
+static void ot_alert_regs_write(void *opaque, hwaddr addr, uint64_t val64,
+ unsigned size)
+{
+ OtAlertState *s = opaque;
+ OtAlertRegs *regs = s->regs;
+ uint32_t val32 = (uint32_t)val64;
+
+ hwaddr reg = R32_OFF(addr);
+
+ uint64_t pc = ibex_get_current_pc();
+ trace_ot_alert_io_write((unsigned)addr, val64, pc);
+
+ switch (reg) {
+ case R_INTR_STATE:
+ val32 &= INTR_MASK;
+ regs->intr.state &= ~val32; /* RW1C */
+ ot_alert_update_irqs(s);
+ break;
+ case R_INTR_ENABLE:
+ val32 &= INTR_MASK;
+ regs->intr.enable = val32;
+ ot_alert_update_irqs(s);
+ break;
+ case R_INTR_TEST:
+ val32 &= INTR_MASK;
+ regs->intr.state |= val32;
+ ot_alert_update_irqs(s);
+ break;
+ case R_PING_TIMER_REGWEN:
+ val32 &= R_PING_TIMER_REGWEN_EN_MASK;
+ regs->ping.timer_regwen &= ~val32; /* RW1C */
+ break;
+ case R_PING_TIMEOUT_CYC_SHADOWED:
+ val32 &= R_PING_TIMEOUT_CYC_SHADOWED_VAL_MASK;
+ regs->ping.timeout_cyc_shadowed = val32;
+ break;
+ case R_PING_TIMER_EN_SHADOWED:
+ val32 = R_PING_TIMER_EN_SHADOWED_EN_MASK; /* RW1S */
+ regs->ping.timer_en_shadowed |= val32;
+ break;
+ case CASE_RANGE(R_ALERT_REGWEN, PARAM_N_ALERTS):
+ val32 &= ALERT_REGWEN_EN_MASK;
+ regs->alerts[ALERT_SLOT(reg)].regwen &= val32; /* RW0C */
+ break;
+ case CASE_RANGE(R_ALERT_EN_SHADOWED, PARAM_N_ALERTS):
+ if (CHECK_REGWEN(reg, regs->alerts[reg - R_ALERT_EN_SHADOWED].regwen)) {
+ val32 &= ALERT_EN_SHADOWED_EN_MASK;
+ regs->alerts[ALERT_SLOT(reg)].en_shadowed = val32;
+ }
+ break;
+ case CASE_RANGE(R_ALERT_CLASS_SHADOWED, PARAM_N_ALERTS):
+ if (CHECK_REGWEN(reg,
+ regs->alerts[reg - R_ALERT_CLASS_SHADOWED].regwen)) {
+ val32 &= ALERT_CLASS_SHADOWED_EN_MASK;
+ regs->alerts[ALERT_SLOT(reg)].en_shadowed = val32;
+ }
+ break;
+ case CASE_RANGE(R_ALERT_CAUSE, PARAM_N_ALERTS):
+ val32 = ALERT_CAUSE_EN_MASK;
+ regs->alerts[ALERT_SLOT(reg)].cause &= ~val32; /* RW1C */
+ break;
+ case CASE_RANGE(R_LOC_ALERT_REGWEN, PARAM_N_LOC_ALERT):
+ val32 &= LOC_ALERT_REGWEN_EN_MASK;
+ regs->loc_alerts[LOC_ALERT_SLOT(reg)].regwen &= val32; /* RW0C */
+ break;
+ case CASE_RANGE(R_LOC_ALERT_EN_SHADOWED, PARAM_N_LOC_ALERT):
+ if (CHECK_REGWEN(reg, regs->loc_alerts[LOC_ALERT_SLOT(reg)].regwen)) {
+ val32 &= LOC_ALERT_EN_SHADOWED_EN_MASK;
+ regs->loc_alerts[LOC_ALERT_SLOT(reg)].en_shadowed = val32;
+ }
+ break;
+ case CASE_RANGE(R_LOC_ALERT_CLASS_SHADOWED, PARAM_N_LOC_ALERT):
+ if (CHECK_REGWEN(reg, regs->loc_alerts[LOC_ALERT_SLOT(reg)].regwen)) {
+ val32 &= LOC_ALERT_CLASS_SHADOWED_EN_MASK;
+ regs->loc_alerts[LOC_ALERT_SLOT(reg)].en_shadowed = val32;
+ }
+ break;
+ case CASE_RANGE(R_LOC_ALERT_CAUSE, PARAM_N_LOC_ALERT):
+ val32 = LOC_ALERT_CAUSE_EN_MASK;
+ regs->loc_alerts[LOC_ALERT_SLOT(reg)].cause &= ~val32; /* RW1C */
+ break;
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_REGWEN, ALERT_CLASSD):
+ val32 = R_CLASS_REGWEN_EN_MASK;
+ regs->classes[CLASS_SLOT(reg)].regwen &= val32; /* RW0C */
+ break;
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CTRL_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ val32 &= CLASS_CTRL_SHADOWED_MASK;
+ regs->classes[CLASS_SLOT(reg)].ctrl_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CLR_REGWEN, ALERT_CLASSD):
+ val32 &= CLASS_CLR_REGWEN_EN_MASK;
+ regs->classes[CLASS_SLOT(reg)].clr_regwen &= val32; /* RW0C */
+ break;
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CLR_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].clr_regwen)) {
+ val32 &= CLASS_CLR_SHADOWED_EN_MASK;
+ regs->classes[CLASS_SLOT(reg)].clr_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_ACCUM_THRESH_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ val32 &= CLASS_ACCUM_THRESH_SHADOWED_MASK;
+ regs->classes[CLASS_SLOT(reg)].accum_thresh_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_TIMEOUT_CYC_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ regs->classes[CLASS_SLOT(reg)].timeout_cyc_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_CRASHDUMP_TRIGGER_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ val32 &= CLASS_CRASHDUMP_TRIGGER_SHADOWED_MASK;
+ regs->classes[CLASS_SLOT(reg)].crashdump_trigger_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE0_CYC_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ regs->classes[CLASS_SLOT(reg)].phase0_cyc_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE1_CYC_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ regs->classes[CLASS_SLOT(reg)].phase1_cyc_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE2_CYC_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ regs->classes[CLASS_SLOT(reg)].phase2_cyc_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_PHASE3_CYC_SHADOWED, ALERT_CLASSD):
+ if (CHECK_REGWEN(reg, regs->classes[CLASS_SLOT(reg)].regwen)) {
+ regs->classes[CLASS_SLOT(reg)].phase3_cyc_shadowed = val32;
+ }
+ break;
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_ACCUM_CNT, ALERT_CLASSD):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_ESC_CNT, ALERT_CLASSD):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSA):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSB):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSC):
+ case CASE_STRIDE(R_CLASS_STATE, ALERT_CLASSD):
+ qemu_log_mask(LOG_GUEST_ERROR, "R/O register 0x02%" HWADDR_PRIx "\n",
+ addr);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ break;
+ }
+};
+
+static const MemoryRegionOps ot_alert_regs_ops = {
+ .read = &ot_alert_regs_read,
+ .write = &ot_alert_regs_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4u,
+ .impl.max_access_size = 4u,
+};
+
+static void ot_alert_reset_enter(Object *obj, ResetType type)
+{
+ OtAlertClass *c = OT_ALERT_GET_CLASS(obj);
+ OtAlertState *s = OT_ALERT(obj);
+
+ if (c->parent_phases.enter) {
+ c->parent_phases.enter(obj, type);
+ }
+
+ OtAlertRegs *regs = s->regs;
+ memset(regs, 0, sizeof(*regs));
+
+ regs->ping.timer_regwen = 0x1u;
+ regs->ping.timeout_cyc_shadowed = 0x100u;
+ for (unsigned ix = 0; ix < PARAM_N_ALERTS; ix++) {
+ regs->alerts[ix].regwen = 0x1u;
+ }
+ for (unsigned ix = 0; ix < PARAM_N_LOC_ALERT; ix++) {
+ regs->loc_alerts[ix].regwen = 0x1u;
+ }
+ for (unsigned ix = 0; ix < PARAM_N_CLASSES; ix++) {
+ regs->classes[ix].regwen = 0x1u;
+ regs->classes[ix].ctrl_shadowed = 0x393cu;
+ regs->classes[ix].clr_regwen = 0x1u;
+ }
+
+ ot_alert_update_irqs(s);
+}
+
+static void ot_alert_init(Object *obj)
+{
+ OtAlertState *s = OT_ALERT(obj);
+
+ memory_region_init_io(&s->mmio, obj, &ot_alert_regs_ops, s, TYPE_OT_ALERT,
+ REGS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio);
+
+ s->regs = g_new0(OtAlertRegs, 1);
+
+ for (unsigned ix = 0; ix < ARRAY_SIZE(s->irqs); ix++) {
+ ibex_sysbus_init_irq(obj, &s->irqs[ix]);
+ }
+}
+
+static void ot_alert_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ OtAlertClass *ac = OT_ALERT_CLASS(klass);
+ resettable_class_set_parent_phases(rc, &ot_alert_reset_enter, NULL, NULL,
+ &ac->parent_phases);
+}
+
+static const TypeInfo ot_alert_info = {
+ .name = TYPE_OT_ALERT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OtAlertState),
+ .instance_init = &ot_alert_init,
+ .class_size = sizeof(OtAlertClass),
+ .class_init = &ot_alert_class_init,
+};
+
+static void ot_alert_register_types(void)
+{
+ type_register_static(&ot_alert_info);
+}
+
+type_init(ot_alert_register_types)
diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events
index 695debd380..39dea92d2b 100644
--- a/hw/opentitan/trace-events
+++ b/hw/opentitan/trace-events
@@ -1 +1,6 @@
# OpenTitan EarlGrey Trace Events
+
+# ot_alert.c
+
+ot_alert_io_read_out(unsigned int addr, uint64_t val, uint64_t pc) "addr=0x%02x, val=0x%" PRIx64 ", pc=0x%" PRIx64
+ot_alert_io_write(unsigned int addr, uint64_t val, uint64_t pc) "addr=0x%02x, val=0x%" PRIx64 ", pc=0x%" PRIx64
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 76588c43ae..8fdb048ea7 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -17,6 +17,7 @@ config OT_EARLGREY
default y
select IBEX
select IBEX_COMMON
+ select OT_ALERT
select SIFIVE_PLIC
config MICROCHIP_PFSOC
diff --git a/hw/riscv/ibex_common.c b/hw/riscv/ibex_common.c
index 6b53662a5b..dc0ae79777 100644
--- a/hw/riscv/ibex_common.c
+++ b/hw/riscv/ibex_common.c
@@ -1,5 +1,5 @@
/*
- * QEMU RISC-V Helpers for LowRISC Ibex Demo System & OpenTitan EarlGrey
+ * QEMU RISC-V Helpers for LowRISC OpenTitan EarlGrey and related systems
*
* Copyright (c) 2022-2023 Rivos, Inc.
*
diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c
index 77fb1b207c..ccb9ad0e7e 100644
--- a/hw/riscv/ot_earlgrey.c
+++ b/hw/riscv/ot_earlgrey.c
@@ -24,6 +24,7 @@
#include "hw/core/boards.h"
#include "hw/intc/sifive_plic.h"
#include "hw/misc/unimp.h"
+#include "hw/opentitan/ot_alert.h"
#include "hw/core/qdev-properties.h"
#include "hw/riscv/ibex_common.h"
#include "hw/riscv/ot_earlgrey.h"
@@ -261,12 +262,16 @@ static const IbexDeviceDef ot_earlgrey_soc_devices[] = {
),
},
[OT_EARLGREY_SOC_DEV_ALERT_HANDLER] = {
- .type = TYPE_UNIMPLEMENTED_DEVICE,
- .name = "ot-alert_handler",
- .cfg = &ibex_unimp_configure,
+ .type = TYPE_OT_ALERT,
.memmap = MEMMAPENTRIES(
{ 0x40150000u, 0x800u }
),
+ .gpio = IBEXGPIOCONNDEFS(
+ OT_EARLGREY_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 127),
+ OT_EARLGREY_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 128),
+ OT_EARLGREY_SOC_GPIO_SYSBUS_IRQ(0, PLIC, 129),
+ OT_EARLGREY_SOC_GPIO_SYSBUS_IRQ(1, PLIC, 130)
+ ),
},
[OT_EARLGREY_SOC_DEV_SPI_HOST0] = {
.type = TYPE_UNIMPLEMENTED_DEVICE,
diff --git a/include/hw/opentitan/ot_alert.h b/include/hw/opentitan/ot_alert.h
new file mode 100644
index 0000000000..d68f4c9e6e
--- /dev/null
+++ b/include/hw/opentitan/ot_alert.h
@@ -0,0 +1,23 @@
+/*
+ * QEMU OpenTitan Alert handler device
+ *
+ * Copyright (c) 2022-2025 Rivos, Inc.
+ *
+ * Author(s):
+ * Emmanuel Blot <eblot@rivosinc.com>
+ * Loïc Lefort <loic@rivosinc.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef HW_OPENTITAN_OT_ALERT
+#define HW_OPENTITAN_OT_ALERT
+
+#include "qom/object.h"
+
+#define OPENTITAN_DEVICE_ALERT "ot-alert"
+
+#define TYPE_OT_ALERT "ot-alert"
+OBJECT_DECLARE_TYPE(OtAlertState, OtAlertClass, OT_ALERT)
+
+#endif /* HW_OPENTITAN_OT_ALERT */
diff --git a/include/hw/riscv/ibex_irq.h b/include/hw/riscv/ibex_irq.h
index 22ccff6d5e..c1e11e20f6 100644
--- a/include/hw/riscv/ibex_irq.h
+++ b/include/hw/riscv/ibex_irq.h
@@ -12,9 +12,9 @@
#include "qemu/osdep.h"
#include "qom/object.h"
-#include "hw/irq.h"
-#include "hw/qdev-core.h"
-#include "hw/sysbus.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
/** Simple IRQ wrapper to limit propagation of no-change calls */
--
2.49.1
© 2016 - 2026 Red Hat, Inc.