powerpc: implement the new page table range API

Add set_ptes(), update_mmu_cache_range() and flush_dcache_folio().  Change
the PG_arch_1 (aka PG_dcache_dirty) flag from being per-page to per-folio.

[willy@infradead.org: re-export flush_dcache_icache_folio()]
  Link: https://lkml.kernel.org/r/ZMx1daYwvD9EM7Cv@casper.infradead.org
Link: https://lkml.kernel.org/r/20230802151406.3735276-22-willy@infradead.org
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Acked-by: Mike Rapoport (IBM) <rppt@kernel.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Nicholas Piggin <npiggin@gmail.com>
Cc: Christophe Leroy <christophe.leroy@csgroup.eu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Matthew Wilcox (Oracle) 2023-08-02 16:13:49 +01:00 committed by Andrew Morton
parent e70bbca607
commit 9fee28baa6
11 changed files with 89 additions and 93 deletions

View File

@ -462,11 +462,6 @@ static inline pte_t pfn_pte(unsigned long pfn, pgprot_t pgprot)
pgprot_val(pgprot)); pgprot_val(pgprot));
} }
static inline unsigned long pte_pfn(pte_t pte)
{
return pte_val(pte) >> PTE_RPN_SHIFT;
}
/* Generic modifiers for PTE bits */ /* Generic modifiers for PTE bits */
static inline pte_t pte_wrprotect(pte_t pte) static inline pte_t pte_wrprotect(pte_t pte)
{ {

View File

@ -104,6 +104,7 @@
* and every thing below PAGE_SHIFT; * and every thing below PAGE_SHIFT;
*/ */
#define PTE_RPN_MASK (((1UL << _PAGE_PA_MAX) - 1) & (PAGE_MASK)) #define PTE_RPN_MASK (((1UL << _PAGE_PA_MAX) - 1) & (PAGE_MASK))
#define PTE_RPN_SHIFT PAGE_SHIFT
/* /*
* set of bits not changed in pmd_modify. Even though we have hash specific bits * set of bits not changed in pmd_modify. Even though we have hash specific bits
* in here, on radix we expect them to be zero. * in here, on radix we expect them to be zero.
@ -569,11 +570,6 @@ static inline pte_t pfn_pte(unsigned long pfn, pgprot_t pgprot)
return __pte(((pte_basic_t)pfn << PAGE_SHIFT) | pgprot_val(pgprot) | _PAGE_PTE); return __pte(((pte_basic_t)pfn << PAGE_SHIFT) | pgprot_val(pgprot) | _PAGE_PTE);
} }
static inline unsigned long pte_pfn(pte_t pte)
{
return (pte_val(pte) & PTE_RPN_MASK) >> PAGE_SHIFT;
}
/* Generic modifiers for PTE bits */ /* Generic modifiers for PTE bits */
static inline pte_t pte_wrprotect(pte_t pte) static inline pte_t pte_wrprotect(pte_t pte)
{ {

View File

@ -9,13 +9,6 @@
#endif #endif
#ifndef __ASSEMBLY__ #ifndef __ASSEMBLY__
/* Insert a PTE, top-level function is out of line. It uses an inline
* low level function in the respective pgtable-* files
*/
extern void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
pte_t pte);
#define __HAVE_ARCH_PTEP_SET_ACCESS_FLAGS #define __HAVE_ARCH_PTEP_SET_ACCESS_FLAGS
extern int ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address, extern int ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address,
pte_t *ptep, pte_t entry, int dirty); pte_t *ptep, pte_t entry, int dirty);
@ -36,7 +29,9 @@ void __update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t
* corresponding HPTE into the hash table ahead of time, instead of * corresponding HPTE into the hash table ahead of time, instead of
* waiting for the inevitable extra hash-table miss exception. * waiting for the inevitable extra hash-table miss exception.
*/ */
static inline void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) static inline void update_mmu_cache_range(struct vm_fault *vmf,
struct vm_area_struct *vma, unsigned long address,
pte_t *ptep, unsigned int nr)
{ {
if (IS_ENABLED(CONFIG_PPC32) && !mmu_has_feature(MMU_FTR_HPTE_TABLE)) if (IS_ENABLED(CONFIG_PPC32) && !mmu_has_feature(MMU_FTR_HPTE_TABLE))
return; return;

View File

@ -35,13 +35,19 @@ static inline void flush_cache_vmap(unsigned long start, unsigned long end)
* It just marks the page as not i-cache clean. We do the i-cache * It just marks the page as not i-cache clean. We do the i-cache
* flush later when the page is given to a user process, if necessary. * flush later when the page is given to a user process, if necessary.
*/ */
static inline void flush_dcache_page(struct page *page) static inline void flush_dcache_folio(struct folio *folio)
{ {
if (cpu_has_feature(CPU_FTR_COHERENT_ICACHE)) if (cpu_has_feature(CPU_FTR_COHERENT_ICACHE))
return; return;
/* avoid an atomic op if possible */ /* avoid an atomic op if possible */
if (test_bit(PG_dcache_clean, &page->flags)) if (test_bit(PG_dcache_clean, &folio->flags))
clear_bit(PG_dcache_clean, &page->flags); clear_bit(PG_dcache_clean, &folio->flags);
}
#define flush_dcache_folio flush_dcache_folio
static inline void flush_dcache_page(struct page *page)
{
flush_dcache_folio(page_folio(page));
} }
void flush_icache_range(unsigned long start, unsigned long stop); void flush_icache_range(unsigned long start, unsigned long stop);
@ -51,7 +57,7 @@ void flush_icache_user_page(struct vm_area_struct *vma, struct page *page,
unsigned long addr, int len); unsigned long addr, int len);
#define flush_icache_user_page flush_icache_user_page #define flush_icache_user_page flush_icache_user_page
void flush_dcache_icache_page(struct page *page); void flush_dcache_icache_folio(struct folio *folio);
/** /**
* flush_dcache_range(): Write any modified data cache blocks out to memory and * flush_dcache_range(): Write any modified data cache blocks out to memory and

View File

@ -894,7 +894,7 @@ void kvmppc_init_lpid(unsigned long nr_lpids);
static inline void kvmppc_mmu_flush_icache(kvm_pfn_t pfn) static inline void kvmppc_mmu_flush_icache(kvm_pfn_t pfn)
{ {
struct page *page; struct folio *folio;
/* /*
* We can only access pages that the kernel maps * We can only access pages that the kernel maps
* as memory. Bail out for unmapped ones. * as memory. Bail out for unmapped ones.
@ -903,10 +903,10 @@ static inline void kvmppc_mmu_flush_icache(kvm_pfn_t pfn)
return; return;
/* Clear i-cache for new pages */ /* Clear i-cache for new pages */
page = pfn_to_page(pfn); folio = page_folio(pfn_to_page(pfn));
if (!test_bit(PG_dcache_clean, &page->flags)) { if (!test_bit(PG_dcache_clean, &folio->flags)) {
flush_dcache_icache_page(page); flush_dcache_icache_folio(folio);
set_bit(PG_dcache_clean, &page->flags); set_bit(PG_dcache_clean, &folio->flags);
} }
} }

