Skip to content

Commit

Permalink
[PATCH] fix atapi_packet_task vs. intr race (take 2)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Tejun Heo authored and Jeff Garzik committed Aug 23, 2005
1 parent c0b34ad commit c138950
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 17 deletions.
33 changes: 22 additions & 11 deletions drivers/scsi/libata-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion drivers/scsi/sata_nv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion drivers/scsi/sata_promise.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions drivers/scsi/sata_qstor.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion drivers/scsi/sata_sx4.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion drivers/scsi/sata_vsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions include/linux/libata.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ enum {
ATA_FLAG_MMIO = (1 << 6), /* use MMIO, not PIO */
ATA_FLAG_SATA_RESET = (1 << 7), /* use COMRESET */
ATA_FLAG_PIO_DMA = (1 << 8), /* PIO cmds via DMA */
ATA_FLAG_NOINTR = (1 << 9), /* FIXME: Remove this once
* proper HSM is in place. */

ATA_QCFLAG_ACTIVE = (1 << 1), /* cmd not yet ack'd to scsi lyer */
ATA_QCFLAG_SG = (1 << 3), /* have s/g table? */
Expand Down

0 comments on commit c138950

Please sign in to comment.