Hi Mikail,

Just a reminder to follow the latest coding style.

> Subject: [PATCH v1 4/5] ufs/aspeed: Add AST2700 UFS host controller
> 
> Add an emulation of the AST2700 UFS host controller
> (aspeed,ufshc-m31-16nm) and wire it into the AST2700 SoC.
> Implement the functionality needed by U-Boot and Linux to enumerate the
> device and access a BlockBackend using the UFS protocol, allowing the
> AST2700 machine to boot from a UFS image.
> 
> Signed-off-by: Mikail Sadic <[email protected]>
> ---
>  MAINTAINERS                 |    1 +
>  docs/specs/aspeed-ufs.rst   |   69 +++
>  docs/specs/index.rst        |    1 +
>  include/hw/arm/aspeed_soc.h |    3 +
>  include/hw/ufs/aspeed_ufs.h |   66 +++
>  hw/arm/aspeed.c             |   12 +
>  hw/arm/aspeed_ast27x0.c     |   13 +
>  hw/ufs/aspeed_ufs.c         | 1095
> +++++++++++++++++++++++++++++++++++
>  hw/ufs/meson.build          |    1 +
>  9 files changed, 1261 insertions(+)
>  create mode 100644 docs/specs/aspeed-ufs.rst  create mode 100644
> include/hw/ufs/aspeed_ufs.h  create mode 100644 hw/ufs/aspeed_ufs.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b2861dfc79..d35c29c58d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2696,6 +2696,7 @@ M: Jeuk Kim <[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..f4eb6f1ce6
> --- /dev/null
> +++ b/docs/specs/aspeed-ufs.rst
> @@ -0,0 +1,69 @@
> +ASPEED AST2700 UFS Host Controller
> +===================================
> +
> +The AST2700 SoC includes a UFS host controller identified in the device
> +tree as ``aspeed,ufshc-m31-16nm``, mapped at ``0x12c08200`` (IRQ SPI
> +118). QEMU models it as a sysbus device implementing the UFSHCI v2.0
> +register interface required by U-Boot and the Linux ``aspeed-ufshcd`` driver.
> +
> +The clock/reset wrapper at ``0x12c08000`` (``aspeed,ast2700-ufscnr``)
> +is left as an ``UnimplementedDevice``.
> +
> +Implemented functionality
> +--------------------------
> +
> +Host Controller Enable (HCE)
> +  On a ``HCE=1`` write the register is cleared to 0 and a bottom-half
> +is
> +  scheduled to set it back to 1 with ``HCS`` ready bits. The read
> +handler
> +  returns 0 while in phase 1, so polling sees the 0 to 1 transition
> +without
> +  a timeout. If the controller is already in the ready state (phase 2),
> +a
> +  subsequent ``HCE=1`` write is a no-op; the controller stays enabled
> +for
> +  Linux after U-Boot has left it running.
> +
> +UIC commands
> +  ``DME_LINKSTARTUP``, ``DME_GET``, ``DME_PEER_GET``, ``DME_SET``,
> and
> +  ``DME_PEER_SET`` all succeed immediately. ``DME_LINKSTARTUP`` sets
> +  ``HCS.DP`` and raises ``IS.UIC_LINK_STARTUP`` (bit 8). ``DME_SET``
> +and
> +  ``DME_PEER_SET`` set ``HCS[10:8] = PWR_LOCAL`` and raise
> +  ``IS.UIC_POWER_MODE``.
> +
> +NOP OUT / NOP IN
> +  Doorbell processing is synchronous (inline) rather than deferred, as
> +  U-Boot's ``NOP_OUT_TIMEOUT`` is shorter than a deferred bottom-half
> +can
> +  fire in QEMU's event loop.
> +
> +SCSI block I/O
> +  READ_10, READ_16, WRITE_10, WRITE_16, INQUIRY,
> READ_CAPACITY_10/16,
> +and
> +  REPORT_LUNS are forwarded to the attached ``BlockBackend``. When
> +  ``prdtl = 0`` (no scatter-gather list), response data is placed in
> +the
> +  Response UPIU data segment; when ``prdtl > 0`` it is written into the
> +PRDT
> +  buffers.
> +
> +Query UPIU descriptors
> +  Device, Geometry, Unit, Power (idn 8), and String (idn 5) descriptors
> +are
> +  returned, capped to the requested length.
> +
> +Well-Known LUNs
> +  All four W-LUNs (0xD0 UFS Device, 0xC4 RPMB, 0xB0 Boot, 0x81 Report
> +LUNs)
> +  respond as present devices.
> +
> +Task management (UTMRL)
> +  ``LOGICAL_UNIT_RESET`` commands are acknowledged immediately by
> +writing
> +  ``OCS_SUCCESS`` into the UTMRD slot, clearing the doorbell, and
> +raising
> +  ``IS.UTP_TASK_REQ_COMPL`` (bit 9).
> +
> +Usage
> +-----
> +
> +The UFS controller picks up the first ``IF_NONE`` block device at index 0.
> +Pass a UFS disk image with ``-drive if=none``:
> +
> +.. code-block:: console
> +
> +  qemu-system-aarch64 -M huygens-bmc \
> +    -drive file=image-bmc,if=mtd,format=raw \
> +    -drive file=ufs.img,if=none,format=raw \
> +    -nographic
> +
> +Please check :doc:`../../system/arm/aspeed` for more details on the
> +``huygens-bmc`` machine.
> diff --git a/docs/specs/index.rst b/docs/specs/index.rst index
> 4de65e2fdf..dd0cdec8d4 100644
> --- a/docs/specs/index.rst
> +++ b/docs/specs/index.rst
> @@ -40,4 +40,5 @@ guest hardware that is specific to QEMU.
>     riscv-aia
>     aspeed-intc
>     ucd90320
> +   aspeed-ufs
>     iommu-testdev
> diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
> index 41dc04e293..28ca97d35c 100644
> --- a/include/hw/arm/aspeed_soc.h
> +++ b/include/hw/arm/aspeed_soc.h
> @@ -35,6 +35,7 @@
>  #include "hw/gpio/aspeed_gpio.h"
>  #include "hw/gpio/aspeed_sgpio.h"
>  #include "hw/sd/aspeed_sdhci.h"
> +#include "hw/ufs/aspeed_ufs.h"
>  #include "hw/usb/hcd-ehci.h"
>  #include "qom/object.h"
>  #include "hw/misc/aspeed_lpc.h"
> @@ -118,6 +119,7 @@ struct AspeedSoCState {
>      AspeedAPB2OPBState fsi[2];
>      AspeedLTPIState ltpi_ctrl[ASPEED_IOEXP_NUM];
>      AspeedAST1700SoCState ioexp[ASPEED_IOEXP_NUM];
> +    AspeedUFSState ufs;
>  };
> 
>  #define TYPE_ASPEED_SOC "aspeed-soc"
> @@ -298,6 +300,7 @@ enum {
>      ASPEED_DEV_PRIC0,
>      ASPEED_DEV_PRIC1,
>      ASPEED_DEV_OTP,
> +    ASPEED_DEV_UFS,
>  };
> 
>  const char *aspeed_soc_cpu_type(const char * const *valid_cpu_types); diff
> --git a/include/hw/ufs/aspeed_ufs.h b/include/hw/ufs/aspeed_ufs.h new file
> mode 100644 index 0000000000..6d0aa345e9
> --- /dev/null
> +++ b/include/hw/ufs/aspeed_ufs.h
> @@ -0,0 +1,66 @@
> +/*
> + * ASPEED AST2700 UFS Host Controller
> + *
> + * Copyright 2026 IBM Corp.
> + * SPDX-License-Identifier: GPL-2.0-or-later  */
> +
> +#ifndef ASPEED_UFS_H
> +#define ASPEED_UFS_H
> +
> +#include "hw/core/sysbus.h"
> +#include "block/ufs.h"
> +#include "system/block-backend.h"
> +
> +#define TYPE_ASPEED_UFS "aspeed-ufs"
> +OBJECT_DECLARE_SIMPLE_TYPE(AspeedUFSState, ASPEED_UFS)
> +
> +/* UFSHCI register space is 256 bytes (0x00-0xFF) */ #define
> +ASPEED_UFS_MMIO_SIZE  0x100
> +#define ASPEED_UFS_NUM_REGS   (ASPEED_UFS_MMIO_SIZE /
> sizeof(uint32_t))
> +
> +/* Number of UTP Transfer Request slots advertised */
> +#define ASPEED_UFS_NUTRS      32
> +/* Number of UTP Task Management Request slots */
> +#define ASPEED_UFS_NUTMRS     8
> +
> +/*
> + * Reset / power-on values.
> + *
> + * CAP: NUTRS=31 (0x1f, 5-bit), RTT=2, NUTMRS=7, 64AS=1
> + * VER: UFSHCI 2.0 (matches aspeed,ufshc-m31-16nm)
> + * HCS: DP|UTRLRDY|UTMRLRDY|UCRDY (bits 0-3)  */ #define
> +ASPEED_UFS_CAP_RESET  0x0702031f #define ASPEED_UFS_VER_RESET
> +0x00000200 #define ASPEED_UFS_HCS_READY  0x0000000f
> +
> +struct AspeedUFSState {
> +    SysBusDevice  parent_obj;
> +
> +    MemoryRegion  iomem;
> +    qemu_irq      irq;
> +
> +    BlockBackend *blk;
> +    uint64_t      num_sectors;
> +
> +    uint32_t      regs[ASPEED_UFS_NUM_REGS];
> +
> +    /*
> +     * HCE state machine phase:
> +     *   0 = idle (HCE=0)
> +     *   1 = cleared (firmware wrote 1, we stored 0, BH pending)
> +     *   2 = ready (BH fired, HCE=1, HCS ready bits set)
> +     */
> +    int           hce_phase;
> +    QEMUBH       *hce_bh;
> +
> +    uint32_t      utrldbr;       /* pending doorbell bits */
> +    uint64_t      utrl_base;     /* 64-bit physical base of UTRL */
> +    uint64_t      utmrl_base;    /* 64-bit physical base of UTMRL */
> +
> +    /* Unit Descriptor; capacity filled at realize time */
> +    uint8_t       unit_desc[0x23];
> +};
> +
> +#endif /* ASPEED_UFS_H */
> diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index
> a48c442058..1f57f32ad4 100644
> --- a/hw/arm/aspeed.c
> +++ b/hw/arm/aspeed.c
> @@ -184,6 +184,18 @@ static void aspeed_machine_init(MachineState
> *machine)
>          object_property_set_int(OBJECT(bmc->soc), "hw-prot-key",
>                                  ASPEED_SCU_PROT_KEY,
> &error_abort);
>      }
> +    /*
> +     * Attach the UFS backing drive before realizing the SoC so that
> +     * the UFS controller can read the drive size during realization.
> +     */
> +    {
> +        DriveInfo *ufs_dinfo = drive_get(IF_NONE, 0, 0);
> +        if (ufs_dinfo) {
> +            qdev_prop_set_drive_err(DEVICE(&bmc->soc->ufs), "drive",
> +                                    blk_by_legacy_dinfo(ufs_dinfo),
> +                                    &error_fatal);
> +        }
> +    }
>      aspeed_connect_serial_hds_to_uarts(bmc);
>      qdev_realize(DEVICE(bmc->soc), NULL, &error_abort);
> 
> diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index
> b908d7d4ff..04aa4e15a5 100644
> --- a/hw/arm/aspeed_ast27x0.c
> +++ b/hw/arm/aspeed_ast27x0.c
> @@ -39,6 +39,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = {
>      [ASPEED_DEV_EHCI2]     =  0x12063000,
>      [ASPEED_DEV_HACE]      =  0x12070000,
>      [ASPEED_DEV_EMMC]      =  0x12090000,
> +    [ASPEED_DEV_UFS]       =  0x12c08200,

Please order the entries by Register address.

>      [ASPEED_DEV_PCIE0]     =  0x120E0000,
>      [ASPEED_DEV_PCIE1]     =  0x120F0000,
>      [ASPEED_DEV_INTC]      =  0x12100000,
> @@ -116,6 +117,7 @@ static const int aspeed_soc_ast2700a1_irqmap[] = {
>      [ASPEED_DEV_SCU]       = 12,
>      [ASPEED_DEV_RTC]       = 13,
>      [ASPEED_DEV_EMMC]      = 15,
> +    [ASPEED_DEV_UFS]       = 118,

Please order the entries by IRQ value.

>      [ASPEED_DEV_TIMER1]    = 16,
>      [ASPEED_DEV_TIMER2]    = 17,
>      [ASPEED_DEV_TIMER3]    = 18,
> @@ -533,6 +535,8 @@ static void aspeed_soc_ast2700_init(Object *obj)
>      object_initialize_child(obj, "emmc-controller.sdhci", &s->emmc.slots[0],
>                              TYPE_SYSBUS_SDHCI);
> 
> +    object_initialize_child(obj, "ufs", &s->ufs, TYPE_ASPEED_UFS);
> +
>      snprintf(typename, sizeof(typename), "aspeed.timer-%s", socname);
>      object_initialize_child(obj, "timerctrl", &s->timerctrl, typename);
> 
> @@ -1039,6 +1043,15 @@ static void
> aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
>      sysbus_connect_irq(SYS_BUS_DEVICE(&s->emmc), 0,
>                         aspeed_soc_ast2700_get_irq(s,
> ASPEED_DEV_EMMC));
> 
> +    /* UFS */
> +    if (!sysbus_realize(SYS_BUS_DEVICE(&s->ufs), errp)) {
> +        return;
> +    }
> +    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->ufs), 0,
> +                    sc->memmap[ASPEED_DEV_UFS]);
> +    sysbus_connect_irq(SYS_BUS_DEVICE(&s->ufs), 0,
> +                       aspeed_soc_ast2700_get_irq(s,
> ASPEED_DEV_UFS));
> +
>      /* Timer */
>      object_property_set_link(OBJECT(&s->timerctrl), "scu",
> OBJECT(&s->scu),
>                               &error_abort); diff --git
> a/hw/ufs/aspeed_ufs.c b/hw/ufs/aspeed_ufs.c new file mode 100644 index
> 0000000000..673aced63b
> --- /dev/null
> +++ b/hw/ufs/aspeed_ufs.c
> @@ -0,0 +1,1095 @@
> +/*
> + * ASPEED AST2700 UFS Host Controller
> + *
> + * Sysbus device mapped at 0x12c08200 (256 bytes).  Implements the
> +UFSHCI
> + * register interface required by U-Boot ufshcd_probe() and Linux
> + * aspeed-ufshcd, including UIC command handling, NOP OUT/IN, SCSI
> +block I/O,
> + * Query UPIU descriptor reads, W-LUN support, and UTMRL task
> management.
> + *
> + * Register offsets follow JEDEC UFSHCI spec v2.0, matching the REG_*
> + * definitions in include/block/ufs.h.
> + *
> + * The ASPEED clock/reset wrapper at 0x12c08000 (aspeed,ast2700-ufscnr)
> +is
> + * left as an UnimplementedDevice; writes to MPHY registers there
> +require no
> + * readback.
> + *
> + * Copyright 2026 IBM Corp.
> + * SPDX-License-Identifier: GPL-2.0-or-later  */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/qdev-properties-system.h"
> +#include "hw/core/irq.h"
> +#include "migration/vmstate.h"
> +#include "system/block-backend.h"
> +#include "system/block-backend-io.h"
> +#include "system/dma.h"
> +#include "system/address-spaces.h"
> +#include "hw/ufs/aspeed_ufs.h"
> +
> +/* UFSHCI register offsets (JEDEC UFSHCI spec v2.0) */
> +#define REG_CAP          0x00
> +#define REG_VER          0x08
> +#define REG_HCPID        0x10
> +#define REG_HCMID        0x14
> +#define REG_AHIT         0x18
> +#define REG_IS           0x20
> +#define REG_IE           0x24
> +#define REG_HCS          0x30
> +#define REG_HCE          0x34


