From: Kane-Chen-AS <kane_c...@aspeedtech.com>

Introduce a QEMU model for ASPEED One-Time Programmable (OTP) memory.

This device provides:
- Bit-level irreversible programming (0→1 for even, 1→0 for odd)
- Read, program, and default-value initialization interfaces
- File-backed OTP content via machine parameter
- Trace support for bit conflict debugging
- Error propagation via QEMU's Error** interface

The OTP backend is designed to integrate with SoCs requiring secure fuse storage
or secure boot features.

Signed-off-by: Kane-Chen-AS <kane_c...@aspeedtech.com>
---
 hw/misc/aspeed_otpmem.c         | 198 ++++++++++++++++++++++++++++++++
 hw/misc/meson.build             |   1 +
 hw/misc/trace-events            |   5 +
 include/hw/misc/aspeed_otpmem.h |  39 +++++++
 4 files changed, 243 insertions(+)
 create mode 100644 hw/misc/aspeed_otpmem.c
 create mode 100644 include/hw/misc/aspeed_otpmem.h

diff --git a/hw/misc/aspeed_otpmem.c b/hw/misc/aspeed_otpmem.c
new file mode 100644
index 0000000000..4c1dee8782
--- /dev/null
+++ b/hw/misc/aspeed_otpmem.c
@@ -0,0 +1,198 @@
+/*
+ *  ASPEED OTP (One-Time Programmable) memory
+ *
+ *  Copyright (C) 2025 Aspeed
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/misc/aspeed_otpmem.h"
+
+void aspeed_otpmem_set_backend(AspeedOTPMemState *s, const char *path)
+{
+    g_free(s->otpmem_img_path);
+    s->otpmem_img_path = g_strdup(path);
+}
+
+static void aspeed_otpmem_sync_region(AspeedOTPMemState *s,
+                                      hwaddr offset, hwaddr size)
+{
+    memory_region_msync(&s->iomem, offset, size);
+}
+
+static uint32_t aspeed_otpmem_read(AspeedOtpmemState *s,
+                                   uint32_t addr, Error **errp)
+{
+    uint32_t val = 0;
+    MemTxResult ret;
+
+    ret = address_space_read(&s->as, addr, MEMTXATTRS_UNSPECIFIED,
+                             (uint8_t *)&val, sizeof(val));
+    if (ret != MEMTX_OK) {
+        error_setg(errp, "Failed to read data from 0x%x", addr);
+        return OTPMEM_ERR_MAGIC;
+    }
+    return val;
+}
+
+static bool valid_program_data(uint32_t otp_addr,
+                                 uint32_t value, uint32_t prog_bit)
+{
+    uint32_t programmed_bits, has_programmable_bits;
+    bool is_odd = otp_addr & 1;
+
+    /*
+     * prog_bit uses 0s to indicate target bits to program:
+     *   - if OTP word is even-indexed, programmed bits flip 0->1
+     *   - if odd, bits flip 1->0
+     * Bit programming is one-way only and irreversible.
+     */
+    if (is_odd) {
+        programmed_bits = ~value & prog_bit;
+    } else {
+        programmed_bits = value & (~prog_bit);
+    }
+
+    /* If any bit can be programmed, accept the request */
+    has_programmable_bits = value ^ (~prog_bit);
+
+    if (programmed_bits) {
+        trace_aspeed_otpmem_prog_conflict(otp_addr, programmed_bits);
+        for (int i = 0; i < 32; ++i) {
+            if (programmed_bits & (1U << i)) {
+                trace_aspeed_otpmem_prog_bit(i);
+            }
+        }
+    }
+
+    return has_programmable_bits != 0;
+}
+
+static bool program_otpmem_data(void *opaque, uint32_t otp_addr,
+                             uint32_t prog_bit, uint32_t *value)
+{
+    AspeedOTPMemState *s = ASPEED_OTPMEM(opaque);
+    bool is_odd = otp_addr & 1;
+    uint32_t otp_offset = otp_addr << 2;
+    MemTxResult ret;
+
+    ret = address_space_read(&s->as, otp_offset, MEMTXATTRS_UNSPECIFIED,
+                             value, sizeof(uint32_t));
+    if (ret != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "Failed to read data 0x%x\n",
+                      otp_offset);
+        return false;
+    }
+
+    if (!valid_program_data(otp_addr, *value, prog_bit)) {
+        return false;
+    }
+
+    if (is_odd) {
+        *value &= ~prog_bit;
+    } else {
+        *value |= ~prog_bit;
+    }
+
+    return true;
+}
+
+static void aspeed_otpmem_prog(AspeedOtpmemState *s, uint32_t otp_addr,
+                               uint32_t val, Error **errp)
+{
+    uint32_t otp_offset, value;
+    MemTxResult ret;
+
+    if (!program_otpmem_data(s, otp_addr, val, &value)) {
+        error_setg(errp, "Failed to program data");
+        return;
+    }
+
+    otp_offset = otp_addr << 2;
+    ret = address_space_write(&s->as, otp_offset, MEMTXATTRS_UNSPECIFIED,
+                              (uint8_t *)&value, sizeof(value));
+    if (ret != MEMTX_OK) {
+        error_setg(errp, "Failed to write %x to OTP [%x]", val, otp_addr);
+        return;
+    }
+    trace_aspeed_otpmem_prog(otp_offset, value, val);
+    aspeed_otpmem_sync_region(s, otp_offset, sizeof(value));
+}
+
+static void aspeed_otpmem_set_default(AspeedOtpmemState *s, uint32_t 
otp_offset,
+                                      uint32_t val, Error **errp)
+{
+    MemTxResult ret;
+
+    ret = address_space_write(&s->as, otp_offset, MEMTXATTRS_UNSPECIFIED,
+                              (uint8_t *)&val, sizeof(val));
+    if (ret != MEMTX_OK) {
+        error_setg(errp, "Failed to set value %x to OTP [%x]", val, 
otp_offset);
+        return;
+    }
+    aspeed_otpmem_sync_region(s, otp_offset, sizeof(val));
+}
+
+static const AspeedOTPMemOps aspeed_otpmem_default_ops = {
+    .read = aspeed_otpmem_read,
+    .prog = aspeed_otpmem_prog,
+    .set_default = aspeed_otpmem_set_default,
+};
+
+const AspeedOTPMemOps *aspeed_otpmem_get_ops(AspeedOTPMemState *s)
+{
+    return s->ops;
+}
+
+static void aspeed_otpmem_realize(DeviceState *dev, Error **errp)
+{
+    AspeedOTPMemState *s = ASPEED_OTPMEM(dev);
+    struct stat st;
+
+    s->size = OTPMEM_SIZE;
+    s->ops = &aspeed_otpmem_default_ops;
+
+    if (s->otpmem_img_path && strlen(s->otpmem_img_path)) {
+        if (stat(s->otpmem_img_path, &st) < 0) {
+            error_setg(errp, "Failed to open %s",
+                       s->otpmem_img_path);
+            return;
+        }
+        if (st.st_size != OTPMEM_SIZE) {
+            error_setg(errp, "Invalid OTP size %ld",
+                       st.st_size);
+            return;
+        }
+        memory_region_init_ram_from_file(&s->iomem, OBJECT(dev),
+                "aspeed.otpmem.backend", s->size, s->size,
+                RAM_SHARED, s->otpmem_img_path, 0, errp);
+        address_space_init(&s->as, &s->iomem, NULL);
+    }
+}
+
+static void aspeed_otpmem_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    dc->realize = aspeed_otpmem_realize;
+    dc->user_creatable = false;
+}
+
+static const TypeInfo aspeed_otpmem_info = {
+    .name          = TYPE_ASPEED_OTPMEM,
+    .parent        = TYPE_DEVICE,
+    .instance_size = sizeof(AspeedOTPMemState),
+    .class_init    = aspeed_otpmem_class_init,
+};
+
+static void aspeed_otpmem_register_types(void)
+{
+    type_register_static(&aspeed_otpmem_info);
+}
+
+type_init(aspeed_otpmem_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 6d47de482c..ed1eaaa2ad 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -136,6 +136,7 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
   'aspeed_sbc.c',
   'aspeed_sdmc.c',
   'aspeed_xdma.c',
+  'aspeed_otpmem.c',
   'aspeed_peci.c',
   'aspeed_sli.c'))
 
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 4383808d7a..e9df349f6d 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -90,6 +90,11 @@ slavio_sysctrl_mem_readl(uint32_t ret) "Read system control 
0x%08x"
 slavio_led_mem_writew(uint32_t val) "Write diagnostic LED 0x%04x"
 slavio_led_mem_readw(uint32_t ret) "Read diagnostic LED 0x%04x"
 
