Skip to content

Commit 49c87f7

Browse files
Ryan Robertswilldeacon
authored andcommitted
arm64: hugetlb: Fix huge_ptep_get_and_clear() for non-present ptes
arm64 supports multiple huge_pte sizes. Some of the sizes are covered by a single pte entry at a particular level (PMD_SIZE, PUD_SIZE), and some are covered by multiple ptes at a particular level (CONT_PTE_SIZE, CONT_PMD_SIZE). So the function has to figure out the size from the huge_pte pointer. This was previously done by walking the pgtable to determine the level and by using the PTE_CONT bit to determine the number of ptes at the level. But the PTE_CONT bit is only valid when the pte is present. For non-present pte values (e.g. markers, migration entries), the previous implementation was therefore erroneously determining the size. There is at least one known caller in core-mm, move_huge_pte(), which may call huge_ptep_get_and_clear() for a non-present pte. So we must be robust to this case. Additionally the "regular" ptep_get_and_clear() is robust to being called for non-present ptes so it makes sense to follow the behavior. Fix this by using the new sz parameter which is now provided to the function. Additionally when clearing each pte in a contig range, don't gather the access and dirty bits if the pte is not present. An alternative approach that would not require API changes would be to store the PTE_CONT bit in a spare bit in the swap entry pte for the non-present case. But it felt cleaner to follow other APIs' lead and just pass in the size. As an aside, PTE_CONT is bit 52, which corresponds to bit 40 in the swap entry offset field (layout of non-present pte). Since hugetlb is never swapped to disk, this field will only be populated for markers, which always set this bit to 0 and hwpoison swap entries, which set the offset field to a PFN; So it would only ever be 1 for a 52-bit PVA system where memory in that high half was poisoned (I think!). So in practice, this bit would almost always be zero for non-present ptes and we would only clear the first entry if it was actually a contiguous block. That's probably a less severe symptom than if it was always interpreted as 1 and cleared out potentially-present neighboring PTEs. Cc: [email protected] Fixes: 66b3923 ("arm64: hugetlb: add support for PTE contiguous bit") Reviewed-by: Catalin Marinas <[email protected]> Signed-off-by: Ryan Roberts <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Will Deacon <[email protected]>
1 parent 02410ac commit 49c87f7

File tree

1 file changed

+20
-33
lines changed

1 file changed

+20
-33
lines changed

arch/arm64/mm/hugetlbpage.c

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,11 @@ static int find_num_contig(struct mm_struct *mm, unsigned long addr,
100100

101101
static inline int num_contig_ptes(unsigned long size, size_t *pgsize)
102102
{
103-
int contig_ptes = 0;
103+
int contig_ptes = 1;
104104

105105
*pgsize = size;
106106

107107
switch (size) {
108-
#ifndef __PAGETABLE_PMD_FOLDED
109-
case PUD_SIZE:
110-
if (pud_sect_supported())
111-
contig_ptes = 1;
112-
break;
113-
#endif
114-
case PMD_SIZE:
115-
contig_ptes = 1;
116-
break;
117108
case CONT_PMD_SIZE:
118109
*pgsize = PMD_SIZE;
119110
contig_ptes = CONT_PMDS;
@@ -122,6 +113,8 @@ static inline int num_contig_ptes(unsigned long size, size_t *pgsize)
122113
*pgsize = PAGE_SIZE;
123114
contig_ptes = CONT_PTES;
124115
break;
116+
default:
117+
WARN_ON(!__hugetlb_valid_size(size));
125118
}
126119

127120
return contig_ptes;
@@ -163,24 +156,23 @@ static pte_t get_clear_contig(struct mm_struct *mm,
163156
unsigned long pgsize,
164157
unsigned long ncontig)
165158
{
166-
pte_t orig_pte = __ptep_get(ptep);
167-
unsigned long i;
168-
169-
for (i = 0; i < ncontig; i++, addr += pgsize, ptep++) {
170-
pte_t pte = __ptep_get_and_clear(mm, addr, ptep);
171-
172-
/*
173-
* If HW_AFDBM is enabled, then the HW could turn on
174-
* the dirty or accessed bit for any page in the set,
175-
* so check them all.
176-
*/
177-
if (pte_dirty(pte))
178-
orig_pte = pte_mkdirty(orig_pte);
179-
180-
if (pte_young(pte))
181-
orig_pte = pte_mkyoung(orig_pte);
159+
pte_t pte, tmp_pte;
160+
bool present;
161+
162+
pte = __ptep_get_and_clear(mm, addr, ptep);
163+
present = pte_present(pte);
164+
while (--ncontig) {
165+
ptep++;
166+
addr += pgsize;
167+
tmp_pte = __ptep_get_and_clear(mm, addr, ptep);
168+
if (present) {
169+
if (pte_dirty(tmp_pte))
170+
pte = pte_mkdirty(pte);
171+
if (pte_young(tmp_pte))
172+
pte = pte_mkyoung(pte);
173+
}
182174
}
183-
return orig_pte;
175+
return pte;
184176
}
185177

186178
static pte_t get_clear_contig_flush(struct mm_struct *mm,
@@ -401,13 +393,8 @@ pte_t huge_ptep_get_and_clear(struct mm_struct *mm, unsigned long addr,
401393
{
402394
int ncontig;
403395
size_t pgsize;
404-
pte_t orig_pte = __ptep_get(ptep);
405-
406-
if (!pte_cont(orig_pte))
407-
return __ptep_get_and_clear(mm, addr, ptep);
408-
409-
ncontig = find_num_contig(mm, addr, ptep, &pgsize);
410396

397+
ncontig = num_contig_ptes(sz, &pgsize);
411398
return get_clear_contig(mm, addr, ptep, pgsize, ncontig);
412399
}
413400

0 commit comments

Comments
 (0)