All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700
@ 2026-08-10 18:57 Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 1/8] fsi/cfam: Add common CFAM base class Mikail Sadic
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

This series adds initial support for the IBM Huygens BMC, the baseboard
management controller of an IBM POWER12 server platform, based on the
ASPEED AST2700 A2 SoC.

It introduces a CFAM-S FSI responder model, wires the AST2700 FSI
controllers to their APB-to-OPB bridges, fixes an aspeed I2C DMA
block-read corner case, adds a TI UCD90320 PMBus power-sequencer model,
adds an AST2700 UFS host controller on top of the generic sysbus UFS
device, and finally adds the huygens-bmc machine that ties these together
to boot OpenBMC from FMC flash and a UFS root filesystem.

Dependency: this series applies on top of Jeuk Kim's UFS restructure,
which is on-list but not yet merged:

  https://lore.kernel.org/qemu-devel/cover.1786096976.git.jeuk20.kim@samsung.com/

The base-commit trailer below therefore names a local commit that is
v11.1.0-rc3 (3e3ccab106) plus those two patches. Patches 6 and 7 need
it, patches 1-5 and 8 do not.

Patch summary:

  1. fsi/cfam: a common CFAM model holding the slot, config table,
     responder and local bus, with the existing cfam as its first
     derivative. No functional change to the existing cfam.
  2. fsi/cfam: the CFAM-S as a second derivative - its own config table,
     the slave-ID alias window, and a v1 mailbox engine added to lbus.c
     alongside the scratchpad.
  3. arm/aspeed: wire the two AST2700 FSI controllers to their APB-to-OPB
     bridges.
  4. i2c/aspeed: fix DMA-receive first-byte handling so SMBus block reads
     (I2C_M_RECV_LEN) report the correct length.
  5. hw/sensor: TI UCD90320 24-rail PMBus power sequencer.
  6. ufs: a configurable logical-block-size on ufs-lu, and an unmapped LUN
     answering INQUIRY as "not connected". The ufs PCI device keeps its
     current property set and geometry.
  7. ufs/aspeed: the AST2700 UFS host controller as a subclass of
     TYPE_SYSBUS_UFS, adding only the UFSHCI version.
  8. arm/aspeed: the huygens-bmc machine.

Changes since v2:

* UFS: rebased onto Jeuk Kim's restructure, as requested. The v2 core
  refactor (v2 patch 6) is dropped entirely, Jeuk's patch 1 supersedes it.
  hw/ufs/aspeed_ufs.c is now a subclass of TYPE_SYSBUS_UFS whose only
  AST2700-specific behavior is pinning the controller version register
  to 2.0, chained after the parent realize. MMIO, IRQ, DMA address space,
  properties, migration state and unrealize all come from the generic
  sysbus device, and the Aspeed type adds no instance fields.

  The two behaviors Huygens needs that the restructure does not provide
  are now their own patch (6), placed ahead of the Aspeed patch: the
  ufs-lu logical-block-size property (default unchanged, so the PCI
  device is unaffected) and the unmapped-LUN INQUIRY reporting "not
  connected" rather than failing the request. logical-block-size now also
  rejects non-powers-of-two, values outside 512..UFS_BLOCK_SIZE, and a
  drive shorter than one block, which would previously have realized a
  zero-block unit.

  The Aspeed SoC Kconfig now selects UFS_SYSBUS. It previously relied on
  UFS_PCI being enabled via "imply PCI_DEVICES".

* CFAM: dropped the State suffix from the instance struct names (Miles).
  FSICFAMCommonState becomes FSICFAMCommon; FSICFAMState, which predates
  this series, becomes FSICFAM to match rather than leaving the two
  spellings side by side in one header; FSICFAMSState becomes FSICFAMS.
  The FSI prefix is kept. The class structs keep their suffix, as is
  usual for QOM. Also fixed patch 1 to check the realize_engines() return
  value.

* Machine: reworded the commit message (Miles). No code change.

* Rebased onto v11.1.0-rc3.

Not addressed in this series:

* CFAM extended addressing (Miles): the mode where a single CFAM has 8MB
  rather than four cascaded CFAMs sharing that space. This changes the
  addressing model of the existing FSI/CFAM code rather than anything this
  series introduces, so it's left it as follow-up work. It does bear on
  patch 2 though: the CFAM-S there aliases one 2MiB slot across the 8MiB
  window to fold the slave-ID views together, and if extended addressing
  is modelled properly that window is probably the wrong shape or should
  not exist at all. I am happy to take direction on whether to do that
  first.

Testing done:

* Whole series builds, and each of the 8 patches builds standalone.
* make check passes.
* huygens-bmc boots OpenBMC to the login prompt and multi-user target
  from FMC flash + UFS storage (internal images).
* PCI UFS qtests pass (13/13), unchanged by patch 6.
* aspeed_fsi-test passes (4/4), unchanged by the CFAM patches.
* huygens-bmc, ast2700-evb and ast2600-evb all construct successfully.
* On huygens-bmc, the UFS version register reads 0x00000200, and the
  ufs-lu created by the board reports a 512-byte block size and passes it
  to its backing scsi-hd. The PCI ufs device with no property set still
  reports 4096.

Changes since v1 (unchanged from the v2 posting, for reference):

* Split the original 5 commits into 8. The aspeed I2C DMA fix, the FSI
  controller wiring, the CFAM common base class, and the UFS
  core-vs-frontend split are now separate patches (each buildable on its
  own).

* UFS: reworked per review (Jeuk Kim). Instead of a second, standalone
  UFS implementation, the common core was separated from the PCI glue so
  the AST2700 controller could be a thin sysbus frontend on top of it.
  (Superseded in v3 - see above.)

* CFAM-S: reworked per review (Ninad). It now shares a common base class
  with the cfam rather than being a parallel implementation: patch 1 adds
  an abstract cfam-common whose realize is driven by an FSICFAMCommonClass
  (config table, responder and local-bus offsets, and an engines hook),
  and both cfam and cfam-s derive from it. They are siblings rather than
  parent and child, since a CFAM-S is another kind of CFAM and not a
  specialisation of the P9 one. Patch 2 adds cfam-s supplying only its own
  config table, its mailbox engine and the slave-ID alias window; the v1
  mailbox is a local bus engine so it sits in lbus.c next to the
  scratchpad. Duplicated code has been cleaned. Two behavior changes
  follow from sharing the common config ops, both noted in patch 2: cfam-s
  now honors the SID_BREAK sequence, and config-table access is word-only
  for both types. Also made generic, all specific references dropped.

  Patch 1 is intended to be a no-op for the existing cfam, and was
  checked that way: aspeed_fsi-test passes, the four P9 config words read
  back identically over the OPB, and "info mtree" for ast2600-evb is
  unchanged. The Rainier boot test was also run and verified.

* i2c/aspeed: the DMA block-read fix is now its own patch, with a commit
  message explaining the I2C_M_RECV_LEN + DMA interaction (Cédric).

* UCD90320: the device-id/monitor-config/mfr-status responses are now
  device reset state rather than file-scope constants, with matching
  VMState (Cédric).

* Docs: the huygens-bmc and aspeed-ufs boot examples now use -blockdev
  instead of -drive (Cédric).

* Machine: commit message expanded (POWER12 BMC on AST2700 A2), the VPD
  buffer uses g_autofree, the docs describe the flash-vs-UFS split, and
  the networking example was simplified.

* New device files use DEFINE_TYPES() with inlined TypeInfo (Jamin Lin).

* Dropped the v1 functional test for now: depends on non-public Huygens
  images. It will be resent once images go public.

Note: checkpatch reports a couple of MAINTAINERS/SPDX warnings for the
new files; they are covered by existing wildcard entries in the ASPEED
and FSI sections.

Thank you again for all of the feedback.

Mikail Sadic (8):
  fsi/cfam: Add common CFAM base class
  fsi/cfam: Add CFAM-S model
  arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges
  i2c/aspeed: Fix DMA receive first-byte handling for block reads
  hw/sensor: Add UCD90320 model
  ufs: Make the logical block size configurable and answer absent LUNs
  ufs/aspeed: Add AST2700 UFS host controller
  arm/aspeed: Add AST2700 Huygens machine

 MAINTAINERS                     |   3 +
 docs/specs/aspeed-ufs.rst       |  46 ++++++
 docs/specs/fsi.rst              |  15 ++
 docs/specs/index.rst            |   2 +
 docs/specs/ucd90320.rst         |  36 +++++
 docs/system/arm/aspeed.rst      |  50 ++++++-
 hw/ufs/ufs.h                    |   3 +
 include/hw/arm/aspeed.h         |   1 +
 include/hw/arm/aspeed_soc.h     |   3 +
 include/hw/fsi/cfam-s.h         |  40 ++++++
 include/hw/fsi/cfam.h           |  68 ++++++++-
 include/hw/fsi/fsi-master.h     |   4 +-
 include/hw/fsi/lbus.h           |  11 ++
 include/hw/ufs/aspeed_ufs.h     |  15 ++
 hw/arm/aspeed.c                 |  25 ++++
 hw/arm/aspeed_ast27x0.c         |  31 ++++
 hw/arm/aspeed_ast27x0_huygens.c | 241 ++++++++++++++++++++++++++++++++
 hw/fsi/cfam-s.c                 |  91 ++++++++++++
 hw/fsi/cfam.c                   | 125 ++++++++++-------
 hw/fsi/fsi-master.c             |   9 +-
 hw/fsi/lbus.c                   |  69 +++++++++
 hw/i2c/aspeed_i2c.c             |   7 +
 hw/sensor/ucd90320.c            | 169 ++++++++++++++++++++++
 hw/ufs/aspeed_ufs.c             |  67 +++++++++
 hw/ufs/lu.c                     |  67 ++++++++-
 hw/ufs/ufs.c                    |   2 +-
 hw/arm/Kconfig                  |   2 +
 hw/arm/meson.build              |   1 +
 hw/fsi/meson.build              |   2 +-
 hw/fsi/trace-events             |   2 +
 hw/sensor/Kconfig               |   4 +
 hw/sensor/meson.build           |   1 +
 hw/ufs/meson.build              |   1 +
 33 files changed, 1149 insertions(+), 64 deletions(-)
 create mode 100644 docs/specs/aspeed-ufs.rst
 create mode 100644 docs/specs/ucd90320.rst
 create mode 100644 include/hw/fsi/cfam-s.h
 create mode 100644 include/hw/ufs/aspeed_ufs.h
 create mode 100644 hw/arm/aspeed_ast27x0_huygens.c
 create mode 100644 hw/fsi/cfam-s.c
 create mode 100644 hw/sensor/ucd90320.c
 create mode 100644 hw/ufs/aspeed_ufs.c

base-commit: 8c9baa57b781ae86330ba036415a768da12d9b28

-- 
2.53.0



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 1/8] fsi/cfam: Add common CFAM base class
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-11 13:36   ` Miles Glenn
  2026-08-10 18:57 ` [PATCH v3 2/8] fsi/cfam: Add CFAM-S model Mikail Sadic
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

Prepare a common CFAM parent class for CFAM-S implementation.

Add an abstract TYPE_FSI_CFAM_COMMON with an FSICFAMCommonClass holding
what varies between types: the config table, the offsets of the FSI
responder and the local bus within the register slot, and a
realize_engines() hook for the engines on the local bus. Its realize is
driven by those fields, so one realize serves every type. TYPE_FSI_CFAM
keeps its name and becomes the first derived type, supplying the P9
config table, its slot offsets and the scratchpad engine. The config
table becomes data, still written with the same CFAM_CONFIG_REG()
expressions, which move to the header so a derived type can describe its
own table the same way.

Name the new instance struct without a State suffix and rename
FSICFAMState to FSICFAM to match.

No functional change: same memory map, same config words, same type and
child names. aspeed_fsi-test verified.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 include/hw/fsi/cfam.h       |  58 ++++++++++++++++-
 include/hw/fsi/fsi-master.h |   2 +-
 hw/fsi/cfam.c               | 125 +++++++++++++++++++++---------------
 hw/fsi/fsi-master.c         |   2 +-
 4 files changed, 130 insertions(+), 57 deletions(-)

diff --git a/include/hw/fsi/cfam.h b/include/hw/fsi/cfam.h
index cceb4bd6f1..bdf579b4fe 100644
--- a/include/hw/fsi/cfam.h
+++ b/include/hw/fsi/cfam.h
@@ -7,18 +7,44 @@
 #ifndef FSI_CFAM_H
 #define FSI_CFAM_H
 
+#include "qemu/units.h"
 #include "system/memory.h"
 
 #include "hw/fsi/fsi.h"
 #include "hw/fsi/lbus.h"
 
+/*
+ * All CFAM flavors present a register slot holding a config table, an FSI
+ * responder and a local bus carrying the engines the config table describes.
+ * That is the common model; each flavor supplies its own table, slot layout
+ * and engines.
+ */
+#define TYPE_FSI_CFAM_COMMON "cfam-common"
+OBJECT_DECLARE_TYPE(FSICFAMCommon, FSICFAMCommonClass, FSI_CFAM_COMMON)
+
 #define TYPE_FSI_CFAM "cfam"
-#define FSI_CFAM(obj) OBJECT_CHECK(FSICFAMState, (obj), TYPE_FSI_CFAM)
+OBJECT_DECLARE_SIMPLE_TYPE(FSICFAM, FSI_CFAM)
 
 /* P9-ism */
 #define CFAM_CONFIG_NR_REGS 0x28
 
-typedef struct FSICFAMState {
+#define FSI_CFAM_SLOT_SIZE   (2 * MiB)
+#define FSI_CFAM_CONFIG_SIZE 0x400
+
+#define ENGINE_CONFIG_NEXT            BIT(31)
+#define ENGINE_CONFIG_TYPE_PEEK       (0x02 << 4)
+#define ENGINE_CONFIG_TYPE_FSI        (0x03 << 4)
+#define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4)
+
+/* Valid, slots, version, type, crc */
+#define CFAM_CONFIG_REG(__VER, __TYPE, __CRC)   \
+    (ENGINE_CONFIG_NEXT       |   \
+     0x00010000               |   \
+     (__VER)                  |   \
+     (__TYPE)                 |   \
+     (__CRC))
+
+struct FSICFAMCommon {
     /* < private > */
     FSISlaveState parent;
 
@@ -28,7 +54,33 @@ typedef struct FSICFAMState {
     MemoryRegion mr;
 
     FSILBus lbus;
+};
+
+struct FSICFAMCommonClass {
+    /* < private > */
+    DeviceClass parent_class;
+
+    /* < public > */
+    /* Config table served by the common ops, one word per 4-byte offset */
+    const uint32_t *config;
+    unsigned config_nr;
+
+    /* Layout of the register slot */
+    hwaddr responder_offset;
+    hwaddr lbus_offset;
+
+    /* Realize and map this flavor's local bus engines */
+    bool (*realize_engines)(FSICFAMCommon *cfam, Error **errp);
+};
+
+struct FSICFAM {
+    /* < private > */
+    FSICFAMCommon parent;
+
     FSIScratchPad scratchpad;
-} FSICFAMState;
+};
+
+bool fsi_cfam_add_engine(FSICFAMCommon *cfam, DeviceState *engine,
+                         hwaddr offset, Error **errp);
 
 #endif /* FSI_CFAM_H */
diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
index 60ddaa994f..87c11ec3dc 100644
--- a/include/hw/fsi/fsi-master.h
+++ b/include/hw/fsi/fsi-master.h
@@ -25,7 +25,7 @@ typedef struct FSIMasterState {
     FSIBus bus;
 
     uint32_t regs[FSI_MASTER_NR_REGS];
-    FSICFAMState cfam;
+    FSICFAM cfam;
 } FSIMasterState;
 
 
diff --git a/hw/fsi/cfam.c b/hw/fsi/cfam.c
index 54c0b05769..5d4d0622d0 100644
--- a/hw/fsi/cfam.c
+++ b/hw/fsi/cfam.c
@@ -16,19 +16,6 @@
 
 #include "hw/core/qdev-properties.h"
 
-#define ENGINE_CONFIG_NEXT            BIT(31)
-#define ENGINE_CONFIG_TYPE_PEEK       (0x02 << 4)
-#define ENGINE_CONFIG_TYPE_FSI        (0x03 << 4)
-#define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4)
-
-/* Valid, slots, version, type, crc */
-#define CFAM_CONFIG_REG(__VER, __TYPE, __CRC)   \
-    (ENGINE_CONFIG_NEXT       |   \
-     0x00010000               |   \
-     (__VER)                  |   \
-     (__TYPE)                 |   \
-     (__CRC))
-
 #define TO_REG(x)                          ((x) >> 2)
 
 #define CFAM_CONFIG_CHIP_ID                TO_REG(0x00)
@@ -36,34 +23,33 @@
 #define CFAM_CONFIG_CHIP_ID_P9             0xc0022d15
 #define CFAM_CONFIG_CHIP_ID_BREAK          0xc0de0000
 
+/*
+ * Config table of the P9 CFAM: the chip ID followed by one entry per engine,
+ * entry n describing the engine at address n * 4. We need to add future
+ * engines from address 0x10 onwards.
+ */
+static const uint32_t cfam_p9_config[] = {
+    CFAM_CONFIG_CHIP_ID_P9,
+    CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_PEEK, 0xc),
+    CFAM_CONFIG_REG(0x5000, ENGINE_CONFIG_TYPE_FSI, 0xa),
+    CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_SCRATCHPAD, 0x7),
+};
+
 static uint64_t fsi_cfam_config_read(void *opaque, hwaddr addr, unsigned size)
 {
+    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_GET_CLASS(opaque);
+    unsigned int reg = TO_REG(addr);
+
     trace_fsi_cfam_config_read(addr, size);
 
-    switch (addr) {
-    case 0x00:
-        return CFAM_CONFIG_CHIP_ID_P9;
-    case 0x04:
-        return CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_PEEK, 0xc);
-    case 0x08:
-        return CFAM_CONFIG_REG(0x5000, ENGINE_CONFIG_TYPE_FSI, 0xa);
-    case 0xc:
-        return CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_SCRATCHPAD, 0x7);
-    default:
-        /*
-         * The config table contains different engines from 0xc onwards.
-         * The scratch pad is already added at address 0xc. We need to add
-         * future engines from address 0x10 onwards. Returning 0 as engine
-         * is not implemented.
-         */
-        return 0;
-    }
+    /* Engines past the end of the table are not implemented */
+    return reg < cc->config_nr ? cc->config[reg] : 0;
 }
 
 static void fsi_cfam_config_write(void *opaque, hwaddr addr, uint64_t data,
                                   unsigned size)
 {
-    FSICFAMState *cfam = FSI_CFAM(opaque);
+    FSICFAMCommon *cfam = FSI_CFAM_COMMON(opaque);
 
     trace_fsi_cfam_config_write(addr, size, data);
 
@@ -109,59 +95,94 @@ static const struct MemoryRegionOps fsi_cfam_unimplemented_ops = {
     .endianness = DEVICE_BIG_ENDIAN,
 };
 
-static void fsi_cfam_instance_init(Object *obj)
+bool fsi_cfam_add_engine(FSICFAMCommon *cfam, DeviceState *engine,
+                         hwaddr offset, Error **errp)
 {
-    FSICFAMState *s = FSI_CFAM(obj);
+    if (!qdev_realize(engine, BUS(&cfam->lbus), errp)) {
+        return false;
+    }
 
-    object_initialize_child(obj, "scratchpad", &s->scratchpad,
-                            TYPE_FSI_SCRATCHPAD);
+    memory_region_add_subregion(&cfam->lbus.mr, offset,
+                                &FSI_LBUS_DEVICE(engine)->iomem);
+    return true;
 }
 
-static void fsi_cfam_realize(DeviceState *dev, Error **errp)
+static void fsi_cfam_common_realize(DeviceState *dev, Error **errp)
 {
-    FSICFAMState *cfam = FSI_CFAM(dev);
+    FSICFAMCommon *cfam = FSI_CFAM_COMMON(dev);
+    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_GET_CLASS(dev);
     FSISlaveState *slave = FSI_SLAVE(dev);
+    const char *type = object_get_typename(OBJECT(dev));
+    g_autofree char *config_name = g_strdup_printf("%s.config", type);
 
     /* Each slave has a 2MiB address space */
     memory_region_init_io(&cfam->mr, OBJECT(cfam), &fsi_cfam_unimplemented_ops,
-                          cfam, TYPE_FSI_CFAM, 2 * MiB);
+                          cfam, type, FSI_CFAM_SLOT_SIZE);
 
     qbus_init(&cfam->lbus, sizeof(cfam->lbus), TYPE_FSI_LBUS, DEVICE(cfam),
               NULL);
 
     memory_region_init_io(&cfam->config_iomem, OBJECT(cfam), &cfam_config_ops,
-                          cfam, TYPE_FSI_CFAM ".config", 0x400);
+                          cfam, config_name, FSI_CFAM_CONFIG_SIZE);
 
     memory_region_add_subregion(&cfam->mr, 0, &cfam->config_iomem);
-    memory_region_add_subregion(&cfam->mr, 0x800, &slave->iomem);
-    memory_region_add_subregion(&cfam->mr, 0xc00, &cfam->lbus.mr);
+    memory_region_add_subregion(&cfam->mr, cc->responder_offset, &slave->iomem);
+    memory_region_add_subregion(&cfam->mr, cc->lbus_offset, &cfam->lbus.mr);
 
-    /* Add scratchpad engine */
-    if (!qdev_realize(DEVICE(&cfam->scratchpad), BUS(&cfam->lbus), errp)) {
+    if (!cc->realize_engines(cfam, errp)) {
         return;
     }
-
-    FSILBusDevice *fsi_dev = FSI_LBUS_DEVICE(&cfam->scratchpad);
-    memory_region_add_subregion(&cfam->lbus.mr, 0, &fsi_dev->iomem);
 }
 
-static void fsi_cfam_class_init(ObjectClass *klass, const void *data)
+static void fsi_cfam_common_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
+
     dc->bus_type = TYPE_FSI_BUS;
-    dc->realize = fsi_cfam_realize;
+    dc->realize = fsi_cfam_common_realize;
+}
+
+static bool fsi_cfam_realize_engines(FSICFAMCommon *cfam, Error **errp)
+{
+    FSICFAM *s = FSI_CFAM(cfam);
+
+    /* Add scratchpad engine */
+    object_initialize_child(OBJECT(s), "scratchpad", &s->scratchpad,
+                            TYPE_FSI_SCRATCHPAD);
+
+    return fsi_cfam_add_engine(cfam, DEVICE(&s->scratchpad), 0, errp);
 }
 
+static void fsi_cfam_class_init(ObjectClass *klass, const void *data)
+{
+    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_CLASS(klass);
+
+    cc->config = cfam_p9_config;
+    cc->config_nr = ARRAY_SIZE(cfam_p9_config);
+    cc->responder_offset = 0x800;
+    cc->lbus_offset = 0xc00;
+    cc->realize_engines = fsi_cfam_realize_engines;
+}
+
+static const TypeInfo fsi_cfam_common_info = {
+    .name = TYPE_FSI_CFAM_COMMON,
+    .parent = TYPE_FSI_SLAVE,
+    .instance_size = sizeof(FSICFAMCommon),
+    .class_size = sizeof(FSICFAMCommonClass),
+    .class_init = fsi_cfam_common_class_init,
+    .abstract = true,
+};
+
 static const TypeInfo fsi_cfam_info = {
     .name = TYPE_FSI_CFAM,
-    .parent = TYPE_FSI_SLAVE,
-    .instance_init = fsi_cfam_instance_init,
-    .instance_size = sizeof(FSICFAMState),
+    .parent = TYPE_FSI_CFAM_COMMON,
+    .instance_size = sizeof(FSICFAM),
     .class_init = fsi_cfam_class_init,
 };
 
 static void fsi_cfam_register_types(void)
 {
+    type_register_static(&fsi_cfam_common_info);
     type_register_static(&fsi_cfam_info);
 }
 
diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c
index 083a5507ab..d82df1c094 100644
--- a/hw/fsi/fsi-master.c
+++ b/hw/fsi/fsi-master.c
@@ -130,7 +130,7 @@ static void fsi_master_realize(DeviceState *dev, Error **errp)
     }
 
     /* address ? */
-    memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.mr);
+    memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.parent.mr);
 }
 
 static void fsi_master_reset(DeviceState *dev)
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 2/8] fsi/cfam: Add CFAM-S model
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 1/8] fsi/cfam: Add common CFAM base class Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 3/8] arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges Mikail Sadic
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

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 <mikail.sadic@ibm.com>
---
 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..041607d5ce
--- /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(FSICFAMS, 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 FSICFAMS {
+    /* < private > */
+    FSICFAMCommon 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 bdf579b4fe..a9cd7b7f63 100644
--- a/include/hw/fsi/cfam.h
+++ b/include/hw/fsi/cfam.h
@@ -35,6 +35,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FSICFAM, 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(FSICFAM, 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 FSICFAMCommon {
     /* < private > */
     FSISlaveState parent;
diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
index 87c11ec3dc..dc51c96840 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];
     FSICFAM cfam;
+    FSICFAMS 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..7a6047944f
--- /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(FSICFAMCommon *cfam, Error **errp)
+{
+    FSICFAMS *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();
+    FSICFAMS *cfam_s = FSI_CFAM_S(dev);
+    FSICFAMCommon *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(FSICFAMS),
+        .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



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 3/8] arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 1/8] fsi/cfam: Add common CFAM base class Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 2/8] fsi/cfam: Add CFAM-S model Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-11  4:32   ` Cédric Le Goater
  2026-08-10 18:57 ` [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads Mikail Sadic
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

The AST2700 SoC exposes two FSI controllers behind APB-to-OPB bridges.
Add the ASPEED_DEV_FSI1/FSI2 memory-map entries, instantiate both fsi[]
APB-to-OPB bridges and map them during SoC realize, so the CFAM/CFAM-S
devices on the FSI local buses become reachable from the SoC.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 hw/arm/aspeed_ast27x0.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index dddd7d2106..b908d7d4ff 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -17,6 +17,7 @@
 #include "qemu/module.h"
 #include "qemu/error-report.h"
 #include "hw/i2c/aspeed_i2c.h"
+#include "hw/fsi/aspeed_apb2opb.h"
 #include "net/net.h"
 #include "system/qtest.h"
 #include "system/system.h"
@@ -98,6 +99,8 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
     [ASPEED_DEV_PCIE_MMIO0] = 0x60000000,
     [ASPEED_DEV_PCIE_MMIO1] = 0x80000000,
     [ASPEED_DEV_PCIE_MMIO2] = 0xA0000000,
+    [ASPEED_DEV_FSI1]      =  0x21800000,
+    [ASPEED_DEV_FSI2]      =  0x23800000,
     [ASPEED_DEV_SPI_BOOT]  =  0x100000000,
     [ASPEED_DEV_SDRAM]     =  0x400000000,
 };
@@ -559,6 +562,11 @@ static void aspeed_soc_ast2700_init(Object *obj)
                              sc->silicon_rev);
     }
 
+    for (i = 0; i < ARRAY_SIZE(s->fsi); i++) {
+        object_initialize_child(obj, "fsi[*]", &s->fsi[i],
+                                TYPE_ASPEED_APB2OPB);
+    }
+
     object_initialize_child(obj, "dpmcu", &s->dpmcu,
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "iomem", &s->iomem,
@@ -1132,6 +1140,16 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
         }
     }
 
+    /* FSI / OPB */
+    for (i = 0; i < ARRAY_SIZE(s->fsi); i++) {
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->fsi[i]), errp)) {
+            return;
+        }
+        aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->fsi[i]), 0,
+                        sc->memmap[ASPEED_DEV_FSI1 + i]);
+    }
+
+
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->dpmcu),
                                   "aspeed.dpmcu",
                                   sc->memmap[ASPEED_DEV_DPMCU],
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
                   ` (2 preceding siblings ...)
  2026-08-10 18:57 ` [PATCH v3 3/8] arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-11  4:30   ` Cédric Le Goater
  2026-08-10 18:57 ` [PATCH v3 5/8] hw/sensor: Add UCD90320 model Mikail Sadic
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

An SMBus block read (I2C_M_RECV_LEN) reads the block length from the
first received byte. The Linux/U-Boot aspeed I2C driver obtains that
first byte from the I2CC_STS_AND_BUFF register (modelled here as
reg_byte_buf), even when the transfer uses DMA. The DMA receive path,
however, only wrote received data to DRAM and never updated
reg_byte_buf, so block reads read a stale/zero length.

Mirror the first DMA-received byte into reg_byte_buf so that
I2C_M_RECV_LEN transfers using DMA report the correct block length.
This is required for the ucd9000 driver, which uses
i2c_smbus_read_block_data().

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 hw/i2c/aspeed_i2c.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
index 27afcaecee..facb54d27e 100644
--- a/hw/i2c/aspeed_i2c.c
+++ b/hw/i2c/aspeed_i2c.c
@@ -365,6 +365,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
     uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
     uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
     uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
+    bool first_dma_byte;
     int pool_rx_count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
                                                 RX_SIZE) + 1;
 
@@ -391,6 +392,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
         }
 
         aspeed_i2c_set_rx_dma_dram_offset(bus);
+        first_dma_byte = true;
         while (bus->regs[reg_dma_len]) {
             MemTxResult result;
 
@@ -407,6 +409,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
                 return;
             }
 
+            /* Mirror first byte to reg_byte_buf for I2C_M_RECV_LEN. */
+            if (first_dma_byte) {
+                SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data);
+                first_dma_byte = false;
+            }
             bus->dma_dram_offset++;
             bus->regs[reg_dma_len]--;
             /* In new mode, keep track of how many bytes we RXed */
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 5/8] hw/sensor: Add UCD90320 model
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
                   ` (3 preceding siblings ...)
  2026-08-10 18:57 ` [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 6/8] ufs: Make the logical block size configurable and answer absent LUNs Mikail Sadic
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

Add a PMBus device model for the TI-UCD90320 24-rail power
sequencer. Configures 24 pages with linear vout mode and responds to
the vendor-specific UCD9000_DEVICE_ID, NUM_PAGES, MONITOR_CONFIG, and
MFR_STATUS commands, allowing the Linux ucd9000 driver to bind and
create hwmon sysfs entries.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 MAINTAINERS             |   1 +
 docs/specs/index.rst    |   1 +
 docs/specs/ucd90320.rst |  36 +++++++++
 hw/sensor/ucd90320.c    | 169 ++++++++++++++++++++++++++++++++++++++++
 hw/arm/Kconfig          |   1 +
 hw/sensor/Kconfig       |   4 +
 hw/sensor/meson.build   |   1 +
 7 files changed, 213 insertions(+)
 create mode 100644 docs/specs/ucd90320.rst
 create mode 100644 hw/sensor/ucd90320.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 902db77218..ec415cb8e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4063,6 +4063,7 @@ F: hw/i2c/pmbus_device.c
 F: hw/sensor/adm1272.c
 F: hw/sensor/isl_pmbus_vr.c
 F: hw/sensor/max34451.c
+F: hw/sensor/ucd90320.c
 F: include/hw/i2c/pmbus_device.h
 F: include/hw/sensor/isl_pmbus_vr.h
 F: tests/qtest/adm1272-test.c
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index b7909a108a..4de65e2fdf 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -39,4 +39,5 @@ guest hardware that is specific to QEMU.
    riscv-iommu
    riscv-aia
    aspeed-intc
+   ucd90320
    iommu-testdev
