Merge branch 'work.mount0' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-block.git] / drivers / dma-buf / dma-buf.c
index 7c858020d14b8e91a1af439193873f3bd5434f88..f45bfb29ef960346ae122dc94c067286fcf8c7de 100644 (file)
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
  * Framework for buffer objects that can be shared across devices/subsystems.
  *
@@ -8,18 +9,6 @@
  * Arnd Bergmann <arnd@arndb.de>, Rob Clark <rob@ti.com> and
  * Daniel Vetter <daniel@ffwll.ch> for their support in creation and
  * refining of this idea.
- *
- * 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/fs.h>
 #include <linux/poll.h>
 #include <linux/reservation.h>
 #include <linux/mm.h>
+#include <linux/mount.h>
+#include <linux/pseudo_fs.h>
 
 #include <uapi/linux/dma-buf.h>
+#include <uapi/linux/magic.h>
 
 static inline int is_dma_buf_file(struct file *);
 
@@ -46,6 +38,45 @@ struct dma_buf_list {
 
 static struct dma_buf_list db_list;
 
+static char *dmabuffs_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+       struct dma_buf *dmabuf;
+       char name[DMA_BUF_NAME_LEN];
+       size_t ret = 0;
+
+       dmabuf = dentry->d_fsdata;
+       mutex_lock(&dmabuf->lock);
+       if (dmabuf->name)
+               ret = strlcpy(name, dmabuf->name, DMA_BUF_NAME_LEN);
+       mutex_unlock(&dmabuf->lock);
+
+       return dynamic_dname(dentry, buffer, buflen, "/%s:%s",
+                            dentry->d_name.name, ret > 0 ? name : "");
+}
+
+static const struct dentry_operations dma_buf_dentry_ops = {
+       .d_dname = dmabuffs_dname,
+};
+
+static struct vfsmount *dma_buf_mnt;
+
+static int dma_buf_fs_init_context(struct fs_context *fc)
+{
+       struct pseudo_fs_context *ctx;
+
+       ctx = init_pseudo(fc, DMA_BUF_MAGIC);
+       if (!ctx)
+               return -ENOMEM;
+       ctx->dops = &dma_buf_dentry_ops;
+       return 0;
+}
+
+static struct file_system_type dma_buf_fs_type = {
+       .name = "dmabuf",
+       .init_fs_context = dma_buf_fs_init_context,
+       .kill_sb = kill_anon_super,
+};
+
 static int dma_buf_release(struct inode *inode, struct file *file)
 {
        struct dma_buf *dmabuf;
@@ -90,6 +121,10 @@ static int dma_buf_mmap_internal(struct file *file, struct vm_area_struct *vma)
 
        dmabuf = file->private_data;
 
+       /* check if buffer supports mmap */
+       if (!dmabuf->ops->mmap)
+               return -EINVAL;
+
        /* check for overflowing the buffer's size */
        if (vma->vm_pgoff + vma_pages(vma) >
            dmabuf->size >> PAGE_SHIFT)
@@ -276,6 +311,43 @@ out:
        return events;
 }
 
+/**
+ * dma_buf_set_name - Set a name to a specific dma_buf to track the usage.
+ * The name of the dma-buf buffer can only be set when the dma-buf is not
+ * attached to any devices. It could theoritically support changing the
+ * name of the dma-buf if the same piece of memory is used for multiple
+ * purpose between different devices.
+ *
+ * @dmabuf [in]     dmabuf buffer that will be renamed.
+ * @buf:   [in]     A piece of userspace memory that contains the name of
+ *                  the dma-buf.
+ *
+ * Returns 0 on success. If the dma-buf buffer is already attached to
+ * devices, return -EBUSY.
+ *
+ */
+static long dma_buf_set_name(struct dma_buf *dmabuf, const char __user *buf)
+{
+       char *name = strndup_user(buf, DMA_BUF_NAME_LEN);
+       long ret = 0;
+
+       if (IS_ERR(name))
+               return PTR_ERR(name);
+
+       mutex_lock(&dmabuf->lock);
+       if (!list_empty(&dmabuf->attachments)) {
+               ret = -EBUSY;
+               kfree(name);
+               goto out_unlock;
+       }
+       kfree(dmabuf->name);
+       dmabuf->name = name;
+
+out_unlock:
+       mutex_unlock(&dmabuf->lock);
+       return ret;
+}
+
 static long dma_buf_ioctl(struct file *file,
                          unsigned int cmd, unsigned long arg)
 {
@@ -314,11 +386,29 @@ static long dma_buf_ioctl(struct file *file,
                        ret = dma_buf_begin_cpu_access(dmabuf, direction);
 
                return ret;
+
+       case DMA_BUF_SET_NAME:
+               return dma_buf_set_name(dmabuf, (const char __user *)arg);
+
        default:
                return -ENOTTY;
        }
 }
 
