Fix a corner case in demotion of kernel mappings.
It is possible for the kernel mapping to be created with superpage by directly installing pde using pmap_enter_2mpage() without filling the corresponding page table page. This can happen e.g. if the range is already backed by reservation and vm_fault_soft_fast() conditions are satisfied, which was observed on the pipe_map. In this case, demotion must fill the page obtained from the pmap radix, same as if the page is newly allocated. Use PG_PROMOTED bit as an indicator that the page is valid, instead of the wire count of the page table page. Since the PG_PROMOTED bit is set on pde when we leave TLB entries for 4k pages around, which in particular means that the ptes were filled, it provides more correct indicator. Note that pmap_protect_pde() clears PG_PROMOTED, which handles the case when protection was changed on the superpage without adjusting ptes. Reported by: pho In collaboration with: alc Tested by: alc, pho Sponsored by: The FreeBSD Foundation MFC after: 1 week Differential revision: https://reviews.freebsd.org/D20380
This commit is contained in:
parent
707b2d6586
commit
a9c7546a1d
@ -4537,8 +4537,10 @@ pmap_demote_pde_locked(pmap_t pmap, pd_entry_t *pde, vm_offset_t va,
|
||||
" in pmap %p", va, pmap);
|
||||
return (FALSE);
|
||||
}
|
||||
if (va < VM_MAXUSER_ADDRESS)
|
||||
if (va < VM_MAXUSER_ADDRESS) {
|
||||
mpte->wire_count = NPTEPG;
|
||||
pmap_resident_count_inc(pmap, 1);
|
||||
}
|
||||
}
|
||||
mptepa = VM_PAGE_TO_PHYS(mpte);
|
||||
firstpte = (pt_entry_t *)PHYS_TO_DMAP(mptepa);
|
||||
@ -4551,12 +4553,12 @@ pmap_demote_pde_locked(pmap_t pmap, pd_entry_t *pde, vm_offset_t va,
|
||||
newpte = pmap_swap_pat(pmap, newpte);
|
||||
|
||||
/*
|
||||
* If the page table page is new, initialize it.
|
||||
* If the page table page is not leftover from an earlier promotion,
|
||||
* initialize it.
|
||||
*/
|
||||
if (mpte->wire_count == 1) {
|
||||
mpte->wire_count = NPTEPG;
|
||||
if ((oldpde & PG_PROMOTED) == 0)
|
||||
pmap_fill_ptp(firstpte, newpte);
|
||||
}
|
||||
|
||||
KASSERT((*firstpte & PG_FRAME) == (newpte & PG_FRAME),
|
||||
("pmap_demote_pde: firstpte and newpte map different physical"
|
||||
" addresses"));
|
||||
|
@ -2768,8 +2768,10 @@ pmap_demote_pde(pmap_t pmap, pd_entry_t *pde, vm_offset_t va)
|
||||
" in pmap %p", va, pmap);
|
||||
return (FALSE);
|
||||
}
|
||||
if (pmap != kernel_pmap)
|
||||
if (pmap != kernel_pmap) {
|
||||
mpte->wire_count = NPTEPG;
|
||||
pmap->pm_stats.resident_count++;
|
||||
}
|
||||
}
|
||||
mptepa = VM_PAGE_TO_PHYS(mpte);
|
||||
|
||||
@ -2818,12 +2820,12 @@ pmap_demote_pde(pmap_t pmap, pd_entry_t *pde, vm_offset_t va)
|
||||
newpte ^= PG_PDE_PAT | PG_PTE_PAT;
|
||||
|
||||
/*
|
||||
* If the page table page is new, initialize it.
|
||||
* If the page table page is not leftover from an earlier promotion,
|
||||
* initialize it.
|
||||
*/
|
||||
if (mpte->wire_count == 1) {
|
||||
mpte->wire_count = NPTEPG;
|
||||
if ((oldpde & PG_PROMOTED) == 0)
|
||||
pmap_fill_ptp(firstpte, newpte);
|
||||
}
|
||||
|
||||
KASSERT((*firstpte & PG_FRAME) == (newpte & PG_FRAME),
|
||||
("pmap_demote_pde: firstpte and newpte map different physical"
|
||||
" addresses"));
|
||||
|
Loading…
Reference in New Issue
Block a user