Skip to content

Commit

Permalink
[libata] Improve timeout handling
Browse files Browse the repository at this point in the history
On a timeout call a device specific handler early in the recovery so that
we can complete and process successful commands which timed out due to IRQ
loss or the like rather more elegantly.

[Revised to exclude the timeout handling on a few devices that inherit from
 SFF but are not SFF enough to use the default timeout handler]

Signed-off-by: Alan Cox <alan@redhat.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
  • Loading branch information
Alan Cox authored and Jeff Garzik committed Mar 25, 2009
1 parent 3d47aa8 commit c96f173
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 5 deletions.
19 changes: 17 additions & 2 deletions drivers/ata/libata-eh.c
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ void ata_scsi_error(struct Scsi_Host *host)

/* For new EH, all qcs are finished in one of three ways -
* normal completion, error completion, and SCSI timeout.
* Both cmpletions can race against SCSI timeout. When normal
* Both completions can race against SCSI timeout. When normal
* completion wins, the qc never reaches EH. When error
* completion wins, the qc has ATA_QCFLAG_FAILED set.
*
Expand All @@ -562,7 +562,19 @@ void ata_scsi_error(struct Scsi_Host *host)
int nr_timedout = 0;

spin_lock_irqsave(ap->lock, flags);


/* This must occur under the ap->lock as we don't want
a polled recovery to race the real interrupt handler
The lost_interrupt handler checks for any completed but
non-notified command and completes much like an IRQ handler.
We then fall into the error recovery code which will treat
this as if normal completion won the race */

if (ap->ops->lost_interrupt)
ap->ops->lost_interrupt(ap);

list_for_each_entry_safe(scmd, tmp, &host->eh_cmd_q, eh_entry) {
struct ata_queued_cmd *qc;

Expand Down Expand Up @@ -606,6 +618,9 @@ void ata_scsi_error(struct Scsi_Host *host)
ap->eh_tries = ATA_EH_MAX_TRIES;
} else
spin_unlock_wait(ap->lock);

/* If we timed raced normal completion and there is nothing to
recover nr_timedout == 0 why exactly are we doing error recovery ? */

repeat:
/* invoke error handler */
Expand Down
46 changes: 45 additions & 1 deletion drivers/ata/libata-sff.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ const struct ata_port_operations ata_sff_port_ops = {
.sff_irq_on = ata_sff_irq_on,
.sff_irq_clear = ata_sff_irq_clear,

.lost_interrupt = ata_sff_lost_interrupt,

.port_start = ata_sff_port_start,
};
EXPORT_SYMBOL_GPL(ata_sff_port_ops);
Expand Down Expand Up @@ -1647,7 +1649,7 @@ EXPORT_SYMBOL_GPL(ata_sff_qc_fill_rtf);
* RETURNS:
* One if interrupt was handled, zero if not (shared irq).
*/
inline unsigned int ata_sff_host_intr(struct ata_port *ap,
unsigned int ata_sff_host_intr(struct ata_port *ap,
struct ata_queued_cmd *qc)
{
struct ata_eh_info *ehi = &ap->link.eh_info;
Expand Down Expand Up @@ -1775,6 +1777,48 @@ irqreturn_t ata_sff_interrupt(int irq, void *dev_instance)
}
EXPORT_SYMBOL_GPL(ata_sff_interrupt);

/**
* ata_sff_lost_interrupt - Check for an apparent lost interrupt
* @ap: port that appears to have timed out
*
* Called from the libata error handlers when the core code suspects
* an interrupt has been lost. If it has complete anything we can and
* then return. Interface must support altstatus for this faster
* recovery to occur.
*
* Locking:
* Caller holds host lock
*/

void ata_sff_lost_interrupt(struct ata_port *ap)
{
u8 status;
struct ata_queued_cmd *qc;

/* Only one outstanding command per SFF channel */
qc = ata_qc_from_tag(ap, ap->link.active_tag);
/* Check we have a live one.. */
if (qc == NULL || !(qc->flags & ATA_QCFLAG_ACTIVE))
return;
/* We cannot lose an interrupt on a polled command */
if (qc->tf.flags & ATA_TFLAG_POLLING)
return;
/* See if the controller thinks it is still busy - if so the command
isn't a lost IRQ but is still in progress */
status = ata_sff_altstatus(ap);
if (status & ATA_BUSY)
return;

/* There was a command running, we are no longer busy and we have
no interrupt. */
ata_port_printk(ap, KERN_WARNING, "lost interrupt (Status 0x%x)\n",
status);
/* Run the host interrupt logic as if the interrupt had not been
lost */
ata_sff_host_intr(ap, qc);
}
EXPORT_SYMBOL_GPL(ata_sff_lost_interrupt);

/**
* ata_sff_freeze - Freeze SFF controller port
* @ap: port to freeze
Expand Down
12 changes: 10 additions & 2 deletions drivers/ata/pata_isapnp.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include <linux/libata.h>

#define DRV_NAME "pata_isapnp"
#define DRV_VERSION "0.2.2"
#define DRV_VERSION "0.2.5"

static struct scsi_host_template isapnp_sht = {
ATA_PIO_SHT(DRV_NAME),
Expand All @@ -28,6 +28,13 @@ static struct ata_port_operations isapnp_port_ops = {
.cable_detect = ata_cable_40wire,
};

static struct ata_port_operations isapnp_noalt_port_ops = {
.inherits = &ata_sff_port_ops,
.cable_detect = ata_cable_40wire,
/* No altstatus so we don't want to use the lost interrupt poll */
.lost_interrupt = ATA_OP_NULL,
};

