Commit 08c6d46621 for qemu.org

commit 08c6d46621cca906fa701714de87a3de93955d95
Author: Keoseong Park <keosung.park@samsung.com>
Date:   Tue Jun 9 13:06:46 2026 +0900

    hw/ufs: Add Host Initiated Defragmentation (HID) support

    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>

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 */