diff --git a/docs/specs/ucd90320.rst b/docs/specs/ucd90320.rst
new file mode 100644
index 0000000000..fdc0878c75
--- /dev/null
+++ b/docs/specs/ucd90320.rst
@@ -0,0 +1,36 @@
+Texas Instruments UCD90320 Power Sequencer
+==========================================
+
+The UCD90320 is a 24-rail PMBus power sequencer. QEMU models it as a PMBus
+device (``"ucd90320"``) so the Linux ``ucd9000`` driver can bind and create
+``hwmon`` sysfs entries.
+
+The model configures 24 PMBus pages (one per rail) with ``VOUT_MODE = 0x00``
+(linear, exponent 0). ``READ_VOUT`` and ``MFR_STATUS`` return zero on all
+pages.
+
+Vendor-specific commands handled:
+
++------+---------------------------+--------------------------------------------+
+| Code | Name                      | Response                                   |
++======+===========================+============================================+
+| 0xD5 | ``UCD9000_MONITOR_CONFIG``| Block: one byte ``0x00``                   |
++------+---------------------------+--------------------------------------------+
+| 0xD6 | ``UCD9000_NUM_PAGES``     | Byte: ``24``                               |
++------+---------------------------+--------------------------------------------+
+| 0xF3 | ``UCD9000_MFR_STATUS``    | Block: four bytes ``0x00 0x00 0x00 0x00``  |
++------+---------------------------+--------------------------------------------+
+| 0xFD | ``UCD9000_DEVICE_ID``     | Block: ASCII string ``"UCD90320"``         |
++------+---------------------------+--------------------------------------------+
+
+``DEVICE_ID`` uses the vendor-specific code ``0xFD`` rather than the standard
+``PMBUS_IC_DEVICE_ID`` (``0xAD``), matching the Linux ``ucd9000`` probe
+sequence.
+
+The UCD90320 is instantiated automatically in the ``huygens-bmc`` machine on
+I2C bus 5 at address ``0x11``. To instantiate on a different Aspeed machine:
+
+.. code-block:: c
+
+  i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5),
+                          "ucd90320", 0x11);
diff --git a/hw/sensor/ucd90320.c b/hw/sensor/ucd90320.c
new file mode 100644
index 0000000000..6d4f828448
--- /dev/null
+++ b/hw/sensor/ucd90320.c
@@ -0,0 +1,169 @@
+/*
+ * Texas Instruments UCD90320 24-Rail PMBus Power Sequencer
+ *
+ * Copyright 2026 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_UCD90320 "ucd90320"
+
+/* UCD90320 has 24 sequenced power-rail pages */
+#define UCD90320_NUM_PAGES 24
+
+/* Vendor-specific command codes (not in the standard PMBus register table) */
+#define UCD9000_MONITOR_CONFIG  0xd5
+#define UCD9000_NUM_PAGES       0xd6
+#define UCD9000_MFR_STATUS      0xf3
+#define UCD9000_DEVICE_ID       0xfd
+
+#define UCD90320_DEVICE_ID_LEN     8
+#define UCD90320_MFR_STATUS_LEN    4
+
+typedef struct UCD90320State {
+    PMBusDevice parent;
+
+    /* Reset values for the vendor-specific block-read commands. */
+    uint8_t device_id[UCD90320_DEVICE_ID_LEN];
+    uint8_t monitor_config;
+    uint8_t mfr_status[UCD90320_MFR_STATUS_LEN];
+} UCD90320State;
+
+#define UCD90320(obj) OBJECT_CHECK(UCD90320State, (obj), TYPE_UCD90320)
+
+static void ucd90320_send_block(PMBusDevice *pmdev,
+                                const uint8_t *data, uint8_t len)
+{
+    int i;
+
+    pmdev->out_buf[len + pmdev->out_buf_len] = len;
+    for (i = len - 1; i >= 0; i--) {
+        pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
+    }
+    pmdev->out_buf_len += len + 1;
+}
+
+static uint8_t ucd90320_read_byte(PMBusDevice *pmdev)
+{
+    UCD90320State *s = UCD90320(pmdev);
+
+    switch (pmdev->code) {
+    case UCD9000_DEVICE_ID:
+        ucd90320_send_block(pmdev, s->device_id, sizeof(s->device_id));
+        pmbus_idle(pmdev);
+        return 0;
+    case UCD9000_NUM_PAGES:
+        pmbus_send8(pmdev, UCD90320_NUM_PAGES);
+        pmbus_idle(pmdev);
+        return 0;
+
+    case UCD9000_MONITOR_CONFIG:
+        ucd90320_send_block(pmdev, &s->monitor_config,
+                            sizeof(s->monitor_config));
+        pmbus_idle(pmdev);
+        return 0;
+    case UCD9000_MFR_STATUS:
+        ucd90320_send_block(pmdev, s->mfr_status, sizeof(s->mfr_status));
+        pmbus_idle(pmdev);
+        return 0;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: reading from unsupported register: 0x%02x\n",
+                      __func__, pmdev->code);
+        break;
+    }
+    return 0xFF;
+}
+
+static int ucd90320_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+                               uint8_t len)
+{
+    if (len == 0) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+        return -1;
+    }
+
+    pmdev->code = buf[0];
+
+    if (len == 1) {
+        return 0;
+    }
+
+    return 0;
+}
+
+static void ucd90320_exit_reset(Object *obj, ResetType type)
+{
+    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+    UCD90320State *s = UCD90320(obj);
+
+    pmdev->capability = 0x20; /* PEC supported */
+
+    for (int i = 0; i < UCD90320_NUM_PAGES; i++) {
+        pmdev->pages[i].operation     = 0x80; /* on */
+        pmdev->pages[i].on_off_config = 0x1a;
+        pmdev->pages[i].vout_mode     = 0x00; /* linear mode, exponent=0 */
+        pmdev->pages[i].read_vout     = 0;    /* rails off, pgood=0 */
+    }
+
+    memcpy(s->device_id, "UCD90320", sizeof(s->device_id));
+    s->monitor_config = 0x00;
+    memset(s->mfr_status, 0x00, sizeof(s->mfr_status));
+}
+
+static void ucd90320_init(Object *obj)
+{
+    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+    uint64_t flags = PB_HAS_VOUT | PB_HAS_VOUT_MODE |
+                     PB_HAS_STATUS_MFR_SPECIFIC;
+
+    for (int i = 0; i < UCD90320_NUM_PAGES; i++) {
+        pmbus_page_config(pmdev, i, flags);
+    }
+}
+
+static const VMStateDescription vmstate_ucd90320 = {
+    .name = TYPE_UCD90320,
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (const VMStateField[]) {
+        VMSTATE_PMBUS_DEVICE(parent, UCD90320State),
+        VMSTATE_UINT8_ARRAY(device_id, UCD90320State, UCD90320_DEVICE_ID_LEN),
+        VMSTATE_UINT8(monitor_config, UCD90320State),
+        VMSTATE_UINT8_ARRAY(mfr_status, UCD90320State,
+                            UCD90320_MFR_STATUS_LEN),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ucd90320_class_init(ObjectClass *klass, const void *data)
+{
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+    dc->desc = "Texas Instruments UCD90320 24-Rail Power Sequencer";
+    dc->vmsd = &vmstate_ucd90320;
+    k->write_data = ucd90320_write_data;
+    k->receive_byte = ucd90320_read_byte;
+    k->device_num_pages = UCD90320_NUM_PAGES;
+    rc->phases.exit = ucd90320_exit_reset;
+}
+
+static const TypeInfo ucd90320_types[] = {
+    {
+        .name          = TYPE_UCD90320,
+        .parent        = TYPE_PMBUS_DEVICE,
+        .instance_size = sizeof(UCD90320State),
+        .instance_init = ucd90320_init,
+        .class_init    = ucd90320_class_init,
+    },
+};
+
+DEFINE_TYPES(ucd90320_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 82e0bc2e70..ac44740547 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -555,6 +555,7 @@ config ASPEED_SOC
     select LED
     select PMBUS
     select MAX31785
+    select UCD90320
     select FSI_APB2OPB_ASPEED
     select AT24C
     select PCI_EXPRESS
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4ab..135ffa7d5d 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -43,3 +43,7 @@ config ISL_PMBUS_VR
 config MAX31785
     bool
     depends on PMBUS
+
+config UCD90320
+    bool
+    depends on PMBUS
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3359..c0583161d3 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
 system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
 system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
 system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
+system_ss.add(when: 'CONFIG_UCD90320', if_true: files('ucd90320.c'))
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 6/8] ufs: Make the logical block size configurable and answer absent LUNs
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
                   ` (4 preceding siblings ...)
  2026-08-10 18:57 ` [PATCH v3 5/8] hw/sensor: Add UCD90320 model Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 7/8] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 8/8] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
  7 siblings, 0 replies; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

Two things the ufs-lu model hardcodes that hardware does not.

The logical block size is fixed at UFS_BLOCK_SIZE (4096), both in the unit
descriptor the logical unit reports and in the block size of the scsi-hd
backing it. UFS does not require that: the unit descriptor carries a
base-2 exponent, and 512-byte blocks are common on real hardware and in
images built for it. Add a 'logical-block-size' property that sets both,
defaulting to UFS_BLOCK_SIZE so the ufs PCI device keeps the geometry and
the property set it has today. Restrict it to powers of two from 512 bytes
to UFS_BLOCK_SIZE, and reject a drive shorter than one block, which would
otherwise realize a unit reporting zero blocks.

An INQUIRY addressed to a logical unit that is not mapped fails the
request outright. SPC has a specific answer for this case: return the
standard INQUIRY data with the peripheral qualifier and device type saying
that no device is present on that logical unit, with GOOD status, so that
a host enumerating logical units can tell an absent unit from a transport
error. hw/scsi/scsi-bus.c already answers this way for a target's
unsupported logical units. Do the same for an unmapped ufs-lu, using
TYPE_NO_LUN. Commands other than a standard INQUIRY still fail, now with
LOGICAL UNIT NOT SUPPORTED sense data rather than an unadorned request
failure. The invalid-LUN trace point is unchanged.

Both are needed by the AST2700 UFS controller added later in this series:
the OpenBMC images it boots are laid out for 512-byte sectors, and U-Boot
logs an OCS failure for every unpopulated logical unit while probing it.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 hw/ufs/ufs.h |  3 +++
 hw/ufs/lu.c  | 67 +++++++++++++++++++++++++++++++++++++++++++++++++---
 hw/ufs/ufs.c |  2 +-
 3 files changed, 68 insertions(+), 4 deletions(-)

diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index aa8361d93d..47d1c72ef3 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -21,6 +21,7 @@
 #define UFS_MAX_MCQ_QNUM 32
 #define UFS_BLOCK_SIZE_SHIFT 12
 #define UFS_BLOCK_SIZE (1 << UFS_BLOCK_SIZE_SHIFT)
+#define UFS_MIN_BLOCK_SIZE 512
 
 typedef struct UfsBusClass {
     BusClass parent_class;
@@ -80,6 +81,7 @@ typedef UfsReqResult (*UfsScsiOp)(struct UfsLu *, UfsRequest *);
 typedef struct UfsLu {
     DeviceState qdev;
     uint8_t lun;
+    uint32_t logical_block_size;
     UnitDescriptor unit_desc;
     SCSIBus bus;
     SCSIDevice *scsi_dev;
@@ -303,6 +305,7 @@ void ufs_build_query_response(UfsRequest *req);
 void ufs_complete_req(UfsRequest *req, UfsReqResult req_result);
 void ufs_wb_update_avail_buffer(UfsHc *u);
 void ufs_init_wlu(UfsLu *wlu, uint8_t wlun);
+UfsReqResult ufs_emulate_absent_lun(UfsRequest *req);
 bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as,
                  Error **errp);
 void ufs_unrealize(UfsHc *u);
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index eeca865eb5..b1aba79a53 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -308,6 +308,44 @@ static int ufs_emulate_wlun_inquiry(UfsRequest *req, uint8_t *outbuf,
     return SCSI_INQUIRY_LEN;
 }
 
+/*
+ * A logical unit that is not mapped answers a standard INQUIRY as "not
+ * connected" with GOOD status, as hardware does, so that a host bus scan
+ * skips it instead of reporting a controller error. Any other command is
+ * rejected.
+ */
+UfsReqResult ufs_emulate_absent_lun(UfsRequest *req)
+{
+    QEMU_UNINITIALIZED uint8_t outbuf[SCSI_INQUIRY_LEN];
+    uint8_t sense_buf[UFS_SENSE_SIZE];
+    uint8_t scsi_status;
+    int len = 0;
+
+    if (req->req_upiu.sc.cdb[0] == INQUIRY &&
+        !(req->req_upiu.sc.cdb[1] & 0x1)) {
+        memset(outbuf, 0, sizeof(outbuf));
+        outbuf[0] = TYPE_NO_LUN;
+        outbuf[3] = 0x2;
+        outbuf[4] = SCSI_INQUIRY_LEN - 5;
+        len = SCSI_INQUIRY_LEN;
+        scsi_status = GOOD;
+    } else {
+        scsi_build_sense(sense_buf, SENSE_CODE(LUN_NOT_SUPPORTED));
+        scsi_status = CHECK_CONDITION;
+    }
+
+    len = MIN(len, (int)req->data_len);
+    if (scsi_status == GOOD && len > 0 &&
+        dma_buf_read(outbuf, len, NULL, req->sg, MEMTXATTRS_UNSPECIFIED) !=
+            MEMTX_OK) {
+        return UFS_REQUEST_FAIL;
+    }
+
+    ufs_build_scsi_response_upiu(req, sense_buf, sizeof(sense_buf), len,
+                                 scsi_status);
+    return UFS_REQUEST_SUCCESS;
+}
+
 static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, UfsRequest *req)
 {
     uint8_t lun = lu->lun;
@@ -394,6 +432,8 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
 static const Property ufs_lu_props[] = {
     DEFINE_PROP_DRIVE("drive", UfsLu, conf.blk),
     DEFINE_PROP_UINT8("lun", UfsLu, lun, 0),
+    DEFINE_PROP_UINT32("logical-block-size", UfsLu, logical_block_size,
+                       UFS_BLOCK_SIZE),
 };
 
 static bool ufs_add_lu(UfsHc *u, UfsLu *lu, Error **errp)
@@ -435,7 +475,7 @@ static void ufs_init_lu(UfsLu *lu)
     lu->unit_desc.length = sizeof(UnitDescriptor);
     lu->unit_desc.descriptor_idn = UFS_QUERY_DESC_IDN_UNIT;
     lu->unit_desc.lu_enable = 0x01;
-    lu->unit_desc.logical_block_size = UFS_BLOCK_SIZE_SHIFT;
+    lu->unit_desc.logical_block_size = ctz32(lu->logical_block_size);
     lu->unit_desc.unit_index = lu->lun;
     lu->unit_desc.logical_block_count =
         cpu_to_be64(brdv_len / (1 << lu->unit_desc.logical_block_size));
@@ -455,6 +495,25 @@ static bool ufs_lu_check_constraints(UfsLu *lu, Error **errp)
         return false;
     }
 
+    if (!is_power_of_2(lu->logical_block_size)) {
+        error_setg(errp, "logical-block-size must be a power of 2, not %"
+                   PRIu32, lu->logical_block_size);
+        return false;
+    }
+
+    if (lu->logical_block_size < UFS_MIN_BLOCK_SIZE ||
+        lu->logical_block_size > UFS_BLOCK_SIZE) {
+        error_setg(errp, "logical-block-size must be between %d and %d bytes",
+                   UFS_MIN_BLOCK_SIZE, UFS_BLOCK_SIZE);
+        return false;
+    }
+
+    if (blk_getlength(lu->conf.blk) < lu->logical_block_size) {
+        error_setg(errp, "drive is smaller than one %" PRIu32 "-byte block",
+                   lu->logical_block_size);
+        return false;
+    }
+
     return true;
 }
 
@@ -475,8 +534,10 @@ static void ufs_init_scsi_device(UfsLu *lu, BlockBackend *blk, Error **errp)
     scsi_dev = qdev_new("scsi-hd");
     object_property_add_child(OBJECT(&lu->bus), "ufs-scsi", OBJECT(scsi_dev));
 
