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