Commit c821f2f69f for qemu.org

commit c821f2f69f1e9d94873dbe7a60f6e8eac74e8ec9
Author: Stanley Jhu <stanleyjhu@google.com>
Date:   Fri Oct 23 19:38:23 2026 +0800

    hw/ufs: Add experimental fault injection properties for holding requests

    Add experimental properties x-fault-inject-tag and x-fault-inject-mode
    to ufs-pci and ufs-sysbus devices for testing host driver error
    handling and recovery routines.

    When x-fault-inject-tag is set to a specific tag (0..255), the
    controller holds the matching SCSI command request in u->held_req
    without completing it. When x-fault-inject-tag is set to 0xfffffffe
    (UFS_FAULT_INJECT_TAG_ANY), the controller intercepts the first
    READ_10, WRITE_10, READ_16, or WRITE_16 command with LBA >= 512,
    skipping boot-time metadata reads. The default value is 0xffffffff
    (UFS_FAULT_INJECT_TAG_NONE), which disables fault injection.

    Both properties can be changed at runtime through QOM (for example
    QMP qom-set). A held request is released on HCE reset. Task
    management handling for held requests follows in the next patch.

    Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
    Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
    Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>

diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index db9d172713..17acbebd30 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -16,6 +16,8 @@ ufs_mcq_create_sq(uint8_t sqid, uint8_t cqid, uint64_t addr, uint16_t size) "mcq
 ufs_mcq_create_cq(uint8_t cqid, uint64_t addr, uint16_t size) "mcq create cq cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16""
 ufs_write_mcq_op_reg(uint8_t qid, uint32_t offset, uint32_t data) "qid %"PRIu8", offset 0x%"PRIx32", data 0x%"PRIx32""
 ufs_process_tmr(uint8_t func, uint32_t tag, uint8_t resp) "query_func 0x%"PRIx8", task_tag %"PRIu32", tm_resp 0x%"PRIx8""
+ufs_fault_inject_hold_req(uint8_t tag, uint64_t lba) "held command tag %"PRIu8", lba %"PRIu64""
+ufs_fault_inject_set_tag(uint32_t tag) "fault-inject-tag set to 0x%"PRIx32""
 ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and request lists"

 # error condition
diff --git a/hw/ufs/ufs-pci.c b/hw/ufs/ufs-pci.c
index 8abd7d98e3..8a2268f11f 100644
--- a/hw/ufs/ufs-pci.c
+++ b/hw/ufs/ufs-pci.c
@@ -72,6 +72,13 @@ static const Property ufs_pci_props[] = {
                        0x400),
     DEFINE_PROP_UINT32("wb-min-size", UfsPciState, ufs.params.wb_min_size,
                        0x100),
+    DEFINE_PROP_UNSIGNED("x-fault-inject-tag", UfsPciState,
+                         ufs.params.fault_inject_tag, UFS_FAULT_INJECT_TAG_NONE,
+                         ufs_prop_fault_inject_tag, uint32_t),
+    DEFINE_PROP_SIGNED("x-fault-inject-mode", UfsPciState,
+                       ufs.params.fault_inject_mode,
+                       UFS_FAULT_INJECT_ABORT_SUCCESS,
+                       ufs_prop_fault_inject_mode, UfsFaultInjectMode),
 };

 static const VMStateDescription ufs_pci_vmstate = {
diff --git a/hw/ufs/ufs-sysbus.c b/hw/ufs/ufs-sysbus.c
index 84de2e95ac..7e85d8fc4c 100644
--- a/hw/ufs/ufs-sysbus.c
+++ b/hw/ufs/ufs-sysbus.c
@@ -45,6 +45,13 @@ static const Property ufs_sysbus_props[] = {
                        0x400),
     DEFINE_PROP_UINT32("wb-min-size", SysbusUfsState, ufs.params.wb_min_size,
                        0x100),
+    DEFINE_PROP_UNSIGNED("x-fault-inject-tag", SysbusUfsState,
+                         ufs.params.fault_inject_tag, UFS_FAULT_INJECT_TAG_NONE,
+                         ufs_prop_fault_inject_tag, uint32_t),
+    DEFINE_PROP_SIGNED("x-fault-inject-mode", SysbusUfsState,
+                       ufs.params.fault_inject_mode,
+                       UFS_FAULT_INJECT_ABORT_SUCCESS,
+                       ufs_prop_fault_inject_mode, UfsFaultInjectMode),
 };

 static const VMStateDescription ufs_sysbus_vmstate = {
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index cd28f97a12..1a5bc54eac 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -15,8 +15,10 @@

 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "qapi/visitor.h"
 #include "scsi/constants.h"
 #include "hw/core/irq.h"
+#include "hw/core/qdev-prop-internal.h"
 #include "trace.h"
 #include "ufs.h"

@@ -778,6 +780,77 @@ static bool ufs_mcq_delete_cq(UfsHc *u, uint8_t qid)
     return true;
 }