-    qdev_prop_set_uint32(scsi_dev, "physical_block_size", UFS_BLOCK_SIZE);
-    qdev_prop_set_uint32(scsi_dev, "logical_block_size", UFS_BLOCK_SIZE);
+    qdev_prop_set_uint32(scsi_dev, "physical_block_size",
+                         lu->logical_block_size);
+    qdev_prop_set_uint32(scsi_dev, "logical_block_size",
+                         lu->logical_block_size);
     qdev_prop_set_uint32(scsi_dev, "scsi-id", 0);
     qdev_prop_set_uint32(scsi_dev, "lun", lu->lun);
     if (!qdev_prop_set_drive_err(scsi_dev, "drive", blk, errp)) {
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 36c674af32..016876eb63 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -1083,7 +1083,7 @@ static UfsReqResult ufs_exec_scsi_cmd(UfsRequest *req)
 
     if (!is_wlun(lun) && (lun >= UFS_MAX_LUS || u->lus[lun] == NULL)) {
         trace_ufs_err_scsi_cmd_invalid_lun(lun);
-        return UFS_REQUEST_FAIL;
+        return ufs_emulate_absent_lun(req);
     }
 
     switch (lun) {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 7/8] ufs/aspeed: Add AST2700 UFS host controller
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
                   ` (5 preceding siblings ...)
  2026-08-10 18:57 ` [PATCH v3 6/8] ufs: Make the logical block size configurable and answer absent LUNs Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-10 18:57 ` [PATCH v3 8/8] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
  7 siblings, 0 replies; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

The AST2700 carries a UFS host controller (aspeed,ufshc-m31-16nm) at
0x12c08200 on IRQ 118, used by the OpenBMC image to reach its root
filesystem.

Add 'aspeed-ufs' as a subclass of the generic sysbus UFS controller. The
sysbus device supplies the MMIO region, the interrupt, the DMA address
space and the controller properties, so the only thing left to model is
the UFSHCI version: the AST2700 reports 2.0, while the core reports 4.1,
which makes the U-Boot and Linux drivers run 4.x-only probe steps this
model does not implement.

Wire the controller into the AST2700 SoC and select UFS_SYSBUS, so an
Aspeed target no longer depends on the PCI UFS device being configured
to get a UFS controller.

The UFS bus only exists once the controller is realized, so the board
creates the logical unit for the first backend drive after SoC realize
rather than through a controller drive property. AspeedMachineClass
gains a ufs_block_size field for machines whose image geometry differs
from the ufs-lu default; zero leaves the default in place.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 MAINTAINERS                 |  2 ++
 docs/specs/aspeed-ufs.rst   | 46 +++++++++++++++++++++++++
 docs/specs/index.rst        |  1 +
 include/hw/arm/aspeed.h     |  1 +
 include/hw/arm/aspeed_soc.h |  3 ++
 include/hw/ufs/aspeed_ufs.h | 15 +++++++++
 hw/arm/aspeed.c             | 25 ++++++++++++++
 hw/arm/aspeed_ast27x0.c     | 13 +++++++
 hw/ufs/aspeed_ufs.c         | 67 +++++++++++++++++++++++++++++++++++++
 hw/arm/Kconfig              |  1 +
 hw/ufs/meson.build          |  1 +
 11 files changed, 175 insertions(+)
 create mode 100644 docs/specs/aspeed-ufs.rst
 create mode 100644 include/hw/ufs/aspeed_ufs.h
 create mode 100644 hw/ufs/aspeed_ufs.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ec415cb8e4..4b335c5dc0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2700,6 +2700,8 @@ M: Jeuk Kim <jeuk20.kim@samsung.com>
 S: Supported
 F: hw/ufs/*
 F: include/block/ufs.h
+F: include/hw/ufs/aspeed_ufs.h
+F: docs/specs/aspeed-ufs.rst
 F: tests/qtest/ufs-test.c
 
 megasas
diff --git a/docs/specs/aspeed-ufs.rst b/docs/specs/aspeed-ufs.rst
new file mode 100644
index 0000000000..2ab7e7e771
--- /dev/null
+++ b/docs/specs/aspeed-ufs.rst
@@ -0,0 +1,46 @@
+ASPEED AST2700 UFS Host Controller
+===================================
+
+The AST2700 SoC includes a UFS host controller identified in the device tree
+as ``aspeed,ufshc-m31-16nm``, mapped at ``0x12c08200`` (IRQ SPI 118).
+
+QEMU models it as ``aspeed-ufs``, a subclass of the generic sysbus UFS
+controller ``sysbus-ufs`` (``hw/ufs/ufs-sysbus.c``), which in turn wraps the
+transport-independent UFS core (``hw/ufs/ufs.c`` and ``hw/ufs/lu.c``) that
+also backs the PCI UFS device. The sysbus device provides the MMIO region,
+the interrupt line, the DMA address space and the controller properties; the
+core implements the UFSHCI registers, the UTP transfer and task list
+processing, the UPIU and query handling and the SCSI logical-unit logic.
+
+The only AST2700-specific behaviour ``aspeed-ufs`` adds is the controller
+version register value ``0x00000200``, the UFSHCI 2.0 interface the hardware
+reports.
+
+The clock/reset wrapper at ``0x12c08000`` (``aspeed,ast2700-ufscnr``) is left
+as an ``UnimplementedDevice``.
+
+Logical units
+-------------
+
+Storage is attached through ``ufs-lu`` devices on the controller's UFS bus
+(``ufs-bus.0``), exactly as for the PCI UFS device. The Huygens OpenBMC image
+is laid out for 512-byte sectors, so its logical unit is created with
+``logical-block-size=512``.
+
+Usage
+-----
+
+Attach a UFS image as logical unit 0 of the controller's UFS bus:
+
+.. code-block:: console
+
+  qemu-system-aarch64 -M huygens-bmc \
+    -nodefaults \
+    -blockdev node-name=fmc0,driver=file,filename=image-bmc \
+    -device w25q01jvq,bus=ssi.0,cs=0,drive=fmc0 \
+    -blockdev node-name=ufs0,driver=file,filename=ufs.img \
+    -device ufs-lu,bus=ufs-bus.0,drive=ufs0,lun=0,logical-block-size=512 \
+    -display none -serial mon:stdio
+
+Please check :doc:`../../system/arm/aspeed` for more details on the
+``huygens-bmc`` machine.
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index 4de65e2fdf..dd0cdec8d4 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -40,4 +40,5 @@ guest hardware that is specific to QEMU.
    riscv-aia
    aspeed-intc
    ucd90320
+   aspeed-ufs
    iommu-testdev
diff --git a/include/hw/arm/aspeed.h b/include/hw/arm/aspeed.h
index a00238ed74..dedc784926 100644
--- a/include/hw/arm/aspeed.h
+++ b/include/hw/arm/aspeed.h
@@ -62,6 +62,7 @@ struct AspeedMachineClass {
     uint32_t uart_default;
     bool sdhci_wp_inverted;
     bool vbootrom;
+    uint32_t ufs_block_size;
 };
 
 /*
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 41dc04e293..8a562d47ec 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -35,6 +35,7 @@
 #include "hw/gpio/aspeed_gpio.h"
 #include "hw/gpio/aspeed_sgpio.h"
 #include "hw/sd/aspeed_sdhci.h"
+#include "hw/ufs/aspeed_ufs.h"
 #include "hw/usb/hcd-ehci.h"
 #include "qom/object.h"
 #include "hw/misc/aspeed_lpc.h"
@@ -118,6 +119,7 @@ struct AspeedSoCState {
     AspeedAPB2OPBState fsi[2];
     AspeedLTPIState ltpi_ctrl[ASPEED_IOEXP_NUM];
     AspeedAST1700SoCState ioexp[ASPEED_IOEXP_NUM];
+    SysbusUfsState ufs;
 };
 
 #define TYPE_ASPEED_SOC "aspeed-soc"
@@ -298,6 +300,7 @@ enum {
     ASPEED_DEV_PRIC0,
     ASPEED_DEV_PRIC1,
     ASPEED_DEV_OTP,
+    ASPEED_DEV_UFS,
 };
 
 const char *aspeed_soc_cpu_type(const char * const *valid_cpu_types);
diff --git a/include/hw/ufs/aspeed_ufs.h b/include/hw/ufs/aspeed_ufs.h
new file mode 100644
index 0000000000..6b9f85498a
--- /dev/null
+++ b/include/hw/ufs/aspeed_ufs.h
@@ -0,0 +1,15 @@
+/*
+ * ASPEED AST2700 UFS Host Controller
+ *
+ * Copyright 2026 IBM Corp.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ASPEED_UFS_H
+#define ASPEED_UFS_H
+
+#include "hw/ufs/ufs-sysbus.h"
+
+#define TYPE_ASPEED_UFS "aspeed-ufs"
+
+#endif /* ASPEED_UFS_H */
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index a48c442058..b70a1326a1 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -187,6 +187,31 @@ static void aspeed_machine_init(MachineState *machine)
     aspeed_connect_serial_hds_to_uarts(bmc);
     qdev_realize(DEVICE(bmc->soc), NULL, &error_abort);
 
+    /*
+     * If the SoC instantiated a UFS host controller (AST2700), attach the
+     * first backend drive to it as logical unit 0.  The controller and its
+     * UFS bus only exist once the SoC has been realized, so the ufs-lu is
+     * created here rather than through a controller drive property.
+     */
+    if (object_resolve_path_component(OBJECT(bmc->soc), "ufs")) {
+        DriveInfo *ufs_dinfo = drive_get(IF_NONE, 0, 0);
+
+        if (ufs_dinfo) {
+            DeviceState *ufs_lu = qdev_new(TYPE_UFS_LU);
+
+            qdev_prop_set_uint8(ufs_lu, "lun", 0);
+            if (amc->ufs_block_size) {
+                qdev_prop_set_uint32(ufs_lu, "logical-block-size",
+                                     amc->ufs_block_size);
+            }
+            qdev_prop_set_drive_err(ufs_lu, "drive",
+                                    blk_by_legacy_dinfo(ufs_dinfo),
+                                    &error_fatal);
+            qdev_realize_and_unref(ufs_lu, BUS(&bmc->soc->ufs.ufs.bus),
+                                   &error_fatal);
+        }
+    }
+
     if (defaults_enabled()) {
         aspeed_board_init_flashes(&bmc->soc->fmc,
                               bmc->fmc_model ? bmc->fmc_model : amc->fmc_model,
diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index b908d7d4ff..1ee8f3a210 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -46,6 +46,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
     [ASPEED_GIC_REDIST]    =  0x12280000,
     [ASPEED_DEV_SDMC]      =  0x12C00000,
     [ASPEED_DEV_SCU]       =  0x12C02000,
+    [ASPEED_DEV_UFS]       =  0x12c08200,
     [ASPEED_DEV_RTC]       =  0x12C0F000,
     [ASPEED_DEV_TIMER1]    =  0x12C10000,
     [ASPEED_DEV_PCIE_PHY0] =  0x12C15000,
@@ -129,6 +130,7 @@ static const int aspeed_soc_ast2700a1_irqmap[] = {
     [ASPEED_DEV_EHCI2]     = 37,
     [ASPEED_DEV_PCIE0]     = 56,
     [ASPEED_DEV_PCIE1]     = 57,
+    [ASPEED_DEV_UFS]       = 118,
     [ASPEED_DEV_LPC]       = 192,
     [ASPEED_DEV_IBT]       = 192,
     [ASPEED_DEV_KCS]       = 192,
@@ -533,6 +535,8 @@ static void aspeed_soc_ast2700_init(Object *obj)
     object_initialize_child(obj, "emmc-controller.sdhci", &s->emmc.slots[0],
                             TYPE_SYSBUS_SDHCI);
 
+    object_initialize_child(obj, "ufs", &s->ufs, TYPE_ASPEED_UFS);
+
     snprintf(typename, sizeof(typename), "aspeed.timer-%s", socname);
     object_initialize_child(obj, "timerctrl", &s->timerctrl, typename);
 
@@ -1039,6 +1043,15 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->emmc), 0,
                        aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_EMMC));
 
+    /* UFS */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->ufs), errp)) {
+        return;
+    }
+    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->ufs), 0,
+                    sc->memmap[ASPEED_DEV_UFS]);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->ufs), 0,
+                       aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_UFS));
+
     /* Timer */
     object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&s->scu),
                              &error_abort);
diff --git a/hw/ufs/aspeed_ufs.c b/hw/ufs/aspeed_ufs.c
new file mode 100644
index 0000000000..bfe540b929
--- /dev/null
+++ b/hw/ufs/aspeed_ufs.c
@@ -0,0 +1,67 @@
+/*
+ * ASPEED AST2700 UFS Host Controller
+ *
+ * The AST2700 host controller (aspeed,ufshc-m31-16nm) is the generic sysbus
+ * UFS controller, so this model only overrides the UFSHCI version.
+ *
+ * The clock/reset wrapper at 0x12c08000 (aspeed,ast2700-ufscnr) is modelled
+ * elsewhere as an UnimplementedDevice.
+ *
+ * Copyright 2026 IBM Corp.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/ufs/aspeed_ufs.h"
+
+/* UFSHCI 2.0, against the 4.1 the core reports */
+#define ASPEED_UFS_HCI_VERSION 0x00000200
+
+OBJECT_DECLARE_TYPE(AspeedUfsState, AspeedUfsClass, ASPEED_UFS)
+
+struct AspeedUfsState {
+    SysbusUfsState parent_obj;
+};
+
+struct AspeedUfsClass {
+    DeviceClass parent_class;
+
+    DeviceRealize parent_realize;
+};
+
+static void aspeed_ufs_realize(DeviceState *dev, Error **errp)
+{
+    ERRP_GUARD();
+    AspeedUfsClass *ac = ASPEED_UFS_GET_CLASS(dev);
+    SysbusUfsState *s = SYSBUS_UFS(dev);
+
+    ac->parent_realize(dev, errp);
+    if (*errp) {
+        return;
+    }
+
+    s->ufs.reg.ver = ASPEED_UFS_HCI_VERSION;
+}
+
+static void aspeed_ufs_class_init(ObjectClass *oc, const void *data)
+{
+    AspeedUfsClass *ac = ASPEED_UFS_CLASS(oc);
+    DeviceClass *dc = DEVICE_CLASS(oc);
+
+    device_class_set_parent_realize(dc, aspeed_ufs_realize,
+                                    &ac->parent_realize);
+    dc->desc = "ASPEED UFS Host Controller";
+}
+
+static const TypeInfo aspeed_ufs_types[] = {
+    {
+        .name          = TYPE_ASPEED_UFS,
+        .parent        = TYPE_SYSBUS_UFS,
+        .instance_size = sizeof(AspeedUfsState),
+        .class_size    = sizeof(AspeedUfsClass),
+        .class_init    = aspeed_ufs_class_init,
+    }
+};
+
+DEFINE_TYPES(aspeed_ufs_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index ac44740547..14a076f4f9 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -557,6 +557,7 @@ config ASPEED_SOC
     select MAX31785
     select UCD90320
     select FSI_APB2OPB_ASPEED
+    select UFS_SYSBUS
     select AT24C
     select PCI_EXPRESS
     select PCI_EXPRESS_ASPEED
diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build
index a84627b96a..9266a580f5 100644
--- a/hw/ufs/meson.build
+++ b/hw/ufs/meson.build
@@ -1,3 +1,4 @@
 system_ss.add(when: 'CONFIG_UFS', if_true: files('ufs.c', 'lu.c'))
 system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs-pci.c'))
 system_ss.add(when: 'CONFIG_UFS_SYSBUS', if_true: files('ufs-sysbus.c'))
+system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_ufs.c'))
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 8/8] arm/aspeed: Add AST2700 Huygens machine
  2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
                   ` (6 preceding siblings ...)
  2026-08-10 18:57 ` [PATCH v3 7/8] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
@ 2026-08-10 18:57 ` Mikail Sadic
  2026-08-11  4:35   ` Cédric Le Goater
  7 siblings, 1 reply; 14+ messages in thread
From: Mikail Sadic @ 2026-08-10 18:57 UTC (permalink / raw)
  To: clg, peter.maydell
  Cc: Mikail Sadic, pbonzini, ninad, titusr, jeuk20.kim, philmd,
	steven_lee, leetroy, jamin_lin, kane_chen, andrew, joel, calebs,
	milesg, qemu-arm, qemu-devel

Huygens is the IBM POWER12 server platform. The BMC is built around the
Aspeed AST2700 A2 SoC and handles power sequencing, thermal management,
VPD access and host firmware boot for the POWER host processors.

Add a 'huygens-bmc' machine that instantiates the AST2700 SoC with the
board-specific configuration needed to boot the OpenBMC Linux image:
the FMC/SPI flash models, the I2C topology (BMC, system and chassis VPD
EEPROMs, the UCD90320 power sequencer and temperature sensors) and the
network controllers.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 docs/system/arm/aspeed.rst      |  50 ++++++-
 hw/arm/aspeed_ast27x0_huygens.c | 241 ++++++++++++++++++++++++++++++++
 hw/arm/meson.build              |   1 +
 3 files changed, 290 insertions(+), 2 deletions(-)
 create mode 100644 hw/arm/aspeed_ast27x0_huygens.c

diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 2d51ceeb84..4e42b1edee 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -271,8 +271,8 @@ configuration file for OTP memory:
     done > otpmem.img
   fi
 
-Aspeed 2700 family boards (``ast2700-evb``, ``ast2700fc``)
-==========================================================
+Aspeed 2700 family boards (``ast2700-evb``, ``ast2700fc``, ``huygens-bmc``)
+============================================================================
 
 The QEMU Aspeed machines model BMCs of Aspeed evaluation boards.
 They are based on different releases of the Aspeed SoC :
@@ -285,6 +285,7 @@ AST2700 SoC based machines :
 
 - ``ast2700-evb``          Aspeed AST2700 Evaluation board (Cortex-A35)
 - ``ast2700fc``            Aspeed AST2700 Evaluation board (Cortex-A35 + Cortex-M4)
+- ``huygens-bmc``          Aspeed AST2700 IBM Huygens POWER12 BMC
 
 Supported devices
 -----------------
@@ -312,6 +313,8 @@ Supported devices
  * PECI Controller (minimal)
  * I3C Controller
  * Internal Bridge Controller (SLI dummy)
+ * UFS Host Controller (aspeed,ufshc-m31-16nm) - ``huygens-bmc`` only
+ * FSI APB-to-OPB bridge with CFAM-S mailbox
 
 Missing devices
 ---------------
@@ -440,6 +443,49 @@ Use ``tio`` or another terminal emulator to connect to the consoles:
    $ tio /dev/pts/56
    $ tio /dev/pts/57
 
+Booting the huygens-bmc machine
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The IBM Huygens BMC is based on the AST2700 A2 SoC. Its storage is
+split across two images:
+
+* the FMC (SPI NOR) flash image (``image-bmc``) holds U-Boot, the Linux
+  kernel and the FIT image;
+* the UFS image (``ufs.img``) holds the root filesystem.
+
+Both images can be built from the OpenBMC project tree.
+
+To boot the machine, attach the flash image to the FMC device and the
+root filesystem image to a UFS logical unit. ``-nodefaults`` is used so the
+board does not also create a default flash at chip-select 0, and the console
+is wired explicitly since ``-nodefaults`` disables the implicit one:
+
+.. code-block:: bash
+
+  $ qemu-system-aarch64 -M huygens-bmc \
+        -nodefaults \
+        -blockdev node-name=fmc0,driver=file,filename=image-bmc \
+        -device w25q01jvq,bus=ssi.0,cs=0,drive=fmc0 \
+        -blockdev node-name=ufs0,driver=file,filename=ufs.img \
+        -device ufs-lu,bus=ufs-bus.0,drive=ufs0,lun=0,logical-block-size=512 \
+        -display none -serial mon:stdio
+
+The FMC flash sits on chip-select 0 of the ``ssi.0`` bus and the UFS logical
+unit on ``ufs-bus.0``. The Huygens image is laid out for 512-byte sectors,
+hence ``logical-block-size=512``.
+
+The machine instantiates three ``ftgmac100`` Ethernet controllers, which
+are wired up by the board. To expose them with user networking, add three
+``-nic user`` options to the command above:
+
+.. code-block:: bash
+
+        -nic user \
+        -nic user \
+        -nic user
+
+The default BMC console is ``uart12``.
+
 
 Aspeed Bridge IC and Platform Root of Trust processor family boards (``ast1030-evb``, ``ast1040-evb``, ``ast1060-evb``)
 =======================================================================================================================
diff --git a/hw/arm/aspeed_ast27x0_huygens.c b/hw/arm/aspeed_ast27x0_huygens.c
new file mode 100644
index 0000000000..424962c536
--- /dev/null
+++ b/hw/arm/aspeed_ast27x0_huygens.c
@@ -0,0 +1,241 @@
+/*
+ * IBM Huygens BMC
+ *
+ * Copyright 2026 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/arm/machines-qom.h"
+#include "hw/arm/aspeed.h"
+#include "hw/arm/aspeed_soc.h"
+#include "hw/nvram/eeprom_at24c.h"
+#include "hw/i2c/i2c_mux_pca954x.h"
+#include "hw/sensor/tmp105.h"
+
+/* SCU HW Strap1 */
+#define HUYGENS_BMC_HW_STRAP1 0x00000800
+/* SCUIO HW Strap1 */
+#define HUYGENS_BMC_HW_STRAP2 0x00000700
+
+/*
+ * 2048-byte IPZ VPD image:
+ * VHDR, VTOC, VINI, VMPU, VSBP, VSYS, UTIL, DINF, VCEN + ECC
+ */
+static const uint8_t huygens_bmc_fruid[] = {
+    0x00, 0x0f, 0x17, 0xba, 0x3a, 0xc9, 0x32, 0x31, 0x49, 0xb2, 0xde, 0x84,
+    0x28, 0x00, 0x52, 0x54, 0x04, 0x56, 0x48, 0x44, 0x52, 0x56, 0x44, 0x02,
+    0x30, 0x31, 0x50, 0x54, 0x0e, 0x56, 0x54, 0x4f, 0x43, 0xd5, 0x00, 0x37,
+    0x00, 0x74, 0x00, 0xa6, 0x05, 0x1d, 0x00, 0x50, 0x46, 0x08, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84, 0x70, 0x00, 0x52, 0x54,
+    0x04, 0x56, 0x54, 0x4f, 0x43, 0x50, 0x54, 0x62, 0x56, 0x49, 0x4e, 0x49,
+    0xd5, 0x00, 0xab, 0x00, 0x84, 0x00, 0x85, 0x05, 0x21, 0x00, 0x56, 0x4d,
+    0x50, 0x55, 0xff, 0x00, 0x2f, 0x01, 0xa4, 0x00, 0x5c, 0x05, 0x29, 0x00,
+    0x56, 0x53, 0x42, 0x50, 0xff, 0x00, 0xd3, 0x01, 0x30, 0x00, 0x50, 0x05,
+    0x0c, 0x00, 0x56, 0x53, 0x59, 0x53, 0xff, 0x00, 0x03, 0x02, 0x44, 0x01,
+    0xff, 0x04, 0x51, 0x00, 0x55, 0x54, 0x49, 0x4c, 0xff, 0x00, 0x47, 0x03,
+    0xe8, 0x00, 0xc5, 0x04, 0x3a, 0x00, 0x44, 0x49, 0x4e, 0x46, 0xff, 0x00,
+    0x2f, 0x04, 0x30, 0x00, 0xb9, 0x04, 0x0c, 0x00, 0x56, 0x43, 0x45, 0x4e,
+    0xff, 0x00, 0x5f, 0x04, 0x48, 0x00, 0xa7, 0x04, 0x12, 0x00, 0x50, 0x46,
+    0x01, 0x00, 0x78, 0x84, 0x80, 0x00, 0x52, 0x54, 0x04, 0x56, 0x49, 0x4e,
+    0x49, 0x44, 0x52, 0x10, 0x51, 0x45, 0x4d, 0x55, 0x20, 0x48, 0x55, 0x59,
+    0x47, 0x45, 0x4e, 0x53, 0x20, 0x42, 0x4d, 0x43, 0x43, 0x45, 0x01, 0x20,
+    0x56, 0x5a, 0x02, 0x20, 0x20, 0x46, 0x4e, 0x07, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x50, 0x4e, 0x07, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x53, 0x4e, 0x0c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x43, 0x43, 0x04, 0x20, 0x20, 0x20, 0x20, 0x48,
+    0x45, 0x04, 0x20, 0x20, 0x20, 0x20, 0x43, 0x54, 0x04, 0x00, 0x00, 0x00,
+    0x00, 0x48, 0x57, 0x02, 0x00, 0x01, 0x42, 0x33, 0x06, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x42, 0x34, 0x01, 0x20, 0x42, 0x37, 0x0c, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x46,
+    0x01, 0x00, 0x78, 0x84, 0xa0, 0x00, 0x52, 0x54, 0x04, 0x56, 0x4d, 0x50,
+    0x55, 0x56, 0x5a, 0x02, 0x30, 0x31, 0x53, 0x4f, 0x02, 0x00, 0x00, 0x44,
+    0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4e, 0x80, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02, 0x00, 0x00, 0x78, 0x84,
+    0x2c, 0x00, 0x52, 0x54, 0x04, 0x56, 0x53, 0x42, 0x50, 0x44, 0x52, 0x10,
+    0x56, 0x50, 0x44, 0x20, 0x52, 0x4f, 0x4f, 0x54, 0x20, 0x4e, 0x4f, 0x44,
+    0x45, 0x20, 0x20, 0x20, 0x50, 0x41, 0x01, 0x59, 0x49, 0x4d, 0x04, 0x70,
+    0x00, 0x10, 0x00, 0x50, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84,
+    0x40, 0x01, 0x52, 0x54, 0x04, 0x56, 0x53, 0x59, 0x53, 0x44, 0x52, 0x06,
+    0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x42, 0x52, 0x02, 0x20, 0x20, 0x53,
+    0x45, 0x07, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x47, 0x07,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x4d, 0x08, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x4e, 0x08, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x4e, 0x07, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x49, 0x44, 0x02, 0x20, 0x20, 0x53, 0x55, 0x06, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x4e, 0x10, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x52, 0x47, 0x04, 0x20, 0x20, 0x20, 0x20, 0x52, 0x42, 0x04, 0x20, 0x20,
+    0x20, 0x20, 0x57, 0x4e, 0x0c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x56, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x41, 0x41, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x41, 0x42, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x41, 0x4b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x41, 0x54, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x42, 0x4e, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x42, 0x41, 0x01, 0x20, 0x4d,
+    0x4d, 0x01, 0x20, 0x50, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84,
+    0xe4, 0x00, 0x52, 0x54, 0x04, 0x55, 0x54, 0x49, 0x4c, 0x44, 0x30, 0x01,
+    0x00, 0x44, 0x31, 0x01, 0x00, 0x44, 0x32, 0x01, 0x00, 0x44, 0x33, 0x01,
+    0x00, 0x44, 0x34, 0x04, 0x00, 0x00, 0x00, 0x00, 0x44, 0x35, 0x04, 0x00,
+    0x00, 0x00, 0x00, 0x44, 0x36, 0x04, 0x00, 0x00, 0x00, 0x00, 0x44, 0x37,
+    0x04, 0x00, 0x00, 0x00, 0x00, 0x44, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x44, 0x39, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x46, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x46, 0x31, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x46, 0x32, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x46, 0x33, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
+    0x34, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x35,
+    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x36, 0x10, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x46, 0x37, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x38, 0x10, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x46, 0x39, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02,
+    0x00, 0x00, 0x78, 0x84, 0x2c, 0x00, 0x52, 0x54, 0x04, 0x44, 0x49, 0x4e,
+    0x46, 0x52, 0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x46, 0x4c, 0x14, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x46, 0x04, 0x00, 0x00,
+    0x00, 0x00, 0x78, 0x84, 0x44, 0x00, 0x52, 0x54, 0x04, 0x56, 0x43, 0x45,
+    0x4e, 0x44, 0x52, 0x06, 0x43, 0x45, 0x43, 0x20, 0x20, 0x20, 0x53, 0x45,
+    0x07, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x4d, 0x08, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x43, 0x08, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x52, 0x47, 0x04, 0x20, 0x20, 0x20,
+    0x20, 0x52, 0x42, 0x04, 0x20, 0x20, 0x20, 0x20, 0x50, 0x46, 0x03, 0x00,
+    /* ECC area */
+    0x00, 0x00, 0x78, 0x4d, 0x5d, 0x84, 0x8c, 0x0a, 0xf9, 0xed, 0x3d, 0x96,
+    0x11, 0x4a, 0x45, 0x9a, 0xb6, 0xb2, 0xd9, 0x19, 0x43, 0x05, 0x0f, 0xdd,
+    0xb5, 0xf9, 0x3d, 0x5b, 0x81, 0xb3, 0x79, 0xcb, 0x19, 0x48, 0xbb, 0xa2,
+    0x57, 0x10, 0x6e, 0x39, 0x38, 0x85, 0x5e, 0xa3, 0x24, 0xbc, 0x02, 0x63,
+    0x6b, 0x79, 0xa3, 0x6b, 0xed, 0xcd, 0x27, 0x4e, 0x79, 0x9f, 0x76, 0x4e,
+    0x7b, 0x93, 0x2d, 0xf4, 0x4c, 0x51, 0x0e, 0x28, 0x46, 0x43, 0x2b, 0xd9,
+    0x1b, 0x21, 0x21, 0xc8, 0xe4, 0x6e, 0x9b, 0x4e, 0x34, 0x7e, 0x6e, 0x82,
+    0x1b, 0x09, 0x13, 0xf1, 0x40, 0x7b, 0x9f, 0x6d, 0x50, 0x3e, 0x65, 0x31,
+    0x1f, 0x19, 0x6f, 0x61, 0x78, 0x27, 0x72, 0x7c, 0x59, 0x34, 0x7c, 0x05,
+    0x58, 0x13, 0x6f, 0x78, 0x13, 0x56, 0x33, 0x19, 0x1e, 0x79, 0x17, 0x41,
+    0x28, 0x58, 0x6d, 0x26, 0x01, 0x34, 0x7d, 0x68, 0x1d, 0x7e, 0x1c, 0x42,
+    0x49, 0x2a, 0x11, 0x7c, 0x70, 0x1a, 0x66, 0x47, 0x68, 0x6e, 0x3f, 0x5e,
+    0x6b, 0x6d, 0x25, 0x7e, 0x3e, 0x24, 0x39, 0x07, 0x3c, 0x6e, 0x5b, 0x5d,
+    0x4e, 0x59, 0x79, 0x7d, 0x6f, 0x5f, 0x7f, 0x5f, 0x4b, 0x5f, 0x6d, 0x3e,
+    0x58, 0x57, 0x1b, 0x19, 0x5d, 0x14, 0x43, 0x83, 0xfc, 0xe6, 0x49, 0x84,
+    0x41, 0x28, 0x0b, 0x0a, 0x00, 0x5b, 0xec, 0x46, 0x13, 0x4d, 0x00, 0xdc,
+    0x89, 0xab, 0xbb, 0x77, 0x77, 0x7e, 0xee, 0xff, 0xfd, 0xdf, 0xff, 0xbb,
+    0xbf, 0xff, 0x77, 0x7f, 0xfe, 0xee, 0xff, 0xfd, 0xdf, 0xff, 0xbb, 0xbf,
+    0xfe, 0x76, 0x7e, 0xca, 0xc8, 0x99, 0x51, 0xd7, 0x70, 0x00, 0x03, 0x26,
+    0x9e, 0x4e, 0x7f, 0x13, 0x38, 0xbb, 0x3c, 0xbb, 0x00, 0x02, 0xa0, 0x0a,
+    0xcb, 0x70, 0xb7, 0x9f, 0x2b, 0x79, 0xa4, 0x00, 0x07, 0x71, 0xad, 0x17,
+    0x41, 0xd7, 0xb9, 0x8c, 0x6d, 0x08, 0x2c, 0x80, 0x04, 0x56, 0x6b, 0x9f,
+    0x00, 0x9f, 0xbe, 0xdb, 0x71, 0x63, 0x01, 0xb8, 0xf0, 0x41, 0xc0, 0xe1,
+    0x8d, 0x3a, 0x6f, 0xc0, 0x66, 0x5f, 0xb6, 0x4c, 0x2a, 0x5f, 0x06, 0x00,
+    /* Rest is 0x00 */
+};
+static const size_t huygens_bmc_fruid_len = sizeof(huygens_bmc_fruid);
+
+/* chassis1 VPD: huygens_bmc_fruid with VCEN.FC patched to "2E4C-001" */
+#define HUYGENS_CHASSIS1_FC_OFFSET 0x048a
+static const uint8_t huygens_chassis1_fc[] = {
+    0x32, 0x45, 0x34, 0x43, 0x2d, 0x30, 0x30, 0x31
+};
+
+static void huygens_bmc_i2c_init(AspeedMachineState *bmc)
+{
+    AspeedSoCState *soc = bmc->soc;
+
+    /* I2C0: BMC EEPROM */
+    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50, 8 * KiB);
+
+    /* I2C5: UCD90320 power sequencer */
+    i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5), "ucd90320", 0x11);
+
+    /* I2C6: chassis1 backplane VPD EEPROM */
+    {
+        g_autofree uint8_t *ch1 = g_malloc(huygens_bmc_fruid_len);
+        memcpy(ch1, huygens_bmc_fruid, huygens_bmc_fruid_len);
+        memcpy(ch1 + HUYGENS_CHASSIS1_FC_OFFSET,
+               huygens_chassis1_fc, sizeof(huygens_chassis1_fc));
+        at24c_eeprom_init_rom(aspeed_i2c_get_bus(&soc->i2c, 6), 0x50,
+                              8 * KiB, ch1, huygens_bmc_fruid_len);
+    }
+
+    /* I2C8: System VPD at 0x53 */
+    at24c_eeprom_init_rom(aspeed_i2c_get_bus(&soc->i2c, 8), 0x53,
+                          4 * KiB, huygens_bmc_fruid, huygens_bmc_fruid_len);
+    /* LCD EEPROM */
+    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 8), 0x51, 8 * KiB);
+
+    /* I2C9: System VPD redundant and op-panel devices */
+    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 9),
+                      0x53, 8 * KiB);  /* SYSVPD-redundant */
+
+    /* TMP275 temperature sensor (compatible with TMP105) */
+    i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 9),
+                            TYPE_TMP105, 0x48);
+
+    /* Op-panel EEPROM */
+    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 9), 0x51, 8 * KiB);
+
+    /* PCA9552 LED controllers on bus 9 */
+    aspeed_create_pca9552(soc, 9, 0x62);
+    aspeed_create_pca9552(soc, 9, 0x64);
+    aspeed_create_pca9552(soc, 9, 0x66);
+
+    /* DPS310 pressure sensor */
+    i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 9), "dps310", 0x76);
+}
+
+static void aspeed_machine_huygens_class_init(ObjectClass *oc,
+                                              const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+    AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc);
+
+    mc->alias      = "ast2700-huygens";
+    mc->desc       = "Aspeed AST2700 A2 [IBM Huygens]";
+    amc->soc_name  = "ast2700-a2";
+    amc->hw_strap1 = HUYGENS_BMC_HW_STRAP1;
+    amc->hw_strap2 = HUYGENS_BMC_HW_STRAP2;
+    amc->fmc_model = "w25q01jvq";
+    amc->spi_model = "w25q512jv";
+    amc->num_cs    = 2;
+    amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON;
+    amc->uart_default = ASPEED_DEV_UART12;
+    amc->i2c_init  = huygens_bmc_i2c_init;
+    amc->vbootrom  = true;
+    /* UFS LUN block size to match the OpenBMC image layout */
+    amc->ufs_block_size = 512;
+    mc->default_ram_size = 2 * GiB;
+    aspeed_machine_class_init_cpus_defaults(mc);
+}
+
+static const TypeInfo aspeed_ast27x0_huygens_types[] = {
+    {
+        .name       = MACHINE_TYPE_NAME("huygens-bmc"),
+        .parent     = TYPE_ASPEED_MACHINE,
+        .class_init = aspeed_machine_huygens_class_init,
+        .interfaces = aarch64_machine_interfaces,
+    }
+};
+
+DEFINE_TYPES(aspeed_ast27x0_huygens_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 4233a800be..a237f1d04f 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -75,6 +75,7 @@ arm_common_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
   'aspeed_ast27x0-fc.c',
   'aspeed_ast27x0-ssp.c',
   'aspeed_ast27x0-tsp.c',
+  'aspeed_ast27x0_huygens.c',
   'aspeed_coprocessor_common.c'))
 arm_common_ss.add(when: 'CONFIG_MPS2', if_true: files('mps2.c'))
 arm_common_ss.add(when: 'CONFIG_MPS2', if_true: files('mps2-tz.c'))
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads
  2026-08-10 18:57 ` [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads Mikail Sadic
@ 2026-08-11  4:30   ` Cédric Le Goater
  2026-08-11  7:33     ` Jamin Lin
  0 siblings, 1 reply; 14+ messages in thread
From: Cédric Le Goater @ 2026-08-11  4:30 UTC (permalink / raw)
  To: Mikail Sadic, peter.maydell
  Cc: pbonzini, ninad, titusr, jeuk20.kim, philmd, steven_lee, leetroy,
	jamin_lin, kane_chen, andrew, joel, calebs, milesg, qemu-arm,
	qemu-devel

Jamin,

I would appreciate a feedback from you on this change.

On 8/10/26 20:57, Mikail Sadic wrote:
> An SMBus block read (I2C_M_RECV_LEN) reads the block length from the
> first received byte. The Linux/U-Boot aspeed I2C driver obtains that
> first byte from the I2CC_STS_AND_BUFF register (modelled here as
> reg_byte_buf), even when the transfer uses DMA. The DMA receive path,
> however, only wrote received data to DRAM and never updated
> reg_byte_buf, so block reads read a stale/zero length.
> 
> Mirror the first DMA-received byte into reg_byte_buf so that
> I2C_M_RECV_LEN transfers using DMA report the correct block length.
> This is required for the ucd9000 driver, which uses
> i2c_smbus_read_block_data().
> 
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>

Does this change deserve a Fixes: tag ?

Thanks,

C.

> ---
>   hw/i2c/aspeed_i2c.c | 7 +++++++
>   1 file changed, 7 insertions(+)
> 
> diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
> index 27afcaecee..facb54d27e 100644
> --- a/hw/i2c/aspeed_i2c.c
> +++ b/hw/i2c/aspeed_i2c.c
> @@ -365,6 +365,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
>       uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
>       uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
>       uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
> +    bool first_dma_byte;
>       int pool_rx_count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
>                                                   RX_SIZE) + 1;
>   
> @@ -391,6 +392,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
>           }
>   
>           aspeed_i2c_set_rx_dma_dram_offset(bus);
> +        first_dma_byte = true;
>           while (bus->regs[reg_dma_len]) {
>               MemTxResult result;
>   
> @@ -407,6 +409,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
>                   return;
>               }
>   
> +            /* Mirror first byte to reg_byte_buf for I2C_M_RECV_LEN. */
> +            if (first_dma_byte) {
> +                SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data);
> +                first_dma_byte = false;
> +            }
>               bus->dma_dram_offset++;
>               bus->regs[reg_dma_len]--;
>               /* In new mode, keep track of how many bytes we RXed */



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 3/8] arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges
  2026-08-10 18:57 ` [PATCH v3 3/8] arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges Mikail Sadic
@ 2026-08-11  4:32   ` Cédric Le Goater
  0 siblings, 0 replies; 14+ messages in thread
