Re: [PATCH v5 1/2] ata: libata-scsi: terminate deferred commands on time out
Hannes Reinecke <[email protected]> Thu, 16 Jul 2026 15:46:23 +0200
| Newsgroups | org.kernel.vger.linux-ide,org.kernel.vger.linux-scsi |
|---|---|
| Message-ID | <[email protected]> |
On 7/16/26 10:38 AM, Damien Le Moal wrote:
> If a command timeout occurs while we have deferred non-NCQ commands
> waiting to be issued, the SCSI EH task is not immediately woken up as the
> waiting deferred commands are never issued nor completed, thus leaving
> these commands to always be counted as "busy" for the SCSI host. This
> results in the test "shost->host_failed != scsi_host_busy(shost))" in the
> function scsi_error_handler() to always be true, keeping the SCSI EH task
> sleeping. Eventually, when the deferred commands also time out, the SCSI
> EH task is woken up and the timeout processing occurs.
>
> Avoid this unnecessary SCSI EH task wake-up additional time by immediately
> scheduling a retry of all waiting deferred QCs, using the eh_timed_out
> SCSI host template operation. The function ata_scsi_eh_timed_out() is
> introduced to implement this operation.
>
> However, ata_scsi_eh_timed_out() cannot use the function
> ata_scsi_requeue_deferred_qc() to force a retry of waiting deferred QCs,
> because this function completes the waiting QCs using ata_scsi_qc_done(),
> thus trigerring a requeue at the block layer, which itself may result in
> an immediate reissuing of the QCs, thus keeping the device in a busy state
> which prevents the SCSI EH task from waking up. To avoid this,
> ata_scsi_requeue_deferred_qc() is reimplemented as
> ata_eh_retry_deferred_qc() so that the retry of the waiting deferred QCs
> is scheduled through libata EH with ata_qc_schedule_eh(), with the waiting
> QCs flagged with ATA_QCFLAG_RETRY. Since ata_scsi_requeue_deferred_qc()
> was already called only when EH was already scheduled or running, this new
> scheduling of libata EH does not add overhead and guarantees that the
> device is not kept in a busy state, allowing the SCSI EH task to run and
> to execute libata EH. Once done, the deferred QCs retry is scheduled from
> ata_eh_finish() calling ata_eh_qc_retry(). This new path requires a small
> modification of __ata_eh_qc_complete() to avoid warnings due to the fact
> that deferred QCs are not flagged as active (ATA_QCFLAG_ACTIVE is not set
> as these QCs have not been issued yet).
>
> Of note is that since ata_scsi_cmd_error_handler() already handles
> directly deferred QCs that timed out, ata_eh_retry_deferred_qc() always
> ignores a deferred QC that correspond to a timed out SCSI command.
>
> Since ata_scsi_eh_timed_out() does not fully handle the timeout itself,
> this function returns SCSI_EH_NOT_HANDLED to have scsi_timeout() continue
> with the regular timeout handling, using scsi_abort_command() and
> scsi_eh_scmd_add(), thus preventing the wkae-up delay for SCSI EH task.
>
> Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation")
> Cc: [email protected]
> Signed-off-by: Damien Le Moal <[email protected]>
> ---
> drivers/ata/libata-eh.c | 38 +++++++++++++++++++++-----
> drivers/ata/libata-scsi.c | 56 +++++++++++++++++++++++----------------
> drivers/ata/libata.h | 2 +-
> include/linux/libata.h | 2 ++
> 4 files changed, 68 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
> index 29ec0f7fef4a..7943593d49c8 100644
> --- a/drivers/ata/libata-eh.c
> +++ b/drivers/ata/libata-eh.c
> @@ -546,6 +546,27 @@ static void ata_eh_unload(struct ata_port *ap)
> spin_unlock_irqrestore(ap->lock, flags);
> }
>
> +void ata_eh_retry_deferred_qc(struct ata_port *ap,
> + struct scsi_cmnd *timedout_scmd)
> +{
> + struct ata_queued_cmd *qc;
> + struct ata_link *link;
> +
> + lockdep_assert_held(ap->lock);
> +
> + /* Trigger EH for retrying any deferred qc that has not timed out. */
> + ata_for_each_link(link, ap, PMP_FIRST) {
> + qc = link->deferred_qc;
> + if (!qc || qc->scsicmd == timedout_scmd)
> + continue;
> +
> + link->deferred_qc = NULL;
> + cancel_work(&link->deferred_qc_work);
> + qc->flags |= ATA_QCFLAG_RETRY;
> + ata_qc_schedule_eh(qc);
> + }
> +}
> +
> /**
> * ata_scsi_error - SCSI layer error handler callback
> * @host: SCSI host on which error occurred
> @@ -947,11 +968,8 @@ static void ata_eh_set_pending(struct ata_port *ap, bool fastdrain)
>
> ap->pflags |= ATA_PFLAG_EH_PENDING;
>
> - /*
> - * If we have a deferred qc, requeue it so that it is retried once EH
> - * completes.
> - */
> - ata_scsi_requeue_deferred_qc(ap);
> + /* If we have deferred QCs, tell EH to retry them. */
> + ata_eh_retry_deferred_qc(ap, NULL);
>
> if (!fastdrain)
> return;
> @@ -1214,9 +1232,17 @@ static void __ata_eh_qc_complete(struct ata_queued_cmd *qc)
> struct scsi_cmnd *scmd = qc->scsicmd;
> unsigned long flags;
>
> +
> + /*
> + * If we are retrying a deferred QC after a timeout, it is not active
> + * and all we need to do is to complete it directly.
> + */
> spin_lock_irqsave(ap->lock, flags);
> qc->scsidone = ata_eh_scsidone;
> - __ata_qc_complete(qc);
> + if ((qc->flags & ATA_QCFLAG_RETRY) && !(qc->flags & ATA_QCFLAG_ACTIVE))
> + qc->complete_fn(qc);
> + else
> + __ata_qc_complete(qc);
The original code did set a SCSI status, which I don't see here.
ata_scsi_qc_complete() will try to generate a status code from
the qc, which will be different from the original (DID_REQUEUE).
Sholdn't we set 'DID_REQUEUE', too?
> WARN_ON(ata_tag_valid(qc->tag));
> spin_unlock_irqrestore(ap->lock, flags);
>
> diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
> index 5868526301a2..1870669b8e05 100644
> --- a/drivers/ata/libata-scsi.c
> +++ b/drivers/ata/libata-scsi.c
> @@ -1685,28 +1685,6 @@ void ata_scsi_deferred_qc_work(struct work_struct *work)
> spin_unlock_irqrestore(ap->lock, flags);
> }
>
> -void ata_scsi_requeue_deferred_qc(struct ata_port *ap)
> -{
> - struct ata_link *link;
> -
> - lockdep_assert_held(ap->lock);
> -
> - /*
> - * If we have a deferred qc when a reset occurs or NCQ commands fail,
> - * do not try to be smart about what to do with this deferred command
> - * and simply requeue it by completing it with DID_REQUEUE.
> - */
> - ata_for_each_link(link, ap, PMP_FIRST) {
> - struct ata_queued_cmd *qc = link->deferred_qc;
> -
> - if (qc) {
> - link->deferred_qc = NULL;
> - cancel_work(&link->deferred_qc_work);
> - ata_scsi_qc_done(qc, true, DID_REQUEUE << 16);
> - }
> - }
> -}
> -
> static void ata_scsi_schedule_deferred_qc(struct ata_link *link)
> {
> struct ata_queued_cmd *qc = link->deferred_qc;
> @@ -1723,13 +1701,45 @@ static void ata_scsi_schedule_deferred_qc(struct ata_link *link)
> return;
>
> if (ata_port_eh_scheduled(ap)) {
> - ata_scsi_requeue_deferred_qc(ap);
> + ata_eh_retry_deferred_qc(ap, NULL);
> return;
> }
> if (!ap->ops->qc_defer(qc))
> queue_work(system_highpri_wq, &link->deferred_qc_work);
> }
>
> +static void ata_scsi_retry_deferred_qc(struct ata_port *ap,
> + struct scsi_cmnd *scmd)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(ap->lock, flags);
> + ata_eh_retry_deferred_qc(ap, scmd);
> + spin_unlock_irqrestore(ap->lock, flags);
> +}
> +
> +enum scsi_timeout_action ata_scsi_eh_timed_out(struct scsi_cmnd *scmd)
> +{
> + struct ata_port *ap = ata_shost_to_port(scmd->device->host);
> +
> + /*
> + * ata_scsi_cmd_error_handler() takes care of timed-out deferred queued
> + * commands. However, if we had any other command time out and we have
> + * deferred queued commands, we must let scsi_timeout() handle them
> + * with scsi_eh_scmd_add() so that we do not unnecessarilly delay
> + * starting the SCSI EH task. So schedule all deferred queued commands
> + * for retry through EH.
> + */
> + ata_scsi_retry_deferred_qc(ap, scmd);
> +
> + /*
> + * Let scsi_timeout() know that it must continue with handling the
> + * timeout as we in fact did not do much here.
> + */
> + return SCSI_EH_NOT_HANDLED;
> +}
> +EXPORT_SYMBOL_GPL(ata_scsi_eh_timed_out);
> +
> static void ata_scsi_qc_complete(struct ata_queued_cmd *qc)
> {
> struct ata_link *link = qc->dev->link;
> diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h
> index 700627596ce1..efaccebe93f5 100644
> --- a/drivers/ata/libata.h
> +++ b/drivers/ata/libata.h
> @@ -180,7 +180,6 @@ enum scsi_qc_status __ata_scsi_queuecmd(struct scsi_cmnd *scmd,
> struct ata_port *ap)
> __must_hold(ap->lock);
> void ata_scsi_deferred_qc_work(struct work_struct *work);
> -void ata_scsi_requeue_deferred_qc(struct ata_port *ap);
>
> /* libata-eh.c */
> extern unsigned int ata_internal_cmd_timeout(struct ata_device *dev, u8 cmd);
> @@ -198,6 +197,7 @@ extern void ata_eh_about_to_do(struct ata_link *link, struct ata_device *dev,
> unsigned int action);
> extern void ata_eh_done(struct ata_link *link, struct ata_device *dev,
> unsigned int action);
> +void ata_eh_retry_deferred_qc(struct ata_port *ap, struct scsi_cmnd *scmd);
> extern void ata_eh_autopsy(struct ata_port *ap);
> const char *ata_get_cmd_name(u8 command);
> extern void ata_eh_report(struct ata_port *ap);
> diff --git a/include/linux/libata.h b/include/linux/libata.h
> index 30fdf673b887..51178acd68d7 100644
> --- a/include/linux/libata.h
> +++ b/include/linux/libata.h
> @@ -1153,6 +1153,7 @@ extern int ata_scsi_ioctl(struct scsi_device *dev, unsigned int cmd,
> #endif
> extern enum scsi_qc_status ata_scsi_queuecmd(struct Scsi_Host *h,
> struct scsi_cmnd *cmd);
> +enum scsi_timeout_action ata_scsi_eh_timed_out(struct scsi_cmnd *cmd);
> #if IS_REACHABLE(CONFIG_ATA)
> bool ata_scsi_dma_need_drain(struct request *rq);
> #else
> @@ -1461,6 +1462,7 @@ extern const struct attribute_group *ata_common_sdev_groups[];
> .ioctl = ata_scsi_ioctl, \
> ATA_SCSI_COMPAT_IOCTL \
> .queuecommand = ata_scsi_queuecmd, \
> + .eh_timed_out = ata_scsi_eh_timed_out, \
> .dma_need_drain = ata_scsi_dma_need_drain, \
> .this_id = ATA_SHT_THIS_ID, \
> .emulated = ATA_SHT_EMULATED, \
Otherwise looks okay.
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
[email protected] +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich