162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* arch/sparc64/mm/tlb.c 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2004 David S. Miller <davem@redhat.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/percpu.h> 962306a36Sopenharmony_ci#include <linux/mm.h> 1062306a36Sopenharmony_ci#include <linux/swap.h> 1162306a36Sopenharmony_ci#include <linux/preempt.h> 1262306a36Sopenharmony_ci#include <linux/pagemap.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <asm/tlbflush.h> 1562306a36Sopenharmony_ci#include <asm/cacheflush.h> 1662306a36Sopenharmony_ci#include <asm/mmu_context.h> 1762306a36Sopenharmony_ci#include <asm/tlb.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Heavily inspired by the ppc64 code. */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct tlb_batch, tlb_batch); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_civoid flush_tlb_pending(void) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci struct tlb_batch *tb = &get_cpu_var(tlb_batch); 2662306a36Sopenharmony_ci struct mm_struct *mm = tb->mm; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci if (!tb->tlb_nr) 2962306a36Sopenharmony_ci goto out; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci flush_tsb_user(tb); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci if (CTX_VALID(mm->context)) { 3462306a36Sopenharmony_ci if (tb->tlb_nr == 1) { 3562306a36Sopenharmony_ci global_flush_tlb_page(mm, tb->vaddrs[0]); 3662306a36Sopenharmony_ci } else { 3762306a36Sopenharmony_ci#ifdef CONFIG_SMP 3862306a36Sopenharmony_ci smp_flush_tlb_pending(tb->mm, tb->tlb_nr, 3962306a36Sopenharmony_ci &tb->vaddrs[0]); 4062306a36Sopenharmony_ci#else 4162306a36Sopenharmony_ci __flush_tlb_pending(CTX_HWBITS(tb->mm->context), 4262306a36Sopenharmony_ci tb->tlb_nr, &tb->vaddrs[0]); 4362306a36Sopenharmony_ci#endif 4462306a36Sopenharmony_ci } 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci tb->tlb_nr = 0; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ciout: 5062306a36Sopenharmony_ci put_cpu_var(tlb_batch); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_civoid arch_enter_lazy_mmu_mode(void) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct tlb_batch *tb = this_cpu_ptr(&tlb_batch); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci tb->active = 1; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_civoid arch_leave_lazy_mmu_mode(void) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct tlb_batch *tb = this_cpu_ptr(&tlb_batch); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if (tb->tlb_nr) 6562306a36Sopenharmony_ci flush_tlb_pending(); 6662306a36Sopenharmony_ci tb->active = 0; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic void tlb_batch_add_one(struct mm_struct *mm, unsigned long vaddr, 7062306a36Sopenharmony_ci bool exec, unsigned int hugepage_shift) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct tlb_batch *tb = &get_cpu_var(tlb_batch); 7362306a36Sopenharmony_ci unsigned long nr; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci vaddr &= PAGE_MASK; 7662306a36Sopenharmony_ci if (exec) 7762306a36Sopenharmony_ci vaddr |= 0x1UL; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci nr = tb->tlb_nr; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (unlikely(nr != 0 && mm != tb->mm)) { 8262306a36Sopenharmony_ci flush_tlb_pending(); 8362306a36Sopenharmony_ci nr = 0; 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (!tb->active) { 8762306a36Sopenharmony_ci flush_tsb_user_page(mm, vaddr, hugepage_shift); 8862306a36Sopenharmony_ci global_flush_tlb_page(mm, vaddr); 8962306a36Sopenharmony_ci goto out; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (nr == 0) { 9362306a36Sopenharmony_ci tb->mm = mm; 9462306a36Sopenharmony_ci tb->hugepage_shift = hugepage_shift; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (tb->hugepage_shift != hugepage_shift) { 9862306a36Sopenharmony_ci flush_tlb_pending(); 9962306a36Sopenharmony_ci tb->hugepage_shift = hugepage_shift; 10062306a36Sopenharmony_ci nr = 0; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci tb->vaddrs[nr] = vaddr; 10462306a36Sopenharmony_ci tb->tlb_nr = ++nr; 10562306a36Sopenharmony_ci if (nr >= TLB_BATCH_NR) 10662306a36Sopenharmony_ci flush_tlb_pending(); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ciout: 10962306a36Sopenharmony_ci put_cpu_var(tlb_batch); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_civoid tlb_batch_add(struct mm_struct *mm, unsigned long vaddr, 11362306a36Sopenharmony_ci pte_t *ptep, pte_t orig, int fullmm, 11462306a36Sopenharmony_ci unsigned int hugepage_shift) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci if (tlb_type != hypervisor && 11762306a36Sopenharmony_ci pte_dirty(orig)) { 11862306a36Sopenharmony_ci unsigned long paddr, pfn = pte_pfn(orig); 11962306a36Sopenharmony_ci struct address_space *mapping; 12062306a36Sopenharmony_ci struct page *page; 12162306a36Sopenharmony_ci struct folio *folio; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (!pfn_valid(pfn)) 12462306a36Sopenharmony_ci goto no_cache_flush; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci page = pfn_to_page(pfn); 12762306a36Sopenharmony_ci if (PageReserved(page)) 12862306a36Sopenharmony_ci goto no_cache_flush; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci /* A real file page? */ 13162306a36Sopenharmony_ci folio = page_folio(page); 13262306a36Sopenharmony_ci mapping = folio_flush_mapping(folio); 13362306a36Sopenharmony_ci if (!mapping) 13462306a36Sopenharmony_ci goto no_cache_flush; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci paddr = (unsigned long) page_address(page); 13762306a36Sopenharmony_ci if ((paddr ^ vaddr) & (1 << 13)) 13862306a36Sopenharmony_ci flush_dcache_folio_all(mm, folio); 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cino_cache_flush: 14262306a36Sopenharmony_ci if (!fullmm) 14362306a36Sopenharmony_ci tlb_batch_add_one(mm, vaddr, pte_exec(orig), hugepage_shift); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci#ifdef CONFIG_TRANSPARENT_HUGEPAGE 14762306a36Sopenharmony_cistatic void tlb_batch_pmd_scan(struct mm_struct *mm, unsigned long vaddr, 14862306a36Sopenharmony_ci pmd_t pmd) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci unsigned long end; 15162306a36Sopenharmony_ci pte_t *pte; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci pte = pte_offset_map(&pmd, vaddr); 15462306a36Sopenharmony_ci if (!pte) 15562306a36Sopenharmony_ci return; 15662306a36Sopenharmony_ci end = vaddr + HPAGE_SIZE; 15762306a36Sopenharmony_ci while (vaddr < end) { 15862306a36Sopenharmony_ci if (pte_val(*pte) & _PAGE_VALID) { 15962306a36Sopenharmony_ci bool exec = pte_exec(*pte); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci tlb_batch_add_one(mm, vaddr, exec, PAGE_SHIFT); 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci pte++; 16462306a36Sopenharmony_ci vaddr += PAGE_SIZE; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci pte_unmap(pte); 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic void __set_pmd_acct(struct mm_struct *mm, unsigned long addr, 17162306a36Sopenharmony_ci pmd_t orig, pmd_t pmd) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci if (mm == &init_mm) 17462306a36Sopenharmony_ci return; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci if ((pmd_val(pmd) ^ pmd_val(orig)) & _PAGE_PMD_HUGE) { 17762306a36Sopenharmony_ci /* 17862306a36Sopenharmony_ci * Note that this routine only sets pmds for THP pages. 17962306a36Sopenharmony_ci * Hugetlb pages are handled elsewhere. We need to check 18062306a36Sopenharmony_ci * for huge zero page. Huge zero pages are like hugetlb 18162306a36Sopenharmony_ci * pages in that there is no RSS, but there is the need 18262306a36Sopenharmony_ci * for TSB entries. So, huge zero page counts go into 18362306a36Sopenharmony_ci * hugetlb_pte_count. 18462306a36Sopenharmony_ci */ 18562306a36Sopenharmony_ci if (pmd_val(pmd) & _PAGE_PMD_HUGE) { 18662306a36Sopenharmony_ci if (is_huge_zero_page(pmd_page(pmd))) 18762306a36Sopenharmony_ci mm->context.hugetlb_pte_count++; 18862306a36Sopenharmony_ci else 18962306a36Sopenharmony_ci mm->context.thp_pte_count++; 19062306a36Sopenharmony_ci } else { 19162306a36Sopenharmony_ci if (is_huge_zero_page(pmd_page(orig))) 19262306a36Sopenharmony_ci mm->context.hugetlb_pte_count--; 19362306a36Sopenharmony_ci else 19462306a36Sopenharmony_ci mm->context.thp_pte_count--; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* Do not try to allocate the TSB hash table if we 19862306a36Sopenharmony_ci * don't have one already. We have various locks held 19962306a36Sopenharmony_ci * and thus we'll end up doing a GFP_KERNEL allocation 20062306a36Sopenharmony_ci * in an atomic context. 20162306a36Sopenharmony_ci * 20262306a36Sopenharmony_ci * Instead, we let the first TLB miss on a hugepage 20362306a36Sopenharmony_ci * take care of this. 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (!pmd_none(orig)) { 20862306a36Sopenharmony_ci addr &= HPAGE_MASK; 20962306a36Sopenharmony_ci if (pmd_trans_huge(orig)) { 21062306a36Sopenharmony_ci pte_t orig_pte = __pte(pmd_val(orig)); 21162306a36Sopenharmony_ci bool exec = pte_exec(orig_pte); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci tlb_batch_add_one(mm, addr, exec, REAL_HPAGE_SHIFT); 21462306a36Sopenharmony_ci tlb_batch_add_one(mm, addr + REAL_HPAGE_SIZE, exec, 21562306a36Sopenharmony_ci REAL_HPAGE_SHIFT); 21662306a36Sopenharmony_ci } else { 21762306a36Sopenharmony_ci tlb_batch_pmd_scan(mm, addr, orig); 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_civoid set_pmd_at(struct mm_struct *mm, unsigned long addr, 22362306a36Sopenharmony_ci pmd_t *pmdp, pmd_t pmd) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci pmd_t orig = *pmdp; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci *pmdp = pmd; 22862306a36Sopenharmony_ci __set_pmd_acct(mm, addr, orig, pmd); 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic inline pmd_t pmdp_establish(struct vm_area_struct *vma, 23262306a36Sopenharmony_ci unsigned long address, pmd_t *pmdp, pmd_t pmd) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci pmd_t old; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci do { 23762306a36Sopenharmony_ci old = *pmdp; 23862306a36Sopenharmony_ci } while (cmpxchg64(&pmdp->pmd, old.pmd, pmd.pmd) != old.pmd); 23962306a36Sopenharmony_ci __set_pmd_acct(vma->vm_mm, address, old, pmd); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci return old; 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci/* 24562306a36Sopenharmony_ci * This routine is only called when splitting a THP 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_cipmd_t pmdp_invalidate(struct vm_area_struct *vma, unsigned long address, 24862306a36Sopenharmony_ci pmd_t *pmdp) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci pmd_t old, entry; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci entry = __pmd(pmd_val(*pmdp) & ~_PAGE_VALID); 25362306a36Sopenharmony_ci old = pmdp_establish(vma, address, pmdp, entry); 25462306a36Sopenharmony_ci flush_tlb_range(vma, address, address + HPAGE_PMD_SIZE); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* 25762306a36Sopenharmony_ci * set_pmd_at() will not be called in a way to decrement 25862306a36Sopenharmony_ci * thp_pte_count when splitting a THP, so do it now. 25962306a36Sopenharmony_ci * Sanity check pmd before doing the actual decrement. 26062306a36Sopenharmony_ci */ 26162306a36Sopenharmony_ci if ((pmd_val(entry) & _PAGE_PMD_HUGE) && 26262306a36Sopenharmony_ci !is_huge_zero_page(pmd_page(entry))) 26362306a36Sopenharmony_ci (vma->vm_mm)->context.thp_pte_count--; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci return old; 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_civoid pgtable_trans_huge_deposit(struct mm_struct *mm, pmd_t *pmdp, 26962306a36Sopenharmony_ci pgtable_t pgtable) 27062306a36Sopenharmony_ci{ 27162306a36Sopenharmony_ci struct list_head *lh = (struct list_head *) pgtable; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci assert_spin_locked(&mm->page_table_lock); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* FIFO */ 27662306a36Sopenharmony_ci if (!pmd_huge_pte(mm, pmdp)) 27762306a36Sopenharmony_ci INIT_LIST_HEAD(lh); 27862306a36Sopenharmony_ci else 27962306a36Sopenharmony_ci list_add(lh, (struct list_head *) pmd_huge_pte(mm, pmdp)); 28062306a36Sopenharmony_ci pmd_huge_pte(mm, pmdp) = pgtable; 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cipgtable_t pgtable_trans_huge_withdraw(struct mm_struct *mm, pmd_t *pmdp) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct list_head *lh; 28662306a36Sopenharmony_ci pgtable_t pgtable; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci assert_spin_locked(&mm->page_table_lock); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* FIFO */ 29162306a36Sopenharmony_ci pgtable = pmd_huge_pte(mm, pmdp); 29262306a36Sopenharmony_ci lh = (struct list_head *) pgtable; 29362306a36Sopenharmony_ci if (list_empty(lh)) 29462306a36Sopenharmony_ci pmd_huge_pte(mm, pmdp) = NULL; 29562306a36Sopenharmony_ci else { 29662306a36Sopenharmony_ci pmd_huge_pte(mm, pmdp) = (pgtable_t) lh->next; 29762306a36Sopenharmony_ci list_del(lh); 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci pte_val(pgtable[0]) = 0; 30062306a36Sopenharmony_ci pte_val(pgtable[1]) = 0; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci return pgtable; 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci#endif /* CONFIG_TRANSPARENT_HUGEPAGE */ 305