+static void dma_buf_show_fdinfo(struct seq_file *m, struct file *file)
+{
+       struct dma_buf *dmabuf = file->private_data;
+
+       seq_printf(m, "size:\t%zu\n", dmabuf->size);
+       /* Don't count the temporary reference taken inside procfs seq_show */
+       seq_printf(m, "count:\t%ld\n", file_count(dmabuf->file) - 1);
+       seq_printf(m, "exp_name:\t%s\n", dmabuf->exp_name);
+       mutex_lock(&dmabuf->lock);
+       if (dmabuf->name)
+               seq_printf(m, "name:\t%s\n", dmabuf->name);
+       mutex_unlock(&dmabuf->lock);
+}
+
 static const struct file_operations dma_buf_fops = {
        .release        = dma_buf_release,
        .mmap           = dma_buf_mmap_internal,
@@ -328,6 +418,7 @@ static const struct file_operations dma_buf_fops = {
 #ifdef CONFIG_COMPAT
        .compat_ioctl   = dma_buf_ioctl,
 #endif
+       .show_fdinfo    = dma_buf_show_fdinfo,
 };
 
 /*
@@ -338,6 +429,32 @@ static inline int is_dma_buf_file(struct file *file)
        return file->f_op == &dma_buf_fops;
 }
 
+static struct file *dma_buf_getfile(struct dma_buf *dmabuf, int flags)
+{
+       struct file *file;
+       struct inode *inode = alloc_anon_inode(dma_buf_mnt->mnt_sb);
+
+       if (IS_ERR(inode))
+               return ERR_CAST(inode);
+
+       inode->i_size = dmabuf->size;
+       inode_set_bytes(inode, dmabuf->size);
+
+       file = alloc_file_pseudo(inode, dma_buf_mnt, "dmabuf",
+                                flags, &dma_buf_fops);
+       if (IS_ERR(file))
+               goto err_alloc_file;
+       file->f_flags = flags & (O_ACCMODE | O_NONBLOCK);
+       file->private_data = dmabuf;
+       file->f_path.dentry->d_fsdata = dmabuf;
+
+       return file;
+
+err_alloc_file:
+       iput(inode);
+       return file;
+}
+
 /**
  * DOC: dma buf device access
  *
@@ -404,8 +521,7 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
                          || !exp_info->ops
                          || !exp_info->ops->map_dma_buf
                          || !exp_info->ops->unmap_dma_buf
-                         || !exp_info->ops->release
-                         || !exp_info->ops->mmap)) {
+                         || !exp_info->ops->release)) {
                return ERR_PTR(-EINVAL);
        }
 
@@ -433,8 +549,7 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
        }
        dmabuf->resv = resv;
 
-       file = anon_inode_getfile("dmabuf", &dma_buf_fops, dmabuf,
-                                       exp_info->flags);
+       file = dma_buf_getfile(dmabuf, exp_info->flags);
        if (IS_ERR(file)) {
                ret = PTR_ERR(file);
                goto err_dmabuf;
@@ -573,6 +688,7 @@ struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf,
        list_add(&attach->node, &dmabuf->attachments);
 
        mutex_unlock(&dmabuf->lock);
+
        return attach;
 
 err_attach:
@@ -595,6 +711,9 @@ void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach)
        if (WARN_ON(!dmabuf || !attach))
                return;
 
+       if (attach->sgt)
+               dmabuf->ops->unmap_dma_buf(attach, attach->sgt, attach->dir);
+
        mutex_lock(&dmabuf->lock);
        list_del(&attach->node);
        if (dmabuf->ops->detach)
@@ -630,10 +749,27 @@ struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach,
        if (WARN_ON(!attach || !attach->dmabuf))
                return ERR_PTR(-EINVAL);
 
+       if (attach->sgt) {
+               /*
+                * Two mappings with different directions for the same
+                * attachment are not allowed.
+                */
+               if (attach->dir != direction &&
+                   attach->dir != DMA_BIDIRECTIONAL)
+                       return ERR_PTR(-EBUSY);
+
+               return attach->sgt;
+       }
+
        sg_table = attach->dmabuf->ops->map_dma_buf(attach, direction);
        if (!sg_table)
                sg_table = ERR_PTR(-ENOMEM);
 
