Re: [PATCH 6/8] drm: Add a drm_get_unmapped_area() helper

From: Thomas HellstrÃm (VMware)
Date: Wed Dec 04 2019 - 06:37:01 EST


On 12/4/19 12:11 PM, Christian KÃnig wrote:
Am 03.12.19 um 14:22 schrieb Thomas HellstrÃm (VMware):
From: Thomas Hellstrom <thellstrom@xxxxxxxxxx>

This helper is used to align user-space buffer object addresses to
huge page boundaries, minimizing the chance of alignment mismatch
between user-space addresses and physical addresses.

Mhm, I'm wondering if that is really such a good idea.

Could you elaborate? What drawbacks do you see?
Note that this is the way other subsystems are doing it. Take a look at shmem_get_unmapped_area() for instance.


Wouldn't it be sufficient if userspace uses MAP_HUGETLB?

MAP_HUGETLB is something different and appears to be tied to the kernel persistent huge page mechanism, whereas the TTM huge pages is tided to the THP functionality (although skipped by khugepaged).

Thanks,

Thomas




Regards,
Christian.


Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
Cc: Michal Hocko <mhocko@xxxxxxxx>
Cc: "Matthew Wilcox (Oracle)" <willy@xxxxxxxxxxxxx>
Cc: "Kirill A. Shutemov" <kirill.shutemov@xxxxxxxxxxxxxxx>
Cc: Ralph Campbell <rcampbell@xxxxxxxxxx>
Cc: "JÃrÃme Glisse" <jglisse@xxxxxxxxxx>
Cc: "Christian KÃnig" <christian.koenig@xxxxxxx>
Signed-off-by: Thomas Hellstrom <thellstrom@xxxxxxxxxx>
---
 drivers/gpu/drm/drm_file.c | 130 +++++++++++++++++++++++++++++++++++++
 include/drm/drm_file.h | 5 ++
 2 files changed, 135 insertions(+)

diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ea34bc991858..e5b4024cd397 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -31,6 +31,8 @@
ÂÂ * OTHER DEALINGS IN THE SOFTWARE.
ÂÂ */
 +#include <uapi/asm/mman.h>
+
 #include <linux/dma-fence.h>
 #include <linux/module.h>
 #include <linux/pci.h>
@@ -41,6 +43,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
 #include <drm/drm_print.h>
+#include <drm/drm_vma_manager.h>
  #include "drm_crtc_internal.h"
 #include "drm_internal.h"
