mmap() calls on the drm file pointer currently always end up using
mm_get_unmapped_area() to get a free mapping region. On builds with
CONFIG_TRANSPARENT_HUGEPAGE enabled, this isn't ideal for GEM objects
backed by shmem buffers on mount points setting the 'huge=' option
because it can't correctly figure out the potentially huge address
alignment required.
This commit introduces the drm_gem_get_unmapped_area() function which
is meant to be used as a get_unmapped_area file operation on the drm
file pointer to lookup GEM objects based on their fake offsets and get
a properly aligned region by calling shmem_get_unmapped_area() with
the right file pointer. If a GEM object isn't available at the given
offset or if the caller isn't granted access to it, the function falls
back to mm_get_unmapped_area().
This also makes drm_gem_get_unmapped_area() part of the default GEM
file operations so that all the drm drivers can benefit from more
efficient mappings thanks to the huge page fault handler introduced in
previous commit 'drm/shmem-helper: Add huge page fault handler'.
The shmem_get_unmapped_area() function needs to be exported so that
it can be used from the drm subsystem.
v3:
- add missing include: 'linux/sched/mm.h'
- forward to shmem layer in !CONFIG_TRANSPARENT_HUGEPAGE builds
Signed-off-by: Loïc Molinari <loic.molinari@collabora.com>
---
drivers/gpu/drm/drm_gem.c | 107 ++++++++++++++++++++++++++++++--------
include/drm/drm_gem.h | 4 ++
mm/shmem.c | 1 +
3 files changed, 90 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index cbeb76b2124f..f5a10ff363c9 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -36,6 +36,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>
@@ -1187,36 +1188,27 @@ int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
}
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_from_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 +1227,85 @@ 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;
+}
+
+/**
+ * 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_from_offset(filp, pgoff, len >> PAGE_SHIFT);
+ if (IS_ERR(obj))
+ return mm_get_unmapped_area(current->mm, filp, uaddr, len, 0,
+ flags);
+
+ ret = shmem_get_unmapped_area(obj->filp, uaddr, len, 0, flags);
+
+ drm_gem_object_put(obj);
+
+ return ret;
+}
+EXPORT_SYMBOL(drm_gem_get_unmapped_area);
+
+/**
+ * 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_from_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);
diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index 8d48d2af2649..7c8bd67d087c 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -469,6 +469,7 @@ struct drm_gem_object {
.poll = drm_poll,\
.read = drm_read,\
.llseek = noop_llseek,\
+ .get_unmapped_area = drm_gem_get_unmapped_area,\
.mmap = drm_gem_mmap, \
.fop_flags = FOP_UNSIGNED_OFFSET
@@ -506,6 +507,9 @@ void drm_gem_vm_close(struct vm_area_struct *vma);
int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
struct vm_area_struct *vma);
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+unsigned long drm_gem_get_unmapped_area(struct file *filp, unsigned long uaddr,
+ unsigned long len, unsigned long pgoff,
+ unsigned long flags);
/**
* drm_gem_object_get - acquire a GEM buffer object reference
diff --git a/mm/shmem.c b/mm/shmem.c
index e2c76a30802b..b2f41b430daa 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2915,6 +2915,7 @@ unsigned long shmem_get_unmapped_area(struct file *file,
return addr;
return inflated_addr;
}
+EXPORT_SYMBOL_GPL(shmem_get_unmapped_area);
#ifdef CONFIG_NUMA
static int shmem_set_policy(struct vm_area_struct *vma, struct mempolicy *mpol)
--
2.47.3
Hi Loïc,
kernel test robot noticed the following build errors:
[auto build test ERROR on drm-misc/drm-misc-next]
[also build test ERROR on linus/master v6.17 next-20251009]
[cannot apply to akpm-mm/mm-everything]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Lo-c-Molinari/drm-shmem-helper-Add-huge-page-fault-handler/20251004-173347
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20251004093054.21388-3-loic.molinari%40collabora.com
patch subject: [PATCH v3 02/10] drm/gem: Introduce drm_gem_get_unmapped_area() fop
config: riscv-randconfig-001-20251010 (https://download.01.org/0day-ci/archive/20251010/202510101507.UiRzhiAP-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 39f292ffa13d7ca0d1edff27ac8fd55024bb4d19)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251010/202510101507.UiRzhiAP-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202510101507.UiRzhiAP-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:804:2: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
804 | insb(addr, buffer, count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/io.h:104:53: note: expanded from macro 'insb'
104 | #define insb(addr, buffer, count) __insb(PCI_IOBASE + (addr), buffer, count)
| ~~~~~~~~~~ ^
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:812:2: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
812 | insw(addr, buffer, count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/io.h:105:53: note: expanded from macro 'insw'
105 | #define insw(addr, buffer, count) __insw(PCI_IOBASE + (addr), buffer, count)
| ~~~~~~~~~~ ^
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:820:2: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
820 | insl(addr, buffer, count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/io.h:106:53: note: expanded from macro 'insl'
106 | #define insl(addr, buffer, count) __insl(PCI_IOBASE + (addr), buffer, count)
| ~~~~~~~~~~ ^
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:829:2: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
829 | outsb(addr, buffer, count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/io.h:118:55: note: expanded from macro 'outsb'
118 | #define outsb(addr, buffer, count) __outsb(PCI_IOBASE + (addr), buffer, count)
| ~~~~~~~~~~ ^
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:838:2: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
838 | outsw(addr, buffer, count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/io.h:119:55: note: expanded from macro 'outsw'
119 | #define outsw(addr, buffer, count) __outsw(PCI_IOBASE + (addr), buffer, count)
| ~~~~~~~~~~ ^
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:847:2: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
847 | outsl(addr, buffer, count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/io.h:120:55: note: expanded from macro 'outsl'
120 | #define outsl(addr, buffer, count) __outsl(PCI_IOBASE + (addr), buffer, count)
| ~~~~~~~~~~ ^
In file included from drivers/gpu/drm/drm_gem.c:28:
In file included from include/linux/dma-buf.h:16:
In file included from include/linux/iosys-map.h:10:
In file included from include/linux/io.h:12:
In file included from arch/riscv/include/asm/io.h:136:
include/asm-generic/io.h:1175:55: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
1175 | return (port > MMIO_UPPER_LIMIT) ? NULL : PCI_IOBASE + port;
| ~~~~~~~~~~ ^
>> drivers/gpu/drm/drm_gem.c:1271:10: error: call to undeclared function 'mm_get_unmapped_area'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
1271 | return mm_get_unmapped_area(current->mm, filp, uaddr, len, 0,
| ^
drivers/gpu/drm/drm_gem.c:1271:10: note: did you mean '__get_unmapped_area'?
include/linux/mm.h:3337:1: note: '__get_unmapped_area' declared here
3337 | __get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
| ^
7 warnings and 1 error generated.
vim +/mm_get_unmapped_area +1271 drivers/gpu/drm/drm_gem.c
1239
1240 /**
1241 * drm_gem_get_unmapped_area - get memory mapping region routine for GEM objects
1242 * @filp: DRM file pointer
1243 * @uaddr: User address hint
1244 * @len: Mapping length
1245 * @pgoff: Offset (in pages)
1246 * @flags: Mapping flags
1247 *
1248 * If a driver supports GEM object mapping, before ending up in drm_gem_mmap(),
1249 * mmap calls on the DRM file descriptor will first try to find a free linear
1250 * address space large enough for a mapping. Since GEM objects are backed by
1251 * shmem buffers, this should preferably be handled by the shmem virtual memory
1252 * filesystem which can appropriately align addresses to huge page sizes when
1253 * needed.
1254 *
1255 * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
1256 * contain the fake offset we created) and call shmem_get_unmapped_area() with
1257 * the right file pointer.
1258 *
1259 * If a GEM object is not available at the given offset or if the caller is not
1260 * granted access to it, fall back to mm_get_unmapped_area().
1261 */
1262 unsigned long drm_gem_get_unmapped_area(struct file *filp, unsigned long uaddr,
1263 unsigned long len, unsigned long pgoff,
1264 unsigned long flags)
1265 {
1266 struct drm_gem_object *obj;
1267 unsigned long ret;
1268
1269 obj = drm_gem_object_lookup_from_offset(filp, pgoff, len >> PAGE_SHIFT);
1270 if (IS_ERR(obj))
> 1271 return mm_get_unmapped_area(current->mm, filp, uaddr, len, 0,
1272 flags);
1273
1274 ret = shmem_get_unmapped_area(obj->filp, uaddr, len, 0, flags);
1275
1276 drm_gem_object_put(obj);
1277
1278 return ret;
1279 }
1280 EXPORT_SYMBOL(drm_gem_get_unmapped_area);
1281
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.