Message ID | 20250410-uio-dma-v1-3-6468ace2c786@bootlin.com |
---|---|
State | New |
Headers | show |
Series | uio/dma-buf: Give UIO users access to DMA addresses. | expand |
Hi Bastien, Le jeudi 10 avril 2025 à 16:53 +0200, Bastien Curutchet a écrit : > Some UIO users need to access DMA addresses from userspace to be able to > configure DMA done by the UIO device. Currently there is no way of doing > this. > > Add a UIO_DMABUF_HEAP Kconfig option. When selected, a dma-heap > allocator is created for every new UIO device. This allocator only > implements 4 basic operations: allocate, release, mmap and get_dma_addr. > The buffer allocation is done through dma_alloc_coherent(). This is quite redundant with the CMA heap. I believe a better design is to make UIO devices dmabuf importers. This will make your UIO dmabuf implementation a lot more useful. As for the physical addresses, everywhere you currently pass a physical address, you should be able to add ioctl to pass a DMABuf FD, or a handle to an UIO specific object (similar to buffer objects in DRM) and hide these. regards, Nicolas > > Signed-off-by: Bastien Curutchet <bastien.curutchet@bootlin.com> > --- > drivers/uio/Kconfig | 9 ++++ > drivers/uio/Makefile | 1 + > drivers/uio/uio.c | 4 ++ > drivers/uio/uio_heap.c | 120 +++++++++++++++++++++++++++++++++++++++++++++ > include/linux/uio_driver.h | 2 + > 5 files changed, 136 insertions(+) > > diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig > index b060dcd7c6350191726c0830a1ae7b9a388ca4bb..2f3b1e57fceb01354b65cc4d39f342f645a238db 100644 > --- a/drivers/uio/Kconfig > +++ b/drivers/uio/Kconfig > @@ -164,4 +164,13 @@ config UIO_DFL > opae-sdk/tools/libopaeuio/ > > If you compile this as a module, it will be called uio_dfl. > + > +config UIO_DMABUF_HEAP > + bool "DMA-BUF UIO Heap" > + select DMABUF_HEAPS > + help > + Choose this option to enable DMA-BUF UIO heap. It will create a new > + heap allocator under /dev/dma_heap/ for every UIO device. This > + allocator allows userspace applications to allocate DMA buffers and > + access their DMA addresses thanks to the DMA_BUF_IOCTL_GET_DMA_HANDLE > endif > diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile > index 1c5f3b5a95cf5b681a843b745a046d7ce123255d..f6696daa36567a4e5d18b1b89ba688057e758400 100644 > --- a/drivers/uio/Makefile > +++ b/drivers/uio/Makefile > @@ -11,3 +11,4 @@ obj-$(CONFIG_UIO_MF624) += uio_mf624.o > obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o > obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o > obj-$(CONFIG_UIO_DFL) += uio_dfl.o > +obj-$(CONFIG_UIO_DMABUF_HEAP) += uio_heap.o > diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c > index d93ed4e86a174b5bad193a61aa522cd833fe7bb5..f31936a897805a284165cccfee3d66e96acd4b39 100644 > --- a/drivers/uio/uio.c > +++ b/drivers/uio/uio.c > @@ -1046,7 +1046,11 @@ int __uio_register_device(struct module *owner, > } > } > > +#if defined(CONFIG_UIO_DMABUF_HEAP) > + return add_uio_heap(idev); > +#else > return 0; > +#endif > > err_request_irq: > uio_dev_del_attributes(idev); > diff --git a/drivers/uio/uio_heap.c b/drivers/uio/uio_heap.c > new file mode 100644 > index 0000000000000000000000000000000000000000..2e836b503458e280babba0e0adc4f6d8344efc82 > --- /dev/null > +++ b/drivers/uio/uio_heap.c > @@ -0,0 +1,120 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include <linux/dma-buf.h> > +#include <linux/dma-heap.h> > +#include <linux/uio_driver.h> > + > +struct uio_heap { > + struct dma_heap *heap; > + struct device *dev; > +}; > + > +struct uio_heap_buffer { > + struct uio_heap *heap; > + dma_addr_t dma_addr; > + unsigned long len; > + void *vaddr; > +}; > + > +static int uio_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) > +{ > + struct uio_heap_buffer *buffer = dmabuf->priv; > + > + return dma_mmap_coherent(buffer->heap->dev, vma, buffer->vaddr, > + buffer->dma_addr, buffer->len); > +} > + > +static void uio_heap_dma_buf_release(struct dma_buf *dmabuf) > +{ > + struct uio_heap_buffer *buffer = dmabuf->priv; > + > + dma_free_coherent(buffer->heap->dev, buffer->len, buffer->vaddr, > + buffer->dma_addr); > + kfree(buffer); > +} > + > +static int uio_heap_get_dma_addr(struct dma_buf *dmabuf, u64 *addr) > +{ > + struct uio_heap_buffer *buffer = dmabuf->priv; > + > + *addr = buffer->dma_addr; > + return 0; > +} > + > +static const struct dma_buf_ops uio_heap_buf_ops = { > + .mmap = uio_heap_mmap, > + .release = uio_heap_dma_buf_release, > + .get_dma_addr = uio_heap_get_dma_addr, > +}; > + > +static struct dma_buf *uio_heap_allocate(struct dma_heap *heap, > + unsigned long len, > + u32 fd_flags, > + u64 heap_flags) > +{ > + struct uio_heap *uio_heap = dma_heap_get_drvdata(heap); > + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); > + struct uio_heap_buffer *buffer; > + struct dma_buf *dmabuf; > + > + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); > + if (!buffer) > + return ERR_PTR(-ENOMEM); > + > + dma_set_coherent_mask(uio_heap->dev, DMA_BIT_MASK(32)); > + > + buffer->heap = uio_heap; > + buffer->len = len; > + buffer->vaddr = dma_alloc_coherent(uio_heap->dev, buffer->len, > + &buffer->dma_addr, GFP_KERNEL); > + if (IS_ERR(buffer->vaddr)) > + goto free_buf; > + > + exp_info.exp_name = dma_heap_get_name(heap); > + exp_info.ops = &uio_heap_buf_ops; > + exp_info.size = buffer->len; > + exp_info.flags = fd_flags; > + exp_info.priv = buffer; > + dmabuf = dma_buf_export(&exp_info); > + if (IS_ERR(dmabuf)) > + goto free_dma; > + > + return dmabuf; > + > +free_dma: > + dma_free_coherent(uio_heap->dev, buffer->len, buffer->vaddr, buffer->dma_addr); > +free_buf: > + kfree(buffer); > + > + return ERR_PTR(-ENOMEM); > +} > + > +static const struct dma_heap_ops uio_heap_ops = { > + .allocate = uio_heap_allocate, > +}; > + > +int add_uio_heap(struct uio_device *uio) > +{ > + struct dma_heap_export_info exp_info; > + struct uio_heap *uio_heap; > + > + uio_heap = kzalloc(sizeof(*uio_heap), GFP_KERNEL); > + if (!uio_heap) > + return -ENOMEM; > + > + uio_heap->dev = &uio->dev; > + > + /* Use device name as heap name */ > + exp_info.name = uio_heap->dev->kobj.name; > + exp_info.ops = &uio_heap_ops; > + exp_info.priv = uio_heap; > + > + uio_heap->heap = dma_heap_add(&exp_info); > + if (IS_ERR(uio_heap->heap)) { > + int ret = PTR_ERR(uio_heap->heap); > + > + kfree(uio_heap); > + return ret; > + } > + > + return 0; > +} > diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h > index 18238dc8bfd356a20996ba6cd84f1ff508bbb81c..f8b774d2fa1c7de4b6af881f1e53dfa9f25b3dbf 100644 > --- a/include/linux/uio_driver.h > +++ b/include/linux/uio_driver.h > @@ -143,6 +143,8 @@ extern int __must_check > struct device *parent, > struct uio_info *info); > > +extern int add_uio_heap(struct uio_device *uio); > + > /* use a define to avoid include chaining to get THIS_MODULE */ > > /**
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index b060dcd7c6350191726c0830a1ae7b9a388ca4bb..2f3b1e57fceb01354b65cc4d39f342f645a238db 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -164,4 +164,13 @@ config UIO_DFL opae-sdk/tools/libopaeuio/ If you compile this as a module, it will be called uio_dfl. + +config UIO_DMABUF_HEAP + bool "DMA-BUF UIO Heap" + select DMABUF_HEAPS + help + Choose this option to enable DMA-BUF UIO heap. It will create a new + heap allocator under /dev/dma_heap/ for every UIO device. This + allocator allows userspace applications to allocate DMA buffers and + access their DMA addresses thanks to the DMA_BUF_IOCTL_GET_DMA_HANDLE endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index 1c5f3b5a95cf5b681a843b745a046d7ce123255d..f6696daa36567a4e5d18b1b89ba688057e758400 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_UIO_MF624) += uio_mf624.o obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o obj-$(CONFIG_UIO_DFL) += uio_dfl.o +obj-$(CONFIG_UIO_DMABUF_HEAP) += uio_heap.o diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index d93ed4e86a174b5bad193a61aa522cd833fe7bb5..f31936a897805a284165cccfee3d66e96acd4b39 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -1046,7 +1046,11 @@ int __uio_register_device(struct module *owner, } } +#if defined(CONFIG_UIO_DMABUF_HEAP) + return add_uio_heap(idev); +#else return 0; +#endif err_request_irq: uio_dev_del_attributes(idev); diff --git a/drivers/uio/uio_heap.c b/drivers/uio/uio_heap.c new file mode 100644 index 0000000000000000000000000000000000000000..2e836b503458e280babba0e0adc4f6d8344efc82 --- /dev/null +++ b/drivers/uio/uio_heap.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/dma-buf.h> +#include <linux/dma-heap.h> +#include <linux/uio_driver.h> + +struct uio_heap { + struct dma_heap *heap; + struct device *dev; +}; + +struct uio_heap_buffer { + struct uio_heap *heap; + dma_addr_t dma_addr; + unsigned long len; + void *vaddr; +}; + +static int uio_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) +{ + struct uio_heap_buffer *buffer = dmabuf->priv; + + return dma_mmap_coherent(buffer->heap->dev, vma, buffer->vaddr, + buffer->dma_addr, buffer->len); +} + +static void uio_heap_dma_buf_release(struct dma_buf *dmabuf) +{ + struct uio_heap_buffer *buffer = dmabuf->priv; + + dma_free_coherent(buffer->heap->dev, buffer->len, buffer->vaddr, + buffer->dma_addr); + kfree(buffer); +} + +static int uio_heap_get_dma_addr(struct dma_buf *dmabuf, u64 *addr) +{ + struct uio_heap_buffer *buffer = dmabuf->priv; + + *addr = buffer->dma_addr; + return 0; +} + +static const struct dma_buf_ops uio_heap_buf_ops = { + .mmap = uio_heap_mmap, + .release = uio_heap_dma_buf_release, + .get_dma_addr = uio_heap_get_dma_addr, +}; + +static struct dma_buf *uio_heap_allocate(struct dma_heap *heap, + unsigned long len, + u32 fd_flags, + u64 heap_flags) +{ + struct uio_heap *uio_heap = dma_heap_get_drvdata(heap); + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct uio_heap_buffer *buffer; + struct dma_buf *dmabuf; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); + + dma_set_coherent_mask(uio_heap->dev, DMA_BIT_MASK(32)); + + buffer->heap = uio_heap; + buffer->len = len; + buffer->vaddr = dma_alloc_coherent(uio_heap->dev, buffer->len, + &buffer->dma_addr, GFP_KERNEL); + if (IS_ERR(buffer->vaddr)) + goto free_buf; + + exp_info.exp_name = dma_heap_get_name(heap); + exp_info.ops = &uio_heap_buf_ops; + exp_info.size = buffer->len; + exp_info.flags = fd_flags; + exp_info.priv = buffer; + dmabuf = dma_buf_export(&exp_info); + if (IS_ERR(dmabuf)) + goto free_dma; + + return dmabuf; + +free_dma: + dma_free_coherent(uio_heap->dev, buffer->len, buffer->vaddr, buffer->dma_addr); +free_buf: + kfree(buffer); + + return ERR_PTR(-ENOMEM); +} + +static const struct dma_heap_ops uio_heap_ops = { + .allocate = uio_heap_allocate, +}; + +int add_uio_heap(struct uio_device *uio) +{ + struct dma_heap_export_info exp_info; + struct uio_heap *uio_heap; + + uio_heap = kzalloc(sizeof(*uio_heap), GFP_KERNEL); + if (!uio_heap) + return -ENOMEM; + + uio_heap->dev = &uio->dev; + + /* Use device name as heap name */ + exp_info.name = uio_heap->dev->kobj.name; + exp_info.ops = &uio_heap_ops; + exp_info.priv = uio_heap; + + uio_heap->heap = dma_heap_add(&exp_info); + if (IS_ERR(uio_heap->heap)) { + int ret = PTR_ERR(uio_heap->heap); + + kfree(uio_heap); + return ret; + } + + return 0; +} diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h index 18238dc8bfd356a20996ba6cd84f1ff508bbb81c..f8b774d2fa1c7de4b6af881f1e53dfa9f25b3dbf 100644 --- a/include/linux/uio_driver.h +++ b/include/linux/uio_driver.h @@ -143,6 +143,8 @@ extern int __must_check struct device *parent, struct uio_info *info); +extern int add_uio_heap(struct uio_device *uio); + /* use a define to avoid include chaining to get THIS_MODULE */ /**
Some UIO users need to access DMA addresses from userspace to be able to configure DMA done by the UIO device. Currently there is no way of doing this. Add a UIO_DMABUF_HEAP Kconfig option. When selected, a dma-heap allocator is created for every new UIO device. This allocator only implements 4 basic operations: allocate, release, mmap and get_dma_addr. The buffer allocation is done through dma_alloc_coherent(). Signed-off-by: Bastien Curutchet <bastien.curutchet@bootlin.com> --- drivers/uio/Kconfig | 9 ++++ drivers/uio/Makefile | 1 + drivers/uio/uio.c | 4 ++ drivers/uio/uio_heap.c | 120 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/uio_driver.h | 2 + 5 files changed, 136 insertions(+)