/* * 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 . */ #include #include #include #include #include #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 */