/**
* isapnp_init_one - attach an isapnp interface
* @idev: PnP device
Expand Down Expand Up @@ -65,7 +72,7 @@ static int isapnp_init_one(struct pnp_dev *idev, const struct pnp_device_id *dev

ap = host->ports[0];

ap->ops = &isapnp_port_ops;
ap->ops = &isapnp_noalt_port_ops;
ap->pio_mask = ATA_PIO0;
ap->flags |= ATA_FLAG_SLAVE_POSS;

Expand All @@ -76,6 +83,7 @@ static int isapnp_init_one(struct pnp_dev *idev, const struct pnp_device_id *dev
pnp_port_start(idev, 1), 1);
ap->ioaddr.altstatus_addr = ctl_addr;
ap->ioaddr.ctl_addr = ctl_addr;
ap->ops = &isapnp_port_ops;
}

ata_sff_std_ports(&ap->ioaddr);
Expand Down
2 changes: 2 additions & 0 deletions drivers/ata/pdc_adma.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ static struct scsi_host_template adma_ata_sht = {
static struct ata_port_operations adma_ata_ops = {
.inherits = &ata_sff_port_ops,

.lost_interrupt = ATA_OP_NULL,

.check_atapi_dma = adma_check_atapi_dma,
.qc_prep = adma_qc_prep,
.qc_issue = adma_qc_issue,
Expand Down
2 changes: 2 additions & 0 deletions drivers/ata/sata_mv.c
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,8 @@ static struct scsi_host_template mv6_sht = {
static struct ata_port_operations mv5_ops = {
.inherits = &ata_sff_port_ops,

.lost_interrupt = ATA_OP_NULL,

.qc_defer = mv_qc_defer,
.qc_prep = mv_qc_prep,
.qc_issue = mv_qc_issue,
Expand Down
1 change: 1 addition & 0 deletions drivers/ata/sata_nv.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ static struct scsi_host_template nv_swncq_sht = {

static struct ata_port_operations nv_common_ops = {
.inherits = &ata_bmdma_port_ops,
.lost_interrupt = ATA_OP_NULL,
.scr_read = nv_scr_read,
.scr_write = nv_scr_write,
};
Expand Down
2 changes: 2 additions & 0 deletions drivers/ata/sata_promise.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ static const struct ata_port_operations pdc_common_ops = {
.check_atapi_dma = pdc_check_atapi_dma,
.qc_prep = pdc_qc_prep,
.qc_issue = pdc_qc_issue,

.sff_irq_clear = pdc_irq_clear,
.lost_interrupt = ATA_OP_NULL,

.post_internal_cmd = pdc_post_internal_cmd,
.error_handler = pdc_error_handler,
Expand Down
1 change: 1 addition & 0 deletions drivers/ata/sata_qstor.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ static struct ata_port_operations qs_ata_ops = {
.softreset = ATA_OP_NULL,
.error_handler = qs_error_handler,
.post_internal_cmd = ATA_OP_NULL,
.lost_interrupt = ATA_OP_NULL,

.scr_read = qs_scr_read,
.scr_write = qs_scr_write,
Expand Down
3 changes: 3 additions & 0 deletions drivers/ata/sata_vsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ static struct scsi_host_template vsc_sata_sht = {

static struct ata_port_operations vsc_sata_ops = {
.inherits = &ata_bmdma_port_ops,
/* The IRQ handling is not quite standard SFF behaviour so we
cannot use the default lost interrupt handler */
.lost_interrupt = ATA_OP_NULL,
.sff_tf_load = vsc_sata_tf_load,
.sff_tf_read = vsc_sata_tf_read,
.freeze = vsc_freeze,
Expand Down
2 changes: 2 additions & 0 deletions include/linux/libata.h
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ struct ata_port_operations {
ata_reset_fn_t pmp_hardreset;
ata_postreset_fn_t pmp_postreset;
void (*error_handler)(struct ata_port *ap);
void (*lost_interrupt)(struct ata_port *ap);
void (*post_internal_cmd)(struct ata_queued_cmd *qc);

/*
Expand Down Expand Up @@ -1577,6 +1578,7 @@ extern bool ata_sff_qc_fill_rtf(struct ata_queued_cmd *qc);
extern unsigned int ata_sff_host_intr(struct ata_port *ap,
struct ata_queued_cmd *qc);
extern irqreturn_t ata_sff_interrupt(int irq, void *dev_instance);
extern void ata_sff_lost_interrupt(struct ata_port *ap);
extern void ata_sff_freeze(struct ata_port *ap);
extern void ata_sff_thaw(struct ata_port *ap);
extern int ata_sff_prereset(struct ata_link *link, unsigned long deadline);
Expand Down

0 comments on commit c96f173

Please sign in to comment.