162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Framework for userspace DMA-BUF allocations 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2011 Google, Inc. 662306a36Sopenharmony_ci * Copyright (C) 2019 Linaro Ltd. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/cdev.h> 1062306a36Sopenharmony_ci#include <linux/debugfs.h> 1162306a36Sopenharmony_ci#include <linux/device.h> 1262306a36Sopenharmony_ci#include <linux/dma-buf.h> 1362306a36Sopenharmony_ci#include <linux/err.h> 1462306a36Sopenharmony_ci#include <linux/xarray.h> 1562306a36Sopenharmony_ci#include <linux/list.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/nospec.h> 1862306a36Sopenharmony_ci#include <linux/uaccess.h> 1962306a36Sopenharmony_ci#include <linux/syscalls.h> 2062306a36Sopenharmony_ci#include <linux/dma-heap.h> 2162306a36Sopenharmony_ci#include <uapi/linux/dma-heap.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define DEVNAME "dma_heap" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define NUM_HEAP_MINORS 128 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/** 2862306a36Sopenharmony_ci * struct dma_heap - represents a dmabuf heap in the system 2962306a36Sopenharmony_ci * @name: used for debugging/device-node name 3062306a36Sopenharmony_ci * @ops: ops struct for this heap 3162306a36Sopenharmony_ci * @heap_devt heap device node 3262306a36Sopenharmony_ci * @list list head connecting to list of heaps 3362306a36Sopenharmony_ci * @heap_cdev heap char device 3462306a36Sopenharmony_ci * 3562306a36Sopenharmony_ci * Represents a heap of memory from which buffers can be made. 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_cistruct dma_heap { 3862306a36Sopenharmony_ci const char *name; 3962306a36Sopenharmony_ci const struct dma_heap_ops *ops; 4062306a36Sopenharmony_ci void *priv; 4162306a36Sopenharmony_ci dev_t heap_devt; 4262306a36Sopenharmony_ci struct list_head list; 4362306a36Sopenharmony_ci struct cdev heap_cdev; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic LIST_HEAD(heap_list); 4762306a36Sopenharmony_cistatic DEFINE_MUTEX(heap_list_lock); 4862306a36Sopenharmony_cistatic dev_t dma_heap_devt; 4962306a36Sopenharmony_cistatic struct class *dma_heap_class; 5062306a36Sopenharmony_cistatic DEFINE_XARRAY_ALLOC(dma_heap_minors); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, 5362306a36Sopenharmony_ci unsigned int fd_flags, 5462306a36Sopenharmony_ci unsigned int heap_flags) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci struct dma_buf *dmabuf; 5762306a36Sopenharmony_ci int fd; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* 6062306a36Sopenharmony_ci * Allocations from all heaps have to begin 6162306a36Sopenharmony_ci * and end on page boundaries. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci len = PAGE_ALIGN(len); 6462306a36Sopenharmony_ci if (!len) 6562306a36Sopenharmony_ci return -EINVAL; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci dmabuf = heap->ops->allocate(heap, len, fd_flags, heap_flags); 6862306a36Sopenharmony_ci if (IS_ERR(dmabuf)) 6962306a36Sopenharmony_ci return PTR_ERR(dmabuf); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci fd = dma_buf_fd(dmabuf, fd_flags); 7262306a36Sopenharmony_ci if (fd < 0) { 7362306a36Sopenharmony_ci dma_buf_put(dmabuf); 7462306a36Sopenharmony_ci /* just return, as put will call release and that will free */ 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci return fd; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int dma_heap_open(struct inode *inode, struct file *file) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct dma_heap *heap; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci heap = xa_load(&dma_heap_minors, iminor(inode)); 8462306a36Sopenharmony_ci if (!heap) { 8562306a36Sopenharmony_ci pr_err("dma_heap: minor %d unknown.\n", iminor(inode)); 8662306a36Sopenharmony_ci return -ENODEV; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* instance data as context */ 9062306a36Sopenharmony_ci file->private_data = heap; 9162306a36Sopenharmony_ci nonseekable_open(inode, file); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic long dma_heap_ioctl_allocate(struct file *file, void *data) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct dma_heap_allocation_data *heap_allocation = data; 9962306a36Sopenharmony_ci struct dma_heap *heap = file->private_data; 10062306a36Sopenharmony_ci int fd; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci if (heap_allocation->fd) 10362306a36Sopenharmony_ci return -EINVAL; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS) 10662306a36Sopenharmony_ci return -EINVAL; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS) 10962306a36Sopenharmony_ci return -EINVAL; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci fd = dma_heap_buffer_alloc(heap, heap_allocation->len, 11262306a36Sopenharmony_ci heap_allocation->fd_flags, 11362306a36Sopenharmony_ci heap_allocation->heap_flags); 11462306a36Sopenharmony_ci if (fd < 0) 11562306a36Sopenharmony_ci return fd; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci heap_allocation->fd = fd; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci return 0; 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic unsigned int dma_heap_ioctl_cmds[] = { 12362306a36Sopenharmony_ci DMA_HEAP_IOCTL_ALLOC, 12462306a36Sopenharmony_ci}; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic long dma_heap_ioctl(struct file *file, unsigned int ucmd, 12762306a36Sopenharmony_ci unsigned long arg) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci char stack_kdata[128]; 13062306a36Sopenharmony_ci char *kdata = stack_kdata; 13162306a36Sopenharmony_ci unsigned int kcmd; 13262306a36Sopenharmony_ci unsigned int in_size, out_size, drv_size, ksize; 13362306a36Sopenharmony_ci int nr = _IOC_NR(ucmd); 13462306a36Sopenharmony_ci int ret = 0; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds)) 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci nr = array_index_nospec(nr, ARRAY_SIZE(dma_heap_ioctl_cmds)); 14062306a36Sopenharmony_ci /* Get the kernel ioctl cmd that matches */ 14162306a36Sopenharmony_ci kcmd = dma_heap_ioctl_cmds[nr]; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci /* Figure out the delta between user cmd size and kernel cmd size */ 14462306a36Sopenharmony_ci drv_size = _IOC_SIZE(kcmd); 14562306a36Sopenharmony_ci out_size = _IOC_SIZE(ucmd); 14662306a36Sopenharmony_ci in_size = out_size; 14762306a36Sopenharmony_ci if ((ucmd & kcmd & IOC_IN) == 0) 14862306a36Sopenharmony_ci in_size = 0; 14962306a36Sopenharmony_ci if ((ucmd & kcmd & IOC_OUT) == 0) 15062306a36Sopenharmony_ci out_size = 0; 15162306a36Sopenharmony_ci ksize = max(max(in_size, out_size), drv_size); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci /* If necessary, allocate buffer for ioctl argument */ 15462306a36Sopenharmony_ci if (ksize > sizeof(stack_kdata)) { 15562306a36Sopenharmony_ci kdata = kmalloc(ksize, GFP_KERNEL); 15662306a36Sopenharmony_ci if (!kdata) 15762306a36Sopenharmony_ci return -ENOMEM; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) { 16162306a36Sopenharmony_ci ret = -EFAULT; 16262306a36Sopenharmony_ci goto err; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* zero out any difference between the kernel/user structure size */ 16662306a36Sopenharmony_ci if (ksize > in_size) 16762306a36Sopenharmony_ci memset(kdata + in_size, 0, ksize - in_size); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci switch (kcmd) { 17062306a36Sopenharmony_ci case DMA_HEAP_IOCTL_ALLOC: 17162306a36Sopenharmony_ci ret = dma_heap_ioctl_allocate(file, kdata); 17262306a36Sopenharmony_ci break; 17362306a36Sopenharmony_ci default: 17462306a36Sopenharmony_ci ret = -ENOTTY; 17562306a36Sopenharmony_ci goto err; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (copy_to_user((void __user *)arg, kdata, out_size) != 0) 17962306a36Sopenharmony_ci ret = -EFAULT; 18062306a36Sopenharmony_cierr: 18162306a36Sopenharmony_ci if (kdata != stack_kdata) 18262306a36Sopenharmony_ci kfree(kdata); 18362306a36Sopenharmony_ci return ret; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic const struct file_operations dma_heap_fops = { 18762306a36Sopenharmony_ci .owner = THIS_MODULE, 18862306a36Sopenharmony_ci .open = dma_heap_open, 18962306a36Sopenharmony_ci .unlocked_ioctl = dma_heap_ioctl, 19062306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 19162306a36Sopenharmony_ci .compat_ioctl = dma_heap_ioctl, 19262306a36Sopenharmony_ci#endif 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/** 19662306a36Sopenharmony_ci * dma_heap_get_drvdata() - get per-subdriver data for the heap 19762306a36Sopenharmony_ci * @heap: DMA-Heap to retrieve private data for 19862306a36Sopenharmony_ci * 19962306a36Sopenharmony_ci * Returns: 20062306a36Sopenharmony_ci * The per-subdriver data for the heap. 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_civoid *dma_heap_get_drvdata(struct dma_heap *heap) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci return heap->priv; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci/** 20862306a36Sopenharmony_ci * dma_heap_get_name() - get heap name 20962306a36Sopenharmony_ci * @heap: DMA-Heap to retrieve private data for 21062306a36Sopenharmony_ci * 21162306a36Sopenharmony_ci * Returns: 21262306a36Sopenharmony_ci * The char* for the heap name. 21362306a36Sopenharmony_ci */ 21462306a36Sopenharmony_ciconst char *dma_heap_get_name(struct dma_heap *heap) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci return heap->name; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistruct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci struct dma_heap *heap, *h, *err_ret; 22262306a36Sopenharmony_ci struct device *dev_ret; 22362306a36Sopenharmony_ci unsigned int minor; 22462306a36Sopenharmony_ci int ret; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (!exp_info->name || !strcmp(exp_info->name, "")) { 22762306a36Sopenharmony_ci pr_err("dma_heap: Cannot add heap without a name\n"); 22862306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci if (!exp_info->ops || !exp_info->ops->allocate) { 23262306a36Sopenharmony_ci pr_err("dma_heap: Cannot add heap with invalid ops struct\n"); 23362306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci heap = kzalloc(sizeof(*heap), GFP_KERNEL); 23762306a36Sopenharmony_ci if (!heap) 23862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci heap->name = exp_info->name; 24162306a36Sopenharmony_ci heap->ops = exp_info->ops; 24262306a36Sopenharmony_ci heap->priv = exp_info->priv; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* Find unused minor number */ 24562306a36Sopenharmony_ci ret = xa_alloc(&dma_heap_minors, &minor, heap, 24662306a36Sopenharmony_ci XA_LIMIT(0, NUM_HEAP_MINORS - 1), GFP_KERNEL); 24762306a36Sopenharmony_ci if (ret < 0) { 24862306a36Sopenharmony_ci pr_err("dma_heap: Unable to get minor number for heap\n"); 24962306a36Sopenharmony_ci err_ret = ERR_PTR(ret); 25062306a36Sopenharmony_ci goto err0; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci /* Create device */ 25462306a36Sopenharmony_ci heap->heap_devt = MKDEV(MAJOR(dma_heap_devt), minor); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci cdev_init(&heap->heap_cdev, &dma_heap_fops); 25762306a36Sopenharmony_ci ret = cdev_add(&heap->heap_cdev, heap->heap_devt, 1); 25862306a36Sopenharmony_ci if (ret < 0) { 25962306a36Sopenharmony_ci pr_err("dma_heap: Unable to add char device\n"); 26062306a36Sopenharmony_ci err_ret = ERR_PTR(ret); 26162306a36Sopenharmony_ci goto err1; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci dev_ret = device_create(dma_heap_class, 26562306a36Sopenharmony_ci NULL, 26662306a36Sopenharmony_ci heap->heap_devt, 26762306a36Sopenharmony_ci NULL, 26862306a36Sopenharmony_ci heap->name); 26962306a36Sopenharmony_ci if (IS_ERR(dev_ret)) { 27062306a36Sopenharmony_ci pr_err("dma_heap: Unable to create device\n"); 27162306a36Sopenharmony_ci err_ret = ERR_CAST(dev_ret); 27262306a36Sopenharmony_ci goto err2; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci mutex_lock(&heap_list_lock); 27662306a36Sopenharmony_ci /* check the name is unique */ 27762306a36Sopenharmony_ci list_for_each_entry(h, &heap_list, list) { 27862306a36Sopenharmony_ci if (!strcmp(h->name, exp_info->name)) { 27962306a36Sopenharmony_ci mutex_unlock(&heap_list_lock); 28062306a36Sopenharmony_ci pr_err("dma_heap: Already registered heap named %s\n", 28162306a36Sopenharmony_ci exp_info->name); 28262306a36Sopenharmony_ci err_ret = ERR_PTR(-EINVAL); 28362306a36Sopenharmony_ci goto err3; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci } 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* Add heap to the list */ 28862306a36Sopenharmony_ci list_add(&heap->list, &heap_list); 28962306a36Sopenharmony_ci mutex_unlock(&heap_list_lock); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return heap; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cierr3: 29462306a36Sopenharmony_ci device_destroy(dma_heap_class, heap->heap_devt); 29562306a36Sopenharmony_cierr2: 29662306a36Sopenharmony_ci cdev_del(&heap->heap_cdev); 29762306a36Sopenharmony_cierr1: 29862306a36Sopenharmony_ci xa_erase(&dma_heap_minors, minor); 29962306a36Sopenharmony_cierr0: 30062306a36Sopenharmony_ci kfree(heap); 30162306a36Sopenharmony_ci return err_ret; 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic char *dma_heap_devnode(const struct device *dev, umode_t *mode) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci return kasprintf(GFP_KERNEL, "dma_heap/%s", dev_name(dev)); 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic int dma_heap_init(void) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci int ret; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci ret = alloc_chrdev_region(&dma_heap_devt, 0, NUM_HEAP_MINORS, DEVNAME); 31462306a36Sopenharmony_ci if (ret) 31562306a36Sopenharmony_ci return ret; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci dma_heap_class = class_create(DEVNAME); 31862306a36Sopenharmony_ci if (IS_ERR(dma_heap_class)) { 31962306a36Sopenharmony_ci unregister_chrdev_region(dma_heap_devt, NUM_HEAP_MINORS); 32062306a36Sopenharmony_ci return PTR_ERR(dma_heap_class); 32162306a36Sopenharmony_ci } 32262306a36Sopenharmony_ci dma_heap_class->devnode = dma_heap_devnode; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_cisubsys_initcall(dma_heap_init); 327