summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_gem.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/drm_gem.c')
-rw-r--r--drivers/gpu/drm/drm_gem.c205
1 files changed, 156 insertions, 49 deletions
diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index 25f68fed9b48..ffa7852c8f6c 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -29,6 +29,9 @@
#include <linux/export.h>
#include <linux/file.h>
#include <linux/fs.h>
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+#include <linux/fs_context.h>
+#endif
#include <linux/iosys-map.h>
#include <linux/mem_encrypt.h>
#include <linux/mm.h>
@@ -36,6 +39,7 @@
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/pagevec.h>
+#include <linux/sched/mm.h>
#include <linux/shmem_fs.h>
#include <linux/slab.h>
#include <linux/string_helpers.h>
@@ -81,6 +85,60 @@
* up at a later date, and as our interface with shmfs for memory allocation.
*/
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+static void drm_gem_huge_mnt_free(struct drm_device *dev, void *data)
+{
+ kern_unmount(dev->huge_mnt);
+}
+
+/**
+ * drm_gem_huge_mnt_create - Create, mount and use a huge tmpfs mountpoint
+ * @dev: DRM device that will use the huge tmpfs mountpoint
+ * @value: huge tmpfs mount option value
+ *
+ * This function creates and mounts a dedicated huge tmpfs mountpoint for the
+ * lifetime of the DRM device @dev which is used at GEM object initialization
+ * with drm_gem_object_init().
+ *
+ * The most common option for @value is "within_size" which only allocates huge
+ * pages if the page will be fully within the GEM object size. "always",
+ * "advise" and "never" are supported too but the latter would just create a
+ * mountpoint similar to the default one (`shm_mnt`). See shmemfs and
+ * Transparent Hugepage for more information.
+ *
+ * Returns:
+ * 0 on success or a negative error code on failure.
+ */
+int drm_gem_huge_mnt_create(struct drm_device *dev, const char *value)
+{
+ struct file_system_type *type;
+ struct fs_context *fc;
+ int ret;
+
+ if (unlikely(drm_gem_get_huge_mnt(dev)))
+ return 0;
+
+ type = get_fs_type("tmpfs");
+ if (unlikely(!type))
+ return -EOPNOTSUPP;
+ fc = fs_context_for_mount(type, SB_KERNMOUNT);
+ if (IS_ERR(fc))
+ return PTR_ERR(fc);
+ ret = vfs_parse_fs_string(fc, "source", "tmpfs");
+ if (unlikely(ret))
+ return -ENOPARAM;
+ ret = vfs_parse_fs_string(fc, "huge", value);
+ if (unlikely(ret))
+ return -ENOPARAM;
+
+ dev->huge_mnt = fc_mount_longterm(fc);
+ put_fs_context(fc);
+
+ return drmm_add_action_or_reset(dev, drm_gem_huge_mnt_free, NULL);
+}
+EXPORT_SYMBOL_GPL(drm_gem_huge_mnt_create);
+#endif
+
static void
drm_gem_init_release(struct drm_device *dev, void *ptr)
{
@@ -113,29 +171,28 @@ drm_gem_init(struct drm_device *dev)
}
/**
- * drm_gem_object_init_with_mnt - initialize an allocated shmem-backed GEM
- * object in a given shmfs mountpoint
+ * drm_gem_object_init - initialize an allocated shmem-backed GEM object
*
* @dev: drm_device the object should be initialized for
* @obj: drm_gem_object to initialize
* @size: object size
- * @gemfs: tmpfs mount where the GEM object will be created. If NULL, use
- * the usual tmpfs mountpoint (`shm_mnt`).
*
* Initialize an already allocated GEM object of the specified size with
- * shmfs backing store.
+ * shmfs backing store. A huge mountpoint can be used by calling
+ * drm_gem_huge_mnt_create() beforehand.
*/
-int drm_gem_object_init_with_mnt(struct drm_device *dev,
- struct drm_gem_object *obj, size_t size,
- struct vfsmount *gemfs)
+int drm_gem_object_init(struct drm_device *dev, struct drm_gem_object *obj,
+ size_t size)
{
+ struct vfsmount *huge_mnt;
struct file *filp;
drm_gem_private_object_init(dev, obj, size);
- if (gemfs)
- filp = shmem_file_setup_with_mnt(gemfs, "drm mm object", size,
- VM_NORESERVE);
+ huge_mnt = drm_gem_get_huge_mnt(dev);
+ if (huge_mnt)
+ filp = shmem_file_setup_with_mnt(huge_mnt, "drm mm object",
+ size, VM_NORESERVE);
else
filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
@@ -146,22 +203,6 @@ int drm_gem_object_init_with_mnt(struct drm_device *dev,
return 0;
}
-EXPORT_SYMBOL(drm_gem_object_init_with_mnt);
-
-/**
- * drm_gem_object_init - initialize an allocated shmem-backed GEM object
- * @dev: drm_device the object should be initialized for
- * @obj: drm_gem_object to initialize
- * @size: object size
- *
- * Initialize an already allocated GEM object of the specified size with
- * shmfs backing store.
- */
-int drm_gem_object_init(struct drm_device *dev, struct drm_gem_object *obj,
- size_t size)
-{
- return drm_gem_object_init_with_mnt(dev, obj, size, NULL);
-}
EXPORT_SYMBOL(drm_gem_object_init);
/**
@@ -1187,36 +1228,27 @@ err_drm_gem_object_put:
}
EXPORT_SYMBOL(drm_gem_mmap_obj);
-/**
- * drm_gem_mmap - memory map routine for GEM objects
- * @filp: DRM file pointer
- * @vma: VMA for the area to be mapped
- *
- * If a driver supports GEM object mapping, mmap calls on the DRM file
- * descriptor will end up here.
- *
- * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
- * contain the fake offset we created when the GTT map ioctl was called on
- * the object) and map it with a call to drm_gem_mmap_obj().
- *
- * If the caller is not granted access to the buffer object, the mmap will fail
- * with EACCES. Please see the vma manager for more information.
+/*
+ * Look up a GEM object in offset space based on the exact start address. The
+ * caller must be granted access to the object. Returns a GEM object on success
+ * or a negative error code on failure. The returned GEM object needs to be
+ * released with drm_gem_object_put().
*/
-int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+static struct drm_gem_object *
+drm_gem_object_lookup_at_offset(struct file *filp, unsigned long start,
+ unsigned long pages)
{
struct drm_file *priv = filp->private_data;
struct drm_device *dev = priv->minor->dev;
struct drm_gem_object *obj = NULL;
struct drm_vma_offset_node *node;
- int ret;
if (drm_dev_is_unplugged(dev))
- return -ENODEV;
+ return ERR_PTR(-ENODEV);
drm_vma_offset_lock_lookup(dev->vma_offset_manager);
node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
- vma->vm_pgoff,
- vma_pages(vma));
+ start, pages);
if (likely(node)) {
obj = container_of(node, struct drm_gem_object, vma_node);
/*
@@ -1235,14 +1267,89 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
if (!obj)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
if (!drm_vma_node_is_allowed(node, priv)) {
drm_gem_object_put(obj);
- return -EACCES;
+ return ERR_PTR(-EACCES);
}
- ret = drm_gem_mmap_obj(obj, drm_vma_node_size(node) << PAGE_SHIFT,
+ return obj;
+}
+
+#ifdef CONFIG_MMU
+/**
+ * drm_gem_get_unmapped_area - get memory mapping region routine for GEM objects
+ * @filp: DRM file pointer
+ * @uaddr: User address hint
+ * @len: Mapping length
+ * @pgoff: Offset (in pages)
+ * @flags: Mapping flags
+ *
+ * If a driver supports GEM object mapping, before ending up in drm_gem_mmap(),
+ * mmap calls on the DRM file descriptor will first try to find a free linear
+ * address space large enough for a mapping. Since GEM objects are backed by
+ * shmem buffers, this should preferably be handled by the shmem virtual memory
+ * filesystem which can appropriately align addresses to huge page sizes when
+ * needed.
+ *
+ * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
+ * contain the fake offset we created) and call shmem_get_unmapped_area() with
+ * the right file pointer.
+ *
+ * If a GEM object is not available at the given offset or if the caller is not
+ * granted access to it, fall back to mm_get_unmapped_area().
+ */
+unsigned long drm_gem_get_unmapped_area(struct file *filp, unsigned long uaddr,
+ unsigned long len, unsigned long pgoff,
+ unsigned long flags)
+{
+ struct drm_gem_object *obj;
+ unsigned long ret;
+
+ obj = drm_gem_object_lookup_at_offset(filp, pgoff, len >> PAGE_SHIFT);
+ if (IS_ERR(obj))
+ obj = NULL;
+
+ if (!obj || !obj->filp || !obj->filp->f_op->get_unmapped_area)
+ ret = mm_get_unmapped_area(filp, uaddr, len, 0, flags);
+ else
+ ret = obj->filp->f_op->get_unmapped_area(obj->filp, uaddr, len, 0, flags);
+
+ drm_gem_object_put(obj);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gem_get_unmapped_area);
+#endif
+
+/**
+ * drm_gem_mmap - memory map routine for GEM objects
+ * @filp: DRM file pointer
+ * @vma: VMA for the area to be mapped
+ *
+ * If a driver supports GEM object mapping, mmap calls on the DRM file
+ * descriptor will end up here.
+ *
+ * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
+ * contain the fake offset we created) and map it with a call to
+ * drm_gem_mmap_obj().
+ *
+ * If the caller is not granted access to the buffer object, the mmap will fail
+ * with EACCES. Please see the vma manager for more information.
+ */
+int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj;
+ int ret;
+
+ obj = drm_gem_object_lookup_at_offset(filp, vma->vm_pgoff,
+ vma_pages(vma));
+ if (IS_ERR(obj))
+ return PTR_ERR(obj);
+
+ ret = drm_gem_mmap_obj(obj,
+ drm_vma_node_size(&obj->vma_node) << PAGE_SHIFT,
vma);
drm_gem_object_put(obj);