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