[PATCH 41/46] mm/vma: call ->may_split() before a partial merge
Gregory Price
gourry at gourry.net
Mon Oct 26 20:45:01 PDT 2026
On 2026-10-27 00:18 +0100, David Hildenbrand (Arm) wrote:
> From: Chris Mason <mason at kernel.org>
>
> A driver can use the ->may_split() hook to forbid a VMA boundary at
> certain addresses.
>
... snip ...
>
> Fixes: 38607c62b34b ("fs/dax: properly refcount fs dax pages")
> Cc: stable at vger.kernel.org
> Signed-off-by: Chris Mason <mason at kernel.org>
> Reviewed-by: Lorenzo Stoakes (ARM) <ljs at kernel.org>
> [ david: improve description; cleanup the code ]
> Signed-off-by: David Hildenbrand (Arm) <david at kernel.org>
Reviewed-by: Gregory Price (Meta) <gourry at gourry.net>
Tested-by: Gregory Price (Meta) <gourry at gourry.net>
Attached a vma unit test I used to test this
-- >8 --
From: Gregory Price <gourry at gourry.net>
Subject: [PATCH] tools/testing/vma: test may_split during partial merges
A partial VMA merge moves a boundary just as a split does, so it must
consult the driver's may_split callback. Removing an entire VMA during a
merge needs no split check.
Exercise permitted and rejected partial merges in both directions through
vma_modify_flags(). Check the callback address and resulting boundaries,
including unchanged boundaries on rejection. Also check that a full merge
succeeds without calling a rejecting may_split callback.
Signed-off-by: Gregory Price (Meta) <gourry at gourry.net>
---
tools/testing/vma/tests/merge.c | 87 +++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index b26f1a66a1707..fcc052be15601 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -1488,8 +1488,95 @@ static bool test_expand_only_mode(void)
return true;
}
+static unsigned long split_checked_addr;
+static unsigned int split_checks;
+static bool split_allowed;
+
+static int merge_may_split(struct vm_area_struct *vma, unsigned long addr)
+{
+ split_checked_addr = addr;
+ split_checks++;
+ return split_allowed ? 0 : -EINVAL;
+}
+
+static const struct vm_operations_struct merge_split_ops = {
+ .may_split = merge_may_split,
+};
+
+static bool check_partial_merge_split(bool left_edge, bool allow)
+{
+ struct mm_struct mm = {};
+ vma_flags_t left_flags = mk_vma_flags(VMA_READ_BIT, VMA_MAYREAD_BIT,
+ VMA_MAYWRITE_BIT);
+ vma_flags_t right_flags = mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT,
+ VMA_MAYREAD_BIT, VMA_MAYWRITE_BIT);
+ struct vm_area_struct *left = alloc_and_link_vma(&mm, 0x1000, 0x3000,
+ 1, left_flags);
+ struct vm_area_struct *right = alloc_and_link_vma(&mm, 0x3000, 0x5000,
+ 3, right_flags);
+ struct vm_area_struct *middle = left_edge ? right : left;
+ unsigned long start = left_edge ? 0x3000 : 0x2000;
+ unsigned long end = left_edge ? 0x4000 : 0x3000;
+ vma_flags_t flags = left_edge ? left_flags : right_flags;
+ struct vm_area_struct *result;
+ VMA_ITERATOR(vmi, &mm, start);
+
+ left->vm_ops = &merge_split_ops;
+ right->vm_ops = &merge_split_ops;
+ split_checks = 0;
+ split_allowed = allow;
+ result = vma_modify_flags(&vmi, left, middle, start, end, &flags);
+ ASSERT_TRUE(split_checks > 0);
+ ASSERT_EQ(split_checked_addr, left_edge ? end : start);
+ if (allow) {
+ ASSERT_EQ(result, left_edge ? left : right);
+ ASSERT_EQ(left->vm_end, left_edge ? end : start);
+ ASSERT_EQ(right->vm_start, left_edge ? end : start);
+ } else {
+ ASSERT_EQ(PTR_ERR(result), -EINVAL);
+ ASSERT_EQ(left->vm_end, 0x3000);
+ ASSERT_EQ(right->vm_start, 0x3000);
+ ASSERT_EQ(mm.map_count, 2);
+ }
+ cleanup_mm(&mm, &vmi);
+ return true;
+}
+
+static bool test_partial_merge_may_split(void)
+{
+ ASSERT_TRUE(check_partial_merge_split(true, false));
+ ASSERT_TRUE(check_partial_merge_split(false, false));
+ ASSERT_TRUE(check_partial_merge_split(true, true));
+ ASSERT_TRUE(check_partial_merge_split(false, true));
+ return true;
+}
+
+static bool test_full_merge_no_split(void)
+{
+ struct mm_struct mm = {};
+ vma_flags_t flags = mk_vma_flags(VMA_READ_BIT, VMA_MAYREAD_BIT);
+ struct vm_area_struct *left = alloc_and_link_vma(&mm, 0x1000, 0x3000,
+ 1, flags);
+ struct vm_area_struct *right = alloc_and_link_vma(&mm, 0x3000, 0x5000,
+ 3, flags);
+ VMA_ITERATOR(vmi, &mm, 0x3000);
+
+ left->vm_ops = &merge_split_ops;
+ right->vm_ops = &merge_split_ops;
+ split_checks = 0;
+ split_allowed = false;
+ ASSERT_EQ(vma_modify_flags(&vmi, left, right, 0x3000, 0x5000, &flags), left);
+ ASSERT_EQ(split_checks, 0);
+ ASSERT_EQ(left->vm_start, 0x1000);
+ ASSERT_EQ(left->vm_end, 0x5000);
+ cleanup_mm(&mm, &vmi);
+ return true;
+}
+
static void run_merge_tests(int *num_tests, int *num_fail)
{
+ TEST(partial_merge_may_split);
+ TEST(full_merge_no_split);
/* Very simple tests to kick the tyres. */
TEST(simple_merge);
TEST(simple_modify);
--
Gregory Price <gourry at gourry.net>
More information about the kexec
mailing list