xref: /linux/arch/alpha/mm/hugetlbpage.c (revision 742f62b63904cdc20a364a49d2861e32464a0af7)
1*8ba8994eSMatt Turner // SPDX-License-Identifier: GPL-2.0
2*8ba8994eSMatt Turner /*
3*8ba8994eSMatt Turner  * Alpha huge page support, built on the page table entry granularity hint
4*8ba8994eSMatt Turner  * described in asm/pgtable.h. All PTEs of a block have to agree in bits
5*8ba8994eSMatt Turner  * <15:0>, so every update below rewrites the whole block.
6*8ba8994eSMatt Turner  */
7*8ba8994eSMatt Turner 
8*8ba8994eSMatt Turner #include <linux/hugetlb.h>
9*8ba8994eSMatt Turner #include <linux/mm.h>
10*8ba8994eSMatt Turner #include <linux/pgtable.h>
11*8ba8994eSMatt Turner 
12*8ba8994eSMatt Turner #include <asm/tlbflush.h>
13*8ba8994eSMatt Turner 
14*8ba8994eSMatt Turner /*
15*8ba8994eSMatt Turner  * Every hint size is smaller than PMD_SIZE, so a block always fits inside a
16*8ba8994eSMatt Turner  * single last level page table and its entry count is just its page count.
17*8ba8994eSMatt Turner  */
num_contig_ptes(unsigned long sz)18*8ba8994eSMatt Turner static inline unsigned long num_contig_ptes(unsigned long sz)
19*8ba8994eSMatt Turner {
20*8ba8994eSMatt Turner 	return sz >> PAGE_SHIFT;
21*8ba8994eSMatt Turner }
22*8ba8994eSMatt Turner 
23*8ba8994eSMatt Turner /* Hint order that maps @sz, or 0 if @sz is not a hint size. */
gh_order_from_size(unsigned long sz)24*8ba8994eSMatt Turner static unsigned int gh_order_from_size(unsigned long sz)
25*8ba8994eSMatt Turner {
26*8ba8994eSMatt Turner 	unsigned int order;
27*8ba8994eSMatt Turner 
28*8ba8994eSMatt Turner 	for_each_gh_order(order)
29*8ba8994eSMatt Turner 		if (gh_cont_size(order) == sz)
30*8ba8994eSMatt Turner 			return order;
31*8ba8994eSMatt Turner 
32*8ba8994eSMatt Turner 	return 0;
33*8ba8994eSMatt Turner }
34*8ba8994eSMatt Turner 
huge_pte_alloc(struct mm_struct * mm,struct vm_area_struct * vma,unsigned long addr,unsigned long sz)35*8ba8994eSMatt Turner pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma,
36*8ba8994eSMatt Turner 		      unsigned long addr, unsigned long sz)
37*8ba8994eSMatt Turner {
38*8ba8994eSMatt Turner 	unsigned int order = gh_order_from_size(sz);
39*8ba8994eSMatt Turner 	pgd_t *pgd;
40*8ba8994eSMatt Turner 	p4d_t *p4d;
41*8ba8994eSMatt Turner 	pud_t *pud;
42*8ba8994eSMatt Turner 	pmd_t *pmd;
43*8ba8994eSMatt Turner 
44*8ba8994eSMatt Turner 	if (!order)
45*8ba8994eSMatt Turner 		return NULL;
46*8ba8994eSMatt Turner 
47*8ba8994eSMatt Turner 	pgd = pgd_offset(mm, addr);
48*8ba8994eSMatt Turner 	p4d = p4d_alloc(mm, pgd, addr);
49*8ba8994eSMatt Turner 	if (!p4d)
50*8ba8994eSMatt Turner 		return NULL;
51*8ba8994eSMatt Turner 
52*8ba8994eSMatt Turner 	pud = pud_alloc(mm, p4d, addr);
53*8ba8994eSMatt Turner 	if (!pud)
54*8ba8994eSMatt Turner 		return NULL;
55*8ba8994eSMatt Turner 
56*8ba8994eSMatt Turner 	pmd = pmd_alloc(mm, pud, addr);
57*8ba8994eSMatt Turner 	if (!pmd)
58*8ba8994eSMatt Turner 		return NULL;
59*8ba8994eSMatt Turner 
60*8ba8994eSMatt Turner 	return pte_alloc_huge(mm, pmd, addr & gh_cont_mask(order));
61*8ba8994eSMatt Turner }
62*8ba8994eSMatt Turner 
huge_pte_offset(struct mm_struct * mm,unsigned long addr,unsigned long sz)63*8ba8994eSMatt Turner pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr,
64*8ba8994eSMatt Turner 		       unsigned long sz)
65*8ba8994eSMatt Turner {
66*8ba8994eSMatt Turner 	unsigned int order = gh_order_from_size(sz);
67*8ba8994eSMatt Turner 	pgd_t *pgd;
68*8ba8994eSMatt Turner 	p4d_t *p4d;
69*8ba8994eSMatt Turner 	pud_t *pud;
70*8ba8994eSMatt Turner 	pmd_t *pmd;
71*8ba8994eSMatt Turner 
72*8ba8994eSMatt Turner 	if (!order)
73*8ba8994eSMatt Turner 		return NULL;
74*8ba8994eSMatt Turner 
75*8ba8994eSMatt Turner 	pgd = pgd_offset(mm, addr);
76*8ba8994eSMatt Turner 	if (!pgd_present(pgdp_get(pgd)))
77*8ba8994eSMatt Turner 		return NULL;
78*8ba8994eSMatt Turner 
79*8ba8994eSMatt Turner 	p4d = p4d_offset(pgd, addr);
80*8ba8994eSMatt Turner 	if (!p4d_present(p4dp_get(p4d)))
81*8ba8994eSMatt Turner 		return NULL;
82*8ba8994eSMatt Turner 
83*8ba8994eSMatt Turner 	pud = pud_offset(p4d, addr);
84*8ba8994eSMatt Turner 	if (!pud_present(pudp_get(pud)))
85*8ba8994eSMatt Turner 		return NULL;
86*8ba8994eSMatt Turner 
87*8ba8994eSMatt Turner 	pmd = pmd_offset(pud, addr);
88*8ba8994eSMatt Turner 	if (!pmd_present(pmdp_get(pmd)))
89*8ba8994eSMatt Turner 		return NULL;
90*8ba8994eSMatt Turner 
91*8ba8994eSMatt Turner 	return pte_offset_huge(pmd, addr & gh_cont_mask(order));
92*8ba8994eSMatt Turner }
93*8ba8994eSMatt Turner 
94*8ba8994eSMatt Turner /*
95*8ba8994eSMatt Turner  * A block is only as large as its own hint, so the walk can skip to the end of
96*8ba8994eSMatt Turner  * the containing last level page table but no further.
97*8ba8994eSMatt Turner  */
hugetlb_mask_last_page(struct hstate * h)98*8ba8994eSMatt Turner unsigned long hugetlb_mask_last_page(struct hstate *h)
99*8ba8994eSMatt Turner {
100*8ba8994eSMatt Turner 	return PMD_SIZE - huge_page_size(h);
101*8ba8994eSMatt Turner }
102*8ba8994eSMatt Turner 
huge_ptep_get(struct mm_struct * mm,unsigned long addr,pte_t * ptep)103*8ba8994eSMatt Turner pte_t huge_ptep_get(struct mm_struct *mm, unsigned long addr, pte_t *ptep)
104*8ba8994eSMatt Turner {
105*8ba8994eSMatt Turner 	pte_t orig_pte = ptep_get(ptep);
106*8ba8994eSMatt Turner 	unsigned long i, ncontig;
107*8ba8994eSMatt Turner 
108*8ba8994eSMatt Turner 	/* Swap, migration and marker entries carry no hint. */
109*8ba8994eSMatt Turner 	if (!pte_huge(orig_pte))
110*8ba8994eSMatt Turner 		return orig_pte;
111*8ba8994eSMatt Turner 
112*8ba8994eSMatt Turner 	ncontig = gh_pte_num(pte_gh_order(orig_pte));
113*8ba8994eSMatt Turner 
114*8ba8994eSMatt Turner 	for (i = 0; i < ncontig; i++, ptep++) {
115*8ba8994eSMatt Turner 		pte_t pte = ptep_get(ptep);
116*8ba8994eSMatt Turner 
117*8ba8994eSMatt Turner 		if (pte_dirty(pte))
118*8ba8994eSMatt Turner 			orig_pte = pte_mkdirty(orig_pte);
119*8ba8994eSMatt Turner 		if (pte_young(pte))
120*8ba8994eSMatt Turner 			orig_pte = pte_mkyoung(orig_pte);
121*8ba8994eSMatt Turner 	}
122*8ba8994eSMatt Turner 
123*8ba8994eSMatt Turner 	return orig_pte;
124*8ba8994eSMatt Turner }
125*8ba8994eSMatt Turner 
get_clear_contig(struct mm_struct * mm,unsigned long addr,pte_t * ptep,unsigned long ncontig)126*8ba8994eSMatt Turner static pte_t get_clear_contig(struct mm_struct *mm, unsigned long addr,
127*8ba8994eSMatt Turner 			      pte_t *ptep, unsigned long ncontig)
128*8ba8994eSMatt Turner {
129*8ba8994eSMatt Turner 	pte_t pte = ptep_get_and_clear(mm, addr, ptep);
130*8ba8994eSMatt Turner 	bool present = pte_present(pte);
131*8ba8994eSMatt Turner 
132*8ba8994eSMatt Turner 	while (--ncontig) {
133*8ba8994eSMatt Turner 		pte_t tmp_pte;
134*8ba8994eSMatt Turner 
135*8ba8994eSMatt Turner 		ptep++;
136*8ba8994eSMatt Turner 		addr += PAGE_SIZE;
137*8ba8994eSMatt Turner 		tmp_pte = ptep_get_and_clear(mm, addr, ptep);
138*8ba8994eSMatt Turner 		if (present) {
139*8ba8994eSMatt Turner 			if (pte_dirty(tmp_pte))
140*8ba8994eSMatt Turner 				pte = pte_mkdirty(pte);
141*8ba8994eSMatt Turner 			if (pte_young(tmp_pte))
142*8ba8994eSMatt Turner 				pte = pte_mkyoung(pte);
143*8ba8994eSMatt Turner 		}
144*8ba8994eSMatt Turner 	}
145*8ba8994eSMatt Turner 
146*8ba8994eSMatt Turner 	return pte;
147*8ba8994eSMatt Turner }
148*8ba8994eSMatt Turner 
get_clear_contig_flush(struct mm_struct * mm,unsigned long addr,pte_t * ptep,unsigned long ncontig)149*8ba8994eSMatt Turner static pte_t get_clear_contig_flush(struct mm_struct *mm, unsigned long addr,
150*8ba8994eSMatt Turner 				    pte_t *ptep, unsigned long ncontig)
151*8ba8994eSMatt Turner {
152*8ba8994eSMatt Turner 	pte_t orig_pte = get_clear_contig(mm, addr, ptep, ncontig);
153*8ba8994eSMatt Turner 	struct vm_area_struct vma = TLB_FLUSH_VMA(mm, 0);
154*8ba8994eSMatt Turner 
155*8ba8994eSMatt Turner 	if (!pte_none(orig_pte))
156*8ba8994eSMatt Turner 		flush_tlb_range(&vma, addr, addr + ncontig * PAGE_SIZE);
157*8ba8994eSMatt Turner 
158*8ba8994eSMatt Turner 	return orig_pte;
159*8ba8994eSMatt Turner }
160*8ba8994eSMatt Turner 
arch_make_huge_pte(pte_t entry,unsigned int shift,vm_flags_t flags)161*8ba8994eSMatt Turner pte_t arch_make_huge_pte(pte_t entry, unsigned int shift, vm_flags_t flags)
162*8ba8994eSMatt Turner {
163*8ba8994eSMatt Turner 	unsigned int order = gh_order_from_size(1UL << shift);
164*8ba8994eSMatt Turner 
165*8ba8994eSMatt Turner 	/*
166*8ba8994eSMatt Turner 	 * Not every caller asks for a size Alpha can hint; mm/debug_vm_pgtable.c
167*8ba8994eSMatt Turner 	 * probes with PMD_SHIFT. Leave the entry alone rather than complaining.
168*8ba8994eSMatt Turner 	 */
169*8ba8994eSMatt Turner 	if (order)
170*8ba8994eSMatt Turner 		entry = pte_mkgh(entry, order);
171*8ba8994eSMatt Turner 
172*8ba8994eSMatt Turner 	return entry;
173*8ba8994eSMatt Turner }
174*8ba8994eSMatt Turner 
set_huge_pte_at(struct mm_struct * mm,unsigned long addr,pte_t * ptep,pte_t pte,unsigned long sz)175*8ba8994eSMatt Turner void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
176*8ba8994eSMatt Turner 		     pte_t pte, unsigned long sz)
177*8ba8994eSMatt Turner {
178*8ba8994eSMatt Turner 	unsigned long i, ncontig = num_contig_ptes(sz);
179*8ba8994eSMatt Turner 
180*8ba8994eSMatt Turner 	/* The hint requires the block to be naturally aligned in the VA too. */
181*8ba8994eSMatt Turner 	VM_WARN_ON_ONCE(addr & (sz - 1));
182*8ba8994eSMatt Turner 
183*8ba8994eSMatt Turner 	if (!pte_present(pte)) {
184*8ba8994eSMatt Turner 		/*
185*8ba8994eSMatt Turner 		 * Swap, migration and marker entries hold no frame number, so
186*8ba8994eSMatt Turner 		 * every slot gets the same value.
187*8ba8994eSMatt Turner 		 */
188*8ba8994eSMatt Turner 		for (i = 0; i < ncontig; i++, ptep++, addr += PAGE_SIZE)
189*8ba8994eSMatt Turner 			set_ptes(mm, addr, ptep, pte, 1);
190*8ba8994eSMatt Turner 		return;
191*8ba8994eSMatt Turner 	}
192*8ba8994eSMatt Turner 
193*8ba8994eSMatt Turner 	/*
194*8ba8994eSMatt Turner 	 * All PTEs of a block must agree in bits <15:0> (ARM Table 22-3, note
195*8ba8994eSMatt Turner 	 * 2), which rewriting a valid block in place would violate part way
196*8ba8994eSMatt Turner 	 * through. Invalidate it everywhere first, following ARM 11.6.1: the
197*8ba8994eSMatt Turner 	 * invalidate has to assume the hint is zero, so it must cover every
198*8ba8994eSMatt Turner 	 * page of the block.
199*8ba8994eSMatt Turner 	 */
200*8ba8994eSMatt Turner 	if (pte_present(ptep_get(ptep)))
201*8ba8994eSMatt Turner 		get_clear_contig_flush(mm, addr, ptep, ncontig);
202*8ba8994eSMatt Turner 
203*8ba8994eSMatt Turner 	/* set_ptes() advances the frame number for each entry. */
204*8ba8994eSMatt Turner 	set_ptes(mm, addr, ptep, pte, ncontig);
205*8ba8994eSMatt Turner }
206*8ba8994eSMatt Turner 
huge_ptep_get_and_clear(struct mm_struct * mm,unsigned long addr,pte_t * ptep,unsigned long sz)207*8ba8994eSMatt Turner pte_t huge_ptep_get_and_clear(struct mm_struct *mm, unsigned long addr,
208*8ba8994eSMatt Turner 			      pte_t *ptep, unsigned long sz)
209*8ba8994eSMatt Turner {
210*8ba8994eSMatt Turner 	return get_clear_contig(mm, addr, ptep, num_contig_ptes(sz));
211*8ba8994eSMatt Turner }
212*8ba8994eSMatt Turner 
213*8ba8994eSMatt Turner /*
214*8ba8994eSMatt Turner  * The block size comes from the hstate rather than from the entry, because
215*8ba8994eSMatt Turner  * this is also reached for non-present entries, which carry no hint.
216*8ba8994eSMatt Turner  */
huge_ptep_clear_flush(struct vm_area_struct * vma,unsigned long addr,pte_t * ptep)217*8ba8994eSMatt Turner pte_t huge_ptep_clear_flush(struct vm_area_struct *vma, unsigned long addr,
218*8ba8994eSMatt Turner 			    pte_t *ptep)
219*8ba8994eSMatt Turner {
220*8ba8994eSMatt Turner 	unsigned long sz = huge_page_size(hstate_vma(vma));
221*8ba8994eSMatt Turner 
222*8ba8994eSMatt Turner 	return get_clear_contig_flush(vma->vm_mm, addr, ptep,
223*8ba8994eSMatt Turner 				      num_contig_ptes(sz));
224*8ba8994eSMatt Turner }
225*8ba8994eSMatt Turner 
huge_ptep_modify_prot_start(struct vm_area_struct * vma,unsigned long addr,pte_t * ptep)226*8ba8994eSMatt Turner pte_t huge_ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr,
227*8ba8994eSMatt Turner 				  pte_t *ptep)
228*8ba8994eSMatt Turner {
229*8ba8994eSMatt Turner 	return huge_ptep_clear_flush(vma, addr, ptep);
230*8ba8994eSMatt Turner }
231*8ba8994eSMatt Turner 
232*8ba8994eSMatt Turner /* True if any entry of the block differs from @pte outside the frame number. */
gh_access_flags_changed(pte_t * ptep,pte_t pte,unsigned long ncontig)233*8ba8994eSMatt Turner static bool gh_access_flags_changed(pte_t *ptep, pte_t pte,
234*8ba8994eSMatt Turner 				    unsigned long ncontig)
235*8ba8994eSMatt Turner {
236*8ba8994eSMatt Turner 	unsigned long i;
237*8ba8994eSMatt Turner 
238*8ba8994eSMatt Turner 	for (i = 0; i < ncontig; i++)
239*8ba8994eSMatt Turner 		if ((pte_val(ptep_get(ptep + i)) ^ pte_val(pte)) & ~_PFN_MASK)
240*8ba8994eSMatt Turner 			return true;
241*8ba8994eSMatt Turner 
242*8ba8994eSMatt Turner 	return false;
243*8ba8994eSMatt Turner }
244*8ba8994eSMatt Turner 
huge_ptep_set_access_flags(struct vm_area_struct * vma,unsigned long addr,pte_t * ptep,pte_t pte,int dirty)245*8ba8994eSMatt Turner int huge_ptep_set_access_flags(struct vm_area_struct *vma, unsigned long addr,
246*8ba8994eSMatt Turner 			       pte_t *ptep, pte_t pte, int dirty)
247*8ba8994eSMatt Turner {
248*8ba8994eSMatt Turner 	struct mm_struct *mm = vma->vm_mm;
249*8ba8994eSMatt Turner 	unsigned long sz = huge_page_size(hstate_vma(vma));
250*8ba8994eSMatt Turner 	unsigned long ncontig = num_contig_ptes(sz);
251*8ba8994eSMatt Turner 	pte_t orig_pte = huge_ptep_get(mm, addr, ptep);
252*8ba8994eSMatt Turner 
253*8ba8994eSMatt Turner 	/* Keep the young and dirty state the block already has. */
254*8ba8994eSMatt Turner 	if (pte_dirty(orig_pte))
255*8ba8994eSMatt Turner 		pte = pte_mkdirty(pte);
256*8ba8994eSMatt Turner 	if (pte_young(orig_pte))
257*8ba8994eSMatt Turner 		pte = pte_mkyoung(pte);
258*8ba8994eSMatt Turner 
259*8ba8994eSMatt Turner 	/* Breaking an unchanged block only makes concurrent touchers refault. */
260*8ba8994eSMatt Turner 	if (!gh_access_flags_changed(ptep, pte, ncontig))
261*8ba8994eSMatt Turner 		return 0;
262*8ba8994eSMatt Turner 
263*8ba8994eSMatt Turner 	get_clear_contig_flush(mm, addr, ptep, ncontig);
264*8ba8994eSMatt Turner 	set_ptes(mm, addr, ptep, pte, ncontig);
265*8ba8994eSMatt Turner 
266*8ba8994eSMatt Turner 	return 1;
267*8ba8994eSMatt Turner }
268*8ba8994eSMatt Turner 
huge_ptep_set_wrprotect(struct mm_struct * mm,unsigned long addr,pte_t * ptep)269*8ba8994eSMatt Turner void huge_ptep_set_wrprotect(struct mm_struct *mm, unsigned long addr,
270*8ba8994eSMatt Turner 			     pte_t *ptep)
271*8ba8994eSMatt Turner {
272*8ba8994eSMatt Turner 	pte_t pte = ptep_get(ptep);
273*8ba8994eSMatt Turner 	unsigned long ncontig = gh_pte_num(pte_gh_order(pte));
274*8ba8994eSMatt Turner 
275*8ba8994eSMatt Turner 	VM_WARN_ON_ONCE(!pte_huge(pte));
276*8ba8994eSMatt Turner 
277*8ba8994eSMatt Turner 	/*
278*8ba8994eSMatt Turner 	 * pte_wrprotect() sets _PAGE_FOW, which lives in the bits the whole
279*8ba8994eSMatt Turner 	 * block must agree on, so this takes the break before make path.
280*8ba8994eSMatt Turner 	 */
281*8ba8994eSMatt Turner 	pte = pte_wrprotect(get_clear_contig_flush(mm, addr, ptep, ncontig));
282*8ba8994eSMatt Turner 	set_ptes(mm, addr, ptep, pte, ncontig);
283*8ba8994eSMatt Turner }
284*8ba8994eSMatt Turner 
huge_pte_clear(struct mm_struct * mm,unsigned long addr,pte_t * ptep,unsigned long sz)285*8ba8994eSMatt Turner void huge_pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
286*8ba8994eSMatt Turner 		    unsigned long sz)
287*8ba8994eSMatt Turner {
288*8ba8994eSMatt Turner 	unsigned long i, ncontig = num_contig_ptes(sz);
289*8ba8994eSMatt Turner 
290*8ba8994eSMatt Turner 	for (i = 0; i < ncontig; i++, addr += PAGE_SIZE, ptep++)
291*8ba8994eSMatt Turner 		pte_clear(mm, addr, ptep);
292*8ba8994eSMatt Turner }
293*8ba8994eSMatt Turner 
arch_hugetlb_valid_size(unsigned long size)294*8ba8994eSMatt Turner bool __init arch_hugetlb_valid_size(unsigned long size)
295*8ba8994eSMatt Turner {
296*8ba8994eSMatt Turner 	return gh_order_from_size(size) != 0;
297*8ba8994eSMatt Turner }
298*8ba8994eSMatt Turner 
alpha_hugetlbpage_init(void)299*8ba8994eSMatt Turner static __init int alpha_hugetlbpage_init(void)
300*8ba8994eSMatt Turner {
301*8ba8994eSMatt Turner 	unsigned int order;
302*8ba8994eSMatt Turner 
303*8ba8994eSMatt Turner 	for_each_gh_order(order)
304*8ba8994eSMatt Turner 		hugetlb_add_hstate(gh_cont_shift(order) - PAGE_SHIFT);
305*8ba8994eSMatt Turner 
306*8ba8994eSMatt Turner 	return 0;
307*8ba8994eSMatt Turner }
308*8ba8994eSMatt Turner arch_initcall(alpha_hugetlbpage_init);
309