Commit a5e4946ba0 for qemu.org
commit a5e4946ba0753c0b715d1189505582607be0f8ea
Author: Stanley Jhu <stanleyjhu@google.com>
Date: Fri Oct 23 19:38:29 2026 +0800
tests/qtest/ufs-test: Add MCQ runtime register and TMR handling test
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>
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 9270e3b137..78feeed859 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);