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 <[email protected]> --- tests/qtest/ufs-test.c | 333 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 332 insertions(+), 1 deletion(-) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index bbc90f2218..8fe53c1b81 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); @@ -2184,6 +2184,335 @@ 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 }; + 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); + + /* + * Case 2: x-fault-inject-mode = in-transition. + * UFS_QUERY_TASK completes the held request normally (UFS_OCS_SUCCESS CQE). + */ + 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_scsi_cmd_async(ufs, 0, test_unit_ready_cdb); + qtest_clock_step(qts, 100); + + 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_COMPL); + 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); + + /* + * 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); + + guest_free(alloc, utmrlba); + ufs_exit(ufs, alloc); +} + #define ZONE_SIZE (2 * 1024 * 1024) #define ZONE_BLOCKS (ZONE_SIZE / 4096) #define MAX_OPEN_ZONE 6 @@ -3364,6 +3693,8 @@ 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("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.56.0.218.g1ec904b173-goog
