mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Bart Van Assche <bvanassche@acm.org>
To: Alexey Charkov <alchark@flipper.net>,
	"James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>,
	"Martin K. Petersen" <mkp@kernel.org>
Cc: linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2] scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices
Date: Thu, 29 Oct 2026 10:19:57 -0700	[thread overview]
Message-ID: <e9ada0a2-aa36-4cde-a7b7-5d4693d372ae@acm.org> (raw)
In-Reply-To: <20261029-biwin-tua-quirk-v2-1-4d71af956ac5@flipper.net>

[-- Attachment #1: Type: text/plain, Size: 583 bytes --]

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.

[-- Attachment #2: 0001-scsi-core-Clear-persistent-UNIT-ATTENTION-with-TEST-.patch --]
[-- Type: text/x-patch, Size: 9153 bytes --]

From a81dfc5b9d1ae8d77c0ef4ea70322c937767a8f2 Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bvanassche@acm.org>
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@flipper.net>
Closes: https://lore.kernel.org/linux-scsi/20261028-biwin-tua-quirk-v1-1-767a12c9b9cc@flipper.net/
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 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

      parent reply	other threads:[~2026-10-29 17:20 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 10:41 Alexey Charkov
     [not found] ` <sashiko-outbox-189185@kernel.org>
2026-10-29 13:33   ` Alexey Charkov
2026-10-29 17:19 ` Bart Van Assche [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=e9ada0a2-aa36-4cde-a7b7-5d4693d372ae@acm.org \
    --to=bvanassche@acm.org \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=alchark@flipper.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=mkp@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®