:p
atchew
Login
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
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 XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/aspeed.rst +++ b/docs/system/arm/aspeed.rst @@ -XXX,XX +XXX,XX @@ 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 : @@ -XXX,XX +XXX,XX @@ 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 ----------------- @@ -XXX,XX +XXX,XX @@ 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 --------------- @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/arm/aspeed_ast27x0_huygens.c @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -XXX,XX +XXX,XX @@ 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
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 XXXXXXX..XXXXXXX 100644 --- a/docs/specs/fsi.rst +++ b/docs/specs/fsi.rst @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/fsi/cfam-s.h @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX 100644 --- a/include/hw/fsi/fsi-master.h +++ b/include/hw/fsi/fsi-master.h @@ -XXX,XX +XXX,XX @@ #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) @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -XXX,XX +XXX,XX @@ #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" @@ -XXX,XX +XXX,XX @@ 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, }; @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/fsi/cfam-s.c @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/fsi-master.c +++ b/hw/fsi/fsi-master.c @@ -XXX,XX +XXX,XX @@ #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" @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/meson.build +++ b/hw/fsi/meson.build @@ -XXX,XX +XXX,XX @@ -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
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 XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/docs/specs/ucd90320.rst @@ -XXX,XX +XXX,XX @@ +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 XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/sensor/ucd90320.c @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/sensor/Kconfig +++ b/hw/sensor/Kconfig @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/sensor/meson.build +++ b/hw/sensor/meson.build @@ -XXX,XX +XXX,XX @@ 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
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 XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/docs/specs/aspeed-ufs.rst @@ -XXX,XX +XXX,XX @@ +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 XXXXXXX..XXXXXXX 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -XXX,XX +XXX,XX @@ #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" @@ -XXX,XX +XXX,XX @@ struct AspeedSoCState { AspeedAPB2OPBState fsi[2]; AspeedLTPIState ltpi_ctrl[ASPEED_IOEXP_NUM]; AspeedAST1700SoCState ioexp[ASPEED_IOEXP_NUM]; + AspeedUFSState ufs; }; #define TYPE_ASPEED_SOC "aspeed-soc" @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/ufs/aspeed_ufs.h @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ 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); @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/ufs/aspeed_ufs.c @@ -XXX,XX +XXX,XX @@ +/* + * 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 XXXXXXX..XXXXXXX 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
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 XXXXXXX..XXXXXXX 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -XXX,XX +XXX,XX @@ test_aarch64_timeouts = { 'aspeed_ast2700a1' : 600, 'aspeed_ast2700a2' : 600, 'aspeed_ast2700fc' : 600, + 'aspeed_huygens' : 600, 'device_passthrough' : 720, 'imx8mm_evk' : 240, 'imx8mp_evk' : 240, @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/functional/aarch64/test_aspeed_huygens.py @@ -XXX,XX +XXX,XX @@ +#!/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
This series adds initial support for the IBM Huygens BMC, the baseboard management controller of an IBM POWER12 server platform, based on the ASPEED AST2700 A2 SoC. It introduces a CFAM-S FSI responder model, wires the AST2700 FSI controllers to their APB-to-OPB bridges, fixes an aspeed I2C DMA block-read corner case, adds a TI UCD90320 PMBus power-sequencer model, refactors the UFS device into a shared core with a new AST2700 sysbus frontend, and finally adds the huygens-bmc machine that ties these together to boot OpenBMC from FMC flash and a UFS root filesystem. Patch summary: 1. fsi/cfam: a common CFAM model holding the slot, config table, responder and local bus, with the existing cfam as its first derivative. No functional change to the existing cfam. 2. fsi/cfam: the CFAM-S as a second derivative - its own config table, the slave-ID alias window, and a v1 mailbox engine added to lbus.c alongside the scratchpad. 3. arm/aspeed: wire the two AST2700 FSI controllers to their APB-to-OPB bridges. 4. i2c/aspeed: fix DMA-receive first-byte handling so SMBus block reads (I2C_M_RECV_LEN) report the correct length. 5. hw/sensor: TI UCD90320 24-rail PMBus power sequencer. 6. ufs: refactor the transport-independent UFS core out of the PCI device (SDHCI/XHCI-style) so PCI and sysbus frontends share it. TYPE_UFS stays a PCI device and -device ufs is unchanged. 7. ufs/aspeed: AST2700 sysbus UFS frontend on the shared core. 8. arm/aspeed: the huygens-bmc machine. Changes since v1: * Split the original 5 commits into 8. The aspeed I2C DMA fix, the FSI controller wiring, the CFAM common base class, and the UFS core-vs-frontend split are now separate patches (each buildable on its own). * UFS: reworked per review (Jeuk Kim). Instead of a second, standalone UFS implementation, the common core (register block, UTRL/UTMRL, UPIU, query and ufs-lu handling) is separated from the PCI glue (DMA address space, IRQ, realize) so the AST2700 controller is a thin sysbus frontend on top of it, following the SDHCI/XHCI pattern. Two small, backwards-compatible additions ride on the core: a configurable ufs-lu logical-block-size (default unchanged, so the PCI device is unaffected) and an unmapped-LUN INQUIRY that reports "not connected" instead of failing the request. The PCI ufs-tests qtests still pass. * CFAM-S: reworked per review (Ninad). It now shares a common base class with the cfam rather than being a parallel implementation: patch 1 adds an abstract cfam-common whose realize is driven by an FSICFAMCommonClass (config table, responder and local-bus offsets, and an engines hook), and both cfam and cfam-s derive from it. They are siblings rather than parent and child, since a CFAM-S is another kind of CFAM and not a specialisation of the P9 one. Patch 2 adds cfam-s supplying only its own config table, its mailbox engine and the slave-ID alias window; the v1 mailbox is a local bus engine so it sits in lbus.c next to the scratchpad. Duplicated code has been cleaned. Two behavior changes follow from sharing the common config ops, both noted in patch 2: cfam-s now honors the SID_BREAK sequence, and config-table access is word-only for both types. Also made generic, all specific references dropped. Patch 1 is intended to be a no-op for the existing cfam, and was checked that way: aspeed_fsi-test passes, the four P9 config words read back identically over the OPB, and "info mtree" for ast2600-evb is unchanged. The Rainier boot test was also run and verified. * i2c/aspeed: the DMA block-read fix is now its own patch, with a commit message explaining the I2C_M_RECV_LEN + DMA interaction (Cédric). * UCD90320: the device-id/monitor-config/mfr-status responses are now device reset state rather than file-scope constants, with matching VMState (Cédric). * Docs: the huygens-bmc and aspeed-ufs boot examples now use -blockdev instead of -drive (Cédric). * Machine: commit message expanded (POWER12 BMC on AST2700 A2), the VPD buffer uses g_autofree, the docs describe the flash-vs-UFS split, and the networking example was simplified. * New device files use DEFINE_TYPES() with inlined TypeInfo (Jamin Lin). * Dropped the v1 functional test for now: depends on non-public Huygens images. It will be resent once images go public. Testing done: * Whole series builds. * aspeed_fsi-test passes (4/4), unchanged by the CFAM refactor. * huygens-bmc boots OpenBMC to the login prompt and multi-user target from FMC flash + UFS storage (internal images). * PCI UFS ufs-tests qtests pass, unchanged by the refactor. * make check passes all tests. Note: checkpatch reports a couple of MAINTAINERS/SPDX warnings for the new files; they are covered by existing wildcard entries in the ASPEED and FSI sections. Thank you for all of the helpful feedback. Mikail Sadic (8): fsi/cfam: Add common CFAM base class fsi/cfam: Add CFAM-S model arm/aspeed: Wire AST2700 FSI controllers to APB-to-OPB bridges i2c/aspeed: Fix DMA receive first-byte handling for block reads hw/sensor: Add UCD90320 model ufs: Refactor common core out of the PCI implementation ufs/aspeed: Add AST2700 sysbus UFS frontend arm/aspeed: Add AST2700 Huygens machine MAINTAINERS | 2 + docs/specs/aspeed-ufs.rst | 40 ++++++ docs/specs/fsi.rst | 15 ++ docs/specs/index.rst | 2 + docs/specs/ucd90320.rst | 36 +++++ docs/system/arm/aspeed.rst | 50 ++++++- hw/ufs/ufs.h | 18 ++- include/hw/arm/aspeed.h | 1 + include/hw/arm/aspeed_soc.h | 3 + include/hw/fsi/cfam-s.h | 40 ++++++ include/hw/fsi/cfam.h | 68 ++++++++- include/hw/fsi/fsi-master.h | 2 + include/hw/fsi/lbus.h | 11 ++ include/hw/ufs/aspeed_ufs.h | 16 +++ hw/arm/aspeed.c | 25 ++++ hw/arm/aspeed_ast27x0.c | 31 ++++ hw/arm/aspeed_ast27x0_huygens.c | 241 ++++++++++++++++++++++++++++++++ hw/fsi/cfam-s.c | 91 ++++++++++++ hw/fsi/cfam.c | 123 +++++++++------- hw/fsi/fsi-master.c | 9 +- hw/fsi/lbus.c | 69 +++++++++ hw/i2c/aspeed_i2c.c | 7 + hw/sensor/ucd90320.c | 169 ++++++++++++++++++++++ hw/ufs/aspeed_ufs.c | 75 ++++++++++ hw/ufs/lu.c | 50 ++++++- hw/ufs/ufs.c | 57 +++++--- hw/arm/Kconfig | 1 + hw/arm/meson.build | 1 + hw/fsi/meson.build | 2 +- hw/fsi/trace-events | 2 + hw/sensor/Kconfig | 4 + hw/sensor/meson.build | 1 + hw/ufs/meson.build | 1 + 33 files changed, 1182 insertions(+), 81 deletions(-) create mode 100644 docs/specs/aspeed-ufs.rst create mode 100644 docs/specs/ucd90320.rst create mode 100644 include/hw/fsi/cfam-s.h create mode 100644 include/hw/ufs/aspeed_ufs.h create mode 100644 hw/arm/aspeed_ast27x0_huygens.c create mode 100644 hw/fsi/cfam-s.c create mode 100644 hw/sensor/ucd90320.c create mode 100644 hw/ufs/aspeed_ufs.c base-commit: e1705a25aff35635c360bbaba4c2731d019a422a -- 2.53.0
Prepare a common CFAM parent class for CFAM-S implementation. Add an abstract TYPE_FSI_CFAM_COMMON with an FSICFAMCommonClass holding what varies between types: the config table, the offsets of the FSI responder and the local bus within the register slot, and a realize_engines() hook for the engines on the local bus. Its realize is driven by those fields, so one realize serves every type. TYPE_FSI_CFAM keeps its name and becomes the first derived type, supplying the P9 config table, its slot offsets and the scratchpad engine. The config table becomes data, still written with the same CFAM_CONFIG_REG() expressions, which move to the header so a derived type can describe its own table the same way. No functional change: same memory map, same config words, same type and child names. aspeed_fsi-test verified. Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- include/hw/fsi/cfam.h | 58 ++++++++++++++++++-- hw/fsi/cfam.c | 123 ++++++++++++++++++++++++------------------ hw/fsi/fsi-master.c | 2 +- 3 files changed, 127 insertions(+), 56 deletions(-) diff --git a/include/hw/fsi/cfam.h b/include/hw/fsi/cfam.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/fsi/cfam.h +++ b/include/hw/fsi/cfam.h @@ -XXX,XX +XXX,XX @@ #ifndef FSI_CFAM_H #define FSI_CFAM_H +#include "qemu/units.h" #include "system/memory.h" #include "hw/fsi/fsi.h" #include "hw/fsi/lbus.h" +/* + * All CFAM flavors present a register slot holding a config table, an FSI + * responder and a local bus carrying the engines the config table describes. + * That is the common model; each flavor supplies its own table, slot layout + * and engines. + */ +#define TYPE_FSI_CFAM_COMMON "cfam-common" +OBJECT_DECLARE_TYPE(FSICFAMCommonState, FSICFAMCommonClass, FSI_CFAM_COMMON) + #define TYPE_FSI_CFAM "cfam" -#define FSI_CFAM(obj) OBJECT_CHECK(FSICFAMState, (obj), TYPE_FSI_CFAM) +OBJECT_DECLARE_SIMPLE_TYPE(FSICFAMState, FSI_CFAM) /* P9-ism */ #define CFAM_CONFIG_NR_REGS 0x28 -typedef struct FSICFAMState { +#define FSI_CFAM_SLOT_SIZE (2 * MiB) +#define FSI_CFAM_CONFIG_SIZE 0x400 + +#define ENGINE_CONFIG_NEXT BIT(31) +#define ENGINE_CONFIG_TYPE_PEEK (0x02 << 4) +#define ENGINE_CONFIG_TYPE_FSI (0x03 << 4) +#define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4) + +/* Valid, slots, version, type, crc */ +#define CFAM_CONFIG_REG(__VER, __TYPE, __CRC) \ + (ENGINE_CONFIG_NEXT | \ + 0x00010000 | \ + (__VER) | \ + (__TYPE) | \ + (__CRC)) + +struct FSICFAMCommonState { /* < private > */ FSISlaveState parent; @@ -XXX,XX +XXX,XX @@ typedef struct FSICFAMState { MemoryRegion mr; FSILBus lbus; +}; + +struct FSICFAMCommonClass { + /* < private > */ + DeviceClass parent_class; + + /* < public > */ + /* Config table served by the common ops, one word per 4-byte offset */ + const uint32_t *config; + unsigned config_nr; + + /* Layout of the register slot */ + hwaddr responder_offset; + hwaddr lbus_offset; + + /* Realize and map this flavor's local bus engines */ + bool (*realize_engines)(FSICFAMCommonState *cfam, Error **errp); +}; + +struct FSICFAMState { + /* < private > */ + FSICFAMCommonState parent; + FSIScratchPad scratchpad; -} FSICFAMState; +}; + +bool fsi_cfam_add_engine(FSICFAMCommonState *cfam, DeviceState *engine, + hwaddr offset, Error **errp); #endif /* FSI_CFAM_H */ diff --git a/hw/fsi/cfam.c b/hw/fsi/cfam.c index XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/cfam.c +++ b/hw/fsi/cfam.c @@ -XXX,XX +XXX,XX @@ #include "hw/core/qdev-properties.h" -#define ENGINE_CONFIG_NEXT BIT(31) -#define ENGINE_CONFIG_TYPE_PEEK (0x02 << 4) -#define ENGINE_CONFIG_TYPE_FSI (0x03 << 4) -#define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4) - -/* Valid, slots, version, type, crc */ -#define CFAM_CONFIG_REG(__VER, __TYPE, __CRC) \ - (ENGINE_CONFIG_NEXT | \ - 0x00010000 | \ - (__VER) | \ - (__TYPE) | \ - (__CRC)) - #define TO_REG(x) ((x) >> 2) #define CFAM_CONFIG_CHIP_ID TO_REG(0x00) @@ -XXX,XX +XXX,XX @@ #define CFAM_CONFIG_CHIP_ID_P9 0xc0022d15 #define CFAM_CONFIG_CHIP_ID_BREAK 0xc0de0000 +/* + * Config table of the P9 CFAM: the chip ID followed by one entry per engine, + * entry n describing the engine at address n * 4. We need to add future + * engines from address 0x10 onwards. + */ +static const uint32_t cfam_p9_config[] = { + CFAM_CONFIG_CHIP_ID_P9, + CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_PEEK, 0xc), + CFAM_CONFIG_REG(0x5000, ENGINE_CONFIG_TYPE_FSI, 0xa), + CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_SCRATCHPAD, 0x7), +}; + static uint64_t fsi_cfam_config_read(void *opaque, hwaddr addr, unsigned size) { + FSICFAMCommonClass *cc = FSI_CFAM_COMMON_GET_CLASS(opaque); + unsigned int reg = TO_REG(addr); + trace_fsi_cfam_config_read(addr, size); - switch (addr) { - case 0x00: - return CFAM_CONFIG_CHIP_ID_P9; - case 0x04: - return CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_PEEK, 0xc); - case 0x08: - return CFAM_CONFIG_REG(0x5000, ENGINE_CONFIG_TYPE_FSI, 0xa); - case 0xc: - return CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_SCRATCHPAD, 0x7); - default: - /* - * The config table contains different engines from 0xc onwards. - * The scratch pad is already added at address 0xc. We need to add - * future engines from address 0x10 onwards. Returning 0 as engine - * is not implemented. - */ - return 0; - } + /* Engines past the end of the table are not implemented */ + return reg < cc->config_nr ? cc->config[reg] : 0; } static void fsi_cfam_config_write(void *opaque, hwaddr addr, uint64_t data, unsigned size) { - FSICFAMState *cfam = FSI_CFAM(opaque); + FSICFAMCommonState *cfam = FSI_CFAM_COMMON(opaque); trace_fsi_cfam_config_write(addr, size, data); @@ -XXX,XX +XXX,XX @@ static const struct MemoryRegionOps fsi_cfam_unimplemented_ops = { .endianness = DEVICE_BIG_ENDIAN, }; -static void fsi_cfam_instance_init(Object *obj) +bool fsi_cfam_add_engine(FSICFAMCommonState *cfam, DeviceState *engine, + hwaddr offset, Error **errp) { - FSICFAMState *s = FSI_CFAM(obj); + if (!qdev_realize(engine, BUS(&cfam->lbus), errp)) { + return false; + } - object_initialize_child(obj, "scratchpad", &s->scratchpad, - TYPE_FSI_SCRATCHPAD); + memory_region_add_subregion(&cfam->lbus.mr, offset, + &FSI_LBUS_DEVICE(engine)->iomem); + return true; } -static void fsi_cfam_realize(DeviceState *dev, Error **errp) +static void fsi_cfam_common_realize(DeviceState *dev, Error **errp) { - FSICFAMState *cfam = FSI_CFAM(dev); + FSICFAMCommonState *cfam = FSI_CFAM_COMMON(dev); + FSICFAMCommonClass *cc = FSI_CFAM_COMMON_GET_CLASS(dev); FSISlaveState *slave = FSI_SLAVE(dev); + const char *type = object_get_typename(OBJECT(dev)); + g_autofree char *config_name = g_strdup_printf("%s.config", type); /* Each slave has a 2MiB address space */ memory_region_init_io(&cfam->mr, OBJECT(cfam), &fsi_cfam_unimplemented_ops, - cfam, TYPE_FSI_CFAM, 2 * MiB); + cfam, type, FSI_CFAM_SLOT_SIZE); qbus_init(&cfam->lbus, sizeof(cfam->lbus), TYPE_FSI_LBUS, DEVICE(cfam), NULL); memory_region_init_io(&cfam->config_iomem, OBJECT(cfam), &cfam_config_ops, - cfam, TYPE_FSI_CFAM ".config", 0x400); + cfam, config_name, FSI_CFAM_CONFIG_SIZE); memory_region_add_subregion(&cfam->mr, 0, &cfam->config_iomem); - memory_region_add_subregion(&cfam->mr, 0x800, &slave->iomem); - memory_region_add_subregion(&cfam->mr, 0xc00, &cfam->lbus.mr); + memory_region_add_subregion(&cfam->mr, cc->responder_offset, &slave->iomem); + memory_region_add_subregion(&cfam->mr, cc->lbus_offset, &cfam->lbus.mr); + + cc->realize_engines(cfam, errp); +} + +static void fsi_cfam_common_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->bus_type = TYPE_FSI_BUS; + dc->realize = fsi_cfam_common_realize; +} + +static bool fsi_cfam_realize_engines(FSICFAMCommonState *cfam, Error **errp) +{ + FSICFAMState *s = FSI_CFAM(cfam); /* Add scratchpad engine */ - if (!qdev_realize(DEVICE(&cfam->scratchpad), BUS(&cfam->lbus), errp)) { - return; - } + object_initialize_child(OBJECT(s), "scratchpad", &s->scratchpad, + TYPE_FSI_SCRATCHPAD); - FSILBusDevice *fsi_dev = FSI_LBUS_DEVICE(&cfam->scratchpad); - memory_region_add_subregion(&cfam->lbus.mr, 0, &fsi_dev->iomem); + return fsi_cfam_add_engine(cfam, DEVICE(&s->scratchpad), 0, errp); } static void fsi_cfam_class_init(ObjectClass *klass, const void *data) { - DeviceClass *dc = DEVICE_CLASS(klass); - dc->bus_type = TYPE_FSI_BUS; - dc->realize = fsi_cfam_realize; + FSICFAMCommonClass *cc = FSI_CFAM_COMMON_CLASS(klass); + + cc->config = cfam_p9_config; + cc->config_nr = ARRAY_SIZE(cfam_p9_config); + cc->responder_offset = 0x800; + cc->lbus_offset = 0xc00; + cc->realize_engines = fsi_cfam_realize_engines; } +static const TypeInfo fsi_cfam_common_info = { + .name = TYPE_FSI_CFAM_COMMON, + .parent = TYPE_FSI_SLAVE, + .instance_size = sizeof(FSICFAMCommonState), + .class_size = sizeof(FSICFAMCommonClass), + .class_init = fsi_cfam_common_class_init, + .abstract = true, +}; + static const TypeInfo fsi_cfam_info = { .name = TYPE_FSI_CFAM, - .parent = TYPE_FSI_SLAVE, - .instance_init = fsi_cfam_instance_init, + .parent = TYPE_FSI_CFAM_COMMON, .instance_size = sizeof(FSICFAMState), .class_init = fsi_cfam_class_init, }; static void fsi_cfam_register_types(void) { + type_register_static(&fsi_cfam_common_info); type_register_static(&fsi_cfam_info); } diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c index XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/fsi-master.c +++ b/hw/fsi/fsi-master.c @@ -XXX,XX +XXX,XX @@ static void fsi_master_realize(DeviceState *dev, Error **errp) } /* address ? */ - memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.mr); + memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.parent.mr); } static void fsi_master_reset(DeviceState *dev) -- 2.53.0
Add TYPE_FSI_CFAM_S, derivative of CFAM, as a second type on the common CFAM base class. The base class builds the register slot, the config table region, the FSI responder and the local bus, so the CFAM-S supplies only its config table (chip ID with major 9, an FSI responder and a v1 mailbox), its slot offsets and the mailbox engine. Its realize chains the base one and adds the one thing that is new: the slot is aliased across the four slave-ID views, so enumeration and normal access reach the same registers. The v1 mailbox is a local bus engine added in lbus.c next to the scratchpad which the CFAM-S just instantiates. Sharing the base config table implementation means the CFAM-S also honors the SID_BREAK sequence and word-only config access. FSIMasterState gains a cfam_s field. fsi_master_init() creates both the cfam and cfam-s children, realize hooks them into the OPB-to-FSI aperture. Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- docs/specs/fsi.rst | 15 ++++++ include/hw/fsi/cfam-s.h | 40 ++++++++++++++++ include/hw/fsi/cfam.h | 10 ++++ include/hw/fsi/fsi-master.h | 2 + include/hw/fsi/lbus.h | 11 +++++ hw/fsi/cfam-s.c | 91 +++++++++++++++++++++++++++++++++++++ hw/fsi/fsi-master.c | 7 +++ hw/fsi/lbus.c | 69 ++++++++++++++++++++++++++++ hw/fsi/meson.build | 2 +- hw/fsi/trace-events | 2 + 10 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 include/hw/fsi/cfam-s.h create mode 100644 hw/fsi/cfam-s.c diff --git a/docs/specs/fsi.rst b/docs/specs/fsi.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/specs/fsi.rst +++ b/docs/specs/fsi.rst @@ -XXX,XX +XXX,XX @@ from the BMC. (see the `pdbg source repository`_ for more details) .. _pdbg source repository: https://github.com/open-power/pdbg + +CFAM-S model +------------ + +The CFAM-S is a derivative of the CFAM. Both are modelled on a common CFAM +model that builds the register slot, the configuration table, the FSI +responder and the local bus; each supplies its own configuration table, slot +layout and engines. The CFAM-S supports a limited set of engines: an FSI +responder and a version 1 mailbox. + +The configuration table advertises the responder and the mailbox engines. +The responder is backed by the FSI slave control registers; the mailbox +provides a small block of scratch registers. The register block is exposed +through each slave-ID view so that enumeration and normal access reach the +same registers. diff --git a/include/hw/fsi/cfam-s.h b/include/hw/fsi/cfam-s.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/fsi/cfam-s.h @@ -XXX,XX +XXX,XX @@ +/* + * IBM Common FRU Access Macro - S variant (CFAM-S) + * + * Copyright (C) 2026 IBM Corp. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef FSI_CFAM_S_H +#define FSI_CFAM_S_H + +#include "system/memory.h" +#include "hw/fsi/cfam.h" +#include "hw/fsi/fsi.h" +#include "hw/fsi/lbus.h" + +#define TYPE_FSI_CFAM_S "cfam-s" +OBJECT_DECLARE_TYPE(FSICFAMSState, FSICFAMSClass, FSI_CFAM_S) + +/* The register slot is visible in each of the four slave-ID views */ +#define CFAM_S_WINDOW_SIZE (4 * FSI_CFAM_SLOT_SIZE) + +struct FSICFAMSState { + /* < private > */ + FSICFAMCommonState parent; + + /* parent.mr aliased across the slave-ID views */ + MemoryRegion window; + MemoryRegion slot_alias[3]; + + FSIMbox mbox; +}; + +struct FSICFAMSClass { + /* < private > */ + FSICFAMCommonClass parent_class; + + DeviceRealize parent_realize; +}; + +#endif /* FSI_CFAM_S_H */ diff --git a/include/hw/fsi/cfam.h b/include/hw/fsi/cfam.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/fsi/cfam.h +++ b/include/hw/fsi/cfam.h @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FSICFAMState, FSI_CFAM) #define ENGINE_CONFIG_TYPE_PEEK (0x02 << 4) #define ENGINE_CONFIG_TYPE_FSI (0x03 << 4) #define ENGINE_CONFIG_TYPE_SCRATCHPAD (0x06 << 4) +#define ENGINE_CONFIG_TYPE_MBOX_V1 (0x14 << 4) /* Valid, slots, version, type, crc */ #define CFAM_CONFIG_REG(__VER, __TYPE, __CRC) \ @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FSICFAMState, FSI_CFAM) (__TYPE) | \ (__CRC)) +/* As above, for the last entry in a table: NEXT is clear */ +#define CFAM_CONFIG_LAST(__VER, __TYPE, __CRC) \ + (0x00010000 | \ + (__VER) | \ + (__TYPE) | \ + (__CRC)) + +#define CFAM_CONFIG_CHIP_ID_MAJOR(__MAJOR) (((__MAJOR) & 0xf) << 8) + struct FSICFAMCommonState { /* < private > */ FSISlaveState parent; diff --git a/include/hw/fsi/fsi-master.h b/include/hw/fsi/fsi-master.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/fsi/fsi-master.h +++ b/include/hw/fsi/fsi-master.h @@ -XXX,XX +XXX,XX @@ #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) @@ -XXX,XX +XXX,XX @@ typedef struct FSIMasterState { uint32_t regs[FSI_MASTER_NR_REGS]; FSICFAMState cfam; + FSICFAMSState cfam_s; } FSIMasterState; diff --git a/include/hw/fsi/lbus.h b/include/hw/fsi/lbus.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/fsi/lbus.h +++ b/include/hw/fsi/lbus.h @@ -XXX,XX +XXX,XX @@ typedef struct FSIScratchPad { uint32_t regs[FSI_SCRATCHPAD_NR_REGS]; } FSIScratchPad; +#define TYPE_FSI_MBOX "fsi.mbox" +OBJECT_DECLARE_SIMPLE_TYPE(FSIMbox, FSI_MBOX) + +#define FSI_MBOX_SCRATCH_NUM 5 + +struct FSIMbox { + FSILBusDevice parent; + + uint32_t scratch[FSI_MBOX_SCRATCH_NUM]; +}; + #endif /* FSI_LBUS_H */ diff --git a/hw/fsi/cfam-s.c b/hw/fsi/cfam-s.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/fsi/cfam-s.c @@ -XXX,XX +XXX,XX @@ +/* + * IBM Common FRU Access Macro - S variant (CFAM-S) + * + * A CFAM flavor built on the common CFAM model (see cfam.c). It supports an + * FSI responder and a v1 mailbox, and exposes its register slot in each + * slave-ID view of the link. + * + * Copyright (C) 2026 IBM Corp. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/units.h" +#include "hw/fsi/cfam-s.h" +#include "hw/fsi/fsi.h" + +/* Slot layout: 0x000 config table, 0x400 responder, 0x800 mailbox */ +#define CFAM_S_RESPONDER_BASE 0x400 +#define CFAM_S_MBOX_BASE 0x800 + +/* Config table: chip ID with major 9 (the CFAM-S), then the engines */ +static const uint32_t cfam_s_config[] = { + ENGINE_CONFIG_NEXT | CFAM_CONFIG_CHIP_ID_MAJOR(9) | 0xd, + CFAM_CONFIG_REG(0x1000, ENGINE_CONFIG_TYPE_FSI, 0xb), + CFAM_CONFIG_LAST(0x1000, ENGINE_CONFIG_TYPE_MBOX_V1, 0x3), +}; + +static bool fsi_cfam_s_realize_engines(FSICFAMCommonState *cfam, Error **errp) +{ + FSICFAMSState *cfam_s = FSI_CFAM_S(cfam); + + object_initialize_child(OBJECT(cfam_s), "mbox", &cfam_s->mbox, + TYPE_FSI_MBOX); + return fsi_cfam_add_engine(cfam, DEVICE(&cfam_s->mbox), 0, errp); +} + +static void fsi_cfam_s_realize(DeviceState *dev, Error **errp) +{ + ERRP_GUARD(); + FSICFAMSState *cfam_s = FSI_CFAM_S(dev); + FSICFAMCommonState *cfam = FSI_CFAM_COMMON(dev); + FSICFAMSClass *sc = FSI_CFAM_S_GET_CLASS(dev); + + sc->parent_realize(dev, errp); + if (*errp) { + return; + } + + memory_region_init(&cfam_s->window, OBJECT(cfam_s), + TYPE_FSI_CFAM_S ".window", CFAM_S_WINDOW_SIZE); + memory_region_add_subregion(&cfam_s->window, 0, &cfam->mr); + + /* Alias the slot into the other three slave-ID views */ + for (int i = 0; i < ARRAY_SIZE(cfam_s->slot_alias); i++) { + memory_region_init_alias(&cfam_s->slot_alias[i], OBJECT(cfam_s), + TYPE_FSI_CFAM_S ".slot-alias", &cfam->mr, 0, + FSI_CFAM_SLOT_SIZE); + memory_region_add_subregion(&cfam_s->window, + (i + 1) * FSI_CFAM_SLOT_SIZE, + &cfam_s->slot_alias[i]); + } +} + +static void fsi_cfam_s_class_init(ObjectClass *klass, const void *data) +{ + FSICFAMSClass *sc = FSI_CFAM_S_CLASS(klass); + FSICFAMCommonClass *cc = FSI_CFAM_COMMON_CLASS(klass); + + device_class_set_parent_realize(DEVICE_CLASS(klass), fsi_cfam_s_realize, + &sc->parent_realize); + + cc->config = cfam_s_config; + cc->config_nr = ARRAY_SIZE(cfam_s_config); + cc->responder_offset = CFAM_S_RESPONDER_BASE; + cc->lbus_offset = CFAM_S_MBOX_BASE; + cc->realize_engines = fsi_cfam_s_realize_engines; +} + +static const TypeInfo cfam_s_types[] = { + { + .name = TYPE_FSI_CFAM_S, + .parent = TYPE_FSI_CFAM_COMMON, + .instance_size = sizeof(FSICFAMSState), + .class_size = sizeof(FSICFAMSClass), + .class_init = fsi_cfam_s_class_init, + }, +}; + +DEFINE_TYPES(cfam_s_types) diff --git a/hw/fsi/fsi-master.c b/hw/fsi/fsi-master.c index XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/fsi-master.c +++ b/hw/fsi/fsi-master.c @@ -XXX,XX +XXX,XX @@ #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" @@ -XXX,XX +XXX,XX @@ static void fsi_master_init(Object *o) FSIMasterState *s = FSI_MASTER(o); object_initialize_child(o, "cfam", &s->cfam, TYPE_FSI_CFAM); + object_initialize_child(o, "cfam-s", &s->cfam_s, TYPE_FSI_CFAM_S); qbus_init(&s->bus, sizeof(s->bus), TYPE_FSI_BUS, DEVICE(s), NULL); @@ -XXX,XX +XXX,XX @@ static void fsi_master_realize(DeviceState *dev, Error **errp) /* address ? */ memory_region_add_subregion(&s->opb2fsi, 0, &s->cfam.parent.mr); + + if (!qdev_realize(DEVICE(&s->cfam_s), BUS(&s->bus), errp)) { + return; + } + memory_region_add_subregion(&s->opb2fsi, 2 * MiB, &s->cfam_s.window); } static void fsi_master_reset(DeviceState *dev) diff --git a/hw/fsi/lbus.c b/hw/fsi/lbus.c index XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/lbus.c +++ b/hw/fsi/lbus.c @@ -XXX,XX +XXX,XX @@ static const TypeInfo fsi_scratchpad_info = { .class_init = fsi_scratchpad_class_init, }; +/* The mailbox exposes its scratch registers at this offset */ +#define FSI_MBOX_SCRATCH_OFF 0xe0 +#define FSI_MBOX_SCRATCH_END (FSI_MBOX_SCRATCH_OFF + FSI_MBOX_SCRATCH_NUM * 4) + +static uint64_t fsi_mbox_read(void *opaque, hwaddr addr, unsigned size) +{ + FSIMbox *mbox = FSI_MBOX(opaque); + + trace_fsi_mbox_read(addr, size); + + if (addr < FSI_MBOX_SCRATCH_OFF || addr >= FSI_MBOX_SCRATCH_END) { + return 0; + } + + return mbox->scratch[TO_REG(addr - FSI_MBOX_SCRATCH_OFF)]; +} + +static void fsi_mbox_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + FSIMbox *mbox = FSI_MBOX(opaque); + + trace_fsi_mbox_write(addr, size, data); + + if (addr < FSI_MBOX_SCRATCH_OFF || addr >= FSI_MBOX_SCRATCH_END) { + return; + } + + mbox->scratch[TO_REG(addr - FSI_MBOX_SCRATCH_OFF)] = data; +} + +static const struct MemoryRegionOps fsi_mbox_ops = { + .read = fsi_mbox_read, + .write = fsi_mbox_write, + .endianness = DEVICE_BIG_ENDIAN, +}; + +static void fsi_mbox_realize(DeviceState *dev, Error **errp) +{ + FSILBusDevice *ldev = FSI_LBUS_DEVICE(dev); + + memory_region_init_io(&ldev->iomem, OBJECT(ldev), &fsi_mbox_ops, + ldev, TYPE_FSI_MBOX, 0x400); +} + +static void fsi_mbox_reset(DeviceState *dev) +{ + FSIMbox *mbox = FSI_MBOX(dev); + + memset(mbox->scratch, 0, sizeof(mbox->scratch)); +} + +static void fsi_mbox_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->bus_type = TYPE_FSI_LBUS; + dc->realize = fsi_mbox_realize; + device_class_set_legacy_reset(dc, fsi_mbox_reset); +} + +static const TypeInfo fsi_mbox_info = { + .name = TYPE_FSI_MBOX, + .parent = TYPE_FSI_LBUS_DEVICE, + .instance_size = sizeof(FSIMbox), + .class_init = fsi_mbox_class_init, +}; + static void fsi_lbus_register_types(void) { type_register_static(&fsi_lbus_info); type_register_static(&fsi_lbus_device_type_info); type_register_static(&fsi_scratchpad_info); + type_register_static(&fsi_mbox_info); } type_init(fsi_lbus_register_types); diff --git a/hw/fsi/meson.build b/hw/fsi/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/meson.build +++ b/hw/fsi/meson.build @@ -XXX,XX +XXX,XX @@ -system_ss.add(when: 'CONFIG_FSI', if_true: files('lbus.c','fsi.c','cfam.c','fsi-master.c')) +system_ss.add(when: 'CONFIG_FSI', if_true: files('lbus.c','fsi.c','cfam.c','cfam-s.c','fsi-master.c')) system_ss.add(when: 'CONFIG_FSI_APB2OPB_ASPEED', if_true: files('aspeed_apb2opb.c')) diff --git a/hw/fsi/trace-events b/hw/fsi/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/fsi/trace-events +++ b/hw/fsi/trace-events @@ -XXX,XX +XXX,XX @@ fsi_scratchpad_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d" fsi_scratchpad_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64 +fsi_mbox_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d" +fsi_mbox_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64 fsi_slave_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d" fsi_slave_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64 fsi_cfam_config_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d" -- 2.53.0
The AST2700 SoC exposes two FSI controllers behind APB-to-OPB bridges. Add the ASPEED_DEV_FSI1/FSI2 memory-map entries, instantiate both fsi[] APB-to-OPB bridges and map them during SoC realize, so the CFAM/CFAM-S devices on the FSI local buses become reachable from the SoC. Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- hw/arm/aspeed_ast27x0.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -XXX,XX +XXX,XX @@ #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" @@ -XXX,XX +XXX,XX @@ 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, }; @@ -XXX,XX +XXX,XX @@ 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, @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) } } + /* FSI / OPB */ + for (i = 0; i < ARRAY_SIZE(s->fsi); i++) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->fsi[i]), errp)) { + return; + } + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->fsi[i]), 0, + sc->memmap[ASPEED_DEV_FSI1 + i]); + } + + aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->dpmcu), "aspeed.dpmcu", sc->memmap[ASPEED_DEV_DPMCU], -- 2.53.0
An SMBus block read (I2C_M_RECV_LEN) reads the block length from the first received byte. The Linux/U-Boot aspeed I2C driver obtains that first byte from the I2CC_STS_AND_BUFF register (modelled here as reg_byte_buf), even when the transfer uses DMA. The DMA receive path, however, only wrote received data to DRAM and never updated reg_byte_buf, so block reads read a stale/zero length. Mirror the first DMA-received byte into reg_byte_buf so that I2C_M_RECV_LEN transfers using DMA report the correct block length. This is required for the ucd9000 driver, which uses i2c_smbus_read_block_data(). Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- hw/i2c/aspeed_i2c.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ 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; @@ -XXX,XX +XXX,XX @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus) return; } + /* Mirror first byte to reg_byte_buf for I2C_M_RECV_LEN. */ + if (first_dma_byte) { + SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data); + first_dma_byte = false; + } bus->dma_dram_offset++; bus->regs[reg_dma_len]--; /* In new mode, keep track of how many bytes we RXed */ -- 2.53.0
Add a PMBus device model for the TI-UCD90320 24-rail power sequencer. Configures 24 pages with linear vout mode and responds to the vendor-specific UCD9000_DEVICE_ID, NUM_PAGES, MONITOR_CONFIG, and MFR_STATUS commands, allowing the Linux ucd9000 driver to bind and create hwmon sysfs entries. Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- MAINTAINERS | 1 + docs/specs/index.rst | 1 + docs/specs/ucd90320.rst | 36 +++++++++ hw/sensor/ucd90320.c | 169 ++++++++++++++++++++++++++++++++++++++++ hw/arm/Kconfig | 1 + hw/sensor/Kconfig | 4 + hw/sensor/meson.build | 1 + 7 files changed, 213 insertions(+) create mode 100644 docs/specs/ucd90320.rst create mode 100644 hw/sensor/ucd90320.c diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/docs/specs/ucd90320.rst @@ -XXX,XX +XXX,XX @@ +Texas Instruments UCD90320 Power Sequencer +========================================== + +The UCD90320 is a 24-rail PMBus power sequencer. QEMU models it as a PMBus +device (``"ucd90320"``) so the Linux ``ucd9000`` driver can bind and create +``hwmon`` sysfs entries. + +The model configures 24 PMBus pages (one per rail) with ``VOUT_MODE = 0x00`` +(linear, exponent 0). ``READ_VOUT`` and ``MFR_STATUS`` return zero on all +pages. + +Vendor-specific commands handled: + ++------+---------------------------+--------------------------------------------+ +| Code | Name | Response | ++======+===========================+============================================+ +| 0xD5 | ``UCD9000_MONITOR_CONFIG``| Block: one byte ``0x00`` | ++------+---------------------------+--------------------------------------------+ +| 0xD6 | ``UCD9000_NUM_PAGES`` | Byte: ``24`` | ++------+---------------------------+--------------------------------------------+ +| 0xF3 | ``UCD9000_MFR_STATUS`` | Block: four bytes ``0x00 0x00 0x00 0x00`` | ++------+---------------------------+--------------------------------------------+ +| 0xFD | ``UCD9000_DEVICE_ID`` | Block: ASCII string ``"UCD90320"`` | ++------+---------------------------+--------------------------------------------+ + +``DEVICE_ID`` uses the vendor-specific code ``0xFD`` rather than the standard +``PMBUS_IC_DEVICE_ID`` (``0xAD``), matching the Linux ``ucd9000`` probe +sequence. + +The UCD90320 is instantiated automatically in the ``huygens-bmc`` machine on +I2C bus 5 at address ``0x11``. To instantiate on a different Aspeed machine: + +.. code-block:: c + + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5), + "ucd90320", 0x11); diff --git a/hw/sensor/ucd90320.c b/hw/sensor/ucd90320.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/sensor/ucd90320.c @@ -XXX,XX +XXX,XX @@ +/* + * Texas Instruments UCD90320 24-Rail PMBus Power Sequencer + * + * Copyright 2026 IBM Corp. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/i2c/pmbus_device.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" + +#define TYPE_UCD90320 "ucd90320" + +/* UCD90320 has 24 sequenced power-rail pages */ +#define UCD90320_NUM_PAGES 24 + +/* Vendor-specific command codes (not in the standard PMBus register table) */ +#define UCD9000_MONITOR_CONFIG 0xd5 +#define UCD9000_NUM_PAGES 0xd6 +#define UCD9000_MFR_STATUS 0xf3 +#define UCD9000_DEVICE_ID 0xfd + +#define UCD90320_DEVICE_ID_LEN 8 +#define UCD90320_MFR_STATUS_LEN 4 + +typedef struct UCD90320State { + PMBusDevice parent; + + /* Reset values for the vendor-specific block-read commands. */ + uint8_t device_id[UCD90320_DEVICE_ID_LEN]; + uint8_t monitor_config; + uint8_t mfr_status[UCD90320_MFR_STATUS_LEN]; +} UCD90320State; + +#define UCD90320(obj) OBJECT_CHECK(UCD90320State, (obj), TYPE_UCD90320) + +static void ucd90320_send_block(PMBusDevice *pmdev, + const uint8_t *data, uint8_t len) +{ + int i; + + pmdev->out_buf[len + pmdev->out_buf_len] = len; + for (i = len - 1; i >= 0; i--) { + pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i]; + } + pmdev->out_buf_len += len + 1; +} + +static uint8_t ucd90320_read_byte(PMBusDevice *pmdev) +{ + UCD90320State *s = UCD90320(pmdev); + + switch (pmdev->code) { + case UCD9000_DEVICE_ID: + ucd90320_send_block(pmdev, s->device_id, sizeof(s->device_id)); + pmbus_idle(pmdev); + return 0; + case UCD9000_NUM_PAGES: + pmbus_send8(pmdev, UCD90320_NUM_PAGES); + pmbus_idle(pmdev); + return 0; + + case UCD9000_MONITOR_CONFIG: + ucd90320_send_block(pmdev, &s->monitor_config, + sizeof(s->monitor_config)); + pmbus_idle(pmdev); + return 0; + case UCD9000_MFR_STATUS: + ucd90320_send_block(pmdev, s->mfr_status, sizeof(s->mfr_status)); + pmbus_idle(pmdev); + return 0; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: reading from unsupported register: 0x%02x\n", + __func__, pmdev->code); + break; + } + return 0xFF; +} + +static int ucd90320_write_data(PMBusDevice *pmdev, const uint8_t *buf, + uint8_t len) +{ + if (len == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__); + return -1; + } + + pmdev->code = buf[0]; + + if (len == 1) { + return 0; + } + + return 0; +} + +static void ucd90320_exit_reset(Object *obj, ResetType type) +{ + PMBusDevice *pmdev = PMBUS_DEVICE(obj); + UCD90320State *s = UCD90320(obj); + + pmdev->capability = 0x20; /* PEC supported */ + + for (int i = 0; i < UCD90320_NUM_PAGES; i++) { + pmdev->pages[i].operation = 0x80; /* on */ + pmdev->pages[i].on_off_config = 0x1a; + pmdev->pages[i].vout_mode = 0x00; /* linear mode, exponent=0 */ + pmdev->pages[i].read_vout = 0; /* rails off, pgood=0 */ + } + + memcpy(s->device_id, "UCD90320", sizeof(s->device_id)); + s->monitor_config = 0x00; + memset(s->mfr_status, 0x00, sizeof(s->mfr_status)); +} + +static void ucd90320_init(Object *obj) +{ + PMBusDevice *pmdev = PMBUS_DEVICE(obj); + uint64_t flags = PB_HAS_VOUT | PB_HAS_VOUT_MODE | + PB_HAS_STATUS_MFR_SPECIFIC; + + for (int i = 0; i < UCD90320_NUM_PAGES; i++) { + pmbus_page_config(pmdev, i, flags); + } +} + +static const VMStateDescription vmstate_ucd90320 = { + .name = TYPE_UCD90320, + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_PMBUS_DEVICE(parent, UCD90320State), + VMSTATE_UINT8_ARRAY(device_id, UCD90320State, UCD90320_DEVICE_ID_LEN), + VMSTATE_UINT8(monitor_config, UCD90320State), + VMSTATE_UINT8_ARRAY(mfr_status, UCD90320State, + UCD90320_MFR_STATUS_LEN), + VMSTATE_END_OF_LIST() + } +}; + +static void ucd90320_class_init(ObjectClass *klass, const void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass); + + dc->desc = "Texas Instruments UCD90320 24-Rail Power Sequencer"; + dc->vmsd = &vmstate_ucd90320; + k->write_data = ucd90320_write_data; + k->receive_byte = ucd90320_read_byte; + k->device_num_pages = UCD90320_NUM_PAGES; + rc->phases.exit = ucd90320_exit_reset; +} + +static const TypeInfo ucd90320_types[] = { + { + .name = TYPE_UCD90320, + .parent = TYPE_PMBUS_DEVICE, + .instance_size = sizeof(UCD90320State), + .instance_init = ucd90320_init, + .class_init = ucd90320_class_init, + }, +}; + +DEFINE_TYPES(ucd90320_types) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/sensor/Kconfig +++ b/hw/sensor/Kconfig @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX 100644 --- a/hw/sensor/meson.build +++ b/hw/sensor/meson.build @@ -XXX,XX +XXX,XX @@ 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
In preparation for a sysbus UFS frontend (used by the Aspeed AST2700), separate the transport-independent UFS host-controller logic from the PCI-specific glue, following the pattern used by SDHCI and XHCI. UfsHc's PCIDevice parent is replaced by an anonymous union of a PCIDevice and a SysBusDevice, and the controller gains an explicit AddressSpace *dma_as used by ufs_addr_read()/ufs_addr_write() and the SG-list helpers instead of the PCI address space. Interrupt delivery goes through qemu_set_irq() in ufs_irq_check(). The realize path is split into a transport-agnostic ufs_realize_core() plus ufs_init_mmio(), and UfsBus gains an 'hc' back-pointer so ufs_lu_realize() no longer reaches through the PCI device. TYPE_UFS remains a PCI device, so '-device ufs' is unchanged and the PCI ufs-tests qtests still pass. Two small behaviour additions ride along on the core so that frontends with differing geometry and bus-scan behaviour can be modelled: - ufs-lu gains configurable 'logical-block-size' property. It defaults to UFS_BLOCK_SIZE, so the PCI device is unaffected. - an unmapped LUN now answers INQUIRY as "not connected" rather than failing the request, so that a host bus-scan does not observe controller errors for absent LUNs. Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- hw/ufs/ufs.h | 18 ++++++++++++++++- hw/ufs/lu.c | 50 +++++++++++++++++++++++++++++++++++++++++---- hw/ufs/ufs.c | 57 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 103 insertions(+), 22 deletions(-) diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index XXXXXXX..XXXXXXX 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -XXX,XX +XXX,XX @@ #define HW_UFS_UFS_H #include "hw/pci/pci_device.h" +#include "hw/core/sysbus.h" #include "hw/scsi/scsi.h" +#include "scsi/constants.h" #include "block/ufs.h" #define UFS_MAX_LUS 32 @@ -XXX,XX +XXX,XX @@ typedef struct UfsBusClass { typedef struct UfsBus { BusState parent_bus; + struct UfsHc *hc; /* host controller owning this bus (frontend-agnostic) */ } UfsBus; #define TYPE_UFS_BUS "ufs-bus" @@ -XXX,XX +XXX,XX @@ typedef UfsReqResult (*UfsScsiOp)(struct UfsLu *, UfsRequest *); typedef struct UfsLu { DeviceState qdev; uint8_t lun; + uint32_t logical_block_size; UnitDescriptor unit_desc; SCSIBus bus; SCSIDevice *scsi_dev; @@ -XXX,XX +XXX,XX @@ typedef struct UfsWb { } UfsWb; typedef struct UfsHc { - PCIDevice parent_obj; + /* + * The controller can be instantiated either as a PCI function or as a + * sysbus device. Following the SDHCI model, both frontends share this + * state structure via a union of the possible parent objects. + */ + union { + PCIDevice pci_dev; + SysBusDevice sbdev; + }; UfsBus bus; MemoryRegion iomem; UfsReg reg; @@ -XXX,XX +XXX,XX @@ typedef struct UfsHc { Flags flags; qemu_irq irq; + AddressSpace *dma_as; QEMUBH *doorbell_bh; QEMUBH *complete_bh; @@ -XXX,XX +XXX,XX @@ void ufs_build_query_response(UfsRequest *req); void ufs_complete_req(UfsRequest *req, UfsReqResult req_result); void ufs_wb_update_avail_buffer(UfsHc *u); void ufs_init_wlu(UfsLu *wlu, uint8_t wlun); +UfsReqResult ufs_emulate_absent_lun(UfsRequest *req); +void ufs_init_mmio(UfsHc *u); +bool ufs_realize_core(UfsHc *u, Error **errp); #endif /* HW_UFS_UFS_H */ diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index XXXXXXX..XXXXXXX 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -XXX,XX +XXX,XX @@ static int ufs_emulate_wlun_inquiry(UfsRequest *req, uint8_t *outbuf, return SCSI_INQUIRY_LEN; } +UfsReqResult ufs_emulate_absent_lun(UfsRequest *req) +{ + QEMU_UNINITIALIZED uint8_t outbuf[SCSI_INQUIRY_LEN]; + uint8_t sense_buf[UFS_SENSE_SIZE]; + uint8_t scsi_status; + int len = 0; + + if (req->req_upiu.sc.cdb[0] == INQUIRY && + !(req->req_upiu.sc.cdb[1] & 0x1)) { + /* + * Standard INQUIRY to a logical unit that is not mapped: report it + * as "not connected" (peripheral qualifier 0x3, device type 0x1f) + * with GOOD status, like real hardware. Host-side bus scans then + * skip the unit quietly instead of flagging a controller error. + */ + memset(outbuf, 0, sizeof(outbuf)); + outbuf[0] = TYPE_NO_LUN; + outbuf[3] = 0x2; + outbuf[4] = SCSI_INQUIRY_LEN - 5; + len = SCSI_INQUIRY_LEN; + scsi_status = GOOD; + } else { + scsi_build_sense(sense_buf, SENSE_CODE(LUN_NOT_SUPPORTED)); + scsi_status = CHECK_CONDITION; + } + + len = MIN(len, (int)req->data_len); + if (scsi_status == GOOD && len > 0 && + dma_buf_read(outbuf, len, NULL, req->sg, MEMTXATTRS_UNSPECIFIED) != + MEMTX_OK) { + return UFS_REQUEST_FAIL; + } + + ufs_build_scsi_response_upiu(req, sense_buf, sizeof(sense_buf), len, + scsi_status); + return UFS_REQUEST_SUCCESS; +} + static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, UfsRequest *req) { uint8_t lun = lu->lun; @@ -XXX,XX +XXX,XX @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req) static const Property ufs_lu_props[] = { DEFINE_PROP_DRIVE("drive", UfsLu, conf.blk), DEFINE_PROP_UINT8("lun", UfsLu, lun, 0), + DEFINE_PROP_UINT32("logical-block-size", UfsLu, logical_block_size, + UFS_BLOCK_SIZE), }; static bool ufs_add_lu(UfsHc *u, UfsLu *lu, Error **errp) @@ -XXX,XX +XXX,XX @@ static void ufs_init_lu(UfsLu *lu) lu->unit_desc.length = sizeof(UnitDescriptor); lu->unit_desc.descriptor_idn = UFS_QUERY_DESC_IDN_UNIT; lu->unit_desc.lu_enable = 0x01; - lu->unit_desc.logical_block_size = UFS_BLOCK_SIZE_SHIFT; + lu->unit_desc.logical_block_size = ctz32(lu->logical_block_size); lu->unit_desc.unit_index = lu->lun; lu->unit_desc.logical_block_count = cpu_to_be64(brdv_len / (1 << lu->unit_desc.logical_block_size)); @@ -XXX,XX +XXX,XX @@ static void ufs_init_scsi_device(UfsLu *lu, BlockBackend *blk, Error **errp) scsi_dev = qdev_new("scsi-hd"); object_property_add_child(OBJECT(&lu->bus), "ufs-scsi", OBJECT(scsi_dev)); - qdev_prop_set_uint32(scsi_dev, "physical_block_size", UFS_BLOCK_SIZE); - qdev_prop_set_uint32(scsi_dev, "logical_block_size", UFS_BLOCK_SIZE); + qdev_prop_set_uint32(scsi_dev, "physical_block_size", + lu->logical_block_size); + qdev_prop_set_uint32(scsi_dev, "logical_block_size", + lu->logical_block_size); qdev_prop_set_uint32(scsi_dev, "scsi-id", 0); qdev_prop_set_uint32(scsi_dev, "lun", lu->lun); if (!qdev_prop_set_drive_err(scsi_dev, "drive", blk, errp)) { @@ -XXX,XX +XXX,XX @@ static void ufs_lu_realize(DeviceState *dev, Error **errp) { UfsLu *lu = DO_UPCAST(UfsLu, qdev, dev); BusState *s = qdev_get_parent_bus(dev); - UfsHc *u = UFS(s->parent); + UfsHc *u = UFS_BUS(s)->hc; BlockBackend *blk = lu->conf.blk; if (!ufs_lu_check_constraints(lu, errp)) { diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index XXXXXXX..XXXXXXX 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -XXX,XX +XXX,XX @@ static MemTxResult ufs_addr_read(UfsHc *u, hwaddr addr, void *buf, int size) return MEMTX_DECODE_ERROR; } - return pci_dma_read(PCI_DEVICE(u), addr, buf, size); + return dma_memory_read(u->dma_as, addr, buf, size, + MEMTXATTRS_UNSPECIFIED); } static MemTxResult ufs_addr_write(UfsHc *u, hwaddr addr, const void *buf, @@ -XXX,XX +XXX,XX @@ static MemTxResult ufs_addr_write(UfsHc *u, hwaddr addr, const void *buf, return MEMTX_DECODE_ERROR; } - return pci_dma_write(PCI_DEVICE(u), addr, buf, size); + return dma_memory_write(u->dma_as, addr, buf, size, + MEMTXATTRS_UNSPECIFIED); } static inline hwaddr ufs_get_utrd_addr(UfsHc *u, uint32_t slot) @@ -XXX,XX +XXX,XX @@ static MemTxResult ufs_dma_read_prdt(UfsRequest *req) } req->sg = g_malloc0(sizeof(QEMUSGList)); - pci_dma_sglist_init(req->sg, PCI_DEVICE(u), prdt_len); + qemu_sglist_init(req->sg, DEVICE(u), prdt_len, u->dma_as); req->data_len = 0; for (uint16_t i = 0; i < prdt_len; ++i) { @@ -XXX,XX +XXX,XX @@ static MemTxResult ufs_dma_write_upiu(UfsRequest *req) static void ufs_irq_check(UfsHc *u) { - PCIDevice *pci = PCI_DEVICE(u); - if ((u->reg.is & UFS_INTR_MASK) & u->reg.ie) { trace_ufs_irq_raise(); - pci_irq_assert(pci); + qemu_set_irq(u->irq, 1); } else { trace_ufs_irq_lower(); - pci_irq_deassert(pci); + qemu_set_irq(u->irq, 0); } } @@ -XXX,XX +XXX,XX @@ static UfsReqResult ufs_exec_scsi_cmd(UfsRequest *req) if (!is_wlun(lun) && (lun >= UFS_MAX_LUS || u->lus[lun] == NULL)) { trace_ufs_err_scsi_cmd_invalid_lun(lun); - return UFS_REQUEST_FAIL; + return ufs_emulate_absent_lun(req); } switch (lun) { @@ -XXX,XX +XXX,XX @@ static bool ufs_check_constraints(UfsHc *u, Error **errp) return true; } +void ufs_init_mmio(UfsHc *u) +{ + memory_region_init_io(&u->iomem, OBJECT(u), &ufs_mmio_ops, u, "ufs", + u->reg_size); +} + static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) { uint8_t *pci_conf = pci_dev->config; @@ -XXX,XX +XXX,XX @@ static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) pci_conf[PCI_INTERRUPT_PIN] = 1; pci_config_set_prog_interface(pci_conf, 0x1); - memory_region_init_io(&u->iomem, OBJECT(u), &ufs_mmio_ops, u, "ufs", - u->reg_size); + ufs_init_mmio(u); pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &u->iomem); u->irq = pci_allocate_irq(pci_dev); } @@ -XXX,XX +XXX,XX @@ static void ufs_init_hc(UfsHc *u) timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK); } -static void ufs_realize(PCIDevice *pci_dev, Error **errp) +/* + * Frontend-agnostic realization: everything except the host-bus specific + * MMIO/IRQ/DMA plumbing, which the PCI or sysbus frontend sets up around + * this call. The frontend must assign u->dma_as before calling. + */ +bool ufs_realize_core(UfsHc *u, Error **errp) { - UfsHc *u = UFS(pci_dev); - if (!ufs_check_constraints(u, errp)) { - return; + return false; } - qbus_init(&u->bus, sizeof(UfsBus), TYPE_UFS_BUS, &pci_dev->qdev, - u->parent_obj.qdev.id); + qbus_init(&u->bus, sizeof(UfsBus), TYPE_UFS_BUS, DEVICE(u), + DEVICE(u)->id); + u->bus.hc = u; ufs_init_state(u); ufs_init_hc(u); - ufs_init_pci(u, pci_dev); ufs_init_wlu(&u->report_wlu, UFS_UPIU_REPORT_LUNS_WLUN); ufs_init_wlu(&u->dev_wlu, UFS_UPIU_UFS_DEVICE_WLUN); ufs_init_wlu(&u->boot_wlu, UFS_UPIU_BOOT_WLUN); ufs_init_wlu(&u->rpmb_wlu, UFS_UPIU_RPMB_WLUN); + + return true; +} + +static void ufs_realize(PCIDevice *pci_dev, Error **errp) +{ + UfsHc *u = UFS(pci_dev); + + u->dma_as = pci_get_address_space(pci_dev); + + if (!ufs_realize_core(u, errp)) { + return; + } + + ufs_init_pci(u, pci_dev); } static void ufs_exit(PCIDevice *pci_dev) -- 2.53.0
Add TYPE_ASPEED_UFS, a sysbus frontend around the common UFS core, and wire it into the AST2700 SoC. The frontend reuses UfsHc (instance_size = sizeof(UfsHc)), sets dma_as to address_space_memory, and calls ufs_realize_core() before exposing its MMIO region and IRQ via sysbus. It reports UFSHCI 2.0 (reg.ver = 0x0200) to match the AST2700 (aspeed,ufshc-m31-16nm). Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- MAINTAINERS | 1 + docs/specs/aspeed-ufs.rst | 40 ++++++++++++++++++++ docs/specs/index.rst | 1 + include/hw/arm/aspeed.h | 1 + include/hw/arm/aspeed_soc.h | 3 ++ include/hw/ufs/aspeed_ufs.h | 16 ++++++++ hw/arm/aspeed.c | 25 +++++++++++++ hw/arm/aspeed_ast27x0.c | 13 +++++++ hw/ufs/aspeed_ufs.c | 75 +++++++++++++++++++++++++++++++++++++ hw/ufs/meson.build | 1 + 10 files changed, 176 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 XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/docs/specs/aspeed-ufs.rst @@ -XXX,XX +XXX,XX @@ +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 frontend on top of the shared UFS core that also backs +the PCI UFS device (``hw/ufs/ufs.c`` and ``hw/ufs/lu.c``). The frontend only +provides the sysbus MMIO region, the interrupt line and the DMA address +space; all UFSHCI register behaviour, UTP transfer/task list processing, +UPIU and query handling and the SCSI logical-unit logic are implemented by +the core. + +The clock/reset wrapper at ``0x12c08000`` (``aspeed,ast2700-ufscnr``) is left +as an ``UnimplementedDevice``. + +Logical units +------------- + +Storage is attached through ``ufs-lu`` devices on the controller's UFS bus +(``ufs-bus.0``), exactly as for the PCI UFS device. The Huygens OpenBMC image +is laid out for 512-byte sectors, so its logical unit is created with +``logical-block-size=512``. + +Usage +----- + +Attach a UFS image as logical unit 0 of the controller's UFS bus: + +.. code-block:: console + + qemu-system-aarch64 -M huygens-bmc \ + -nodefaults \ + -blockdev node-name=fmc0,driver=file,filename=image-bmc \ + -device w25q01jvq,bus=ssi.0,cs=0,drive=fmc0 \ + -blockdev node-name=ufs0,driver=file,filename=ufs.img \ + -device ufs-lu,bus=ufs-bus.0,drive=ufs0,lun=0,logical-block-size=512 \ + -display none -serial mon:stdio + +Please check :doc:`../../system/arm/aspeed` for more details on the +``huygens-bmc`` machine. diff --git a/docs/specs/index.rst b/docs/specs/index.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -XXX,XX +XXX,XX @@ guest hardware that is specific to QEMU. riscv-aia aspeed-intc ucd90320 + aspeed-ufs iommu-testdev diff --git a/include/hw/arm/aspeed.h b/include/hw/arm/aspeed.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/aspeed.h +++ b/include/hw/arm/aspeed.h @@ -XXX,XX +XXX,XX @@ struct AspeedMachineClass { uint32_t uart_default; bool sdhci_wp_inverted; bool vbootrom; + uint32_t ufs_block_size; }; /* diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -XXX,XX +XXX,XX @@ #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" @@ -XXX,XX +XXX,XX @@ struct AspeedSoCState { AspeedAPB2OPBState fsi[2]; AspeedLTPIState ltpi_ctrl[ASPEED_IOEXP_NUM]; AspeedAST1700SoCState ioexp[ASPEED_IOEXP_NUM]; + UfsHc ufs; }; #define TYPE_ASPEED_SOC "aspeed-soc" @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/ufs/aspeed_ufs.h @@ -XXX,XX +XXX,XX @@ +/* + * ASPEED AST2700 UFS Host Controller + * + * Copyright 2026 IBM Corp. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ASPEED_UFS_H +#define ASPEED_UFS_H + +#include "hw/ufs/ufs.h" + +#define TYPE_ASPEED_UFS "aspeed-ufs" +#define ASPEED_UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_ASPEED_UFS) + +#endif /* ASPEED_UFS_H */ diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -XXX,XX +XXX,XX @@ static void aspeed_machine_init(MachineState *machine) aspeed_connect_serial_hds_to_uarts(bmc); qdev_realize(DEVICE(bmc->soc), NULL, &error_abort); + /* + * If the SoC instantiated a UFS host controller (AST2700), attach the + * first backend drive to it as logical unit 0. The controller and its + * UFS bus only exist once the SoC has been realized, so the ufs-lu is + * created here rather than through a controller drive property. + */ + if (object_resolve_path_component(OBJECT(bmc->soc), "ufs")) { + DriveInfo *ufs_dinfo = drive_get(IF_NONE, 0, 0); + + if (ufs_dinfo) { + DeviceState *ufs_lu = qdev_new(TYPE_UFS_LU); + + qdev_prop_set_uint8(ufs_lu, "lun", 0); + if (amc->ufs_block_size) { + qdev_prop_set_uint32(ufs_lu, "logical-block-size", + amc->ufs_block_size); + } + qdev_prop_set_drive_err(ufs_lu, "drive", + blk_by_legacy_dinfo(ufs_dinfo), + &error_fatal); + qdev_realize_and_unref(ufs_lu, BUS(&bmc->soc->ufs.bus), + &error_fatal); + } + } + if (defaults_enabled()) { aspeed_board_init_flashes(&bmc->soc->fmc, bmc->fmc_model ? bmc->fmc_model : amc->fmc_model, diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -XXX,XX +XXX,XX @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { [ASPEED_GIC_REDIST] = 0x12280000, [ASPEED_DEV_SDMC] = 0x12C00000, [ASPEED_DEV_SCU] = 0x12C02000, + [ASPEED_DEV_UFS] = 0x12c08200, [ASPEED_DEV_RTC] = 0x12C0F000, [ASPEED_DEV_TIMER1] = 0x12C10000, [ASPEED_DEV_PCIE_PHY0] = 0x12C15000, @@ -XXX,XX +XXX,XX @@ static const int aspeed_soc_ast2700a1_irqmap[] = { [ASPEED_DEV_EHCI2] = 37, [ASPEED_DEV_PCIE0] = 56, [ASPEED_DEV_PCIE1] = 57, + [ASPEED_DEV_UFS] = 118, [ASPEED_DEV_LPC] = 192, [ASPEED_DEV_IBT] = 192, [ASPEED_DEV_KCS] = 192, @@ -XXX,XX +XXX,XX @@ 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); @@ -XXX,XX +XXX,XX @@ 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 XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/ufs/aspeed_ufs.c @@ -XXX,XX +XXX,XX @@ +/* + * ASPEED AST2700 UFS Host Controller + * + * Sysbus frontend for the AST2700 UFS host controller + * (aspeed,ufshc-m31-16nm). The UFSHCI register interface, UTRL/UTMRL + * processing, UPIU/query handling and the SCSI/logical-unit logic are all + * provided by the shared UFS core (hw/ufs/ufs.c, hw/ufs/lu.c); this file + * only supplies the sysbus-specific MMIO, IRQ and DMA plumbing. + * + * The ASPEED clock/reset wrapper at 0x12c08000 (aspeed,ast2700-ufscnr) is + * modelled elsewhere as an UnimplementedDevice. + * + * Copyright 2026 IBM Corp. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "system/address-spaces.h" +#include "hw/ufs/aspeed_ufs.h" + +static void aspeed_ufs_realize(DeviceState *dev, Error **errp) +{ + UfsHc *u = ASPEED_UFS(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + /* AST2700 UFS masters DMA into system memory. */ + u->dma_as = &address_space_memory; + + if (!ufs_realize_core(u, errp)) { + return; + } + + /* + * The AST2700 host controller (aspeed,ufshc-m31-16nm) reports UFSHCI + * version 2.0. The shared core defaults to 4.1, which makes the U-Boot + * and Linux aspeed-ufs drivers run 4.x-only probe steps that this model + * does not implement. Pin the controller version register to 2.0. + */ + u->reg.ver = 0x00000200; + + ufs_init_mmio(u); + sysbus_init_mmio(sbd, &u->iomem); + sysbus_init_irq(sbd, &u->irq); +} + +static const Property aspeed_ufs_props[] = { + DEFINE_PROP_STRING("serial", UfsHc, params.serial), + DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), + DEFINE_PROP_UINT8("nutmrs", UfsHc, params.nutmrs, 8), + DEFINE_PROP_UINT32("wb-max-size", UfsHc, params.wb_max_size, 0x400), + DEFINE_PROP_UINT32("wb-min-size", UfsHc, params.wb_min_size, 0x100), +}; + +static void aspeed_ufs_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = aspeed_ufs_realize; + dc->desc = "ASPEED UFS Host Controller"; + device_class_set_props(dc, aspeed_ufs_props); + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); +} + +static const TypeInfo aspeed_ufs_types[] = { + { + .name = TYPE_ASPEED_UFS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(UfsHc), + .class_init = aspeed_ufs_class_init, + }, +}; + +DEFINE_TYPES(aspeed_ufs_types) diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build index XXXXXXX..XXXXXXX 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
Huygens is the BMC of the IBM POWER12 server platform. The BMC handles power sequencing, thermal management, VPD access and host firmware boot for the POWER host processors. It is built around the Aspeed AST2700 A2 SoC. Add a 'huygens-bmc' machine that instantiates the AST2700 SoC with the board-specific configuration needed to boot the OpenBMC Linux image: the FMC/SPI flash models, the I2C topology (BMC, system and chassis VPD EEPROMs, the UCD90320 power sequencer and temperature sensors) and the network controllers. Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com> --- docs/system/arm/aspeed.rst | 50 ++++++- hw/arm/aspeed_ast27x0_huygens.c | 241 ++++++++++++++++++++++++++++++++ hw/arm/meson.build | 1 + 3 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 hw/arm/aspeed_ast27x0_huygens.c diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/aspeed.rst +++ b/docs/system/arm/aspeed.rst @@ -XXX,XX +XXX,XX @@ 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 : @@ -XXX,XX +XXX,XX @@ 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 ----------------- @@ -XXX,XX +XXX,XX @@ Supported devices * PECI Controller (minimal) * I3C Controller * Internal Bridge Controller (SLI dummy) + * UFS Host Controller (aspeed,ufshc-m31-16nm) - ``huygens-bmc`` only + * FSI APB-to-OPB bridge with CFAM-S mailbox Missing devices --------------- @@ -XXX,XX +XXX,XX @@ Use ``tio`` or another terminal emulator to connect to the consoles: $ tio /dev/pts/56 $ tio /dev/pts/57 +Booting the huygens-bmc machine +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The IBM Huygens BMC is based on the AST2700 A2 SoC. Its storage is +split across two images: + +* the FMC (SPI NOR) flash image (``image-bmc``) holds U-Boot, the Linux + kernel and the FIT image; +* the UFS image (``ufs.img``) holds the root filesystem. + +Both images can be built from the OpenBMC project tree. + +To boot the machine, attach the flash image to the FMC device and the +root filesystem image to a UFS logical unit. ``-nodefaults`` is used so the +board does not also create a default flash at chip-select 0, and the console +is wired explicitly since ``-nodefaults`` disables the implicit one: + +.. code-block:: bash + + $ qemu-system-aarch64 -M huygens-bmc \ + -nodefaults \ + -blockdev node-name=fmc0,driver=file,filename=image-bmc \ + -device w25q01jvq,bus=ssi.0,cs=0,drive=fmc0 \ + -blockdev node-name=ufs0,driver=file,filename=ufs.img \ + -device ufs-lu,bus=ufs-bus.0,drive=ufs0,lun=0,logical-block-size=512 \ + -display none -serial mon:stdio + +The FMC flash sits on chip-select 0 of the ``ssi.0`` bus and the UFS logical +unit on ``ufs-bus.0``. The Huygens image is laid out for 512-byte sectors, +hence ``logical-block-size=512``. + +The machine instantiates three ``ftgmac100`` Ethernet controllers, which +are wired up by the board. To expose them with user networking, add three +``-nic user`` options to the command above: + +.. code-block:: bash + + -nic user \ + -nic user \ + -nic user + +The default BMC console is ``uart12``. + Aspeed Bridge IC and Platform Root of Trust processor family boards (``ast1030-evb``, ``ast1040-evb``, ``ast1060-evb``) ======================================================================================================================= diff --git a/hw/arm/aspeed_ast27x0_huygens.c b/hw/arm/aspeed_ast27x0_huygens.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/arm/aspeed_ast27x0_huygens.c @@ -XXX,XX +XXX,XX @@ +/* + * 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); + + /* I2C5: UCD90320 power sequencer */ + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5), "ucd90320", 0x11); + + /* I2C6: chassis1 backplane VPD EEPROM */ + { + g_autofree uint8_t *ch1 = g_malloc(huygens_bmc_fruid_len); + memcpy(ch1, huygens_bmc_fruid, huygens_bmc_fruid_len); + memcpy(ch1 + HUYGENS_CHASSIS1_FC_OFFSET, + huygens_chassis1_fc, sizeof(huygens_chassis1_fc)); + at24c_eeprom_init_rom(aspeed_i2c_get_bus(&soc->i2c, 6), 0x50, + 8 * KiB, ch1, huygens_bmc_fruid_len); + } + + /* I2C8: System VPD at 0x53 */ + at24c_eeprom_init_rom(aspeed_i2c_get_bus(&soc->i2c, 8), 0x53, + 4 * KiB, huygens_bmc_fruid, huygens_bmc_fruid_len); + /* LCD EEPROM */ + at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 8), 0x51, 8 * KiB); + + /* I2C9: System VPD redundant and op-panel devices */ + at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 9), + 0x53, 8 * KiB); /* SYSVPD-redundant */ + + /* TMP275 temperature sensor (compatible with TMP105) */ + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 9), + TYPE_TMP105, 0x48); + + /* Op-panel EEPROM */ + at24c_eeprom_init(aspeed_i2c_get_bus(&soc->i2c, 9), 0x51, 8 * KiB); + + /* PCA9552 LED controllers on bus 9 */ + aspeed_create_pca9552(soc, 9, 0x62); + aspeed_create_pca9552(soc, 9, 0x64); + aspeed_create_pca9552(soc, 9, 0x66); + + /* DPS310 pressure sensor */ + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 9), "dps310", 0x76); +} + +static void aspeed_machine_huygens_class_init(ObjectClass *oc, + const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); + + mc->alias = "ast2700-huygens"; + mc->desc = "Aspeed AST2700 A2 [IBM Huygens]"; + amc->soc_name = "ast2700-a2"; + amc->hw_strap1 = HUYGENS_BMC_HW_STRAP1; + amc->hw_strap2 = HUYGENS_BMC_HW_STRAP2; + amc->fmc_model = "w25q01jvq"; + amc->spi_model = "w25q512jv"; + amc->num_cs = 2; + amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON; + amc->uart_default = ASPEED_DEV_UART12; + amc->i2c_init = huygens_bmc_i2c_init; + amc->vbootrom = true; + /* UFS LUN block size to match the OpenBMC image layout */ + amc->ufs_block_size = 512; + mc->default_ram_size = 2 * GiB; + aspeed_machine_class_init_cpus_defaults(mc); +} + +static const TypeInfo aspeed_ast27x0_huygens_types[] = { + { + .name = MACHINE_TYPE_NAME("huygens-bmc"), + .parent = TYPE_ASPEED_MACHINE, + .class_init = aspeed_machine_huygens_class_init, + .interfaces = aarch64_machine_interfaces, + } +}; + +DEFINE_TYPES(aspeed_ast27x0_huygens_types) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -XXX,XX +XXX,XX @@ 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