diff options
-rw-r--r-- | arch/arm64/mm/hugetlbpage.c | 112 |
1 files changed, 91 insertions, 21 deletions
diff --git a/arch/arm64/mm/hugetlbpage.c b/arch/arm64/mm/hugetlbpage.c index 08deed7c71f0..b82df85fe920 100644 --- a/arch/arm64/mm/hugetlbpage.c +++ b/arch/arm64/mm/hugetlbpage.c | |||
@@ -68,6 +68,66 @@ static int find_num_contig(struct mm_struct *mm, unsigned long addr, | |||
68 | return CONT_PTES; | 68 | return CONT_PTES; |
69 | } | 69 | } |
70 | 70 | ||
71 | /* | ||
72 | * Changing some bits of contiguous entries requires us to follow a | ||
73 | * Break-Before-Make approach, breaking the whole contiguous set | ||
74 | * before we can change any entries. See ARM DDI 0487A.k_iss10775, | ||
75 | * "Misprogramming of the Contiguous bit", page D4-1762. | ||
76 | * | ||
77 | * This helper performs the break step. | ||
78 | */ | ||
79 | static pte_t get_clear_flush(struct mm_struct *mm, | ||
80 | unsigned long addr, | ||
81 | pte_t *ptep, | ||
82 | unsigned long pgsize, | ||
83 | unsigned long ncontig) | ||
84 | { | ||
85 | struct vm_area_struct vma = { .vm_mm = mm }; | ||
86 | pte_t orig_pte = huge_ptep_get(ptep); | ||
87 | bool valid = pte_valid(orig_pte); | ||
88 | unsigned long i, saddr = addr; | ||
89 | |||
90 | for (i = 0; i < ncontig; i++, addr += pgsize, ptep++) { | ||
91 | pte_t pte = ptep_get_and_clear(mm, addr, ptep); | ||
92 | |||
93 | /* | ||
94 | * If HW_AFDBM is enabled, then the HW could turn on | ||
95 | * the dirty bit for any page in the set, so check | ||
96 | * them all. All hugetlb entries are already young. | ||
97 | */ | ||
98 | if (pte_dirty(pte)) | ||
99 | orig_pte = pte_mkdirty(orig_pte); | ||
100 | } | ||
101 | |||
102 | if (valid) | ||
103 | flush_tlb_range(&vma, saddr, addr); | ||
104 | return orig_pte; | ||
105 | } | ||
106 | |||
107 | /* | ||
108 | * Changing some bits of contiguous entries requires us to follow a | ||
109 | * Break-Before-Make approach, breaking the whole contiguous set | ||
110 | * before we can change any entries. See ARM DDI 0487A.k_iss10775, | ||
111 | * "Misprogramming of the Contiguous bit", page D4-1762. | ||
112 | * | ||
113 | * This helper performs the break step for use cases where the | ||
114 | * original pte is not needed. | ||
115 | */ | ||
116 | static void clear_flush(struct mm_struct *mm, | ||
117 | unsigned long addr, | ||
118 | pte_t *ptep, | ||
119 | unsigned long pgsize, | ||
120 | unsigned long ncontig) | ||
121 | { | ||
122 | struct vm_area_struct vma = { .vm_mm = mm }; | ||
123 | unsigned long i, saddr = addr; | ||
124 | |||
125 | for (i = 0; i < ncontig; i++, addr += pgsize, ptep++) | ||
126 | pte_clear(mm, addr, ptep); | ||
127 | |||
128 | flush_tlb_range(&vma, saddr, addr); | ||
129 | } | ||
130 | |||
71 | void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, | 131 | void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, |
72 | pte_t *ptep, pte_t pte) | 132 | pte_t *ptep, pte_t pte) |
73 | { | 133 | { |
@@ -93,6 +153,8 @@ void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, | |||
93 | dpfn = pgsize >> PAGE_SHIFT; | 153 | dpfn = pgsize >> PAGE_SHIFT; |
94 | hugeprot = pte_pgprot(pte); | 154 | hugeprot = pte_pgprot(pte); |
95 | 155 | ||
156 | clear_flush(mm, addr, ptep, pgsize, ncontig); | ||
157 | |||
96 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize, pfn += dpfn) { | 158 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize, pfn += dpfn) { |
97 | pr_debug("%s: set pte %p to 0x%llx\n", __func__, ptep, | 159 | pr_debug("%s: set pte %p to 0x%llx\n", __func__, ptep, |
98 | pte_val(pfn_pte(pfn, hugeprot))); | 160 | pte_val(pfn_pte(pfn, hugeprot))); |
@@ -194,7 +256,7 @@ pte_t arch_make_huge_pte(pte_t entry, struct vm_area_struct *vma, | |||
194 | pte_t huge_ptep_get_and_clear(struct mm_struct *mm, | 256 | pte_t huge_ptep_get_and_clear(struct mm_struct *mm, |
195 | unsigned long addr, pte_t *ptep) | 257 | unsigned long addr, pte_t *ptep) |
196 | { | 258 | { |
197 | int ncontig, i; | 259 | int ncontig; |
198 | size_t pgsize; | 260 | size_t pgsize; |
199 | pte_t orig_pte = huge_ptep_get(ptep); | 261 | pte_t orig_pte = huge_ptep_get(ptep); |
200 | 262 | ||
@@ -202,17 +264,8 @@ pte_t huge_ptep_get_and_clear(struct mm_struct *mm, | |||
202 | return ptep_get_and_clear(mm, addr, ptep); | 264 | return ptep_get_and_clear(mm, addr, ptep); |
203 | 265 | ||
204 | ncontig = find_num_contig(mm, addr, ptep, &pgsize); | 266 | ncontig = find_num_contig(mm, addr, ptep, &pgsize); |
205 | for (i = 0; i < ncontig; i++, addr += pgsize, ptep++) { | ||
206 | /* | ||
207 | * If HW_AFDBM is enabled, then the HW could | ||
208 | * turn on the dirty bit for any of the page | ||
209 | * in the set, so check them all. | ||
210 | */ | ||
211 | if (pte_dirty(ptep_get_and_clear(mm, addr, ptep))) | ||
212 | orig_pte = pte_mkdirty(orig_pte); | ||
213 | } | ||
214 | 267 | ||
215 | return orig_pte; | 268 | return get_clear_flush(mm, addr, ptep, pgsize, ncontig); |
216 | } | 269 | } |
217 | 270 | ||
218 | int huge_ptep_set_access_flags(struct vm_area_struct *vma, | 271 | int huge_ptep_set_access_flags(struct vm_area_struct *vma, |
@@ -223,18 +276,25 @@ int huge_ptep_set_access_flags(struct vm_area_struct *vma, | |||
223 | size_t pgsize = 0; | 276 | size_t pgsize = 0; |
224 | unsigned long pfn = pte_pfn(pte), dpfn; | 277 | unsigned long pfn = pte_pfn(pte), dpfn; |
225 | pgprot_t hugeprot; | 278 | pgprot_t hugeprot; |
279 | pte_t orig_pte; | ||
226 | 280 | ||
227 | if (!pte_cont(pte)) | 281 | if (!pte_cont(pte)) |
228 | return ptep_set_access_flags(vma, addr, ptep, pte, dirty); | 282 | return ptep_set_access_flags(vma, addr, ptep, pte, dirty); |
229 | 283 | ||
230 | ncontig = find_num_contig(vma->vm_mm, addr, ptep, &pgsize); | 284 | ncontig = find_num_contig(vma->vm_mm, addr, ptep, &pgsize); |
231 | dpfn = pgsize >> PAGE_SHIFT; | 285 | dpfn = pgsize >> PAGE_SHIFT; |
232 | hugeprot = pte_pgprot(pte); | ||
233 | 286 | ||
234 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize, pfn += dpfn) { | 287 | orig_pte = get_clear_flush(vma->vm_mm, addr, ptep, pgsize, ncontig); |
235 | changed |= ptep_set_access_flags(vma, addr, ptep, | 288 | if (!pte_same(orig_pte, pte)) |
236 | pfn_pte(pfn, hugeprot), dirty); | 289 | changed = 1; |
237 | } | 290 | |
291 | /* Make sure we don't lose the dirty state */ | ||
292 | if (pte_dirty(orig_pte)) | ||
293 | pte = pte_mkdirty(pte); | ||
294 | |||
295 | hugeprot = pte_pgprot(pte); | ||
296 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize, pfn += dpfn) | ||
297 | set_pte_at(vma->vm_mm, addr, ptep, pfn_pte(pfn, hugeprot)); | ||
238 | 298 | ||
239 | return changed; | 299 | return changed; |
240 | } | 300 | } |
@@ -242,8 +302,11 @@ int huge_ptep_set_access_flags(struct vm_area_struct *vma, | |||
242 | void huge_ptep_set_wrprotect(struct mm_struct *mm, | 302 | void huge_ptep_set_wrprotect(struct mm_struct *mm, |
243 | unsigned long addr, pte_t *ptep) | 303 | unsigned long addr, pte_t *ptep) |
244 | { | 304 | { |
305 | unsigned long pfn, dpfn; | ||
306 | pgprot_t hugeprot; | ||
245 | int ncontig, i; | 307 | int ncontig, i; |
246 | size_t pgsize; | 308 | size_t pgsize; |
309 | pte_t pte; | ||
247 | 310 | ||
248 | if (!pte_cont(*ptep)) { | 311 | if (!pte_cont(*ptep)) { |
249 | ptep_set_wrprotect(mm, addr, ptep); | 312 | ptep_set_wrprotect(mm, addr, ptep); |
@@ -251,15 +314,23 @@ void huge_ptep_set_wrprotect(struct mm_struct *mm, | |||
251 | } | 314 | } |
252 | 315 | ||
253 | ncontig = find_num_contig(mm, addr, ptep, &pgsize); | 316 | ncontig = find_num_contig(mm, addr, ptep, &pgsize); |
254 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize) | 317 | dpfn = pgsize >> PAGE_SHIFT; |
255 | ptep_set_wrprotect(mm, addr, ptep); | 318 | |
319 | pte = get_clear_flush(mm, addr, ptep, pgsize, ncontig); | ||
320 | pte = pte_wrprotect(pte); | ||
321 | |||
322 | hugeprot = pte_pgprot(pte); | ||
323 | pfn = pte_pfn(pte); | ||
324 | |||
325 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize, pfn += dpfn) | ||
326 | set_pte_at(mm, addr, ptep, pfn_pte(pfn, hugeprot)); | ||
256 | } | 327 | } |
257 | 328 | ||
258 | void huge_ptep_clear_flush(struct vm_area_struct *vma, | 329 | void huge_ptep_clear_flush(struct vm_area_struct *vma, |
259 | unsigned long addr, pte_t *ptep) | 330 | unsigned long addr, pte_t *ptep) |
260 | { | 331 | { |
261 | int ncontig, i; | ||
262 | size_t pgsize; | 332 | size_t pgsize; |
333 | int ncontig; | ||
263 | 334 | ||
264 | if (!pte_cont(*ptep)) { | 335 | if (!pte_cont(*ptep)) { |
265 | ptep_clear_flush(vma, addr, ptep); | 336 | ptep_clear_flush(vma, addr, ptep); |
@@ -267,8 +338,7 @@ void huge_ptep_clear_flush(struct vm_area_struct *vma, | |||
267 | } | 338 | } |
268 | 339 | ||
269 | ncontig = find_num_contig(vma->vm_mm, addr, ptep, &pgsize); | 340 | ncontig = find_num_contig(vma->vm_mm, addr, ptep, &pgsize); |
270 | for (i = 0; i < ncontig; i++, ptep++, addr += pgsize) | 341 | clear_flush(vma->vm_mm, addr, ptep, pgsize, ncontig); |
271 | ptep_clear_flush(vma, addr, ptep); | ||
272 | } | 342 | } |
273 | 343 | ||
274 | static __init int setup_hugepagesz(char *opt) | 344 | static __init int setup_hugepagesz(char *opt) |