[RFC] Experimental DMA-BUF Device Heaps

Daniel Vetter daniel at ffwll.ch
Wed Sep 16 17:01:14 UTC 2020


On Mon, Aug 17, 2020 at 9:09 AM Ezequiel Garcia <ezequiel at collabora.com> wrote:
>
> This heap is basically a wrapper around DMA-API dma_alloc_attrs,
> which will allocate memory suitable for the given device.
>
> The implementation is mostly a port of the Contiguous Videobuf2
> memory allocator (see videobuf2/videobuf2-dma-contig.c)
> over to the DMA-BUF Heap interface.
>
> The intention of this allocator is to provide applications
> with a more system-agnostic API: the only thing the application
> needs to know is which device to get the buffer for.
>
> Whether the buffer is backed by CMA, IOMMU or a DMA Pool
> is unknown to the application.
>
> I'm not really expecting this patch to be correct or even
> a good idea, but just submitting it to start a discussion on DMA-BUF
> heap discovery and negotiation.
>
> Given Plumbers is just a couple weeks from now, I've submitted
> a BoF proposal to discuss this, as perhaps it would make
> sense to discuss this live?
>
> Not-signed-off-by: Ezequiel Garcia <ezequiel at collabora.com>

Adding Simon and James who've done a presentation at XDC just now
about allocation constraint solving and heap ids came up. There's
going to be a discussion tomorrow in the workshop track, might be
worth and try joining if you can make it happen (xdc is virtual and
free).

Cheers, Daniel

