xref: /openbmc/linux/arch/powerpc/mm/book3s64/hugetlbpage.c (revision d2c5231581d636af8d5af888ee13048dfbb438c7)
1f43d2ffbSNicholas Piggin // SPDX-License-Identifier: GPL-2.0
2f43d2ffbSNicholas Piggin /*
3f43d2ffbSNicholas Piggin  * PPC64 Huge TLB Page Support for hash based MMUs (POWER4 and later)
4f43d2ffbSNicholas Piggin  *
5f43d2ffbSNicholas Piggin  * Copyright (C) 2003 David Gibson, IBM Corporation.
6f43d2ffbSNicholas Piggin  *
7f43d2ffbSNicholas Piggin  * Based on the IA-32 version:
8f43d2ffbSNicholas Piggin  * Copyright (C) 2002, Rohit Seth <rohit.seth@intel.com>
9f43d2ffbSNicholas Piggin  */
10f43d2ffbSNicholas Piggin 
11f43d2ffbSNicholas Piggin #include <linux/mm.h>
12f43d2ffbSNicholas Piggin #include <linux/hugetlb.h>
13f43d2ffbSNicholas Piggin #include <asm/cacheflush.h>
14f43d2ffbSNicholas Piggin #include <asm/machdep.h>
15f43d2ffbSNicholas Piggin 
16f43d2ffbSNicholas Piggin unsigned int hpage_shift;
17f43d2ffbSNicholas Piggin EXPORT_SYMBOL(hpage_shift);
18f43d2ffbSNicholas Piggin 
19387e220aSNicholas Piggin #ifdef CONFIG_PPC_64S_HASH_MMU
__hash_page_huge(unsigned long ea,unsigned long access,unsigned long vsid,pte_t * ptep,unsigned long trap,unsigned long flags,int ssize,unsigned int shift,unsigned int mmu_psize)20f43d2ffbSNicholas Piggin int __hash_page_huge(unsigned long ea, unsigned long access, unsigned long vsid,
21f43d2ffbSNicholas Piggin 		     pte_t *ptep, unsigned long trap, unsigned long flags,
22f43d2ffbSNicholas Piggin 		     int ssize, unsigned int shift, unsigned int mmu_psize)
23f43d2ffbSNicholas Piggin {
24f43d2ffbSNicholas Piggin 	real_pte_t rpte;
25f43d2ffbSNicholas Piggin 	unsigned long vpn;
26f43d2ffbSNicholas Piggin 	unsigned long old_pte, new_pte;
27f43d2ffbSNicholas Piggin 	unsigned long rflags, pa;
28f43d2ffbSNicholas Piggin 	long slot, offset;
29f43d2ffbSNicholas Piggin 
30f43d2ffbSNicholas Piggin 	BUG_ON(shift != mmu_psize_defs[mmu_psize].shift);
31f43d2ffbSNicholas Piggin 
32f43d2ffbSNicholas Piggin 	/* Search the Linux page table for a match with va */
33f43d2ffbSNicholas Piggin 	vpn = hpt_vpn(ea, vsid, ssize);
34f43d2ffbSNicholas Piggin 
35f43d2ffbSNicholas Piggin 	/*
36f43d2ffbSNicholas Piggin 	 * At this point, we have a pte (old_pte) which can be used to build
37f43d2ffbSNicholas Piggin 	 * or update an HPTE. There are 2 cases:
38f43d2ffbSNicholas Piggin 	 *
39f43d2ffbSNicholas Piggin 	 * 1. There is a valid (present) pte with no associated HPTE (this is
40f43d2ffbSNicholas Piggin 	 *	the most common case)
41f43d2ffbSNicholas Piggin 	 * 2. There is a valid (present) pte with an associated HPTE. The
42f43d2ffbSNicholas Piggin 	 *	current values of the pp bits in the HPTE prevent access
43f43d2ffbSNicholas Piggin 	 *	because we are doing software DIRTY bit management and the
44f43d2ffbSNicholas Piggin 	 *	page is currently not DIRTY.
45f43d2ffbSNicholas Piggin 	 */
46f43d2ffbSNicholas Piggin 
47f43d2ffbSNicholas Piggin 
48f43d2ffbSNicholas Piggin 	do {
49f43d2ffbSNicholas Piggin 		old_pte = pte_val(*ptep);
50f43d2ffbSNicholas Piggin 		/* If PTE busy, retry the access */
51f43d2ffbSNicholas Piggin 		if (unlikely(old_pte & H_PAGE_BUSY))
52f43d2ffbSNicholas Piggin 			return 0;
53f43d2ffbSNicholas Piggin 		/* If PTE permissions don't match, take page fault */
54f43d2ffbSNicholas Piggin 		if (unlikely(!check_pte_access(access, old_pte)))
55f43d2ffbSNicholas Piggin 			return 1;
56f43d2ffbSNicholas Piggin 
57f43d2ffbSNicholas Piggin 		/*
58f43d2ffbSNicholas Piggin 		 * Try to lock the PTE, add ACCESSED and DIRTY if it was
59f43d2ffbSNicholas Piggin 		 * a write access
60f43d2ffbSNicholas Piggin 		 */
61f43d2ffbSNicholas Piggin 		new_pte = old_pte | H_PAGE_BUSY | _PAGE_ACCESSED;
62f43d2ffbSNicholas Piggin 		if (access & _PAGE_WRITE)
63f43d2ffbSNicholas Piggin 			new_pte |= _PAGE_DIRTY;
64f43d2ffbSNicholas Piggin 	} while(!pte_xchg(ptep, __pte(old_pte), __pte(new_pte)));
65f43d2ffbSNicholas Piggin 
66f43d2ffbSNicholas Piggin 	/* Make sure this is a hugetlb entry */
67f43d2ffbSNicholas Piggin 	if (old_pte & (H_PAGE_THP_HUGE | _PAGE_DEVMAP))
68f43d2ffbSNicholas Piggin 		return 0;
69f43d2ffbSNicholas Piggin 
70f43d2ffbSNicholas Piggin 	rflags = htab_convert_pte_flags(new_pte, flags);
71f43d2ffbSNicholas Piggin 	if (unlikely(mmu_psize == MMU_PAGE_16G))
72f43d2ffbSNicholas Piggin 		offset = PTRS_PER_PUD;
73f43d2ffbSNicholas Piggin 	else
74f43d2ffbSNicholas Piggin 		offset = PTRS_PER_PMD;
75f43d2ffbSNicholas Piggin 	rpte = __real_pte(__pte(old_pte), ptep, offset);
76f43d2ffbSNicholas Piggin 
77f43d2ffbSNicholas Piggin 	if (!cpu_has_feature(CPU_FTR_COHERENT_ICACHE))
78f43d2ffbSNicholas Piggin 		/*
79f43d2ffbSNicholas Piggin 		 * No CPU has hugepages but lacks no execute, so we
80f43d2ffbSNicholas Piggin 		 * don't need to worry about that case
81f43d2ffbSNicholas Piggin 		 */
82f43d2ffbSNicholas Piggin 		rflags = hash_page_do_lazy_icache(rflags, __pte(old_pte), trap);
83f43d2ffbSNicholas Piggin 
84f43d2ffbSNicholas Piggin 	/* Check if pte already has an hpte (case 2) */
85f43d2ffbSNicholas Piggin 	if (unlikely(old_pte & H_PAGE_HASHPTE)) {
86f43d2ffbSNicholas Piggin 		/* There MIGHT be an HPTE for this pte */
87f43d2ffbSNicholas Piggin 		unsigned long gslot;
88f43d2ffbSNicholas Piggin 
89f43d2ffbSNicholas Piggin 		gslot = pte_get_hash_gslot(vpn, shift, ssize, rpte, 0);
90f43d2ffbSNicholas Piggin 		if (mmu_hash_ops.hpte_updatepp(gslot, rflags, vpn, mmu_psize,
91f43d2ffbSNicholas Piggin 					       mmu_psize, ssize, flags) == -1)
92f43d2ffbSNicholas Piggin 			old_pte &= ~_PAGE_HPTEFLAGS;
93f43d2ffbSNicholas Piggin 	}
94f43d2ffbSNicholas Piggin 
95f43d2ffbSNicholas Piggin 	if (likely(!(old_pte & H_PAGE_HASHPTE))) {
96f43d2ffbSNicholas Piggin 		unsigned long hash = hpt_hash(vpn, shift, ssize);
97f43d2ffbSNicholas Piggin 
98f43d2ffbSNicholas Piggin 		pa = pte_pfn(__pte(old_pte)) << PAGE_SHIFT;
99f43d2ffbSNicholas Piggin 
100f43d2ffbSNicholas Piggin 		/* clear HPTE slot informations in new PTE */
101f43d2ffbSNicholas Piggin 		new_pte = (new_pte & ~_PAGE_HPTEFLAGS) | H_PAGE_HASHPTE;
102f43d2ffbSNicholas Piggin 
103f43d2ffbSNicholas Piggin 		slot = hpte_insert_repeating(hash, vpn, pa, rflags, 0,
104f43d2ffbSNicholas Piggin 					     mmu_psize, ssize);
105f43d2ffbSNicholas Piggin 
106f43d2ffbSNicholas Piggin 		/*
107f43d2ffbSNicholas Piggin 		 * Hypervisor failure. Restore old pte and return -1
108f43d2ffbSNicholas Piggin 		 * similar to __hash_page_*
109f43d2ffbSNicholas Piggin 		 */
110f43d2ffbSNicholas Piggin 		if (unlikely(slot == -2)) {
111f43d2ffbSNicholas Piggin 			*ptep = __pte(old_pte);
112f43d2ffbSNicholas Piggin 			hash_failure_debug(ea, access, vsid, trap, ssize,
113f43d2ffbSNicholas Piggin 					   mmu_psize, mmu_psize, old_pte);
114f43d2ffbSNicholas Piggin 			return -1;
115f43d2ffbSNicholas Piggin 		}
116f43d2ffbSNicholas Piggin 
117f43d2ffbSNicholas Piggin 		new_pte |= pte_set_hidx(ptep, rpte, 0, slot, offset);
118f43d2ffbSNicholas Piggin 	}
119f43d2ffbSNicholas Piggin 
120f43d2ffbSNicholas Piggin 	/*
121f43d2ffbSNicholas Piggin 	 * No need to use ldarx/stdcx here
122f43d2ffbSNicholas Piggin 	 */
123f43d2ffbSNicholas Piggin 	*ptep = __pte(new_pte & ~H_PAGE_BUSY);
124f43d2ffbSNicholas Piggin 	return 0;
125f43d2ffbSNicholas Piggin }
126387e220aSNicholas Piggin #endif
127f43d2ffbSNicholas Piggin 
huge_ptep_modify_prot_start(struct vm_area_struct * vma,unsigned long addr,pte_t * ptep)128f43d2ffbSNicholas Piggin pte_t huge_ptep_modify_prot_start(struct vm_area_struct *vma,
129f43d2ffbSNicholas Piggin 				  unsigned long addr, pte_t *ptep)
130f43d2ffbSNicholas Piggin {
131f43d2ffbSNicholas Piggin 	unsigned long pte_val;
132f43d2ffbSNicholas Piggin 	/*
133f43d2ffbSNicholas Piggin 	 * Clear the _PAGE_PRESENT so that no hardware parallel update is
134f43d2ffbSNicholas Piggin 	 * possible. Also keep the pte_present true so that we don't take
135f43d2ffbSNicholas Piggin 	 * wrong fault.
136f43d2ffbSNicholas Piggin 	 */
137f43d2ffbSNicholas Piggin 	pte_val = pte_update(vma->vm_mm, addr, ptep,
138f43d2ffbSNicholas Piggin 			     _PAGE_PRESENT, _PAGE_INVALID, 1);
139f43d2ffbSNicholas Piggin 
140f43d2ffbSNicholas Piggin 	return __pte(pte_val);
141f43d2ffbSNicholas Piggin }
142f43d2ffbSNicholas Piggin 
huge_ptep_modify_prot_commit(struct vm_area_struct * vma,unsigned long addr,pte_t * ptep,pte_t old_pte,pte_t pte)143f43d2ffbSNicholas Piggin void huge_ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr,
144f43d2ffbSNicholas Piggin 				  pte_t *ptep, pte_t old_pte, pte_t pte)
145f43d2ffbSNicholas Piggin {
146*935d4f0cSRyan Roberts 	unsigned long psize;
147f43d2ffbSNicholas Piggin 
148f43d2ffbSNicholas Piggin 	if (radix_enabled())
149f43d2ffbSNicholas Piggin 		return radix__huge_ptep_modify_prot_commit(vma, addr, ptep,
150f43d2ffbSNicholas Piggin 							   old_pte, pte);
151*935d4f0cSRyan Roberts 
152*935d4f0cSRyan Roberts 	psize = huge_page_size(hstate_vma(vma));
153*935d4f0cSRyan Roberts 	set_huge_pte_at(vma->vm_mm, addr, ptep, pte, psize);
154f43d2ffbSNicholas Piggin }
155f43d2ffbSNicholas Piggin 
hugetlbpage_init_defaultsize(void)1562354ad25SAneesh Kumar K.V void __init hugetlbpage_init_defaultsize(void)
157f43d2ffbSNicholas Piggin {
158f43d2ffbSNicholas Piggin 	/* Set default large page size. Currently, we pick 16M or 1M
159f43d2ffbSNicholas Piggin 	 * depending on what is available
160f43d2ffbSNicholas Piggin 	 */
161f43d2ffbSNicholas Piggin 	if (mmu_psize_defs[MMU_PAGE_16M].shift)
162f43d2ffbSNicholas Piggin 		hpage_shift = mmu_psize_defs[MMU_PAGE_16M].shift;
163f43d2ffbSNicholas Piggin 	else if (mmu_psize_defs[MMU_PAGE_1M].shift)
164f43d2ffbSNicholas Piggin 		hpage_shift = mmu_psize_defs[MMU_PAGE_1M].shift;
165f43d2ffbSNicholas Piggin 	else if (mmu_psize_defs[MMU_PAGE_2M].shift)
166f43d2ffbSNicholas Piggin 		hpage_shift = mmu_psize_defs[MMU_PAGE_2M].shift;
167f43d2ffbSNicholas Piggin }
168