diff options
author | Tejun Heo <htejun@gmail.com> | 2006-02-10 15:10:48 +0900 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2006-02-10 06:50:46 -0500 |
commit | f29841e08fa20a7f2c8bc1b70306975299c66ee7 (patch) | |
tree | c2f6c87cf79fbd36ec49ea3c2247a382da5d75cc | |
parent | 341963b909a01d2f38d86f5db8dd1f8c80bd6dbf (diff) |
[PATCH] libata: implement ata_scsi_timed_out()
Implement ata_scsi_timed_out(), to be used as
scsi_host_template->eh_timed_out callback for all libata drivers.
Without this function, the following race exists.
If a qc completes after SCSI timer expires but before libata EH kicks
in, the qc gets completed but the scsicmd still gets passed to libata
EH resulting in ->eng_timeout invocation with NULL qc, which none is
handling properly.
This patch makes sure that scmd and qc share the same lifetime.
Original idea from Jeff Garzik <jgarzik@pobox.com>.
Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Jeff Garzik <jgarzik@pobox.com>
-rw-r--r-- | drivers/scsi/libata-core.c | 1 | ||||
-rw-r--r-- | drivers/scsi/libata-scsi.c | 41 | ||||
-rw-r--r-- | include/linux/libata.h | 1 |
3 files changed, 43 insertions, 0 deletions
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c index 977a53dd167..d53e0bce2be 100644 --- a/drivers/scsi/libata-core.c +++ b/drivers/scsi/libata-core.c @@ -4913,6 +4913,7 @@ EXPORT_SYMBOL_GPL(ata_ratelimit); EXPORT_SYMBOL_GPL(ata_busy_sleep); EXPORT_SYMBOL_GPL(ata_scsi_ioctl); EXPORT_SYMBOL_GPL(ata_scsi_queuecmd); +EXPORT_SYMBOL_GPL(ata_scsi_timed_out); EXPORT_SYMBOL_GPL(ata_scsi_error); EXPORT_SYMBOL_GPL(ata_scsi_slave_config); EXPORT_SYMBOL_GPL(ata_scsi_release); diff --git a/drivers/scsi/libata-scsi.c b/drivers/scsi/libata-scsi.c index 1df468eb2bf..d67cc2fb569 100644 --- a/drivers/scsi/libata-scsi.c +++ b/drivers/scsi/libata-scsi.c @@ -717,6 +717,47 @@ int ata_scsi_slave_config(struct scsi_device *sdev) } /** + * ata_scsi_timed_out - SCSI layer time out callback + * @cmd: timed out SCSI command + * + * Handles SCSI layer timeout. We race with normal completion of + * the qc for @cmd. If the qc is already gone, we lose and let + * the scsi command finish (EH_HANDLED). Otherwise, the qc has + * timed out and EH should be invoked. Prevent ata_qc_complete() + * from finishing it by setting EH_SCHEDULED and return + * EH_NOT_HANDLED. + * + * LOCKING: + * Called from timer context + * + * RETURNS: + * EH_HANDLED or EH_NOT_HANDLED + */ +enum scsi_eh_timer_return ata_scsi_timed_out(struct scsi_cmnd *cmd) +{ + struct Scsi_Host *host = cmd->device->host; + struct ata_port *ap = (struct ata_port *) &host->hostdata[0]; + unsigned long flags; + struct ata_queued_cmd *qc; + enum scsi_eh_timer_return ret = EH_HANDLED; + + DPRINTK("ENTER\n"); + + spin_lock_irqsave(&ap->host_set->lock, flags); + qc = ata_qc_from_tag(ap, ap->active_tag); + if (qc) { + assert(qc->scsicmd == cmd); + qc->flags |= ATA_QCFLAG_EH_SCHEDULED; + qc->err_mask |= AC_ERR_TIMEOUT; + ret = EH_NOT_HANDLED; + } + spin_unlock_irqrestore(&ap->host_set->lock, flags); + + DPRINTK("EXIT, ret=%d\n", ret); + return ret; +} + +/** * ata_scsi_error - SCSI layer error handler callback * @host: SCSI host on which error occurred * diff --git a/include/linux/libata.h b/include/linux/libata.h index 5c70a57f93e..c1e198655bb 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -509,6 +509,7 @@ extern void ata_host_set_remove(struct ata_host_set *host_set); extern int ata_scsi_detect(struct scsi_host_template *sht); extern int ata_scsi_ioctl(struct scsi_device *dev, int cmd, void __user *arg); extern int ata_scsi_queuecmd(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)); +extern enum scsi_eh_timer_return ata_scsi_timed_out(struct scsi_cmnd *cmd); extern int ata_scsi_error(struct Scsi_Host *host); extern void ata_eh_qc_complete(struct ata_queued_cmd *qc); extern void ata_eh_qc_retry(struct ata_queued_cmd *qc); |