summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Garzik <jgarzik@pobox.com>2004-12-07 08:54:34 -0500
committerJeff Garzik <jgarzik@pobox.com>2004-12-07 08:54:34 -0500
commit046de9be7179b9b97eb899aa7c5b0c2c83a87b21 (patch)
treeffb869b73cb051b6231c8331fb46db273b45eb3c
parent6695ad97269159f81361d988b773750ce81fba37 (diff)
[libata] only DMA map data for DMA commands (fix >=4GB bug)
libata made the assumption that (for PIO commands in this case) it could modify DMA memory at the kernel-virtual address, after mapping this. This is incorrect, and fails on e.g. platforms that copy DMA memory back and forth (swiotlb on Intel EM64T and IA64). Remove this assumption by ensuring that we only call the DMA mapping routines if we really are going to use DMA for data xfer. Also: remove a bogus WARN_ON() in ata_sg_init_one() which caused bug reports (but no problems).
-rw-r--r--drivers/scsi/ahci.c3
-rw-r--r--drivers/scsi/libata-core.c42
-rw-r--r--include/linux/libata.h1
3 files changed, 37 insertions, 9 deletions
diff --git a/drivers/scsi/ahci.c b/drivers/scsi/ahci.c
index 8c300e0a52d9..e0cd4ba41678 100644
--- a/drivers/scsi/ahci.c
+++ b/drivers/scsi/ahci.c
@@ -229,7 +229,8 @@ static struct ata_port_info ahci_port_info[] = {
{
.sht = &ahci_sht,
.host_flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
- ATA_FLAG_SATA_RESET | ATA_FLAG_MMIO,
+ ATA_FLAG_SATA_RESET | ATA_FLAG_MMIO |
+ ATA_FLAG_PIO_DMA,
.pio_mask = 0x03, /* pio3-4 */
.udma_mask = 0x7f, /* udma0-6 ; FIXME */
.port_ops = &ahci_ops,
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
index 1d2b344aab01..93d987433966 100644
--- a/drivers/scsi/libata-core.c
+++ b/drivers/scsi/libata-core.c
@@ -1950,8 +1950,6 @@ void ata_sg_init_one(struct ata_queued_cmd *qc, void *buf, unsigned int buflen)
sg->page = virt_to_page(buf);
sg->offset = (unsigned long) buf & ~PAGE_MASK;
sg_dma_len(sg) = buflen;
-
- WARN_ON(buflen > PAGE_SIZE);
}
void ata_sg_init(struct ata_queued_cmd *qc, struct scatterlist *sg,
@@ -2693,6 +2691,30 @@ void ata_qc_complete(struct ata_queued_cmd *qc, u8 drv_stat)
VPRINTK("EXIT\n");
}
+static inline int ata_should_dma_map(struct ata_queued_cmd *qc)
+{
+ struct ata_port *ap = qc->ap;
+
+ switch (qc->tf.protocol) {
+ case ATA_PROT_DMA:
+ case ATA_PROT_ATAPI_DMA:
+ return 1;
+
+ case ATA_PROT_ATAPI:
+ case ATA_PROT_PIO:
+ case ATA_PROT_PIO_MULT:
+ if (ap->flags & ATA_FLAG_PIO_DMA)
+ return 1;
+
+ /* fall through */
+
+ default:
+ return 0;
+ }
+
+ /* never reached */
+}
+
/**
* ata_qc_issue - issue taskfile to device
* @qc: command to issue to device
@@ -2713,12 +2735,16 @@ int ata_qc_issue(struct ata_queued_cmd *qc)
{
struct ata_port *ap = qc->ap;
- if (qc->flags & ATA_QCFLAG_SG) {
- if (ata_sg_setup(qc))
- goto err_out;
- } else if (qc->flags & ATA_QCFLAG_SINGLE) {
- if (ata_sg_setup_one(qc))
- goto err_out;
+ if (ata_should_dma_map(qc)) {
+ if (qc->flags & ATA_QCFLAG_SG) {
+ if (ata_sg_setup(qc))
+ goto err_out;
+ } else if (qc->flags & ATA_QCFLAG_SINGLE) {
+ if (ata_sg_setup_one(qc))
+ goto err_out;
+ }
+ } else {
+ qc->flags &= ~ATA_QCFLAG_DMAMAP;
}
ap->ops->qc_prep(qc);
diff --git a/include/linux/libata.h b/include/linux/libata.h
index 605e0a728c0e..95a7b0ddb096 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -112,6 +112,7 @@ enum {
ATA_FLAG_SRST = (1 << 5), /* use ATA SRST, not E.D.D. */
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_QCFLAG_ACTIVE = (1 << 1), /* cmd not yet ack'd to scsi lyer */
ATA_QCFLAG_SG = (1 << 3), /* have s/g table? */