* [PULL 00/12] UFS command fixes and fault injection tests
@ 2026-10-30 8:14 Jeuk Kim
2026-10-30 8:14 ` [PULL 01/12] hw/ufs: Add support for REPORT_LUNS SCSI command for Well Known LUNs Jeuk Kim
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Jeuk Kim <jeuk20.kim@samsung.com>
The following changes since commit b574eaf640f02c4edf28cbee6417378f0d79cda0:
Merge tag 'for_upstream' of https://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging (2026-10-29 13:47:40 -0500)
are available in the Git repository at:
https://gitlab.com/jeuk20.kim/qemu.git tags/pull-ufs-20261030
for you to fetch changes up to cc145a41586bde12c94b7e8932988cff323ad3a7:
tests/qtest/ufs-test: Add QOM fault injection task abort test (2026-10-30 17:00:58 +0900)
----------------------------------------------------------------
UFS command fixes and fault injection tests
- Add REPORT LUNS support for well-known LUNs
- Fix PRDT validation and SYNCHRONIZE CACHE handling
- Add fault injection and reset/task management tests
----------------------------------------------------------------
Daejun Park (3):
hw/ufs: Fail SCSI commands that move data without a PRDT
hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10)
hw/ufs: Check the PRDT size against the transfer length of the CDB
Stanley Jhu (8):
hw/ufs: Add experimental fault injection properties for holding requests
hw/ufs: Handle task management requests for fault-injected held requests
hw/ufs: Add ssu-timeout mode for power management recovery testing
docs/system/ufs: Update task management request support status
tests/qtest/ufs-test: Add TMR and async MCQ SCSI submission helpers
tests/qtest/ufs-test: Add HCE reset unit test
tests/qtest/ufs-test: Add MCQ runtime register and TMR handling test
tests/qtest/ufs-test: Add QOM fault injection task abort test
dridiha (1):
hw/ufs: Add support for REPORT_LUNS SCSI command for Well Known LUNs
docs/system/devices/ufs.rst | 7 +-
hw/ufs/lu.c | 85 ++-
hw/ufs/trace-events | 3 +
hw/ufs/ufs-pci.c | 7 +
hw/ufs/ufs-sysbus.c | 7 +
hw/ufs/ufs.c | 305 ++++++++++-
hw/ufs/ufs.h | 68 +++
include/block/ufs.h | 3 +
tests/qtest/ufs-test.c | 1202 ++++++++++++++++++++++++++++++++++++++++++-
9 files changed, 1666 insertions(+), 21 deletions(-)
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PULL 01/12] hw/ufs: Add support for REPORT_LUNS SCSI command for Well Known LUNs
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 02/12] hw/ufs: Fail SCSI commands that move data without a PRDT Jeuk Kim
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: dridiha <dridiha98@gmail.com>
According to the UFS specification, the REPORT_LUNS SCSI command can query
Well Known LUNs by setting CDB[2] to 0x01 (select Well Known LUNs) or 0x02
(select all LUNs).
Currently, the UFS emulation layer lacks handling for these CDB[2] select
report values, causing command failures when guests attempt to report Well
Known LUNs.
This patch adds support for handling CDB[2] values 0x01 and 0x02 in the
REPORT_LUNS command, returning the appropriate Well Known LUN descriptors
in the SCSI response payload.
Signed-off-by: Hamza Dridi <dridiha98@gmail.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/lu.c | 44 ++++++++++++++++++++++++++++++++++----------
include/block/ufs.h | 3 +++
2 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index 5905c49fac9..ee4bb75361c 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -218,33 +218,57 @@ static const struct SCSIBusInfo ufs_scsi_info = {
.cancel = ufs_scsi_command_cancelled,
};
+static bool ufs_append_lun(uint8_t *outbuf, uint32_t outbuf_len, int *len,
+ uint8_t lun, uint8_t lun_msb)
+{
+ if (*len + 8 > outbuf_len) {
+ return false;
+ }
+
+ memset(outbuf + *len, 0, 8);
+ outbuf[*len] = lun_msb;
+ outbuf[*len + 1] = lun;
+ *len += 8;
+
+ return true;
+}
+
static int ufs_emulate_report_luns(UfsRequest *req, uint8_t *outbuf,
uint32_t outbuf_len)
{
UfsHc *u = req->hc;
int len = 0;
- /* TODO: Support for cases where SELECT REPORT is 1 and 2 */
- if (req->req_upiu.sc.cdb[2] != 0) {
+ uint8_t report_lun_cmd = req->req_upiu.sc.cdb[2];
+
+ if (report_lun_cmd > 2) {
return SCSI_COMMAND_FAIL;
}
if (outbuf_len < 8) {
return SCSI_COMMAND_FAIL;
}
+
memset(outbuf, 0, 8);
len += 8;
- for (uint8_t lun = 0; lun < UFS_MAX_LUS; ++lun) {
- if (u->lus[lun]) {
- if (len + 8 > outbuf_len) {
+ if (report_lun_cmd == 2 || report_lun_cmd == 1) {
+ ufs_append_lun(outbuf, outbuf_len, &len, u->report_wlu.lun & 0x7F,
+ UFS_WLUN_MSB);
+ ufs_append_lun(outbuf, outbuf_len, &len, u->dev_wlu.lun & 0x7F,
+ UFS_WLUN_MSB);
+ ufs_append_lun(outbuf, outbuf_len, &len, u->boot_wlu.lun & 0x7F,
+ UFS_WLUN_MSB);
+ ufs_append_lun(outbuf, outbuf_len, &len, u->rpmb_wlu.lun & 0x7F,
+ UFS_WLUN_MSB);
+ }
+
+ if (report_lun_cmd == 0 || report_lun_cmd == 2) {
+ for (uint8_t lun = 0; lun < UFS_MAX_LUS; ++lun) {
+ if (u->lus[lun] &&
+ !ufs_append_lun(outbuf, outbuf_len, &len, lun, 0)) {
break;
}
-
- memset(outbuf + len, 0, 8);
- outbuf[len] = 0;
- outbuf[len + 1] = lun;
- len += 8;
}
}
diff --git a/include/block/ufs.h b/include/block/ufs.h
index 4a614fbe526..f4a3371bcea 100644
--- a/include/block/ufs.h
+++ b/include/block/ufs.h
@@ -835,6 +835,9 @@ typedef struct QEMU_PACKED UtpTaskReqDesc {
*/
#define UFS_EHS_OFFSET_IN_RESPONSE 32
+/* Most significant byte in the Report Lun response for Well Known LU */
+#define UFS_WLUN_MSB 0xC1
+
/* Well known logical unit id in LUN field of UPIU */
enum {
UFS_UPIU_REPORT_LUNS_WLUN = 0x81,
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 02/12] hw/ufs: Fail SCSI commands that move data without a PRDT
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
2026-10-30 8:14 ` [PULL 01/12] hw/ufs: Add support for REPORT_LUNS SCSI command for Well Known LUNs Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 03/12] hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10) Jeuk Kim
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi, qemu-stable
From: Daejun Park <daejun7.park@samsung.com>
A command whose CDB transfers data, sent with a PRDT length of 0,
reaches scsi-hd without an SG list. The SCSI layer then calls the
transfer_data callback of the bus, which ufs_scsi_info does not set, and
QEMU dies with SIGSEGV. A guest triggers this with, for example, an
INQUIRY whose PRDT is empty; the new no-prdt qtest crashed QEMU before
this change.
Check the transfer direction that scsi_req_new() parsed, and fail such a
command with OCS MISMATCH DATA BUFFER SIZE before it reaches scsi-hd:
the PRDT describes less data than the command moves. virtio-scsi
likewise fails a command that has no matching buffer
(VIRTIO_SCSI_S_OVERRUN). A request that only reports a pending unit
attention moves no data and goes through, so that the guest still sees
the unit attention.
Handing scsi-hd an empty SG list instead is not enough: READ and WRITE
then complete synchronously in dma_blk_io() and trip the assertion in
scsi_dma_complete().
Fixes: 2a8b36a4967d ("hw/ufs: Support for UFS logical unit")
Cc: qemu-stable@nongnu.org
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/lu.c | 24 ++++++++++++++++++----
hw/ufs/ufs.c | 10 ++++++++--
hw/ufs/ufs.h | 2 ++
tests/qtest/ufs-test.c | 45 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 75 insertions(+), 6 deletions(-)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index ee4bb75361c..1101e368734 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -406,6 +406,8 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, UfsRequest *req)
static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
{
uint8_t task_tag = req->req_upiu.header.task_tag;
+ SCSIRequest *scsi_req;
+ uint32_t len;
/*
* Each ufs-lu has its own independent virtual SCSI bus. Therefore, we can't
@@ -417,12 +419,26 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
return ufs_emulate_scsi_cmd(lu, req);
}
- SCSIRequest *scsi_req =
- scsi_req_new(lu->scsi_dev, task_tag, lu->lun, req->req_upiu.sc.cdb,
- UFS_CDB_SIZE, req);
+ scsi_req = scsi_req_new(lu->scsi_dev, task_tag, lu->lun,
+ req->req_upiu.sc.cdb, UFS_CDB_SIZE, req);
+
+ /*
+ * A command that moves data needs a PRDT. Without one there is no SG
+ * list, and the SCSI layer would call the transfer_data callback, which
+ * ufs-lu does not set. Fail the request with OCS MISMATCH DATA BUFFER
+ * SIZE, as virtio-scsi fails a buffer that does not match the command.
+ * A request that reports a pending unit attention instead moves no data;
+ * its sense is already set, so let it through and the guest sees the UA.
+ */
+ if (scsi_req->cmd.mode != SCSI_XFER_NONE && !req->sg &&
+ !scsi_req->sense_len) {
+ scsi_req_unref(scsi_req);
+ return UFS_REQUEST_MISMATCH_DATA_BUF;
+ }
+
req->sreq = scsi_req;
- uint32_t len = scsi_req_enqueue(scsi_req);
+ len = scsi_req_enqueue(scsi_req);
if (len) {
scsi_req_continue(scsi_req);
}
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index f008af7467e..dda615da7ce 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -2451,10 +2451,16 @@ void ufs_complete_req(UfsRequest *req, UfsReqResult req_result)
UfsHc *u = req->hc;
assert(req->state == UFS_REQUEST_RUNNING);
- if (req_result == UFS_REQUEST_SUCCESS) {
+ switch (req_result) {
+ case UFS_REQUEST_SUCCESS:
req->utrd.header.dword_2 = cpu_to_le32(UFS_OCS_SUCCESS);
- } else {
+ break;
+ case UFS_REQUEST_MISMATCH_DATA_BUF:
+ req->utrd.header.dword_2 = cpu_to_le32(UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+ break;
+ default:
req->utrd.header.dword_2 = cpu_to_le32(UFS_OCS_INVALID_CMD_TABLE_ATTR);
+ break;
}
req->state = UFS_REQUEST_COMPLETE;
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 265a43faaa6..adf74b6833d 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -47,6 +47,8 @@ typedef enum UfsReqResult {
UFS_REQUEST_SUCCESS = 0,
UFS_REQUEST_FAIL = 1,
UFS_REQUEST_NO_COMPLETE = 2,
+ /* The PRDT describes less data than the command moves */
+ UFS_REQUEST_MISMATCH_DATA_BUF = 3,
} UfsReqResult;
#define UFS_INVALID_SLOT (-1)
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 9a9c5ca8346..ea7b9aa62a7 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -770,6 +770,50 @@ static void ufstest_read_write(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static void ufstest_no_prdt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ uint8_t buf[4096];
+ const uint8_t inquiry_cdb[UFS_CDB_SIZE] = {
+ /* INQUIRY, allocation length 36 */
+ INQUIRY, 0x00, 0x00, 0x00, 0x24
+ };
+ const uint8_t write_cdb[UFS_CDB_SIZE] = {
+ /* WRITE(10) to LBA 0, transfer length 1 */
+ WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00
+ };
+ enum UtpOcsCodes ocs;
+ UtpUpiuRsp rsp_upiu;
+ const int test_lun = 1;
+
+ ufs_init(ufs, alloc);
+
+ /* A pending unit attention is reported even without a PRDT */
+ ocs = ufs_send_scsi_command(ufs, test_lun, write_cdb, NULL, 0, NULL, 0,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, CHECK_CONDITION);
+ g_assert_cmpuint(rsp_upiu.sr.sense_data[2] & 0xf, ==, UNIT_ATTENTION);
+
+ /* Commands that move data but come without a PRDT fail */
+ ocs = ufs_send_scsi_command(ufs, test_lun, inquiry_cdb, NULL, 0, NULL, 0,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, write_cdb, NULL, 0, NULL, 0,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+
+ /* The logical unit keeps working */
+ ocs = ufs_send_scsi_command(ufs, test_lun, inquiry_cdb, NULL, 0, buf, 0x24,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpuint(buf[0] & 0x1f, ==, TYPE_DISK);
+
+ ufs_exit(ufs, alloc);
+}
+
static void ufstest_mcq_cq_wraparound(void *obj, void *data,
QGuestAllocator *alloc)
{
@@ -1721,6 +1765,7 @@ static void ufs_register_nodes(void)
qos_add_test("init", "ufs", ufstest_init, NULL);
qos_add_test("legacy-read-write", "ufs", ufstest_read_write, &io_test_opts);
qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts);
+ qos_add_test("no-prdt", "ufs", ufstest_no_prdt, &io_test_opts);
qos_add_test("mcq-cq-wraparound", "ufs", ufstest_mcq_cq_wraparound,
&mcq_test_opts);
qos_add_test("query-flag", "ufs", ufstest_query_flag_request,
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 03/12] hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10)
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
2026-10-30 8:14 ` [PULL 01/12] hw/ufs: Add support for REPORT_LUNS SCSI command for Well Known LUNs Jeuk Kim
2026-10-30 8:14 ` [PULL 02/12] hw/ufs: Fail SCSI commands that move data without a PRDT Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 04/12] hw/ufs: Check the PRDT size against the transfer length of the CDB Jeuk Kim
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Daejun Park <daejun7.park@samsung.com>
scsi-hd implements SYNCHRONIZE CACHE(10) only and fails the 16-byte
variant with INVALID COMMAND OPERATION CODE. Linux sends SYNCHRONIZE
CACHE(16) to zoned disks (sd_zbc sets use_16_for_sync), so once a
logical unit can be zoned, every flush from such a guest fails. With
the zoned logical units and without this change, a Linux 7.3-rc7
guest logged
sd 2:0:0:1: [sdb] tag#9 Add. Sense: Invalid command operation code
critical target error, dev sdb, sector 0 op 0x1:(WRITE) flags 0x800
and fsync() on the disk failed with EREMOTEIO.
scsi-hd flushes the whole medium for SYNCHRONIZE CACHE(10) and does not
look at its LBA range. Hand the 16-byte command to scsi-hd as the
10-byte one and keep the IMMED bit. This keeps the UFS change inside
hw/ufs; if scsi-disk learns SYNCHRONIZE CACHE(16), the translation can
go.
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/lu.c | 17 ++++++++++++++--
tests/qtest/ufs-test.c | 46 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index 1101e368734..a7b6d778f9b 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -406,6 +406,8 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, UfsRequest *req)
static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
{
uint8_t task_tag = req->req_upiu.header.task_tag;
+ uint8_t *cdb = req->req_upiu.sc.cdb;
+ uint8_t sync_cache_10[UFS_CDB_SIZE] = { SYNCHRONIZE_CACHE };
SCSIRequest *scsi_req;
uint32_t len;
@@ -419,8 +421,19 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
return ufs_emulate_scsi_cmd(lu, req);
}
- scsi_req = scsi_req_new(lu->scsi_dev, task_tag, lu->lun,
- req->req_upiu.sc.cdb, UFS_CDB_SIZE, req);
+ /*
+ * scsi-hd implements SYNCHRONIZE CACHE(10) only, while Linux sends the
+ * 16-byte variant to zoned disks. scsi-hd flushes the whole medium for
+ * SYNCHRONIZE CACHE(10) whatever its LBA range, so pass the 16-byte
+ * command on as the 10-byte one, keeping IMMED.
+ */
+ if (cdb[0] == SYNCHRONIZE_CACHE_16) {
+ sync_cache_10[1] = cdb[1] & 0x02;
+ cdb = sync_cache_10;
+ }
+
+ scsi_req = scsi_req_new(lu->scsi_dev, task_tag, lu->lun, cdb, UFS_CDB_SIZE,
+ req);
/*
* A command that moves data needs a PRDT. Without one there is no SG
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index ea7b9aa62a7..3c9e9e4032c 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -814,6 +814,51 @@ static void ufstest_no_prdt(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static void ufstest_sync_cache(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ uint8_t buf[4096];
+ const uint8_t request_sense_cdb[UFS_CDB_SIZE] = {
+ REQUEST_SENSE,
+ };
+ const uint8_t sync_cache_10_cdb[UFS_CDB_SIZE] = {
+ SYNCHRONIZE_CACHE,
+ };
+ const uint8_t sync_cache_16_cdb[UFS_CDB_SIZE] = {
+ SYNCHRONIZE_CACHE_16,
+ };
+ const uint8_t sync_cache_16_immed_cdb[UFS_CDB_SIZE] = {
+ SYNCHRONIZE_CACHE_16, 0x02 /* IMMED */
+ };
+ enum UtpOcsCodes ocs;
+ UtpUpiuRsp rsp_upiu;
+ const int test_lun = 1;
+
+ ufs_init(ufs, alloc);
+
+ /* Clear Unit Attention */
+ ocs = ufs_send_scsi_command(ufs, test_lun, request_sense_cdb, NULL, 0,
+ buf, sizeof(buf), &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, sync_cache_10_cdb, NULL, 0,
+ NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, sync_cache_16_cdb, NULL, 0,
+ NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, sync_cache_16_immed_cdb, NULL,
+ 0, NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ufs_exit(ufs, alloc);
+}
+
static void ufstest_mcq_cq_wraparound(void *obj, void *data,
QGuestAllocator *alloc)
{
@@ -1766,6 +1811,7 @@ static void ufs_register_nodes(void)
qos_add_test("legacy-read-write", "ufs", ufstest_read_write, &io_test_opts);
qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts);
qos_add_test("no-prdt", "ufs", ufstest_no_prdt, &io_test_opts);
+ qos_add_test("sync-cache", "ufs", ufstest_sync_cache, &io_test_opts);
qos_add_test("mcq-cq-wraparound", "ufs", ufstest_mcq_cq_wraparound,
&mcq_test_opts);
qos_add_test("query-flag", "ufs", ufstest_query_flag_request,
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 04/12] hw/ufs: Check the PRDT size against the transfer length of the CDB
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (2 preceding siblings ...)
2026-10-30 8:14 ` [PULL 03/12] hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10) Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 05/12] hw/ufs: Add experimental fault injection properties for holding requests Jeuk Kim
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi, qemu-stable
From: Daejun Park <daejun7.park@samsung.com>
scsi-hd moves as many bytes as the SG list of a request holds, and
ufs-lu builds that list from the whole PRDT. When the PRDT is larger
than the transfer length in the CDB, a WRITE writes more blocks than
the CDB names and a READ reads more. A one-block WRITE(10) at LBA 0
with an 8 KiB PRDT also wrote LBA 1, and its response reported no
residual. A UFS device transfers what the CDB asks for and reports the
rest of the Expected Data Transfer Length as a residual.
The commands that scsi-hd emulates had their residual computed against
the PRDT too: an INQUIRY with an allocation length of 36, a 4 KiB PRDT
and an Expected Data Transfer Length of 4096 reported an overflow of
4294959176 bytes. It now reports an underflow of 4060.
When the PRDT is smaller than the transfer length, scsi-hd moved what it
holds and the request completed without an error, for example a
two-block WRITE(10) with a 4 KiB PRDT. Such a request now fails with OCS
MISMATCH DATA BUFFER SIZE, as one without a PRDT does. As in
virtio-scsi, the PRDT is compared with the transfer length of the CDB,
which for commands that return data is the allocation length, and before
scsi-hd checks the CDB.
The new ufs_fit_data_buffer() in ufs.c, where the SG list is built from
the PRDT, cuts a longer list and reports a shorter one; ufs-lu passes it
the transfer length that scsi_req_new() parsed. The HID qtest wrote
eight blocks from a one-block buffer, so give it eight blocks. The new
long-prdt and short-prdt qtests failed before this change.
Fixes: 2a8b36a4967d ("hw/ufs: Support for UFS logical unit")
Cc: qemu-stable@nongnu.org
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
Reviewed-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/lu.c | 20 +++---
hw/ufs/trace-events | 1 +
hw/ufs/ufs.c | 35 ++++++++++
hw/ufs/ufs.h | 1 +
tests/qtest/ufs-test.c | 141 ++++++++++++++++++++++++++++++++++++++++-
5 files changed, 189 insertions(+), 9 deletions(-)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index a7b6d778f9b..bbb0554fb0d 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -436,15 +436,19 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
req);
/*
- * A command that moves data needs a PRDT. Without one there is no SG
- * list, and the SCSI layer would call the transfer_data callback, which
- * ufs-lu does not set. Fail the request with OCS MISMATCH DATA BUFFER
- * SIZE, as virtio-scsi fails a buffer that does not match the command.
- * A request that reports a pending unit attention instead moves no data;
- * its sense is already set, so let it through and the guest sees the UA.
+ * scsi-hd moves as many bytes as the SG list holds, and without an SG
+ * list it would call the transfer_data callback, which ufs-lu does not
+ * set. A UFS device moves what the CDB asks for, so fit the PRDT to the
+ * transfer length, and fail a request whose PRDT describes less, a
+ * missing PRDT included, with OCS MISMATCH DATA BUFFER SIZE, as
+ * virtio-scsi fails a buffer that does not match the command. The
+ * response reports the rest of the Expected Data Transfer Length as a
+ * residual. A request that reports a pending unit attention moves no
+ * data; its sense is already set, so let it through and the guest sees
+ * the UA.
*/
- if (scsi_req->cmd.mode != SCSI_XFER_NONE && !req->sg &&
- !scsi_req->sense_len) {
+ if (!scsi_req->sense_len &&
+ !ufs_fit_data_buffer(req, scsi_req->cmd.xfer)) {
scsi_req_unref(scsi_req);
return UFS_REQUEST_MISMATCH_DATA_BUF;
}
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index 5e5a54a3fb3..db9d1727135 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -22,6 +22,7 @@ ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and request
ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read utrd. UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64""
ufs_err_dma_read_req_upiu(uint32_t slot, uint64_t addr) "failed to read req upiu. UTRLDBR slot %"PRIu32", request upiu addr %"PRIu64""
ufs_err_dma_read_prdt(uint32_t slot, uint64_t addr) "failed to read prdt. UTRLDBR slot %"PRIu32", prdt addr %"PRIu64""
+ufs_err_mismatch_data_buf(uint32_t slot, uint8_t task_tag, uint64_t xfer, uint64_t prdt_size) "slot %"PRIu32", task tag %"PRIu8", transfer length %"PRIu64", PRDT %"PRIu64""
ufs_err_dma_read_sq(uint8_t sqid, uint64_t addr) "failed to read sq entry. sqid %"PRIu8", hwaddr %"PRIu64""
ufs_err_dma_write_utrd(uint32_t slot, uint64_t addr) "failed to write utrd. UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64""
ufs_err_dma_write_rsp_upiu(uint32_t slot, uint64_t addr) "failed to write rsp upiu. UTRLDBR slot %"PRIu32", response upiu addr %"PRIu64""
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index dda615da7ce..cd28f97a123 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -227,6 +227,41 @@ static MemTxResult ufs_dma_read_prdt(UfsRequest *req)
return MEMTX_OK;
}
+/*
+ * Fit the SG list built from the PRDT to the len bytes that the command of
+ * req moves: keep only the first len bytes of a longer list. Return false if
+ * the PRDT describes fewer than len bytes.
+ */
+bool ufs_fit_data_buffer(UfsRequest *req, uint64_t len)
+{
+ QEMUSGList *prdt_sg = req->sg;
+ uint64_t prdt_size = prdt_sg ? prdt_sg->size : 0;
+ QEMUSGList *sg;
+ int i;
+
+ if (prdt_size < len) {
+ trace_ufs_err_mismatch_data_buf(req->slot,
+ req->req_upiu.header.task_tag, len,
+ prdt_size);
+ return false;
+ }
+ if (prdt_size == len) {
+ return true;
+ }
+
+ sg = g_new0(QEMUSGList, 1);
+ qemu_sglist_init(sg, prdt_sg->dev, prdt_sg->nsg, prdt_sg->as);
+ for (i = 0; i < prdt_sg->nsg && sg->size < len; i++) {
+ qemu_sglist_add(sg, prdt_sg->sg[i].base,
+ MIN(prdt_sg->sg[i].len, len - sg->size));
+ }
+ qemu_sglist_destroy(prdt_sg);
+ g_free(prdt_sg);
+ req->sg = sg;
+ req->data_len = sg->size;
+ return true;
+}
+
static MemTxResult ufs_dma_read_upiu(UfsRequest *req)
{
MemTxResult ret;
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index adf74b6833d..15b3a352f3b 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -306,6 +306,7 @@ void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags,
uint16_t data_segment_length);
void ufs_build_query_response(UfsRequest *req);
void ufs_complete_req(UfsRequest *req, UfsReqResult req_result);
+bool ufs_fit_data_buffer(UfsRequest *req, uint64_t len);
void ufs_wb_update_avail_buffer(UfsHc *u);
void ufs_init_wlu(UfsLu *wlu, uint8_t wlun);
bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as,
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 3c9e9e4032c..e8993e8c246 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -859,6 +859,142 @@ static void ufstest_sync_cache(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static void ufstest_long_prdt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ const uint8_t request_sense_cdb[UFS_CDB_SIZE] = {
+ REQUEST_SENSE,
+ };
+ /* One block at LBA 0 and at LBA 1 */
+ const uint8_t write_lba0_cdb[UFS_CDB_SIZE] = {
+ WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ };
+ const uint8_t read_lba0_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ };
+ const uint8_t read_lba1_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00,
+ };
+ /* Allocation length 36 */
+ const uint8_t inquiry_cdb[UFS_CDB_SIZE] = {
+ INQUIRY, 0x00, 0x00, 0x00, 0x24, 0x00,
+ };
+ uint8_t buf[2 * 4096];
+ uint8_t zero_buf[4096] = { 0 };
+ uint8_t write_buf[2 * 4096];
+ UtpUpiuRsp rsp_upiu, write_rsp_upiu;
+ enum UtpOcsCodes ocs;
+
+ ufs_init(ufs, alloc);
+ ocs = ufs_send_scsi_command(ufs, 1, request_sense_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+
+ /* A one-block WRITE with a two-block PRDT writes one block */
+ memset(write_buf, 0xa5, sizeof(write_buf));
+ ocs = ufs_send_scsi_command(ufs, 1, write_lba0_cdb, write_buf,
+ sizeof(write_buf), NULL, 0, &write_rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(write_rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ocs = ufs_send_scsi_command(ufs, 1, read_lba1_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpmem(buf, 4096, zero_buf, sizeof(zero_buf));
+
+ g_assert_cmpuint(write_rsp_upiu.header.flags, ==, UFS_UPIU_FLAG_UNDERFLOW);
+ g_assert_cmpuint(be32_to_cpu(write_rsp_upiu.sr.residual_transfer_count),
+ ==, 4096);
+
+ /* A one-block READ with a two-block PRDT reads one block */
+ ocs = ufs_send_scsi_command(ufs, 1, read_lba0_cdb, NULL, 0, buf,
+ sizeof(buf), &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpuint(rsp_upiu.header.flags, ==, UFS_UPIU_FLAG_UNDERFLOW);
+ g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), ==,
+ 4096);
+ g_assert_cmpmem(buf, 4096, write_buf, 4096);
+
+ /* A 36-byte INQUIRY with a 4 KiB PRDT reports the rest as a residual */
+ ocs = ufs_send_scsi_command(ufs, 1, inquiry_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpuint(rsp_upiu.header.flags, ==, UFS_UPIU_FLAG_UNDERFLOW);
+ g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), ==,
+ 4096 - 36);
+
+ ufs_exit(ufs, alloc);
+}
+
+static void ufstest_short_prdt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ const uint8_t request_sense_cdb[UFS_CDB_SIZE] = {
+ REQUEST_SENSE,
+ };
+ /* Two blocks at LBA 2, and one */
+ const uint8_t write_2_cdb[UFS_CDB_SIZE] = {
+ WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00,
+ };
+ const uint8_t read_2_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00,
+ };
+ const uint8_t write_1_cdb[UFS_CDB_SIZE] = {
+ WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00,
+ };
+ const uint8_t read_1_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00,
+ };
+ /* Allocation length 36 */
+ const uint8_t inquiry_cdb[UFS_CDB_SIZE] = {
+ INQUIRY, 0x00, 0x00, 0x00, 0x24, 0x00,
+ };
+ uint8_t buf[4096];
+ uint8_t zero_buf[4096] = { 0 };
+ uint8_t write_buf[4096];
+ UtpUpiuRsp rsp_upiu;
+ enum UtpOcsCodes ocs;
+
+ ufs_init(ufs, alloc);
+ ocs = ufs_send_scsi_command(ufs, 1, request_sense_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ memset(write_buf, 0x5a, sizeof(write_buf));
+
+ /* A PRDT that describes less than the command moves fails */
+ ocs = ufs_send_scsi_command(ufs, 1, write_2_cdb, write_buf,
+ sizeof(write_buf), NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+ ocs = ufs_send_scsi_command(ufs, 1, read_2_cdb, NULL, 0, buf, sizeof(buf),
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+ ocs = ufs_send_scsi_command(ufs, 1, inquiry_cdb, NULL, 0, buf, 16,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+
+ /* The failed WRITE wrote nothing */
+ ocs = ufs_send_scsi_command(ufs, 1, read_1_cdb, NULL, 0, buf, sizeof(buf),
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpmem(buf, sizeof(buf), zero_buf, sizeof(zero_buf));
+
+ /* A matching PRDT still works */
+ ocs = ufs_send_scsi_command(ufs, 1, write_1_cdb, write_buf,
+ sizeof(write_buf), NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ ocs = ufs_send_scsi_command(ufs, 1, read_1_cdb, NULL, 0, buf, sizeof(buf),
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpmem(buf, sizeof(buf), write_buf, sizeof(write_buf));
+
+ ufs_exit(ufs, alloc);
+}
+
static void ufstest_mcq_cq_wraparound(void *obj, void *data,
QGuestAllocator *alloc)
{
@@ -1577,7 +1713,8 @@ static void ufstest_hid(void *obj, void *data, QGuestAllocator *alloc)
/* WRITE(10) to LBA 0, transfer length 8 sectors = 1 fragment */
WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00
};
- uint8_t write_buf[4096];
+ /* The PRDT must hold the eight blocks that the WRITE(10) moves */
+ uint8_t write_buf[8 * 4096];
uint8_t read_buf[4096];
int i;
@@ -1812,6 +1949,8 @@ static void ufs_register_nodes(void)
qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts);
qos_add_test("no-prdt", "ufs", ufstest_no_prdt, &io_test_opts);
qos_add_test("sync-cache", "ufs", ufstest_sync_cache, &io_test_opts);
+ qos_add_test("long-prdt", "ufs", ufstest_long_prdt, &io_test_opts);
+ qos_add_test("short-prdt", "ufs", ufstest_short_prdt, &io_test_opts);
qos_add_test("mcq-cq-wraparound", "ufs", ufstest_mcq_cq_wraparound,
&mcq_test_opts);
qos_add_test("query-flag", "ufs", ufstest_query_flag_request,
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 05/12] hw/ufs: Add experimental fault injection properties for holding requests
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (3 preceding siblings ...)
2026-10-30 8:14 ` [PULL 04/12] hw/ufs: Check the PRDT size against the transfer length of the CDB Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 06/12] hw/ufs: Handle task management requests for fault-injected held requests Jeuk Kim
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Add experimental properties x-fault-inject-tag and x-fault-inject-mode
to ufs-pci and ufs-sysbus devices for testing host driver error
handling and recovery routines.
When x-fault-inject-tag is set to a specific tag (0..255), the
controller holds the matching SCSI command request in u->held_req
without completing it. When x-fault-inject-tag is set to 0xfffffffe
(UFS_FAULT_INJECT_TAG_ANY), the controller intercepts the first
READ_10, WRITE_10, READ_16, or WRITE_16 command with LBA >= 512,
skipping boot-time metadata reads. The default value is 0xffffffff
(UFS_FAULT_INJECT_TAG_NONE), which disables fault injection.
Both properties can be changed at runtime through QOM (for example
QMP qom-set). A held request is released on HCE reset. Task
management handling for held requests follows in the next patch.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/trace-events | 2 +
hw/ufs/ufs-pci.c | 7 ++
hw/ufs/ufs-sysbus.c | 7 ++
hw/ufs/ufs.c | 164 ++++++++++++++++++++++++++++++++++++++++++++
hw/ufs/ufs.h | 61 ++++++++++++++++
5 files changed, 241 insertions(+)
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index db9d1727135..17acbebd301 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -16,6 +16,8 @@ ufs_mcq_create_sq(uint8_t sqid, uint8_t cqid, uint64_t addr, uint16_t size) "mcq
ufs_mcq_create_cq(uint8_t cqid, uint64_t addr, uint16_t size) "mcq create cq cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16""
ufs_write_mcq_op_reg(uint8_t qid, uint32_t offset, uint32_t data) "qid %"PRIu8", offset 0x%"PRIx32", data 0x%"PRIx32""
ufs_process_tmr(uint8_t func, uint32_t tag, uint8_t resp) "query_func 0x%"PRIx8", task_tag %"PRIu32", tm_resp 0x%"PRIx8""
+ufs_fault_inject_hold_req(uint8_t tag, uint64_t lba) "held command tag %"PRIu8", lba %"PRIu64""
+ufs_fault_inject_set_tag(uint32_t tag) "fault-inject-tag set to 0x%"PRIx32""
ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and request lists"
# error condition
diff --git a/hw/ufs/ufs-pci.c b/hw/ufs/ufs-pci.c
index 8abd7d98e3d..8a2268f11ff 100644
--- a/hw/ufs/ufs-pci.c
+++ b/hw/ufs/ufs-pci.c
@@ -72,6 +72,13 @@ static const Property ufs_pci_props[] = {
0x400),
DEFINE_PROP_UINT32("wb-min-size", UfsPciState, ufs.params.wb_min_size,
0x100),
+ DEFINE_PROP_UNSIGNED("x-fault-inject-tag", UfsPciState,
+ ufs.params.fault_inject_tag, UFS_FAULT_INJECT_TAG_NONE,
+ ufs_prop_fault_inject_tag, uint32_t),
+ DEFINE_PROP_SIGNED("x-fault-inject-mode", UfsPciState,
+ ufs.params.fault_inject_mode,
+ UFS_FAULT_INJECT_ABORT_SUCCESS,
+ ufs_prop_fault_inject_mode, UfsFaultInjectMode),
};
static const VMStateDescription ufs_pci_vmstate = {
diff --git a/hw/ufs/ufs-sysbus.c b/hw/ufs/ufs-sysbus.c
index 84de2e95ac4..7e85d8fc4c9 100644
--- a/hw/ufs/ufs-sysbus.c
+++ b/hw/ufs/ufs-sysbus.c
@@ -45,6 +45,13 @@ static const Property ufs_sysbus_props[] = {
0x400),
DEFINE_PROP_UINT32("wb-min-size", SysbusUfsState, ufs.params.wb_min_size,
0x100),
+ DEFINE_PROP_UNSIGNED("x-fault-inject-tag", SysbusUfsState,
+ ufs.params.fault_inject_tag, UFS_FAULT_INJECT_TAG_NONE,
+ ufs_prop_fault_inject_tag, uint32_t),
+ DEFINE_PROP_SIGNED("x-fault-inject-mode", SysbusUfsState,
+ ufs.params.fault_inject_mode,
+ UFS_FAULT_INJECT_ABORT_SUCCESS,
+ ufs_prop_fault_inject_mode, UfsFaultInjectMode),
};
static const VMStateDescription ufs_sysbus_vmstate = {
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index cd28f97a123..1a5bc54eac4 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -15,8 +15,10 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
+#include "qapi/visitor.h"
#include "scsi/constants.h"
#include "hw/core/irq.h"
+#include "hw/core/qdev-prop-internal.h"
#include "trace.h"
#include "ufs.h"
@@ -778,6 +780,77 @@ static bool ufs_mcq_delete_cq(UfsHc *u, uint8_t qid)
return true;
}
+static void ufs_fault_inject_init(UfsHc *u)
+{
+ u->active_fault_inject_tag = u->params.fault_inject_tag;
+ u->held_req = NULL;
+}
+
+static void ufs_fault_inject_hce_reset(UfsHc *u)
+{
+ if (u->held_req) {
+ u->held_req = NULL;
+ u->active_fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ u->params.fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ } else {
+ u->active_fault_inject_tag = u->params.fault_inject_tag;
+ }
+}
+
+static bool ufs_fault_inject_exec_req(UfsRequest *req)
+{
+ UfsHc *u = req->hc;
+
+ if (likely(u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_NONE) ||
+ u->held_req != NULL ||
+ req->req_upiu.header.trans_type != UFS_UPIU_TRANSACTION_COMMAND) {
+ return false;
+ }
+
+ if (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY) {
+ uint8_t op = req->req_upiu.sc.cdb[0];
+ uint64_t lba = 0;
+ bool is_match = false;
+
+ if (op == READ_10 || op == WRITE_10) {
+ /*
+ * In 10-byte SCSI read/write commands (SBC-4), bytes 2..5
+ * encode the 32-bit Logical Block Address (LBA) in
+ * big-endian.
+ */
+ lba = ldl_be_p(&req->req_upiu.sc.cdb[2]);
+ is_match = (lba >= UFS_FAULT_INJECT_MIN_LBA);
+ } else if (op == READ_16 || op == WRITE_16) {
+ /*
+ * In 16-byte SCSI read/write commands (SBC-4), bytes 2..9
+ * encode the 64-bit Logical Block Address (LBA) in
+ * big-endian.
+ */
+ lba = ldq_be_p(&req->req_upiu.sc.cdb[2]);
+ is_match = (lba >= UFS_FAULT_INJECT_MIN_LBA);
+ }
+
+ /*
+ * Only intercept target I/O targeting LBA >=
+ * UFS_FAULT_INJECT_MIN_LBA. Skips guest boot-time partition
+ * table and superblock scanning.
+ */
+ if (is_match) {
+ u->active_fault_inject_tag = req->req_upiu.header.task_tag;
+ u->held_req = req;
+ trace_ufs_fault_inject_hold_req(
+ req->req_upiu.header.task_tag, lba);
+ return true;
+ }
+ } else if (req->req_upiu.header.task_tag == u->active_fault_inject_tag) {
+ u->held_req = req;
+ trace_ufs_fault_inject_hold_req(req->req_upiu.header.task_tag, 0);
+ return true;
+ }
+
+ return false;
+}
+
static void ufs_hce_reset(UfsHc *u)
{
int i;
@@ -854,6 +927,8 @@ static void ufs_hce_reset(UfsHc *u)
memset(u->mcq_op_reg, 0, sizeof(u->mcq_op_reg));
}
+ ufs_fault_inject_hce_reset(u);
+
u->resetting = false;
/* 5. De-assert IRQ */
@@ -2432,6 +2507,10 @@ static void ufs_exec_req(UfsRequest *req)
return;
}
+ if (ufs_fault_inject_exec_req(req)) {
+ return;
+ }
+
switch (req->req_upiu.header.trans_type) {
case UFS_UPIU_TRANSACTION_NOP_OUT:
req_result = ufs_exec_nop_cmd(req);
@@ -3058,8 +3137,93 @@ static void ufs_init_hc(UfsHc *u)
timer_init_ms(&u->idle_timer, QEMU_CLOCK_VIRTUAL_RT, ufs_idle_timer_cb, u);
timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK);
+
+ ufs_fault_inject_init(u);
}
+const QEnumLookup UfsFaultInjectMode_lookup = {
+ .array = (const char *const[]) {
+ [UFS_FAULT_INJECT_ABORT_SUCCESS] = "abort-success",
+ [UFS_FAULT_INJECT_ABORT_FAILED] = "abort-failed",
+ [UFS_FAULT_INJECT_IN_TRANSITION] = "in-transition",
+ [UFS_FAULT_INJECT_TIMEOUT] = "timeout",
+ },
+ .size = UFS_FAULT_INJECT_MODE_MAX,
+};
+
+static void ufs_prop_get_fault_inject_tag(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ Property *prop = opaque;
+ uint32_t *ptr = object_field_prop_ptr(obj, prop);
+ UfsParams *params = container_of(ptr, UfsParams, fault_inject_tag);
+ UfsHc *u = container_of(params, UfsHc, params);
+
+ visit_type_uint32(v, name, &u->active_fault_inject_tag, errp);
+}
+
+static void ufs_prop_set_fault_inject_tag(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ Property *prop = opaque;
+ uint32_t *ptr = object_field_prop_ptr(obj, prop);
+ UfsParams *params = container_of(ptr, UfsParams, fault_inject_tag);
+ UfsHc *u = container_of(params, UfsHc, params);
+ uint32_t val;
+
+ if (!visit_type_uint32(v, name, &val, errp)) {
+ return;
+ }
+
+ if (val > 255 && val != UFS_FAULT_INJECT_TAG_NONE &&
+ val != UFS_FAULT_INJECT_TAG_ANY) {
+ error_setg(errp,
+ "invalid %s %u: must be 0..255, 0x%x (none), or 0x%x (any)",
+ name, val, UFS_FAULT_INJECT_TAG_NONE,
+ UFS_FAULT_INJECT_TAG_ANY);
+ return;
+ }
+
+ if (u->held_req != NULL) {
+ error_setg(errp,
+ "cannot change %s while a request (tag %u) is held",
+ name, u->held_req->req_upiu.header.task_tag);
+ return;
+ }
+
+ *ptr = val;
+ u->active_fault_inject_tag = val;
+ trace_ufs_fault_inject_set_tag(val);
+}
+
+static void ufs_prop_set_default_fault_inject_tag(ObjectProperty *op,
+ const Property *prop)
+{
+ object_property_set_default_uint(op, prop->defval.u);
+}
+
+const PropertyInfo ufs_prop_fault_inject_tag = {
+ .type = "uint32",
+ .description = "Task tag to hold for fault injection",
+ .get = ufs_prop_get_fault_inject_tag,
+ .set = ufs_prop_set_fault_inject_tag,
+ .set_default_value = ufs_prop_set_default_fault_inject_tag,
+ .realized_set_allowed = true,
+};
+
+const PropertyInfo ufs_prop_fault_inject_mode = {
+ .type = "UfsFaultInjectMode",
+ .description = "Fault injection mode: abort-success, abort-failed, "
+ "in-transition, timeout",
+ .enum_table = &UfsFaultInjectMode_lookup,
+ .get = qdev_propinfo_get_enum,
+ .set = qdev_propinfo_set_enum,
+ .set_default_value = qdev_propinfo_set_default_value_enum,
+ .realized_set_allowed = true,
+};
+
bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as, Error **errp)
{
u->dev = dev;
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 15b3a352f3b..80bdaa1a2aa 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -90,6 +90,56 @@ typedef struct UfsLu {
UfsScsiOp scsi_op;
} UfsLu;
+/*
+ * Fault injection modes for x-fault-inject-mode property.
+ * Controls how the held transfer request and subsequent Task Management
+ * Request (TMR) are resolved by the controller:
+ *
+ * UFS_FAULT_INJECT_ABORT_SUCCESS: TMR ABORT_TASK aborts the held request,
+ * clears its state, and returns SUCCESS
+ * (COMPL).
+ * UFS_FAULT_INJECT_ABORT_FAILED: TMR ABORT_TASK returns
+ * TASK_MANAGEMENT_FUNC_FAILED, forcing host
+ * driver to escalate to Host Reset.
+ * UFS_FAULT_INJECT_IN_TRANSITION: TMR QUERY_TASK releases the held request to
+ * execute normally through the SCSI layer and
+ * returns the real task status (SUCCEEDED while
+ * the command is still in flight);
+ * ABORT_TASK cancels the held request and
+ * returns FUNCTION_COMPLETE.
+ * UFS_FAULT_INJECT_TIMEOUT: TMR ABORT_TASK does not complete or clear
+ * doorbell, triggering hardware-level TMR
+ * timeout.
+ */
+typedef enum UfsFaultInjectMode {
+ UFS_FAULT_INJECT_ABORT_SUCCESS = 0,
+ UFS_FAULT_INJECT_ABORT_FAILED,
+ UFS_FAULT_INJECT_IN_TRANSITION,
+ UFS_FAULT_INJECT_TIMEOUT,
+ UFS_FAULT_INJECT_MODE_MAX,
+} UfsFaultInjectMode;
+
+/*
+ * Sentinel values for x-fault-inject-tag property.
+ * Task Tags in UFS are 8-bit (0..255). Values >= 256 are reserved as
+ * sentinel flags to preserve full testability of Tag 0.
+ *
+ * UFS_FAULT_INJECT_TAG_NONE: Disable fault injection (default).
+ * UFS_FAULT_INJECT_TAG_ANY: Automatically intercept the first user-space
+ * READ_10, WRITE_10, READ_16, or WRITE_16 command
+ * targeting LBA >= UFS_FAULT_INJECT_MIN_LBA.
+ */
+#define UFS_FAULT_INJECT_TAG_NONE 0xffffffff
+#define UFS_FAULT_INJECT_TAG_ANY 0xfffffffe
+
+/*
+ * Minimum Logical Block Address (LBA) for automatic fault injection.
+ * Skip early disk blocks (LBA 0..511, e.g. MBR, GPT partition tables, EFI,
+ * and filesystem superblocks) to ensure guest OS boot-time disk discovery
+ * and partition scanning complete cleanly without being held.
+ */
+#define UFS_FAULT_INJECT_MIN_LBA 512
+
typedef struct UfsParams {
char *serial;
uint8_t nutrs; /* Number of UTP Transfer Request Slots */
@@ -99,6 +149,8 @@ typedef struct UfsParams {
uint8_t mcq_maxq; /* MCQ Maximum number of Queues */
uint32_t wb_max_size; /* WB Maximum allocation units */
uint32_t wb_min_size; /* WB Minimum allocation units */
+ uint32_t fault_inject_tag;
+ UfsFaultInjectMode fault_inject_mode;
} UfsParams;
/*
@@ -188,6 +240,10 @@ typedef struct UfsHc {
uint32_t hid_fragment_count; /* Remaining fragmented 4KB units */
uint32_t hid_defrag_total; /* Requested units at defrag start */
uint32_t hid_defrag_remaining; /* Requested units left to move */
+
+ /* Test and fault injection properties */
+ uint32_t active_fault_inject_tag;
+ UfsRequest *held_req;
} UfsHc;
static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid)
@@ -312,4 +368,9 @@ void ufs_init_wlu(UfsLu *wlu, uint8_t wlun);
bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as,
Error **errp);
void ufs_unrealize(UfsHc *u);
+
+extern const PropertyInfo ufs_prop_fault_inject_tag;
+extern const PropertyInfo ufs_prop_fault_inject_mode;
+extern const QEnumLookup UfsFaultInjectMode_lookup;
+
#endif /* HW_UFS_UFS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 06/12] hw/ufs: Handle task management requests for fault-injected held requests
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (4 preceding siblings ...)
2026-10-30 8:14 ` [PULL 05/12] hw/ufs: Add experimental fault injection properties for holding requests Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 07/12] hw/ufs: Add ssu-timeout mode for power management recovery testing Jeuk Kim
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Wire up Task Management Request (TMR) and MCQ submission queue cleanup
handling for transfer requests held by x-fault-inject-tag according to
x-fault-inject-mode:
- "abort-success": UFS_QUERY_TASK returns
UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED. UFS_ABORT_TASK cancels the
underlying SCSI request and marks the MCQ request in error state for
SQRTC.ICU cleanup (or clears the legacy UTRL slot), then returns
UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL.
- "abort-failed": UFS_ABORT_TASK returns
UFS_UPIU_TASK_MANAGEMENT_FUNC_FAILED while keeping the request held.
- "in-transition": UFS_QUERY_TASK releases the held transfer request
and executes it through the normal SCSI command path, completing
with genuine device I/O and CQE generation, while returning
UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED per SAM-5 task set rules.
UFS_ABORT_TASK releases the held request to generic abort processing.
- "timeout": UFS_ABORT_TASK skips completion so the host driver hits
its TMR timeout path.
Also clear u->held_req and disarm active_fault_inject_tag when an MCQ
submission queue containing the held request is cleaned up via
ufs_mcq_sq_cleanup().
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/ufs.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 80 insertions(+), 1 deletion(-)
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 1a5bc54eac4..83d5fc24414 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -37,6 +37,7 @@
#define UFS_HID_PROGRESS_COMPLETE 100
static void ufs_exec_req(UfsRequest *req);
+static void ufs_fault_inject_execute_held_req(UfsRequest *hreq);
static void ufs_clear_req(UfsRequest *req);
static void ufs_mcq_process_cq(void *opaque);
@@ -797,6 +798,67 @@ static void ufs_fault_inject_hce_reset(UfsHc *u)
}
}
+static bool ufs_fault_inject_tmr(UfsHc *u, uint8_t tm_func, uint8_t lun,
+ uint32_t task_tag, uint8_t *tm_resp,
+ bool *skip_completion)
+{
+ UfsRequest *hreq = u->held_req;
+
+ *skip_completion = false;
+ if (!hreq || task_tag != u->active_fault_inject_tag ||
+ hreq->req_upiu.header.lun != lun) {
+ return false;
+ }
+
+ if (tm_func == UFS_QUERY_TASK) {
+ if (u->params.fault_inject_mode == UFS_FAULT_INJECT_IN_TRANSITION) {
+ u->held_req = NULL;
+ u->active_fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ u->params.fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ ufs_fault_inject_execute_held_req(hreq);
+ return false;
+ }
+ /*
+ * Generic QUERY TASK in ufs_process_tmr() will check
+ * ufs_find_req_by_tag() and return SUCCEEDED for the held request.
+ */
+ return false;
+ }
+
+ if (tm_func == UFS_ABORT_TASK) {
+ if (u->params.fault_inject_mode == UFS_FAULT_INJECT_ABORT_FAILED) {
+ *tm_resp = UFS_UPIU_TASK_MANAGEMENT_FUNC_FAILED;
+ return true;
+ }
+ if (u->params.fault_inject_mode == UFS_FAULT_INJECT_TIMEOUT) {
+ *skip_completion = true;
+ return true;
+ }
+
+ /*
+ * UFS_FAULT_INJECT_ABORT_SUCCESS, UFS_FAULT_INJECT_IN_TRANSITION,
+ * or default:
+ * Release the fault-injection hold, then let generic ABORT TASK
+ * in ufs_process_tmr() handle cancellation.
+ */
+ u->held_req = NULL;
+ u->active_fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ u->params.fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ return false;
+ }
+
+ return false;
+}
+
+static void ufs_fault_inject_sq_cleanup(UfsHc *u, UfsRequest *req)
+{
+ if (u->held_req == req) {
+ u->held_req = NULL;
+ u->active_fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ u->params.fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+ }
+}
+
static bool ufs_fault_inject_exec_req(UfsRequest *req)
{
UfsHc *u = req->hc;
@@ -980,6 +1042,7 @@ static void ufs_process_tmr(UfsHc *u, uint32_t val)
UtpTaskReqDesc desc;
uint8_t tm_func, tm_resp, lun;
uint32_t task_tag;
+ bool skip_completion = false;
if (ufs_addr_read(u, desc_addr, &desc, sizeof(desc))) {
continue;
@@ -989,7 +1052,12 @@ static void ufs_process_tmr(UfsHc *u, uint32_t val)
lun = desc.upiu_req.req_header.lun;
task_tag = be32_to_cpu(desc.upiu_req.input_param2);
- if (tm_func == UFS_QUERY_TASK) {
+ if (ufs_fault_inject_tmr(u, tm_func, lun, task_tag, &tm_resp,
+ &skip_completion)) {
+ if (skip_completion) {
+ continue;
+ }
+ } else if (tm_func == UFS_QUERY_TASK) {
UfsRequest *req = ufs_find_req_by_tag(u, lun, task_tag);
if (req) {
@@ -1257,6 +1325,7 @@ static void ufs_mcq_sq_cleanup(UfsHc *u, uint8_t qid)
}
if (req) {
+ ufs_fault_inject_sq_cleanup(u, req);
if (req->sreq != NULL) {
SCSIRequest *sreq = req->sreq;
req->sreq = NULL;
@@ -2537,6 +2606,16 @@ static void ufs_exec_req(UfsRequest *req)
}
}
+static void ufs_fault_inject_execute_held_req(UfsRequest *hreq)
+{
+ UfsReqResult req_result;
+
+ req_result = ufs_exec_scsi_cmd(hreq);
+ if (req_result != UFS_REQUEST_NO_COMPLETE) {
+ ufs_complete_req(hreq, req_result);
+ }
+}
+
static void ufs_process_req(void *opaque)
{
UfsHc *u = opaque;
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 07/12] hw/ufs: Add ssu-timeout mode for power management recovery testing
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (5 preceding siblings ...)
2026-10-30 8:14 ` [PULL 06/12] hw/ufs: Handle task management requests for fault-injected held requests Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 08/12] docs/system/ufs: Update task management request support status Jeuk Kim
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Extend the experimental fault injection property x-fault-inject-mode
with a new "ssu-timeout" mode to facilitate automated testing of host
driver power management and Start Stop Unit (SSU) recovery handling.
When x-fault-inject-mode="ssu-timeout" is set and x-fault-inject-tag
is armed (e.g., 0xfffffffe for any tag, or a matching tag), the
controller only intercepts SCSI START_STOP (0x1B) commands sent to
the UFS Device Well-Known LUN (WLUN) and holds them without queuing
them to the SCSI layer, while letting non-SSU commands complete
normally. This triggers the host driver's 10-second SSU command
timeout and subsequent power management recovery routines (e.g.,
during suspend-to-idle or freeze transitions), verifying host error
recovery and link restoration paths.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/ufs.c | 19 +++++++++++++++++--
hw/ufs/ufs.h | 4 ++++
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 83d5fc24414..f8da7c9554c 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -862,6 +862,7 @@ static void ufs_fault_inject_sq_cleanup(UfsHc *u, UfsRequest *req)
static bool ufs_fault_inject_exec_req(UfsRequest *req)
{
UfsHc *u = req->hc;
+ uint8_t op;
if (likely(u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_NONE) ||
u->held_req != NULL ||
@@ -869,8 +870,21 @@ static bool ufs_fault_inject_exec_req(UfsRequest *req)
return false;
}
+ op = req->req_upiu.sc.cdb[0];
+ if (u->params.fault_inject_mode == UFS_FAULT_INJECT_SSU_TIMEOUT) {
+ if (op == START_STOP &&
+ req->req_upiu.header.lun == UFS_UPIU_UFS_DEVICE_WLUN &&
+ (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY ||
+ req->req_upiu.header.task_tag == u->active_fault_inject_tag)) {
+ u->active_fault_inject_tag = req->req_upiu.header.task_tag;
+ u->held_req = req;
+ trace_ufs_fault_inject_hold_req(req->req_upiu.header.task_tag, 0);
+ return true;
+ }
+ return false;
+ }
+
if (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY) {
- uint8_t op = req->req_upiu.sc.cdb[0];
uint64_t lba = 0;
bool is_match = false;
@@ -3226,6 +3240,7 @@ const QEnumLookup UfsFaultInjectMode_lookup = {
[UFS_FAULT_INJECT_ABORT_FAILED] = "abort-failed",
[UFS_FAULT_INJECT_IN_TRANSITION] = "in-transition",
[UFS_FAULT_INJECT_TIMEOUT] = "timeout",
+ [UFS_FAULT_INJECT_SSU_TIMEOUT] = "ssu-timeout",
},
.size = UFS_FAULT_INJECT_MODE_MAX,
};
@@ -3295,7 +3310,7 @@ const PropertyInfo ufs_prop_fault_inject_tag = {
const PropertyInfo ufs_prop_fault_inject_mode = {
.type = "UfsFaultInjectMode",
.description = "Fault injection mode: abort-success, abort-failed, "
- "in-transition, timeout",
+ "in-transition, timeout, ssu-timeout",
.enum_table = &UfsFaultInjectMode_lookup,
.get = qdev_propinfo_get_enum,
.set = qdev_propinfo_set_enum,
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 80bdaa1a2aa..7ddc5405465 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -110,12 +110,16 @@ typedef struct UfsLu {
* UFS_FAULT_INJECT_TIMEOUT: TMR ABORT_TASK does not complete or clear
* doorbell, triggering hardware-level TMR
* timeout.
+ * UFS_FAULT_INJECT_SSU_TIMEOUT: Intercepts SCSI START_STOP (0x1B) commands
+ * targeting the UFS Device WLUN when
+ * x-fault-inject-tag is armed.
*/
typedef enum UfsFaultInjectMode {
UFS_FAULT_INJECT_ABORT_SUCCESS = 0,
UFS_FAULT_INJECT_ABORT_FAILED,
UFS_FAULT_INJECT_IN_TRANSITION,
UFS_FAULT_INJECT_TIMEOUT,
+ UFS_FAULT_INJECT_SSU_TIMEOUT,
UFS_FAULT_INJECT_MODE_MAX,
} UfsFaultInjectMode;
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 08/12] docs/system/ufs: Update task management request support status
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (6 preceding siblings ...)
2026-10-30 8:14 ` [PULL 07/12] hw/ufs: Add ssu-timeout mode for power management recovery testing Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 09/12] tests/qtest/ufs-test: Add TMR and async MCQ SCSI submission helpers Jeuk Kim
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
The task management request (TMR) handling implemented for UFS supports
QUERY TASK and ABORT TASK, while other task management functions return
TASK MANAGEMENT FUNCTION NOT SUPPORTED. Update the documentation in
ufs.rst to reflect the current implementation by removing the outdated
note under nutmrs and updating the Limitations section accordingly.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
docs/system/devices/ufs.rst | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/docs/system/devices/ufs.rst b/docs/system/devices/ufs.rst
index d3482e92992..7e1fa671ab4 100644
--- a/docs/system/devices/ufs.rst
+++ b/docs/system/devices/ufs.rst
@@ -51,7 +51,6 @@ Host Controller Properties
``nutmrs`` (default 8, maximum 8)
Number of UTP task management request slots advertised to the guest.
- Task management requests are not processed yet (see Limitations).
``mcq`` (default off)
Advertise Multi-Circular Queue (MCQ) support. When off, the guest
@@ -81,8 +80,10 @@ Limitations
link startup, DME attribute access and hibernation are handled; DME
reads return fixed values, and DME writes, power mode changes and
hibernation do not change any link state.
-* UTP task management requests are not processed, so a guest that
- tries to abort a command or reset a logical unit gets no completion.
+* Only the ``QUERY TASK`` and ``ABORT TASK`` UTP task management
+ requests are supported; other task management functions such as
+ ``LOGICAL UNIT RESET`` return ``TASK MANAGEMENT FUNCTION NOT
+ SUPPORTED``.
* The Boot and RPMB well-known logical units only answer basic SCSI
commands such as INQUIRY; boot and RPMB data access are not emulated.
* The device cannot be migrated.
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 09/12] tests/qtest/ufs-test: Add TMR and async MCQ SCSI submission helpers
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (7 preceding siblings ...)
2026-10-30 8:14 ` [PULL 08/12] docs/system/ufs: Update task management request support status Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 10/12] tests/qtest/ufs-test: Add HCE reset unit test Jeuk Kim
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Add ufs_send_tmr() and ufs_mcq_send_scsi_cmd_async() helper functions
in preparation for UTMRL task management and QOM fault injection unit
tests.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
tests/qtest/ufs-test.c | 53 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index e8993e8c246..2914723430c 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -1899,6 +1899,59 @@ static void ufstest_hid(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static G_GNUC_UNUSED void ufs_send_tmr(QUfs *ufs, uint64_t utmrlba, int slot,
+ uint8_t tm_func, uint8_t lun,
+ uint32_t task_tag,
+ UtpTaskReqDesc *utrd_out)
+{
+ uint64_t desc_addr = utmrlba + slot * sizeof(UtpTaskReqDesc);
+ UtpTaskReqDesc desc = { 0 };
+
+ desc.header.dword_0 = cpu_to_le32(UFS_UTP_REQ_DESC_INT_CMD);
+ desc.header.dword_2 = cpu_to_le32(UFS_OCS_INVALID_COMMAND_STATUS);
+
+ desc.upiu_req.req_header.trans_type = UFS_UPIU_TRANSACTION_TASK_REQ;
+ desc.upiu_req.req_header.query_func = tm_func;
+ desc.upiu_req.req_header.lun = lun;
+ desc.upiu_req.req_header.task_tag = 0x10 + slot;
+ desc.upiu_req.input_param1 = cpu_to_be32(lun);
+ desc.upiu_req.input_param2 = cpu_to_be32(task_tag);
+
+ qtest_memwrite(ufs->dev.bus->qts, desc_addr, &desc, sizeof(desc));
+
+ ufs_wreg(ufs, A_UTMRLDBR, 1 << slot);
+ ufs_wait_for_irq(ufs);
+ g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTMRCS));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTMRCS, 1));
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLDBR) & (1 << slot), ==, 0);
+
+ qtest_memread(ufs->dev.bus->qts, desc_addr, utrd_out, sizeof(*utrd_out));
+}
+
+static G_GNUC_UNUSED int ufs_mcq_send_scsi_cmd_async(QUfs *ufs, uint8_t lun,
+ const uint8_t *cdb)
+{
+ int cmd_desc_slot = alloc_cmd_desc_slot(ufs);
+ uint64_t req_upiu_addr =
+ ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE;
+ UtpUpiuReq req_upiu = { 0 };
+ UtpTransferReqDesc utrd;
+
+ req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_COMMAND;
+ req_upiu.header.flags = UFS_UPIU_CMD_FLAGS_NONE;
+ req_upiu.header.lun = lun;
+ req_upiu.header.task_tag = cmd_desc_slot;
+ memcpy(req_upiu.sc.cdb, cdb, UFS_CDB_SIZE);
+ qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu,
+ sizeof(req_upiu));
+
+ utrd = ufs_build_req_utrd(req_upiu_addr, UFS_UTP_NO_DATA_TRANSFER, 0);
+ __ufs_send_transfer_request_mcq_async(ufs, lun, &utrd);
+
+ return cmd_desc_slot;
+}
+
+
static void ufs_register_nodes(void)
{
const char *arch;
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 10/12] tests/qtest/ufs-test: Add HCE reset unit test
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (8 preceding siblings ...)
2026-10-30 8:14 ` [PULL 09/12] tests/qtest/ufs-test: Add TMR and async MCQ SCSI submission helpers Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 11/12] tests/qtest/ufs-test: Add MCQ runtime register and TMR handling test Jeuk Kim
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Add ufs_test_hce_reset() to verify HCE 1->0->1 controller reset
behavior, checking that UTMRLRSR and dynamic MCQ queue configuration and
operational registers reset to 0 while host-configured registers (IE,
UTMRLBA/UTMRLBAU, MCQCONFIG) and static capability offsets (SQDAO/CQDAO)
are preserved and subsequent I/O succeeds.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
tests/qtest/ufs-test.c | 79 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 2914723430c..9270e3b137a 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -1952,6 +1952,84 @@ static G_GNUC_UNUSED int ufs_mcq_send_scsi_cmd_async(QUfs *ufs, uint8_t lun,
}
+static void ufs_test_hce_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ uint32_t mcqcap, qcfgptr, mcq_reg_offset;
+ uint32_t orig_sqdao, orig_cqdao, orig_ie;
+ enum UtpOcsCodes ocs;
+ UtpUpiuRsp rsp_upiu;
+
+ ufs_init(ufs, alloc);
+ g_assert_true(ufs->support_mcq);
+
+ mcqcap = ufs_rreg(ufs, A_MCQCAP);
+ qcfgptr = FIELD_EX32(mcqcap, MCQCAP, QCFGPTR);
+ mcq_reg_offset = qcfgptr * 0x200 + TEST_QID * 0x40;
+
+ orig_sqdao = ufs_rreg(ufs, mcq_reg_offset + A_SQDAO);
+ orig_cqdao = ufs_rreg(ufs, mcq_reg_offset + A_CQDAO);
+ orig_ie = ufs_rreg(ufs, A_IE);
+ g_assert_cmpuint(orig_sqdao, !=, 0);
+ g_assert_cmpuint(orig_cqdao, !=, 0);
+ g_assert_cmpuint(orig_ie, !=, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_SQATTR), !=, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_CQATTR), !=, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_sqdao + 0x4), !=, 0);
+
+ ufs_wreg(ufs, A_UTMRLBA, 0x1000);
+ ufs_wreg(ufs, A_UTMRLBAU, 0x1);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+ ufs_wreg(ufs, A_MCQCONFIG, 0x100);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLRSR), ==, 1);
+
+ /* Write HCE=0 to trigger controller reset */
+ ufs_wreg(ufs, A_HCE, 0);
+ g_assert_cmpuint(FIELD_EX32(ufs_rreg(ufs, A_HCE), HCE, HCE), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, A_HCS), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLRSR), ==, 0);
+
+ /* Verify host-configured registers remain preserved across HCE=0 */
+ g_assert_cmpuint(ufs_rreg(ufs, A_IE), ==, orig_ie);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLBA), ==, 0x1000);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLBAU), ==, 0x1);
+ g_assert_cmpuint(ufs_rreg(ufs, A_MCQCONFIG), ==, 0x100);
+
+ /* Verify dynamic queue configuration registers reset to 0 */
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_SQATTR), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_SQLBA), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_CQATTR), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_CQLBA), ==, 0);
+
+ /* Verify dynamic MCQ operational registers reset to 0 on HCE=0 */
+ g_assert_cmpuint(ufs_rreg(ufs, orig_sqdao), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_sqdao + 0x4), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_cqdao), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_cqdao + 0x4), ==, 0);
+
+ /* Verify read-only capability offsets (SQDAO/CQDAO) remain intact */
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_SQDAO), ==, orig_sqdao);
+ g_assert_cmpuint(ufs_rreg(ufs, mcq_reg_offset + A_CQDAO), ==, orig_cqdao);
+
+ /* Re-enable HCE=1 and verify dynamic queue status/pointers remain 0 */
+ ufs_wreg(ufs, A_HCE, FIELD_DP32(0, HCE, HCE, 1));
+ g_assert_cmpuint(FIELD_EX32(ufs_rreg(ufs, A_HCE), HCE, HCE), ==, 1);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_sqdao), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_sqdao + 0x4), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_cqdao), ==, 0);
+ g_assert_cmpuint(ufs_rreg(ufs, orig_cqdao + 0x4), ==, 0);
+
+ /* Free previous guest allocations before re-initializing */
+ ufs_exit(ufs, alloc);
+
+ /* Re-enable HCE=1, re-init controller, and verify normal I/O succeeds */
+ ufs_init(ufs, alloc);
+ ocs = ufs_send_nop_out(ufs, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+
+ ufs_exit(ufs, alloc);
+}
+
static void ufs_register_nodes(void)
{
const char *arch;
@@ -2012,6 +2090,7 @@ static void ufs_register_nodes(void)
&io_test_opts);
qos_add_test("query-desciptor", "ufs", ufstest_query_desc_request,
&io_test_opts);
+ qos_add_test("hce-reset", "ufs", ufs_test_hce_reset, &mcq_test_opts);
qos_add_test("wb-init", "ufs", ufstest_wb_init, &wb_test_opts);
qos_add_test("wb-read-write", "ufs", ufstest_wb_read_write, &wb_test_opts);
qos_add_test("hid", "ufs", ufstest_hid, &io_test_opts);
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 11/12] tests/qtest/ufs-test: Add MCQ runtime register and TMR handling test
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (9 preceding siblings ...)
2026-10-30 8:14 ` [PULL 10/12] tests/qtest/ufs-test: Add HCE reset unit test Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 8:14 ` [PULL 12/12] tests/qtest/ufs-test: Add QOM fault injection task abort test Jeuk Kim
2026-10-30 12:32 ` [PULL 00/12] UFS command fixes and fault injection tests Richard Henderson
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Add ufs_test_tmr_handling() to verify UTMRL UFS_QUERY_TASK execution
(both interrupt-driven I=1 and polling I=0 UTMRDs), SQCTI/CQIACR
register access, SQRTC/SQRTS queue stop/start and self-clearing ICU
transitions across running, stopped, and disabled SQ states, nullified
SQE (CT = 0xF) completion with UFS_OCS_ABORTED, and CQIS.TEPS
write-1-to-clear synchronization with IS.CQES.
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
tests/qtest/ufs-test.c | 161 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 160 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 9270e3b137a..78feeed8599 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -1899,7 +1899,7 @@ static void ufstest_hid(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
-static G_GNUC_UNUSED void ufs_send_tmr(QUfs *ufs, uint64_t utmrlba, int slot,
+static void ufs_send_tmr(QUfs *ufs, uint64_t utmrlba, int slot,
uint8_t tm_func, uint8_t lun,
uint32_t task_tag,
UtpTaskReqDesc *utrd_out)
@@ -2030,6 +2030,164 @@ static void ufs_test_hce_reset(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static void ufs_test_tmr_handling(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ uint64_t utmrlba, desc_addr, req_upiu_addr, rsp_upiu_addr;
+ UtpTaskReqDesc desc = { 0 }, utrd_result;
+ UtpTransferReqDesc null_utrd;
+ UtpUpiuReq req_upiu = { 0 };
+ UfsCqEntry cqe;
+ uint32_t mcq_reg_offset, sqdao, cqisao, sqrts, sqrtc, orig_sqattr;
+ int null_slot;
+ enum UtpOcsCodes ocs;
+ UtpUpiuRsp rsp_upiu;
+
+ ufs_init(ufs, alloc);
+ g_assert_true(ufs->support_mcq);
+
+ /* Set up UTMRL */
+ utmrlba = guest_alloc(alloc, 8 * sizeof(UtpTaskReqDesc));
+ ufs_wreg(ufs, A_UTMRLBA, utmrlba & 0xffffffff);
+ ufs_wreg(ufs, A_UTMRLBAU, utmrlba >> 32);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+
+ /* Issue UFS_QUERY_TASK for inactive tag 15 (Interrupt bit I=1) */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_QUERY_TASK, 0, 15, &utrd_result);
+ g_assert_cmpuint(le32_to_cpu(utrd_result.header.dword_2) & UFS_MASK_OCS,
+ ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL);
+
+ /* Issue polling UTMRD with I=0 (slot 1): clears doorbell without UTMRCS */
+ desc_addr = utmrlba + 1 * sizeof(UtpTaskReqDesc);
+ desc.header.dword_0 = 0;
+ desc.header.dword_2 = cpu_to_le32(UFS_OCS_INVALID_COMMAND_STATUS);
+ desc.upiu_req.req_header.trans_type = UFS_UPIU_TRANSACTION_TASK_REQ;
+ desc.upiu_req.req_header.query_func = UFS_QUERY_TASK;
+ desc.upiu_req.req_header.lun = 0;
+ desc.upiu_req.req_header.task_tag = 0x11;
+ desc.upiu_req.input_param1 = cpu_to_be32(0);
+ desc.upiu_req.input_param2 = cpu_to_be32(15);
+ qtest_memwrite(ufs->dev.bus->qts, desc_addr, &desc, sizeof(desc));
+
+ ufs_wreg(ufs, A_UTMRLDBR, 1 << 1);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLDBR) & (1 << 1), ==, 0);
+ g_assert_cmpuint(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTMRCS), ==, 0);
+ qtest_memread(ufs->dev.bus->qts, desc_addr, &utrd_result,
+ sizeof(utrd_result));
+ g_assert_cmpuint(le32_to_cpu(utrd_result.header.dword_2) & UFS_MASK_OCS,
+ ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL);
+
+ /* Verify SQCTI, CQIACR, SQRTS/SQRTC, and CQIS/IS.CQES W1C sync */
+ sqdao = ufs->sqdao[TEST_QID];
+ mcq_reg_offset =
+ FIELD_EX32(ufs_rreg(ufs, A_MCQCAP), MCQCAP, QCFGPTR) * 0x200 +
+ TEST_QID * 0x40;
+ cqisao = ufs_rreg(ufs, mcq_reg_offset + A_CQISAO);
+
+ /* SQCTI and CQIACR read/write */
+ ufs_wreg(ufs, sqdao + A_SQCTI, (1 << 8) | 0x2b);
+ g_assert_cmpuint(ufs_rreg(ufs, sqdao + A_SQCTI), ==, (1 << 8) | 0x2b);
+ ufs_wreg(ufs, cqisao + offsetof(UfsMcqCqIntReg, iacr), 0x101);
+ g_assert_cmpuint(ufs_rreg(ufs, cqisao + offsetof(UfsMcqCqIntReg, iacr)),
+ ==, 0x101);
+
+ /* SQ_ICU while running (STS=0) -> sets SQRTS.CUS=1, RTC=2 */
+ ufs_wreg(ufs, sqdao + A_SQRTC, FIELD_DP32(0, SQRTC, ICU, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ sqrtc = ufs_rreg(ufs, sqdao + A_SQRTC);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, STS), ==, 0);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, RTC), ==, 2);
+ g_assert_cmpuint(FIELD_EX32(sqrtc, SQRTC, ICU), ==, 0);
+
+ /* SQ_STOP -> sets SQRTS.STS = 1 */
+ ufs_wreg(ufs, sqdao + A_SQRTC, FIELD_DP32(0, SQRTC, STOP, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, STS), ==, 1);
+
+ /*
+ * SQ_ICU while stopped (no matching req) -> sets SQRTS.CUS=1, RTC=1 and
+ * self-clears ICU.
+ */
+ ufs_wreg(ufs, sqdao + A_SQRTC,
+ FIELD_DP32(0, SQRTC, STOP, 1) | FIELD_DP32(0, SQRTC, ICU, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ sqrtc = ufs_rreg(ufs, sqdao + A_SQRTC);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, STS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, RTC), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrtc, SQRTC, ICU), ==, 0);
+
+ /*
+ * Queue a nullified SQE (CT = 0xF) while SQ is stopped, then resume SQ
+ * (STOP 1->0): SQRTS.STS clears to 0 while SQRTS.CUS remains 1, and the
+ * nullified SQE posts CQE with UFS_OCS_ABORTED without writing rsp_upiu.
+ */
+ null_slot = alloc_cmd_desc_slot(ufs);
+ req_upiu_addr =
+ ufs->cmd_desc_addr + null_slot * UTP_COMMAND_DESCRIPTOR_SIZE;
+ rsp_upiu_addr = req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET;
+ req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_NOP_OUT;
+ req_upiu.header.task_tag = null_slot;
+ qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu,
+ sizeof(req_upiu));
+ memset(&rsp_upiu, 0xa5, sizeof(rsp_upiu));
+ qtest_memwrite(ufs->dev.bus->qts, rsp_upiu_addr, &rsp_upiu,
+ sizeof(rsp_upiu));
+
+ null_utrd = ufs_build_req_utrd(req_upiu_addr, UFS_UTP_NO_DATA_TRANSFER, 0);
+ null_utrd.header.dword_0 = cpu_to_le32((UFS_UTP_CMD_TYPE_NULL << 28) |
+ UFS_UTP_NO_DATA_TRANSFER |
+ UFS_UTP_REQ_DESC_INT_CMD);
+ __ufs_send_transfer_request_mcq_async(ufs, 0, &null_utrd);
+
+ /* SQ_START (STOP 1->0) -> clears SQRTS.STS = 0 while preserving CUS = 1 */
+ ufs_wreg(ufs, sqdao + A_SQRTC, 0);
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, STS), ==, 0);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+
+ ufs_wait_for_irq(ufs);
+ g_assert_cmpuint(ufs_mcq_poll_cq(ufs, &cqe, 1), ==, 1);
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_ABORTED);
+ g_assert_cmpuint(cqe.task_tag, ==, null_slot);
+ qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, &rsp_upiu,
+ sizeof(rsp_upiu));
+ g_assert_cmpuint(rsp_upiu.header.trans_type, ==, 0xa5);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ release_cmd_desc_slot(ufs, null_slot);
+
+ /* Verify MCQ command sets CQIS.TEPS=1 and W1C clears CQIS.TEPS & IS.CQES */
+ ocs = ufs_send_nop_out(ufs, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(FIELD_EX32(ufs_rreg(ufs, cqisao + A_CQIS), CQIS, TEPS),
+ ==, 1);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ g_assert_cmpuint(FIELD_EX32(ufs_rreg(ufs, cqisao + A_CQIS), CQIS, TEPS),
+ ==, 0);
+ g_assert_cmpuint(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, CQES), ==, 0);
+
+ /* SQ_ICU on disabled SQ (SQEN=0) -> sets SQRTS.CUS=1, RTC=3 */
+ orig_sqattr = ufs_rreg(ufs, mcq_reg_offset + A_SQATTR);
+ ufs_wreg(ufs, mcq_reg_offset + A_SQATTR, 0);
+ ufs_wreg(ufs, sqdao + A_SQRTC, FIELD_DP32(0, SQRTC, ICU, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, RTC), ==, 3);
+ ufs_wreg(ufs, mcq_reg_offset + A_SQATTR, orig_sqattr);
+
+ guest_free(alloc, utmrlba);
+ ufs_exit(ufs, alloc);
+}
+
static void ufs_register_nodes(void)
{
const char *arch;
@@ -2091,6 +2249,7 @@ static void ufs_register_nodes(void)
qos_add_test("query-desciptor", "ufs", ufstest_query_desc_request,
&io_test_opts);
qos_add_test("hce-reset", "ufs", ufs_test_hce_reset, &mcq_test_opts);
+ qos_add_test("tmr-handling", "ufs", ufs_test_tmr_handling, &mcq_test_opts);
qos_add_test("wb-init", "ufs", ufstest_wb_init, &wb_test_opts);
qos_add_test("wb-read-write", "ufs", ufstest_wb_read_write, &wb_test_opts);
qos_add_test("hid", "ufs", ufstest_hid, &io_test_opts);
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PULL 12/12] tests/qtest/ufs-test: Add QOM fault injection task abort test
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (10 preceding siblings ...)
2026-10-30 8:14 ` [PULL 11/12] tests/qtest/ufs-test: Add MCQ runtime register and TMR handling test Jeuk Kim
@ 2026-10-30 8:14 ` Jeuk Kim
2026-10-30 12:32 ` [PULL 00/12] UFS command fixes and fault injection tests Richard Henderson
12 siblings, 0 replies; 14+ messages in thread
From: Jeuk Kim @ 2026-10-30 8:14 UTC (permalink / raw)
To: qemu-devel, richard.henderson
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
From: Stanley Jhu <stanleyjhu@google.com>
Add ufs_test_fault_injection_abort() to validate runtime QOM fault
injection modes via x-fault-inject-tag and x-fault-inject-mode
(abort-success with held-request tag overwrite rejection, two-stage
UFS_ABORT_TASK + SQRTC.ICU and LUN matching, in-transition completion,
timeout with UTMRLCLR clear-on-zero verification, abort-failed
rejection, and ssu-timeout WLUN START_STOP filtering).
Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
tests/qtest/ufs-test.c | 681 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 680 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 78feeed8599..1f86d834ac9 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -1928,7 +1928,7 @@ static void ufs_send_tmr(QUfs *ufs, uint64_t utmrlba, int slot,
qtest_memread(ufs->dev.bus->qts, desc_addr, utrd_out, sizeof(*utrd_out));
}
-static G_GNUC_UNUSED int ufs_mcq_send_scsi_cmd_async(QUfs *ufs, uint8_t lun,
+static int ufs_mcq_send_scsi_cmd_async(QUfs *ufs, uint8_t lun,
const uint8_t *cdb)
{
int cmd_desc_slot = alloc_cmd_desc_slot(ufs);
@@ -1951,6 +1951,66 @@ static G_GNUC_UNUSED int ufs_mcq_send_scsi_cmd_async(QUfs *ufs, uint8_t lun,
return cmd_desc_slot;
}
+static int ufs_mcq_send_write_async(QUfs *ufs, uint8_t lun,
+ const uint8_t *cdb,
+ const uint8_t *data_in,
+ size_t data_in_len)
+{
+ QTestState *qts = ufs->dev.bus->qts;
+ int cmd_desc_slot = alloc_cmd_desc_slot(ufs);
+ uint64_t req_upiu_addr =
+ ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE;
+ UfshcdSgEntry entry = { 0 };
+ UtpUpiuReq req_upiu = { 0 };
+ UtpTransferReqDesc utrd;
+
+ g_assert_true(ufs->support_mcq);
+ g_assert_cmpuint(data_in_len, <=, PRD_ENTRY_DATA_SIZE);
+ qtest_memwrite(qts, ufs->data_buffer_addr, data_in, data_in_len);
+ entry.addr = cpu_to_le64(ufs->data_buffer_addr);
+ entry.size = cpu_to_le32(data_in_len - 1);
+ qtest_memwrite(qts, req_upiu_addr + UTP_PRDT_UPIU_OFFSET, &entry,
+ sizeof(entry));
+
+ req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_COMMAND;
+ req_upiu.header.flags = UFS_UPIU_CMD_FLAGS_WRITE;
+ req_upiu.header.lun = lun;
+ req_upiu.header.task_tag = cmd_desc_slot;
+ req_upiu.sc.exp_data_transfer_len = cpu_to_be32(data_in_len);
+ memcpy(req_upiu.sc.cdb, cdb, UFS_CDB_SIZE);
+ qtest_memwrite(qts, req_upiu_addr, &req_upiu, sizeof(req_upiu));
+
+ utrd = ufs_build_req_utrd(req_upiu_addr, UFS_UTP_HOST_TO_DEVICE, 1);
+ __ufs_send_transfer_request_mcq_async(ufs, lun, &utrd);
+ return cmd_desc_slot;
+}
+
+static int ufs_send_scsi_cmd_doorbell_async(QUfs *ufs, uint8_t lun,
+ const uint8_t *cdb)
+{
+ int cmd_desc_slot = alloc_cmd_desc_slot(ufs);
+ uint64_t req_upiu_addr =
+ ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE;
+ uint64_t utrd_addr =
+ ufs->utrlba + cmd_desc_slot * sizeof(UtpTransferReqDesc);
+ UtpUpiuReq req_upiu = { 0 };
+ UtpTransferReqDesc utrd;
+
+ req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_COMMAND;
+ req_upiu.header.flags = UFS_UPIU_CMD_FLAGS_NONE;
+ req_upiu.header.lun = lun;
+ req_upiu.header.task_tag = cmd_desc_slot;
+ memcpy(req_upiu.sc.cdb, cdb, UFS_CDB_SIZE);
+ qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu,
+ sizeof(req_upiu));
+
+ utrd = ufs_build_req_utrd(req_upiu_addr, UFS_UTP_NO_DATA_TRANSFER, 0);
+ qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd));
+
+ ufs_wreg(ufs, A_UTRLDBR, 1 << cmd_desc_slot);
+ return cmd_desc_slot;
+}
+
static void ufs_test_hce_reset(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -2188,6 +2248,621 @@ static void ufs_test_tmr_handling(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+#define UFS_FAULT_INJECT_TAG_NONE 0xffffffffU
+#define UFS_FAULT_INJECT_TAG_ANY 0xfffffffeU
+
+static void ufs_test_fault_injection_abort(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ QTestState *qts = ufs->dev.bus->qts;
+ uint64_t utmrlba, desc_addr;
+ UtpTaskReqDesc utrd, utrd_result;
+ const uint8_t test_unit_ready_cdb[UFS_CDB_SIZE] = { TEST_UNIT_READY };
+ const uint8_t read10_lba512_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ const uint8_t start_stop_cdb[UFS_CDB_SIZE] = { START_STOP };
+ const uint8_t request_sense_cdb[UFS_CDB_SIZE] = { REQUEST_SENSE };
+ const uint8_t write10_cdb[UFS_CDB_SIZE] = {
+ WRITE_10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00
+ };
+ const uint8_t read10_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00
+ };
+ const uint8_t read10_lba0_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ const uint8_t read16_lba0_cdb[UFS_CDB_SIZE] = {
+ READ_16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ const uint8_t read16_lba512_cdb[UFS_CDB_SIZE] = {
+ READ_16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t write_buf[4096];
+ uint8_t read_buf[4096];
+ UtpUpiuRsp read_rsp;
+ enum UtpOcsCodes ocs;
+ uint32_t sqdao, cqdao, mcq_reg_offset, cqisao, sqrts, cqhp, cqtp;
+ UfsCqEntry cqe;
+ int slot;
+ QDict *rsp;
+
+ ufs_init(ufs, alloc);
+ g_assert_true(ufs->support_mcq);
+
+ utmrlba = guest_alloc(alloc, 8 * sizeof(UtpTaskReqDesc));
+ ufs_wreg(ufs, A_UTMRLBA, utmrlba & 0xffffffff);
+ ufs_wreg(ufs, A_UTMRLBAU, utmrlba >> 32);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+
+ sqdao = ufs->sqdao[TEST_QID];
+ cqdao = ufs->cqdao[TEST_QID];
+ mcq_reg_offset =
+ FIELD_EX32(ufs_rreg(ufs, A_MCQCAP), MCQCAP, QCFGPTR) * 0x200 +
+ TEST_QID * 0x40;
+ cqisao = ufs_rreg(ufs, mcq_reg_offset + A_CQISAO);
+
+ /*
+ * Case 1: x-fault-inject-mode = abort-success (two-stage MCQ abort +
+ * SQRTC.ICU).
+ */
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'abort-success' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, test_unit_ready_cdb);
+ qtest_clock_step(qts, 100);
+
+ /* Issue UFS_QUERY_TASK -> assert Target Success & SUCCEEDED (0x08) */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_QUERY_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED);
+
+ /* Verify x-fault-inject-tag cannot be overwritten while a req is held */
+ rsp = qtest_qmp(qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': 7 } }");
+ g_assert_true(qdict_haskey(rsp, "error"));
+ qobject_unref(rsp);
+
+ /* Issue UFS_ABORT_TASK -> assert COMPL (0x00) and tag cleared */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_ABORT_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL);
+
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==,
+ UFS_FAULT_INJECT_TAG_NONE);
+ qobject_unref(rsp);
+
+ /*
+ * UFS_ABORT_TASK alone must NOT post a CQE
+ * before SQRTC.ICU is issued.
+ */
+ cqhp = ufs_rreg(ufs, cqdao);
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, ==, cqhp);
+
+ /* Stop SQ and test SQRTC.ICU with a mismatched LUN (lun=1 vs lun=0) */
+ ufs_wreg(ufs, sqdao + A_SQRTC, FIELD_DP32(0, SQRTC, STOP, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, STS), ==, 1);
+
+ ufs_wreg(ufs, sqdao + A_SQCTI, (1 << 8) | slot);
+ ufs_wreg(ufs, sqdao + A_SQRTC,
+ FIELD_DP32(0, SQRTC, STOP, 1) | FIELD_DP32(0, SQRTC, ICU, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, RTC), ==, 1);
+ g_assert_cmpuint(ufs_rreg(ufs, cqdao + 0x4), ==, cqhp);
+
+ /* Issue SQRTC.ICU with matching SQCTI (lun=0) -> posts ABORTED CQE */
+ ufs_wreg(ufs, sqdao + A_SQCTI, (0 << 8) | slot);
+ ufs_wreg(ufs, sqdao + A_SQRTC,
+ FIELD_DP32(0, SQRTC, STOP, 1) | FIELD_DP32(0, SQRTC, ICU, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, RTC), ==, 0);
+
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, !=, cqhp);
+ qtest_memread(qts, ufs->cqlba[TEST_QID] + cqhp, &cqe, sizeof(cqe));
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_ABORTED);
+ g_assert_cmpuint(cqe.task_tag, ==, slot);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ ufs_wreg(ufs, sqdao + A_SQRTC, 0);
+ release_cmd_desc_slot(ufs, slot);
+
+ /*
+ * Direct SQRTC.ICU without prior UFS_ABORT_TASK:
+ * ufs_fault_inject_sq_cleanup() must clear held_req and active tag.
+ */
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'abort-success' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, test_unit_ready_cdb);
+ qtest_clock_step(qts, 100);
+
+ /* Abort directly via SQRTC.ICU */
+ ufs_wreg(ufs, sqdao + A_SQRTC, FIELD_DP32(0, SQRTC, STOP, 1));
+ ufs_wreg(ufs, sqdao + A_SQCTI, (0 << 8) | slot);
+ ufs_wreg(ufs, sqdao + A_SQRTC,
+ FIELD_DP32(0, SQRTC, STOP, 1) | FIELD_DP32(0, SQRTC, ICU, 1));
+ sqrts = ufs_rreg(ufs, sqdao + A_SQRTS);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, CUS), ==, 1);
+ g_assert_cmpuint(FIELD_EX32(sqrts, SQRTS, RTC), ==, 0);
+
+ /* Verify ufs_fault_inject_sq_cleanup cleared the held request */
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==,
+ UFS_FAULT_INJECT_TAG_NONE);
+ qobject_unref(rsp);
+
+ /* Verify setting tag now succeeds (not blocked by held_req) */
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': 7 } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %u } }",
+ UFS_FAULT_INJECT_TAG_NONE);
+
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ ufs_wreg(ufs, sqdao + A_SQRTC, 0);
+ release_cmd_desc_slot(ufs, slot);
+
+ /*
+ * Case 2: x-fault-inject-mode = in-transition.
+ * WRITE a pattern while held -> QUERY TASK (in-transition) releases
+ * and really executes the request -> READ back shows the pattern.
+ */
+ memset(write_buf, 0x5a, sizeof(write_buf));
+ memset(read_buf, 0, sizeof(read_buf));
+
+ /* Clear Unit Attention on LUN 1 before I/O */
+ ufs_send_scsi_command(ufs, 1, request_sense_cdb, NULL, 0, read_buf,
+ sizeof(read_buf), &read_rsp);
+
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'in-transition' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_write_async(ufs, 1, write10_cdb, write_buf,
+ sizeof(write_buf));
+ qtest_clock_step(qts, 100);
+
+ /* Command is held; no CQE posted yet */
+ g_assert_cmpuint(ufs_rreg(ufs, cqdao + 0x4), ==, cqhp);
+
+ /* QUERY TASK releases the held request and lets it actually execute */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_QUERY_TASK, 1, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED);
+
+ /* Wait for the released request to finish and post its CQE */
+ ufs_wait_for_irq(ufs);
+ g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, CQES));
+
+ /* Verify completion CQE was posted with real status (SUCCESS) */
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, !=, cqhp);
+ qtest_memread(qts, ufs->cqlba[TEST_QID] + cqhp, &cqe, sizeof(cqe));
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_SUCCESS);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ release_cmd_desc_slot(ufs, slot);
+
+ /* READ back LBA 512 and verify the pattern actually reached the LU */
+ ocs = ufs_send_scsi_command(ufs, 1, read10_cdb, NULL, 0, read_buf,
+ sizeof(read_buf), &read_rsp);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpmem(read_buf, sizeof(read_buf), write_buf, sizeof(write_buf));
+
+ /*
+ * Case 3: x-fault-inject-mode = timeout + A_UTMRLCLR clear-on-zero.
+ * UFS_ABORT_TASK times out, leaving UTMRLDBR bit 2 set.
+ *
+ * UFSHCI defines UTMRLCLR as write-0-to-clear: writing 0 to a bit clears
+ * that slot in UTMRLDBR, writing 1 leaves it unchanged.
+ */
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'timeout' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, test_unit_ready_cdb);
+ qtest_clock_step(qts, 100);
+
+ memset(&utrd, 0, sizeof(utrd));
+ utrd.header.dword_0 = cpu_to_le32(UFS_UTP_REQ_DESC_INT_CMD);
+ utrd.header.dword_2 = cpu_to_le32(UFS_OCS_INVALID_COMMAND_STATUS);
+ utrd.upiu_req.req_header.trans_type = UFS_UPIU_TRANSACTION_TASK_REQ;
+ utrd.upiu_req.req_header.query_func = UFS_ABORT_TASK;
+ utrd.upiu_req.req_header.lun = 0;
+ utrd.upiu_req.req_header.task_tag = 2;
+ utrd.upiu_req.input_param1 = cpu_to_be32(0);
+ utrd.upiu_req.input_param2 = cpu_to_be32(slot);
+ desc_addr = utmrlba + 2 * sizeof(UtpTaskReqDesc);
+ qtest_memwrite(qts, desc_addr, &utrd, sizeof(utrd));
+
+ ufs_wreg(ufs, A_UTMRLDBR, 1 << 2);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLDBR) & (1 << 2), ==, 1 << 2);
+
+ /*
+ * Writing 1s to A_UTMRLCLR indicates no change per UFSHCI spec,
+ * so it must NOT clear UTMRLDBR bit 2.
+ */
+ ufs_wreg(ufs, A_UTMRLCLR, 0xffffffffU);
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLDBR) & (1 << 2), ==, 1 << 2);
+
+ /*
+ * Writing 0 at bit 2 (~(1 << 2)) to A_UTMRLCLR indicates slot cleared,
+ * so it MUST clear bit 2.
+ */
+ ufs_wreg(ufs, A_UTMRLCLR, ~(1U << 2));
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTMRLDBR) & (1 << 2), ==, 0);
+
+ ufs_wreg(ufs, A_HCE, 0);
+ release_cmd_desc_slot(ufs, slot);
+ ufs_exit(ufs, alloc);
+ ufs_init(ufs, alloc);
+ ufs_wreg(ufs, A_UTMRLBA, utmrlba & 0xffffffff);
+ ufs_wreg(ufs, A_UTMRLBAU, utmrlba >> 32);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+
+ /*
+ * Case 4: x-fault-inject-mode = abort-failed.
+ */
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'abort-failed' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, test_unit_ready_cdb);
+ qtest_clock_step(qts, 100);
+
+ /* Issue UFS_QUERY_TASK -> assert Target Success & SUCCEEDED (0x08) */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_QUERY_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED);
+
+ /* Issue UFS_ABORT_TASK -> assert Target Failure & FAILED (0x05) */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_ABORT_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_FAIL);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_FAILED);
+
+ /* Reset controller to clear held request cleanly */
+ ufs_wreg(ufs, A_HCE, 0);
+ release_cmd_desc_slot(ufs, slot);
+ ufs_exit(ufs, alloc);
+ ufs_init(ufs, alloc);
+ ufs_wreg(ufs, A_UTMRLBA, utmrlba & 0xffffffff);
+ ufs_wreg(ufs, A_UTMRLBAU, utmrlba >> 32);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+
+ /*
+ * Case 5: x-fault-inject-mode = ssu-timeout.
+ * Non-SSU commands on LU 0 complete normally even with a matching
+ * specific tag or UFS_FAULT_INJECT_TAG_ANY, whereas START_STOP to
+ * UFS_UPIU_UFS_DEVICE_WLUN is held.
+ */
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'ssu-timeout' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, test_unit_ready_cdb);
+ qtest_clock_step(qts, 100);
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, !=, cqhp);
+ qtest_memread(qts, ufs->cqlba[TEST_QID] + cqhp, &cqe, sizeof(cqe));
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_SUCCESS);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ release_cmd_desc_slot(ufs, slot);
+
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %u } }",
+ UFS_FAULT_INJECT_TAG_ANY);
+
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, read10_lba512_cdb);
+ qtest_clock_step(qts, 100);
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, !=, cqhp);
+ qtest_memread(qts, ufs->cqlba[TEST_QID] + cqhp, &cqe, sizeof(cqe));
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_SUCCESS);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ release_cmd_desc_slot(ufs, slot);
+
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, UFS_UPIU_UFS_DEVICE_WLUN,
+ start_stop_cdb);
+ qtest_clock_step(qts, 100);
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, ==, cqhp);
+
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_QUERY_TASK, UFS_UPIU_UFS_DEVICE_WLUN,
+ slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED);
+
+ ufs_wreg(ufs, A_HCE, 0);
+ release_cmd_desc_slot(ufs, slot);
+ ufs_exit(ufs, alloc);
+ ufs_init(ufs, alloc);
+ ufs_wreg(ufs, A_UTMRLBA, utmrlba & 0xffffffff);
+ ufs_wreg(ufs, A_UTMRLBAU, utmrlba >> 32);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+
+ /*
+ * Case 6: x-fault-inject-tag = ANY with READ_10 and READ_16.
+ * Commands with LBA < 512 must complete normally (not held).
+ * Commands with LBA >= 512 must be held.
+ */
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'abort-success' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %u } }",
+ UFS_FAULT_INJECT_TAG_ANY);
+
+ /* (a) READ_10 LBA 0 (< 512) -> completes normally, NOT held */
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, read10_lba0_cdb);
+ qtest_clock_step(qts, 100);
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, !=, cqhp);
+ qtest_memread(qts, ufs->cqlba[TEST_QID] + cqhp, &cqe, sizeof(cqe));
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_SUCCESS);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ release_cmd_desc_slot(ufs, slot);
+
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==,
+ UFS_FAULT_INJECT_TAG_ANY);
+ qobject_unref(rsp);
+
+ /* (b) READ_10 LBA 512 (>= 512) -> HELD */
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, read10_lba512_cdb);
+ qtest_clock_step(qts, 100);
+ g_assert_cmpuint(ufs_rreg(ufs, cqdao + 0x4), ==, cqhp);
+
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==, slot);
+ qobject_unref(rsp);
+
+ /* Abort the held READ_10 */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_ABORT_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL);
+ release_cmd_desc_slot(ufs, slot);
+
+ /* Re-arm ANY for 16-byte testing */
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %u } }",
+ UFS_FAULT_INJECT_TAG_ANY);
+
+ /* (c) READ_16 LBA 0 (< 512) -> completes normally, NOT held */
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, read16_lba0_cdb);
+ qtest_clock_step(qts, 100);
+ cqtp = ufs_rreg(ufs, cqdao + 0x4);
+ g_assert_cmpuint(cqtp, !=, cqhp);
+ qtest_memread(qts, ufs->cqlba[TEST_QID] + cqhp, &cqe, sizeof(cqe));
+ g_assert_cmpuint(cqe.status, ==, UFS_OCS_SUCCESS);
+ ufs_wreg(ufs, cqdao, cqtp);
+ ufs_wreg(ufs, cqisao + A_CQIS, FIELD_DP32(0, CQIS, TEPS, 1));
+ ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1));
+ release_cmd_desc_slot(ufs, slot);
+
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==,
+ UFS_FAULT_INJECT_TAG_ANY);
+ qobject_unref(rsp);
+
+ /* (d) READ_16 LBA 512 (>= 512) -> HELD */
+ cqhp = ufs_rreg(ufs, cqdao);
+ slot = ufs_mcq_send_scsi_cmd_async(ufs, 0, read16_lba512_cdb);
+ qtest_clock_step(qts, 100);
+ g_assert_cmpuint(ufs_rreg(ufs, cqdao + 0x4), ==, cqhp);
+
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==, slot);
+ qobject_unref(rsp);
+
+ /* Abort the held READ_16 */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_ABORT_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL);
+ release_cmd_desc_slot(ufs, slot);
+
+ ufs_wreg(ufs, A_HCE, 0);
+ guest_free(alloc, utmrlba);
+ ufs_exit(ufs, alloc);
+}
+
+static void ufs_test_fault_injection_legacy(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ QTestState *qts = ufs->dev.bus->qts;
+ uint64_t utmrlba;
+ UtpTaskReqDesc utrd_result;
+ const uint8_t test_unit_ready_cdb[UFS_CDB_SIZE] = { TEST_UNIT_READY };
+ int slot;
+ QDict *rsp;
+
+ ufs_init(ufs, alloc);
+ g_assert_false(ufs->support_mcq);
+
+ utmrlba = guest_alloc(alloc, 8 * sizeof(UtpTaskReqDesc));
+ ufs_wreg(ufs, A_UTMRLBA, utmrlba & 0xffffffff);
+ ufs_wreg(ufs, A_UTMRLBAU, utmrlba >> 32);
+ ufs_wreg(ufs, A_UTMRLRSR, 1);
+
+ slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC);
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-mode', 'value': 'abort-success' } }");
+ qtest_qmp_assert_success(
+ qts,
+ "{ 'execute': 'qom-set', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag', 'value': %d } }", slot);
+
+ slot = ufs_send_scsi_cmd_doorbell_async(ufs, 0, test_unit_ready_cdb);
+ qtest_clock_step(qts, 100);
+
+ /* Assert UTRLDBR bit is set while command is held */
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTRLDBR) & (1 << slot), ==, 1 << slot);
+
+ /* Issue UFS_ABORT_TASK -> clears UTRLDBR bit and returns COMPL */
+ ufs_send_tmr(ufs, utmrlba, 0, UFS_ABORT_TASK, 0, slot, &utrd_result);
+ g_assert_cmpuint(utrd_result.upiu_rsp.rsp_header.response,
+ ==, UFS_COMMAND_RESULT_SUCCESS);
+ g_assert_cmpuint(be32_to_cpu(utrd_result.upiu_rsp.output_param1),
+ ==, UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL);
+
+ /* Verify UTRLDBR bit was cleared by the abort */
+ g_assert_cmpuint(ufs_rreg(ufs, A_UTRLDBR) & (1 << slot), ==, 0);
+
+ rsp = qtest_qmp(
+ qts,
+ "{ 'execute': 'qom-get', 'arguments': { "
+ "'path': '/machine/peripheral/ufs0', "
+ "'property': 'x-fault-inject-tag' } }");
+ g_assert_cmpuint(qdict_get_int(rsp, "return"), ==,
+ UFS_FAULT_INJECT_TAG_NONE);
+ qobject_unref(rsp);
+
+ release_cmd_desc_slot(ufs, slot);
+ guest_free(alloc, utmrlba);
+ ufs_exit(ufs, alloc);
+}
+
static void ufs_register_nodes(void)
{
const char *arch;
@@ -2250,6 +2925,10 @@ static void ufs_register_nodes(void)
&io_test_opts);
qos_add_test("hce-reset", "ufs", ufs_test_hce_reset, &mcq_test_opts);
qos_add_test("tmr-handling", "ufs", ufs_test_tmr_handling, &mcq_test_opts);
+ qos_add_test("fault-injection-abort", "ufs",
+ ufs_test_fault_injection_abort, &mcq_test_opts);
+ qos_add_test("fault-injection-legacy", "ufs",
+ ufs_test_fault_injection_legacy, &io_test_opts);
qos_add_test("wb-init", "ufs", ufstest_wb_init, &wb_test_opts);
qos_add_test("wb-read-write", "ufs", ufstest_wb_read_write, &wb_test_opts);
qos_add_test("hid", "ufs", ufstest_hid, &io_test_opts);
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PULL 00/12] UFS command fixes and fault injection tests
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
` (11 preceding siblings ...)
2026-10-30 8:14 ` [PULL 12/12] tests/qtest/ufs-test: Add QOM fault injection task abort test Jeuk Kim
@ 2026-10-30 12:32 ` Richard Henderson
12 siblings, 0 replies; 14+ messages in thread
From: Richard Henderson @ 2026-10-30 12:32 UTC (permalink / raw)
To: Jeuk Kim, qemu-devel
Cc: stefanha, pbonzini, fam, kwolf, hreitz, qemu-block, daejun7.park,
stanleyjhu, dridiha98, jeuk20.kim, j-young.choi
On 10/30/26 03:14, Jeuk Kim wrote:
> From: Jeuk Kim<jeuk20.kim@samsung.com>
>
> The following changes since commit b574eaf640f02c4edf28cbee6417378f0d79cda0:
>
> Merge tag 'for_upstream' ofhttps://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging (2026-10-29 13:47:40 -0500)
>
> are available in the Git repository at:
>
> https://gitlab.com/jeuk20.kim/qemu.git tags/pull-ufs-20261030
>
> for you to fetch changes up to cc145a41586bde12c94b7e8932988cff323ad3a7:
>
> tests/qtest/ufs-test: Add QOM fault injection task abort test (2026-10-30 17:00:58 +0900)
>
> ----------------------------------------------------------------
> UFS command fixes and fault injection tests
>
> - Add REPORT LUNS support for well-known LUNs
> - Fix PRDT validation and SYNCHRONIZE CACHE handling
> - Add fault injection and reset/task management tests
Applied, thanks.
r~
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-30 12:33 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 8:14 [PULL 00/12] UFS command fixes and fault injection tests Jeuk Kim
2026-10-30 8:14 ` [PULL 01/12] hw/ufs: Add support for REPORT_LUNS SCSI command for Well Known LUNs Jeuk Kim
2026-10-30 8:14 ` [PULL 02/12] hw/ufs: Fail SCSI commands that move data without a PRDT Jeuk Kim
2026-10-30 8:14 ` [PULL 03/12] hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10) Jeuk Kim
2026-10-30 8:14 ` [PULL 04/12] hw/ufs: Check the PRDT size against the transfer length of the CDB Jeuk Kim
2026-10-30 8:14 ` [PULL 05/12] hw/ufs: Add experimental fault injection properties for holding requests Jeuk Kim
2026-10-30 8:14 ` [PULL 06/12] hw/ufs: Handle task management requests for fault-injected held requests Jeuk Kim
2026-10-30 8:14 ` [PULL 07/12] hw/ufs: Add ssu-timeout mode for power management recovery testing Jeuk Kim
2026-10-30 8:14 ` [PULL 08/12] docs/system/ufs: Update task management request support status Jeuk Kim
2026-10-30 8:14 ` [PULL 09/12] tests/qtest/ufs-test: Add TMR and async MCQ SCSI submission helpers Jeuk Kim
2026-10-30 8:14 ` [PULL 10/12] tests/qtest/ufs-test: Add HCE reset unit test Jeuk Kim
2026-10-30 8:14 ` [PULL 11/12] tests/qtest/ufs-test: Add MCQ runtime register and TMR handling test Jeuk Kim
2026-10-30 8:14 ` [PULL 12/12] tests/qtest/ufs-test: Add QOM fault injection task abort test Jeuk Kim
2026-10-30 12:32 ` [PULL 00/12] UFS command fixes and fault injection tests Richard Henderson
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.