| /* |
| * Copyright(C) 2018 Samsung Electronics Co., Ltd. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published by |
| * the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/dma-buf.h> |
| #include <linux/miscdevice.h> |
| #include <linux/dma-buf-container.h> |
| |
| #include "dma-buf-container.h" |
| |
| #define MAX_BUFCON_BUFS 32 |
| #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 == DMA_BUF_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 == DMA_BUF_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; |
| u32 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 void *dmabuf_container_dma_buf_kmap(struct dma_buf *dmabuf, |
| unsigned long offset) |
| { |
| return NULL; |
| } |
| |
| 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, |
| .map_atomic = dmabuf_container_dma_buf_kmap, |
| .map = dmabuf_container_dma_buf_kmap, |
| .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 *base, |
| struct dma_buf *src[], int count) |
| { |
| struct dma_buf_container *container; |
| struct dma_buf *merged; |
| int total = 0; |
| int i, isrc; |
| int nelem; |
| |
| for (i = 0; i < count; i++) |
| total += get_dma_buf_count(src[i]); |
| total += get_dma_buf_count(base); |
| |
| 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); |
| |
| nelem = get_dma_buf_count(base); |
| for (i = 0; i < nelem; i++) |
| container->dmabufs[i] = __dmabuf_container_get_buffer(base, i); |
| |
| for (isrc = 0; isrc < count; isrc++) |
| for (i = 0; i < get_dma_buf_count(src[isrc]); i++) |
| container->dmabufs[nelem++] = |
| __dmabuf_container_get_buffer(src[isrc], i); |
| |
| 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(struct dma_buf *dmabuf, |
| int fds[], int count) |
| { |
| struct dma_buf *src[MAX_BUFCON_SRC_BUFS]; |
| struct dma_buf *merged; |
| int i; |
| |
| for (i = 0; i < count; i++) { |
| src[i] = dma_buf_get(fds[i]); |
| if (IS_ERR(src[i])) { |
| merged = src[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(dmabuf, src, count); |
| /* |
| * reference count of dma_bufs (file->f_count) in src[] are increased |
| * again in create_dmabuf_container(). So they should be decremented |
| * before return. |
| */ |
| err_get: |
| while (i-- > 0) |
| dma_buf_put(src[i]); |
| |
| return merged; |
| } |
| |
| long dma_buf_merge_ioctl(struct dma_buf *dmabuf, |
| 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(dmabuf, 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; |
| } |
| |
| return 0; |
| } |
| |
| int dmabuf_container_set_mask_user(struct dma_buf *dmabuf, unsigned long arg) |
| { |
| u32 mask; |
| |
| if (get_user(mask, (u32 __user *)arg)) { |
| pr_err("%s: failed to read mask from user\n", __func__); |
| return -EFAULT; |
| } |
| |
| return dmabuf_container_set_mask(dmabuf, mask); |
| } |
| |
| int dmabuf_container_get_mask_user(struct dma_buf *dmabuf, unsigned long arg) |
| { |
| u32 mask; |
| int ret; |
| |
| ret = dmabuf_container_get_mask(dmabuf, &mask); |
| if (ret < 0) |
| return ret; |
| |
| if (put_user(mask, (u32 __user *)arg)) { |
| pr_err("%s: failed to write mask to user\n", __func__); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| int dmabuf_container_set_mask(struct dma_buf *dmabuf, u32 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, u32 *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); |
| |
| #ifdef CONFIG_DMA_BUF_CONTAINER_TEST |
| |
| static int dmabuf_container_verify_one(struct dma_buf_container *container, |
| struct dma_buf *dmabuf, int idx) |
| { |
| struct dma_buf_container *subset; |
| int subidx; |
| |
| if (!is_dmabuf_container(dmabuf)) { |
| if (container->dmabufs[idx] == dmabuf) |
| return 1; |
| return -ENOENT; |
| } |
| |
| subset = get_container(dmabuf); |
| |
| for (subidx = 0; subidx < subset->count; subidx++) |
| if (subset->dmabufs[subidx] != container->dmabufs[idx++]) |
| return -ENOENT; |
| |
| return subset->count; |
| } |
| |
| static long dmabuf_container_verify(struct dma_buf_container *container, |
| struct dma_buf *dmabufs[], int count) |
| { |
| int idx = 0, i = 0; |
| |
| while ((i < count) && (idx < container->count)) { |
| int ret = dmabuf_container_verify_one(container, |
| dmabufs[i], idx); |
| |
| if (ret < 0) { |
| pr_err("%s: verify failed @ container[%d]/dmabuf[%d]\n", |
| __func__, idx, i); |
| return ret; |
| } |
| |
| idx += ret; |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| struct bufcon_test_data { |
| int *buf_fds; |
| __s32 num_fd; |
| __s32 bufcon_fd; |
| __u32 total_size; |
| __s32 reserved; |
| }; |
| |
| static long dmabuf_container_test(struct bufcon_test_data *data) |
| { |
| struct dma_buf *container = dma_buf_get(data->bufcon_fd); |
| struct dma_buf **dmabufs; |
| int i; |
| long ret = -EINVAL; |
| |
| if (IS_ERR(container)) { |
| pr_err("%s: fd %d is not a dmabuf\n", |
| __func__, data->bufcon_fd); |
| return PTR_ERR(container); |
| } |
| |
| if (!is_dmabuf_container(container)) { |
| pr_err("%s: fd %d is not a dmabuf container\n", |
| __func__, data->bufcon_fd); |
| goto err_bufcon; |
| } |
| |
| if (container->size != data->total_size) { |
| pr_err("%s: the size of dmabuf container %zu is not %u\n", |
| __func__, container->size, data->total_size); |
| goto err_bufcon; |
| } |
| |
| dmabufs = kmalloc_array(data->num_fd, sizeof(dmabufs[0]), GFP_KERNEL); |
| if (!dmabufs) { |
| ret = -ENOMEM; |
| goto err_bufcon; |
| } |
| |
| for (i = 0; i < data->num_fd; i++) { |
| dmabufs[i] = dma_buf_get(data->buf_fds[i]); |
| if (IS_ERR(dmabufs[i])) { |
| ret = PTR_ERR(dmabufs[i]); |
| goto err; |
| } |
| } |
| |
| ret = dmabuf_container_verify(container->priv, dmabufs, data->num_fd); |
| err: |
| while (i--) |
| dma_buf_put(dmabufs[i]); |
| kfree(dmabufs); |
| err_bufcon: |
| dma_buf_put(container); |
| |
| return ret; |
| } |
| |
| #define TEST_IOC_TEST _IOR('T', 0, struct bufcon_test_data) |
| |
| static long dmabuf_container_test_ioctl(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct bufcon_test_data __user *udata = (void __user *)arg; |
| struct bufcon_test_data data; |
| int __user *buf_fds; |
| long ret = 0; |
| |
| if (cmd != TEST_IOC_TEST) { |
| pr_err("%s: unknown cmd %#x\n", __func__, cmd); |
| return -ENOTTY; |
| } |
| |
| if (copy_from_user(&data, udata, sizeof(data))) { |
| pr_err("%s: failed to read user data\n", __func__); |
| return -EFAULT; |
| } |
| |
| if ((data.num_fd < 1) || (data.num_fd > MAX_BUFCON_BUFS)) { |
| pr_err("%s: invalid number of dma-buf fds %d\n", |
| __func__, data.num_fd); |
| return -EINVAL; |
| } |
| |
| buf_fds = data.buf_fds; |
| |
| data.buf_fds = kcalloc(data.num_fd, sizeof(*data.buf_fds), GFP_KERNEL); |
| if (!data.buf_fds) |
| return -ENOMEM; |
| |
| if (copy_from_user(data.buf_fds, buf_fds, |
| sizeof(data.buf_fds[0]) * data.num_fd)) { |
| pr_err("%s: failed to read fd list\n", __func__); |
| ret = -EFAULT; |
| goto err; |
| } |
| |
| ret = dmabuf_container_test(&data); |
| err: |
| kfree(data.buf_fds); |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| struct compat_bufcon_test_data { |
| compat_uptr_t buf_fds; |
| __s32 num_fd; |
| __s32 bufcon_fd; |
| __u32 total_size; |
| __s32 reserved; |
| }; |
| |
| #define COMPAT_TEST_IOC_TEST _IOR('T', 0, struct compat_bufcon_test_data) |
| |
| static long dmabuf_container_test_compat_ioctl(struct file *filp, |
| unsigned int cmd, |
| unsigned long arg) |
| { |
| struct compat_bufcon_test_data __user *udata = compat_ptr(arg); |
| struct bufcon_test_data data; |
| compat_uptr_t buf_fds; |
| long ret = 0; |
| |
| if (cmd != COMPAT_TEST_IOC_TEST) { |
| pr_err("%s: unknown cmd %#x\n", __func__, cmd); |
| return -ENOTTY; |
| } |
| |
| ret = get_user(buf_fds, &udata->buf_fds); |
| ret |= get_user(data.num_fd, &udata->num_fd); |
| ret |= get_user(data.bufcon_fd, &udata->bufcon_fd); |
| ret |= get_user(data.total_size, &udata->total_size); |
| if (ret) { |
| pr_err("%s: failed to read user data\n", __func__); |
| return -EFAULT; |
| } |
| |
| if ((data.num_fd < 1) || (data.num_fd > MAX_BUFCON_BUFS)) { |
| pr_err("%s: invalid number of dma-buf fds %d\n", |
| __func__, data.num_fd); |
| return -EINVAL; |
| } |
| |
| data.buf_fds = kcalloc(data.num_fd, sizeof(*data.buf_fds), GFP_KERNEL); |
| if (!data.buf_fds) |
| return -ENOMEM; |
| |
| if (copy_from_user(data.buf_fds, compat_ptr(buf_fds), |
| sizeof(data.buf_fds[0]) * data.num_fd)) { |
| pr_err("%s: failed to read fd list\n", __func__); |
| ret = -EFAULT; |
| goto err; |
| } |
| |
| ret = dmabuf_container_test(&data); |
| err: |
| kfree(data.buf_fds); |
| |
| return 0; |
| } |
| #endif |
| |
| static const struct file_operations dmabuf_container_test_fops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = dmabuf_container_test_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = dmabuf_container_test_compat_ioctl, |
| #endif |
| }; |
| |
| static struct miscdevice dmabuf_container_test_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "bufcon-test", |
| .fops = &dmabuf_container_test_fops, |
| }; |
| |
| static int __init dmabuf_container_test_init(void) |
| { |
| int ret = misc_register(&dmabuf_container_test_dev); |
| |
| if (ret) { |
| pr_err("%s: failed to register %s\n", __func__, |
| dmabuf_container_test_dev.name); |
| return ret; |
| } |
| |
| return 0; |
| } |
| device_initcall(dmabuf_container_test_init); |
| |
| #endif /* CONFIG_DMA_BUF_CONTAINER_TEST */ |