Hello.

There appears to be a hardware bug in that it chokes on scatterlist
if the last item is larger than 164 bytes.

The patch that follows fixes my problem on 2.6.22.

I can't think of a way to avoid second pass over scatterlist without
duplicating code (ata_qc_prep() and ata_fill_sg() from libata-core.c).


--- a/drivers/ata/sata_promise.c        2007-07-09 03:32:17.000000000 +0400
+++ b/drivers/ata/sata_promise.c        2007-10-27 17:20:03.000000000 +0400
@@ -531,6 +531,80 @@
        memcpy(buf+31, cdb, cdb_len);
 }

+/**
+ *     pdc_qc_prep - Fill PCI IDE PRD table
+ *     @qc: Metadata associated with taskfile to be transferred
+ *
+ *     Fill PCI IDE PRD (scatter-gather) table with segments
+ *     associated with the current disk command.
+ *     Make sure hardware does not choke on it.
+ *
+ *     LOCKING:
+ *     spin_lock_irqsave(host lock)
+ *
+ */
+static void pdc_qc_prep(struct ata_queued_cmd *qc)
+{
+       struct ata_port *ap = qc->ap;
+       struct scatterlist *sg;
+       unsigned int idx;
+       const u32 SG_COUNT_ASIC_BUG = 41*4;
+
+       if (!(qc->flags & ATA_QCFLAG_DMAMAP))
+               return;
+       
+       WARN_ON(qc->__sg == NULL);
+       WARN_ON(qc->n_elem == 0 && qc->pad_len == 0);
+
+       idx = 0;
+       ata_for_each_sg(sg, qc) {
+               u32 addr, offset;
+               u32 sg_len, len;
+
+               /* determine if physical DMA addr spans 64K boundary.
+                * Note h/w doesn't support 64-bit, so we unconditionally
+                * truncate dma_addr_t to u32.
+                */
+               addr = (u32) sg_dma_address(sg);
+               sg_len = sg_dma_len(sg);
+
+               while (sg_len) {
+                       offset = addr & 0xffff;
+                       len = sg_len;
+                       if ((offset + sg_len) > 0x10000)
+                               len = 0x10000 - offset;
+
+                       ap->prd[idx].addr = cpu_to_le32(addr);
+                       ap->prd[idx].flags_len = cpu_to_le32(len & 0xffff);
+                       VPRINTK("PRD[%u] = (0x%X, 0x%X)\n", idx, addr, len);
+
+                       idx++;
+                       sg_len -= len;
+                       addr += len;
+               }
+       }
+
+       if (idx) {
+               u32 len = ap->prd[idx - 1].flags_len;
+               if (len > SG_COUNT_ASIC_BUG) {
+                       u32 addr, len;
+
+                       VPRINTK("Last PRD split\n");
+                       
+                       len = le32_to_cpu(ap->prd[idx - 1].flags_len) - 
SG_COUNT_ASIC_BUG;
+                       addr = le32_to_cpu(ap->prd[idx - 1].addr);
+                       ap->prd[idx - 1].flags_len = cpu_to_le32(len);
+                       VPRINTK("PRD[%u] = (0x%X, 0x%X)\n", idx, addr, len);
+                       
+                       ap->prd[idx].flags_len = cpu_to_le32(SG_COUNT_ASIC_BUG);
+                       ap->prd[idx].addr = cpu_to_le32(addr + len);
+                       idx++;
+                       VPRINTK("PRD[%u] = (0x%X, 0x%X)\n", idx, addr + len, 
SG_COUNT_ASIC_BUG);
+               }
+               ap->prd[idx - 1].flags_len |= cpu_to_le32(ATA_PRD_EOT);
+       }
+}
+
 static void pdc_qc_prep(struct ata_queued_cmd *qc)
 {
        struct pdc_port_priv *pp = qc->ap->private_data;
@@ -540,7 +614,7 @@

        switch (qc->tf.protocol) {
        case ATA_PROT_DMA:
-               ata_qc_prep(qc);
+               pdc_qc_prep(qc);
                /* fall through */

        case ATA_PROT_NODATA:

-
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to