[PATCH v2 2/8] fsi/cfam: Add CFAM-S model

Mikail Sadic <[email protected]> Thu, 30 Jul 2026 15:09:37 -0500
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
Add TYPE_FSI_CFAM_S, derivative of CFAM, as a second type on the common
CFAM base class. The base class builds the register slot, the config
table region, the FSI responder and the local bus, so the CFAM-S supplies
only its config table (chip ID with major 9, an FSI responder and a v1
mailbox), its slot offsets and the mailbox engine. Its realize chains the
base one and adds the one thing that is new: the slot is aliased across
the four slave-ID views, so enumeration and normal access reach the same
registers.

The v1 mailbox is a local bus engine added in lbus.c next to the
scratchpad which the CFAM-S just instantiates.

Sharing the base config table implementation means the CFAM-S also
honors the SID_BREAK sequence and word-only config access.

FSIMasterState gains a cfam_s field. fsi_master_init() creates both the
cfam and cfam-s children, realize hooks them into the OPB-to-FSI aperture.

Signed-off-by: Mikail Sadic <[email protected]>
---
 docs/specs/fsi.rst          | 15 ++++++
 include/hw/fsi/cfam-s.h     | 40 ++++++++++++++++
 include/hw/fsi/cfam.h       | 10 ++++
 include/hw/fsi/fsi-master.h |  2 +
 include/hw/fsi/lbus.h       | 11 +++++
 hw/fsi/cfam-s.c             | 91 +++++++++++++++++++++++++++++++++++++
 hw/fsi/fsi-master.c         |  7 +++
 hw/fsi/lbus.c               | 69 ++++++++++++++++++++++++++++
 hw/fsi/meson.build          |  2 +-
 hw/fsi/trace-events         |  2 +
 10 files changed, 248 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/fsi/cfam-s.h
 create mode 100644 hw/fsi/cfam-s.c

diff --git a/docs/specs/fsi.rst b/docs/specs/fsi.rst
index f7d86d3e37..fb7a3669ef 100644
--- a/docs/specs/fsi.rst
+++ b/docs/specs/fsi.rst
@@ -120,3 +120,18 @@ from the BMC. (see the `pdbg source repository`_ for more details)
 
 .. _pdbg source repository:
    https://github.com/open-power/pdbg
+
+CFAM-S model
+------------
+
+The CFAM-S is a derivative of the CFAM. Both are modelled on a common CFAM
+model that builds the register slot, the configuration table, the FSI
+responder and the local bus; each supplies its own configuration table, slot
+layout and engines. The CFAM-S supports a limited set of engines: an FSI
+responder and a version 1 mailbox.
+
+The configuration table advertises the responder and the mailbox engines.
+The responder is backed by the FSI slave control registers; the mailbox
+provides a small block of scratch registers. The register block is exposed
+through each slave-ID view so that enumeration and normal access reach the
+same registers.
diff --git a/include/hw/fsi/cfam-s.h b/include/hw/fsi/cfam-s.h
new file mode 100644
index 0000000000..daf72124c4
--- /dev/null
+++ b/include/hw/fsi/cfam-s.h
@@ -0,0 +1,40 @@
+/*
+ * IBM Common FRU Access Macro - S variant (CFAM-S)
+ *
+ * Copyright (C) 2026 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef FSI_CFAM_S_H
+#define FSI_CFAM_S_H
+
+#include "system/memory.h"
+#include "hw/fsi/cfam.h"
+#include "hw/fsi/fsi.h"
+#include "hw/fsi/lbus.h"
+
+#define TYPE_FSI_CFAM_S "cfam-s"
+OBJECT_DECLARE_TYPE(FSICFAMSState, FSICFAMSClass, FSI_CFAM_S)
+
+/* The register slot is visible in each of the four slave-ID views */
+#define CFAM_S_WINDOW_SIZE (4 * FSI_CFAM_SLOT_SIZE)
+
+struct FSICFAMSState {
+    /* < private > */
+    FSICFAMCommonState parent;
+
+    /* parent.mr aliased across the slave-ID views */
+    MemoryRegion window;
+    MemoryRegion slot_alias[3];
+
+    FSIMbox mbox;
+};
+
+struct FSICFAMSClass {
+    /* < private > */
+    FSICFAMCommonClass parent_class;
+
+    DeviceRealize parent_realize;
+};
+
+#endif /* FSI_CFAM_S_H */
diff --git a/include/hw/fsi/cfam.h b/include/hw/fsi/cfam.h
index 0f5464b4a0..43f6be9d18 100644
--- a/include/hw/fsi/cfam.h
+++ b/include/hw/fsi/cfam.h
@@ -35,6 +35,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FSICFAMState, FSI_CFAM)
 #define ENGINE_CONFIG_TYPE_PEEK       (0x02 << 4)
 #define ENGINE_CONFIG_TYPE_FSI        (0x03 << 4)
 #define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4)
