162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PARISC64 Huge TLB page support. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * This parisc implementation is heavily based on the SPARC and x86 code. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (C) 2015 Helge Deller <deller@gmx.de> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/fs.h> 1162306a36Sopenharmony_ci#include <linux/mm.h> 1262306a36Sopenharmony_ci#include <linux/sched/mm.h> 1362306a36Sopenharmony_ci#include <linux/hugetlb.h> 1462306a36Sopenharmony_ci#include <linux/pagemap.h> 1562306a36Sopenharmony_ci#include <linux/sysctl.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <asm/mman.h> 1862306a36Sopenharmony_ci#include <asm/tlb.h> 1962306a36Sopenharmony_ci#include <asm/tlbflush.h> 2062306a36Sopenharmony_ci#include <asm/cacheflush.h> 2162306a36Sopenharmony_ci#include <asm/mmu_context.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ciunsigned long 2562306a36Sopenharmony_cihugetlb_get_unmapped_area(struct file *file, unsigned long addr, 2662306a36Sopenharmony_ci unsigned long len, unsigned long pgoff, unsigned long flags) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci struct hstate *h = hstate_file(file); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci if (len & ~huge_page_mask(h)) 3162306a36Sopenharmony_ci return -EINVAL; 3262306a36Sopenharmony_ci if (len > TASK_SIZE) 3362306a36Sopenharmony_ci return -ENOMEM; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci if (flags & MAP_FIXED) 3662306a36Sopenharmony_ci if (prepare_hugepage_range(file, addr, len)) 3762306a36Sopenharmony_ci return -EINVAL; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci if (addr) 4062306a36Sopenharmony_ci addr = ALIGN(addr, huge_page_size(h)); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci /* we need to make sure the colouring is OK */ 4362306a36Sopenharmony_ci return arch_get_unmapped_area(file, addr, len, pgoff, flags); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cipte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma, 4862306a36Sopenharmony_ci unsigned long addr, unsigned long sz) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci pgd_t *pgd; 5162306a36Sopenharmony_ci p4d_t *p4d; 5262306a36Sopenharmony_ci pud_t *pud; 5362306a36Sopenharmony_ci pmd_t *pmd; 5462306a36Sopenharmony_ci pte_t *pte = NULL; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci /* We must align the address, because our caller will run 5762306a36Sopenharmony_ci * set_huge_pte_at() on whatever we return, which writes out 5862306a36Sopenharmony_ci * all of the sub-ptes for the hugepage range. So we have 5962306a36Sopenharmony_ci * to give it the first such sub-pte. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_ci addr &= HPAGE_MASK; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci pgd = pgd_offset(mm, addr); 6462306a36Sopenharmony_ci p4d = p4d_offset(pgd, addr); 6562306a36Sopenharmony_ci pud = pud_alloc(mm, p4d, addr); 6662306a36Sopenharmony_ci if (pud) { 6762306a36Sopenharmony_ci pmd = pmd_alloc(mm, pud, addr); 6862306a36Sopenharmony_ci if (pmd) 6962306a36Sopenharmony_ci pte = pte_alloc_huge(mm, pmd, addr); 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci return pte; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cipte_t *huge_pte_offset(struct mm_struct *mm, 7562306a36Sopenharmony_ci unsigned long addr, unsigned long sz) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci pgd_t *pgd; 7862306a36Sopenharmony_ci p4d_t *p4d; 7962306a36Sopenharmony_ci pud_t *pud; 8062306a36Sopenharmony_ci pmd_t *pmd; 8162306a36Sopenharmony_ci pte_t *pte = NULL; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci addr &= HPAGE_MASK; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci pgd = pgd_offset(mm, addr); 8662306a36Sopenharmony_ci if (!pgd_none(*pgd)) { 8762306a36Sopenharmony_ci p4d = p4d_offset(pgd, addr); 8862306a36Sopenharmony_ci if (!p4d_none(*p4d)) { 8962306a36Sopenharmony_ci pud = pud_offset(p4d, addr); 9062306a36Sopenharmony_ci if (!pud_none(*pud)) { 9162306a36Sopenharmony_ci pmd = pmd_offset(pud, addr); 9262306a36Sopenharmony_ci if (!pmd_none(*pmd)) 9362306a36Sopenharmony_ci pte = pte_offset_huge(pmd, addr); 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci return pte; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* Purge data and instruction TLB entries. Must be called holding 10162306a36Sopenharmony_ci * the pa_tlb_lock. The TLB purge instructions are slow on SMP 10262306a36Sopenharmony_ci * machines since the purge must be broadcast to all CPUs. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_cistatic inline void purge_tlb_entries_huge(struct mm_struct *mm, unsigned long addr) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci int i; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci /* We may use multiple physical huge pages (e.g. 2x1 MB) to emulate 10962306a36Sopenharmony_ci * Linux standard huge pages (e.g. 2 MB) */ 11062306a36Sopenharmony_ci BUILD_BUG_ON(REAL_HPAGE_SHIFT > HPAGE_SHIFT); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci addr &= HPAGE_MASK; 11362306a36Sopenharmony_ci addr |= _HUGE_PAGE_SIZE_ENCODING_DEFAULT; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci for (i = 0; i < (1 << (HPAGE_SHIFT-REAL_HPAGE_SHIFT)); i++) { 11662306a36Sopenharmony_ci purge_tlb_entries(mm, addr); 11762306a36Sopenharmony_ci addr += (1UL << REAL_HPAGE_SHIFT); 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/* __set_huge_pte_at() must be called holding the pa_tlb_lock. */ 12262306a36Sopenharmony_cistatic void __set_huge_pte_at(struct mm_struct *mm, unsigned long addr, 12362306a36Sopenharmony_ci pte_t *ptep, pte_t entry) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci unsigned long addr_start; 12662306a36Sopenharmony_ci int i; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci addr &= HPAGE_MASK; 12962306a36Sopenharmony_ci addr_start = addr; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci for (i = 0; i < (1 << HUGETLB_PAGE_ORDER); i++) { 13262306a36Sopenharmony_ci set_pte(ptep, entry); 13362306a36Sopenharmony_ci ptep++; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci addr += PAGE_SIZE; 13662306a36Sopenharmony_ci pte_val(entry) += PAGE_SIZE; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci purge_tlb_entries_huge(mm, addr_start); 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_civoid set_huge_pte_at(struct mm_struct *mm, unsigned long addr, 14362306a36Sopenharmony_ci pte_t *ptep, pte_t entry, unsigned long sz) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci __set_huge_pte_at(mm, addr, ptep, entry); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cipte_t huge_ptep_get_and_clear(struct mm_struct *mm, unsigned long addr, 15062306a36Sopenharmony_ci pte_t *ptep) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci pte_t entry; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci entry = *ptep; 15562306a36Sopenharmony_ci __set_huge_pte_at(mm, addr, ptep, __pte(0)); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return entry; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_civoid huge_ptep_set_wrprotect(struct mm_struct *mm, 16262306a36Sopenharmony_ci unsigned long addr, pte_t *ptep) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci pte_t old_pte; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci old_pte = *ptep; 16762306a36Sopenharmony_ci __set_huge_pte_at(mm, addr, ptep, pte_wrprotect(old_pte)); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ciint huge_ptep_set_access_flags(struct vm_area_struct *vma, 17162306a36Sopenharmony_ci unsigned long addr, pte_t *ptep, 17262306a36Sopenharmony_ci pte_t pte, int dirty) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci int changed; 17562306a36Sopenharmony_ci struct mm_struct *mm = vma->vm_mm; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci changed = !pte_same(*ptep, pte); 17862306a36Sopenharmony_ci if (changed) { 17962306a36Sopenharmony_ci __set_huge_pte_at(mm, addr, ptep, pte); 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci return changed; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciint pmd_huge(pmd_t pmd) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ciint pud_huge(pud_t pud) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci return 0; 19362306a36Sopenharmony_ci} 194