* [PULL 1/2] hw/ufs: Add Host Initiated Defragmentation (HID) support
2026-06-12 0:29 [PULL 0/2] ufs queue Jeuk Kim
@ 2026-06-12 0:29 ` Jeuk Kim
2026-06-12 0:29 ` [PULL 2/2] tests/qtest: Add UFS HID qtest Jeuk Kim
2026-06-12 14:38 ` [PULL 0/2] ufs queue Stefan Hajnoczi
2 siblings, 0 replies; 7+ messages in thread
From: Jeuk Kim @ 2026-06-12 0:29 UTC (permalink / raw)
To: qemu-devel, stefanha
Cc: kwolf, hreitz, farosas, lvivier, pbonzini, qemu-block, jeuk20.kim,
j-young.choi, Keoseong Park
From: Keoseong Park <keosung.park@samsung.com>
Emulate the UFS HID extended feature. Host interacts via five
attributes (IDN 0x35-0x39):
bDefragOperation trigger: Disable / Analysis / Defrag
dHIDAvailableSize fragmented 4KB units (published by analysis)
dHIDSize host-requested defrag target (4KB units)
bHIDProgressRatio 0-100%; reading 100 resets HID
bHIDState current state; terminal-state read resets HID
Successful user-data SCSI WRITE commands increment an internal
fragment counter; HID analysis publishes the counter through
dHIDAvailableSize. Defrag operates on min(dHIDSize,
dHIDAvailableSize), so a small dHIDSize yields a partial defrag.
bDefragOperation auto-clears on terminal state. The state machine
advances from ufs_process_idle(); transitions occur only while the
device is idle.
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>
---
hw/ufs/lu.c | 16 ++++-
hw/ufs/trace-events | 4 ++
hw/ufs/ufs.c | 154 ++++++++++++++++++++++++++++++++++++++++++--
hw/ufs/ufs.h | 4 ++
include/block/ufs.h | 18 ++++++
5 files changed, 188 insertions(+), 8 deletions(-)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index f13fc6e342..1e75b1eb4d 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -154,15 +154,29 @@ static void ufs_wb_process_write_req(UfsRequest *req, uint32_t transfered_len)
ufs_wb_update_avail_buffer(u);
}
+#define UFS_HID_MAX_FRAGMENTS 1024
+static void ufs_hid_count_fragments(UfsRequest *req)
+{
+ UfsHc *u = req->hc;
+
+ if (!ufs_is_write_req(req)) {
+ return;
+ }
+
+ u->hid_fragment_count =
+ MIN(u->hid_fragment_count + 1, UFS_HID_MAX_FRAGMENTS);
+}
+
static void ufs_scsi_command_complete(SCSIRequest *scsi_req, size_t resid)
{
UfsRequest *req = scsi_req->hba_private;
int16_t status = scsi_req->status;
uint32_t transfered_len = scsi_req->cmd.xfer - resid;
- /* WB accounting should only happen for successful commands */
+ /* WB / HID accounting should only happen for successful commands */
if (status == GOOD) {
ufs_wb_process_write_req(req, transfered_len);
+ ufs_hid_count_fragments(req);
}
ufs_build_scsi_response_upiu(req, scsi_req->sense, scsi_req->sense_len,
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index 6f7ea9c95f..662d9afee3 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -51,3 +51,7 @@ ufs_err_mcq_create_cq_already_exists(uint8_t qid) "mcq cqid %"PRIu8 "already exi
ufs_err_mcq_delete_cq_invalid_cqid(uint8_t qid) "invalid mcq cqid %"PRIu8""
ufs_err_mcq_delete_cq_not_exists(uint8_t qid) "mcq cqid %"PRIu8 "not exists"
ufs_err_mcq_delete_cq_sq_not_deleted(uint8_t sqid, uint8_t cqid) "mcq sq %"PRIu8" still has cq %"PRIu8""
+
+# HID (Host Initiated Defragmentation)
+ufs_hid_defrag_operation(uint8_t op, uint8_t state) "HID defrag operation 0x%"PRIx8", new state 0x%"PRIx8""
+ufs_hid_defrag_progress(uint32_t remaining, uint8_t progress) "HID defrag remaining %"PRIu32", progress %"PRIu8"%%"
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 6780d73174..35c2444385 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -40,6 +40,9 @@
#define UFS_TOO_HIGH_TEMP_BOUNDARY 160
#define UFS_TOO_LOW_TEMP_BOUNDARY 60
+#define UFS_HID_DEFRAG_BATCH_DIV 10 /* ~10% of remaining per tick */
+#define UFS_HID_PROGRESS_COMPLETE 100
+
static void ufs_exec_req(UfsRequest *req);
static void ufs_clear_req(UfsRequest *req);
@@ -1288,10 +1291,9 @@ static const int attr_permission[UFS_QUERY_ATTR_IDN_COUNT] = {
[UFS_QUERY_ATTR_IDN_REFRESH_UNIT] = UFS_QUERY_ATTR_READ,
[UFS_QUERY_ATTR_IDN_TIMESTAMP] = UFS_QUERY_ATTR_WRITE,
[UFS_QUERY_ATTR_IDN_DEVICE_LEVEL_EXCEPTION_ID] = UFS_QUERY_ATTR_READ,
- /* host initiated defragmentation is not supported */
- [UFS_QUERY_ATTR_IDN_DEFRAG_OP] = UFS_QUERY_ATTR_READ,
+ [UFS_QUERY_ATTR_IDN_DEFRAG_OP] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE,
[UFS_QUERY_ATTR_IDN_HID_AVAIL_SIZE] = UFS_QUERY_ATTR_READ,
- [UFS_QUERY_ATTR_IDN_HID_SIZE] = UFS_QUERY_ATTR_READ,
+ [UFS_QUERY_ATTR_IDN_HID_SIZE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE,
[UFS_QUERY_ATTR_IDN_HID_PROG_RATIO] = UFS_QUERY_ATTR_READ,
[UFS_QUERY_ATTR_IDN_HID_STATE] = UFS_QUERY_ATTR_READ,
[UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_HINT] = UFS_QUERY_ATTR_READ,
@@ -1371,8 +1373,31 @@ static inline uint32_t ufs_wb_read_resize_status(UfsHc *u)
return value;
}
+static void ufs_hid_reset(UfsHc *u)
+{
+ u->attributes.defrag_op = UFS_HID_OP_DISABLE;
+ u->attributes.hid_state = UFS_HID_STATE_IDLE;
+ u->attributes.hid_prog_ratio = 0;
+ u->attributes.hid_avail_size = cpu_to_be32(0xFFFFFFFF);
+ u->hid_defrag_total = 0;
+ u->hid_defrag_remaining = 0;
+}
+
+static uint32_t ufs_hid_read_progress_ratio(UfsHc *u)
+{
+ uint32_t value = u->attributes.hid_prog_ratio;
+
+ if (value == UFS_HID_PROGRESS_COMPLETE) {
+ ufs_hid_reset(u);
+ }
+
+ return value;
+}
+
static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn)
{
+ uint8_t state;
+
switch (idn) {
case UFS_QUERY_ATTR_IDN_BOOT_LU_EN:
return u->attributes.boot_lun_en;
@@ -1451,9 +1476,16 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn)
case UFS_QUERY_ATTR_IDN_HID_SIZE:
return be32_to_cpu(u->attributes.hid_size);
case UFS_QUERY_ATTR_IDN_HID_PROG_RATIO:
- return u->attributes.hid_prog_ratio;
+ return ufs_hid_read_progress_ratio(u);
case UFS_QUERY_ATTR_IDN_HID_STATE:
- return u->attributes.hid_state;
+ state = u->attributes.hid_state;
+
+ if (state == UFS_HID_STATE_DEFRAG_COMPLETED ||
+ state == UFS_HID_STATE_DEFRAG_NOT_REQUIRED) {
+ ufs_hid_reset(u);
+ }
+
+ return state;
case UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_HINT:
return u->attributes.wb_buffer_resize_hint;
case UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_STATUS:
@@ -1604,6 +1636,26 @@ static bool ufs_wb_pinned_min_size(UfsHc *u, uint32_t value)
return true;
}
+static QueryRespCode ufs_hid_write_defrag_operation(UfsHc *u, uint32_t value)
+{
+ switch (value) {
+ case UFS_HID_OP_DISABLE:
+ ufs_hid_reset(u);
+ break;
+ case UFS_HID_OP_ANALYSIS:
+ case UFS_HID_OP_DEFRAG:
+ u->attributes.defrag_op = value;
+ u->attributes.hid_state = UFS_HID_STATE_ANALYSIS_IN_PROGRESS;
+ u->attributes.hid_prog_ratio = 0;
+ break;
+ default:
+ return UFS_QUERY_RESULT_INVALID_VALUE;
+ }
+
+ trace_ufs_hid_defrag_operation(value, u->attributes.hid_state);
+ return UFS_QUERY_RESULT_SUCCESS;
+}
+
static QueryRespCode ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value)
{
switch (idn) {
@@ -1668,6 +1720,11 @@ static QueryRespCode ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value)
return UFS_QUERY_RESULT_INVALID_VALUE;
}
break;
+ case UFS_QUERY_ATTR_IDN_DEFRAG_OP:
+ return ufs_hid_write_defrag_operation(u, value);
+ case UFS_QUERY_ATTR_IDN_HID_SIZE:
+ u->attributes.hid_size = cpu_to_be32(value);
+ break;
default:
g_assert_not_reached();
return 0;
@@ -2211,11 +2268,92 @@ static void ufs_wb_process_resize(UfsHc *u)
u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_COMPLETED;
}
+static void ufs_hid_process(UfsHc *u)
+{
+ uint32_t requested, batch, done;
+
+ switch (u->attributes.hid_state) {
+ case UFS_HID_STATE_ANALYSIS_IN_PROGRESS:
+ u->attributes.hid_avail_size = cpu_to_be32(u->hid_fragment_count);
+ if (u->hid_fragment_count > 0) {
+ u->attributes.hid_state = UFS_HID_STATE_DEFRAG_REQUIRED;
+ } else {
+ u->attributes.hid_state = UFS_HID_STATE_DEFRAG_NOT_REQUIRED;
+ }
+
+ if (u->attributes.defrag_op == UFS_HID_OP_ANALYSIS ||
+ u->attributes.hid_state == UFS_HID_STATE_DEFRAG_NOT_REQUIRED) {
+ u->attributes.defrag_op = UFS_HID_OP_DISABLE;
+ }
+
+ trace_ufs_hid_defrag_operation(u->attributes.defrag_op,
+ u->attributes.hid_state);
+ break;
+
+ case UFS_HID_STATE_DEFRAG_REQUIRED:
+ if (u->attributes.defrag_op != UFS_HID_OP_DEFRAG) {
+ break;
+ }
+
+ requested = MIN(be32_to_cpu(u->attributes.hid_size),
+ be32_to_cpu(u->attributes.hid_avail_size));
+ if (!requested) {
+ u->attributes.hid_state = UFS_HID_STATE_DEFRAG_COMPLETED;
+ u->attributes.defrag_op = UFS_HID_OP_DISABLE;
+ u->attributes.hid_prog_ratio = UFS_HID_PROGRESS_COMPLETE;
+
+ trace_ufs_hid_defrag_operation(u->attributes.defrag_op,
+ u->attributes.hid_state);
+ break;
+ }
+
+ u->attributes.hid_state = UFS_HID_STATE_DEFRAG_IN_PROGRESS;
+ u->hid_defrag_total = requested;
+ u->hid_defrag_remaining = requested;
+ u->attributes.hid_prog_ratio = 0;
+ break;
+
+ case UFS_HID_STATE_DEFRAG_IN_PROGRESS:
+ if (u->hid_defrag_remaining > 0) {
+ batch = u->hid_defrag_remaining / UFS_HID_DEFRAG_BATCH_DIV;
+ if (batch == 0) {
+ batch = 1;
+ }
+
+ u->hid_defrag_remaining -= batch;
+ u->hid_fragment_count -= batch;
+
+ done = u->hid_defrag_total - u->hid_defrag_remaining;
+ u->attributes.hid_prog_ratio =
+ ((uint64_t)done * UFS_HID_PROGRESS_COMPLETE) /
+ u->hid_defrag_total;
+
+ trace_ufs_hid_defrag_progress(u->hid_defrag_remaining,
+ u->attributes.hid_prog_ratio);
+ }
+
+ if (!u->hid_defrag_remaining) {
+ u->attributes.hid_state = UFS_HID_STATE_DEFRAG_COMPLETED;
+ u->attributes.defrag_op = UFS_HID_OP_DISABLE;
+ u->attributes.hid_prog_ratio = UFS_HID_PROGRESS_COMPLETE;
+
+ trace_ufs_hid_defrag_operation(u->attributes.defrag_op,
+ u->attributes.hid_state);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+}
+
static void ufs_process_idle(UfsHc *u)
{
ufs_wb_process_flush(u);
ufs_wb_process_resize(u);
ufs_wb_sync_buffer_size(u);
+ ufs_hid_process(u);
}
static inline bool ufs_check_idle(UfsHc *u)
@@ -2384,8 +2522,8 @@ static void ufs_init_hc(UfsHc *u)
uint32_t mcqconfig = 0;
uint32_t mcqcap = 0;
uint32_t ext_wb_sup = WB_RESIZE | WB_FIFO | WB_PINNED;
- uint32_t ext_ufs_feat_sup =
- UFS_DEV_WB_SUPPORT | UFS_DEV_HIGH_TEMP_NOTIF | UFS_DEV_LOW_TEMP_NOTIF;
+ uint32_t ext_ufs_feat_sup = UFS_DEV_WB_SUPPORT | UFS_DEV_HIGH_TEMP_NOTIF |
+ UFS_DEV_LOW_TEMP_NOTIF | UFS_DEV_HID_SUPPORT;
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT);
u->reg_size = pow2ceil(ufs_reg_size(u));
@@ -2487,6 +2625,8 @@ static void ufs_init_hc(UfsHc *u)
u->attributes.max_num_of_rtt = 0x02;
u->attributes.device_too_high_temp_boundary = UFS_TOO_HIGH_TEMP_BOUNDARY;
u->attributes.device_too_low_temp_boundary = UFS_TOO_LOW_TEMP_BOUNDARY;
+ u->attributes.hid_avail_size = cpu_to_be32(0xFFFFFFFF);
+ u->attributes.hid_size = cpu_to_be32(0xFFFFFFFF);
memset(&u->flags, 0, sizeof(u->flags));
u->flags.permanently_disable_fw_update = 1;
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 8743501810..feb47f460d 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -175,6 +175,10 @@ typedef struct UfsHc {
uint8_t temperature;
QEMUTimer idle_timer;
+
+ uint32_t hid_fragment_count; /* Remaining fragmented 4KB units */
+ uint32_t hid_defrag_total; /* Requested units at defrag start */
+ uint32_t hid_defrag_remaining; /* Requested units left to move */
} UfsHc;
static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid)
diff --git a/include/block/ufs.h b/include/block/ufs.h
index 0f7cc9c21b..6dd91181e5 100644
--- a/include/block/ufs.h
+++ b/include/block/ufs.h
@@ -951,6 +951,23 @@ enum attr_idn {
UFS_QUERY_ATTR_IDN_COUNT,
};
+/* HID (Host Initiated Defragmentation) operation values for bDefragOperation */
+enum ufs_hid_op {
+ UFS_HID_OP_DISABLE = 0x00,
+ UFS_HID_OP_ANALYSIS = 0x01,
+ UFS_HID_OP_DEFRAG = 0x02,
+};
+
+/* HID state values for bHIDState */
+enum ufs_hid_state {
+ UFS_HID_STATE_IDLE = 0x00,
+ UFS_HID_STATE_ANALYSIS_IN_PROGRESS = 0x01,
+ UFS_HID_STATE_DEFRAG_REQUIRED = 0x02,
+ UFS_HID_STATE_DEFRAG_IN_PROGRESS = 0x03,
+ UFS_HID_STATE_DEFRAG_COMPLETED = 0x04,
+ UFS_HID_STATE_DEFRAG_NOT_REQUIRED = 0x05,
+};
+
/* Descriptor idn for Query requests */
enum desc_idn {
UFS_QUERY_DESC_IDN_DEVICE = 0x0,
@@ -1142,6 +1159,7 @@ enum {
/* Possible values for dExtendedUFSFeaturesSupport */
enum {
UFS_DEV_WB_SUPPORT = BIT(8),
+ UFS_DEV_HID_SUPPORT = BIT(13),
};
/* WriteBooster buffer mode */
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PULL 2/2] tests/qtest: Add UFS HID qtest
2026-06-12 0:29 [PULL 0/2] ufs queue Jeuk Kim
2026-06-12 0:29 ` [PULL 1/2] hw/ufs: Add Host Initiated Defragmentation (HID) support Jeuk Kim
@ 2026-06-12 0:29 ` Jeuk Kim
2026-06-12 14:38 ` [PULL 0/2] ufs queue Stefan Hajnoczi
2 siblings, 0 replies; 7+ messages in thread
From: Jeuk Kim @ 2026-06-12 0:29 UTC (permalink / raw)
To: qemu-devel, stefanha
Cc: kwolf, hreitz, farosas, lvivier, pbonzini, qemu-block, jeuk20.kim,
j-young.choi, Keoseong Park
From: Keoseong Park <keosung.park@samsung.com>
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>
---
tests/qtest/ufs-test.c | 275 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 275 insertions(+)
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);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread