From nobody Mon Apr 6 19:36:58 2026 Received: from mail-pl1-f175.google.com (mail-pl1-f175.google.com [209.85.214.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1265E1B4257 for ; Wed, 18 Mar 2026 04:18:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773807496; cv=none; b=lbuRK8tNuJVaO4YsaDQLcms61A8f41anbAj6mHXyWHJZPnEe+EJE21lVkrtdZqQG7ykQ8hMHDfz92ZsLbPpCN/51CHib2KJ9GyMWZ1G5ofSV3Wow4NfteQg2TZKNPpMcu/k17wErQEzMQ/HOCuGKm1WcgdBsCrmzuOVplnIKgT0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773807496; c=relaxed/simple; bh=cU4xf4Eh+lqYYN+H3LadtDOPt9AQFQm4099xMMpjf+0=; h=Date:From:To:Cc:Subject:Message-ID:MIME-Version:Content-Type: Content-Disposition; b=AjdWNSr/0C9IAvluC7D56Pcbhu06QdHE693soByBSGg6VArgKnhuWMF5ybIONjQNvhl3jS3zojgBMA4CCMbN63YifxNIvVTljbqL51MFb613vPdnzv+w3xBk9HFRtVZ+FbJOo1ytj/MTuLyPrbhMLLYqaxqUchCVjifWQZoT6CY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=chrisdown.name; spf=pass smtp.mailfrom=chrisdown.name; dkim=pass (1024-bit key) header.d=chrisdown.name header.i=@chrisdown.name header.b=G5sk7Fz6; arc=none smtp.client-ip=209.85.214.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=chrisdown.name Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chrisdown.name Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chrisdown.name header.i=@chrisdown.name header.b="G5sk7Fz6" Received: by mail-pl1-f175.google.com with SMTP id d9443c01a7336-2b0484a6de4so1376005ad.3 for ; Tue, 17 Mar 2026 21:18:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chrisdown.name; s=google; t=1773807494; x=1774412294; darn=vger.kernel.org; h=user-agent:content-disposition:mime-version:message-id:subject:cc :to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=3O/0uI2fOT2z8JCTJP8YkN4bS7/cK07tAt5pbKhSODY=; b=G5sk7Fz6CxZzcqHOcP+2/xj5F9MNjzbOr82l4Hneu4J6DPyMH+sziS3axWwvD2Ifvr eiAw7GFyo5grF1ARDYHq3wjhVltItZiraRPYtNn2q4VviNZmCJyIff1Xm0UlWUbtn62d WXfjU77b1zLd60M9vM9cC9VjlqbJCuWz6oCmM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773807494; x=1774412294; h=user-agent:content-disposition:mime-version:message-id:subject:cc :to:from:date:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=3O/0uI2fOT2z8JCTJP8YkN4bS7/cK07tAt5pbKhSODY=; b=XnHuFqyvP3ZzHBpNj2cwUio8Ko3YlGSx1gJyKjscdfkZyQYWoCPCNCLCQl2kZxK7C2 ppSWfC0oMDmFCDQjGAXygI2DOq5/ZORRkV8EJ0LOpWvxqXmBd7fGA8sKErVriIsLyozH ZPuRbZynK1XzOoDXoAPjz1FDxExN3yKAdR7ydv8KRtbpYyJr9eajqiixYfOtiY/FduOV ai/7j6KV6y0zQcr/jB4b4gWEUh5tQkFJNQ7/iw2TkrCvf3U/YxleTe6FQa+gNskUIS0U rzJeAvhM7WZUuHla8bo7Dl9nXjSoKGsW9S9j730MX1WGoHQ5XEH6FtNKNnMmN9Qw9gGO qFjw== X-Forwarded-Encrypted: i=1; AJvYcCX2hOUb5Uv+GVir9I3F3iXx2HkRa5kNrMmFziPwbhtUsfmsSDCkyOwv/zeBLCNyIZLvfCY0J9GqmiJrc2o=@vger.kernel.org X-Gm-Message-State: AOJu0Yxl9mwS8zL2/wnVPEg5rLU/k3dz2VnNhNz35+BAD5U8BVMQfmFx UCNIvHUsHakdWH0xiZQQaYT2HDVPL9JMC08ojfxBZzQ82uDd1WqllgQ5OAqLY1tX9GY= X-Gm-Gg: ATEYQzzCHqhazfB2NhoEtO7Y+aZhRauBJ41iYoYpN/FV0VC5ojJHTQFGIFOmzniipTX e8Ye/aWhS7byihkYHxIXQG0WIRGop3yxA2p+R7PL665Ptbhc3HLNtXqpXEGjy3Tl0B/0w+IzKNY JBDu5EVaoNJP+kkIwRMSfH0YahHOo1uvPjS+/4wI5nTxLl9qttBNsWB250An7x4EbhAkyhIn84O e5govqrStHIWFIDW9D9ddNY61n43+IvijipVWRtCcKU7pADzRMJnbq3K5U+mgeIHQx9fP3f74Dw sXAWZchfuRe3/mycBHZtSbv30CkGfRCb0/zWttkJhvI0jBhnZyACkU7PE+6cANt2I300Nlm9SSW VBKImB96DSnpzD+FILf7idfkjl1wLltJr48Ec48e+TruWguZS5PCbQKYVUF577mbIC/hJAAMEYe mdv0bwC/C7IHsNGJX8/k/7 X-Received: by 2002:a17:903:1666:b0:2ae:3afc:eb42 with SMTP id d9443c01a7336-2b06e3f7ce6mr19434655ad.38.1773807494369; Tue, 17 Mar 2026 21:18:14 -0700 (PDT) Received: from localhost ([116.86.198.140]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b06e632418sm12962395ad.81.2026.03.17.21.18.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 17 Mar 2026 21:18:13 -0700 (PDT) Date: Wed, 18 Mar 2026 12:18:11 +0800 From: Chris Down To: Andrew Morton Cc: Mike Rapoport , Peter Xu , David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Suren Baghdasaryan , Michal Hocko , Shuah Khan , Matthew Wilcox , kernel-team@fb.com, linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3] selftests/mm: Add UFFDIO_MOVE huge zeropage PMD regression test Message-ID: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/2.3 (50e3b1f3) (2026-01-25) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The existing uffd-unit-tests move-pmd coverage exercises PMD-sized UFFDIO_MOVE on anonymous THPs, but it does not force the huge zeropage PMD path in move_pages_huge_pmd(). Add a dedicated anonymous UFFDIO_MOVE PMD test that exercises this relatively comprehensively. Signed-off-by: Chris Down Reviewed-by: Mike Rapoport (Microsoft) --- tools/testing/selftests/mm/uffd-unit-tests.c | 100 +++++++++++++++++++ tools/testing/selftests/mm/vm_util.c | 12 +++ tools/testing/selftests/mm/vm_util.h | 1 + 3 files changed, 113 insertions(+) diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/s= elftests/mm/uffd-unit-tests.c index 6f5e404a446c..9cc4cbab9a23 100644 --- a/tools/testing/selftests/mm/uffd-unit-tests.c +++ b/tools/testing/selftests/mm/uffd-unit-tests.c @@ -1227,6 +1227,99 @@ static void uffd_move_pmd_test(uffd_global_test_opts= _t *gopts, uffd_test_args_t uffd_move_pmd_handle_fault); } =20 +static void uffd_move_pmd_huge_zeropage_test(uffd_global_test_opts_t *gopt= s, + uffd_test_args_t *targs) +{ + unsigned long pmd_size =3D read_pmd_pagesize(); + unsigned long pmd_pages; + unsigned long bytes =3D gopts->nr_pages * gopts->page_size; + char *orig_area_src =3D gopts->area_src, *orig_area_dst =3D gopts->area_d= st; + char *aligned_src, *aligned_dst; + unsigned long src_offs, dst_offs, max_offs; + pthread_t uffd_mon; + struct uffd_args args =3D { 0 }; + char c =3D '\0'; + int pagemap_fd; + + if (pmd_size <=3D gopts->page_size) { + uffd_test_skip("huge page size is 0, feature missing?"); + return; + } + if (!detect_huge_zeropage()) { + uffd_test_skip("transparent huge zeropage disabled"); + return; + } + + pmd_pages =3D pmd_size / gopts->page_size; + if (bytes < pmd_size) { + uffd_test_skip("not enough pages for one PMD-sized move"); + return; + } + + aligned_src =3D ALIGN_UP(orig_area_src, pmd_size); + aligned_dst =3D ALIGN_UP(orig_area_dst, pmd_size); + src_offs =3D (aligned_src - orig_area_src) / gopts->page_size; + dst_offs =3D (aligned_dst - orig_area_dst) / gopts->page_size; + max_offs =3D src_offs > dst_offs ? src_offs : dst_offs; + if (max_offs + pmd_pages > gopts->nr_pages) { + uffd_test_skip("could not find aligned PMD-sized src/dst window"); + return; + } + + if (madvise(orig_area_dst, bytes, MADV_HUGEPAGE)) + err("madvise(MADV_HUGEPAGE) failure"); + if (madvise(orig_area_src, bytes, MADV_DONTFORK)) + err("madvise(MADV_DONTFORK) failure"); + if (madvise(aligned_src, pmd_size, MADV_DONTNEED)) + err("madvise(MADV_DONTNEED) failure"); + + /* Fault in a PMD-sized huge zeropage mapping in the source. */ + force_read_pages(aligned_src, pmd_pages, gopts->page_size); + + pagemap_fd =3D pagemap_open(); + if (!pagemap_is_huge_zero(pagemap_fd, aligned_src)) { + close(pagemap_fd); + uffd_test_skip("could not fault in the huge zeropage"); + return; + } + gopts->area_src =3D aligned_src; + gopts->area_dst =3D aligned_dst; + + if (uffd_register(gopts->uffd, gopts->area_dst, pmd_size, true, false, fa= lse)) + err("register failure"); + + args.gopts =3D gopts; + args.handle_fault =3D uffd_move_pmd_handle_fault; + if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args)) + err("uffd_poll_thread create"); + + /* + * One fault on dst should trigger a single PMD-sized UFFDIO_MOVE from + * the huge zeropage PMD we populated in the source. + */ + force_read_pages(gopts->area_dst, pmd_pages, gopts->page_size); + + if (write(gopts->pipefd[1], &c, sizeof(c)) !=3D sizeof(c)) + err("pipe write"); + if (pthread_join(uffd_mon, NULL)) + err("join() failed"); + + if (args.missing_faults !=3D 1 || args.minor_faults !=3D 0) { + uffd_test_fail("stats check error"); + } else if (!pagemap_is_huge_zero(pagemap_fd, gopts->area_dst)) { + uffd_test_fail("moved destination is not a huge zeropage PMD"); + } else if (!check_huge_anon(gopts->area_dst, 0, pmd_size)) { + /* vm_normal_page_pmd() must continue to treat the moved PMD as special.= */ + uffd_test_fail("moved huge zeropage PMD counted as AnonHugePages"); + } else { + uffd_test_pass(); + } + + gopts->area_src =3D orig_area_src; + gopts->area_dst =3D orig_area_dst; + close(pagemap_fd); +} + static void uffd_move_pmd_split_test(uffd_global_test_opts_t *gopts, uffd_= test_args_t *targs) { if (madvise(gopts->area_dst, gopts->nr_pages * gopts->page_size, MADV_NOH= UGEPAGE)) @@ -1550,6 +1643,13 @@ uffd_test_case_t uffd_tests[] =3D { .uffd_feature_required =3D UFFD_FEATURE_MOVE, .test_case_ops =3D &uffd_move_test_pmd_case_ops, }, + { + .name =3D "move-pmd-huge-zeropage", + .uffd_fn =3D uffd_move_pmd_huge_zeropage_test, + .mem_targets =3D MEM_ANON, + .uffd_feature_required =3D UFFD_FEATURE_MOVE, + .test_case_ops =3D &uffd_move_test_pmd_case_ops, + }, { .name =3D "move-pmd-split", .uffd_fn =3D uffd_move_pmd_split_test, diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests= /mm/vm_util.c index a6d4ff7dfdc0..55cc4cc058a2 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -120,6 +120,18 @@ bool pagemap_is_populated(int fd, char *start) PAGE_IS_PRESENT | PAGE_IS_SWAPPED); } =20 +bool pagemap_is_huge_zero(int fd, char *start) +{ + uint64_t categories; + + if (!pagemap_scan_supported(fd, start)) + return false; + + categories =3D pagemap_scan_get_categories(fd, start); + return (categories & (PAGE_IS_PRESENT | PAGE_IS_PFNZERO | PAGE_IS_HUGE)) = =3D=3D + (PAGE_IS_PRESENT | PAGE_IS_PFNZERO | PAGE_IS_HUGE); +} + unsigned long pagemap_get_pfn(int fd, char *start) { uint64_t entry =3D pagemap_get_entry(fd, start); diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests= /mm/vm_util.h index e9c4e24769c1..e0b6ae1b12f5 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -85,6 +85,7 @@ uint64_t pagemap_get_entry(int fd, char *start); bool pagemap_is_softdirty(int fd, char *start); bool pagemap_is_swapped(int fd, char *start); bool pagemap_is_populated(int fd, char *start); +bool pagemap_is_huge_zero(int fd, char *start); unsigned long pagemap_get_pfn(int fd, char *start); void clear_softdirty(void); bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t le= n); base-commit: f0caa1d49cc07b30a7e2f104d3853ec6dc1c3cad --=20 2.53.0