Extend the experimental fault injection property x-fault-inject-mode with a new "ssu-timeout" mode to facilitate automated testing of host driver power management and Start Stop Unit (SSU) recovery handling.
When x-fault-inject-mode="ssu-timeout" is set and x-fault-inject-tag is armed (e.g., 0xfffffffe for any tag, or a matching tag), the controller only intercepts SCSI START_STOP (0x1B) commands sent to the UFS Device Well-Known LUN (WLUN) and holds them without queuing them to the SCSI layer, while letting non-SSU commands complete normally. This triggers the host driver's 10-second SSU command timeout and subsequent power management recovery routines (e.g., during suspend-to-idle or freeze transitions), verifying host error recovery and link restoration paths. Signed-off-by: Stanley Jhu <[email protected]> --- hw/ufs/ufs.c | 24 +++++++++++++++++++++--- hw/ufs/ufs.h | 4 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 6d7bd16953..c54b31716e 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -884,6 +884,7 @@ static void ufs_fault_inject_sq_cleanup(UfsHc *u, UfsRequest *req) static bool ufs_fault_inject_exec_req(UfsRequest *req) { UfsHc *u = req->hc; + uint8_t op; if (likely(u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_NONE) || u->held_req != NULL || @@ -891,9 +892,21 @@ static bool ufs_fault_inject_exec_req(UfsRequest *req) return false; } - if (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY) { - uint8_t op = req->req_upiu.sc.cdb[0]; + op = req->req_upiu.sc.cdb[0]; + if (u->fault_inject_mode == UFS_FAULT_INJECT_SSU_TIMEOUT) { + if (op == START_STOP && + req->req_upiu.header.lun == UFS_UPIU_UFS_DEVICE_WLUN && + (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY || + req->req_upiu.header.task_tag == u->active_fault_inject_tag)) { + 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, 0); + return true; + } + return false; + } + if (u->active_fault_inject_tag == UFS_FAULT_INJECT_TAG_ANY) { if (op == READ_10 || op == WRITE_10) { /* * In 10-byte SCSI read/write commands (SBC-4), bytes 2..5 @@ -3233,6 +3246,8 @@ static bool ufs_fault_inject_parse_mode(const char *str, *mode = UFS_FAULT_INJECT_IN_TRANSITION; } else if (g_strcmp0(str, "timeout") == 0) { *mode = UFS_FAULT_INJECT_TIMEOUT; + } else if (g_strcmp0(str, "ssu-timeout") == 0) { + *mode = UFS_FAULT_INJECT_SSU_TIMEOUT; } else { error_setg(errp, "invalid x-fault-inject-mode: %s", str); return false; @@ -3320,6 +3335,9 @@ static void ufs_prop_get_fault_inject_mode(Object *obj, Visitor *v, case UFS_FAULT_INJECT_TIMEOUT: str = "timeout"; break; + case UFS_FAULT_INJECT_SSU_TIMEOUT: + str = "ssu-timeout"; + break; case UFS_FAULT_INJECT_ABORT_SUCCESS: default: str = "abort-success"; @@ -3370,7 +3388,7 @@ static void ufs_prop_release_fault_inject_mode(Object *obj, const char *name, const PropertyInfo ufs_prop_fault_inject_mode = { .type = "str", .description = "Fault injection mode: abort-success, abort-failed, " - "in-transition, timeout", + "in-transition, timeout, ssu-timeout", .get = ufs_prop_get_fault_inject_mode, .set = ufs_prop_set_fault_inject_mode, .release = ufs_prop_release_fault_inject_mode, diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 39213650bb..29332e6b1d 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -151,12 +151,16 @@ typedef struct UfsLu { * UFS_FAULT_INJECT_TIMEOUT: TMR ABORT_TASK does not complete or clear * doorbell, triggering hardware-level TMR * timeout. + * UFS_FAULT_INJECT_SSU_TIMEOUT: Intercepts SCSI START_STOP (0x1B) commands + * targeting the UFS Device WLUN when + * x-fault-inject-tag is armed. */ 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_SSU_TIMEOUT, } UfsFaultInjectMode; /* -- 2.56.0.218.g1ec904b173-goog
