* [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700
@ 2026-07-15 15:50 Mikail Sadic
2026-07-15 15:50 ` [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
` (5 more replies)
0 siblings, 6 replies; 15+ messages in thread
From: Mikail Sadic @ 2026-07-15 15:50 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 platform based
on the ASPEED AST2700 A2 SoC.
The series introduces a new Huygens machine model, adds a dedicated
CFAM-S implementation for the AST2700 FSI responder framework, provides
a PMBus model for the TI UCD90320 power sequencer, and implements the
AST2700 UFS host controller required for booting from UFS storage.
It also includes an initial functional test covering the Huygens boot
flow.
Patch summary:
1. Add the AST2700 Huygens machine.
2. Add the CFAM-S model for AST2700.
3. Add the TI UCD90320 PMBus device model.
4. Add the AST2700 UFS host controller.
5. Add an initial Huygens functional boot test.
Testing done:
* Booted OpenBMC using UFS storage.
* Verified OpenBMC reaches the login prompt & Active/Ready state.
* Verified `make check` test cases all pass.
Note: checkpatch has some warns about MAINTAINERS for patches 1, 2,
and 5, but the new files should already be covered by existing
wildcard entries in the ASPEED BMCs and FSI sections.
Mikail Sadic (5):
arm/aspeed: Add AST2700 Huygens machine
fsi/cfam: Add CFAM-S model for AST2700
hw/sensor: Add UCD90320 model
ufs/aspeed: Add AST2700 UFS host controller
tests/functional: Add Huygens BMC boot test
MAINTAINERS | 2 +
docs/specs/aspeed-ufs.rst | 69 ++
docs/specs/fsi.rst | 58 +
docs/specs/index.rst | 2 +
docs/specs/ucd90320.rst | 36 +
docs/system/arm/aspeed.rst | 41 +-
include/hw/arm/aspeed_soc.h | 3 +
include/hw/fsi/cfam-s.h | 26 +
include/hw/fsi/fsi-master.h | 2 +
include/hw/ufs/aspeed_ufs.h | 66 +
hw/arm/aspeed.c | 12 +
hw/arm/aspeed_ast27x0.c | 31 +
hw/arm/aspeed_ast27x0_huygens.c | 242 ++++
hw/fsi/cfam-s.c | 145 +++
hw/fsi/fsi-master.c | 10 +-
hw/i2c/aspeed_i2c.c | 7 +
hw/sensor/ucd90320.c | 144 +++
hw/ufs/aspeed_ufs.c | 1095 +++++++++++++++++
hw/arm/Kconfig | 1 +
hw/arm/meson.build | 1 +
hw/fsi/meson.build | 2 +-
hw/sensor/Kconfig | 4 +
hw/sensor/meson.build | 1 +
hw/ufs/meson.build | 1 +
tests/functional/aarch64/meson.build | 2 +
.../functional/aarch64/test_aspeed_huygens.py | 49 +
26 files changed, 2047 insertions(+), 5 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
create mode 100755 tests/functional/aarch64/test_aspeed_huygens.py
--
2.53.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
@ 2026-07-15 15:50 ` Mikail Sadic
2026-07-16 13:56 ` Cédric Le Goater
2026-07-15 15:50 ` [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700 Mikail Sadic
` (4 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Mikail Sadic @ 2026-07-15 15:50 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 support for the IBM Huygens BMC, based on the AST2700 A2 SoC.
Instantiate the AST2700 SoC with the devices and drives required
to boot Huygens OBMC Linux image on the Huygens platform.
Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
docs/system/arm/aspeed.rst | 41 +++++-
hw/arm/aspeed_ast27x0_huygens.c | 242 ++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
3 files changed, 282 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..32999d6284 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)
+ * FSI APB-to-OPB bridge with CFAM-S mailbox
Missing devices
---------------
@@ -440,6 +443,40 @@ 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. It boots from FMC
+(SPI NOR) flash and a UFS storage image. Images can be built from the
+OpenBMC project tree.
+
+To boot the machine from flash and UFS storage:
+
+.. code-block:: bash
+
+ $ qemu-system-aarch64 -M huygens-bmc \
+ -drive file=image-bmc,if=mtd,format=raw \
+ -drive file=ufs.img,if=none,format=raw \
+ -nographic
+
+The ``if=none`` interface is required for the UFS drive. The Aspeed UFS host
+controller picks up the first ``IF_NONE`` block device at index 0.
+
+The machine instantiates three ``ftgmac100`` Ethernet controllers. To
+expose them with port forwarding:
+
+.. code-block:: bash
+
+ $ qemu-system-aarch64 -M huygens-bmc \
+ -drive file=image-bmc,if=mtd,format=raw \
+ -drive file=ufs.img,if=none,format=raw \
+ -nic user,model=ftgmac100 \
+ -nic user,model=ftgmac100 \
+ -nic user,model=ftgmac100 \
+ -nographic
+
+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..951008c310
--- /dev/null
+++ b/hw/arm/aspeed_ast27x0_huygens.c
@@ -0,0 +1,242 @@
+/*
+ * IBM Huygens
+ *
+ * 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);
+
+ /* I2C2: TPM */
+
+ /* I2C5: UCD90320 power sequencer */
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5), "ucd90320", 0x11);
+
+ /* I2C6: chassis1 backplane VPD EEPROM */
+ {
+ 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);
+ g_free(ch1);
+ }
+
+ /* 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;
+ 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] 15+ messages in thread
* [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
2026-07-15 15:50 ` [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
@ 2026-07-15 15:50 ` Mikail Sadic
2026-07-16 14:01 ` Cédric Le Goater
2026-07-22 17:45 ` Ninad Palsule
2026-07-15 15:50 ` [PATCH v1 3/5] hw/sensor: Add UCD90320 model Mikail Sadic
` (3 subsequent siblings)
5 siblings, 2 replies; 15+ messages in thread
From: Mikail Sadic @ 2026-07-15 15:50 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
Introduce TYPE_FSI_CFAM_S as a dedicated QOM type that presents the
config table and mailbox scratch registers expected by the new Linux
FSI responder framework. FSIMasterState gains a cfam_s field.
fsi_master_realize() instantiates both cfam (at offset 0) and cfam-s
(at offset 2 MiB) into the OPB-to-FSI aperture. aspeed_ast27x0.c wires
up both fsi[0] and fsi[1] APB-to-OPB bridges for the AST2700 SoC.
Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
docs/specs/fsi.rst | 58 +++++++++++++++
include/hw/fsi/cfam-s.h | 26 +++++++
include/hw/fsi/fsi-master.h | 2 +
hw/arm/aspeed_ast27x0.c | 18 +++++
hw/fsi/cfam-s.c | 145 ++++++++++++++++++++++++++++++++++++
hw/fsi/fsi-master.c | 10 ++-
hw/fsi/meson.build | 2 +-
7 files changed, 258 insertions(+), 3 deletions(-)
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..a8896b67ef 100644
--- a/docs/specs/fsi.rst
+++ b/docs/specs/fsi.rst
@@ -120,3 +120,61 @@ from the BMC. (see the `pdbg source repository`_ for more details)
.. _pdbg source repository:
https://github.com/open-power/pdbg
+
+CFAM-S model (AST2700)
+----------------------
+
+The AST2700 uses a CFAM variant known as the CFAM-S, required by the new
+Linux FSI responder framework. A dedicated QOM type, ``TYPE_FSI_CFAM_S``
+(``"cfam-s"``), implements this alongside the existing ``TYPE_FSI_CFAM``
+(``"cfam"``) used by the AST2600 machines.
+
+``FSIMasterState`` realizes both ``cfam`` and ``cfam-s``. The regular
+``cfam`` is mapped at offset 0 into the OPB-to-FSI aperture; ``cfam-s`` is
+mapped at offset 2 MiB. The DTS overrides ``fsim0`` with
+``compatible = "aspeed,ast2600-fsi-master"`` and ``reg = <0x21800000>``,
+pointing the kernel FSI driver at the QEMU APB-to-OPB bridge, which routes
+through the FSI master's OPB-to-FSI window to reach the CFAM-S.
+
+The CFAM-S model presents an 8 MiB region and folds the SID bits ``[22:21]``
+so both the SID_BREAK enumeration view (``0x600000``) and the runtime view
+(``0x000000``) address the same register space. The config table at folded
+offset ``0x000`` contains three CRC4-valid words: a chip_id word with
+``MAJOR=9`` (selecting the ``cfam_s`` kernel driver), a responder engine
+entry (``TYPE=0x3``), and a mailbox v1 engine entry (``TYPE=0x14``) at
+engine address ``0x800``.
+
+The responder registers at folded offset ``0x400`` implement ``SMODE``,
+``SSTAT``, ``SRES``, ``SSISM``, and ``SLBUS`` with store-on-write semantics.
+The mailbox scratch registers at folded offset ``0x8e0`` are five stateful
+32-bit registers served to the ``ibm,mbox-cfam-s`` kernel driver, which
+creates ``/dev/fsi/mbox0``.
+
+The following commands start the ``huygens-bmc`` machine with the built-in
+CFAM-S model. There are no model specific arguments. Please check this
+document to learn more about Aspeed ``huygens-bmc`` machine:
+(:doc:`../../system/arm/aspeed`)
+
+.. code-block:: console
+
+ qemu-system-aarch64 -M huygens-bmc \
+ -drive file=image-bmc,if=mtd,format=raw \
+ -drive file=ufs.img,if=none,format=raw \
+ -nographic
+
+The CFAM-S appears as follows in the QEMU device tree:
+
+.. code-block:: console
+
+ (qemu) info qtree
+ bus: main-system-bus
+ type System
+ ...
+ dev: aspeed.apb2opb, id ""
+ mmio 0000000021800000/0000000000001000
+ bus: opb.0
+ type opb
+ dev: fsi.master, id ""
+ bus: fsi.bus.0
+ type fsi.bus
+ dev: cfam-s, id ""
diff --git a/include/hw/fsi/cfam-s.h b/include/hw/fsi/cfam-s.h
new file mode 100644
index 0000000000..3793b01471
--- /dev/null
+++ b/include/hw/fsi/cfam-s.h
@@ -0,0 +1,26 @@
+/*
+ * 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/fsi.h"
+
+#define TYPE_FSI_CFAM_S "cfam-s"
+#define FSI_CFAM_S(obj) OBJECT_CHECK(FSICFAMSState, (obj), TYPE_FSI_CFAM_S)
+
+#define CFAM_S_MBOX_SCRATCH_NUM 5
+
+typedef struct FSICFAMSState {
+ FSISlaveState parent;
+
+ MemoryRegion mr;
+ uint32_t mbox_scratch[CFAM_S_MBOX_SCRATCH_NUM];
+} FSICFAMSState;
+
+#endif /* FSI_CFAM_S_H */
diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
index 60ddaa994f..6a2782fdf2 100644
--- a/include/hw/fsi/fsi-master.h
+++ b/include/hw/fsi/fsi-master.h
@@ -11,6 +11,7 @@
#include "hw/core/qdev.h"
#include "hw/fsi/fsi.h"
#include "hw/fsi/cfam.h"
+#include "hw/fsi/cfam-s.h"
#define TYPE_FSI_MASTER "fsi.master"
OBJECT_DECLARE_SIMPLE_TYPE(FSIMasterState, FSI_MASTER)
@@ -26,6 +27,7 @@ typedef struct FSIMasterState {
uint32_t regs[FSI_MASTER_NR_REGS];
FSICFAMState cfam;
+ FSICFAMSState cfam_s;
} FSIMasterState;
diff --git a/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],
diff --git a/hw/fsi/cfam-s.c b/hw/fsi/cfam-s.c
new file mode 100644
index 0000000000..bf000d1879
--- /dev/null
+++ b/hw/fsi/cfam-s.c
@@ -0,0 +1,145 @@
+/*
+ * IBM Common FRU Access Macro - S variant (CFAM-S)
+ *
+ * Copyright (C) 2026 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/fsi/cfam-s.h"
+#include "hw/fsi/fsi.h"
+
+/* bits [22:21] are the slave ID, low 21 bits address registers within it */
+#define CFAM_SID_MASK 0x1fffff
+#define CFAM_WINDOW_SIZE 0x800000 /* 8 MiB per slave, covers SID 0..3 */
+
+#define CFAM_RESPONDER_BASE 0x400 /* == FSI_RESPONDER_PAGE_SIZE */
+
+/* Config-table word fields (see Linux fsi-master.h) */
+#define CFAM_CONF_NEXT (1u << 31)
+#define CFAM_CONF_SLOTS(n) (((n) & 0xff) << 16)
+#define CFAM_CONF_VERSION(v) (((v) & 0xf) << 12)
+#define CFAM_CONF_TYPE(t) (((t) & 0xff) << 4)
+#define CFAM_CHIP_ID_MAJOR(m) (((m) & 0xf) << 8)
+
+/* Engine IDs (include/linux/fsi.h) */
+#define FSI_ENGINE_ID_RESPONDER 0x3
+#define FSI_ENGINE_ID_MBOXV1 0x14
+#define FSI_CHIP_ID_MAJOR_CFAM_S 0x9 /* major == 9 -> CFAM-S */
+
+/* Engine layout: 0x000 config table, 0x400 responder, 0x800 mbox */
+#define CFAM_MBOX_BASE 0x800
+#define CFAM_MBOX_SCRATCH_OFF 0xe0
+#define CFAM_MBOX_SCRATCH_BASE (CFAM_MBOX_BASE + CFAM_MBOX_SCRATCH_OFF)
+
+static uint8_t cfam_s_crc4(uint8_t c, uint64_t x, int bits)
+{
+ static const uint8_t tab[16] = {
+ 0x0, 0x7, 0xe, 0x9, 0xb, 0xc, 0x5, 0x2,
+ 0x1, 0x6, 0xf, 0x8, 0xa, 0xd, 0x4, 0x3,
+ };
+ int i;
+
+ x &= (1ull << bits) - 1;
+ bits = (bits + 3) & ~0x3;
+ for (i = bits - 4; i >= 0; i -= 4) {
+ c = tab[c ^ ((x >> i) & 0xf)];
+ }
+ return c;
+}
+
+static uint32_t cfam_s_cfg_word(uint32_t fields)
+{
+ return fields | cfam_s_crc4(0, fields >> 4, 28);
+}
+
+static uint64_t fsi_cfam_s_read(void *opaque, hwaddr addr, unsigned size)
+{
+ FSICFAMSState *cfam = FSI_CFAM_S(opaque);
+ uint32_t off = (uint32_t)addr & CFAM_SID_MASK;
+ uint32_t val = 0;
+
+ if (off < CFAM_RESPONDER_BASE) {
+ switch (off) {
+ case 0x00:
+ /* chip-id: NEXT set, MAJOR=9 (CFAM-S) */
+ val = cfam_s_cfg_word(CFAM_CONF_NEXT |
+ CFAM_CHIP_ID_MAJOR(FSI_CHIP_ID_MAJOR_CFAM_S));
+ break;
+ case 0x04:
+ /* responder engine entry */
+ val = cfam_s_cfg_word(CFAM_CONF_NEXT | CFAM_CONF_SLOTS(1) |
+ CFAM_CONF_VERSION(1) |
+ CFAM_CONF_TYPE(FSI_ENGINE_ID_RESPONDER));
+ break;
+ case 0x08:
+ /* mailbox engine entry, last (NEXT clear) */
+ val = cfam_s_cfg_word(CFAM_CONF_SLOTS(1) | CFAM_CONF_VERSION(1) |
+ CFAM_CONF_TYPE(FSI_ENGINE_ID_MBOXV1));
+ break;
+ default:
+ break;
+ }
+ } else if (off >= CFAM_MBOX_SCRATCH_BASE &&
+ off < CFAM_MBOX_SCRATCH_BASE + CFAM_S_MBOX_SCRATCH_NUM * 4) {
+ val = cfam->mbox_scratch[(off - CFAM_MBOX_SCRATCH_BASE) / 4];
+ }
+
+ trace_fsi_cfam_config_read(addr, size);
+ return val;
+}
+
+static void fsi_cfam_s_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+ FSICFAMSState *cfam = FSI_CFAM_S(opaque);
+ uint32_t off = (uint32_t)addr & CFAM_SID_MASK;
+
+ if (off >= CFAM_MBOX_SCRATCH_BASE &&
+ off < CFAM_MBOX_SCRATCH_BASE + CFAM_S_MBOX_SCRATCH_NUM * 4) {
+ cfam->mbox_scratch[(off - CFAM_MBOX_SCRATCH_BASE) / 4] = (uint32_t)data;
+ }
+ trace_fsi_cfam_config_write(addr, size, data);
+}
+
+static const MemoryRegionOps cfam_s_ops = {
+ .read = fsi_cfam_s_read,
+ .write = fsi_cfam_s_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static void fsi_cfam_s_realize(DeviceState *dev, Error **errp)
+{
+ FSICFAMSState *cfam = FSI_CFAM_S(dev);
+
+ memory_region_init_io(&cfam->mr, OBJECT(cfam), &cfam_s_ops, cfam,
+ TYPE_FSI_CFAM_S, CFAM_WINDOW_SIZE);
+}
+
+static void fsi_cfam_s_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->bus_type = TYPE_FSI_BUS;
+ dc->realize = fsi_cfam_s_realize;
+}
+
+static const TypeInfo fsi_cfam_s_info = {
+ .name = TYPE_FSI_CFAM_S,
+ .parent = TYPE_FSI_SLAVE,
+ .instance_size = sizeof(FSICFAMSState),
+ .class_init = fsi_cfam_s_class_init,
+};
+
+static void fsi_cfam_s_register_types(void)
+{
+ type_register_static(&fsi_cfam_s_info);
+}
+
+type_init(fsi_cfam_s_register_types);
diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c
index 083a5507ab..f7c19e24b0 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"
@@ -112,8 +113,6 @@ static void fsi_master_init(Object *o)
{
FSIMasterState *s = FSI_MASTER(o);
- object_initialize_child(o, "cfam", &s->cfam, TYPE_FSI_CFAM);
-
qbus_init(&s->bus, sizeof(s->bus), TYPE_FSI_BUS, DEVICE(s), NULL);
memory_region_init_io(&s->iomem, OBJECT(s), &fsi_master_ops, s,
@@ -125,12 +124,19 @@ static void fsi_master_realize(DeviceState *dev, Error **errp)
{
FSIMasterState *s = FSI_MASTER(dev);
+ object_initialize_child(OBJECT(dev), "cfam", &s->cfam, TYPE_FSI_CFAM);
if (!qdev_realize(DEVICE(&s->cfam), BUS(&s->bus), errp)) {
return;
}
/* address ? */
memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.mr);
+
+ object_initialize_child(OBJECT(dev), "cfam-s", &s->cfam_s, TYPE_FSI_CFAM_S);
+ if (!qdev_realize(DEVICE(&s->cfam_s), BUS(&s->bus), errp)) {
+ return;
+ }
+ memory_region_add_subregion(&s->opb2fsi, 2 * MiB, &s->cfam_s.mr);
}
static void fsi_master_reset(DeviceState *dev)
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'))
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 3/5] hw/sensor: Add UCD90320 model
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
2026-07-15 15:50 ` [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
2026-07-15 15:50 ` [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700 Mikail Sadic
@ 2026-07-15 15:50 ` Mikail Sadic
2026-07-16 14:04 ` Cédric Le Goater
2026-07-15 15:50 ` [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
` (2 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Mikail Sadic @ 2026-07-15 15:50 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/i2c/aspeed_i2c.c | 7 ++
hw/sensor/ucd90320.c | 144 ++++++++++++++++++++++++++++++++++++++++
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 ++
hw/sensor/meson.build | 1 +
8 files changed, 195 insertions(+)
create mode 100644 docs/specs/ucd90320.rst
create mode 100644 hw/sensor/ucd90320.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ecb8cfdc41..b2861dfc79 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4059,6 +4059,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/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 */
diff --git a/hw/sensor/ucd90320.c b/hw/sensor/ucd90320.c
new file mode 100644
index 0000000000..f3b2a5d777
--- /dev/null
+++ b/hw/sensor/ucd90320.c
@@ -0,0 +1,144 @@
+/*
+ * 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 "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
+
+typedef struct UCD90320State {
+ PMBusDevice parent;
+} UCD90320State;
+
+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)
+{
+ switch (pmdev->code) {
+ case UCD9000_DEVICE_ID: {
+ static const uint8_t id[] = "UCD90320";
+ ucd90320_send_block(pmdev, id, sizeof(id) - 1);
+ pmbus_idle(pmdev);
+ return 0;
+ }
+ case UCD9000_NUM_PAGES:
+ pmbus_send8(pmdev, UCD90320_NUM_PAGES);
+ pmbus_idle(pmdev);
+ return 0;
+
+ case UCD9000_MONITOR_CONFIG: {
+ static const uint8_t cfg[] = { 0x00 };
+ ucd90320_send_block(pmdev, cfg, sizeof(cfg));
+ pmbus_idle(pmdev);
+ return 0;
+ }
+ case UCD9000_MFR_STATUS: {
+ static const uint8_t status[] = { 0x00, 0x00, 0x00, 0x00 };
+ ucd90320_send_block(pmdev, status, sizeof(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);
+
+ 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 */
+ }
+}
+
+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 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";
+ 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_info = {
+ .name = TYPE_UCD90320,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(UCD90320State),
+ .instance_init = ucd90320_init,
+ .class_init = ucd90320_class_init,
+};
+
+static void ucd90320_register_types(void)
+{
+ type_register_static(&ucd90320_info);
+}
+
+type_init(ucd90320_register_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] 15+ messages in thread
* [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
` (2 preceding siblings ...)
2026-07-15 15:50 ` [PATCH v1 3/5] hw/sensor: Add UCD90320 model Mikail Sadic
@ 2026-07-15 15:50 ` Mikail Sadic
2026-07-16 0:31 ` Jeuk Kim
2026-07-16 0:48 ` Jamin Lin
2026-07-15 15:50 ` [PATCH v1 5/5] tests/functional: Add Huygens BMC boot test Mikail Sadic
2026-07-15 16:01 ` [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Glenn Miles
5 siblings, 2 replies; 15+ messages in thread
From: Mikail Sadic @ 2026-07-15 15:50 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 an emulation of the AST2700 UFS host controller
(aspeed,ufshc-m31-16nm) and wire it into the AST2700 SoC.
Implement the functionality needed by U-Boot and Linux to enumerate the
device and access a BlockBackend using the UFS protocol, allowing the
AST2700 machine to boot from a UFS image.
Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
MAINTAINERS | 1 +
docs/specs/aspeed-ufs.rst | 69 +++
docs/specs/index.rst | 1 +
include/hw/arm/aspeed_soc.h | 3 +
include/hw/ufs/aspeed_ufs.h | 66 +++
hw/arm/aspeed.c | 12 +
hw/arm/aspeed_ast27x0.c | 13 +
hw/ufs/aspeed_ufs.c | 1095 +++++++++++++++++++++++++++++++++++
hw/ufs/meson.build | 1 +
9 files changed, 1261 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 b2861dfc79..d35c29c58d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2696,6 +2696,7 @@ 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: 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..f4eb6f1ce6
--- /dev/null
+++ b/docs/specs/aspeed-ufs.rst
@@ -0,0 +1,69 @@
+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 a sysbus device implementing the UFSHCI v2.0 register interface
+required by U-Boot and the Linux ``aspeed-ufshcd`` driver.
+
+The clock/reset wrapper at ``0x12c08000`` (``aspeed,ast2700-ufscnr``) is left
+as an ``UnimplementedDevice``.
+
+Implemented functionality
+--------------------------
+
+Host Controller Enable (HCE)
+ On a ``HCE=1`` write the register is cleared to 0 and a bottom-half is
+ scheduled to set it back to 1 with ``HCS`` ready bits. The read handler
+ returns 0 while in phase 1, so polling sees the 0 to 1 transition without
+ a timeout. If the controller is already in the ready state (phase 2), a
+ subsequent ``HCE=1`` write is a no-op; the controller stays enabled for
+ Linux after U-Boot has left it running.
+
+UIC commands
+ ``DME_LINKSTARTUP``, ``DME_GET``, ``DME_PEER_GET``, ``DME_SET``, and
+ ``DME_PEER_SET`` all succeed immediately. ``DME_LINKSTARTUP`` sets
+ ``HCS.DP`` and raises ``IS.UIC_LINK_STARTUP`` (bit 8). ``DME_SET`` and
+ ``DME_PEER_SET`` set ``HCS[10:8] = PWR_LOCAL`` and raise
+ ``IS.UIC_POWER_MODE``.
+
+NOP OUT / NOP IN
+ Doorbell processing is synchronous (inline) rather than deferred, as
+ U-Boot's ``NOP_OUT_TIMEOUT`` is shorter than a deferred bottom-half can
+ fire in QEMU's event loop.
+
+SCSI block I/O
+ READ_10, READ_16, WRITE_10, WRITE_16, INQUIRY, READ_CAPACITY_10/16, and
+ REPORT_LUNS are forwarded to the attached ``BlockBackend``. When
+ ``prdtl = 0`` (no scatter-gather list), response data is placed in the
+ Response UPIU data segment; when ``prdtl > 0`` it is written into the PRDT
+ buffers.
+
+Query UPIU descriptors
+ Device, Geometry, Unit, Power (idn 8), and String (idn 5) descriptors are
+ returned, capped to the requested length.
+
+Well-Known LUNs
+ All four W-LUNs (0xD0 UFS Device, 0xC4 RPMB, 0xB0 Boot, 0x81 Report LUNs)
+ respond as present devices.
+
+Task management (UTMRL)
+ ``LOGICAL_UNIT_RESET`` commands are acknowledged immediately by writing
+ ``OCS_SUCCESS`` into the UTMRD slot, clearing the doorbell, and raising
+ ``IS.UTP_TASK_REQ_COMPL`` (bit 9).
+
+Usage
+-----
+
+The UFS controller picks up the first ``IF_NONE`` block device at index 0.
+Pass a UFS disk image with ``-drive if=none``:
+
+.. code-block:: console
+
+ qemu-system-aarch64 -M huygens-bmc \
+ -drive file=image-bmc,if=mtd,format=raw \
+ -drive file=ufs.img,if=none,format=raw \
+ -nographic
+
+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_soc.h b/include/hw/arm/aspeed_soc.h
index 41dc04e293..28ca97d35c 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];
+ AspeedUFSState 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..6d0aa345e9
--- /dev/null
+++ b/include/hw/ufs/aspeed_ufs.h
@@ -0,0 +1,66 @@
+/*
+ * 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/core/sysbus.h"
+#include "block/ufs.h"
+#include "system/block-backend.h"
+
+#define TYPE_ASPEED_UFS "aspeed-ufs"
+OBJECT_DECLARE_SIMPLE_TYPE(AspeedUFSState, ASPEED_UFS)
+
+/* UFSHCI register space is 256 bytes (0x00-0xFF) */
+#define ASPEED_UFS_MMIO_SIZE 0x100
+#define ASPEED_UFS_NUM_REGS (ASPEED_UFS_MMIO_SIZE / sizeof(uint32_t))
+
+/* Number of UTP Transfer Request slots advertised */
+#define ASPEED_UFS_NUTRS 32
+/* Number of UTP Task Management Request slots */
+#define ASPEED_UFS_NUTMRS 8
+
+/*
+ * Reset / power-on values.
+ *
+ * CAP: NUTRS=31 (0x1f, 5-bit), RTT=2, NUTMRS=7, 64AS=1
+ * VER: UFSHCI 2.0 (matches aspeed,ufshc-m31-16nm)
+ * HCS: DP|UTRLRDY|UTMRLRDY|UCRDY (bits 0-3)
+ */
+#define ASPEED_UFS_CAP_RESET 0x0702031f
+#define ASPEED_UFS_VER_RESET 0x00000200
+#define ASPEED_UFS_HCS_READY 0x0000000f
+
+struct AspeedUFSState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ BlockBackend *blk;
+ uint64_t num_sectors;
+
+ uint32_t regs[ASPEED_UFS_NUM_REGS];
+
+ /*
+ * HCE state machine phase:
+ * 0 = idle (HCE=0)
+ * 1 = cleared (firmware wrote 1, we stored 0, BH pending)
+ * 2 = ready (BH fired, HCE=1, HCS ready bits set)
+ */
+ int hce_phase;
+ QEMUBH *hce_bh;
+
+ uint32_t utrldbr; /* pending doorbell bits */
+ uint64_t utrl_base; /* 64-bit physical base of UTRL */
+ uint64_t utmrl_base; /* 64-bit physical base of UTMRL */
+
+ /* Unit Descriptor; capacity filled at realize time */
+ uint8_t unit_desc[0x23];
+};
+
+#endif /* ASPEED_UFS_H */
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index a48c442058..1f57f32ad4 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -184,6 +184,18 @@ static void aspeed_machine_init(MachineState *machine)
object_property_set_int(OBJECT(bmc->soc), "hw-prot-key",
ASPEED_SCU_PROT_KEY, &error_abort);
}
+ /*
+ * Attach the UFS backing drive before realizing the SoC so that
+ * the UFS controller can read the drive size during realization.
+ */
+ {
+ DriveInfo *ufs_dinfo = drive_get(IF_NONE, 0, 0);
+ if (ufs_dinfo) {
+ qdev_prop_set_drive_err(DEVICE(&bmc->soc->ufs), "drive",
+ blk_by_legacy_dinfo(ufs_dinfo),
+ &error_fatal);
+ }
+ }
aspeed_connect_serial_hds_to_uarts(bmc);
qdev_realize(DEVICE(bmc->soc), NULL, &error_abort);
diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index b908d7d4ff..04aa4e15a5 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -39,6 +39,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
[ASPEED_DEV_EHCI2] = 0x12063000,
[ASPEED_DEV_HACE] = 0x12070000,
[ASPEED_DEV_EMMC] = 0x12090000,
+ [ASPEED_DEV_UFS] = 0x12c08200,
[ASPEED_DEV_PCIE0] = 0x120E0000,
[ASPEED_DEV_PCIE1] = 0x120F0000,
[ASPEED_DEV_INTC] = 0x12100000,
@@ -116,6 +117,7 @@ static const int aspeed_soc_ast2700a1_irqmap[] = {
[ASPEED_DEV_SCU] = 12,
[ASPEED_DEV_RTC] = 13,
[ASPEED_DEV_EMMC] = 15,
+ [ASPEED_DEV_UFS] = 118,
[ASPEED_DEV_TIMER1] = 16,
[ASPEED_DEV_TIMER2] = 17,
[ASPEED_DEV_TIMER3] = 18,
@@ -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..673aced63b
--- /dev/null
+++ b/hw/ufs/aspeed_ufs.c
@@ -0,0 +1,1095 @@
+/*
+ * ASPEED AST2700 UFS Host Controller
+ *
+ * Sysbus device mapped at 0x12c08200 (256 bytes). Implements the UFSHCI
+ * register interface required by U-Boot ufshcd_probe() and Linux
+ * aspeed-ufshcd, including UIC command handling, NOP OUT/IN, SCSI block I/O,
+ * Query UPIU descriptor reads, W-LUN support, and UTMRL task management.
+ *
+ * Register offsets follow JEDEC UFSHCI spec v2.0, matching the REG_*
+ * definitions in include/block/ufs.h.
+ *
+ * The ASPEED clock/reset wrapper at 0x12c08000 (aspeed,ast2700-ufscnr) is
+ * left as an UnimplementedDevice; writes to MPHY registers there require no
+ * readback.
+ *
+ * Copyright 2026 IBM Corp.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-properties-system.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "system/block-backend.h"
+#include "system/block-backend-io.h"
+#include "system/dma.h"
+#include "system/address-spaces.h"
+#include "hw/ufs/aspeed_ufs.h"
+
+/* UFSHCI register offsets (JEDEC UFSHCI spec v2.0) */
+#define REG_CAP 0x00
+#define REG_VER 0x08
+#define REG_HCPID 0x10
+#define REG_HCMID 0x14
+#define REG_AHIT 0x18
+#define REG_IS 0x20
+#define REG_IE 0x24
+#define REG_HCS 0x30
+#define REG_HCE 0x34
+#define REG_UECPA 0x38
+#define REG_UECDL 0x3c
+#define REG_UECN 0x40
+#define REG_UECT 0x44
+#define REG_UECDME 0x48
+#define REG_UTRLBA 0x50
+#define REG_UTRLBAU 0x54
+#define REG_UTRLDBR 0x58
+#define REG_UTRLCLR 0x5c
+#define REG_UTRLRSR 0x60
+#define REG_UTMRLBA 0x70
+#define REG_UTMRLBAU 0x74
+#define REG_UTMRLDBR 0x78
+#define REG_UTMRLCLR 0x7c
+#define REG_UTMRLRSR 0x80
+#define REG_UICCMD 0x90
+#define REG_UCMDARG1 0x94
+#define REG_UCMDARG2 0x98
+#define REG_UCMDARG3 0x9c
+
+/* HCE / HCS bits */
+#define HCE_ENABLE BIT(0)
+#define HCS_DP BIT(0)
+#define HCS_UTRLRDY BIT(1)
+#define HCS_UTMRLRDY BIT(2)
+#define HCS_UCRDY BIT(3)
+
+/* IS bits (UFSHCI spec sec. 5.3) */
+#define IS_UTRCS BIT(0) /* UTP Transfer Request Completion */
+#define IS_UIC_POWER_MODE BIT(4) /* UIC Power Mode Status */
+#define IS_UIC_LINK_STARTUP BIT(8) /* UIC Link Startup Status */
+#define IS_UTMRCS BIT(9) /* UTP Task Mgmt Request Completion */
+#define IS_UCCS BIT(10) /* UIC Command Completion Status */
+
+/* UIC command opcodes */
+#define DME_LINKSTARTUP 0x16
+#define DME_GET 0x01
+#define DME_SET 0x02
+#define DME_PEER_GET 0x03
+#define DME_PEER_SET 0x04
+
+/* UIC command result (success) in UCMDARG2[7:0] */
+#define UIC_CMD_RESULT_OK 0x00
+
+/* HCS power-mode state field bits [10:8]: PWR_LOCAL = 1 */
+#define HCS_PWR_LOCAL (1 << 8)
+
+/* Register array index helper */
+#define R(off) ((off) / sizeof(uint32_t))
+
+/*
+ * UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1).
+ */
+#define UTRD_SIZE 32 /* bytes per UTRL slot */
+
+/*
+ * UTMRL slot size (UTP Task Management Request List):
+ * - header : 16 bytes
+ * - upiu_req : 32 bytes
+ * - upiu_rsp : 32 bytes
+ */
+#define UTMRD_SIZE 80
+
+/* byte offset of OCS within request_desc_header */
+#define UTMRD_OCS_OFFSET 8
+
+/* Command Type in UTRD DW0[29:28] */
+#define CT_UFS_STORAGE 0x1
+
+/* Data Direction in UTRD DW0[25:24] */
+#define DD_NO_DATA 0x0
+#define DD_HOST_TO_DEV 0x2
+#define DD_DEV_TO_HOST 0x4
+
+/* OCS in UTRD DW2[7:0] */
+#define OCS_SUCCESS 0x0
+
+/* UPIU transaction types */
+#define UPIU_TYPE_NOP_OUT 0x00
+#define UPIU_TYPE_CMD 0x01
+#define UPIU_TYPE_QUERY_REQ 0x16
+#define UPIU_TYPE_NOP_IN 0x20
+#define UPIU_TYPE_RESPONSE 0x21
+#define UPIU_TYPE_QUERY_RSP 0x36
+
+/* Query opcodes */
+#define QUERY_OP_READ_DESC 0x01
+#define QUERY_OP_WRITE_DESC 0x02
+#define QUERY_OP_READ_ATTR 0x03
+#define QUERY_OP_WRITE_ATTR 0x04
+#define QUERY_OP_READ_FLAG 0x05
+#define QUERY_OP_SET_FLAG 0x06
+#define QUERY_OP_CLEAR_FLAG 0x07
+
+/* Descriptor idn */
+#define DESC_IDN_DEVICE 0x00
+#define DESC_IDN_CONFIGURATION 0x01
+#define DESC_IDN_UNIT 0x02
+#define DESC_IDN_GEOMETRY 0x07
+#define DESC_IDN_POWER 0x08
+#define DESC_IDN_STRING 0x05
+
+/* SCSI commands */
+#define SCSI_TEST_UNIT_READY 0x00
+#define SCSI_INQUIRY 0x12
+#define SCSI_READ_CAPACITY_10 0x25
+#define SCSI_READ_10 0x28
+#define SCSI_WRITE_10 0x2a
+#define SCSI_READ_16 0x88
+#define SCSI_WRITE_16 0x8a
+#define SCSI_READ_CAPACITY_16 0x9e /* service action 0x10 */
+#define SCSI_REPORT_LUNS 0xa0
+
+/*
+ * Well-Known LUN addresses probed by ufshcd_scsi_add_wlus().
+ * All four must appear as present devices or async_scan fails.
+ */
+#define WLUN_REPORT_LUNS 0x81
+#define WLUN_UFS_DEVICE 0xd0
+#define WLUN_BOOT 0xb0
+#define WLUN_RPMB 0xc4
+
+static bool aspeed_ufs_is_wlun(uint8_t lun)
+{
+ return lun == WLUN_UFS_DEVICE || lun == WLUN_RPMB ||
+ lun == WLUN_BOOT || lun == WLUN_REPORT_LUNS;
+}
+
+static void ufs_dma_read(hwaddr paddr, void *buf, size_t len)
+{
+ dma_memory_read(&address_space_memory, paddr, buf, len,
+ MEMTXATTRS_UNSPECIFIED);
+}
+
+static void ufs_dma_write(hwaddr paddr, const void *buf, size_t len)
+{
+ dma_memory_write(&address_space_memory, paddr, buf, len,
+ MEMTXATTRS_UNSPECIFIED);
+}
+
+static void aspeed_ufs_update_irq(AspeedUFSState *s)
+{
+ uint32_t is = s->regs[R(REG_IS)];
+ uint32_t ie = s->regs[R(REG_IE)];
+
+ qemu_set_irq(s->irq, !!(is & ie));
+}
+
+/*
+ * HCE bottom-half: completes the 1->0->1 toggle that the host driver polls.
+ *
+ * On any HCE=1 write the write handler clears HCE to 0 and schedules this
+ * BH. Once it fires the controller is considered ready.
+ */
+static void aspeed_ufs_hce_bh(void *opaque)
+{
+ AspeedUFSState *s = opaque;
+
+ s->regs[R(REG_HCE)] = HCE_ENABLE;
+ s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY;
+ s->hce_phase = 2;
+}
+
+/* 12-byte UPIU header (big-endian per UFSHCI spec) */
+typedef struct {
+ uint8_t trans_type;
+ uint8_t flags;
+ uint8_t lun;
+ uint8_t task_tag;
+ uint8_t iid_cmd_set;
+ uint8_t query_func;
+ uint8_t response;
+ uint8_t status;
+ uint8_t ehs_len;
+ uint8_t device_info;
+ uint16_t data_seg_len_be;
+} UpiuHeader;
+
+/* 32-byte UPIU (header + TSF + reserved dword) */
+typedef struct {
+ UpiuHeader hdr;
+ uint8_t tsf[16];
+ uint32_t reserved;
+} Upiu32;
+
+/* UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1, 32 bytes) */
+typedef struct {
+ uint32_t dw0;
+ uint32_t dw1;
+ uint32_t dw2; /* OCS[7:0] */
+ uint32_t dw3;
+ uint32_t dw4; /* UCDBA: Command UPIU base, lower 32 bits */
+ uint32_t dw5; /* UCDBAU: upper 32 bits */
+ uint32_t dw6; /* RUL[31:16] | RUO[15:0] in DWORDs */
+ uint32_t dw7; /* PRDTO[31:16] | PRDTL[15:0] */
+} Utrd;
+
+/* Physical Region Descriptor Table entry */
+typedef struct {
+ uint32_t dba; /* Data Buffer Address (lower) */
+ uint32_t dbau; /* Data Buffer Address (upper) */
+ uint32_t reserved;
+ uint32_t size; /* byte count - 1, bits [17:0] */
+} PrdtEntry;
+
+/*
+ * Minimal UFS Device Descriptor (64 bytes).
+ * Values follow UFS 2.1 spec defaults; only fields checked by Linux and
+ * U-Boot ufshcd are set to non-zero.
+ */
+static const uint8_t ufs_device_desc[] = {
+ 0x40, /* bLength = 64 */
+ DESC_IDN_DEVICE, /* bDescriptorIDN */
+ 0x00, /* bDevice */
+ 0x00, /* bDeviceClass */
+ 0x00, /* bDeviceSubClass */
+ 0x01, /* bProtocol */
+ 0x01, /* bNumberLU */
+ 0x00, /* bNumberWLU */
+ 0x00, /* bBootEnable */
+ 0x00, /* bDescrAccessEn */
+ 0x00, /* bInitPowerMode */
+ 0x01, /* bHighPriorityLUN */
+ 0x00, /* bSecureRemovalType */
+ 0x00, /* bSecurityLU */
+ 0x00, /* bBackgroundOpsTermLat */
+ 0x01, /* bInitActiveICCLevel */
+ 0x02, 0x10, /* wSpecVersion = 0x0210 (UFS 2.1) */
+ 0x00, 0x00, /* wManufactureDate */
+ 0x00, /* iManufacturerName */
+ 0x02, /* iProductName (string index 2) */
+ 0x00, /* iSerialNumber */
+ 0x00, /* iOemID */
+ 0x00, 0x00, /* wManufacturerID */
+ 0x00, /* bUD0BaseOffset */
+ 0x00, /* bUDConfigPlength */
+ 0x00, /* bDeviceRTTCap */
+ 0x00, 0x00, /* wPeriodicRTCUpdate */
+ 0x00, /* bUFSFeaturesSupport */
+ 0x00, /* bFFUTimeout */
+ 0x00, /* bQueueDepth */
+ 0x00, 0x00, /* wDeviceVersion */
+ 0x00, /* bNumSecureWPArea */
+ 0x00, 0x00, 0x00, 0x00, /* dPSAMaxDataSize */
+ 0x00, /* bPSAStateTimeout */
+ 0x00, /* iProductRevisionLevel */
+ /* pad to 0x40 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+/* Minimal Geometry Descriptor (0x48 bytes) */
+static const uint8_t ufs_geometry_desc[] = {
+ 0x48, /* bLength */
+ DESC_IDN_GEOMETRY, /* bDescriptorIDN */
+ 0x00, /* bMediaTechnology */
+ 0x00, /* reserved */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, /* qTotalRawDeviceCapacity (8 bytes, big-endian) */
+ 0x00, /* bMaxNumberLU */
+ 0x00, 0x10, 0x00, 0x00, /* dSegmentSize = 4096 sectors */
+ 0x08, /* bAllocationUnitSize */
+ 0x01, /* bMinAddrBlockSize */
+ 0x00, /* bOptimalReadBlockSize */
+ 0x00, /* bOptimalWriteBlockSize */
+ 0x00, /* bMaxInBufferSize */
+ 0x00, /* bMaxOutBufferSize */
+ 0x00, /* bRPMB_ReadWriteSize */
+ 0x00, /* bDynamicCapacityResourcePolicy */
+ 0x00, /* bDataOrdering */
+ 0x00, /* bMaxContexIDNumber */
+ 0x00, /* bSysDataTagUnitSize */
+ 0x00, /* bSysDataTagResSize */
+ 0x01, /* bSupportedSecRTypes */
+ 0x00, 0x03, /* wSupportedMemoryTypes */
+ 0x00, 0x00, 0x00, 0x00, /* dSystemCodeMaxNAllocU */
+ 0x00, 0x00, /* wSystemCodeCapAdjFac */
+ 0x00, 0x00, 0x00, 0x00, /* dNonPersistMaxNAllocU */
+ 0x00, 0x00, /* wNonPersistCapAdjFac */
+ 0x00, 0x00, 0x00, 0x00, /* dEnhanced1MaxNAllocU */
+ 0x00, 0x00, /* wEnhanced1CapAdjFac */
+ 0x00, 0x00, 0x00, 0x00, /* dEnhanced2MaxNAllocU */
+ 0x00, 0x00, /* wEnhanced2CapAdjFac */
+ 0x00, 0x00, 0x00, 0x00, /* dEnhanced3MaxNAllocU */
+ 0x00, 0x00, /* wEnhanced3CapAdjFac */
+ 0x00, 0x00, 0x00, 0x00, /* dEnhanced4MaxNAllocU */
+ 0x00, 0x00, /* wEnhanced4CapAdjFac */
+ /* pad to 0x48 */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * Power Descriptor (idn 8, bLength 0x62).
+ * Linux ufshcd_probe() reads this; a zero-filled body of the right length
+ * satisfies the length check.
+ */
+static const uint8_t ufs_power_desc[0x62] = {
+ 0x62, /* bLength */
+ DESC_IDN_POWER, /* bDescriptorIDN */
+ /* remaining bytes zero-initialized */
+};
+
+/*
+ * String Descriptor (idn 5).
+ * iProductName points to string index 2 in the device descriptor. A
+ * minimal well-formed header (type + zero length) keeps ufshcd happy.
+ */
+static const uint8_t ufs_string_desc[] = {
+ 0x02, /* bLength = 2 (header only, no string data) */
+ DESC_IDN_STRING, /* bDescriptorIDN */
+};
+
+/* Unit Descriptor (0x23 bytes) - capacity filled at realize time */
+
+static void aspeed_ufs_init_unit_desc(AspeedUFSState *s)
+{
+ uint64_t lbc;
+
+ memset(s->unit_desc, 0, sizeof(s->unit_desc));
+ s->unit_desc[0x00] = 0x23; /* bLength */
+ s->unit_desc[0x01] = DESC_IDN_UNIT; /* bDescriptorIDN */
+ s->unit_desc[0x02] = 0x00; /* bUnitIndex */
+ s->unit_desc[0x03] = 0x01; /* bLUEnable */
+ s->unit_desc[0x08] = 0x02; /* bMemoryType */
+ s->unit_desc[0x0a] = 0x0c; /* bLogicalBlockSize: 1 << 12 = 4096 */
+
+ lbc = cpu_to_be64(s->num_sectors);
+ memcpy(&s->unit_desc[0x0b], &lbc, 8); /* qLogicalBlockCount */
+}
+
+/* Build a 36-byte SCSI INQUIRY response */
+static void scsi_build_inquiry(uint8_t *buf, uint8_t lun)
+{
+ memset(buf, 0, 36);
+ /*
+ * W-LUNs use device_type 0x1e (well known LU); LUN 0 is a direct-access
+ * block device (0x00).
+ */
+ buf[0] = aspeed_ufs_is_wlun(lun) ? 0x1e : 0x00;
+ buf[1] = 0x00; /* not removable */
+ buf[2] = 0x05; /* SPC-3 */
+ buf[3] = 0x12; /* response data format = 2, HiSup */
+ buf[4] = 0x1f; /* additional length = 31 */
+ memcpy(&buf[8], "ASPEED ", 8);
+ memcpy(&buf[16], "UFS QEMU ", 16);
+ memcpy(&buf[32], "1.00", 4);
+}
+
+/* Build an 8-byte READ_CAPACITY(10) response */
+static void scsi_build_read_cap10(uint8_t *buf, uint64_t sectors)
+{
+ uint32_t last_lba = (sectors > 0xffffffff) ? 0xffffffff
+ : (uint32_t)(sectors - 1);
+ uint32_t block_size = cpu_to_be32(512);
+
+ last_lba = cpu_to_be32(last_lba);
+ memcpy(buf, &last_lba, 4);
+ memcpy(buf + 4, &block_size, 4);
+}
+
+/* Build a 32-byte READ_CAPACITY(16) response */
+static void scsi_build_read_cap16(uint8_t *buf, uint64_t sectors)
+{
+ uint64_t last_lba = cpu_to_be64(sectors - 1);
+ uint32_t block_size = cpu_to_be32(512);
+ memcpy(buf, &last_lba, 8);
+ memcpy(buf + 8, &block_size, 4);
+}
+
+/* Process one UTRL slot */
+static void aspeed_ufs_process_slot(AspeedUFSState *s, int slot)
+{
+ uint64_t utrd_addr = s->utrl_base + (uint64_t)slot * UTRD_SIZE;
+ Utrd utrd;
+ uint64_t upiu_base, resp_base, prdt_base;
+ uint32_t dw6, dw7, ruo, prdtl, prdto;
+ UpiuHeader req_hdr;
+ uint8_t trans_type, task_tag;
+
+ ufs_dma_read(utrd_addr, &utrd, sizeof(utrd));
+
+ upiu_base = (uint64_t)le32_to_cpu(utrd.dw4) |
+ ((uint64_t)le32_to_cpu(utrd.dw5) << 32);
+
+ /*
+ * DW6 holds the Response UPIU offset (RUO) and length (RUL), both in
+ * DWORDs. The response UPIU follows the request UPIU in the same
+ * guest buffer.
+ */
+ dw6 = le32_to_cpu(utrd.dw6);
+ ruo = dw6 & 0xffff;
+ resp_base = upiu_base + (uint64_t)ruo * 4;
+
+ /* DW7: PRDTL (entry count) and PRDTO (offset in DWORDs) */
+ dw7 = le32_to_cpu(utrd.dw7);
+ prdtl = dw7 & 0xffff;
+ prdto = (dw7 >> 16) & 0xffff;
+ prdt_base = upiu_base + (uint64_t)prdto * 4;
+
+ ufs_dma_read(upiu_base, &req_hdr, sizeof(req_hdr));
+ trans_type = req_hdr.trans_type & 0x3f;
+ task_tag = req_hdr.task_tag;
+
+ /* NOP OUT -> NOP IN */
+ if (trans_type == UPIU_TYPE_NOP_OUT) {
+ uint8_t rsp[32];
+ memset(rsp, 0, sizeof(rsp));
+ rsp[0] = UPIU_TYPE_NOP_IN;
+ rsp[3] = task_tag;
+ ufs_dma_write(resp_base, rsp, 32);
+ goto done;
+ }
+
+ /* Query Request -> Query Response */
+ if (trans_type == UPIU_TYPE_QUERY_REQ) {
+ uint8_t req_buf[32];
+ uint8_t rsp[32];
+ uint8_t opcode, idn;
+
+ ufs_dma_read(upiu_base, req_buf, 32);
+ opcode = req_buf[12];
+ idn = req_buf[13];
+
+ memset(rsp, 0, sizeof(rsp));
+ rsp[0] = UPIU_TYPE_QUERY_RSP;
+ rsp[3] = task_tag;
+ memcpy(&rsp[12], &req_buf[12], 16); /* echo TSF */
+
+ if (opcode == QUERY_OP_READ_DESC) {
+ const uint8_t *desc = NULL;
+ uint16_t desc_len = 0;
+ uint16_t req_len;
+
+ switch (idn) {
+ case DESC_IDN_DEVICE:
+ desc = ufs_device_desc;
+ desc_len = sizeof(ufs_device_desc);
+ break;
+ case DESC_IDN_GEOMETRY:
+ desc = ufs_geometry_desc;
+ desc_len = sizeof(ufs_geometry_desc);
+ break;
+ case DESC_IDN_UNIT:
+ desc = s->unit_desc;
+ desc_len = sizeof(s->unit_desc);
+ break;
+ case DESC_IDN_POWER:
+ desc = ufs_power_desc;
+ desc_len = sizeof(ufs_power_desc);
+ break;
+ case DESC_IDN_STRING:
+ desc = ufs_string_desc;
+ desc_len = sizeof(ufs_string_desc);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Cap the returned length to what the host requested (TSF
+ * bytes 6-7 hold the allocation length). Returning more than
+ * asked causes the driver to reject the response with -EINVAL.
+ */
+ req_len = ((uint16_t)req_buf[18] << 8) | req_buf[19];
+ if (desc_len > req_len) {
+ desc_len = req_len;
+ }
+
+ rsp[10] = (desc_len >> 8) & 0xff;
+ rsp[11] = desc_len & 0xff;
+ rsp[18] = rsp[10];
+ rsp[19] = rsp[11];
+
+ ufs_dma_write(resp_base, rsp, 32);
+ if (desc && desc_len) {
+ ufs_dma_write(resp_base + 32, desc, desc_len);
+ }
+ } else {
+ /* READ_ATTR, READ_FLAG, SET_FLAG, etc. - echo TSF, success */
+ ufs_dma_write(resp_base, rsp, 32);
+ }
+ goto done;
+ }
+
+ /* SCSI Command UPIU */
+ if (trans_type == UPIU_TYPE_CMD) {
+ uint8_t req_buf[32];
+ uint8_t *cdb;
+ uint8_t scsi_cmd;
+ bool ok = true;
+ uint8_t sense[18];
+ uint8_t *xfer_buf = NULL;
+ uint32_t xfer_len = 0;
+
+ ufs_dma_read(upiu_base, req_buf, 32);
+ cdb = &req_buf[16];
+ scsi_cmd = cdb[0];
+
+ memset(sense, 0, sizeof(sense));
+
+ /* Non-zero LUNs that are not W-LUNs have no device. */
+ if (req_hdr.lun != 0 && !aspeed_ufs_is_wlun(req_hdr.lun)) {
+ ok = false;
+ sense[0] = 0x70;
+ sense[2] = 0x05; /* ILLEGAL REQUEST */
+ sense[7] = 0x0a;
+ goto scsi_done;
+ }
+
+ if (prdtl > 0) {
+ uint32_t i;
+
+ for (i = 0; i < prdtl; i++) {
+ PrdtEntry pe;
+ ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
+ &pe, sizeof(pe));
+ xfer_len += (le32_to_cpu(pe.size) & 0x3ffff) + 1;
+ }
+ xfer_buf = g_malloc0(xfer_len);
+ }
+
+ switch (scsi_cmd) {
+
+ case SCSI_TEST_UNIT_READY:
+ break;
+
+ case SCSI_INQUIRY: {
+ uint8_t inq[36];
+ uint32_t alloc, send;
+
+ /*
+ * EVPD queries (cdb[1] & 0x01) return an empty VPD page list
+ * for W-LUNs. This stops the driver from iterating all 256
+ * possible VPD pages after it finds zero supported ones.
+ */
+ if ((cdb[1] & 0x01) && aspeed_ufs_is_wlun(req_hdr.lun)) {
+ uint8_t evpd[4];
+ memset(evpd, 0, sizeof(evpd));
+ evpd[0] = 0x1e; /* device_type: well known LU */
+ /* page_length = 0: no VPD pages supported */
+ alloc = ((uint16_t)cdb[3] << 8) | cdb[4];
+ send = MIN(alloc, sizeof(evpd));
+ if (prdtl > 0 && xfer_buf) {
+ memcpy(xfer_buf, evpd, send);
+ } else {
+ ufs_dma_write(resp_base + 32, evpd, send);
+ }
+ xfer_len = send;
+ break;
+ }
+
+ scsi_build_inquiry(inq, req_hdr.lun);
+ alloc = ((uint16_t)cdb[3] << 8) | cdb[4];
+ send = MIN(alloc, 36);
+ if (prdtl > 0 && xfer_buf) {
+ memcpy(xfer_buf, inq, send);
+ } else {
+ ufs_dma_write(resp_base + 32, inq, send);
+ }
+ xfer_len = send;
+ break;
+ }
+
+ case SCSI_READ_CAPACITY_10: {
+ uint8_t cap[8];
+ scsi_build_read_cap10(cap, s->num_sectors);
+ if (prdtl > 0 && xfer_buf) {
+ memcpy(xfer_buf, cap, 8);
+ } else {
+ ufs_dma_write(resp_base + 32, cap, 8);
+ }
+ xfer_len = 8;
+ break;
+ }
+
+ case SCSI_READ_CAPACITY_16:
+ /* service action must be 0x10 */
+ if ((cdb[1] & 0x1f) == 0x10) {
+ uint8_t cap[32];
+ memset(cap, 0, sizeof(cap));
+ scsi_build_read_cap16(cap, s->num_sectors);
+ if (prdtl > 0 && xfer_buf) {
+ memcpy(xfer_buf, cap, 32);
+ } else {
+ ufs_dma_write(resp_base + 32, cap, 32);
+ }
+ xfer_len = 32;
+ }
+ break;
+
+ case SCSI_READ_10:
+ case SCSI_READ_16: {
+ uint64_t lba;
+ uint32_t num_blocks, byte_count;
+
+ if (scsi_cmd == SCSI_READ_10) {
+ lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 |
+ (uint64_t)cdb[4] << 8 | cdb[5];
+ num_blocks = (uint16_t)cdb[7] << 8 | cdb[8];
+ } else {
+ lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 |
+ (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 |
+ (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 |
+ (uint64_t)cdb[8] << 8 | cdb[9];
+ num_blocks = (uint32_t)cdb[10] << 24 |
+ (uint32_t)cdb[11] << 16 |
+ (uint32_t)cdb[12] << 8 | cdb[13];
+ }
+ byte_count = num_blocks * 512;
+ if (s->blk && byte_count > 0) {
+ uint8_t *rbuf = g_malloc(byte_count);
+ if (blk_pread(s->blk, lba * 512, byte_count, rbuf, 0) < 0) {
+ ok = false;
+ } else if (prdtl > 0 && xfer_buf) {
+ memcpy(xfer_buf, rbuf,
+ MIN(byte_count, xfer_len));
+ } else {
+ ufs_dma_write(resp_base + 32, rbuf, byte_count);
+ }
+ g_free(rbuf);
+ }
+ xfer_len = byte_count;
+ break;
+ }
+
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_16: {
+ uint64_t lba;
+ uint32_t num_blocks, byte_count;
+
+ if (scsi_cmd == SCSI_WRITE_10) {
+ lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 |
+ (uint64_t)cdb[4] << 8 | cdb[5];
+ num_blocks = (uint16_t)cdb[7] << 8 | cdb[8];
+ } else {
+ lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 |
+ (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 |
+ (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 |
+ (uint64_t)cdb[8] << 8 | cdb[9];
+ num_blocks = (uint32_t)cdb[10] << 24 |
+ (uint32_t)cdb[11] << 16 |
+ (uint32_t)cdb[12] << 8 | cdb[13];
+ }
+ byte_count = num_blocks * 512;
+ if (s->blk && byte_count > 0 && prdtl > 0 && xfer_buf) {
+ uint32_t copied = 0, i;
+ for (i = 0; i < prdtl && copied < byte_count; i++) {
+ PrdtEntry pe;
+ uint64_t dba;
+ uint32_t chunk;
+ ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
+ &pe, sizeof(pe));
+ dba = (uint64_t)le32_to_cpu(pe.dba) |
+ ((uint64_t)le32_to_cpu(pe.dbau) << 32);
+ chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1;
+ chunk = MIN(chunk, byte_count - copied);
+ ufs_dma_read(dba, xfer_buf + copied, chunk);
+ copied += chunk;
+ }
+ if (blk_pwrite(s->blk, lba * 512, byte_count,
+ xfer_buf, 0) < 0) {
+ ok = false;
+ }
+ }
+ xfer_len = 0;
+ break;
+ }
+
+ case SCSI_REPORT_LUNS: {
+ uint8_t rl[16];
+ memset(rl, 0, sizeof(rl));
+ rl[3] = 8; /* list length = 8 (one LUN entry) */
+ if (prdtl > 0 && xfer_buf) {
+ memcpy(xfer_buf, rl, 16);
+ } else {
+ ufs_dma_write(resp_base + 32, rl, 16);
+ }
+ xfer_len = 16;
+ break;
+ }
+
+ default:
+ ok = false;
+ sense[0] = 0x70;
+ sense[2] = 0x05; /* ILLEGAL REQUEST */
+ sense[7] = 0x0a;
+ xfer_len = 0;
+ break;
+ }
+
+ /* Scatter read data back into guest PRDT buffers */
+ if (ok && prdtl > 0 && xfer_buf && xfer_len > 0) {
+ uint32_t copied = 0, i;
+ for (i = 0; i < prdtl && copied < xfer_len; i++) {
+ PrdtEntry pe;
+ uint64_t dba;
+ uint32_t chunk;
+ ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
+ &pe, sizeof(pe));
+ dba = (uint64_t)le32_to_cpu(pe.dba) |
+ ((uint64_t)le32_to_cpu(pe.dbau) << 32);
+ chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1;
+ chunk = MIN(chunk, xfer_len - copied);
+ ufs_dma_write(dba, xfer_buf + copied, chunk);
+ copied += chunk;
+ }
+ }
+
+ g_free(xfer_buf);
+
+scsi_done:
+ /* Build SCSI Response UPIU */
+ {
+ uint8_t rsp[32];
+ uint16_t seg_len = 0;
+
+ memset(rsp, 0, sizeof(rsp));
+ rsp[0] = UPIU_TYPE_RESPONSE;
+ rsp[3] = task_tag;
+ rsp[7] = ok ? 0x00 : 0x02; /* SCSI status */
+
+ /*
+ * If prdtl=0 (no scatter-gather list), the data was written
+ * directly to the response UPIU data segment; report its length
+ * in data_seg_len so the host driver knows where to find it.
+ */
+ if (ok && prdtl == 0 && xfer_len > 0) {
+ seg_len = (uint16_t)xfer_len;
+ } else if (!ok) {
+ seg_len = sizeof(sense);
+ }
+ rsp[10] = (seg_len >> 8) & 0xff;
+ rsp[11] = seg_len & 0xff;
+
+ ufs_dma_write(resp_base, rsp, 32);
+ if (!ok) {
+ ufs_dma_write(resp_base + 32, sense, sizeof(sense));
+ }
+ }
+ goto done;
+ }
+
+ qemu_log_mask(LOG_UNIMP, "aspeed-ufs: unhandled UPIU type 0x%02x slot %d\n",
+ trans_type, slot);
+
+done:
+ /* Mark OCS=SUCCESS in UTRD DW2 and write back */
+ utrd.dw2 = cpu_to_le32(OCS_SUCCESS);
+ ufs_dma_write(utrd_addr, &utrd, sizeof(utrd));
+
+ s->utrldbr &= ~BIT(slot);
+ s->regs[R(REG_UTRLDBR)] = s->utrldbr;
+
+ s->regs[R(REG_IS)] |= IS_UTRCS;
+ aspeed_ufs_update_irq(s);
+}
+
+/*
+ * Process all pending UTRL doorbell slots. Called synchronously from the
+ * write handler to complete requests before the host driver's timeout fires.
+ *
+ * Note: despite the _bh suffix this is not a QEMU BH callback. It is
+ * called inline so that U-Boot's 30 ms NOP_OUT_TIMEOUT cannot expire before
+ * a deferred callback would run.
+ */
+static void aspeed_ufs_doorbell_bh(AspeedUFSState *s)
+{
+ while (s->utrldbr) {
+ int slot = ctz32(s->utrldbr);
+ aspeed_ufs_process_slot(s, slot);
+ }
+}
+
+static uint64_t aspeed_ufs_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AspeedUFSState *s = opaque;
+
+ if (addr + size > ASPEED_UFS_MMIO_SIZE) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "aspeed-ufs: read out of range 0x%" HWADDR_PRIx "\n",
+ addr);
+ return 0;
+ }
+
+ /*
+ * HCE state machine: after a HCE=1 write the register is cleared to 0
+ * (phase 1) so the first read returns 0; the BH then sets it back to 1
+ * (phase 2) and subsequent reads return 1.
+ */
+ if (addr == REG_HCE && s->hce_phase == 1) {
+ return 0;
+ }
+
+ return s->regs[R(addr)];
+}
+
+static void aspeed_ufs_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ AspeedUFSState *s = opaque;
+
+ if (addr + size > ASPEED_UFS_MMIO_SIZE) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "aspeed-ufs: write out of range 0x%" HWADDR_PRIx "\n",
+ addr);
+ return;
+ }
+
+ s->regs[R(addr)] = (uint32_t)val;
+
+ switch (addr) {
+
+ case REG_HCE:
+ if (val & HCE_ENABLE) {
+ /*
+ * Restart the 1->0->1 toggle on every HCE=1 write, including
+ * when Linux re-enables the controller after U-Boot.
+ */
+ if (s->hce_phase != 2) {
+ s->regs[R(REG_HCE)] = 0;
+ s->hce_phase = 1;
+ qemu_bh_schedule(s->hce_bh);
+ }
+ } else {
+ s->regs[R(REG_HCE)] = 0;
+ s->regs[R(REG_HCS)] = 0;
+ s->hce_phase = 0;
+ }
+ break;
+
+ case REG_UICCMD: {
+ uint8_t opcode = (uint8_t)(val & 0xff);
+
+ s->regs[R(REG_UCMDARG2)] = UIC_CMD_RESULT_OK;
+ s->regs[R(REG_IS)] |= IS_UCCS;
+
+ if (opcode == DME_LINKSTARTUP) {
+ s->regs[R(REG_IS)] |= IS_UIC_LINK_STARTUP;
+ s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY;
+ }
+
+ /*
+ * Power mode changes (DME_SET / DME_PEER_SET): set HCS[10:8] to
+ * PWR_LOCAL and raise IS_UIC_POWER_MODE so ufshcd_uic_pwr_ctrl()
+ * does not time out waiting for the completion interrupt.
+ */
+ if (opcode == DME_SET || opcode == DME_PEER_SET) {
+ s->regs[R(REG_HCS)] |= HCS_PWR_LOCAL;
+ s->regs[R(REG_IS)] |= IS_UIC_POWER_MODE;
+ }
+
+ /*
+ * DME_GET / DME_PEER_GET: return plausible gear/lane values so
+ * ufshcd_get_max_pwr_mode() does not fail with rx=0, tx=0.
+ */
+ if (opcode == DME_GET || opcode == DME_PEER_GET) {
+ s->regs[R(REG_UCMDARG3)] = 0x00000003;
+ }
+
+ aspeed_ufs_update_irq(s);
+ break;
+ }
+
+ case REG_IS:
+ /* W1C: write-1-to-clear */
+ s->regs[R(REG_IS)] &= ~(uint32_t)val;
+ aspeed_ufs_update_irq(s);
+ break;
+
+ case REG_IE:
+ aspeed_ufs_update_irq(s);
+ break;
+
+ case REG_UTRLBA:
+ s->utrl_base = (s->utrl_base & 0xffffffff00000000ULL) |
+ ((uint64_t)(val & 0xfffffc00));
+ break;
+
+ case REG_UTRLBAU:
+ s->utrl_base = (s->utrl_base & 0x00000000ffffffffULL) |
+ ((uint64_t)val << 32);
+ break;
+
+ case REG_UTMRLBA:
+ s->utmrl_base = (s->utmrl_base & 0xffffffff00000000ULL) |
+ ((uint64_t)(val & 0xfffffc00));
+ break;
+
+ case REG_UTMRLBAU:
+ s->utmrl_base = (s->utmrl_base & 0x00000000ffffffffULL) |
+ ((uint64_t)val << 32);
+ break;
+
+ case REG_UTRLRSR:
+ case REG_UTMRLRSR:
+ break;
+
+ case REG_UTRLDBR:
+ s->utrldbr |= (uint32_t)val;
+ s->regs[R(REG_UTRLDBR)] = s->utrldbr;
+ aspeed_ufs_doorbell_bh(s);
+ break;
+
+ case REG_UTRLCLR:
+ s->utrldbr &= (uint32_t)val;
+ s->regs[R(REG_UTRLDBR)] = s->utrldbr;
+ break;
+
+ case REG_UTMRLDBR: {
+ /*
+ * Task management doorbell. Write OCS=SUCCESS into each requested
+ * UTMRD so ufshcd_issue_tm_cmd() reads back success, clear the
+ * doorbell, then raise IS_UTMRCS so ufshcd_tmc_handler() fires
+ * complete() on the waiting thread.
+ *
+ * UTMRD slot stride is 80 bytes (request_desc_header:16 +
+ * upiu_req:32 + upiu_rsp:32); OCS is at byte 8 of the header.
+ */
+ uint32_t dbr = (uint32_t)val;
+ while (dbr) {
+ int slot = ctz32(dbr);
+ uint64_t ocs_addr = s->utmrl_base +
+ (uint64_t)slot * UTMRD_SIZE +
+ UTMRD_OCS_OFFSET;
+ uint8_t ocs = OCS_SUCCESS;
+ ufs_dma_write(ocs_addr, &ocs, sizeof(ocs));
+ dbr &= ~(1u << slot);
+ }
+ s->regs[R(REG_UTMRLDBR)] = 0;
+ s->regs[R(REG_IS)] |= IS_UTMRCS;
+ aspeed_ufs_update_irq(s);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps aspeed_ufs_ops = {
+ .read = aspeed_ufs_read,
+ .write = aspeed_ufs_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void aspeed_ufs_reset(DeviceState *dev)
+{
+ AspeedUFSState *s = ASPEED_UFS(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ s->regs[R(REG_CAP)] = ASPEED_UFS_CAP_RESET;
+ s->regs[R(REG_VER)] = ASPEED_UFS_VER_RESET;
+ s->regs[R(REG_HCPID)] = 0x41535045; /* "ASPE" in ASCII */
+ s->regs[R(REG_HCMID)] = 0x45440000; /* "ED\0\0" in ASCII */
+
+ s->regs[R(REG_HCE)] = 0;
+ s->regs[R(REG_HCS)] = 0;
+
+ s->hce_phase = 0;
+ s->utrldbr = 0;
+ s->utrl_base = 0;
+ s->utmrl_base = 0;
+
+ qemu_set_irq(s->irq, 0);
+}
+
+static void aspeed_ufs_realize(DeviceState *dev, Error **errp)
+{
+ AspeedUFSState *s = ASPEED_UFS(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_ufs_ops, s,
+ TYPE_ASPEED_UFS, ASPEED_UFS_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->hce_bh = qemu_bh_new(aspeed_ufs_hce_bh, s);
+
+ if (s->blk) {
+ int64_t sz = blk_getlength(s->blk);
+ if (sz <= 0) {
+ error_setg(errp, "aspeed-ufs: cannot determine drive size");
+ return;
+ }
+ s->num_sectors = (uint64_t)sz / 512;
+ blk_set_dev_ops(s->blk, NULL, NULL);
+ if (blk_set_perm(s->blk,
+ BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE,
+ BLK_PERM_ALL, errp) < 0) {
+ return;
+ }
+ } else {
+ /* No drive attached; expose a minimal read-only stub. */
+ s->num_sectors = 2048;
+ }
+
+ aspeed_ufs_init_unit_desc(s);
+ aspeed_ufs_reset(dev);
+}
+
+static const VMStateDescription vmstate_aspeed_ufs = {
+ .name = TYPE_ASPEED_UFS,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedUFSState, ASPEED_UFS_NUM_REGS),
+ VMSTATE_INT32(hce_phase, AspeedUFSState),
+ VMSTATE_UINT32(utrldbr, AspeedUFSState),
+ VMSTATE_UINT64(utrl_base, AspeedUFSState),
+ VMSTATE_UINT64(utmrl_base, AspeedUFSState),
+ VMSTATE_UINT64(num_sectors, AspeedUFSState),
+ VMSTATE_UINT8_ARRAY(unit_desc, AspeedUFSState, 0x23),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const Property aspeed_ufs_properties[] = {
+ DEFINE_PROP_DRIVE("drive", AspeedUFSState, blk),
+};
+
+static void aspeed_ufs_class_init(ObjectClass *oc, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = aspeed_ufs_realize;
+ device_class_set_legacy_reset(dc, aspeed_ufs_reset);
+ dc->vmsd = &vmstate_aspeed_ufs;
+ dc->desc = "ASPEED AST2700 UFS Host Controller";
+ device_class_set_props(dc, aspeed_ufs_properties);
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo aspeed_ufs_info = {
+ .name = TYPE_ASPEED_UFS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedUFSState),
+ .class_init = aspeed_ufs_class_init,
+};
+
+static void aspeed_ufs_register_types(void)
+{
+ type_register_static(&aspeed_ufs_info);
+}
+
+type_init(aspeed_ufs_register_types)
diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build
index 6e68328b93..86052e6fd2 100644
--- a/hw/ufs/meson.build
+++ b/hw/ufs/meson.build
@@ -1 +1,2 @@
system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c'))
+system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_ufs.c'))
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 5/5] tests/functional: Add Huygens BMC boot test
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
` (3 preceding siblings ...)
2026-07-15 15:50 ` [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
@ 2026-07-15 15:50 ` Mikail Sadic
2026-07-16 14:05 ` Cédric Le Goater
2026-07-15 16:01 ` [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Glenn Miles
5 siblings, 1 reply; 15+ messages in thread
From: Mikail Sadic @ 2026-07-15 15:50 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 functional test for the huygens-bmc machine that boots from FMC
flash and a UFS storage image, verifying the full boot sequence from
U-Boot through OpenBMC login and multi-user target.
Note: The Huygens img is not yet public, so this test is for future/internal
use. It has been tested/verified with the correct image, and will gracefully
exit if image is not found.
Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
tests/functional/aarch64/meson.build | 2 +
.../functional/aarch64/test_aspeed_huygens.py | 49 +++++++++++++++++++
2 files changed, 51 insertions(+)
create mode 100755 tests/functional/aarch64/test_aspeed_huygens.py
diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
index e81afd6c39..d81c0d6761 100644
--- a/tests/functional/aarch64/meson.build
+++ b/tests/functional/aarch64/meson.build
@@ -4,6 +4,7 @@ test_aarch64_timeouts = {
'aspeed_ast2700a1' : 600,
'aspeed_ast2700a2' : 600,
'aspeed_ast2700fc' : 600,
+ 'aspeed_huygens' : 600,
'device_passthrough' : 720,
'imx8mm_evk' : 240,
'imx8mp_evk' : 240,
@@ -29,6 +30,7 @@ tests_aarch64_system_thorough = [
'aspeed_ast2700a1',
'aspeed_ast2700a2',
'aspeed_ast2700fc',
+ 'aspeed_huygens',
'device_passthrough',
'hotplug_pci',
'imx8mm_evk',
diff --git a/tests/functional/aarch64/test_aspeed_huygens.py b/tests/functional/aarch64/test_aspeed_huygens.py
new file mode 100755
index 0000000000..750d6e0d7f
--- /dev/null
+++ b/tests/functional/aarch64/test_aspeed_huygens.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots the ASPEED Huygens machine with UFS storage
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+
+from aspeed import AspeedTest
+
+class HuygensMachine(AspeedTest):
+
+ def test_arm_aspeed_ufs_boot(self):
+ self.set_machine('huygens-bmc')
+ self.require_netdev('user')
+
+ # Huygens image is not public yet, skip if missing
+ fmc_path = '/tmp/fmc-huygens.img'
+ ufs_path = '/tmp/ufs-huygens.img'
+
+ if not os.path.exists(fmc_path) or not os.path.exists(ufs_path):
+ self.skipTest(f'Missing required images: {fmc_path}, {ufs_path}')
+
+ self.vm.set_console()
+ self.vm.add_args('-drive',
+ 'file=' + fmc_path + ',if=mtd,format=raw',
+ '-drive',
+ 'file=' + ufs_path +
+ ',if=none,format=raw,readonly=off',
+ '-nic', 'user,model=ftgmac100,net=10.0.2.0/24',
+ '-nic', 'user,model=ftgmac100,net=10.0.3.0/24',
+ '-nic', 'user,model=ftgmac100,net=10.0.4.0/24')
+ self.vm.launch()
+
+ self.wait_for_console_pattern('U-Boot 2023.10')
+ self.wait_for_console_pattern(
+ 'Vendor: ASPEED Prod.: UFS QEMU Rev: 1.00')
+ self.wait_for_console_pattern('Starting kernel ...')
+ self.wait_for_console_pattern('Machine model: Huygens')
+ self.wait_for_console_pattern('Starting systemd-udevd version 257.1')
+ self.wait_for_console_pattern('Phosphor OpenBMC')
+ self.wait_for_console_pattern('huygens login:')
+ self.wait_for_console_pattern('Active BMC Target')
+ self.wait_for_console_pattern('Multi-User System')
+ self.vm.shutdown()
+
+
+if __name__ == '__main__':
+ AspeedTest.main()
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
` (4 preceding siblings ...)
2026-07-15 15:50 ` [PATCH v1 5/5] tests/functional: Add Huygens BMC boot test Mikail Sadic
@ 2026-07-15 16:01 ` Glenn Miles
2026-07-15 17:08 ` CALEB SCHLOSSIN
5 siblings, 1 reply; 15+ messages in thread
From: Glenn Miles @ 2026-07-15 16:01 UTC (permalink / raw)
To: Mikail Sadic, clg@kaod.org, peter.maydell@linaro.org
Cc: Paolo Bonzini, ninad@linux.ibm.com, titusr@google.com,
jeuk20.kim@samsung.com, philmd@mailo.com,
steven_lee@aspeedtech.com, leetroy@gmail.com,
jamin_lin@aspeedtech.com, kane_chen@aspeedtech.com,
andrew@codeconstruct.com.au, joel@jms.id.au, CALEB SCHLOSSIN,
qemu-arm@nongnu.org, qemu-devel@nongnu.org
[-- Attachment #1: Type: text/plain, Size: 4369 bytes --]
Hi Mikail,
Could you use my milesg@linux.ibm.com email address for patches please?
Thanks,
Glenn
________________________________
From: Mikail Sadic <mikail.sadic@ibm.com>
Sent: Wednesday, July 15, 2026 10:50 AM
To: clg@kaod.org <clg@kaod.org>; peter.maydell@linaro.org <peter.maydell@linaro.org>
Cc: Mikail Sadic <Mikail.Sadic@ibm.com>; Paolo Bonzini <pbonzini@redhat.com>; ninad@linux.ibm.com <ninad@linux.ibm.com>; titusr@google.com <titusr@google.com>; jeuk20.kim@samsung.com <jeuk20.kim@samsung.com>; philmd@mailo.com <philmd@mailo.com>; steven_lee@aspeedtech.com <steven_lee@aspeedtech.com>; leetroy@gmail.com <leetroy@gmail.com>; jamin_lin@aspeedtech.com <jamin_lin@aspeedtech.com>; kane_chen@aspeedtech.com <kane_chen@aspeedtech.com>; andrew@codeconstruct.com.au <andrew@codeconstruct.com.au>; joel@jms.id.au <joel@jms.id.au>; CALEB SCHLOSSIN <calebs@us.ibm.com>; Glenn Miles <milesg@ibm.com>; qemu-arm@nongnu.org <qemu-arm@nongnu.org>; qemu-devel@nongnu.org <qemu-devel@nongnu.org>
Subject: [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700
This series adds initial support for the IBM Huygens BMC platform based
on the ASPEED AST2700 A2 SoC.
The series introduces a new Huygens machine model, adds a dedicated
CFAM-S implementation for the AST2700 FSI responder framework, provides
a PMBus model for the TI UCD90320 power sequencer, and implements the
AST2700 UFS host controller required for booting from UFS storage.
It also includes an initial functional test covering the Huygens boot
flow.
Patch summary:
1. Add the AST2700 Huygens machine.
2. Add the CFAM-S model for AST2700.
3. Add the TI UCD90320 PMBus device model.
4. Add the AST2700 UFS host controller.
5. Add an initial Huygens functional boot test.
Testing done:
* Booted OpenBMC using UFS storage.
* Verified OpenBMC reaches the login prompt & Active/Ready state.
* Verified `make check` test cases all pass.
Note: checkpatch has some warns about MAINTAINERS for patches 1, 2,
and 5, but the new files should already be covered by existing
wildcard entries in the ASPEED BMCs and FSI sections.
Mikail Sadic (5):
arm/aspeed: Add AST2700 Huygens machine
fsi/cfam: Add CFAM-S model for AST2700
hw/sensor: Add UCD90320 model
ufs/aspeed: Add AST2700 UFS host controller
tests/functional: Add Huygens BMC boot test
MAINTAINERS | 2 +
docs/specs/aspeed-ufs.rst | 69 ++
docs/specs/fsi.rst | 58 +
docs/specs/index.rst | 2 +
docs/specs/ucd90320.rst | 36 +
docs/system/arm/aspeed.rst | 41 +-
include/hw/arm/aspeed_soc.h | 3 +
include/hw/fsi/cfam-s.h | 26 +
include/hw/fsi/fsi-master.h | 2 +
include/hw/ufs/aspeed_ufs.h | 66 +
hw/arm/aspeed.c | 12 +
hw/arm/aspeed_ast27x0.c | 31 +
hw/arm/aspeed_ast27x0_huygens.c | 242 ++++
hw/fsi/cfam-s.c | 145 +++
hw/fsi/fsi-master.c | 10 +-
hw/i2c/aspeed_i2c.c | 7 +
hw/sensor/ucd90320.c | 144 +++
hw/ufs/aspeed_ufs.c | 1095 +++++++++++++++++
hw/arm/Kconfig | 1 +
hw/arm/meson.build | 1 +
hw/fsi/meson.build | 2 +-
hw/sensor/Kconfig | 4 +
hw/sensor/meson.build | 1 +
hw/ufs/meson.build | 1 +
tests/functional/aarch64/meson.build | 2 +
.../functional/aarch64/test_aspeed_huygens.py | 49 +
26 files changed, 2047 insertions(+), 5 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
create mode 100755 tests/functional/aarch64/test_aspeed_huygens.py
--
2.53.0
[-- Attachment #2: Type: text/html, Size: 10107 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700
2026-07-15 16:01 ` [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Glenn Miles
@ 2026-07-15 17:08 ` CALEB SCHLOSSIN
0 siblings, 0 replies; 15+ messages in thread
From: CALEB SCHLOSSIN @ 2026-07-15 17:08 UTC (permalink / raw)
To: Glenn Miles, Mikail Sadic, clg@kaod.org, peter.maydell@linaro.org
Cc: Paolo Bonzini, ninad@linux.ibm.com, titusr@google.com,
jeuk20.kim@samsung.com, philmd@mailo.com,
steven_lee@aspeedtech.com, leetroy@gmail.com,
jamin_lin@aspeedtech.com, kane_chen@aspeedtech.com,
andrew@codeconstruct.com.au, joel@jms.id.au, qemu-arm@nongnu.org,
qemu-devel@nongnu.org
[-- Attachment #1: Type: text/plain, Size: 5519 bytes --]
Mikail,
Same for me, please use calebs@linux.ibm.com.
Thanks,
Caleb Schlossin
----------------------------------------
Power Simulation Architect
IBM Rochester, MN
----------------------------------------
________________________________
From: Glenn Miles <milesg@ibm.com>
Sent: Wednesday, July 15, 2026 11:01 AM
To: Mikail Sadic <Mikail.Sadic@ibm.com>; clg@kaod.org <clg@kaod.org>; peter.maydell@linaro.org <peter.maydell@linaro.org>
Cc: Paolo Bonzini <pbonzini@redhat.com>; ninad@linux.ibm.com <ninad@linux.ibm.com>; titusr@google.com <titusr@google.com>; jeuk20.kim@samsung.com <jeuk20.kim@samsung.com>; philmd@mailo.com <philmd@mailo.com>; steven_lee@aspeedtech.com <steven_lee@aspeedtech.com>; leetroy@gmail.com <leetroy@gmail.com>; jamin_lin@aspeedtech.com <jamin_lin@aspeedtech.com>; kane_chen@aspeedtech.com <kane_chen@aspeedtech.com>; andrew@codeconstruct.com.au <andrew@codeconstruct.com.au>; joel@jms.id.au <joel@jms.id.au>; CALEB SCHLOSSIN <calebs@us.ibm.com>; qemu-arm@nongnu.org <qemu-arm@nongnu.org>; qemu-devel@nongnu.org <qemu-devel@nongnu.org>
Subject: Re: [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700
Hi Mikail,
Could you use my milesg@linux.ibm.com email address for patches please?
Thanks,
Glenn
________________________________
From: Mikail Sadic <mikail.sadic@ibm.com>
Sent: Wednesday, July 15, 2026 10:50 AM
To: clg@kaod.org <clg@kaod.org>; peter.maydell@linaro.org <peter.maydell@linaro.org>
Cc: Mikail Sadic <Mikail.Sadic@ibm.com>; Paolo Bonzini <pbonzini@redhat.com>; ninad@linux.ibm.com <ninad@linux.ibm.com>; titusr@google.com <titusr@google.com>; jeuk20.kim@samsung.com <jeuk20.kim@samsung.com>; philmd@mailo.com <philmd@mailo.com>; steven_lee@aspeedtech.com <steven_lee@aspeedtech.com>; leetroy@gmail.com <leetroy@gmail.com>; jamin_lin@aspeedtech.com <jamin_lin@aspeedtech.com>; kane_chen@aspeedtech.com <kane_chen@aspeedtech.com>; andrew@codeconstruct.com.au <andrew@codeconstruct.com.au>; joel@jms.id.au <joel@jms.id.au>; CALEB SCHLOSSIN <calebs@us.ibm.com>; Glenn Miles <milesg@ibm.com>; qemu-arm@nongnu.org <qemu-arm@nongnu.org>; qemu-devel@nongnu.org <qemu-devel@nongnu.org>
Subject: [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700
This series adds initial support for the IBM Huygens BMC platform based
on the ASPEED AST2700 A2 SoC.
The series introduces a new Huygens machine model, adds a dedicated
CFAM-S implementation for the AST2700 FSI responder framework, provides
a PMBus model for the TI UCD90320 power sequencer, and implements the
AST2700 UFS host controller required for booting from UFS storage.
It also includes an initial functional test covering the Huygens boot
flow.
Patch summary:
1. Add the AST2700 Huygens machine.
2. Add the CFAM-S model for AST2700.
3. Add the TI UCD90320 PMBus device model.
4. Add the AST2700 UFS host controller.
5. Add an initial Huygens functional boot test.
Testing done:
* Booted OpenBMC using UFS storage.
* Verified OpenBMC reaches the login prompt & Active/Ready state.
* Verified `make check` test cases all pass.
Note: checkpatch has some warns about MAINTAINERS for patches 1, 2,
and 5, but the new files should already be covered by existing
wildcard entries in the ASPEED BMCs and FSI sections.
Mikail Sadic (5):
arm/aspeed: Add AST2700 Huygens machine
fsi/cfam: Add CFAM-S model for AST2700
hw/sensor: Add UCD90320 model
ufs/aspeed: Add AST2700 UFS host controller
tests/functional: Add Huygens BMC boot test
MAINTAINERS | 2 +
docs/specs/aspeed-ufs.rst | 69 ++
docs/specs/fsi.rst | 58 +
docs/specs/index.rst | 2 +
docs/specs/ucd90320.rst | 36 +
docs/system/arm/aspeed.rst | 41 +-
include/hw/arm/aspeed_soc.h | 3 +
include/hw/fsi/cfam-s.h | 26 +
include/hw/fsi/fsi-master.h | 2 +
include/hw/ufs/aspeed_ufs.h | 66 +
hw/arm/aspeed.c | 12 +
hw/arm/aspeed_ast27x0.c | 31 +
hw/arm/aspeed_ast27x0_huygens.c | 242 ++++
hw/fsi/cfam-s.c | 145 +++
hw/fsi/fsi-master.c | 10 +-
hw/i2c/aspeed_i2c.c | 7 +
hw/sensor/ucd90320.c | 144 +++
hw/ufs/aspeed_ufs.c | 1095 +++++++++++++++++
hw/arm/Kconfig | 1 +
hw/arm/meson.build | 1 +
hw/fsi/meson.build | 2 +-
hw/sensor/Kconfig | 4 +
hw/sensor/meson.build | 1 +
hw/ufs/meson.build | 1 +
tests/functional/aarch64/meson.build | 2 +
.../functional/aarch64/test_aspeed_huygens.py | 49 +
26 files changed, 2047 insertions(+), 5 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
create mode 100755 tests/functional/aarch64/test_aspeed_huygens.py
--
2.53.0
[-- Attachment #2: Type: text/html, Size: 13064 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller
2026-07-15 15:50 ` [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
@ 2026-07-16 0:31 ` Jeuk Kim
2026-07-16 0:48 ` Jamin Lin
1 sibling, 0 replies; 15+ messages in thread
From: Jeuk Kim @ 2026-07-16 0:31 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
On Wed, 15 Jul 2026 10:50:35 -0500, Mikail Sadic <mikail.sadic@ibm.com> wrote:
> Add an emulation of the AST2700 UFS host controller
> (aspeed,ufshc-m31-16nm) and wire it into the AST2700 SoC.
> Implement the functionality needed by U-Boot and Linux to enumerate the
> device and access a BlockBackend using the UFS protocol, allowing the
> AST2700 machine to boot from a UFS image.
>
Hi Mikail,
This patch duplicates a large amount of logic that already exists in
hw/ufs/. aspeed_ufs.c reimplements the UFSHCI register interface,
UTRL and doorbell handling, UPIU processing, Query handling, and SCSI
command handling, much of which is already implemented in hw/ufs/ufs.c
and hw/ufs/lu.c. It also introduces register and UPIU/UTRD definitions
that overlap with definitions already present in include/block/ufs.h.
For these reasons, I don't think we should add a second independent UFS
implementation under hw/ufs/. This needs to be based on a shared UFS
core instead.
If the motivation for the separate implementation is that the existing
UFS device, TYPE_UFS, is a PCI device, the solution should be to
refactor the existing implementation rather than reimplement it. The
common core, including the register block, UTRL, UPIU, Query, and
ufs-lu command handling, should be separated from the PCI-specific
glue so that both PCI and sysbus frontends can be built on top of it.
The PCI coupling in ufs.c appears to be relatively thin: DMA access,
interrupt handling, and PCI realization and BAR setup. QEMU already has
well-established examples of this structure, such as SDHCI, with a
common core and PCI/sysbus frontends, and XHCI, with hcd-xhci.c shared
by its PCI and sysbus wrappers.
The ASPEED controller should then be implemented as a sysbus frontend
on top of the shared UFS core, with only the ASPEED-specific integration
kept in the frontend.
Thanks,
--
Jeuk Kim <jeuk20.kim@samsung.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* RE: [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller
2026-07-15 15:50 ` [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
2026-07-16 0:31 ` Jeuk Kim
@ 2026-07-16 0:48 ` Jamin Lin
1 sibling, 0 replies; 15+ messages in thread
From: Jamin Lin @ 2026-07-16 0:48 UTC (permalink / raw)
To: Mikail Sadic, clg@kaod.org, 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@us.ibm.com, milesg@ibm.com,
qemu-arm@nongnu.org, qemu-devel@nongnu.org
Hi Mikail,
Just a reminder to follow the latest coding style.
> Subject: [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller
>
> Add an emulation of the AST2700 UFS host controller
> (aspeed,ufshc-m31-16nm) and wire it into the AST2700 SoC.
> Implement the functionality needed by U-Boot and Linux to enumerate the
> device and access a BlockBackend using the UFS protocol, allowing the
> AST2700 machine to boot from a UFS image.
>
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
> MAINTAINERS | 1 +
> docs/specs/aspeed-ufs.rst | 69 +++
> docs/specs/index.rst | 1 +
> include/hw/arm/aspeed_soc.h | 3 +
> include/hw/ufs/aspeed_ufs.h | 66 +++
> hw/arm/aspeed.c | 12 +
> hw/arm/aspeed_ast27x0.c | 13 +
> hw/ufs/aspeed_ufs.c | 1095
> +++++++++++++++++++++++++++++++++++
> hw/ufs/meson.build | 1 +
> 9 files changed, 1261 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 b2861dfc79..d35c29c58d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2696,6 +2696,7 @@ 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: 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..f4eb6f1ce6
> --- /dev/null
> +++ b/docs/specs/aspeed-ufs.rst
> @@ -0,0 +1,69 @@
> +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 a sysbus device implementing the UFSHCI v2.0
> +register interface required by U-Boot and the Linux ``aspeed-ufshcd`` driver.
> +
> +The clock/reset wrapper at ``0x12c08000`` (``aspeed,ast2700-ufscnr``)
> +is left as an ``UnimplementedDevice``.
> +
> +Implemented functionality
> +--------------------------
> +
> +Host Controller Enable (HCE)
> + On a ``HCE=1`` write the register is cleared to 0 and a bottom-half
> +is
> + scheduled to set it back to 1 with ``HCS`` ready bits. The read
> +handler
> + returns 0 while in phase 1, so polling sees the 0 to 1 transition
> +without
> + a timeout. If the controller is already in the ready state (phase 2),
> +a
> + subsequent ``HCE=1`` write is a no-op; the controller stays enabled
> +for
> + Linux after U-Boot has left it running.
> +
> +UIC commands
> + ``DME_LINKSTARTUP``, ``DME_GET``, ``DME_PEER_GET``, ``DME_SET``,
> and
> + ``DME_PEER_SET`` all succeed immediately. ``DME_LINKSTARTUP`` sets
> + ``HCS.DP`` and raises ``IS.UIC_LINK_STARTUP`` (bit 8). ``DME_SET``
> +and
> + ``DME_PEER_SET`` set ``HCS[10:8] = PWR_LOCAL`` and raise
> + ``IS.UIC_POWER_MODE``.
> +
> +NOP OUT / NOP IN
> + Doorbell processing is synchronous (inline) rather than deferred, as
> + U-Boot's ``NOP_OUT_TIMEOUT`` is shorter than a deferred bottom-half
> +can
> + fire in QEMU's event loop.
> +
> +SCSI block I/O
> + READ_10, READ_16, WRITE_10, WRITE_16, INQUIRY,
> READ_CAPACITY_10/16,
> +and
> + REPORT_LUNS are forwarded to the attached ``BlockBackend``. When
> + ``prdtl = 0`` (no scatter-gather list), response data is placed in
> +the
> + Response UPIU data segment; when ``prdtl > 0`` it is written into the
> +PRDT
> + buffers.
> +
> +Query UPIU descriptors
> + Device, Geometry, Unit, Power (idn 8), and String (idn 5) descriptors
> +are
> + returned, capped to the requested length.
> +
> +Well-Known LUNs
> + All four W-LUNs (0xD0 UFS Device, 0xC4 RPMB, 0xB0 Boot, 0x81 Report
> +LUNs)
> + respond as present devices.
> +
> +Task management (UTMRL)
> + ``LOGICAL_UNIT_RESET`` commands are acknowledged immediately by
> +writing
> + ``OCS_SUCCESS`` into the UTMRD slot, clearing the doorbell, and
> +raising
> + ``IS.UTP_TASK_REQ_COMPL`` (bit 9).
> +
> +Usage
> +-----
> +
> +The UFS controller picks up the first ``IF_NONE`` block device at index 0.
> +Pass a UFS disk image with ``-drive if=none``:
> +
> +.. code-block:: console
> +
> + qemu-system-aarch64 -M huygens-bmc \
> + -drive file=image-bmc,if=mtd,format=raw \
> + -drive file=ufs.img,if=none,format=raw \
> + -nographic
> +
> +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_soc.h b/include/hw/arm/aspeed_soc.h
> index 41dc04e293..28ca97d35c 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];
> + AspeedUFSState 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..6d0aa345e9
> --- /dev/null
> +++ b/include/hw/ufs/aspeed_ufs.h
> @@ -0,0 +1,66 @@
> +/*
> + * 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/core/sysbus.h"
> +#include "block/ufs.h"
> +#include "system/block-backend.h"
> +
> +#define TYPE_ASPEED_UFS "aspeed-ufs"
> +OBJECT_DECLARE_SIMPLE_TYPE(AspeedUFSState, ASPEED_UFS)
> +
> +/* UFSHCI register space is 256 bytes (0x00-0xFF) */ #define
> +ASPEED_UFS_MMIO_SIZE 0x100
> +#define ASPEED_UFS_NUM_REGS (ASPEED_UFS_MMIO_SIZE /
> sizeof(uint32_t))
> +
> +/* Number of UTP Transfer Request slots advertised */
> +#define ASPEED_UFS_NUTRS 32
> +/* Number of UTP Task Management Request slots */
> +#define ASPEED_UFS_NUTMRS 8
> +
> +/*
> + * Reset / power-on values.
> + *
> + * CAP: NUTRS=31 (0x1f, 5-bit), RTT=2, NUTMRS=7, 64AS=1
> + * VER: UFSHCI 2.0 (matches aspeed,ufshc-m31-16nm)
> + * HCS: DP|UTRLRDY|UTMRLRDY|UCRDY (bits 0-3) */ #define
> +ASPEED_UFS_CAP_RESET 0x0702031f #define ASPEED_UFS_VER_RESET
> +0x00000200 #define ASPEED_UFS_HCS_READY 0x0000000f
> +
> +struct AspeedUFSState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + qemu_irq irq;
> +
> + BlockBackend *blk;
> + uint64_t num_sectors;
> +
> + uint32_t regs[ASPEED_UFS_NUM_REGS];
> +
> + /*
> + * HCE state machine phase:
> + * 0 = idle (HCE=0)
> + * 1 = cleared (firmware wrote 1, we stored 0, BH pending)
> + * 2 = ready (BH fired, HCE=1, HCS ready bits set)
> + */
> + int hce_phase;
> + QEMUBH *hce_bh;
> +
> + uint32_t utrldbr; /* pending doorbell bits */
> + uint64_t utrl_base; /* 64-bit physical base of UTRL */
> + uint64_t utmrl_base; /* 64-bit physical base of UTMRL */
> +
> + /* Unit Descriptor; capacity filled at realize time */
> + uint8_t unit_desc[0x23];
> +};
> +
> +#endif /* ASPEED_UFS_H */
> diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index
> a48c442058..1f57f32ad4 100644
> --- a/hw/arm/aspeed.c
> +++ b/hw/arm/aspeed.c
> @@ -184,6 +184,18 @@ static void aspeed_machine_init(MachineState
> *machine)
> object_property_set_int(OBJECT(bmc->soc), "hw-prot-key",
> ASPEED_SCU_PROT_KEY,
> &error_abort);
> }
> + /*
> + * Attach the UFS backing drive before realizing the SoC so that
> + * the UFS controller can read the drive size during realization.
> + */
> + {
> + DriveInfo *ufs_dinfo = drive_get(IF_NONE, 0, 0);
> + if (ufs_dinfo) {
> + qdev_prop_set_drive_err(DEVICE(&bmc->soc->ufs), "drive",
> + blk_by_legacy_dinfo(ufs_dinfo),
> + &error_fatal);
> + }
> + }
> aspeed_connect_serial_hds_to_uarts(bmc);
> qdev_realize(DEVICE(bmc->soc), NULL, &error_abort);
>
> diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index
> b908d7d4ff..04aa4e15a5 100644
> --- a/hw/arm/aspeed_ast27x0.c
> +++ b/hw/arm/aspeed_ast27x0.c
> @@ -39,6 +39,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
> [ASPEED_DEV_EHCI2] = 0x12063000,
> [ASPEED_DEV_HACE] = 0x12070000,
> [ASPEED_DEV_EMMC] = 0x12090000,
> + [ASPEED_DEV_UFS] = 0x12c08200,
Please order the entries by Register address.
> [ASPEED_DEV_PCIE0] = 0x120E0000,
> [ASPEED_DEV_PCIE1] = 0x120F0000,
> [ASPEED_DEV_INTC] = 0x12100000,
> @@ -116,6 +117,7 @@ static const int aspeed_soc_ast2700a1_irqmap[] = {
> [ASPEED_DEV_SCU] = 12,
> [ASPEED_DEV_RTC] = 13,
> [ASPEED_DEV_EMMC] = 15,
> + [ASPEED_DEV_UFS] = 118,
Please order the entries by IRQ value.
> [ASPEED_DEV_TIMER1] = 16,
> [ASPEED_DEV_TIMER2] = 17,
> [ASPEED_DEV_TIMER3] = 18,
> @@ -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..673aced63b
> --- /dev/null
> +++ b/hw/ufs/aspeed_ufs.c
> @@ -0,0 +1,1095 @@
> +/*
> + * ASPEED AST2700 UFS Host Controller
> + *
> + * Sysbus device mapped at 0x12c08200 (256 bytes). Implements the
> +UFSHCI
> + * register interface required by U-Boot ufshcd_probe() and Linux
> + * aspeed-ufshcd, including UIC command handling, NOP OUT/IN, SCSI
> +block I/O,
> + * Query UPIU descriptor reads, W-LUN support, and UTMRL task
> management.
> + *
> + * Register offsets follow JEDEC UFSHCI spec v2.0, matching the REG_*
> + * definitions in include/block/ufs.h.
> + *
> + * The ASPEED clock/reset wrapper at 0x12c08000 (aspeed,ast2700-ufscnr)
> +is
> + * left as an UnimplementedDevice; writes to MPHY registers there
> +require no
> + * readback.
> + *
> + * Copyright 2026 IBM Corp.
> + * SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/qdev-properties-system.h"
> +#include "hw/core/irq.h"
> +#include "migration/vmstate.h"
> +#include "system/block-backend.h"
> +#include "system/block-backend-io.h"
> +#include "system/dma.h"
> +#include "system/address-spaces.h"
> +#include "hw/ufs/aspeed_ufs.h"
> +
> +/* UFSHCI register offsets (JEDEC UFSHCI spec v2.0) */
> +#define REG_CAP 0x00
> +#define REG_VER 0x08
> +#define REG_HCPID 0x10
> +#define REG_HCMID 0x14
> +#define REG_AHIT 0x18
> +#define REG_IS 0x20
> +#define REG_IE 0x24
> +#define REG_HCS 0x30
> +#define REG_HCE 0x34
REG32(REG_HCE, 0x34) ?
FIELD(REG_HCE, HCE_ENABLE, 0, 1) ?
> +#define REG_UECPA 0x38
> +#define REG_UECDL 0x3c
> +#define REG_UECN 0x40
> +#define REG_UECT 0x44
> +#define REG_UECDME 0x48
> +#define REG_UTRLBA 0x50
> +#define REG_UTRLBAU 0x54
> +#define REG_UTRLDBR 0x58
> +#define REG_UTRLCLR 0x5c
> +#define REG_UTRLRSR 0x60
> +#define REG_UTMRLBA 0x70
> +#define REG_UTMRLBAU 0x74
> +#define REG_UTMRLDBR 0x78
> +#define REG_UTMRLCLR 0x7c
> +#define REG_UTMRLRSR 0x80
> +#define REG_UICCMD 0x90
> +#define REG_UCMDARG1 0x94
> +#define REG_UCMDARG2 0x98
> +#define REG_UCMDARG3 0x9c
> +
> +/* HCE / HCS bits */
> +#define HCE_ENABLE BIT(0)
> +#define HCS_DP BIT(0)
> +#define HCS_UTRLRDY BIT(1)
> +#define HCS_UTMRLRDY BIT(2)
> +#define HCS_UCRDY BIT(3)
> +
> +/* IS bits (UFSHCI spec sec. 5.3) */
> +#define IS_UTRCS BIT(0) /* UTP Transfer Request
> Completion */
> +#define IS_UIC_POWER_MODE BIT(4) /* UIC Power Mode Status */
> +#define IS_UIC_LINK_STARTUP BIT(8) /* UIC Link Startup Status */
> +#define IS_UTMRCS BIT(9) /* UTP Task Mgmt Request
> Completion */
> +#define IS_UCCS BIT(10) /* UIC Command Completion
> Status */
> +
> +/* UIC command opcodes */
> +#define DME_LINKSTARTUP 0x16
> +#define DME_GET 0x01
> +#define DME_SET 0x02
> +#define DME_PEER_GET 0x03
> +#define DME_PEER_SET 0x04
> +
> +/* UIC command result (success) in UCMDARG2[7:0] */
> +#define UIC_CMD_RESULT_OK 0x00
> +
> +/* HCS power-mode state field bits [10:8]: PWR_LOCAL = 1 */
> +#define HCS_PWR_LOCAL (1 << 8)
> +
> +/* Register array index helper */
> +#define R(off) ((off) / sizeof(uint32_t))
> +
> +/*
> + * UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1).
> + */
> +#define UTRD_SIZE 32 /* bytes per UTRL slot */
> +
> +/*
> + * UTMRL slot size (UTP Task Management Request List):
> + * - header : 16 bytes
> + * - upiu_req : 32 bytes
> + * - upiu_rsp : 32 bytes
> + */
> +#define UTMRD_SIZE 80
> +
> +/* byte offset of OCS within request_desc_header */ #define
> +UTMRD_OCS_OFFSET 8
> +
> +/* Command Type in UTRD DW0[29:28] */
> +#define CT_UFS_STORAGE 0x1
> +
> +/* Data Direction in UTRD DW0[25:24] */
> +#define DD_NO_DATA 0x0
> +#define DD_HOST_TO_DEV 0x2
> +#define DD_DEV_TO_HOST 0x4
> +
> +/* OCS in UTRD DW2[7:0] */
> +#define OCS_SUCCESS 0x0
> +
> +/* UPIU transaction types */
> +#define UPIU_TYPE_NOP_OUT 0x00
> +#define UPIU_TYPE_CMD 0x01
> +#define UPIU_TYPE_QUERY_REQ 0x16
> +#define UPIU_TYPE_NOP_IN 0x20
> +#define UPIU_TYPE_RESPONSE 0x21
> +#define UPIU_TYPE_QUERY_RSP 0x36
> +
> +/* Query opcodes */
> +#define QUERY_OP_READ_DESC 0x01
> +#define QUERY_OP_WRITE_DESC 0x02
> +#define QUERY_OP_READ_ATTR 0x03
> +#define QUERY_OP_WRITE_ATTR 0x04
> +#define QUERY_OP_READ_FLAG 0x05
> +#define QUERY_OP_SET_FLAG 0x06
> +#define QUERY_OP_CLEAR_FLAG 0x07
> +
> +/* Descriptor idn */
> +#define DESC_IDN_DEVICE 0x00
> +#define DESC_IDN_CONFIGURATION 0x01
> +#define DESC_IDN_UNIT 0x02
> +#define DESC_IDN_GEOMETRY 0x07
> +#define DESC_IDN_POWER 0x08
> +#define DESC_IDN_STRING 0x05
> +
> +/* SCSI commands */
> +#define SCSI_TEST_UNIT_READY 0x00
> +#define SCSI_INQUIRY 0x12
> +#define SCSI_READ_CAPACITY_10 0x25
> +#define SCSI_READ_10 0x28
> +#define SCSI_WRITE_10 0x2a
> +#define SCSI_READ_16 0x88
> +#define SCSI_WRITE_16 0x8a
> +#define SCSI_READ_CAPACITY_16 0x9e /* service action 0x10 */
> +#define SCSI_REPORT_LUNS 0xa0
> +
> +/*
> + * Well-Known LUN addresses probed by ufshcd_scsi_add_wlus().
> + * All four must appear as present devices or async_scan fails.
> + */
> +#define WLUN_REPORT_LUNS 0x81
> +#define WLUN_UFS_DEVICE 0xd0
> +#define WLUN_BOOT 0xb0
> +#define WLUN_RPMB 0xc4
> +
> +static bool aspeed_ufs_is_wlun(uint8_t lun) {
> + return lun == WLUN_UFS_DEVICE || lun == WLUN_RPMB ||
> + lun == WLUN_BOOT || lun == WLUN_REPORT_LUNS;
> +}
> +
> +static void ufs_dma_read(hwaddr paddr, void *buf, size_t len) {
> + dma_memory_read(&address_space_memory, paddr, buf, len,
> + MEMTXATTRS_UNSPECIFIED); }
> +
> +static void ufs_dma_write(hwaddr paddr, const void *buf, size_t len) {
> + dma_memory_write(&address_space_memory, paddr, buf, len,
> + MEMTXATTRS_UNSPECIFIED); }
> +
> +static void aspeed_ufs_update_irq(AspeedUFSState *s) {
> + uint32_t is = s->regs[R(REG_IS)];
> + uint32_t ie = s->regs[R(REG_IE)];
> +
> + qemu_set_irq(s->irq, !!(is & ie));
> +}
> +
> +/*
> + * HCE bottom-half: completes the 1->0->1 toggle that the host driver polls.
> + *
> + * On any HCE=1 write the write handler clears HCE to 0 and schedules
> +this
> + * BH. Once it fires the controller is considered ready.
> + */
> +static void aspeed_ufs_hce_bh(void *opaque) {
> + AspeedUFSState *s = opaque;
> +
> + s->regs[R(REG_HCE)] = HCE_ENABLE;
R_ REG_HCE ?
> + s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY;
> + s->hce_phase = 2;
> +}
> +
> +/* 12-byte UPIU header (big-endian per UFSHCI spec) */ typedef struct {
> + uint8_t trans_type;
> + uint8_t flags;
> + uint8_t lun;
> + uint8_t task_tag;
> + uint8_t iid_cmd_set;
> + uint8_t query_func;
> + uint8_t response;
> + uint8_t status;
> + uint8_t ehs_len;
> + uint8_t device_info;
> + uint16_t data_seg_len_be;
> +} UpiuHeader;
> +
> +/* 32-byte UPIU (header + TSF + reserved dword) */ typedef struct {
> + UpiuHeader hdr;
> + uint8_t tsf[16];
> + uint32_t reserved;
> +} Upiu32;
> +
> +/* UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1, 32 bytes)
> +*/ typedef struct {
> + uint32_t dw0;
> + uint32_t dw1;
> + uint32_t dw2; /* OCS[7:0] */
> + uint32_t dw3;
> + uint32_t dw4; /* UCDBA: Command UPIU base, lower 32 bits */
> + uint32_t dw5; /* UCDBAU: upper 32 bits */
> + uint32_t dw6; /* RUL[31:16] | RUO[15:0] in DWORDs */
> + uint32_t dw7; /* PRDTO[31:16] | PRDTL[15:0] */
> +} Utrd;
> +
> +/* Physical Region Descriptor Table entry */ typedef struct {
> + uint32_t dba; /* Data Buffer Address (lower) */
> + uint32_t dbau; /* Data Buffer Address (upper) */
> + uint32_t reserved;
> + uint32_t size; /* byte count - 1, bits [17:0] */
> +} PrdtEntry;
> +
> +/*
> + * Minimal UFS Device Descriptor (64 bytes).
> + * Values follow UFS 2.1 spec defaults; only fields checked by Linux
> +and
> + * U-Boot ufshcd are set to non-zero.
> + */
> +static const uint8_t ufs_device_desc[] = {
> + 0x40, /* bLength = 64 */
> + DESC_IDN_DEVICE, /* bDescriptorIDN */
> + 0x00, /* bDevice */
> + 0x00, /* bDeviceClass */
> + 0x00, /* bDeviceSubClass */
> + 0x01, /* bProtocol */
> + 0x01, /* bNumberLU */
> + 0x00, /* bNumberWLU */
> + 0x00, /* bBootEnable */
> + 0x00, /* bDescrAccessEn */
> + 0x00, /* bInitPowerMode */
> + 0x01, /* bHighPriorityLUN */
> + 0x00, /* bSecureRemovalType */
> + 0x00, /* bSecurityLU */
> + 0x00, /* bBackgroundOpsTermLat */
> + 0x01, /* bInitActiveICCLevel */
> + 0x02, 0x10, /* wSpecVersion = 0x0210 (UFS 2.1) */
> + 0x00, 0x00, /* wManufactureDate */
> + 0x00, /* iManufacturerName */
> + 0x02, /* iProductName (string index 2) */
> + 0x00, /* iSerialNumber */
> + 0x00, /* iOemID */
> + 0x00, 0x00, /* wManufacturerID */
> + 0x00, /* bUD0BaseOffset */
> + 0x00, /* bUDConfigPlength */
> + 0x00, /* bDeviceRTTCap */
> + 0x00, 0x00, /* wPeriodicRTCUpdate */
> + 0x00, /* bUFSFeaturesSupport */
> + 0x00, /* bFFUTimeout */
> + 0x00, /* bQueueDepth */
> + 0x00, 0x00, /* wDeviceVersion */
> + 0x00, /* bNumSecureWPArea */
> + 0x00, 0x00, 0x00, 0x00, /* dPSAMaxDataSize */
> + 0x00, /* bPSAStateTimeout */
> + 0x00, /* iProductRevisionLevel */
> + /* pad to 0x40 */
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
> +
> +/* Minimal Geometry Descriptor (0x48 bytes) */ static const uint8_t
> +ufs_geometry_desc[] = {
> + 0x48, /* bLength */
> + DESC_IDN_GEOMETRY, /* bDescriptorIDN */
> + 0x00, /* bMediaTechnology */
> + 0x00, /* reserved */
> + 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, /* qTotalRawDeviceCapacity (8 bytes, big-endian)
> */
> + 0x00, /* bMaxNumberLU */
> + 0x00, 0x10, 0x00, 0x00, /* dSegmentSize = 4096 sectors */
> + 0x08, /* bAllocationUnitSize */
> + 0x01, /* bMinAddrBlockSize */
> + 0x00, /* bOptimalReadBlockSize */
> + 0x00, /* bOptimalWriteBlockSize */
> + 0x00, /* bMaxInBufferSize */
> + 0x00, /* bMaxOutBufferSize */
> + 0x00, /* bRPMB_ReadWriteSize */
> + 0x00, /* bDynamicCapacityResourcePolicy */
> + 0x00, /* bDataOrdering */
> + 0x00, /* bMaxContexIDNumber */
> + 0x00, /* bSysDataTagUnitSize */
> + 0x00, /* bSysDataTagResSize */
> + 0x01, /* bSupportedSecRTypes */
> + 0x00, 0x03, /* wSupportedMemoryTypes */
> + 0x00, 0x00, 0x00, 0x00, /* dSystemCodeMaxNAllocU */
> + 0x00, 0x00, /* wSystemCodeCapAdjFac */
> + 0x00, 0x00, 0x00, 0x00, /* dNonPersistMaxNAllocU */
> + 0x00, 0x00, /* wNonPersistCapAdjFac */
> + 0x00, 0x00, 0x00, 0x00, /* dEnhanced1MaxNAllocU */
> + 0x00, 0x00, /* wEnhanced1CapAdjFac */
> + 0x00, 0x00, 0x00, 0x00, /* dEnhanced2MaxNAllocU */
> + 0x00, 0x00, /* wEnhanced2CapAdjFac */
> + 0x00, 0x00, 0x00, 0x00, /* dEnhanced3MaxNAllocU */
> + 0x00, 0x00, /* wEnhanced3CapAdjFac */
> + 0x00, 0x00, 0x00, 0x00, /* dEnhanced4MaxNAllocU */
> + 0x00, 0x00, /* wEnhanced4CapAdjFac */
> + /* pad to 0x48 */
> + 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00,
> +};
> +
> +/*
> + * Power Descriptor (idn 8, bLength 0x62).
> + * Linux ufshcd_probe() reads this; a zero-filled body of the right
> +length
> + * satisfies the length check.
> + */
> +static const uint8_t ufs_power_desc[0x62] = {
> + 0x62, /* bLength */
> + DESC_IDN_POWER, /* bDescriptorIDN */
> + /* remaining bytes zero-initialized */ };
> +
> +/*
> + * String Descriptor (idn 5).
> + * iProductName points to string index 2 in the device descriptor. A
> + * minimal well-formed header (type + zero length) keeps ufshcd happy.
> + */
> +static const uint8_t ufs_string_desc[] = {
> + 0x02, /* bLength = 2 (header only, no string data) */
> + DESC_IDN_STRING, /* bDescriptorIDN */
> +};
> +
> +/* Unit Descriptor (0x23 bytes) - capacity filled at realize time */
> +
> +static void aspeed_ufs_init_unit_desc(AspeedUFSState *s) {
> + uint64_t lbc;
> +
> + memset(s->unit_desc, 0, sizeof(s->unit_desc));
> + s->unit_desc[0x00] = 0x23; /* bLength */
> + s->unit_desc[0x01] = DESC_IDN_UNIT; /* bDescriptorIDN */
> + s->unit_desc[0x02] = 0x00; /* bUnitIndex */
> + s->unit_desc[0x03] = 0x01; /* bLUEnable */
> + s->unit_desc[0x08] = 0x02; /* bMemoryType */
> + s->unit_desc[0x0a] = 0x0c; /* bLogicalBlockSize: 1 << 12 =
> 4096 */
> +
> + lbc = cpu_to_be64(s->num_sectors);
> + memcpy(&s->unit_desc[0x0b], &lbc, 8); /* qLogicalBlockCount */ }
> +
> +/* Build a 36-byte SCSI INQUIRY response */ static void
> +scsi_build_inquiry(uint8_t *buf, uint8_t lun) {
> + memset(buf, 0, 36);
> + /*
> + * W-LUNs use device_type 0x1e (well known LU); LUN 0 is a
> direct-access
> + * block device (0x00).
> + */
> + buf[0] = aspeed_ufs_is_wlun(lun) ? 0x1e : 0x00;
> + buf[1] = 0x00; /* not removable */
> + buf[2] = 0x05; /* SPC-3 */
> + buf[3] = 0x12; /* response data format = 2, HiSup */
> + buf[4] = 0x1f; /* additional length = 31 */
> + memcpy(&buf[8], "ASPEED ", 8);
> + memcpy(&buf[16], "UFS QEMU ", 16);
> + memcpy(&buf[32], "1.00", 4);
> +}
> +
> +/* Build an 8-byte READ_CAPACITY(10) response */ static void
> +scsi_build_read_cap10(uint8_t *buf, uint64_t sectors) {
> + uint32_t last_lba = (sectors > 0xffffffff) ? 0xffffffff
> + : (uint32_t)(sectors
> - 1);
> + uint32_t block_size = cpu_to_be32(512);
> +
> + last_lba = cpu_to_be32(last_lba);
> + memcpy(buf, &last_lba, 4);
> + memcpy(buf + 4, &block_size, 4);
> +}
> +
> +/* Build a 32-byte READ_CAPACITY(16) response */ static void
> +scsi_build_read_cap16(uint8_t *buf, uint64_t sectors) {
> + uint64_t last_lba = cpu_to_be64(sectors - 1);
> + uint32_t block_size = cpu_to_be32(512);
> + memcpy(buf, &last_lba, 8);
> + memcpy(buf + 8, &block_size, 4);
> +}
> +
> +/* Process one UTRL slot */
> +static void aspeed_ufs_process_slot(AspeedUFSState *s, int slot) {
> + uint64_t utrd_addr = s->utrl_base + (uint64_t)slot * UTRD_SIZE;
> + Utrd utrd;
> + uint64_t upiu_base, resp_base, prdt_base;
> + uint32_t dw6, dw7, ruo, prdtl, prdto;
> + UpiuHeader req_hdr;
> + uint8_t trans_type, task_tag;
> +
> + ufs_dma_read(utrd_addr, &utrd, sizeof(utrd));
> +
> + upiu_base = (uint64_t)le32_to_cpu(utrd.dw4) |
> + ((uint64_t)le32_to_cpu(utrd.dw5) << 32);
> +
> + /*
> + * DW6 holds the Response UPIU offset (RUO) and length (RUL), both in
> + * DWORDs. The response UPIU follows the request UPIU in the same
> + * guest buffer.
> + */
> + dw6 = le32_to_cpu(utrd.dw6);
> + ruo = dw6 & 0xffff;
> + resp_base = upiu_base + (uint64_t)ruo * 4;
> +
> + /* DW7: PRDTL (entry count) and PRDTO (offset in DWORDs) */
> + dw7 = le32_to_cpu(utrd.dw7);
> + prdtl = dw7 & 0xffff;
> + prdto = (dw7 >> 16) & 0xffff;
> + prdt_base = upiu_base + (uint64_t)prdto * 4;
> +
> + ufs_dma_read(upiu_base, &req_hdr, sizeof(req_hdr));
> + trans_type = req_hdr.trans_type & 0x3f;
> + task_tag = req_hdr.task_tag;
> +
> + /* NOP OUT -> NOP IN */
> + if (trans_type == UPIU_TYPE_NOP_OUT) {
> + uint8_t rsp[32];
> + memset(rsp, 0, sizeof(rsp));
> + rsp[0] = UPIU_TYPE_NOP_IN;
> + rsp[3] = task_tag;
> + ufs_dma_write(resp_base, rsp, 32);
> + goto done;
> + }
> +
> + /* Query Request -> Query Response */
> + if (trans_type == UPIU_TYPE_QUERY_REQ) {
> + uint8_t req_buf[32];
> + uint8_t rsp[32];
> + uint8_t opcode, idn;
> +
> + ufs_dma_read(upiu_base, req_buf, 32);
> + opcode = req_buf[12];
> + idn = req_buf[13];
> +
> + memset(rsp, 0, sizeof(rsp));
> + rsp[0] = UPIU_TYPE_QUERY_RSP;
> + rsp[3] = task_tag;
> + memcpy(&rsp[12], &req_buf[12], 16); /* echo TSF */
> +
> + if (opcode == QUERY_OP_READ_DESC) {
> + const uint8_t *desc = NULL;
> + uint16_t desc_len = 0;
> + uint16_t req_len;
> +
> + switch (idn) {
> + case DESC_IDN_DEVICE:
> + desc = ufs_device_desc;
> + desc_len = sizeof(ufs_device_desc);
> + break;
> + case DESC_IDN_GEOMETRY:
> + desc = ufs_geometry_desc;
> + desc_len = sizeof(ufs_geometry_desc);
> + break;
> + case DESC_IDN_UNIT:
> + desc = s->unit_desc;
> + desc_len = sizeof(s->unit_desc);
> + break;
> + case DESC_IDN_POWER:
> + desc = ufs_power_desc;
> + desc_len = sizeof(ufs_power_desc);
> + break;
> + case DESC_IDN_STRING:
> + desc = ufs_string_desc;
> + desc_len = sizeof(ufs_string_desc);
> + break;
> + default:
> + break;
> + }
> +
> + /*
> + * Cap the returned length to what the host requested (TSF
> + * bytes 6-7 hold the allocation length). Returning more than
> + * asked causes the driver to reject the response with -EINVAL.
> + */
> + req_len = ((uint16_t)req_buf[18] << 8) | req_buf[19];
> + if (desc_len > req_len) {
> + desc_len = req_len;
> + }
> +
> + rsp[10] = (desc_len >> 8) & 0xff;
> + rsp[11] = desc_len & 0xff;
> + rsp[18] = rsp[10];
> + rsp[19] = rsp[11];
> +
> + ufs_dma_write(resp_base, rsp, 32);
> + if (desc && desc_len) {
> + ufs_dma_write(resp_base + 32, desc, desc_len);
> + }
> + } else {
> + /* READ_ATTR, READ_FLAG, SET_FLAG, etc. - echo TSF, success
> */
> + ufs_dma_write(resp_base, rsp, 32);
> + }
> + goto done;
> + }
> +
> + /* SCSI Command UPIU */
> + if (trans_type == UPIU_TYPE_CMD) {
> + uint8_t req_buf[32];
> + uint8_t *cdb;
> + uint8_t scsi_cmd;
> + bool ok = true;
> + uint8_t sense[18];
> + uint8_t *xfer_buf = NULL;
> + uint32_t xfer_len = 0;
> +
> + ufs_dma_read(upiu_base, req_buf, 32);
> + cdb = &req_buf[16];
> + scsi_cmd = cdb[0];
> +
> + memset(sense, 0, sizeof(sense));
> +
> + /* Non-zero LUNs that are not W-LUNs have no device. */
> + if (req_hdr.lun != 0 && !aspeed_ufs_is_wlun(req_hdr.lun)) {
> + ok = false;
> + sense[0] = 0x70;
> + sense[2] = 0x05; /* ILLEGAL REQUEST */
> + sense[7] = 0x0a;
> + goto scsi_done;
> + }
> +
> + if (prdtl > 0) {
> + uint32_t i;
> +
> + for (i = 0; i < prdtl; i++) {
> + PrdtEntry pe;
> + ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
> + &pe, sizeof(pe));
> + xfer_len += (le32_to_cpu(pe.size) & 0x3ffff) + 1;
> + }
> + xfer_buf = g_malloc0(xfer_len);
> + }
> +
> + switch (scsi_cmd) {
> +
> + case SCSI_TEST_UNIT_READY:
> + break;
> +
> + case SCSI_INQUIRY: {
> + uint8_t inq[36];
> + uint32_t alloc, send;
> +
> + /*
> + * EVPD queries (cdb[1] & 0x01) return an empty VPD page
> list
> + * for W-LUNs. This stops the driver from iterating all 256
> + * possible VPD pages after it finds zero supported ones.
> + */
> + if ((cdb[1] & 0x01) && aspeed_ufs_is_wlun(req_hdr.lun)) {
> + uint8_t evpd[4];
> + memset(evpd, 0, sizeof(evpd));
> + evpd[0] = 0x1e; /* device_type: well known LU */
> + /* page_length = 0: no VPD pages supported */
> + alloc = ((uint16_t)cdb[3] << 8) | cdb[4];
> + send = MIN(alloc, sizeof(evpd));
> + if (prdtl > 0 && xfer_buf) {
> + memcpy(xfer_buf, evpd, send);
> + } else {
> + ufs_dma_write(resp_base + 32, evpd, send);
> + }
> + xfer_len = send;
> + break;
> + }
> +
> + scsi_build_inquiry(inq, req_hdr.lun);
> + alloc = ((uint16_t)cdb[3] << 8) | cdb[4];
> + send = MIN(alloc, 36);
> + if (prdtl > 0 && xfer_buf) {
> + memcpy(xfer_buf, inq, send);
> + } else {
> + ufs_dma_write(resp_base + 32, inq, send);
> + }
> + xfer_len = send;
> + break;
> + }
> +
> + case SCSI_READ_CAPACITY_10: {
> + uint8_t cap[8];
> + scsi_build_read_cap10(cap, s->num_sectors);
> + if (prdtl > 0 && xfer_buf) {
> + memcpy(xfer_buf, cap, 8);
> + } else {
> + ufs_dma_write(resp_base + 32, cap, 8);
> + }
> + xfer_len = 8;
> + break;
> + }
> +
> + case SCSI_READ_CAPACITY_16:
> + /* service action must be 0x10 */
> + if ((cdb[1] & 0x1f) == 0x10) {
> + uint8_t cap[32];
> + memset(cap, 0, sizeof(cap));
> + scsi_build_read_cap16(cap, s->num_sectors);
> + if (prdtl > 0 && xfer_buf) {
> + memcpy(xfer_buf, cap, 32);
> + } else {
> + ufs_dma_write(resp_base + 32, cap, 32);
> + }
> + xfer_len = 32;
> + }
> + break;
> +
> + case SCSI_READ_10:
> + case SCSI_READ_16: {
> + uint64_t lba;
> + uint32_t num_blocks, byte_count;
> +
> + if (scsi_cmd == SCSI_READ_10) {
> + lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 |
> + (uint64_t)cdb[4] << 8 | cdb[5];
> + num_blocks = (uint16_t)cdb[7] << 8 | cdb[8];
> + } else {
> + lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 |
> + (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 |
> + (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 |
> + (uint64_t)cdb[8] << 8 | cdb[9];
> + num_blocks = (uint32_t)cdb[10] << 24 |
> + (uint32_t)cdb[11] << 16 |
> + (uint32_t)cdb[12] << 8 | cdb[13];
> + }
> + byte_count = num_blocks * 512;
> + if (s->blk && byte_count > 0) {
> + uint8_t *rbuf = g_malloc(byte_count);
> + if (blk_pread(s->blk, lba * 512, byte_count, rbuf, 0) < 0) {
> + ok = false;
> + } else if (prdtl > 0 && xfer_buf) {
> + memcpy(xfer_buf, rbuf,
> + MIN(byte_count, xfer_len));
> + } else {
> + ufs_dma_write(resp_base + 32, rbuf, byte_count);
> + }
> + g_free(rbuf);
> + }
> + xfer_len = byte_count;
> + break;
> + }
> +
> + case SCSI_WRITE_10:
> + case SCSI_WRITE_16: {
> + uint64_t lba;
> + uint32_t num_blocks, byte_count;
> +
> + if (scsi_cmd == SCSI_WRITE_10) {
> + lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 |
> + (uint64_t)cdb[4] << 8 | cdb[5];
> + num_blocks = (uint16_t)cdb[7] << 8 | cdb[8];
> + } else {
> + lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 |
> + (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 |
> + (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 |
> + (uint64_t)cdb[8] << 8 | cdb[9];
> + num_blocks = (uint32_t)cdb[10] << 24 |
> + (uint32_t)cdb[11] << 16 |
> + (uint32_t)cdb[12] << 8 | cdb[13];
> + }
> + byte_count = num_blocks * 512;
> + if (s->blk && byte_count > 0 && prdtl > 0 && xfer_buf) {
> + uint32_t copied = 0, i;
> + for (i = 0; i < prdtl && copied < byte_count; i++) {
> + PrdtEntry pe;
> + uint64_t dba;
> + uint32_t chunk;
> + ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
> + &pe, sizeof(pe));
> + dba = (uint64_t)le32_to_cpu(pe.dba) |
> + ((uint64_t)le32_to_cpu(pe.dbau) << 32);
> + chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1;
> + chunk = MIN(chunk, byte_count - copied);
> + ufs_dma_read(dba, xfer_buf + copied, chunk);
> + copied += chunk;
> + }
> + if (blk_pwrite(s->blk, lba * 512, byte_count,
> + xfer_buf, 0) < 0) {
> + ok = false;
> + }
> + }
> + xfer_len = 0;
> + break;
> + }
> +
> + case SCSI_REPORT_LUNS: {
> + uint8_t rl[16];
> + memset(rl, 0, sizeof(rl));
> + rl[3] = 8; /* list length = 8 (one LUN entry) */
> + if (prdtl > 0 && xfer_buf) {
> + memcpy(xfer_buf, rl, 16);
> + } else {
> + ufs_dma_write(resp_base + 32, rl, 16);
> + }
> + xfer_len = 16;
> + break;
> + }
> +
> + default:
> + ok = false;
> + sense[0] = 0x70;
> + sense[2] = 0x05; /* ILLEGAL REQUEST */
> + sense[7] = 0x0a;
> + xfer_len = 0;
> + break;
> + }
> +
> + /* Scatter read data back into guest PRDT buffers */
> + if (ok && prdtl > 0 && xfer_buf && xfer_len > 0) {
> + uint32_t copied = 0, i;
> + for (i = 0; i < prdtl && copied < xfer_len; i++) {
> + PrdtEntry pe;
> + uint64_t dba;
> + uint32_t chunk;
> + ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
> + &pe, sizeof(pe));
> + dba = (uint64_t)le32_to_cpu(pe.dba) |
> + ((uint64_t)le32_to_cpu(pe.dbau) << 32);
> + chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1;
> + chunk = MIN(chunk, xfer_len - copied);
> + ufs_dma_write(dba, xfer_buf + copied, chunk);
> + copied += chunk;
> + }
> + }
> +
> + g_free(xfer_buf);
> +
> +scsi_done:
> + /* Build SCSI Response UPIU */
> + {
> + uint8_t rsp[32];
> + uint16_t seg_len = 0;
> +
> + memset(rsp, 0, sizeof(rsp));
> + rsp[0] = UPIU_TYPE_RESPONSE;
> + rsp[3] = task_tag;
> + rsp[7] = ok ? 0x00 : 0x02; /* SCSI status */
> +
> + /*
> + * If prdtl=0 (no scatter-gather list), the data was written
> + * directly to the response UPIU data segment; report its
> length
> + * in data_seg_len so the host driver knows where to find it.
> + */
> + if (ok && prdtl == 0 && xfer_len > 0) {
> + seg_len = (uint16_t)xfer_len;
> + } else if (!ok) {
> + seg_len = sizeof(sense);
> + }
> + rsp[10] = (seg_len >> 8) & 0xff;
> + rsp[11] = seg_len & 0xff;
> +
> + ufs_dma_write(resp_base, rsp, 32);
> + if (!ok) {
> + ufs_dma_write(resp_base + 32, sense, sizeof(sense));
> + }
> + }
> + goto done;
> + }
> +
> + qemu_log_mask(LOG_UNIMP, "aspeed-ufs: unhandled UPIU type 0x%02x
> slot %d\n",
> + trans_type, slot);
> +
> +done:
> + /* Mark OCS=SUCCESS in UTRD DW2 and write back */
> + utrd.dw2 = cpu_to_le32(OCS_SUCCESS);
> + ufs_dma_write(utrd_addr, &utrd, sizeof(utrd));
> +
> + s->utrldbr &= ~BIT(slot);
> + s->regs[R(REG_UTRLDBR)] = s->utrldbr;
> +
> + s->regs[R(REG_IS)] |= IS_UTRCS;
> + aspeed_ufs_update_irq(s);
> +}
> +
> +/*
> + * Process all pending UTRL doorbell slots. Called synchronously from
> +the
> + * write handler to complete requests before the host driver's timeout fires.
> + *
> + * Note: despite the _bh suffix this is not a QEMU BH callback. It is
> + * called inline so that U-Boot's 30 ms NOP_OUT_TIMEOUT cannot expire
> +before
> + * a deferred callback would run.
> + */
> +static void aspeed_ufs_doorbell_bh(AspeedUFSState *s) {
> + while (s->utrldbr) {
> + int slot = ctz32(s->utrldbr);
> + aspeed_ufs_process_slot(s, slot);
> + }
> +}
> +
> +static uint64_t aspeed_ufs_read(void *opaque, hwaddr addr, unsigned
> +size) {
> + AspeedUFSState *s = opaque;
> +
> + if (addr + size > ASPEED_UFS_MMIO_SIZE) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "aspeed-ufs: read out of range 0x%"
> HWADDR_PRIx "\n",
> + addr);
> + return 0;
> + }
> +
> + /*
> + * HCE state machine: after a HCE=1 write the register is cleared to 0
> + * (phase 1) so the first read returns 0; the BH then sets it back to 1
> + * (phase 2) and subsequent reads return 1.
> + */
> + if (addr == REG_HCE && s->hce_phase == 1) {
> + return 0;
> + }
> +
> + return s->regs[R(addr)];
> +}
> +
> +static void aspeed_ufs_write(void *opaque, hwaddr addr, uint64_t val,
> + unsigned size) {
> + AspeedUFSState *s = opaque;
> +
> + if (addr + size > ASPEED_UFS_MMIO_SIZE) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "aspeed-ufs: write out of range 0x%"
> HWADDR_PRIx "\n",
> + addr);
> + return;
> + }
> +
> + s->regs[R(addr)] = (uint32_t)val;
> +
> + switch (addr) {
> +
> + case REG_HCE:
> + if (val & HCE_ENABLE) {
> + /*
> + * Restart the 1->0->1 toggle on every HCE=1 write, including
> + * when Linux re-enables the controller after U-Boot.
> + */
> + if (s->hce_phase != 2) {
> + s->regs[R(REG_HCE)] = 0;
> + s->hce_phase = 1;
> + qemu_bh_schedule(s->hce_bh);
> + }
> + } else {
> + s->regs[R(REG_HCE)] = 0;
> + s->regs[R(REG_HCS)] = 0;
> + s->hce_phase = 0;
> + }
> + break;
> +
> + case REG_UICCMD: {
> + uint8_t opcode = (uint8_t)(val & 0xff);
> +
> + s->regs[R(REG_UCMDARG2)] = UIC_CMD_RESULT_OK;
> + s->regs[R(REG_IS)] |= IS_UCCS;
> +
> + if (opcode == DME_LINKSTARTUP) {
> + s->regs[R(REG_IS)] |= IS_UIC_LINK_STARTUP;
> + s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY;
> + }
> +
> + /*
> + * Power mode changes (DME_SET / DME_PEER_SET): set
> HCS[10:8] to
> + * PWR_LOCAL and raise IS_UIC_POWER_MODE so
> ufshcd_uic_pwr_ctrl()
> + * does not time out waiting for the completion interrupt.
> + */
> + if (opcode == DME_SET || opcode == DME_PEER_SET) {
> + s->regs[R(REG_HCS)] |= HCS_PWR_LOCAL;
> + s->regs[R(REG_IS)] |= IS_UIC_POWER_MODE;
> + }
> +
> + /*
> + * DME_GET / DME_PEER_GET: return plausible gear/lane values
> so
> + * ufshcd_get_max_pwr_mode() does not fail with rx=0, tx=0.
> + */
> + if (opcode == DME_GET || opcode == DME_PEER_GET) {
> + s->regs[R(REG_UCMDARG3)] = 0x00000003;
> + }
> +
> + aspeed_ufs_update_irq(s);
> + break;
> + }
> +
> + case REG_IS:
> + /* W1C: write-1-to-clear */
> + s->regs[R(REG_IS)] &= ~(uint32_t)val;
> + aspeed_ufs_update_irq(s);
> + break;
> +
> + case REG_IE:
> + aspeed_ufs_update_irq(s);
> + break;
> +
> + case REG_UTRLBA:
> + s->utrl_base = (s->utrl_base & 0xffffffff00000000ULL) |
> + ((uint64_t)(val & 0xfffffc00));
> + break;
> +
> + case REG_UTRLBAU:
> + s->utrl_base = (s->utrl_base & 0x00000000ffffffffULL) |
> + ((uint64_t)val << 32);
> + break;
> +
> + case REG_UTMRLBA:
> + s->utmrl_base = (s->utmrl_base & 0xffffffff00000000ULL) |
> + ((uint64_t)(val & 0xfffffc00));
> + break;
> +
> + case REG_UTMRLBAU:
> + s->utmrl_base = (s->utmrl_base & 0x00000000ffffffffULL) |
> + ((uint64_t)val << 32);
> + break;
> +
> + case REG_UTRLRSR:
> + case REG_UTMRLRSR:
> + break;
> +
> + case REG_UTRLDBR:
> + s->utrldbr |= (uint32_t)val;
> + s->regs[R(REG_UTRLDBR)] = s->utrldbr;
> + aspeed_ufs_doorbell_bh(s);
> + break;
> +
> + case REG_UTRLCLR:
> + s->utrldbr &= (uint32_t)val;
> + s->regs[R(REG_UTRLDBR)] = s->utrldbr;
> + break;
> +
> + case REG_UTMRLDBR: {
> + /*
> + * Task management doorbell. Write OCS=SUCCESS into each
> requested
> + * UTMRD so ufshcd_issue_tm_cmd() reads back success, clear the
> + * doorbell, then raise IS_UTMRCS so ufshcd_tmc_handler() fires
> + * complete() on the waiting thread.
> + *
> + * UTMRD slot stride is 80 bytes (request_desc_header:16 +
> + * upiu_req:32 + upiu_rsp:32); OCS is at byte 8 of the header.
> + */
> + uint32_t dbr = (uint32_t)val;
> + while (dbr) {
> + int slot = ctz32(dbr);
> + uint64_t ocs_addr = s->utmrl_base +
> + (uint64_t)slot * UTMRD_SIZE +
> + UTMRD_OCS_OFFSET;
> + uint8_t ocs = OCS_SUCCESS;
> + ufs_dma_write(ocs_addr, &ocs, sizeof(ocs));
> + dbr &= ~(1u << slot);
> + }
> + s->regs[R(REG_UTMRLDBR)] = 0;
> + s->regs[R(REG_IS)] |= IS_UTMRCS;
> + aspeed_ufs_update_irq(s);
> + break;
> + }
> +
> + default:
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps aspeed_ufs_ops = {
> + .read = aspeed_ufs_read,
> + .write = aspeed_ufs_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void aspeed_ufs_reset(DeviceState *dev) {
> + AspeedUFSState *s = ASPEED_UFS(dev);
> +
> + memset(s->regs, 0, sizeof(s->regs));
> +
> + s->regs[R(REG_CAP)] = ASPEED_UFS_CAP_RESET;
> + s->regs[R(REG_VER)] = ASPEED_UFS_VER_RESET;
> + s->regs[R(REG_HCPID)] = 0x41535045; /* "ASPE" in ASCII */
> + s->regs[R(REG_HCMID)] = 0x45440000; /* "ED\0\0" in ASCII */
> +
> + s->regs[R(REG_HCE)] = 0;
> + s->regs[R(REG_HCS)] = 0;
> +
> + s->hce_phase = 0;
> + s->utrldbr = 0;
> + s->utrl_base = 0;
> + s->utmrl_base = 0;
> +
> + qemu_set_irq(s->irq, 0);
> +}
> +
> +static void aspeed_ufs_realize(DeviceState *dev, Error **errp) {
> + AspeedUFSState *s = ASPEED_UFS(dev);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_ufs_ops, s,
> + TYPE_ASPEED_UFS,
> ASPEED_UFS_MMIO_SIZE);
> + sysbus_init_mmio(sbd, &s->iomem);
> + sysbus_init_irq(sbd, &s->irq);
> +
> + s->hce_bh = qemu_bh_new(aspeed_ufs_hce_bh, s);
> +
> + if (s->blk) {
> + int64_t sz = blk_getlength(s->blk);
> + if (sz <= 0) {
> + error_setg(errp, "aspeed-ufs: cannot determine drive size");
> + return;
> + }
> + s->num_sectors = (uint64_t)sz / 512;
> + blk_set_dev_ops(s->blk, NULL, NULL);
> + if (blk_set_perm(s->blk,
> + BLK_PERM_CONSISTENT_READ |
> BLK_PERM_WRITE,
> + BLK_PERM_ALL, errp) < 0) {
> + return;
> + }
> + } else {
> + /* No drive attached; expose a minimal read-only stub. */
> + s->num_sectors = 2048;
> + }
> +
> + aspeed_ufs_init_unit_desc(s);
> + aspeed_ufs_reset(dev);
> +}
> +
> +static const VMStateDescription vmstate_aspeed_ufs = {
> + .name = TYPE_ASPEED_UFS,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32_ARRAY(regs, AspeedUFSState,
> ASPEED_UFS_NUM_REGS),
> + VMSTATE_INT32(hce_phase, AspeedUFSState),
> + VMSTATE_UINT32(utrldbr, AspeedUFSState),
> + VMSTATE_UINT64(utrl_base, AspeedUFSState),
> + VMSTATE_UINT64(utmrl_base, AspeedUFSState),
> + VMSTATE_UINT64(num_sectors, AspeedUFSState),
> + VMSTATE_UINT8_ARRAY(unit_desc, AspeedUFSState, 0x23),
> + VMSTATE_END_OF_LIST()
> + },
> +};
> +
> +static const Property aspeed_ufs_properties[] = {
> + DEFINE_PROP_DRIVE("drive", AspeedUFSState, blk), };
> +
> +static void aspeed_ufs_class_init(ObjectClass *oc, const void *data) {
> + DeviceClass *dc = DEVICE_CLASS(oc);
> +
> + dc->realize = aspeed_ufs_realize;
> + device_class_set_legacy_reset(dc, aspeed_ufs_reset);
Convert to use Resettable interface
https://lore.kernel.org/qemu-devel/20260525044129.3133916-1-kane_chen@aspeedtech.com/
> + dc->vmsd = &vmstate_aspeed_ufs;
> + dc->desc = "ASPEED AST2700 UFS Host Controller";
> + device_class_set_props(dc, aspeed_ufs_properties);
> + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); }
> +
> +static const TypeInfo aspeed_ufs_info = {
> + .name = TYPE_ASPEED_UFS,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(AspeedUFSState),
> + .class_init = aspeed_ufs_class_init,
> +};
> +
> +static void aspeed_ufs_register_types(void) {
> + type_register_static(&aspeed_ufs_info);
> +}
> +
> +type_init(aspeed_ufs_register_types)
Convert to DEFINE_TYPES() with inlined TypeInfo
https://lore.kernel.org/qemu-devel/20260601024959.2347639-1-jamin_lin@aspeedtech.com/
> diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build index
> 6e68328b93..86052e6fd2 100644
> --- a/hw/ufs/meson.build
> +++ b/hw/ufs/meson.build
> @@ -1 +1,2 @@
> system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c'))
> +system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true:
> +files('aspeed_ufs.c'))
> --
> 2.53.0
Thanks,
Jamin
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine
2026-07-15 15:50 ` [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
@ 2026-07-16 13:56 ` Cédric Le Goater
0 siblings, 0 replies; 15+ messages in thread
From: Cédric Le Goater @ 2026-07-16 13:56 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 7/15/26 17:50, Mikail Sadic wrote:
> Add support for the IBM Huygens BMC, based on the AST2700 A2 SoC.
You're keeping all the interesting info from us ! What is Huygens ?
a P11, P11+, P11-next, P12, Z ? Tell us more :)
>
> Instantiate the AST2700 SoC with the devices and drives required
> to boot Huygens OBMC Linux image on the Huygens platform.
>
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
> docs/system/arm/aspeed.rst | 41 +++++-
> hw/arm/aspeed_ast27x0_huygens.c | 242 ++++++++++++++++++++++++++++++++
> hw/arm/meson.build | 1 +
> 3 files changed, 282 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..32999d6284 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
Ah ! Now we know :) We had guessed.
>
> 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)
This might not be ready yet.
> + * FSI APB-to-OPB bridge with CFAM-S mailbox
>
> Missing devices
> ---------------
> @@ -440,6 +443,40 @@ 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. It boots from FMC
> +(SPI NOR) flash and a UFS storage image.
What is on the and what is on UFS ?
> Images can be built from the
> +OpenBMC project tree.
A functional test with such images will be strongly appreciated.
> +
> +To boot the machine from flash and UFS storage:
> +
> +.. code-block:: bash
> +
> + $ qemu-system-aarch64 -M huygens-bmc \
> + -drive file=image-bmc,if=mtd,format=raw \
> + -drive file=ufs.img,if=none,format=raw \
hmm, we have been trying to remove the -drive command line option
for years now. Please use blockdev instead.
> + -nographic
> +
> +The ``if=none`` interface is required for the UFS drive. The Aspeed UFS host
> +controller picks up the first ``IF_NONE`` block device at index 0.
> +
> +The machine instantiates three ``ftgmac100`` Ethernet controllers. To
> +expose them with port forwarding:
> +
> +.. code-block:: bash
> +
> + $ qemu-system-aarch64 -M huygens-bmc \
> + -drive file=image-bmc,if=mtd,format=raw \
> + -drive file=ufs.img,if=none,format=raw \
> + -nic user,model=ftgmac100 \
> + -nic user,model=ftgmac100 \
> + -nic user,model=ftgmac100 \
I don't think you need to repeat the command line and 'model=ftgmac100'
seems useless.
> + -nographic
> +
> +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..951008c310
> --- /dev/null
> +++ b/hw/arm/aspeed_ast27x0_huygens.c
> @@ -0,0 +1,242 @@
> +/*
> + * IBM Huygens
> + *
> + * 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);
> +
> + /* I2C2: TPM */
QEMU has a TPM support.
> + /* I2C5: UCD90320 power sequencer */
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5), "ucd90320", 0x11);
> +
> + /* I2C6: chassis1 backplane VPD EEPROM */
> + {
> + uint8_t *ch1 = g_malloc(huygens_bmc_fruid_len);
use g_autofree variables.
> + 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);
> + g_free(ch1);
> + }
> +
> + /* 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;
> + 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'))
Thanks,
C.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700
2026-07-15 15:50 ` [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700 Mikail Sadic
@ 2026-07-16 14:01 ` Cédric Le Goater
2026-07-22 17:45 ` Ninad Palsule
1 sibling, 0 replies; 15+ messages in thread
From: Cédric Le Goater @ 2026-07-16 14:01 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
Ninad,
Since you are the FSI maintainer, Could you please provide feedback ?
Thanks,
C.
On 7/15/26 17:50, Mikail Sadic wrote:
> Introduce TYPE_FSI_CFAM_S as a dedicated QOM type that presents the
> config table and mailbox scratch registers expected by the new Linux
> FSI responder framework. FSIMasterState gains a cfam_s field.
> fsi_master_realize() instantiates both cfam (at offset 0) and cfam-s
> (at offset 2 MiB) into the OPB-to-FSI aperture. aspeed_ast27x0.c wires
> up both fsi[0] and fsi[1] APB-to-OPB bridges for the AST2700 SoC.
>
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
> docs/specs/fsi.rst | 58 +++++++++++++++
> include/hw/fsi/cfam-s.h | 26 +++++++
> include/hw/fsi/fsi-master.h | 2 +
> hw/arm/aspeed_ast27x0.c | 18 +++++
> hw/fsi/cfam-s.c | 145 ++++++++++++++++++++++++++++++++++++
> hw/fsi/fsi-master.c | 10 ++-
> hw/fsi/meson.build | 2 +-
> 7 files changed, 258 insertions(+), 3 deletions(-)
> 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..a8896b67ef 100644
> --- a/docs/specs/fsi.rst
> +++ b/docs/specs/fsi.rst
> @@ -120,3 +120,61 @@ from the BMC. (see the `pdbg source repository`_ for more details)
>
> .. _pdbg source repository:
> https://github.com/open-power/pdbg
> +
> +CFAM-S model (AST2700)
> +----------------------
> +
> +The AST2700 uses a CFAM variant known as the CFAM-S, required by the new
> +Linux FSI responder framework. A dedicated QOM type, ``TYPE_FSI_CFAM_S``
> +(``"cfam-s"``), implements this alongside the existing ``TYPE_FSI_CFAM``
> +(``"cfam"``) used by the AST2600 machines.
> +
> +``FSIMasterState`` realizes both ``cfam`` and ``cfam-s``. The regular
> +``cfam`` is mapped at offset 0 into the OPB-to-FSI aperture; ``cfam-s`` is
> +mapped at offset 2 MiB. The DTS overrides ``fsim0`` with
> +``compatible = "aspeed,ast2600-fsi-master"`` and ``reg = <0x21800000>``,
> +pointing the kernel FSI driver at the QEMU APB-to-OPB bridge, which routes
> +through the FSI master's OPB-to-FSI window to reach the CFAM-S.
> +
> +The CFAM-S model presents an 8 MiB region and folds the SID bits ``[22:21]``
> +so both the SID_BREAK enumeration view (``0x600000``) and the runtime view
> +(``0x000000``) address the same register space. The config table at folded
> +offset ``0x000`` contains three CRC4-valid words: a chip_id word with
> +``MAJOR=9`` (selecting the ``cfam_s`` kernel driver), a responder engine
> +entry (``TYPE=0x3``), and a mailbox v1 engine entry (``TYPE=0x14``) at
> +engine address ``0x800``.
> +
> +The responder registers at folded offset ``0x400`` implement ``SMODE``,
> +``SSTAT``, ``SRES``, ``SSISM``, and ``SLBUS`` with store-on-write semantics.
> +The mailbox scratch registers at folded offset ``0x8e0`` are five stateful
> +32-bit registers served to the ``ibm,mbox-cfam-s`` kernel driver, which
> +creates ``/dev/fsi/mbox0``.
> +
> +The following commands start the ``huygens-bmc`` machine with the built-in
> +CFAM-S model. There are no model specific arguments. Please check this
> +document to learn more about Aspeed ``huygens-bmc`` machine:
> +(:doc:`../../system/arm/aspeed`)
> +
> +.. code-block:: console
> +
> + qemu-system-aarch64 -M huygens-bmc \
> + -drive file=image-bmc,if=mtd,format=raw \
> + -drive file=ufs.img,if=none,format=raw \
> + -nographic
> +
> +The CFAM-S appears as follows in the QEMU device tree:
> +
> +.. code-block:: console
> +
> + (qemu) info qtree
> + bus: main-system-bus
> + type System
> + ...
> + dev: aspeed.apb2opb, id ""
> + mmio 0000000021800000/0000000000001000
> + bus: opb.0
> + type opb
> + dev: fsi.master, id ""
> + bus: fsi.bus.0
> + type fsi.bus
> + dev: cfam-s, id ""
> diff --git a/include/hw/fsi/cfam-s.h b/include/hw/fsi/cfam-s.h
> new file mode 100644
> index 0000000000..3793b01471
> --- /dev/null
> +++ b/include/hw/fsi/cfam-s.h
> @@ -0,0 +1,26 @@
> +/*
> + * 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/fsi.h"
> +
> +#define TYPE_FSI_CFAM_S "cfam-s"
> +#define FSI_CFAM_S(obj) OBJECT_CHECK(FSICFAMSState, (obj), TYPE_FSI_CFAM_S)
> +
> +#define CFAM_S_MBOX_SCRATCH_NUM 5
> +
> +typedef struct FSICFAMSState {
> + FSISlaveState parent;
> +
> + MemoryRegion mr;
> + uint32_t mbox_scratch[CFAM_S_MBOX_SCRATCH_NUM];
> +} FSICFAMSState;
> +
> +#endif /* FSI_CFAM_S_H */
> diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
> index 60ddaa994f..6a2782fdf2 100644
> --- a/include/hw/fsi/fsi-master.h
> +++ b/include/hw/fsi/fsi-master.h
> @@ -11,6 +11,7 @@
> #include "hw/core/qdev.h"
> #include "hw/fsi/fsi.h"
> #include "hw/fsi/cfam.h"
> +#include "hw/fsi/cfam-s.h"
>
> #define TYPE_FSI_MASTER "fsi.master"
> OBJECT_DECLARE_SIMPLE_TYPE(FSIMasterState, FSI_MASTER)
> @@ -26,6 +27,7 @@ typedef struct FSIMasterState {
>
> uint32_t regs[FSI_MASTER_NR_REGS];
> FSICFAMState cfam;
> + FSICFAMSState cfam_s;
> } FSIMasterState;
>
>
> diff --git a/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],
> diff --git a/hw/fsi/cfam-s.c b/hw/fsi/cfam-s.c
> new file mode 100644
> index 0000000000..bf000d1879
> --- /dev/null
> +++ b/hw/fsi/cfam-s.c
> @@ -0,0 +1,145 @@
> +/*
> + * IBM Common FRU Access Macro - S variant (CFAM-S)
> + *
> + * Copyright (C) 2026 IBM Corp.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +#include "hw/fsi/cfam-s.h"
> +#include "hw/fsi/fsi.h"
> +
> +/* bits [22:21] are the slave ID, low 21 bits address registers within it */
> +#define CFAM_SID_MASK 0x1fffff
> +#define CFAM_WINDOW_SIZE 0x800000 /* 8 MiB per slave, covers SID 0..3 */
> +
> +#define CFAM_RESPONDER_BASE 0x400 /* == FSI_RESPONDER_PAGE_SIZE */
> +
> +/* Config-table word fields (see Linux fsi-master.h) */
> +#define CFAM_CONF_NEXT (1u << 31)
> +#define CFAM_CONF_SLOTS(n) (((n) & 0xff) << 16)
> +#define CFAM_CONF_VERSION(v) (((v) & 0xf) << 12)
> +#define CFAM_CONF_TYPE(t) (((t) & 0xff) << 4)
> +#define CFAM_CHIP_ID_MAJOR(m) (((m) & 0xf) << 8)
> +
> +/* Engine IDs (include/linux/fsi.h) */
> +#define FSI_ENGINE_ID_RESPONDER 0x3
> +#define FSI_ENGINE_ID_MBOXV1 0x14
> +#define FSI_CHIP_ID_MAJOR_CFAM_S 0x9 /* major == 9 -> CFAM-S */
> +
> +/* Engine layout: 0x000 config table, 0x400 responder, 0x800 mbox */
> +#define CFAM_MBOX_BASE 0x800
> +#define CFAM_MBOX_SCRATCH_OFF 0xe0
> +#define CFAM_MBOX_SCRATCH_BASE (CFAM_MBOX_BASE + CFAM_MBOX_SCRATCH_OFF)
> +
> +static uint8_t cfam_s_crc4(uint8_t c, uint64_t x, int bits)
> +{
> + static const uint8_t tab[16] = {
> + 0x0, 0x7, 0xe, 0x9, 0xb, 0xc, 0x5, 0x2,
> + 0x1, 0x6, 0xf, 0x8, 0xa, 0xd, 0x4, 0x3,
> + };
> + int i;
> +
> + x &= (1ull << bits) - 1;
> + bits = (bits + 3) & ~0x3;
> + for (i = bits - 4; i >= 0; i -= 4) {
> + c = tab[c ^ ((x >> i) & 0xf)];
> + }
> + return c;
> +}
> +
> +static uint32_t cfam_s_cfg_word(uint32_t fields)
> +{
> + return fields | cfam_s_crc4(0, fields >> 4, 28);
> +}
> +
> +static uint64_t fsi_cfam_s_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + FSICFAMSState *cfam = FSI_CFAM_S(opaque);
> + uint32_t off = (uint32_t)addr & CFAM_SID_MASK;
> + uint32_t val = 0;
> +
> + if (off < CFAM_RESPONDER_BASE) {
> + switch (off) {
> + case 0x00:
> + /* chip-id: NEXT set, MAJOR=9 (CFAM-S) */
> + val = cfam_s_cfg_word(CFAM_CONF_NEXT |
> + CFAM_CHIP_ID_MAJOR(FSI_CHIP_ID_MAJOR_CFAM_S));
> + break;
> + case 0x04:
> + /* responder engine entry */
> + val = cfam_s_cfg_word(CFAM_CONF_NEXT | CFAM_CONF_SLOTS(1) |
> + CFAM_CONF_VERSION(1) |
> + CFAM_CONF_TYPE(FSI_ENGINE_ID_RESPONDER));
> + break;
> + case 0x08:
> + /* mailbox engine entry, last (NEXT clear) */
> + val = cfam_s_cfg_word(CFAM_CONF_SLOTS(1) | CFAM_CONF_VERSION(1) |
> + CFAM_CONF_TYPE(FSI_ENGINE_ID_MBOXV1));
> + break;
> + default:
> + break;
> + }
> + } else if (off >= CFAM_MBOX_SCRATCH_BASE &&
> + off < CFAM_MBOX_SCRATCH_BASE + CFAM_S_MBOX_SCRATCH_NUM * 4) {
> + val = cfam->mbox_scratch[(off - CFAM_MBOX_SCRATCH_BASE) / 4];
> + }
> +
> + trace_fsi_cfam_config_read(addr, size);
> + return val;
> +}
> +
> +static void fsi_cfam_s_write(void *opaque, hwaddr addr, uint64_t data,
> + unsigned size)
> +{
> + FSICFAMSState *cfam = FSI_CFAM_S(opaque);
> + uint32_t off = (uint32_t)addr & CFAM_SID_MASK;
> +
> + if (off >= CFAM_MBOX_SCRATCH_BASE &&
> + off < CFAM_MBOX_SCRATCH_BASE + CFAM_S_MBOX_SCRATCH_NUM * 4) {
> + cfam->mbox_scratch[(off - CFAM_MBOX_SCRATCH_BASE) / 4] = (uint32_t)data;
> + }
> + trace_fsi_cfam_config_write(addr, size, data);
> +}
> +
> +static const MemoryRegionOps cfam_s_ops = {
> + .read = fsi_cfam_s_read,
> + .write = fsi_cfam_s_write,
> + .endianness = DEVICE_BIG_ENDIAN,
> + .valid.min_access_size = 1,
> + .valid.max_access_size = 4,
> + .impl.min_access_size = 1,
> + .impl.max_access_size = 4,
> +};
> +
> +static void fsi_cfam_s_realize(DeviceState *dev, Error **errp)
> +{
> + FSICFAMSState *cfam = FSI_CFAM_S(dev);
> +
> + memory_region_init_io(&cfam->mr, OBJECT(cfam), &cfam_s_ops, cfam,
> + TYPE_FSI_CFAM_S, CFAM_WINDOW_SIZE);
This could be in a instance_init() handler.
> +}
> +
> +static void fsi_cfam_s_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + dc->bus_type = TYPE_FSI_BUS;
> + dc->realize = fsi_cfam_s_realize;
> +}
> +
> +static const TypeInfo fsi_cfam_s_info = {
> + .name = TYPE_FSI_CFAM_S,
> + .parent = TYPE_FSI_SLAVE,
> + .instance_size = sizeof(FSICFAMSState),
> + .class_init = fsi_cfam_s_class_init,
> +};
> +
> +static void fsi_cfam_s_register_types(void)
> +{
> + type_register_static(&fsi_cfam_s_info);
> +}
> +
> +type_init(fsi_cfam_s_register_types);
> diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c
> index 083a5507ab..f7c19e24b0 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"
> @@ -112,8 +113,6 @@ static void fsi_master_init(Object *o)
> {
> FSIMasterState *s = FSI_MASTER(o);
>
> - object_initialize_child(o, "cfam", &s->cfam, TYPE_FSI_CFAM);
> -
euh. Don't to that. Why ?
> qbus_init(&s->bus, sizeof(s->bus), TYPE_FSI_BUS, DEVICE(s), NULL);
>
> memory_region_init_io(&s->iomem, OBJECT(s), &fsi_master_ops, s,
> @@ -125,12 +124,19 @@ static void fsi_master_realize(DeviceState *dev, Error **errp)
> {
> FSIMasterState *s = FSI_MASTER(dev);
>
> + object_initialize_child(OBJECT(dev), "cfam", &s->cfam, TYPE_FSI_CFAM);
no again.> if (!qdev_realize(DEVICE(&s->cfam), BUS(&s->bus), errp)) {
> return;
> }
>
> /* address ? */
> memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.mr);
> +
> + object_initialize_child(OBJECT(dev), "cfam-s", &s->cfam_s, TYPE_FSI_CFAM_S);
no again.
> + if (!qdev_realize(DEVICE(&s->cfam_s), BUS(&s->bus), errp)) {
> + return;
> + }
> + memory_region_add_subregion(&s->opb2fsi, 2 * MiB, &s->cfam_s.mr);
> }
>
> static void fsi_master_reset(DeviceState *dev)
> 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'))
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 3/5] hw/sensor: Add UCD90320 model
2026-07-15 15:50 ` [PATCH v1 3/5] hw/sensor: Add UCD90320 model Mikail Sadic
@ 2026-07-16 14:04 ` Cédric Le Goater
0 siblings, 0 replies; 15+ messages in thread
From: Cédric Le Goater @ 2026-07-16 14:04 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 7/15/26 17:50, Mikail Sadic wrote:
> 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/i2c/aspeed_i2c.c | 7 ++
> hw/sensor/ucd90320.c | 144 ++++++++++++++++++++++++++++++++++++++++
> hw/arm/Kconfig | 1 +
> hw/sensor/Kconfig | 4 ++
> hw/sensor/meson.build | 1 +
> 8 files changed, 195 insertions(+)
> create mode 100644 docs/specs/ucd90320.rst
> create mode 100644 hw/sensor/ucd90320.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ecb8cfdc41..b2861dfc79 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4059,6 +4059,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/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 */
The I2C part belongs to another patch.
> diff --git a/hw/sensor/ucd90320.c b/hw/sensor/ucd90320.c
> new file mode 100644
> index 0000000000..f3b2a5d777
> --- /dev/null
> +++ b/hw/sensor/ucd90320.c
> @@ -0,0 +1,144 @@
> +/*
> + * 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 "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
> +
> +typedef struct UCD90320State {
> + PMBusDevice parent;
> +} UCD90320State;
> +
> +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)
> +{
> + switch (pmdev->code) {
> + case UCD9000_DEVICE_ID: {
> + static const uint8_t id[] = "UCD90320";
this is hacky. use device constant reset values or class fields.
Same below.
> + ucd90320_send_block(pmdev, id, sizeof(id) - 1);
> + pmbus_idle(pmdev);
> + return 0;
> + }
> + case UCD9000_NUM_PAGES:
> + pmbus_send8(pmdev, UCD90320_NUM_PAGES);
> + pmbus_idle(pmdev);
> + return 0;
> +
> + case UCD9000_MONITOR_CONFIG: {
> + static const uint8_t cfg[] = { 0x00 };
> + ucd90320_send_block(pmdev, cfg, sizeof(cfg));
> + pmbus_idle(pmdev);
> + return 0;
> + }
> + case UCD9000_MFR_STATUS: {
> + static const uint8_t status[] = { 0x00, 0x00, 0x00, 0x00 };
> + ucd90320_send_block(pmdev, status, sizeof(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);
> +
> + 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 */
> + }
> +}
> +
> +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 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";
> + 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_info = {
> + .name = TYPE_UCD90320,
> + .parent = TYPE_PMBUS_DEVICE,
> + .instance_size = sizeof(UCD90320State),
> + .instance_init = ucd90320_init,
> + .class_init = ucd90320_class_init,
> +};
> +
> +static void ucd90320_register_types(void)
> +{
> + type_register_static(&ucd90320_info);
> +}
> +
> +type_init(ucd90320_register_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'))
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 5/5] tests/functional: Add Huygens BMC boot test
2026-07-15 15:50 ` [PATCH v1 5/5] tests/functional: Add Huygens BMC boot test Mikail Sadic
@ 2026-07-16 14:05 ` Cédric Le Goater
0 siblings, 0 replies; 15+ messages in thread
From: Cédric Le Goater @ 2026-07-16 14:05 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 7/15/26 17:50, Mikail Sadic wrote:
> Add a functional test for the huygens-bmc machine that boots from FMC
> flash and a UFS storage image, verifying the full boot sequence from
> U-Boot through OpenBMC login and multi-user target.
> Note: The Huygens img is not yet public, so this test is for future/internal
> use. It has been tested/verified with the correct image, and will gracefully
> exit if image is not found.
>
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
> tests/functional/aarch64/meson.build | 2 +
> .../functional/aarch64/test_aspeed_huygens.py | 49 +++++++++++++++++++
> 2 files changed, 51 insertions(+)
> create mode 100755 tests/functional/aarch64/test_aspeed_huygens.py
>
> diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
> index e81afd6c39..d81c0d6761 100644
> --- a/tests/functional/aarch64/meson.build
> +++ b/tests/functional/aarch64/meson.build
> @@ -4,6 +4,7 @@ test_aarch64_timeouts = {
> 'aspeed_ast2700a1' : 600,
> 'aspeed_ast2700a2' : 600,
> 'aspeed_ast2700fc' : 600,
> + 'aspeed_huygens' : 600,
> 'device_passthrough' : 720,
> 'imx8mm_evk' : 240,
> 'imx8mp_evk' : 240,
> @@ -29,6 +30,7 @@ tests_aarch64_system_thorough = [
> 'aspeed_ast2700a1',
> 'aspeed_ast2700a2',
> 'aspeed_ast2700fc',
> + 'aspeed_huygens',
> 'device_passthrough',
> 'hotplug_pci',
> 'imx8mm_evk',
> diff --git a/tests/functional/aarch64/test_aspeed_huygens.py b/tests/functional/aarch64/test_aspeed_huygens.py
> new file mode 100755
> index 0000000000..750d6e0d7f
> --- /dev/null
> +++ b/tests/functional/aarch64/test_aspeed_huygens.py
> @@ -0,0 +1,49 @@
> +#!/usr/bin/env python3
> +#
> +# Functional test that boots the ASPEED Huygens machine with UFS storage
> +#
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +import os
> +
> +from aspeed import AspeedTest
> +
> +class HuygensMachine(AspeedTest):
> +
> + def test_arm_aspeed_ufs_boot(self):
> + self.set_machine('huygens-bmc')
> + self.require_netdev('user')
> +
> + # Huygens image is not public yet, skip if missing
> + fmc_path = '/tmp/fmc-huygens.img'
> + ufs_path = '/tmp/ufs-huygens.img'
This is dead code. resend when they are then.
Thanks,
C.
> + if not os.path.exists(fmc_path) or not os.path.exists(ufs_path):
> + self.skipTest(f'Missing required images: {fmc_path}, {ufs_path}')
> +
> + self.vm.set_console()
> + self.vm.add_args('-drive',
> + 'file=' + fmc_path + ',if=mtd,format=raw',
> + '-drive',
> + 'file=' + ufs_path +
> + ',if=none,format=raw,readonly=off',
> + '-nic', 'user,model=ftgmac100,net=10.0.2.0/24',
> + '-nic', 'user,model=ftgmac100,net=10.0.3.0/24',
> + '-nic', 'user,model=ftgmac100,net=10.0.4.0/24')
> + self.vm.launch()
> +
> + self.wait_for_console_pattern('U-Boot 2023.10')
> + self.wait_for_console_pattern(
> + 'Vendor: ASPEED Prod.: UFS QEMU Rev: 1.00')
> + self.wait_for_console_pattern('Starting kernel ...')
> + self.wait_for_console_pattern('Machine model: Huygens')
> + self.wait_for_console_pattern('Starting systemd-udevd version 257.1')
> + self.wait_for_console_pattern('Phosphor OpenBMC')
> + self.wait_for_console_pattern('huygens login:')
> + self.wait_for_console_pattern('Active BMC Target')
> + self.wait_for_console_pattern('Multi-User System')
> + self.vm.shutdown()
> +
> +
> +if __name__ == '__main__':
> + AspeedTest.main()
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700
2026-07-15 15:50 ` [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700 Mikail Sadic
2026-07-16 14:01 ` Cédric Le Goater
@ 2026-07-22 17:45 ` Ninad Palsule
1 sibling, 0 replies; 15+ messages in thread
From: Ninad Palsule @ 2026-07-22 17:45 UTC (permalink / raw)
To: mikail.sadic
Cc: andrew, calebs, clg, jamin_lin, jeuk20.kim, joel, kane_chen,
leetroy, milesg, ninad, pbonzini, peter.maydell, philmd, qemu-arm,
qemu-devel, steven_lee, titusr
Hi Mikail,
Thanks for the work. Please see my comments inline.
cfam-s has nothing to do with ast2700. It is a derivative of the cfam. So please remove all references of ast2700.
cfam/cfam-s is the device sits near the host processor for the FRU service.
FYI... Aspeed chip (ast2700) has 2 FSI master controller. Each has two links. 1 link can be connected to cfam and second link can be connected to cfam-s.
On 7/15/26 10:50 AM, Mikail Sadic wrote:
> Introduce TYPE_FSI_CFAM_S as a dedicated QOM type that presents the
> config table and mailbox scratch registers expected by the new Linux
> FSI responder framework. FSIMasterState gains a cfam_s field.
> fsi_master_realize() instantiates both cfam (at offset 0) and cfam-s
> (at offset 2 MiB) into the OPB-to-FSI aperture. aspeed_ast27x0.c wires
> up both fsi[0] and fsi[1] APB-to-OPB bridges for the AST2700 SoC.
We don't need references to aspeed or ast2700.
>
>
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
> docs/specs/fsi.rst | 58 +++++++++++++++
> include/hw/fsi/cfam-s.h | 26 +++++++
> include/hw/fsi/fsi-master.h | 2 +
> hw/arm/aspeed_ast27x0.c | 18 +++++
> hw/fsi/cfam-s.c | 145 ++++++++++++++++++++++++++++++++++++
> hw/fsi/fsi-master.c | 10 ++-
> hw/fsi/meson.build | 2 +-
> 7 files changed, 258 insertions(+), 3 deletions(-)
> 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..a8896b67ef 100644
> --- a/docs/specs/fsi.rst
> +++ b/docs/specs/fsi.rst
> @@ -120,3 +120,61 @@ from the BMC. (see the `pdbg source repository`_ for more details)
>
> .. _pdbg source repository:
> https://github.com/open-power/pdbg
> +
> +CFAM-S model (AST2700)
> +----------------------
> +
> +The AST2700 uses a CFAM variant known as the CFAM-S, required by the new
> +Linux FSI responder framework. A dedicated QOM type, ``TYPE_FSI_CFAM_S``
> +(``"cfam-s"``), implements this alongside the existing ``TYPE_FSI_CFAM``
> +(``"cfam"``) used by the AST2600 machines.
> +
Remove references to ast2700/ast2600 and aspeed. You can say that cfam-s is derived from cfam and it has IO units and limited engines supported.
>
> +``FSIMasterState`` realizes both ``cfam`` and ``cfam-s``. The regular
> +``cfam`` is mapped at offset 0 into the OPB-to-FSI aperture; ``cfam-s`` is
> +mapped at offset 2 MiB. The DTS overrides ``fsim0`` with
> +``compatible = "aspeed,ast2600-fsi-master"`` and ``reg = <0x21800000>``,
> +pointing the kernel FSI driver at the QEMU APB-to-OPB bridge, which routes
> +through the FSI master's OPB-to-FSI window to reach the CFAM-S.
This is a hardware so we don't want to talk about dts and fsi driver.
>
> +
> +The CFAM-S model presents an 8 MiB region and folds the SID bits ``[22:21]``
> +so both the SID_BREAK enumeration view (``0x600000``) and the runtime view
> +(``0x000000``) address the same register space. The config table at folded
> +offset ``0x000`` contains three CRC4-valid words: a chip_id word with
> +``MAJOR=9`` (selecting the ``cfam_s`` kernel driver), a responder engine
> +entry (``TYPE=0x3``), and a mailbox v1 engine entry (``TYPE=0x14``) at
> +engine address ``0x800``.
> +
> +The responder registers at folded offset ``0x400`` implement ``SMODE``,
> +``SSTAT``, ``SRES``, ``SSISM``, and ``SLBUS`` with store-on-write semantics.
> +The mailbox scratch registers at folded offset ``0x8e0`` are five stateful
> +32-bit registers served to the ``ibm,mbox-cfam-s`` kernel driver, which
> +creates ``/dev/fsi/mbox0``.
We don't want to provide linux device path etc and we should reduce the details.
>
> +
> +The following commands start the ``huygens-bmc`` machine with the built-in
> +CFAM-S model. There are no model specific arguments. Please check this
> +document to learn more about Aspeed ``huygens-bmc`` machine:
> +(:doc:`../../system/arm/aspeed`)
> +
> +.. code-block:: console
> +
> + qemu-system-aarch64 -M huygens-bmc \
> + -drive file=image-bmc,if=mtd,format=raw \
> + -drive file=ufs.img,if=none,format=raw \
> + -nographic
> +
> +The CFAM-S appears as follows in the QEMU device tree:
> +
> +.. code-block:: console
> +
> + (qemu) info qtree
> + bus: main-system-bus
> + type System
> + ...
> + dev: aspeed.apb2opb, id ""
> + mmio 0000000021800000/0000000000001000
> + bus: opb.0
> + type opb
> + dev: fsi.master, id ""
> + bus: fsi.bus.0
> + type fsi.bus
> + dev: cfam-s, id ""
> diff --git a/include/hw/fsi/cfam-s.h b/include/hw/fsi/cfam-s.h
> new file mode 100644
> index 0000000000..3793b01471
> --- /dev/null
> +++ b/include/hw/fsi/cfam-s.h
> @@ -0,0 +1,26 @@
> +/*
> + * 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/fsi.h"
> +
> +#define TYPE_FSI_CFAM_S "cfam-s"
> +#define FSI_CFAM_S(obj) OBJECT_CHECK(FSICFAMSState, (obj), TYPE_FSI_CFAM_S)
> +
> +#define CFAM_S_MBOX_SCRATCH_NUM 5
> +
> +typedef struct FSICFAMSState {
> + FSISlaveState parent;
> +
> + MemoryRegion mr;
> + uint32_t mbox_scratch[CFAM_S_MBOX_SCRATCH_NUM];
similar to cfam, cfam-s also has local bus and engines are connected to that bus. So we need to model similar to the cfam.c. See if we can make things common to avoid duplicates.
>
> +} FSICFAMSState;
> +
> +#endif /* FSI_CFAM_S_H */
> diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h
> index 60ddaa994f..6a2782fdf2 100644
> --- a/include/hw/fsi/fsi-master.h
> +++ b/include/hw/fsi/fsi-master.h
> @@ -11,6 +11,7 @@
> #include "hw/core/qdev.h"
> #include "hw/fsi/fsi.h"
> #include "hw/fsi/cfam.h"
> +#include "hw/fsi/cfam-s.h"
>
> #define TYPE_FSI_MASTER "fsi.master"
> OBJECT_DECLARE_SIMPLE_TYPE(FSIMasterState, FSI_MASTER)
> @@ -26,6 +27,7 @@ typedef struct FSIMasterState {
>
> uint32_t regs[FSI_MASTER_NR_REGS];
> FSICFAMState cfam;
> + FSICFAMSState cfam_s;
> } FSIMasterState;
>
>
> diff --git a/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],
Above aspeed (aspeed_ast27x0.c) changes should be in different patch
>
> diff --git a/hw/fsi/cfam-s.c b/hw/fsi/cfam-s.c
> new file mode 100644
> index 0000000000..bf000d1879
> --- /dev/null
> +++ b/hw/fsi/cfam-s.c
> @@ -0,0 +1,145 @@
> +/*
> + * IBM Common FRU Access Macro - S variant (CFAM-S)
> + *
> + * Copyright (C) 2026 IBM Corp.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +#include "hw/fsi/cfam-s.h"
> +#include "hw/fsi/fsi.h"
> +
> +/* bits [22:21] are the slave ID, low 21 bits address registers within it */
> +#define CFAM_SID_MASK 0x1fffff
> +#define CFAM_WINDOW_SIZE 0x800000 /* 8 MiB per slave, covers SID 0..3 */
> +
> +#define CFAM_RESPONDER_BASE 0x400 /* == FSI_RESPONDER_PAGE_SIZE */
> +
> +/* Config-table word fields (see Linux fsi-master.h) */
> +#define CFAM_CONF_NEXT (1u << 31)
> +#define CFAM_CONF_SLOTS(n) (((n) & 0xff) << 16)
> +#define CFAM_CONF_VERSION(v) (((v) & 0xf) << 12)
> +#define CFAM_CONF_TYPE(t) (((t) & 0xff) << 4)
> +#define CFAM_CHIP_ID_MAJOR(m) (((m) & 0xf) << 8)
> +
> +/* Engine IDs (include/linux/fsi.h) */
> +#define FSI_ENGINE_ID_RESPONDER 0x3
> +#define FSI_ENGINE_ID_MBOXV1 0x14
> +#define FSI_CHIP_ID_MAJOR_CFAM_S 0x9 /* major == 9 -> CFAM-S */
> +
> +/* Engine layout: 0x000 config table, 0x400 responder, 0x800 mbox */
> +#define CFAM_MBOX_BASE 0x800
> +#define CFAM_MBOX_SCRATCH_OFF 0xe0
> +#define CFAM_MBOX_SCRATCH_BASE (CFAM_MBOX_BASE + CFAM_MBOX_SCRATCH_OFF)
> +
> +static uint8_t cfam_s_crc4(uint8_t c, uint64_t x, int bits)
> +{
> + static const uint8_t tab[16] = {
> + 0x0, 0x7, 0xe, 0x9, 0xb, 0xc, 0x5, 0x2,
> + 0x1, 0x6, 0xf, 0x8, 0xa, 0xd, 0x4, 0x3,
> + };
> + int i;
> +
> + x &= (1ull << bits) - 1;
> + bits = (bits + 3) & ~0x3;
> + for (i = bits - 4; i >= 0; i -= 4) {
> + c = tab[c ^ ((x >> i) & 0xf)];
> + }
> + return c;
> +}
> +
> +static uint32_t cfam_s_cfg_word(uint32_t fields)
> +{
> + return fields | cfam_s_crc4(0, fields >> 4, 28);
> +}
> +
> +static uint64_t fsi_cfam_s_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + FSICFAMSState *cfam = FSI_CFAM_S(opaque);
> + uint32_t off = (uint32_t)addr & CFAM_SID_MASK;
> + uint32_t val = 0;
> +
> + if (off < CFAM_RESPONDER_BASE) {
> + switch (off) {
> + case 0x00:
> + /* chip-id: NEXT set, MAJOR=9 (CFAM-S) */
> + val = cfam_s_cfg_word(CFAM_CONF_NEXT |
> + CFAM_CHIP_ID_MAJOR(FSI_CHIP_ID_MAJOR_CFAM_S));
> + break;
> + case 0x04:
> + /* responder engine entry */
> + val = cfam_s_cfg_word(CFAM_CONF_NEXT | CFAM_CONF_SLOTS(1) |
> + CFAM_CONF_VERSION(1) |
> + CFAM_CONF_TYPE(FSI_ENGINE_ID_RESPONDER));
> + break;
> + case 0x08:
> + /* mailbox engine entry, last (NEXT clear) */
> + val = cfam_s_cfg_word(CFAM_CONF_SLOTS(1) | CFAM_CONF_VERSION(1) |
> + CFAM_CONF_TYPE(FSI_ENGINE_ID_MBOXV1));
> + break;
> + default:
> + break;
> + }
> + } else if (off >= CFAM_MBOX_SCRATCH_BASE &&
> + off < CFAM_MBOX_SCRATCH_BASE + CFAM_S_MBOX_SCRATCH_NUM * 4) {
> + val = cfam->mbox_scratch[(off - CFAM_MBOX_SCRATCH_BASE) / 4];
> + }
> +
> + trace_fsi_cfam_config_read(addr, size);
> + return val;
> +}
> +
> +static void fsi_cfam_s_write(void *opaque, hwaddr addr, uint64_t data,
> + unsigned size)
> +{
> + FSICFAMSState *cfam = FSI_CFAM_S(opaque);
> + uint32_t off = (uint32_t)addr & CFAM_SID_MASK;
> +
> + if (off >= CFAM_MBOX_SCRATCH_BASE &&
> + off < CFAM_MBOX_SCRATCH_BASE + CFAM_S_MBOX_SCRATCH_NUM * 4) {
> + cfam->mbox_scratch[(off - CFAM_MBOX_SCRATCH_BASE) / 4] = (uint32_t)data;
> + }
> + trace_fsi_cfam_config_write(addr, size, data);
> +}
> +
> +static const MemoryRegionOps cfam_s_ops = {
> + .read = fsi_cfam_s_read,
> + .write = fsi_cfam_s_write,
> + .endianness = DEVICE_BIG_ENDIAN,
> + .valid.min_access_size = 1,
> + .valid.max_access_size = 4,
> + .impl.min_access_size = 1,
> + .impl.max_access_size = 4,
> +};
> +
> +static void fsi_cfam_s_realize(DeviceState *dev, Error **errp)
> +{
> + FSICFAMSState *cfam = FSI_CFAM_S(dev);
> +
> + memory_region_init_io(&cfam->mr, OBJECT(cfam), &cfam_s_ops, cfam,
> + TYPE_FSI_CFAM_S, CFAM_WINDOW_SIZE);
> +}
> +
> +static void fsi_cfam_s_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + dc->bus_type = TYPE_FSI_BUS;
> + dc->realize = fsi_cfam_s_realize;
> +}
> +
> +static const TypeInfo fsi_cfam_s_info = {
> + .name = TYPE_FSI_CFAM_S,
> + .parent = TYPE_FSI_SLAVE,
> + .instance_size = sizeof(FSICFAMSState),
> + .class_init = fsi_cfam_s_class_init,
> +};
> +
> +static void fsi_cfam_s_register_types(void)
> +{
> + type_register_static(&fsi_cfam_s_info);
> +}
Above changes should look similar to cfam.c with lbus.
>
> +
> +type_init(fsi_cfam_s_register_types);
> diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c
> index 083a5507ab..f7c19e24b0 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"
> @@ -112,8 +113,6 @@ static void fsi_master_init(Object *o)
> {
> FSIMasterState *s = FSI_MASTER(o);
>
> - object_initialize_child(o, "cfam", &s->cfam, TYPE_FSI_CFAM);
> -
> qbus_init(&s->bus, sizeof(s->bus), TYPE_FSI_BUS, DEVICE(s), NULL);
>
> memory_region_init_io(&s->iomem, OBJECT(s), &fsi_master_ops, s,
> @@ -125,12 +124,19 @@ static void fsi_master_realize(DeviceState *dev, Error **errp)
> {
> FSIMasterState *s = FSI_MASTER(dev);
>
> + object_initialize_child(OBJECT(dev), "cfam", &s->cfam, TYPE_FSI_CFAM);
why did we move from master_init to master_realize?
Thanks & Regards,
Ninad Palsule
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-22 17:46 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 15:50 [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Mikail Sadic
2026-07-15 15:50 ` [PATCH v1 1/5] arm/aspeed: Add AST2700 Huygens machine Mikail Sadic
2026-07-16 13:56 ` Cédric Le Goater
2026-07-15 15:50 ` [PATCH v1 2/5] fsi/cfam: Add CFAM-S model for AST2700 Mikail Sadic
2026-07-16 14:01 ` Cédric Le Goater
2026-07-22 17:45 ` Ninad Palsule
2026-07-15 15:50 ` [PATCH v1 3/5] hw/sensor: Add UCD90320 model Mikail Sadic
2026-07-16 14:04 ` Cédric Le Goater
2026-07-15 15:50 ` [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller Mikail Sadic
2026-07-16 0:31 ` Jeuk Kim
2026-07-16 0:48 ` Jamin Lin
2026-07-15 15:50 ` [PATCH v1 5/5] tests/functional: Add Huygens BMC boot test Mikail Sadic
2026-07-16 14:05 ` Cédric Le Goater
2026-07-15 16:01 ` [PATCH v1 0/5] Add IBM Huygens BMC machine for AST2700 Glenn Miles
2026-07-15 17:08 ` CALEB SCHLOSSIN
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.