* [PATCH 1/3] mm/huge_memory: allow splitting mappingless swapcache folios
2026-08-05 11:18 [PATCH 0/3] mm: support splitting mappingless swapcache folios Shivam Kalra via B4 Relay
@ 2026-08-05 11:18 ` Shivam Kalra via B4 Relay
2026-08-05 11:18 ` [PATCH 2/3] mm: add KUnit coverage for " Shivam Kalra via B4 Relay
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-08-05 11:18 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Nico Pache
Cc: linux-mm, linux-kernel, linux-kselftest, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
A shmem folio is removed from its page cache when it is written to swap.
While it remains in the swap cache, it has no address_space mapping, so
folio_check_splittable() mistakes it for a truncated folio and rejects the
split with -EBUSY.
Allow a mappingless folio when it is in the swap cache. Initialize the
XArray state without an address-space mapping, then assign the XArray only
for mapped file folios. The split path already replaces each split folio
in the swap cache while holding the swap-cluster lock.
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
mm/huge_memory.c | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 804b8f6aa557..ecfe40b1400b 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3882,12 +3882,10 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
/*
* Folios that just got truncated cannot get split. Signal to the
- * caller that there was a race.
- *
- * TODO: this will also currently refuse folios without a mapping in the
- * swapcache (shmem or to-be-anon folios).
+ * caller that there was a race. A mappingless swapcache folio can be
+ * either shmem or not yet associated with an anon_vma, and is valid.
*/
- if (!folio->mapping && !folio_test_anon(folio))
+ if (!folio->mapping && !folio_test_swapcache(folio))
return -EBUSY;
/* order-1 is not supported for anonymous THP. */
@@ -4022,10 +4020,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
if (do_lru)
lru_add_split_folio(folio, new_folio, lruvec, list);
- /*
- * Anonymous folio with swap cache.
- * NOTE: shmem in swap cache is not supported yet.
- */
+ /* Folio in the swap cache. */
if (ci) {
__swap_cache_replace_folio(ci, folio, new_folio);
continue;
@@ -4103,7 +4098,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
struct page *split_at, struct page *lock_at,
struct list_head *list, enum split_type split_type)
{
- XA_STATE(xas, &folio->mapping->i_pages, folio->index);
+ XA_STATE(xas, NULL, folio->index);
struct folio *end_folio = folio_next(folio);
bool is_anon = folio_test_anon(folio);
struct mem_cgroup *memcg, *old_memcg;
@@ -4158,11 +4153,12 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
}
anon_vma_lock_write(anon_vma);
mapping = NULL;
- } else {
+ } else if (folio->mapping) {
unsigned int min_order;
gfp_t gfp;
mapping = folio->mapping;
+ xas.xa = &mapping->i_pages;
min_order = mapping_min_folio_order(mapping);
if (new_order < min_order) {
ret = -EINVAL;
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/3] mm: add KUnit coverage for mappingless swapcache folios
2026-08-05 11:18 [PATCH 0/3] mm: support splitting mappingless swapcache folios Shivam Kalra via B4 Relay
2026-08-05 11:18 ` [PATCH 1/3] mm/huge_memory: allow " Shivam Kalra via B4 Relay
@ 2026-08-05 11:18 ` Shivam Kalra via B4 Relay
2026-08-05 11:18 ` [PATCH 3/3] selftests/mm: test hwpoison recovery of mappingless swapcache THPs Shivam Kalra via B4 Relay
2026-08-05 12:03 ` [PATCH 0/3] mm: support splitting mappingless swapcache folios Zi Yan
3 siblings, 0 replies; 6+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-08-05 11:18 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Nico Pache
Cc: linux-mm, linux-kernel, linux-kselftest, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
Add focused coverage for the folio_check_splittable() eligibility checks.
Verify that an ordinary mappingless folio is rejected, a mappingless
swapcache folio is accepted, and unsupported target orders and split types
remain rejected.
Build the test only when KUnit is built in because
folio_check_splittable() is intentionally not exported.
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
mm/Kconfig | 14 ++++++++++++
mm/Makefile | 1 +
mm/tests/folio_split_kunit.c | 52 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+)
diff --git a/mm/Kconfig b/mm/Kconfig
index 331daf7fcfab..164dc1438900 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1503,6 +1503,20 @@ config LAZY_MMU_MODE_KUNIT_TEST
If unsure, say N.
+config FOLIO_SPLIT_KUNIT_TEST
+ bool "KUnit tests for folio splitting" if !KUNIT_ALL_TESTS
+ depends on KUNIT=y
+ depends on TRANSPARENT_HUGEPAGE
+ depends on SWAP
+ default KUNIT_ALL_TESTS
+ help
+ Enable this option to test folio split eligibility checks. The tests
+ verify support for mappingless folios in the swap cache and ensure
+ that unsupported target orders and split types are still rejected.
+ The tests are built into the kernel and run during KUnit execution.
+
+ If unsure, say N.
+
source "mm/damon/Kconfig"
endmenu
diff --git a/mm/Makefile b/mm/Makefile
index ab37ef428d98..f09057b40e1a 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -146,4 +146,5 @@ obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o
obj-$(CONFIG_EXECMEM) += execmem.o
obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) += tests/lazy_mmu_mode_kunit.o
+obj-$(CONFIG_FOLIO_SPLIT_KUNIT_TEST) += tests/folio_split_kunit.o
obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o
diff --git a/mm/tests/folio_split_kunit.c b/mm/tests/folio_split_kunit.c
new file mode 100644
index 000000000000..f91c1a320bbf
--- /dev/null
+++ b/mm/tests/folio_split_kunit.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <kunit/test.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/huge_mm.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+
+static void folio_check_splittable_mappingless_swapcache(struct kunit *test)
+{
+ struct folio *folio;
+ int ret;
+
+ folio = folio_alloc(GFP_KERNEL, 2);
+ KUNIT_ASSERT_NOT_NULL(test, folio);
+ folio_lock(folio);
+
+ KUNIT_EXPECT_PTR_EQ(test, folio->mapping, NULL);
+ ret = folio_check_splittable(folio, 0, SPLIT_TYPE_UNIFORM);
+ KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+
+ /* Only the eligibility check is exercised here. */
+ folio_set_swapbacked(folio);
+ folio_set_swapcache(folio);
+
+ ret = folio_check_splittable(folio, 0, SPLIT_TYPE_UNIFORM);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = folio_check_splittable(folio, 1, SPLIT_TYPE_UNIFORM);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ ret = folio_check_splittable(folio, 0, SPLIT_TYPE_NON_UNIFORM);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ folio_clear_swapcache(folio);
+ folio_clear_swapbacked(folio);
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
+static struct kunit_case folio_split_test_cases[] = {
+ KUNIT_CASE(folio_check_splittable_mappingless_swapcache),
+ {}
+};
+
+static struct kunit_suite folio_split_test_suite = {
+ .name = "folio_split",
+ .test_cases = folio_split_test_cases,
+};
+
+kunit_test_suite(folio_split_test_suite);
+
+MODULE_DESCRIPTION("Tests for folio splitting");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/3] selftests/mm: test hwpoison recovery of mappingless swapcache THPs
2026-08-05 11:18 [PATCH 0/3] mm: support splitting mappingless swapcache folios Shivam Kalra via B4 Relay
2026-08-05 11:18 ` [PATCH 1/3] mm/huge_memory: allow " Shivam Kalra via B4 Relay
2026-08-05 11:18 ` [PATCH 2/3] mm: add KUnit coverage for " Shivam Kalra via B4 Relay
@ 2026-08-05 11:18 ` Shivam Kalra via B4 Relay
2026-08-05 12:03 ` [PATCH 0/3] mm: support splitting mappingless swapcache folios Zi Yan
3 siblings, 0 replies; 6+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-08-05 11:18 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Nico Pache
Cc: linux-mm, linux-kernel, linux-kselftest, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
Exercise memory_failure() on a tail page of a shmem THP after MADV_PAGEOUT
has made it a mappingless swapcache folio. Verify that the folio is split,
only the target PFN is poisoned, and the remaining mapping can be faulted
back in with its original data.
Use a temporary loop-backed swap device so the test also works when the
kselftest directory is on 9p or NFS. Register the wrapper in the
memory-failure test suite and clean up the loop device and swap file on
every exit path.
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
tools/testing/selftests/mm/Makefile | 2 +
tools/testing/selftests/mm/run_vmtests.sh | 1 +
.../selftests/mm/split_hwpoison_swapcache.sh | 57 +++++
.../selftests/mm/split_hwpoison_swapcache_test.c | 261 +++++++++++++++++++++
4 files changed, 321 insertions(+)
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index 2d5366196e30..f0ae0b5685a6 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -93,6 +93,7 @@ TEST_GEN_FILES += uffd-stress
TEST_GEN_FILES += uffd-unit-tests
TEST_GEN_FILES += uffd-wp-mremap
TEST_GEN_FILES += split_huge_page_test
+TEST_GEN_FILES += split_hwpoison_swapcache_test
TEST_GEN_FILES += ksm_tests
TEST_GEN_FILES += ksm_functional_tests
TEST_GEN_FILES += mdwe_test
@@ -177,6 +178,7 @@ TEST_PROGS += ksft_vmalloc.sh
TEST_FILES := test_vmalloc.sh
TEST_FILES += test_hmm.sh
TEST_FILES += va_high_addr_switch.sh
+TEST_FILES += split_hwpoison_swapcache.sh
TEST_FILES += charge_reserved_hugetlb.sh
TEST_FILES += hugetlb_reparenting_test.sh
TEST_FILES += test_page_frag.sh
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 687d115e3bd8..39c8cf92ccc1 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -459,6 +459,7 @@ CATEGORY="page_frag" run_test ./test_page_frag.sh nonaligned
CATEGORY="rmap" run_test ./rmap
CATEGORY="memory-failure" run_test ./memory-failure
+CATEGORY="memory-failure" run_test ./split_hwpoison_swapcache.sh
echo "SUMMARY: PASS=${count_pass} SKIP=${count_skip} FAIL=${count_fail}" | tap_prefix
echo "1..${count_total}" | tap_output
diff --git a/tools/testing/selftests/mm/split_hwpoison_swapcache.sh b/tools/testing/selftests/mm/split_hwpoison_swapcache.sh
new file mode 100755
index 000000000000..fe986a09f697
--- /dev/null
+++ b/tools/testing/selftests/mm/split_hwpoison_swapcache.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Setup swap and run the mappingless swapcache hwpoison split test
+
+ksft_skip=4
+swap_file=
+loop_dev=
+
+skip() {
+ echo "skip: $*"
+ exit "$ksft_skip"
+}
+
+cleanup() {
+ if [ -n "$loop_dev" ]; then
+ swapoff "$loop_dev" 2>/dev/null || true
+ losetup -d "$loop_dev" 2>/dev/null || true
+ fi
+ if [ -n "$swap_file" ]; then
+ rm -f "$swap_file"
+ fi
+}
+
+trap cleanup EXIT
+trap 'exit 1' HUP INT TERM
+
+if [ "$(id -u)" -ne 0 ]; then
+ skip "must run as root"
+fi
+
+if [ ! -w /sys/kernel/debug/hwpoison/corrupt-pfn ]; then
+ skip "hwpoison injection is not available"
+fi
+
+# Use a loop device because the kselftest directory may be on 9p or NFS.
+if ! swap_file=$(mktemp /tmp/hwpoison_swap.XXXXXX); then
+ echo "FAIL: could not create a temporary swap file"
+ exit 1
+fi
+if ! dd if=/dev/zero of="$swap_file" bs=1M count=128 status=none; then
+ echo "FAIL: could not initialize the temporary swap file"
+ exit 1
+fi
+if ! loop_dev=$(losetup --find --show "$swap_file"); then
+ skip "no loop device is available"
+fi
+if ! mkswap "$loop_dev" >/dev/null; then
+ echo "FAIL: could not initialize swap on $loop_dev"
+ exit 1
+fi
+if ! swapon "$loop_dev"; then
+ echo "FAIL: could not enable swap on $loop_dev"
+ exit 1
+fi
+
+"$(dirname "$(readlink -f "$0")")"/split_hwpoison_swapcache_test
diff --git a/tools/testing/selftests/mm/split_hwpoison_swapcache_test.c b/tools/testing/selftests/mm/split_hwpoison_swapcache_test.c
new file mode 100644
index 000000000000..2d956ae517cf
--- /dev/null
+++ b/tools/testing/selftests/mm/split_hwpoison_swapcache_test.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test that memory failure can split a mappingless shmem THP in swap cache.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "../kselftest.h"
+#include "hugepage_settings.h"
+#include "vm_util.h"
+
+#define HWPOISON_PATH "/sys/kernel/debug/hwpoison/corrupt-pfn"
+
+#define KPF_MMAP BIT_ULL(11)
+#define KPF_SWAPCACHE BIT_ULL(13)
+#define KPF_SWAPBACKED BIT_ULL(14)
+
+#define TEST_BYTE 0x5a
+#define PAGEOUT_RETRIES 100
+#define PAGEOUT_DELAY_US 100000
+
+static bool is_swapcache_thp(uint64_t flags, bool head)
+{
+ uint64_t required = KPF_SWAPCACHE | KPF_SWAPBACKED | KPF_THP;
+
+ required |= head ? KPF_COMPOUND_HEAD : KPF_COMPOUND_TAIL;
+ return (flags & required) == required && !(flags & KPF_MMAP);
+}
+
+static int wait_for_swapcache_thp(unsigned long head_pfn, unsigned long target_pfn,
+ int kpageflags_fd)
+{
+ uint64_t head_flags = 0;
+ uint64_t target_flags = 0;
+ int i;
+
+ for (i = 0; i < PAGEOUT_RETRIES; i++) {
+ if (pageflags_get(head_pfn, kpageflags_fd, &head_flags) ||
+ pageflags_get(target_pfn, kpageflags_fd, &target_flags))
+ return -1;
+
+ if (is_swapcache_thp(head_flags, true) &&
+ is_swapcache_thp(target_flags, false))
+ return 0;
+
+ usleep(PAGEOUT_DELAY_US);
+ }
+
+ ksft_print_msg("Swapcache THP flags: head=%#llx target=%#llx\n",
+ (unsigned long long)head_flags,
+ (unsigned long long)target_flags);
+ return 1;
+}
+
+static bool folio_was_split(unsigned long head_pfn, unsigned long target_pfn,
+ unsigned long nr_pages, int kpageflags_fd)
+{
+ const uint64_t compound = KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL;
+ uint64_t flags;
+ unsigned long i;
+
+ for (i = 0; i < nr_pages; i++) {
+ if (pageflags_get(head_pfn + i, kpageflags_fd, &flags))
+ return false;
+ if (flags & compound) {
+ ksft_print_msg("PFN %#lx is still compound (flags=%#llx)\n",
+ head_pfn + i,
+ (unsigned long long)flags);
+ return false;
+ }
+ if ((head_pfn + i == target_pfn) != !!(flags & KPF_HWPOISON)) {
+ ksft_print_msg(
+ "Unexpected HWPoison state at PFN %#lx (flags=%#llx)\n",
+ head_pfn + i, (unsigned long long)flags);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool mapping_has_expected_data(const unsigned char *addr, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ if (addr[i] != TEST_BYTE) {
+ ksft_print_msg("Data mismatch at offset %#zx: %#x != %#x\n",
+ i, addr[i], TEST_BYTE);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int inject_hwpoison(unsigned long pfn)
+{
+ char buf[32];
+ ssize_t written;
+ int fd;
+ int len;
+ int saved_errno;
+
+ fd = open(HWPOISON_PATH, O_WRONLY);
+ if (fd < 0)
+ return -errno;
+
+ len = snprintf(buf, sizeof(buf), "%#lx\n", pfn);
+ written = write(fd, buf, len);
+ saved_errno = errno;
+ close(fd);
+
+ if (written != len)
+ return written < 0 ? -saved_errno : -EIO;
+
+ return 0;
+}
+
+int main(void)
+{
+ struct thp_settings settings;
+ unsigned long target_pfn;
+ unsigned long head_pfn;
+ unsigned long nr_pages;
+ unsigned long page_size;
+ unsigned long pmd_size;
+ unsigned char *mapping;
+ unsigned char *addr;
+ uint64_t flags;
+ bool poisoned = false;
+ bool pass = false;
+ int kpageflags_fd = -1;
+ int pagemap_fd = -1;
+ int memfd = -1;
+ int ret;
+
+ ksft_print_header();
+ ksft_set_plan(1);
+
+ if (geteuid())
+ ksft_exit_skip("Please run the test as root\n");
+
+ pmd_size = read_pmd_pagesize();
+ if (!thp_available() || !pmd_size)
+ ksft_exit_skip("Transparent Huge Pages are not available\n");
+
+ if (access(HWPOISON_PATH, W_OK))
+ ksft_exit_skip("HWPoison injection is not available\n");
+
+ page_size = getpagesize();
+ if (pmd_size % page_size)
+ ksft_exit_fail_msg("Invalid PMD page size %#lx\n", pmd_size);
+ nr_pages = pmd_size / page_size;
+
+ thp_save_settings();
+ thp_read_settings(&settings);
+ settings.shmem_enabled = SHMEM_ADVISE;
+ thp_write_settings(&settings);
+
+ memfd = memfd_create("split_hwpoison_swapcache", MFD_CLOEXEC);
+ if (memfd < 0)
+ ksft_exit_fail_perror("memfd_create");
+ if (ftruncate(memfd, pmd_size))
+ ksft_exit_fail_perror("ftruncate");
+
+ /* Reserve enough space to obtain a PMD-aligned file mapping. */
+ mapping = mmap(NULL, 2 * pmd_size, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (mapping == MAP_FAILED)
+ ksft_exit_fail_perror("mmap");
+ addr = (unsigned char *)(((uintptr_t)mapping + pmd_size - 1) &
+ ~(pmd_size - 1));
+ if (mmap(addr, pmd_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, memfd, 0) == MAP_FAILED)
+ ksft_exit_fail_perror("mmap");
+
+ if (madvise(addr, pmd_size, MADV_HUGEPAGE))
+ ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)");
+ memset(addr, TEST_BYTE, pmd_size);
+
+ if (!check_huge_shmem(addr, 1, pmd_size))
+ ksft_exit_skip("Failed to allocate a PMD-sized shmem THP\n");
+
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0)
+ ksft_exit_fail_perror("open(/proc/self/pagemap)");
+ kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
+ if (kpageflags_fd < 0)
+ ksft_exit_fail_perror("open(/proc/kpageflags)");
+
+ head_pfn = pagemap_get_pfn(pagemap_fd, (char *)addr);
+ if (head_pfn == -1UL)
+ ksft_exit_fail_msg("Failed to obtain the shmem THP PFN\n");
+
+ /* Poison a tail page so success necessarily requires a real split. */
+ target_pfn = head_pfn + nr_pages / 2;
+ if (pageflags_get(head_pfn, kpageflags_fd, &flags) ||
+ (flags & (KPF_THP | KPF_COMPOUND_HEAD)) !=
+ (KPF_THP | KPF_COMPOUND_HEAD))
+ ksft_exit_fail_msg("PFN %#lx is not a THP head\n", head_pfn);
+ if (pageflags_get(target_pfn, kpageflags_fd, &flags) ||
+ (flags & (KPF_THP | KPF_COMPOUND_TAIL)) !=
+ (KPF_THP | KPF_COMPOUND_TAIL))
+ ksft_exit_fail_msg("PFN %#lx is not a THP tail\n", target_pfn);
+
+ if (madvise(addr, pmd_size, MADV_PAGEOUT))
+ ksft_exit_skip("madvise(MADV_PAGEOUT) failed: %s\n",
+ strerror(errno));
+
+ ret = wait_for_swapcache_thp(head_pfn, target_pfn, kpageflags_fd);
+ if (ret < 0)
+ ksft_exit_fail_msg("Failed to read kpageflags\n");
+ if (ret > 0)
+ ksft_exit_skip("Failed to create a mappingless swapcache THP; "
+ "is swap enabled?\n");
+
+ ksft_print_msg("Injecting HWPoison into tail PFN %#lx of THP %#lx\n",
+ target_pfn, head_pfn);
+ ret = inject_hwpoison(target_pfn);
+ poisoned = true;
+ if (ret) {
+ ksft_print_msg("HWPoison injection failed: %s\n", strerror(-ret));
+ goto out;
+ }
+
+ if (!folio_was_split(head_pfn, target_pfn, nr_pages, kpageflags_fd))
+ goto out;
+
+ /*
+ * A clean poisoned swapcache page is discarded. Faulting the mapping
+ * back in must recover the original data from swap.
+ */
+ if (!mapping_has_expected_data(addr, pmd_size))
+ goto out;
+
+ pass = true;
+out:
+ if (poisoned && unpoison_memory(target_pfn)) {
+ ksft_print_msg("Failed to unpoison PFN %#lx\n", target_pfn);
+ pass = false;
+ }
+ if (kpageflags_fd >= 0)
+ close(kpageflags_fd);
+ if (pagemap_fd >= 0)
+ close(pagemap_fd);
+ munmap(mapping, 2 * pmd_size);
+ close(memfd);
+
+ ksft_test_result(pass, "memory failure splits a mappingless swapcache THP\n");
+ ksft_finished();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH 0/3] mm: support splitting mappingless swapcache folios
2026-08-05 11:18 [PATCH 0/3] mm: support splitting mappingless swapcache folios Shivam Kalra via B4 Relay
` (2 preceding siblings ...)
2026-08-05 11:18 ` [PATCH 3/3] selftests/mm: test hwpoison recovery of mappingless swapcache THPs Shivam Kalra via B4 Relay
@ 2026-08-05 12:03 ` Zi Yan
2026-08-05 15:54 ` Kairui Song
3 siblings, 1 reply; 6+ messages in thread
From: Zi Yan @ 2026-08-05 12:03 UTC (permalink / raw)
To: Shivam Kalra, Kairui Song
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Baolin Wang,
Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Michal Hocko, Shuah Khan, Nico Pache, linux-mm, linux-kernel,
linux-kselftest
On 5 Aug 2026, at 7:18, Shivam Kalra via B4 Relay wrote:
> Large shmem folios lose their address-space mapping when they are written
> to swap, but remain valid members of the swap cache. Folios read from swap
> but not yet associated with an anon_vma can have the same mappingless
> swapcache state. folio_check_splittable() currently mistakes both cases for
> truncation and rejects the split with -EBUSY.
>
> Implement the longstanding TODO for this state. Allow mappingless
> swapcache folios to use the existing uniform order-0 swapcache split path,
> while continuing to reject truly truncated folios and unsupported
> higher-order or non-uniform swapcache splits.
Thank you for your patches.
As Kairui (cc’d) mentioned in [1] (see “A bit more details on this”),
we might not need to implement this split. I will let Kairui to decide
how we should deal with this patchset.
[1] https://lore.kernel.org/all/CAMgjq7CHDG8JhesSPMkn1kzG8jKb0BXO756BvxMQC_MCn2eTyA@mail.gmail.com/
>
> memory_failure() is one caller affected by the restriction: it cannot
> isolate a poisoned base page within a mappingless swapcache THP. The
> end-to-end test uses this case to exercise the complete shmem pageout,
> swapcache split, and swapin path.
>
> The first patch contains the MM implementation. The second adds focused
> KUnit coverage for the split eligibility checks. The third adds the
> end-to-end hwpoison selftest, which pages out a shmem THP, poisons a tail
> page, and verifies both isolation and the data read back from swap.
>
> Tests:
> - The split_hwpoison_swapcache kselftest passes under x86_64 QEMU
> using virtme-ng 1.41 (2 GiB RAM, 4 KiB pages).
>
> Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
> ---
> Shivam Kalra (3):
> mm/huge_memory: allow splitting mappingless swapcache folios
> mm: add KUnit coverage for mappingless swapcache folios
> selftests/mm: test hwpoison recovery of mappingless swapcache THPs
>
> mm/Kconfig | 14 ++
> mm/Makefile | 1 +
> mm/huge_memory.c | 18 +-
> mm/tests/folio_split_kunit.c | 52 ++++
> tools/testing/selftests/mm/Makefile | 2 +
> tools/testing/selftests/mm/run_vmtests.sh | 1 +
> .../selftests/mm/split_hwpoison_swapcache.sh | 57 +++++
> .../selftests/mm/split_hwpoison_swapcache_test.c | 261 +++++++++++++++++++++
> 8 files changed, 395 insertions(+), 11 deletions(-)
> ---
> base-commit: 0b53bff4fa05ff0d3ffbd3d3bb10fae69dfab498
> change-id: 20260722-b4-mappingless-swapcache-9badf123138a
>
> Best regards,
> --
> Shivam Kalra <shivamkalra98@zohomail.in>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH 0/3] mm: support splitting mappingless swapcache folios
2026-08-05 12:03 ` [PATCH 0/3] mm: support splitting mappingless swapcache folios Zi Yan
@ 2026-08-05 15:54 ` Kairui Song
0 siblings, 0 replies; 6+ messages in thread
From: Kairui Song @ 2026-08-05 15:54 UTC (permalink / raw)
To: Zi Yan
Cc: Shivam Kalra, Kairui Song, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Baolin Wang, Liam R. Howlett, Ryan Roberts,
Dev Jain, Barry Song, Lance Yang, Usama Arif, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan,
Nico Pache, linux-mm, linux-kernel, linux-kselftest
On Wed, Aug 05, 2026 at 08:03:33AM +0800, Zi Yan wrote:
> On 5 Aug 2026, at 7:18, Shivam Kalra via B4 Relay wrote:
>
> > Large shmem folios lose their address-space mapping when they are written
> > to swap, but remain valid members of the swap cache. Folios read from swap
> > but not yet associated with an anon_vma can have the same mappingless
> > swapcache state. folio_check_splittable() currently mistakes both cases for
> > truncation and rejects the split with -EBUSY.
> >
> > Implement the longstanding TODO for this state. Allow mappingless
> > swapcache folios to use the existing uniform order-0 swapcache split path,
> > while continuing to reject truly truncated folios and unsupported
> > higher-order or non-uniform swapcache splits.
>
> Thank you for your patches.
>
> As Kairui (cc’d) mentioned in [1] (see “A bit more details on this”),
> we might not need to implement this split. I will let Kairui to decide
> how we should deal with this patchset.
>
> [1] https://lore.kernel.org/all/CAMgjq7CHDG8JhesSPMkn1kzG8jKb0BXO756BvxMQC_MCn2eTyA@mail.gmail.com/
Thanks for the CC, this is actually not hard to implement as shown by
Shivam, I just found the code more and more hard to follow and fragile
as we add more logic to it. And we don't have to reject higher order
split either which can be seen easily if the code is cleaner.
Personally I think doing some cleanup first is better, I haven't post any
code as right now there doesn't seem to be much user of this. It will be
needed if more clean THP swapcache begin to show up due to things like
THP readahead for swap, which isn't here yet. I think I can send an RFC
tomorrow just for reference. I'm fine if we prefer to remove that TODO
using this smaller change first :)
^ permalink raw reply [flat|nested] 6+ messages in thread