162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * IA-64 Huge TLB Page Support for Kernel. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2002-2004 Rohit Seth <rohit.seth@intel.com> 662306a36Sopenharmony_ci * Copyright (C) 2003-2004 Ken Chen <kenneth.w.chen@intel.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Sep, 2003: add numa support 962306a36Sopenharmony_ci * Feb, 2004: dynamic hugetlb page size via boot parameter 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/fs.h> 1462306a36Sopenharmony_ci#include <linux/mm.h> 1562306a36Sopenharmony_ci#include <linux/hugetlb.h> 1662306a36Sopenharmony_ci#include <linux/pagemap.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/sysctl.h> 1962306a36Sopenharmony_ci#include <linux/log2.h> 2062306a36Sopenharmony_ci#include <asm/mman.h> 2162306a36Sopenharmony_ci#include <asm/tlb.h> 2262306a36Sopenharmony_ci#include <asm/tlbflush.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ciunsigned int hpage_shift = HPAGE_SHIFT_DEFAULT; 2562306a36Sopenharmony_ciEXPORT_SYMBOL(hpage_shift); 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cipte_t * 2862306a36Sopenharmony_cihuge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma, 2962306a36Sopenharmony_ci unsigned long addr, unsigned long sz) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci unsigned long taddr = htlbpage_to_page(addr); 3262306a36Sopenharmony_ci pgd_t *pgd; 3362306a36Sopenharmony_ci p4d_t *p4d; 3462306a36Sopenharmony_ci pud_t *pud; 3562306a36Sopenharmony_ci pmd_t *pmd; 3662306a36Sopenharmony_ci pte_t *pte = NULL; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci pgd = pgd_offset(mm, taddr); 3962306a36Sopenharmony_ci p4d = p4d_offset(pgd, taddr); 4062306a36Sopenharmony_ci pud = pud_alloc(mm, p4d, taddr); 4162306a36Sopenharmony_ci if (pud) { 4262306a36Sopenharmony_ci pmd = pmd_alloc(mm, pud, taddr); 4362306a36Sopenharmony_ci if (pmd) 4462306a36Sopenharmony_ci pte = pte_alloc_huge(mm, pmd, taddr); 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci return pte; 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cipte_t * 5062306a36Sopenharmony_cihuge_pte_offset (struct mm_struct *mm, unsigned long addr, unsigned long sz) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci unsigned long taddr = htlbpage_to_page(addr); 5362306a36Sopenharmony_ci pgd_t *pgd; 5462306a36Sopenharmony_ci p4d_t *p4d; 5562306a36Sopenharmony_ci pud_t *pud; 5662306a36Sopenharmony_ci pmd_t *pmd; 5762306a36Sopenharmony_ci pte_t *pte = NULL; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci pgd = pgd_offset(mm, taddr); 6062306a36Sopenharmony_ci if (pgd_present(*pgd)) { 6162306a36Sopenharmony_ci p4d = p4d_offset(pgd, taddr); 6262306a36Sopenharmony_ci if (p4d_present(*p4d)) { 6362306a36Sopenharmony_ci pud = pud_offset(p4d, taddr); 6462306a36Sopenharmony_ci if (pud_present(*pud)) { 6562306a36Sopenharmony_ci pmd = pmd_offset(pud, taddr); 6662306a36Sopenharmony_ci if (pmd_present(*pmd)) 6762306a36Sopenharmony_ci pte = pte_offset_huge(pmd, taddr); 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return pte; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci#define mk_pte_huge(entry) { pte_val(entry) |= _PAGE_P; } 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci/* 7862306a36Sopenharmony_ci * Don't actually need to do any preparation, but need to make sure 7962306a36Sopenharmony_ci * the address is in the right region. 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ciint prepare_hugepage_range(struct file *file, 8262306a36Sopenharmony_ci unsigned long addr, unsigned long len) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci if (len & ~HPAGE_MASK) 8562306a36Sopenharmony_ci return -EINVAL; 8662306a36Sopenharmony_ci if (addr & ~HPAGE_MASK) 8762306a36Sopenharmony_ci return -EINVAL; 8862306a36Sopenharmony_ci if (REGION_NUMBER(addr) != RGN_HPAGE) 8962306a36Sopenharmony_ci return -EINVAL; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci return 0; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ciint pmd_huge(pmd_t pmd) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci return 0; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ciint pud_huge(pud_t pud) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci return 0; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_civoid hugetlb_free_pgd_range(struct mmu_gather *tlb, 10562306a36Sopenharmony_ci unsigned long addr, unsigned long end, 10662306a36Sopenharmony_ci unsigned long floor, unsigned long ceiling) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci /* 10962306a36Sopenharmony_ci * This is called to free hugetlb page tables. 11062306a36Sopenharmony_ci * 11162306a36Sopenharmony_ci * The offset of these addresses from the base of the hugetlb 11262306a36Sopenharmony_ci * region must be scaled down by HPAGE_SIZE/PAGE_SIZE so that 11362306a36Sopenharmony_ci * the standard free_pgd_range will free the right page tables. 11462306a36Sopenharmony_ci * 11562306a36Sopenharmony_ci * If floor and ceiling are also in the hugetlb region, they 11662306a36Sopenharmony_ci * must likewise be scaled down; but if outside, left unchanged. 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci addr = htlbpage_to_page(addr); 12062306a36Sopenharmony_ci end = htlbpage_to_page(end); 12162306a36Sopenharmony_ci if (REGION_NUMBER(floor) == RGN_HPAGE) 12262306a36Sopenharmony_ci floor = htlbpage_to_page(floor); 12362306a36Sopenharmony_ci if (REGION_NUMBER(ceiling) == RGN_HPAGE) 12462306a36Sopenharmony_ci ceiling = htlbpage_to_page(ceiling); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci free_pgd_range(tlb, addr, end, floor, ceiling); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ciunsigned long hugetlb_get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, 13062306a36Sopenharmony_ci unsigned long pgoff, unsigned long flags) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct vm_unmapped_area_info info; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (len > RGN_MAP_LIMIT) 13562306a36Sopenharmony_ci return -ENOMEM; 13662306a36Sopenharmony_ci if (len & ~HPAGE_MASK) 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Handle MAP_FIXED */ 14062306a36Sopenharmony_ci if (flags & MAP_FIXED) { 14162306a36Sopenharmony_ci if (prepare_hugepage_range(file, addr, len)) 14262306a36Sopenharmony_ci return -EINVAL; 14362306a36Sopenharmony_ci return addr; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* This code assumes that RGN_HPAGE != 0. */ 14762306a36Sopenharmony_ci if ((REGION_NUMBER(addr) != RGN_HPAGE) || (addr & (HPAGE_SIZE - 1))) 14862306a36Sopenharmony_ci addr = HPAGE_REGION_BASE; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci info.flags = 0; 15162306a36Sopenharmony_ci info.length = len; 15262306a36Sopenharmony_ci info.low_limit = addr; 15362306a36Sopenharmony_ci info.high_limit = HPAGE_REGION_BASE + RGN_MAP_LIMIT; 15462306a36Sopenharmony_ci info.align_mask = PAGE_MASK & (HPAGE_SIZE - 1); 15562306a36Sopenharmony_ci info.align_offset = 0; 15662306a36Sopenharmony_ci return vm_unmapped_area(&info); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic int __init hugetlb_setup_sz(char *str) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci u64 tr_pages; 16262306a36Sopenharmony_ci unsigned long long size; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (ia64_pal_vm_page_size(&tr_pages, NULL) != 0) 16562306a36Sopenharmony_ci /* 16662306a36Sopenharmony_ci * shouldn't happen, but just in case. 16762306a36Sopenharmony_ci */ 16862306a36Sopenharmony_ci tr_pages = 0x15557000UL; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci size = memparse(str, &str); 17162306a36Sopenharmony_ci if (*str || !is_power_of_2(size) || !(tr_pages & size) || 17262306a36Sopenharmony_ci size <= PAGE_SIZE || 17362306a36Sopenharmony_ci size > (1UL << PAGE_SHIFT << MAX_ORDER)) { 17462306a36Sopenharmony_ci printk(KERN_WARNING "Invalid huge page size specified\n"); 17562306a36Sopenharmony_ci return 1; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci hpage_shift = __ffs(size); 17962306a36Sopenharmony_ci /* 18062306a36Sopenharmony_ci * boot cpu already executed ia64_mmu_init, and has HPAGE_SHIFT_DEFAULT 18162306a36Sopenharmony_ci * override here with new page shift. 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_ci ia64_set_rr(HPAGE_REGION_BASE, hpage_shift << 2); 18462306a36Sopenharmony_ci return 0; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ciearly_param("hugepagesz", hugetlb_setup_sz); 187