[PATCH v2 14/20] hugetlb_vmemmap: Add fault injection for in-place vmemmap PTE updates

From: James Houghton

Date: Fri Oct 02 2026 - 20:26:30 EST


try_update_vmemmap_pte() may fail, e.g. on arm64 when the update keeps
racing with hardware access flag updates. HVO handles such failures by
rolling back the optimization, or by leaving folios partially optimized
if the rollback or a later restore fails. These paths are rare to hit
normally.

Add a fault-injection capability, fail_hugetlb_vmemmap_pte, under
CONFIG_FAIL_HUGETLB_VMEMMAP. When a fault is injected, the PTE update
fails with -EAGAIN without touching the page tables, as if the in-place
update had given up.

It can be configured through debugfs or through the
fail_hugetlb_vmemmap_pte= boot option, the latter allowing failures to
be injected when optimizing bootmem folios.

Assisted-by: LLM
Signed-off-by: James Houghton <jthoughton@xxxxxxxxxx>
---
.../fault-injection/fault-injection.rst | 6 +++
lib/Kconfig.debug | 9 +++++
mm/hugetlb_vmemmap.c | 38 ++++++++++++++++++-
3 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/Documentation/fault-injection/fault-injection.rst b/Documentation/fault-injection/fault-injection.rst
index c2d3996b5b40..403206645fa4 100644
--- a/Documentation/fault-injection/fault-injection.rst
+++ b/Documentation/fault-injection/fault-injection.rst
@@ -16,6 +16,11 @@ Available fault injection capabilities

injects page allocation failures. (alloc_pages(), get_free_pages(), ...)

+- fail_hugetlb_vmemmap_pte
+
+ injects failures of the in-place vmemmap PTE remaps done by HugeTLB vmemmap
+ optimization. (try_update_vmemmap_pte())
+
- fail_usercopy

injects failures in user memory access functions. (copy_from_user(), get_user(), ...)
@@ -263,6 +268,7 @@ use the boot option::

failslab=
fail_page_alloc=
+ fail_hugetlb_vmemmap_pte=
fail_usercopy=
fail_make_request=
fail_futex=
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 134b15a44625..96cd1f1da94a 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2066,6 +2066,15 @@ config FAIL_PAGE_ALLOC
help
Provide fault-injection capability for alloc_pages().

+config FAIL_HUGETLB_VMEMMAP
+ bool "Fault-injection capability for HugeTLB vmemmap optimization"
+ depends on FAULT_INJECTION && HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+ help
+ Provide fault-injection capability for the in-place vmemmap page
+ table updates done by HugeTLB vmemmap optimization (HVO), i.e.
+ try_update_vmemmap_pte(). This exercises the rollback and
+ partially-optimized folio paths.
+
config FAULT_INJECTION_USERCOPY
bool "Fault injection capability for usercopy functions"
depends on FAULT_INJECTION
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index fabf2b25fe59..1eca03a3def3 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -17,6 +17,7 @@
#include <linux/pgalloc.h>
#include <linux/vmemmap-optimization.h>
#include <linux/hugetlb.h>
+#include <linux/fault-inject.h>

#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
@@ -50,6 +51,39 @@ struct vmemmap_remap_walk {
unsigned long flags;
};

+#ifdef CONFIG_FAIL_HUGETLB_VMEMMAP
+static DECLARE_FAULT_ATTR(fail_hugetlb_vmemmap_pte);
+
+static int __init setup_fail_hugetlb_vmemmap_pte(char *str)
+{
+ return setup_fault_attr(&fail_hugetlb_vmemmap_pte, str);
+}
+__setup("fail_hugetlb_vmemmap_pte=", setup_fail_hugetlb_vmemmap_pte);
+
+#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
+static int __init fail_hugetlb_vmemmap_debugfs(void)
+{
+ fault_create_debugfs_attr("fail_hugetlb_vmemmap_pte", NULL,
+ &fail_hugetlb_vmemmap_pte);
+ return 0;
+}
+late_initcall(fail_hugetlb_vmemmap_debugfs);
+#endif /* CONFIG_FAULT_INJECTION_DEBUG_FS */
+
+/*
+ * Inject failures as if the in-place update lost a race too many times
+ * (see the arm64 implementations), without touching the page tables.
+ */
+static int hvo_update_vmemmap_pte(unsigned long addr, pte_t *ptep, pte_t pte)
+{
+ if (should_fail(&fail_hugetlb_vmemmap_pte, PAGE_SIZE))
+ return -EAGAIN;
+ return try_update_vmemmap_pte(addr, ptep, pte);
+}
+#else
+#define hvo_update_vmemmap_pte try_update_vmemmap_pte
+#endif /* CONFIG_FAIL_HUGETLB_VMEMMAP */
+
static int vmemmap_split_pmd(pmd_t *pmd, struct page *head, unsigned long start,
struct vmemmap_remap_walk *walk)
{
@@ -235,7 +269,7 @@ static int vmemmap_remap_pte(pte_t *pte, unsigned long addr,
entry = mk_pte(walk->vmemmap_tail, PAGE_KERNEL_RO);
}

- ret = try_update_vmemmap_pte(addr, pte, entry);
+ ret = hvo_update_vmemmap_pte(addr, pte, entry);
if (ret)
return ret;

@@ -279,7 +313,7 @@ static int vmemmap_restore_pte(pte_t *pte, unsigned long addr,
*/
smp_wmb();

- ret = try_update_vmemmap_pte(addr, pte, mk_pte(dst, PAGE_KERNEL));
+ ret = hvo_update_vmemmap_pte(addr, pte, mk_pte(dst, PAGE_KERNEL));
if (ret)
return ret;

--
2.56.0.rc1.315.gc6ed9934b7-goog