+static void ufs_fault_inject_init(UfsHc *u)
+{
+    u->active_fault_inject_tag = u->params.fault_inject_tag;
+    u->held_req = NULL;
+}
+
+static void ufs_fault_inject_hce_reset(UfsHc *u)
+{
+    if (u->held_req) {
+        u->held_req = NULL;
+        u->active_fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+        u->params.fault_inject_tag = UFS_FAULT_INJECT_TAG_NONE;
+    } else {
+        u->active_fault_inject_tag = u->params.fault_inject_tag;
+    }
+}
+
+static bool ufs_fault_inject_exec_req(UfsRequest *req)
+{
+    UfsHc *u = req->hc;
+
+    if (likely(u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_NONE) ||
+        u->held_req != NULL ||
+        req->req_upiu.header.trans_type != UFS_UPIU_TRANSACTION_COMMAND) {
+        return false;
+    }
+
+    if (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY) {
+        uint8_t op = req->req_upiu.sc.cdb[0];
+        uint64_t lba = 0;
+        bool is_match = false;
+
+        if (op == READ_10 || op == WRITE_10) {
+            /*
+             * In 10-byte SCSI read/write commands (SBC-4), bytes 2..5
+             * encode the 32-bit Logical Block Address (LBA) in
+             * big-endian.
+             */
+            lba = ldl_be_p(&req->req_upiu.sc.cdb[2]);
+            is_match = (lba >= UFS_FAULT_INJECT_MIN_LBA);
+        } else if (op == READ_16 || op == WRITE_16) {
+            /*
+             * In 16-byte SCSI read/write commands (SBC-4), bytes 2..9
+             * encode the 64-bit Logical Block Address (LBA) in
+             * big-endian.
+             */
+            lba = ldq_be_p(&req->req_upiu.sc.cdb[2]);
+            is_match = (lba >= UFS_FAULT_INJECT_MIN_LBA);
+        }
+
+        /*
+         * Only intercept target I/O targeting LBA >=
+         * UFS_FAULT_INJECT_MIN_LBA. Skips guest boot-time partition
+         * table and superblock scanning.
+         */
+        if (is_match) {
+            u->active_fault_inject_tag = req->req_upiu.header.task_tag;
+            u->held_req = req;
+            trace_ufs_fault_inject_hold_req(
+                req->req_upiu.header.task_tag, lba);
+            return true;
+        }
+    } else if (req->req_upiu.header.task_tag == u->active_fault_inject_tag) {
+        u->held_req = req;
+        trace_ufs_fault_inject_hold_req(req->req_upiu.header.task_tag, 0);
+        return true;
+    }
+
+    return false;
+}
+
 static void ufs_hce_reset(UfsHc *u)
 {
     int i;
@@ -854,6 +927,8 @@ static void ufs_hce_reset(UfsHc *u)
         memset(u->mcq_op_reg, 0, sizeof(u->mcq_op_reg));
     }

+    ufs_fault_inject_hce_reset(u);
+
     u->resetting = false;

     /* 5. De-assert IRQ */
@@ -2432,6 +2507,10 @@ static void ufs_exec_req(UfsRequest *req)
         return;
     }

