kernel_samsung_a53x/drivers/dma-buf/heaps/samsung/dma-buf-container.c
2024-06-15 16:02:09 -03:00

473 lines
11 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Dma buf container support
*
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
*/
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/dma-buf.h>
#include <linux/miscdevice.h>
#include <linux/dma-buf-container.h>
#include <linux/module.h>
#include "dma-buf-container.h"
#define MAX_BUFCON_BUFS 64
#define MAX_BUFCON_SRC_BUFS (MAX_BUFCON_BUFS - 1)
static int dmabuf_container_get_user_data(unsigned int cmd, void __user *arg,
int fds[])
{
int __user *ufds;
int count, ret;
#ifdef CONFIG_COMPAT
if (cmd == DMABUF_CONTAINER_COMPAT_IOCTL_MERGE) {
struct compat_dma_buf_merge __user *udata = arg;
compat_uptr_t compat_ufds;
ret = get_user(compat_ufds, &udata->dma_bufs);
ret |= get_user(count, &udata->count);
ufds = compat_ptr(compat_ufds);
} else
#endif
{
struct dma_buf_merge __user *udata = arg;
ret = get_user(ufds, &udata->dma_bufs);
ret |= get_user(count, &udata->count);
}
if (ret) {
pr_err("%s: failed to read data from user\n", __func__);
return -EFAULT;
}
if ((count < 1) || (count > MAX_BUFCON_SRC_BUFS)) {
pr_err("%s: invalid buffer count %u\n", __func__, count);
return -EINVAL;
}
if (copy_from_user(fds, ufds, sizeof(fds[0]) * count)) {
pr_err("%s: failed to read %u dma_bufs from user\n",
__func__, count);
return -EFAULT;
}
return count;
}
static int dmabuf_container_put_user_data(unsigned int cmd, void __user *arg,
struct dma_buf *merged)
{
int fd = get_unused_fd_flags(O_CLOEXEC);
int ret;
if (fd < 0) {
pr_err("%s: failed to get new fd\n", __func__);
return fd;
}
#ifdef CONFIG_COMPAT
if (cmd == DMABUF_CONTAINER_COMPAT_IOCTL_MERGE) {
struct compat_dma_buf_merge __user *udata = arg;
ret = put_user(fd, &udata->dmabuf_container);
} else
#endif
{
struct dma_buf_merge __user *udata = arg;
ret = put_user(fd, &udata->dmabuf_container);
}
if (ret) {
pr_err("%s: failed to store dmabuf_container fd to user\n",
__func__);
put_unused_fd(fd);
return ret;
}
fd_install(fd, merged->file);
return 0;
}
/*
* struct dma_buf_container - container description
* @table: dummy sg_table for container
* @count: the number of the buffers
* @dmabuf_mask: bit-mask of dma-bufs in @dmabufs.
* @dmabuf_mask is 0(unmasked) on creation of a dma-buf container.
* @dmabufs: dmabuf array representing each buffers
*/
struct dma_buf_container {
struct sg_table table;
int count;
u64 dmabuf_mask;
struct dma_buf *dmabufs[0];
};
static void dmabuf_container_put_dmabuf(struct dma_buf_container *container)
{
int i;
for (i = 0; i < container->count; i++)
dma_buf_put(container->dmabufs[i]);
}
static void dmabuf_container_dma_buf_release(struct dma_buf *dmabuf)
{
dmabuf_container_put_dmabuf(dmabuf->priv);
kfree(dmabuf->priv);
}
static struct sg_table *dmabuf_container_map_dma_buf(
struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct dma_buf_container *container = attachment->dmabuf->priv;
return &container->table;
}
static void dmabuf_container_unmap_dma_buf(struct dma_buf_attachment *attach,
struct sg_table *table,
enum dma_data_direction direction)
{
}
static int dmabuf_container_mmap(struct dma_buf *dmabuf,
struct vm_area_struct *vma)
{
pr_err("%s: dmabuf container does not support mmap\n", __func__);
return -EACCES;
}
static struct dma_buf_ops dmabuf_container_dma_buf_ops = {
.map_dma_buf = dmabuf_container_map_dma_buf,
.unmap_dma_buf = dmabuf_container_unmap_dma_buf,
.release = dmabuf_container_dma_buf_release,
.mmap = dmabuf_container_mmap,
};
static bool is_dmabuf_container(struct dma_buf *dmabuf)
{
return dmabuf->ops == &dmabuf_container_dma_buf_ops;
}
static struct dma_buf_container *get_container(struct dma_buf *dmabuf)
{
return dmabuf->priv;
}
static int get_dma_buf_count(struct dma_buf *dmabuf)
{
return is_dmabuf_container(dmabuf) ? get_container(dmabuf)->count : 1;
}
static struct dma_buf *__dmabuf_container_get_buffer(struct dma_buf *dmabuf,
int index)
{
struct dma_buf *out = is_dmabuf_container(dmabuf)
? get_container(dmabuf)->dmabufs[index] : dmabuf;
get_dma_buf(out);
return out;
}
static struct dma_buf *dmabuf_container_export(struct dma_buf_container *bufcon)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
unsigned long size = 0;
int i;
for (i = 0; i < bufcon->count; i++)
size += bufcon->dmabufs[i]->size;
exp_info.ops = &dmabuf_container_dma_buf_ops;
exp_info.size = size;
exp_info.flags = O_RDWR;
exp_info.priv = bufcon;
return dma_buf_export(&exp_info);
}
static struct dma_buf *create_dmabuf_container(struct dma_buf *bufs[],
int count)
{
struct dma_buf_container *container;
struct dma_buf *merged;
int total = 0;
int i, j, nelem = 0;
for (i = 0; i < count; i++)
total += get_dma_buf_count(bufs[i]);
if (total > MAX_BUFCON_BUFS) {
pr_err("%s: too many (%u) dmabuf merge request\n",
__func__, total);
return ERR_PTR(-EINVAL);
}
container = kzalloc(sizeof(*container) +
sizeof(container->dmabufs[0]) * total, GFP_KERNEL);
if (!container)
return ERR_PTR(-ENOMEM);
for (i = 0; i < count; i++)
for (j = 0; j < get_dma_buf_count(bufs[i]); j++)
container->dmabufs[nelem++] =
__dmabuf_container_get_buffer(bufs[i], j);
container->count = nelem;
merged = dmabuf_container_export(container);
if (IS_ERR(merged)) {
pr_err("%s: failed to export dmabuf container.\n", __func__);
dmabuf_container_put_dmabuf(container);
kfree(container);
}
return merged;
}
static struct dma_buf *dmabuf_container_create(int fds[], int count)
{
struct dma_buf *bufs[MAX_BUFCON_SRC_BUFS];
struct dma_buf *merged;
int i;
for (i = 0; i < count; i++) {
bufs[i] = dma_buf_get(fds[i]);
if (IS_ERR(bufs[i])) {
merged = bufs[i];
pr_err("%s: failed to get dmabuf of fd %d @ %u/%u\n",
__func__, fds[i], i, count);
goto err_get;
}
}
merged = create_dmabuf_container(bufs, count);
/*
* reference count of dma_bufs (file->f_count) in bufs[] are increased
* again in create_dmabuf_container(). So they should be decremented
* before return.
*/
err_get:
while (i-- > 0)
dma_buf_put(bufs[i]);
return merged;
}
long dma_buf_merge_ioctl(unsigned int cmd, unsigned long arg)
{
int fds[MAX_BUFCON_SRC_BUFS];
int count;
struct dma_buf *merged;
long ret;
count = dmabuf_container_get_user_data(cmd, (void __user *)arg, fds);
if (count < 0)
return count;
merged = dmabuf_container_create(fds, count);
if (IS_ERR(merged))
return PTR_ERR(merged);
ret = dmabuf_container_put_user_data(cmd, (void __user *)arg, merged);
if (ret)
dma_buf_put(merged);
return ret;
}
int dmabuf_container_set_mask_user(unsigned long arg)
{
struct dma_buf_mask __user *udata = (void __user *)arg;
struct dma_buf *dmabuf;
int container, ret;
u32 mask_array[2];
u64 mask;
ret = get_user(container, &udata->dmabuf_container);
ret |= copy_from_user(mask_array, udata->mask, sizeof(mask_array));
if (ret) {
pr_err("%s: failed to data from user\n", __func__);
return -EFAULT;
}
dmabuf = dma_buf_get(container);
if (IS_ERR(dmabuf)) {
pr_err("%s: failed to get dmabuf %d\n", __func__, container);
return PTR_ERR(dmabuf);
}
mask = ((u64)mask_array[1] << 32) | mask_array[0];
ret = dmabuf_container_set_mask(dmabuf, mask);
dma_buf_put(dmabuf);
return ret;
}
int dmabuf_container_get_mask_user(unsigned long arg)
{
struct dma_buf_mask __user *udata = (void __user *)arg;
struct dma_buf *dmabuf;
int container, ret;
u32 mask_array[2];
u64 mask;
ret = get_user(container, &udata->dmabuf_container);
if (ret) {
pr_err("%s: failed to read data from user\n", __func__);
return -EFAULT;
}
dmabuf = dma_buf_get(container);
if (IS_ERR(dmabuf)) {
pr_err("%s: failed to get dmabuf %d\n", __func__, container);
return PTR_ERR(dmabuf);
}
ret = dmabuf_container_get_mask(dmabuf, &mask);
if (ret)
goto err;
mask_array[0] = (mask << 32) >> 32;
mask_array[1] = mask >> 32;
ret = copy_to_user(udata->mask, mask_array, sizeof(mask_array));
if (ret)
pr_err("%s: failed to write mask to user\n", __func__);
err:
dma_buf_put(dmabuf);
return ret;
}
int dmabuf_container_set_mask(struct dma_buf *dmabuf, u64 mask)
{
struct dma_buf_container *container;
if (!is_dmabuf_container(dmabuf)) {
pr_err("%s: given dmabuf is not dma-buf container\n", __func__);
return -EINVAL;
}
container = get_container(dmabuf);
if (mask & ~((1 << container->count) - 1)) {
pr_err("%s: invalid mask %#x for %u buffers\n",
__func__, mask, container->count);
return -EINVAL;
}
get_container(dmabuf)->dmabuf_mask = mask;
return 0;
}
EXPORT_SYMBOL_GPL(dmabuf_container_set_mask);
int dmabuf_container_get_mask(struct dma_buf *dmabuf, u64 *mask)
{
if (!is_dmabuf_container(dmabuf)) {
pr_err("%s: given dmabuf is not dma-buf container\n", __func__);
return -EINVAL;
}
*mask = get_container(dmabuf)->dmabuf_mask;
return 0;
}
EXPORT_SYMBOL_GPL(dmabuf_container_get_mask);
int dmabuf_container_get_count(struct dma_buf *dmabuf)
{
if (!is_dmabuf_container(dmabuf))
return -EINVAL;
return get_container(dmabuf)->count;
}
EXPORT_SYMBOL_GPL(dmabuf_container_get_count);
struct dma_buf *dmabuf_container_get_buffer(struct dma_buf *dmabuf, int index)
{
struct dma_buf_container *container = get_container(dmabuf);
if (!is_dmabuf_container(dmabuf))
return NULL;
if (WARN_ON(index >= container->count))
return NULL;
get_dma_buf(container->dmabufs[index]);
return container->dmabufs[index];
}
EXPORT_SYMBOL_GPL(dmabuf_container_get_buffer);
struct dma_buf *dma_buf_get_any(int fd)
{
struct dma_buf *dmabuf = dma_buf_get(fd);
struct dma_buf *anybuf = __dmabuf_container_get_buffer(dmabuf, 0);
dma_buf_put(dmabuf);
return anybuf;
}
EXPORT_SYMBOL_GPL(dma_buf_get_any);
static long dmabuf_container_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
#ifdef CONFIG_COMPAT
case DMABUF_CONTAINER_COMPAT_IOCTL_MERGE:
#endif
case DMABUF_CONTAINER_IOCTL_MERGE:
return dma_buf_merge_ioctl(cmd, arg);
case DMABUF_CONTAINER_IOCTL_SET_MASK:
return dmabuf_container_set_mask_user(arg);
case DMABUF_CONTAINER_IOCTL_GET_MASK:
return dmabuf_container_get_mask_user(arg);
default:
return -ENOTTY;
}
}
static const struct file_operations dmabuf_container_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = dmabuf_container_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = dmabuf_container_ioctl,
#endif
};
static struct miscdevice dmabuf_container_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "dmabuf_container",
.fops = &dmabuf_container_fops,
};
static int __init dmabuf_container_init(void)
{
int ret = misc_register(&dmabuf_container_dev);
if (ret) {
pr_err("%s: failed to register %s\n", __func__,
dmabuf_container_dev.name);
return ret;
}
return 0;
}
module_init(dmabuf_container_init);
MODULE_LICENSE("GPL v2");