From: Cédric Le Goater @ 2026-08-11  4:32 UTC (permalink / raw)
  To: Mikail Sadic, peter.maydell
  Cc: pbonzini, ninad, titusr, jeuk20.kim, philmd, steven_lee, leetroy,
	jamin_lin, kane_chen, andrew, joel, calebs, milesg, qemu-arm,
	qemu-devel

On 8/10/26 20:57, Mikail Sadic wrote:
> The AST2700 SoC exposes two FSI controllers behind APB-to-OPB bridges.
> Add the ASPEED_DEV_FSI1/FSI2 memory-map entries, instantiate both fsi[]
> APB-to-OPB bridges and map them during SoC realize, so the CFAM/CFAM-S
> devices on the FSI local buses become reachable from the SoC.
> 
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
>   hw/arm/aspeed_ast27x0.c | 18 ++++++++++++++++++
>   1 file changed, 18 insertions(+)
> 
> diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
> index dddd7d2106..b908d7d4ff 100644
> --- a/hw/arm/aspeed_ast27x0.c
> +++ b/hw/arm/aspeed_ast27x0.c
> @@ -17,6 +17,7 @@
>   #include "qemu/module.h"
>   #include "qemu/error-report.h"
>   #include "hw/i2c/aspeed_i2c.h"
> +#include "hw/fsi/aspeed_apb2opb.h"
>   #include "net/net.h"
>   #include "system/qtest.h"
>   #include "system/system.h"
> @@ -98,6 +99,8 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
>       [ASPEED_DEV_PCIE_MMIO0] = 0x60000000,
>       [ASPEED_DEV_PCIE_MMIO1] = 0x80000000,
>       [ASPEED_DEV_PCIE_MMIO2] = 0xA0000000,
> +    [ASPEED_DEV_FSI1]      =  0x21800000,
> +    [ASPEED_DEV_FSI2]      =  0x23800000,
>       [ASPEED_DEV_SPI_BOOT]  =  0x100000000,
>       [ASPEED_DEV_SDRAM]     =  0x400000000,
>   };
> @@ -559,6 +562,11 @@ static void aspeed_soc_ast2700_init(Object *obj)
>                                sc->silicon_rev);
>       }
>   
> +    for (i = 0; i < ARRAY_SIZE(s->fsi); i++) {
> +        object_initialize_child(obj, "fsi[*]", &s->fsi[i],
> +                                TYPE_ASPEED_APB2OPB);
> +    }
> +
>       object_initialize_child(obj, "dpmcu", &s->dpmcu,
>                               TYPE_UNIMPLEMENTED_DEVICE);
>       object_initialize_child(obj, "iomem", &s->iomem,
> @@ -1132,6 +1140,16 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
>           }
>       }
>   
> +    /* FSI / OPB */
> +    for (i = 0; i < ARRAY_SIZE(s->fsi); i++) {
> +        if (!sysbus_realize(SYS_BUS_DEVICE(&s->fsi[i]), errp)) {
> +            return;
> +        }
> +        aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->fsi[i]), 0,
> +                        sc->memmap[ASPEED_DEV_FSI1 + i]);
> +    }
> +
> +
>       aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->dpmcu),
>                                     "aspeed.dpmcu",
>                                     sc->memmap[ASPEED_DEV_DPMCU],