View File

@ -101,8 +101,6 @@ static inline bool pte_access_permitted(pte_t pte, bool write)
static inline pte_t pfn_pte(unsigned long pfn, pgprot_t pgprot) { static inline pte_t pfn_pte(unsigned long pfn, pgprot_t pgprot) {
return __pte(((pte_basic_t)(pfn) << PTE_RPN_SHIFT) | return __pte(((pte_basic_t)(pfn) << PTE_RPN_SHIFT) |
pgprot_val(pgprot)); } pgprot_val(pgprot)); }
static inline unsigned long pte_pfn(pte_t pte) {
return pte_val(pte) >> PTE_RPN_SHIFT; }
/* Generic modifiers for PTE bits */ /* Generic modifiers for PTE bits */
static inline pte_t pte_exprotect(pte_t pte) static inline pte_t pte_exprotect(pte_t pte)
@ -166,12 +164,6 @@ static inline pte_t pte_swp_clear_exclusive(pte_t pte)
return __pte(pte_val(pte) & ~_PAGE_SWP_EXCLUSIVE); return __pte(pte_val(pte) & ~_PAGE_SWP_EXCLUSIVE);
} }
/* Insert a PTE, top-level function is out of line. It uses an inline
* low level function in the respective pgtable-* files
*/
extern void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
pte_t pte);
/* This low level function performs the actual PTE insertion /* This low level function performs the actual PTE insertion
* Setting the PTE depends on the MMU type and other factors. It's * Setting the PTE depends on the MMU type and other factors. It's
* an horrible mess that I'm not going to try to clean up now but * an horrible mess that I'm not going to try to clean up now but
@ -282,10 +274,12 @@ static inline int pud_huge(pud_t pud)
* for the page which has just been mapped in. * for the page which has just been mapped in.
*/ */
#if defined(CONFIG_PPC_E500) && defined(CONFIG_HUGETLB_PAGE) #if defined(CONFIG_PPC_E500) && defined(CONFIG_HUGETLB_PAGE)
void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep); void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,
unsigned long address, pte_t *ptep, unsigned int nr);
#else #else
static inline static inline void update_mmu_cache_range(struct vm_fault *vmf,
void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) {} struct vm_area_struct *vma, unsigned long address,
pte_t *ptep, unsigned int nr) {}
#endif #endif
#endif /* __ASSEMBLY__ */ #endif /* __ASSEMBLY__ */

