From b527ac1cfec1c29fd3799de2df9cc9abdbe448e7 Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 12:24:36 +0000
Subject: [PATCH 1/8] [ot] hw/opentitan: ot_flash: Add initial prog/erase
 control logic

This commit implements some of the initial types & control register
logic required for implementing the program & erase commands, which
will be added later.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c   | 116 ++++++++++++++++++++++++++++++++------
 hw/opentitan/trace-events |   6 +-
 2 files changed, 103 insertions(+), 19 deletions(-)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index 63ab51b98bc8c..64f2d33ac486a 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -435,6 +435,30 @@ REG32(CSR20, 0x50u)
 
 /* clang-format on */
 
+typedef enum {
+    OP_NONE,
+    OP_INIT,
+    OP_READ,
+    OP_PROG,
+    OP_ERASE,
+} OtFlashOperation;
+
+typedef enum {
+    CONTROL_OP_READ = 0x0,
+    CONTROL_OP_PROG = 0x1,
+    CONTROL_OP_ERASE = 0x2,
+} OtFlashControlOperation;
+
+typedef enum {
+    ERASE_SEL_PAGE = 0x0,
+    ERASE_SEL_BANK = 0x1,
+} OtFlashControlEraseSelection;
+
+typedef enum {
+    PROG_SEL_NORMAL = 0x0,
+    PROG_SEL_REPAIR = 0x1,
+} OtFlashControlProgramSelection;
+
 #define R32_OFF(_r_) ((_r_) / sizeof(uint32_t))
 
 #define R_LAST_REG (R_RD_FIFO)
@@ -576,6 +600,50 @@ static const char *CSR_NAMES[CSRS_COUNT] = {
 };
 #undef CSR_NAME_ENTRY
 
+#define FLASH_NAME_ENTRY(_st_) [_st_] = stringify(_st_)
+
+static const char *OP_NAMES[] = {
+    FLASH_NAME_ENTRY(OP_NONE),  FLASH_NAME_ENTRY(OP_INIT),
+    FLASH_NAME_ENTRY(OP_READ),  FLASH_NAME_ENTRY(OP_PROG),
+    FLASH_NAME_ENTRY(OP_ERASE),
+};
+
+static const char *CONTROL_OP_NAMES[] = {
+    FLASH_NAME_ENTRY(CONTROL_OP_READ),
+    FLASH_NAME_ENTRY(CONTROL_OP_PROG),
+    FLASH_NAME_ENTRY(CONTROL_OP_ERASE),
+};
+
+static const char *ERASE_SELECTION_NAMES[] = {
+    FLASH_NAME_ENTRY(ERASE_SEL_PAGE),
+    FLASH_NAME_ENTRY(ERASE_SEL_BANK),
+};
+
+static const char *PROGRAM_SELECTION_NAMES[] = {
+    FLASH_NAME_ENTRY(PROG_SEL_NORMAL),
+    FLASH_NAME_ENTRY(PROG_SEL_REPAIR),
+};
+
+#undef FLASH_NAME_ENTRY
+
+#define OP_NAME(_st_) \
+    (((unsigned)(_st_)) < ARRAY_SIZE(OP_NAMES) ? OP_NAMES[(_st_)] : "?")
+
+#define CONTROL_OP_NAME(_st_) \
+    ((unsigned)(_st_) < ARRAY_SIZE(CONTROL_OP_NAMES) ? \
+         CONTROL_OP_NAMES[(_st_)] : \
+         "?")
+
+#define ERASE_NAME(_st_) \
+    ((unsigned)(_st_) < ARRAY_SIZE(ERASE_SELECTION_NAMES) ? \
+         ERASE_SELECTION_NAMES[(_st_)] : \
+         "?")
+
+#define PROGRAM_NAME(_st_) \
+    ((unsigned)(_st_) < ARRAY_SIZE(PROGRAM_SELECTION_NAMES) ? \
+         PROGRAM_SELECTION_NAMES[(_st_)] : \
+         "?")
+
 /**
  * Bank 0 information partition type 0 pages.
  *
@@ -609,12 +677,6 @@ static const char *CSR_NAMES[CSRS_COUNT] = {
 #define xtrace_ot_flash_info(_msg_, _val_) \
     trace_ot_flash_info(__func__, __LINE__, _msg_, _val_)
 
-typedef enum {
-    OP_NONE,
-    OP_INIT,
-    OP_READ,
-} OtFlashOperation;
-
 enum {
     BIN_APP_OTRE,
     BIN_APP_OTB0,
@@ -681,6 +743,8 @@ struct OtFlashState {
         unsigned address;
         unsigned info_sel;
         bool info_part;
+        bool prog_sel;
+        bool erase_sel;
     } op;
     OtFifo32 rd_fifo;
     OtFlashStorage flash;
@@ -727,7 +791,7 @@ static void ot_flash_op_signal(void *opaque)
             FIELD_DP32(s->regs[R_STATUS], STATUS, INITIALIZED, 1u);
         s->regs[R_PHY_STATUS] =
             FIELD_DP32(s->regs[R_PHY_STATUS], PHY_STATUS, INIT_WIP, 0u);
-        trace_ot_flash_op_complete(s->op.kind, true);
+        trace_ot_flash_op_complete(OP_NAME(s->op.kind), true);
         s->op.kind = OP_NONE;
         break;
     default:
@@ -747,7 +811,7 @@ static void ot_flash_initialize(OtFlashState *s)
     }
 
     s->op.kind = OP_INIT;
-    trace_ot_flash_op_start(s->op.kind);
+    trace_ot_flash_op_start(OP_NAME(s->op.kind));
     s->regs[R_STATUS] = FIELD_DP32(s->regs[R_STATUS], STATUS, INIT_WIP, 1u);
     s->regs[R_PHY_STATUS] =
         FIELD_DP32(s->regs[R_PHY_STATUS], PHY_STATUS, INIT_WIP, 0u);
@@ -768,7 +832,7 @@ static void ot_flash_set_error(OtFlashState *s, uint32_t ebit, uint32_t eaddr)
         s->regs[R_ERR_ADDR] = FIELD_DP32(0, ERR_ADDR, ERR_ADDR, eaddr);
         s->regs[R_ERR_CODE] = ebit;
     }
-    trace_ot_flash_set_error(s->op.kind, ebit, eaddr);
+    trace_ot_flash_set_error(OP_NAME(s->op.kind), ebit, eaddr);
     ot_flash_update_irqs(s);
 }
 
@@ -780,7 +844,7 @@ static void ot_flash_op_complete(OtFlashState *s)
      */
     s->regs[R_OP_STATUS] |= R_OP_STATUS_DONE_MASK;
     s->regs[R_INTR_STATE] |= INTR_OP_DONE_MASK;
-    trace_ot_flash_op_complete(s->op.kind, !s->regs[R_ERR_CODE]);
+    trace_ot_flash_op_complete(OP_NAME(s->op.kind), !s->regs[R_ERR_CODE]);
     s->op.kind = OP_NONE;
     ot_flash_update_irqs(s);
 }
@@ -874,7 +938,7 @@ static void ot_flash_op_execute(OtFlashState *s)
 {
     switch (s->op.kind) {
     case OP_READ:
-        trace_ot_flash_op_start(s->op.kind);
+        trace_ot_flash_op_start(OP_NAME(s->op.kind));
         ot_flash_op_read(s);
         break;
     default:
@@ -1127,24 +1191,44 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
         bool part_sel = (bool)FIELD_EX32(val32, CONTROL, PARTITION_SEL);
         unsigned info_sel = (unsigned)FIELD_EX32(val32, CONTROL, INFO_SEL);
         unsigned num = (unsigned)FIELD_EX32(val32, CONTROL, NUM);
+
         if (start && s->op.kind == OP_NONE) {
-            /* NOLINTNEXTLINE */
             switch (op) {
-            case 0:
+            case CONTROL_OP_READ:
                 s->op.kind = OP_READ;
                 s->op.address = s->regs[R_ADDR] & ~3u;
                 s->op.info_part = part_sel;
                 s->op.info_sel = info_sel;
                 xtrace_ot_flash_info("Read from", s->op.address);
+                s->op.count = num + 1u;
+                break;
+            case CONTROL_OP_PROG:
+                s->op.kind = OP_PROG;
+                s->op.address = s->regs[R_ADDR] & ~3u;
+                s->op.info_part = part_sel;
+                s->op.info_sel = info_sel;
+                s->op.prog_sel = (bool)prog_sel;
+                s->op.count = num + 1u;
+                xtrace_ot_flash_info("Write to", s->op.address);
+                break;
+            case CONTROL_OP_ERASE:
+                s->op.kind = OP_ERASE;
+                s->op.address = s->regs[R_ADDR] & ~3u;
+                s->op.info_part = part_sel;
+                s->op.info_sel = info_sel;
+                s->op.erase_sel = (bool)erase_sel;
+                /* Erase operations neither go through FIFOs nor use/require a word count */
+                s->op.count = 0u;
+                xtrace_ot_flash_info("Erase at", s->op.address);
                 break;
             default:
-                qemu_log_mask(LOG_UNIMP, "%s: Operation %u not implemented\n",
-                              __func__, op);
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "%s: Operation %u (%s) is invalid\n", __func__,
+                              op, CONTROL_OP_NAME(op));
                 ot_flash_set_error(s, R_ERR_CODE_OP_ERR_MASK, 0u);
                 ot_flash_op_complete(s);
                 return;
             }
-            s->op.count = num + 1u;
         }
         ot_flash_op_execute(s);
         break;
diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events
index 0361338ba19f2..9e6b1d927191f 100644
--- a/hw/opentitan/trace-events
+++ b/hw/opentitan/trace-events
@@ -182,9 +182,9 @@ ot_flash_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t
 ot_flash_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x"
 ot_flash_irqs(uint32_t active, uint32_t mask, uint32_t eff) "act:0x%08x msk:0x%08x eff:0x%08x"
 ot_flash_mem_read_out(uint32_t addr, unsigned size, uint32_t val, uint32_t pc) "addr=0x%02x (%u), val=0x%08x, pc=0x%x"
-ot_flash_op_complete(int op, bool success) "%d: %u"
-ot_flash_op_start(int op) "%d"
-ot_flash_set_error(int op, uint32_t err_code, uint32_t err_addr) "%d: err=%08x at addr=0x%06x"
+ot_flash_op_complete(const char *op, bool success) "%s: %u"
+ot_flash_op_start(const char *op) "%s"
+ot_flash_set_error(const char *op, uint32_t err_code, uint32_t err_addr) "%s: err=%08x at addr=0x%06x"
 
 # ot_gpio.c
 

From 2880bd47603e7301e73bcad011820f2c71248437 Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 14:16:03 +0000
Subject: [PATCH 2/8] [ot] hw/opentitan: ot_flash: Add program resolution &
 type checks

Models the behaviour of OpenTitan's RTL: at the start of any (single- or
multi-word) program transaction, two checks are performed.
 1. Check that the start of the program beat is in the same program
 window as the end beat. This window boundary is typically, though not
 always, nicely aligned in memory.
 2. Check that the selected type of program command is enabled.