Reviewed-by: Cédric Le Goater <clg@redhat.com>

Thanks,

C.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 8/8] arm/aspeed: Add AST2700 Huygens machine
  2026-08-10 18:57 ` [PATCH v3 8/8] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
@ 2026-08-11  4:35   ` Cédric Le Goater
  0 siblings, 0 replies; 14+ messages in thread
From: Cédric Le Goater @ 2026-08-11  4:35 UTC (permalink / raw)
  To: Mikail Sadic, peter.maydell
  Cc: pbonzini, ninad, titusr, jeuk20.kim, philmd, steven_lee, leetroy,
	jamin_lin, kane_chen, andrew, joel, calebs, milesg, qemu-arm,
	qemu-devel

On 8/10/26 20:57, Mikail Sadic wrote:
> Huygens is the IBM POWER12 server platform. The BMC is built around the
> Aspeed AST2700 A2 SoC and handles power sequencing, thermal management,
> VPD access and host firmware boot for the POWER host processors.
> 
> Add a 'huygens-bmc' machine that instantiates the AST2700 SoC with the
> board-specific configuration needed to boot the OpenBMC Linux image:
> the FMC/SPI flash models, the I2C topology (BMC, system and chassis VPD
> EEPROMs, the UCD90320 power sequencer and temperature sensors) and the
> network controllers.
> 
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
>   docs/system/arm/aspeed.rst      |  50 ++++++-
>   hw/arm/aspeed_ast27x0_huygens.c | 241 ++++++++++++++++++++++++++++++++
>   hw/arm/meson.build              |   1 +
>   3 files changed, 290 insertions(+), 2 deletions(-)
>   create mode 100644 hw/arm/aspeed_ast27x0_huygens.c
> 
> diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
> index 2d51ceeb84..4e42b1edee 100644
> --- a/docs/system/arm/aspeed.rst
> +++ b/docs/system/arm/aspeed.rst
> @@ -271,8 +271,8 @@ configuration file for OTP memory:
>       done > otpmem.img
>     fi
>   
> -Aspeed 2700 family boards (``ast2700-evb``, ``ast2700fc``)
> -==========================================================
> +Aspeed 2700 family boards (``ast2700-evb``, ``ast2700fc``, ``huygens-bmc``)
> +============================================================================
>   
>   The QEMU Aspeed machines model BMCs of Aspeed evaluation boards.
>   They are based on different releases of the Aspeed SoC :
> @@ -285,6 +285,7 @@ AST2700 SoC based machines :
>   
>   - ``ast2700-evb``          Aspeed AST2700 Evaluation board (Cortex-A35)
>   - ``ast2700fc``            Aspeed AST2700 Evaluation board (Cortex-A35 + Cortex-M4)
> +- ``huygens-bmc``          Aspeed AST2700 IBM Huygens POWER12 BMC
>   
>   Supported devices
>   -----------------
> @@ -312,6 +313,8 @@ Supported devices
>    * PECI Controller (minimal)
>    * I3C Controller
>    * Internal Bridge Controller (SLI dummy)
> + * UFS Host Controller (aspeed,ufshc-m31-16nm) - ``huygens-bmc`` only
> + * FSI APB-to-OPB bridge with CFAM-S mailbox
>   
>   Missing devices
>   ---------------
> @@ -440,6 +443,49 @@ Use ``tio`` or another terminal emulator to connect to the consoles:
>      $ tio /dev/pts/56
>      $ tio /dev/pts/57
>   
> +Booting the huygens-bmc machine
> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> +
> +The IBM Huygens BMC is based on the AST2700 A2 SoC. Its storage is
> +split across two images:
> +
> +* the FMC (SPI NOR) flash image (``image-bmc``) holds U-Boot, the Linux
> +  kernel and the FIT image;
> +* the UFS image (``ufs.img``) holds the root filesystem.
> +
> +Both images can be built from the OpenBMC project tree.
> +
> +To boot the machine, attach the flash image to the FMC device and the
> +root filesystem image to a UFS logical unit. ``-nodefaults`` is used so the
> +board does not also create a default flash at chip-select 0, and the console
> +is wired explicitly since ``-nodefaults`` disables the implicit one:
> +
> +.. code-block:: bash
> +
> +  $ qemu-system-aarch64 -M huygens-bmc \
> +        -nodefaults \
> +        -blockdev node-name=fmc0,driver=file,filename=image-bmc \
> +        -device w25q01jvq,bus=ssi.0,cs=0,drive=fmc0 \
> +        -blockdev node-name=ufs0,driver=file,filename=ufs.img \
> +        -device ufs-lu,bus=ufs-bus.0,drive=ufs0,lun=0,logical-block-size=512 \
> +        -display none -serial mon:stdio
> +
> +The FMC flash sits on chip-select 0 of the ``ssi.0`` bus and the UFS logical
> +unit on ``ufs-bus.0``. The Huygens image is laid out for 512-byte sectors,
> +hence ``logical-block-size=512``.
> +
> +The machine instantiates three ``ftgmac100`` Ethernet controllers, which
> +are wired up by the board. To expose them with user networking, add three
> +``-nic user`` options to the command above:
> +
> +.. code-block:: bash
> +
> +        -nic user \
> +        -nic user \
> +        -nic user
> +
> +The default BMC console is ``uart12``.
> +
>   
>   Aspeed Bridge IC and Platform Root of Trust processor family boards (``ast1030-evb``, ``ast1040-evb``, ``ast1060-evb``)
>   =======================================================================================================================
> diff --git a/hw/arm/aspeed_ast27x0_huygens.c b/hw/arm/aspeed_ast27x0_huygens.c
> new file mode 100644
> index 0000000000..424962c536
> --- /dev/null
> +++ b/hw/arm/aspeed_ast27x0_huygens.c
> @@ -0,0 +1,241 @@
> +/*
> + * IBM Huygens BMC
> + *
> + * Copyright 2026 IBM Corp.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "hw/arm/machines-qom.h"
> +#include "hw/arm/aspeed.h"
> +#include "hw/arm/aspeed_soc.h"
> +#include "hw/nvram/eeprom_at24c.h"
> +#include "hw/i2c/i2c_mux_pca954x.h"
> +#include "hw/sensor/tmp105.h"
> +
> +/* SCU HW Strap1 */
> +#define HUYGENS_BMC_HW_STRAP1 0x00000800
> +/* SCUIO HW Strap1 */
> +#define HUYGENS_BMC_HW_STRAP2 0x00000700
> +
> +/*
> + * 2048-byte IPZ VPD image:
> + * VHDR, VTOC, VINI, VMPU, VSBP, VSYS, UTIL, DINF, VCEN + ECC
> + */
> +static const uint8_t huygens_bmc_fruid[] = {
> +    0x00, 0x0f, 0x17, 0xba, 0x3a, 0xc9, 0x32, 0x31, 0x49, 0xb2, 0xde, 0x84,
> +    0x28, 0x00, 0x52, 0x54, 0x04, 0x56, 0x48, 0x44, 0x52, 0x56, 0x44, 0x02,
> +    0x30, 0x31, 0x50, 0x54, 0x0e, 0x56, 0x54, 0x4f, 0x43, 0xd5, 0x00, 0x37,
> +    0x00, 0x74, 0x00, 0xa6, 0x05, 0x1d, 0x00, 0x50, 0x46, 0x08, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84, 0x70, 0x00, 0x52, 0x54,
> +    0x04, 0x56, 0x54, 0x4f, 0x43, 0x50, 0x54, 0x62, 0x56, 0x49, 0x4e, 0x49,
> +    0xd5, 0x00, 0xab, 0x00, 0x84, 0x00, 0x85, 0x05, 0x21, 0x00, 0x56, 0x4d,
> +    0x50, 0x55, 0xff, 0x00, 0x2f, 0x01, 0xa4, 0x00, 0x5c, 0x05, 0x29, 0x00,
> +    0x56, 0x53, 0x42, 0x50, 0xff, 0x00, 0xd3, 0x01, 0x30, 0x00, 0x50, 0x05,
> +    0x0c, 0x00, 0x56, 0x53, 0x59, 0x53, 0xff, 0x00, 0x03, 0x02, 0x44, 0x01,
> +    0xff, 0x04, 0x51, 0x00, 0x55, 0x54, 0x49, 0x4c, 0xff, 0x00, 0x47, 0x03,
> +    0xe8, 0x00, 0xc5, 0x04, 0x3a, 0x00, 0x44, 0x49, 0x4e, 0x46, 0xff, 0x00,
> +    0x2f, 0x04, 0x30, 0x00, 0xb9, 0x04, 0x0c, 0x00, 0x56, 0x43, 0x45, 0x4e,
> +    0xff, 0x00, 0x5f, 0x04, 0x48, 0x00, 0xa7, 0x04, 0x12, 0x00, 0x50, 0x46,
> +    0x01, 0x00, 0x78, 0x84, 0x80, 0x00, 0x52, 0x54, 0x04, 0x56, 0x49, 0x4e,
> +    0x49, 0x44, 0x52, 0x10, 0x51, 0x45, 0x4d, 0x55, 0x20, 0x48, 0x55, 0x59,
> +    0x47, 0x45, 0x4e, 0x53, 0x20, 0x42, 0x4d, 0x43, 0x43, 0x45, 0x01, 0x20,
> +    0x56, 0x5a, 0x02, 0x20, 0x20, 0x46, 0x4e, 0x07, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x50, 0x4e, 0x07, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x53, 0x4e, 0x0c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x43, 0x43, 0x04, 0x20, 0x20, 0x20, 0x20, 0x48,
> +    0x45, 0x04, 0x20, 0x20, 0x20, 0x20, 0x43, 0x54, 0x04, 0x00, 0x00, 0x00,
> +    0x00, 0x48, 0x57, 0x02, 0x00, 0x01, 0x42, 0x33, 0x06, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x42, 0x34, 0x01, 0x20, 0x42, 0x37, 0x0c, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x46,
> +    0x01, 0x00, 0x78, 0x84, 0xa0, 0x00, 0x52, 0x54, 0x04, 0x56, 0x4d, 0x50,
> +    0x55, 0x56, 0x5a, 0x02, 0x30, 0x31, 0x53, 0x4f, 0x02, 0x00, 0x00, 0x44,
> +    0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4e, 0x80, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02, 0x00, 0x00, 0x78, 0x84,
> +    0x2c, 0x00, 0x52, 0x54, 0x04, 0x56, 0x53, 0x42, 0x50, 0x44, 0x52, 0x10,
> +    0x56, 0x50, 0x44, 0x20, 0x52, 0x4f, 0x4f, 0x54, 0x20, 0x4e, 0x4f, 0x44,
> +    0x45, 0x20, 0x20, 0x20, 0x50, 0x41, 0x01, 0x59, 0x49, 0x4d, 0x04, 0x70,
> +    0x00, 0x10, 0x00, 0x50, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84,
> +    0x40, 0x01, 0x52, 0x54, 0x04, 0x56, 0x53, 0x59, 0x53, 0x44, 0x52, 0x06,
> +    0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x42, 0x52, 0x02, 0x20, 0x20, 0x53,
> +    0x45, 0x07, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x47, 0x07,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x4d, 0x08, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x4e, 0x08, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x4e, 0x07, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x49, 0x44, 0x02, 0x20, 0x20, 0x53, 0x55, 0x06, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x4e, 0x10, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x52, 0x47, 0x04, 0x20, 0x20, 0x20, 0x20, 0x52, 0x42, 0x04, 0x20, 0x20,
> +    0x20, 0x20, 0x57, 0x4e, 0x0c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x56, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x41, 0x41, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x41, 0x42, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x41, 0x4b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x41, 0x54, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x42, 0x4e, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x42, 0x41, 0x01, 0x20, 0x4d,
> +    0x4d, 0x01, 0x20, 0x50, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84,
> +    0xe4, 0x00, 0x52, 0x54, 0x04, 0x55, 0x54, 0x49, 0x4c, 0x44, 0x30, 0x01,
> +    0x00, 0x44, 0x31, 0x01, 0x00, 0x44, 0x32, 0x01, 0x00, 0x44, 0x33, 0x01,
> +    0x00, 0x44, 0x34, 0x04, 0x00, 0x00, 0x00, 0x00, 0x44, 0x35, 0x04, 0x00,
> +    0x00, 0x00, 0x00, 0x44, 0x36, 0x04, 0x00, 0x00, 0x00, 0x00, 0x44, 0x37,
> +    0x04, 0x00, 0x00, 0x00, 0x00, 0x44, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x44, 0x39, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x46, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x46, 0x31, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x46, 0x32, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x46, 0x33, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
> +    0x34, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x35,
> +    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x36, 0x10, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x46, 0x37, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x38, 0x10, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x46, 0x39, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02,
> +    0x00, 0x00, 0x78, 0x84, 0x2c, 0x00, 0x52, 0x54, 0x04, 0x44, 0x49, 0x4e,
> +    0x46, 0x52, 0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x46, 0x4c, 0x14, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x46, 0x04, 0x00, 0x00,
> +    0x00, 0x00, 0x78, 0x84, 0x44, 0x00, 0x52, 0x54, 0x04, 0x56, 0x43, 0x45,
> +    0x4e, 0x44, 0x52, 0x06, 0x43, 0x45, 0x43, 0x20, 0x20, 0x20, 0x53, 0x45,
> +    0x07, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x4d, 0x08, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x43, 0x08, 0x20, 0x20,
> +    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x52, 0x47, 0x04, 0x20, 0x20, 0x20,
> +    0x20, 0x52, 0x42, 0x04, 0x20, 0x20, 0x20, 0x20, 0x50, 0x46, 0x03, 0x00,
> +    /* ECC area */
> +    0x00, 0x00, 0x78, 0x4d, 0x5d, 0x84, 0x8c, 0x0a, 0xf9, 0xed, 0x3d, 0x96,
> +    0x11, 0x4a, 0x45, 0x9a, 0xb6, 0xb2, 0xd9, 0x19, 0x43, 0x05, 0x0f, 0xdd,
> +    0xb5, 0xf9, 0x3d, 0x5b, 0x81, 0xb3, 0x79, 0xcb, 0x19, 0x48, 0xbb, 0xa2,
> +    0x57, 0x10, 0x6e, 0x39, 0x38, 0x85, 0x5e, 0xa3, 0x24, 0xbc, 0x02, 0x63,
> +    0x6b, 0x79, 0xa3, 0x6b, 0xed, 0xcd, 0x27, 0x4e, 0x79, 0x9f, 0x76, 0x4e,
> +    0x7b, 0x93, 0x2d, 0xf4, 0x4c, 0x51, 0x0e, 0x28, 0x46, 0x43, 0x2b, 0xd9,
> +    0x1b, 0x21, 0x21, 0xc8, 0xe4, 0x6e, 0x9b, 0x4e, 0x34, 0x7e, 0x6e, 0x82,
> +    0x1b, 0x09, 0x13, 0xf1, 0x40, 0x7b, 0x9f, 0x6d, 0x50, 0x3e, 0x65, 0x31,
> +    0x1f, 0x19, 0x6f, 0x61, 0x78, 0x27, 0x72, 0x7c, 0x59, 0x34, 0x7c, 0x05,
> +    0x58, 0x13, 0x6f, 0x78, 0x13, 0x56, 0x33, 0x19, 0x1e, 0x79, 0x17, 0x41,
> +    0x28, 0x58, 0x6d, 0x26, 0x01, 0x34, 0x7d, 0x68, 0x1d, 0x7e, 0x1c, 0x42,
> +    0x49, 0x2a, 0x11, 0x7c, 0x70, 0x1a, 0x66, 0x47, 0x68, 0x6e, 0x3f, 0x5e,
> +    0x6b, 0x6d, 0x25, 0x7e, 0x3e, 0x24, 0x39, 0x07, 0x3c, 0x6e, 0x5b, 0x5d,
> +    0x4e, 0x59, 0x79, 0x7d, 0x6f, 0x5f, 0x7f, 0x5f, 0x4b, 0x5f, 0x6d, 0x3e,
> +    0x58, 0x57, 0x1b, 0x19, 0x5d, 0x14, 0x43, 0x83, 0xfc, 0xe6, 0x49, 0x84,
> +    0x41, 0x28, 0x0b, 0x0a, 0x00, 0x5b, 0xec, 0x46, 0x13, 0x4d, 0x00, 0xdc,
> +    0x89, 0xab, 0xbb, 0x77, 0x77, 0x7e, 0xee, 0xff, 0xfd, 0xdf, 0xff, 0xbb,
> +    0xbf, 0xff, 0x77, 0x7f, 0xfe, 0xee, 0xff, 0xfd, 0xdf, 0xff, 0xbb, 0xbf,
> +    0xfe, 0x76, 0x7e, 0xca, 0xc8, 0x99, 0x51, 0xd7, 0x70, 0x00, 0x03, 0x26,
> +    0x9e, 0x4e, 0x7f, 0x13, 0x38, 0xbb, 0x3c, 0xbb, 0x00, 0x02, 0xa0, 0x0a,
> +    0xcb, 0x70, 0xb7, 0x9f, 0x2b, 0x79, 0xa4, 0x00, 0x07, 0x71, 0xad, 0x17,
> +    0x41, 0xd7, 0xb9, 0x8c, 0x6d, 0x08, 0x2c, 0x80, 0x04, 0x56, 0x6b, 0x9f,
> +    0x00, 0x9f, 0xbe, 0xdb, 0x71, 0x63, 0x01, 0xb8, 0xf0, 0x41, 0xc0, 0xe1,
> +    0x8d, 0x3a, 0x6f, 0xc0, 0x66, 0x5f, 0xb6, 0x4c, 0x2a, 0x5f, 0x06, 0x00,
> +    /* Rest is 0x00 */
> +};
> +static const size_t huygens_bmc_fruid_len = sizeof(huygens_bmc_fruid);
> +
> +/* chassis1 VPD: huygens_bmc_fruid with VCEN.FC patched to "2E4C-001" */
> +#define HUYGENS_CHASSIS1_FC_OFFSET 0x048a
> +static const uint8_t huygens_chassis1_fc[] = {
> +    0x32, 0x45, 0x34, 0x43, 0x2d, 0x30, 0x30, 0x31
> +};
> +
> +static void huygens_bmc_i2c_init(AspeedMachineState *bmc)
> +{
> +    AspeedSoCState *soc = bmc->soc;
> +
> +    /* I2C0: BMC EEPROM */
> +    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50, 8 * KiB);
> +
> +    /* I2C5: UCD90320 power sequencer */
> +    i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5), "ucd90320", 0x11);
> +
> +    /* I2C6: chassis1 backplane VPD EEPROM */
> +    {
> +        g_autofree uint8_t *ch1 = g_malloc(huygens_bmc_fruid_len);
> +        memcpy(ch1, huygens_bmc_fruid, huygens_bmc_fruid_len);
> +        memcpy(ch1 + HUYGENS_CHASSIS1_FC_OFFSET,
> +               huygens_chassis1_fc, sizeof(huygens_chassis1_fc));
> +        at24c_eeprom_init_rom(aspeed_i2c_get_bus(&soc->i2c, 6), 0x50,
> +                              8 * KiB, ch1, huygens_bmc_fruid_len);
> +    }
> +
> +    /* I2C8: System VPD at 0x53 */
> +    at24c_eeprom_init_rom(aspeed_i2c_get_bus(&soc->i2c, 8), 0x53,
> +                          4 * KiB, huygens_bmc_fruid, huygens_bmc_fruid_len);
> +    /* LCD EEPROM */
> +    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 8), 0x51, 8 * KiB);
> +
> +    /* I2C9: System VPD redundant and op-panel devices */
> +    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 9),
> +                      0x53, 8 * KiB);  /* SYSVPD-redundant */
> +
> +    /* TMP275 temperature sensor (compatible with TMP105) */
> +    i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 9),
> +                            TYPE_TMP105, 0x48);
> +
> +    /* Op-panel EEPROM */
> +    at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 9), 0x51, 8 * KiB);
> +
> +    /* PCA9552 LED controllers on bus 9 */
> +    aspeed_create_pca9552(soc, 9, 0x62);
> +    aspeed_create_pca9552(soc, 9, 0x64);
> +    aspeed_create_pca9552(soc, 9, 0x66);
> +
> +    /* DPS310 pressure sensor */
> +    i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 9), "dps310", 0x76);
> +}
> +
> +static void aspeed_machine_huygens_class_init(ObjectClass *oc,
> +                                              const void *data)
> +{
> +    MachineClass *mc = MACHINE_CLASS(oc);
> +    AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc);
> +
> +    mc->alias      = "ast2700-huygens";
> +    mc->desc       = "Aspeed AST2700 A2 [IBM Huygens]";
> +    amc->soc_name  = "ast2700-a2";
> +    amc->hw_strap1 = HUYGENS_BMC_HW_STRAP1;
> +    amc->hw_strap2 = HUYGENS_BMC_HW_STRAP2;
> +    amc->fmc_model = "w25q01jvq";
> +    amc->spi_model = "w25q512jv";
> +    amc->num_cs    = 2;
> +    amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON;
> +    amc->uart_default = ASPEED_DEV_UART12;
> +    amc->i2c_init  = huygens_bmc_i2c_init;
> +    amc->vbootrom  = true;
> +    /* UFS LUN block size to match the OpenBMC image layout */
> +    amc->ufs_block_size = 512;
> +    mc->default_ram_size = 2 * GiB;
> +    aspeed_machine_class_init_cpus_defaults(mc);
> +}
> +
> +static const TypeInfo aspeed_ast27x0_huygens_types[] = {
> +    {
> +        .name       = MACHINE_TYPE_NAME("huygens-bmc"),
> +        .parent     = TYPE_ASPEED_MACHINE,
> +        .class_init = aspeed_machine_huygens_class_init,
> +        .interfaces = aarch64_machine_interfaces,
> +    }
> +};
> +
> +DEFINE_TYPES(aspeed_ast27x0_huygens_types)
> diff --git a/hw/arm/meson.build b/hw/arm/meson.build
> index 4233a800be..a237f1d04f 100644
> --- a/hw/arm/meson.build
> +++ b/hw/arm/meson.build
> @@ -75,6 +75,7 @@ arm_common_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
>     'aspeed_ast27x0-fc.c',
>     'aspeed_ast27x0-ssp.c',
>     'aspeed_ast27x0-tsp.c',
> +  'aspeed_ast27x0_huygens.c',
>     'aspeed_coprocessor_common.c'))
>   arm_common_ss.add(when: 'CONFIG_MPS2', if_true: files('mps2.c'))
>   arm_common_ss.add(when: 'CONFIG_MPS2', if_true: files('mps2-tz.c'))