+#define ENGINE_CONFIG_TYPE_MBOX_V1    (0x14 << 4)
 
 /* Valid, slots, version, type, crc */
 #define CFAM_CONFIG_REG(__VER, __TYPE, __CRC)   \
@@ -44,6 +45,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(FSICFAMState, FSI_CFAM)
      (__TYPE)                 |   \
      (__CRC))
 
+/* As above, for the last entry in a table: NEXT is clear */
+#define CFAM_CONFIG_LAST(__VER, __TYPE, __CRC)  \
+    (0x00010000               |   \
+     (__VER)                  |   \
+     (__TYPE)                 |   \
+     (__CRC))
+
+#define CFAM_CONFIG_CHIP_ID_MAJOR(__MAJOR) (((__MAJOR) & 0xf) << 8)
+
 struct FSICFAMCommonState {
     /* < private > */
     FSISlaveState parent;
diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
index 60ddaa994f..6a2782fdf2 100644
--- a/include/hw/fsi/fsi-master.h
+++ b/include/hw/fsi/fsi-master.h
@@ -11,6 +11,7 @@
 #include "hw/core/qdev.h"
 #include "hw/fsi/fsi.h"
 #include "hw/fsi/cfam.h"
+#include "hw/fsi/cfam-s.h"
 
 #define TYPE_FSI_MASTER "fsi.master"
 OBJECT_DECLARE_SIMPLE_TYPE(FSIMasterState, FSI_MASTER)
@@ -26,6 +27,7 @@ typedef struct FSIMasterState {
 
     uint32_t regs[FSI_MASTER_NR_REGS];
     FSICFAMState cfam;
+    FSICFAMSState cfam_s;
 } FSIMasterState;
 
 
diff --git a/include/hw/fsi/lbus.h b/include/hw/fsi/lbus.h
index 1b894509fe..582d7169d6 100644
--- a/include/hw/fsi/lbus.h
+++ b/include/hw/fsi/lbus.h
@@ -40,4 +40,15 @@ typedef struct FSIScratchPad {
         uint32_t regs[FSI_SCRATCHPAD_NR_REGS];
 } FSIScratchPad;
 
+#define TYPE_FSI_MBOX "fsi.mbox"
+OBJECT_DECLARE_SIMPLE_TYPE(FSIMbox, FSI_MBOX)
+
+#define FSI_MBOX_SCRATCH_NUM 5
+
+struct FSIMbox {
+    FSILBusDevice parent;
+
+    uint32_t scratch[FSI_MBOX_SCRATCH_NUM];
+};
+
 #endif /* FSI_LBUS_H */
diff --git a/hw/fsi/cfam-s.c b/hw/fsi/cfam-s.c
new file mode 100644
index 0000000000..3df6da3788
--- /dev/null
+++ b/hw/fsi/cfam-s.c
@@ -0,0 +1,91 @@
+/*
+ * IBM Common FRU Access Macro - S variant (CFAM-S)
+ *
+ * A CFAM flavor built on the common CFAM model (see cfam.c). It supports an
+ * FSI responder and a v1 mailbox, and exposes its register slot in each
+ * slave-ID view of the link.
+ *
+ * Copyright (C) 2026 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/units.h"
+#include "hw/fsi/cfam-s.h"
+#include "hw/fsi/fsi.h"
+
+/* Slot layout: 0x000 config table, 0x400 responder, 0x800 mailbox */
+#define CFAM_S_RESPONDER_BASE 0x400
+#define CFAM_S_MBOX_BASE      0x800
+
+/* Config table: chip ID with major 9 (the CFAM-S), then the engines */
+static const uint32_t cfam_s_config[] = {
+    ENGINE_CONFIG_NEXT | CFAM_CONFIG_CHIP_ID_MAJOR(9) | 0xd,
+    CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_FSI, 0xb),
+    CFAM_CONFIG_LAST(0x1000, ENGINE_CONFIG_TYPE_MBOX_V1, 0x3),
+};
+
+static bool fsi_cfam_s_realize_engines(FSICFAMCommonState *cfam, Error **errp)
+{
+    FSICFAMSState *cfam_s = FSI_CFAM_S(cfam);
+
+    object_initialize_child(OBJECT(cfam_s), "mbox", &cfam_s->mbox,
+                            TYPE_FSI_MBOX);
+    return fsi_cfam_add_engine(cfam, DEVICE(&cfam_s->mbox), 0, errp);
+}
+
+static void fsi_cfam_s_realize(DeviceState *dev, Error **errp)
+{
+    ERRP_GUARD();
+    FSICFAMSState *cfam_s = FSI_CFAM_S(dev);
+    FSICFAMCommonState *cfam = FSI_CFAM_COMMON(dev);
+    FSICFAMSClass *sc = FSI_CFAM_S_GET_CLASS(dev);
+
+    sc->parent_realize(dev, errp);
+    if (*errp) {
+        return;
+    }
+
+    memory_region_init(&cfam_s->window, OBJECT(cfam_s),
+                       TYPE_FSI_CFAM_S ".window", CFAM_S_WINDOW_SIZE);
+    memory_region_add_subregion(&cfam_s->window, 0, &cfam->mr);
+
+    /* Alias the slot into the other three slave-ID views */
+    for (int i = 0; i < ARRAY_SIZE(cfam_s->slot_alias); i++) {
+        memory_region_init_alias(&cfam_s->slot_alias[i], OBJECT(cfam_s),
+                                 TYPE_FSI_CFAM_S ".slot-alias", &cfam->mr, 0,
+                                 FSI_CFAM_SLOT_SIZE);
+        memory_region_add_subregion(&cfam_s->window,
+                                    (i + 1) * FSI_CFAM_SLOT_SIZE,
+                                    &cfam_s->slot_alias[i]);
+    }
+}
+
+static void fsi_cfam_s_class_init(ObjectClass *klass, const void *data)
+{
+    FSICFAMSClass *sc = FSI_CFAM_S_CLASS(klass);
+    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_CLASS(klass);
+
+    device_class_set_parent_realize(DEVICE_CLASS(klass), fsi_cfam_s_realize,
+                                    &sc->parent_realize);
+
+    cc->config = cfam_s_config;
+    cc->config_nr = ARRAY_SIZE(cfam_s_config);
+    cc->responder_offset = CFAM_S_RESPONDER_BASE;
+    cc->lbus_offset = CFAM_S_MBOX_BASE;
+    cc->realize_engines = fsi_cfam_s_realize_engines;
+}
+
+static const TypeInfo cfam_s_types[] = {
+    {
+        .name = TYPE_FSI_CFAM_S,
+        .parent = TYPE_FSI_CFAM_COMMON,
+        .instance_size = sizeof(FSICFAMSState),
+        .class_size = sizeof(FSICFAMSClass),
+        .class_init = fsi_cfam_s_class_init,
+    },
+};
+
+DEFINE_TYPES(cfam_s_types)
diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c
index d82df1c094..82991b1005 100644
--- a/hw/fsi/fsi-master.c
+++ b/hw/fsi/fsi-master.c
@@ -8,6 +8,7 @@
 #include "qemu/osdep.h"
 #include "qapi/error.h"
 #include "qemu/log.h"
