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