FU540 and PolarFire use the same SiFive L2 configuration and way-control
interface. Implement Config, monotonic WayEnable, and indexed WayMask
registers with a configurable master count for the 21-master FU540 and
15-master PolarFire integrations.

Own the fixed 2 MiB SRAM backing inside the controller and expose the
remaining LIM ways through a resizable alias. Also expose a separately
sized L2 Zero window backed by RAM to approximate scratchpad accesses.
The Zero aperture is independent of the cache/LIM way partition; cache
allocation, replacement, coherence, and ECC remain unimplemented.

Export register, LIM, and Zero windows for the SoC to map.

Signed-off-by: Bin Meng <[email protected]>

---

Changes in v3:
- Name the QOM parent field parent_obj
- Move the LIM and Zero windows into L2CC
- Use the shared SiFive name and indexed WayMask registers
- Parameterize master count and the RAM-backed Zero aperture
- Resize a LIM alias while keeping its SRAM backing fixed

Changes in v2:
- Use the Resettable API instead of a legacy reset handler

 include/hw/misc/sifive_l2cc.h |  42 +++++
 hw/misc/sifive_l2cc.c         | 282 ++++++++++++++++++++++++++++++++++
 hw/misc/Kconfig               |   3 +
 hw/misc/meson.build           |   1 +
 4 files changed, 328 insertions(+)
 create mode 100644 include/hw/misc/sifive_l2cc.h
 create mode 100644 hw/misc/sifive_l2cc.c

