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