diff options
author | Tejun Heo <htejun@gmail.com> | 2005-08-22 14:59:24 +0900 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2005-08-23 01:05:55 -0400 |
commit | c1389503710ef4b4e5d21bea284afde19e9619cf (patch) | |
tree | 270bf8ea0c2ac354273766d8a1ddfb12cd58b608 /drivers/scsi | |
parent | c0b34ad2956036cdba87792d6c46d8f491539df1 (diff) | |
download | op-kernel-dev-c1389503710ef4b4e5d21bea284afde19e9619cf.zip op-kernel-dev-c1389503710ef4b4e5d21bea284afde19e9619cf.tar.gz |
[PATCH] fix atapi_packet_task vs. intr race (take 2)
Interrupts from devices sharing the same IRQ could cause
ata_host_intr to finish commands being processed by atapi_packet_task
if the commands are using ATA_PROT_ATAPI_NODATA or ATA_PROT_ATAPI_DMA
protocol. This is because libata interrupt handler is unaware that
interrupts are not expected during that period. This patch adds
ATA_FLAG_NOINTR flag to tell the interrupt handler that we're not
expecting interrupts.
Note that once proper HSM is implemented for interrupt-driven PIO,
this should be merged into it and this flag will be removed.
ahci.c is a different kind of beast, so it's left alone.
* The following drivers use ata_qc_issue_prot and ata_interrupt, so
changes in libata core will do.
ata_piix sata_sil sata_svw sata_via sata_sis sata_uli
* The following drivers use ata_qc_issue_prot and custom intr handler.
They need this change to work correctly.
sata_nv sata_vsc
* The following drivers use custom issue function and intr handler.
Currently all custom issue functions don't support ATAPI, so this
change is irrelevant, updated for consistency and to avoid later
mistakes.
sata_promise sata_qstor sata_sx4
Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Jeff Garzik <jgarzik@pobox.com>
Diffstat (limited to 'drivers/scsi')
-rw-r--r-- | drivers/scsi/libata-core.c | 33 | ||||
-rw-r--r-- | drivers/scsi/sata_nv.c | 3 | ||||
-rw-r--r-- | drivers/scsi/sata_promise.c | 3 | ||||
-rw-r--r-- | drivers/scsi/sata_qstor.c | 6 | ||||
-rw-r--r-- | drivers/scsi/sata_sx4.c | 3 | ||||
-rw-r--r-- | drivers/scsi/sata_vsc.c | 3 |
6 files changed, 34 insertions, 17 deletions
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c index 9a6aacf..c92439f 100644 --- a/drivers/scsi/libata-core.c +++ b/drivers/scsi/libata-core.c @@ -3350,11 +3350,13 @@ int ata_qc_issue_prot(struct ata_queued_cmd *qc) break; case ATA_PROT_ATAPI_NODATA: + ap->flags |= ATA_FLAG_NOINTR; ata_tf_to_host_nolock(ap, &qc->tf); queue_work(ata_wq, &ap->packet_task); break; case ATA_PROT_ATAPI_DMA: + ap->flags |= ATA_FLAG_NOINTR; ap->ops->tf_load(ap, &qc->tf); /* load tf registers */ ap->ops->bmdma_setup(qc); /* set up bmdma */ queue_work(ata_wq, &ap->packet_task); @@ -3708,7 +3710,8 @@ irqreturn_t ata_interrupt (int irq, void *dev_instance, struct pt_regs *regs) struct ata_port *ap; ap = host_set->ports[i]; - if (ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (ap && + !(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; qc = ata_qc_from_tag(ap, ap->active_tag); @@ -3760,19 +3763,27 @@ static void atapi_packet_task(void *_data) /* send SCSI cdb */ DPRINTK("send cdb\n"); assert(ap->cdb_len >= 12); - ata_data_xfer(ap, qc->cdb, ap->cdb_len, 1); - /* if we are DMA'ing, irq handler takes over from here */ - if (qc->tf.protocol == ATA_PROT_ATAPI_DMA) - ap->ops->bmdma_start(qc); /* initiate bmdma */ + if (qc->tf.protocol == ATA_PROT_ATAPI_DMA || + qc->tf.protocol == ATA_PROT_ATAPI_NODATA) { + unsigned long flags; - /* non-data commands are also handled via irq */ - else if (qc->tf.protocol == ATA_PROT_ATAPI_NODATA) { - /* do nothing */ - } + /* Once we're done issuing command and kicking bmdma, + * irq handler takes over. To not lose irq, we need + * to clear NOINTR flag before sending cdb, but + * interrupt handler shouldn't be invoked before we're + * finished. Hence, the following locking. + */ + spin_lock_irqsave(&ap->host_set->lock, flags); + ap->flags &= ~ATA_FLAG_NOINTR; + ata_data_xfer(ap, qc->cdb, ap->cdb_len, 1); + if (qc->tf.protocol == ATA_PROT_ATAPI_DMA) + ap->ops->bmdma_start(qc); /* initiate bmdma */ + spin_unlock_irqrestore(&ap->host_set->lock, flags); + } else { + ata_data_xfer(ap, qc->cdb, ap->cdb_len, 1); - /* PIO commands are handled by polling */ - else { + /* PIO commands are handled by polling */ ap->pio_task_state = PIO_ST; queue_work(ata_wq, &ap->pio_task); } diff --git a/drivers/scsi/sata_nv.c b/drivers/scsi/sata_nv.c index 9b91427..41a3421 100644 --- a/drivers/scsi/sata_nv.c +++ b/drivers/scsi/sata_nv.c @@ -291,7 +291,8 @@ static irqreturn_t nv_interrupt (int irq, void *dev_instance, struct ata_port *ap; ap = host_set->ports[i]; - if (ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (ap && + !(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; qc = ata_qc_from_tag(ap, ap->active_tag); diff --git a/drivers/scsi/sata_promise.c b/drivers/scsi/sata_promise.c index cc613b3..6defd79 100644 --- a/drivers/scsi/sata_promise.c +++ b/drivers/scsi/sata_promise.c @@ -445,7 +445,8 @@ static irqreturn_t pdc_interrupt (int irq, void *dev_instance, struct pt_regs *r VPRINTK("port %u\n", i); ap = host_set->ports[i]; tmp = mask & (1 << (i + 1)); - if (tmp && ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (tmp && ap && + !(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; qc = ata_qc_from_tag(ap, ap->active_tag); diff --git a/drivers/scsi/sata_qstor.c b/drivers/scsi/sata_qstor.c index dca9ed7..08a8404 100644 --- a/drivers/scsi/sata_qstor.c +++ b/drivers/scsi/sata_qstor.c @@ -386,7 +386,8 @@ static inline unsigned int qs_intr_pkt(struct ata_host_set *host_set) DPRINTK("SFF=%08x%08x: sCHAN=%u sHST=%d sDST=%02x\n", sff1, sff0, port_no, sHST, sDST); handled = 1; - if (ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (ap && !(ap->flags & + (ATA_FLAG_PORT_DISABLED|ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; struct qs_port_priv *pp = ap->private_data; if (!pp || pp->state != qs_state_pkt) @@ -417,7 +418,8 @@ static inline unsigned int qs_intr_mmio(struct ata_host_set *host_set) for (port_no = 0; port_no < host_set->n_ports; ++port_no) { struct ata_port *ap; ap = host_set->ports[port_no]; - if (ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (ap && + !(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; struct qs_port_priv *pp = ap->private_data; if (!pp || pp->state != qs_state_mmio) diff --git a/drivers/scsi/sata_sx4.c b/drivers/scsi/sata_sx4.c index 76644ea..e2db499 100644 --- a/drivers/scsi/sata_sx4.c +++ b/drivers/scsi/sata_sx4.c @@ -825,7 +825,8 @@ static irqreturn_t pdc20621_interrupt (int irq, void *dev_instance, struct pt_re ap = host_set->ports[port_no]; tmp = mask & (1 << i); VPRINTK("seq %u, port_no %u, ap %p, tmp %x\n", i, port_no, ap, tmp); - if (tmp && ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (tmp && ap && + !(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; qc = ata_qc_from_tag(ap, ap->active_tag); diff --git a/drivers/scsi/sata_vsc.c b/drivers/scsi/sata_vsc.c index cb3a6d8..6f25621 100644 --- a/drivers/scsi/sata_vsc.c +++ b/drivers/scsi/sata_vsc.c @@ -173,7 +173,8 @@ static irqreturn_t vsc_sata_interrupt (int irq, void *dev_instance, struct ata_port *ap; ap = host_set->ports[i]; - if (ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) { + if (ap && !(ap->flags & + (ATA_FLAG_PORT_DISABLED|ATA_FLAG_NOINTR))) { struct ata_queued_cmd *qc; qc = ata_qc_from_tag(ap, ap->active_tag); |