Re: [PATCH] mm: mmu_gather: remove __tlb_reset_range() for force flush

From: Peter Zijlstra
Date: Thu May 09 2019 - 15:57:45 EST


On Thu, May 09, 2019 at 12:38:13PM +0200, Peter Zijlstra wrote:

> diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
> index 99740e1dd273..fe768f8d612e 100644
> --- a/mm/mmu_gather.c
> +++ b/mm/mmu_gather.c
> @@ -244,15 +244,20 @@ void tlb_finish_mmu(struct mmu_gather *tlb,
> unsigned long start, unsigned long end)
> {
> /*
> - * If there are parallel threads are doing PTE changes on same range
> - * under non-exclusive lock(e.g., mmap_sem read-side) but defer TLB
> - * flush by batching, a thread has stable TLB entry can fail to flush
> - * the TLB by observing pte_none|!pte_dirty, for example so flush TLB
> - * forcefully if we detect parallel PTE batching threads.
> + * Sensible comment goes here..
> */
> - if (mm_tlb_flush_nested(tlb->mm)) {
> - __tlb_reset_range(tlb);
> - __tlb_adjust_range(tlb, start, end - start);
> + if (mm_tlb_flush_nested(tlb->mm) && !tlb->full_mm) {
> + /*
> + * Since we're can't tell what we actually should have
> + * flushed flush everything in the given range.
> + */
> + tlb->start = start;
> + tlb->end = end;
> + tlb->freed_tables = 1;
> + tlb->cleared_ptes = 1;
> + tlb->cleared_pmds = 1;
> + tlb->cleared_puds = 1;
> + tlb->cleared_p4ds = 1;
> }
>
> tlb_flush_mmu(tlb);

So PPC-radix has page-size dependent TLBI, but the above doesn't work
for them, because they use the tlb_change_page_size() interface and
don't look at tlb->cleared_p*().

Concequently, they have their own special magic :/

Nick, how about you use the tlb_change_page_size() interface to
find/flush on the page-size boundaries, but otherwise use the
tlb->cleared_p* flags to select which actual sizes to flush?

AFAICT that should work just fine for you guys. Maybe something like so?

(fwiw, there's an aweful lot of almost identical functions there)

---

diff --git a/arch/powerpc/mm/tlb-radix.c b/arch/powerpc/mm/tlb-radix.c
index 6a23b9ebd2a1..efc99ef78db6 100644
--- a/arch/powerpc/mm/tlb-radix.c
+++ b/arch/powerpc/mm/tlb-radix.c
@@ -692,7 +692,7 @@ static unsigned long tlb_local_single_page_flush_ceiling __read_mostly = POWER9_

static inline void __radix__flush_tlb_range(struct mm_struct *mm,
unsigned long start, unsigned long end,
- bool flush_all_sizes)
+ bool pflush, bool hflush, bool gflush)

{
unsigned long pid;
@@ -734,14 +734,9 @@ static inline void __radix__flush_tlb_range(struct mm_struct *mm,
_tlbie_pid(pid, RIC_FLUSH_TLB);
}
} else {
- bool hflush = flush_all_sizes;
- bool gflush = flush_all_sizes;
unsigned long hstart, hend;
unsigned long gstart, gend;

- if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
- hflush = true;
-
if (hflush) {
hstart = (start + PMD_SIZE - 1) & PMD_MASK;
hend = end & PMD_MASK;
@@ -758,7 +753,9 @@ static inline void __radix__flush_tlb_range(struct mm_struct *mm,

asm volatile("ptesync": : :"memory");
if (local) {
- __tlbiel_va_range(start, end, pid, page_size, mmu_virtual_psize);
+ if (pflush)
+ __tlbiel_va_range(start, end, pid,
+ page_size, mmu_virtual_psize);
if (hflush)
__tlbiel_va_range(hstart, hend, pid,
PMD_SIZE, MMU_PAGE_2M);
@@ -767,7 +764,9 @@ static inline void __radix__flush_tlb_range(struct mm_struct *mm,
PUD_SIZE, MMU_PAGE_1G);
asm volatile("ptesync": : :"memory");
} else {
- __tlbie_va_range(start, end, pid, page_size, mmu_virtual_psize);
+ if (pflush)
+ __tlbie_va_range(start, end, pid,
+ page_size, mmu_virtual_psize);
if (hflush)
__tlbie_va_range(hstart, hend, pid,
PMD_SIZE, MMU_PAGE_2M);
@@ -785,12 +784,17 @@ void radix__flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end)

{
+ bool hflush = false;
+
#ifdef CONFIG_HUGETLB_PAGE
if (is_vm_hugetlb_page(vma))
return radix__flush_hugetlb_tlb_range(vma, start, end);
#endif

- __radix__flush_tlb_range(vma->vm_mm, start, end, false);
+ if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+ hflush = true;
+
+ __radix__flush_tlb_range(vma->vm_mm, start, end, true, hflush, false);
}
EXPORT_SYMBOL(radix__flush_tlb_range);

@@ -881,49 +885,14 @@ void radix__tlb_flush(struct mmu_gather *tlb)
*/
if (tlb->fullmm) {
__flush_all_mm(mm, true);
-#if defined(CONFIG_TRANSPARENT_HUGEPAGE) || defined(CONFIG_HUGETLB_PAGE)
- } else if (mm_tlb_flush_nested(mm)) {
- /*
- * If there is a concurrent invalidation that is clearing ptes,
- * then it's possible this invalidation will miss one of those
- * cleared ptes and miss flushing the TLB. If this invalidate
- * returns before the other one flushes TLBs, that can result
- * in it returning while there are still valid TLBs inside the
- * range to be invalidated.
- *
- * See mm/memory.c:tlb_finish_mmu() for more details.
- *
- * The solution to this is ensure the entire range is always
- * flushed here. The problem for powerpc is that the flushes
- * are page size specific, so this "forced flush" would not
- * do the right thing if there are a mix of page sizes in
- * the range to be invalidated. So use __flush_tlb_range
- * which invalidates all possible page sizes in the range.
- *
- * PWC flush probably is not be required because the core code
- * shouldn't free page tables in this path, but accounting
- * for the possibility makes us a bit more robust.
- *
- * need_flush_all is an uncommon case because page table
- * teardown should be done with exclusive locks held (but
- * after locks are dropped another invalidate could come
- * in), it could be optimized further if necessary.
- */
- if (!tlb->need_flush_all)
- __radix__flush_tlb_range(mm, start, end, true);
- else
- radix__flush_all_mm(mm);
-#endif
- } else if ( (psize = radix_get_mmu_psize(page_size)) == -1) {
- if (!tlb->need_flush_all)
- radix__flush_tlb_mm(mm);
- else
- radix__flush_all_mm(mm);
} else {
if (!tlb->need_flush_all)
- radix__flush_tlb_range_psize(mm, start, end, psize);
+ __radix__flush_tlb_range(mm, start, end,
+ tlb->cleared_pte,
+ tlb->cleared_pmd,
+ tlb->cleared_pud);
else
- radix__flush_tlb_pwc_range_psize(mm, start, end, psize);
+ radix__flush_all_mm(mm);
}
tlb->need_flush_all = 0;
}