+    if (ufs_fault_inject_exec_req(req)) {
+        return;
+    }
+
     switch (req->req_upiu.header.trans_type) {
     case UFS_UPIU_TRANSACTION_NOP_OUT:
         req_result = ufs_exec_nop_cmd(req);
@@ -3058,8 +3137,93 @@ static void ufs_init_hc(UfsHc *u)

     timer_init_ms(&u->idle_timer, QEMU_CLOCK_VIRTUAL_RT, ufs_idle_timer_cb, u);
     timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK);
+
+    ufs_fault_inject_init(u);
 }

+const QEnumLookup UfsFaultInjectMode_lookup = {
+    .array = (const char *const[]) {
+        [UFS_FAULT_INJECT_ABORT_SUCCESS] = "abort-success",
+        [UFS_FAULT_INJECT_ABORT_FAILED] = "abort-failed",
+        [UFS_FAULT_INJECT_IN_TRANSITION] = "in-transition",
+        [UFS_FAULT_INJECT_TIMEOUT] = "timeout",
+    },
+    .size = UFS_FAULT_INJECT_MODE_MAX,
+};
+
+static void ufs_prop_get_fault_inject_tag(Object *obj, Visitor *v,
+                                          const char *name, void *opaque,
+                                          Error **errp)
+{
+    Property *prop = opaque;
+    uint32_t *ptr = object_field_prop_ptr(obj, prop);
+    UfsParams *params = container_of(ptr, UfsParams, fault_inject_tag);
+    UfsHc *u = container_of(params, UfsHc, params);
+
+    visit_type_uint32(v, name, &u->active_fault_inject_tag, errp);
+}
+
+static void ufs_prop_set_fault_inject_tag(Object *obj, Visitor *v,
+                                          const char *name, void *opaque,
+                                          Error **errp)
+{
+    Property *prop = opaque;
+    uint32_t *ptr = object_field_prop_ptr(obj, prop);
+    UfsParams *params = container_of(ptr, UfsParams, fault_inject_tag);
+    UfsHc *u = container_of(params, UfsHc, params);
+    uint32_t val;
+
+    if (!visit_type_uint32(v, name, &val, errp)) {
+        return;
+    }
+
+    if (val > 255 && val != UFS_FAULT_INJECT_TAG_NONE &&
+        val != UFS_FAULT_INJECT_TAG_ANY) {
+        error_setg(errp,
+                   "invalid %s %u: must be 0..255, 0x%x (none), or 0x%x (any)",
+                   name, val, UFS_FAULT_INJECT_TAG_NONE,
+                   UFS_FAULT_INJECT_TAG_ANY);
+        return;
+    }
+
+    if (u->held_req != NULL) {
+        error_setg(errp,
+                   "cannot change %s while a request (tag %u) is held",
+                   name, u->held_req->req_upiu.header.task_tag);
+        return;
+    }
+
+    *ptr = val;
+    u->active_fault_inject_tag = val;
+    trace_ufs_fault_inject_set_tag(val);
+}
+
+static void ufs_prop_set_default_fault_inject_tag(ObjectProperty *op,
+                                                  const Property *prop)
+{
+    object_property_set_default_uint(op, prop->defval.u);
+}
+
+const PropertyInfo ufs_prop_fault_inject_tag = {
+    .type = "uint32",
+    .description = "Task tag to hold for fault injection",
+    .get = ufs_prop_get_fault_inject_tag,
+    .set = ufs_prop_set_fault_inject_tag,
+    .set_default_value = ufs_prop_set_default_fault_inject_tag,
+    .realized_set_allowed = true,
+};
+
+const PropertyInfo ufs_prop_fault_inject_mode = {
+    .type = "UfsFaultInjectMode",
+    .description = "Fault injection mode: abort-success, abort-failed, "
+                   "in-transition, timeout",
+    .enum_table = &UfsFaultInjectMode_lookup,
+    .get = qdev_propinfo_get_enum,
+    .set = qdev_propinfo_set_enum,
+    .set_default_value = qdev_propinfo_set_default_value_enum,
+    .realized_set_allowed = true,
+};
+
 bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as, Error **errp)
 {
     u->dev = dev;
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 15b3a352f3..80bdaa1a2a 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -90,6 +90,56 @@ typedef struct UfsLu {
     UfsScsiOp scsi_op;
 } UfsLu;

+/*
+ * Fault injection modes for x-fault-inject-mode property.
+ * Controls how the held transfer request and subsequent Task Management
+ * Request (TMR) are resolved by the controller:
+ *
+ * UFS_FAULT_INJECT_ABORT_SUCCESS: TMR ABORT_TASK aborts the held request,
+ *                                 clears its state, and returns SUCCESS
+ *                                 (COMPL).
+ * UFS_FAULT_INJECT_ABORT_FAILED:  TMR ABORT_TASK returns
+ *                                 TASK_MANAGEMENT_FUNC_FAILED, forcing host
+ *                                 driver to escalate to Host Reset.
+ * UFS_FAULT_INJECT_IN_TRANSITION: TMR QUERY_TASK releases the held request to
+ *                                 execute normally through the SCSI layer and
+ *                                 returns the real task status (SUCCEEDED while
+ *                                 the command is still in flight);
+ *                                 ABORT_TASK cancels the held request and
+ *                                 returns FUNCTION_COMPLETE.
+ * UFS_FAULT_INJECT_TIMEOUT:       TMR ABORT_TASK does not complete or clear
+ *                                 doorbell, triggering hardware-level TMR
+ *                                 timeout.
+ */
+typedef enum UfsFaultInjectMode {
+    UFS_FAULT_INJECT_ABORT_SUCCESS = 0,
+    UFS_FAULT_INJECT_ABORT_FAILED,
+    UFS_FAULT_INJECT_IN_TRANSITION,
+    UFS_FAULT_INJECT_TIMEOUT,
+    UFS_FAULT_INJECT_MODE_MAX,
+} UfsFaultInjectMode;
+
+/*
+ * Sentinel values for x-fault-inject-tag property.
+ * Task Tags in UFS are 8-bit (0..255). Values >= 256 are reserved as
+ * sentinel flags to preserve full testability of Tag 0.
+ *
+ * UFS_FAULT_INJECT_TAG_NONE: Disable fault injection (default).
+ * UFS_FAULT_INJECT_TAG_ANY:  Automatically intercept the first user-space
+ *                            READ_10, WRITE_10, READ_16, or WRITE_16 command
+ *                            targeting LBA >= UFS_FAULT_INJECT_MIN_LBA.
+ */
+#define UFS_FAULT_INJECT_TAG_NONE 0xffffffff
+#define UFS_FAULT_INJECT_TAG_ANY  0xfffffffe
+
+/*
+ * Minimum Logical Block Address (LBA) for automatic fault injection.
+ * Skip early disk blocks (LBA 0..511, e.g. MBR, GPT partition tables, EFI,
+ * and filesystem superblocks) to ensure guest OS boot-time disk discovery
+ * and partition scanning complete cleanly without being held.
+ */
+#define UFS_FAULT_INJECT_MIN_LBA 512
+
 typedef struct UfsParams {
     char *serial;
     uint8_t nutrs; /* Number of UTP Transfer Request Slots */
@@ -99,6 +149,8 @@ typedef struct UfsParams {
     uint8_t mcq_maxq; /* MCQ Maximum number of Queues */
     uint32_t wb_max_size; /* WB Maximum allocation units */
     uint32_t wb_min_size; /* WB Minimum allocation units */
+    uint32_t fault_inject_tag;
+    UfsFaultInjectMode fault_inject_mode;
 } UfsParams;

 /*
@@ -188,6 +240,10 @@ typedef struct UfsHc {
     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 */
+
+    /* Test and fault injection properties */
+    uint32_t active_fault_inject_tag;
+    UfsRequest *held_req;
 } UfsHc;

 static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid)
@@ -312,4 +368,9 @@ void ufs_init_wlu(UfsLu *wlu, uint8_t wlun);
 bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as,
                  Error **errp);
 void ufs_unrealize(UfsHc *u);
+
+extern const PropertyInfo ufs_prop_fault_inject_tag;
+extern const PropertyInfo ufs_prop_fault_inject_mode;
+extern const QEnumLookup UfsFaultInjectMode_lookup;
+
 #endif /* HW_UFS_UFS_H */