* Re: [PATCH v2] scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices
2026-10-29 10:41 [PATCH v2] scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices Alexey Charkov
@ 2026-10-29 11:12 ` sashiko-bot
2026-10-29 13:33 ` Alexey Charkov
2026-10-29 17:19 ` Bart Van Assche
1 sibling, 1 reply; 4+ messages in thread
From: sashiko-bot @ 2026-10-29 11:12 UTC (permalink / raw)
To: Alexey Charkov; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] scsi: core: tag starvation in scsi_clear_reset_ua() when writes saturate queue
- [High] scsi: core: missing quiesce and negative RPM check in scsi_clear_reset_ua()
--
commit a245ee3df8cf93fd94b7ad3e84680f44164fbb37
Author: Alexey Charkov <alchark@flipper.net>
scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices
This commit adds the BLIST_TUR_AFTER_RESET quirk flag for BIWIN BWU2A05 UFS
devices and issues a TEST UNIT READY command from scsi_evt_thread() when
SDEV_EVT_POWER_ON_RESET_OCCURRED is pending and the device is runtime active.
> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> index ab39514cc0c6b..863ae93f2618b 100644
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
[ ... ]
> @@ -2713,6 +2714,32 @@ static void scsi_evt_emit(struct scsi_device *sdev, struct scsi_event *evt)
> kobject_uevent_env(&sdev->sdev_gendev.kobj, KOBJ_CHANGE, envp);
> }
>
> +/*
> + * Some devices do not clear a power on or reset UNIT ATTENTION when reporting
> + * it to commands such as WRITE, which then keep failing on every retry. Send
> + * them a TEST UNIT READY, which does clear it.
> + *
> + * Leave a device that is not runtime active alone. Until it is, only PM
> + * requests can reach it, and their senders deal with the UNIT ATTENTION. Any
> + * other command that keeps failing with it holds a queue reference, which
> + * aborts a runtime suspend, and re-arms this event on every retry.
> + */
> +static void scsi_clear_reset_ua(struct scsi_device *sdev)
> +{
> + struct device *dev = &sdev->sdev_gendev;
> + struct scsi_sense_hdr sshdr;
> + int active;
> +
> + active = pm_runtime_get_if_active(dev);
> + if (!active)
> + return;
[Severity: High]
What happens if runtime PM is disabled on dev, or if sdev is quiesced when
scsi_clear_reset_ua() runs?
In drivers/base/power/runtime.c:pm_runtime_get_conditional(), disable_depth
is checked before runtime_status:
spin_lock_irqsave(&dev->power.lock, flags);
if (dev->power.disable_depth > 0) {
retval = -EINVAL;
} else if (dev->power.runtime_status != RPM_ACTIVE) {
retval = 0;
When runtime PM is disabled (for example during system suspend via
device_suspend() / __device_suspend_late() or during device removal via
__scsi_remove_device() -> device_del()), pm_runtime_get_if_active() returns
-EINVAL even if the device is RPM_SUSPENDED. Because if (!active) treats
-EINVAL as non-zero, scsi_clear_reset_ua() proceeds to issue
scsi_test_unit_ready(), which blocks in blk_queue_enter() on q->mq_freeze_wq.
Even when active > 0, the device may be quiesced via
drivers/scsi/scsi_lib.c:scsi_device_quiesce():
blk_set_pm_only(q);
memflags = blk_mq_freeze_queue(q);
synchronize_rcu();
blk_mq_unfreeze_queue(q, memflags);
mutex_lock(&sdev->state_mutex);
err = scsi_device_set_state(sdev, SDEV_QUIESCE);
This leaves dev->power.runtime_status at RPM_ACTIVE with disable_depth == 0
during system suspend/resume or UFS shutdown in
drivers/ufs/core/ufshcd.c:ufshcd_wl_shutdown():
ufshcd_rpm_get_sync(hba);
ufshcd_store_tx_eq_settings(hba);
scsi_device_quiesce(sdev);
...
__ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
On BIWIN BWU2A05 devices, hba->ufs_device_wlun also gets tur_after_reset = 1.
If a BLK_MQ_REQ_PM command (such as START_STOP in ufshcd_set_dev_pwr_mode()
or SYNCHRONIZE_CACHE in sd_suspend_common()) reports a power-on or reset unit
attention while sdev is quiesced, scsi_report_sense() schedules
sdev->event_work. When scsi_clear_reset_ua() then submits a non-PM request via
scsi_test_unit_ready(), blk_queue_enter() sleeps in TASK_UNINTERRUPTIBLE on
q->mq_freeze_wq while holding the runtime PM reference (hanging indefinitely
in ufshcd_wl_shutdown() where scsi_device_resume() is never called).
Should scsi_clear_reset_ua() check if (active <= 0) (or handle -EINVAL when
CONFIG_PM=y) and also guard against sdev->sdev_state != SDEV_RUNNING ||
blk_queue_pm_only(sdev->request_queue), similar to
drivers/scsi/scsi_scan.c:scsi_rescan_device()?
> +
> + scsi_test_unit_ready(sdev, sdev->request_queue->rq_timeout, 1, &sshdr);
[Severity: High]
Can scsi_test_unit_ready() starve waiting for a request tag when retrying
writes saturate the queue?
scsi_test_unit_ready() calls scsi_execute_cmd() -> scsi_alloc_request() ->
blk_mq_alloc_request() with flags == 0, which waits in blk_mq_get_tag() for a
normal non-reserved tag from bitmap_tags.
When write commands receive a power-on/reset unit attention,
drivers/scsi/scsi_lib.c:scsi_io_completion_action() requeues them immediately:
case ACTION_RETRY:
/* Retry the same command immediately */
__scsi_queue_insert(cmd, SCSI_MLQUEUE_EH_RETRY, false);
break;
In __scsi_queue_insert() -> blk_mq_requeue_request() ->
__blk_mq_requeue_request() -> blk_mq_put_driver_tag():
if (rq->tag == BLK_MQ_NO_TAG || rq->internal_tag == BLK_MQ_NO_TAG)
return;
__blk_mq_put_driver_tag(rq->mq_hctx, rq);
When an I/O scheduler is attached (such as the default mq-deadline on UFS,
where dd_init_sched() sets q->async_depth = q->nr_requests so writes can
consume all tags in sched_tags->bitmap_tags), __blk_mq_requeue_request()
releases rq->tag but keeps the scheduler tag (rq->internal_tag) across all
retries. When no I/O scheduler is configured, blk_mq_limit_depth() imposes no
limit on writes and blk_mq_put_driver_tag() does not release rq->tag on
requeue because rq->internal_tag == BLK_MQ_NO_TAG.
If enough write requests are queued to fill all non-reserved tags across a
reset or runtime resume, won't those writes endlessly fail and requeue while
pinning every tag, blocking scsi_test_unit_ready() in blk_mq_get_tag() until
a write exceeds the 180s retry window and fails with an I/O error?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-biwin-tua-quirk-v2-1-4d71af956ac5@flipper.net?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH v2] scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices
2026-10-29 10:41 [PATCH v2] scsi: core: Clear power-on UNIT ATTENTION on BIWIN UFS devices Alexey Charkov
2026-10-29 11:12 ` sashiko-bot
@ 2026-10-29 17:19 ` Bart Van Assche
1 sibling, 0 replies; 4+ messages in thread
From: Bart Van Assche @ 2026-10-29 17:19 UTC (permalink / raw)
To: Alexey Charkov, James E.J. Bottomley, Martin K. Petersen
Cc: linux-scsi, linux-kernel
[-- 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
^ permalink raw reply related [flat|nested] 4+ messages in thread