If either of these fail, the transaction is aborted before it starts.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c          | 46 ++++++++++++++++++++++++++++++++
 include/hw/opentitan/ot_common.h |  7 +++++
 2 files changed, 53 insertions(+)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index 64f2d33ac486a..6b66b76189427 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -686,6 +686,7 @@ enum {
 #define OP_INIT_DURATION_NS     1000000u /* 1 ms */
 #define ELFNAME_SIZE            256u
 #define OT_FLASH_READ_FIFO_SIZE 16u
+#define BUS_PGM_RES             ((REG_BUS_PGM_RES_BYTES) / (OT_TL_UL_D_WIDTH_BYTES))
 
 typedef struct {
     unsigned offset; /* storage offset in bank, relative to first info page */
@@ -947,6 +948,43 @@ static void ot_flash_op_execute(OtFlashState *s)
     }
 }
 
+static bool ot_flash_check_program_resolution(OtFlashState *s)
+{
+    unsigned start_address = s->op.address / sizeof(uint32_t);
+    unsigned end_address = start_address - 1u + s->op.count;
+    unsigned start_window = start_address / BUS_PGM_RES;
+    unsigned end_window = end_address / BUS_PGM_RES;
+    if (start_window != end_window) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: program resolution error for addr=%u, count=%u "
+                      "(start_window=%u, end_window=%u)\n",
+                      __func__, s->op.address, s->op.count, start_window,
+                      end_window);
+        ot_flash_set_error(s, R_ERR_CODE_PROG_WIN_ERR_MASK, s->op.address);
+        ot_flash_op_complete(s);
+        return false;
+    }
+    return true;
+}
+
+static bool ot_flash_check_program_type(OtFlashState *s)
+{
+    uint32_t en_mask;
+    if (s->op.prog_sel == PROG_SEL_NORMAL) {
+        en_mask = R_PROG_TYPE_EN_NORMAL_MASK;
+    } else { /* PROG_SEL_REPAIR */
+        en_mask = R_PROG_TYPE_EN_REPAIR_MASK;
+    }
+    if (!(s->regs[R_PROG_TYPE_EN] & en_mask)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: program type not enabled: %s\n",
+                      __func__, PROGRAM_NAME(s->op.prog_sel));
+        ot_flash_set_error(s, R_ERR_CODE_PROG_TYPE_ERR_MASK, s->op.address);
+        ot_flash_op_complete(s);
+        return false;
+    }
+    return true;
+}
+
 static uint64_t ot_flash_regs_read(void *opaque, hwaddr addr, unsigned size)
 {
     OtFlashState *s = opaque;
@@ -1209,6 +1247,14 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
                 s->op.info_sel = info_sel;
                 s->op.prog_sel = (bool)prog_sel;
                 s->op.count = num + 1u;
+                /*
+                 * On encountering either a program resolution error or program
+                 * type error, do not start the transaction.
+                 */
+                if (!ot_flash_check_program_resolution(s) ||
+                    !ot_flash_check_program_type(s)) {
+                    return;
+                }
                 xtrace_ot_flash_info("Write to", s->op.address);
                 break;
             case CONTROL_OP_ERASE:
diff --git a/include/hw/opentitan/ot_common.h b/include/hw/opentitan/ot_common.h
index 1e8bf7e7a861b..6c7df18cef75b 100644
--- a/include/hw/opentitan/ot_common.h
+++ b/include/hw/opentitan/ot_common.h
@@ -35,6 +35,13 @@
 /* QEMU virtual timer to use for OpenTitan devices */
 #define OT_VIRTUAL_CLOCK QEMU_CLOCK_VIRTUAL
 
+/* ------------------------------------------------------------------------ */
+/* TL-UL Bus Characteristics */
+/* ------------------------------------------------------------------------ */
+
+#define OT_TL_UL_D_WIDTH_BITS  32u
+#define OT_TL_UL_D_WIDTH_BYTES ((OT_TL_UL_D_WIDTH_BITS) / 8u)
+
 /* ------------------------------------------------------------------------ */
 /* Multi-bit boolean values */
 /* ------------------------------------------------------------------------ */

From e390fad041144564c4e60b4874f8d58ae3804861 Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 14:33:59 +0000
Subject: [PATCH 3/8] [ot] hw/opentitan: ot_flash: Fix flash disable behaviour

In the existing flash, if you write to the `DIS` register to disable the
flash, it completely removes the ability to read from or write to any of
the CSRs.

Referencing the documentation:
https://opentitan.org/book/hw/top_earlgrey/ip_autogen/flash_ctrl/doc/theory_of_operation.html#flash-access-disable

Upon disabling the flash, it should instead finish existing stateful
operations, and then give a memory protection error for any further
flash protocol controller initiated operations. That is; you should
still be able to interact with the registers, but no new flash
transactions can be started via the Flash Controller. The host-facing
TL-UL adapter errors back all host initiated operations when disabled,
so the disabling the relevant mem region is kept.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c   | 136 ++++++++++++++++++++++++++++++--------
 hw/opentitan/trace-events |   3 +
 2 files changed, 110 insertions(+), 29 deletions(-)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index 6b66b76189427..d624d9cae6a9f 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -686,6 +686,7 @@ enum {
 #define OP_INIT_DURATION_NS     1000000u /* 1 ms */
 #define ELFNAME_SIZE            256u
 #define OT_FLASH_READ_FIFO_SIZE 16u
+#define OT_FLASH_PROG_FIFO_SIZE 16u
 #define BUS_PGM_RES             ((REG_BUS_PGM_RES_BYTES) / (OT_TL_UL_D_WIDTH_BYTES))
 
 typedef struct {
@@ -748,6 +749,7 @@ struct OtFlashState {
         bool erase_sel;
     } op;
     OtFifo32 rd_fifo;
+    OtFifo32 prog_fifo;
     OtFlashStorage flash;
 
     BlockBackend *blk; /* Flash backend */
@@ -781,6 +783,42 @@ static bool ot_flash_regs_is_wr_enabled(OtFlashState *s, unsigned regwen)
     return (bool)(s->regs[regwen] & REGWEN_EN_MASK);
 }
 
+static void ot_flash_update_rd_watermark(OtFlashState *s)
+{
+    unsigned rd_watermark_level =
+        SHARED_FIELD_EX32(s->regs[R_FIFO_LVL], FIFO_LVL_RD);
+    unsigned lvl = ot_fifo32_num_used(&s->rd_fifo);
+
+    /* Read FIFO watermark generates an interrupt when the Read FIFO fills to
+    (equal to or greater than) the watermark level. */
+    if (lvl >= rd_watermark_level) {
+        s->regs[R_INTR_STATE] |= INTR_RD_LVL_MASK;
+    } else {
+        s->regs[R_INTR_STATE] &= ~INTR_RD_LVL_MASK;
+    }
+    trace_ot_flash_update_rd_watermark(lvl, rd_watermark_level);
+    ot_flash_update_irqs(s);
+}
+
+static void ot_flash_update_prog_watermark(OtFlashState *s)
+{
+    unsigned prog_watermark_level =
+        SHARED_FIELD_EX32(s->regs[R_FIFO_LVL], FIFO_LVL_PROG);
+    unsigned lvl = ot_fifo32_num_used(&s->prog_fifo);
+
+    /* Prog FIFO watermark generates an interrupt when the Prog FIFO drains to
+    (equal to or less than) the watermark level. */
+    if (lvl <= prog_watermark_level) {
+        s->regs[R_INTR_STATE] |= INTR_PROG_LVL_MASK;
+    } else {
+        s->regs[R_INTR_STATE] &= ~INTR_PROG_LVL_MASK;
+    }
+    trace_ot_flash_update_prog_watermark(lvl, prog_watermark_level);
+    ot_flash_update_irqs(s);
+}
+
+
+
 static void ot_flash_op_signal(void *opaque)
 {
     OtFlashState *s = opaque;
@@ -825,6 +863,26 @@ static bool ot_flash_fifo_in_reset(OtFlashState *s)
     return (bool)s->regs[R_FIFO_RST];
 }
 
+static void ot_flash_reset_rd_fifo(OtFlashState *s)
+{
+    ot_fifo32_reset(&s->rd_fifo);
+    s->regs[R_STATUS] |= R_STATUS_RD_EMPTY_MASK;
+    s->regs[R_STATUS] &= ~R_STATUS_RD_FULL_MASK;
+    s->regs[R_INTR_STATE] &= ~INTR_RD_FULL_MASK;
+    trace_ot_flash_reset_fifo("rd");
+    ot_flash_update_rd_watermark(s);
+}
+
+static void ot_flash_reset_prog_fifo(OtFlashState *s)
+{
+    ot_fifo32_reset(&s->prog_fifo);
+    s->regs[R_STATUS] |= R_STATUS_PROG_EMPTY_MASK;
+    s->regs[R_STATUS] &= ~R_STATUS_PROG_FULL_MASK;
+    s->regs[R_INTR_STATE] |= INTR_PROG_EMPTY_MASK;
+    trace_ot_flash_reset_fifo("prog");
+    ot_flash_update_prog_watermark(s);
+}
+
 static void ot_flash_set_error(OtFlashState *s, uint32_t ebit, uint32_t eaddr)
 {
     if (ebit) {
@@ -921,6 +979,7 @@ static void ot_flash_op_read(OtFlashState *s)
         if (!ot_flash_fifo_in_reset(s)) {
             ot_fifo32_push(&s->rd_fifo, word);
             s->regs[R_STATUS] &= ~R_STATUS_RD_EMPTY_MASK;
+            ot_flash_update_rd_watermark(s);
         }
         if (ot_fifo32_is_full(&s->rd_fifo)) {
             s->regs[R_STATUS] |= R_STATUS_RD_FULL_MASK;
@@ -991,12 +1050,6 @@ static uint64_t ot_flash_regs_read(void *opaque, hwaddr addr, unsigned size)
     (void)size;
     uint32_t val32;
 
-    if (ot_flash_is_disabled(s)) {
-        qemu_log_mask(LOG_GUEST_ERROR, "%s: flash has been disabled\n",
-                      __func__);
-        return 0u;
-    }
-
     hwaddr reg = R32_OFF(addr);
 
     switch (reg) {
@@ -1111,6 +1164,10 @@ static uint64_t ot_flash_regs_read(void *opaque, hwaddr addr, unsigned size)
                            (uint32_t)ot_fifo32_is_full(&s->rd_fifo));
         val32 = FIELD_DP32(val32, STATUS, RD_EMPTY,
                            (uint32_t)ot_fifo32_is_empty(&s->rd_fifo));
+        val32 = FIELD_DP32(val32, STATUS, PROG_FULL,
+                           (uint32_t)ot_fifo32_is_full(&s->prog_fifo));
+        val32 = FIELD_DP32(val32, STATUS, PROG_EMPTY,
+                           (uint32_t)ot_fifo32_is_empty(&s->prog_fifo));
         break;
     case R_RD_FIFO:
         if (!ot_fifo32_is_empty(&s->rd_fifo)) {
@@ -1120,6 +1177,7 @@ static uint64_t ot_flash_regs_read(void *opaque, hwaddr addr, unsigned size)
             if (ot_fifo32_is_empty(&s->rd_fifo)) {
                 s->regs[R_STATUS] |= R_STATUS_RD_EMPTY_MASK;
             }
+            ot_flash_update_rd_watermark(s);
             ot_flash_update_irqs(s);
             if (s->op.count) {
                 ot_flash_op_execute(s);
@@ -1130,7 +1188,10 @@ static uint64_t ot_flash_regs_read(void *opaque, hwaddr addr, unsigned size)
         }
         break;
     case R_CURR_FIFO_LVL:
-        val32 = ot_fifo32_num_used(&s->rd_fifo) << FIFO_LVL_RD_SHIFT;
+        val32 =
+            SHARED_FIELD_DP32(0u, FIFO_LVL_RD, ot_fifo32_num_used(&s->rd_fifo));
+        val32 = SHARED_FIELD_DP32(val32, FIFO_LVL_PROG,
+                                  ot_fifo32_num_used(&s->prog_fifo));
         break;
     case R_ALERT_TEST:
     case R_PROG_FIFO:
@@ -1164,12 +1225,6 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
     uint32_t pc = ibex_get_current_pc();
     trace_ot_flash_io_write((uint32_t)addr, REG_NAME(reg), val32, pc);
 
-    if (ot_flash_is_disabled(s)) {
-        qemu_log_mask(LOG_GUEST_ERROR, "%s: flash has been disabled\n",
-                      __func__);
-        return;
-    }
-
     switch (reg) {
     case R_INTR_STATE: {
         uint32_t rw1c_mask = INTR_CORR_ERR_MASK | INTR_OP_DONE_MASK;
@@ -1205,8 +1260,6 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
         if (ot_flash_is_disabled(s)) {
             xtrace_ot_flash_error("flash controller disabled by SW");
             memory_region_set_enabled(&s->mmio.mem, false);
-            memory_region_set_enabled(&s->mmio.csrs, false);
-            memory_region_set_enabled(&s->mmio.regs, false);
         }
         break;
     case R_INIT:
@@ -1231,6 +1284,20 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
         unsigned num = (unsigned)FIELD_EX32(val32, CONTROL, NUM);
 
         if (start && s->op.kind == OP_NONE) {
+            /*
+             * If the flash controller is disabled by software, then (a) the
+             * flash protocol controller completes existing software commands,
+             * (b) the flash physical controller completes existing stateful
+             * operations, and (c) the flash protocol controller MP errors
+             * back all controller initiated operations.
+             */
+            if (ot_flash_is_disabled(s)) {
+                qemu_log_mask(LOG_GUEST_ERROR, "%s: flash has been disabled\n",
+                              __func__);
+                ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, 0u);
+                return;
+            }
+
             switch (op) {
             case CONTROL_OP_READ:
                 s->op.kind = OP_READ;
@@ -1455,14 +1522,35 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
     case R_FIFO_LVL:
         val32 &= FIFO_LVL_PROG_MASK | FIFO_LVL_RD_MASK;
         s->regs[reg] = val32;
+        ot_flash_update_rd_watermark(s);
+        ot_flash_update_prog_watermark(s);
         break;
     case R_FIFO_RST:
         val32 &= R_FIFO_RST_EN_MASK;
         s->regs[reg] = val32;
         if (val32) {
-            ot_fifo32_reset(&s->rd_fifo);
+            ot_flash_reset_rd_fifo(s);
+            ot_flash_reset_prog_fifo(s);
         }
+        break;
     case R_PROG_FIFO:
+        if (!ot_fifo32_is_full(&s->prog_fifo)) {
+            if (!ot_flash_fifo_in_reset(s)) {
+                ot_fifo32_push(&s->prog_fifo, val32);
+                s->regs[R_STATUS] &= ~R_STATUS_PROG_EMPTY_MASK;
+                s->regs[R_INTR_STATE] &= ~INTR_PROG_EMPTY_MASK;
+                ot_flash_update_prog_watermark(s);
+                ot_flash_update_irqs(s);
+            }
+            if (ot_fifo32_is_full(&s->prog_fifo)) {
+                s->regs[R_STATUS] |= R_STATUS_PROG_FULL_MASK;
+            }
+            if (s->op.count) {
+                ot_flash_op_execute(s);
+            }
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: Write full FIFO\n", __func__);
+        }
         break;
     case R_FAULT_STATUS: {
         uint32_t rw0c_mask = (R_FAULT_STATUS_PHY_RELBL_ERR_MASK |
@@ -1504,12 +1592,6 @@ static uint64_t ot_flash_csrs_read(void *opaque, hwaddr addr, unsigned size)
     (void)size;
     uint32_t val32;
 
-    if (ot_flash_is_disabled(s)) {
-        qemu_log_mask(LOG_GUEST_ERROR, "%s: flash has been disabled\n",
-                      __func__);
-        return 0u;
-    }
-
     hwaddr csr = R32_OFF(addr);
 
     switch (csr) {
@@ -1561,12 +1643,6 @@ static void ot_flash_csrs_write(void *opaque, hwaddr addr, uint64_t val64,
     uint32_t pc = ibex_get_current_pc();
     trace_ot_flash_io_write((uint32_t)addr, CSR_NAME(csr), val32, pc);
 
-    if (ot_flash_is_disabled(s)) {
-        qemu_log_mask(LOG_GUEST_ERROR, "%s: flash has been disabled\n",
-                      __func__);
-        return;
-    }
-
     bool enable = s->csrs[R_CSR0_REGWEN] & R_CSR0_REGWEN_FIELD0_MASK;
     switch (csr) {
     case R_CSR0_REGWEN:
@@ -1760,7 +1836,8 @@ static void ot_flash_reset(DeviceState *dev)
     ot_flash_update_irqs(s);
     ot_flash_update_alerts(s);
 
-    ot_fifo32_reset(&s->rd_fifo);
+    ot_flash_reset_rd_fifo(s);
+    ot_flash_reset_prog_fifo(s);
 }
 
 #ifdef USE_HEXDUMP
@@ -2035,6 +2112,7 @@ static void ot_flash_init(Object *obj)
     s->regs = g_new0(uint32_t, REGS_COUNT);
     s->csrs = g_new0(uint32_t, CSRS_COUNT);
     ot_fifo32_create(&s->rd_fifo, OT_FLASH_READ_FIFO_SIZE);
+    ot_fifo32_create(&s->prog_fifo, OT_FLASH_PROG_FIFO_SIZE);
 
     for (unsigned ix = 0; ix < PARAM_NUM_IRQS; ix++) {
         ibex_sysbus_init_irq(obj, &s->irqs[ix]);
diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events
index 9e6b1d927191f..e5a174519ffcd 100644
--- a/hw/opentitan/trace-events
+++ b/hw/opentitan/trace-events
@@ -185,6 +185,9 @@ ot_flash_mem_read_out(uint32_t addr, unsigned size, uint32_t val, uint32_t pc) "
 ot_flash_op_complete(const char *op, bool success) "%s: %u"
 ot_flash_op_start(const char *op) "%s"
 ot_flash_set_error(const char *op, uint32_t err_code, uint32_t err_addr) "%s: err=%08x at addr=0x%06x"
+ot_flash_update_rd_watermark(unsigned val, unsigned watermark) "%u >= %u"
+ot_flash_update_prog_watermark(unsigned val, unsigned watermark) "%u <= %u"
+ot_flash_reset_fifo(const char *name) "%s"
 
 # ot_gpio.c
 

From 89d665b58e34077a004292440c86ff979250853f Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 15:04:08 +0000
Subject: [PATCH 4/8] [ot] hw/opentitan: ot_flash: Refactor to implement
 multi-word reads

This commit refactors `ot_flash_op_read` so that it can properly support
multi-word reads.

Two fields: `remaining`/`error` are introduced to the operation to count
the number of processed/remaining words in the current transaction, and
to track errors in multi-word transactions. These fields are used to
allow us to retain the initial operation configuration and calculate the
current address at any time.

Under previous operation, if the read FIFO filled, the re-execution of
the `ot_flash_op_read` function read from the initial `op.address`,
thus reading repeated (garbage) data. This new logic fixes the issue and
allows multi-word reads, whilst remaining generic for future operations.

Calculation of the address has moved inside of the iteration, because
this functionality is needed for the later addition of memory protection
region functionality, and because it makes it easier to implement
correct errors for individual operations in multi-word transactions. It
also better decouples the transaction's memory accesses from our flash
backend structure; it is possible for multi-word operations to occur
over some boundary (e.g. read over a page boundary).

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c   | 159 +++++++++++++++++++++++---------------
 hw/opentitan/trace-events |   4 +-
 2 files changed, 100 insertions(+), 63 deletions(-)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index d624d9cae6a9f..e29045f070e89 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -742,11 +742,13 @@ struct OtFlashState {
     struct {
         OtFlashOperation kind;
         unsigned count;
+        unsigned remaining;
         unsigned address;
         unsigned info_sel;
         bool info_part;
         bool prog_sel;
         bool erase_sel;
+        bool failed;
     } op;
     OtFifo32 rd_fifo;
     OtFifo32 prog_fifo;
@@ -818,7 +820,6 @@ static void ot_flash_update_prog_watermark(OtFlashState *s)
 }
 
 
-
 static void ot_flash_op_signal(void *opaque)
 {
     OtFlashState *s = opaque;
@@ -908,78 +909,105 @@ static void ot_flash_op_complete(OtFlashState *s)
     ot_flash_update_irqs(s);
 }
 
+static unsigned ot_flash_next_info_address(OtFlashState *s)
+{
+    OtFlashStorage *storage = &s->flash;
+    unsigned bank_size = storage->data_size;
+    unsigned info_partition = s->op.info_sel;
+
+    /* offset the address by the number of processed ops to get next addr */
+    unsigned op_offset = (s->op.count - s->op.remaining) * sizeof(uint32_t);
+    unsigned op_address = s->op.address + op_offset;
+
+    if (info_partition >= storage->info_part_count) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid info partition: %u\n",
+                      __func__, s->op.info_sel);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
+        s->op.failed = true;
+        return op_address;
+    }
+
+    /* extract the bank & bank-relative address from the address */
+    unsigned bank = op_address / bank_size;
+    if (bank >= storage->bank_count) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid bank: %d\n", __func__,
+                      bank);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
+        s->op.failed = true;
+        return op_address;
+    }
+    unsigned address_in_bank = op_address % bank_size;
+    if (address_in_bank >= storage->info_parts[info_partition].size) {
+        /*
+         * Purposefuly do not check the whole transaction's address width here:
+         * the RTL only errors when it attempts to read an invalid address.
+         */
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid address in partition: %u %u\n", __func__,
+                      op_address, info_partition);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
+        s->op.failed = true;
+        return op_address;
+    }
+
+    /* Retrieve the raw backend byte address in the info partitions. */
+    unsigned bank_offset = bank * storage->info_size;
+    unsigned info_part_offset = storage->info_parts[info_partition].offset;
+    unsigned address = address_in_bank + bank_offset + info_part_offset;
+    trace_ot_flash_info_part(s->op.address, s->op.count, s->op.remaining, bank,
+                             info_partition, address);
+    return address;
+}
+
+static unsigned ot_flash_next_data_address(OtFlashState *s)
+{
+    OtFlashStorage *storage = &s->flash;
+    unsigned bank_size = storage->data_size;
+
+    /* offset the address by the number of proessed ops to get next addr */
+    unsigned op_offset = (s->op.count - s->op.remaining) * sizeof(uint32_t);
+    unsigned address = s->op.address + op_offset;
+    unsigned bank = address / bank_size;
+    if (bank >= storage->bank_count) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid bank: %d\n", __func__,
+                      bank);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, address);
+        s->op.failed = true;
+        return address;
+    }
+    trace_ot_flash_data_part(s->op.address, s->op.count, s->op.remaining, bank,
+                             address);
+    return address;
+}
+
 static void ot_flash_op_read(OtFlashState *s)
 {
     if (ot_fifo32_is_full(&s->rd_fifo)) {
         xtrace_ot_flash_error("read while RD FIFO full");
         return;
     }
-    unsigned max_size;
-    unsigned offset;
-    unsigned address;
+
     OtFlashStorage *storage = &s->flash;
-    uint32_t *src;
-
-    if (s->op.info_part) {
-        if (s->op.info_sel >= storage->info_part_count) {
-            qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid info partition: %u\n",
-                          __func__, s->op.info_sel);
-            ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, s->op.address);
-            ot_flash_op_complete(s);
-            return;
-        }
-        max_size = storage->info_size;
-        /* relative storage offset in the info storage */
-        offset = storage->info_parts[s->op.info_sel].offset;
-        unsigned bank_size = storage->data_size;
-        /* extract the bank from the address */
-        unsigned bank = s->op.address / bank_size;
-        if (bank >= storage->bank_count) {
-            qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid bank: %d\n", __func__,
-                          bank);
-            ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, s->op.address);
-            ot_flash_op_complete(s);
-            return;
-        }
-        /* get the adress relative to the bank */
-        address = s->op.address % bank_size;
-        if (address + s->op.count * sizeof(uint32_t) >=
-            storage->info_parts[s->op.info_sel].size) {
-            qemu_log_mask(LOG_GUEST_ERROR,
-                          "%s: invalid address in partition: %u %u\n", __func__,
-                          address, s->op.info_sel);
-            ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, s->op.address);
-            ot_flash_op_complete(s);
-            return;
+    uint32_t *src = s->op.info_part ? storage->info : storage->data;
+
+    while (s->op.remaining) {
+        uint32_t word = 0xFFFFFFFFu;
+        if (!s->op.failed) {
+            unsigned address = s->op.info_part ? ot_flash_next_info_address(s) :
+                                                 ot_flash_next_data_address(s);
+            address /= sizeof(uint32_t); /* convert to word address */
+
+            /* For multi-word read access permission errors, return all 1s. */
+            if (!s->op.failed) {
+                word = src[address];
+            }
         }
-        /* add the bank offset of the first byte of info part */
-        address += bank * storage->info_size;
-        /* add the offset of the partition in the current bank */
-        address += offset;
-        src = storage->info;
-        trace_ot_flash_info_part(s->op.address, bank, s->op.info_sel, address);
-    } else {
-        max_size = storage->data_size;
-        address = s->op.address;
-        src = storage->data;
-    }
-
-    /* sanity check */
-    if (address >= max_size * storage->bank_count) {
-        xtrace_ot_flash_error("read address out of bound");
-        g_assert_not_reached();
-    }
-
-    /* convert to word address */
-    address /= sizeof(uint32_t);
 
-    while (s->op.count) {
-        uint32_t word = src[address++];
-        s->op.count--;
         if (!ot_flash_fifo_in_reset(s)) {
             ot_fifo32_push(&s->rd_fifo, word);
             s->regs[R_STATUS] &= ~R_STATUS_RD_EMPTY_MASK;
             ot_flash_update_rd_watermark(s);
+            s->op.remaining--;
         }
         if (ot_fifo32_is_full(&s->rd_fifo)) {
             s->regs[R_STATUS] |= R_STATUS_RD_FULL_MASK;
@@ -989,7 +1017,11 @@ static void ot_flash_op_read(OtFlashState *s)
         }
     }
 
-    if (!s->op.count) {
+    /*
+     * If we finished the entire read operation (i.e. no early exit as FIFO
+     * is full), mark the operation as completed.
+     */
+    if (!s->op.remaining) {
         ot_flash_op_complete(s);
     }
 }
@@ -998,7 +1030,7 @@ static void ot_flash_op_execute(OtFlashState *s)
 {
     switch (s->op.kind) {
     case OP_READ:
-        trace_ot_flash_op_start(OP_NAME(s->op.kind));
+        trace_ot_flash_op_execute(OP_NAME(s->op.kind));
         ot_flash_op_read(s);
         break;
     default:
@@ -1342,6 +1374,9 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
                 ot_flash_op_complete(s);
                 return;
             }
+            s->op.failed = false;
+            s->op.remaining = s->op.count;
+            trace_ot_flash_op_start(OP_NAME(s->op.kind));
         }
         ot_flash_op_execute(s);
         break;
diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events
index e5a174519ffcd..abe48b2bd8ca2 100644
--- a/hw/opentitan/trace-events
+++ b/hw/opentitan/trace-events
@@ -177,12 +177,14 @@ ot_entropy_src_update_generation(unsigned gennum) "%u"
 
 ot_flash_error(const char *func, int line, const char *err) "%s:%d %s"
 ot_flash_info(const char *func, int line, const char *msg, uint32_t value) "%s:%d %s: 0x%08x"
-ot_flash_info_part(uint32_t op_addr, unsigned bank, unsigned infosel, uint32_t addr) "op_addr 0x%06x bank %u infosel %u addr 0x%06x"
+ot_flash_info_part(unsigned op_addr, unsigned count, unsigned remaining, unsigned bank, unsigned infosel, unsigned addr) "op_addr 0x%06x count %u remaining %u bank %u infosel %u addr 0x%06x"
+ot_flash_data_part(unsigned op_addr, unsigned count, unsigned remaining, unsigned bank, unsigned addr) "op_addr 0x%06x count %u remaining %u bank %u addr 0x%06x"
 ot_flash_io_read_out(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x"
 ot_flash_io_write(uint32_t addr, const char * regname, uint32_t val, uint32_t pc) "addr=0x%02x (%s), val=0x%x, pc=0x%x"
 ot_flash_irqs(uint32_t active, uint32_t mask, uint32_t eff) "act:0x%08x msk:0x%08x eff:0x%08x"
 ot_flash_mem_read_out(uint32_t addr, unsigned size, uint32_t val, uint32_t pc) "addr=0x%02x (%u), val=0x%08x, pc=0x%x"
 ot_flash_op_complete(const char *op, bool success) "%s: %u"
+ot_flash_op_execute(const char *op) "%s"
 ot_flash_op_start(const char *op) "%s"
 ot_flash_set_error(const char *op, uint32_t err_code, uint32_t err_addr) "%s: err=%08x at addr=0x%06x"
 ot_flash_update_rd_watermark(unsigned val, unsigned watermark) "%u >= %u"

From 7e4bcea45a07fe5536885a9fbd42b81762748f3f Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 15:19:12 +0000
Subject: [PATCH 5/8] [ot] hw/opentitan: ot_flash: Add program (write)
 functionality

Adds functionality that allows single/multi-word program (write)
operations. This is similar to reading, but must also propagate changes
back to our flash backend via the `blk_pwrite` API. The existing
address calculation functionality from the flash read is re-used, as
the flash controller transaction arbitration logic remains common
across these operations.

Upon encountering an error in a multi-word program transaction, the
flash controller will continue to empty the program FIFO, but not
perform any further writes. Writes before the error are valid.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c   | 114 ++++++++++++++++++++++++++++++++++----
 hw/opentitan/trace-events |   2 +
 2 files changed, 106 insertions(+), 10 deletions(-)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index e29045f070e89..a1897d5b926c8 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -775,6 +775,20 @@ static void ot_flash_update_alerts(OtFlashState *s)
     }
 }
 
+static bool ot_flash_is_backend_writable(const OtFlashState *s)
+{
+    return (s->blk != NULL) && blk_is_writable(s->blk);
+}
+
+static bool ot_flash_write_backend(OtFlashState *s, const void *buffer,
+                                   unsigned offset, size_t size)
+{
+    /* NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) */
+    return blk_pwrite(s->blk, (int64_t)(intptr_t)offset, (int64_t)size, buffer,
+                      (BdrvRequestFlags)0);
+    /* NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) */
+}
+
 static bool ot_flash_is_disabled(OtFlashState *s)
 {
     return s->regs[R_DIS] != OT_MULTIBITBOOL4_FALSE;
@@ -1026,6 +1040,88 @@ static void ot_flash_op_read(OtFlashState *s)
     }
 }
 
+static void ot_flash_op_prog(OtFlashState *s)
+{
+    if (ot_fifo32_is_empty(&s->prog_fifo)) {
+        xtrace_ot_flash_error("prog while prog FIFO empty");
+        return;
+    }
+
+    OtFlashStorage *storage = &s->flash;
+    uint32_t *dest = s->op.info_part ? storage->info : storage->data;
+
+    while (s->op.remaining) {
+        if (ot_flash_fifo_in_reset(s)) {
+            continue;
+        }
+        uint32_t word = ot_fifo32_pop(&s->prog_fifo);
+        s->regs[R_STATUS] &= ~R_STATUS_PROG_FULL_MASK;
+        ot_flash_update_prog_watermark(s);
+        bool fifo_empty = ot_fifo32_is_empty(&s->prog_fifo);
+        if (fifo_empty) {
+            s->regs[R_STATUS] |= R_STATUS_PROG_EMPTY_MASK;
+            s->regs[R_INTR_STATE] |= INTR_PROG_EMPTY_MASK;
+            ot_flash_update_irqs(s);
+        }
+
+        /* Must calculate next addr before decrementing the remaining count. */
+        unsigned address = 0u;
+        if (!s->op.failed) {
+            address = s->op.info_part ? ot_flash_next_info_address(s) :
+                                        ot_flash_next_data_address(s);
+            address /= sizeof(uint32_t); /* convert to word address */
+        }
+        s->op.remaining--;
+
+        /*
+         * On encountering a multi-word write error, we must continue to empty
+         * the prog FIFO regardless, hence we retrieve the word to program
+         * before checking for errors.
+         */
+        trace_ot_flash_op_prog(s->op.address, s->op.count, s->op.remaining,
+                               s->op.failed, fifo_empty);
+        if (s->op.failed) {
+            if (!fifo_empty) {
+                continue;
+            }
+            break;
+        }
+
+        /*
+         * Bits cannot be programmed back to 1 once programmed to 0; they must
+         * be erased instead.
+         */
+        g_assert(address <
+                 ((s->op.info_part ? storage->info_size : storage->data_size) *
+                  storage->bank_count));
+        dest[address] &= word;
+        trace_ot_flash_prog_word(s->op.info_part, address, word);
+        if (ot_flash_is_backend_writable(s)) {
+            uintptr_t dest_offset = (uintptr_t)dest - (uintptr_t)storage->data;
+            if (ot_flash_write_backend(s, &dest[address],
+                                       (unsigned)(dest_offset + address),
+                                       sizeof(uint32_t))) {
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "%s: cannot update flash backend\n", __func__);
+                ot_flash_set_error(s, R_ERR_CODE_PROG_ERR_MASK,
+                                   address * sizeof(uint32_t));
+            }
+        }
+
+        if (fifo_empty) {
+            break;
+        }
+    }
+
+    /*
+     * If we finished the entire program operation (i.e. no early exit as FIFO
+     * is empty), mark the operation as completed.
+     */
+    if (!s->op.remaining) {
+        ot_flash_op_complete(s);
+    }
+}
+
 static void ot_flash_op_execute(OtFlashState *s)
 {
     switch (s->op.kind) {
@@ -1033,6 +1129,10 @@ static void ot_flash_op_execute(OtFlashState *s)
         trace_ot_flash_op_execute(OP_NAME(s->op.kind));
         ot_flash_op_read(s);
         break;
+    case OP_PROG:
+        trace_ot_flash_op_execute(OP_NAME(s->op.kind));
+        ot_flash_op_prog(s);
+        break;
     default:
         xtrace_ot_flash_error("unsupported");
         break;
@@ -1897,12 +1997,6 @@ static const char *ot_flash_hexdump(const uint8_t *buf, size_t size)
 
 static void ot_flash_load(OtFlashState *s, Error **errp)
 {
-    /*
-     * Notes:
-     *   1. only support read access to the flash backend
-     *   2. only data partition for now
-     */
-
     OtFlashStorage *flash = &s->flash;
     memset(flash, 0, sizeof(OtFlashStorage));
 
@@ -1925,7 +2019,7 @@ static void ot_flash_load(OtFlashState *s, Error **errp)
             blk_blockalign(s->blk, sizeof(OtFlashBackendHeader));
 
         int rc;
-        // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
+        /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */
         rc = blk_pread(s->blk, 0, sizeof(*header), header, 0);
         if (rc < 0) {
             error_setg(errp, "failed to read the flash header content: %d", rc);
@@ -1979,7 +2073,7 @@ static void ot_flash_load(OtFlashState *s, Error **errp)
         unsigned offset = offsetof(OtFlashBackendHeader, hlength) +
                           sizeof(header->hlength) + header->hlength;
 
-        // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
+        /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */
         rc = blk_pread(s->blk, (int64_t)offset, flash_size, flash->storage, 0);
         if (rc < 0) {
             error_setg(errp, "failed to read the initial flash content: %d",
@@ -1994,10 +2088,10 @@ static void ot_flash_load(OtFlashState *s, Error **errp)
         size_t debug_trailer_size =
             (size_t)(flash->bank_count) * ELFNAME_SIZE * BIN_APP_COUNT;
         uint8_t *elfnames = blk_blockalign(s->blk, debug_trailer_size);
-        // NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange)
+        /* NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) */
         rc = blk_pread(s->blk, (int64_t)offset + flash_size,
                        (int64_t)debug_trailer_size, elfnames, 0);
-        // NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange)
+        /* NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) */
         if (!rc) {
             const char *elfname = (const char *)elfnames;
             for (unsigned ix = 0; ix < BIN_APP_COUNT; ix++) {
diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events
index abe48b2bd8ca2..231b12db2efd5 100644
--- a/hw/opentitan/trace-events
+++ b/hw/opentitan/trace-events
@@ -190,6 +190,8 @@ ot_flash_set_error(const char *op, uint32_t err_code, uint32_t err_addr) "%s: er
 ot_flash_update_rd_watermark(unsigned val, unsigned watermark) "%u >= %u"
 ot_flash_update_prog_watermark(unsigned val, unsigned watermark) "%u <= %u"
 ot_flash_reset_fifo(const char *name) "%s"
+ot_flash_op_prog(unsigned op_addr, unsigned count, unsigned remaining, bool failed, bool fifo_empty) "op_addr 0x%06x count %u remaining %u failed %u fifo_empty %u"
+ot_flash_prog_word(bool info_part, unsigned word_addr, uint32_t word) "info_part %u word_addr %u word %u"
 
 # ot_gpio.c
 

From 2885588f452ea8dfb0f045d7745b4c3c635b3edf Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 15:28:42 +0000
Subject: [PATCH 6/8] [ot] hw/opentitan: ot_flash: Add page/bank erase
 functionality

Adds flash erase operation functionality, which works at either page or
bank granularities. Erase ops hence do not have lengths/counts, and the
area to be erased is determine based on the address and partition
selections. Successful erases are then propagated to the flash backend.

Page erases use similar functionality to read/programs to calculate the
page address and erase the page. Bank erases are more involved: they
bypass regular memory protection mechanisms. This commit implements
these bank protection mechanisms, as well as the logic for erasing just
the data partition of the bank, or both data and info partitions.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c   | 148 +++++++++++++++++++++++++++++++++++++-
 hw/opentitan/trace-events |   1 +
 2 files changed, 146 insertions(+), 3 deletions(-)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index a1897d5b926c8..9ac5b28d1f76f 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -789,12 +789,12 @@ static bool ot_flash_write_backend(OtFlashState *s, const void *buffer,
     /* NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) */
 }
 
-static bool ot_flash_is_disabled(OtFlashState *s)
+static bool ot_flash_is_disabled(const OtFlashState *s)
 {
     return s->regs[R_DIS] != OT_MULTIBITBOOL4_FALSE;
 }
 
-static bool ot_flash_regs_is_wr_enabled(OtFlashState *s, unsigned regwen)
+static bool ot_flash_regs_is_wr_enabled(const OtFlashState *s, unsigned regwen)
 {
     return (bool)(s->regs[regwen] & REGWEN_EN_MASK);
 }
@@ -873,7 +873,7 @@ static void ot_flash_initialize(OtFlashState *s)
               qemu_clock_get_ns(OT_VIRTUAL_CLOCK) + OP_INIT_DURATION_NS);
 }
 
-static bool ot_flash_fifo_in_reset(OtFlashState *s)
+static bool ot_flash_fifo_in_reset(const OtFlashState *s)
 {
     return (bool)s->regs[R_FIFO_RST];
 }
@@ -923,6 +923,23 @@ static void ot_flash_op_complete(OtFlashState *s)
     ot_flash_update_irqs(s);
 }
 
+static bool ot_flash_can_erase_bank(const OtFlashState *s, unsigned bank)
+{
+    switch (bank) {
+    case 0u:
+        return (bool)FIELD_EX32(s->regs[R_MP_BANK_CFG_SHADOWED],
+                                MP_BANK_CFG_SHADOWED, ERASE_EN_0);
+    case 1u:
+        return (bool)FIELD_EX32(s->regs[R_MP_BANK_CFG_SHADOWED],
+                                MP_BANK_CFG_SHADOWED, ERASE_EN_1);
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: unknown bank %u for bank erase operation", __func__,
+                      bank);
+        return false;
+    }
+}
+
 static unsigned ot_flash_next_info_address(OtFlashState *s)
 {
     OtFlashStorage *storage = &s->flash;
@@ -1122,6 +1139,127 @@ static void ot_flash_op_prog(OtFlashState *s)
     }
 }
 
+static void ot_flash_op_erase_page(OtFlashState *s, unsigned address)
+{
+    OtFlashStorage *storage = &s->flash;
+    uint32_t *dest = s->op.info_part ? storage->info : storage->data;
+    unsigned page_size = BYTES_PER_PAGE;
+    unsigned page_address = address - (address % page_size);
+    page_address /= sizeof(uint32_t); /* convert to word address */
+
+    g_assert((page_address + page_size) <
+             ((s->op.info_part ? storage->info_size : storage->data_size) *
+              storage->bank_count));
+    memset(&dest[page_address], 0xFFu, page_size);
+    trace_ot_flash_erase(s->op.info_part, page_address, page_size);
+    if (ot_flash_is_backend_writable(s)) {
+        uintptr_t dest_offset = (uintptr_t)dest - (uintptr_t)storage->data;
+        if (ot_flash_write_backend(s, &dest[page_address],
+                                   (unsigned)(dest_offset + page_address),
+                                   page_size)) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: cannot update flash backend\n",
+                          __func__);
+            ot_flash_set_error(s, R_ERR_CODE_PROG_ERR_MASK,
+                               address * sizeof(uint32_t));
+            ot_flash_op_complete(s);
+            return;
+        }
+    }
+
+    ot_flash_op_complete(s);
+}
+
+static void ot_flash_op_erase_bank(OtFlashState *s, unsigned address)
+{
+    OtFlashStorage *storage = &s->flash;
+    unsigned bank_size =
+        s->op.info_part ? storage->info_size : storage->data_size;
+    unsigned bank = address / bank_size;
+
+    if (!ot_flash_can_erase_bank(s, bank)) {
+        qemu_log_mask(
+            LOG_GUEST_ERROR,
+            "%s: cannot erase bank %u when bank-wide erase not enabled\n",
+            __func__, bank);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, address);
+        ot_flash_op_complete(s);
+        return;
+    }
+
+    /*
+        * For bank erase only, if the data partition is selected, just the
+        * data partition is erased. If the info partition is selected, BOTH
+        * the data and info partitions are erased.
+        */
+    unsigned bank_address = address - (address % bank_size);
+    bank_address /= sizeof(uint32_t); /* convert to word address */
+    unsigned data_address, info_address = 0u;
+    if (!s->op.info_part) {
+        data_address = bank_address;
+        g_assert((data_address + bank_size) <=
+                 (storage->data_size * storage->bank_count));
+        memset(&storage->data[data_address], 0xFFu, bank_size);
+        trace_ot_flash_erase(s->op.info_part, data_address, bank_size);
+    } else {
+        info_address = bank_address;
+        g_assert((info_address + bank_size) <=
+                 (storage->info_size * storage->bank_count));
+        memset(&storage->info[info_address], 0xFFu, bank_size);
+        trace_ot_flash_erase(true, info_address, bank_size);
+
+        bank_size = storage->data_size;
+        data_address = bank_size * bank / sizeof(uint32_t);
+        g_assert((data_address + bank_size) <=
+                 (storage->data_size * storage->bank_count));
+        memset(&storage->data[data_address], 0xFFu, bank_size);
+        trace_ot_flash_erase(false, data_address, bank_size);
+    }
+
+    if (ot_flash_is_backend_writable(s)) {
+        int data_write_err =
+            ot_flash_write_backend(s, &storage->data[data_address],
+                                   (unsigned)(data_address),
+                                   storage->data_size);
+        int info_write_err = 0u;
+        if (s->op.info_part) {
+            uintptr_t offset =
+                (uintptr_t)storage->info - (uintptr_t)storage->data;
+            info_write_err =
+                ot_flash_write_backend(s, &storage->info[info_address],
+                                       (unsigned)(offset + info_address),
+                                       storage->info_size);
+        }
+
+        if (data_write_err || info_write_err) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: cannot update flash backend\n",
+                          __func__);
+            ot_flash_set_error(s, R_ERR_CODE_PROG_ERR_MASK,
+                               address * sizeof(uint32_t));
+            ot_flash_op_complete(s);
+            return;
+        }
+    }
+
+    ot_flash_op_complete(s);
+}
+
+static void ot_flash_op_erase(OtFlashState *s)
+{
+    unsigned address = s->op.info_part ? ot_flash_next_info_address(s) :
+                                         ot_flash_next_data_address(s);
+    if (s->op.failed) {
+        ot_flash_op_complete(s);
+        return;
+    }
+
+    /* Try to erase all bits in the page/bank back to 1. */
+    if (s->op.erase_sel == ERASE_SEL_PAGE) {
+        ot_flash_op_erase_page(s, address);
+    } else { /* ERASE_SEL_BANK */
+        ot_flash_op_erase_bank(s, address);
+    }
+}
+
 static void ot_flash_op_execute(OtFlashState *s)
 {
     switch (s->op.kind) {
@@ -1133,6 +1271,10 @@ static void ot_flash_op_execute(OtFlashState *s)
         trace_ot_flash_op_execute(OP_NAME(s->op.kind));
         ot_flash_op_prog(s);
         break;
+    case OP_ERASE:
+        trace_ot_flash_op_execute(OP_NAME(s->op.kind));
+        ot_flash_op_erase(s);
+        break;
     default:
         xtrace_ot_flash_error("unsupported");
         break;
diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events
index 231b12db2efd5..dbb8bb49e6bc6 100644
--- a/hw/opentitan/trace-events
+++ b/hw/opentitan/trace-events
@@ -192,6 +192,7 @@ ot_flash_update_prog_watermark(unsigned val, unsigned watermark) "%u <= %u"
 ot_flash_reset_fifo(const char *name) "%s"
 ot_flash_op_prog(unsigned op_addr, unsigned count, unsigned remaining, bool failed, bool fifo_empty) "op_addr 0x%06x count %u remaining %u failed %u fifo_empty %u"
 ot_flash_prog_word(bool info_part, unsigned word_addr, uint32_t word) "info_part %u word_addr %u word %u"
+ot_flash_erase(bool info_part, unsigned word_addr, unsigned num_bytes) "info_part %u word_addr %u num_bytes %u"
 
 # ot_gpio.c
 

From a636e5af7c8dd0700206e93c7a2438dabbd9bf4b Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 15:37:15 +0000
Subject: [PATCH 7/8] [ot] hw/opentitan: ot_flash: Add memory protection
 functionality

Adds the functionality for the flash controller's Memory Protection, for
both info and data partitions.

For access to info partitions, each page has a separate register
`BANKX_INFOY_PAGE_CFG_Z` which controls the permissions for that
specific page. For access to data partitions, there are a sequence of
10 `MP_REGION_X` and `MP_REGION_CFG_X` registers which define the
size/bounds and permissions of 10 different regions of flash memory
respectively. Data MP regions are defined by a base page and a size in
base pages, used to calculate their relative bounds. Any data page
accesses that do not fall under any of these 10 (enabled) regions will
instead use the memory protection permissions of the default region,
which is configured through the DEFAULT_REGION register.

All MP configuration registers (except the DEFAULT_REGION) can be
enabled/disabled. If enabled, they will match and their permissions
apply. For data pages, if multiple regions match a given address, the
region with the lowest matching index is applied. Implemented MP
permissions include read/program/erase.

This commit also introduces a `no-protection` property to allow the
optional disabling of these MP checks if not needed. Although manual
testing suggests low overheads, this avoids the overhead introduced by
querying the ranges of all 10 regions for every data word read/progs.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c | 210 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 210 insertions(+)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index 9ac5b28d1f76f..f14fcebf2d31d 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -755,6 +755,7 @@ struct OtFlashState {
     OtFlashStorage flash;
 
     BlockBackend *blk; /* Flash backend */
+    bool no_mem_prot; /* Flag to disable mem protection features */
 };
 
 static void ot_flash_update_irqs(OtFlashState *s)
@@ -923,6 +924,121 @@ static void ot_flash_op_complete(OtFlashState *s)
     ot_flash_update_irqs(s);
 }
 
+static uint32_t ot_flash_get_info_page_cfg_reg(
+    unsigned bank, unsigned info_partition, unsigned page)
+{
+    switch (bank) {
+    case 0u:
+        switch (info_partition) {
+        case 0u:
+            return R_BANK0_INFO0_PAGE_CFG_0 + page;
+        case 1u:
+            return R_BANK0_INFO1_PAGE_CFG;
+        case 2u:
+            return R_BANK0_INFO2_PAGE_CFG_0 + page;
+        default:
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid info partition: %u\n",
+                          __func__, info_partition);
+            return 0u;
+        }
+    case 1u:
+        switch (info_partition) {
+        case 0u:
+            return R_BANK1_INFO0_PAGE_CFG_0 + page;
+        case 1u:
+            return R_BANK1_INFO1_PAGE_CFG;
+        case 2u:
+            return R_BANK1_INFO2_PAGE_CFG_0 + page;
+        default:
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid info partition: %u\n",
+                          __func__, info_partition);
+            return 0u;
+        }
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid bank: %d\n", __func__,
+                      bank);
+        return 0u;
+    }
+}
+
+static bool
+ot_flash_info_page_cfg_op_enabled(OtFlashState *s, uint32_t info_page_cfg_reg)
+{
+    unsigned en_field;
+    switch (s->op.kind) {
+    case OP_READ:
+        en_field = SHARED_FIELD_EX32(s->regs[info_page_cfg_reg],
+                                     BANK_INFO_PAGE_CFG_RD_EN);
+        break;
+    case OP_PROG:
+        en_field = SHARED_FIELD_EX32(s->regs[info_page_cfg_reg],
+                                     BANK_INFO_PAGE_CFG_PROG_EN);
+        break;
+    case OP_ERASE:
+        en_field = SHARED_FIELD_EX32(s->regs[info_page_cfg_reg],
+                                     BANK_INFO_PAGE_CFG_ERASE_EN);
+        break;
+    case OP_NONE:
+        xtrace_ot_flash_error("cannot check mp without operation");
+        return false;
+    default:
+        xtrace_ot_flash_error("unsupported operation?");
+        return false;
+    }
+    return en_field == OT_MULTIBITBOOL4_TRUE;
+}
+
+static bool
+ot_flash_mp_region_cfg_op_enabled(OtFlashState *s, uint32_t mp_cfg_reg)
+{
+    unsigned en_field;
+    switch (s->op.kind) {
+    case OP_READ:
+        en_field = SHARED_FIELD_EX32(s->regs[mp_cfg_reg], MP_REGION_CFG_RD_EN);
+        break;
+    case OP_PROG:
+        en_field =
+            SHARED_FIELD_EX32(s->regs[mp_cfg_reg], MP_REGION_CFG_PROG_EN);
+        break;
+    case OP_ERASE:
+        en_field =
+            SHARED_FIELD_EX32(s->regs[mp_cfg_reg], MP_REGION_CFG_ERASE_EN);
+        break;
+    case OP_NONE:
+        xtrace_ot_flash_error("cannot check mp without operation");
+        return false;
+    default:
+        xtrace_ot_flash_error("unsupported operation?");
+        return false;
+    }
+    return en_field == OT_MULTIBITBOOL4_TRUE;
+}
+
+static bool ot_flash_default_region_cfg_op_enabled(OtFlashState *s)
+{
+    unsigned en_field;
+    switch (s->op.kind) {
+    case OP_READ:
+        en_field = FIELD_EX32(s->regs[R_DEFAULT_REGION], DEFAULT_REGION, RD_EN);
+        break;
+    case OP_PROG:
+        en_field =
+            FIELD_EX32(s->regs[R_DEFAULT_REGION], DEFAULT_REGION, PROG_EN);
+        break;
+    case OP_ERASE:
+        en_field =
+            FIELD_EX32(s->regs[R_DEFAULT_REGION], DEFAULT_REGION, ERASE_EN);
+        break;
+    case OP_NONE:
+        xtrace_ot_flash_error("cannot check mp without operation");
+        return false;
+    default:
+        xtrace_ot_flash_error("unsupported operation?");
+        return false;
+    }
+    return en_field == OT_MULTIBITBOOL4_TRUE;
+}
+
 static bool ot_flash_can_erase_bank(const OtFlashState *s, unsigned bank)
 {
     switch (bank) {
@@ -987,6 +1103,35 @@ static unsigned ot_flash_next_info_address(OtFlashState *s)
     unsigned address = address_in_bank + bank_offset + info_part_offset;
     trace_ot_flash_info_part(s->op.address, s->op.count, s->op.remaining, bank,
                              info_partition, address);
+    if (s->no_mem_prot ||
+        (s->op.kind == OP_ERASE && s->op.erase_sel == ERASE_SEL_BANK)) {
+        return address;
+    }
+
+    /* Check the matching info partition page config register */
+    unsigned page = address_in_bank / BYTES_PER_PAGE;
+    uint32_t info_page_cfg_reg =
+        ot_flash_get_info_page_cfg_reg(bank, info_partition, page);
+    if (!info_page_cfg_reg) {
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
+        s->op.failed = true;
+        return address;
+    }
+    if (SHARED_FIELD_EX32(s->regs[info_page_cfg_reg], BANK_INFO_PAGE_CFG_EN) !=
+        OT_MULTIBITBOOL4_TRUE) {
+        return address; /* page config is disabled; so access is permitted. */
+    }
+    if (!ot_flash_info_page_cfg_op_enabled(s, info_page_cfg_reg)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: operation %s on info page %u in partition %u of "
+                      "bank %u is disabled by page config\n",
+                      __func__, OP_NAME(s->op.kind), page, bank,
+                      info_partition);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
+        s->op.failed = true;
+        return address;
+    }
+
     return address;
 }
 