diff --git a/include/hw/misc/sifive_l2cc.h b/include/hw/misc/sifive_l2cc.h
new file mode 100644
index 0000000000..b1cd9c4054
--- /dev/null
+++ b/include/hw/misc/sifive_l2cc.h
@@ -0,0 +1,42 @@
+/*
+ * SiFive L2 cache controller
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SIFIVE_L2CC_H
+#define SIFIVE_L2CC_H
+
+#include "hw/core/register.h"
+#include "hw/core/sysbus.h"
+
+#define SIFIVE_L2CC_REG_SIZE    0x1000
+#define SIFIVE_L2CC_REG_NUM     (SIFIVE_L2CC_REG_SIZE / 8)
+#define SIFIVE_L2CC_MAX_MASTERS 21
+
+enum {
+    SIFIVE_L2CC_MMIO_REGS,
+    SIFIVE_L2CC_MMIO_LIM,
+    SIFIVE_L2CC_MMIO_ZERO,
+};
+
+typedef struct SiFiveL2CCState {
+    SysBusDevice parent_obj;
+    uint64_t regs[SIFIVE_L2CC_REG_NUM];
+    RegisterInfo regs_info[SIFIVE_L2CC_REG_NUM];
+    MemoryRegion lim_ram;
+    MemoryRegion lim;
+    MemoryRegion zero;
+    uint32_t num_masters;
+    uint64_t zero_size;
+} SiFiveL2CCState;
+
+#define TYPE_SIFIVE_L2CC "sifive.l2cc"
+OBJECT_DECLARE_SIMPLE_TYPE(SiFiveL2CCState, SIFIVE_L2CC)
+
+#endif
diff --git a/hw/misc/sifive_l2cc.c b/hw/misc/sifive_l2cc.c
new file mode 100644
index 0000000000..f99c44b301
--- /dev/null
+++ b/hw/misc/sifive_l2cc.c
@@ -0,0 +1,282 @@
+/*
+ * SiFive L2 cache controller
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/register.h"
+#include "hw/core/registerfields.h"
+#include "hw/misc/sifive_l2cc.h"
+#include "migration/vmstate.h"
+
+REG64(L2_CONFIG, 0x000)
+    FIELD(L2_CONFIG, BANKS, 0, 8)
+    FIELD(L2_CONFIG, WAYS, 8, 8)
+    FIELD(L2_CONFIG, LG_SETS, 16, 8)
+    FIELD(L2_CONFIG, LG_BLOCK_BYTES, 24, 8)
+REG64(L2_WAY_ENABLE, 0x008)
+    FIELD(L2_WAY_ENABLE, VALUE, 0, 8)
+REG64(L2_WAY_MASK, 0x800)
+
+#define L2_CONFIG_RESET         0x06091004
+#define L2_LIM_WAY_COUNT        15
+#define L2_WAY_SIZE             (128 * KiB)
+#define L2_CACHE_SIZE           (2 * MiB)
+#define L2_ZERO_MAX_SIZE        (32 * MiB)
+
+static void sifive_l2cc_update_l2lim(SiFiveL2CCState *s, uint64_t way_enable)
+{
+    uint64_t l2lim_size;
+
+    if (way_enable >= L2_LIM_WAY_COUNT) {
+        l2lim_size = 0;
+    } else {
+        l2lim_size = (L2_LIM_WAY_COUNT - way_enable) * L2_WAY_SIZE;
+    }
+
+    memory_region_transaction_begin();
+    memory_region_set_size(&s->lim, l2lim_size);
+    memory_region_set_enabled(&s->lim, l2lim_size != 0);
+    memory_region_transaction_commit();
+}
+
+static uint64_t sifive_l2cc_way_enable_pre_write(RegisterInfo *reg,
+                                                 uint64_t value)
+{
+    uint64_t current = *(uint64_t *)reg->data;
+
+    return MAX(current, value);
+}
+
+static void sifive_l2cc_way_enable_post_write(RegisterInfo *reg,
+                                              uint64_t value)
+{
+    SiFiveL2CCState *s = SIFIVE_L2CC(reg->opaque);
+
+    sifive_l2cc_update_l2lim(s, value);
+}
+
+#define WAY_MASK_REGISTER(_id)                  \
+    {                                           \
+        .name = "WAY_MASK_" #_id,               \
+        .addr = A_L2_WAY_MASK + 8 * (_id),       \
+        .reset = UINT64_MAX,                    \
+    }
+
+static const RegisterAccessInfo sifive_l2cc_regs_info[] = {
+    {
+        .name = "CONFIG",
+        .addr = A_L2_CONFIG,
+        .reset = L2_CONFIG_RESET,
+        .ro = UINT64_MAX,
+    }, {
+        .name = "WAY_ENABLE",
+        .addr = A_L2_WAY_ENABLE,
+        .rsvd = ~R_L2_WAY_ENABLE_VALUE_MASK,
+        .pre_write = sifive_l2cc_way_enable_pre_write,
+        .post_write = sifive_l2cc_way_enable_post_write,
+    },
+    WAY_MASK_REGISTER(0),
+    WAY_MASK_REGISTER(1),
+    WAY_MASK_REGISTER(2),
+    WAY_MASK_REGISTER(3),
+    WAY_MASK_REGISTER(4),
+    WAY_MASK_REGISTER(5),
+    WAY_MASK_REGISTER(6),
+    WAY_MASK_REGISTER(7),
+    WAY_MASK_REGISTER(8),
+    WAY_MASK_REGISTER(9),
+    WAY_MASK_REGISTER(10),
+    WAY_MASK_REGISTER(11),
+    WAY_MASK_REGISTER(12),
+    WAY_MASK_REGISTER(13),
+    WAY_MASK_REGISTER(14),
+    WAY_MASK_REGISTER(15),
+    WAY_MASK_REGISTER(16),
+    WAY_MASK_REGISTER(17),
+    WAY_MASK_REGISTER(18),
+    WAY_MASK_REGISTER(19),
+    WAY_MASK_REGISTER(20),
+};
+
+static bool sifive_l2cc_register_implemented(SiFiveL2CCState *s,
+                                             hwaddr offset)
+{
+    if (offset == A_L2_CONFIG || offset == A_L2_WAY_ENABLE) {
+        return true;
+    }
+
+    return offset >= A_L2_WAY_MASK &&
+           offset < A_L2_WAY_MASK + 8 * s->num_masters && !(offset & 7);
+}
+
+static uint64_t sifive_l2cc_read(void *opaque, hwaddr offset,
+                                 unsigned size)
+{
+    RegisterInfoArray *reg_array = opaque;
+    SiFiveL2CCState *s = SIFIVE_L2CC(reg_array->r[0]->opaque);
+
+    if (sifive_l2cc_register_implemented(s, offset)) {
+        return register_read_memory(opaque, offset, size);
+    }
+
+    qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+                  "(size %u, offset 0x%" HWADDR_PRIx ")\n",
+                  __func__, size, offset);
+    return 0;
+}
+
+static void sifive_l2cc_write(void *opaque, hwaddr offset,
+                              uint64_t value, unsigned size)
+{
+    RegisterInfoArray *reg_array = opaque;
+    SiFiveL2CCState *s = SIFIVE_L2CC(reg_array->r[0]->opaque);
+
+    if (sifive_l2cc_register_implemented(s, offset)) {
+        register_write_memory(opaque, offset, value, size);
+        return;
+    }
+
+    qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+                  "(size %u, value 0x%" PRIx64
+                  ", offset 0x%" HWADDR_PRIx ")\n",
+                  __func__, size, value, offset);
+}
+
+static const MemoryRegionOps sifive_l2cc_ops = {
+    .read = sifive_l2cc_read,
+    .write = sifive_l2cc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = { .min_access_size = 1, .max_access_size = 8 },
+    .impl = { .min_access_size = 1, .max_access_size = 8 },
+};
+
+static void sifive_l2cc_reset_hold(Object *obj, ResetType type)
+{
+    SiFiveL2CCState *s = SIFIVE_L2CC(obj);
+    size_t i;
+
+    memset(s->regs, 0, sizeof(s->regs));
+    for (i = 0; i < 2 + s->num_masters; i++) {
+        hwaddr addr = sifive_l2cc_regs_info[i].addr;
+
+        register_reset(&s->regs_info[addr / sizeof(uint64_t)]);
+    }
+}
+
+static void sifive_l2cc_init(Object *obj)
+{
+    SiFiveL2CCState *s = SIFIVE_L2CC(obj);
+    RegisterInfoArray *reg_array;
+
+    reg_array = register_init_block64(
+        DEVICE(obj), sifive_l2cc_regs_info,
+        ARRAY_SIZE(sifive_l2cc_regs_info), s->regs_info, s->regs,
+        &sifive_l2cc_ops, false, SIFIVE_L2CC_REG_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &reg_array->mem);
+}
+
+static void sifive_l2cc_realize(DeviceState *dev, Error **errp)
+{
+    SiFiveL2CCState *s = SIFIVE_L2CC(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    if (!s->num_masters || s->num_masters > SIFIVE_L2CC_MAX_MASTERS) {
+        error_setg(errp, "num-masters must be between 1 and %u",
+                   SIFIVE_L2CC_MAX_MASTERS);
+        return;
+    }
+    if (!s->zero_size || s->zero_size > L2_ZERO_MAX_SIZE) {
+        error_setg(errp, "zero-size must be between 1 byte and 32 MiB");
+        return;
+    }
+
+    /* Keep the SRAM backing fixed while WayEnable changes the LIM alias */
+    if (!memory_region_init_ram(&s->lim_ram, OBJECT(dev),
+                                TYPE_SIFIVE_L2CC ".lim-ram",
+                                L2_CACHE_SIZE, errp)) {
+        return;
+    }
+    memory_region_init_alias(&s->lim, OBJECT(dev), TYPE_SIFIVE_L2CC ".lim",
+                             &s->lim_ram, 0, L2_LIM_WAY_COUNT * L2_WAY_SIZE);
+    sysbus_init_mmio(sbd, &s->lim);
+
+    /*
+     * L2 Zero allocates into enabled cache ways without external memory
+     * backing. Use a separate RAM stand-in because QEMU does not model
+     * cache allocation, replacement, or coherence. Its aperture size is
+     * independent of the cache/LIM way partition.
+     */
+    if (!memory_region_init_ram(&s->zero, OBJECT(dev),
+                                TYPE_SIFIVE_L2CC ".zero",
+                                s->zero_size, errp)) {
+        return;
+    }
+    sysbus_init_mmio(sbd, &s->zero);
+
+    sifive_l2cc_update_l2lim(s, s->regs[R_L2_WAY_ENABLE]);
+}
+
+static const Property sifive_l2cc_properties[] = {
+    DEFINE_PROP_UINT32("num-masters", SiFiveL2CCState, num_masters,
+                       SIFIVE_L2CC_MAX_MASTERS),
+    DEFINE_PROP_SIZE("zero-size", SiFiveL2CCState, zero_size,
+                     L2_ZERO_MAX_SIZE),
+};
+
+static int sifive_l2cc_post_load(void *opaque, int version_id)
+{
+    SiFiveL2CCState *s = opaque;
+
+    sifive_l2cc_update_l2lim(s, s->regs[R_L2_WAY_ENABLE]);
+    return 0;
+}
+
+static const VMStateDescription vmstate_sifive_l2cc = {
+    .name = TYPE_SIFIVE_L2CC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = sifive_l2cc_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_EQUAL(num_masters, SiFiveL2CCState),
+        VMSTATE_UINT64_EQUAL(zero_size, SiFiveL2CCState),
+        VMSTATE_UINT64_ARRAY(regs, SiFiveL2CCState, SIFIVE_L2CC_REG_NUM),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void sifive_l2cc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.hold = sifive_l2cc_reset_hold;
+    device_class_set_props(dc, sifive_l2cc_properties);
+    dc->realize = sifive_l2cc_realize;
+    dc->vmsd = &vmstate_sifive_l2cc;
+}
+
+static const TypeInfo sifive_l2cc_info = {
+    .name = TYPE_SIFIVE_L2CC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(SiFiveL2CCState),
+    .instance_init = sifive_l2cc_init,
+    .class_init = sifive_l2cc_class_init,
+};
+
+static void sifive_l2cc_register_types(void)
+{
+    type_register_static(&sifive_l2cc_info);
+}
+
+type_init(sifive_l2cc_register_types)
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 46e3c03cc8..254103e4ff 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -206,6 +206,9 @@ config MCHP_PFSOC_IOSCB
 config MCHP_PFSOC_SYSREG
     bool
 
+config SIFIVE_L2CC
+    bool
+
 config SIFIVE_TEST
     bool
 
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 9790afa104..6c401541e9 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -33,6 +33,7 @@ system_ss.add(when: 'CONFIG_K230', if_true: 
files('k230_ddr.c'))
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true: 
files('mchp_pfsoc_dmc.c'))
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_IOSCB', if_true: 
files('mchp_pfsoc_ioscb.c'))
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true: 
files('mchp_pfsoc_sysreg.c'))
+system_ss.add(when: 'CONFIG_SIFIVE_L2CC', if_true: files('sifive_l2cc.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_TEST', if_true: files('sifive_test.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c'))
-- 
2.53.0


Reply via email to