Hi Yong, On Tue, Jul 18, 2017 at 10:12:58PM -0500, Yong Zhi wrote: > From: Tomasz Figa <tfiga@xxxxxxxxxxxx> > > This patch adds support for the IPU3 DMA mapping API. > > Signed-off-by: Tomasz Figa <tfiga@xxxxxxxxxxxx> > Signed-off-by: Yong Zhi <yong.zhi@xxxxxxxxx> > --- > drivers/media/pci/intel/ipu3/Kconfig | 8 + > drivers/media/pci/intel/ipu3/Makefile | 2 +- > drivers/media/pci/intel/ipu3/ipu3-dmamap.c | 302 +++++++++++++++++++++++++++++ > drivers/media/pci/intel/ipu3/ipu3-dmamap.h | 22 +++ > 4 files changed, 333 insertions(+), 1 deletion(-) > create mode 100644 drivers/media/pci/intel/ipu3/ipu3-dmamap.c > create mode 100644 drivers/media/pci/intel/ipu3/ipu3-dmamap.h > > diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig > index 7bcdfa5..d503806 100644 > --- a/drivers/media/pci/intel/ipu3/Kconfig > +++ b/drivers/media/pci/intel/ipu3/Kconfig > @@ -24,3 +24,11 @@ config INTEL_IPU3_MMU > ---help--- > For IPU3, this option enables its MMU driver to translate its internal > virtual address to 39 bits wide physical address for 64GBytes space access. > + > +config INTEL_IPU3_DMAMAP > + tristate > + default n > + select IOMMU_DMA > + select IOMMU_IOVA > + ---help--- > + This is IPU3 IOMMU domain specific DMA driver. > diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile > index 91cac9c..6517732 100644 > --- a/drivers/media/pci/intel/ipu3/Makefile > +++ b/drivers/media/pci/intel/ipu3/Makefile > @@ -13,4 +13,4 @@ > > obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o > obj-$(CONFIG_INTEL_IPU3_MMU) += ipu3-mmu.o > - > +obj-$(CONFIG_INTEL_IPU3_DMAMAP) += ipu3-dmamap.o > diff --git a/drivers/media/pci/intel/ipu3/ipu3-dmamap.c b/drivers/media/pci/intel/ipu3/ipu3-dmamap.c > new file mode 100644 > index 0000000..86a0e15 > --- /dev/null > +++ b/drivers/media/pci/intel/ipu3/ipu3-dmamap.c > @@ -0,0 +1,302 @@ > +/* > + * Copyright (c) 2017 Intel Corporation. > + * Copyright (C) 2017 Google, Inc. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License version > + * 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > +#include <linux/types.h> > +#include <linux/dma-iommu.h> > +#include <linux/dma-mapping.h> > +#include <linux/highmem.h> > +#include <linux/module.h> > +#include <linux/slab.h> > +#include <linux/version.h> > +#include <linux/vmalloc.h> > +#include "ipu3-mmu.h" > + > +/* > + * Based on arch/arm64/mm/dma-mapping.c, with simplifications possible due > + * to driver-specific character of this file. > + */ > + > +static pgprot_t __get_dma_pgprot(unsigned long attrs, pgprot_t prot) > +{ > + if (DMA_ATTR_NON_CONSISTENT & attrs) > + return prot; > + return pgprot_writecombine(prot); > +} > + > +static void flush_page(struct device *dev, const void *virt, phys_addr_t phys) > +{ > + /* > + * FIXME: Yes, casting to override the const specifier is ugly. > + * However, for some reason, this callback is intended to flush cache > + * for a page pointed to by a const pointer, even though the cach > + * flush operation by definition does not keep the affected memory > + * constant... > + */ > + clflush_cache_range((void *)virt, PAGE_SIZE); Hmm. Is this needed? The hardware is coherent --- apart from the MMU tables. Same for the flushes below. > +} > + > +static void *ipu3_dmamap_alloc(struct device *dev, size_t size, > + dma_addr_t *handle, gfp_t gfp, > + unsigned long attrs) > +{ > + int ioprot = dma_info_to_prot(DMA_BIDIRECTIONAL, false, attrs); > + size_t iosize = size; > + struct page **pages; > + pgprot_t prot; > + void *addr; > + > + if (WARN(!dev, "cannot create IOMMU mapping for unknown device\n")) > + return NULL; > + > + if (WARN(!gfpflags_allow_blocking(gfp), > + "atomic allocations not supported\n") || > + WARN((DMA_ATTR_FORCE_CONTIGUOUS & attrs), > + "contiguous allocations not supported\n")) > + return NULL; > + > + size = PAGE_ALIGN(size); > + > + dev_dbg(dev, "%s: allocating %zu\n", __func__, size); > + > + /* > + * Some drivers rely on this, and we probably don't want the > + * possibility of stale kernel data being read by devices anyway. > + */ > + gfp |= __GFP_ZERO; > + > + /* > + * On x86, __GFP_DMA or __GFP_DMA32 might be added implicitly, based > + * on device DMA mask. However the mask does not apply to the IOMMU, > + * which is expected to be able to map any physical page. > + */ > + gfp &= ~(__GFP_DMA | __GFP_DMA32); > + > + pages = iommu_dma_alloc(dev, iosize, gfp, attrs, ioprot, > + handle, flush_page); > + if (!pages) > + return NULL; > + > + prot = __get_dma_pgprot(attrs, PAGE_KERNEL); > + addr = dma_common_pages_remap(pages, size, VM_USERMAP, prot, > + __builtin_return_address(0)); > + if (!addr) > + iommu_dma_free(dev, pages, iosize, handle); > + > + dev_dbg(dev, "%s: allocated %zu @ IOVA %pad @ VA %p\n", > + __func__, size, handle, addr); > + > + return addr; > +} > + > +static void ipu3_dmamap_free(struct device *dev, size_t size, void *cpu_addr, > + dma_addr_t handle, unsigned long attrs) > +{ > + struct page **pages; > + size_t iosize = size; > + > + size = PAGE_ALIGN(size); > + > + pages = dma_common_get_mapped_pages(cpu_addr, VM_USERMAP); > + if (WARN_ON(!pages)) > + return; > + > + dev_dbg(dev, "%s: freeing %zu @ IOVA %pad @ VA %p\n", > + __func__, size, &handle, cpu_addr); > + > + iommu_dma_free(dev, pages, iosize, &handle); > + > + dma_common_free_remap(cpu_addr, size, VM_USERMAP); > +} > + > +static int ipu3_dmamap_mmap(struct device *dev, struct vm_area_struct *vma, > + void *cpu_addr, dma_addr_t dma_addr, size_t size, > + unsigned long attrs) > +{ > + struct page **pages; > + > + vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot); > + > + pages = dma_common_get_mapped_pages(cpu_addr, VM_USERMAP); > + if (WARN_ON(!pages)) > + return -ENXIO; > + > + return iommu_dma_mmap(pages, size, vma); > +} > + > +static int ipu3_dmamap_get_sgtable(struct device *dev, struct sg_table *sgt, > + void *cpu_addr, dma_addr_t dma_addr, > + size_t size, unsigned long attrs) > +{ > + unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT; > + struct page **pages; > + > + pages = dma_common_get_mapped_pages(cpu_addr, VM_USERMAP); > + if (WARN_ON(!pages)) > + return -ENXIO; > + > + return sg_alloc_table_from_pages(sgt, pages, count, 0, size, > + GFP_KERNEL); > +} > + > +static void ipu3_dmamap_sync_single_for_cpu(struct device *dev, > + dma_addr_t dev_addr, size_t size, > + enum dma_data_direction dir) > +{ > + phys_addr_t phys; > + > + phys = iommu_iova_to_phys(iommu_get_domain_for_dev(dev), dev_addr); > + clflush_cache_range(phys_to_virt(phys), size); > +} > + > +static void ipu3_dmamap_sync_single_for_device(struct device *dev, > + dma_addr_t dev_addr, size_t size, > + enum dma_data_direction dir) > +{ > + phys_addr_t phys; > + > + phys = iommu_iova_to_phys(iommu_get_domain_for_dev(dev), dev_addr); > + clflush_cache_range(phys_to_virt(phys), size); > +} > + > +static dma_addr_t ipu3_dmamap_map_page(struct device *dev, struct page *page, > + unsigned long offset, size_t size, > + enum dma_data_direction dir, > + unsigned long attrs) > +{ > + int prot = dma_info_to_prot(dir, false, attrs); > + dma_addr_t dev_addr = iommu_dma_map_page(dev, page, offset, size, prot); > + > + if (!iommu_dma_mapping_error(dev, dev_addr) && > + (DMA_ATTR_SKIP_CPU_SYNC & attrs) == 0) > + ipu3_dmamap_sync_single_for_device(dev, dev_addr, size, dir); > + > + return dev_addr; > +} > + > +static void ipu3_dmamap_unmap_page(struct device *dev, dma_addr_t dev_addr, > + size_t size, enum dma_data_direction dir, > + unsigned long attrs) > +{ > + if ((DMA_ATTR_SKIP_CPU_SYNC & attrs) == 0) > + ipu3_dmamap_sync_single_for_cpu(dev, dev_addr, size, dir); > + > + iommu_dma_unmap_page(dev, dev_addr, size, dir, attrs); > +} > + > +static void ipu3_dmamap_sync_sg_for_cpu(struct device *dev, > + struct scatterlist *sgl, int nelems, > + enum dma_data_direction dir) > +{ > + struct scatterlist *sg; > + int i; > + > + for_each_sg(sgl, sg, nelems, i) > + clflush_cache_range(sg_virt(sg), sg->length); > +} > + > +static void ipu3_dmamap_sync_sg_for_device(struct device *dev, > + struct scatterlist *sgl, int nelems, > + enum dma_data_direction dir) > +{ > + struct scatterlist *sg; > + int i; > + > + for_each_sg(sgl, sg, nelems, i) > + clflush_cache_range(sg_virt(sg), sg->length); > +} > + > +static int ipu3_dmamap_map_sg(struct device *dev, struct scatterlist *sgl, > + int nents, enum dma_data_direction dir, > + unsigned long attrs) > +{ > + if ((DMA_ATTR_SKIP_CPU_SYNC & attrs) == 0) > + ipu3_dmamap_sync_sg_for_device(dev, sgl, nents, dir); > + > + return iommu_dma_map_sg(dev, sgl, nents, > + dma_info_to_prot(dir, false, attrs)); > +} > + > +static void ipu3_dmamap_unmap_sg(struct device *dev, struct scatterlist *sgl, > + int nents, enum dma_data_direction dir, > + unsigned long attrs) > +{ > + if ((DMA_ATTR_SKIP_CPU_SYNC & attrs) == 0) > + ipu3_dmamap_sync_sg_for_cpu(dev, sgl, nents, dir); > + > + iommu_dma_unmap_sg(dev, sgl, nents, dir, attrs); > +} > + > +static struct dma_map_ops ipu3_dmamap_ops = { const? > + .alloc = ipu3_dmamap_alloc, > + .free = ipu3_dmamap_free, > + .mmap = ipu3_dmamap_mmap, > + .get_sgtable = ipu3_dmamap_get_sgtable, > + .map_page = ipu3_dmamap_map_page, > + .unmap_page = ipu3_dmamap_unmap_page, > + .map_sg = ipu3_dmamap_map_sg, > + .unmap_sg = ipu3_dmamap_unmap_sg, > + .sync_single_for_cpu = ipu3_dmamap_sync_single_for_cpu, > + .sync_single_for_device = ipu3_dmamap_sync_single_for_device, > + .sync_sg_for_cpu = ipu3_dmamap_sync_sg_for_cpu, > + .sync_sg_for_device = ipu3_dmamap_sync_sg_for_device, > + .mapping_error = iommu_dma_mapping_error, > +}; > + > +int ipu3_dmamap_init(struct device *dev, u64 dma_base, u64 size) > +{ > + struct iommu_domain *domain; > + int ret; > + > + ret = iommu_dma_init(); > + if (ret) > + return ret; > + > + /* > + * The IOMMU core code allocates the default DMA domain, which the > + * underlying IOMMU driver needs to support via the dma-iommu layer. > + */ > + domain = iommu_get_domain_for_dev(dev); > + if (!domain) { > + pr_warn("Failed to get IOMMU domain for device %s\n", > + dev_name(dev)); > + return -ENODEV; > + } > + > + if (WARN(domain->type != IOMMU_DOMAIN_DMA, "device %s already managed?\n", > + dev_name(dev))) > + return -EINVAL; > + > + ret = iommu_dma_init_domain(domain, dma_base, size, dev); > + if (ret) { > + pr_warn("Failed to init IOMMU domain for device %s\n", > + dev_name(dev)); > + return ret; > + } > + > + dev->dma_ops = &ipu3_dmamap_ops; > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(ipu3_dmamap_init); > + > +void ipu3_dmamap_cleanup(struct device *dev) > +{ > + dev->dma_ops = &ipu3_dmamap_ops; > + iommu_dma_cleanup(); > +} > +EXPORT_SYMBOL_GPL(ipu3_dmamap_cleanup); > + > +MODULE_AUTHOR("Tomasz Figa <tfiga@xxxxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); > +MODULE_DESCRIPTION("IPU3 DMA mapping support"); > diff --git a/drivers/media/pci/intel/ipu3/ipu3-dmamap.h b/drivers/media/pci/intel/ipu3/ipu3-dmamap.h > new file mode 100644 > index 0000000..fe5d0a4 > --- /dev/null > +++ b/drivers/media/pci/intel/ipu3/ipu3-dmamap.h > @@ -0,0 +1,22 @@ > +/* > + * Copyright (c) 2017 Intel Corporation. > + * Copyright (C) 2017 Google, Inc. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License version > + * 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#ifndef __IPU3_DMAMAP_H > +#define __IPU3_DMAMAP_H > + > +int ipu3_dmamap_init(struct device *dev, u64 dma_base, u64 size); > +void ipu3_dmamap_cleanup(struct device *dev); > + > +#endif -- Regards, Sakari Ailus e-mail: sakari.ailus@xxxxxx XMPP: sailus@xxxxxxxxxxxxxx