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, [ASPEED_DEV_PCIE0] = 0x120E0000, [ASPEED_DEV_PCIE1] = 0x120F0000, [ASPEED_DEV_INTC] = 0x12100000, @@ -116,6 +117,7 @@ static const int aspeed_soc_ast2700a1_irqmap[] = { [ASPEED_DEV_SCU] = 12, [ASPEED_DEV_RTC] = 13, [ASPEED_DEV_EMMC] = 15, + [ASPEED_DEV_UFS] = 118, [ASPEED_DEV_TIMER1] = 16, [ASPEED_DEV_TIMER2] = 17, [ASPEED_DEV_TIMER3] = 18, @@ -533,6 +535,8 @@ static void aspeed_soc_ast2700_init(Object *obj) object_initialize_child(obj, "emmc-controller.sdhci", &s->emmc.slots[0], TYPE_SYSBUS_SDHCI); + object_initialize_child(obj, "ufs", &s->ufs, TYPE_ASPEED_UFS); + snprintf(typename, sizeof(typename), "aspeed.timer-%s", socname); object_initialize_child(obj, "timerctrl", &s->timerctrl, typename); @@ -1039,6 +1043,15 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->emmc), 0, aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_EMMC)); + /* UFS */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ufs), errp)) { + return; + } + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->ufs), 0, + sc->memmap[ASPEED_DEV_UFS]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ufs), 0, + aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_UFS)); + /* Timer */ object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&s->scu), &error_abort); diff --git a/hw/ufs/aspeed_ufs.c b/hw/ufs/aspeed_ufs.c new file mode 100644 index 0000000000..673aced63b --- /dev/null +++ b/hw/ufs/aspeed_ufs.c @@ -0,0 +1,1095 @@ +/* + * ASPEED AST2700 UFS Host Controller + * + * Sysbus device mapped at 0x12c08200 (256 bytes). Implements the UFSHCI + * register interface required by U-Boot ufshcd_probe() and Linux + * aspeed-ufshcd, including UIC command handling, NOP OUT/IN, SCSI block I/O, + * Query UPIU descriptor reads, W-LUN support, and UTMRL task management. + * + * Register offsets follow JEDEC UFSHCI spec v2.0, matching the REG_* + * definitions in include/block/ufs.h. + * + * The ASPEED clock/reset wrapper at 0x12c08000 (aspeed,ast2700-ufscnr) is + * left as an UnimplementedDevice; writes to MPHY registers there require no + * readback. + * + * Copyright 2026 IBM Corp. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "system/block-backend.h" +#include "system/block-backend-io.h" +#include "system/dma.h" +#include "system/address-spaces.h" +#include "hw/ufs/aspeed_ufs.h" + +/* UFSHCI register offsets (JEDEC UFSHCI spec v2.0) */ +#define REG_CAP 0x00 +#define REG_VER 0x08 +#define REG_HCPID 0x10 +#define REG_HCMID 0x14 +#define REG_AHIT 0x18 +#define REG_IS 0x20 +#define REG_IE 0x24 +#define REG_HCS 0x30 +#define REG_HCE 0x34 +#define REG_UECPA 0x38 +#define REG_UECDL 0x3c +#define REG_UECN 0x40 +#define REG_UECT 0x44 +#define REG_UECDME 0x48 +#define REG_UTRLBA 0x50 +#define REG_UTRLBAU 0x54 +#define REG_UTRLDBR 0x58 +#define REG_UTRLCLR 0x5c +#define REG_UTRLRSR 0x60 +#define REG_UTMRLBA 0x70 +#define REG_UTMRLBAU 0x74 +#define REG_UTMRLDBR 0x78 +#define REG_UTMRLCLR 0x7c +#define REG_UTMRLRSR 0x80 +#define REG_UICCMD 0x90 +#define REG_UCMDARG1 0x94 +#define REG_UCMDARG2 0x98 +#define REG_UCMDARG3 0x9c + +/* HCE / HCS bits */ +#define HCE_ENABLE BIT(0) +#define HCS_DP BIT(0) +#define HCS_UTRLRDY BIT(1) +#define HCS_UTMRLRDY BIT(2) +#define HCS_UCRDY BIT(3) + +/* IS bits (UFSHCI spec sec. 5.3) */ +#define IS_UTRCS BIT(0) /* UTP Transfer Request Completion */ +#define IS_UIC_POWER_MODE BIT(4) /* UIC Power Mode Status */ +#define IS_UIC_LINK_STARTUP BIT(8) /* UIC Link Startup Status */ +#define IS_UTMRCS BIT(9) /* UTP Task Mgmt Request Completion */ +#define IS_UCCS BIT(10) /* UIC Command Completion Status */ + +/* UIC command opcodes */ +#define DME_LINKSTARTUP 0x16 +#define DME_GET 0x01 +#define DME_SET 0x02 +#define DME_PEER_GET 0x03 +#define DME_PEER_SET 0x04 + +/* UIC command result (success) in UCMDARG2[7:0] */ +#define UIC_CMD_RESULT_OK 0x00 + +/* HCS power-mode state field bits [10:8]: PWR_LOCAL = 1 */ +#define HCS_PWR_LOCAL (1 << 8) + +/* Register array index helper */ +#define R(off) ((off) / sizeof(uint32_t)) + +/* + * UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1). + */ +#define UTRD_SIZE 32 /* bytes per UTRL slot */ + +/* + * UTMRL slot size (UTP Task Management Request List): + * - header : 16 bytes + * - upiu_req : 32 bytes + * - upiu_rsp : 32 bytes + */ +#define UTMRD_SIZE 80 + +/* byte offset of OCS within request_desc_header */ +#define UTMRD_OCS_OFFSET 8 + +/* Command Type in UTRD DW0[29:28] */ +#define CT_UFS_STORAGE 0x1 + +/* Data Direction in UTRD DW0[25:24] */ +#define DD_NO_DATA 0x0 +#define DD_HOST_TO_DEV 0x2 +#define DD_DEV_TO_HOST 0x4 + +/* OCS in UTRD DW2[7:0] */ +#define OCS_SUCCESS 0x0 + +/* UPIU transaction types */ +#define UPIU_TYPE_NOP_OUT 0x00 +#define UPIU_TYPE_CMD 0x01 +#define UPIU_TYPE_QUERY_REQ 0x16 +#define UPIU_TYPE_NOP_IN 0x20 +#define UPIU_TYPE_RESPONSE 0x21 +#define UPIU_TYPE_QUERY_RSP 0x36 + +/* Query opcodes */ +#define QUERY_OP_READ_DESC 0x01 +#define QUERY_OP_WRITE_DESC 0x02 +#define QUERY_OP_READ_ATTR 0x03 +#define QUERY_OP_WRITE_ATTR 0x04 +#define QUERY_OP_READ_FLAG 0x05 +#define QUERY_OP_SET_FLAG 0x06 +#define QUERY_OP_CLEAR_FLAG 0x07 + +/* Descriptor idn */ +#define DESC_IDN_DEVICE 0x00 +#define DESC_IDN_CONFIGURATION 0x01 +#define DESC_IDN_UNIT 0x02 +#define DESC_IDN_GEOMETRY 0x07 +#define DESC_IDN_POWER 0x08 +#define DESC_IDN_STRING 0x05 + +/* SCSI commands */ +#define SCSI_TEST_UNIT_READY 0x00 +#define SCSI_INQUIRY 0x12 +#define SCSI_READ_CAPACITY_10 0x25 +#define SCSI_READ_10 0x28 +#define SCSI_WRITE_10 0x2a +#define SCSI_READ_16 0x88 +#define SCSI_WRITE_16 0x8a +#define SCSI_READ_CAPACITY_16 0x9e /* service action 0x10 */ +#define SCSI_REPORT_LUNS 0xa0 + +/* + * Well-Known LUN addresses probed by ufshcd_scsi_add_wlus(). + * All four must appear as present devices or async_scan fails. + */ +#define WLUN_REPORT_LUNS 0x81 +#define WLUN_UFS_DEVICE 0xd0 +#define WLUN_BOOT 0xb0 +#define WLUN_RPMB 0xc4 + +static bool aspeed_ufs_is_wlun(uint8_t lun) +{ + return lun == WLUN_UFS_DEVICE || lun == WLUN_RPMB || + lun == WLUN_BOOT || lun == WLUN_REPORT_LUNS; +} + +static void ufs_dma_read(hwaddr paddr, void *buf, size_t len) +{ + dma_memory_read(&address_space_memory, paddr, buf, len, + MEMTXATTRS_UNSPECIFIED); +} + +static void ufs_dma_write(hwaddr paddr, const void *buf, size_t len) +{ + dma_memory_write(&address_space_memory, paddr, buf, len, + MEMTXATTRS_UNSPECIFIED); +} + +static void aspeed_ufs_update_irq(AspeedUFSState *s) +{ + uint32_t is = s->regs[R(REG_IS)]; + uint32_t ie = s->regs[R(REG_IE)]; + + qemu_set_irq(s->irq, !!(is & ie)); +} + +/* + * HCE bottom-half: completes the 1->0->1 toggle that the host driver polls. + * + * On any HCE=1 write the write handler clears HCE to 0 and schedules this + * BH. Once it fires the controller is considered ready. + */ +static void aspeed_ufs_hce_bh(void *opaque) +{ + AspeedUFSState *s = opaque; + + s->regs[R(REG_HCE)] = HCE_ENABLE; + s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY; + s->hce_phase = 2; +} + +/* 12-byte UPIU header (big-endian per UFSHCI spec) */ +typedef struct { + uint8_t trans_type; + uint8_t flags; + uint8_t lun; + uint8_t task_tag; + uint8_t iid_cmd_set; + uint8_t query_func; + uint8_t response; + uint8_t status; + uint8_t ehs_len; + uint8_t device_info; + uint16_t data_seg_len_be; +} UpiuHeader; + +/* 32-byte UPIU (header + TSF + reserved dword) */ +typedef struct { + UpiuHeader hdr; + uint8_t tsf[16]; + uint32_t reserved; +} Upiu32; + +/* UTP Transfer Request Descriptor (UFSHCI spec sec. 7.2.1, 32 bytes) */ +typedef struct { + uint32_t dw0; + uint32_t dw1; + uint32_t dw2; /* OCS[7:0] */ + uint32_t dw3; + uint32_t dw4; /* UCDBA: Command UPIU base, lower 32 bits */ + uint32_t dw5; /* UCDBAU: upper 32 bits */ + uint32_t dw6; /* RUL[31:16] | RUO[15:0] in DWORDs */ + uint32_t dw7; /* PRDTO[31:16] | PRDTL[15:0] */ +} Utrd; + +/* Physical Region Descriptor Table entry */ +typedef struct { + uint32_t dba; /* Data Buffer Address (lower) */ + uint32_t dbau; /* Data Buffer Address (upper) */ + uint32_t reserved; + uint32_t size; /* byte count - 1, bits [17:0] */ +} PrdtEntry; + +/* + * Minimal UFS Device Descriptor (64 bytes). + * Values follow UFS 2.1 spec defaults; only fields checked by Linux and + * U-Boot ufshcd are set to non-zero. + */ +static const uint8_t ufs_device_desc[] = { + 0x40, /* bLength = 64 */ + DESC_IDN_DEVICE, /* bDescriptorIDN */ + 0x00, /* bDevice */ + 0x00, /* bDeviceClass */ + 0x00, /* bDeviceSubClass */ + 0x01, /* bProtocol */ + 0x01, /* bNumberLU */ + 0x00, /* bNumberWLU */ + 0x00, /* bBootEnable */ + 0x00, /* bDescrAccessEn */ + 0x00, /* bInitPowerMode */ + 0x01, /* bHighPriorityLUN */ + 0x00, /* bSecureRemovalType */ + 0x00, /* bSecurityLU */ + 0x00, /* bBackgroundOpsTermLat */ + 0x01, /* bInitActiveICCLevel */ + 0x02, 0x10, /* wSpecVersion = 0x0210 (UFS 2.1) */ + 0x00, 0x00, /* wManufactureDate */ + 0x00, /* iManufacturerName */ + 0x02, /* iProductName (string index 2) */ + 0x00, /* iSerialNumber */ + 0x00, /* iOemID */ + 0x00, 0x00, /* wManufacturerID */ + 0x00, /* bUD0BaseOffset */ + 0x00, /* bUDConfigPlength */ + 0x00, /* bDeviceRTTCap */ + 0x00, 0x00, /* wPeriodicRTCUpdate */ + 0x00, /* bUFSFeaturesSupport */ + 0x00, /* bFFUTimeout */ + 0x00, /* bQueueDepth */ + 0x00, 0x00, /* wDeviceVersion */ + 0x00, /* bNumSecureWPArea */ + 0x00, 0x00, 0x00, 0x00, /* dPSAMaxDataSize */ + 0x00, /* bPSAStateTimeout */ + 0x00, /* iProductRevisionLevel */ + /* pad to 0x40 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +/* Minimal Geometry Descriptor (0x48 bytes) */ +static const uint8_t ufs_geometry_desc[] = { + 0x48, /* bLength */ + DESC_IDN_GEOMETRY, /* bDescriptorIDN */ + 0x00, /* bMediaTechnology */ + 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, /* qTotalRawDeviceCapacity (8 bytes, big-endian) */ + 0x00, /* bMaxNumberLU */ + 0x00, 0x10, 0x00, 0x00, /* dSegmentSize = 4096 sectors */ + 0x08, /* bAllocationUnitSize */ + 0x01, /* bMinAddrBlockSize */ + 0x00, /* bOptimalReadBlockSize */ + 0x00, /* bOptimalWriteBlockSize */ + 0x00, /* bMaxInBufferSize */ + 0x00, /* bMaxOutBufferSize */ + 0x00, /* bRPMB_ReadWriteSize */ + 0x00, /* bDynamicCapacityResourcePolicy */ + 0x00, /* bDataOrdering */ + 0x00, /* bMaxContexIDNumber */ + 0x00, /* bSysDataTagUnitSize */ + 0x00, /* bSysDataTagResSize */ + 0x01, /* bSupportedSecRTypes */ + 0x00, 0x03, /* wSupportedMemoryTypes */ + 0x00, 0x00, 0x00, 0x00, /* dSystemCodeMaxNAllocU */ + 0x00, 0x00, /* wSystemCodeCapAdjFac */ + 0x00, 0x00, 0x00, 0x00, /* dNonPersistMaxNAllocU */ + 0x00, 0x00, /* wNonPersistCapAdjFac */ + 0x00, 0x00, 0x00, 0x00, /* dEnhanced1MaxNAllocU */ + 0x00, 0x00, /* wEnhanced1CapAdjFac */ + 0x00, 0x00, 0x00, 0x00, /* dEnhanced2MaxNAllocU */ + 0x00, 0x00, /* wEnhanced2CapAdjFac */ + 0x00, 0x00, 0x00, 0x00, /* dEnhanced3MaxNAllocU */ + 0x00, 0x00, /* wEnhanced3CapAdjFac */ + 0x00, 0x00, 0x00, 0x00, /* dEnhanced4MaxNAllocU */ + 0x00, 0x00, /* wEnhanced4CapAdjFac */ + /* pad to 0x48 */ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +/* + * Power Descriptor (idn 8, bLength 0x62). + * Linux ufshcd_probe() reads this; a zero-filled body of the right length + * satisfies the length check. + */ +static const uint8_t ufs_power_desc[0x62] = { + 0x62, /* bLength */ + DESC_IDN_POWER, /* bDescriptorIDN */ + /* remaining bytes zero-initialized */ +}; + +/* + * String Descriptor (idn 5). + * iProductName points to string index 2 in the device descriptor. A + * minimal well-formed header (type + zero length) keeps ufshcd happy. + */ +static const uint8_t ufs_string_desc[] = { + 0x02, /* bLength = 2 (header only, no string data) */ + DESC_IDN_STRING, /* bDescriptorIDN */ +}; + +/* Unit Descriptor (0x23 bytes) - capacity filled at realize time */ + +static void aspeed_ufs_init_unit_desc(AspeedUFSState *s) +{ + uint64_t lbc; + + memset(s->unit_desc, 0, sizeof(s->unit_desc)); + s->unit_desc[0x00] = 0x23; /* bLength */ + s->unit_desc[0x01] = DESC_IDN_UNIT; /* bDescriptorIDN */ + s->unit_desc[0x02] = 0x00; /* bUnitIndex */ + s->unit_desc[0x03] = 0x01; /* bLUEnable */ + s->unit_desc[0x08] = 0x02; /* bMemoryType */ + s->unit_desc[0x0a] = 0x0c; /* bLogicalBlockSize: 1 << 12 = 4096 */ + + lbc = cpu_to_be64(s->num_sectors); + memcpy(&s->unit_desc[0x0b], &lbc, 8); /* qLogicalBlockCount */ +} + +/* Build a 36-byte SCSI INQUIRY response */ +static void scsi_build_inquiry(uint8_t *buf, uint8_t lun) +{ + memset(buf, 0, 36); + /* + * W-LUNs use device_type 0x1e (well known LU); LUN 0 is a direct-access + * block device (0x00). + */ + buf[0] = aspeed_ufs_is_wlun(lun) ? 0x1e : 0x00; + buf[1] = 0x00; /* not removable */ + buf[2] = 0x05; /* SPC-3 */ + buf[3] = 0x12; /* response data format = 2, HiSup */ + buf[4] = 0x1f; /* additional length = 31 */ + memcpy(&buf[8], "ASPEED ", 8); + memcpy(&buf[16], "UFS QEMU ", 16); + memcpy(&buf[32], "1.00", 4); +} + +/* Build an 8-byte READ_CAPACITY(10) response */ +static void scsi_build_read_cap10(uint8_t *buf, uint64_t sectors) +{ + uint32_t last_lba = (sectors > 0xffffffff) ? 0xffffffff + : (uint32_t)(sectors - 1); + uint32_t block_size = cpu_to_be32(512); + + last_lba = cpu_to_be32(last_lba); + memcpy(buf, &last_lba, 4); + memcpy(buf + 4, &block_size, 4); +} + +/* Build a 32-byte READ_CAPACITY(16) response */ +static void scsi_build_read_cap16(uint8_t *buf, uint64_t sectors) +{ + uint64_t last_lba = cpu_to_be64(sectors - 1); + uint32_t block_size = cpu_to_be32(512); + memcpy(buf, &last_lba, 8); + memcpy(buf + 8, &block_size, 4); +} + +/* Process one UTRL slot */ +static void aspeed_ufs_process_slot(AspeedUFSState *s, int slot) +{ + uint64_t utrd_addr = s->utrl_base + (uint64_t)slot * UTRD_SIZE; + Utrd utrd; + uint64_t upiu_base, resp_base, prdt_base; + uint32_t dw6, dw7, ruo, prdtl, prdto; + UpiuHeader req_hdr; + uint8_t trans_type, task_tag; + + ufs_dma_read(utrd_addr, &utrd, sizeof(utrd)); + + upiu_base = (uint64_t)le32_to_cpu(utrd.dw4) | + ((uint64_t)le32_to_cpu(utrd.dw5) << 32); + + /* + * DW6 holds the Response UPIU offset (RUO) and length (RUL), both in + * DWORDs. The response UPIU follows the request UPIU in the same + * guest buffer. + */ + dw6 = le32_to_cpu(utrd.dw6); + ruo = dw6 & 0xffff; + resp_base = upiu_base + (uint64_t)ruo * 4; + + /* DW7: PRDTL (entry count) and PRDTO (offset in DWORDs) */ + dw7 = le32_to_cpu(utrd.dw7); + prdtl = dw7 & 0xffff; + prdto = (dw7 >> 16) & 0xffff; + prdt_base = upiu_base + (uint64_t)prdto * 4; + + ufs_dma_read(upiu_base, &req_hdr, sizeof(req_hdr)); + trans_type = req_hdr.trans_type & 0x3f; + task_tag = req_hdr.task_tag; + + /* NOP OUT -> NOP IN */ + if (trans_type == UPIU_TYPE_NOP_OUT) { + uint8_t rsp[32]; + memset(rsp, 0, sizeof(rsp)); + rsp[0] = UPIU_TYPE_NOP_IN; + rsp[3] = task_tag; + ufs_dma_write(resp_base, rsp, 32); + goto done; + } + + /* Query Request -> Query Response */ + if (trans_type == UPIU_TYPE_QUERY_REQ) { + uint8_t req_buf[32]; + uint8_t rsp[32]; + uint8_t opcode, idn; + + ufs_dma_read(upiu_base, req_buf, 32); + opcode = req_buf[12]; + idn = req_buf[13]; + + memset(rsp, 0, sizeof(rsp)); + rsp[0] = UPIU_TYPE_QUERY_RSP; + rsp[3] = task_tag; + memcpy(&rsp[12], &req_buf[12], 16); /* echo TSF */ + + if (opcode == QUERY_OP_READ_DESC) { + const uint8_t *desc = NULL; + uint16_t desc_len = 0; + uint16_t req_len; + + switch (idn) { + case DESC_IDN_DEVICE: + desc = ufs_device_desc; + desc_len = sizeof(ufs_device_desc); + break; + case DESC_IDN_GEOMETRY: + desc = ufs_geometry_desc; + desc_len = sizeof(ufs_geometry_desc); + break; + case DESC_IDN_UNIT: + desc = s->unit_desc; + desc_len = sizeof(s->unit_desc); + break; + case DESC_IDN_POWER: + desc = ufs_power_desc; + desc_len = sizeof(ufs_power_desc); + break; + case DESC_IDN_STRING: + desc = ufs_string_desc; + desc_len = sizeof(ufs_string_desc); + break; + default: + break; + } + + /* + * Cap the returned length to what the host requested (TSF + * bytes 6-7 hold the allocation length). Returning more than + * asked causes the driver to reject the response with -EINVAL. + */ + req_len = ((uint16_t)req_buf[18] << 8) | req_buf[19]; + if (desc_len > req_len) { + desc_len = req_len; + } + + rsp[10] = (desc_len >> 8) & 0xff; + rsp[11] = desc_len & 0xff; + rsp[18] = rsp[10]; + rsp[19] = rsp[11]; + + ufs_dma_write(resp_base, rsp, 32); + if (desc && desc_len) { + ufs_dma_write(resp_base + 32, desc, desc_len); + } + } else { + /* READ_ATTR, READ_FLAG, SET_FLAG, etc. - echo TSF, success */ + ufs_dma_write(resp_base, rsp, 32); + } + goto done; + } + + /* SCSI Command UPIU */ + if (trans_type == UPIU_TYPE_CMD) { + uint8_t req_buf[32]; + uint8_t *cdb; + uint8_t scsi_cmd; + bool ok = true; + uint8_t sense[18]; + uint8_t *xfer_buf = NULL; + uint32_t xfer_len = 0; + + ufs_dma_read(upiu_base, req_buf, 32); + cdb = &req_buf[16]; + scsi_cmd = cdb[0]; + + memset(sense, 0, sizeof(sense)); + + /* Non-zero LUNs that are not W-LUNs have no device. */ + if (req_hdr.lun != 0 && !aspeed_ufs_is_wlun(req_hdr.lun)) { + ok = false; + sense[0] = 0x70; + sense[2] = 0x05; /* ILLEGAL REQUEST */ + sense[7] = 0x0a; + goto scsi_done; + } + + if (prdtl > 0) { + uint32_t i; + + for (i = 0; i < prdtl; i++) { + PrdtEntry pe; + ufs_dma_read(prdt_base + i * sizeof(PrdtEntry), + &pe, sizeof(pe)); + xfer_len += (le32_to_cpu(pe.size) & 0x3ffff) + 1; + } + xfer_buf = g_malloc0(xfer_len); + } + + switch (scsi_cmd) { + + case SCSI_TEST_UNIT_READY: + break; + + case SCSI_INQUIRY: { + uint8_t inq[36]; + uint32_t alloc, send; + + /* + * EVPD queries (cdb[1] & 0x01) return an empty VPD page list + * for W-LUNs. This stops the driver from iterating all 256 + * possible VPD pages after it finds zero supported ones. + */ + if ((cdb[1] & 0x01) && aspeed_ufs_is_wlun(req_hdr.lun)) { + uint8_t evpd[4]; + memset(evpd, 0, sizeof(evpd)); + evpd[0] = 0x1e; /* device_type: well known LU */ + /* page_length = 0: no VPD pages supported */ + alloc = ((uint16_t)cdb[3] << 8) | cdb[4]; + send = MIN(alloc, sizeof(evpd)); + if (prdtl > 0 && xfer_buf) { + memcpy(xfer_buf, evpd, send); + } else { + ufs_dma_write(resp_base + 32, evpd, send); + } + xfer_len = send; + break; + } + + scsi_build_inquiry(inq, req_hdr.lun); + alloc = ((uint16_t)cdb[3] << 8) | cdb[4]; + send = MIN(alloc, 36); + if (prdtl > 0 && xfer_buf) { + memcpy(xfer_buf, inq, send); + } else { + ufs_dma_write(resp_base + 32, inq, send); + } + xfer_len = send; + break; + } + + case SCSI_READ_CAPACITY_10: { + uint8_t cap[8]; + scsi_build_read_cap10(cap, s->num_sectors); + if (prdtl > 0 && xfer_buf) { + memcpy(xfer_buf, cap, 8); + } else { + ufs_dma_write(resp_base + 32, cap, 8); + } + xfer_len = 8; + break; + } + + case SCSI_READ_CAPACITY_16: + /* service action must be 0x10 */ + if ((cdb[1] & 0x1f) == 0x10) { + uint8_t cap[32]; + memset(cap, 0, sizeof(cap)); + scsi_build_read_cap16(cap, s->num_sectors); + if (prdtl > 0 && xfer_buf) { + memcpy(xfer_buf, cap, 32); + } else { + ufs_dma_write(resp_base + 32, cap, 32); + } + xfer_len = 32; + } + break; + + case SCSI_READ_10: + case SCSI_READ_16: { + uint64_t lba; + uint32_t num_blocks, byte_count; + + if (scsi_cmd == SCSI_READ_10) { + lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 | + (uint64_t)cdb[4] << 8 | cdb[5]; + num_blocks = (uint16_t)cdb[7] << 8 | cdb[8]; + } else { + lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 | + (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 | + (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 | + (uint64_t)cdb[8] << 8 | cdb[9]; + num_blocks = (uint32_t)cdb[10] << 24 | + (uint32_t)cdb[11] << 16 | + (uint32_t)cdb[12] << 8 | cdb[13]; + } + byte_count = num_blocks * 512; + if (s->blk && byte_count > 0) { + uint8_t *rbuf = g_malloc(byte_count); + if (blk_pread(s->blk, lba * 512, byte_count, rbuf, 0) < 0) { + ok = false; + } else if (prdtl > 0 && xfer_buf) { + memcpy(xfer_buf, rbuf, + MIN(byte_count, xfer_len)); + } else { + ufs_dma_write(resp_base + 32, rbuf, byte_count); + } + g_free(rbuf); + } + xfer_len = byte_count; + break; + } + + case SCSI_WRITE_10: + case SCSI_WRITE_16: { + uint64_t lba; + uint32_t num_blocks, byte_count; + + if (scsi_cmd == SCSI_WRITE_10) { + lba = (uint64_t)cdb[2] << 24 | (uint64_t)cdb[3] << 16 | + (uint64_t)cdb[4] << 8 | cdb[5]; + num_blocks = (uint16_t)cdb[7] << 8 | cdb[8]; + } else { + lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 | + (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 | + (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 | + (uint64_t)cdb[8] << 8 | cdb[9]; + num_blocks = (uint32_t)cdb[10] << 24 | + (uint32_t)cdb[11] << 16 | + (uint32_t)cdb[12] << 8 | cdb[13]; + } + byte_count = num_blocks * 512; + if (s->blk && byte_count > 0 && prdtl > 0 && xfer_buf) { + uint32_t copied = 0, i; + for (i = 0; i < prdtl && copied < byte_count; i++) { + PrdtEntry pe; + uint64_t dba; + uint32_t chunk; + ufs_dma_read(prdt_base + i * sizeof(PrdtEntry), + &pe, sizeof(pe)); + dba = (uint64_t)le32_to_cpu(pe.dba) | + ((uint64_t)le32_to_cpu(pe.dbau) << 32); + chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1; + chunk = MIN(chunk, byte_count - copied); + ufs_dma_read(dba, xfer_buf + copied, chunk); + copied += chunk; + } + if (blk_pwrite(s->blk, lba * 512, byte_count, + xfer_buf, 0) < 0) { + ok = false; + } + } + xfer_len = 0; + break; + } + + case SCSI_REPORT_LUNS: { + uint8_t rl[16]; + memset(rl, 0, sizeof(rl)); + rl[3] = 8; /* list length = 8 (one LUN entry) */ + if (prdtl > 0 && xfer_buf) { + memcpy(xfer_buf, rl, 16); + } else { + ufs_dma_write(resp_base + 32, rl, 16); + } + xfer_len = 16; + break; + } + + default: + ok = false; + sense[0] = 0x70; + sense[2] = 0x05; /* ILLEGAL REQUEST */ + sense[7] = 0x0a; + xfer_len = 0; + break; + } + + /* Scatter read data back into guest PRDT buffers */ + if (ok && prdtl > 0 && xfer_buf && xfer_len > 0) { + uint32_t copied = 0, i; + for (i = 0; i < prdtl && copied < xfer_len; i++) { + PrdtEntry pe; + uint64_t dba; + uint32_t chunk; + ufs_dma_read(prdt_base + i * sizeof(PrdtEntry), + &pe, sizeof(pe)); + dba = (uint64_t)le32_to_cpu(pe.dba) | + ((uint64_t)le32_to_cpu(pe.dbau) << 32); + chunk = (le32_to_cpu(pe.size) & 0x3ffff) + 1; + chunk = MIN(chunk, xfer_len - copied); + ufs_dma_write(dba, xfer_buf + copied, chunk); + copied += chunk; + } + } + + g_free(xfer_buf); + +scsi_done: + /* Build SCSI Response UPIU */ + { + uint8_t rsp[32]; + uint16_t seg_len = 0; + + memset(rsp, 0, sizeof(rsp)); + rsp[0] = UPIU_TYPE_RESPONSE; + rsp[3] = task_tag; + rsp[7] = ok ? 0x00 : 0x02; /* SCSI status */ + + /* + * If prdtl=0 (no scatter-gather list), the data was written + * directly to the response UPIU data segment; report its length + * in data_seg_len so the host driver knows where to find it. + */ + if (ok && prdtl == 0 && xfer_len > 0) { + seg_len = (uint16_t)xfer_len; + } else if (!ok) { + seg_len = sizeof(sense); + } + rsp[10] = (seg_len >> 8) & 0xff; + rsp[11] = seg_len & 0xff; + + ufs_dma_write(resp_base, rsp, 32); + if (!ok) { + ufs_dma_write(resp_base + 32, sense, sizeof(sense)); + } + } + goto done; + } + + qemu_log_mask(LOG_UNIMP, "aspeed-ufs: unhandled UPIU type 0x%02x slot %d\n", + trans_type, slot); + +done: + /* Mark OCS=SUCCESS in UTRD DW2 and write back */ + utrd.dw2 = cpu_to_le32(OCS_SUCCESS); + ufs_dma_write(utrd_addr, &utrd, sizeof(utrd)); + + s->utrldbr &= ~BIT(slot); + s->regs[R(REG_UTRLDBR)] = s->utrldbr; + + s->regs[R(REG_IS)] |= IS_UTRCS; + aspeed_ufs_update_irq(s); +} + +/* + * Process all pending UTRL doorbell slots. Called synchronously from the + * write handler to complete requests before the host driver's timeout fires. + * + * Note: despite the _bh suffix this is not a QEMU BH callback. It is + * called inline so that U-Boot's 30 ms NOP_OUT_TIMEOUT cannot expire before + * a deferred callback would run. + */ +static void aspeed_ufs_doorbell_bh(AspeedUFSState *s) +{ + while (s->utrldbr) { + int slot = ctz32(s->utrldbr); + aspeed_ufs_process_slot(s, slot); + } +} + +static uint64_t aspeed_ufs_read(void *opaque, hwaddr addr, unsigned size) +{ + AspeedUFSState *s = opaque; + + if (addr + size > ASPEED_UFS_MMIO_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "aspeed-ufs: read out of range 0x%" HWADDR_PRIx "\n", + addr); + return 0; + } + + /* + * HCE state machine: after a HCE=1 write the register is cleared to 0 + * (phase 1) so the first read returns 0; the BH then sets it back to 1 + * (phase 2) and subsequent reads return 1. + */ + if (addr == REG_HCE && s->hce_phase == 1) { + return 0; + } + + return s->regs[R(addr)]; +} + +static void aspeed_ufs_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + AspeedUFSState *s = opaque; + + if (addr + size > ASPEED_UFS_MMIO_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "aspeed-ufs: write out of range 0x%" HWADDR_PRIx "\n", + addr); + return; + } + + s->regs[R(addr)] = (uint32_t)val; + + switch (addr) { + + case REG_HCE: + if (val & HCE_ENABLE) { + /* + * Restart the 1->0->1 toggle on every HCE=1 write, including + * when Linux re-enables the controller after U-Boot. + */ + if (s->hce_phase != 2) { + s->regs[R(REG_HCE)] = 0; + s->hce_phase = 1; + qemu_bh_schedule(s->hce_bh); + } + } else { + s->regs[R(REG_HCE)] = 0; + s->regs[R(REG_HCS)] = 0; + s->hce_phase = 0; + } + break; + + case REG_UICCMD: { + uint8_t opcode = (uint8_t)(val & 0xff); + + s->regs[R(REG_UCMDARG2)] = UIC_CMD_RESULT_OK; + s->regs[R(REG_IS)] |= IS_UCCS; + + if (opcode == DME_LINKSTARTUP) { + s->regs[R(REG_IS)] |= IS_UIC_LINK_STARTUP; + s->regs[R(REG_HCS)] = ASPEED_UFS_HCS_READY; + } + + /* + * Power mode changes (DME_SET / DME_PEER_SET): set HCS[10:8] to + * PWR_LOCAL and raise IS_UIC_POWER_MODE so ufshcd_uic_pwr_ctrl() + * does not time out waiting for the completion interrupt. + */ + if (opcode == DME_SET || opcode == DME_PEER_SET) { + s->regs[R(REG_HCS)] |= HCS_PWR_LOCAL; + s->regs[R(REG_IS)] |= IS_UIC_POWER_MODE; + } + + /* + * DME_GET / DME_PEER_GET: return plausible gear/lane values so + * ufshcd_get_max_pwr_mode() does not fail with rx=0, tx=0. + */ + if (opcode == DME_GET || opcode == DME_PEER_GET) { + s->regs[R(REG_UCMDARG3)] = 0x00000003; + } + + aspeed_ufs_update_irq(s); + break; + } + + case REG_IS: + /* W1C: write-1-to-clear */ + s->regs[R(REG_IS)] &= ~(uint32_t)val; + aspeed_ufs_update_irq(s); + break; + + case REG_IE: + aspeed_ufs_update_irq(s); + break; + + case REG_UTRLBA: + s->utrl_base = (s->utrl_base & 0xffffffff00000000ULL) | + ((uint64_t)(val & 0xfffffc00)); + break; + + case REG_UTRLBAU: + s->utrl_base = (s->utrl_base & 0x00000000ffffffffULL) | + ((uint64_t)val << 32); + break; + + case REG_UTMRLBA: + s->utmrl_base = (s->utmrl_base & 0xffffffff00000000ULL) | + ((uint64_t)(val & 0xfffffc00)); + break; + + case REG_UTMRLBAU: + s->utmrl_base = (s->utmrl_base & 0x00000000ffffffffULL) | + ((uint64_t)val << 32); + break; + + case REG_UTRLRSR: + case REG_UTMRLRSR: + break; + + case REG_UTRLDBR: + s->utrldbr |= (uint32_t)val; + s->regs[R(REG_UTRLDBR)] = s->utrldbr; + aspeed_ufs_doorbell_bh(s); + break; + + case REG_UTRLCLR: + s->utrldbr &= (uint32_t)val; + s->regs[R(REG_UTRLDBR)] = s->utrldbr; + break; + + case REG_UTMRLDBR: { + /* + * Task management doorbell. Write OCS=SUCCESS into each requested + * UTMRD so ufshcd_issue_tm_cmd() reads back success, clear the + * doorbell, then raise IS_UTMRCS so ufshcd_tmc_handler() fires + * complete() on the waiting thread. + * + * UTMRD slot stride is 80 bytes (request_desc_header:16 + + * upiu_req:32 + upiu_rsp:32); OCS is at byte 8 of the header. + */ + uint32_t dbr = (uint32_t)val; + while (dbr) { + int slot = ctz32(dbr); + uint64_t ocs_addr = s->utmrl_base + + (uint64_t)slot * UTMRD_SIZE + + UTMRD_OCS_OFFSET; + uint8_t ocs = OCS_SUCCESS; + ufs_dma_write(ocs_addr, &ocs, sizeof(ocs)); + dbr &= ~(1u << slot); + } + s->regs[R(REG_UTMRLDBR)] = 0; + s->regs[R(REG_IS)] |= IS_UTMRCS; + aspeed_ufs_update_irq(s); + break; + } + + default: + break; + } +} + +static const MemoryRegionOps aspeed_ufs_ops = { + .read = aspeed_ufs_read, + .write = aspeed_ufs_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void aspeed_ufs_reset(DeviceState *dev) +{ + AspeedUFSState *s = ASPEED_UFS(dev); + + memset(s->regs, 0, sizeof(s->regs)); + + s->regs[R(REG_CAP)] = ASPEED_UFS_CAP_RESET; + s->regs[R(REG_VER)] = ASPEED_UFS_VER_RESET; + s->regs[R(REG_HCPID)] = 0x41535045; /* "ASPE" in ASCII */ + s->regs[R(REG_HCMID)] = 0x45440000; /* "ED\0\0" in ASCII */ + + s->regs[R(REG_HCE)] = 0; + s->regs[R(REG_HCS)] = 0; + + s->hce_phase = 0; + s->utrldbr = 0; + s->utrl_base = 0; + s->utmrl_base = 0; + + qemu_set_irq(s->irq, 0); +} + +static void aspeed_ufs_realize(DeviceState *dev, Error **errp) +{ + AspeedUFSState *s = ASPEED_UFS(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_ufs_ops, s, + TYPE_ASPEED_UFS, ASPEED_UFS_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + + s->hce_bh = qemu_bh_new(aspeed_ufs_hce_bh, s); + + if (s->blk) { + int64_t sz = blk_getlength(s->blk); + if (sz <= 0) { + error_setg(errp, "aspeed-ufs: cannot determine drive size"); + return; + } + s->num_sectors = (uint64_t)sz / 512; + blk_set_dev_ops(s->blk, NULL, NULL); + if (blk_set_perm(s->blk, + BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, + BLK_PERM_ALL, errp) < 0) { + return; + } + } else { + /* No drive attached; expose a minimal read-only stub. */ + s->num_sectors = 2048; + } + + aspeed_ufs_init_unit_desc(s); + aspeed_ufs_reset(dev); +} + +static const VMStateDescription vmstate_aspeed_ufs = { + .name = TYPE_ASPEED_UFS, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AspeedUFSState, ASPEED_UFS_NUM_REGS), + VMSTATE_INT32(hce_phase, AspeedUFSState), + VMSTATE_UINT32(utrldbr, AspeedUFSState), + VMSTATE_UINT64(utrl_base, AspeedUFSState), + VMSTATE_UINT64(utmrl_base, AspeedUFSState), + VMSTATE_UINT64(num_sectors, AspeedUFSState), + VMSTATE_UINT8_ARRAY(unit_desc, AspeedUFSState, 0x23), + VMSTATE_END_OF_LIST() + }, +}; + +static const Property aspeed_ufs_properties[] = { + DEFINE_PROP_DRIVE("drive", AspeedUFSState, blk), +}; + +static void aspeed_ufs_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = aspeed_ufs_realize; + device_class_set_legacy_reset(dc, aspeed_ufs_reset); + dc->vmsd = &vmstate_aspeed_ufs; + dc->desc = "ASPEED AST2700 UFS Host Controller"; + device_class_set_props(dc, aspeed_ufs_properties); + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); +} + +static const TypeInfo aspeed_ufs_info = { + .name = TYPE_ASPEED_UFS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedUFSState), + .class_init = aspeed_ufs_class_init, +}; + +static void aspeed_ufs_register_types(void) +{ + type_register_static(&aspeed_ufs_info); +} + +type_init(aspeed_ufs_register_types) diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build index 6e68328b93..86052e6fd2 100644 --- a/hw/ufs/meson.build +++ b/hw/ufs/meson.build @@ -1 +1,2 @@ system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c')) +system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_ufs.c')) -- 2.53.0
