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);