Do you plan to send a functional test ?

Thanks,

C.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads
  2026-08-11  4:30   ` Cédric Le Goater
@ 2026-08-11  7:33     ` Jamin Lin
  0 siblings, 0 replies; 14+ messages in thread
From: Jamin Lin @ 2026-08-11  7:33 UTC (permalink / raw)
  To: Cédric Le Goater, Mikail Sadic, peter.maydell@linaro.org
  Cc: pbonzini@redhat.com, ninad@linux.ibm.com, titusr@google.com,
	jeuk20.kim@samsung.com, philmd@mailo.com, Steven Lee,
	leetroy@gmail.com, Kane Chen, andrew@codeconstruct.com.au,
	joel@jms.id.au, calebs@linux.ibm.com, milesg@linux.ibm.com,
	qemu-arm@nongnu.org, qemu-devel@nongnu.org

Hi Mikail,

> -----Original Message-----
> From: Cédric Le Goater <clg@kaod.org>
> Sent: Tuesday, August 11, 2026 12:31 PM
> To: Mikail Sadic <mikail.sadic@ibm.com>; peter.maydell@linaro.org
> Cc: pbonzini@redhat.com; ninad@linux.ibm.com; titusr@google.com;
> jeuk20.kim@samsung.com; philmd@mailo.com; Steven Lee
> <steven_lee@aspeedtech.com>; leetroy@gmail.com; Jamin Lin
> <jamin_lin@aspeedtech.com>; Kane Chen <kane_chen@aspeedtech.com>;
> andrew@codeconstruct.com.au; joel@jms.id.au; calebs@linux.ibm.com;
> milesg@linux.ibm.com; qemu-arm@nongnu.org; qemu-devel@nongnu.org
> Subject: Re: [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for
> block reads
> 
> Jamin,
> 
> I would appreciate a feedback from you on this change.
> 
Hi Mikail,

Before going further -- are you sure this value is correct in DMA mode?
AST2600/AST2700 datasheet, I2CC08 (Transmit/Receive Byte Buffer):
    15:8  RO  Receive Byte Buffer
              "This register is valid when DMA Buffer is not enabled."

That reads to me as: the receive byte buffer only holds a defined value
in pool buffer mode, not when DMA is enabled. If that is right, then
modelling it in the DMA path makes QEMU report a block length the
silicon would not provide, and a driver relying on it would pass under
emulation but fail on a real board.

Have you confirmed this works on real hardware, or only in QEMU?
If the datasheet reading is correct, I think the mirror belongs in the
pool buffer path instead -- ast2600_i2c_setup_buff_rx() does not enable
the DMA buffer, yet aspeed_i2c_bus_recv()'s RX_BUFF_EN branch never
updates reg_byte_buf either. That is also the default mode:
i2c-ast2600.c sets mode = BUFF_MODE and no AST2600 board DT overrides
aspeed,transfer-mode.

1. On AST2600, is your setup using DMA mode or buffer mode?  ---> AS far As I know, customers should use the buffer mode because I2C DMA mode issue in AST2600.

2. Are you testing AST2700 as well? There both modes set RX_DMA_EN (DMA and Buffer mode) --
   ast2700_i2c_setup_buff_rx() does too -- so I2CC08[15:8] would never
   be valid, and the driver reads the length from "BYTE_DATA_LOG (0x84)",
   which QEMU does not model at all. That would need a separate patch.
   https://github.com/AspeedTech-BMC/linux/commit/f9938ec5799e83f5b09d3a7acac7287c0cde2c26
   https://patchwork.kernel.org/project/qemu-devel/patch/20260804081955.1563537-2-jamin_lin@aspeedtech.com/ 

   
Thanks,
Jamin


> On 8/10/26 20:57, Mikail Sadic wrote:
> > An SMBus block read (I2C_M_RECV_LEN) reads the block length from the
> > first received byte. The Linux/U-Boot aspeed I2C driver obtains that
> > first byte from the I2CC_STS_AND_BUFF register (modelled here as
> > reg_byte_buf), even when the transfer uses DMA. The DMA receive path,
> > however, only wrote received data to DRAM and never updated
> > reg_byte_buf, so block reads read a stale/zero length.
> >
> > Mirror the first DMA-received byte into reg_byte_buf so that
> > I2C_M_RECV_LEN transfers using DMA report the correct block length.
> > This is required for the ucd9000 driver, which uses
> > i2c_smbus_read_block_data().
> >
> > Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> 
> Does this change deserve a Fixes: tag ?
> 
> Thanks,
> 
> C.
> 
> > ---
> >   hw/i2c/aspeed_i2c.c | 7 +++++++
> >   1 file changed, 7 insertions(+)
> >
> > diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index
> > 27afcaecee..facb54d27e 100644
> > --- a/hw/i2c/aspeed_i2c.c
> > +++ b/hw/i2c/aspeed_i2c.c
> > @@ -365,6 +365,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
> >       uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
> >       uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
> >       uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
> > +    bool first_dma_byte;
> >       int pool_rx_count = SHARED_ARRAY_FIELD_EX32(bus->regs,
> reg_pool_ctrl,
> >                                                   RX_SIZE) + 1;
> >
> > @@ -391,6 +392,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
> >           }
> >
> >           aspeed_i2c_set_rx_dma_dram_offset(bus);
> > +        first_dma_byte = true;
> >           while (bus->regs[reg_dma_len]) {
> >               MemTxResult result;
> >
> > @@ -407,6 +409,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus
> *bus)
> >                   return;
> >               }
> >
> > +            /* Mirror first byte to reg_byte_buf for I2C_M_RECV_LEN. */
> > +            if (first_dma_byte) {
> > +                SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf,
> RX_BUF, data);
> > +                first_dma_byte = false;
> > +            }
> >               bus->dma_dram_offset++;
> >               bus->regs[reg_dma_len]--;
> >               /* In new mode, keep track of how many bytes we RXed */


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/8] fsi/cfam: Add common CFAM base class
  2026-08-10 18:57 ` [PATCH v3 1/8] fsi/cfam: Add common CFAM base class Mikail Sadic
@ 2026-08-11 13:36   ` Miles Glenn
  0 siblings, 0 replies; 14+ messages in thread
