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