| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/01 23:43 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"MMU",
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"tlb_remove_table",
"pte_free_defer",
"zap_huge_pmd",
"tlb_remove_table_sync_rcu",
"tlb_remove_table_sync_one"
],
"KMSANReasoning": "The patch series standardizes page table freeing across architectures to use RCU-deferred freeing (unifying MMU gather and removing CONFIG_MMU_GATHER_RCU_TABLE_FREE / CONFIG_MMU_GATHER_TABLE_FREE configs). It adapts architecture-specific page table freeing routines (e.g. m68k, arc, microblaze, sh, sparc, xtensa) to use tlb_remove_ptdesc / tlb_remove_table, adds spinlocks for ptable management on m68k/sparc, and updates deferred freeing in THP (pte_free_defer).\n\nThese changes deal strictly with MMU gather lifecycle, RCU grace periods for page table memory, and synchronization of page table freeing vs lockless/RCU page table walkers. Potential bugs in these changes would manifest as use-after-free, double-free, concurrency races, or deadlocks—which are covered by KASAN and LOCKDEP. The patch does not introduce any uninitialized memory allocations, padding leaks, or uninitialized reads/copies to userspace. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch unifies and enforces RCU-deferred freeing for page table pages across all architectures by removing conditional MMU_GATHER_RCU_TABLE_FREE configuration options and standardizing on tlb_remove_table(). It also modifies THP deposited table handling in mm/huge_memory.c to use pte_free_defer() under RCU and updates architecture-specific page table removal routines. These core MM logic changes affect TLB gathering, page table tearing down, and memory reclaim paths.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/01 23:43 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 28e8e22fb2f922a5d98ac272c04a39fb5d0a7ee1\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Sep 1 23:43:52 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/mm/process_addrs.rst b/Documentation/mm/process_addrs.rst\nindex a7296f251799c..1e65b139f3559 100644\n--- a/Documentation/mm/process_addrs.rst\n+++ b/Documentation/mm/process_addrs.rst\n@@ -537,6 +537,12 @@ We establish basic locking rules when interacting with page tables:\n * When changing a page table entry the page table lock for that page table\n **must** be held, except if you can safely assume nobody can access the page\n tables concurrently (such as on invocation of :c:func:`!free_pgtables`).\n+* Page tables may be *walked* under RCU alone, as page tables are freed only\n+ after an RCU grace period has elapsed. However, any entry found must be\n+ revalidated after the page table lock is taken (such as the\n+ :c:func:`!pmd_same` recheck performed by :c:func:`!pte_offset_map_lock`)\n+ before it is acted upon. Changing an entry always requires the page table\n+ lock.\n * Reads from and writes to page table entries must be *appropriately*\n atomic. See the section on atomicity below for details.\n * Populating previously empty entries requires that the mmap or VMA locks are\ndiff --git a/arch/Kconfig b/arch/Kconfig\nindex 45c6577723623..6f7516916797e 100644\n--- a/arch/Kconfig\n+++ b/arch/Kconfig\n@@ -526,13 +526,6 @@ config HAVE_ARCH_JUMP_LABEL\n config HAVE_ARCH_JUMP_LABEL_RELATIVE\n \tbool\n \n-config MMU_GATHER_TABLE_FREE\n-\tbool\n-\n-config MMU_GATHER_RCU_TABLE_FREE\n-\tbool\n-\tselect MMU_GATHER_TABLE_FREE\n-\n config MMU_GATHER_PAGE_SIZE\n \tbool\n \n@@ -548,7 +541,6 @@ config MMU_GATHER_MERGE_VMAS\n \n config MMU_GATHER_NO_GATHER\n \tbool\n-\tdepends on MMU_GATHER_TABLE_FREE\n \n config ARCH_WANT_IRQS_OFF_ACTIVATE_MM\n \tbool\ndiff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig\nindex e53ef2d884636..9063c7bda4e41 100644\n--- a/arch/alpha/Kconfig\n+++ b/arch/alpha/Kconfig\n@@ -42,7 +42,6 @@ config ALPHA\n \tselect ARCH_STACKWALK\n \tselect CPU_NO_EFFICIENT_FFS if !ALPHA_EV67\n \tselect MMU_GATHER_NO_RANGE\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect SPARSEMEM_EXTREME if SPARSEMEM\n \tselect ZONE_DMA\n \tselect TRACE_IRQFLAGS_SUPPORT\ndiff --git a/arch/arc/include/asm/pgalloc.h b/arch/arc/include/asm/pgalloc.h\nindex dfae070fe8d55..9b6c37f92e97f 100644\n--- a/arch/arc/include/asm/pgalloc.h\n+++ b/arch/arc/include/asm/pgalloc.h\n@@ -72,7 +72,7 @@ static inline void p4d_populate(struct mm_struct *mm, p4d_t *p4dp, pud_t *pudp)\n \tset_p4d(p4dp, __p4d((unsigned long)pudp));\n }\n \n-#define __pud_free_tlb(tlb, pmd, addr) pud_free((tlb)-\u003emm, pmd)\n+#define __pud_free_tlb(tlb, pmd, addr) tlb_remove_ptdesc((tlb), virt_to_ptdesc(pmd))\n \n #endif\n \n@@ -83,10 +83,10 @@ static inline void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmdp)\n \tset_pud(pudp, __pud((unsigned long)pmdp));\n }\n \n-#define __pmd_free_tlb(tlb, pmd, addr) pmd_free((tlb)-\u003emm, pmd)\n+#define __pmd_free_tlb(tlb, pmd, addr) tlb_remove_ptdesc((tlb), virt_to_ptdesc(pmd))\n \n #endif\n \n-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)-\u003emm, pte)\n+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_ptdesc((tlb), page_ptdesc(pte))\n \n #endif /* _ASM_ARC_PGALLOC_H */\ndiff --git a/arch/arm/Kconfig b/arch/arm/Kconfig\nindex 408aa58a2a5bb..0cc289a7184ab 100644\n--- a/arch/arm/Kconfig\n+++ b/arch/arm/Kconfig\n@@ -134,7 +134,6 @@ config ARM\n \tselect HAVE_PERF_REGS\n \tselect HAVE_PERF_USER_STACK_DUMP\n \tselect HAVE_POSIX_CPU_TIMERS_TASK_WORK\n-\tselect MMU_GATHER_RCU_TABLE_FREE if SMP \u0026\u0026 ARM_LPAE\n \tselect HAVE_REGS_AND_STACK_ACCESS_API\n \tselect HAVE_RSEQ\n \tselect HAVE_RUST if CPU_LITTLE_ENDIAN \u0026\u0026 CPU_32v7 \u0026\u0026 !KASAN\ndiff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig\nindex 2bbeded33da0d..b6c2dd8b26124 100644\n--- a/arch/arm64/Kconfig\n+++ b/arch/arm64/Kconfig\n@@ -221,7 +221,6 @@ config ARM64\n \tselect HAVE_RELIABLE_STACKTRACE\n \tselect HAVE_POSIX_CPU_TIMERS_TASK_WORK\n \tselect HAVE_FUNCTION_ARG_ACCESS_API\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect HAVE_RSEQ\n \tselect HAVE_RUST if RUSTC_SUPPORTS_ARM64\n \tselect HAVE_STACKPROTECTOR\ndiff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig\nindex 9c5def7062222..d1b23da407375 100644\n--- a/arch/loongarch/Kconfig\n+++ b/arch/loongarch/Kconfig\n@@ -188,7 +188,6 @@ config LOONGARCH\n \tselect IRQ_LOONGARCH_CPU\n \tselect LOCK_MM_AND_FIND_VMA\n \tselect MMU_GATHER_MERGE_VMAS if MMU\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect MODULES_USE_ELF_RELA if MODULES\n \tselect NEED_PER_CPU_EMBED_FIRST_CHUNK\n \tselect NEED_PER_CPU_PAGE_FIRST_CHUNK\ndiff --git a/arch/m68k/Kconfig b/arch/m68k/Kconfig\nindex 11835eb59d94d..eb84c3af92c02 100644\n--- a/arch/m68k/Kconfig\n+++ b/arch/m68k/Kconfig\n@@ -29,6 +29,7 @@ config M68K\n \tselect HAVE_ARCH_LIBGCC_H\n \tselect HAVE_ARCH_SECCOMP\n \tselect HAVE_ARCH_SECCOMP_FILTER\n+\tselect HAVE_ARCH_TLB_REMOVE_TABLE if MMU_MOTOROLA\n \tselect HAVE_ASM_MODVERSIONS\n \tselect HAVE_DEBUG_BUGVERBOSE\n \tselect HAVE_EFFICIENT_UNALIGNED_ACCESS if !CPU_HAS_NO_UNALIGNED\ndiff --git a/arch/m68k/include/asm/mcf_pgalloc.h b/arch/m68k/include/asm/mcf_pgalloc.h\nindex fc5454d37da31..b53ff0950db2e 100644\n--- a/arch/m68k/include/asm/mcf_pgalloc.h\n+++ b/arch/m68k/include/asm/mcf_pgalloc.h\n@@ -39,10 +39,7 @@ extern inline pmd_t *pmd_alloc_kernel(pgd_t *pgd, unsigned long address)\n static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pgtable,\n \t\t\t\t unsigned long address)\n {\n-\tstruct ptdesc *ptdesc = virt_to_ptdesc(pgtable);\n-\n-\tpagetable_dtor(ptdesc);\n-\tpagetable_free(ptdesc);\n+\ttlb_remove_ptdesc(tlb, virt_to_ptdesc(pgtable));\n }\n \n static inline pgtable_t pte_alloc_one(struct mm_struct *mm)\ndiff --git a/arch/m68k/include/asm/motorola_pgalloc.h b/arch/m68k/include/asm/motorola_pgalloc.h\nindex 1091fb0affbee..dcde40e8b5c6a 100644\n--- a/arch/m68k/include/asm/motorola_pgalloc.h\n+++ b/arch/m68k/include/asm/motorola_pgalloc.h\n@@ -17,6 +17,7 @@ enum m68k_table_types {\n extern void init_pointer_table(void *table, int type);\n extern void *get_pointer_table(struct mm_struct *mm, int type);\n extern int free_pointer_table(void *table, int type);\n+extern void __tlb_remove_table(void *table);\n \n /*\n * Allocate and free page tables. The xxx_kernel() versions are\n@@ -47,7 +48,7 @@ static inline void pte_free(struct mm_struct *mm, pgtable_t pgtable)\n static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pgtable,\n \t\t\t\t unsigned long address)\n {\n-\tfree_pointer_table(pgtable, TABLE_PTE);\n+\ttlb_remove_table(tlb, (void *)((unsigned long)pgtable | TABLE_PTE));\n }\n \n \n@@ -61,10 +62,10 @@ static inline int pmd_free(struct mm_struct *mm, pmd_t *pmd)\n \treturn free_pointer_table(pmd, TABLE_PMD);\n }\n \n-static inline int __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd,\n-\t\t\t\t unsigned long address)\n+static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd,\n+\t\t\t\t unsigned long address)\n {\n-\treturn free_pointer_table(pmd, TABLE_PMD);\n+\ttlb_remove_table(tlb, (void *)((unsigned long)pmd | TABLE_PMD));\n }\n \n \ndiff --git a/arch/m68k/mm/motorola.c b/arch/m68k/mm/motorola.c\nindex b30aa69a73a6a..ffc80483440bc 100644\n--- a/arch/m68k/mm/motorola.c\n+++ b/arch/m68k/mm/motorola.c\n@@ -20,6 +20,7 @@\n #include \u003clinux/init.h\u003e\n #include \u003clinux/memblock.h\u003e\n #include \u003clinux/gfp.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n \n #include \u003casm/setup.h\u003e\n #include \u003clinux/uaccess.h\u003e\n@@ -103,6 +104,8 @@ static struct list_head ptable_list[3] = {\n \tLIST_HEAD_INIT(ptable_list[2]),\n };\n \n+static DEFINE_SPINLOCK(ptable_lock);\n+\n #define PD_PTABLE(ptdesc) ((ptable_desc *)\u0026(virt_to_ptdesc((void *)(ptdesc))-\u003ept_list))\n #define PD_PTDESC(ptable) (list_entry(ptable, struct ptdesc, pt_list))\n #define PD_MARKBITS(dp) (*(unsigned int *)\u0026PD_PTDESC(dp)-\u003ept_index)\n@@ -139,52 +142,66 @@ void __init init_pointer_table(void *table, int type)\n \treturn;\n }\n \n-void *get_pointer_table(struct mm_struct *mm, int type)\n+/*\n+ * For a pointer table for a user process address space, a\n+ * table is taken from a ptdesc allocated for the purpose. Each\n+ * ptdesc can hold 8 pointer tables. The ptdesc is remapped in\n+ * virtual address space to be noncacheable.\n+ */\n+static void *add_pointer_table(struct mm_struct *mm, int type)\n {\n-\tptable_desc *dp = ptable_list[type].next;\n-\tunsigned int mask = list_empty(\u0026ptable_list[type]) ? 0 : PD_MARKBITS(dp);\n-\tunsigned int tmp, off;\n+\tstruct ptdesc *ptdesc;\n+\tptable_desc *new;\n+\tvoid *pt_addr;\n \n-\t/*\n-\t * For a pointer table for a user process address space, a\n-\t * table is taken from a ptdesc allocated for the purpose. Each\n-\t * ptdesc can hold 8 pointer tables. The ptdesc is remapped in\n-\t * virtual address space to be noncacheable.\n-\t */\n-\tif (mask == 0) {\n-\t\tstruct ptdesc *ptdesc;\n-\t\tptable_desc *new;\n-\t\tvoid *pt_addr;\n-\n-\t\tptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0);\n-\t\tif (!ptdesc)\n-\t\t\treturn NULL;\n-\n-\t\tpt_addr = ptdesc_address(ptdesc);\n-\n-\t\tswitch (type) {\n-\t\tcase TABLE_PTE:\n-\t\t\t/*\n-\t\t\t * m68k doesn't have SPLIT_PTE_PTLOCKS for not having\n-\t\t\t * SMP.\n-\t\t\t */\n-\t\t\tpagetable_pte_ctor(mm, ptdesc);\n-\t\t\tbreak;\n-\t\tcase TABLE_PMD:\n-\t\t\tpagetable_pmd_ctor(mm, ptdesc);\n-\t\t\tbreak;\n-\t\tcase TABLE_PGD:\n-\t\t\tpagetable_pgd_ctor(ptdesc);\n-\t\t\tbreak;\n-\t\t}\n+\tptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0);\n+\tif (!ptdesc)\n+\t\treturn NULL;\n+\n+\tpt_addr = ptdesc_address(ptdesc);\n+\n+\tswitch (type) {\n+\tcase TABLE_PTE:\n+\t\t/*\n+\t\t * m68k doesn't have SPLIT_PTE_PTLOCKS for not having\n+\t\t * SMP.\n+\t\t */\n+\t\tpagetable_pte_ctor(mm, ptdesc);\n+\t\tbreak;\n+\tcase TABLE_PMD:\n+\t\tpagetable_pmd_ctor(mm, ptdesc);\n+\t\tbreak;\n+\tcase TABLE_PGD:\n+\t\tpagetable_pgd_ctor(ptdesc);\n+\t\tbreak;\n+\t}\n+\n+\tmmu_page_ctor(pt_addr);\n+\n+\tnew = PD_PTABLE(pt_addr);\n \n-\t\tmmu_page_ctor(pt_addr);\n+\tPD_MARKBITS(new) = ptable_mask(type) - 1;\n+\tscoped_guard(spinlock_irqsave, \u0026ptable_lock)\n+\t\tlist_add(new, \u0026ptable_list[type]);\n \n-\t\tnew = PD_PTABLE(pt_addr);\n-\t\tPD_MARKBITS(new) = ptable_mask(type) - 1;\n-\t\tlist_add_tail(new, dp);\n+\treturn (pmd_t *)pt_addr;\n+}\n+\n+void *get_pointer_table(struct mm_struct *mm, int type)\n+{\n+\tunsigned int tmp, off;\n+\tunsigned long mask;\n+\tunsigned long flags;\n+\tptable_desc *dp;\n+\tvoid *ret;\n \n-\t\treturn (pmd_t *)pt_addr;\n+\tspin_lock_irqsave(\u0026ptable_lock, flags);\n+\tdp = ptable_list[type].next;\n+\tmask = list_empty(\u0026ptable_list[type]) ? 0 : PD_MARKBITS(dp);\n+\n+\tif (mask == 0) {\n+\t\tspin_unlock_irqrestore(\u0026ptable_lock, flags);\n+\t\treturn add_pointer_table(mm, type);\n \t}\n \n \tfor (tmp = 1, off = 0; (mask \u0026 tmp) == 0; tmp \u003c\u003c= 1, off += ptable_size(type))\n@@ -194,7 +211,10 @@ void *get_pointer_table(struct mm_struct *mm, int type)\n \t\t/* move to end of list */\n \t\tlist_move_tail(dp, \u0026ptable_list[type]);\n \t}\n-\treturn ptdesc_address(PD_PTDESC(dp)) + off;\n+\n+\tret = ptdesc_address(PD_PTDESC(dp)) + off;\n+\tspin_unlock_irqrestore(\u0026ptable_lock, flags);\n+\treturn ret;\n }\n \n int free_pointer_table(void *table, int type)\n@@ -203,6 +223,9 @@ int free_pointer_table(void *table, int type)\n \tunsigned long ptable = (unsigned long)table;\n \tunsigned long pt_addr = ptable \u0026 PAGE_MASK;\n \tunsigned int mask = 1U \u003c\u003c ((ptable - pt_addr)/ptable_size(type));\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026ptable_lock, flags);\n \n \tdp = PD_PTABLE(pt_addr);\n \tif (PD_MARKBITS (dp) \u0026 mask)\n@@ -213,6 +236,8 @@ int free_pointer_table(void *table, int type)\n \tif (PD_MARKBITS(dp) == ptable_mask(type)) {\n \t\t/* all tables in ptdesc are free, free ptdesc */\n \t\tlist_del(dp);\n+\t\tspin_unlock_irqrestore(\u0026ptable_lock, flags);\n+\n \t\tmmu_page_dtor((void *)pt_addr);\n \t\tpagetable_dtor_free(virt_to_ptdesc((void *)pt_addr));\n \t\treturn 1;\n@@ -223,9 +248,21 @@ int free_pointer_table(void *table, int type)\n \t\t */\n \t\tlist_move(dp, \u0026ptable_list[type]);\n \t}\n+\n+\tspin_unlock_irqrestore(\u0026ptable_lock, flags);\n \treturn 0;\n }\n \n+void __tlb_remove_table(void *table)\n+{\n+\t/* The bottom 2 bits are used to encode page table type. */\n+\tconst unsigned long encoded = (unsigned long)table;\n+\tvoid *addr = (void *)(encoded \u0026 ~3UL);\n+\tconst int type = encoded \u0026 3;\n+\n+\tfree_pointer_table(addr, type);\n+}\n+\n /* size of memory already mapped in head.S */\n extern __initdata unsigned long m68k_init_mapped_size;\n \ndiff --git a/arch/microblaze/include/asm/pgalloc.h b/arch/microblaze/include/asm/pgalloc.h\nindex 084a8a0dc2395..ffee6a009219a 100644\n--- a/arch/microblaze/include/asm/pgalloc.h\n+++ b/arch/microblaze/include/asm/pgalloc.h\n@@ -25,7 +25,7 @@ extern void __bad_pte(pmd_t *pmd);\n \n extern pte_t *pte_alloc_one_kernel(struct mm_struct *mm);\n \n-#define __pte_free_tlb(tlb, pte, addr)\tpte_free((tlb)-\u003emm, (pte))\n+#define __pte_free_tlb(tlb, pte, addr)\ttlb_remove_ptdesc((tlb), page_ptdesc(pte))\n \n #define pmd_populate(mm, pmd, pte) \\\n \t\t\t(pmd_val(*(pmd)) = (unsigned long)page_address(pte))\ndiff --git a/arch/mips/Kconfig b/arch/mips/Kconfig\nindex e2eb9627bd14c..f0c43d118ca00 100644\n--- a/arch/mips/Kconfig\n+++ b/arch/mips/Kconfig\n@@ -97,7 +97,6 @@ config MIPS\n \tselect IRQ_FORCED_THREADING\n \tselect ISA if EISA\n \tselect LOCK_MM_AND_FIND_VMA\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect MODULES_USE_ELF_REL if MODULES\n \tselect MODULES_USE_ELF_RELA if MODULES \u0026\u0026 64BIT\n \tselect PERF_USE_VMALLOC\ndiff --git a/arch/parisc/Kconfig b/arch/parisc/Kconfig\nindex d3afac2f0d9be..77f67028ad89c 100644\n--- a/arch/parisc/Kconfig\n+++ b/arch/parisc/Kconfig\n@@ -80,7 +80,6 @@ config PARISC\n \tselect GENERIC_CLOCKEVENTS\n \tselect CPU_NO_EFFICIENT_FFS\n \tselect THREAD_INFO_IN_TASK\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect NEED_DMA_MAP_STATE\n \tselect NEED_SG_DMA_LENGTH\n \tselect HAVE_ARCH_KGDB\ndiff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig\nindex 2580e27e43287..0767cfcbaa422 100644\n--- a/arch/powerpc/Kconfig\n+++ b/arch/powerpc/Kconfig\n@@ -307,7 +307,6 @@ config PPC\n \tselect KASAN_VMALLOC\t\t\tif KASAN \u0026\u0026 EXECMEM\n \tselect LOCK_MM_AND_FIND_VMA\n \tselect MMU_GATHER_PAGE_SIZE\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect HAVE_ARCH_TLB_REMOVE_TABLE\n \tselect MMU_GATHER_MERGE_VMAS\n \tselect MMU_LAZY_TLB_SHOOTDOWN\t\tif PPC_BOOK3S_64\ndiff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig\nindex 505eed4af932b..7741a42874981 100644\n--- a/arch/riscv/Kconfig\n+++ b/arch/riscv/Kconfig\n@@ -208,7 +208,6 @@ config RISCV\n \tselect IRQ_FORCED_THREADING\n \tselect KASAN_VMALLOC if KASAN\n \tselect LOCK_MM_AND_FIND_VMA\n-\tselect MMU_GATHER_RCU_TABLE_FREE if SMP \u0026\u0026 MMU\n \tselect MODULES_USE_ELF_RELA if MODULES\n \tselect OF\n \tselect OF_EARLY_FLATTREE\ndiff --git a/arch/s390/Kconfig b/arch/s390/Kconfig\nindex b88b850421369..a34376c05f6e3 100644\n--- a/arch/s390/Kconfig\n+++ b/arch/s390/Kconfig\n@@ -267,7 +267,6 @@ config S390\n \tselect LOCK_MM_AND_FIND_VMA\n \tselect MMU_GATHER_MERGE_VMAS\n \tselect MMU_GATHER_NO_GATHER\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect MODULES_USE_ELF_RELA\n \tselect NEED_DMA_MAP_STATE\tif PCI\n \tselect NEED_PER_CPU_EMBED_FIRST_CHUNK\ndiff --git a/arch/sh/Kconfig b/arch/sh/Kconfig\nindex d60f1d5a94c0f..fe859def918cc 100644\n--- a/arch/sh/Kconfig\n+++ b/arch/sh/Kconfig\n@@ -33,6 +33,7 @@ config SUPERH\n \tselect HAVE_ARCH_AUDITSYSCALL\n \tselect HAVE_ARCH_KGDB\n \tselect HAVE_ARCH_SECCOMP_FILTER\n+\tselect HAVE_ARCH_TLB_REMOVE_TABLE if X2TLB\n \tselect HAVE_ARCH_TRACEHOOK\n \tselect HAVE_DEBUG_BUGVERBOSE\n \tselect HAVE_DEBUG_KMEMLEAK\ndiff --git a/arch/sh/include/asm/pgalloc.h b/arch/sh/include/asm/pgalloc.h\nindex 6fe7123d38fa9..67ce7fa23fa12 100644\n--- a/arch/sh/include/asm/pgalloc.h\n+++ b/arch/sh/include/asm/pgalloc.h\n@@ -17,7 +17,11 @@ extern void pgd_free(struct mm_struct *mm, pgd_t *pgd);\n extern void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmd);\n extern pmd_t *pmd_alloc_one(struct mm_struct *mm, unsigned long address);\n extern void pmd_free(struct mm_struct *mm, pmd_t *pmd);\n-#define __pmd_free_tlb(tlb, pmdp, addr)\t\tpmd_free((tlb)-\u003emm, (pmdp))\n+extern void __tlb_remove_table(void *table);\n+\n+/* PMDs are slab-allocated, tag so they are freed correctly. */\n+#define __pmd_free_tlb(tlb, pmdp, addr)\t\t\t\t\t\\\n+\ttlb_remove_table((tlb), (void *)((unsigned long)(pmdp) | 1))\n #endif\n \n static inline void pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmd,\ndiff --git a/arch/sh/mm/pgtable.c b/arch/sh/mm/pgtable.c\nindex 3a4085ea0161f..f6184b86b89c6 100644\n--- a/arch/sh/mm/pgtable.c\n+++ b/arch/sh/mm/pgtable.c\n@@ -56,4 +56,24 @@ void pmd_free(struct mm_struct *mm, pmd_t *pmd)\n {\n \tkmem_cache_free(pmd_cachep, pmd);\n }\n+\n+static void __tlb_remove_table_slab(void *table)\n+{\n+\tkmem_cache_free(pmd_cachep, table);\n+}\n+\n+static void __tlb_remove_table_pgtable(void *table)\n+{\n+\tpagetable_dtor_free(table);\n+}\n+\n+void __tlb_remove_table(void *table)\n+{\n+\tconst unsigned long addr = (unsigned long)table;\n+\n+\tif (addr \u0026 1)\n+\t\t__tlb_remove_table_slab((void *)(addr \u0026 ~1UL));\n+\telse\n+\t\t__tlb_remove_table_pgtable(table);\n+}\n #endif /* PAGETABLE_LEVELS \u003e 2 */\ndiff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig\nindex ab77d3f2536e1..742ffff8c37f2 100644\n--- a/arch/sparc/Kconfig\n+++ b/arch/sparc/Kconfig\n@@ -64,6 +64,7 @@ config SPARC32\n \tselect HAVE_UID16\n \tselect HAVE_PAGE_SIZE_4KB\n \tselect LOCK_MM_AND_FIND_VMA\n+\tselect HAVE_ARCH_TLB_REMOVE_TABLE\n \tselect OLD_SIGACTION\n \tselect ZONE_DMA\n \n@@ -75,8 +76,7 @@ config SPARC64\n \tselect HAVE_FUNCTION_GRAPH_TRACER\n \tselect HAVE_KRETPROBES\n \tselect HAVE_KPROBES\n-\tselect MMU_GATHER_RCU_TABLE_FREE if SMP\n-\tselect HAVE_ARCH_TLB_REMOVE_TABLE if SMP\n+\tselect HAVE_ARCH_TLB_REMOVE_TABLE\n \tselect MMU_GATHER_MERGE_VMAS\n \tselect MMU_GATHER_NO_FLUSH_CACHE\n \tselect HAVE_ARCH_TRANSPARENT_HUGEPAGE\ndiff --git a/arch/sparc/include/asm/pgalloc_32.h b/arch/sparc/include/asm/pgalloc_32.h\nindex 4f73e87b22a32..36010852ba0c0 100644\n--- a/arch/sparc/include/asm/pgalloc_32.h\n+++ b/arch/sparc/include/asm/pgalloc_32.h\n@@ -48,7 +48,9 @@ static inline void free_pmd_fast(pmd_t * pmd)\n }\n \n #define pmd_free(mm, pmd)\t\tfree_pmd_fast(pmd)\n-#define __pmd_free_tlb(tlb, pmd, addr)\tpmd_free((tlb)-\u003emm, pmd)\n+\n+#define __pmd_free_tlb(tlb, pmd, addr)\t\t\t\t\t\\\n+\ttlb_remove_table((tlb), (void *)((unsigned long)(pmd) | 1UL))\n \n #define pmd_populate(mm, pmd, pte)\tpmd_set(pmd, pte)\n \n@@ -72,6 +74,7 @@ static inline void free_pte_fast(pte_t *pte)\n #define pte_free_kernel(mm, pte)\tfree_pte_fast(pte)\n \n void pte_free(struct mm_struct * mm, pgtable_t pte);\n-#define __pte_free_tlb(tlb, pte, addr)\tpte_free((tlb)-\u003emm, pte)\n+void __tlb_remove_table(void *table);\n+#define __pte_free_tlb(tlb, pte, addr)\ttlb_remove_table((tlb), (void *)(pte))\n \n #endif /* _SPARC_PGALLOC_H */\ndiff --git a/arch/sparc/include/asm/pgalloc_64.h b/arch/sparc/include/asm/pgalloc_64.h\nindex caa7632be4c2a..b5055d259b74d 100644\n--- a/arch/sparc/include/asm/pgalloc_64.h\n+++ b/arch/sparc/include/asm/pgalloc_64.h\n@@ -74,8 +74,6 @@ void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\n \n void pgtable_free(void *table, bool is_page);\n \n-#ifdef CONFIG_SMP\n-\n struct mmu_gather;\n void tlb_remove_table(struct mmu_gather *, void *);\n \n@@ -96,12 +94,6 @@ static inline void __tlb_remove_table(void *_table)\n \t\tis_page = true;\n \tpgtable_free(table, is_page);\n }\n-#else /* CONFIG_SMP */\n-static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, bool is_page)\n-{\n-\tpgtable_free(table, is_page);\n-}\n-#endif /* !CONFIG_SMP */\n \n static inline void __pte_free_tlb(struct mmu_gather *tlb, pte_t *pte,\n \t\t\t\t unsigned long address)\ndiff --git a/arch/sparc/include/asm/tlb_64.h b/arch/sparc/include/asm/tlb_64.h\nindex 3037187482db7..f5f9631685d50 100644\n--- a/arch/sparc/include/asm/tlb_64.h\n+++ b/arch/sparc/include/asm/tlb_64.h\n@@ -29,9 +29,7 @@ void flush_tlb_pending(void);\n * and therefore we don't need a TLBI when freeing page-table pages.\n */\n \n-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE\n #define tlb_needs_table_invalidate()\t(false)\n-#endif\n \n #include \u003casm-generic/tlb.h\u003e\n \ndiff --git a/arch/sparc/lib/bitext.c b/arch/sparc/lib/bitext.c\nindex 32a5c1d9459cd..c309e27973ce6 100644\n--- a/arch/sparc/lib/bitext.c\n+++ b/arch/sparc/lib/bitext.c\n@@ -22,8 +22,6 @@\n * @align: requested alignment\n *\n * Returns offset in the map or -1 if out of space.\n- *\n- * Not safe to call from an interrupt (uses spin_lock).\n */\n int bit_map_string_get(struct bit_map *t, int len, int align)\n {\n@@ -31,6 +29,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)\n \tint off_new;\n \tint align1;\n \tint i, color;\n+\tunsigned long flags;\n \n \tif (t-\u003enum_colors) {\n \t\t/* align is overloaded to be the page color */\n@@ -50,7 +49,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)\n \t\tBUG();\n \tcolor \u0026= align1;\n \n-\tspin_lock(\u0026t-\u003elock);\n+\tspin_lock_irqsave(\u0026t-\u003elock, flags);\n \tif (len \u003c t-\u003elast_size)\n \t\toffset = t-\u003efirst_free;\n \telse\n@@ -64,7 +63,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)\n \t\tif (offset \u003e= t-\u003esize)\n \t\t\toffset = 0;\n \t\tif (count + len \u003e t-\u003esize) {\n-\t\t\tspin_unlock(\u0026t-\u003elock);\n+\t\t\tspin_unlock_irqrestore(\u0026t-\u003elock, flags);\n /* P3 */ printk(KERN_ERR\n \"bitmap out: size %d used %d off %d len %d align %d count %d\\n\",\n t-\u003esize, t-\u003eused, offset, len, align, count);\n@@ -90,7 +89,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)\n \t\t\t\t\tt-\u003elast_off = 0;\n \t\t\t\tt-\u003eused += len;\n \t\t\t\tt-\u003elast_size = len;\n-\t\t\t\tspin_unlock(\u0026t-\u003elock);\n+\t\t\t\tspin_unlock_irqrestore(\u0026t-\u003elock, flags);\n \t\t\t\treturn offset;\n \t\t\t}\n \t\t}\n@@ -103,10 +102,11 @@ int bit_map_string_get(struct bit_map *t, int len, int align)\n void bit_map_clear(struct bit_map *t, int offset, int len)\n {\n \tint i;\n+\tunsigned long flags;\n \n \tif (t-\u003eused \u003c len)\n \t\tBUG();\t\t/* Much too late to do any good, but alas... */\n-\tspin_lock(\u0026t-\u003elock);\n+\tspin_lock_irqsave(\u0026t-\u003elock, flags);\n \tfor (i = 0; i \u003c len; i++) {\n \t\tif (test_bit(offset + i, t-\u003emap) == 0)\n \t\t\tBUG();\n@@ -115,7 +115,7 @@ void bit_map_clear(struct bit_map *t, int offset, int len)\n \tif (offset \u003c t-\u003efirst_free)\n \t\tt-\u003efirst_free = offset;\n \tt-\u003eused -= len;\n-\tspin_unlock(\u0026t-\u003elock);\n+\tspin_unlock_irqrestore(\u0026t-\u003elock, flags);\n }\n \n void bit_map_init(struct bit_map *t, unsigned long *map, int size)\ndiff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c\nindex 9a74902ad1814..2a2c7bd210114 100644\n--- a/arch/sparc/mm/srmmu.c\n+++ b/arch/sparc/mm/srmmu.c\n@@ -359,19 +359,39 @@ pgtable_t pte_alloc_one(struct mm_struct *mm)\n \treturn ptep;\n }\n \n-void pte_free(struct mm_struct *mm, pgtable_t ptep)\n+static void __pte_free(struct mm_struct *mm, pgtable_t ptep)\n {\n+\tconst bool process_context = mm;\n \tstruct page *page;\n \n \tpage = pfn_to_page(__nocache_pa((unsigned long)ptep) \u003e\u003e PAGE_SHIFT);\n-\tspin_lock(\u0026mm-\u003epage_table_lock);\n+\tif (process_context)\n+\t\tspin_lock(\u0026mm-\u003epage_table_lock);\n \tif (page_ref_dec_return(page) == 1)\n \t\tpagetable_dtor(page_ptdesc(page));\n-\tspin_unlock(\u0026mm-\u003epage_table_lock);\n+\tif (process_context)\n+\t\tspin_unlock(\u0026mm-\u003epage_table_lock);\n \n \tsrmmu_free_nocache(ptep, SRMMU_PTE_TABLE_SIZE);\n }\n \n+void pte_free(struct mm_struct *mm, pgtable_t ptep)\n+{\n+\t__pte_free(mm, ptep);\n+}\n+\n+void __tlb_remove_table(void *table)\n+{\n+\tconst unsigned long encoded = (unsigned long)table;\n+\tconst unsigned long addr = encoded \u0026 ~1UL;\n+\tconst bool is_pmd = encoded \u0026 1;\n+\n+\tif (is_pmd)\n+\t\tfree_pmd_fast((pmd_t *)addr);\n+\telse /* Called from softirq context, no mm. */\n+\t\t__pte_free(NULL, (pgtable_t)addr);\n+}\n+\n /* context handling - a dynamically sized pool is used */\n #define NO_CONTEXT\t-1\n \ndiff --git a/arch/um/Kconfig b/arch/um/Kconfig\nindex d9541d13d9eb0..94b8ff70f578b 100644\n--- a/arch/um/Kconfig\n+++ b/arch/um/Kconfig\n@@ -44,7 +44,6 @@ config UML\n \tselect HAVE_SYSCALL_TRACEPOINTS\n \tselect THREAD_INFO_IN_TASK\n \tselect SPARSE_IRQ\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \n config MMU\n \tbool\ndiff --git a/arch/x86/Kconfig b/arch/x86/Kconfig\nindex a8c3b3d31a276..6e5e462ec059a 100644\n--- a/arch/x86/Kconfig\n+++ b/arch/x86/Kconfig\n@@ -283,7 +283,6 @@ config X86\n \tselect HAVE_PERF_REGS\n \tselect HAVE_PERF_USER_STACK_DUMP\n \tselect ASYNC_KERNEL_PGTABLE_FREE\tif IOMMU_SVA\n-\tselect MMU_GATHER_RCU_TABLE_FREE\n \tselect MMU_GATHER_MERGE_VMAS\n \tselect HAVE_POSIX_CPU_TIMERS_TASK_WORK\n \tselect HAVE_REGS_AND_STACK_ACCESS_API\ndiff --git a/arch/xtensa/include/asm/tlb.h b/arch/xtensa/include/asm/tlb.h\nindex 8c3ceb4270180..6fb7b78154f62 100644\n--- a/arch/xtensa/include/asm/tlb.h\n+++ b/arch/xtensa/include/asm/tlb.h\n@@ -16,7 +16,7 @@\n \n #include \u003casm-generic/tlb.h\u003e\n \n-#define __pte_free_tlb(tlb, pte, address)\tpte_free((tlb)-\u003emm, pte)\n+#define __pte_free_tlb(tlb, pte, address)\ttlb_remove_ptdesc((tlb), page_ptdesc(pte))\n \n void check_tlb_sanity(void);\n \ndiff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h\nindex bdcc2778ac64f..044dabc1fe9cb 100644\n--- a/include/asm-generic/tlb.h\n+++ b/include/asm-generic/tlb.h\n@@ -67,11 +67,8 @@\n * - tlb_remove_table()\n *\n * tlb_remove_table() is the basic primitive to free page-table directories\n- * (__p*_free_tlb()). In it's most primitive form it is an alias for\n- * tlb_remove_page() below, for when page directories are pages and have no\n- * additional constraints.\n- *\n- * See also MMU_GATHER_TABLE_FREE and MMU_GATHER_RCU_TABLE_FREE.\n+ * (__p*_free_tlb()). Page directories are freed after an RCU grace\n+ * period - see the comment in mm/mmu_gather.c.\n *\n * - tlb_remove_page() / tlb_remove_page_size()\n * - __tlb_remove_folio_pages() / __tlb_remove_page_size()\n@@ -151,24 +148,15 @@\n * This might be useful if your architecture has size specific TLB\n * invalidation instructions.\n *\n- * MMU_GATHER_TABLE_FREE\n- *\n- * This provides tlb_remove_table(), to be used instead of tlb_remove_page()\n- * for page directores (__p*_free_tlb()).\n- *\n- * Useful if your architecture has non-page page directories.\n+ * Page directories (__p*_free_tlb()) are always freed via tlb_remove_table(),\n+ * after an RCU grace period (see mm/mmu_gather.c).\n *\n- * When used, an architecture is expected to provide __tlb_remove_table() or\n- * use the generic __tlb_remove_table(), which does the actual freeing of these\n- * pages.\n+ * This serialises against software page-table walkers, including architectures\n+ * which do not use IPIs for remote TLB invalidates.\n *\n- * MMU_GATHER_RCU_TABLE_FREE\n- *\n- * Like MMU_GATHER_TABLE_FREE, and adds semi-RCU semantics to the free (see\n- * comment below).\n- *\n- * Useful if your architecture doesn't use IPIs for remote TLB invalidates\n- * and therefore doesn't naturally serialize with software page-table walkers.\n+ * An architecture is expected to provide __tlb_remove_table() (see\n+ * HAVE_ARCH_TLB_REMOVE_TABLE) or use the generic __tlb_remove_table(), which\n+ * does the actual freeing of these pages.\n *\n * MMU_GATHER_NO_FLUSH_CACHE\n *\n@@ -200,12 +188,8 @@\n * various ptep_get_and_clear() functions.\n */\n \n-#ifdef CONFIG_MMU_GATHER_TABLE_FREE\n-\n struct mmu_table_batch {\n-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE\n \tstruct rcu_head\t\trcu;\n-#endif\n \tunsigned int\t\tnr;\n \tvoid\t\t\t*tables[];\n };\n@@ -224,23 +208,6 @@ static inline void __tlb_remove_table(void *table)\n \n extern void tlb_remove_table(struct mmu_gather *tlb, void *table);\n \n-#else /* !CONFIG_MMU_GATHER_TABLE_FREE */\n-\n-static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page);\n-/*\n- * Without MMU_GATHER_TABLE_FREE the architecture is assumed to have page based\n- * page directories and we can use the normal page batching to free them.\n- */\n-static inline void tlb_remove_table(struct mmu_gather *tlb, void *table)\n-{\n-\tstruct ptdesc *ptdesc = (struct ptdesc *)table;\n-\n-\tpagetable_dtor(ptdesc);\n-\ttlb_remove_page(tlb, ptdesc_page(ptdesc));\n-}\n-#endif /* CONFIG_MMU_GATHER_TABLE_FREE */\n-\n-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE\n /*\n * This allows an architecture that does not use the linux page-tables for\n * hardware to skip the TLBI when freeing page tables.\n@@ -253,19 +220,6 @@ void tlb_remove_table_sync_one(void);\n \n void tlb_remove_table_sync_rcu(void);\n \n-#else\n-\n-#ifdef tlb_needs_table_invalidate\n-#error tlb_needs_table_invalidate() requires MMU_GATHER_RCU_TABLE_FREE\n-#endif\n-\n-static inline void tlb_remove_table_sync_one(void) { }\n-\n-static inline void tlb_remove_table_sync_rcu(void) { }\n-\n-#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */\n-\n-\n #ifndef CONFIG_MMU_GATHER_NO_GATHER\n /*\n * If we can't allocate a page to make a big batch of page pointers\n@@ -325,9 +279,7 @@ static inline void tlb_flush_rmaps(struct mmu_gather *tlb, struct vm_area_struct\n struct mmu_gather {\n \tstruct mm_struct\t*mm;\n \n-#ifdef CONFIG_MMU_GATHER_TABLE_FREE\n \tstruct mmu_table_batch\t*batch;\n-#endif\n \n \tunsigned long\t\tstart;\n \tunsigned long\t\tend;\ndiff --git a/mm/Kconfig b/mm/Kconfig\nindex c1ddf59c0d71a..bc7befafb47b5 100644\n--- a/mm/Kconfig\n+++ b/mm/Kconfig\n@@ -1465,7 +1465,7 @@ config HAVE_ARCH_TLB_REMOVE_TABLE\n \n config PT_RECLAIM\n \tdef_bool y\n-\tdepends on MMU_GATHER_RCU_TABLE_FREE \u0026\u0026 !HAVE_ARCH_TLB_REMOVE_TABLE\n+\tdepends on MMU \u0026\u0026 !HAVE_ARCH_TLB_REMOVE_TABLE\n \thelp\n \t Try to reclaim empty user page table pages in paths other than munmap\n \t and exit_mmap path.\ndiff --git a/mm/gup.c b/mm/gup.c\nindex eb898ea1ee22e..63b435ec605c8 100644\n--- a/mm/gup.c\n+++ b/mm/gup.c\n@@ -2700,8 +2700,9 @@ EXPORT_SYMBOL(get_user_pages_unlocked);\n * Before activating this code, please be aware that the following assumptions\n * are currently made:\n *\n- * *) Either MMU_GATHER_RCU_TABLE_FREE is enabled, and tlb_remove_table() is used to\n- * free pages containing page tables or TLB flushing requires IPI broadcast.\n+ * *) tlb_remove_table() is used to free pages containing page tables, with\n+ * the free deferred until an RCU grace period has elapsed (see\n+ * mm/mmu_gather.c).\n *\n * *) ptes can be read atomically by the architecture.\n *\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex 54494c3fa9835..505f7b62ff281 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -2476,7 +2476,7 @@ static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)\n \tpgtable_t pgtable;\n \n \tpgtable = pgtable_trans_huge_withdraw(mm, pmd);\n-\tpte_free(mm, pgtable);\n+\tpte_free_defer(mm, pgtable);\n \tmm_dec_nr_ptes(mm);\n }\n \ndiff --git a/mm/mmu_gather.c b/mm/mmu_gather.c\nindex 3985d856de7f9..2a72a9686773a 100644\n--- a/mm/mmu_gather.c\n+++ b/mm/mmu_gather.c\n@@ -218,8 +218,6 @@ bool __tlb_remove_page_size(struct mmu_gather *tlb, struct page *page, int page_\n \n #endif /* MMU_GATHER_NO_GATHER */\n \n-#ifdef CONFIG_MMU_GATHER_TABLE_FREE\n-\n static void __tlb_remove_table_free(struct mmu_table_batch *batch)\n {\n \tint i;\n@@ -230,10 +228,8 @@ static void __tlb_remove_table_free(struct mmu_table_batch *batch)\n \tfree_page((unsigned long)batch);\n }\n \n-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE\n-\n /*\n- * Semi RCU freeing of the page directories.\n+ * RCU freeing of the page directories.\n *\n * This is needed by some architectures to implement software pagetable walkers.\n *\n@@ -259,13 +255,13 @@ static void __tlb_remove_table_free(struct mmu_table_batch *batch)\n * means.\n *\n * What we do is batch the freed directory pages (tables) and RCU free them.\n- * We use the sched RCU variant, as that guarantees that IRQ/preempt disabling\n- * holds off grace periods.\n+ * Disabling IRQs or preemption holds off RCU grace periods, so this protects\n+ * both rcu_read_lock() and IRQ-disabling walkers.\n *\n * However, in order to batch these pages we need to allocate storage, this\n * allocation is deep inside the MM code and can thus easily fail on memory\n- * pressure. To guarantee progress we fall back to single table freeing, see\n- * the implementation of tlb_remove_table_one().\n+ * pressure. To guarantee progress we fall back to single table freeing, which\n+ * is also RCU-deferred - see the implementation of tlb_remove_table_one().\n *\n */\n \n@@ -315,15 +311,6 @@ void tlb_remove_table_sync_rcu(void)\n \tsynchronize_rcu();\n }\n \n-#else /* !CONFIG_MMU_GATHER_RCU_TABLE_FREE */\n-\n-static void tlb_remove_table_free(struct mmu_table_batch *batch)\n-{\n-\t__tlb_remove_table_free(batch);\n-}\n-\n-#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */\n-\n /*\n * If we want tlb_remove_table() to imply TLB invalidates.\n */\n@@ -403,13 +390,6 @@ static inline void tlb_table_init(struct mmu_gather *tlb)\n \ttlb-\u003ebatch = NULL;\n }\n \n-#else /* !CONFIG_MMU_GATHER_TABLE_FREE */\n-\n-static inline void tlb_table_flush(struct mmu_gather *tlb) { }\n-static inline void tlb_table_init(struct mmu_gather *tlb) { }\n-\n-#endif /* CONFIG_MMU_GATHER_TABLE_FREE */\n-\n static void tlb_flush_mmu_free(struct mmu_gather *tlb)\n {\n \ttlb_table_flush(tlb);\ndiff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c\nindex b91b1a98029c7..ff8ff37064853 100644\n--- a/mm/pgtable-generic.c\n+++ b/mm/pgtable-generic.c\n@@ -386,9 +386,21 @@ pte_t *pte_offset_map_rw_nolock(struct mm_struct *mm, pmd_t *pmd,\n * be read-only/read-write protected.\n *\n * Note that free_pgtables(), used after unmapping detached vmas, or when\n- * exiting the whole mm, does not take page table lock before freeing a page\n- * table, and may not use RCU at all: \"outsiders\" like khugepaged should avoid\n- * pte_offset_map() and co once the vma is detached from mm or mm_users is zero.\n+ * exiting the whole mm, does not take the page table lock before freeing a\n+ * table.\n+ *\n+ * However, the PMD entry is cleared first, and the table freed only after\n+ * an RCU grace period, so a walker that mapped the table under\n+ * rcu_read_lock() stays safe, and the pmd_same() recheck in\n+ * pte_offset_map_lock() detects the teardown.\n+ *\n+ * Therefore it is safe for \"outsiders\" like khugepaged to use\n+ * pte_offset_map() and co. for VMAs that might be undergoing page table\n+ * teardown.\n+ *\n+ * Note that the PGD itself is freed at mmdrop() time, not under RCU - so the\n+ * walker must keep the mm alive via mmgrab(). With that held, walking remains\n+ * safe even once mm_users has reached zero.\n */\n pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,\n \t\t\t unsigned long addr, spinlock_t **ptlp)\n"
} |
| 2/1 | 2026/09/01 23:43 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"MMU",
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"tlb_remove_table",
"pte_free_defer",
"zap_huge_pmd",
"tlb_remove_table_sync_rcu",
"tlb_remove_table_sync_one"
],
"Reasoning": "The patch unifies and enforces RCU-deferred freeing for page table pages across all architectures by removing conditional MMU_GATHER_RCU_TABLE_FREE configuration options and standardizing on tlb_remove_table(). It also modifies THP deposited table handling in mm/huge_memory.c to use pte_free_defer() under RCU and updates architecture-specific page table removal routines. These core MM logic changes affect TLB gathering, page table tearing down, and memory reclaim paths.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 28e8e22fb2f922a5d98ac272c04a39fb5d0a7ee1
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 1 23:43:52 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/mm/process_addrs.rst b/Documentation/mm/process_addrs.rst
index a7296f251799c..1e65b139f3559 100644
--- a/Documentation/mm/process_addrs.rst
+++ b/Documentation/mm/process_addrs.rst
@@ -537,6 +537,12 @@ We establish basic locking rules when interacting with page tables:
* When changing a page table entry the page table lock for that page table
**must** be held, except if you can safely assume nobody can access the page
tables concurrently (such as on invocation of :c:func:`!free_pgtables`).
+* Page tables may be *walked* under RCU alone, as page tables are freed only
+ after an RCU grace period has elapsed. However, any entry found must be
+ revalidated after the page table lock is taken (such as the
+ :c:func:`!pmd_same` recheck performed by :c:func:`!pte_offset_map_lock`)
+ before it is acted upon. Changing an entry always requires the page table
+ lock.
* Reads from and writes to page table entries must be *appropriately*
atomic. See the section on atomicity below for details.
* Populating previously empty entries requires that the mmap or VMA locks are
diff --git a/arch/Kconfig b/arch/Kconfig
index 45c6577723623..6f7516916797e 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -526,13 +526,6 @@ config HAVE_ARCH_JUMP_LABEL
config HAVE_ARCH_JUMP_LABEL_RELATIVE
bool
-config MMU_GATHER_TABLE_FREE
- bool
-
-config MMU_GATHER_RCU_TABLE_FREE
- bool
- select MMU_GATHER_TABLE_FREE
-
config MMU_GATHER_PAGE_SIZE
bool
@@ -548,7 +541,6 @@ config MMU_GATHER_MERGE_VMAS
config MMU_GATHER_NO_GATHER
bool
- depends on MMU_GATHER_TABLE_FREE
config ARCH_WANT_IRQS_OFF_ACTIVATE_MM
bool
diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig
index e53ef2d884636..9063c7bda4e41 100644
--- a/arch/alpha/Kconfig
+++ b/arch/alpha/Kconfig
@@ -42,7 +42,6 @@ config ALPHA
select ARCH_STACKWALK
select CPU_NO_EFFICIENT_FFS if !ALPHA_EV67
select MMU_GATHER_NO_RANGE
- select MMU_GATHER_RCU_TABLE_FREE
select SPARSEMEM_EXTREME if SPARSEMEM
select ZONE_DMA
select TRACE_IRQFLAGS_SUPPORT
diff --git a/arch/arc/include/asm/pgalloc.h b/arch/arc/include/asm/pgalloc.h
index dfae070fe8d55..9b6c37f92e97f 100644
--- a/arch/arc/include/asm/pgalloc.h
+++ b/arch/arc/include/asm/pgalloc.h
@@ -72,7 +72,7 @@ static inline void p4d_populate(struct mm_struct *mm, p4d_t *p4dp, pud_t *pudp)
set_p4d(p4dp, __p4d((unsigned long)pudp));
}
-#define __pud_free_tlb(tlb, pmd, addr) pud_free((tlb)->mm, pmd)
+#define __pud_free_tlb(tlb, pmd, addr) tlb_remove_ptdesc((tlb), virt_to_ptdesc(pmd))
#endif
@@ -83,10 +83,10 @@ static inline void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmdp)
set_pud(pudp, __pud((unsigned long)pmdp));
}
-#define __pmd_free_tlb(tlb, pmd, addr) pmd_free((tlb)->mm, pmd)
+#define __pmd_free_tlb(tlb, pmd, addr) tlb_remove_ptdesc((tlb), virt_to_ptdesc(pmd))
#endif
-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)->mm, pte)
+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_ptdesc((tlb), page_ptdesc(pte))
#endif /* _ASM_ARC_PGALLOC_H */
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 408aa58a2a5bb..0cc289a7184ab 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -134,7 +134,6 @@ config ARM
select HAVE_PERF_REGS
select HAVE_PERF_USER_STACK_DUMP
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
- select MMU_GATHER_RCU_TABLE_FREE if SMP && ARM_LPAE
select HAVE_REGS_AND_STACK_ACCESS_API
select HAVE_RSEQ
select HAVE_RUST if CPU_LITTLE_ENDIAN && CPU_32v7 && !KASAN
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 2bbeded33da0d..b6c2dd8b26124 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -221,7 +221,6 @@ config ARM64
select HAVE_RELIABLE_STACKTRACE
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
select HAVE_FUNCTION_ARG_ACCESS_API
- select MMU_GATHER_RCU_TABLE_FREE
select HAVE_RSEQ
select HAVE_RUST if RUSTC_SUPPORTS_ARM64
select HAVE_STACKPROTECTOR
diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 9c5def7062222..d1b23da407375 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -188,7 +188,6 @@ config LOONGARCH
select IRQ_LOONGARCH_CPU
select LOCK_MM_AND_FIND_VMA
select MMU_GATHER_MERGE_VMAS if MMU
- select MMU_GATHER_RCU_TABLE_FREE
select MODULES_USE_ELF_RELA if MODULES
select NEED_PER_CPU_EMBED_FIRST_CHUNK
select NEED_PER_CPU_PAGE_FIRST_CHUNK
diff --git a/arch/m68k/Kconfig b/arch/m68k/Kconfig
index 11835eb59d94d..eb84c3af92c02 100644
--- a/arch/m68k/Kconfig
+++ b/arch/m68k/Kconfig
@@ -29,6 +29,7 @@ config M68K
select HAVE_ARCH_LIBGCC_H
select HAVE_ARCH_SECCOMP
select HAVE_ARCH_SECCOMP_FILTER
+ select HAVE_ARCH_TLB_REMOVE_TABLE if MMU_MOTOROLA
select HAVE_ASM_MODVERSIONS
select HAVE_DEBUG_BUGVERBOSE
select HAVE_EFFICIENT_UNALIGNED_ACCESS if !CPU_HAS_NO_UNALIGNED
diff --git a/arch/m68k/include/asm/mcf_pgalloc.h b/arch/m68k/include/asm/mcf_pgalloc.h
index fc5454d37da31..b53ff0950db2e 100644
--- a/arch/m68k/include/asm/mcf_pgalloc.h
+++ b/arch/m68k/include/asm/mcf_pgalloc.h
@@ -39,10 +39,7 @@ extern inline pmd_t *pmd_alloc_kernel(pgd_t *pgd, unsigned long address)
static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pgtable,
unsigned long address)
{
- struct ptdesc *ptdesc = virt_to_ptdesc(pgtable);
-
- pagetable_dtor(ptdesc);
- pagetable_free(ptdesc);
+ tlb_remove_ptdesc(tlb, virt_to_ptdesc(pgtable));
}
static inline pgtable_t pte_alloc_one(struct mm_struct *mm)
diff --git a/arch/m68k/include/asm/motorola_pgalloc.h b/arch/m68k/include/asm/motorola_pgalloc.h
index 1091fb0affbee..dcde40e8b5c6a 100644
--- a/arch/m68k/include/asm/motorola_pgalloc.h
+++ b/arch/m68k/include/asm/motorola_pgalloc.h
@@ -17,6 +17,7 @@ enum m68k_table_types {
extern void init_pointer_table(void *table, int type);
extern void *get_pointer_table(struct mm_struct *mm, int type);
extern int free_pointer_table(void *table, int type);
+extern void __tlb_remove_table(void *table);
/*
* Allocate and free page tables. The xxx_kernel() versions are
@@ -47,7 +48,7 @@ static inline void pte_free(struct mm_struct *mm, pgtable_t pgtable)
static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pgtable,
unsigned long address)
{
- free_pointer_table(pgtable, TABLE_PTE);
+ tlb_remove_table(tlb, (void *)((unsigned long)pgtable | TABLE_PTE));
}
@@ -61,10 +62,10 @@ static inline int pmd_free(struct mm_struct *mm, pmd_t *pmd)
return free_pointer_table(pmd, TABLE_PMD);
}
-static inline int __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd,
- unsigned long address)
+static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd,
+ unsigned long address)
{
- return free_pointer_table(pmd, TABLE_PMD);
+ tlb_remove_table(tlb, (void *)((unsigned long)pmd | TABLE_PMD));
}
diff --git a/arch/m68k/mm/motorola.c b/arch/m68k/mm/motorola.c
index b30aa69a73a6a..ffc80483440bc 100644
--- a/arch/m68k/mm/motorola.c
+++ b/arch/m68k/mm/motorola.c
@@ -20,6 +20,7 @@
#include <linux/init.h>
#include <linux/memblock.h>
#include <linux/gfp.h>
+#include <linux/cleanup.h>
#include <asm/setup.h>
#include <linux/uaccess.h>
@@ -103,6 +104,8 @@ static struct list_head ptable_list[3] = {
LIST_HEAD_INIT(ptable_list[2]),
};
+static DEFINE_SPINLOCK(ptable_lock);
+
#define PD_PTABLE(ptdesc) ((ptable_desc *)&(virt_to_ptdesc((void *)(ptdesc))->pt_list))
#define PD_PTDESC(ptable) (list_entry(ptable, struct ptdesc, pt_list))
#define PD_MARKBITS(dp) (*(unsigned int *)&PD_PTDESC(dp)->pt_index)
@@ -139,52 +142,66 @@ void __init init_pointer_table(void *table, int type)
return;
}
-void *get_pointer_table(struct mm_struct *mm, int type)
+/*
+ * For a pointer table for a user process address space, a
+ * table is taken from a ptdesc allocated for the purpose. Each
+ * ptdesc can hold 8 pointer tables. The ptdesc is remapped in
+ * virtual address space to be noncacheable.
+ */
+static void *add_pointer_table(struct mm_struct *mm, int type)
{
- ptable_desc *dp = ptable_list[type].next;
- unsigned int mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp);
- unsigned int tmp, off;
+ struct ptdesc *ptdesc;
+ ptable_desc *new;
+ void *pt_addr;
- /*
- * For a pointer table for a user process address space, a
- * table is taken from a ptdesc allocated for the purpose. Each
- * ptdesc can hold 8 pointer tables. The ptdesc is remapped in
- * virtual address space to be noncacheable.
- */
- if (mask == 0) {
- struct ptdesc *ptdesc;
- ptable_desc *new;
- void *pt_addr;
-
- ptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0);
- if (!ptdesc)
- return NULL;
-
- pt_addr = ptdesc_address(ptdesc);
-
- switch (type) {
- case TABLE_PTE:
- /*
- * m68k doesn't have SPLIT_PTE_PTLOCKS for not having
- * SMP.
- */
- pagetable_pte_ctor(mm, ptdesc);
- break;
- case TABLE_PMD:
- pagetable_pmd_ctor(mm, ptdesc);
- break;
- case TABLE_PGD:
- pagetable_pgd_ctor(ptdesc);
- break;
- }
+ ptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0);
+ if (!ptdesc)
+ return NULL;
+
+ pt_addr = ptdesc_address(ptdesc);
+
+ switch (type) {
+ case TABLE_PTE:
+ /*
+ * m68k doesn't have SPLIT_PTE_PTLOCKS for not having
+ * SMP.
+ */
+ pagetable_pte_ctor(mm, ptdesc);
+ break;
+ case TABLE_PMD:
+ pagetable_pmd_ctor(mm, ptdesc);
+ break;
+ case TABLE_PGD:
+ pagetable_pgd_ctor(ptdesc);
+ break;
+ }
+
+ mmu_page_ctor(pt_addr);
+
+ new = PD_PTABLE(pt_addr);
- mmu_page_ctor(pt_addr);
+ PD_MARKBITS(new) = ptable_mask(type) - 1;
+ scoped_guard(spinlock_irqsave, &ptable_lock)
+ list_add(new, &ptable_list[type]);
- new = PD_PTABLE(pt_addr);
- PD_MARKBITS(new) = ptable_mask(type) - 1;
- list_add_tail(new, dp);
+ return (pmd_t *)pt_addr;
+}
+
+void *get_pointer_table(struct mm_struct *mm, int type)
+{
+ unsigned int tmp, off;
+ unsigned long mask;
+ unsigned long flags;
+ ptable_desc *dp;
+ void *ret;
- return (pmd_t *)pt_addr;
+ spin_lock_irqsave(&ptable_lock, flags);
+ dp = ptable_list[type].next;
+ mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp);
+
+ if (mask == 0) {
+ spin_unlock_irqrestore(&ptable_lock, flags);
+ return add_pointer_table(mm, type);
}
for (tmp = 1, off = 0; (mask & tmp) == 0; tmp <<= 1, off += ptable_size(type))
@@ -194,7 +211,10 @@ void *get_pointer_table(struct mm_struct *mm, int type)
/* move to end of list */
list_move_tail(dp, &ptable_list[type]);
}
- return ptdesc_address(PD_PTDESC(dp)) + off;
+
+ ret = ptdesc_address(PD_PTDESC(dp)) + off;
+ spin_unlock_irqrestore(&ptable_lock, flags);
+ return ret;
}
int free_pointer_table(void *table, int type)
@@ -203,6 +223,9 @@ int free_pointer_table(void *table, int type)
unsigned long ptable = (unsigned long)table;
unsigned long pt_addr = ptable & PAGE_MASK;
unsigned int mask = 1U << ((ptable - pt_addr)/ptable_size(type));
+ unsigned long flags;
+
+ spin_lock_irqsave(&ptable_lock, flags);
dp = PD_PTABLE(pt_addr);
if (PD_MARKBITS (dp) & mask)
@@ -213,6 +236,8 @@ int free_pointer_table(void *table, int type)
if (PD_MARKBITS(dp) == ptable_mask(type)) {
/* all tables in ptdesc are free, free ptdesc */
list_del(dp);
+ spin_unlock_irqrestore(&ptable_lock, flags);
+
mmu_page_dtor((void *)pt_addr);
pagetable_dtor_free(virt_to_ptdesc((void *)pt_addr));
return 1;
@@ -223,9 +248,21 @@ int free_pointer_table(void *table, int type)
*/
list_move(dp, &ptable_list[type]);
}
+
+ spin_unlock_irqrestore(&ptable_lock, flags);
return 0;
}
+void __tlb_remove_table(void *table)
+{
+ /* The bottom 2 bits are used to encode page table type. */
+ const unsigned long encoded = (unsigned long)table;
+ void *addr = (void *)(encoded & ~3UL);
+ const int type = encoded & 3;
+
+ free_pointer_table(addr, type);
+}
+
/* size of memory already mapped in head.S */
extern __initdata unsigned long m68k_init_mapped_size;
diff --git a/arch/microblaze/include/asm/pgalloc.h b/arch/microblaze/include/asm/pgalloc.h
index 084a8a0dc2395..ffee6a009219a 100644
--- a/arch/microblaze/include/asm/pgalloc.h
+++ b/arch/microblaze/include/asm/pgalloc.h
@@ -25,7 +25,7 @@ extern void __bad_pte(pmd_t *pmd);
extern pte_t *pte_alloc_one_kernel(struct mm_struct *mm);
-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)->mm, (pte))
+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_ptdesc((tlb), page_ptdesc(pte))
#define pmd_populate(mm, pmd, pte) \
(pmd_val(*(pmd)) = (unsigned long)page_address(pte))
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index e2eb9627bd14c..f0c43d118ca00 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -97,7 +97,6 @@ config MIPS
select IRQ_FORCED_THREADING
select ISA if EISA
select LOCK_MM_AND_FIND_VMA
- select MMU_GATHER_RCU_TABLE_FREE
select MODULES_USE_ELF_REL if MODULES
select MODULES_USE_ELF_RELA if MODULES && 64BIT
select PERF_USE_VMALLOC
diff --git a/arch/parisc/Kconfig b/arch/parisc/Kconfig
index d3afac2f0d9be..77f67028ad89c 100644
--- a/arch/parisc/Kconfig
+++ b/arch/parisc/Kconfig
@@ -80,7 +80,6 @@ config PARISC
select GENERIC_CLOCKEVENTS
select CPU_NO_EFFICIENT_FFS
select THREAD_INFO_IN_TASK
- select MMU_GATHER_RCU_TABLE_FREE
select NEED_DMA_MAP_STATE
select NEED_SG_DMA_LENGTH
select HAVE_ARCH_KGDB
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 2580e27e43287..0767cfcbaa422 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -307,7 +307,6 @@ config PPC
select KASAN_VMALLOC if KASAN && EXECMEM
select LOCK_MM_AND_FIND_VMA
select MMU_GATHER_PAGE_SIZE
- select MMU_GATHER_RCU_TABLE_FREE
select HAVE_ARCH_TLB_REMOVE_TABLE
select MMU_GATHER_MERGE_VMAS
select MMU_LAZY_TLB_SHOOTDOWN if PPC_BOOK3S_64
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 505eed4af932b..7741a42874981 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -208,7 +208,6 @@ config RISCV
select IRQ_FORCED_THREADING
select KASAN_VMALLOC if KASAN
select LOCK_MM_AND_FIND_VMA
- select MMU_GATHER_RCU_TABLE_FREE if SMP && MMU
select MODULES_USE_ELF_RELA if MODULES
select OF
select OF_EARLY_FLATTREE
diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig
index b88b850421369..a34376c05f6e3 100644
--- a/arch/s390/Kconfig
+++ b/arch/s390/Kconfig
@@ -267,7 +267,6 @@ config S390
select LOCK_MM_AND_FIND_VMA
select MMU_GATHER_MERGE_VMAS
select MMU_GATHER_NO_GATHER
- select MMU_GATHER_RCU_TABLE_FREE
select MODULES_USE_ELF_RELA
select NEED_DMA_MAP_STATE if PCI
select NEED_PER_CPU_EMBED_FIRST_CHUNK
diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig
index d60f1d5a94c0f..fe859def918cc 100644
--- a/arch/sh/Kconfig
+++ b/arch/sh/Kconfig
@@ -33,6 +33,7 @@ config SUPERH
select HAVE_ARCH_AUDITSYSCALL
select HAVE_ARCH_KGDB
select HAVE_ARCH_SECCOMP_FILTER
+ select HAVE_ARCH_TLB_REMOVE_TABLE if X2TLB
select HAVE_ARCH_TRACEHOOK
select HAVE_DEBUG_BUGVERBOSE
select HAVE_DEBUG_KMEMLEAK
diff --git a/arch/sh/include/asm/pgalloc.h b/arch/sh/include/asm/pgalloc.h
index 6fe7123d38fa9..67ce7fa23fa12 100644
--- a/arch/sh/include/asm/pgalloc.h
+++ b/arch/sh/include/asm/pgalloc.h
@@ -17,7 +17,11 @@ extern void pgd_free(struct mm_struct *mm, pgd_t *pgd);
extern void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmd);
extern pmd_t *pmd_alloc_one(struct mm_struct *mm, unsigned long address);
extern void pmd_free(struct mm_struct *mm, pmd_t *pmd);
-#define __pmd_free_tlb(tlb, pmdp, addr) pmd_free((tlb)->mm, (pmdp))
+extern void __tlb_remove_table(void *table);
+
+/* PMDs are slab-allocated, tag so they are freed correctly. */
+#define __pmd_free_tlb(tlb, pmdp, addr) \
+ tlb_remove_table((tlb), (void *)((unsigned long)(pmdp) | 1))
#endif
static inline void pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmd,
diff --git a/arch/sh/mm/pgtable.c b/arch/sh/mm/pgtable.c
index 3a4085ea0161f..f6184b86b89c6 100644
--- a/arch/sh/mm/pgtable.c
+++ b/arch/sh/mm/pgtable.c
@@ -56,4 +56,24 @@ void pmd_free(struct mm_struct *mm, pmd_t *pmd)
{
kmem_cache_free(pmd_cachep, pmd);
}
+
+static void __tlb_remove_table_slab(void *table)
+{
+ kmem_cache_free(pmd_cachep, table);
+}
+
+static void __tlb_remove_table_pgtable(void *table)
+{
+ pagetable_dtor_free(table);
+}
+
+void __tlb_remove_table(void *table)
+{
+ const unsigned long addr = (unsigned long)table;
+
+ if (addr & 1)
+ __tlb_remove_table_slab((void *)(addr & ~1UL));
+ else
+ __tlb_remove_table_pgtable(table);
+}
#endif /* PAGETABLE_LEVELS > 2 */
diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig
index ab77d3f2536e1..742ffff8c37f2 100644
--- a/arch/sparc/Kconfig
+++ b/arch/sparc/Kconfig
@@ -64,6 +64,7 @@ config SPARC32
select HAVE_UID16
select HAVE_PAGE_SIZE_4KB
select LOCK_MM_AND_FIND_VMA
+ select HAVE_ARCH_TLB_REMOVE_TABLE
select OLD_SIGACTION
select ZONE_DMA
@@ -75,8 +76,7 @@ config SPARC64
select HAVE_FUNCTION_GRAPH_TRACER
select HAVE_KRETPROBES
select HAVE_KPROBES
- select MMU_GATHER_RCU_TABLE_FREE if SMP
- select HAVE_ARCH_TLB_REMOVE_TABLE if SMP
+ select HAVE_ARCH_TLB_REMOVE_TABLE
select MMU_GATHER_MERGE_VMAS
select MMU_GATHER_NO_FLUSH_CACHE
select HAVE_ARCH_TRANSPARENT_HUGEPAGE
diff --git a/arch/sparc/include/asm/pgalloc_32.h b/arch/sparc/include/asm/pgalloc_32.h
index 4f73e87b22a32..36010852ba0c0 100644
--- a/arch/sparc/include/asm/pgalloc_32.h
+++ b/arch/sparc/include/asm/pgalloc_32.h
@@ -48,7 +48,9 @@ static inline void free_pmd_fast(pmd_t * pmd)
}
#define pmd_free(mm, pmd) free_pmd_fast(pmd)
-#define __pmd_free_tlb(tlb, pmd, addr) pmd_free((tlb)->mm, pmd)
+
+#define __pmd_free_tlb(tlb, pmd, addr) \
+ tlb_remove_table((tlb), (void *)((unsigned long)(pmd) | 1UL))
#define pmd_populate(mm, pmd, pte) pmd_set(pmd, pte)
@@ -72,6 +74,7 @@ static inline void free_pte_fast(pte_t *pte)
#define pte_free_kernel(mm, pte) free_pte_fast(pte)
void pte_free(struct mm_struct * mm, pgtable_t pte);
-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)->mm, pte)
+void __tlb_remove_table(void *table);
+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_table((tlb), (void *)(pte))
#endif /* _SPARC_PGALLOC_H */
diff --git a/arch/sparc/include/asm/pgalloc_64.h b/arch/sparc/include/asm/pgalloc_64.h
index caa7632be4c2a..b5055d259b74d 100644
--- a/arch/sparc/include/asm/pgalloc_64.h
+++ b/arch/sparc/include/asm/pgalloc_64.h
@@ -74,8 +74,6 @@ void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);
void pgtable_free(void *table, bool is_page);
-#ifdef CONFIG_SMP
-
struct mmu_gather;
void tlb_remove_table(struct mmu_gather *, void *);
@@ -96,12 +94,6 @@ static inline void __tlb_remove_table(void *_table)
is_page = true;
pgtable_free(table, is_page);
}
-#else /* CONFIG_SMP */
-static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, bool is_page)
-{
- pgtable_free(table, is_page);
-}
-#endif /* !CONFIG_SMP */
static inline void __pte_free_tlb(struct mmu_gather *tlb, pte_t *pte,
unsigned long address)
diff --git a/arch/sparc/include/asm/tlb_64.h b/arch/sparc/include/asm/tlb_64.h
index 3037187482db7..f5f9631685d50 100644
--- a/arch/sparc/include/asm/tlb_64.h
+++ b/arch/sparc/include/asm/tlb_64.h
@@ -29,9 +29,7 @@ void flush_tlb_pending(void);
* and therefore we don't need a TLBI when freeing page-table pages.
*/
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
#define tlb_needs_table_invalidate() (false)
-#endif
#include <asm-generic/tlb.h>
diff --git a/arch/sparc/lib/bitext.c b/arch/sparc/lib/bitext.c
index 32a5c1d9459cd..c309e27973ce6 100644
--- a/arch/sparc/lib/bitext.c
+++ b/arch/sparc/lib/bitext.c
@@ -22,8 +22,6 @@
* @align: requested alignment
*
* Returns offset in the map or -1 if out of space.
- *
- * Not safe to call from an interrupt (uses spin_lock).
*/
int bit_map_string_get(struct bit_map *t, int len, int align)
{
@@ -31,6 +29,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
int off_new;
int align1;
int i, color;
+ unsigned long flags;
if (t->num_colors) {
/* align is overloaded to be the page color */
@@ -50,7 +49,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
BUG();
color &= align1;
- spin_lock(&t->lock);
+ spin_lock_irqsave(&t->lock, flags);
if (len < t->last_size)
offset = t->first_free;
else
@@ -64,7 +63,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
if (offset >= t->size)
offset = 0;
if (count + len > t->size) {
- spin_unlock(&t->lock);
+ spin_unlock_irqrestore(&t->lock, flags);
/* P3 */ printk(KERN_ERR
"bitmap out: size %d used %d off %d len %d align %d count %d\n",
t->size, t->used, offset, len, align, count);
@@ -90,7 +89,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
t->last_off = 0;
t->used += len;
t->last_size = len;
- spin_unlock(&t->lock);
+ spin_unlock_irqrestore(&t->lock, flags);
return offset;
}
}
@@ -103,10 +102,11 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
void bit_map_clear(struct bit_map *t, int offset, int len)
{
int i;
+ unsigned long flags;
if (t->used < len)
BUG(); /* Much too late to do any good, but alas... */
- spin_lock(&t->lock);
+ spin_lock_irqsave(&t->lock, flags);
for (i = 0; i < len; i++) {
if (test_bit(offset + i, t->map) == 0)
BUG();
@@ -115,7 +115,7 @@ void bit_map_clear(struct bit_map *t, int offset, int len)
if (offset < t->first_free)
t->first_free = offset;
t->used -= len;
- spin_unlock(&t->lock);
+ spin_unlock_irqrestore(&t->lock, flags);
}
void bit_map_init(struct bit_map *t, unsigned long *map, int size)
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
index 9a74902ad1814..2a2c7bd210114 100644
--- a/arch/sparc/mm/srmmu.c
+++ b/arch/sparc/mm/srmmu.c
@@ -359,19 +359,39 @@ pgtable_t pte_alloc_one(struct mm_struct *mm)
return ptep;
}
-void pte_free(struct mm_struct *mm, pgtable_t ptep)
+static void __pte_free(struct mm_struct *mm, pgtable_t ptep)
{
+ const bool process_context = mm;
struct page *page;
page = pfn_to_page(__nocache_pa((unsigned long)ptep) >> PAGE_SHIFT);
- spin_lock(&mm->page_table_lock);
+ if (process_context)
+ spin_lock(&mm->page_table_lock);
if (page_ref_dec_return(page) == 1)
pagetable_dtor(page_ptdesc(page));
- spin_unlock(&mm->page_table_lock);
+ if (process_context)
+ spin_unlock(&mm->page_table_lock);
srmmu_free_nocache(ptep, SRMMU_PTE_TABLE_SIZE);
}
+void pte_free(struct mm_struct *mm, pgtable_t ptep)
+{
+ __pte_free(mm, ptep);
+}
+
+void __tlb_remove_table(void *table)
+{
+ const unsigned long encoded = (unsigned long)table;
+ const unsigned long addr = encoded & ~1UL;
+ const bool is_pmd = encoded & 1;
+
+ if (is_pmd)
+ free_pmd_fast((pmd_t *)addr);
+ else /* Called from softirq context, no mm. */
+ __pte_free(NULL, (pgtable_t)addr);
+}
+
/* context handling - a dynamically sized pool is used */
#define NO_CONTEXT -1
diff --git a/arch/um/Kconfig b/arch/um/Kconfig
index d9541d13d9eb0..94b8ff70f578b 100644
--- a/arch/um/Kconfig
+++ b/arch/um/Kconfig
@@ -44,7 +44,6 @@ config UML
select HAVE_SYSCALL_TRACEPOINTS
select THREAD_INFO_IN_TASK
select SPARSE_IRQ
- select MMU_GATHER_RCU_TABLE_FREE
config MMU
bool
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index a8c3b3d31a276..6e5e462ec059a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -283,7 +283,6 @@ config X86
select HAVE_PERF_REGS
select HAVE_PERF_USER_STACK_DUMP
select ASYNC_KERNEL_PGTABLE_FREE if IOMMU_SVA
- select MMU_GATHER_RCU_TABLE_FREE
select MMU_GATHER_MERGE_VMAS
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
select HAVE_REGS_AND_STACK_ACCESS_API
diff --git a/arch/xtensa/include/asm/tlb.h b/arch/xtensa/include/asm/tlb.h
index 8c3ceb4270180..6fb7b78154f62 100644
--- a/arch/xtensa/include/asm/tlb.h
+++ b/arch/xtensa/include/asm/tlb.h
@@ -16,7 +16,7 @@
#include <asm-generic/tlb.h>
-#define __pte_free_tlb(tlb, pte, address) pte_free((tlb)->mm, pte)
+#define __pte_free_tlb(tlb, pte, address) tlb_remove_ptdesc((tlb), page_ptdesc(pte))
void check_tlb_sanity(void);
diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h
index bdcc2778ac64f..044dabc1fe9cb 100644
--- a/include/asm-generic/tlb.h
+++ b/include/asm-generic/tlb.h
@@ -67,11 +67,8 @@
* - tlb_remove_table()
*
* tlb_remove_table() is the basic primitive to free page-table directories
- * (__p*_free_tlb()). In it's most primitive form it is an alias for
- * tlb_remove_page() below, for when page directories are pages and have no
- * additional constraints.
- *
- * See also MMU_GATHER_TABLE_FREE and MMU_GATHER_RCU_TABLE_FREE.
+ * (__p*_free_tlb()). Page directories are freed after an RCU grace
+ * period - see the comment in mm/mmu_gather.c.
*
* - tlb_remove_page() / tlb_remove_page_size()
* - __tlb_remove_folio_pages() / __tlb_remove_page_size()
@@ -151,24 +148,15 @@
* This might be useful if your architecture has size specific TLB
* invalidation instructions.
*
- * MMU_GATHER_TABLE_FREE
- *
- * This provides tlb_remove_table(), to be used instead of tlb_remove_page()
- * for page directores (__p*_free_tlb()).
- *
- * Useful if your architecture has non-page page directories.
+ * Page directories (__p*_free_tlb()) are always freed via tlb_remove_table(),
+ * after an RCU grace period (see mm/mmu_gather.c).
*
- * When used, an architecture is expected to provide __tlb_remove_table() or
- * use the generic __tlb_remove_table(), which does the actual freeing of these
- * pages.
+ * This serialises against software page-table walkers, including architectures
+ * which do not use IPIs for remote TLB invalidates.
*
- * MMU_GATHER_RCU_TABLE_FREE
- *
- * Like MMU_GATHER_TABLE_FREE, and adds semi-RCU semantics to the free (see
- * comment below).
- *
- * Useful if your architecture doesn't use IPIs for remote TLB invalidates
- * and therefore doesn't naturally serialize with software page-table walkers.
+ * An architecture is expected to provide __tlb_remove_table() (see
+ * HAVE_ARCH_TLB_REMOVE_TABLE) or use the generic __tlb_remove_table(), which
+ * does the actual freeing of these pages.
*
* MMU_GATHER_NO_FLUSH_CACHE
*
@@ -200,12 +188,8 @@
* various ptep_get_and_clear() functions.
*/
-#ifdef CONFIG_MMU_GATHER_TABLE_FREE
-
struct mmu_table_batch {
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
struct rcu_head rcu;
-#endif
unsigned int nr;
void *tables[];
};
@@ -224,23 +208,6 @@ static inline void __tlb_remove_table(void *table)
extern void tlb_remove_table(struct mmu_gather *tlb, void *table);
-#else /* !CONFIG_MMU_GATHER_TABLE_FREE */
-
-static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page);
-/*
- * Without MMU_GATHER_TABLE_FREE the architecture is assumed to have page based
- * page directories and we can use the normal page batching to free them.
- */
-static inline void tlb_remove_table(struct mmu_gather *tlb, void *table)
-{
- struct ptdesc *ptdesc = (struct ptdesc *)table;
-
- pagetable_dtor(ptdesc);
- tlb_remove_page(tlb, ptdesc_page(ptdesc));
-}
-#endif /* CONFIG_MMU_GATHER_TABLE_FREE */
-
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
/*
* This allows an architecture that does not use the linux page-tables for
* hardware to skip the TLBI when freeing page tables.
@@ -253,19 +220,6 @@ void tlb_remove_table_sync_one(void);
void tlb_remove_table_sync_rcu(void);
-#else
-
-#ifdef tlb_needs_table_invalidate
-#error tlb_needs_table_invalidate() requires MMU_GATHER_RCU_TABLE_FREE
-#endif
-
-static inline void tlb_remove_table_sync_one(void) { }
-
-static inline void tlb_remove_table_sync_rcu(void) { }
-
-#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */
-
-
#ifndef CONFIG_MMU_GATHER_NO_GATHER
/*
* If we can't allocate a page to make a big batch of page pointers
@@ -325,9 +279,7 @@ static inline void tlb_flush_rmaps(struct mmu_gather *tlb, struct vm_area_struct
struct mmu_gather {
struct mm_struct *mm;
-#ifdef CONFIG_MMU_GATHER_TABLE_FREE
struct mmu_table_batch *batch;
-#endif
unsigned long start;
unsigned long end;
diff --git a/mm/Kconfig b/mm/Kconfig
index c1ddf59c0d71a..bc7befafb47b5 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1465,7 +1465,7 @@ config HAVE_ARCH_TLB_REMOVE_TABLE
config PT_RECLAIM
def_bool y
- depends on MMU_GATHER_RCU_TABLE_FREE && !HAVE_ARCH_TLB_REMOVE_TABLE
+ depends on MMU && !HAVE_ARCH_TLB_REMOVE_TABLE
help
Try to reclaim empty user page table pages in paths other than munmap
and exit_mmap path.
diff --git a/mm/gup.c b/mm/gup.c
index eb898ea1ee22e..63b435ec605c8 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -2700,8 +2700,9 @@ EXPORT_SYMBOL(get_user_pages_unlocked);
* Before activating this code, please be aware that the following assumptions
* are currently made:
*
- * *) Either MMU_GATHER_RCU_TABLE_FREE is enabled, and tlb_remove_table() is used to
- * free pages containing page tables or TLB flushing requires IPI broadcast.
+ * *) tlb_remove_table() is used to free pages containing page tables, with
+ * the free deferred until an RCU grace period has elapsed (see
+ * mm/mmu_gather.c).
*
* *) ptes can be read atomically by the architecture.
*
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 54494c3fa9835..505f7b62ff281 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2476,7 +2476,7 @@ static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)
pgtable_t pgtable;
pgtable = pgtable_trans_huge_withdraw(mm, pmd);
- pte_free(mm, pgtable);
+ pte_free_defer(mm, pgtable);
mm_dec_nr_ptes(mm);
}
diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
index 3985d856de7f9..2a72a9686773a 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -218,8 +218,6 @@ bool __tlb_remove_page_size(struct mmu_gather *tlb, struct page *page, int page_
#endif /* MMU_GATHER_NO_GATHER */
-#ifdef CONFIG_MMU_GATHER_TABLE_FREE
-
static void __tlb_remove_table_free(struct mmu_table_batch *batch)
{
int i;
@@ -230,10 +228,8 @@ static void __tlb_remove_table_free(struct mmu_table_batch *batch)
free_page((unsigned long)batch);
}
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
-
/*
- * Semi RCU freeing of the page directories.
+ * RCU freeing of the page directories.
*
* This is needed by some architectures to implement software pagetable walkers.
*
@@ -259,13 +255,13 @@ static void __tlb_remove_table_free(struct mmu_table_batch *batch)
* means.
*
* What we do is batch the freed directory pages (tables) and RCU free them.
- * We use the sched RCU variant, as that guarantees that IRQ/preempt disabling
- * holds off grace periods.
+ * Disabling IRQs or preemption holds off RCU grace periods, so this protects
+ * both rcu_read_lock() and IRQ-disabling walkers.
*
* However, in order to batch these pages we need to allocate storage, this
* allocation is deep inside the MM code and can thus easily fail on memory
- * pressure. To guarantee progress we fall back to single table freeing, see
- * the implementation of tlb_remove_table_one().
+ * pressure. To guarantee progress we fall back to single table freeing, which
+ * is also RCU-deferred - see the implementation of tlb_remove_table_one().
*
*/
@@ -315,15 +311,6 @@ void tlb_remove_table_sync_rcu(void)
synchronize_rcu();
}
-#else /* !CONFIG_MMU_GATHER_RCU_TABLE_FREE */
-
-static void tlb_remove_table_free(struct mmu_table_batch *batch)
-{
- __tlb_remove_table_free(batch);
-}
-
-#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */
-
/*
* If we want tlb_remove_table() to imply TLB invalidates.
*/
@@ -403,13 +390,6 @@ static inline void tlb_table_init(struct mmu_gather *tlb)
tlb->batch = NULL;
}
-#else /* !CONFIG_MMU_GATHER_TABLE_FREE */
-
-static inline void tlb_table_flush(struct mmu_gather *tlb) { }
-static inline void tlb_table_init(struct mmu_gather *tlb) { }
-
-#endif /* CONFIG_MMU_GATHER_TABLE_FREE */
-
static void tlb_flush_mmu_free(struct mmu_gather *tlb)
{
tlb_table_flush(tlb);
diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c
index b91b1a98029c7..ff8ff37064853 100644
--- a/mm/pgtable-generic.c
+++ b/mm/pgtable-generic.c
@@ -386,9 +386,21 @@ pte_t *pte_offset_map_rw_nolock(struct mm_struct *mm, pmd_t *pmd,
* be read-only/read-write protected.
*
* Note that free_pgtables(), used after unmapping detached vmas, or when
- * exiting the whole mm, does not take page table lock before freeing a page
- * table, and may not use RCU at all: "outsiders" like khugepaged should avoid
- * pte_offset_map() and co once the vma is detached from mm or mm_users is zero.
+ * exiting the whole mm, does not take the page table lock before freeing a
+ * table.
+ *
+ * However, the PMD entry is cleared first, and the table freed only after
+ * an RCU grace period, so a walker that mapped the table under
+ * rcu_read_lock() stays safe, and the pmd_same() recheck in
+ * pte_offset_map_lock() detects the teardown.
+ *
+ * Therefore it is safe for "outsiders" like khugepaged to use
+ * pte_offset_map() and co. for VMAs that might be undergoing page table
+ * teardown.
+ *
+ * Note that the PGD itself is freed at mmdrop() time, not under RCU - so the
+ * walker must keep the mm alive via mmgrab(). With that held, walking remains
+ * safe even once mm_users has reached zero.
*/
pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
unsigned long addr, spinlock_t **ptlp)
|
| 3/2 | 2026/09/01 23:43 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 15995 output: 19 thoughts: 547 Thoughts: **Analyzing the Patch: RCU Table Freeing** Okay, let's dive into this patch and figure out what's going on. My initial impression is that it's simplifying RCU table freeing across the board. The goal seems to be to make RCU-based table freeing the standard, regardless of architecture, instead of conditionally compiling it in. We're getting rid of `CONFIG_MMU_GATHER_TABLE_FREE` and `CONFIG_MMU_GATHER_RCU_TABLE_FREE`, which suggests a move towards a unified approach. I need to confirm this. Looking at the diff, I see changes scattered across the codebase. `arch/Kconfig` files are being updated, and several architecture-specific `include/asm/pgalloc.h` files are being updated to use `tlb_remove_ptdesc`, rather than conditional calls to `__pud_free_tlb`, `__pmd_free_tlb`, and `__pte_free_tlb`. That reinforces the idea of standardizing on the RCU mechanism. The `include/asm-generic/tlb.h` is also removing the `ifdef`s that used to control the `struct mmu_table_batch` and RCU table removal. Further down the line, I'm noting changes to `mm/Kconfig` (the `PT_RECLAIM` option looks like it's being conditioned on the MMU and an architecture not having `HAVE_ARCH_TLB_REMOVE_TABLE`), and comments in `mm/gup.c` and `mm/pgtable-generic.c`. The most interesting part, though, is the change to `mm/huge_memory.c`. `zap_deposited_table` is now using `pte_free_defer` instead of `pte_free`. This is a crucial point, I need to check exactly what `zap_deposited_table` does, and understand `pte_free_defer` in this context. I need to understand if the huge page code is adopting the same strategy. I'm going to quickly check how `pte_free_defer` is implemented. I'll also read up on `zap_deposited_table` to fully understand what is happening here. I'm going to use `grepper` or `read-file` to check `pte_free_defer` and `zap_deposited_table`, and then I will be able to verify that my initial understanding is correct. |
| 4/2 | 2026/09/01 23:43 | tool | grepper |
0mArgs:{
"Expression": "pte_free_defer"
}Results: {
"Output": "arch/powerpc/include/asm/pgalloc.h=43=static inline void pte_free(struct mm_struct *mm, pgtable_t ptepage)\n--\narch/powerpc/include/asm/pgalloc.h-47-\narch/powerpc/include/asm/pgalloc.h:48:/* arch use pte_free_defer() implementation in arch/powerpc/mm/pgtable-frag.c */\narch/powerpc/include/asm/pgalloc.h:49:#define pte_free_defer pte_free_defer\narch/powerpc/include/asm/pgalloc.h:50:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\narch/powerpc/include/asm/pgalloc.h-51-\n--\narch/powerpc/mm/pgtable-frag.c=117=void pte_fragment_free(unsigned long *table, int kernel)\n--\narch/powerpc/mm/pgtable-frag.c-133-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\narch/powerpc/mm/pgtable-frag.c:134:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\narch/powerpc/mm/pgtable-frag.c-135-{\n--\narch/s390/include/asm/pgalloc.h=151=static inline void pmd_populate(struct mm_struct *mm,\n--\narch/s390/include/asm/pgalloc.h-167-\narch/s390/include/asm/pgalloc.h:168:/* arch use pte_free_defer() implementation in arch/s390/mm/pgalloc.c */\narch/s390/include/asm/pgalloc.h:169:#define pte_free_defer pte_free_defer\narch/s390/include/asm/pgalloc.h:170:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\narch/s390/include/asm/pgalloc.h-171-\n--\narch/s390/mm/pgalloc.c=149=static void pte_free_now(struct rcu_head *head)\n--\narch/s390/mm/pgalloc.c-155-\narch/s390/mm/pgalloc.c:156:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\narch/s390/mm/pgalloc.c-157-{\n--\narch/sparc/include/asm/pgalloc_64.h=66=void pte_free(struct mm_struct *mm, pgtable_t ptepage);\narch/sparc/include/asm/pgalloc_64.h-67-\narch/sparc/include/asm/pgalloc_64.h:68:/* arch use pte_free_defer() implementation in arch/sparc/mm/init_64.c */\narch/sparc/include/asm/pgalloc_64.h:69:#define pte_free_defer pte_free_defer\narch/sparc/include/asm/pgalloc_64.h:70:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\narch/sparc/include/asm/pgalloc_64.h-71-\n--\narch/sparc/mm/init_64.c=2866=static void pte_free_now(struct rcu_head *head)\n--\narch/sparc/mm/init_64.c-2873-\narch/sparc/mm/init_64.c:2874:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\narch/sparc/mm/init_64.c-2875-{\n--\ninclude/linux/pgtable.h=115=static inline void pte_unmap(pte_t *pte)\n--\ninclude/linux/pgtable.h-120-\ninclude/linux/pgtable.h:121:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\ninclude/linux/pgtable.h-122-\n--\nmm/huge_memory.c=2474=static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)\n--\nmm/huge_memory.c-2478-\tpgtable = pgtable_trans_huge_withdraw(mm, pmd);\nmm/huge_memory.c:2479:\tpte_free_defer(mm, pgtable);\nmm/huge_memory.c-2480-\tmm_dec_nr_ptes(mm);\n--\nmm/khugepaged.c=1862=static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsigned long addr,\n--\nmm/khugepaged.c-2067-\tpage_table_check_pte_clear_range(mm, haddr, pgt_pmd);\nmm/khugepaged.c:2068:\tpte_free_defer(mm, pmd_pgtable(pgt_pmd));\nmm/khugepaged.c-2069-\n--\nmm/khugepaged.c=2151=static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)\n--\nmm/khugepaged.c-2228-\t\t\tpage_table_check_pte_clear_range(mm, addr, pgt_pmd);\nmm/khugepaged.c:2229:\t\t\tpte_free_defer(mm, pmd_pgtable(pgt_pmd));\nmm/khugepaged.c-2230-\t\t}\n--\nmm/pgtable-generic.c=221=pmd_t pmdp_collapse_flush(struct vm_area_struct *vma, unsigned long address,\n--\nmm/pgtable-generic.c-239-\nmm/pgtable-generic.c:240:/* arch define pte_free_defer in asm/pgalloc.h for its own implementation */\nmm/pgtable-generic.c:241:#ifndef pte_free_defer\nmm/pgtable-generic.c-242-static void pte_free_now(struct rcu_head *head)\n--\nmm/pgtable-generic.c-249-\nmm/pgtable-generic.c:250:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\nmm/pgtable-generic.c-251-{\n--\nmm/pgtable-generic.c-256-}\nmm/pgtable-generic.c:257:#endif /* pte_free_defer */\nmm/pgtable-generic.c-258-#endif /* CONFIG_TRANSPARENT_HUGEPAGE */\n"
} |
| 5/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 18853 output: 36 thoughts: 889 Thoughts: **Fuzzing Feasibility Assessment: RCU Page Table Freeing** Okay, so this patch series looks like a significant refactor to make RCU-based page table freeing the standard approach across all architectures. It's essentially unifying the method of freeing page tables under `MMU_GATHER_RCU_TABLE_FREE`, cleaning up some Kconfig options related to table freeing, and updating architecture-specific implementations to use this generic mechanism. The `zap_deposited_table` function now uses `pte_free_defer`, which is a key change. My first thought is, "Is this functionally significant enough to warrant fuzzing?" Based on the guidelines, I need to determine if this impacts reachable core kernel logic or architectures. Let's see... I'm seeing changes across a bunch of core MM code, like `mm/mmu_gather.c`, `mm/huge_memory.c`, `include/asm-generic/tlb.h`, and `mm/Kconfig`, and architecture-specific code for things like ARC, m68k, microblaze, sh, sparc, xtensa, and x86. This isn't just cosmetic; it's a fundamental change to how page tables are managed and freed. Specifically: * In `mm/huge_memory.c`, the critical change in `zap_deposited_table` now defers the freeing of the deposited page table using RCU via `pte_free_defer`. That's a huge shift from direct freeing. * In `mm/mmu_gather.c` and `include/asm-generic/tlb.h`, all architectures are now using RCU-backed table freeing. * The architecture-specific implementations (e.g., m68k, sparc, arc, etc.) are being updated to use `tlb_remove_table` or similar RCU-based functions instead of synchronous freeing in `__pte_free_tlb` / `__pmd_free_tlb`. * There are architectural changes, like spinlock adjustments and rewritten functions in m68k and sparc. So, yeah, this clearly affects core MM code, page table teardown, THP zap/collapse and TLB gather table freeing! This is definitely a functional change. Therefore, this patch series warrants a closer look for fuzzing potential. It seems like a high-value target. Next, I need to figure out which config options are relevant and what focus symbols would be useful. I'll need to check the code around `zap_deposited_table`, `pte_free_defer`, `tlb_remove_table`, and `tlb_table_flush` in `mm/huge_memory.c`, `mm/mmu_gather.c`, and `mm/pgtable-generic.c` to narrow down the test case focus. Let's dig in. |
| 6/2 | 2026/09/01 23:44 | tool | read-file |
0mArgs:{
"File": "mm/huge_memory.c",
"FirstLine": 2465,
"LineCount": 40
}Results: {
"Contents": "2465:\t\n2466:\t\tfolio_mark_lazyfree(folio);\n2467:\t\tret = true;\n2468:\tout:\n2469:\t\tspin_unlock(ptl);\n2470:\tout_unlocked:\n2471:\t\treturn ret;\n2472:\t}\n2473:\t\n2474:\tstatic inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)\n2475:\t{\n2476:\t\tpgtable_t pgtable;\n2477:\t\n2478:\t\tpgtable = pgtable_trans_huge_withdraw(mm, pmd);\n2479:\t\tpte_free_defer(mm, pgtable);\n2480:\t\tmm_dec_nr_ptes(mm);\n2481:\t}\n2482:\t\n2483:\tstatic void zap_huge_pmd_folio(struct mm_struct *mm, struct vm_area_struct *vma,\n2484:\t\t\tpmd_t pmdval, struct folio *folio, bool is_present)\n2485:\t{\n2486:\t\tconst bool is_device_private = folio_is_device_private(folio);\n2487:\t\n2488:\t\t/* Present and device private folios are rmappable. */\n2489:\t\tif (is_present || is_device_private)\n2490:\t\t\tfolio_remove_rmap_pmd(folio, \u0026folio-\u003epage, vma);\n2491:\t\n2492:\t\tif (folio_test_anon(folio)) {\n2493:\t\t\tadd_mm_counter(mm, MM_ANONPAGES, -HPAGE_PMD_NR);\n2494:\t\t} else {\n2495:\t\t\tadd_mm_counter(mm, mm_counter_file(folio),\n2496:\t\t\t\t -HPAGE_PMD_NR);\n2497:\t\n2498:\t\t\tif (is_present \u0026\u0026 pmd_dirty(pmdval))\n2499:\t\t\t\tfolio_mark_dirty(folio);\n2500:\t\t\tif (is_present \u0026\u0026 pmd_young(pmdval) \u0026\u0026\n2501:\t\t\t likely(vma_has_recency(vma)))\n2502:\t\t\t\tfolio_mark_accessed(folio);\n2503:\t\t}\n2504:\t\n"
} |
| 7/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21031 output: 27 thoughts: 15 Thoughts: Here's my take: **Tracing the Lineage of `zap_deposited_table`** Okay, so I need to understand the lifecycle of `zap_deposited_table`. It's a key data point, and I need to see where it originates, how it's used, and if there are any potential bottlenecks or dependencies. The best first step is to see exactly where this table, or rather its name, is referenced within the codebase. I need to understand the calls being made. Knowing the call stack will let me understand its purpose and integration within the broader system. I'll start with a straightforward search for calls. This will provide a solid foundation for further investigation, allowing me to build a proper mental model of its impact. |
| 8/2 | 2026/09/01 23:44 | tool | grepper |
0mArgs:{
"Expression": "zap_deposited_table",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/huge_memory.c=2400=bool madvise_free_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,\n--\nmm/huge_memory.c-2473-\nmm/huge_memory.c:2474:static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)\nmm/huge_memory.c-2475-{\n--\nmm/huge_memory.c=2551=bool zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,\n--\nmm/huge_memory.c-2582-\tif (has_deposit)\nmm/huge_memory.c:2583:\t\tzap_deposited_table(mm, pmd);\nmm/huge_memory.c-2584-\n--\nmm/huge_memory.c=3188=static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n--\nmm/huge_memory.c-3216-\t\tif (arch_needs_pgtable_deposit())\nmm/huge_memory.c:3217:\t\t\tzap_deposited_table(mm, pmd);\nmm/huge_memory.c-3218-\t\tif (vma_is_special_huge(vma))\n--\nmm/huge_memory.c=3561=static bool __discard_anon_folio_pmd_locked(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-3614-\tfolio_remove_rmap_pmd(folio, pmd_page(orig_pmd), vma);\nmm/huge_memory.c:3615:\tzap_deposited_table(mm, pmdp);\nmm/huge_memory.c-3616-\tadd_mm_counter(mm, MM_ANONPAGES, -HPAGE_PMD_NR);\n"
} |
| 9/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21733 output: 36 thoughts: 54 Thoughts: Okay, let's dive into this. **Examining Huge Page Unmapping and Gather Operations** Right, time to get my hands dirty. I need to refresh my memory on how huge pages are unmapped within the kernel. My first instinct is to zero in on `zap_huge_pmd` within `mm/huge_memory.c`. That's where I'd expect to find the core logic for removing a huge page's mapping. I'll need to scrutinize how it handles the details, especially regarding potential TLB invalidation and synchronization. Then, to understand the broader context, I should also look at the `mmu_gather.c` file. Specifically, I'll need to understand the role of the functions within `mm/mmu_gather.c`. I'll be looking to see how the gathers are implemented within `mmu_gather.c` and how that interacts with the page unmapping process. I need to understand what the gathers are gathering, the reasons for gathering, and how that interacts with the specific unmapping calls within the `zap_huge_pmd` process. With a strong understanding of both, I should get a more complete picture of the whole process. |
| 10/2 | 2026/09/01 23:44 | tool | read-file |
0mArgs:{
"File": "mm/mmu_gather.c",
"FirstLine": 270,
"LineCount": 80
}Results: {
"Contents": " 270:\t\t/* Simply deliver the interrupt */\n 271:\t}\n 272:\t\n 273:\tvoid tlb_remove_table_sync_one(void)\n 274:\t{\n 275:\t\t/*\n 276:\t\t * This isn't an RCU grace period and hence the page-tables cannot be\n 277:\t\t * assumed to be actually RCU-freed.\n 278:\t\t *\n 279:\t\t * It is however sufficient for software page-table walkers that rely on\n 280:\t\t * IRQ disabling.\n 281:\t\t */\n 282:\t\tsmp_call_function(tlb_remove_table_smp_sync, NULL, 1);\n 283:\t}\n 284:\t\n 285:\tstatic void tlb_remove_table_rcu(struct rcu_head *head)\n 286:\t{\n 287:\t\t__tlb_remove_table_free(container_of(head, struct mmu_table_batch, rcu));\n 288:\t}\n 289:\t\n 290:\tstatic void tlb_remove_table_free(struct mmu_table_batch *batch)\n 291:\t{\n 292:\t\tcall_rcu(\u0026batch-\u003ercu, tlb_remove_table_rcu);\n 293:\t}\n 294:\t\n 295:\t/**\n 296:\t * tlb_remove_table_sync_rcu - synchronize with software page-table walkers\n 297:\t *\n 298:\t * Like tlb_remove_table_sync_one() but uses RCU grace period instead of IPI\n 299:\t * broadcast. Use in slow paths where sleeping is acceptable.\n 300:\t *\n 301:\t * Software/Lockless page-table walkers use local_irq_disable(), which is also\n 302:\t * an RCU read-side critical section. synchronize_rcu() waits for all such\n 303:\t * sections, providing the same guarantee as tlb_remove_table_sync_one() but\n 304:\t * without disrupting all CPUs with IPIs.\n 305:\t *\n 306:\t * Do not use for freeing memory. Use RCU callbacks instead to avoid latency\n 307:\t * spikes.\n 308:\t */\n 309:\tvoid tlb_remove_table_sync_rcu(void)\n 310:\t{\n 311:\t\tsynchronize_rcu();\n 312:\t}\n 313:\t\n 314:\t/*\n 315:\t * If we want tlb_remove_table() to imply TLB invalidates.\n 316:\t */\n 317:\tstatic inline void tlb_table_invalidate(struct mmu_gather *tlb)\n 318:\t{\n 319:\t\tif (tlb_needs_table_invalidate()) {\n 320:\t\t\t/*\n 321:\t\t\t * Invalidate page-table caches used by hardware walkers. Then\n 322:\t\t\t * we still need to RCU-sched wait while freeing the pages\n 323:\t\t\t * because software walkers can still be in-flight.\n 324:\t\t\t */\n 325:\t\t\ttlb_flush_mmu_tlbonly(tlb);\n 326:\t\t}\n 327:\t}\n 328:\t\n 329:\t#ifdef CONFIG_PT_RECLAIM\n 330:\tstatic inline void __tlb_remove_table_one_rcu(struct rcu_head *head)\n 331:\t{\n 332:\t\tstruct ptdesc *ptdesc;\n 333:\t\n 334:\t\tptdesc = container_of(head, struct ptdesc, pt_rcu_head);\n 335:\t\t__tlb_remove_table(ptdesc);\n 336:\t}\n 337:\t\n 338:\tstatic inline void __tlb_remove_table_one(void *table)\n 339:\t{\n 340:\t\tstruct ptdesc *ptdesc;\n 341:\t\n 342:\t\tptdesc = table;\n 343:\t\tcall_rcu(\u0026ptdesc-\u003ept_rcu_head, __tlb_remove_table_one_rcu);\n 344:\t}\n 345:\t#else\n 346:\tstatic inline void __tlb_remove_table_one(void *table)\n 347:\t{\n 348:\t\ttlb_remove_table_sync_rcu();\n 349:\t\t__tlb_remove_table(table);\n"
} |
| 11/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 23299 output: 36 thoughts: 23 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the `tlb_remove_table` Implementation** Okay, time to dissect `tlb_remove_table`. The function itself is in `mm/mmu_gather.c`, so that's where I'll start. I'm going to want to understand exactly how it handles invalidating TLB entries associated with a page table. Specifically, I'm interested in how it interacts with the gather context, and how it deals with potential concurrency issues. I'll be looking for any hints of optimization strategies, and if the implementation takes advantage of any processor-specific instructions or features. Beyond that, I will check the usual suspects - the error handling, and the edge cases. I'm especially keen to see the details of any architectural quirks that are explicitly handled. I expect to be able to follow the logic and explain it back to myself in detail. |
| 12/2 | 2026/09/01 23:44 | tool | read-file |
0mArgs:{
"File": "mm/mmu_gather.c",
"FirstLine": 350,
"LineCount": 55
}Results: {
"Contents": " 350:\t}\n 351:\t#endif /* CONFIG_PT_RECLAIM */\n 352:\t\n 353:\tstatic void tlb_remove_table_one(void *table)\n 354:\t{\n 355:\t\t__tlb_remove_table_one(table);\n 356:\t}\n 357:\t\n 358:\tstatic void tlb_table_flush(struct mmu_gather *tlb)\n 359:\t{\n 360:\t\tstruct mmu_table_batch **batch = \u0026tlb-\u003ebatch;\n 361:\t\n 362:\t\tif (*batch) {\n 363:\t\t\ttlb_table_invalidate(tlb);\n 364:\t\t\ttlb_remove_table_free(*batch);\n 365:\t\t\t*batch = NULL;\n 366:\t\t}\n 367:\t}\n 368:\t\n 369:\tvoid tlb_remove_table(struct mmu_gather *tlb, void *table)\n 370:\t{\n 371:\t\tstruct mmu_table_batch **batch = \u0026tlb-\u003ebatch;\n 372:\t\n 373:\t\tif (*batch == NULL) {\n 374:\t\t\t*batch = (struct mmu_table_batch *)__get_free_page(GFP_NOWAIT);\n 375:\t\t\tif (*batch == NULL) {\n 376:\t\t\t\ttlb_table_invalidate(tlb);\n 377:\t\t\t\ttlb_remove_table_one(table);\n 378:\t\t\t\treturn;\n 379:\t\t\t}\n 380:\t\t\t(*batch)-\u003enr = 0;\n 381:\t\t}\n 382:\t\n 383:\t\t(*batch)-\u003etables[(*batch)-\u003enr++] = table;\n 384:\t\tif ((*batch)-\u003enr == MAX_TABLE_BATCH)\n 385:\t\t\ttlb_table_flush(tlb);\n 386:\t}\n 387:\t\n 388:\tstatic inline void tlb_table_init(struct mmu_gather *tlb)\n 389:\t{\n 390:\t\ttlb-\u003ebatch = NULL;\n 391:\t}\n 392:\t\n 393:\tstatic void tlb_flush_mmu_free(struct mmu_gather *tlb)\n 394:\t{\n 395:\t\ttlb_table_flush(tlb);\n 396:\t#ifndef CONFIG_MMU_GATHER_NO_GATHER\n 397:\t\ttlb_batch_pages_flush(tlb);\n 398:\t#endif\n 399:\t}\n 400:\t\n 401:\tvoid tlb_flush_mmu(struct mmu_gather *tlb)\n 402:\t{\n 403:\t\ttlb_flush_mmu_tlbonly(tlb);\n 404:\t\ttlb_flush_mmu_free(tlb);\n"
} |
| 13/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24348 output: 29 thoughts: 26 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Investigating `tlb_remove_ptdesc`** Okay, let's dive in. I need to refresh my memory on the `tlb_remove_ptdesc` function. I know it's related to TLB management, specifically page table descriptor removal, but I want to be certain about the exact implementation details and potential implications for performance. I'm going to quickly check the definition in `include/asm-generic/tlb.h`. This generic header should provide a good overview of the interface and likely point me to the architecture-specific implementations if necessary. Understanding the generic behavior is the key starting point to consider before looking for processor-specific optimisations or issues. |
| 14/2 | 2026/09/01 23:44 | tool | grepper |
0mArgs:{
"Expression": "tlb_remove_ptdesc",
"PathPrefix": "include/"
}Results: {
"Output": "include/asm-generic/tlb.h=472=static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page)\n--\ninclude/asm-generic/tlb.h-476-\ninclude/asm-generic/tlb.h:477:static inline void tlb_remove_ptdesc(struct mmu_gather *tlb, struct ptdesc *pt)\ninclude/asm-generic/tlb.h-478-{\n"
} |
| 15/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24694 output: 38 thoughts: 23 Thoughts: Here's my summary, as if I were thinking these thoughts: **Diving into the TLB Implementation** Okay, time to get my hands dirty. I want to understand a bit more about how the TLB is handled in the generic architecture, so let's jump right into the heart of it. Specifically, I'm heading over to the `include/asm-generic/tlb.h` file. I'm aiming for around line 475, give or take, as that's usually a good spot to find some interesting generic TLB-related definitions or macros. I'm expecting to find some crucial pieces of the puzzle there. This is where the core logic usually resides. |
| 16/2 | 2026/09/01 23:44 | tool | read-file |
0mArgs:{
"File": "include/asm-generic/tlb.h",
"FirstLine": 465,
"LineCount": 30
}Results: {
"Contents": " 465:\tstatic inline void tlb_remove_page_size(struct mmu_gather *tlb,\n 466:\t\t\t\t\t\tstruct page *page, int page_size)\n 467:\t{\n 468:\t\tif (__tlb_remove_page_size(tlb, page, page_size))\n 469:\t\t\ttlb_flush_mmu(tlb);\n 470:\t}\n 471:\t\n 472:\tstatic inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page)\n 473:\t{\n 474:\t\treturn tlb_remove_page_size(tlb, page, PAGE_SIZE);\n 475:\t}\n 476:\t\n 477:\tstatic inline void tlb_remove_ptdesc(struct mmu_gather *tlb, struct ptdesc *pt)\n 478:\t{\n 479:\t\ttlb_remove_table(tlb, pt);\n 480:\t}\n 481:\t\n 482:\tstatic inline void tlb_change_page_size(struct mmu_gather *tlb,\n 483:\t\t\t\t\t\t\t unsigned int page_size)\n 484:\t{\n 485:\t#ifdef CONFIG_MMU_GATHER_PAGE_SIZE\n 486:\t\tif (tlb-\u003epage_size \u0026\u0026 tlb-\u003epage_size != page_size) {\n 487:\t\t\tif (!tlb-\u003efullmm \u0026\u0026 !tlb-\u003eneed_flush_all)\n 488:\t\t\t\ttlb_flush_mmu(tlb);\n 489:\t\t}\n 490:\t\n 491:\t\ttlb-\u003epage_size = page_size;\n 492:\t#endif\n 493:\t}\n 494:\t\n"
} |
| 17/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 25384 output: 36 thoughts: 387 Thoughts: **Analyzing the TLB Removal and Page Table Reclamation Changes** Okay, let's break this down. It seems the key function we're looking at is `tlb_remove_ptdesc` which is now consistently calling `tlb_remove_table(tlb, pt)`. The crucial shift here is in the implementation of `tlb_remove_table`. Before this patch, the code was inlined and handled immediately, which involved a simple destruction of the page table descriptor and immediate removal from the TLB. Now, though, `tlb_remove_table` is ALWAYS defined in `mm/mmu_gather.c` and leverages RCU batching, using either `call_rcu` or `synchronize_rcu`. So, we're deferring the actual table removal using RCU now, correct? Alright, let's quickly check the `PT_RECLAIM` option in the kernel configuration. Previously, its dependency was tied to `MMU_GATHER_RCU_TABLE_FREE` and the absence of an architecture-specific `HAVE_ARCH_TLB_REMOVE_TABLE` definition. Now, with the removal of `MMU_GATHER_RCU_TABLE_FREE`, it solely depends on `MMU` and the absence of the architecture-specific function. I'm inferring this means `PT_RECLAIM` becomes less architecture-specific. Interesting... Finally, the discussion of `pte_free_defer` indicates we need to examine `mm/pgtable-generic.c` around line 240. That's likely where the actual page table freeing deferral is being handled or called in conjunction with RCU. So, the key is the page table removal is no longer immediate, it's being deferred, probably improving the performance as a result of RCU batching. |
| 18/2 | 2026/09/01 23:44 | tool | read-file |
0mArgs:{
"File": "mm/pgtable-generic.c",
"FirstLine": 235,
"LineCount": 35
}Results: {
"Contents": " 235:\t\tflush_tlb_range(vma, address, address + HPAGE_PMD_SIZE);\n 236:\t\treturn pmd;\n 237:\t}\n 238:\t#endif\n 239:\t\n 240:\t/* arch define pte_free_defer in asm/pgalloc.h for its own implementation */\n 241:\t#ifndef pte_free_defer\n 242:\tstatic void pte_free_now(struct rcu_head *head)\n 243:\t{\n 244:\t\tstruct page *page;\n 245:\t\n 246:\t\tpage = container_of(head, struct page, rcu_head);\n 247:\t\tpte_free(NULL /* mm not passed and not used */, (pgtable_t)page);\n 248:\t}\n 249:\t\n 250:\tvoid pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\n 251:\t{\n 252:\t\tstruct page *page;\n 253:\t\n 254:\t\tpage = pgtable;\n 255:\t\tcall_rcu(\u0026page-\u003ercu_head, pte_free_now);\n 256:\t}\n 257:\t#endif /* pte_free_defer */\n 258:\t#endif /* CONFIG_TRANSPARENT_HUGEPAGE */\n 259:\t\n 260:\t#if defined(CONFIG_GUP_GET_PXX_LOW_HIGH) \u0026\u0026 \\\n 261:\t\t(defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_RCU))\n 262:\t/*\n 263:\t * See the comment above ptep_get_lockless() in include/linux/pgtable.h:\n 264:\t * the barriers in pmdp_get_lockless() cannot guarantee that the value in\n 265:\t * pmd_high actually belongs with the value in pmd_low; but holding interrupts\n 266:\t * off blocks the TLB flush between present updates, which guarantees that a\n 267:\t * successful __pte_offset_map() points to a page from matched halves.\n 268:\t */\n 269:\tstatic unsigned long pmdp_get_lockless_start(void)\n"
} |
| 19/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 26803 output: 28 thoughts: 544 Thoughts: **Analyzing Page Table Freeing in the Kernel** Okay, let's break this down. It looks like we've got some interesting changes around page table freeing, specifically related to HugeTLB pages. The `zap_deposited_table` function within `mm/huge_memory.c` now calls `pte_free_defer`, which is using RCU for deferred freeing. This is only compiled when `CONFIG_TRANSPARENT_HUGEPAGE` is enabled, which makes sense. Then we have `tlb_remove_table` in `mm/mmu_gather.c`, which is compiled when `CONFIG_MMU` is enabled, and `CONFIG_PT_RECLAIM` is enabled when both `CONFIG_MMU` is enabled and `!HAVE_ARCH_TLB_REMOVE_TABLE`. So, the key configs to keep in mind are `MMU` and `TRANSPARENT_HUGEPAGE`. I should probably double-check their interactions. Now, let's identify the relevant entrypoints. Obviously, `tlb_remove_table` (in `mm/mmu_gather.c`) is critical, as it's global and called whenever page tables or directories are being freed. Then there's the sync variants: `tlb_remove_table_sync_one` and `tlb_remove_table_sync_rcu` in `mm/mmu_gather.c`. We also have `pte_free_defer` in `mm/pgtable-generic.c`, which is the RCU deferral mechanism. And of course, `zap_huge_pmd` in `mm/huge_memory.c` is important. Also, the standard MM entrypoints like `zap_page_range_single`, `zap_vma_ptes`, and `free_pgtables` will likely trigger these calls. We also have `__split_huge_pmd_locked` and `madvise_free_huge_pmd`. I should check the non-static, non-inlined functions to get a clearer picture of potential call sites. Specifically: `tlb_remove_table`, `tlb_remove_table_sync_one`, `tlb_remove_table_sync_rcu` (all in `mm/mmu_gather.c`), `pte_free_defer` (in `mm/pgtable-generic.c`), and `zap_huge_pmd` (in `mm/huge_memory.c`). This gives a clear picture of what we can trace, or override. Now, let's dive into `zap_huge_pmd` in `mm/huge_memory.c`. I have a feeling there will be more interesting details there. |
| 20/2 | 2026/09/01 23:44 | tool | grepper |
0mArgs:{
"Expression": "zap_huge_pmd",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/huge_memory.c=2474=static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)\n--\nmm/huge_memory.c-2482-\nmm/huge_memory.c:2483:static void zap_huge_pmd_folio(struct mm_struct *mm, struct vm_area_struct *vma,\nmm/huge_memory.c-2484-\t\tpmd_t pmdval, struct folio *folio, bool is_present)\n--\nmm/huge_memory.c=2521=static bool has_deposited_pgtable(struct vm_area_struct *vma, pmd_t pmdval,\n--\nmm/huge_memory.c-2542-/**\nmm/huge_memory.c:2543: * zap_huge_pmd - Zap a huge THP which is of PMD size.\nmm/huge_memory.c-2544- * @tlb: The MMU gather TLB state associated with the operation.\n--\nmm/huge_memory.c-2550- */\nmm/huge_memory.c:2551:bool zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,\nmm/huge_memory.c-2552-\t\t pmd_t *pmd, unsigned long addr)\n--\nmm/huge_memory.c-2580-\tif (folio)\nmm/huge_memory.c:2581:\t\tzap_huge_pmd_folio(mm, vma, orig_pmd, folio, is_present);\nmm/huge_memory.c-2582-\tif (has_deposit)\n--\nmm/internal.h=568=void pmd_install(struct mm_struct *mm, pmd_t *pmd, pgtable_t *pte);\n--\nmm/internal.h-575- * When we find a pmd_none() while unmapping a folio without holding the PTL,\nmm/internal.h:576: * zap_huge_pmd() may have cleared the PMD but not yet modified the folio to\nmm/internal.h-577- * indicate that it's unmapped. Skipping the PMD without synchronization could\n--\nmm/memory.c=2099=static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,\n--\nmm/memory.c-2112-\t\t\t\t__split_huge_pmd(vma, pmd, addr, false);\nmm/memory.c:2113:\t\t\telse if (zap_huge_pmd(tlb, vma, pmd, addr)) {\nmm/memory.c-2114-\t\t\t\taddr = next;\n"
} |
| 21/2 | 2026/09/01 23:44 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 28654 output: 202 thoughts: 619 Thoughts: **My Analysis of This Patch** Okay, let's break this down. First, I need to identify the scope of this patch. It looks like it significantly impacts page table management, specifically around freeing and reclaim. My initial scan of the code changes confirms this. I see modifications to several architecture-specific files (`m68k`, `sh`, `sparc`) and generic memory management files (`mmu_gather.c`, `huge_memory.c`, `pgtable-generic.c`). Specifically, I'm noting the introduction or modification of functions like `get_pointer_table`, `free_pointer_table`, `__tlb_remove_table`, `pte_free`, `tlb_remove_table`, and `zap_deposited_table` across these various files. The inlining of `zap_deposited_table` into functions like `zap_huge_pmd` is also worth noting. I'm also paying close attention to any configuration dependencies. `CONFIG_MMU` is, as expected, a base requirement. `CONFIG_TRANSPARENT_HUGEPAGE` comes into play for the huge memory zapping and the deferred PTE freeing. I then check `CONFIG_PT_RECLAIM` and its dependency on `MMU && !HAVE_ARCH_TLB_REMOVE_TABLE`. Now, for the key findings to be reported. This patch is making page table freeing unconditionally deferred via RCU across *all* architectures. It does this by removing the `MMU_GATHER_RCU_TABLE_FREE` and `MMU_GATHER_TABLE_FREE` Kconfig flags, thus making RCU table batching a generic process. This change also updates `zap_deposited_table()` in `mm/huge_memory.c` to leverage the deferred RCU `pte_free_defer()` function, and then adapts various architecture-specific page table freeing implementations, like those on `m68k`, `sparc`, `sh`, `arc`, and `xtensa`. My `set-results` data will include: `WorthFuzzing`: true (because of these core changes); a concise `Reasoning` based on the above; the following `FocusSymbols`: `["tlb_remove_table", "pte_free_defer", "zap_huge_pmd", "tlb_remove_table_sync_rcu", "tlb_remove_table_sync_one"]`; and `EnableConfigs`: `["MMU", "TRANSPARENT_HUGEPAGE"]`. Finally, a quick sanity check to ensure the instructions are correct: EnableConfigs without the `CONFIG_` prefix; FocusSymbols are non-static and non-inlined API entrypoints; and that `set-results` is called once before the final reply. Everything checks out. |
| 22/2 | 2026/09/01 23:44 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MMU",
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"tlb_remove_table",
"pte_free_defer",
"zap_huge_pmd",
"tlb_remove_table_sync_rcu",
"tlb_remove_table_sync_one"
],
"Reasoning": "The patch unifies and enforces RCU-deferred freeing for page table pages across all architectures by removing conditional MMU_GATHER_RCU_TABLE_FREE configuration options and standardizing on tlb_remove_table(). It also modifies THP deposited table handling in mm/huge_memory.c to use pte_free_defer() under RCU and updates architecture-specific page table removal routines. These core MM logic changes affect TLB gathering, page table tearing down, and memory reclaim paths.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MMU",
"TRANSPARENT_HUGEPAGE"
],
"FocusSymbols": [
"tlb_remove_table",
"pte_free_defer",
"zap_huge_pmd",
"tlb_remove_table_sync_rcu",
"tlb_remove_table_sync_one"
],
"Reasoning": "The patch unifies and enforces RCU-deferred freeing for page table pages across all architectures by removing conditional MMU_GATHER_RCU_TABLE_FREE configuration options and standardizing on tlb_remove_table(). It also modifies THP deposited table handling in mm/huge_memory.c to use pte_free_defer() under RCU and updates architecture-specific page table removal routines. These core MM logic changes affect TLB gathering, page table tearing down, and memory reclaim paths.",
"WorthFuzzing": true
} |
| 23/1 | 2026/09/01 23:44 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 24/2 | 2026/09/01 23:44 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series standardizes page table freeing across architectures to use RCU-deferred freeing (unifying MMU gather and removing CONFIG_MMU_GATHER_RCU_TABLE_FREE / CONFIG_MMU_GATHER_TABLE_FREE configs). It adapts architecture-specific page table freeing routines (e.g. m68k, arc, microblaze, sh, sparc, xtensa) to use tlb_remove_ptdesc / tlb_remove_table, adds spinlocks for ptable management on m68k/sparc, and updates deferred freeing in THP (pte_free_defer).\n\nThese changes deal strictly with MMU gather lifecycle, RCU grace periods for page table memory, and synchronization of page table freeing vs lockless/RCU page table walkers. Potential bugs in these changes would manifest as use-after-free, double-free, concurrency races, or deadlocks—which are covered by KASAN and LOCKDEP. The patch does not introduce any uninitialized memory allocations, padding leaks, or uninitialized reads/copies to userspace. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 28e8e22fb2f922a5d98ac272c04a39fb5d0a7ee1
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 1 23:43:52 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/mm/process_addrs.rst b/Documentation/mm/process_addrs.rst
index a7296f251799c..1e65b139f3559 100644
--- a/Documentation/mm/process_addrs.rst
+++ b/Documentation/mm/process_addrs.rst
@@ -537,6 +537,12 @@ We establish basic locking rules when interacting with page tables:
* When changing a page table entry the page table lock for that page table
**must** be held, except if you can safely assume nobody can access the page
tables concurrently (such as on invocation of :c:func:`!free_pgtables`).
+* Page tables may be *walked* under RCU alone, as page tables are freed only
+ after an RCU grace period has elapsed. However, any entry found must be
+ revalidated after the page table lock is taken (such as the
+ :c:func:`!pmd_same` recheck performed by :c:func:`!pte_offset_map_lock`)
+ before it is acted upon. Changing an entry always requires the page table
+ lock.
* Reads from and writes to page table entries must be *appropriately*
atomic. See the section on atomicity below for details.
* Populating previously empty entries requires that the mmap or VMA locks are
diff --git a/arch/Kconfig b/arch/Kconfig
index 45c6577723623..6f7516916797e 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -526,13 +526,6 @@ config HAVE_ARCH_JUMP_LABEL
config HAVE_ARCH_JUMP_LABEL_RELATIVE
bool
-config MMU_GATHER_TABLE_FREE
- bool
-
-config MMU_GATHER_RCU_TABLE_FREE
- bool
- select MMU_GATHER_TABLE_FREE
-
config MMU_GATHER_PAGE_SIZE
bool
@@ -548,7 +541,6 @@ config MMU_GATHER_MERGE_VMAS
config MMU_GATHER_NO_GATHER
bool
- depends on MMU_GATHER_TABLE_FREE
config ARCH_WANT_IRQS_OFF_ACTIVATE_MM
bool
diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig
index e53ef2d884636..9063c7bda4e41 100644
--- a/arch/alpha/Kconfig
+++ b/arch/alpha/Kconfig
@@ -42,7 +42,6 @@ config ALPHA
select ARCH_STACKWALK
select CPU_NO_EFFICIENT_FFS if !ALPHA_EV67
select MMU_GATHER_NO_RANGE
- select MMU_GATHER_RCU_TABLE_FREE
select SPARSEMEM_EXTREME if SPARSEMEM
select ZONE_DMA
select TRACE_IRQFLAGS_SUPPORT
diff --git a/arch/arc/include/asm/pgalloc.h b/arch/arc/include/asm/pgalloc.h
index dfae070fe8d55..9b6c37f92e97f 100644
--- a/arch/arc/include/asm/pgalloc.h
+++ b/arch/arc/include/asm/pgalloc.h
@@ -72,7 +72,7 @@ static inline void p4d_populate(struct mm_struct *mm, p4d_t *p4dp, pud_t *pudp)
set_p4d(p4dp, __p4d((unsigned long)pudp));
}
-#define __pud_free_tlb(tlb, pmd, addr) pud_free((tlb)->mm, pmd)
+#define __pud_free_tlb(tlb, pmd, addr) tlb_remove_ptdesc((tlb), virt_to_ptdesc(pmd))
#endif
@@ -83,10 +83,10 @@ static inline void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmdp)
set_pud(pudp, __pud((unsigned long)pmdp));
}
-#define __pmd_free_tlb(tlb, pmd, addr) pmd_free((tlb)->mm, pmd)
+#define __pmd_free_tlb(tlb, pmd, addr) tlb_remove_ptdesc((tlb), virt_to_ptdesc(pmd))
#endif
-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)->mm, pte)
+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_ptdesc((tlb), page_ptdesc(pte))
#endif /* _ASM_ARC_PGALLOC_H */
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 408aa58a2a5bb..0cc289a7184ab 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -134,7 +134,6 @@ config ARM
select HAVE_PERF_REGS
select HAVE_PERF_USER_STACK_DUMP
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
- select MMU_GATHER_RCU_TABLE_FREE if SMP && ARM_LPAE
select HAVE_REGS_AND_STACK_ACCESS_API
select HAVE_RSEQ
select HAVE_RUST if CPU_LITTLE_ENDIAN && CPU_32v7 && !KASAN
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 2bbeded33da0d..b6c2dd8b26124 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -221,7 +221,6 @@ config ARM64
select HAVE_RELIABLE_STACKTRACE
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
select HAVE_FUNCTION_ARG_ACCESS_API
- select MMU_GATHER_RCU_TABLE_FREE
select HAVE_RSEQ
select HAVE_RUST if RUSTC_SUPPORTS_ARM64
select HAVE_STACKPROTECTOR
diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 9c5def7062222..d1b23da407375 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -188,7 +188,6 @@ config LOONGARCH
select IRQ_LOONGARCH_CPU
select LOCK_MM_AND_FIND_VMA
select MMU_GATHER_MERGE_VMAS if MMU
- select MMU_GATHER_RCU_TABLE_FREE
select MODULES_USE_ELF_RELA if MODULES
select NEED_PER_CPU_EMBED_FIRST_CHUNK
select NEED_PER_CPU_PAGE_FIRST_CHUNK
diff --git a/arch/m68k/Kconfig b/arch/m68k/Kconfig
index 11835eb59d94d..eb84c3af92c02 100644
--- a/arch/m68k/Kconfig
+++ b/arch/m68k/Kconfig
@@ -29,6 +29,7 @@ config M68K
select HAVE_ARCH_LIBGCC_H
select HAVE_ARCH_SECCOMP
select HAVE_ARCH_SECCOMP_FILTER
+ select HAVE_ARCH_TLB_REMOVE_TABLE if MMU_MOTOROLA
select HAVE_ASM_MODVERSIONS
select HAVE_DEBUG_BUGVERBOSE
select HAVE_EFFICIENT_UNALIGNED_ACCESS if !CPU_HAS_NO_UNALIGNED
diff --git a/arch/m68k/include/asm/mcf_pgalloc.h b/arch/m68k/include/asm/mcf_pgalloc.h
index fc5454d37da31..b53ff0950db2e 100644
--- a/arch/m68k/include/asm/mcf_pgalloc.h
+++ b/arch/m68k/include/asm/mcf_pgalloc.h
@@ -39,10 +39,7 @@ extern inline pmd_t *pmd_alloc_kernel(pgd_t *pgd, unsigned long address)
static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pgtable,
unsigned long address)
{
- struct ptdesc *ptdesc = virt_to_ptdesc(pgtable);
-
- pagetable_dtor(ptdesc);
- pagetable_free(ptdesc);
+ tlb_remove_ptdesc(tlb, virt_to_ptdesc(pgtable));
}
static inline pgtable_t pte_alloc_one(struct mm_struct *mm)
diff --git a/arch/m68k/include/asm/motorola_pgalloc.h b/arch/m68k/include/asm/motorola_pgalloc.h
index 1091fb0affbee..dcde40e8b5c6a 100644
--- a/arch/m68k/include/asm/motorola_pgalloc.h
+++ b/arch/m68k/include/asm/motorola_pgalloc.h
@@ -17,6 +17,7 @@ enum m68k_table_types {
extern void init_pointer_table(void *table, int type);
extern void *get_pointer_table(struct mm_struct *mm, int type);
extern int free_pointer_table(void *table, int type);
+extern void __tlb_remove_table(void *table);
/*
* Allocate and free page tables. The xxx_kernel() versions are
@@ -47,7 +48,7 @@ static inline void pte_free(struct mm_struct *mm, pgtable_t pgtable)
static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pgtable,
unsigned long address)
{
- free_pointer_table(pgtable, TABLE_PTE);
+ tlb_remove_table(tlb, (void *)((unsigned long)pgtable | TABLE_PTE));
}
@@ -61,10 +62,10 @@ static inline int pmd_free(struct mm_struct *mm, pmd_t *pmd)
return free_pointer_table(pmd, TABLE_PMD);
}
-static inline int __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd,
- unsigned long address)
+static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd,
+ unsigned long address)
{
- return free_pointer_table(pmd, TABLE_PMD);
+ tlb_remove_table(tlb, (void *)((unsigned long)pmd | TABLE_PMD));
}
diff --git a/arch/m68k/mm/motorola.c b/arch/m68k/mm/motorola.c
index b30aa69a73a6a..ffc80483440bc 100644
--- a/arch/m68k/mm/motorola.c
+++ b/arch/m68k/mm/motorola.c
@@ -20,6 +20,7 @@
#include <linux/init.h>
#include <linux/memblock.h>
#include <linux/gfp.h>
+#include <linux/cleanup.h>
#include <asm/setup.h>
#include <linux/uaccess.h>
@@ -103,6 +104,8 @@ static struct list_head ptable_list[3] = {
LIST_HEAD_INIT(ptable_list[2]),
};
+static DEFINE_SPINLOCK(ptable_lock);
+
#define PD_PTABLE(ptdesc) ((ptable_desc *)&(virt_to_ptdesc((void *)(ptdesc))->pt_list))
#define PD_PTDESC(ptable) (list_entry(ptable, struct ptdesc, pt_list))
#define PD_MARKBITS(dp) (*(unsigned int *)&PD_PTDESC(dp)->pt_index)
@@ -139,52 +142,66 @@ void __init init_pointer_table(void *table, int type)
return;
}
-void *get_pointer_table(struct mm_struct *mm, int type)
+/*
+ * For a pointer table for a user process address space, a
+ * table is taken from a ptdesc allocated for the purpose. Each
+ * ptdesc can hold 8 pointer tables. The ptdesc is remapped in
+ * virtual address space to be noncacheable.
+ */
+static void *add_pointer_table(struct mm_struct *mm, int type)
{
- ptable_desc *dp = ptable_list[type].next;
- unsigned int mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp);
- unsigned int tmp, off;
+ struct ptdesc *ptdesc;
+ ptable_desc *new;
+ void *pt_addr;
- /*
- * For a pointer table for a user process address space, a
- * table is taken from a ptdesc allocated for the purpose. Each
- * ptdesc can hold 8 pointer tables. The ptdesc is remapped in
- * virtual address space to be noncacheable.
- */
- if (mask == 0) {
- struct ptdesc *ptdesc;
- ptable_desc *new;
- void *pt_addr;
-
- ptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0);
- if (!ptdesc)
- return NULL;
-
- pt_addr = ptdesc_address(ptdesc);
-
- switch (type) {
- case TABLE_PTE:
- /*
- * m68k doesn't have SPLIT_PTE_PTLOCKS for not having
- * SMP.
- */
- pagetable_pte_ctor(mm, ptdesc);
- break;
- case TABLE_PMD:
- pagetable_pmd_ctor(mm, ptdesc);
- break;
- case TABLE_PGD:
- pagetable_pgd_ctor(ptdesc);
- break;
- }
+ ptdesc = pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0);
+ if (!ptdesc)
+ return NULL;
+
+ pt_addr = ptdesc_address(ptdesc);
+
+ switch (type) {
+ case TABLE_PTE:
+ /*
+ * m68k doesn't have SPLIT_PTE_PTLOCKS for not having
+ * SMP.
+ */
+ pagetable_pte_ctor(mm, ptdesc);
+ break;
+ case TABLE_PMD:
+ pagetable_pmd_ctor(mm, ptdesc);
+ break;
+ case TABLE_PGD:
+ pagetable_pgd_ctor(ptdesc);
+ break;
+ }
+
+ mmu_page_ctor(pt_addr);
+
+ new = PD_PTABLE(pt_addr);
- mmu_page_ctor(pt_addr);
+ PD_MARKBITS(new) = ptable_mask(type) - 1;
+ scoped_guard(spinlock_irqsave, &ptable_lock)
+ list_add(new, &ptable_list[type]);
- new = PD_PTABLE(pt_addr);
- PD_MARKBITS(new) = ptable_mask(type) - 1;
- list_add_tail(new, dp);
+ return (pmd_t *)pt_addr;
+}
+
+void *get_pointer_table(struct mm_struct *mm, int type)
+{
+ unsigned int tmp, off;
+ unsigned long mask;
+ unsigned long flags;
+ ptable_desc *dp;
+ void *ret;
- return (pmd_t *)pt_addr;
+ spin_lock_irqsave(&ptable_lock, flags);
+ dp = ptable_list[type].next;
+ mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp);
+
+ if (mask == 0) {
+ spin_unlock_irqrestore(&ptable_lock, flags);
+ return add_pointer_table(mm, type);
}
for (tmp = 1, off = 0; (mask & tmp) == 0; tmp <<= 1, off += ptable_size(type))
@@ -194,7 +211,10 @@ void *get_pointer_table(struct mm_struct *mm, int type)
/* move to end of list */
list_move_tail(dp, &ptable_list[type]);
}
- return ptdesc_address(PD_PTDESC(dp)) + off;
+
+ ret = ptdesc_address(PD_PTDESC(dp)) + off;
+ spin_unlock_irqrestore(&ptable_lock, flags);
+ return ret;
}
int free_pointer_table(void *table, int type)
@@ -203,6 +223,9 @@ int free_pointer_table(void *table, int type)
unsigned long ptable = (unsigned long)table;
unsigned long pt_addr = ptable & PAGE_MASK;
unsigned int mask = 1U << ((ptable - pt_addr)/ptable_size(type));
+ unsigned long flags;
+
+ spin_lock_irqsave(&ptable_lock, flags);
dp = PD_PTABLE(pt_addr);
if (PD_MARKBITS (dp) & mask)
@@ -213,6 +236,8 @@ int free_pointer_table(void *table, int type)
if (PD_MARKBITS(dp) == ptable_mask(type)) {
/* all tables in ptdesc are free, free ptdesc */
list_del(dp);
+ spin_unlock_irqrestore(&ptable_lock, flags);
+
mmu_page_dtor((void *)pt_addr);
pagetable_dtor_free(virt_to_ptdesc((void *)pt_addr));
return 1;
@@ -223,9 +248,21 @@ int free_pointer_table(void *table, int type)
*/
list_move(dp, &ptable_list[type]);
}
+
+ spin_unlock_irqrestore(&ptable_lock, flags);
return 0;
}
+void __tlb_remove_table(void *table)
+{
+ /* The bottom 2 bits are used to encode page table type. */
+ const unsigned long encoded = (unsigned long)table;
+ void *addr = (void *)(encoded & ~3UL);
+ const int type = encoded & 3;
+
+ free_pointer_table(addr, type);
+}
+
/* size of memory already mapped in head.S */
extern __initdata unsigned long m68k_init_mapped_size;
diff --git a/arch/microblaze/include/asm/pgalloc.h b/arch/microblaze/include/asm/pgalloc.h
index 084a8a0dc2395..ffee6a009219a 100644
--- a/arch/microblaze/include/asm/pgalloc.h
+++ b/arch/microblaze/include/asm/pgalloc.h
@@ -25,7 +25,7 @@ extern void __bad_pte(pmd_t *pmd);
extern pte_t *pte_alloc_one_kernel(struct mm_struct *mm);
-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)->mm, (pte))
+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_ptdesc((tlb), page_ptdesc(pte))
#define pmd_populate(mm, pmd, pte) \
(pmd_val(*(pmd)) = (unsigned long)page_address(pte))
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index e2eb9627bd14c..f0c43d118ca00 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -97,7 +97,6 @@ config MIPS
select IRQ_FORCED_THREADING
select ISA if EISA
select LOCK_MM_AND_FIND_VMA
- select MMU_GATHER_RCU_TABLE_FREE
select MODULES_USE_ELF_REL if MODULES
select MODULES_USE_ELF_RELA if MODULES && 64BIT
select PERF_USE_VMALLOC
diff --git a/arch/parisc/Kconfig b/arch/parisc/Kconfig
index d3afac2f0d9be..77f67028ad89c 100644
--- a/arch/parisc/Kconfig
+++ b/arch/parisc/Kconfig
@@ -80,7 +80,6 @@ config PARISC
select GENERIC_CLOCKEVENTS
select CPU_NO_EFFICIENT_FFS
select THREAD_INFO_IN_TASK
- select MMU_GATHER_RCU_TABLE_FREE
select NEED_DMA_MAP_STATE
select NEED_SG_DMA_LENGTH
select HAVE_ARCH_KGDB
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 2580e27e43287..0767cfcbaa422 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -307,7 +307,6 @@ config PPC
select KASAN_VMALLOC if KASAN && EXECMEM
select LOCK_MM_AND_FIND_VMA
select MMU_GATHER_PAGE_SIZE
- select MMU_GATHER_RCU_TABLE_FREE
select HAVE_ARCH_TLB_REMOVE_TABLE
select MMU_GATHER_MERGE_VMAS
select MMU_LAZY_TLB_SHOOTDOWN if PPC_BOOK3S_64
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 505eed4af932b..7741a42874981 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -208,7 +208,6 @@ config RISCV
select IRQ_FORCED_THREADING
select KASAN_VMALLOC if KASAN
select LOCK_MM_AND_FIND_VMA
- select MMU_GATHER_RCU_TABLE_FREE if SMP && MMU
select MODULES_USE_ELF_RELA if MODULES
select OF
select OF_EARLY_FLATTREE
diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig
index b88b850421369..a34376c05f6e3 100644
--- a/arch/s390/Kconfig
+++ b/arch/s390/Kconfig
@@ -267,7 +267,6 @@ config S390
select LOCK_MM_AND_FIND_VMA
select MMU_GATHER_MERGE_VMAS
select MMU_GATHER_NO_GATHER
- select MMU_GATHER_RCU_TABLE_FREE
select MODULES_USE_ELF_RELA
select NEED_DMA_MAP_STATE if PCI
select NEED_PER_CPU_EMBED_FIRST_CHUNK
diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig
index d60f1d5a94c0f..fe859def918cc 100644
--- a/arch/sh/Kconfig
+++ b/arch/sh/Kconfig
@@ -33,6 +33,7 @@ config SUPERH
select HAVE_ARCH_AUDITSYSCALL
select HAVE_ARCH_KGDB
select HAVE_ARCH_SECCOMP_FILTER
+ select HAVE_ARCH_TLB_REMOVE_TABLE if X2TLB
select HAVE_ARCH_TRACEHOOK
select HAVE_DEBUG_BUGVERBOSE
select HAVE_DEBUG_KMEMLEAK
diff --git a/arch/sh/include/asm/pgalloc.h b/arch/sh/include/asm/pgalloc.h
index 6fe7123d38fa9..67ce7fa23fa12 100644
--- a/arch/sh/include/asm/pgalloc.h
+++ b/arch/sh/include/asm/pgalloc.h
@@ -17,7 +17,11 @@ extern void pgd_free(struct mm_struct *mm, pgd_t *pgd);
extern void pud_populate(struct mm_struct *mm, pud_t *pudp, pmd_t *pmd);
extern pmd_t *pmd_alloc_one(struct mm_struct *mm, unsigned long address);
extern void pmd_free(struct mm_struct *mm, pmd_t *pmd);
-#define __pmd_free_tlb(tlb, pmdp, addr) pmd_free((tlb)->mm, (pmdp))
+extern void __tlb_remove_table(void *table);
+
+/* PMDs are slab-allocated, tag so they are freed correctly. */
+#define __pmd_free_tlb(tlb, pmdp, addr) \
+ tlb_remove_table((tlb), (void *)((unsigned long)(pmdp) | 1))
#endif
static inline void pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmd,
diff --git a/arch/sh/mm/pgtable.c b/arch/sh/mm/pgtable.c
index 3a4085ea0161f..f6184b86b89c6 100644
--- a/arch/sh/mm/pgtable.c
+++ b/arch/sh/mm/pgtable.c
@@ -56,4 +56,24 @@ void pmd_free(struct mm_struct *mm, pmd_t *pmd)
{
kmem_cache_free(pmd_cachep, pmd);
}
+
+static void __tlb_remove_table_slab(void *table)
+{
+ kmem_cache_free(pmd_cachep, table);
+}
+
+static void __tlb_remove_table_pgtable(void *table)
+{
+ pagetable_dtor_free(table);
+}
+
+void __tlb_remove_table(void *table)
+{
+ const unsigned long addr = (unsigned long)table;
+
+ if (addr & 1)
+ __tlb_remove_table_slab((void *)(addr & ~1UL));
+ else
+ __tlb_remove_table_pgtable(table);
+}
#endif /* PAGETABLE_LEVELS > 2 */
diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig
index ab77d3f2536e1..742ffff8c37f2 100644
--- a/arch/sparc/Kconfig
+++ b/arch/sparc/Kconfig
@@ -64,6 +64,7 @@ config SPARC32
select HAVE_UID16
select HAVE_PAGE_SIZE_4KB
select LOCK_MM_AND_FIND_VMA
+ select HAVE_ARCH_TLB_REMOVE_TABLE
select OLD_SIGACTION
select ZONE_DMA
@@ -75,8 +76,7 @@ config SPARC64
select HAVE_FUNCTION_GRAPH_TRACER
select HAVE_KRETPROBES
select HAVE_KPROBES
- select MMU_GATHER_RCU_TABLE_FREE if SMP
- select HAVE_ARCH_TLB_REMOVE_TABLE if SMP
+ select HAVE_ARCH_TLB_REMOVE_TABLE
select MMU_GATHER_MERGE_VMAS
select MMU_GATHER_NO_FLUSH_CACHE
select HAVE_ARCH_TRANSPARENT_HUGEPAGE
diff --git a/arch/sparc/include/asm/pgalloc_32.h b/arch/sparc/include/asm/pgalloc_32.h
index 4f73e87b22a32..36010852ba0c0 100644
--- a/arch/sparc/include/asm/pgalloc_32.h
+++ b/arch/sparc/include/asm/pgalloc_32.h
@@ -48,7 +48,9 @@ static inline void free_pmd_fast(pmd_t * pmd)
}
#define pmd_free(mm, pmd) free_pmd_fast(pmd)
-#define __pmd_free_tlb(tlb, pmd, addr) pmd_free((tlb)->mm, pmd)
+
+#define __pmd_free_tlb(tlb, pmd, addr) \
+ tlb_remove_table((tlb), (void *)((unsigned long)(pmd) | 1UL))
#define pmd_populate(mm, pmd, pte) pmd_set(pmd, pte)
@@ -72,6 +74,7 @@ static inline void free_pte_fast(pte_t *pte)
#define pte_free_kernel(mm, pte) free_pte_fast(pte)
void pte_free(struct mm_struct * mm, pgtable_t pte);
-#define __pte_free_tlb(tlb, pte, addr) pte_free((tlb)->mm, pte)
+void __tlb_remove_table(void *table);
+#define __pte_free_tlb(tlb, pte, addr) tlb_remove_table((tlb), (void *)(pte))
#endif /* _SPARC_PGALLOC_H */
diff --git a/arch/sparc/include/asm/pgalloc_64.h b/arch/sparc/include/asm/pgalloc_64.h
index caa7632be4c2a..b5055d259b74d 100644
--- a/arch/sparc/include/asm/pgalloc_64.h
+++ b/arch/sparc/include/asm/pgalloc_64.h
@@ -74,8 +74,6 @@ void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);
void pgtable_free(void *table, bool is_page);
-#ifdef CONFIG_SMP
-
struct mmu_gather;
void tlb_remove_table(struct mmu_gather *, void *);
@@ -96,12 +94,6 @@ static inline void __tlb_remove_table(void *_table)
is_page = true;
pgtable_free(table, is_page);
}
-#else /* CONFIG_SMP */
-static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, bool is_page)
-{
- pgtable_free(table, is_page);
-}
-#endif /* !CONFIG_SMP */
static inline void __pte_free_tlb(struct mmu_gather *tlb, pte_t *pte,
unsigned long address)
diff --git a/arch/sparc/include/asm/tlb_64.h b/arch/sparc/include/asm/tlb_64.h
index 3037187482db7..f5f9631685d50 100644
--- a/arch/sparc/include/asm/tlb_64.h
+++ b/arch/sparc/include/asm/tlb_64.h
@@ -29,9 +29,7 @@ void flush_tlb_pending(void);
* and therefore we don't need a TLBI when freeing page-table pages.
*/
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
#define tlb_needs_table_invalidate() (false)
-#endif
#include <asm-generic/tlb.h>
diff --git a/arch/sparc/lib/bitext.c b/arch/sparc/lib/bitext.c
index 32a5c1d9459cd..c309e27973ce6 100644
--- a/arch/sparc/lib/bitext.c
+++ b/arch/sparc/lib/bitext.c
@@ -22,8 +22,6 @@
* @align: requested alignment
*
* Returns offset in the map or -1 if out of space.
- *
- * Not safe to call from an interrupt (uses spin_lock).
*/
int bit_map_string_get(struct bit_map *t, int len, int align)
{
@@ -31,6 +29,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
int off_new;
int align1;
int i, color;
+ unsigned long flags;
if (t->num_colors) {
/* align is overloaded to be the page color */
@@ -50,7 +49,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
BUG();
color &= align1;
- spin_lock(&t->lock);
+ spin_lock_irqsave(&t->lock, flags);
if (len < t->last_size)
offset = t->first_free;
else
@@ -64,7 +63,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
if (offset >= t->size)
offset = 0;
if (count + len > t->size) {
- spin_unlock(&t->lock);
+ spin_unlock_irqrestore(&t->lock, flags);
/* P3 */ printk(KERN_ERR
"bitmap out: size %d used %d off %d len %d align %d count %d\n",
t->size, t->used, offset, len, align, count);
@@ -90,7 +89,7 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
t->last_off = 0;
t->used += len;
t->last_size = len;
- spin_unlock(&t->lock);
+ spin_unlock_irqrestore(&t->lock, flags);
return offset;
}
}
@@ -103,10 +102,11 @@ int bit_map_string_get(struct bit_map *t, int len, int align)
void bit_map_clear(struct bit_map *t, int offset, int len)
{
int i;
+ unsigned long flags;
if (t->used < len)
BUG(); /* Much too late to do any good, but alas... */
- spin_lock(&t->lock);
+ spin_lock_irqsave(&t->lock, flags);
for (i = 0; i < len; i++) {
if (test_bit(offset + i, t->map) == 0)
BUG();
@@ -115,7 +115,7 @@ void bit_map_clear(struct bit_map *t, int offset, int len)
if (offset < t->first_free)
t->first_free = offset;
t->used -= len;
- spin_unlock(&t->lock);
+ spin_unlock_irqrestore(&t->lock, flags);
}
void bit_map_init(struct bit_map *t, unsigned long *map, int size)
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
index 9a74902ad1814..2a2c7bd210114 100644
--- a/arch/sparc/mm/srmmu.c
+++ b/arch/sparc/mm/srmmu.c
@@ -359,19 +359,39 @@ pgtable_t pte_alloc_one(struct mm_struct *mm)
return ptep;
}
-void pte_free(struct mm_struct *mm, pgtable_t ptep)
+static void __pte_free(struct mm_struct *mm, pgtable_t ptep)
{
+ const bool process_context = mm;
struct page *page;
page = pfn_to_page(__nocache_pa((unsigned long)ptep) >> PAGE_SHIFT);
- spin_lock(&mm->page_table_lock);
+ if (process_context)
+ spin_lock(&mm->page_table_lock);
if (page_ref_dec_return(page) == 1)
pagetable_dtor(page_ptdesc(page));
- spin_unlock(&mm->page_table_lock);
+ if (process_context)
+ spin_unlock(&mm->page_table_lock);
srmmu_free_nocache(ptep, SRMMU_PTE_TABLE_SIZE);
}
+void pte_free(struct mm_struct *mm, pgtable_t ptep)
+{
+ __pte_free(mm, ptep);
+}
+
+void __tlb_remove_table(void *table)
+{
+ const unsigned long encoded = (unsigned long)table;
+ const unsigned long addr = encoded & ~1UL;
+ const bool is_pmd = encoded & 1;
+
+ if (is_pmd)
+ free_pmd_fast((pmd_t *)addr);
+ else /* Called from softirq context, no mm. */
+ __pte_free(NULL, (pgtable_t)addr);
+}
+
/* context handling - a dynamically sized pool is used */
#define NO_CONTEXT -1
diff --git a/arch/um/Kconfig b/arch/um/Kconfig
index d9541d13d9eb0..94b8ff70f578b 100644
--- a/arch/um/Kconfig
+++ b/arch/um/Kconfig
@@ -44,7 +44,6 @@ config UML
select HAVE_SYSCALL_TRACEPOINTS
select THREAD_INFO_IN_TASK
select SPARSE_IRQ
- select MMU_GATHER_RCU_TABLE_FREE
config MMU
bool
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index a8c3b3d31a276..6e5e462ec059a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -283,7 +283,6 @@ config X86
select HAVE_PERF_REGS
select HAVE_PERF_USER_STACK_DUMP
select ASYNC_KERNEL_PGTABLE_FREE if IOMMU_SVA
- select MMU_GATHER_RCU_TABLE_FREE
select MMU_GATHER_MERGE_VMAS
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
select HAVE_REGS_AND_STACK_ACCESS_API
diff --git a/arch/xtensa/include/asm/tlb.h b/arch/xtensa/include/asm/tlb.h
index 8c3ceb4270180..6fb7b78154f62 100644
--- a/arch/xtensa/include/asm/tlb.h
+++ b/arch/xtensa/include/asm/tlb.h
@@ -16,7 +16,7 @@
#include <asm-generic/tlb.h>
-#define __pte_free_tlb(tlb, pte, address) pte_free((tlb)->mm, pte)
+#define __pte_free_tlb(tlb, pte, address) tlb_remove_ptdesc((tlb), page_ptdesc(pte))
void check_tlb_sanity(void);
diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h
index bdcc2778ac64f..044dabc1fe9cb 100644
--- a/include/asm-generic/tlb.h
+++ b/include/asm-generic/tlb.h
@@ -67,11 +67,8 @@
* - tlb_remove_table()
*
* tlb_remove_table() is the basic primitive to free page-table directories
- * (__p*_free_tlb()). In it's most primitive form it is an alias for
- * tlb_remove_page() below, for when page directories are pages and have no
- * additional constraints.
- *
- * See also MMU_GATHER_TABLE_FREE and MMU_GATHER_RCU_TABLE_FREE.
+ * (__p*_free_tlb()). Page directories are freed after an RCU grace
+ * period - see the comment in mm/mmu_gather.c.
*
* - tlb_remove_page() / tlb_remove_page_size()
* - __tlb_remove_folio_pages() / __tlb_remove_page_size()
@@ -151,24 +148,15 @@
* This might be useful if your architecture has size specific TLB
* invalidation instructions.
*
- * MMU_GATHER_TABLE_FREE
- *
- * This provides tlb_remove_table(), to be used instead of tlb_remove_page()
- * for page directores (__p*_free_tlb()).
- *
- * Useful if your architecture has non-page page directories.
+ * Page directories (__p*_free_tlb()) are always freed via tlb_remove_table(),
+ * after an RCU grace period (see mm/mmu_gather.c).
*
- * When used, an architecture is expected to provide __tlb_remove_table() or
- * use the generic __tlb_remove_table(), which does the actual freeing of these
- * pages.
+ * This serialises against software page-table walkers, including architectures
+ * which do not use IPIs for remote TLB invalidates.
*
- * MMU_GATHER_RCU_TABLE_FREE
- *
- * Like MMU_GATHER_TABLE_FREE, and adds semi-RCU semantics to the free (see
- * comment below).
- *
- * Useful if your architecture doesn't use IPIs for remote TLB invalidates
- * and therefore doesn't naturally serialize with software page-table walkers.
+ * An architecture is expected to provide __tlb_remove_table() (see
+ * HAVE_ARCH_TLB_REMOVE_TABLE) or use the generic __tlb_remove_table(), which
+ * does the actual freeing of these pages.
*
* MMU_GATHER_NO_FLUSH_CACHE
*
@@ -200,12 +188,8 @@
* various ptep_get_and_clear() functions.
*/
-#ifdef CONFIG_MMU_GATHER_TABLE_FREE
-
struct mmu_table_batch {
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
struct rcu_head rcu;
-#endif
unsigned int nr;
void *tables[];
};
@@ -224,23 +208,6 @@ static inline void __tlb_remove_table(void *table)
extern void tlb_remove_table(struct mmu_gather *tlb, void *table);
-#else /* !CONFIG_MMU_GATHER_TABLE_FREE */
-
-static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page);
-/*
- * Without MMU_GATHER_TABLE_FREE the architecture is assumed to have page based
- * page directories and we can use the normal page batching to free them.
- */
-static inline void tlb_remove_table(struct mmu_gather *tlb, void *table)
-{
- struct ptdesc *ptdesc = (struct ptdesc *)table;
-
- pagetable_dtor(ptdesc);
- tlb_remove_page(tlb, ptdesc_page(ptdesc));
-}
-#endif /* CONFIG_MMU_GATHER_TABLE_FREE */
-
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
/*
* This allows an architecture that does not use the linux page-tables for
* hardware to skip the TLBI when freeing page tables.
@@ -253,19 +220,6 @@ void tlb_remove_table_sync_one(void);
void tlb_remove_table_sync_rcu(void);
-#else
-
-#ifdef tlb_needs_table_invalidate
-#error tlb_needs_table_invalidate() requires MMU_GATHER_RCU_TABLE_FREE
-#endif
-
-static inline void tlb_remove_table_sync_one(void) { }
-
-static inline void tlb_remove_table_sync_rcu(void) { }
-
-#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */
-
-
#ifndef CONFIG_MMU_GATHER_NO_GATHER
/*
* If we can't allocate a page to make a big batch of page pointers
@@ -325,9 +279,7 @@ static inline void tlb_flush_rmaps(struct mmu_gather *tlb, struct vm_area_struct
struct mmu_gather {
struct mm_struct *mm;
-#ifdef CONFIG_MMU_GATHER_TABLE_FREE
struct mmu_table_batch *batch;
-#endif
unsigned long start;
unsigned long end;
diff --git a/mm/Kconfig b/mm/Kconfig
index c1ddf59c0d71a..bc7befafb47b5 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1465,7 +1465,7 @@ config HAVE_ARCH_TLB_REMOVE_TABLE
config PT_RECLAIM
def_bool y
- depends on MMU_GATHER_RCU_TABLE_FREE && !HAVE_ARCH_TLB_REMOVE_TABLE
+ depends on MMU && !HAVE_ARCH_TLB_REMOVE_TABLE
help
Try to reclaim empty user page table pages in paths other than munmap
and exit_mmap path.
diff --git a/mm/gup.c b/mm/gup.c
index eb898ea1ee22e..63b435ec605c8 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -2700,8 +2700,9 @@ EXPORT_SYMBOL(get_user_pages_unlocked);
* Before activating this code, please be aware that the following assumptions
* are currently made:
*
- * *) Either MMU_GATHER_RCU_TABLE_FREE is enabled, and tlb_remove_table() is used to
- * free pages containing page tables or TLB flushing requires IPI broadcast.
+ * *) tlb_remove_table() is used to free pages containing page tables, with
+ * the free deferred until an RCU grace period has elapsed (see
+ * mm/mmu_gather.c).
*
* *) ptes can be read atomically by the architecture.
*
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 54494c3fa9835..505f7b62ff281 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2476,7 +2476,7 @@ static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)
pgtable_t pgtable;
pgtable = pgtable_trans_huge_withdraw(mm, pmd);
- pte_free(mm, pgtable);
+ pte_free_defer(mm, pgtable);
mm_dec_nr_ptes(mm);
}
diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
index 3985d856de7f9..2a72a9686773a 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -218,8 +218,6 @@ bool __tlb_remove_page_size(struct mmu_gather *tlb, struct page *page, int page_
#endif /* MMU_GATHER_NO_GATHER */
-#ifdef CONFIG_MMU_GATHER_TABLE_FREE
-
static void __tlb_remove_table_free(struct mmu_table_batch *batch)
{
int i;
@@ -230,10 +228,8 @@ static void __tlb_remove_table_free(struct mmu_table_batch *batch)
free_page((unsigned long)batch);
}
-#ifdef CONFIG_MMU_GATHER_RCU_TABLE_FREE
-
/*
- * Semi RCU freeing of the page directories.
+ * RCU freeing of the page directories.
*
* This is needed by some architectures to implement software pagetable walkers.
*
@@ -259,13 +255,13 @@ static void __tlb_remove_table_free(struct mmu_table_batch *batch)
* means.
*
* What we do is batch the freed directory pages (tables) and RCU free them.
- * We use the sched RCU variant, as that guarantees that IRQ/preempt disabling
- * holds off grace periods.
+ * Disabling IRQs or preemption holds off RCU grace periods, so this protects
+ * both rcu_read_lock() and IRQ-disabling walkers.
*
* However, in order to batch these pages we need to allocate storage, this
* allocation is deep inside the MM code and can thus easily fail on memory
- * pressure. To guarantee progress we fall back to single table freeing, see
- * the implementation of tlb_remove_table_one().
+ * pressure. To guarantee progress we fall back to single table freeing, which
+ * is also RCU-deferred - see the implementation of tlb_remove_table_one().
*
*/
@@ -315,15 +311,6 @@ void tlb_remove_table_sync_rcu(void)
synchronize_rcu();
}
-#else /* !CONFIG_MMU_GATHER_RCU_TABLE_FREE */
-
-static void tlb_remove_table_free(struct mmu_table_batch *batch)
-{
- __tlb_remove_table_free(batch);
-}
-
-#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */
-
/*
* If we want tlb_remove_table() to imply TLB invalidates.
*/
@@ -403,13 +390,6 @@ static inline void tlb_table_init(struct mmu_gather *tlb)
tlb->batch = NULL;
}
-#else /* !CONFIG_MMU_GATHER_TABLE_FREE */
-
-static inline void tlb_table_flush(struct mmu_gather *tlb) { }
-static inline void tlb_table_init(struct mmu_gather *tlb) { }
-
-#endif /* CONFIG_MMU_GATHER_TABLE_FREE */
-
static void tlb_flush_mmu_free(struct mmu_gather *tlb)
{
tlb_table_flush(tlb);
diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c
index b91b1a98029c7..ff8ff37064853 100644
--- a/mm/pgtable-generic.c
+++ b/mm/pgtable-generic.c
@@ -386,9 +386,21 @@ pte_t *pte_offset_map_rw_nolock(struct mm_struct *mm, pmd_t *pmd,
* be read-only/read-write protected.
*
* Note that free_pgtables(), used after unmapping detached vmas, or when
- * exiting the whole mm, does not take page table lock before freeing a page
- * table, and may not use RCU at all: "outsiders" like khugepaged should avoid
- * pte_offset_map() and co once the vma is detached from mm or mm_users is zero.
+ * exiting the whole mm, does not take the page table lock before freeing a
+ * table.
+ *
+ * However, the PMD entry is cleared first, and the table freed only after
+ * an RCU grace period, so a walker that mapped the table under
+ * rcu_read_lock() stays safe, and the pmd_same() recheck in
+ * pte_offset_map_lock() detects the teardown.
+ *
+ * Therefore it is safe for "outsiders" like khugepaged to use
+ * pte_offset_map() and co. for VMAs that might be undergoing page table
+ * teardown.
+ *
+ * Note that the PGD itself is freed at mmdrop() time, not under RCU - so the
+ * walker must keep the mm alive via mmgrab(). With that held, walking remains
+ * safe even once mm_users has reached zero.
*/
pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
unsigned long addr, spinlock_t **ptlp)
|
| 25/3 | 2026/09/01 23:44 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 15992 output: 19 thoughts: 885 Thoughts: **Analyzing the Patch for Memory Safety and Concurrency Implications** Okay, let's dive into this patch. My immediate concern is whether it introduces any new uninitialized memory vulnerabilities, something KMSAN would flag, or if the changes are mainly related to concurrency, memory management, page table freeing, locking, and potential use-after-free issues, which KASAN and Lockdep would be more relevant for. First, the patch itself is about making the `MMU_GATHER_RCU_TABLE_FREE` option standard across architectures. Essentially, it's about standardizing how page tables are freed using RCU mechanisms, involving `tlb_remove_table` and `tlb_remove_ptdesc`. Now, let's walk through the modified files: * The documentation is getting updated to reflect page table locking rules and RCU usage. Good. * The `Kconfig` files are being cleaned up, removing and consolidating configuration options. This seems straightforward – simplifying the build process. * The architecture-specific `Kconfig` files are getting their configurations cleaned up as well. Also, expected. * We're seeing changes in `pgalloc.h` headers on several architectures (`arc`, `microblaze`, `xtensa`) where direct freeing is being replaced with `tlb_remove_ptdesc`. That seems like a move towards the RCU approach. * There's an implementation of `__tlb_remove_table` along with a spinlock for allocation/freeing on `m68k`. That’s good to see, spinlocks are necessary for concurrent access protection. * `sh` and `sparc` architectures are also getting implementations related to `__tlb_remove_table`. Consistent with the RCU change. * In `include/asm-generic/tlb.h`, the conditional compilation directives for the RCU-based table freeing are removed, indicating that it's now the standard approach. * The `mm/Kconfig` file has a dependency update related to `PT_RECLAIM`. * A comment update in `mm/gup.c`. * `mm/huge_memory.c` has `pte_free(mm, pgtable)` changed to `pte_free_defer(mm, pgtable)` in `zap_deposited_table`. This looks important, because deferring the free via RCU is the core of the patch. * `mm/mmu_gather.c` gets rid of the old conditional code related to RCU table freeing. * The `mm/pgtable-generic.c` comments are updated. Okay, the crucial part is the `zap_deposited_table` and `pte_free_defer` changes. I need to understand exactly how `pte_free_defer` works. I'm going to run `grepper` across the codebase to get a full picture of `pte_free_defer` usage. I want to see how these changes interact with the existing memory management and page table structures, and determine what the deferred freeing actually entails. |
| 26/3 | 2026/09/01 23:44 | tool | grepper |
0mArgs:{
"Expression": "pte_free_defer"
}Results: {
"Output": "arch/powerpc/include/asm/pgalloc.h=43=static inline void pte_free(struct mm_struct *mm, pgtable_t ptepage)\n--\narch/powerpc/include/asm/pgalloc.h-47-\narch/powerpc/include/asm/pgalloc.h:48:/* arch use pte_free_defer() implementation in arch/powerpc/mm/pgtable-frag.c */\narch/powerpc/include/asm/pgalloc.h:49:#define pte_free_defer pte_free_defer\narch/powerpc/include/asm/pgalloc.h:50:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\narch/powerpc/include/asm/pgalloc.h-51-\n--\narch/powerpc/mm/pgtable-frag.c=117=void pte_fragment_free(unsigned long *table, int kernel)\n--\narch/powerpc/mm/pgtable-frag.c-133-#ifdef CONFIG_TRANSPARENT_HUGEPAGE\narch/powerpc/mm/pgtable-frag.c:134:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\narch/powerpc/mm/pgtable-frag.c-135-{\n--\narch/s390/include/asm/pgalloc.h=151=static inline void pmd_populate(struct mm_struct *mm,\n--\narch/s390/include/asm/pgalloc.h-167-\narch/s390/include/asm/pgalloc.h:168:/* arch use pte_free_defer() implementation in arch/s390/mm/pgalloc.c */\narch/s390/include/asm/pgalloc.h:169:#define pte_free_defer pte_free_defer\narch/s390/include/asm/pgalloc.h:170:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\narch/s390/include/asm/pgalloc.h-171-\n--\narch/s390/mm/pgalloc.c=149=static void pte_free_now(struct rcu_head *head)\n--\narch/s390/mm/pgalloc.c-155-\narch/s390/mm/pgalloc.c:156:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\narch/s390/mm/pgalloc.c-157-{\n--\narch/sparc/include/asm/pgalloc_64.h=66=void pte_free(struct mm_struct *mm, pgtable_t ptepage);\narch/sparc/include/asm/pgalloc_64.h-67-\narch/sparc/include/asm/pgalloc_64.h:68:/* arch use pte_free_defer() implementation in arch/sparc/mm/init_64.c */\narch/sparc/include/asm/pgalloc_64.h:69:#define pte_free_defer pte_free_defer\narch/sparc/include/asm/pgalloc_64.h:70:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\narch/sparc/include/asm/pgalloc_64.h-71-\n--\narch/sparc/mm/init_64.c=2866=static void pte_free_now(struct rcu_head *head)\n--\narch/sparc/mm/init_64.c-2873-\narch/sparc/mm/init_64.c:2874:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\narch/sparc/mm/init_64.c-2875-{\n--\ninclude/linux/pgtable.h=115=static inline void pte_unmap(pte_t *pte)\n--\ninclude/linux/pgtable.h-120-\ninclude/linux/pgtable.h:121:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable);\ninclude/linux/pgtable.h-122-\n--\nmm/huge_memory.c=2474=static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)\n--\nmm/huge_memory.c-2478-\tpgtable = pgtable_trans_huge_withdraw(mm, pmd);\nmm/huge_memory.c:2479:\tpte_free_defer(mm, pgtable);\nmm/huge_memory.c-2480-\tmm_dec_nr_ptes(mm);\n--\nmm/khugepaged.c=1862=static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsigned long addr,\n--\nmm/khugepaged.c-2067-\tpage_table_check_pte_clear_range(mm, haddr, pgt_pmd);\nmm/khugepaged.c:2068:\tpte_free_defer(mm, pmd_pgtable(pgt_pmd));\nmm/khugepaged.c-2069-\n--\nmm/khugepaged.c=2151=static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)\n--\nmm/khugepaged.c-2228-\t\t\tpage_table_check_pte_clear_range(mm, addr, pgt_pmd);\nmm/khugepaged.c:2229:\t\t\tpte_free_defer(mm, pmd_pgtable(pgt_pmd));\nmm/khugepaged.c-2230-\t\t}\n--\nmm/pgtable-generic.c=221=pmd_t pmdp_collapse_flush(struct vm_area_struct *vma, unsigned long address,\n--\nmm/pgtable-generic.c-239-\nmm/pgtable-generic.c:240:/* arch define pte_free_defer in asm/pgalloc.h for its own implementation */\nmm/pgtable-generic.c:241:#ifndef pte_free_defer\nmm/pgtable-generic.c-242-static void pte_free_now(struct rcu_head *head)\n--\nmm/pgtable-generic.c-249-\nmm/pgtable-generic.c:250:void pte_free_defer(struct mm_struct *mm, pgtable_t pgtable)\nmm/pgtable-generic.c-251-{\n--\nmm/pgtable-generic.c-256-}\nmm/pgtable-generic.c:257:#endif /* pte_free_defer */\nmm/pgtable-generic.c-258-#endif /* CONFIG_TRANSPARENT_HUGEPAGE */\n"
} |
| 27/3 | 2026/09/01 23:44 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 19322 output: 266 thoughts: 503 Thoughts: **Patch Analysis and KMSAN Assessment** Okay, let's break down this patch. My initial thought is it's a fairly significant unification effort in how the kernel handles MMU gather table freeing. We're talking about standardizing on RCU table freeing across all architectures, which, if done right, should simplify things and improve overall robustness. Specifically, I see it's converting architectures like arc, m68k, microblaze, sh, sparc, and xtensa to use `tlb_remove_ptdesc` or `tlb_remove_table` with `__tlb_remove_table`. The m68k and sparc concurrency fixes are definitely a good thing. I'm also noting the change to `pte_free_defer` in `zap_deposited_table`, which is standard practice in these types of changes. And removing those Kconfig options suggests the design has matured to the point it's safe to default to the RCU approach. Now, the critical question: Does this introduce any risk of uninitialized memory reads or leaks? I'm immediately going through my mental checklist. First, are there any kernel structures being passed to userspace? No, nothing jumping out at me in terms of ioctl/syscall/netlink/BPF/socket/copy_to_user changes. Good. Second, are there any new uninitialized variables, padding issues, or struct fields that could be problematic? Nope, nothing like that here. Third, are we allocating or initializing any complex data structures where uninitialized fields could be read? Checking `m68k/mm/motorola.c`, ah, I see `pagetable_alloc(GFP_KERNEL | __GFP_ZERO, 0)` is used, zeroing page table allocations, which is as it should be. And lastly, what about any bounds checks, lengths, or data buffers? Not in this patch. So, this patch is primarily concerned with page table lifetime, TLB gathering, RCU reclamation of page table pages, locking, and memory management synchronization. My mind immediately goes to the potential pitfalls in this area: concurrency issues, use-after-free conditions, memory leaks, invalid frees, or locking problems. Fortunately, all the tools are there to catch those types of bugs: KASAN, LOCKDEP, and the various page table checkers and debugging tools. Given the nature of the changes and the safeguards in place, I'm comfortable saying there's absolutely no risk of uninitialized memory reads or kernel information leaks to userspace. Therefore, `NeedsKMSAN` is definitely `false`. Let me just double-check, make sure I haven't missed anything: - `NeedsKMSAN`: false. Yep. - `KMSANReasoning`: detailed reasoning contrasting KASAN vs KMSAN applicability. Check. Time to call `set-results`. |
| 28/3 | 2026/09/01 23:44 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series standardizes page table freeing across architectures to use RCU-deferred freeing (unifying MMU gather and removing CONFIG_MMU_GATHER_RCU_TABLE_FREE / CONFIG_MMU_GATHER_TABLE_FREE configs). It adapts architecture-specific page table freeing routines (e.g. m68k, arc, microblaze, sh, sparc, xtensa) to use tlb_remove_ptdesc / tlb_remove_table, adds spinlocks for ptable management on m68k/sparc, and updates deferred freeing in THP (pte_free_defer).\n\nThese changes deal strictly with MMU gather lifecycle, RCU grace periods for page table memory, and synchronization of page table freeing vs lockless/RCU page table walkers. Potential bugs in these changes would manifest as use-after-free, double-free, concurrency races, or deadlocks—which are covered by KASAN and LOCKDEP. The patch does not introduce any uninitialized memory allocations, padding leaks, or uninitialized reads/copies to userspace. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series standardizes page table freeing across architectures to use RCU-deferred freeing (unifying MMU gather and removing CONFIG_MMU_GATHER_RCU_TABLE_FREE / CONFIG_MMU_GATHER_TABLE_FREE configs). It adapts architecture-specific page table freeing routines (e.g. m68k, arc, microblaze, sh, sparc, xtensa) to use tlb_remove_ptdesc / tlb_remove_table, adds spinlocks for ptable management on m68k/sparc, and updates deferred freeing in THP (pte_free_defer).\n\nThese changes deal strictly with MMU gather lifecycle, RCU grace periods for page table memory, and synchronization of page table freeing vs lockless/RCU page table walkers. Potential bugs in these changes would manifest as use-after-free, double-free, concurrency races, or deadlocks—which are covered by KASAN and LOCKDEP. The patch does not introduce any uninitialized memory allocations, padding leaks, or uninitialized reads/copies to userspace. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|