+       if (!IS_ERR(sg_table) && attach->dmabuf->ops->cache_sgt_mapping) {
+               attach->sgt = sg_table;
+               attach->dir = direction;
+       }
+
        return sg_table;
 }
 EXPORT_SYMBOL_GPL(dma_buf_map_attachment);
@@ -657,8 +793,10 @@ void dma_buf_unmap_attachment(struct dma_buf_attachment *attach,
        if (WARN_ON(!attach || !attach->dmabuf || !sg_table))
                return;
 
-       attach->dmabuf->ops->unmap_dma_buf(attach, sg_table,
-                                               direction);
+       if (attach->sgt == sg_table)
+               return;
+
+       attach->dmabuf->ops->unmap_dma_buf(attach, sg_table, direction);
 }
 EXPORT_SYMBOL_GPL(dma_buf_unmap_attachment);
 
@@ -906,6 +1044,10 @@ int dma_buf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma,
        if (WARN_ON(!dmabuf || !vma))
                return -EINVAL;
 
+       /* check if buffer supports mmap */
+       if (!dmabuf->ops->mmap)
+               return -EINVAL;
+
        /* check for offset overflow */
        if (pgoff + vma_pages(vma) < pgoff)
                return -EOVERFLOW;
@@ -1025,8 +1167,8 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
                return ret;
 
        seq_puts(s, "\nDma-buf Objects:\n");
-       seq_printf(s, "%-8s\t%-8s\t%-8s\t%-8s\texp_name\n",
-                  "size", "flags", "mode", "count");
+       seq_printf(s, "%-8s\t%-8s\t%-8s\t%-8s\texp_name\t%-8s\n",
+                  "size", "flags", "mode", "count", "ino");
 
        list_for_each_entry(buf_obj, &db_list.head, list_node) {
                ret = mutex_lock_interruptible(&buf_obj->lock);
@@ -1037,11 +1179,13 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
                        continue;
                }
 
-               seq_printf(s, "%08zu\t%08x\t%08x\t%08ld\t%s\n",
+               seq_printf(s, "%08zu\t%08x\t%08x\t%08ld\t%s\t%08lu\t%s\n",
                                buf_obj->size,
                                buf_obj->file->f_flags, buf_obj->file->f_mode,
                                file_count(buf_obj->file),
-                               buf_obj->exp_name);
+                               buf_obj->exp_name,
+                               file_inode(buf_obj->file)->i_ino,
+                               buf_obj->name ?: "");
 
                robj = buf_obj->resv;
                while (true) {
@@ -1068,6 +1212,7 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
                                   fence->ops->get_driver_name(fence),
                                   fence->ops->get_timeline_name(fence),
                                   dma_fence_is_signaled(fence) ? "" : "un");
+                       dma_fence_put(fence);
                }
                rcu_read_unlock();
 
@@ -1136,6 +1281,10 @@ static inline void dma_buf_uninit_debugfs(void)
 
 static int __init dma_buf_init(void)
 {
+       dma_buf_mnt = kern_mount(&dma_buf_fs_type);
+       if (IS_ERR(dma_buf_mnt))
+               return PTR_ERR(dma_buf_mnt);
+
        mutex_init(&db_list.lock);
        INIT_LIST_HEAD(&db_list.head);
        dma_buf_init_debugfs();
@@ -1146,5 +1295,6 @@ subsys_initcall(dma_buf_init);
 static void __exit dma_buf_deinit(void)
 {
        dma_buf_uninit_debugfs();
+       kern_unmount(dma_buf_mnt);
 }
 __exitcall(dma_buf_deinit);