@@ -754,3 +757,130 @@ void drm_send_event(struct drm_device *dev, struct drm_pending_event *e)
ÂÂÂÂÂ spin_unlock_irqrestore(&dev->event_lock, irqflags);
 }
 EXPORT_SYMBOL(drm_send_event);
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+/*
+ * drm_addr_inflate() attempts to construct an aligned area by inflating
+ * the area size and skipping the unaligned start of the area.
+ * adapted from shmem_get_unmapped_area()
+ */
+static unsigned long drm_addr_inflate(unsigned long addr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long len,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long pgoff,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long flags,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long huge_size)
+{
+ÂÂÂ unsigned long offset, inflated_len;
+ÂÂÂ unsigned long inflated_addr;
+ÂÂÂ unsigned long inflated_offset;
+
+ÂÂÂ offset = (pgoff << PAGE_SHIFT) & (huge_size - 1);
+ÂÂÂ if (offset && offset + len < 2 * huge_size)
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ if ((addr & (huge_size - 1)) == offset)
+ÂÂÂÂÂÂÂ return addr;
+
+ÂÂÂ inflated_len = len + huge_size - PAGE_SIZE;
+ÂÂÂ if (inflated_len > TASK_SIZE)
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ if (inflated_len < len)
+ÂÂÂÂÂÂÂ return addr;
+
+ÂÂÂ inflated_addr = current->mm->get_unmapped_area(NULL, 0, inflated_len,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ 0, flags);
+ÂÂÂ if (IS_ERR_VALUE(inflated_addr))
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ if (inflated_addr & ~PAGE_MASK)
+ÂÂÂÂÂÂÂ return addr;
+
+ÂÂÂ inflated_offset = inflated_addr & (huge_size - 1);
+ÂÂÂ inflated_addr += offset - inflated_offset;
+ÂÂÂ if (inflated_offset > offset)
+ÂÂÂÂÂÂÂ inflated_addr += huge_size;
+
+ÂÂÂ if (inflated_addr > TASK_SIZE - len)
+ÂÂÂÂÂÂÂ return addr;
+
+ÂÂÂ return inflated_addr;
+}
+
+/**
+ * drm_get_unmapped_area() - Get an unused user-space virtual memory area
+ * suitable for huge page table entries.
+ * @file: The struct file representing the address space being mmap()'d.
+ * @uaddr: Start address suggested by user-space.
+ * @len: Length of the area.
+ * @pgoff: The page offset into the address space.
+ * @flags: mmap flags
+ * @mgr: The address space manager used by the drm driver. This argument can
+ * probably be removed at some point when all drivers use the same
+ * address space manager.
+ *
+ * This function attempts to find an unused user-space virtual memory area
+ * that can accommodate the size we want to map, and that is properly
+ * aligned to facilitate huge page table entries matching actual
+ * huge pages or huge page aligned memory in buffer objects. Buffer objects
+ * are assumed to start at huge page boundary pfns (io memory) or be
+ * populated by huge pages aligned to the start of the buffer object
+ * (system- or coherent memory). Adapted from shmem_get_unmapped_area.
+ *
+ * Return: aligned user-space address.
+ */
+unsigned long drm_get_unmapped_area(struct file *file,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long uaddr, unsigned long len,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long pgoff, unsigned long flags,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct drm_vma_offset_manager *mgr)
+{
+ÂÂÂ unsigned long addr;
+ÂÂÂ unsigned long inflated_addr;
+ÂÂÂ struct drm_vma_offset_node *node;
+
+ÂÂÂ if (len > TASK_SIZE)
+ÂÂÂÂÂÂÂ return -ENOMEM;
+
+ÂÂÂ /* Adjust mapping offset to be zero at bo start */
+ÂÂÂ drm_vma_offset_lock_lookup(mgr);
+ÂÂÂ node = drm_vma_offset_lookup_locked(mgr, pgoff, 1);
+ÂÂÂ if (node)
+ÂÂÂÂÂÂÂ pgoff -= node->vm_node.start;
+ÂÂÂ drm_vma_offset_unlock_lookup(mgr);
+
+ÂÂÂ addr = current->mm->get_unmapped_area(file, uaddr, len, pgoff, flags);
+ÂÂÂ if (IS_ERR_VALUE(addr))
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ if (addr & ~PAGE_MASK)
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ if (addr > TASK_SIZE - len)
+ÂÂÂÂÂÂÂ return addr;
+
+ÂÂÂ if (len < HPAGE_PMD_SIZE)
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ if (flags & MAP_FIXED)
+ÂÂÂÂÂÂÂ return addr;
+ÂÂÂ /*
+ÂÂÂÂ * Our priority is to support MAP_SHARED mapped hugely;
+ÂÂÂÂ * and support MAP_PRIVATE mapped hugely too, until it is COWed.
+ÂÂÂÂ * But if caller specified an address hint, respect that as before.
+ÂÂÂÂ */
+ÂÂÂ if (uaddr)
+ÂÂÂÂÂÂÂ return addr;
+
+ÂÂÂ inflated_addr = drm_addr_inflate(addr, len, pgoff, flags,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ HPAGE_PMD_SIZE);
+
+ÂÂÂ if (IS_ENABLED(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD) &&
+ÂÂÂÂÂÂÂ len >= HPAGE_PUD_SIZE)
+ÂÂÂÂÂÂÂ inflated_addr = drm_addr_inflate(inflated_addr, len, pgoff,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ flags, HPAGE_PUD_SIZE);
+ÂÂÂ return inflated_addr;
+}
+#else /* CONFIG_TRANSPARENT_HUGEPAGE */
+unsigned long drm_get_unmapped_area(struct file *file,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long uaddr, unsigned long len,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long pgoff, unsigned long flags,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct drm_vma_offset_manager *mgr)
+{
+ÂÂÂ return current->mm->get_unmapped_area(file, uaddr, len, pgoff, flags);
+}
+#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
+EXPORT_SYMBOL_GPL(drm_get_unmapped_area);
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index 67af60bb527a..4719cc80d547 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -386,5 +386,10 @@ void drm_event_cancel_free(struct drm_device *dev,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct drm_pending_event *p);
 void drm_send_event_locked(struct drm_device *dev, struct drm_pending_event *e);
 void drm_send_event(struct drm_device *dev, struct drm_pending_event *e);
+struct drm_vma_offset_manager;
+unsigned long drm_get_unmapped_area(struct file *file,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long uaddr, unsigned long len,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long pgoff, unsigned long flags,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct drm_vma_offset_manager *mgr);
  #endif /* _DRM_FILE_H_ */