[PATCH v5 4/4] selftests/mm: add hard memory failure anonymous 1G HugeTLB page test
From: Jiaqi Yan
Date: Sun May 31 2026 - 02:04:59 EST
Add a new testcase to validate memory failure recovery for HWPoison
anonymous 1G HugeTLB page, including proper SIGBUS delivery,
releasing a 1G HugeTLB page containing one HWPoison page to buddy
allocator, and isolation of the raw HWPoison page.
Although can be added in future, this patch does not support testing
the MADV_SOFT variant.
Signed-off-by: Jiaqi Yan <jiaqiyan@xxxxxxxxxx>
---
tools/testing/selftests/mm/memory-failure.c | 73 +++++++++++++++++++--
1 file changed, 68 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c
index 032ed952057c..ea43b2877c81 100644
--- a/tools/testing/selftests/mm/memory-failure.c
+++ b/tools/testing/selftests/mm/memory-failure.c
@@ -18,6 +18,7 @@
#include <linux/magic.h>
#include <errno.h>
+#include "hugepage_settings.h"
#include "vm_util.h"
enum inject_type {
@@ -27,6 +28,7 @@ enum inject_type {
enum result_type {
MADV_HARD_ANON,
+ MADV_HARD_ANON_HUGETLB,
MADV_HARD_CLEAN_PAGECACHE,
MADV_HARD_DIRTY_PAGECACHE,
MADV_SOFT_ANON,
@@ -47,6 +49,8 @@ FIXTURE(memory_failure)
int pagemap_fd;
int kpageflags_fd;
bool triggered;
+ /* Number of initial HugeTLB pages with default page size. */
+ unsigned long nr_hugetlb_pages;
};
FIXTURE_VARIANT(memory_failure)
@@ -157,11 +161,11 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
void *vaddr, enum result_type type, int setjmp)
{
unsigned long size;
+ unsigned long nr_hugetlb_pages;
uint64_t pfn_flags;
switch (type) {
case MADV_SOFT_ANON:
- case MADV_HARD_CLEAN_PAGECACHE:
case MADV_SOFT_CLEAN_PAGECACHE:
case MADV_SOFT_DIRTY_PAGECACHE:
/* It is not expected to receive a SIGBUS signal. */
@@ -174,6 +178,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
break;
case MADV_HARD_ANON:
+ case MADV_HARD_ANON_HUGETLB:
case MADV_HARD_DIRTY_PAGECACHE:
/* The SIGBUS signal should have been received. */
ASSERT_EQ(setjmp, 1);
@@ -183,17 +188,36 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR);
ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
ASSERT_EQ(siginfo.si_addr, vaddr);
-
- /* XXX Check backing pte is hwpoison entry when supported. */
- ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
break;
default:
SKIP(return, "unexpected inject type %d.\n", type);
}
+ if (type == MADV_HARD_ANON || type == MADV_HARD_DIRTY_PAGECACHE) {
+ /*
+ * Check backing pte is hwpoison entry when supported.
+ * Although try_to_unmap_one() also installs hwpoison entry
+ * for HugeTLB, pagemap_hugetlb_range() doesn't parse
+ * swap entries at all.
+ */
+ ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
+ }
+
/* Check if the value of HardwareCorrupted has increased. */
ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
- ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
+
+ if (type == MADV_HARD_ANON_HUGETLB) {
+ /*
+ * Only one page is hardware corrupted; the rest should all be
+ * released to buddy allocator.
+ */
+ ASSERT_EQ(size, self->corrupted_size + getpagesize() / 1024);
+ /* HugeTLB should have lost the HWPoison HugeTLB page. */
+ nr_hugetlb_pages = hugetlb_nr_default_pages();
+ ASSERT_EQ(nr_hugetlb_pages + 1, self->nr_hugetlb_pages);
+ } else {
+ ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
+ }
/* Check if HWPoison flag is set. */
ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
@@ -247,6 +271,45 @@ TEST_F(memory_failure, anon)
ASSERT_EQ(munmap(addr, self->page_size), 0);
}
+TEST_F(memory_failure, anon_hugetlb)
+{
+ char *addr;
+ int ret;
+ const unsigned long nr_alloc_hugetlb_pages = 4;
+ unsigned long alloc_size;
+
+ if (variant->type == MADV_SOFT)
+ SKIP(return, "Soft offline test is not implemented");
+
+ /* HugeTLB settings will be automatically restored when test exits. */
+ hugetlb_setup_default(nr_alloc_hugetlb_pages);
+
+ alloc_size = default_huge_page_size() * nr_alloc_hugetlb_pages;
+ self->page_size = default_huge_page_size();
+ self->nr_hugetlb_pages = hugetlb_nr_default_pages();
+
+ addr = mmap(0, alloc_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0);
+ if (addr == MAP_FAILED)
+ SKIP(return, "mmap failed, not enough memory or 1G hugetlb not supported.\n");
+ memset(addr, 0xce, alloc_size);
+
+ prepare(_metadata, self, addr);
+
+ ret = sigsetjmp(signal_jmp_buf, 1);
+ if (!self->triggered) {
+ self->triggered = true;
+ ASSERT_EQ(variant->inject(self, addr), 0);
+ FORCE_READ(*addr);
+ }
+
+ check(_metadata, self, addr, MADV_HARD_ANON_HUGETLB, ret);
+
+ cleanup(_metadata, self, addr);
+
+ ASSERT_EQ(munmap(addr, alloc_size), 0);
+}
+
static int prepare_file(const char *fname, unsigned long size)
{
int fd;
--
2.54.0.823.g6e5bcc1fc9-goog