summary refs log tree commit diff stats
path: root/hw/nvram/aspeed_otp.c
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2025-09-29 10:52:48 -0700
committerRichard Henderson <richard.henderson@linaro.org>2025-09-29 10:52:48 -0700
commit85a3fd1c4cb3ba7a9eb291c1e222f935e922d1f7 (patch)
treec7fc5408fc09ff41395e48a511b9267c6c51ea48 /hw/nvram/aspeed_otp.c
parent9b16edec6e9a483469c789475b2065d26b52db35 (diff)
parent9ec30a07483640ecb8417fce3dfa9273f7a036c9 (diff)
downloadfocaccia-qemu-85a3fd1c4cb3ba7a9eb291c1e222f935e922d1f7.tar.gz
focaccia-qemu-85a3fd1c4cb3ba7a9eb291c1e222f935e922d1f7.zip
Merge tag 'pull-aspeed-20250929' of https://github.com/legoater/qemu into staging
aspeed queue:

* Introduce a new ASPEED OTP memory device model integrated with the
  Secure Boot Controller. It includes a new block device backend
  ('drive' property), is enabled for AST2600 SoCs and AST1030 SoCs.
  Functional tests are included
* Changed "ast2700-evb" alias to point to the "ast2700a1-evb" machine
* Introduce support for Aspeed PCIe host controller, including models
  for the PCIe Root Complex, Root Port, and PHY. Enabled for the
  AST2600 and AST2700 SoCs, and functional tests are included
* Refactor Boot ROM support to improve code reuse across the different
  Aspeed machine. This is in preparation of vbootrom support in the
  ast2700fc machine
* Improved Error Handling in the AST27x0-fc machine init functions

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEEoPZlSPBIlev+awtgUaNDx8/77KEFAmjauRoACgkQUaNDx8/7
# 7KGAxA//YdPPGf8vKhPeblUt0/3760GGhI17TBWJFVZP/aZYcIiE0oRxo5zH0Lne
# YjwFKTtx7GXzbE2wqVCLSt/VPDAEMk6wZGwGvMbmeydssyNjbPuF79+EVYnFsUrQ
# Zkm8YPf/qFcKYFxp8O5GTKedAu70AFDMkFwy2xuBRqE5v0RQJe20+EHaiEC8S+3a
# z5PIZJ74J3m4d+h+BlIHoiPe7hwTiyQ8V4rrWKWupwqDBExZfgNGX0zGPZDOlwOo
# bpV38gb0ugyG93/FJSXyXQqiiH5h+10CaSzc1QuytYtQXAM2qj60Kh86YruTsbLu
# g3TUz+jOgDatTk/MhH8q/gtwDjmqcygGeybbMJZeCzhq1qLIFgJW2KwPNwj8eHCd
# 7jZp6NT9GekVMB+FghApWjc63EozKveJ3wzyHE481GGF7TgvuVF1Km+dVHNPjpBz
# pjXgIeKmDl0hmgGp3Se9S8B1ryWK3+KvuNoKe63UK/NMCkSXF3xTerkU1evJjIrp
# B9Tus7kLRqbDGWPyprp1d7Jv6MKJ6sELKvGHlalMcnzo4vAvQu1RB5s1kYqsCGlY
# 414Bc2v/YdkLxQGU6hCp1rABq3sIdWVzxRJ4c0XalRNZBkOmlsy1p5FaG5RXQdhz
# Gm27nzDAWBeNmWD6Jjjj6VwWmqBbSO4M4mYVTMnTfEaO7y/l1d4=
# =7BzG
# -----END PGP SIGNATURE-----
# gpg: Signature made Mon 29 Sep 2025 09:51:38 AM PDT
# gpg:                using RSA key A0F66548F04895EBFE6B0B6051A343C7CFFBECA1
# gpg: Good signature from "Cédric Le Goater <clg@redhat.com>" [full]
# gpg:                 aka "Cédric Le Goater <clg@kaod.org>" [full]