> ---
>  drivers/dma-buf/heaps/Kconfig       |   9 +
>  drivers/dma-buf/heaps/Makefile      |   1 +
>  drivers/dma-buf/heaps/device_heap.c | 268 ++++++++++++++++++++++++++++
>  include/linux/device.h              |   5 +
>  include/linux/dma-heap.h            |   6 +
>  5 files changed, 289 insertions(+)
>  create mode 100644 drivers/dma-buf/heaps/device_heap.c
>
> diff --git a/drivers/dma-buf/heaps/Kconfig b/drivers/dma-buf/heaps/Kconfig
> index a5eef06c4226..2bb3604184bd 100644
> --- a/drivers/dma-buf/heaps/Kconfig
> +++ b/drivers/dma-buf/heaps/Kconfig
> @@ -12,3 +12,12 @@ config DMABUF_HEAPS_CMA
>           Choose this option to enable dma-buf CMA heap. This heap is backed
>           by the Contiguous Memory Allocator (CMA). If your system has these
>           regions, you should say Y here.
> +
> +config DMABUF_HEAPS_DEVICES
> +       bool "DMA-BUF Device DMA Heap (Experimental)"
> +       depends on DMABUF_HEAPS
> +       help
> +         Choose this option to enable dma-buf per-device heap. This heap is backed
> +         by the DMA-API and it's an Experimental feature, meant mostly for testing
> +         and experimentation.
> +         Just say N here.
> diff --git a/drivers/dma-buf/heaps/Makefile b/drivers/dma-buf/heaps/Makefile
> index 6e54cdec3da0..c691d85b3044 100644
> --- a/drivers/dma-buf/heaps/Makefile
> +++ b/drivers/dma-buf/heaps/Makefile
> @@ -2,3 +2,4 @@
>  obj-y                                  += heap-helpers.o
>  obj-$(CONFIG_DMABUF_HEAPS_SYSTEM)      += system_heap.o
>  obj-$(CONFIG_DMABUF_HEAPS_CMA)         += cma_heap.o
> +obj-$(CONFIG_DMABUF_HEAPS_DEVICES)     += device_heap.o
> diff --git a/drivers/dma-buf/heaps/device_heap.c b/drivers/dma-buf/heaps/device_heap.c
> new file mode 100644
> index 000000000000..1803dc622dd8
> --- /dev/null
> +++ b/drivers/dma-buf/heaps/device_heap.c
> @@ -0,0 +1,268 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DMABUF Device DMA heap exporter
> + *
> + * Copyright (C) 2020, Collabora Ltd.
> + *
> + * Based on:
> + *   videobuf2-dma-contig.c - DMA contig memory allocator for videobuf2
> + *   Copyright (C) 2010 Samsung Electronics
> + */
> +
> +#include <linux/device.h>
> +#include <linux/dma-buf.h>
> +#include <linux/dma-heap.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/scatterlist.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +
> +struct dev_dmabuf_attachment {
> +       struct sg_table sgt;
> +       enum dma_data_direction dma_dir;
> +};
> +
> +struct dev_dmabuf {
> +       struct dma_heap *heap;
> +       struct dma_buf *dmabuf;
> +       struct device *dev;
> +       size_t size;
> +       void *vaddr;
> +       dma_addr_t dma_addr;
> +       unsigned long attrs;
> +
> +       struct sg_table sgt;
> +};
> +
> +static struct sg_table *dev_dmabuf_ops_map(struct dma_buf_attachment *db_attach,
> +                                          enum dma_data_direction dma_dir)
> +{
> +       struct dev_dmabuf_attachment *attach = db_attach->priv;
> +       /* stealing dmabuf mutex to serialize map/unmap operations */
> +       struct mutex *lock = &db_attach->dmabuf->lock;
> +       struct sg_table *sgt;
> +
> +       mutex_lock(lock);
> +
> +       sgt = &attach->sgt;
> +       /* return previously mapped sg table */
> +       if (attach->dma_dir == dma_dir) {
> +               mutex_unlock(lock);
> +               return sgt;
> +       }
> +
> +       /* release any previous cache */
> +       if (attach->dma_dir != DMA_NONE) {
> +               dma_unmap_sg_attrs(db_attach->dev, sgt->sgl, sgt->orig_nents,
> +                                  attach->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
> +               attach->dma_dir = DMA_NONE;
> +       }
> +
> +       /*
> +        * mapping to the client with new direction, no cache sync
> +        * required see comment in .dmabuf_ops_detach()
> +        */
> +       sgt->nents = dma_map_sg_attrs(db_attach->dev, sgt->sgl, sgt->orig_nents,
> +                                     dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
> +       if (!sgt->nents) {
> +               dev_err(db_attach->dev, "failed to map scatterlist\n");
> +               mutex_unlock(lock);
> +               return ERR_PTR(-EIO);
> +       }
> +
> +       attach->dma_dir = dma_dir;
> +
> +       mutex_unlock(lock);
> +
> +       return sgt;
> +}
> +
> +static void dev_dmabuf_ops_unmap(struct dma_buf_attachment *db_attach,
> +                                struct sg_table *sgt,
> +                                enum dma_data_direction dma_dir)
> +{
> +       /* nothing to be done here */
> +}
> +
> +static int dev_dmabuf_ops_attach(struct dma_buf *dmabuf,
> +                                struct dma_buf_attachment *dbuf_attach)
> +{
> +       struct dev_dmabuf_attachment *attach;
> +       unsigned int i;
> +       struct scatterlist *rd, *wr;
> +       struct sg_table *sgt;
> +       struct dev_dmabuf *buf = dmabuf->priv;
> +       int ret;
> +
> +       attach = kzalloc(sizeof(*attach), GFP_KERNEL);
> +       if (!attach)
> +               return -ENOMEM;
> +       sgt = &attach->sgt;
> +
> +       /*
> +        * Copy the buf->sgt scatter list to the attachment, as we can't
> +        * map the same scatter list to multiple attachments at the same time.
> +        */
> +       ret = sg_alloc_table(sgt, buf->sgt.orig_nents, GFP_KERNEL);
> +       if (ret) {
> +               kfree(attach);
> +               return -ENOMEM;
> +       }
> +
> +       rd = buf->sgt.sgl;
> +       wr = sgt->sgl;
> +       for (i = 0; i < sgt->orig_nents; ++i) {
> +               sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
> +               rd = sg_next(rd);
> +               wr = sg_next(wr);
> +       }
> +
> +       attach->dma_dir = DMA_NONE;
> +       dbuf_attach->priv = attach;
> +
> +       return 0;
> +}
> +
> +static void dev_dmabuf_ops_detach(struct dma_buf *dmabuf,
> +                                 struct dma_buf_attachment *db_attach)
> +{
> +       struct dev_dmabuf_attachment *attach = db_attach->priv;
> +       struct sg_table *sgt;
> +
> +       if (!attach)
> +               return;
> +       sgt = &attach->sgt;
> +
> +       /* release the scatterlist cache */
> +       if (attach->dma_dir != DMA_NONE)
> +               /*
> +                * Cache sync can be skipped here, as the memory is
> +                * allocated from device coherent memory, which means the
> +                * memory locations do not require any explicit cache
> +                * maintenance prior or after being used by the device.
> +                *
> +                * XXX: This needs a revisit.
> +                */
> +               dma_unmap_sg_attrs(db_attach->dev, sgt->sgl, sgt->orig_nents,
> +                                  attach->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
> +       sg_free_table(sgt);
> +       kfree(attach);
> +       db_attach->priv = NULL;
> +}
> +
> +
> +static void *dev_dmabuf_ops_vmap(struct dma_buf *dmabuf)
> +{
> +       struct dev_dmabuf *buf = dmabuf->priv;
> +
> +       return buf->vaddr;
> +}
> +
> +static void dev_dmabuf_ops_release(struct dma_buf *dmabuf)
> +{
> +       struct dev_dmabuf *buf = dmabuf->priv;
> +
> +       sg_free_table(&buf->sgt);
> +       dma_free_attrs(buf->dev, buf->size, buf->vaddr,
> +                      buf->dma_addr, buf->attrs);
> +       put_device(buf->dev);
> +       kfree(buf);
> +}
> +
> +static int dev_dmabuf_ops_mmap(struct dma_buf *dmabuf,
> +                              struct vm_area_struct *vma)
> +{
> +       struct dev_dmabuf *buf = dmabuf->priv;
> +       int ret;
> +
> +       ret = dma_mmap_attrs(buf->dev, vma, buf->vaddr,
> +                            buf->dma_addr, buf->size,
> +                            buf->attrs);
> +       if (ret) {
> +               dev_err(buf->dev, "remapping memory failed, error: %d\n", ret);
> +               return ret;
> +       }
> +       vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
> +
> +       return 0;
> +}
> +
> +static const struct dma_buf_ops dev_dmabuf_ops = {
> +       .attach = dev_dmabuf_ops_attach,
> +       .detach = dev_dmabuf_ops_detach,
> +       .map_dma_buf = dev_dmabuf_ops_map,
> +       .unmap_dma_buf = dev_dmabuf_ops_unmap,
> +       .vmap = dev_dmabuf_ops_vmap,
> +       .mmap = dev_dmabuf_ops_mmap,
> +       .release = dev_dmabuf_ops_release,
> +};
> +
> +static int dev_heap_allocate(struct dma_heap *heap,
> +                       unsigned long size,
> +                       unsigned long fd_flags,
> +                       unsigned long heap_flags)
> +{
> +       struct device *dev = dma_heap_get_drvdata(heap);
> +       struct dev_dmabuf *buf;
> +       struct dma_buf_export_info exp_info = {};
> +       unsigned long attrs = 0;
> +       int ret = -ENOMEM;
> +
> +       buf = kzalloc(sizeof(*buf), GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       buf->vaddr = dma_alloc_attrs(dev, size, &buf->dma_addr,
> +                                    GFP_KERNEL, attrs);
> +       /* Prevent the device from being released while the buffer is used */
> +       buf->dev = get_device(dev);
> +       buf->heap = heap;
> +       buf->size = size;
> +       buf->attrs = attrs;
> +
> +       /* XXX: This call is documented as unsafe. See dma_get_sgtable_attrs(). */
> +       ret = dma_get_sgtable_attrs(buf->dev, &buf->sgt,
> +                                   buf->vaddr, buf->dma_addr,
> +                                   buf->size, buf->attrs);
> +       if (ret < 0) {
> +               dev_err(buf->dev, "failed to get scatterlist from DMA API\n");
> +               return ret;
> +       }
> +
> +       exp_info.exp_name = dev_name(dev);
> +       exp_info.owner = THIS_MODULE;
> +       exp_info.ops = &dev_dmabuf_ops;
> +       exp_info.size = size;
> +       exp_info.flags = fd_flags;
> +       exp_info.priv = buf;
> +
> +       buf->dmabuf = dma_buf_export(&exp_info);
> +       if (IS_ERR(buf->dmabuf)) {
> +               dev_err(buf->dev, "failed to export dmabuf\n");
> +               return PTR_ERR(buf->dmabuf);
> +       }
> +
> +       ret = dma_buf_fd(buf->dmabuf, fd_flags);
> +       if (ret < 0) {
> +               dev_err(buf->dev, "failed to get dmabuf fd: %d\n", ret);
> +               return ret;
> +       }
> +
> +       return ret;
> +}
> +
> +static const struct dma_heap_ops dev_heap_ops = {
> +       .allocate = dev_heap_allocate,
> +};
> +
> +void dev_dma_heap_add(struct device *dev)
> +{
> +       struct dma_heap_export_info exp_info;
> +
> +       exp_info.name = dev_name(dev);
> +       exp_info.ops = &dev_heap_ops;
> +       exp_info.priv = dev;
> +
> +       dev->heap = dma_heap_add(&exp_info);
> +}
> +EXPORT_SYMBOL(dev_dma_heap_add);
> diff --git a/include/linux/device.h b/include/linux/device.h
> index ca18da4768e3..1fae95d55ea1 100644
> --- a/include/linux/device.h
> +++ b/include/linux/device.h
> @@ -45,6 +45,7 @@ struct iommu_ops;
>  struct iommu_group;
>  struct dev_pin_info;
>  struct dev_iommu;
> +struct dma_heap;
>
>  /**
>   * struct subsys_interface - interfaces to device functions
> @@ -597,6 +598,10 @@ struct device {
>         struct iommu_group      *iommu_group;
>         struct dev_iommu        *iommu;
>
> +#ifdef CONFIG_DMABUF_HEAPS_DEVICES
> +       struct dma_heap         *heap;
> +#endif
> +
>         bool                    offline_disabled:1;
>         bool                    offline:1;
>         bool                    of_node_reused:1;
> diff --git a/include/linux/dma-heap.h b/include/linux/dma-heap.h
> index 454e354d1ffb..dcf7cca2f487 100644
> --- a/include/linux/dma-heap.h
> +++ b/include/linux/dma-heap.h
> @@ -56,4 +56,10 @@ void *dma_heap_get_drvdata(struct dma_heap *heap);
>   */
>  struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info);
>
> +#ifdef CONFIG_DMABUF_HEAPS_DEVICES
> +void dev_dma_heap_add(struct device *dev);
> +#else
> +static inline void dev_dma_heap_add(struct device *dev) {}
> +#endif
> +
>  #endif /* _DMA_HEAPS_H */
> --
> 2.27.0
>
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel



-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch


More information about the dri-devel mailing list