Add PCI P2PDMA support for dma_direct_map_sg() so that it can map
PCI P2PDMA pages directly without a hack in the callers. This allows
for heterogeneous SGLs that contain both P2PDMA and regular pages.

The DMA_ATTR_P2PDMA flag is added to allow callers to indicate support
for P2PDMA pages. In order for a caller to support P2PDMA pages they
must ensure no segment is greater than 2GB such that the high bit
of the dma length can be used as a flag to indicate a P2PDMA segment.
Such code must then ensure to use sg_dma_p2pdma_len() instead of
sg_dma_len() to filter out the flag.

Signed-off-by: Logan Gunthorpe <log...@deltatee.com>
---
 include/linux/dma-mapping.h | 11 +++++++++++
 kernel/dma/direct.c         | 33 +++++++++++++++++++++++++++++++--
 2 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h
index 956151052d45..8d028e15b531 100644
--- a/include/linux/dma-mapping.h
+++ b/include/linux/dma-mapping.h
@@ -61,6 +61,17 @@
  */
 #define DMA_ATTR_PRIVILEGED            (1UL << 9)
 
+/*
+ * DMA_ATTR_P2PDMA: specifies that dma_map_sg() may return p2pdma
+ * bus addresses. Code that specifies this must ensure to
+ * use sg_dma_p2pdma_len() instead of sg_dma_len() as the high
+ * bit of the length will indicate a P2PDMA bus address.
+ *
+ * If this attribute is not set and P2PDMA pages are encountered,
+ * dma_map_sg() will return an error.
+ */
+#define DMA_ATTR_P2PDMA                        (1UL << 10)
+
 /*
  * A dma_addr_t can hold any valid DMA or bus address for the platform.  It can
  * be given to a device to use as a DMA source or target.  It is specific to a
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index 06c111544f61..2fcb31789436 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -13,6 +13,7 @@
 #include <linux/vmalloc.h>
 #include <linux/set_memory.h>
 #include <linux/slab.h>
+#include <linux/pci-p2pdma.h>
 #include "direct.h"
 
 /*
@@ -387,19 +388,47 @@ void dma_direct_unmap_sg(struct device *dev, struct 
scatterlist *sgl,
        struct scatterlist *sg;
        int i;
 
-       for_each_sg(sgl, sg, nents, i)
+       for_each_sg(sgl, sg, nents, i) {
+               if (attrs & DMA_ATTR_P2PDMA && sg_dma_is_p2pdma(sg)) {
+                       sg_dma_len(sg) &= ~SG_P2PDMA_FLAG;
+                       continue;
+               }
+
                dma_direct_unmap_page(dev, sg->dma_address, sg_dma_len(sg), dir,
                             attrs);
+       }
 }
 #endif
 
 int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, int nents,
                enum dma_data_direction dir, unsigned long attrs)
 {
-       int i;
+       struct dev_pagemap *pgmap = NULL;
+       int i, map = -1;
        struct scatterlist *sg;
+       u64 bus_off;
 
        for_each_sg(sgl, sg, nents, i) {
+               if (is_pci_p2pdma_page(sg_page(sg))) {
+                       if (sg_page(sg)->pgmap != pgmap) {
+                               pgmap = sg_page(sg)->pgmap;
+                               map = pci_p2pdma_should_map_bus(dev, pgmap);
+                               bus_off = pci_p2pdma_bus_offset(sg_page(sg));
+                       }
+
+                       if (map < 0 || !(attrs & DMA_ATTR_P2PDMA)) {
+                               sg->dma_address = DMA_MAPPING_ERROR;
+                               goto out_unmap;
+                       }
+
+                       if (map) {
+                               sg->dma_address = sg_phys(sg) + sg->offset -
+                                       bus_off;
+                               sg_dma_len(sg) = sg->length | SG_P2PDMA_FLAG;
+                               continue;
+                       }
+               }
+
                sg->dma_address = dma_direct_map_page(dev, sg_page(sg),
                                sg->offset, sg->length, dir, attrs);
                if (sg->dma_address == DMA_MAPPING_ERROR)
-- 
2.20.1

Reply via email to