Hi Yong,

On Tue, Jul 18, 2017 at 10:12:58PM -0500, Yong Zhi wrote:
> From: Tomasz Figa <tf...@chromium.org>
> 
> This patch adds support for the IPU3 DMA mapping API.
> 
> Signed-off-by: Tomasz Figa <tf...@chromium.org>
> Signed-off-by: Yong Zhi <yong....@intel.com>
> ---
>  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 <tf...@chromium.org>");
> +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.ai...@iki.fi     XMPP: sai...@retiisi.org.uk

Reply via email to