1c02a81fbSAndrew F. Davis // SPDX-License-Identifier: GPL-2.0 2c02a81fbSAndrew F. Davis /* 3c02a81fbSAndrew F. Davis * Framework for userspace DMA-BUF allocations 4c02a81fbSAndrew F. Davis * 5c02a81fbSAndrew F. Davis * Copyright (C) 2011 Google, Inc. 6c02a81fbSAndrew F. Davis * Copyright (C) 2019 Linaro Ltd. 7c02a81fbSAndrew F. Davis */ 8c02a81fbSAndrew F. Davis 9c02a81fbSAndrew F. Davis #include <linux/cdev.h> 10c02a81fbSAndrew F. Davis #include <linux/debugfs.h> 11c02a81fbSAndrew F. Davis #include <linux/device.h> 12c02a81fbSAndrew F. Davis #include <linux/dma-buf.h> 13c02a81fbSAndrew F. Davis #include <linux/err.h> 14c02a81fbSAndrew F. Davis #include <linux/xarray.h> 15c02a81fbSAndrew F. Davis #include <linux/list.h> 16c02a81fbSAndrew F. Davis #include <linux/slab.h> 17c02a81fbSAndrew F. Davis #include <linux/uaccess.h> 18c02a81fbSAndrew F. Davis #include <linux/syscalls.h> 19c02a81fbSAndrew F. Davis #include <linux/dma-heap.h> 20c02a81fbSAndrew F. Davis #include <uapi/linux/dma-heap.h> 21c02a81fbSAndrew F. Davis 22c02a81fbSAndrew F. Davis #define DEVNAME "dma_heap" 23c02a81fbSAndrew F. Davis 24c02a81fbSAndrew F. Davis #define NUM_HEAP_MINORS 128 25c02a81fbSAndrew F. Davis 26c02a81fbSAndrew F. Davis /** 27c02a81fbSAndrew F. Davis * struct dma_heap - represents a dmabuf heap in the system 28c02a81fbSAndrew F. Davis * @name: used for debugging/device-node name 29c02a81fbSAndrew F. Davis * @ops: ops struct for this heap 30c02a81fbSAndrew F. Davis * @heap_devt heap device node 31c02a81fbSAndrew F. Davis * @list list head connecting to list of heaps 32c02a81fbSAndrew F. Davis * @heap_cdev heap char device 33c02a81fbSAndrew F. Davis * 34c02a81fbSAndrew F. Davis * Represents a heap of memory from which buffers can be made. 35c02a81fbSAndrew F. Davis */ 36c02a81fbSAndrew F. Davis struct dma_heap { 37c02a81fbSAndrew F. Davis const char *name; 38c02a81fbSAndrew F. Davis const struct dma_heap_ops *ops; 39c02a81fbSAndrew F. Davis void *priv; 40c02a81fbSAndrew F. Davis dev_t heap_devt; 41c02a81fbSAndrew F. Davis struct list_head list; 42c02a81fbSAndrew F. Davis struct cdev heap_cdev; 43c02a81fbSAndrew F. Davis }; 44c02a81fbSAndrew F. Davis 45c02a81fbSAndrew F. Davis static LIST_HEAD(heap_list); 46c02a81fbSAndrew F. Davis static DEFINE_MUTEX(heap_list_lock); 47c02a81fbSAndrew F. Davis static dev_t dma_heap_devt; 48c02a81fbSAndrew F. Davis static struct class *dma_heap_class; 49c02a81fbSAndrew F. Davis static DEFINE_XARRAY_ALLOC(dma_heap_minors); 50c02a81fbSAndrew F. Davis 51c02a81fbSAndrew F. Davis static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, 52c02a81fbSAndrew F. Davis unsigned int fd_flags, 53c02a81fbSAndrew F. Davis unsigned int heap_flags) 54c02a81fbSAndrew F. Davis { 55*c7f59e3dSJohn Stultz struct dma_buf *dmabuf; 56*c7f59e3dSJohn Stultz int fd; 57*c7f59e3dSJohn Stultz 58c02a81fbSAndrew F. Davis /* 59c02a81fbSAndrew F. Davis * Allocations from all heaps have to begin 60c02a81fbSAndrew F. Davis * and end on page boundaries. 61c02a81fbSAndrew F. Davis */ 62c02a81fbSAndrew F. Davis len = PAGE_ALIGN(len); 63c02a81fbSAndrew F. Davis if (!len) 64c02a81fbSAndrew F. Davis return -EINVAL; 65c02a81fbSAndrew F. Davis 66*c7f59e3dSJohn Stultz dmabuf = heap->ops->allocate(heap, len, fd_flags, heap_flags); 67*c7f59e3dSJohn Stultz if (IS_ERR(dmabuf)) 68*c7f59e3dSJohn Stultz return PTR_ERR(dmabuf); 69*c7f59e3dSJohn Stultz 70*c7f59e3dSJohn Stultz fd = dma_buf_fd(dmabuf, fd_flags); 71*c7f59e3dSJohn Stultz if (fd < 0) { 72*c7f59e3dSJohn Stultz dma_buf_put(dmabuf); 73*c7f59e3dSJohn Stultz /* just return, as put will call release and that will free */ 74*c7f59e3dSJohn Stultz } 75*c7f59e3dSJohn Stultz return fd; 76c02a81fbSAndrew F. Davis } 77c02a81fbSAndrew F. Davis 78c02a81fbSAndrew F. Davis static int dma_heap_open(struct inode *inode, struct file *file) 79c02a81fbSAndrew F. Davis { 80c02a81fbSAndrew F. Davis struct dma_heap *heap; 81c02a81fbSAndrew F. Davis 82c02a81fbSAndrew F. Davis heap = xa_load(&dma_heap_minors, iminor(inode)); 83c02a81fbSAndrew F. Davis if (!heap) { 84c02a81fbSAndrew F. Davis pr_err("dma_heap: minor %d unknown.\n", iminor(inode)); 85c02a81fbSAndrew F. Davis return -ENODEV; 86c02a81fbSAndrew F. Davis } 87c02a81fbSAndrew F. Davis 88c02a81fbSAndrew F. Davis /* instance data as context */ 89c02a81fbSAndrew F. Davis file->private_data = heap; 90c02a81fbSAndrew F. Davis nonseekable_open(inode, file); 91c02a81fbSAndrew F. Davis 92c02a81fbSAndrew F. Davis return 0; 93c02a81fbSAndrew F. Davis } 94c02a81fbSAndrew F. Davis 95c02a81fbSAndrew F. Davis static long dma_heap_ioctl_allocate(struct file *file, void *data) 96c02a81fbSAndrew F. Davis { 97c02a81fbSAndrew F. Davis struct dma_heap_allocation_data *heap_allocation = data; 98c02a81fbSAndrew F. Davis struct dma_heap *heap = file->private_data; 99c02a81fbSAndrew F. Davis int fd; 100c02a81fbSAndrew F. Davis 101c02a81fbSAndrew F. Davis if (heap_allocation->fd) 102c02a81fbSAndrew F. Davis return -EINVAL; 103c02a81fbSAndrew F. Davis 104c02a81fbSAndrew F. Davis if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS) 105c02a81fbSAndrew F. Davis return -EINVAL; 106c02a81fbSAndrew F. Davis 107c02a81fbSAndrew F. Davis if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS) 108c02a81fbSAndrew F. Davis return -EINVAL; 109c02a81fbSAndrew F. Davis 110c02a81fbSAndrew F. Davis fd = dma_heap_buffer_alloc(heap, heap_allocation->len, 111c02a81fbSAndrew F. Davis heap_allocation->fd_flags, 112c02a81fbSAndrew F. Davis heap_allocation->heap_flags); 113c02a81fbSAndrew F. Davis if (fd < 0) 114c02a81fbSAndrew F. Davis return fd; 115c02a81fbSAndrew F. Davis 116c02a81fbSAndrew F. Davis heap_allocation->fd = fd; 117c02a81fbSAndrew F. Davis 118c02a81fbSAndrew F. Davis return 0; 119c02a81fbSAndrew F. Davis } 120c02a81fbSAndrew F. Davis 1217d411afeSzhong jiang static unsigned int dma_heap_ioctl_cmds[] = { 122b3b43465SAndrew F. Davis DMA_HEAP_IOCTL_ALLOC, 123c02a81fbSAndrew F. Davis }; 124c02a81fbSAndrew F. Davis 125c02a81fbSAndrew F. Davis static long dma_heap_ioctl(struct file *file, unsigned int ucmd, 126c02a81fbSAndrew F. Davis unsigned long arg) 127c02a81fbSAndrew F. Davis { 128c02a81fbSAndrew F. Davis char stack_kdata[128]; 129c02a81fbSAndrew F. Davis char *kdata = stack_kdata; 130c02a81fbSAndrew F. Davis unsigned int kcmd; 131c02a81fbSAndrew F. Davis unsigned int in_size, out_size, drv_size, ksize; 132c02a81fbSAndrew F. Davis int nr = _IOC_NR(ucmd); 133c02a81fbSAndrew F. Davis int ret = 0; 134c02a81fbSAndrew F. Davis 135c02a81fbSAndrew F. Davis if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds)) 136c02a81fbSAndrew F. Davis return -EINVAL; 137c02a81fbSAndrew F. Davis 138c02a81fbSAndrew F. Davis /* Get the kernel ioctl cmd that matches */ 139c02a81fbSAndrew F. Davis kcmd = dma_heap_ioctl_cmds[nr]; 140c02a81fbSAndrew F. Davis 141c02a81fbSAndrew F. Davis /* Figure out the delta between user cmd size and kernel cmd size */ 142c02a81fbSAndrew F. Davis drv_size = _IOC_SIZE(kcmd); 143c02a81fbSAndrew F. Davis out_size = _IOC_SIZE(ucmd); 144c02a81fbSAndrew F. Davis in_size = out_size; 145c02a81fbSAndrew F. Davis if ((ucmd & kcmd & IOC_IN) == 0) 146c02a81fbSAndrew F. Davis in_size = 0; 147c02a81fbSAndrew F. Davis if ((ucmd & kcmd & IOC_OUT) == 0) 148c02a81fbSAndrew F. Davis out_size = 0; 149c02a81fbSAndrew F. Davis ksize = max(max(in_size, out_size), drv_size); 150c02a81fbSAndrew F. Davis 151c02a81fbSAndrew F. Davis /* If necessary, allocate buffer for ioctl argument */ 152c02a81fbSAndrew F. Davis if (ksize > sizeof(stack_kdata)) { 153c02a81fbSAndrew F. Davis kdata = kmalloc(ksize, GFP_KERNEL); 154c02a81fbSAndrew F. Davis if (!kdata) 155c02a81fbSAndrew F. Davis return -ENOMEM; 156c02a81fbSAndrew F. Davis } 157c02a81fbSAndrew F. Davis 158c02a81fbSAndrew F. Davis if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) { 159c02a81fbSAndrew F. Davis ret = -EFAULT; 160c02a81fbSAndrew F. Davis goto err; 161c02a81fbSAndrew F. Davis } 162c02a81fbSAndrew F. Davis 163c02a81fbSAndrew F. Davis /* zero out any difference between the kernel/user structure size */ 164c02a81fbSAndrew F. Davis if (ksize > in_size) 165c02a81fbSAndrew F. Davis memset(kdata + in_size, 0, ksize - in_size); 166c02a81fbSAndrew F. Davis 167c02a81fbSAndrew F. Davis switch (kcmd) { 168b3b43465SAndrew F. Davis case DMA_HEAP_IOCTL_ALLOC: 169c02a81fbSAndrew F. Davis ret = dma_heap_ioctl_allocate(file, kdata); 170c02a81fbSAndrew F. Davis break; 171c02a81fbSAndrew F. Davis default: 172f9d3b2c6SColin Ian King ret = -ENOTTY; 173f9d3b2c6SColin Ian King goto err; 174c02a81fbSAndrew F. Davis } 175c02a81fbSAndrew F. Davis 176c02a81fbSAndrew F. Davis if (copy_to_user((void __user *)arg, kdata, out_size) != 0) 177c02a81fbSAndrew F. Davis ret = -EFAULT; 178c02a81fbSAndrew F. Davis err: 179c02a81fbSAndrew F. Davis if (kdata != stack_kdata) 180c02a81fbSAndrew F. Davis kfree(kdata); 181c02a81fbSAndrew F. Davis return ret; 182c02a81fbSAndrew F. Davis } 183c02a81fbSAndrew F. Davis 184c02a81fbSAndrew F. Davis static const struct file_operations dma_heap_fops = { 185c02a81fbSAndrew F. Davis .owner = THIS_MODULE, 186c02a81fbSAndrew F. Davis .open = dma_heap_open, 187c02a81fbSAndrew F. Davis .unlocked_ioctl = dma_heap_ioctl, 188c02a81fbSAndrew F. Davis #ifdef CONFIG_COMPAT 189c02a81fbSAndrew F. Davis .compat_ioctl = dma_heap_ioctl, 190c02a81fbSAndrew F. Davis #endif 191c02a81fbSAndrew F. Davis }; 192c02a81fbSAndrew F. Davis 193c02a81fbSAndrew F. Davis /** 194c02a81fbSAndrew F. Davis * dma_heap_get_drvdata() - get per-subdriver data for the heap 195c02a81fbSAndrew F. Davis * @heap: DMA-Heap to retrieve private data for 196c02a81fbSAndrew F. Davis * 197c02a81fbSAndrew F. Davis * Returns: 198c02a81fbSAndrew F. Davis * The per-subdriver data for the heap. 199c02a81fbSAndrew F. Davis */ 200c02a81fbSAndrew F. Davis void *dma_heap_get_drvdata(struct dma_heap *heap) 201c02a81fbSAndrew F. Davis { 202c02a81fbSAndrew F. Davis return heap->priv; 203c02a81fbSAndrew F. Davis } 204c02a81fbSAndrew F. Davis 205c02a81fbSAndrew F. Davis struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info) 206c02a81fbSAndrew F. Davis { 207c02a81fbSAndrew F. Davis struct dma_heap *heap, *h, *err_ret; 208c02a81fbSAndrew F. Davis struct device *dev_ret; 209c02a81fbSAndrew F. Davis unsigned int minor; 210c02a81fbSAndrew F. Davis int ret; 211c02a81fbSAndrew F. Davis 212c02a81fbSAndrew F. Davis if (!exp_info->name || !strcmp(exp_info->name, "")) { 213c02a81fbSAndrew F. Davis pr_err("dma_heap: Cannot add heap without a name\n"); 214c02a81fbSAndrew F. Davis return ERR_PTR(-EINVAL); 215c02a81fbSAndrew F. Davis } 216c02a81fbSAndrew F. Davis 217c02a81fbSAndrew F. Davis if (!exp_info->ops || !exp_info->ops->allocate) { 218c02a81fbSAndrew F. Davis pr_err("dma_heap: Cannot add heap with invalid ops struct\n"); 219c02a81fbSAndrew F. Davis return ERR_PTR(-EINVAL); 220c02a81fbSAndrew F. Davis } 221c02a81fbSAndrew F. Davis 222c02a81fbSAndrew F. Davis /* check the name is unique */ 223c02a81fbSAndrew F. Davis mutex_lock(&heap_list_lock); 224c02a81fbSAndrew F. Davis list_for_each_entry(h, &heap_list, list) { 225c02a81fbSAndrew F. Davis if (!strcmp(h->name, exp_info->name)) { 226c02a81fbSAndrew F. Davis mutex_unlock(&heap_list_lock); 227c02a81fbSAndrew F. Davis pr_err("dma_heap: Already registered heap named %s\n", 228c02a81fbSAndrew F. Davis exp_info->name); 229c02a81fbSAndrew F. Davis return ERR_PTR(-EINVAL); 230c02a81fbSAndrew F. Davis } 231c02a81fbSAndrew F. Davis } 232c02a81fbSAndrew F. Davis mutex_unlock(&heap_list_lock); 233c02a81fbSAndrew F. Davis 234c02a81fbSAndrew F. Davis heap = kzalloc(sizeof(*heap), GFP_KERNEL); 235c02a81fbSAndrew F. Davis if (!heap) 236c02a81fbSAndrew F. Davis return ERR_PTR(-ENOMEM); 237c02a81fbSAndrew F. Davis 238c02a81fbSAndrew F. Davis heap->name = exp_info->name; 239c02a81fbSAndrew F. Davis heap->ops = exp_info->ops; 240c02a81fbSAndrew F. Davis heap->priv = exp_info->priv; 241c02a81fbSAndrew F. Davis 242c02a81fbSAndrew F. Davis /* Find unused minor number */ 243c02a81fbSAndrew F. Davis ret = xa_alloc(&dma_heap_minors, &minor, heap, 244c02a81fbSAndrew F. Davis XA_LIMIT(0, NUM_HEAP_MINORS - 1), GFP_KERNEL); 245c02a81fbSAndrew F. Davis if (ret < 0) { 246c02a81fbSAndrew F. Davis pr_err("dma_heap: Unable to get minor number for heap\n"); 247c02a81fbSAndrew F. Davis err_ret = ERR_PTR(ret); 248c02a81fbSAndrew F. Davis goto err0; 249c02a81fbSAndrew F. Davis } 250c02a81fbSAndrew F. Davis 251c02a81fbSAndrew F. Davis /* Create device */ 252c02a81fbSAndrew F. Davis heap->heap_devt = MKDEV(MAJOR(dma_heap_devt), minor); 253c02a81fbSAndrew F. Davis 254c02a81fbSAndrew F. Davis cdev_init(&heap->heap_cdev, &dma_heap_fops); 255c02a81fbSAndrew F. Davis ret = cdev_add(&heap->heap_cdev, heap->heap_devt, 1); 256c02a81fbSAndrew F. Davis if (ret < 0) { 257c02a81fbSAndrew F. Davis pr_err("dma_heap: Unable to add char device\n"); 258c02a81fbSAndrew F. Davis err_ret = ERR_PTR(ret); 259c02a81fbSAndrew F. Davis goto err1; 260c02a81fbSAndrew F. Davis } 261c02a81fbSAndrew F. Davis 262c02a81fbSAndrew F. Davis dev_ret = device_create(dma_heap_class, 263c02a81fbSAndrew F. Davis NULL, 264c02a81fbSAndrew F. Davis heap->heap_devt, 265c02a81fbSAndrew F. Davis NULL, 266c02a81fbSAndrew F. Davis heap->name); 267c02a81fbSAndrew F. Davis if (IS_ERR(dev_ret)) { 268c02a81fbSAndrew F. Davis pr_err("dma_heap: Unable to create device\n"); 269c02a81fbSAndrew F. Davis err_ret = ERR_CAST(dev_ret); 270c02a81fbSAndrew F. Davis goto err2; 271c02a81fbSAndrew F. Davis } 272c02a81fbSAndrew F. Davis /* Add heap to the list */ 273c02a81fbSAndrew F. Davis mutex_lock(&heap_list_lock); 274c02a81fbSAndrew F. Davis list_add(&heap->list, &heap_list); 275c02a81fbSAndrew F. Davis mutex_unlock(&heap_list_lock); 276c02a81fbSAndrew F. Davis 277c02a81fbSAndrew F. Davis return heap; 278c02a81fbSAndrew F. Davis 279c02a81fbSAndrew F. Davis err2: 280c02a81fbSAndrew F. Davis cdev_del(&heap->heap_cdev); 281c02a81fbSAndrew F. Davis err1: 282c02a81fbSAndrew F. Davis xa_erase(&dma_heap_minors, minor); 283c02a81fbSAndrew F. Davis err0: 284c02a81fbSAndrew F. Davis kfree(heap); 285c02a81fbSAndrew F. Davis return err_ret; 286c02a81fbSAndrew F. Davis } 287c02a81fbSAndrew F. Davis 288c02a81fbSAndrew F. Davis static char *dma_heap_devnode(struct device *dev, umode_t *mode) 289c02a81fbSAndrew F. Davis { 290c02a81fbSAndrew F. Davis return kasprintf(GFP_KERNEL, "dma_heap/%s", dev_name(dev)); 291c02a81fbSAndrew F. Davis } 292c02a81fbSAndrew F. Davis 293c02a81fbSAndrew F. Davis static int dma_heap_init(void) 294c02a81fbSAndrew F. Davis { 295c02a81fbSAndrew F. Davis int ret; 296c02a81fbSAndrew F. Davis 297c02a81fbSAndrew F. Davis ret = alloc_chrdev_region(&dma_heap_devt, 0, NUM_HEAP_MINORS, DEVNAME); 298c02a81fbSAndrew F. Davis if (ret) 299c02a81fbSAndrew F. Davis return ret; 300c02a81fbSAndrew F. Davis 301c02a81fbSAndrew F. Davis dma_heap_class = class_create(THIS_MODULE, DEVNAME); 302c02a81fbSAndrew F. Davis if (IS_ERR(dma_heap_class)) { 303c02a81fbSAndrew F. Davis unregister_chrdev_region(dma_heap_devt, NUM_HEAP_MINORS); 304c02a81fbSAndrew F. Davis return PTR_ERR(dma_heap_class); 305c02a81fbSAndrew F. Davis } 306c02a81fbSAndrew F. Davis dma_heap_class->devnode = dma_heap_devnode; 307c02a81fbSAndrew F. Davis 308c02a81fbSAndrew F. Davis return 0; 309c02a81fbSAndrew F. Davis } 310c02a81fbSAndrew F. Davis subsys_initcall(dma_heap_init); 311