+#include "qemu/units.h"
 #include "trace.h"
 
 #include "hw/fsi/fsi-master.h"
@@ -113,6 +114,7 @@ static void fsi_master_init(Object *o)
     FSIMasterState *s = FSI_MASTER(o);
 
     object_initialize_child(o, "cfam", &s->cfam, TYPE_FSI_CFAM);
+    object_initialize_child(o, "cfam-s", &s->cfam_s, TYPE_FSI_CFAM_S);
 
     qbus_init(&s->bus, sizeof(s->bus), TYPE_FSI_BUS, DEVICE(s), NULL);
 
@@ -131,6 +133,11 @@ static void fsi_master_realize(DeviceState *dev, Error **errp)
 
     /* address ? */
     memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.parent.mr);
+
+    if (!qdev_realize(DEVICE(&s->cfam_s), BUS(&s->bus), errp)) {
+        return;
+    }
+    memory_region_add_subregion(&s->opb2fsi, 2 * MiB, &s->cfam_s.window);
 }
 
 static void fsi_master_reset(DeviceState *dev)
diff --git a/hw/fsi/lbus.c b/hw/fsi/lbus.c
index cae29e0658..902f8d8a5e 100644
--- a/hw/fsi/lbus.c
+++ b/hw/fsi/lbus.c
@@ -107,11 +107,80 @@ static const TypeInfo fsi_scratchpad_info = {
     .class_init = fsi_scratchpad_class_init,
 };
 
