Re: [PATCH v2] scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices
From: Bart Van Assche
Date: Thu Oct 29 2026 - 13:20:18 EST
On 10/29/26 3:41 AM, Alexey Charkov wrote:
The SCSI core retries a command that receives a UNIT ATTENTION and relies
on the device to clear the condition once reported. The BIWIN
BWU2A0526B128G and BWU2A0516B064G UFS devices do not clear a power on or
reset UNIT ATTENTION when reporting it to a WRITE. After a device reset,
such as on resume with the UFS link off or in UFS error recovery, writes
then fail on every retry for the full 180s retry window and end in I/O
errors. A TEST UNIT READY or READ clears it.
Does the attached untested patch help?
Thanks,
Bart.From a81dfc5b9d1ae8d77c0ef4ea70322c937767a8f2 Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bvanassche@xxxxxxx>
Date: Thu, 29 Oct 2026 10:11:42 -0700
Subject: [PATCH] scsi: core: Clear persistent UNIT ATTENTION with TEST UNIT
READY
Recover from persistent UNIT ATTENTION conditions in the SCSI midlayer
instead of maintaining a per-device quirk list for targets such as
BIWIN BWU2A05* UFS devices that fail to clear power-on UNIT ATTENTION
on WRITE commands after a reset and only clear it on TEST UNIT READY.
Retry UNIT ATTENTION up to twice on the normal completion path in
scsi_check_sense() for non-removable block requests by returning
NEEDS_RETRY while scmd->retries < 2 and FAILED thereafter, instead of
letting scsi_io_completion_action() retry uncounted until the command
runtime limit is exceeded.
Add scsi_eh_clear_ua() to scsi_eh_ready_devs() ahead of device reset
so that the error handler sends TEST UNIT READY to consume a stuck
UNIT ATTENTION and verifies with a follow-up TEST UNIT READY that the
condition cleared before requeueing affected commands. Save and
restore scmd->sense_buffer across scsi_send_eh_cmnd() so that the
original sense data remains intact while filtering work_q and if
retries are later exhausted.
Introduce scsi_cmd_needs_stu() and use it in scsi_eh_stu() instead of
testing scsi_check_sense(scmd) == FAILED so that scsi_eh_stu() only
sends START_UNIT for LU_NOT_READY_INITIALIZING_COMMAND_REQUIRED and
not for persistent UNIT ATTENTION when sdev->allow_restart is set.
Fail non-removable commands in scsi_io_completion_action() on UNIT
ATTENTION when scsi_noretry_cmd() is true or retries exceed
max(cmd->allowed, 3) to honor failfast flags and prevent infinite
retry loops if a target continues to report UNIT ATTENTION after
recovery.
Reported-by: Alexey Charkov <alchark@xxxxxxxxxxx>
Closes: https://lore.kernel.org/linux-scsi/20261028-biwin-tua-quirk-v1-1-767a12c9b9cc@xxxxxxxxxxx/
Signed-off-by: Bart Van Assche <bvanassche@xxxxxxx>
---
drivers/scsi/scsi_error.c | 167 ++++++++++++++++++++++++++++++++++++--
drivers/scsi/scsi_lib.c | 4 +
2 files changed, 162 insertions(+), 9 deletions(-)
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
index 75442d60c30e..e7899c86d171 100644
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -716,6 +716,20 @@ enum scsi_disposition scsi_check_sense(struct scsi_cmnd *scmd)
sshdr.sense_code ==
LU_NOT_READY_INITIALIZING_COMMAND_REQUIRED)
return FAILED;
+ /*
+ * Retry UNIT ATTENTION up to twice on the normal completion
+ * path for non-removable block requests, then invoke the error
+ * handler so TEST UNIT READY can clear a stuck UNIT ATTENTION.
+ */
+ if (sshdr.sense_key == UNIT_ATTENTION &&
+ !sdev->removable &&
+ !blk_rq_is_passthrough(req) &&
+ scmd->submitter != SUBMITTED_BY_SCSI_ERROR_HANDLER &&
+ !scsi_noretry_cmd(scmd)) {
+ if (scmd->retries < 2)
+ return NEEDS_RETRY;
+ return FAILED;
+ }
/*
* Pass the UA upwards for a determination in the completion
* functions.
@@ -1582,6 +1596,24 @@ static int scsi_eh_try_stu(struct scsi_cmnd *scmd)
return 1;
}
+static bool scsi_cmd_needs_stu(const struct scsi_cmnd *scmd)
+{
+ struct scsi_sense_hdr sshdr;
+
+ if (!scmd->device->allow_restart ||
+ (scmd->eh_eflags & SCSI_EH_ABORT_SCHEDULED) ||
+ host_byte(scmd->result) != DID_OK ||
+ !scsi_status_is_check_condition(scmd->result) ||
+ !SCSI_SENSE_VALID(scmd))
+ return false;
+
+ return scsi_command_normalize_sense(scmd, &sshdr) &&
+ !scsi_sense_is_deferred(&sshdr) &&
+ (sshdr.sense_key == NOT_READY ||
+ sshdr.sense_key == UNIT_ATTENTION) &&
+ sshdr.sense_code == LU_NOT_READY_INITIALIZING_COMMAND_REQUIRED;
+}
+
/**
* scsi_eh_stu - send START_UNIT if needed
* @shost: &scsi host being recovered.
@@ -1610,8 +1642,7 @@ static int scsi_eh_stu(struct Scsi_Host *shost,
}
stu_scmd = NULL;
list_for_each_entry(scmd, work_q, eh_entry)
- if (scmd->device == sdev && SCSI_SENSE_VALID(scmd) &&
- scsi_check_sense(scmd) == FAILED ) {
+ if (scmd->device == sdev && scsi_cmd_needs_stu(scmd)) {
stu_scmd = scmd;
break;
}
@@ -1645,6 +1676,124 @@ static int scsi_eh_stu(struct Scsi_Host *shost,
return list_empty(work_q);
}
+static bool scsi_cmd_has_ua(const struct scsi_cmnd *scmd)
+{
+ struct scsi_sense_hdr sshdr;
+
+ if ((scmd->eh_eflags & SCSI_EH_ABORT_SCHEDULED) ||
+ host_byte(scmd->result) != DID_OK ||
+ !scsi_status_is_check_condition(scmd->result) ||
+ !SCSI_SENSE_VALID(scmd))
+ return false;
+
+ return scsi_command_normalize_sense(scmd, &sshdr) &&
+ !scsi_sense_is_deferred(&sshdr) &&
+ sshdr.sense_key == UNIT_ATTENTION;
+}
+
+static bool scsi_eh_send_tur_clear_ua(struct scsi_cmnd *scmd)
+{
+ static unsigned char tur_command[6] = { TEST_UNIT_READY };
+ u8 sense_buf[SCSI_SENSE_BUFFERSIZE];
+ struct scsi_sense_hdr sshdr;
+ enum scsi_disposition rtn;
+ bool cleared = false;
+ int i;
+
+ memcpy(sense_buf, scmd->sense_buffer, sizeof(sense_buf));
+
+ /*
+ * Send TEST UNIT READY to consume the pending UNIT ATTENTION, then
+ * send a follow-up TEST UNIT READY to verify that the condition
+ * cleared rather than remaining stuck.
+ */
+ for (i = 0; i < 4; i++) {
+ rtn = scsi_send_eh_cmnd(scmd, tur_command, sizeof(tur_command),
+ scmd->device->eh_timeout, 0);
+ if (rtn == NEEDS_RETRY)
+ continue;
+ if (rtn != SUCCESS)
+ break;
+ if (!scsi_command_normalize_sense(scmd, &sshdr) ||
+ sshdr.sense_key == NO_SENSE ||
+ sshdr.sense_key == RECOVERED_ERROR) {
+ cleared = true;
+ break;
+ }
+ if (sshdr.sense_key != UNIT_ATTENTION)
+ break;
+ }
+
+ memcpy(scmd->sense_buffer, sense_buf, sizeof(sense_buf));
+ return cleared;
+}
+
+/**
+ * scsi_eh_clear_ua - clear pending UNIT ATTENTION conditions if needed
+ * @shost: SCSI host being recovered.
+ * @work_q: &list_head for pending commands.
+ * @done_q: &list_head for processed commands.
+ *
+ * If commands fail repeatedly with UNIT ATTENTION because a target does not
+ * clear the condition for those commands (for example, WRITE), send
+ * TEST UNIT READY to clear the UNIT ATTENTION before escalating to a reset.
+ *
+ * Return: 1 if @work_q is empty, 0 otherwise.
+ */
+static int scsi_eh_clear_ua(struct Scsi_Host *shost,
+ struct list_head *work_q,
+ struct list_head *done_q)
+{
+ struct scsi_cmnd *scmd, *ua_scmd, *next;
+ struct scsi_device *sdev;
+
+ shost_for_each_device(sdev, shost) {
+ if (scsi_host_eh_past_deadline(shost)) {
+ SCSI_LOG_ERROR_RECOVERY(3,
+ sdev_printk(KERN_INFO, sdev,
+ "%s: skip clear UA, past eh deadline\n",
+ current->comm));
+ scsi_device_put(sdev);
+ break;
+ }
+
+ ua_scmd = NULL;
+ list_for_each_entry(scmd, work_q, eh_entry) {
+ if (scmd->device == sdev && scsi_cmd_has_ua(scmd)) {
+ ua_scmd = scmd;
+ break;
+ }
+ }
+
+ if (!ua_scmd)
+ continue;
+
+ SCSI_LOG_ERROR_RECOVERY(3,
+ sdev_printk(KERN_INFO, sdev,
+ "%s: Sending TEST_UNIT_READY to clear UA\n",
+ current->comm));
+
+ if (!scsi_device_online(sdev) ||
+ scsi_eh_send_tur_clear_ua(ua_scmd)) {
+ if (sdev->expecting_cc_ua)
+ sdev->expecting_cc_ua = 0;
+ list_for_each_entry_safe(scmd, next, work_q, eh_entry) {
+ if (scmd->device == sdev &&
+ scsi_cmd_has_ua(scmd) &&
+ scsi_eh_action(scmd, SUCCESS) == SUCCESS)
+ scsi_eh_finish_cmd(scmd, done_q);
+ }
+ } else {
+ SCSI_LOG_ERROR_RECOVERY(3,
+ sdev_printk(KERN_INFO, sdev,
+ "%s: Failed to clear UA\n",
+ current->comm));
+ }
+ }
+
+ return list_empty(work_q);
+}
+
/**
* scsi_eh_bus_device_reset - send bdr if needed
@@ -2275,13 +2424,13 @@ void scsi_eh_ready_devs(struct Scsi_Host *shost,
struct list_head *work_q,
struct list_head *done_q)
{
- if (!scsi_eh_stu(shost, work_q, done_q))
- if (!scsi_eh_bus_device_reset(shost, work_q, done_q))
- if (!scsi_eh_target_reset(shost, work_q, done_q))
- if (!scsi_eh_bus_reset(shost, work_q, done_q))
- if (!scsi_eh_host_reset(shost, work_q, done_q))
- scsi_eh_offline_sdevs(work_q,
- done_q);
+ if (!scsi_eh_stu(shost, work_q, done_q) &&
+ !scsi_eh_clear_ua(shost, work_q, done_q) &&
+ !scsi_eh_bus_device_reset(shost, work_q, done_q) &&
+ !scsi_eh_target_reset(shost, work_q, done_q) &&
+ !scsi_eh_bus_reset(shost, work_q, done_q) &&
+ !scsi_eh_host_reset(shost, work_q, done_q))
+ scsi_eh_offline_sdevs(work_q, done_q);
}
EXPORT_SYMBOL_GPL(scsi_eh_ready_devs);
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index ab39514cc0c6..28fcfc6de117 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -849,6 +849,10 @@ static void scsi_io_completion_action(struct scsi_cmnd *cmd, int result)
*/
cmd->device->changed = 1;
action = ACTION_FAIL;
+ } else if (scsi_noretry_cmd(cmd) ||
+ (cmd->allowed != SCSI_CMD_RETRIES_NO_LIMIT &&
+ cmd->retries > max(cmd->allowed, 3))) {
+ action = ACTION_FAIL;
} else {
/* Must have been a power glitch, or a
* bus reset. Could not have been a