Skip to content

Commit b182341

Browse files
committed
drm: Add a drm_get_unmapped_area() helper
Unaligned virtual addresses makes it unlikely that huge page-table entries can be used. So align virtual buffer object address huge page boundaries to the underlying physical address huge page boundaries taking buffer object sizes into account to determine when it might be possible to use huge page-table entries. Cc: Andrew Morton <[email protected]> Cc: Michal Hocko <[email protected]> Cc: "Matthew Wilcox (Oracle)" <[email protected]> Cc: "Kirill A. Shutemov" <[email protected]> Cc: Ralph Campbell <[email protected]> Cc: "Jérôme Glisse" <[email protected]> Cc: "Christian König" <[email protected]> Cc: Dan Williams <[email protected]> Signed-off-by: Thomas Hellstrom (VMware) <[email protected]> Reviewed-by: Roland Scheidegger <[email protected]> Acked-by: Christian König <[email protected]>
1 parent 7539028 commit b182341

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

drivers/gpu/drm/drm_file.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
#include "drm_internal.h"
4949
#include "drm_legacy.h"
5050

51+
#if defined(CONFIG_MMU) && defined(CONFIG_TRANSPARENT_HUGEPAGE)
52+
#include <uapi/asm/mman.h>
53+
#include <drm/drm_vma_manager.h>
54+
#endif
55+
5156
/* from BKL pushdown */
5257
DEFINE_MUTEX(drm_global_mutex);
5358