REG32(REG_HCE, 0x34)  ?
    FIELD(REG_HCE, HCE_ENABLE,      0,  1) ?

> +#define REG_UECPA        0x38
> +#define REG_UECDL        0x3c
> +#define REG_UECN         0x40
> +#define REG_UECT         0x44
> +#define REG_UECDME       0x48
> +#define REG_UTRLBA       0x50
> +#define REG_UTRLBAU      0x54
> +#define REG_UTRLDBR      0x58
> +#define REG_UTRLCLR      0x5c
> +#define REG_UTRLRSR      0x60
> +#define REG_UTMRLBA      0x70
> +#define REG_UTMRLBAU     0x74
> +#define REG_UTMRLDBR     0x78
> +#define REG_UTMRLCLR     0x7c
> +#define REG_UTMRLRSR     0x80
> +#define REG_UICCMD       0x90
> +#define REG_UCMDARG1     0x94
> +#define REG_UCMDARG2     0x98
> +#define REG_UCMDARG3     0x9c
> +
> +/* HCE / HCS bits */
> +#define HCE_ENABLE       BIT(0)
> +#define HCS_DP           BIT(0)
> +#define HCS_UTRLRDY      BIT(1)
> +#define HCS_UTMRLRDY     BIT(2)
> +#define HCS_UCRDY        BIT(3)
> +
> +/* IS bits (UFSHCI spec sec. 5.3) */
> +#define IS_UTRCS            BIT(0)    /* UTP Transfer Request
> Completion */
> +#define IS_UIC_POWER_MODE   BIT(4)    /* UIC Power Mode Status */
> +#define IS_UIC_LINK_STARTUP BIT(8)    /* UIC Link Startup Status */
> +#define IS_UTMRCS           BIT(9)    /* UTP Task Mgmt Request
> Completion */
> +#define IS_UCCS             BIT(10)   /* UIC Command Completion
> Status */
> +
> +/* UIC command opcodes */
> +#define DME_LINKSTARTUP     0x16
> +#define DME_GET             0x01
> +#define DME_SET             0x02
> +#define DME_PEER_GET        0x03
> +#define DME_PEER_SET        0x04
> +
> +/* UIC command result (success) in UCMDARG2[7:0] */
> +#define UIC_CMD_RESULT_OK   0x00
> +
> +/* HCS power-mode state field bits [10:8]: PWR_LOCAL = 1 */
> +#define HCS_PWR_LOCAL       (1 << 8)
> +
> +/* Register array index helper */
> +#define R(off)  ((off) / sizeof(uint32_t))
> +
> +/*
> + * UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1).
> + */
> +#define UTRD_SIZE 32 /* bytes per UTRL slot */
> +
> +/*
> + * UTMRL slot size (UTP Task Management Request List):
> + *  - header   : 16 bytes
> + *  - upiu_req : 32 bytes
> + *  - upiu_rsp : 32 bytes
> + */
> +#define UTMRD_SIZE 80
> +
> +/* byte offset of OCS within request_desc_header */ #define
> +UTMRD_OCS_OFFSET 8
> +
> +/* Command Type in UTRD DW0[29:28] */
> +#define CT_UFS_STORAGE   0x1
> +
> +/* Data Direction in UTRD DW0[25:24] */
> +#define DD_NO_DATA       0x0
> +#define DD_HOST_TO_DEV   0x2
> +#define DD_DEV_TO_HOST   0x4
> +
> +/* OCS in UTRD DW2[7:0] */
> +#define OCS_SUCCESS      0x0
> +
> +/* UPIU transaction types */
> +#define UPIU_TYPE_NOP_OUT     0x00
> +#define UPIU_TYPE_CMD         0x01
> +#define UPIU_TYPE_QUERY_REQ   0x16
> +#define UPIU_TYPE_NOP_IN      0x20
> +#define UPIU_TYPE_RESPONSE    0x21
> +#define UPIU_TYPE_QUERY_RSP   0x36
> +
> +/* Query opcodes */
> +#define QUERY_OP_READ_DESC    0x01
> +#define QUERY_OP_WRITE_DESC   0x02
> +#define QUERY_OP_READ_ATTR    0x03
> +#define QUERY_OP_WRITE_ATTR   0x04
> +#define QUERY_OP_READ_FLAG    0x05
> +#define QUERY_OP_SET_FLAG     0x06
> +#define QUERY_OP_CLEAR_FLAG   0x07
> +
> +/* Descriptor idn */
> +#define DESC_IDN_DEVICE       0x00
> +#define DESC_IDN_CONFIGURATION 0x01
> +#define DESC_IDN_UNIT         0x02
> +#define DESC_IDN_GEOMETRY     0x07
> +#define DESC_IDN_POWER        0x08
> +#define DESC_IDN_STRING       0x05
> +
> +/* SCSI commands */
> +#define SCSI_TEST_UNIT_READY  0x00
> +#define SCSI_INQUIRY          0x12
> +#define SCSI_READ_CAPACITY_10 0x25
> +#define SCSI_READ_10          0x28
> +#define SCSI_WRITE_10         0x2a
> +#define SCSI_READ_16          0x88
> +#define SCSI_WRITE_16         0x8a
> +#define SCSI_READ_CAPACITY_16 0x9e   /* service action 0x10 */
> +#define SCSI_REPORT_LUNS      0xa0
> +
> +/*
> + * Well-Known LUN addresses probed by ufshcd_scsi_add_wlus().
> + * All four must appear as present devices or async_scan fails.
> + */
> +#define WLUN_REPORT_LUNS 0x81
> +#define WLUN_UFS_DEVICE  0xd0
> +#define WLUN_BOOT        0xb0
> +#define WLUN_RPMB        0xc4
> +
> +static bool aspeed_ufs_is_wlun(uint8_t lun) {
> +    return lun == WLUN_UFS_DEVICE || lun == WLUN_RPMB ||
> +           lun == WLUN_BOOT       || lun == WLUN_REPORT_LUNS;
> +}
> +
> +static void ufs_dma_read(hwaddr paddr, void *buf, size_t len) {
> +    dma_memory_read(&address_space_memory, paddr, buf, len,
> +                    MEMTXATTRS_UNSPECIFIED); }
> +
> +static void ufs_dma_write(hwaddr paddr, const void *buf, size_t len) {
> +    dma_memory_write(&address_space_memory, paddr, buf, len,
> +                     MEMTXATTRS_UNSPECIFIED); }
> +
> +static void aspeed_ufs_update_irq(AspeedUFSState *s) {
> +    uint32_t is  = s->regs[R(REG_IS)];
> +    uint32_t ie  = s->regs[R(REG_IE)];
> +
> +    qemu_set_irq(s->irq, !!(is & ie));
> +}
> +
> +/*
> + * HCE bottom-half: completes the 1->0->1 toggle that the host driver polls.
> + *
> + * On any HCE=1 write the write handler clears HCE to 0 and schedules
> +this
> + * BH.  Once it fires the controller is considered ready.
> + */
> +static void aspeed_ufs_hce_bh(void *opaque) {
> +    AspeedUFSState *s = opaque;
> +
> +    s->regs[R(REG_HCE)] = HCE_ENABLE;


R_ REG_HCE ?

> +    s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY;
> +    s->hce_phase = 2;
> +}
> +
> +/* 12-byte UPIU header (big-endian per UFSHCI spec) */ typedef struct {
> +    uint8_t  trans_type;
> +    uint8_t  flags;
> +    uint8_t  lun;
> +    uint8_t  task_tag;
> +    uint8_t  iid_cmd_set;
> +    uint8_t  query_func;
> +    uint8_t  response;
> +    uint8_t  status;
> +    uint8_t  ehs_len;
> +    uint8_t  device_info;
> +    uint16_t data_seg_len_be;
> +} UpiuHeader;
> +
> +/* 32-byte UPIU (header + TSF + reserved dword) */ typedef struct {
> +    UpiuHeader hdr;
> +    uint8_t    tsf[16];
> +    uint32_t   reserved;
> +} Upiu32;
> +
> +/* UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1, 32 bytes)
> +*/ typedef struct {
> +    uint32_t dw0;
> +    uint32_t dw1;
> +    uint32_t dw2;   /* OCS[7:0] */
> +    uint32_t dw3;
> +    uint32_t dw4;   /* UCDBA: Command UPIU base, lower 32 bits */
> +    uint32_t dw5;   /* UCDBAU: upper 32 bits */
> +    uint32_t dw6;   /* RUL[31:16] | RUO[15:0] in DWORDs */
> +    uint32_t dw7;   /* PRDTO[31:16] | PRDTL[15:0] */
> +} Utrd;
> +
> +/* Physical Region Descriptor Table entry */ typedef struct {
> +    uint32_t dba;       /* Data Buffer Address (lower) */
> +    uint32_t dbau;      /* Data Buffer Address (upper) */
> +    uint32_t reserved;
> +    uint32_t size;      /* byte count - 1, bits [17:0] */
> +} PrdtEntry;
> +
> +/*
> + * Minimal UFS Device Descriptor (64 bytes).
> + * Values follow UFS 2.1 spec defaults; only fields checked by Linux
> +and
> + * U-Boot ufshcd are set to non-zero.
> + */
> +static const uint8_t ufs_device_desc[] = {
> +    0x40,                /* bLength = 64 */
> +    DESC_IDN_DEVICE,     /* bDescriptorIDN */
> +    0x00,                /* bDevice */
> +    0x00,                /* bDeviceClass */
> +    0x00,                /* bDeviceSubClass */
> +    0x01,                /* bProtocol */
> +    0x01,                /* bNumberLU */
> +    0x00,                /* bNumberWLU */
> +    0x00,                /* bBootEnable */
> +    0x00,                /* bDescrAccessEn */
> +    0x00,                /* bInitPowerMode */
> +    0x01,                /* bHighPriorityLUN */
> +    0x00,                /* bSecureRemovalType */
> +    0x00,                /* bSecurityLU */
> +    0x00,                /* bBackgroundOpsTermLat */
> +    0x01,                /* bInitActiveICCLevel */
> +    0x02, 0x10,          /* wSpecVersion = 0x0210 (UFS 2.1) */
> +    0x00, 0x00,          /* wManufactureDate */
> +    0x00,                /* iManufacturerName */
> +    0x02,                /* iProductName (string index 2) */
> +    0x00,                /* iSerialNumber */
> +    0x00,                /* iOemID */
> +    0x00, 0x00,          /* wManufacturerID */
> +    0x00,                /* bUD0BaseOffset */
> +    0x00,                /* bUDConfigPlength */
> +    0x00,                /* bDeviceRTTCap */
> +    0x00, 0x00,          /* wPeriodicRTCUpdate */
> +    0x00,                /* bUFSFeaturesSupport */
> +    0x00,                /* bFFUTimeout */
> +    0x00,                /* bQueueDepth */
> +    0x00, 0x00,          /* wDeviceVersion */
> +    0x00,                /* bNumSecureWPArea */
> +    0x00, 0x00, 0x00, 0x00, /* dPSAMaxDataSize */
> +    0x00,                /* bPSAStateTimeout */
> +    0x00,                /* iProductRevisionLevel */
> +    /* pad to 0x40 */
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
> +
> +/* Minimal Geometry Descriptor (0x48 bytes) */ static const uint8_t
> +ufs_geometry_desc[] = {
> +    0x48,                /* bLength */
> +    DESC_IDN_GEOMETRY,   /* bDescriptorIDN */
> +    0x00,                /* bMediaTechnology */
> +    0x00,                /* reserved */
> +    0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00, /* qTotalRawDeviceCapacity (8 bytes, big-endian)
> */
> +    0x00,                /* bMaxNumberLU */
> +    0x00, 0x10, 0x00, 0x00, /* dSegmentSize = 4096 sectors */
> +    0x08,                /* bAllocationUnitSize */
> +    0x01,                /* bMinAddrBlockSize */
> +    0x00,                /* bOptimalReadBlockSize */
> +    0x00,                /* bOptimalWriteBlockSize */
> +    0x00,                /* bMaxInBufferSize */
> +    0x00,                /* bMaxOutBufferSize */
> +    0x00,                /* bRPMB_ReadWriteSize */
> +    0x00,                /* bDynamicCapacityResourcePolicy */
> +    0x00,                /* bDataOrdering */
> +    0x00,                /* bMaxContexIDNumber */
> +    0x00,                /* bSysDataTagUnitSize */
> +    0x00,                /* bSysDataTagResSize */
> +    0x01,                /* bSupportedSecRTypes */
> +    0x00, 0x03,          /* wSupportedMemoryTypes */
> +    0x00, 0x00, 0x00, 0x00, /* dSystemCodeMaxNAllocU */
> +    0x00, 0x00,          /* wSystemCodeCapAdjFac */
> +    0x00, 0x00, 0x00, 0x00, /* dNonPersistMaxNAllocU */
> +    0x00, 0x00,          /* wNonPersistCapAdjFac */
> +    0x00, 0x00, 0x00, 0x00, /* dEnhanced1MaxNAllocU */
> +    0x00, 0x00,          /* wEnhanced1CapAdjFac */
> +    0x00, 0x00, 0x00, 0x00, /* dEnhanced2MaxNAllocU */
> +    0x00, 0x00,          /* wEnhanced2CapAdjFac */
> +    0x00, 0x00, 0x00, 0x00, /* dEnhanced3MaxNAllocU */
> +    0x00, 0x00,          /* wEnhanced3CapAdjFac */
> +    0x00, 0x00, 0x00, 0x00, /* dEnhanced4MaxNAllocU */
> +    0x00, 0x00,          /* wEnhanced4CapAdjFac */
> +    /* pad to 0x48 */
> +    0x00, 0x00, 0x00, 0x00,
> +    0x00, 0x00, 0x00, 0x00,
> +};
> +
> +/*
> + * Power Descriptor (idn 8, bLength 0x62).
> + * Linux ufshcd_probe() reads this; a zero-filled body of the right
> +length
> + * satisfies the length check.
> + */
> +static const uint8_t ufs_power_desc[0x62] = {
> +    0x62,                /* bLength */
> +    DESC_IDN_POWER,      /* bDescriptorIDN */
> +    /* remaining bytes zero-initialized */ };
> +
> +/*
> + * String Descriptor (idn 5).
> + * iProductName points to string index 2 in the device descriptor.  A
> + * minimal well-formed header (type + zero length) keeps ufshcd happy.
> + */
> +static const uint8_t ufs_string_desc[] = {
> +    0x02,                /* bLength = 2 (header only, no string data) */
> +    DESC_IDN_STRING,     /* bDescriptorIDN */
> +};
> +
> +/* Unit Descriptor (0x23 bytes) - capacity filled at realize time */
> +
> +static void aspeed_ufs_init_unit_desc(AspeedUFSState *s) {
> +    uint64_t lbc;
> +
> +    memset(s->unit_desc, 0, sizeof(s->unit_desc));
> +    s->unit_desc[0x00] = 0x23;          /* bLength */
> +    s->unit_desc[0x01] = DESC_IDN_UNIT; /* bDescriptorIDN */
> +    s->unit_desc[0x02] = 0x00;          /* bUnitIndex */
> +    s->unit_desc[0x03] = 0x01;          /* bLUEnable */
> +    s->unit_desc[0x08] = 0x02;          /* bMemoryType */
> +    s->unit_desc[0x0a] = 0x0c;          /* bLogicalBlockSize: 1 << 12 =
> 4096 */
> +
> +    lbc = cpu_to_be64(s->num_sectors);
> +    memcpy(&s->unit_desc[0x0b], &lbc, 8); /* qLogicalBlockCount */ }
> +
> +/* Build a 36-byte SCSI INQUIRY response */ static void
> +scsi_build_inquiry(uint8_t *buf, uint8_t lun) {
> +    memset(buf, 0, 36);
> +    /*
> +     * W-LUNs use device_type 0x1e (well known LU); LUN 0 is a
> direct-access
> +     * block device (0x00).
> +     */
> +    buf[0] = aspeed_ufs_is_wlun(lun) ? 0x1e : 0x00;
> +    buf[1] = 0x00;   /* not removable */
> +    buf[2] = 0x05;   /* SPC-3 */
> +    buf[3] = 0x12;   /* response data format = 2, HiSup */
> +    buf[4] = 0x1f;   /* additional length = 31 */
> +    memcpy(&buf[8],  "ASPEED  ", 8);
> +    memcpy(&buf[16], "UFS QEMU        ", 16);
> +    memcpy(&buf[32], "1.00", 4);
> +}
> +
> +/* Build an 8-byte READ_CAPACITY(10) response */ static void
> +scsi_build_read_cap10(uint8_t *buf, uint64_t sectors) {
> +    uint32_t last_lba = (sectors > 0xffffffff) ? 0xffffffff
> +                                               : (uint32_t)(sectors
> - 1);
> +    uint32_t block_size = cpu_to_be32(512);
> +
> +    last_lba = cpu_to_be32(last_lba);
> +    memcpy(buf,     &last_lba,   4);
> +    memcpy(buf + 4, &block_size, 4);
> +}
> +
> +/* Build a 32-byte READ_CAPACITY(16) response */ static void
> +scsi_build_read_cap16(uint8_t *buf, uint64_t sectors) {
> +    uint64_t last_lba = cpu_to_be64(sectors - 1);
> +    uint32_t block_size = cpu_to_be32(512);
> +    memcpy(buf,     &last_lba,   8);
> +    memcpy(buf + 8, &block_size, 4);
> +}
> +
> +/* Process one UTRL slot */
> +static void aspeed_ufs_process_slot(AspeedUFSState *s, int slot) {
> +    uint64_t utrd_addr = s->utrl_base + (uint64_t)slot * UTRD_SIZE;
> +    Utrd utrd;
> +    uint64_t upiu_base, resp_base, prdt_base;
> +    uint32_t dw6, dw7, ruo, prdtl, prdto;
> +    UpiuHeader req_hdr;
> +    uint8_t trans_type, task_tag;
> +
> +    ufs_dma_read(utrd_addr, &utrd, sizeof(utrd));
> +
> +    upiu_base = (uint64_t)le32_to_cpu(utrd.dw4) |
> +                ((uint64_t)le32_to_cpu(utrd.dw5) << 32);
> +
> +    /*
> +     * DW6 holds the Response UPIU offset (RUO) and length (RUL), both in
> +     * DWORDs.  The response UPIU follows the request UPIU in the same
> +     * guest buffer.
> +     */
> +    dw6  = le32_to_cpu(utrd.dw6);
> +    ruo  = dw6 & 0xffff;
> +    resp_base = upiu_base + (uint64_t)ruo * 4;
> +
> +    /* DW7: PRDTL (entry count) and PRDTO (offset in DWORDs) */
> +    dw7   = le32_to_cpu(utrd.dw7);
> +    prdtl = dw7 & 0xffff;
> +    prdto = (dw7 >> 16) & 0xffff;
> +    prdt_base = upiu_base + (uint64_t)prdto * 4;
> +
> +    ufs_dma_read(upiu_base, &req_hdr, sizeof(req_hdr));
> +    trans_type = req_hdr.trans_type & 0x3f;
> +    task_tag   = req_hdr.task_tag;
> +
> +    /* NOP OUT -> NOP IN */
> +    if (trans_type == UPIU_TYPE_NOP_OUT) {
> +        uint8_t rsp[32];
> +        memset(rsp, 0, sizeof(rsp));
> +        rsp[0] = UPIU_TYPE_NOP_IN;
> +        rsp[3] = task_tag;
> +        ufs_dma_write(resp_base, rsp, 32);
> +        goto done;
> +    }
> +
> +    /* Query Request -> Query Response */
> +    if (trans_type == UPIU_TYPE_QUERY_REQ) {
> +        uint8_t req_buf[32];
> +        uint8_t rsp[32];
> +        uint8_t opcode, idn;
> +
> +        ufs_dma_read(upiu_base, req_buf, 32);
> +        opcode = req_buf[12];
> +        idn    = req_buf[13];
> +
> +        memset(rsp, 0, sizeof(rsp));
> +        rsp[0] = UPIU_TYPE_QUERY_RSP;
> +        rsp[3] = task_tag;
> +        memcpy(&rsp[12], &req_buf[12], 16);  /* echo TSF */
> +
> +        if (opcode == QUERY_OP_READ_DESC) {
> +            const uint8_t *desc = NULL;
> +            uint16_t desc_len = 0;
> +            uint16_t req_len;
> +
> +            switch (idn) {
> +            case DESC_IDN_DEVICE:
> +                desc     = ufs_device_desc;
> +                desc_len = sizeof(ufs_device_desc);
> +                break;
> +            case DESC_IDN_GEOMETRY:
> +                desc     = ufs_geometry_desc;
> +                desc_len = sizeof(ufs_geometry_desc);
> +                break;
> +            case DESC_IDN_UNIT:
> +                desc     = s->unit_desc;
> +                desc_len = sizeof(s->unit_desc);
> +                break;
> +            case DESC_IDN_POWER:
> +                desc     = ufs_power_desc;
> +                desc_len = sizeof(ufs_power_desc);
> +                break;
> +            case DESC_IDN_STRING:
> +                desc     = ufs_string_desc;
> +                desc_len = sizeof(ufs_string_desc);
> +                break;
> +            default:
> +                break;
> +            }
> +
> +            /*
> +             * Cap the returned length to what the host requested (TSF
> +             * bytes 6-7 hold the allocation length).  Returning more than
> +             * asked causes the driver to reject the response with -EINVAL.
> +             */
> +            req_len = ((uint16_t)req_buf[18] << 8) | req_buf[19];
> +            if (desc_len > req_len) {
> +                desc_len = req_len;
> +            }
> +
> +            rsp[10] = (desc_len >> 8) & 0xff;
> +            rsp[11] =  desc_len       & 0xff;
> +            rsp[18] = rsp[10];
> +            rsp[19] = rsp[11];
> +
> +            ufs_dma_write(resp_base, rsp, 32);
> +            if (desc && desc_len) {
> +                ufs_dma_write(resp_base + 32, desc, desc_len);
> +            }
> +        } else {
> +            /* READ_ATTR, READ_FLAG, SET_FLAG, etc. - echo TSF, success
> */
> +            ufs_dma_write(resp_base, rsp, 32);
> +        }
> +        goto done;
> +    }
> +
> +    /* SCSI Command UPIU */
> +    if (trans_type == UPIU_TYPE_CMD) {
> +        uint8_t req_buf[32];
> +        uint8_t *cdb;
> +        uint8_t scsi_cmd;
> +        bool ok = true;
> +        uint8_t sense[18];
> +        uint8_t *xfer_buf = NULL;
> +        uint32_t xfer_len = 0;
> +
> +        ufs_dma_read(upiu_base, req_buf, 32);
> +        cdb      = &req_buf[16];
> +        scsi_cmd = cdb[0];
> +
> +        memset(sense, 0, sizeof(sense));
> +
> +        /* Non-zero LUNs that are not W-LUNs have no device. */
> +        if (req_hdr.lun != 0 && !aspeed_ufs_is_wlun(req_hdr.lun)) {
> +            ok = false;
> +            sense[0] = 0x70;
> +            sense[2] = 0x05;   /* ILLEGAL REQUEST */
> +            sense[7] = 0x0a;
> +            goto scsi_done;
> +        }
> +
> +        if (prdtl > 0) {
> +            uint32_t i;
> +
> +            for (i = 0; i < prdtl; i++) {
> +                PrdtEntry pe;
> +                ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
> +                             &pe, sizeof(pe));
> +                xfer_len += (le32_to_cpu(pe.size) & 0x3ffff) + 1;
> +            }
> +            xfer_buf = g_malloc0(xfer_len);
> +        }
> +
> +        switch (scsi_cmd) {
> +
> +        case SCSI_TEST_UNIT_READY:
> +            break;
> +
> +        case SCSI_INQUIRY: {
> +            uint8_t inq[36];
> +            uint32_t alloc, send;
> +
> +            /*
> +             * EVPD queries (cdb[1] & 0x01) return an empty VPD page
> list
> +             * for W-LUNs.  This stops the driver from iterating all 256
> +             * possible VPD pages after it finds zero supported ones.
> +             */
> +            if ((cdb[1] & 0x01) && aspeed_ufs_is_wlun(req_hdr.lun)) {
> +                uint8_t evpd[4];
> +                memset(evpd, 0, sizeof(evpd));
> +                evpd[0] = 0x1e;   /* device_type: well known LU */
> +                /* page_length = 0: no VPD pages supported */
> +                alloc = ((uint16_t)cdb[3] << 8) | cdb[4];
> +                send  = MIN(alloc, sizeof(evpd));
> +                if (prdtl > 0 && xfer_buf) {
> +                    memcpy(xfer_buf, evpd, send);
> +                } else {
> +                    ufs_dma_write(resp_base + 32, evpd, send);
> +                }
> +                xfer_len = send;
> +                break;
> +            }
> +
> +            scsi_build_inquiry(inq, req_hdr.lun);
> +            alloc = ((uint16_t)cdb[3] << 8) | cdb[4];
> +            send  = MIN(alloc, 36);
> +            if (prdtl > 0 && xfer_buf) {
> +                memcpy(xfer_buf, inq, send);
> +            } else {
> +                ufs_dma_write(resp_base + 32, inq, send);
> +            }
> +            xfer_len = send;
> +            break;
> +        }
> +
> +        case SCSI_READ_CAPACITY_10: {
> +            uint8_t cap[8];
> +            scsi_build_read_cap10(cap, s->num_sectors);
> +            if (prdtl > 0 && xfer_buf) {
> +                memcpy(xfer_buf, cap, 8);
> +            } else {
> +                ufs_dma_write(resp_base + 32, cap, 8);
> +            }
> +            xfer_len = 8;
> +            break;
> +        }
> +
> +        case SCSI_READ_CAPACITY_16:
> +            /* service action must be 0x10 */
> +            if ((cdb[1] & 0x1f) == 0x10) {
> +                uint8_t cap[32];
> +                memset(cap, 0, sizeof(cap));
> +                scsi_build_read_cap16(cap, s->num_sectors);
> +                if (prdtl > 0 && xfer_buf) {
> +                    memcpy(xfer_buf, cap, 32);
> +                } else {
> +                    ufs_dma_write(resp_base + 32, cap, 32);
> +                }
> +                xfer_len = 32;
> +            }
> +            break;
> +
> +        case SCSI_READ_10:
> +        case SCSI_READ_16: {
> +            uint64_t lba;
> +            uint32_t num_blocks, byte_count;
> +
> +            if (scsi_cmd == SCSI_READ_10) {
> +                lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 |
> +                      (uint64_t)cdb[4] <<  8 | cdb[5];
> +                num_blocks = (uint16_t)cdb[7] << 8 | cdb[8];
> +            } else {
> +                lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 |
> +                      (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 |
> +                      (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 |
> +                      (uint64_t)cdb[8] <<  8 | cdb[9];
> +                num_blocks = (uint32_t)cdb[10] << 24 |
> +                             (uint32_t)cdb[11] << 16 |
> +                             (uint32_t)cdb[12] <<  8 | cdb[13];
> +            }
> +            byte_count = num_blocks * 512;
> +            if (s->blk && byte_count > 0) {
> +                uint8_t *rbuf = g_malloc(byte_count);
> +                if (blk_pread(s->blk, lba * 512, byte_count, rbuf, 0) < 0) {
> +                    ok = false;
> +                } else if (prdtl > 0 && xfer_buf) {
> +                    memcpy(xfer_buf, rbuf,
> +                           MIN(byte_count, xfer_len));
> +                } else {
> +                    ufs_dma_write(resp_base + 32, rbuf, byte_count);
> +                }
> +                g_free(rbuf);
> +            }
> +            xfer_len = byte_count;
> +            break;
> +        }
> +
> +        case SCSI_WRITE_10:
> +        case SCSI_WRITE_16: {
> +            uint64_t lba;
> +            uint32_t num_blocks, byte_count;
> +
> +            if (scsi_cmd == SCSI_WRITE_10) {
> +                lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 |
> +                      (uint64_t)cdb[4] <<  8 | cdb[5];
> +                num_blocks = (uint16_t)cdb[7] << 8 | cdb[8];
> +            } else {
> +                lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 |
> +                      (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 |
> +                      (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 |
> +                      (uint64_t)cdb[8] <<  8 | cdb[9];
> +                num_blocks = (uint32_t)cdb[10] << 24 |
> +                             (uint32_t)cdb[11] << 16 |
> +                             (uint32_t)cdb[12] <<  8 | cdb[13];
> +            }
> +            byte_count = num_blocks * 512;
> +            if (s->blk && byte_count > 0 && prdtl > 0 && xfer_buf) {
> +                uint32_t copied = 0, i;
> +                for (i = 0; i < prdtl && copied < byte_count; i++) {
> +                    PrdtEntry pe;
> +                    uint64_t dba;
> +                    uint32_t chunk;
> +                    ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
> +                                 &pe, sizeof(pe));
> +                    dba   = (uint64_t)le32_to_cpu(pe.dba) |
> +                            ((uint64_t)le32_to_cpu(pe.dbau) << 32);
> +                    chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1;
> +                    chunk = MIN(chunk, byte_count - copied);
> +                    ufs_dma_read(dba, xfer_buf + copied, chunk);
> +                    copied += chunk;
> +                }
> +                if (blk_pwrite(s->blk, lba * 512, byte_count,
> +                               xfer_buf, 0) < 0) {
> +                    ok = false;
> +                }
> +            }
> +            xfer_len = 0;
> +            break;
> +        }
> +
> +        case SCSI_REPORT_LUNS: {
> +            uint8_t rl[16];
> +            memset(rl, 0, sizeof(rl));
> +            rl[3] = 8;   /* list length = 8 (one LUN entry) */
> +            if (prdtl > 0 && xfer_buf) {
> +                memcpy(xfer_buf, rl, 16);
> +            } else {
> +                ufs_dma_write(resp_base + 32, rl, 16);
> +            }
> +            xfer_len = 16;
> +            break;
> +        }
> +
> +        default:
> +            ok = false;
> +            sense[0] = 0x70;
> +            sense[2] = 0x05;   /* ILLEGAL REQUEST */
> +            sense[7] = 0x0a;
> +            xfer_len = 0;
> +            break;
> +        }
> +
> +        /* Scatter read data back into guest PRDT buffers */
> +        if (ok && prdtl > 0 && xfer_buf && xfer_len > 0) {
> +            uint32_t copied = 0, i;
> +            for (i = 0; i < prdtl && copied < xfer_len; i++) {
> +                PrdtEntry pe;
> +                uint64_t dba;
> +                uint32_t chunk;
> +                ufs_dma_read(prdt_base + i * sizeof(PrdtEntry),
> +                             &pe, sizeof(pe));
> +                dba   = (uint64_t)le32_to_cpu(pe.dba) |
> +                        ((uint64_t)le32_to_cpu(pe.dbau) << 32);
> +                chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1;
> +                chunk = MIN(chunk, xfer_len - copied);
> +                ufs_dma_write(dba, xfer_buf + copied, chunk);
> +                copied += chunk;
> +            }
> +        }
> +
> +        g_free(xfer_buf);
> +
> +scsi_done:
> +        /* Build SCSI Response UPIU */
> +        {
> +            uint8_t rsp[32];
> +            uint16_t seg_len = 0;
> +
> +            memset(rsp, 0, sizeof(rsp));
> +            rsp[0] = UPIU_TYPE_RESPONSE;
> +            rsp[3] = task_tag;
> +            rsp[7] = ok ? 0x00 : 0x02;   /* SCSI status */
> +
> +            /*
> +             * If prdtl=0 (no scatter-gather list), the data was written
> +             * directly to the response UPIU data segment; report its
> length
> +             * in data_seg_len so the host driver knows where to find it.
> +             */
> +            if (ok && prdtl == 0 && xfer_len > 0) {
> +                seg_len = (uint16_t)xfer_len;
> +            } else if (!ok) {
> +                seg_len = sizeof(sense);
> +            }
> +            rsp[10] = (seg_len >> 8) & 0xff;
> +            rsp[11] =  seg_len       & 0xff;
> +
> +            ufs_dma_write(resp_base, rsp, 32);
> +            if (!ok) {
> +                ufs_dma_write(resp_base + 32, sense, sizeof(sense));
> +            }
> +        }
> +        goto done;
> +    }
> +
> +    qemu_log_mask(LOG_UNIMP, "aspeed-ufs: unhandled UPIU type 0x%02x
> slot %d\n",
> +                  trans_type, slot);
> +
> +done:
> +    /* Mark OCS=SUCCESS in UTRD DW2 and write back */
> +    utrd.dw2 = cpu_to_le32(OCS_SUCCESS);
> +    ufs_dma_write(utrd_addr, &utrd, sizeof(utrd));
> +
> +    s->utrldbr &= ~BIT(slot);
> +    s->regs[R(REG_UTRLDBR)] = s->utrldbr;
> +
> +    s->regs[R(REG_IS)] |= IS_UTRCS;
> +    aspeed_ufs_update_irq(s);
> +}
> +
> +/*
> + * Process all pending UTRL doorbell slots.  Called synchronously from
> +the
> + * write handler to complete requests before the host driver's timeout fires.
> + *
> + * Note: despite the _bh suffix this is not a QEMU BH callback.  It is
> + * called inline so that U-Boot's 30 ms NOP_OUT_TIMEOUT cannot expire
> +before
> + * a deferred callback would run.
> + */
> +static void aspeed_ufs_doorbell_bh(AspeedUFSState *s) {
> +    while (s->utrldbr) {
> +        int slot = ctz32(s->utrldbr);
> +        aspeed_ufs_process_slot(s, slot);
> +    }
> +}
> +
> +static uint64_t aspeed_ufs_read(void *opaque, hwaddr addr, unsigned
> +size) {
> +    AspeedUFSState *s = opaque;
> +
> +    if (addr + size > ASPEED_UFS_MMIO_SIZE) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "aspeed-ufs: read out of range 0x%"
> HWADDR_PRIx "\n",
> +                      addr);
> +        return 0;
> +    }
> +
> +    /*
> +     * HCE state machine: after a HCE=1 write the register is cleared to 0
> +     * (phase 1) so the first read returns 0; the BH then sets it back to 1
> +     * (phase 2) and subsequent reads return 1.
> +     */
> +    if (addr == REG_HCE && s->hce_phase == 1) {
> +        return 0;
> +    }
> +
> +    return s->regs[R(addr)];
> +}
> +
> +static void aspeed_ufs_write(void *opaque, hwaddr addr, uint64_t val,
> +                             unsigned size) {
> +    AspeedUFSState *s = opaque;
> +
> +    if (addr + size > ASPEED_UFS_MMIO_SIZE) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "aspeed-ufs: write out of range 0x%"
> HWADDR_PRIx "\n",
> +                      addr);
> +        return;
> +    }
> +
> +    s->regs[R(addr)] = (uint32_t)val;
> +
> +    switch (addr) {
> +
> +    case REG_HCE:
> +        if (val & HCE_ENABLE) {
> +            /*
> +             * Restart the 1->0->1 toggle on every HCE=1 write, including
> +             * when Linux re-enables the controller after U-Boot.
> +             */
> +            if (s->hce_phase != 2) {
> +                s->regs[R(REG_HCE)] = 0;
> +                s->hce_phase = 1;
> +                qemu_bh_schedule(s->hce_bh);
> +            }
> +        } else {
> +            s->regs[R(REG_HCE)] = 0;
> +            s->regs[R(REG_HCS)] = 0;
> +            s->hce_phase = 0;
> +        }
> +        break;
> +
> +    case REG_UICCMD: {
> +        uint8_t opcode = (uint8_t)(val & 0xff);
> +
> +        s->regs[R(REG_UCMDARG2)] = UIC_CMD_RESULT_OK;
> +        s->regs[R(REG_IS)] |= IS_UCCS;
> +
> +        if (opcode == DME_LINKSTARTUP) {
> +            s->regs[R(REG_IS)]  |= IS_UIC_LINK_STARTUP;
> +            s->regs[R(REG_HCS)]  = ASPEED_UFS_HCS_READY;
> +        }
> +
> +        /*
> +         * Power mode changes (DME_SET / DME_PEER_SET): set
> HCS[10:8] to
> +         * PWR_LOCAL and raise IS_UIC_POWER_MODE so
> ufshcd_uic_pwr_ctrl()
> +         * does not time out waiting for the completion interrupt.
> +         */
> +        if (opcode == DME_SET || opcode == DME_PEER_SET) {
> +            s->regs[R(REG_HCS)] |= HCS_PWR_LOCAL;
> +            s->regs[R(REG_IS)]  |= IS_UIC_POWER_MODE;
> +        }
> +
> +        /*
> +         * DME_GET / DME_PEER_GET: return plausible gear/lane values
> so
> +         * ufshcd_get_max_pwr_mode() does not fail with rx=0, tx=0.
> +         */
> +        if (opcode == DME_GET || opcode == DME_PEER_GET) {
> +            s->regs[R(REG_UCMDARG3)] = 0x00000003;
> +        }
> +
> +        aspeed_ufs_update_irq(s);
> +        break;
> +    }
> +
> +    case REG_IS:
> +        /* W1C: write-1-to-clear */
> +        s->regs[R(REG_IS)] &= ~(uint32_t)val;
> +        aspeed_ufs_update_irq(s);
> +        break;
> +
> +    case REG_IE:
> +        aspeed_ufs_update_irq(s);
> +        break;
> +
> +    case REG_UTRLBA:
> +        s->utrl_base = (s->utrl_base & 0xffffffff00000000ULL) |
> +                       ((uint64_t)(val & 0xfffffc00));
> +        break;
> +
> +    case REG_UTRLBAU:
> +        s->utrl_base = (s->utrl_base & 0x00000000ffffffffULL) |
> +                       ((uint64_t)val << 32);
> +        break;
> +
> +    case REG_UTMRLBA:
> +        s->utmrl_base = (s->utmrl_base & 0xffffffff00000000ULL) |
> +                        ((uint64_t)(val & 0xfffffc00));
> +        break;
> +
> +    case REG_UTMRLBAU:
> +        s->utmrl_base = (s->utmrl_base & 0x00000000ffffffffULL) |
> +                        ((uint64_t)val << 32);
> +        break;
> +
> +    case REG_UTRLRSR:
> +    case REG_UTMRLRSR:
> +        break;
> +
> +    case REG_UTRLDBR:
> +        s->utrldbr |= (uint32_t)val;
> +        s->regs[R(REG_UTRLDBR)] = s->utrldbr;
> +        aspeed_ufs_doorbell_bh(s);
> +        break;
> +
> +    case REG_UTRLCLR:
> +        s->utrldbr &= (uint32_t)val;
> +        s->regs[R(REG_UTRLDBR)] = s->utrldbr;
> +        break;
> +
> +    case REG_UTMRLDBR: {
> +        /*
> +         * Task management doorbell.  Write OCS=SUCCESS into each
> requested
> +         * UTMRD so ufshcd_issue_tm_cmd() reads back success, clear the
> +         * doorbell, then raise IS_UTMRCS so ufshcd_tmc_handler() fires
> +         * complete() on the waiting thread.
> +         *
> +         * UTMRD slot stride is 80 bytes (request_desc_header:16 +
> +         * upiu_req:32 + upiu_rsp:32); OCS is at byte 8 of the header.
> +         */
> +        uint32_t dbr = (uint32_t)val;
> +        while (dbr) {
> +            int slot = ctz32(dbr);
> +            uint64_t ocs_addr = s->utmrl_base +
> +                                (uint64_t)slot * UTMRD_SIZE +
> +                                UTMRD_OCS_OFFSET;
> +            uint8_t ocs = OCS_SUCCESS;
> +            ufs_dma_write(ocs_addr, &ocs, sizeof(ocs));
> +            dbr &= ~(1u << slot);
> +        }
> +        s->regs[R(REG_UTMRLDBR)] = 0;
> +        s->regs[R(REG_IS)] |= IS_UTMRCS;
> +        aspeed_ufs_update_irq(s);
> +        break;
> +    }
> +
> +    default:
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps aspeed_ufs_ops = {
> +    .read       = aspeed_ufs_read,
> +    .write      = aspeed_ufs_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static void aspeed_ufs_reset(DeviceState *dev) {
> +    AspeedUFSState *s = ASPEED_UFS(dev);
> +
> +    memset(s->regs, 0, sizeof(s->regs));
> +
> +    s->regs[R(REG_CAP)]   = ASPEED_UFS_CAP_RESET;
> +    s->regs[R(REG_VER)]   = ASPEED_UFS_VER_RESET;
> +    s->regs[R(REG_HCPID)] = 0x41535045;   /* "ASPE" in ASCII */
> +    s->regs[R(REG_HCMID)] = 0x45440000;   /* "ED\0\0" in ASCII */
> +
> +    s->regs[R(REG_HCE)] = 0;
> +    s->regs[R(REG_HCS)] = 0;
> +
> +    s->hce_phase  = 0;
> +    s->utrldbr    = 0;
> +    s->utrl_base  = 0;
> +    s->utmrl_base = 0;
> +
> +    qemu_set_irq(s->irq, 0);
> +}
> +
> +static void aspeed_ufs_realize(DeviceState *dev, Error **errp) {
> +    AspeedUFSState *s = ASPEED_UFS(dev);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> +    memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_ufs_ops, s,
> +                          TYPE_ASPEED_UFS,
> ASPEED_UFS_MMIO_SIZE);
> +    sysbus_init_mmio(sbd, &s->iomem);
> +    sysbus_init_irq(sbd, &s->irq);
> +
> +    s->hce_bh = qemu_bh_new(aspeed_ufs_hce_bh, s);
> +
> +    if (s->blk) {
> +        int64_t sz = blk_getlength(s->blk);
> +        if (sz <= 0) {
> +            error_setg(errp, "aspeed-ufs: cannot determine drive size");
> +            return;
> +        }
> +        s->num_sectors = (uint64_t)sz / 512;
> +        blk_set_dev_ops(s->blk, NULL, NULL);
> +        if (blk_set_perm(s->blk,
> +                         BLK_PERM_CONSISTENT_READ |
> BLK_PERM_WRITE,
> +                         BLK_PERM_ALL, errp) < 0) {
> +            return;
> +        }
> +    } else {
> +        /* No drive attached; expose a minimal read-only stub. */
> +        s->num_sectors = 2048;
> +    }
> +
> +    aspeed_ufs_init_unit_desc(s);
> +    aspeed_ufs_reset(dev);
> +}
> +
> +static const VMStateDescription vmstate_aspeed_ufs = {
> +    .name               = TYPE_ASPEED_UFS,
> +    .version_id         = 1,
> +    .minimum_version_id = 1,
> +    .fields             = (const VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(regs, AspeedUFSState,
> ASPEED_UFS_NUM_REGS),
> +        VMSTATE_INT32(hce_phase, AspeedUFSState),
> +        VMSTATE_UINT32(utrldbr, AspeedUFSState),
> +        VMSTATE_UINT64(utrl_base, AspeedUFSState),
> +        VMSTATE_UINT64(utmrl_base, AspeedUFSState),
> +        VMSTATE_UINT64(num_sectors, AspeedUFSState),
> +        VMSTATE_UINT8_ARRAY(unit_desc, AspeedUFSState, 0x23),
> +        VMSTATE_END_OF_LIST()
> +    },
> +};
> +
> +static const Property aspeed_ufs_properties[] = {
> +    DEFINE_PROP_DRIVE("drive", AspeedUFSState, blk), };
> +
> +static void aspeed_ufs_class_init(ObjectClass *oc, const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(oc);
> +
> +    dc->realize  = aspeed_ufs_realize;
> +    device_class_set_legacy_reset(dc, aspeed_ufs_reset);

Convert to use Resettable interface
https://lore.kernel.org/qemu-devel/[email protected]/

> +    dc->vmsd     = &vmstate_aspeed_ufs;
> +    dc->desc     = "ASPEED AST2700 UFS Host Controller";
> +    device_class_set_props(dc, aspeed_ufs_properties);
> +    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); }
> +
> +static const TypeInfo aspeed_ufs_info = {
> +    .name          = TYPE_ASPEED_UFS,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(AspeedUFSState),
> +    .class_init    = aspeed_ufs_class_init,
> +};
> +
> +static void aspeed_ufs_register_types(void) {
> +    type_register_static(&aspeed_ufs_info);
> +}
> +
> +type_init(aspeed_ufs_register_types)


Convert to DEFINE_TYPES() with inlined TypeInfo
https://lore.kernel.org/qemu-devel/[email protected]/
 

> diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build index
> 6e68328b93..86052e6fd2 100644
> --- a/hw/ufs/meson.build
> +++ b/hw/ufs/meson.build
> @@ -1 +1,2 @@
>  system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c'))
> +system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true:
> +files('aspeed_ufs.c'))
> --
> 2.53.0

Thanks,
Jamin


Reply via email to