[PATCH v2 7/8] ufs/aspeed: Add AST2700 sysbus UFS frontend

Mikail Sadic <[email protected]> Thu, 30 Jul 2026 15:09:42 -0500
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
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 <[email protected]>
---
 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 ec415cb8e4..abebae2ff9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2700,6 +2700,7 @@ M: Jeuk Kim <[email protected]>
 S: Supported
 F: hw/ufs/*
 F: include/block/ufs.h
+F: include/hw/ufs/aspeed_ufs.h
 F: tests/qtest/ufs-test.c
 
 megasas
diff --git a/docs/specs/aspeed-ufs.rst b/docs/specs/aspeed-ufs.rst
new file mode 100644
index 0000000000..596b5d2756
--- /dev/null
+++ b/docs/specs/aspeed-ufs.rst
@@ -0,0 +1,40 @@
+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 4de65e2fdf..dd0cdec8d4 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -40,4 +40,5 @@ guest hardware that is specific to QEMU.
    riscv-aia
    aspeed-intc
    ucd90320
+   aspeed-ufs
    iommu-testdev
diff --git a/include/hw/arm/aspeed.h b/include/hw/arm/aspeed.h
index a00238ed74..dedc784926 100644
--- a/include/hw/arm/aspeed.h
+++ b/include/hw/arm/aspeed.h
@@ -62,6 +62,7 @@ struct AspeedMachineClass {
     uint32_t uart_default;
     bool sdhci_wp_inverted;
     bool vbootrom;
+    uint32_t ufs_block_size;
 };
 
 /*
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 41dc04e293..56fab0a350 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -35,6 +35,7 @@
 #include "hw/gpio/aspeed_gpio.h"
 #include "hw/gpio/aspeed_sgpio.h"
 #include "hw/sd/aspeed_sdhci.h"
+#include "hw/ufs/aspeed_ufs.h"
 #include "hw/usb/hcd-ehci.h"
 #include "qom/object.h"
 #include "hw/misc/aspeed_lpc.h"
@@ -118,6 +119,7 @@ struct AspeedSoCState {
     AspeedAPB2OPBState fsi[2];
     AspeedLTPIState ltpi_ctrl[ASPEED_IOEXP_NUM];
     AspeedAST1700SoCState ioexp[ASPEED_IOEXP_NUM];
+    UfsHc ufs;
 };
 
 #define TYPE_ASPEED_SOC "aspeed-soc"
@@ -298,6 +300,7 @@ enum {
     ASPEED_DEV_PRIC0,
     ASPEED_DEV_PRIC1,
     ASPEED_DEV_OTP,
+    ASPEED_DEV_UFS,
 };
 
 const char *aspeed_soc_cpu_type(const char * const *valid_cpu_types);
diff --git a/include/hw/ufs/aspeed_ufs.h b/include/hw/ufs/aspeed_ufs.h
new file mode 100644
index 0000000000..1ccd0b51c0
--- /dev/null
+++ b/include/hw/ufs/aspeed_ufs.h
@@ -0,0 +1,16 @@
+/*
+ * 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 a48c442058..5b9943a0e0 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -187,6 +187,31 @@ static void aspeed_machine_init(MachineState *machine)
     aspeed_connect_serial_hds_to_uarts(bmc);
     qdev_realize(DEVICE(bmc->soc), NULL, &error_abort);
 
+    /*
+     * If the SoC instantiated a UFS host controller (AST2700), attach the
+     * first backend drive to it as logical unit 0.  The controller and its
+     * UFS bus only exist once the SoC has been realized, so the ufs-lu is
+     * created here rather than through a controller drive property.
+     */
+    if (object_resolve_path_component(OBJECT(bmc->soc), "ufs")) {
+        DriveInfo *ufs_dinfo = drive_get(IF_NONE, 0, 0);
+
+        if (ufs_dinfo) {
+            DeviceState *ufs_lu = qdev_new(TYPE_UFS_LU);
+
+            qdev_prop_set_uint8(ufs_lu, "lun", 0);
+            if (amc->ufs_block_size) {
+                qdev_prop_set_uint32(ufs_lu, "logical-block-size",
+                                     amc->ufs_block_size);
+            }
+            qdev_prop_set_drive_err(ufs_lu, "drive",
+                                    blk_by_legacy_dinfo(ufs_dinfo),
+                                    &error_fatal);
+            qdev_realize_and_unref(ufs_lu, BUS(&bmc->soc->ufs.bus),
+                                   &error_fatal);
+        }
+    }
+
     if (defaults_enabled()) {
         aspeed_board_init_flashes(&bmc->soc->fmc,
                               bmc->fmc_model ? bmc->fmc_model : amc->fmc_model,
diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index b908d7d4ff..1ee8f3a210 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -46,6 +46,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
     [ASPEED_GIC_REDIST]    =  0x12280000,
     [ASPEED_DEV_SDMC]      =  0x12C00000,
     [ASPEED_DEV_SCU]       =  0x12C02000,
+    [ASPEED_DEV_UFS]       =  0x12c08200,
     [ASPEED_DEV_RTC]       =  0x12C0F000,
     [ASPEED_DEV_TIMER1]    =  0x12C10000,
     [ASPEED_DEV_PCIE_PHY0] =  0x12C15000,
@@ -129,6 +130,7 @@ static const int aspeed_soc_ast2700a1_irqmap[] = {
     [ASPEED_DEV_EHCI2]     = 37,
     [ASPEED_DEV_PCIE0]     = 56,
     [ASPEED_DEV_PCIE1]     = 57,
+    [ASPEED_DEV_UFS]       = 118,
     [ASPEED_DEV_LPC]       = 192,
     [ASPEED_DEV_IBT]       = 192,
     [ASPEED_DEV_KCS]       = 192,
@@ -533,6 +535,8 @@ static void aspeed_soc_ast2700_init(Object *obj)
     object_initialize_child(obj, "emmc-controller.sdhci", &s->emmc.slots[0],
                             TYPE_SYSBUS_SDHCI);
 
+    object_initialize_child(obj, "ufs", &s->ufs, TYPE_ASPEED_UFS);
+
     snprintf(typename, sizeof(typename), "aspeed.timer-%s", socname);
     object_initialize_child(obj, "timerctrl", &s->timerctrl, typename);
 
@@ -1039,6 +1043,15 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->emmc), 0,
                        aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_EMMC));
 
+    /* UFS */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->ufs), errp)) {
+        return;
+    }
+    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->ufs), 0,
+                    sc->memmap[ASPEED_DEV_UFS]);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->ufs), 0,
+                       aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_UFS));
+
     /* Timer */
     object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&s->scu),
                              &error_abort);
diff --git a/hw/ufs/aspeed_ufs.c b/hw/ufs/aspeed_ufs.c
new file mode 100644
index 0000000000..d3e1a9a736
--- /dev/null
+++ b/hw/ufs/aspeed_ufs.c
@@ -0,0 +1,75 @@
+/*
+ * 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 6e68328b93..86052e6fd2 100644
--- a/hw/ufs/meson.build
+++ b/hw/ufs/meson.build
@@ -1 +1,2 @@
 system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c'))
+system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_ufs.c'))
-- 
2.53.0