@@ -872,3 +877,138 @@ struct file *mock_drm_getfile(struct drm_minor *minor, unsigned int flags)
872877
return file;
873878
}
874879
EXPORT_SYMBOL_FOR_TESTS_ONLY(mock_drm_getfile);
880+
881+
#ifdef CONFIG_MMU
882+
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
883+
/*
884+
* drm_addr_inflate() attempts to construct an aligned area by inflating
885+
* the area size and skipping the unaligned start of the area.
886+
* adapted from shmem_get_unmapped_area()
887+
*/
888+
static unsigned long drm_addr_inflate(unsigned long addr,
889+
unsigned long len,
890+
unsigned long pgoff,
891+
unsigned long flags,
892+
unsigned long huge_size)
893+
{
894+
unsigned long offset, inflated_len;
895+
unsigned long inflated_addr;
896+
unsigned long inflated_offset;
897+
898+
offset = (pgoff << PAGE_SHIFT) & (huge_size - 1);
899+
if (offset && offset + len < 2 * huge_size)
900+
return addr;
901+
if ((addr & (huge_size - 1)) == offset)
902+
return addr;
903+
904+
inflated_len = len + huge_size - PAGE_SIZE;
905+
if (inflated_len > TASK_SIZE)
906+
return addr;
907+
if (inflated_len < len)
908+
return addr;
909+
910+
inflated_addr = current->mm->get_unmapped_area(NULL, 0, inflated_len,
911+
0, flags);
912+
if (IS_ERR_VALUE(inflated_addr))
913+
return addr;
914+
if (inflated_addr & ~PAGE_MASK)
915+
return addr;
916+
917+
inflated_offset = inflated_addr & (huge_size - 1);
918+
inflated_addr += offset - inflated_offset;
919+
if (inflated_offset > offset)
920+
inflated_addr += huge_size;
921+
922+
if (inflated_addr > TASK_SIZE - len)
923+
return addr;
924+
925+
return inflated_addr;
926+
}
927+
928+
/**
929+
* drm_get_unmapped_area() - Get an unused user-space virtual memory area
930+
* suitable for huge page table entries.
931+
* @file: The struct file representing the address space being mmap()'d.
932+
* @uaddr: Start address suggested by user-space.
933+
* @len: Length of the area.
934+
* @pgoff: The page offset into the address space.
935+
* @flags: mmap flags
936+
* @mgr: The address space manager used by the drm driver. This argument can
937+
* probably be removed at some point when all drivers use the same
938+
* address space manager.
939+
*
940+
* This function attempts to find an unused user-space virtual memory area
941+
* that can accommodate the size we want to map, and that is properly
942+
* aligned to facilitate huge page table entries matching actual
943+
* huge pages or huge page aligned memory in buffer objects. Buffer objects
944+
* are assumed to start at huge page boundary pfns (io memory) or be
945+
* populated by huge pages aligned to the start of the buffer object
946+
* (system- or coherent memory). Adapted from shmem_get_unmapped_area.
947+
*
948+
* Return: aligned user-space address.
949+
*/
950+
unsigned long drm_get_unmapped_area(struct file *file,
951+
unsigned long uaddr, unsigned long len,
952+
unsigned long pgoff, unsigned long flags,
953+
struct drm_vma_offset_manager *mgr)
954+
{
955+
unsigned long addr;
956+
unsigned long inflated_addr;
957+
struct drm_vma_offset_node *node;
958+
959+
if (len > TASK_SIZE)
960+
return -ENOMEM;
961+
962+
/*
963+
* @pgoff is the file page-offset the huge page boundaries of
964+
* which typically aligns to physical address huge page boundaries.
965+
* That's not true for DRM, however, where physical address huge
966+
* page boundaries instead are aligned with the offset from
967+
* buffer object start. So adjust @pgoff to be the offset from
968+
* buffer object start.
969+
*/
970+
drm_vma_offset_lock_lookup(mgr);
971+
node = drm_vma_offset_lookup_locked(mgr, pgoff, 1);
972+
if (node)
973+
pgoff -= node->vm_node.start;
974+
drm_vma_offset_unlock_lookup(mgr);
975+
976+
addr = current->mm->get_unmapped_area(file, uaddr, len, pgoff, flags);
977+
if (IS_ERR_VALUE(addr))
978+
return addr;
979+
if (addr & ~PAGE_MASK)
980+
return addr;
981+
if (addr > TASK_SIZE - len)
982+
return addr;
983+
984+
if (len < HPAGE_PMD_SIZE)
985+
return addr;
986+
if (flags & MAP_FIXED)
987+
return addr;
988+
/*
989+
* Our priority is to support MAP_SHARED mapped hugely;
990+
* and support MAP_PRIVATE mapped hugely too, until it is COWed.
991+
* But if caller specified an address hint, respect that as before.
992+
*/
993+
if (uaddr)
994+
return addr;
995+
996+
inflated_addr = drm_addr_inflate(addr, len, pgoff, flags,
997+
HPAGE_PMD_SIZE);
998+
999+
if (IS_ENABLED(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD) &&
1000+
len >= HPAGE_PUD_SIZE)
1001+
inflated_addr = drm_addr_inflate(inflated_addr, len, pgoff,
1002+
flags, HPAGE_PUD_SIZE);
1003+
return inflated_addr;
1004+
}
1005+
#else /* CONFIG_TRANSPARENT_HUGEPAGE */
1006+
unsigned long drm_get_unmapped_area(struct file *file,
1007+
unsigned long uaddr, unsigned long len,
1008+
unsigned long pgoff, unsigned long flags,
1009+
struct drm_vma_offset_manager *mgr)
1010+
{
1011+
return current->mm->get_unmapped_area(file, uaddr, len, pgoff, flags);
1012+
}
1013+
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
1014+
#endif /* CONFIG_MMU */

include/drm/drm_file.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,13 @@ void drm_send_event(struct drm_device *dev, struct drm_pending_event *e);
391391

392392
struct file *mock_drm_getfile(struct drm_minor *minor, unsigned int flags);
393393

394+
#ifdef CONFIG_MMU
395+
struct drm_vma_offset_manager;
396+
unsigned long drm_get_unmapped_area(struct file *file,
397+
unsigned long uaddr, unsigned long len,
398+
unsigned long pgoff, unsigned long flags,
399+
struct drm_vma_offset_manager *mgr);
400+
#endif /* CONFIG_MMU */
401+
402+
394403
#endif /* _DRM_FILE_H_ */

0 commit comments

Comments
 (0)