@@ -1008,6 +1153,68 @@ static unsigned ot_flash_next_data_address(OtFlashState *s)
     }
     trace_ot_flash_data_part(s->op.address, s->op.count, s->op.remaining, bank,
                              address);
+
+    if (s->no_mem_prot ||
+        (s->op.kind == OP_ERASE && s->op.erase_sel == ERASE_SEL_BANK)) {
+        return address;
+    }
+
+    /*
+     * Check through the memory protection regions 0->9, and see if the current
+     * page falls within a region. If so, and it is enabled, apply its perms.
+     * Otherwise apply the default region's permissions. Note that MP region
+     * page indexes are cumulative across banks.
+     *
+     * If any two MP regions overlap, the lower index region has priority.
+     */
+    unsigned page = address / BYTES_PER_PAGE;
+    bool matching_region_found = false;
+    for (unsigned region = 0u; region < NUM_REGIONS; region++) {
+        /* Ignore disabled regions */
+        unsigned r_region_cfg = R_MP_REGION_CFG_0 + region;
+        if (SHARED_FIELD_EX32(s->regs[r_region_cfg], MP_REGION_CFG_EN) !=
+            OT_MULTIBITBOOL4_TRUE) {
+            continue;
+        }
+
+        /*
+         * Check if the current flash word falls in this region.
+         * Size is inclusive at the base, but exclusive at (base+size).
+         */
+        unsigned r_region = R_MP_REGION_0 + region;
+        unsigned region_base_page =
+            SHARED_FIELD_EX32(s->regs[r_region], MP_REGION_BASE);
+        unsigned region_size =
+            SHARED_FIELD_EX32(s->regs[r_region], MP_REGION_SIZE);
+        if (page < region_base_page ||
+            page >= (region_base_page + region_size)) {
+            continue;
+        }
+        matching_region_found = true;
+
+        /* Page does fall in this region, so check if enabled for operation. */
+        if (!ot_flash_mp_region_cfg_op_enabled(s, r_region_cfg)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: operation %s on page %u of data partition in "
+                          "bank %u is disabled by MP region %u\n",
+                          __func__, OP_NAME(s->op.kind), page, bank, region);
+            ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, address);
+            s->op.failed = true;
+            return address;
+        }
+        break;
+    }
+
+    /* If page not in any region, apply the default region's permissions. */
+    if (!matching_region_found && !ot_flash_default_region_cfg_op_enabled(s)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: operation %s on page %u of data partition in bank "
+                      "%u is disabled by default region\n",
+                      __func__, OP_NAME(s->op.kind), page, bank);
+        ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, address);
+        s->op.failed = true;
+        return address;
+    }
     return address;
 }
 
