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