18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Framework for userspace DMA-BUF allocations 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2011 Google, Inc. 68c2ecf20Sopenharmony_ci * Copyright (C) 2019 Linaro Ltd. 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/cdev.h> 108c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 118c2ecf20Sopenharmony_ci#include <linux/device.h> 128c2ecf20Sopenharmony_ci#include <linux/dma-buf.h> 138c2ecf20Sopenharmony_ci#include <linux/err.h> 148c2ecf20Sopenharmony_ci#include <linux/xarray.h> 158c2ecf20Sopenharmony_ci#include <linux/list.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/nospec.h> 188c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 198c2ecf20Sopenharmony_ci#include <linux/syscalls.h> 208c2ecf20Sopenharmony_ci#include <linux/dma-heap.h> 218c2ecf20Sopenharmony_ci#include <uapi/linux/dma-heap.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define DEVNAME "dma_heap" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define NUM_HEAP_MINORS 128 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/** 288c2ecf20Sopenharmony_ci * struct dma_heap - represents a dmabuf heap in the system 298c2ecf20Sopenharmony_ci * @name: used for debugging/device-node name 308c2ecf20Sopenharmony_ci * @ops: ops struct for this heap 318c2ecf20Sopenharmony_ci * @heap_devt heap device node 328c2ecf20Sopenharmony_ci * @list list head connecting to list of heaps 338c2ecf20Sopenharmony_ci * @heap_cdev heap char device 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * Represents a heap of memory from which buffers can be made. 368c2ecf20Sopenharmony_ci */ 378c2ecf20Sopenharmony_cistruct dma_heap { 388c2ecf20Sopenharmony_ci const char *name; 398c2ecf20Sopenharmony_ci const struct dma_heap_ops *ops; 408c2ecf20Sopenharmony_ci void *priv; 418c2ecf20Sopenharmony_ci dev_t heap_devt; 428c2ecf20Sopenharmony_ci struct list_head list; 438c2ecf20Sopenharmony_ci struct cdev heap_cdev; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic LIST_HEAD(heap_list); 478c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(heap_list_lock); 488c2ecf20Sopenharmony_cistatic dev_t dma_heap_devt; 498c2ecf20Sopenharmony_cistatic struct class *dma_heap_class; 508c2ecf20Sopenharmony_cistatic DEFINE_XARRAY_ALLOC(dma_heap_minors); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, 538c2ecf20Sopenharmony_ci unsigned int fd_flags, 548c2ecf20Sopenharmony_ci unsigned int heap_flags) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci /* 578c2ecf20Sopenharmony_ci * Allocations from all heaps have to begin 588c2ecf20Sopenharmony_ci * and end on page boundaries. 598c2ecf20Sopenharmony_ci */ 608c2ecf20Sopenharmony_ci len = PAGE_ALIGN(len); 618c2ecf20Sopenharmony_ci if (!len) 628c2ecf20Sopenharmony_ci return -EINVAL; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci return heap->ops->allocate(heap, len, fd_flags, heap_flags); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic int dma_heap_open(struct inode *inode, struct file *file) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci struct dma_heap *heap; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci heap = xa_load(&dma_heap_minors, iminor(inode)); 728c2ecf20Sopenharmony_ci if (!heap) { 738c2ecf20Sopenharmony_ci pr_err("dma_heap: minor %d unknown.\n", iminor(inode)); 748c2ecf20Sopenharmony_ci return -ENODEV; 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* instance data as context */ 788c2ecf20Sopenharmony_ci file->private_data = heap; 798c2ecf20Sopenharmony_ci nonseekable_open(inode, file); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return 0; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic long dma_heap_ioctl_allocate(struct file *file, void *data) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct dma_heap_allocation_data *heap_allocation = data; 878c2ecf20Sopenharmony_ci struct dma_heap *heap = file->private_data; 888c2ecf20Sopenharmony_ci int fd; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (heap_allocation->fd) 918c2ecf20Sopenharmony_ci return -EINVAL; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS) 948c2ecf20Sopenharmony_ci return -EINVAL; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS) 978c2ecf20Sopenharmony_ci return -EINVAL; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci fd = dma_heap_buffer_alloc(heap, heap_allocation->len, 1008c2ecf20Sopenharmony_ci heap_allocation->fd_flags, 1018c2ecf20Sopenharmony_ci heap_allocation->heap_flags); 1028c2ecf20Sopenharmony_ci if (fd < 0) 1038c2ecf20Sopenharmony_ci return fd; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci heap_allocation->fd = fd; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return 0; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic unsigned int dma_heap_ioctl_cmds[] = { 1118c2ecf20Sopenharmony_ci DMA_HEAP_IOCTL_ALLOC, 1128c2ecf20Sopenharmony_ci}; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic long dma_heap_ioctl(struct file *file, unsigned int ucmd, 1158c2ecf20Sopenharmony_ci unsigned long arg) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci char stack_kdata[128]; 1188c2ecf20Sopenharmony_ci char *kdata = stack_kdata; 1198c2ecf20Sopenharmony_ci unsigned int kcmd; 1208c2ecf20Sopenharmony_ci unsigned int in_size, out_size, drv_size, ksize; 1218c2ecf20Sopenharmony_ci int nr = _IOC_NR(ucmd); 1228c2ecf20Sopenharmony_ci int ret = 0; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds)) 1258c2ecf20Sopenharmony_ci return -EINVAL; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci nr = array_index_nospec(nr, ARRAY_SIZE(dma_heap_ioctl_cmds)); 1288c2ecf20Sopenharmony_ci /* Get the kernel ioctl cmd that matches */ 1298c2ecf20Sopenharmony_ci kcmd = dma_heap_ioctl_cmds[nr]; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* Figure out the delta between user cmd size and kernel cmd size */ 1328c2ecf20Sopenharmony_ci drv_size = _IOC_SIZE(kcmd); 1338c2ecf20Sopenharmony_ci out_size = _IOC_SIZE(ucmd); 1348c2ecf20Sopenharmony_ci in_size = out_size; 1358c2ecf20Sopenharmony_ci if ((ucmd & kcmd & IOC_IN) == 0) 1368c2ecf20Sopenharmony_ci in_size = 0; 1378c2ecf20Sopenharmony_ci if ((ucmd & kcmd & IOC_OUT) == 0) 1388c2ecf20Sopenharmony_ci out_size = 0; 1398c2ecf20Sopenharmony_ci ksize = max(max(in_size, out_size), drv_size); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci /* If necessary, allocate buffer for ioctl argument */ 1428c2ecf20Sopenharmony_ci if (ksize > sizeof(stack_kdata)) { 1438c2ecf20Sopenharmony_ci kdata = kmalloc(ksize, GFP_KERNEL); 1448c2ecf20Sopenharmony_ci if (!kdata) 1458c2ecf20Sopenharmony_ci return -ENOMEM; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) { 1498c2ecf20Sopenharmony_ci ret = -EFAULT; 1508c2ecf20Sopenharmony_ci goto err; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* zero out any difference between the kernel/user structure size */ 1548c2ecf20Sopenharmony_ci if (ksize > in_size) 1558c2ecf20Sopenharmony_ci memset(kdata + in_size, 0, ksize - in_size); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci switch (kcmd) { 1588c2ecf20Sopenharmony_ci case DMA_HEAP_IOCTL_ALLOC: 1598c2ecf20Sopenharmony_ci ret = dma_heap_ioctl_allocate(file, kdata); 1608c2ecf20Sopenharmony_ci break; 1618c2ecf20Sopenharmony_ci default: 1628c2ecf20Sopenharmony_ci ret = -ENOTTY; 1638c2ecf20Sopenharmony_ci goto err; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci if (copy_to_user((void __user *)arg, kdata, out_size) != 0) 1678c2ecf20Sopenharmony_ci ret = -EFAULT; 1688c2ecf20Sopenharmony_cierr: 1698c2ecf20Sopenharmony_ci if (kdata != stack_kdata) 1708c2ecf20Sopenharmony_ci kfree(kdata); 1718c2ecf20Sopenharmony_ci return ret; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic const struct file_operations dma_heap_fops = { 1758c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1768c2ecf20Sopenharmony_ci .open = dma_heap_open, 1778c2ecf20Sopenharmony_ci .unlocked_ioctl = dma_heap_ioctl, 1788c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT 1798c2ecf20Sopenharmony_ci .compat_ioctl = dma_heap_ioctl, 1808c2ecf20Sopenharmony_ci#endif 1818c2ecf20Sopenharmony_ci}; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci/** 1848c2ecf20Sopenharmony_ci * dma_heap_get_drvdata() - get per-subdriver data for the heap 1858c2ecf20Sopenharmony_ci * @heap: DMA-Heap to retrieve private data for 1868c2ecf20Sopenharmony_ci * 1878c2ecf20Sopenharmony_ci * Returns: 1888c2ecf20Sopenharmony_ci * The per-subdriver data for the heap. 1898c2ecf20Sopenharmony_ci */ 1908c2ecf20Sopenharmony_civoid *dma_heap_get_drvdata(struct dma_heap *heap) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci return heap->priv; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci/** 1968c2ecf20Sopenharmony_ci * dma_heap_get_name() - get heap name 1978c2ecf20Sopenharmony_ci * @heap: DMA-Heap to retrieve private data for 1988c2ecf20Sopenharmony_ci * 1998c2ecf20Sopenharmony_ci * Returns: 2008c2ecf20Sopenharmony_ci * The char* for the heap name. 2018c2ecf20Sopenharmony_ci */ 2028c2ecf20Sopenharmony_ciconst char *dma_heap_get_name(struct dma_heap *heap) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci return heap->name; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistruct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci struct dma_heap *heap, *h, *err_ret; 2108c2ecf20Sopenharmony_ci struct device *dev_ret; 2118c2ecf20Sopenharmony_ci unsigned int minor; 2128c2ecf20Sopenharmony_ci int ret; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci if (!exp_info->name || !strcmp(exp_info->name, "")) { 2158c2ecf20Sopenharmony_ci pr_err("dma_heap: Cannot add heap without a name\n"); 2168c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci if (!exp_info->ops || !exp_info->ops->allocate) { 2208c2ecf20Sopenharmony_ci pr_err("dma_heap: Cannot add heap with invalid ops struct\n"); 2218c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci heap = kzalloc(sizeof(*heap), GFP_KERNEL); 2258c2ecf20Sopenharmony_ci if (!heap) 2268c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci heap->name = exp_info->name; 2298c2ecf20Sopenharmony_ci heap->ops = exp_info->ops; 2308c2ecf20Sopenharmony_ci heap->priv = exp_info->priv; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci /* Find unused minor number */ 2338c2ecf20Sopenharmony_ci ret = xa_alloc(&dma_heap_minors, &minor, heap, 2348c2ecf20Sopenharmony_ci XA_LIMIT(0, NUM_HEAP_MINORS - 1), GFP_KERNEL); 2358c2ecf20Sopenharmony_ci if (ret < 0) { 2368c2ecf20Sopenharmony_ci pr_err("dma_heap: Unable to get minor number for heap\n"); 2378c2ecf20Sopenharmony_ci err_ret = ERR_PTR(ret); 2388c2ecf20Sopenharmony_ci goto err0; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci /* Create device */ 2428c2ecf20Sopenharmony_ci heap->heap_devt = MKDEV(MAJOR(dma_heap_devt), minor); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci cdev_init(&heap->heap_cdev, &dma_heap_fops); 2458c2ecf20Sopenharmony_ci ret = cdev_add(&heap->heap_cdev, heap->heap_devt, 1); 2468c2ecf20Sopenharmony_ci if (ret < 0) { 2478c2ecf20Sopenharmony_ci pr_err("dma_heap: Unable to add char device\n"); 2488c2ecf20Sopenharmony_ci err_ret = ERR_PTR(ret); 2498c2ecf20Sopenharmony_ci goto err1; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci dev_ret = device_create(dma_heap_class, 2538c2ecf20Sopenharmony_ci NULL, 2548c2ecf20Sopenharmony_ci heap->heap_devt, 2558c2ecf20Sopenharmony_ci NULL, 2568c2ecf20Sopenharmony_ci heap->name); 2578c2ecf20Sopenharmony_ci if (IS_ERR(dev_ret)) { 2588c2ecf20Sopenharmony_ci pr_err("dma_heap: Unable to create device\n"); 2598c2ecf20Sopenharmony_ci err_ret = ERR_CAST(dev_ret); 2608c2ecf20Sopenharmony_ci goto err2; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci mutex_lock(&heap_list_lock); 2648c2ecf20Sopenharmony_ci /* check the name is unique */ 2658c2ecf20Sopenharmony_ci list_for_each_entry(h, &heap_list, list) { 2668c2ecf20Sopenharmony_ci if (!strcmp(h->name, exp_info->name)) { 2678c2ecf20Sopenharmony_ci mutex_unlock(&heap_list_lock); 2688c2ecf20Sopenharmony_ci pr_err("dma_heap: Already registered heap named %s\n", 2698c2ecf20Sopenharmony_ci exp_info->name); 2708c2ecf20Sopenharmony_ci err_ret = ERR_PTR(-EINVAL); 2718c2ecf20Sopenharmony_ci goto err3; 2728c2ecf20Sopenharmony_ci } 2738c2ecf20Sopenharmony_ci } 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* Add heap to the list */ 2768c2ecf20Sopenharmony_ci list_add(&heap->list, &heap_list); 2778c2ecf20Sopenharmony_ci mutex_unlock(&heap_list_lock); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return heap; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cierr3: 2828c2ecf20Sopenharmony_ci device_destroy(dma_heap_class, heap->heap_devt); 2838c2ecf20Sopenharmony_cierr2: 2848c2ecf20Sopenharmony_ci cdev_del(&heap->heap_cdev); 2858c2ecf20Sopenharmony_cierr1: 2868c2ecf20Sopenharmony_ci xa_erase(&dma_heap_minors, minor); 2878c2ecf20Sopenharmony_cierr0: 2888c2ecf20Sopenharmony_ci kfree(heap); 2898c2ecf20Sopenharmony_ci return err_ret; 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_cistatic char *dma_heap_devnode(struct device *dev, umode_t *mode) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci return kasprintf(GFP_KERNEL, "dma_heap/%s", dev_name(dev)); 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic int dma_heap_init(void) 2988c2ecf20Sopenharmony_ci{ 2998c2ecf20Sopenharmony_ci int ret; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci ret = alloc_chrdev_region(&dma_heap_devt, 0, NUM_HEAP_MINORS, DEVNAME); 3028c2ecf20Sopenharmony_ci if (ret) 3038c2ecf20Sopenharmony_ci return ret; 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci dma_heap_class = class_create(THIS_MODULE, DEVNAME); 3068c2ecf20Sopenharmony_ci if (IS_ERR(dma_heap_class)) { 3078c2ecf20Sopenharmony_ci unregister_chrdev_region(dma_heap_devt, NUM_HEAP_MINORS); 3088c2ecf20Sopenharmony_ci return PTR_ERR(dma_heap_class); 3098c2ecf20Sopenharmony_ci } 3108c2ecf20Sopenharmony_ci dma_heap_class->devnode = dma_heap_devnode; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci return 0; 3138c2ecf20Sopenharmony_ci} 3148c2ecf20Sopenharmony_cisubsys_initcall(dma_heap_init); 315