@@ -2002,6 +2209,9 @@ static void ot_flash_csrs_write(void *opaque, hwaddr addr, uint64_t val64,
 
 static Property ot_flash_properties[] = {
     DEFINE_PROP_DRIVE("drive", OtFlashState, blk),
+    /* Optionally disable memory protection, as searching for valid memory
+    regions and checking their config can slow down regular operation. */
+    DEFINE_PROP_BOOL("no-mem-prot", OtFlashState, no_mem_prot, false),
     DEFINE_PROP_END_OF_LIST(),
 };
 

From 27464baa0372569045362b9a5438b15afbcda1d4 Mon Sep 17 00:00:00 2001
From: Alex Jones <alex.jones@lowrisc.org>
Date: Thu, 6 Feb 2025 15:53:38 +0000
Subject: [PATCH 8/8] [ot] hw/opentitan: ot_flash: clear erase suspend &
 documentation

This commit adds a "partway" fix for erase suspend implementation,
immediately clearing the request to simulate the response of "no erase
ongoing", as currently QEMU flash erases are modelled as synchronous
(i.e. the entire erase happens at once, and cannot be suspended).

Also updates the author information in the file prologue, and introduces
some additional documentation about the limitations of the current QEMU
model, and a couple of relevant TODOs.

Signed-off-by: Alex Jones <alex.jones@lowrisc.org>
---
 hw/opentitan/ot_flash.c | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/hw/opentitan/ot_flash.c b/hw/opentitan/ot_flash.c
index f14fcebf2d31d..a23c2f70b9d97 100644
--- a/hw/opentitan/ot_flash.c
+++ b/hw/opentitan/ot_flash.c
@@ -6,6 +6,7 @@
  *
  * Author(s):
  *  Emmanuel Blot <eblot@rivosinc.com>
+ *  Alex Jones <alex.jones@lowrisc.org>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -25,8 +26,15 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  *
- * Note: for now, only a minimalist subset of Power Manager device is
- *       implemented in order to enable OpenTitan's ROM boot to progress
+ * Known limitations:
+ *  - ECC/ICV/Scrambling functionality is not yet implemented in QEMU,
+ *    including ECC single error support.
+ *  - Alert functionality is not yet modelled (outside of test alerts).
+ *  - Program Repair / High Endurance enables are meaningless in the OpenTitan
+ *    Generic Flash Bank and so are not emulated.
+ *  - Erase Suspend is not emulated in QEMU (erases are done synchronously, so
+ *    you can suspend, but the bit will immediately be cleared).
+ *  - HW info cfg overrides are not modelled in QEMU.
  */
 
 #include "qemu/osdep.h"
@@ -769,6 +777,7 @@ static void ot_flash_update_irqs(OtFlashState *s)
 
 static void ot_flash_update_alerts(OtFlashState *s)
 {
+    /* @todo Implement non-test alert sources and update them here as well. */
     uint32_t level = s->regs[R_ALERT_TEST];
 
     for (unsigned ix = 0u; ix < PARAM_NUM_ALERTS; ix++) {
@@ -834,7 +843,6 @@ static void ot_flash_update_prog_watermark(OtFlashState *s)
     ot_flash_update_irqs(s);
 }
 
-
 static void ot_flash_op_signal(void *opaque)
 {
     OtFlashState *s = opaque;
@@ -1838,8 +1846,13 @@ static void ot_flash_regs_write(void *opaque, hwaddr addr, uint64_t val64,
         s->regs[reg] = val32;
         break;
     case R_ERASE_SUSPEND:
-        val32 &= R_ERASE_SUSPEND_REQ_MASK;
-        s->regs[reg] = val32;
+        /*
+         * @todo We do not implement the erase suspend operation in QEMU as we
+         * do all erases synchronously, and so just immediately clear the erase
+         * suspend request. To implement this feature properly we would have to
+         * add delay to bank erases & check for erase suspends at each step.
+         */
+        s->regs[reg] = 0u;
         break;
     case R_REGION_CFG_REGWEN_0:
     case R_REGION_CFG_REGWEN_1:
@@ -2498,6 +2511,8 @@ static void ot_flash_load(OtFlashState *s, Error **errp)
      *   - INFO1 bank 1
      *   - INFO2 bank 1
      * - Debug info (ELF file names)
+     *
+     * @todo Add ECC section to backend for ECC/ICV support also
      */
     flash->data = (uint32_t *)(base);
     flash->info =