From: Miles Glenn @ 2026-08-11 13:36 UTC (permalink / raw)
  To: Mikail Sadic, clg, peter.maydell
  Cc: pbonzini, ninad, titusr, jeuk20.kim, philmd, steven_lee, leetroy,
	jamin_lin, kane_chen, andrew, joel, calebs, qemu-arm, qemu-devel

I'm fine with having the extended addressing added later.

Reviewed-by: Glenn Miles <milesg@linux.ibm.com>

Thanks,

Glenn

On Mon, 2026-08-10 at 13:57 -0500, Mikail Sadic wrote:
> Prepare a common CFAM parent class for CFAM-S implementation.
> 
> Add an abstract TYPE_FSI_CFAM_COMMON with an FSICFAMCommonClass holding
> what varies between types: the config table, the offsets of the FSI
> responder and the local bus within the register slot, and a
> realize_engines() hook for the engines on the local bus. Its realize is
> driven by those fields, so one realize serves every type. TYPE_FSI_CFAM
> keeps its name and becomes the first derived type, supplying the P9
> config table, its slot offsets and the scratchpad engine. The config
> table becomes data, still written with the same CFAM_CONFIG_REG()
> expressions, which move to the header so a derived type can describe its
> own table the same way.
> 
> Name the new instance struct without a State suffix and rename
> FSICFAMState to FSICFAM to match.
> 
> No functional change: same memory map, same config words, same type and
> child names. aspeed_fsi-test verified.
> 
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
>  include/hw/fsi/cfam.h       |  58 ++++++++++++++++-
>  include/hw/fsi/fsi-master.h |   2 +-
>  hw/fsi/cfam.c               | 125 +++++++++++++++++++++---------------
>  hw/fsi/fsi-master.c         |   2 +-
>  4 files changed, 130 insertions(+), 57 deletions(-)
> 
> diff --git a/include/hw/fsi/cfam.h b/include/hw/fsi/cfam.h
> index cceb4bd6f1..bdf579b4fe 100644
> --- a/include/hw/fsi/cfam.h
> +++ b/include/hw/fsi/cfam.h
> @@ -7,18 +7,44 @@
>  #ifndef FSI_CFAM_H
>  #define FSI_CFAM_H
>  
> +#include "qemu/units.h"
>  #include "system/memory.h"
>  
>  #include "hw/fsi/fsi.h"
>  #include "hw/fsi/lbus.h"
>  
> +/*
> + * All CFAM flavors present a register slot holding a config table, an FSI
> + * responder and a local bus carrying the engines the config table describes.
> + * That is the common model; each flavor supplies its own table, slot layout
> + * and engines.
> + */
> +#define TYPE_FSI_CFAM_COMMON "cfam-common"
> +OBJECT_DECLARE_TYPE(FSICFAMCommon, FSICFAMCommonClass, FSI_CFAM_COMMON)
> +
>  #define TYPE_FSI_CFAM "cfam"
> -#define FSI_CFAM(obj) OBJECT_CHECK(FSICFAMState, (obj), TYPE_FSI_CFAM)
> +OBJECT_DECLARE_SIMPLE_TYPE(FSICFAM, FSI_CFAM)
>  
>  /* P9-ism */
>  #define CFAM_CONFIG_NR_REGS 0x28
>  
> -typedef struct FSICFAMState {
> +#define FSI_CFAM_SLOT_SIZE   (2 * MiB)
> +#define FSI_CFAM_CONFIG_SIZE 0x400
> +
> +#define ENGINE_CONFIG_NEXT            BIT(31)
> +#define ENGINE_CONFIG_TYPE_PEEK       (0x02 << 4)
> +#define ENGINE_CONFIG_TYPE_FSI        (0x03 << 4)
> +#define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4)
> +
> +/* Valid, slots, version, type, crc */
> +#define CFAM_CONFIG_REG(__VER, __TYPE, __CRC)   \
> +    (ENGINE_CONFIG_NEXT       |   \
> +     0x00010000               |   \
> +     (__VER)                  |   \
> +     (__TYPE)                 |   \
> +     (__CRC))
> +
> +struct FSICFAMCommon {
>      /* < private > */
>      FSISlaveState parent;
>  
> @@ -28,7 +54,33 @@ typedef struct FSICFAMState {
>      MemoryRegion mr;
>  
>      FSILBus lbus;
> +};
> +
> +struct FSICFAMCommonClass {
> +    /* < private > */
> +    DeviceClass parent_class;
> +
> +    /* < public > */
> +    /* Config table served by the common ops, one word per 4-byte offset */
> +    const uint32_t *config;
> +    unsigned config_nr;
> +
> +    /* Layout of the register slot */
> +    hwaddr responder_offset;
> +    hwaddr lbus_offset;
> +
> +    /* Realize and map this flavor's local bus engines */
> +    bool (*realize_engines)(FSICFAMCommon *cfam, Error **errp);
> +};
> +
> +struct FSICFAM {
> +    /* < private > */
> +    FSICFAMCommon parent;
> +
>      FSIScratchPad scratchpad;
> -} FSICFAMState;
> +};
> +
> +bool fsi_cfam_add_engine(FSICFAMCommon *cfam, DeviceState *engine,
> +                         hwaddr offset, Error **errp);
>  
>  #endif /* FSI_CFAM_H */
> diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
> index 60ddaa994f..87c11ec3dc 100644
> --- a/include/hw/fsi/fsi-master.h
> +++ b/include/hw/fsi/fsi-master.h
> @@ -25,7 +25,7 @@ typedef struct FSIMasterState {
>      FSIBus bus;
>  
>      uint32_t regs[FSI_MASTER_NR_REGS];
> -    FSICFAMState cfam;
> +    FSICFAM cfam;
>  } FSIMasterState;
>  
>  
> diff --git a/hw/fsi/cfam.c b/hw/fsi/cfam.c
> index 54c0b05769..5d4d0622d0 100644
> --- a/hw/fsi/cfam.c
> +++ b/hw/fsi/cfam.c
> @@ -16,19 +16,6 @@
>  
>  #include "hw/core/qdev-properties.h"
>  
> -#define ENGINE_CONFIG_NEXT            BIT(31)
> -#define ENGINE_CONFIG_TYPE_PEEK       (0x02 << 4)
> -#define ENGINE_CONFIG_TYPE_FSI        (0x03 << 4)
> -#define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4)
> -
> -/* Valid, slots, version, type, crc */
> -#define CFAM_CONFIG_REG(__VER, __TYPE, __CRC)   \
> -    (ENGINE_CONFIG_NEXT       |   \
> -     0x00010000               |   \
> -     (__VER)                  |   \
> -     (__TYPE)                 |   \
> -     (__CRC))
> -
>  #define TO_REG(x)                          ((x) >> 2)
>  
>  #define CFAM_CONFIG_CHIP_ID                TO_REG(0x00)
> @@ -36,34 +23,33 @@
>  #define CFAM_CONFIG_CHIP_ID_P9             0xc0022d15
>  #define CFAM_CONFIG_CHIP_ID_BREAK          0xc0de0000
>  
> +/*
> + * Config table of the P9 CFAM: the chip ID followed by one entry per engine,
> + * entry n describing the engine at address n * 4. We need to add future
> + * engines from address 0x10 onwards.
> + */
> +static const uint32_t cfam_p9_config[] = {
> +    CFAM_CONFIG_CHIP_ID_P9,
> +    CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_PEEK, 0xc),
> +    CFAM_CONFIG_REG(0x5000, ENGINE_CONFIG_TYPE_FSI, 0xa),
> +    CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_SCRATCHPAD, 0x7),
> +};
> +
>  static uint64_t fsi_cfam_config_read(void *opaque, hwaddr addr, unsigned size)
>  {
> +    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_GET_CLASS(opaque);
> +    unsigned int reg = TO_REG(addr);
> +
>      trace_fsi_cfam_config_read(addr, size);
>  
> -    switch (addr) {
> -    case 0x00:
> -        return CFAM_CONFIG_CHIP_ID_P9;
> -    case 0x04:
> -        return CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_PEEK, 0xc);
> -    case 0x08:
> -        return CFAM_CONFIG_REG(0x5000, ENGINE_CONFIG_TYPE_FSI, 0xa);
> -    case 0xc:
> -        return CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_SCRATCHPAD, 0x7);
> -    default:
> -        /*
> -         * The config table contains different engines from 0xc onwards.
> -         * The scratch pad is already added at address 0xc. We need to add
> -         * future engines from address 0x10 onwards. Returning 0 as engine
> -         * is not implemented.
> -         */
> -        return 0;
> -    }
> +    /* Engines past the end of the table are not implemented */
> +    return reg < cc->config_nr ? cc->config[reg] : 0;
>  }
>  
>  static void fsi_cfam_config_write(void *opaque, hwaddr addr, uint64_t data,
>                                    unsigned size)
>  {
> -    FSICFAMState *cfam = FSI_CFAM(opaque);
> +    FSICFAMCommon *cfam = FSI_CFAM_COMMON(opaque);
>  
>      trace_fsi_cfam_config_write(addr, size, data);
>  
> @@ -109,59 +95,94 @@ static const struct MemoryRegionOps fsi_cfam_unimplemented_ops = {
>      .endianness = DEVICE_BIG_ENDIAN,
>  };
>  
> -static void fsi_cfam_instance_init(Object *obj)
> +bool fsi_cfam_add_engine(FSICFAMCommon *cfam, DeviceState *engine,
> +                         hwaddr offset, Error **errp)
>  {
> -    FSICFAMState *s = FSI_CFAM(obj);
> +    if (!qdev_realize(engine, BUS(&cfam->lbus), errp)) {
> +        return false;
> +    }
>  
> -    object_initialize_child(obj, "scratchpad", &s->scratchpad,
> -                            TYPE_FSI_SCRATCHPAD);
> +    memory_region_add_subregion(&cfam->lbus.mr, offset,
> +                                &FSI_LBUS_DEVICE(engine)->iomem);
> +    return true;
>  }
>  
> -static void fsi_cfam_realize(DeviceState *dev, Error **errp)
> +static void fsi_cfam_common_realize(DeviceState *dev, Error **errp)
>  {
> -    FSICFAMState *cfam = FSI_CFAM(dev);
> +    FSICFAMCommon *cfam = FSI_CFAM_COMMON(dev);
> +    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_GET_CLASS(dev);
>      FSISlaveState *slave = FSI_SLAVE(dev);
> +    const char *type = object_get_typename(OBJECT(dev));
> +    g_autofree char *config_name = g_strdup_printf("%s.config", type);
>  
>      /* Each slave has a 2MiB address space */
>      memory_region_init_io(&cfam->mr, OBJECT(cfam), &fsi_cfam_unimplemented_ops,
> -                          cfam, TYPE_FSI_CFAM, 2 * MiB);
> +                          cfam, type, FSI_CFAM_SLOT_SIZE);
>  
>      qbus_init(&cfam->lbus, sizeof(cfam->lbus), TYPE_FSI_LBUS, DEVICE(cfam),
>                NULL);
>  
>      memory_region_init_io(&cfam->config_iomem, OBJECT(cfam), &cfam_config_ops,
> -                          cfam, TYPE_FSI_CFAM ".config", 0x400);
> +                          cfam, config_name, FSI_CFAM_CONFIG_SIZE);
>  
>      memory_region_add_subregion(&cfam->mr, 0, &cfam->config_iomem);
> -    memory_region_add_subregion(&cfam->mr, 0x800, &slave->iomem);
> -    memory_region_add_subregion(&cfam->mr, 0xc00, &cfam->lbus.mr);
> +    memory_region_add_subregion(&cfam->mr, cc->responder_offset, &slave->iomem);
> +    memory_region_add_subregion(&cfam->mr, cc->lbus_offset, &cfam->lbus.mr);
>  
> -    /* Add scratchpad engine */
> -    if (!qdev_realize(DEVICE(&cfam->scratchpad), BUS(&cfam->lbus), errp)) {
> +    if (!cc->realize_engines(cfam, errp)) {
>          return;
>      }
> -
> -    FSILBusDevice *fsi_dev = FSI_LBUS_DEVICE(&cfam->scratchpad);
> -    memory_region_add_subregion(&cfam->lbus.mr, 0, &fsi_dev->iomem);
>  }
>  
> -static void fsi_cfam_class_init(ObjectClass *klass, const void *data)
> +static void fsi_cfam_common_class_init(ObjectClass *klass, const void *data)
>  {
>      DeviceClass *dc = DEVICE_CLASS(klass);
> +
>      dc->bus_type = TYPE_FSI_BUS;
> -    dc->realize = fsi_cfam_realize;
> +    dc->realize = fsi_cfam_common_realize;
> +}
> +
> +static bool fsi_cfam_realize_engines(FSICFAMCommon *cfam, Error **errp)
> +{
> +    FSICFAM *s = FSI_CFAM(cfam);
> +
> +    /* Add scratchpad engine */
> +    object_initialize_child(OBJECT(s), "scratchpad", &s->scratchpad,
> +                            TYPE_FSI_SCRATCHPAD);
> +
> +    return fsi_cfam_add_engine(cfam, DEVICE(&s->scratchpad), 0, errp);
>  }
>  
> +static void fsi_cfam_class_init(ObjectClass *klass, const void *data)
> +{
> +    FSICFAMCommonClass *cc = FSI_CFAM_COMMON_CLASS(klass);
> +
> +    cc->config = cfam_p9_config;
> +    cc->config_nr = ARRAY_SIZE(cfam_p9_config);
> +    cc->responder_offset = 0x800;
> +    cc->lbus_offset = 0xc00;
> +    cc->realize_engines = fsi_cfam_realize_engines;
> +}
> +
> +static const TypeInfo fsi_cfam_common_info = {
> +    .name = TYPE_FSI_CFAM_COMMON,
> +    .parent = TYPE_FSI_SLAVE,
> +    .instance_size = sizeof(FSICFAMCommon),
> +    .class_size = sizeof(FSICFAMCommonClass),
> +    .class_init = fsi_cfam_common_class_init,
> +    .abstract = true,
> +};
> +
>  static const TypeInfo fsi_cfam_info = {
>      .name = TYPE_FSI_CFAM,
> -    .parent = TYPE_FSI_SLAVE,
> -    .instance_init = fsi_cfam_instance_init,
> -    .instance_size = sizeof(FSICFAMState),
> +    .parent = TYPE_FSI_CFAM_COMMON,
> +    .instance_size = sizeof(FSICFAM),
>      .class_init = fsi_cfam_class_init,
>  };
>  
>  static void fsi_cfam_register_types(void)
>  {
> +    type_register_static(&fsi_cfam_common_info);
>      type_register_static(&fsi_cfam_info);
>  }
>  
> diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c
> index 083a5507ab..d82df1c094 100644
> --- a/hw/fsi/fsi-master.c
> +++ b/hw/fsi/fsi-master.c
> @@ -130,7 +130,7 @@ static void fsi_master_realize(DeviceState *dev, Error **errp)
>      }
>  
>      /* address ? */
> -    memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.mr);
> +    memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.parent.mr);
>  }
>  
>  static void fsi_master_reset(DeviceState *dev)



^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-08-11 13:37 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 18:57 [PATCH v3 0/8] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
2026-08-10 18:57 ` [PATCH v3 1/8] fsi/cfam: Add common CFAM base class Mikail Sadic
2026-08-11 13:36   ` Miles Glenn
2026-08-10 18:57 ` [PATCH v3 2/8] fsi/cfam: Add CFAM-S model Mikail Sadic
2026-08-10 18:57 ` [PATCH v3 3/8] arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges Mikail Sadic
2026-08-11  4:32   ` Cédric Le Goater
2026-08-10 18:57 ` [PATCH v3 4/8] i2c/aspeed: Fix DMA receive first-byte handling for block reads Mikail Sadic
2026-08-11  4:30   ` Cédric Le Goater
2026-08-11  7:33     ` Jamin Lin
2026-08-10 18:57 ` [PATCH v3 5/8] hw/sensor: Add UCD90320 model Mikail Sadic
2026-08-10 18:57 ` [PATCH v3 6/8] ufs: Make the logical block size configurable and answer absent LUNs Mikail Sadic
2026-08-10 18:57 ` [PATCH v3 7/8] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
2026-08-10 18:57 ` [PATCH v3 8/8] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
2026-08-11  4:35   ` Cédric Le Goater

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.