From 9e7a22d3e2d7c9215ba0327a75867d1738b3f12a Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 8 Dec 2013 23:47:08 +0000 Subject: misc: add bmm_dmabuf allocator Add a dma_buf allocator to allow userspace to allocate and map dma_bufs, and share them with other subsystems for their use. Signed-off-by: Russell King --- drivers/misc/Kconfig | 8 + drivers/misc/Makefile | 1 + drivers/misc/bmm_dmabuf.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/bmm_dmabuf.h | 52 +++++++ 4 files changed, 425 insertions(+) create mode 100644 drivers/misc/bmm_dmabuf.c create mode 100644 drivers/misc/bmm_dmabuf.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 8dacd4c9ee87..58abc5686ee7 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -450,6 +450,14 @@ config ARM_CHARLCD line and the Linux version on the second line, but that's still useful. +config BMM_DMABUF + tristate "CMA backed dma_buf allocator" + depends on ARCH_DOVE + help + This is a simple allocator of dma_bufs for userspace. This + allows userspace to allocate and map dma_bufs, and pass them + into other subsystems. + config BMP085 bool depends on SYSFS diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c235d5b68311..89c15f81e59d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_INTEL_MID_PTI) += pti.o obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o +obj-$(CONFIG_BMM_DMABUF) += bmm_dmabuf.o obj-$(CONFIG_BMP085) += bmp085.o obj-$(CONFIG_BMP085_I2C) += bmp085-i2c.o obj-$(CONFIG_BMP085_SPI) += bmp085-spi.o diff --git a/drivers/misc/bmm_dmabuf.c b/drivers/misc/bmm_dmabuf.c new file mode 100644 index 000000000000..abe204783dcb --- /dev/null +++ b/drivers/misc/bmm_dmabuf.c @@ -0,0 +1,364 @@ +/* + * bmm_dmabuf.c + * + * CMA-backed dma_buf allocator + * + * 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. + */ +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BMM_MINOR 94 + +#include "bmm_dmabuf.h" + +struct bmm_block { + struct dma_attrs attrs; + void *cpu; + size_t size; + dma_addr_t paddr; + + struct list_head list; + struct dma_buf *dmabuf; +}; + +static LIST_HEAD(bmm_used_blocks); +static DEFINE_MUTEX(bmm_mutex); /* mutex for block list */ + +static void bmm_insert_ordered(struct bmm_block *new, struct list_head *head) +{ + struct bmm_block *p; + + if (list_empty(head)) { + list_add(&new->list, head); + return; + } + + list_for_each_entry(p, head, list) + if (p->paddr > new->paddr) + break; + + list_add_tail(&new->list, &p->list); +} + +static struct bmm_block *__bmm_malloc(size_t size, unsigned long attr, + unsigned align) +{ + struct bmm_block *new; + + size = ALIGN(size, align); + if (size == 0 || size > TASK_SIZE) + return NULL; + + new = kmalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return NULL; + + init_dma_attrs(&new->attrs); + + dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &new->attrs); + dma_set_attr(DMA_ATTR_FORCE_CONTIGUOUS, &new->attrs); + if (attr & BMM_ATTR_WRITECOMBINE) + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &new->attrs); + + new->size = size; + new->cpu = dma_alloc_attrs(NULL, size, &new->paddr, + GFP_KERNEL | __GFP_HIGHMEM, + &new->attrs); + if (!new->cpu) { + kfree(new); + return NULL; + } + + mutex_lock(&bmm_mutex); + bmm_insert_ordered(new, &bmm_used_blocks); + mutex_unlock(&bmm_mutex); + + return new; +} + +static void __bmm_free(struct bmm_block *bmm) +{ + mutex_lock(&bmm_mutex); + list_del(&bmm->list); + mutex_unlock(&bmm_mutex); + + dma_free_attrs(NULL, bmm->size, bmm->cpu, bmm->paddr, &bmm->attrs); + kfree(bmm); +} + +static struct sg_table * +bmm_dmabuf_map(struct dma_buf_attachment *attach, enum dma_data_direction dir) +{ + struct bmm_block *bmm = attach->dmabuf->priv; + struct sg_table *sgt; + + sgt = kmalloc(sizeof(*sgt), GFP_KERNEL); + if (sgt) { + if (sg_alloc_table(sgt, 1, GFP_KERNEL)) + goto err_free_sgt; + + sg_dma_address(sgt->sgl) = bmm->paddr; + sg_dma_len(sgt->sgl) = bmm->size; + } + return sgt; + + err_free_sgt: + kfree(sgt); + return NULL; +} + +static void +bmm_dmabuf_unmap(struct dma_buf_attachment *attach, struct sg_table *sgt, + enum dma_data_direction dir) +{ + sg_free_table(sgt); + kfree(sgt); +} + +static void bmm_dmabuf_release(struct dma_buf *buf) +{ + struct bmm_block *bmm = buf->priv; + + bmm->dmabuf = NULL; + __bmm_free(bmm); +} + +static void *bmm_no_kmap(struct dma_buf *buf, unsigned long arg) +{ + return NULL; +} + +static void bmm_no_kunmap(struct dma_buf *buf, unsigned long arg, void *addr) +{ +} + +static int bmm_dmabuf_mmap(struct dma_buf *buf, struct vm_area_struct *vma) +{ + struct bmm_block *bmm = buf->priv; + + return dma_mmap_attrs(NULL, vma, bmm->cpu, bmm->paddr, bmm->size, &bmm->attrs); +} + +static const struct dma_buf_ops bmm_dmabuf_ops = { + .map_dma_buf = bmm_dmabuf_map, + .unmap_dma_buf = bmm_dmabuf_unmap, + .release = bmm_dmabuf_release, + .kmap_atomic = bmm_no_kmap, + .kunmap_atomic = bmm_no_kunmap, + .kmap = bmm_no_kmap, + .kunmap = bmm_no_kunmap, + .mmap = bmm_dmabuf_mmap, +}; + +static int bmm_dmabuf_alloc(struct bmm_fd_alloc *alloc) +{ + struct bmm_block *bmm; + struct dma_buf *buf; + int ret; + + if (alloc->align > PAGE_SIZE) + return -EINVAL; + + bmm = __bmm_malloc(alloc->size, alloc->attr, PAGE_SIZE); + if (!bmm) + return -ENOMEM; + + buf = dma_buf_export(bmm, &bmm_dmabuf_ops, bmm->size, O_RDWR); + if (IS_ERR(buf)) { + __bmm_free(bmm); + return PTR_ERR(buf); + } + + bmm->dmabuf = buf; + + /* Consume the refcount on the bmm */ + ret = dma_buf_fd(buf, O_CLOEXEC); + if (ret >= 0) + alloc->fd = ret; + + return ret < 0 ? ret : 0; +} + +static struct dma_buf *bmm_block_get(int fd) +{ + struct dma_buf *buf = dma_buf_get(fd); + + if (!IS_ERR(buf) && buf->ops != &bmm_dmabuf_ops) { + dma_buf_put(buf); + buf = ERR_PTR(-EINVAL); + } + return buf; +} + +static int bmm_dmabuf_flush(struct bmm_fd_flush *flush) +{ + struct dma_buf *buf = bmm_block_get(flush->fd); + struct bmm_block *bmm; + + if (IS_ERR(buf)) + return PTR_ERR(buf); + + bmm = buf->priv; + + dma_buf_put(buf); + + return -EINVAL; +} + +union ioctl_arg { + struct bmm_fd_alloc alloc; + struct bmm_fd_flush flush; +}; + +static long bmm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + void __user *u_io = (void __user *)arg; + union ioctl_arg io; + int ret; + + if (_IOC_SIZE(cmd) > sizeof(io)) + return -EINVAL; + + memset(&io, 0, sizeof(io)); + if (cmd & IOC_IN && copy_from_user(&io, u_io, _IOC_SIZE(cmd))) { + pr_debug("bmm_ioctl() error in copy_from_user()\n"); + return -EFAULT; + } + + pr_debug("bmm_ioctl(cmd=0x%08x, arg=0x%08lx)\n", cmd, arg); + + switch (cmd) { + case BMM_FD_ALLOC: + ret = bmm_dmabuf_alloc(&io.alloc); + break; + + case BMM_FD_FLUSH: + ret = bmm_dmabuf_flush(&io.flush); + break; + + default: + ret = -ENOTTY; + break; + } + + if (ret == 0 && cmd & IOC_OUT) { + if (copy_to_user(u_io, &io, _IOC_SIZE(cmd))) { + pr_debug("bmm_ioctl() error in copy_to_user()\n"); + return -EFAULT; + } + } + + return ret; +} + +static const struct file_operations bmm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = bmm_ioctl, +}; + +static struct miscdevice bmm_misc = { + .minor = BMM_MINOR, + .name = "bmm", + .fops = &bmm_fops, +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *bmm_dentry; + +static int bmm_debugfs_show(struct seq_file *m, void *data) +{ + struct bmm_block *bmm; + + list_for_each_entry(bmm, &bmm_used_blocks, list) { + seq_printf(m, "0x%08lx-0x%08lx used %p %p\n", + (unsigned long)bmm->paddr, + (unsigned long)bmm->paddr + bmm->size - 1, + bmm->dmabuf, bmm->dmabuf ? bmm->dmabuf->file : NULL); + } + return 0; +} + +static int bmm_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, bmm_debugfs_show, NULL); +} + +static const struct file_operations bmm_debugfs_fops = { + .owner = THIS_MODULE, + .open = bmm_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void bmm_debugfs_init(void) +{ + bmm_dentry = debugfs_create_file("bmm", S_IRUSR, NULL, NULL, + &bmm_debugfs_fops); +} + +static void bmm_debugfs_exit(void) +{ + debugfs_remove(bmm_dentry); +} +#else +#define bmm_debugfs_init() +#define bmm_debugfs_exit() +#endif + +static int bmm_probe(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver bmm_driver = { + .driver = { + .name = "bmm", + .owner = THIS_MODULE, + }, + .probe = bmm_probe, +}; +MODULE_ALIAS(PLATFORM_MODULE_PREFIX "bmm"); + +static int __init bmm_init(void) +{ + int ret; + + bmm_debugfs_init(); + + ret = platform_driver_register(&bmm_driver); + if (ret) + return ret; + + ret = misc_register(&bmm_misc); + if (ret) + platform_driver_unregister(&bmm_driver); + + return ret; +} + +static void __exit bmm_exit(void) +{ + bmm_debugfs_exit(); + misc_deregister(&bmm_misc); + platform_driver_unregister(&bmm_driver); +} +module_init(bmm_init); +module_exit(bmm_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CMA-backed dma_buf Management Module"); diff --git a/drivers/misc/bmm_dmabuf.h b/drivers/misc/bmm_dmabuf.h new file mode 100644 index 000000000000..632982b0c9ba --- /dev/null +++ b/drivers/misc/bmm_dmabuf.h @@ -0,0 +1,52 @@ +/* + * bmm_dmabuf.h + * + * CMA backed dma_buf management + * + * 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. + * + * Portions (C) Copyright 2007 Marvell International Ltd. + * All Rights Reserved + */ + +#ifndef _BMM_DMABUF_H +#define _BMM_DMABUF_H + +#include +#include +#include + +/* ioctl commands */ +struct bmm_fd_alloc { + __u64 size; + __u64 align; + __u32 attr; + __s32 fd; +}; + +struct bmm_fd_flush { + __u64 size; + __u64 offset; + __u64 ptr; + __s32 fd; + __u32 direction; +}; + +#define BMEM_IOCTL_MAGIC 'G' +#define BMM_FD_ALLOC _IOWR(BMEM_IOCTL_MAGIC, 64, struct bmm_fd_alloc) +#define BMM_FD_FLUSH _IOW(BMEM_IOCTL_MAGIC, 65, struct bmm_fd_flush) + +/* ioctl arguments: memory attributes */ +#define BMM_ATTR_DEFAULT (0) /* cacheable bufferable */ +#define BMM_ATTR_WRITECOMBINE (1 << 0) /* non-cacheable & bufferable */ +#define BMM_ATTR_NONCACHED (1 << 1) /* non-cacheable & non-bufferable */ + +/* ioctl arguments: cache flush direction */ +#define BMM_DMA_BIDIRECTIONAL 0 +#define BMM_DMA_TO_DEVICE 1 +#define BMM_DMA_FROM_DEVICE 2 +#define BMM_DMA_NONE 3 + +#endif -- cgit