+/* The mailbox exposes its scratch registers at this offset */
+#define FSI_MBOX_SCRATCH_OFF 0xe0
+#define FSI_MBOX_SCRATCH_END (FSI_MBOX_SCRATCH_OFF + FSI_MBOX_SCRATCH_NUM * 4)
+
+static uint64_t fsi_mbox_read(void *opaque, hwaddr addr, unsigned size)
+{
+    FSIMbox *mbox = FSI_MBOX(opaque);
+
+    trace_fsi_mbox_read(addr, size);
+
+    if (addr < FSI_MBOX_SCRATCH_OFF || addr >= FSI_MBOX_SCRATCH_END) {
+        return 0;
+    }
+
+    return mbox->scratch[TO_REG(addr - FSI_MBOX_SCRATCH_OFF)];
+}
+
+static void fsi_mbox_write(void *opaque, hwaddr addr, uint64_t data,
+                           unsigned size)
+{
+    FSIMbox *mbox = FSI_MBOX(opaque);
+
+    trace_fsi_mbox_write(addr, size, data);
+
+    if (addr < FSI_MBOX_SCRATCH_OFF || addr >= FSI_MBOX_SCRATCH_END) {
+        return;
+    }
+
+    mbox->scratch[TO_REG(addr - FSI_MBOX_SCRATCH_OFF)] = data;
+}
+
+static const struct MemoryRegionOps fsi_mbox_ops = {
+    .read = fsi_mbox_read,
+    .write = fsi_mbox_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static void fsi_mbox_realize(DeviceState *dev, Error **errp)
+{
+    FSILBusDevice *ldev = FSI_LBUS_DEVICE(dev);
+
+    memory_region_init_io(&ldev->iomem, OBJECT(ldev), &fsi_mbox_ops,
+                          ldev, TYPE_FSI_MBOX, 0x400);
+}
+
+static void fsi_mbox_reset(DeviceState *dev)
+{
+    FSIMbox *mbox = FSI_MBOX(dev);
+
+    memset(mbox->scratch, 0, sizeof(mbox->scratch));
+}
+
+static void fsi_mbox_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->bus_type = TYPE_FSI_LBUS;
+    dc->realize = fsi_mbox_realize;
+    device_class_set_legacy_reset(dc, fsi_mbox_reset);
+}
+
+static const TypeInfo fsi_mbox_info = {
+    .name = TYPE_FSI_MBOX,
+    .parent = TYPE_FSI_LBUS_DEVICE,
+    .instance_size = sizeof(FSIMbox),
+    .class_init = fsi_mbox_class_init,
+};
+
 static void fsi_lbus_register_types(void)
 {
     type_register_static(&fsi_lbus_info);
     type_register_static(&fsi_lbus_device_type_info);
     type_register_static(&fsi_scratchpad_info);
+    type_register_static(&fsi_mbox_info);
 }
 
 type_init(fsi_lbus_register_types);
diff --git a/hw/fsi/meson.build b/hw/fsi/meson.build
index a18a076552..585d549405 100644
--- a/hw/fsi/meson.build
+++ b/hw/fsi/meson.build
@@ -1,2 +1,2 @@
-system_ss.add(when: 'CONFIG_FSI', if_true: files('lbus.c','fsi.c','cfam.c','fsi-master.c'))
+system_ss.add(when: 'CONFIG_FSI', if_true: files('lbus.c','fsi.c','cfam.c','cfam-s.c','fsi-master.c'))
 system_ss.add(when: 'CONFIG_FSI_APB2OPB_ASPEED', if_true: files('aspeed_apb2opb.c'))
diff --git a/hw/fsi/trace-events b/hw/fsi/trace-events
index 9e286d08d3..42edce6437 100644
--- a/hw/fsi/trace-events
+++ b/hw/fsi/trace-events
@@ -1,5 +1,7 @@
 fsi_scratchpad_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
 fsi_scratchpad_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64
+fsi_mbox_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
+fsi_mbox_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64
 fsi_slave_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
 fsi_slave_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64
 fsi_cfam_config_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
-- 
2.53.0