View File

@ -41,6 +41,12 @@ struct mm_struct;
#ifndef __ASSEMBLY__ #ifndef __ASSEMBLY__
void set_ptes(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
pte_t pte, unsigned int nr);
#define set_ptes set_ptes
#define update_mmu_cache(vma, addr, ptep) \
update_mmu_cache_range(NULL, vma, addr, ptep, 1)
#ifndef MAX_PTRS_PER_PGD #ifndef MAX_PTRS_PER_PGD
#define MAX_PTRS_PER_PGD PTRS_PER_PGD #define MAX_PTRS_PER_PGD PTRS_PER_PGD
#endif #endif
@ -48,6 +54,12 @@ struct mm_struct;
/* Keep these as a macros to avoid include dependency mess */ /* Keep these as a macros to avoid include dependency mess */
#define pte_page(x) pfn_to_page(pte_pfn(x)) #define pte_page(x) pfn_to_page(pte_pfn(x))
#define mk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot)) #define mk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))
static inline unsigned long pte_pfn(pte_t pte)
{
return (pte_val(pte) & PTE_RPN_MASK) >> PTE_RPN_SHIFT;
}
/* /*
* Select all bits except the pfn * Select all bits except the pfn
*/ */

View File

@ -1307,18 +1307,19 @@ void hash__early_init_mmu_secondary(void)
*/ */
unsigned int hash_page_do_lazy_icache(unsigned int pp, pte_t pte, int trap) unsigned int hash_page_do_lazy_icache(unsigned int pp, pte_t pte, int trap)
{ {
struct page *page; struct folio *folio;
if (!pfn_valid(pte_pfn(pte))) if (!pfn_valid(pte_pfn(pte)))
return pp; return pp;
page = pte_page(pte); folio = page_folio(pte_page(pte));
/* page is dirty */ /* page is dirty */
if (!test_bit(PG_dcache_clean, &page->flags) && !PageReserved(page)) { if (!test_bit(PG_dcache_clean, &folio->flags) &&
!folio_test_reserved(folio)) {
if (trap == INTERRUPT_INST_STORAGE) { if (trap == INTERRUPT_INST_STORAGE) {
flush_dcache_icache_page(page); flush_dcache_icache_folio(folio);
set_bit(PG_dcache_clean, &page->flags); set_bit(PG_dcache_clean, &folio->flags);
} else } else
pp |= HPTE_R_N; pp |= HPTE_R_N;
} }

View File

@ -148,44 +148,31 @@ static void __flush_dcache_icache(void *p)
invalidate_icache_range(addr, addr + PAGE_SIZE); invalidate_icache_range(addr, addr + PAGE_SIZE);
} }
static void flush_dcache_icache_hugepage(struct page *page) void flush_dcache_icache_folio(struct folio *folio)
{ {
int i; unsigned int i, nr = folio_nr_pages(folio);
int nr = compound_nr(page);
if (!PageHighMem(page)) {
for (i = 0; i < nr; i++)
__flush_dcache_icache(lowmem_page_address(page + i));
} else {
for (i = 0; i < nr; i++) {
void *start = kmap_local_page(page + i);
__flush_dcache_icache(start);
kunmap_local(start);
}
}
}
void flush_dcache_icache_page(struct page *page)
{
if (flush_coherent_icache()) if (flush_coherent_icache())
return; return;
if (PageCompound(page)) if (!folio_test_highmem(folio)) {
return flush_dcache_icache_hugepage(page); void *addr = folio_address(folio);
for (i = 0; i < nr; i++)
if (!PageHighMem(page)) { __flush_dcache_icache(addr + i * PAGE_SIZE);
__flush_dcache_icache(lowmem_page_address(page));
} else if (IS_ENABLED(CONFIG_BOOKE) || sizeof(phys_addr_t) > sizeof(void *)) { } else if (IS_ENABLED(CONFIG_BOOKE) || sizeof(phys_addr_t) > sizeof(void *)) {
void *start = kmap_local_page(page); for (i = 0; i < nr; i++) {
void *start = kmap_local_folio(folio, i * PAGE_SIZE);
__flush_dcache_icache(start); __flush_dcache_icache(start);
kunmap_local(start); kunmap_local(start);
}
} else { } else {
flush_dcache_icache_phys(page_to_phys(page)); unsigned long pfn = folio_pfn(folio);
for (i = 0; i < nr; i++)
flush_dcache_icache_phys((pfn + i) * PAGE_SIZE);
} }
} }
EXPORT_SYMBOL(flush_dcache_icache_page); EXPORT_SYMBOL(flush_dcache_icache_folio);
void clear_user_page(void *page, unsigned long vaddr, struct page *pg) void clear_user_page(void *page, unsigned long vaddr, struct page *pg)
{ {

View File

@ -178,7 +178,8 @@ book3e_hugetlb_preload(struct vm_area_struct *vma, unsigned long ea, pte_t pte)
* *
* This must always be called with the pte lock held. * This must always be called with the pte lock held.
*/ */
void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,
unsigned long address, pte_t *ptep, unsigned int nr)
{ {
if (is_vm_hugetlb_page(vma)) if (is_vm_hugetlb_page(vma))
book3e_hugetlb_preload(vma, address, *ptep); book3e_hugetlb_preload(vma, address, *ptep);

View File

@ -58,7 +58,7 @@ static inline int pte_looks_normal(pte_t pte)
return 0; return 0;
} }
static struct page *maybe_pte_to_page(pte_t pte) static struct folio *maybe_pte_to_folio(pte_t pte)
{ {
unsigned long pfn = pte_pfn(pte); unsigned long pfn = pte_pfn(pte);
struct page *page; struct page *page;
@ -68,7 +68,7 @@ static struct page *maybe_pte_to_page(pte_t pte)
page = pfn_to_page(pfn); page = pfn_to_page(pfn);
if (PageReserved(page)) if (PageReserved(page))
return NULL; return NULL;
return page; return page_folio(page);
} }
#ifdef CONFIG_PPC_BOOK3S #ifdef CONFIG_PPC_BOOK3S
@ -84,12 +84,12 @@ static pte_t set_pte_filter_hash(pte_t pte)
pte = __pte(pte_val(pte) & ~_PAGE_HPTEFLAGS); pte = __pte(pte_val(pte) & ~_PAGE_HPTEFLAGS);
if (pte_looks_normal(pte) && !(cpu_has_feature(CPU_FTR_COHERENT_ICACHE) || if (pte_looks_normal(pte) && !(cpu_has_feature(CPU_FTR_COHERENT_ICACHE) ||
cpu_has_feature(CPU_FTR_NOEXECUTE))) { cpu_has_feature(CPU_FTR_NOEXECUTE))) {
struct page *pg = maybe_pte_to_page(pte); struct folio *folio = maybe_pte_to_folio(pte);
if (!pg) if (!folio)
return pte; return pte;
if (!test_bit(PG_dcache_clean, &pg->flags)) { if (!test_bit(PG_dcache_clean, &folio->flags)) {
flush_dcache_icache_page(pg); flush_dcache_icache_folio(folio);
set_bit(PG_dcache_clean, &pg->flags); set_bit(PG_dcache_clean, &folio->flags);
} }
} }
return pte; return pte;
@ -107,7 +107,7 @@ static pte_t set_pte_filter_hash(pte_t pte) { return pte; }
*/ */
static inline pte_t set_pte_filter(pte_t pte) static inline pte_t set_pte_filter(pte_t pte)
{ {
struct page *pg; struct folio *folio;
if (radix_enabled()) if (radix_enabled())
return pte; return pte;
@ -120,18 +120,18 @@ static inline pte_t set_pte_filter(pte_t pte)
return pte; return pte;
/* If you set _PAGE_EXEC on weird pages you're on your own */ /* If you set _PAGE_EXEC on weird pages you're on your own */
pg = maybe_pte_to_page(pte); folio = maybe_pte_to_folio(pte);
if (unlikely(!pg)) if (unlikely(!folio))
return pte; return pte;
/* If the page clean, we move on */ /* If the page clean, we move on */
if (test_bit(PG_dcache_clean, &pg->flags)) if (test_bit(PG_dcache_clean, &folio->flags))
return pte; return pte;
/* If it's an exec fault, we flush the cache and make it clean */ /* If it's an exec fault, we flush the cache and make it clean */
if (is_exec_fault()) { if (is_exec_fault()) {
flush_dcache_icache_page(pg); flush_dcache_icache_folio(folio);
set_bit(PG_dcache_clean, &pg->flags); set_bit(PG_dcache_clean, &folio->flags);
return pte; return pte;
} }
@ -142,7 +142,7 @@ static inline pte_t set_pte_filter(pte_t pte)
static pte_t set_access_flags_filter(pte_t pte, struct vm_area_struct *vma, static pte_t set_access_flags_filter(pte_t pte, struct vm_area_struct *vma,
int dirty) int dirty)
{ {
struct page *pg; struct folio *folio;
if (IS_ENABLED(CONFIG_PPC_BOOK3S_64)) if (IS_ENABLED(CONFIG_PPC_BOOK3S_64))
return pte; return pte;
@ -168,17 +168,17 @@ static pte_t set_access_flags_filter(pte_t pte, struct vm_area_struct *vma,
#endif /* CONFIG_DEBUG_VM */ #endif /* CONFIG_DEBUG_VM */
/* If you set _PAGE_EXEC on weird pages you're on your own */ /* If you set _PAGE_EXEC on weird pages you're on your own */
pg = maybe_pte_to_page(pte); folio = maybe_pte_to_folio(pte);
if (unlikely(!pg)) if (unlikely(!folio))
goto bail; goto bail;
/* If the page is already clean, we move on */ /* If the page is already clean, we move on */
if (test_bit(PG_dcache_clean, &pg->flags)) if (test_bit(PG_dcache_clean, &folio->flags))
goto bail; goto bail;
/* Clean the page and set PG_dcache_clean */ /* Clean the page and set PG_dcache_clean */
flush_dcache_icache_page(pg); flush_dcache_icache_folio(folio);
set_bit(PG_dcache_clean, &pg->flags); set_bit(PG_dcache_clean, &folio->flags);
bail: bail:
return pte_mkexec(pte); return pte_mkexec(pte);
@ -187,8 +187,8 @@ static pte_t set_access_flags_filter(pte_t pte, struct vm_area_struct *vma,
/* /*
* set_pte stores a linux PTE into the linux page table. * set_pte stores a linux PTE into the linux page table.
*/ */
void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep, void set_ptes(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
pte_t pte) pte_t pte, unsigned int nr)
{ {
/* /*
* Make sure hardware valid bit is not set. We don't do * Make sure hardware valid bit is not set. We don't do
@ -203,7 +203,16 @@ void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
pte = set_pte_filter(pte); pte = set_pte_filter(pte);
/* Perform the setting of the PTE */ /* Perform the setting of the PTE */
arch_enter_lazy_mmu_mode();
for (;;) {
__set_pte_at(mm, addr, ptep, pte, 0); __set_pte_at(mm, addr, ptep, pte, 0);
if (--nr == 0)
break;
ptep++;
pte = __pte(pte_val(pte) + (1UL << PTE_RPN_SHIFT));
addr += PAGE_SIZE;
}
arch_leave_lazy_mmu_mode();
} }
void unmap_kernel_page(unsigned long va) void unmap_kernel_page(unsigned long va)