s390/mm: Batch PTE updates in lazy MMU mode
Subsystems (9)
Alexander Gordeev
maintainerv2 cover s390/mm: Batch PTE updates in lazy MMU mode
Hi All!
This is v2 of the batched PTE updates in lazy MMU mode rework.
Changes since v1:
- lazy_mmu_mode_enable_pte() renamed to lazy_mmu_mode_enable_for_pte_range()
- lazy_mmu_mode_enable_for_pte_range() semantics clarified
- some sashiko comments addressed [1] including one bug fix
- patches 2-4 added
Patches 3-6 is the actual s390 rework, while patches 1,2 are a prerequisite that affects the generic code.
Patch 6 needs to be mereged in patch 5 if deemed useful.
This series addresses an s390-specific aspect of how page table entries are modified. In many cases, changing a valid PTE (for example, setting or clearing a hardware bit) requires issuing an Invalidate Page Table Entry (IPTE) instruction beforehand.
A disadvantage of the IPTE instruction is that it may initiate a machine-wide quiesce state. This state acts as an expensive global hardware lock and should be avoided whenever possible.
Currently, IPTE is invoked for each individual PTE update in most code paths. However, the instruction itself supports invalidating multiple PTEs at once, covering up to 256 entries. Using this capability can significantly reduce the number of quiesce events, with a positive impact on overall system performance. At present, this feature is not utilized.
An effort was therefore made to identify kernel code paths that update large numbers of consecutive PTEs. Such updates can be batched and handled by a single IPTE invocation, leveraging the hardware support described above.
A natural candidate for this optimization is page-table walkers that change attributes of memory ranges and thus modify contiguous ranges of PTEs. Many memory-management system calls enter lazy MMU mode while updating such ranges.
This lazy MMU mode can be leveraged to build on the already existing infrastructure and implement a software-level lazy MMU mechanism, allowing expensive PTE invalidations on s390 to be batched.
1. https://sashiko.dev/#/patchset/cover.1774420056.git.agordeev%40linux.ibm.com
Thanks!
Alexander Gordeev (6):
mm: Make lazy MMU mode context-aware
mm/pgtable: Fix bogus comment to clear_not_present_full_ptes() s390/mm: Complete ptep_get() conversion
s390/mm: Make PTC and UV call order consistent s390/mm: Batch PTE updates in lazy MMU mode
s390/mm: Allow lazy MMU mode disabling
arch/s390/Kconfig | 11 + arch/s390/boot/vmem.c | 32 +-- arch/s390/include/asm/hugetlb.h | 2 +- arch/s390/include/asm/pgtable.h | 258 ++++++++++++++++---- arch/s390/mm/Makefile | 1 + arch/s390/mm/hugetlbpage.c | 12 +- arch/s390/mm/ipte_batch.c | 400 ++++++++++++++++++++++++++++++++ arch/s390/mm/pageattr.c | 42 ++-- arch/s390/mm/pgtable.c | 8 +- arch/s390/mm/vmem.c | 82 ++++--- fs/proc/task_mmu.c | 2 +- include/linux/pgtable.h | 51 +++- mm/madvise.c | 8 +- mm/memory.c | 8 +- mm/mprotect.c | 2 +- mm/mremap.c | 2 +- mm/vmalloc.c | 6 +- 17 files changed, 781 insertions(+), 146 deletions(-) create mode 100644 arch/s390/mm/ipte_batch.c
Signature · 1 line
2.51.0
Alexander Gordeev
maintainerPATCH v2 1/6 mm: Make lazy MMU mode context-aware
Lazy MMU mode is assumed to be context-independent, in the sense that it does not need any additional information while operating. However, the s390 architecture benefits from knowing the exact page table entries being modified.
Introduce lazy_mmu_mode_enable_for_pte_range(), which is provided with the process address space and the page table being operated on. This information is required to enable s390-specific optimizations.
The function takes parameters that are typically passed to page- table level walkers, which implies that the span of PTE entries never crosses a page table boundary.
Architectures that do not require such information simply do not need to define the lazy_mmu_mode_enable_for_pte_range() callback.
--- fs/proc/task_mmu.c | 2 +- include/linux/pgtable.h | 47 +++++++++++++++++++++++++++++++++++++++++ mm/madvise.c | 8 +++---- mm/memory.c | 8 +++---- mm/mprotect.c | 2 +- mm/mremap.c | 2 +- mm/vmalloc.c | 6 +++--- 7 files changed, 61 insertions(+), 14 deletions(-)
fs/proc/task_mmu.c +1 -1
@@ -2752,7 +2752,7 @@ static int pagemap_scan_pmd_entry(pmd_t *pmd, unsigned long start,return 0 ; } -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (vma->vm_mm, start, end, start_pte);if ((p->arg.flags &PM_SCAN_WP_MATCHING ) && !p->vec_out) {/* Fast path for performing exclusive WP */
include/linux/pgtable.h +47 -0
59 lines not on this page · raw message
mm/madvise.c +4 -4
@@ -451,7 +451,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,if (!start_pte)return 0 ;flush_tlb_batched_pending (mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, start_pte);for (; addr < end; pte += nr, addr += nr *PAGE_SIZE ) { nr =1 ; ptent =ptep_get (pte);
@@ -506,7 +506,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,if (!start_pte)break ;flush_tlb_batched_pending (mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, start_pte);if (!err) nr =0 ;continue ;
@@ -673,7 +673,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,if (!start_pte)return 0 ;flush_tlb_batched_pending (mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, start_pte);for (; addr != end; pte += nr, addr +=PAGE_SIZE * nr) { nr =1 ; ptent =ptep_get (pte);
@@ -733,7 +733,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,if (!start_pte)break ;flush_tlb_batched_pending (mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, pte);if (!err) nr =0 ;continue ;
mm/memory.c +4 -4
@@ -1269,7 +1269,7 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,spin_lock_nested (src_ptl,SINGLE_DEPTH_NESTING ); orig_src_pte = src_pte; orig_dst_pte = dst_pte; -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (src_mm, addr, end, src_pte); do { nr = 1;
@@ -1917,7 +1917,7 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,return addr;flush_tlb_batched_pending (mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, start_pte); do { bool any_skipped = false;
@@ -2875,7 +2875,7 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd, mapped_pte = pte =pte_alloc_map_lock (mm, pmd, addr, &ptl);if (!pte)return -ENOMEM ; -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, mapped_pte); do { BUG_ON(!pte_none(ptep_get(pte))); if (!pfn_modify_allowed(pfn, prot)) {
@@ -3235,7 +3235,7 @@ static int apply_to_pte_range(struct mm_struct *mm, pmd_t *pmd,return -EINVAL ; } -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, addr, end, mapped_pte);if (fn) { do {
mm/mprotect.c +1 -1
@@ -233,7 +233,7 @@ static long change_pte_range(struct mmu_gather *tlb, is_private_single_threaded =vma_is_single_threaded_private (vma);flush_tlb_batched_pending (vma->vm_mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (vma->vm_mm, addr, end, pte); do { nr_ptes = 1; oldpte = ptep_get(pte);
mm/mremap.c +1 -1
@@ -260,7 +260,7 @@ static int move_ptes(struct pagetable_move_control *pmc,if (new_ptl != old_ptl)spin_lock_nested (new_ptl,SINGLE_DEPTH_NESTING );flush_tlb_batched_pending (vma->vm_mm); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (mm, old_addr, old_end, old_ptep);for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes *PAGE_SIZE , new_ptep += nr_ptes, new_addr += nr_ptes *PAGE_SIZE ) {
mm/vmalloc.c +3 -3
@@ -108,7 +108,7 @@ static int vmap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,if (!pte)return -ENOMEM ; -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (&init_mm, addr, end, pte); do { if (unlikely(!pte_none(ptep_get(pte)))) {
@@ -371,7 +371,7 @@ static void vunmap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,unsigned long size =PAGE_SIZE ; pte =pte_offset_kernel (pmd, addr); -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (&init_mm, addr, end, pte); do { #ifdef CONFIG_HUGETLB_PAGE
@@ -538,7 +538,7 @@ static int vmap_pages_pte_range(pmd_t *pmd, unsigned long addr,if (!pte)return -ENOMEM ; -lazy_mmu_mode_enable (); +lazy_mmu_mode_enable_for_pte_range (&init_mm, addr, end, pte); do { struct page *page = pages[*nr];
Signature · 1 line
2.51.0
Re: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
Quoted 5 lines · Alexander Gordeev
+/**
+ *lazy_mmu_mode_enable_for_pte_range()- Enable the lazy MMU mode with a speedup hint.
+ * @mm: Address space the pages are mapped into.
+ * @addr: Start address of the range.
+ * @end: End address of the range.
Quoted 8 lines
+ * @ptep: Page table pointer for the first entry.
+ *
+ * Enters a new lazy MMU mode section; if the mode was not already enabled,
+ * enables it and callsarch_enter_lazy_mmu_mode_for_pte_range().
+ *
+ * PTEs that fall within the specified range might observe update speedups.
+ * The PTE range must belong to the specified memory space and not cross
+ * a page table boundary.
Does that mean that all PTEs mapping [addr, end) must belong to the same PTE page? I think the wording should be more specific.
LGTM otherwise:
Quoted 23 lines
+ *
+ * There are no requirements on the order or range completeness of PTE
+ * updates for the specified range.
+ *
+ * Must be paired with a call tolazy_mmu_mode_disable().
+ *
+ * Has no effect if called:
+ * - While paused - seelazy_mmu_mode_pause()
+ * - In interrupt context
+ */include/linux/pgtable.h +13
-0+static inline void lazy_mmu_mode_enable_for_pte_range (struct mm_struct *mm, +unsigned long addr,unsigned long end,pte_t *ptep) +{ +struct lazy_mmu_state *state = ¤t->lazy_mmu_state; + +if (in_interrupt () || state->pause_count >0 ) +return ; + +VM_WARN_ON_ONCE (state->enable_count ==U8_MAX ); + +if (state->enable_count++ ==0 ) +arch_enter_lazy_mmu_mode_for_pte_range (mm, addr, end, ptep); +}
Alexander Gordeev
maintainerRe: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
Quoted 15 lines · Kevin Brodsky
Quoted 5 lines · Alexander Gordeev
+/**
+ *lazy_mmu_mode_enable_for_pte_range()- Enable the lazy MMU mode with a speedup hint.
+ * @mm: Address space the pages are mapped into.
+ * @addr: Start address of the range.
+ * @end: End address of the range.Quoted 8 lines
+ * @ptep: Page table pointer for the first entry.
+ *
+ * Enters a new lazy MMU mode section; if the mode was not already enabled,
+ * enables it and callsarch_enter_lazy_mmu_mode_for_pte_range().
+ *
+ * PTEs that fall within the specified range might observe update speedups.
+ * The PTE range must belong to the specified memory space and not cross
+ * a page table boundary.
Quoted 2 lines
Does that mean that all PTEs mapping [addr, end) must belong to the same PTE page? I think the wording should be more specific.
I tried to state that end of the range must not exceed pmd_addr_end(addr, end). Any of these sounds better?
The PTE range must belong to the specified memory space and ...
a) the address range must not cross the parent PMD address range boundary
b) the PTEs must belong to the same parent PMD
Quoted 3 lines
LGTM otherwise:
Reviewed-by Kevin Brodsky <[email protected]>
Thanks!
Re: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
Quoted 11 lines · Alexander Gordeev
Quoted 5 lines
Quoted 3 lines
+ * PTEs that fall within the specified range might observe update speedups.
+ * The PTE range must belong to the specified memory space and not cross
+ * a page table boundary.Does that mean that all PTEs mapping [addr, end) must belong to the same PTE page? I think the wording should be more specific.
I tried to state that end of the range must not exceed
pmd_addr_end(addr, end). Any of these sounds better?The PTE range must belong to the specified memory space and ...
a) the address range must not cross the parent PMD address range boundary
b) the PTEs must belong to the same parent PMD
I think b) is good, maybe "PMD entry" to be even clearer. My previous suggestion of talking about "PTE page" isn't ideal since some architectures have page tables smaller (or larger) than a page.
- Kevin
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
Quoted 6 lines · Kevin Brodsky
Quoted 5 lines · Alexander Gordeev
Quoted 2 lines
Does that mean that all PTEs mapping [addr, end) must belong to the same PTE page? I think the wording should be more specific.
I tried to state that end of the range must not exceed
pmd_addr_end(addr, end). Any of these sounds better?
Quoted 7 lines
Quoted 3 lines
The PTE range must belong to the specified memory space and ...
a) the address range must not cross the parent PMD address range boundary
b) the PTEs must belong to the same parent PMDI think b) is good, maybe "PMD entry" to be even clearer. My previous suggestion of talking about "PTE page" isn't ideal since some architectures have page tables smaller (or larger) than a page.
In other code (e.g., get_and_clear_ptes()) we state "The PTEs are all in the same PMD."
Signature · 3 lines
Cheers, David
Re: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
In reply to Alexander GordeevQuoted 14 lines
include/linux/pgtable.h +14
-0+/** +* lazy_mmu_mode_enable_for_pte_range() - Enable the lazy MMU mode with a speedup hint. +* @mm: Address space the pages are mapped into. +* @addr: Start address of the range. +* @end: End address of the range. +* @ptep: Page table pointer for the first entry. +* +* Enters a new lazy MMU mode section; if the mode was not already enabled, +* enables it and calls arch_enter_lazy_mmu_mode_for_pte_range(). +* +* PTEs that fall within the specified range might observe update speedups. +* The PTE range must belong to the specified memory space and not cross +* a page table boundary. +*
include/linux/pgtable.h static inline void lazy_mmu_mode_enable(void) · line 305
Quoted 10 lines
include/linux/pgtable.h +10
-0+* There are no requirements on the order or range completeness of PTE +* updates for the specified range. +* +* Must be paired with a call to lazy_mmu_mode_disable(). +* +* Has no effect if called: +* - While paused - see lazy_mmu_mode_pause() +* - In interrupt context +*/ +static inline void lazy_mmu_mode_enable_for_pte_range(struct mm_struct *mm,
I'm still wondering about a better name for this. "enable_for" indeed sounds like it's only for that range.
Maybe really something including a hint like you previously proposed
lazy_mmu_mode_enable_with_ptes()
lazy_mmu_mode_enable_with_pte_hints()
I think lazy_mmu_mode_enable_with_ptes() is neat.
Thoughts?
In any case, on the general approach + semantics:
include/linux/pgtable.h static inline void lazy_mmu_mode_enable(void) · line 316
Quoted 11 lines
include/linux/pgtable.h +11
-0+ unsigned long addr, unsigned long end, pte_t *ptep) +{ + struct lazy_mmu_state *state = ¤t->lazy_mmu_state; + +if (in_interrupt () || state->pause_count >0 ) +return ; + +VM_WARN_ON_ONCE (state->enable_count ==U8_MAX ); + +if (state->enable_count++ ==0 ) +arch_enter_lazy_mmu_mode_for_pte_range (mm, addr, end, ptep);
Okay, so the "hint" is ignored for nesting levels, that works.
Signature · 3 lines
Cheers, David
Alexander Gordeev
maintainerRe: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
Quoted 5 lines · David Hildenbrand (Arm)
I'm still wondering about a better name for this. "
enable_for" indeed sounds like it's only for that range.Maybe really something including a hint like you previously proposed
Quoted 7 lines
lazy_mmu_mode_enable_with_ptes()
lazy_mmu_mode_enable_with_pte_hints()I think
lazy_mmu_mode_enable_with_ptes()is neat.Thoughts?
Works for me.
Quoted 3 lines
In any case, on the general approach + semantics:
...
Quoted 4 lines
Signature · 3 lines
Cheers, David
Thanks!
Heiko Carstens
maintainerRe: [PATCH v2 1/6] mm: Make lazy MMU mode context-aware
In reply to Alexander GordeevQuoted 26 lines · Alexander Gordeev
Lazy MMU mode is assumed to be context-independent, in the sense that it does not need any additional information while operating. However, the s390 architecture benefits from knowing the exact page table entries being modified.
Introduce
lazy_mmu_mode_enable_for_pte_range(), which is provided with the process address space and the page table being operated on. This information is required to enable s390-specific optimizations.The function takes parameters that are typically passed to page- table level walkers, which implies that the span of PTE entries never crosses a page table boundary.
Architectures that do not require such information simply do not need to define the
lazy_mmu_mode_enable_for_pte_range()callback.Signed-off-by Alexander Gordeev <[email protected]>--- fs/proc/task_mmu.c | 2 +- include/linux/pgtable.h | 47 +++++++++++++++++++++++++++++++++++++++++ mm/madvise.c | 8 +++---- mm/memory.c | 8 +++---- mm/mprotect.c | 2 +- mm/mremap.c | 2 +- mm/vmalloc.c | 6 +++--- 7 files changed, 61 insertions(+), 14 deletions(-)
...
Quoted 4 lines
+/**
+ *lazy_mmu_mode_enable_for_pte_range()- Enable the lazy MMU mode with a speedup hint.
+ * @mm: Address space the pages are mapped into.
+ * @addr: Start address of the range.
Quoted 8 lines
+ * @end: End address of the range.
+ * @ptep: Page table pointer for the first entry.
+ *
+ * Enters a new lazy MMU mode section; if the mode was not already enabled,
+ * enables it and callsarch_enter_lazy_mmu_mode_for_pte_range().
+ *
+ * PTEs that fall within the specified range might observe update speedups.
+ * The PTE range must belong to the specified memory space and not cross
I guess it would be better use consistent terminology: "memory space" should be changed to "address space" in order to avoid confusion.
Alexander Gordeev
maintainerPATCH v2 2/6 mm/pgtable: Fix bogus comment to clear_not_present_full_ptes()
In reply to Alexander GordeevThe address provided to clear_not_present_full_ptes() is the address of the underlying memory, not address of the first PTE. The exact wording is taken from clear_ptes() comment.
--- include/linux/pgtable.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
include/linux/pgtable.h +2 -2
@@ -1021,8 +1021,8 @@ static inline void pte_clear_not_present_full(struct mm_struct *mm,/** * clear_not_present_full_ptes - Clear multiple not present PTEs which are * consecutive in the pgtable. -* @mm: Address space the ptes represent. -* @addr: Address of the first pte. +* @mm: Address space the pages are mapped into. +* @addr: Address the first page is mapped at. * @ptep: Page table pointer for the first entry. * @nr: Number of entries to clear. * @full: Whether we are clearing a full mm.
Signature · 1 line
2.51.0
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 2/6] mm/pgtable: Fix bogus comment to clear_not_present_full_ptes()
Quoted 15 lines · Alexander Gordeev
The address provided to
clear_not_present_full_ptes()is the address of the underlying memory, not address of the first PTE. The exact wording is taken fromclear_ptes()comment.Suggested-by Kevin Brodsky <[email protected]>Signed-off-by Alexander Gordeev <[email protected]>--- include/linux/pgtable.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)include/linux/pgtable.h +0
-0
include/linux/pgtable.h static inline void pte_clear_not_present_full(struct mm_struct *mm, · line 1025
Quoted 7 lines
include/linux/pgtable.h +2
-2@@ -1021,8 +1021,8 @@ static inline void pte_clear_not_present_full(struct mm_struct *mm,/** * clear_not_present_full_ptes - Clear multiple not present PTEs which are * consecutive in the pgtable. -* @mm: Address space the ptes represent. -* @addr: Address of the first pte. +* @mm: Address space the pages are mapped into. +* @addr: Address the first page is mapped at.
Talking about pages with non-present entries does not make sense. So both changes are wrong. Can you find something better and send that out separately? I don't think this is required for your series, right?
Also, looking at it, it does not make sense to specify "full" when there is nothing to flush. We are dealing with non-present PTEs after all.
Looking into the sparc variant of pte_clear_not_present_full(), it does
__set_pte_at((mm), (addr), (ptep), __pte(0UL), (fullmm))
and that does
pte_t orig = *ptep; *ptep = pte; maybe_tlb_batch_add(mm, addr, ptep, orig, fullmm, PAGE_SHIFT);
but in maybe_tlb_batch_add we have
if (likely(mm != &init_mm) && pte_accessible(mm, orig)) tlb_batch_add(mm, vaddr, ptep, orig, fullmm, hugepage_shift);
And as the pte is non-present, the pte is certainly not accessible. fullmm is unused on that path.
All the talk about flushing TLBs about something that is non-present for pte_clear_not_present_full looks weird.
@Ryan, you added clear_not_present_full_ptes() back then, but you were simply reusing pte_clear_not_present_full(). Does below seem reasonable to you?
From 0899495ba8ecc73273450e58737bc458976dfa12 Mon Sep 17 00:00:00 2001
From: "David Hildenbrand (Arm)" <[email protected]>
Date: Thu, 16 Apr 2026 09:51:07 +0200
Subject: [PATCH] tmp
--- arch/sparc/include/asm/pgtable_64.h | 4 ---- include/linux/pgtable.h | 28 +++++----------------------- mm/madvise.c | 6 +++--- mm/memory.c | 2 +- 4 files changed, 9 insertions(+), 31 deletions(-)
arch/sparc/include/asm/pgtable_64.h +0 -4
@@ -945,10 +945,6 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr,#define pte_clear (mm,addr,ptep) \ set_pte_at((mm), (addr), (ptep), __pte(0UL)) -#define __HAVE_ARCH_PTE_CLEAR_NOT_PRESENT_FULL -#define pte_clear_not_present_full (mm,addr,ptep,fullmm) \ - __set_pte_at((mm), (addr), (ptep), __pte(0UL), (fullmm)) -#ifdef DCACHE_ALIASING_POSSIBLE #define __HAVE_ARCH_MOVE_PTE#define move_pte (pte, old_addr, new_addr) \
include/linux/pgtable.h +5 -23
@@ -955,24 +955,8 @@ static inline void update_mmu_tlb(struct vm_area_struct *vma,update_mmu_tlb_range (vma, address, ptep,1 ); } -/* -* Some architectures may be able to avoid expensive synchronization -* primitives when modifications are made to PTE's which are already -* not present, or in the process of an address space destruction. -*/ -#ifndef __HAVE_ARCH_PTE_CLEAR_NOT_PRESENT_FULL -static inline void pte_clear_not_present_full (struct mm_struct *mm, -unsigned long address, -pte_t *ptep, -int full) -{ -pte_clear (mm, address, ptep); -} -#endif - -#ifndef clear_not_present_full_ptes/** -* clear_not_present_full_ptes - Clear multiple not present PTEs which are +* clear_not_present_full_ptes - Clear multiple non-present PTEs which are * consecutive in the pgtable. * @mm: Address space the ptes represent. * @addr: Address of the first pte.
@@ -980,24 +964,22 @@ static inline void pte_clear_not_present_full(struct mm_struct *mm,* @nr: Number of entries to clear. * @full: Whether we are clearing a full mm. * -* May be overridden by the architecture; otherwise, implemented as a simple -* loop over pte_clear_not_present_full(). +* Implemented as a simple loop over pte_clear(). * * Context: The caller holds the page table lock. The PTEs are all not present. * The PTEs are all in the same PMD. */ -static inline void clear_not_present_full_ptes (struct mm_struct *mm, -unsigned long addr,pte_t *ptep,unsigned int nr,int full) +static inline void clear_non_present_ptes (struct mm_struct *mm, +unsigned long addr,pte_t *ptep,unsigned int nr) {for (;;) { -pte_clear_not_present_full (mm, addr, ptep, full); +pte_clear (mm, addr, ptep);if (--nr ==0 )break ; ptep++; addr +=PAGE_SIZE ; } } -#endif #ifndef __HAVE_ARCH_PTEP_CLEAR_FLUSH extern pte_t ptep_clear_flush(struct vm_area_struct *vma,
mm/madvise.c +3 -3
@@ -695,10 +695,10 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr, nr =swap_pte_batch (pte, max_nr, ptent); nr_swap -= nr;swap_put_entries_direct (entry, nr); -clear_not_present_full_ptes (mm, addr, pte, nr, tlb->fullmm); +clear_non_present_ (mm, addr, pte, nr); }else if (softleaf_is_hwpoison (entry) ||softleaf_is_poison_marker (entry)) { -pte_clear_not_present_full (mm, addr, pte, tlb->fullmm); +pte_clear (mm, addr, pte); }continue ; }
@@ -1234,7 +1234,7 @@ static int guard_remove_pte_entry(pte_t *pte, unsigned long addr,if (is_guard_pte_marker (ptent)) {/* Simply clear the PTE marker. */ -pte_clear_not_present_full (walk->mm, addr, pte, false); +pte_clear (walk->mm, addr, pte);update_mmu_cache (walk->vma, addr, pte); }
mm/memory.c +1 -1
@@ -1781,7 +1781,7 @@ static inline int zap_nonpresent_ptes(struct mmu_gather *tlb,pr_alert ("unrecognized swap entry 0x%lx\n" , entry.val);WARN_ON_ONCE (1 ); } -clear_not_present_full_ptes (vma->vm_mm, addr, pte, nr, tlb->fullmm); +clear_non_present_ptes (vma->vm_mm, addr, pte, nr); *any_skipped =zap_install_uffd_wp_if_needed (vma, addr, pte, nr, details, ptent);return nr;
Signature · 10 lines
2.43.0 -- Cheers, David
Alexander Gordeev
maintainerRe: [PATCH v2 2/6] mm/pgtable: Fix bogus comment to clear_not_present_full_ptes()
Quoted 3 lines · David Hildenbrand (Arm)
Talking about pages with non-present entries does not make sense. So both changes are wrong. Can you find something better and send that out separately? I don't think this is required for your series, right?
Right.
Alexander Gordeev
maintainerPATCH v2 3/6 s390/mm: Complete ptep_get() conversion
In reply to Alexander GordeevFinalize commit c33c794828f2 ("mm: ptep_get() conversion") and replace direct page table entry dereferencing with the proper accessors (ptep_get(), pmdp_get(), etc.).
Override the default getter implementations even though they are currently identical: pud_clear(), p4d_clear(), and pgd_clear() require corresponding architecture-specific getters, but these are not yet defined. This avoids a dependency loop.
--- arch/s390/boot/vmem.c | 32 +++++++------ arch/s390/include/asm/hugetlb.h | 2 +- arch/s390/include/asm/pgtable.h | 60 ++++++++++++++++++------ arch/s390/mm/hugetlbpage.c | 12 ++--- arch/s390/mm/pageattr.c | 42 +++++++++-------- arch/s390/mm/vmem.c | 82 ++++++++++++++++++--------------- 6 files changed, 138 insertions(+), 92 deletions(-)
arch/s390/boot/vmem.c +17 -15
@@ -338,7 +338,7 @@ static void pgtable_pte_populate(pmd_t *pmd, unsigned long addr, unsigned long e pte =pte_offset_kernel (pmd, addr);for (; addr < end; addr +=PAGE_SIZE , pte++) { -if (pte_none (*pte)) { +if (pte_none (ptep_get (pte))) {if (kasan_pte_populate_zero_shadow (pte, mode))continue ; entry =__pte (resolve_pa_may_alloc (addr,PAGE_SIZE , mode));
@@ -355,26 +355,27 @@ static void pgtable_pmd_populate(pud_t *pud, unsigned long addr, unsigned long e enum populate_mode mode) {unsigned long pa, next, pages =0 ; -pmd_t *pmd, entry; +pmd_t *pmd, entry, large_entry;pte_t *pte; pmd =pmd_offset (pud, addr);for (; addr < end; addr = next, pmd++) { next =pmd_addr_end (addr, end); -if (pmd_none (*pmd)) { + entry =pmdp_get (pmd); +if (pmd_none (entry)) {if (kasan_pmd_populate_zero_shadow (pmd, addr, next, mode))continue ; pa =try_get_large_pmd_pa (pmd, addr, next, mode);if (pa !=INVALID_PHYS_ADDR ) { - entry =__pmd (pa); - entry =set_pmd_bit (entry,SEGMENT_KERNEL ); -set_pmd (pmd, entry); + large_entry =__pmd (pa); + large_entry =set_pmd_bit (large_entry,SEGMENT_KERNEL ); +set_pmd (pmd, large_entry); pages++;continue ; } pte =boot_pte_alloc ();pmd_populate (&init_mm, pmd, pte); - }else if (pmd_leaf (*pmd)) { + }else if (pmd_leaf (entry)) {continue ; }pgtable_pte_populate (pmd, addr, next, mode);
@@ -387,26 +388,27 @@ static void pgtable_pud_populate(p4d_t *p4d, unsigned long addr, unsigned long e enum populate_mode mode) {unsigned long pa, next, pages =0 ; -pud_t *pud, entry; +pud_t *pud, entry, large_entry;pmd_t *pmd; pud =pud_offset (p4d, addr);for (; addr < end; addr = next, pud++) { next =pud_addr_end (addr, end); -if (pud_none (*pud)) { + entry =pudp_get (pud); +if (pud_none (entry)) {if (kasan_pud_populate_zero_shadow (pud, addr, next, mode))continue ; pa =try_get_large_pud_pa (pud, addr, next, mode);if (pa !=INVALID_PHYS_ADDR ) { - entry =__pud (pa); - entry =set_pud_bit (entry,REGION3_KERNEL ); -set_pud (pud, entry); + large_entry =__pud (pa); + large_entry =set_pud_bit (large_entry,REGION3_KERNEL ); +set_pud (pud, large_entry); pages++;continue ; } pmd =boot_crst_alloc (_SEGMENT_ENTRY_EMPTY);pud_populate (&init_mm, pud, pmd); - }else if (pud_leaf (*pud)) { + }else if (pud_leaf (entry)) {continue ; }pgtable_pmd_populate (pud, addr, next, mode);
@@ -425,7 +427,7 @@ static void pgtable_p4d_populate(pgd_t *pgd, unsigned long addr, unsigned long e p4d =p4d_offset (pgd, addr);for (; addr < end; addr = next, p4d++) { next =p4d_addr_end (addr, end); -if (p4d_none (*p4d)) { +if (p4d_none (p4dp_get (p4d))) {if (kasan_p4d_populate_zero_shadow (p4d, addr, next, mode))continue ; pud =boot_crst_alloc (_REGION3_ENTRY_EMPTY);
@@ -451,7 +453,7 @@ static void pgtable_populate(unsigned long addr, unsigned long end, enum populat pgd =pgd_offset (&init_mm, addr);for (; addr < end; addr = next, pgd++) { next =pgd_addr_end (addr, end); -if (pgd_none (*pgd)) { +if (pgd_none (pgdp_get (pgd))) {if (kasan_pgd_populate_zero_shadow (pgd, addr, next, mode))continue ; p4d =boot_crst_alloc (_REGION2_ENTRY_EMPTY);
arch/s390/include/asm/hugetlb.h +1 -1
@@ -41,7 +41,7 @@ static inline pte_t huge_ptep_get_and_clear(struct mm_struct *mm,static inline void huge_pte_clear (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned long sz) { -if ((pte_val (*ptep) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3) +if ((pte_val (ptep_get (ptep)) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3)set_pte (ptep,__pte (_REGION3_ENTRY_EMPTY));else set_pte (ptep,__pte (_SEGMENT_ENTRY_EMPTY));
arch/s390/include/asm/pgtable.h +45 -15
104 lines not on this page · raw message
arch/s390/mm/hugetlbpage.c +6 -6
@@ -143,7 +143,7 @@ void __set_huge_pte_at(struct mm_struct *mm, unsigned long addr, rste =__pte_to_rste (pte);/* Set correct table type for 2G hugepages */ -if ((pte_val (*ptep) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3) { +if ((pte_val (ptep_get (ptep)) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3) {if (likely (pte_present (pte))) rste |= _REGION3_ENTRY_LARGE; rste |= _REGION_ENTRY_TYPE_R3;
@@ -161,7 +161,7 @@ void set_huge_pte_at(struct mm_struct *mm, unsigned long addr,pte_t huge_ptep_get (struct mm_struct *mm,unsigned long addr,pte_t *ptep) { -return __rste_to_pte (pte_val (*ptep)); +return __rste_to_pte (pte_val (ptep_get (ptep))); } pte_t __huge_ptep_get_and_clear(struct mm_struct *mm,
@@ -171,7 +171,7 @@ pte_t __huge_ptep_get_and_clear(struct mm_struct *mm,pmd_t *pmdp = (pmd_t *) ptep;pud_t *pudp = (pud_t *) ptep; -if ((pte_val (*ptep) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3) +if ((pte_val (ptep_get (ptep)) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3)pudp_xchg_direct (mm, addr, pudp,__pud (_REGION3_ENTRY_EMPTY));else pmdp_xchg_direct (mm, addr, pmdp,__pmd (_SEGMENT_ENTRY_EMPTY));
@@ -209,13 +209,13 @@ pte_t *huge_pte_offset(struct mm_struct *mm,pmd_t *pmdp =NULL ; pgdp =pgd_offset (mm, addr); -if (pgd_present (*pgdp)) { +if (pgd_present (pgdp_get (pgdp))) { p4dp =p4d_offset (pgdp, addr); -if (p4d_present (*p4dp)) { +if (p4d_present (p4dp_get (p4dp))) { pudp =pud_offset (p4dp, addr);if (sz ==PUD_SIZE )return (pte_t *)pudp; -if (pud_present (*pudp)) +if (pud_present (pudp_get (pudp))) pmdp =pmd_offset (pudp, addr); } }
arch/s390/mm/pageattr.c +23 -19
120 lines not on this page · raw message
arch/s390/mm/vmem.c +46 -36
253 lines not on this page · raw message
Signature · 1 line
2.51.0
Re: [PATCH v2 3/6] s390/mm: Complete ptep_get() conversion
Quoted 10 lines · Alexander Gordeev
Finalize commit
c33c794828f2("mm:ptep_get()conversion") and replace direct page table entry dereferencing with the proper accessors (ptep_get(),pmdp_get(), etc.).Override the default getter implementations even though they are currently identical:
pud_clear(),p4d_clear(), andpgd_clear()require corresponding architecture-specific getters, but these are not yet defined. This avoids a dependency loop.Signed-off-by Alexander Gordeev <[email protected]>
Quoted 8 lines
--- arch/s390/boot/vmem.c | 32 +++++++------ arch/s390/include/asm/hugetlb.h | 2 +- arch/s390/include/asm/pgtable.h | 60 ++++++++++++++++++------ arch/s390/mm/hugetlbpage.c | 12 ++--- arch/s390/mm/pageattr.c | 42 +++++++++-------- arch/s390/mm/vmem.c | 82 ++++++++++++++++++--------------- 6 files changed, 138 insertions(+), 92 deletions(-)
Was this a manual conversion, or did you use some tooling?
I'm wondering since I can spot not converted locations, e.g. one in __kernel_map_pages().
Besides that:
Alexander Gordeev
maintainerRe: [PATCH v2 3/6] s390/mm: Complete ptep_get() conversion
Quoted 1 line · Heiko Carstens
Was this a manual conversion, or did you use some tooling?
Manual :) But I will try to recheck with a tool.
Quoted 2 lines
I'm wondering since I can spot not converted locations, e.g. one in
__kernel_map_pages().
Whether this fixup is fine?
arch/s390/mm/pageattr.c +2 -1
@@ -455,7 +455,8 @@ void __kernel_map_pages(struct page *page, int numpages, int enable) nr =min (numpages - i, nr);if (enable) {for (j =0 ; j < nr; j++) { - pte =clear_pte_bit (*ptep,__pgprot (_PAGE_INVALID)); + pte =ptep_get (ptep); + pte =clear_pte_bit (pte,__pgprot (_PAGE_INVALID));set_pte (ptep, pte); address +=PAGE_SIZE ; ptep++;
Quoted 2 lines
Besides that:
Acked-by Heiko Carstens <[email protected]>
Heiko Carstens
maintainerRe: [PATCH v2 3/6] s390/mm: Complete ptep_get() conversion
Quoted 15 lines · Alexander Gordeev
Quoted 1 line · Heiko Carstens
Was this a manual conversion, or did you use some tooling?
Manual :) But I will try to recheck with a tool.
Quoted 2 lines
I'm wondering since I can spot not converted locations, e.g. one in
__kernel_map_pages().Whether this fixup is fine?
arch/s390/mm/pageattr.c +0
-0
arch/s390/mm/pageattr.c void __kernel_map_pages(struct page *page, int numpages, int enable) · line 459
Quoted 6 lines
arch/s390/mm/pageattr.c +2
-1@@ -455,7 +455,8 @@ void __kernel_map_pages(struct page *page, int numpages, int enable) nr =min (numpages - i, nr);if (enable) {for (j =0 ; j < nr; j++) { - pte =clear_pte_bit (*ptep,__pgprot (_PAGE_INVALID)); + pte =ptep_get (ptep); + pte =clear_pte_bit (pte,__pgprot (_PAGE_INVALID));
Sure.
Alexander Gordeev
maintainerPATCH v2 4/6 s390/mm: Make PTC and UV call order consistent
In reply to Alexander GordeevIn various code paths, page_table_check_pte_clear() is called before converting a secure page, while in others it is called after. Make this consistent and always perform the conversion after the PTC hook has been called. Also make all conversion‑ eligibility condition checks look the same, and rework the one in ptep_get_and_clear_full() slightly.
--- arch/s390/include/asm/pgtable.h | 39 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-)
arch/s390/include/asm/pgtable.h +18 -21
@@ -1219,10 +1219,10 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,pte_t res; res =ptep_xchg_lazy (mm, addr, ptep,__pte (_PAGE_INVALID)); +page_table_check_pte_clear (mm, addr, res);/* At this point the reference through the mapping is still present */ if (mm_is_protected (mm) &&pte_present (res))WARN_ON_ONCE (uv_convert_from_secure_pte (res)); -page_table_check_pte_clear (mm, addr, res);return res; }
@@ -1238,10 +1238,10 @@ static inline pte_t ptep_clear_flush(struct vm_area_struct *vma,pte_t res; res =ptep_xchg_direct (vma->vm_mm, addr, ptep,__pte (_PAGE_INVALID)); +page_table_check_pte_clear (vma->vm_mm, addr, res);/* At this point the reference through the mapping is still present */ if (mm_is_protected (vma->vm_mm) &&pte_present (res))WARN_ON_ONCE (uv_convert_from_secure_pte (res)); -page_table_check_pte_clear (vma->vm_mm, addr, res);return res; }
@@ -1265,26 +1265,23 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm, }else { res =ptep_xchg_lazy (mm, addr, ptep,__pte (_PAGE_INVALID)); } -page_table_check_pte_clear (mm, addr, res); - -/* Nothing to do */ -if (!mm_is_protected (mm) || !pte_present (res)) -return res; -/* -* At this point the reference through the mapping is still present. -* The notifier should have destroyed all protected vCPUs at this -* point, so the destroy should be successful. -*/ -if (full && !uv_destroy_pte (res)) -return res; -/* -* If something went wrong and the page could not be destroyed, or -* if this is not a mm teardown, the slower export is used as -* fallback instead. If even that fails, print a warning and leak -* the page, to avoid crashing the whole system. -*/ -WARN_ON_ONCE (uv_convert_from_secure_pte (res)); +/* At this point the reference through the mapping is still present */ +if (mm_is_protected (mm) &&pte_present (res)) { +/* +* The notifier should have destroyed all protected vCPUs at +* this point, so the destroy should be successful. +*/ +if (full && !uv_destroy_pte (res)) +return res; +/* +* If something went wrong and the page could not be destroyed, +* or if this is not a mm teardown, the slower export is used +* as fallback instead. If even that fails, print a warning and +* leak the page, to avoid crashing the whole system. +*/ +WARN_ON_ONCE (uv_convert_from_secure_pte (res)); + }return res; }
Signature · 1 line
2.51.0
Heiko Carstens
maintainerRe: [PATCH v2 4/6] s390/mm: Make PTC and UV call order consistent
Quoted 7 lines · Alexander Gordeev
In various code paths,
page_table_check_pte_clear()is called before converting a secure page, while in others it is called after. Make this consistent and always perform the conversion after the PTC hook has been called. Also make all conversion‑ eligibility condition checks look the same, and rework the one inptep_get_and_clear_full()slightly.
Quoted 5 lines
Acked-by Claudio Imbrenda <[email protected]>Signed-off-by Alexander Gordeev <[email protected]>--- arch/s390/include/asm/pgtable.h | 39 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-)
Why is this required for this series? It is anything but obvious to me. If it is required please add some reasoning e.g. in the cover letter or to the above changelog.
Alexander Gordeev
maintainerRe: [PATCH v2 4/6] s390/mm: Make PTC and UV call order consistent
Quoted 14 lines · Heiko Carstens
Quoted 7 lines · Alexander Gordeev
In various code paths,
page_table_check_pte_clear()is called before converting a secure page, while in others it is called after. Make this consistent and always perform the conversion after the PTC hook has been called. Also make all conversion‑ eligibility condition checks look the same, and rework the one inptep_get_and_clear_full()slightly.Quoted 5 lines
Acked-by Claudio Imbrenda <[email protected]>Signed-off-by Alexander Gordeev <[email protected]>--- arch/s390/include/asm/pgtable.h | 39 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-)
Quoted 3 lines
Why is this required for this series? It is anything but obvious to me. If it is required please add some reasoning e.g. in the cover letter or to the above changelog.
This is because changes to ptep_get_and_clear() and ptep_clear_flush() look much more clean with this patch. Otherwise, I would have to move page_table_check_pte_clear() before the UV check, which is strictly speaking worth a separate patch - this patch.
It also makes sense as a cleanup. I would prefer to see it as part of the PTC series, but at the time I did not realize it was worthwhile.
Alexander Gordeev
maintainerPATCH v2 5/6 s390/mm: Batch PTE updates in lazy MMU mode
In reply to Alexander GordeevMake use of the IPTE instruction's "Additional Entries" field to invalidate multiple PTEs in one go while in lazy MMU mode. This is the mode in which many memory-management system calls (like mremap(), mprotect(), etc.) update memory attributes.
To achieve that, the set_pte() and ptep_get() primitives use a per-CPU cache to store and retrieve PTE values and apply the cached values to the real page table once lazy MMU mode is left.
The same is done for memory-management platform callbacks that would otherwise cause intense per-PTE IPTE traffic, reducing the number of IPTE instructions from up to PTRS_PER_PTE to a single instruction in the best case. The average reduction is of course smaller.
Since all existing page table iterators called in lazy MMU mode handle one table at a time, the per-CPU cache does not need to be larger than PTRS_PER_PTE entries. That also naturally aligns with the IPTE instruction, which must not cross a page table boundary.
Before this change, the system calls did:
lazy_mmu_mode_enable_pte() ... <update PTEs> // up to PTRS_PER_PTE single-IPTEs ... lazy_mmu_mode_disable()
With this change, the system calls do:
lazy_mmu_mode_enable_pte()
...
<store new PTE values in the per-CPU cache>
...
lazy_mmu_mode_disable() // apply cache with one multi-IPTE
When applied to large memory ranges, some system calls show significant speedups:
mprotect() ~15x
munmap() ~3x
mremap() ~28xAt the same time, fork() shows a measurable slowdown of ~1.5x.
The overall results depend on memory size and access patterns, but the change generally does not degrade performance.
In addition to a process-wide impact, the rework affects the whole Central Electronics Complex (CEC). Each (global) IPTE instruction initiates a quiesce state in a CEC, so reducing the number of IPTE calls relieves CEC-wide quiesce traffic.
In an extreme case of mprotect() contiguously triggering the quiesce state on four LPARs in parallel, measurements show ~25x fewer quiesce events.
--- arch/s390/Kconfig | 11 + arch/s390/include/asm/pgtable.h | 169 ++++++++++++-- arch/s390/mm/Makefile | 1 + arch/s390/mm/ipte_batch.c | 378 ++++++++++++++++++++++++++++++++ arch/s390/mm/pgtable.c | 8 +- 5 files changed, 545 insertions(+), 22 deletions(-) create mode 100644 arch/s390/mm/ipte_batch.c
arch/s390/Kconfig +11 -0
@@ -732,6 +732,17 @@ config MAX_PHYSMEM_BITS Increasing the number of bits also increases the kernel image size. By default 46 bits (64TB) are supported. +config IPTE_BATCH + def_bool y + prompt "Enables Additional Entries for IPTE instruction" + select ARCH_HAS_LAZY_MMU_MODE + help + This option enables using of "Additional Entries" field of the IPTE + instruction, which capitalizes on the lazy MMU mode infrastructure. + As result, multiple PTEs are invalidated in one go. That improves + performance of many memory-management system calls (like mremap(), + mprotect(), etc.) and decreases CEC-wide quiesce traffic. + endmenu menu "I/O subsystem"
arch/s390/include/asm/pgtable.h +151 -18
232 lines not on this page · raw message
arch/s390/mm/Makefile +1 -0
@@ -11,5 +11,6 @@ obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o obj-$(CONFIG_PTDUMP) += dump_pagetables.o obj-$(CONFIG_PFAULT) += pfault.o +obj-$(CONFIG_IPTE_BATCH) += ipte_batch.o obj-$(subst m,y,$(CONFIG_KVM)) += gmap_helpers.o
arch/s390/mm/ipte_batch.c new file +378 -0
378 lines not on this page · raw message
arch/s390/mm/pgtable.c +4 -4
@@ -166,14 +166,14 @@ pte_t ptep_xchg_lazy(struct mm_struct *mm, unsigned long addr, }EXPORT_SYMBOL (ptep_xchg_lazy); -pte_t ptep_modify_prot_start (struct vm_area_struct *vma,unsigned long addr, -pte_t *ptep) +pte_t ___ptep_modify_prot_start (struct vm_area_struct *vma,unsigned long addr, +pte_t *ptep) {return ptep_flush_lazy (vma->vm_mm, addr, ptep,1 ); } -void ptep_modify_prot_commit (struct vm_area_struct *vma,unsigned long addr, -pte_t *ptep,pte_t old_pte,pte_t pte) +void ___ptep_modify_prot_commit (struct vm_area_struct *vma,unsigned long addr, +pte_t *ptep,pte_t old_pte,pte_t pte) {set_pte (ptep, pte); }
Signature · 1 line
2.51.0
Heiko Carstens
maintainerQuoted 10 lines · Alexander Gordeev
arch/s390/Kconfig +2
-0@@ -732,6 +732,17 @@ config MAX_PHYSMEM_BITS Increasing the number of bits also increases the kernel image size. By default 46 bits (64TB) are supported. +config IPTE_BATCH + def_bool y
arch/s390/Kconfig config MAX_PHYSMEM_BITS · line 746
Quoted 10 lines
arch/s390/Kconfig +9
-0+ prompt "Enables Additional Entries for IPTE instruction" + select ARCH_HAS_LAZY_MMU_MODE + help + This option enables using of "Additional Entries" field of the IPTE + instruction, which capitalizes on the lazy MMU mode infrastructure. + As result, multiple PTEs are invalidated in one go. That improves + performance of many memory-management system calls (like mremap(), + mprotect(), etc.) and decreases CEC-wide quiesce traffic. + endmenu
Just some general comments before I find time to look deeper into this
series: please let us not add yet another config option. This will be enabled anywhere in any case.
Not having this option makes maintenance easier.
arch/s390/Kconfig config MAX_PHYSMEM_BITS
Quoted 8 lines
arch/s390/Kconfig +8
-0+#if !defined(CONFIG_IPTE_BATCH) || defined(__DECOMPRESSOR) +static inline +bool ipte_batch_ptep_test_and_clear_young(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, + int *res) +{ + return false; +}
Why does this depend on __DECOMPRESSOR? Which code in the decompressor would generate a call to the ipte_batch functions?
Alexander Gordeev
maintainerQuoted 11 lines · Heiko Carstens
arch/s390/include/asm/pgtable.h enum { · line 49
Quoted 8 lines
arch/s390/include/asm/pgtable.h +8
-0+#if !defined(CONFIG_IPTE_BATCH ) || defined(__DECOMPRESSOR) +static inline +bool ipte_batch_ptep_test_and_clear_young (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +int *res) +{ +return false; +}Why does this depend on
__DECOMPRESSOR? Which code in the decompressor would generate a call to theipte_batchfunctions?
Any call to ptep_get() or set_pte().
Heiko Carstens
maintainerQuoted 28 lines · Alexander Gordeev
Make use of the IPTE instruction's "Additional Entries" field to invalidate multiple PTEs in one go while in lazy MMU mode. This is the mode in which many memory-management system calls (like
mremap(),mprotect(), etc.) update memory attributes.To achieve that, the
set_pte()andptep_get()primitives use a per-CPU cache to store and retrieve PTE values and apply the cached values to the real page table once lazy MMU mode is left.The same is done for memory-management platform callbacks that would otherwise cause intense per-PTE IPTE traffic, reducing the number of IPTE instructions from up to
PTRS_PER_PTEto a single instruction in the best case. The average reduction is of course smaller.Since all existing page table iterators called in lazy MMU mode handle one table at a time, the per-CPU cache does not need to be larger than
PTRS_PER_PTEentries. That also naturally aligns with the IPTE instruction, which must not cross a page table boundary.Before this change, the system calls did:
lazy_mmu_mode_enable_pte() ... <update PTEs> // up to PTRS_PER_PTE single-IPTEs ... lazy_mmu_mode_disable()
Quoted 7 lines
With this change, the system calls do:
lazy_mmu_mode_enable_pte()
...
<store new PTE values in the per-CPU cache>
...
lazy_mmu_mode_disable()// apply cache with one multi-IPTE
I think what is not necessarily immediately obvious: this approach must assure that within such a lazy mmu section there is not a single occurrence of code which doesn't use the above mentioned modified primitives to dereference page table entry pointers.
Directly dereferencing such pointers would bypass the cache and lead to incorrect results. Therefore we do need some mechanism which makes sure this cannot happen. Preferebly that would happen at compile time with static code analysis. Alternatively your Kasan implementation would be helpful to find something like that after-the-fact.
However in any case we need something to address this problem.
Quoted 8 lines
arch/s390/include/asm/pgtable.h +8
-0+#if !defined(CONFIG_IPTE_BATCH ) || defined(__DECOMPRESSOR) +static inline +bool ipte_batch_ptep_test_and_clear_young (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +int *res) +{ +return false; +}
...
arch/s390/include/asm/pgtable.h enum { · line 92
Quoted 3 lines
arch/s390/include/asm/pgtable.h +3
-0+bool ipte_batch_ptep_test_and_clear_young (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +int *res);
Could you change this to something like:
bool __ipte_batch_ptep_test_and_clear_young(struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, int *res);
static inline
bool ipte_batch_ptep_test_and_clear_young(struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, int *res)
{
if (__is_defined(__DECOMPRESSOR))
return false;
return __ipte_batch_ptep_test_and_clear_young(vma, addr, ptep, res);
}? This avoids the ifdef and makes the code easier to read and change.
arch/s390/include/asm/pgtable.h static inline void set_pmd(pmd_t *pmdp, pmd_t pmd) · line 1055
Quoted 5 lines
arch/s390/include/asm/pgtable.h +5
-0+static inline void set_pte (pte_t *ptep,pte_t pte) +{ +if (!ipte_batch_set_pte (ptep, pte)) +__set_pte (ptep, pte); +}
Not sure if you analyzed it, but it looks like this might be the reason why you see the fork() slowdown: every page table operation now comes with a function call, even if is not needed.
I guess it would be helpful to add an early exit to the ipte_batch() inlines. E.g. going back to the example above:
static inline
bool ipte_batch_ptep_test_and_clear_young(struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, int *res)
{
if (__is_defined(__DECOMPRESSOR))
return false;
---> if (unlikely(!ipte_batch_active()))
---> return false;
return __ipte_batch_ptep_test_and_clear_young(vma, addr, ptep, res);
}Where ipte_batch_active() would be an inline function which simply tests a bit in lowcore.
arch/s390/mm/Makefile obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o · line 14
Quoted 9 lines
arch/s390/mm/Makefile +1
-0@@ -11,5 +11,6 @@ obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o obj-$(CONFIG_PTDUMP) += dump_pagetables.o obj-$(CONFIG_PFAULT) += pfault.o +obj-$(CONFIG_IPTE_BATCH) += ipte_batch.o
As already stated before: let's get rid of the Kconfig option. This will be enabled anyway.
arch/s390/mm/Makefile obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o · line 15
Quoted 1 line
arch/s390/mm/Makefile +1
-0+#define PTE_POISON 0
Is there a reason why the PTE_POISON value is zero? If it can be a different value, I would like to propose a PTE which has bit 11 set (the one which must be zero). If that would ever be transferred to page table, and it would be used, we would see that immediately :)
arch/s390/mm/Makefile obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o
Quoted 11 lines
arch/s390/mm/Makefile +11
-0+struct ipte_batch { + struct mm_struct *mm; + unsigned long base_addr; + unsigned long base_end; + pte_t *base_pte; + pte_t *start_pte; + pte_t *end_pte; + pte_t cache[PTRS_PER_PTE]; +}; + +static DEFINE_PER_CPU(struct ipte_batch, ipte_range);
This is ~1MB with 512 possible CPUs. I think it would make sense to allocate and free this data structure with CPU hotplug to avoid wasting too much memory.
Quoted 10 lines
arch/s390/mm/Makefile +10
-0+static int count_contiguous(pte_t *start, pte_t *end, bool *valid) +{ + pte_t pte = __ptep_get(start); + pte_t *ptep; + + *valid = !(pte_val(pte) & _PAGE_INVALID); + + for (ptep = start + 1; ptep < end; ptep++) { + pte = __ptep_get(ptep); + if (*valid) {
arch/s390/mm/Makefile
Quoted 10 lines
arch/s390/mm/Makefile +10
-0+ if (pte_val(pte) & _PAGE_INVALID) + break; + } else { + if (!(pte_val(pte) & _PAGE_INVALID)) + break; + } + } + + return ptep - start; +}
Wouldn't it be possible to shorten this a bit? E.g.:
static int count_contiguous(pte_t *start, pte_t *end, bool *valid)
{
unsigned long inv;
pte_t *ptep;
inv = pte_val(__ptep_get(start)) & _PAGE_INVALID;
*valid = !inv;
for (ptep = start + 1; ptep < end; ptep++) {
if (pte_val(__ptep_get(ptep)) & _PAGE_INVALID != inv)
break;
}
return ptep - start;
}arch/s390/mm/Makefile
Quoted 6 lines
arch/s390/mm/Makefile +6
-0+static void __invalidate_pte_range(struct mm_struct *mm, unsigned long addr, + int nr_ptes, pte_t *ptep) +{ + atomic_inc(&mm->context.flush_count); + if (cpu_has_tlb_lc() && + cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id())))
One line please. Yes, I know, this is more or less copy-pasted, but... :)
Just a general comment about the naming conventions: imho ipte_batch is not a nice choice, since the name of the facility is "ipte range". However I would abstract even more, since nobody knows if there will be a different instruction or facility to achieve all of this in a better way.
Anyway... maybe rename the file simply to mmu.c or tlb.c and change the function prefixes accordingly so we end up with shorter function names?
Alexander Gordeev
maintainerOn Thu, Apr 23, 2026 at 02:28:24PM +0200, Heiko Carstens wrote:
Hi Heiko!
...
Quoted 13 lines
Quoted 7 lines
With this change, the system calls do:
lazy_mmu_mode_enable_pte()
...
<store new PTE values in the per-CPU cache>
...
lazy_mmu_mode_disable()// apply cache with one multi-IPTEI think what is not necessarily immediately obvious: this approach must assure that within such a lazy mmu section there is not a single occurrence of code which doesn't use the above mentioned modified primitives to dereference page table entry pointers.
Quoted 7 lines
Directly dereferencing such pointers would bypass the cache and lead to incorrect results. Therefore we do need some mechanism which makes sure this cannot happen. Preferebly that would happen at compile time with static code analysis. Alternatively your Kasan implementation would be helpful to find something like that after-the-fact.
However in any case we need something to address this problem.
Will try to come up with something.
...
Quoted 15 lines
arch/s390/include/asm/pgtable.h enum { · line 92
Quoted 4 lines
arch/s390/include/asm/pgtable.h +3
-0+bool ipte_batch_ptep_test_and_clear_young (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +int *res);Could you change this to something like:
bool __ipte_batch_ptep_test_and_clear_young(struct
vm_area_struct*vma,unsigned long addr, pte_t *ptep, int *res);static inline
bool ipte_batch_ptep_test_and_clear_young(structvm_area_struct*vma,
unsigned long addr,pte_t*ptep, int *res)
Quoted 7 lines
{ if (__is_defined(__DECOMPRESSOR)) return false; return __ipte_batch_ptep_test_and_clear_young(vma, addr, ptep, res); }? This avoids the ifdef and makes the code easier to read and change.
Yes, I also like it much better and had something similar in early versions. But backed off to keep the whole header style intact. Anyway, will do.
Quoted 25 lines
arch/s390/include/asm/pgtable.h static inline void set_pmd(pmd_t *pmdp, pmd_t pmd) · line 1055
Quoted 5 lines
arch/s390/include/asm/pgtable.h +5
-0+static inline void set_pte (pte_t *ptep,pte_t pte) +{ +if (!ipte_batch_set_pte (ptep, pte)) +__set_pte (ptep, pte); +}Not sure if you analyzed it, but it looks like this might be the reason why you see the
fork()slowdown: every page table operation now comes with a function call, even if is not needed.I guess it would be helpful to add an early exit to the
ipte_batch()inlines. E.g. going back to the example above:static inline
bool ipte_batch_ptep_test_and_clear_young(structvm_area_struct*vma,
unsigned long addr,pte_t*ptep, int *res){ if (__is_defined(__DECOMPRESSOR)) return false; ---> if (unlikely(!ipte_batch_active())) ---> return false; return __ipte_batch_ptep_test_and_clear_young(vma, addr, ptep, res); }
Quoted 2 lines
Where
ipte_batch_active()would be an inline function which simply tests a bit in lowcore.
The bit check alone would not be enough - it should be done with the preemption disabled. Then it would be something like lazy_mmu_mode(), but with one or more values in the lowcore instead of percpu variable (at least ::base_pte to check whether ptep falls into the active range).
...
Quoted 6 lines
Quoted 1 line
+#define
PTE_POISON0Is there a reason why the
PTE_POISONvalue is zero? If it can be a different value, I would like to propose a PTE which has bit 11 set (the one which must be zero). If that would ever be transferred to page table, and it would be used, we would see that immediately :)
The main reason was to avoid explicit initialization of per-CPU arrays. But yes, bit 11 sounds like a much better approach ;)
Quoted 7 lines
Quoted 7 lines
arch/s390/mm/ipte_batch.c new file +7
-0+struct ipte_batch { +struct mm_struct *mm; +unsigned long base_addr; +unsigned long base_end; +pte_t *base_pte; +pte_t *start_pte; +pte_t *end_pte;
Quoted 8 lines
arch/s390/mm/ipte_batch.c line 18
Quoted 4 lines
arch/s390/mm/ipte_batch.c new file +4
-0+pte_t cache[PTRS_PER_PTE ]; +}; + +static DEFINE_PER_CPU(struct ipte_batch, ipte_range);This is ~1MB with 512 possible CPUs. I think it would make sense to allocate and free this data structure with CPU hotplug to avoid wasting too much memory.
Will do.
Quoted 14 lines
Quoted 14 lines
arch/s390/mm/ipte_batch.c new file +14
-0+static int count_contiguous (pte_t *start,pte_t *end,bool *valid) +{ +pte_t pte =__ptep_get (start); +pte_t *ptep; + + *valid = !(pte_val (pte) & _PAGE_INVALID); + +for (ptep = start +1 ; ptep < end; ptep++) { + pte =__ptep_get (ptep); +if (*valid) { +if (pte_val (pte) & _PAGE_INVALID) +break ; + }else { +if (!(pte_val (pte) & _PAGE_INVALID))
Quoted 8 lines
arch/s390/mm/ipte_batch.c line 39
Quoted 6 lines
arch/s390/mm/ipte_batch.c new file +6
-0+break ; + } + } + +return ptep - start; +}Wouldn't it be possible to shorten this a bit? E.g.:
Will try.
Quoted 14 lines
static int
count_contiguous(pte_t *start, pte_t *end, bool *valid){ unsigned long inv; pte_t *ptep; inv = pte_val(__ptep_get(start)) & _PAGE_INVALID; *valid = !inv; for (ptep = start + 1; ptep < end; ptep++) { if (pte_val(__ptep_get(ptep)) & _PAGE_INVALID != inv) break; } return ptep - start; }
Quoted 8 lines
arch/s390/mm/ipte_batch.c line 46
Quoted 6 lines
arch/s390/mm/ipte_batch.c new file +6
-0+static void __invalidate_pte_range (struct mm_struct *mm,unsigned long addr, +int nr_ptes,pte_t *ptep) +{ +atomic_inc (&mm->context.flush_count); +if (cpu_has_tlb_lc () && +cpumask_equal (mm_cpumask (mm),cpumask_of (smp_processor_id ())))One line please. Yes, I know, this is more or less copy-pasted, but... :)
Will do.
Quoted 7 lines
Just a general comment about the naming conventions: imho
ipte_batchis not a nice choice, since the name of the facility is "ipte range". However I would abstract even more, since nobody knows if there will be a different instruction or facility to achieve all of this in a better way.Anyway... maybe rename the file simply to
mmu.cortlb.cand change the function prefixes accordingly so we end up with shorter function names?
I would in turn suggest lazy_mmu_ prefix and lazy_mmu.c source name to emphasize it implements the generic lazy mmu mode. At the same time keep ipte_batch (or ipte_range rather) in the implementation itself.
Thanks a lot for the review!
Heiko Carstens
maintainerQuoted 26 lines · Alexander Gordeev
Quoted 25 lines · Heiko Carstens
arch/s390/include/asm/pgtable.h static inline void set_pmd(pmd_t *pmdp, pmd_t pmd) · line 1055
Quoted 5 lines
arch/s390/include/asm/pgtable.h +5
-0+static inline void set_pte (pte_t *ptep,pte_t pte) +{ +if (!ipte_batch_set_pte (ptep, pte)) +__set_pte (ptep, pte); +}Not sure if you analyzed it, but it looks like this might be the reason why you see the
fork()slowdown: every page table operation now comes with a function call, even if is not needed.I guess it would be helpful to add an early exit to the
ipte_batch()inlines. E.g. going back to the example above:static inline
bool ipte_batch_ptep_test_and_clear_young(structvm_area_struct*vma,
unsigned long addr,pte_t*ptep, int *res){ if (__is_defined(__DECOMPRESSOR)) return false; ---> if (unlikely(!ipte_batch_active())) ---> return false; return __ipte_batch_ptep_test_and_clear_young(vma, addr, ptep, res); }
Quoted 7 lines
Quoted 2 lines
Where
ipte_batch_active()would be an inline function which simply tests a bit in lowcore.The bit check alone would not be enough - it should be done with the preemption disabled. Then it would be something like
lazy_mmu_mode(), but with one or more values in the lowcore instead of percpu variable (at least ::base_pte to check whether ptep falls into the active range).
What I meant: you need a one bit check in the inlined function, so you can decide if it is even necessary to do the function call. More checking could be done. However all the overhead of function calls will go away with single bit check.
Or in other words: enter_ipte_batch() should set a bit in lowcore which can be easily tested. This bit _also_ indicates that preemption is disabled. So all the extra preempt disable / enable pairs within the page table primitives are not needed, after this bit is present und used.
Quoted 10 lines
Quoted 7 lines
Just a general comment about the naming conventions: imho
ipte_batchis not a nice choice, since the name of the facility is "ipte range". However I would abstract even more, since nobody knows if there will be a different instruction or facility to achieve all of this in a better way.Anyway... maybe rename the file simply to
mmu.cortlb.cand change the function prefixes accordingly so we end up with shorter function names?I would in turn suggest
lazy_mmu_prefix andlazy_mmu.csource name to emphasize it implements the generic lazy mmu mode.
Ok.
Quoted 2 lines
At the same time
keepipte_batch(oripte_rangerather) in the implementation itself.
That still doesn't look to good to me, but can be solved at a later time.
Alexander Gordeev
maintainerQuoted 8 lines · Heiko Carstens
Quoted 7 lines
With this change, the system calls do:
lazy_mmu_mode_enable_pte()
...
<store new PTE values in the per-CPU cache>
...
lazy_mmu_mode_disable()// apply cache with one multi-IPTE
Quoted 4 lines
I think what is not necessarily immediately obvious: this approach must assure that within such a lazy mmu section there is not a single occurrence of code which doesn't use the above mentioned modified primitives to dereference page table entry pointers.
So ideally I would do something like __private for pte_t, but I did not manage to circumvent sparse false positives neither with a scalar pte_t, nor with a struct. It is as well-known sparse limitation, if I am not mistaken.
Quoted 5 lines
Directly dereferencing such pointers would bypass the cache and lead to incorrect results. Therefore we do need some mechanism which makes sure this cannot happen. Preferebly that would happen at compile time with static code analysis. Alternatively your Kasan implementation would be helpful to find something like that after-the-fact.
So it appears the only solution ;(
Quoted 1 line
However in any case we need something to address this problem.
Thanks!
Alexander Gordeev
maintainerPATCH v2 6/6 s390/mm: Allow lazy MMU mode disabling
In reply to Alexander GordeevInroduce "lazy_mmu" kernel command line parameter
to allow disabling of the lazy MMU mode on boot.
--- arch/s390/mm/ipte_batch.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
arch/s390/mm/ipte_batch.c +22 -0
@@ -16,6 +16,22 @@ struct ipte_batch { }; static DEFINE_PER_CPU(struct ipte_batch, ipte_range); +static DEFINE_STATIC_KEY_TRUE_RO (lazy_mmu); + +static int __init setup_lazy_mmu (char *str) +{ +bool enable; + +if (kstrtobool (str, &enable)) { +pr_warn ("Failed to setup lazy MMU mode, set to enabled\n" ); + }else if (!enable) { +pr_warn ("Disabling lazy MMU mode\n" ); +static_key_disable (&lazy_mmu.key); + } + +return 0 ; +} +early_param ("lazy_mmu" , setup_lazy_mmu);static int count_contiguous (pte_t *start,pte_t *end,bool *valid) {
@@ -169,6 +185,8 @@ void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, {if (!test_facility (13 ))return ; +if (!static_branch_likely (&lazy_mmu)) +return ;enter_ipte_batch (mm, addr, end, pte); }EXPORT_SYMBOL_IF_KUNIT (arch_enter_lazy_mmu_mode_for_pte_range);
@@ -177,6 +195,8 @@ void arch_leave_lazy_mmu_mode(void) {if (!test_facility (13 ))return ; +if (!static_branch_likely (&lazy_mmu)) +return ;leave_ipte_batch (); }EXPORT_SYMBOL_IF_KUNIT (arch_leave_lazy_mmu_mode);
@@ -185,6 +205,8 @@ void arch_flush_lazy_mmu_mode(void) {if (!test_facility (13 ))return ; +if (!static_branch_likely (&lazy_mmu)) +return ;flush_lazy_mmu_mode (); }EXPORT_SYMBOL_IF_KUNIT (arch_flush_lazy_mmu_mode);
Signature · 1 line
2.51.0
Heiko Carstens
maintainerRe: [PATCH v2 6/6] s390/mm: Allow lazy MMU mode disabling
Quoted 7 lines · Alexander Gordeev
Inroduce "
lazy_mmu" kernel command line parameter
to allow disabling of the lazy MMU mode on boot.Signed-off-by Alexander Gordeev <[email protected]>--- arch/s390/mm/ipte_batch.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
...
Quoted 21 lines
static
DEFINE_PER_CPU(struct ipte_batch, ipte_range);arch/s390/mm/ipte_batch.c +16
-0+static DEFINE_STATIC_KEY_TRUE_RO (lazy_mmu); + +static int __init setup_lazy_mmu (char *str) +{ +bool enable; + +if (kstrtobool (str, &enable)) { +pr_warn ("Failed to setup lazy MMU mode, set to enabled\n" ); + }else if (!enable) { +pr_warn ("Disabling lazy MMU mode\n" ); +static_key_disable (&lazy_mmu.key); + } + +return 0 ; +} +early_param ("lazy_mmu" , setup_lazy_mmu);static int count_contiguous (pte_t *start,pte_t *end,bool *valid) {
arch/s390/mm/ipte_batch.c void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, · line 189
Quoted 5 lines
arch/s390/mm/ipte_batch.c +2
-0@@ -169,6 +185,8 @@ void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, {if (!test_facility (13 ))return ; +if (!static_branch_likely (&lazy_mmu)) +return ;
Looks like there is an alternative chicken switch available: remove facility bit 13 from the facility list via kernel command line option.
Unless I am mistaken: that should be sufficient and avoids adding more code.
Alexander Gordeev
maintainerRe: [PATCH v2 6/6] s390/mm: Allow lazy MMU mode disabling
Quoted 10 lines · Heiko Carstens
arch/s390/mm/ipte_batch.c void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, · line 189
Quoted 6 lines
arch/s390/mm/ipte_batch.c +2
-0@@ -169,6 +185,8 @@ void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, {if (!test_facility (13 ))return ; +if (!static_branch_likely (&lazy_mmu)) +return ;Looks like there is an alternative chicken switch available: remove facility bit 13 from the facility list via kernel command line option.
Quoted 2 lines
Unless I am mistaken: that should be sufficient and avoids adding more code.
But that would also withdraw multi-IPTE from other unrelated parts of the code, e.g. from __kernel_map_pages().
Christian Borntraeger
reviewerRe: [PATCH v2 6/6] s390/mm: Allow lazy MMU mode disabling
Am 16.04.26 um 09:00 schrieb Alexander Gordeev:
Quoted 11 lines · Heiko Carstens
Quoted 10 lines
arch/s390/mm/ipte_batch.c void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, · line 189
Quoted 6 lines
arch/s390/mm/ipte_batch.c +2
-0@@ -169,6 +185,8 @@ void arch_enter_lazy_mmu_mode_for_pte_range(struct mm_struct *mm, {if (!test_facility (13 ))return ; +if (!static_branch_likely (&lazy_mmu)) +return ;Looks like there is an alternative chicken switch available: remove facility bit 13 from the facility list via kernel command line option.
Quoted 5 lines
Quoted 2 lines
Unless I am mistaken: that should be sufficient and avoids adding more code.
But that would also withdraw multi-IPTE from other unrelated parts of the code, e.g. from
__kernel_map_pages().
Which is fine. Do not over-optimize for a case that should not be needed unless we really mess up. This parameter is way too complicated to ever be set/unset by a customer.