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


Reply via email to