[RFC PATCH 6/8] mm: Make hugetlb mappings go through mm_get_unmapped_area_vmflags

Oscar Salvador
2024-07-10T10:51:44Z

Hugetlb mappings will no longer be special cased but rather go through
the generic mm_get_unmapped_area_vmflags function.
For that to happen, let us remove the .get_unmapped_area from
hugetlbfs_file_operations struct, and hint __get_unmapped_area
that it should not send hugetlb mappings through thp_get_unmapped_area_vmflags
but through mm_get_unmapped_area_vmflags.

Create also a function called hugetlb_mmap_check_and_align() where a
couple of safety checks are being done and the addr is aligned to
the huge page size.
Otherwise we will have to do this in every single function, which
duplicates quite a lot of code.

Signed-off-by: Oscar Salvador <osalvador@suse.de>
---
fs/hugetlbfs/inode.c | 22 ++++++++++++++--------
include/linux/hugetlb.h | 8 +++-----
mm/mmap.c | 15 ++++++++++++++-
3 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 412f295acebe..b2d7fcecdb15 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -257,15 +257,22 @@ generic_hugetlb_get_unmapped_area(struct file *file, unsigned long addr,
pgoff, flags);
}

-#ifndef HAVE_ARCH_HUGETLB_UNMAPPED_AREA
-static unsigned long
-hugetlb_get_unmapped_area(struct file *file, unsigned long addr,
- unsigned long len, unsigned long pgoff,
- unsigned long flags)
+unsigned long
+hugetlb_mmap_check_and_align(struct file *file, unsigned long addr,
+ unsigned long len, unsigned long flags)
{
- return generic_hugetlb_get_unmapped_area(file, addr, len, pgoff, flags);
+ unsigned long addr0 = 0;
+ struct hstate *h = hstate_file(file);
+
+ if (len & ~huge_page_mask(h))
+ return -EINVAL;
+ if ((flags & MAP_FIXED) && prepare_hugepage_range(file, addr, len))
+ return -EINVAL;
+ if (addr)
+ addr0 = ALIGN(addr, huge_page_size(h));
+
+ return addr0;
}
-#endif

/*
* Someone wants to read @bytes from a HWPOISON hugetlb @page from @offset.
@@ -1302,7 +1309,6 @@ static const struct file_operations hugetlbfs_file_operations = {
.read_iter = hugetlbfs_read_iter,
.mmap = hugetlbfs_file_mmap,
.fsync = noop_fsync,
- .get_unmapped_area = hugetlb_get_unmapped_area,
.llseek = default_llseek,
.fallocate = hugetlbfs_fallocate,
.fop_flags = FOP_HUGE_PAGES,
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 1c7b0b32ff7e..9183ef95dfb6 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -555,11 +555,9 @@ static inline struct hstate *hstate_inode(struct inode *i)
}
#endif /* !CONFIG_HUGETLBFS */

-#ifdef HAVE_ARCH_HUGETLB_UNMAPPED_AREA
-unsigned long hugetlb_get_unmapped_area(struct file *file, unsigned long addr,
- unsigned long len, unsigned long pgoff,
- unsigned long flags);
-#endif /* HAVE_ARCH_HUGETLB_UNMAPPED_AREA */
+unsigned long
+hugetlb_mmap_check_and_align(struct file *file, unsigned long addr,
+ unsigned long len, unsigned long flags);

unsigned long
generic_hugetlb_get_unmapped_area(struct file *file, unsigned long addr,
diff --git a/mm/mmap.c b/mm/mmap.c
index 09131b705e7b..8130b25b8cf5 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1860,6 +1860,7 @@ __get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
unsigned long, unsigned long, unsigned long)
= NULL;

+ bool is_hugetlb = false;
unsigned long error = arch_mmap_check(addr, len, flags);
if (error)
return error;
@@ -1868,6 +1869,9 @@ __get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
if (len > TASK_SIZE)
return -ENOMEM;

+ if (file && is_file_hugepages(file))
+ is_hugetlb = true;
+
if (file) {
if (file->f_op->get_unmapped_area)
get_area = file->f_op->get_unmapped_area;
@@ -1885,11 +1889,20 @@ __get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,

if (get_area) {
addr = get_area(file, addr, len, pgoff, flags);
- } else if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) {
+ } else if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) && !is_hugetlb) {
/* Ensures that larger anonymous mappings are THP aligned. */
addr = thp_get_unmapped_area_vmflags(file, addr, len,
pgoff, flags, vm_flags);
} else {
+ /*
+ * Consolidate hugepages checks in one place, and also align addr
+ * to hugepage size.
+ */
+ if (is_hugetlb) {
+ addr = hugetlb_mmap_check_and_align(file, addr, len, flags);
+ if (IS_ERR_VALUE(addr))
+ return addr;
+ }
addr = mm_get_unmapped_area_vmflags(current->mm, file, addr, len,
pgoff, flags, vm_flags);
}
--
2.45.2