[PATCH] libata-ncq: implement NCQ command translation and exclusion
[pandora-kernel.git] / drivers / scsi / libata-scsi.c
index 96517ca..9bef68c 100644 (file)
@@ -1103,7 +1103,36 @@ static unsigned int ata_scsi_rw_xlat(struct ata_queued_cmd *qc, const u8 *scsicm
                 */
                goto nothing_to_do;
 
-       if (dev->flags & ATA_DFLAG_LBA) {
+       if ((dev->flags & (ATA_DFLAG_PIO | ATA_DFLAG_NCQ)) == ATA_DFLAG_NCQ) {
+               /* yay, NCQ */
+               if (!lba_48_ok(block, n_block))
+                       goto out_of_range;
+
+               tf->protocol = ATA_PROT_NCQ;
+               tf->flags |= ATA_TFLAG_LBA | ATA_TFLAG_LBA48;
+
+               if (tf->flags & ATA_TFLAG_WRITE)
+                       tf->command = ATA_CMD_FPDMA_WRITE;
+               else
+                       tf->command = ATA_CMD_FPDMA_READ;
+
+               qc->nsect = n_block;
+
+               tf->nsect = qc->tag << 3;
+               tf->hob_feature = (n_block >> 8) & 0xff;
+               tf->feature = n_block & 0xff;
+
+               tf->hob_lbah = (block >> 40) & 0xff;
+               tf->hob_lbam = (block >> 32) & 0xff;
+               tf->hob_lbal = (block >> 24) & 0xff;
+               tf->lbah = (block >> 16) & 0xff;
+               tf->lbam = (block >> 8) & 0xff;
+               tf->lbal = block & 0xff;
+
+               tf->device = 1 << 6;
+               if (tf->flags & ATA_TFLAG_FUA)
+                       tf->device |= 1 << 7;
+       } else if (dev->flags & ATA_DFLAG_LBA) {
                tf->flags |= ATA_TFLAG_LBA;
 
                if (lba_28_ok(block, n_block)) {
@@ -1226,6 +1255,39 @@ static void ata_scsi_qc_complete(struct ata_queued_cmd *qc)
        ata_qc_free(qc);
 }
 
+/**
+ *     ata_scmd_need_defer - Check whether we need to defer scmd
+ *     @dev: ATA device to which the command is addressed
+ *     @is_io: Is the command IO (and thus possibly NCQ)?
+ *
+ *     NCQ and non-NCQ commands cannot run together.  As upper layer
+ *     only knows the queue depth, we are responsible for maintaining
+ *     exclusion.  This function checks whether a new command can be
+ *     issued to @dev.
+ *
+ *     LOCKING:
+ *     spin_lock_irqsave(host_set lock)
+ *
+ *     RETURNS:
+ *     1 if deferring is needed, 0 otherwise.
+ */
+static int ata_scmd_need_defer(struct ata_device *dev, int is_io)
+{
+       struct ata_port *ap = dev->ap;
+
+       if (!(dev->flags & ATA_DFLAG_NCQ))
+               return 0;
+
+       if (is_io) {
+               if (!ata_tag_valid(ap->active_tag))
+                       return 0;
+       } else {
+               if (!ata_tag_valid(ap->active_tag) && !ap->sactive)
+                       return 0;
+       }
+       return 1;
+}
+
 /**
  *     ata_scsi_translate - Translate then issue SCSI command to ATA device
  *     @dev: ATA device to which the command is addressed
@@ -1259,9 +1321,13 @@ static int ata_scsi_translate(struct ata_device *dev, struct scsi_cmnd *cmd,
 {
        struct ata_queued_cmd *qc;
        u8 *scsicmd = cmd->cmnd;
+       int is_io = xlat_func == ata_scsi_rw_xlat;
 
        VPRINTK("ENTER\n");
 
+       if (unlikely(ata_scmd_need_defer(dev, is_io)))
+               goto defer;
+
        qc = ata_scsi_qc_new(dev, cmd, done);
        if (!qc)
                goto err_mem;
@@ -1308,6 +1374,10 @@ err_mem:
        done(cmd);
        DPRINTK("EXIT - internal\n");
        return 0;
+
+defer:
+       DPRINTK("EXIT - defer\n");
+       return SCSI_MLQUEUE_DEVICE_BUSY;
 }
 
 /**