Commit 6b1215d82f for qemu.org

commit 6b1215d82f7cd8a0b562a02a1473aa6714759eed
Author: Keoseong Park <keosung.park@samsung.com>
Date:   Tue Jun 9 13:12:28 2026 +0900

    tests/qtest: Add UFS HID qtest

    Cover the HID attribute permission table, the disable / analysis /
    defrag state transitions, partial defrag bounded by dHIDSize, and
    the terminal auto-reset behaviors.

    Signed-off-by: Keoseong Park <keosung.park@samsung.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 f677896db0..3b85296a72 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -1386,6 +1386,280 @@ static void *ufs_blk_test_setup(GString *cmd_line, void *arg)
     return arg;
 }

+/*
+ * Helper to read a single HID attribute value.
+ * Returns the host-endian attribute value; asserts OCS success.
+ */
+static uint32_t ufs_hid_read_attr(QUfs *ufs, uint8_t idn)
+{
+    enum UtpOcsCodes ocs;
+    UtpUpiuRsp rsp;
+
+    ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST,
+                         UFS_UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0, 0, &rsp);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+    g_assert_cmpuint(rsp.header.response, ==, UFS_COMMAND_RESULT_SUCCESS);
+    return be32_to_cpu(rsp.qr.value);
+}
+
+/*
+ * Helper to write a single HID attribute value.
+ * Asserts OCS success.
+ */
+static void ufs_hid_write_attr(QUfs *ufs, uint8_t idn, uint32_t value)
+{
+    enum UtpOcsCodes ocs;
+    UtpUpiuRsp rsp;
+
+    ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST,
+                         UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, idn, 0, 0, value,
+                         &rsp);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+    g_assert_cmpuint(rsp.header.response, ==, UFS_COMMAND_RESULT_SUCCESS);
+}
+
+static uint32_t ufs_hid_wait_leave_state(QUfs *ufs, uint32_t from)
+{
+    uint64_t end_time =
+        g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND;
+    uint32_t state;
+
+    do {
+        qtest_clock_step(ufs->dev.bus->qts, 100);
+        state = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    } while (state == from && g_get_monotonic_time() < end_time);
+
+    return state;
+}
+
+static uint32_t ufs_hid_wait_state(QUfs *ufs, uint32_t expected)
+{
+    uint64_t end_time =
+        g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND;
+    uint32_t state;
+
+    do {
+        qtest_clock_step(ufs->dev.bus->qts, 100);
+        state = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    } while (state != expected && g_get_monotonic_time() < end_time);
+
+    g_assert_cmpuint(state, ==, expected);
+    return state;
+}
+
+static void ufs_hid_wait_progress_complete(QUfs *ufs)
+{
+    uint64_t end_time =
+        g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND;
+    uint32_t val;
+
+    do {
+        qtest_clock_step(ufs->dev.bus->qts, 100);
+        val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_PROG_RATIO);
+    } while (val != 100 && g_get_monotonic_time() < end_time);
+
+    g_assert_cmpuint(val, ==, 100);
+}
+
+static void ufstest_hid(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QUfs *ufs = obj;
+    uint32_t val;
+    enum UtpOcsCodes ocs;
+    UtpUpiuRsp rsp;
+    const int test_lun = 1;
+    const uint8_t request_sense_cdb[UFS_CDB_SIZE] = {
+        REQUEST_SENSE,
+    };
+    const uint8_t write_cdb[UFS_CDB_SIZE] = {
+        /* 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];
+    uint8_t read_buf[4096];
+    int i;
+
+    ufs_init(ufs, alloc);
+
+    /* Clear Unit Attention */
+    ufs_send_scsi_command(ufs, test_lun, request_sense_cdb, NULL, 0, read_buf,
+                          sizeof(read_buf), &rsp);
+
+    /*
+     * 1. Verify HID default attribute values
+     */
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP);
+    g_assert_cmpuint(val, ==, UFS_HID_OP_DISABLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE);
+    g_assert_cmpuint(val, ==, 0xFFFFFFFF);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_SIZE);
+    g_assert_cmpuint(val, ==, 0xFFFFFFFF);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_PROG_RATIO);
+    g_assert_cmpuint(val, ==, 0);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_IDLE);
+
+    /*
+     * 2. Verify read-only attributes reject writes
+     */
+    ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST,
+                         UFS_UPIU_QUERY_OPCODE_WRITE_ATTR,
+                         UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE, 0, 0, 0x100, &rsp);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR);
+    g_assert_cmpuint(rsp.header.response, ==, UFS_QUERY_RESULT_NOT_WRITEABLE);
+
+    ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST,
+                         UFS_UPIU_QUERY_OPCODE_WRITE_ATTR,
+                         UFS_QUERY_ATTR_IDN_HID_PROG_RATIO, 0, 0, 50, &rsp);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR);
+    g_assert_cmpuint(rsp.header.response, ==, UFS_QUERY_RESULT_NOT_WRITEABLE);
+
+    ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST,
+                         UFS_UPIU_QUERY_OPCODE_WRITE_ATTR,
+                         UFS_QUERY_ATTR_IDN_HID_STATE, 0, 0, 0x01, &rsp);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR);
+    g_assert_cmpuint(rsp.header.response, ==, UFS_QUERY_RESULT_NOT_WRITEABLE);
+
+    /*
+     * 3. Verify invalid bDefragOperation value is rejected
+     */
+    ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST,
+                         UFS_UPIU_QUERY_OPCODE_WRITE_ATTR,
+                         UFS_QUERY_ATTR_IDN_DEFRAG_OP, 0, 0, 0x03, &rsp);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR);
+    g_assert_cmpuint(rsp.header.response, ==, UFS_QUERY_RESULT_INVALID_VALUE);
+
+    /*
+     * 4. dHIDSize is writable and readable
+     */
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_HID_SIZE, 0x100);
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_SIZE);
+    g_assert_cmpuint(val, ==, 0x100);
+
+    /* Restore default */
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_HID_SIZE, 0xFFFFFFFF);
+
+    /*
+     * 5. Analysis with no fragments -> Defrag Not Required, then reading
+     *    bHIDState in a terminal state auto-resets HID to Idle.
+     */
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_ANALYSIS);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_ANALYSIS_IN_PROGRESS);
+
+    /* Idle handler advances analysis; first non-analysis read is decisive */
+    val = ufs_hid_wait_leave_state(ufs, UFS_HID_STATE_ANALYSIS_IN_PROGRESS);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_DEFRAG_NOT_REQUIRED);
+
+    /* Reading Not Required auto-reset to Idle */
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_IDLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE);
+    g_assert_cmpuint(val, ==, 0xFFFFFFFF);
+
+    /*
+     * 6. Generate fragmentation via SCSI WRITE, then analyze (analysis
+     *    only): the machine settles at Defrag Required.
+     */
+    memset(write_buf, 0xab, sizeof(write_buf));
+    for (i = 0; i < 4; i++) {
+        ocs = ufs_send_scsi_command(ufs, test_lun, write_cdb, write_buf,
+                                    sizeof(write_buf), NULL, 0, &rsp);
+        g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+    }
+
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_ANALYSIS);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_ANALYSIS_IN_PROGRESS);
+
+    val = ufs_hid_wait_leave_state(ufs, UFS_HID_STATE_ANALYSIS_IN_PROGRESS);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_DEFRAG_REQUIRED);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP);
+    g_assert_cmpuint(val, ==, UFS_HID_OP_DISABLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE);
+    g_assert_cmpuint(val, >, 0);
+
+    /*
+     * 7. Disable resets to Idle from any state
+     */
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_DISABLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_IDLE);
+
+    /*
+     * 8. Partial defrag cycle: dHIDSize limits the requested defrag size,
+     *    and reading bHIDProgressRatio at 100% resets the HID attributes.
+     */
+    for (i = 0; i < 4; i++) {
+        ocs = ufs_send_scsi_command(ufs, test_lun, write_cdb, write_buf,
+                                    sizeof(write_buf), NULL, 0, &rsp);
+        g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+    }
+
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_HID_SIZE, 2);
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_DEFRAG);
+
+    /* Should start in Analysis In Progress */
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_ANALYSIS_IN_PROGRESS);
+
+    ufs_hid_wait_progress_complete(ufs);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE);
+    g_assert_cmpuint(val, ==, 0xFFFFFFFF);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP);
+    g_assert_cmpuint(val, ==, UFS_HID_OP_DISABLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_PROG_RATIO);
+    g_assert_cmpuint(val, ==, 0);
+
+    /*
+     * Re-analyze after the partial defrag. There were 8 fragments before
+     * defrag, and dHIDSize limited the completed operation to 2.
+     */
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_ANALYSIS);
+
+    val = ufs_hid_wait_leave_state(ufs, UFS_HID_STATE_ANALYSIS_IN_PROGRESS);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_DEFRAG_REQUIRED);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE);
+    g_assert_cmpuint(val, ==, 6);
+
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_DISABLE);
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_HID_SIZE, 0xFFFFFFFF);
+
+    /*
+     * 9. Full defrag cycle: reading bHIDState in Completed state returns
+     *    Completed once and resets HID attributes to Idle/default values.
+     */
+    ufs_hid_write_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP, UFS_HID_OP_DEFRAG);
+
+    val = ufs_hid_wait_state(ufs, UFS_HID_STATE_DEFRAG_COMPLETED);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_DEFRAG_COMPLETED);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_STATE);
+    g_assert_cmpuint(val, ==, UFS_HID_STATE_IDLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_DEFRAG_OP);
+    g_assert_cmpuint(val, ==, UFS_HID_OP_DISABLE);
+
+    val = ufs_hid_read_attr(ufs, UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE);
+    g_assert_cmpuint(val, ==, 0xFFFFFFFF);
+
+    ufs_exit(ufs, alloc);
+}
+
 static void ufs_register_nodes(void)
 {
     const char *arch;
@@ -1439,6 +1713,7 @@ static void ufs_register_nodes(void)
                  &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);
 }

 libqos_init(ufs_register_nodes);