* tag 'pull-aspeed-20250929' of https://github.com/legoater/qemu: (32 commits)
  hw/arm/aspeed_ast27x0-fc: Make sub-init functions return bool with errp
  hw/arm/aspeed_ast27x0-fc: Drop dead return checks
  hw/arm/aspeed: Move aspeed_load_vbootrom to common SoC code
  hw/arm/aspeed: Move aspeed_install_boot_rom to common SoC code
  hw/arm/aspeed: Move write_boot_rom to common SoC code
  hw/arm/aspeed: Move aspeed_board_init_flashes() to common SoC code
  tests/functional/arm/test_aspeed_ast2600: Add PCIe and network test
  hw/arm/aspeed_ast27x0: Introduce 3 PCIe RCs for AST2700
  hw/pci-host/aspeed: Disable Root Device and place Root Port at 00:00.0 to AST2700
  hw/pci-host/aspeed: Add AST2700 PCIe config with dedicated H2X blocks
  hw/pci-host/aspeed: Add AST2700 PCIe PHY
  hw/arm/aspeed_ast2600: Add PCIe RC support (RC_H only)
  hw/arm/aspeed: Wire up PCIe devices in SoC model
  hw/pci-host/aspeed: Add MSI support and per-RC IOMMU address space
  hw/pci-host/aspeed: Add AST2600 PCIe Root Port and make address configurable
  hw/pci-host/aspeed: Add AST2600 PCIe Root Device support
  hw/pci-host/aspeed: Add AST2600 PCIe config space and host bridge
  hw/pci-host/aspeed: Add AST2600 PCIe PHY model
  hw/pci/pci_ids: Add PCI vendor ID for ASPEED
  tests/functional/arm: Add AST2600 boot test with generated OTP image
  ...

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'hw/nvram/aspeed_otp.c')
-rw-r--r--hw/nvram/aspeed_otp.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/hw/nvram/aspeed_otp.c b/hw/nvram/aspeed_otp.c
new file mode 100644
index 0000000000..dcf8ed3917
--- /dev/null
+++ b/hw/nvram/aspeed_otp.c
@@ -0,0 +1,190 @@
+/*
+ *  ASPEED OTP (One-Time Programmable) memory
+ *
+ *  Copyright (C) 2025 Aspeed
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "system/block-backend.h"
+#include "hw/qdev-properties.h"
+#include "hw/nvram/aspeed_otp.h"
+#include "hw/nvram/trace.h"
+
+static uint64_t aspeed_otp_read(void *opaque, hwaddr offset, unsigned size)
+{
+    AspeedOTPState *s = opaque;
+    uint64_t val = 0;
+
+    memcpy(&val, s->storage + offset, size);
+
+    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_otp_prog_conflict(otp_addr, programmed_bits);
+        for (int i = 0; i < 32; ++i) {
+            if (programmed_bits & (1U << i)) {
+                trace_aspeed_otp_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)
+{
+    AspeedOTPState *s = opaque;
+    bool is_odd = otp_addr & 1;
+    uint32_t otp_offset = otp_addr << 2;
+
+    memcpy(value, s->storage + otp_offset, sizeof(uint32_t));
+
+    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_otp_write(void *opaque, hwaddr otp_addr,
+                                uint64_t val, unsigned size)
+{
+    AspeedOTPState *s = opaque;
+    uint32_t otp_offset, value;
+
+    if (!program_otpmem_data(s, otp_addr, val, &value)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to program data, value = %x, bit = %"PRIx64"\n",
+                      __func__, value, val);
+        return;
+    }
+
+    otp_offset = otp_addr << 2;
+    memcpy(s->storage + otp_offset, &value, size);
+
+    if (s->blk) {
+        if (blk_pwrite(s->blk, otp_offset, size, &value, 0) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write %x to %x\n",
+                          __func__, value, otp_offset);
+
+            return;
+        }
+    }
+    trace_aspeed_otp_prog(otp_offset, val, value);
+}
+
+static bool aspeed_otp_init_storage(AspeedOTPState *s, Error **errp)
+{
+    uint32_t *p;
+    int i, num;
+    uint64_t perm;
+
+    if (s->blk) {
+        perm = BLK_PERM_CONSISTENT_READ |
+               (blk_supports_write_perm(s->blk) ? BLK_PERM_WRITE : 0);
+        if (blk_set_perm(s->blk, perm, BLK_PERM_ALL, errp) < 0) {
+            return false;
+        }
+        if (blk_pread(s->blk, 0, s->size, s->storage, 0) < 0) {
+            error_setg(errp, "Failed to read the initial flash content");
+            return false;
+        }
+    } else {
+        num = s->size / sizeof(uint32_t);
+        p = (uint32_t *)s->storage;
+        for (i = 0; i < num; i++) {
+            p[i] = (i % 2 == 0) ? 0x00000000 : 0xFFFFFFFF;
+        }
+    }
+    return true;
+}
+
+static const MemoryRegionOps aspeed_otp_ops = {
+    .read = aspeed_otp_read,
+    .write = aspeed_otp_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 4,
+    .valid.unaligned = true,
+    .impl.unaligned = true
+};
+
+static void aspeed_otp_realize(DeviceState *dev, Error **errp)
+{
+    AspeedOTPState *s = ASPEED_OTP(dev);
+
+    if (s->size == 0) {
+        error_setg(errp, "aspeed.otp: 'size' property must be set");
+        return;
+    }
+
+    s->storage = blk_blockalign(s->blk, s->size);
+
+    if (!aspeed_otp_init_storage(s, errp)) {
+        return;
+    }
+
+    memory_region_init_io(&s->mmio, OBJECT(dev), &aspeed_otp_ops,
+                          s, "aspeed.otp", s->size);
+    address_space_init(&s->as, &s->mmio, NULL);
+}
+
+static const Property aspeed_otp_properties[] = {
+    DEFINE_PROP_UINT64("size", AspeedOTPState, size, 0),
+    DEFINE_PROP_DRIVE("drive", AspeedOTPState, blk),
+};
+
+static void aspeed_otp_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    dc->realize = aspeed_otp_realize;
+    device_class_set_props(dc, aspeed_otp_properties);
+}
+
+static const TypeInfo aspeed_otp_info = {
+    .name          = TYPE_ASPEED_OTP,
+    .parent        = TYPE_DEVICE,
+    .instance_size = sizeof(AspeedOTPState),
+    .class_init    = aspeed_otp_class_init,
+};
+
+static void aspeed_otp_register_types(void)
+{
+    type_register_static(&aspeed_otp_info);
+}
+
+type_init(aspeed_otp_register_types)