summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2013-12-08 23:47:08 +0000
committerRussell King <rmk+kernel@arm.linux.org.uk>2013-12-09 00:22:41 +0000
commit9e7a22d3e2d7c9215ba0327a75867d1738b3f12a (patch)
tree49d5b6ff754a4c6f86e9c51e394de56dea3d878c
parent5e01dc7b26d9f24f39abace5da98ccbd6a5ceb52 (diff)
misc: add bmm_dmabuf allocatorbmm-3.12
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 <rmk+kernel@arm.linux.org.uk>
-rw-r--r--drivers/misc/Kconfig8
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/bmm_dmabuf.c364
-rw-r--r--drivers/misc/bmm_dmabuf.h52
4 files changed, 425 insertions, 0 deletions
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 <linux/debugfs.h>
+#include <linux/dma-buf.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <asm/page.h>
+
+#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 <linux/dma-mapping.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* 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