+# aspeed_otpmem.c
+aspeed_otpmem_prog(uint32_t addr, uint32_t prog_value, uint32_t value) "OTP 
Memory program: addr 0x%" PRIx32 " prog_value 0x%" PRIx32 " value 0x%" PRIx32
+aspeed_otpmem_prog_conflict(uint32_t addr, uint32_t bits) "Conflict at 
addr=0x%x, bits=0x%08x"
+aspeed_otpmem_prog_bit(int bit) "Programmed bit %d"
+
 # aspeed_scu.c
 aspeed_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" 
PRIx64 " of size %u: 0x%" PRIx32
 aspeed_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 
" of size %u: 0x%" PRIx32
diff --git a/include/hw/misc/aspeed_otpmem.h b/include/hw/misc/aspeed_otpmem.h
new file mode 100644
index 0000000000..f50f4cebf1
--- /dev/null
+++ b/include/hw/misc/aspeed_otpmem.h
@@ -0,0 +1,39 @@
+/*
+ *  ASPEED OTP (One-Time Programmable) memory
+ *
+ *  Copyright (C) 2025 Aspeed
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef ASPEED_OTPMMEM_H
+#define ASPEED_OTPMMEM_H
+
+#include "system/memory.h"
+
+#define OTPMEM_SIZE 0x4000
+#define OTPMEM_ERR_MAGIC 0x45727200
+#define TYPE_ASPEED_OTPMEM "aspeed.otpmem"
+OBJECT_DECLARE_SIMPLE_TYPE(AspeedOTPMemState, ASPEED_OTPMEM)
+
+typedef struct AspeedOTPMemOps {
+    uint32_t (*read)(AspeedOTPMemState *s, uint32_t addr, Error **errp);
+    void (*prog)(AspeedOTPMemState *s, uint32_t addr, uint32_t val, Error 
**errp);
+    void (*set_default)(AspeedOTPMemState *s, uint32_t addr, uint32_t val, 
Error **errp);
+} AspeedOTPMemOps;
+
+typedef struct AspeedOTPMemState {
+    DeviceState parent_obj;
+
+    MemoryRegion iomem;
+    AddressSpace as;
+    size_t size;
+
+    const AspeedOTPMemOps *ops;
+    char *otpmem_img_path;
+} AspeedOtpmemState;
+
+const AspeedOTPMemOps *aspeed_otpmem_get_ops(AspeedOTPMemState *s);
+void aspeed_otpmem_set_backend(AspeedOTPMemState *s, const char *path);
+#endif /* ASPEED_OTPMMEM_H */
-- 
2.43.0


Reply via email to