#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pmap.c,v 1.428 2025/09/03 16:07:12 bouyer Exp $");
#include "opt_user_ldt.h"
#include "opt_lockdebug.h"
#include "opt_multiprocessor.h"
#include "opt_xen.h"
#include "opt_svs.h"
#include "opt_kaslr.h"
#include "opt_efi.h"
#define __MUTEX_PRIVATE
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/pool.h>
#include <sys/kernel.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <sys/intr.h>
#include <sys/xcall.h>
#include <sys/kcore.h>
#include <sys/kmem.h>
#include <sys/asan.h>
#include <sys/msan.h>
#include <sys/entropy.h>
#include <uvm/uvm.h>
#include <uvm/pmap/pmap_pvt.h>
#include <dev/isa/isareg.h>
#include <machine/specialreg.h>
#include <machine/gdt.h>
#include <machine/isa_machdep.h>
#include <machine/cpuvar.h>
#include <machine/cputypes.h>
#include <machine/pmap_private.h>
#include <x86/bootspace.h>
#include <x86/pat.h>
#include <x86/pmap_pv.h>
#include <x86/i82489reg.h>
#include <x86/i82489var.h>
#ifdef XEN
#include <xen/include/public/xen.h>
#include <xen/hypervisor.h>
#include <xen/xenpmap.h>
#endif
#ifdef __HAVE_DIRECT_MAP
#include <crypto/nist_hash_drbg/nist_hash_drbg.h>
#endif
#ifdef DIAGNOSTIC
#define PMAP_DUMMY_LOCK(pm) rw_enter(&(pm)->pm_dummy_lock, RW_WRITER)
#define PMAP_DUMMY_UNLOCK(pm) rw_exit(&(pm)->pm_dummy_lock)
#else
#define PMAP_DUMMY_LOCK(pm)
#define PMAP_DUMMY_UNLOCK(pm)
#endif
static const struct uvm_pagerops pmap_pager = {
};
#define pl_i(va, lvl) \
(((VA_SIGN_POS(va)) & ptp_frames[(lvl)-1]) >> ptp_shifts[(lvl)-1])
#define pl_i_roundup(va, lvl) pl_i((va)+ ~ptp_frames[(lvl)-1], (lvl))
#define ptp_va2o(va, lvl) (pl_i(va, (lvl)+1) * PAGE_SIZE)
const vaddr_t ptp_masks[] = PTP_MASK_INITIALIZER;
const vaddr_t ptp_frames[] = PTP_FRAME_INITIALIZER;
const int ptp_shifts[] = PTP_SHIFT_INITIALIZER;
const long nkptpmax[] = NKPTPMAX_INITIALIZER;
const long nbpd[] = NBPD_INITIALIZER;
#ifdef i386
pd_entry_t * const normal_pdes[] = PDES_INITIALIZER;
#else
pd_entry_t *normal_pdes[3];
#endif
long nkptp[] = NKPTP_INITIALIZER;
struct pmap_head pmaps;
kmutex_t pmaps_lock __cacheline_aligned;
struct pcpu_area *pcpuarea __read_mostly;
static vaddr_t pmap_maxkvaddr;
struct evcnt pmap_iobmp_evcnt;
struct evcnt pmap_ldt_evcnt;
static bool cpu_pat_enabled __read_mostly = false;
static struct pmap kernel_pmap_store __cacheline_aligned;
struct pmap *const kernel_pmap_ptr = &kernel_pmap_store;
static rb_tree_t pmap_kernel_rb __cacheline_aligned;
struct bootspace bootspace __read_mostly;
struct slotspace slotspace __read_mostly;
pd_entry_t pmap_pg_nx __read_mostly = 0;
pd_entry_t pmap_pg_g __read_mostly = 0;
int pmap_largepages __read_mostly = 0;
paddr_t lowmem_rsvd __read_mostly;
paddr_t avail_start __read_mostly;
paddr_t avail_end __read_mostly;
#ifdef XENPV
paddr_t pmap_pa_start;
paddr_t pmap_pa_end;
#endif
#define VM_PAGE_TO_PP(pg) (&(pg)->mdpage.mp_pp)
#define PMAP_CHECK_PP(pp) \
KASSERTMSG((pp)->pp_lock.mtx_ipl._ipl == IPL_VM, "bad pmap_page %p", pp)
#define PAGE_ALIGNED(pp) \
__builtin_assume_aligned((void *)(pp), PAGE_SIZE)
static pt_entry_t protection_codes[8] __read_mostly;
static bool pmap_initialized __read_mostly = false;
static vaddr_t virtual_avail __read_mostly;
static vaddr_t virtual_end __read_mostly;
#ifndef XENPV
volatile vaddr_t local_apic_va __read_mostly;
paddr_t local_apic_pa __read_mostly;
#endif
struct pool_cache pmap_cache;
static int pmap_ctor(void *, void *, int);
static void pmap_dtor(void *, void *);
static struct pool_cache pmap_pvp_cache;
#ifdef __HAVE_DIRECT_MAP
vaddr_t pmap_direct_base __read_mostly;
vaddr_t pmap_direct_end __read_mostly;
#endif
#ifndef __HAVE_DIRECT_MAP
static pt_entry_t *early_zero_pte;
static void pmap_vpage_cpualloc(struct cpu_info *);
#ifdef XENPV
char *early_zerop;
#else
static char *early_zerop;
#endif
#endif
int pmap_enter_default(pmap_t, vaddr_t, paddr_t, vm_prot_t, u_int);
static struct pool pmap_pdp_pool;
static void pmap_pdp_init(pd_entry_t *);
static void pmap_pdp_fini(pd_entry_t *);
#ifdef PAE
static void *pmap_pdp_alloc(struct pool *, int);
static void pmap_pdp_free(struct pool *, void *);
static struct pool_allocator pmap_pdp_allocator = {
.pa_alloc = pmap_pdp_alloc,
.pa_free = pmap_pdp_free,
.pa_pagesz = PAGE_SIZE * PDP_SIZE,
};
#endif
extern vaddr_t idt_vaddr;
extern paddr_t idt_paddr;
extern vaddr_t gdt_vaddr;
extern paddr_t gdt_paddr;
extern vaddr_t ldt_vaddr;
extern paddr_t ldt_paddr;
#ifdef i386
extern vaddr_t pentium_idt_vaddr;
#endif
struct pmap_ptparray {
struct vm_page *pg[PTP_LEVELS + 1];
bool alloced[PTP_LEVELS + 1];
};
struct pv_page {
LIST_HEAD(, pv_entry) pvp_pves;
LIST_ENTRY(pv_page) pvp_list;
long pvp_nfree;
struct pmap *pvp_pmap;
};
#define PVE_PER_PVP ((PAGE_SIZE / sizeof(struct pv_entry)) - 1)
static int pmap_compare_key(void *, const void *, const void *);
static int pmap_compare_nodes(void *, const void *, const void *);
static const rb_tree_ops_t pmap_rbtree_ops = {
.rbto_compare_nodes = pmap_compare_nodes,
.rbto_compare_key = pmap_compare_key,
.rbto_node_offset = offsetof(struct pv_entry, pve_rb),
.rbto_context = NULL
};
#ifdef __HAVE_PCPU_AREA
static void pmap_init_pcpu(void);
#endif
#ifdef __HAVE_DIRECT_MAP
static void pmap_init_directmap(struct pmap *);
#endif
#if !defined(XENPV)
static void pmap_remap_global(void);
#endif
#ifndef XENPV
static void pmap_init_lapic(void);
static void pmap_remap_largepages(void);
#endif
static int pmap_get_ptp(struct pmap *, struct pmap_ptparray *, vaddr_t, int,
struct vm_page **);
static void pmap_unget_ptp(struct pmap *, struct pmap_ptparray *);
static void pmap_install_ptp(struct pmap *, struct pmap_ptparray *, vaddr_t,
pd_entry_t * const *);
static struct vm_page *pmap_find_ptp(struct pmap *, vaddr_t, int);
static void pmap_freepage(struct pmap *, struct vm_page *, int);
static void pmap_free_ptp(struct pmap *, struct vm_page *, vaddr_t,
pt_entry_t *, pd_entry_t * const *);
static bool pmap_remove_pte(struct pmap *, struct vm_page *, pt_entry_t *,
vaddr_t);
static void pmap_remove_ptes(struct pmap *, struct vm_page *, vaddr_t, vaddr_t,
vaddr_t);
static int pmap_pvp_ctor(void *, void *, int);
static void pmap_pvp_dtor(void *, void *);
static struct pv_entry *pmap_alloc_pv(struct pmap *);
static void pmap_free_pv(struct pmap *, struct pv_entry *);
static void pmap_drain_pv(struct pmap *);
static void pmap_alloc_level(struct pmap *, vaddr_t, long *);
static void pmap_load1(struct lwp *, struct pmap *, struct pmap *);
static void pmap_reactivate(struct pmap *);
long
pmap_resident_count(struct pmap *pmap)
{
return pmap->pm_stats.resident_count;
}
long
pmap_wired_count(struct pmap *pmap)
{
return pmap->pm_stats.wired_count;
}
static inline void
pmap_stats_update(struct pmap *pmap, int resid_diff, int wired_diff)
{
KASSERT(cold || mutex_owned(&pmap->pm_lock));
pmap->pm_stats.resident_count += resid_diff;
pmap->pm_stats.wired_count += wired_diff;
}
static inline void
pmap_stats_update_bypte(struct pmap *pmap, pt_entry_t npte, pt_entry_t opte)
{
int resid_diff = ((npte & PTE_P) ? 1 : 0) - ((opte & PTE_P) ? 1 : 0);
int wired_diff = ((npte & PTE_WIRED) ? 1 : 0) - ((opte & PTE_WIRED) ? 1 : 0);
KASSERT((npte & (PTE_P | PTE_WIRED)) != PTE_WIRED);
KASSERT((opte & (PTE_P | PTE_WIRED)) != PTE_WIRED);
pmap_stats_update(pmap, resid_diff, wired_diff);
}
static inline struct pmap *
ptp_to_pmap(struct vm_page *ptp)
{
struct pmap *pmap;
if (ptp == NULL) {
return pmap_kernel();
}
pmap = (struct pmap *)ptp->uobject;
KASSERT(pmap != NULL);
KASSERT(&pmap->pm_obj[0] == ptp->uobject);
return pmap;
}
static inline struct pv_pte *
pve_to_pvpte(struct pv_entry *pve)
{
if (pve == NULL)
return NULL;
KASSERT((void *)&pve->pve_pte == (void *)pve);
return &pve->pve_pte;
}
static inline struct pv_entry *
pvpte_to_pve(struct pv_pte *pvpte)
{
struct pv_entry *pve = (void *)pvpte;
KASSERT(pve_to_pvpte(pve) == pvpte);
return pve;
}
static inline bool
pv_pte_embedded(struct pmap_page *pp)
{
KASSERT(mutex_owned(&pp->pp_lock));
return (bool)((vaddr_t)pp->pp_pte.pte_ptp | pp->pp_pte.pte_va);
}
static inline struct pv_pte *
pv_pte_first(struct pmap_page *pp)
{
KASSERT(mutex_owned(&pp->pp_lock));
if (pv_pte_embedded(pp)) {
return &pp->pp_pte;
}
return pve_to_pvpte(LIST_FIRST(&pp->pp_pvlist));
}
static inline struct pv_pte *
pv_pte_next(struct pmap_page *pp, struct pv_pte *pvpte)
{
KASSERT(mutex_owned(&pp->pp_lock));
KASSERT(pvpte != NULL);
if (pvpte == &pp->pp_pte) {
return pve_to_pvpte(LIST_FIRST(&pp->pp_pvlist));
}
return pve_to_pvpte(LIST_NEXT(pvpte_to_pve(pvpte), pve_list));
}
static inline uint8_t
pmap_pte_to_pp_attrs(pt_entry_t pte)
{
uint8_t ret = 0;
if (pte & PTE_D)
ret |= PP_ATTRS_D;
if (pte & PTE_A)
ret |= PP_ATTRS_A;
if (pte & PTE_W)
ret |= PP_ATTRS_W;
return ret;
}
static inline pt_entry_t
pmap_pp_attrs_to_pte(uint8_t attrs)
{
pt_entry_t pte = 0;
if (attrs & PP_ATTRS_D)
pte |= PTE_D;
if (attrs & PP_ATTRS_A)
pte |= PTE_A;
if (attrs & PP_ATTRS_W)
pte |= PTE_W;
return pte;
}
bool
pmap_is_curpmap(struct pmap *pmap)
{
return ((pmap == pmap_kernel()) || (pmap == curcpu()->ci_pmap));
}
inline void
pmap_reference(struct pmap *pmap)
{
atomic_inc_uint(&pmap->pm_obj[0].uo_refs);
}
static int
pmap_compare_nodes(void *context, const void *n1, const void *n2)
{
const struct pv_entry *pve1 = n1;
const struct pv_entry *pve2 = n2;
KASSERT(pve1->pve_pte.pte_ptp == pve2->pve_pte.pte_ptp);
if (pve1->pve_pte.pte_va < pve2->pve_pte.pte_va) {
return -1;
}
if (pve1->pve_pte.pte_va > pve2->pve_pte.pte_va) {
return 1;
}
return 0;
}
static int
pmap_compare_key(void *context, const void *n, const void *k)
{
const struct pv_entry *pve = n;
const vaddr_t key = (vaddr_t)k;
if (pve->pve_pte.pte_va < key) {
return -1;
}
if (pve->pve_pte.pte_va > key) {
return 1;
}
return 0;
}
static inline void
pmap_ptp_range_set(struct vm_page *ptp, vaddr_t va)
{
vaddr_t *min = (vaddr_t *)&ptp->uanon;
if (va < *min) {
*min = va;
}
}
static inline void
pmap_ptp_range_clip(struct vm_page *ptp, vaddr_t *startva, pt_entry_t **pte)
{
vaddr_t sclip;
if (ptp == NULL) {
return;
}
sclip = (vaddr_t)ptp->uanon;
sclip = (*startva < sclip ? sclip : *startva);
*pte += (sclip - *startva) / PAGE_SIZE;
*startva = sclip;
}
void
pmap_map_ptes(struct pmap *pmap, struct pmap **pmap2, pd_entry_t **ptepp,
pd_entry_t * const **pdeppp)
{
struct pmap *curpmap;
struct cpu_info *ci;
lwp_t *l;
kpreempt_disable();
if (pmap == pmap_kernel()) {
*pmap2 = NULL;
*ptepp = PTE_BASE;
*pdeppp = normal_pdes;
return;
}
KASSERT(mutex_owned(&pmap->pm_lock));
l = curlwp;
ci = l->l_cpu;
curpmap = ci->ci_pmap;
if (pmap == curpmap) {
if (__predict_false(ci->ci_tlbstate != TLBSTATE_VALID)) {
pmap_reactivate(pmap);
}
*pmap2 = NULL;
} else {
pmap_reference(pmap);
pmap_load1(l, pmap, curpmap);
*pmap2 = curpmap;
}
KASSERT(ci->ci_tlbstate == TLBSTATE_VALID);
#ifdef DIAGNOSTIC
pmap->pm_pctr = lwp_pctr();
#endif
*ptepp = PTE_BASE;
#if defined(XENPV) && defined(__x86_64__)
KASSERT(ci->ci_normal_pdes[PTP_LEVELS - 2] == L4_BASE);
ci->ci_normal_pdes[PTP_LEVELS - 2] = pmap->pm_pdir;
*pdeppp = ci->ci_normal_pdes;
#else
*pdeppp = normal_pdes;
#endif
}
void
pmap_unmap_ptes(struct pmap *pmap, struct pmap * pmap2)
{
struct cpu_info *ci;
struct pmap *mypmap;
struct lwp *l;
KASSERT(kpreempt_disabled());
if (pmap == pmap_kernel()) {
kpreempt_enable();
return;
}
l = curlwp;
ci = l->l_cpu;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(pmap->pm_pctr == lwp_pctr());
#if defined(XENPV) && defined(__x86_64__)
KASSERT(ci->ci_normal_pdes[PTP_LEVELS - 2] != L4_BASE);
ci->ci_normal_pdes[PTP_LEVELS - 2] = L4_BASE;
#endif
KASSERT(ci->ci_tlbstate == TLBSTATE_VALID);
mypmap = vm_map_pmap(&l->l_proc->p_vmspace->vm_map);
if (ci->ci_pmap == vm_map_pmap(&l->l_proc->p_vmspace->vm_map)) {
ci->ci_want_pmapload = 0;
} else {
ci->ci_want_pmapload = (mypmap != pmap_kernel());
ci->ci_tlbstate = TLBSTATE_LAZY;
}
kpreempt_enable();
if (pmap2 != NULL) {
pmap_destroy(pmap2);
}
}
inline static void
pmap_exec_account(struct pmap *pm, vaddr_t va, pt_entry_t opte, pt_entry_t npte)
{
#if !defined(__x86_64__)
if (curproc == NULL || curproc->p_vmspace == NULL ||
pm != vm_map_pmap(&curproc->p_vmspace->vm_map))
return;
if ((opte ^ npte) & PTE_X)
pmap_update_pg(va);
if ((opte & PTE_X) && (npte & PTE_X) == 0 && va == pm->pm_hiexec) {
struct trapframe *tf = curlwp->l_md.md_regs;
tf->tf_cs = GSEL(GUCODE_SEL, SEL_UPL);
pm->pm_hiexec = I386_MAX_EXE_ADDR;
}
#endif
}
#if !defined(__x86_64__)
int
pmap_exec_fixup(struct vm_map *map, struct trapframe *tf, struct pcb *pcb)
{
struct vm_map_entry *ent;
struct pmap *pm = vm_map_pmap(map);
vaddr_t va = 0;
vm_map_lock_read(map);
for (ent = (&map->header)->next; ent != &map->header; ent = ent->next) {
if (ent->protection & VM_PROT_EXECUTE)
va = trunc_page(ent->end) - PAGE_SIZE;
}
vm_map_unlock_read(map);
if (va == pm->pm_hiexec && tf->tf_cs == GSEL(GUCODEBIG_SEL, SEL_UPL))
return 0;
pm->pm_hiexec = va;
if (pm->pm_hiexec > I386_MAX_EXE_ADDR) {
tf->tf_cs = GSEL(GUCODEBIG_SEL, SEL_UPL);
} else {
tf->tf_cs = GSEL(GUCODE_SEL, SEL_UPL);
return 0;
}
return 1;
}
#endif
void
pat_init(struct cpu_info *ci)
{
#ifndef XENPV
uint64_t pat;
if (!(ci->ci_feat_val[0] & CPUID_PAT))
return;
pat = PATENTRY(0, PAT_WB) | PATENTRY(1, PAT_WC) |
PATENTRY(2, PAT_UCMINUS) | PATENTRY(3, PAT_UC) |
PATENTRY(4, PAT_WB) | PATENTRY(5, PAT_WC) |
PATENTRY(6, PAT_UCMINUS) | PATENTRY(7, PAT_UC);
wrmsr(MSR_CR_PAT, pat);
cpu_pat_enabled = true;
#endif
}
static pt_entry_t
pmap_pat_flags(u_int flags)
{
u_int cacheflags = (flags & PMAP_CACHE_MASK);
if (!cpu_pat_enabled) {
switch (cacheflags) {
case PMAP_NOCACHE:
case PMAP_NOCACHE_OVR:
return PTE_PCD;
default:
return 0;
}
}
switch (cacheflags) {
case PMAP_NOCACHE:
return PGC_UC;
case PMAP_WRITE_COMBINE:
return PGC_WC;
case PMAP_WRITE_BACK:
return PGC_WB;
case PMAP_NOCACHE_OVR:
return PGC_UCMINUS;
}
return 0;
}
void
pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags)
{
pt_entry_t *pte, opte, npte;
KASSERT(!(prot & ~VM_PROT_ALL));
if (va < VM_MIN_KERNEL_ADDRESS)
pte = vtopte(va);
else
pte = kvtopte(va);
#if defined(XENPV) && defined(DOM0OPS)
if (pa < pmap_pa_start || pa >= pmap_pa_end) {
#ifdef DEBUG
printf_nolog("%s: pa %#" PRIxPADDR " for va %#" PRIxVADDR
" outside range\n", __func__, pa, va);
#endif
npte = pa;
} else
#endif
npte = pmap_pa2pte(pa);
npte |= protection_codes[prot] | PTE_P | pmap_pg_g;
npte |= pmap_pat_flags(flags);
opte = pmap_pte_testset(pte, npte);
KASSERTMSG(!(opte & PTE_PS), "PTE_PS va=%#"PRIxVADDR, va);
if ((opte & (PTE_P | PTE_A)) == (PTE_P | PTE_A)) {
printf_nolog("%s: mapping already present\n", __func__);
kpreempt_disable();
pmap_tlb_shootdown(pmap_kernel(), va, opte, TLBSHOOT_KENTER);
kpreempt_enable();
}
}
__strict_weak_alias(pmap_kenter_ma, pmap_kenter_pa);
#if defined(__x86_64__)
void
pmap_changeprot_local(vaddr_t va, vm_prot_t prot)
{
pt_entry_t *pte, opte, npte;
KASSERT(kpreempt_disabled());
if (va < VM_MIN_KERNEL_ADDRESS)
pte = vtopte(va);
else
pte = kvtopte(va);
npte = opte = *pte;
if ((prot & VM_PROT_WRITE) != 0)
npte |= PTE_W;
else
npte &= ~(PTE_W|PTE_D);
if (opte != npte) {
pmap_pte_set(pte, npte);
pmap_pte_flush();
invlpg(va);
}
}
#endif
static void
pmap_kremove1(vaddr_t sva, vsize_t len, bool localonly)
{
pt_entry_t *pte, opte;
vaddr_t va, eva;
eva = sva + len;
kpreempt_disable();
for (va = sva; va < eva; va += PAGE_SIZE) {
pte = kvtopte(va);
opte = pmap_pte_testset(pte, 0);
if ((opte & (PTE_P | PTE_A)) == (PTE_P | PTE_A) && !localonly) {
pmap_tlb_shootdown(pmap_kernel(), va, opte,
TLBSHOOT_KREMOVE);
}
KASSERTMSG((opte & PTE_PS) == 0,
"va %#" PRIxVADDR " is a large page", va);
KASSERTMSG((opte & PTE_PVLIST) == 0,
"va %#" PRIxVADDR " is a pv tracked page", va);
}
if (localonly) {
tlbflushg();
}
kpreempt_enable();
}
void
pmap_kremove(vaddr_t sva, vsize_t len)
{
pmap_kremove1(sva, len, false);
}
void
pmap_kremove_local(vaddr_t sva, vsize_t len)
{
pmap_kremove1(sva, len, true);
}
static vaddr_t
pmap_bootstrap_valloc(size_t npages)
{
vaddr_t va = virtual_avail;
virtual_avail += npages * PAGE_SIZE;
return va;
}
static paddr_t
pmap_bootstrap_palloc(size_t npages)
{
paddr_t pa = avail_start;
avail_start += npages * PAGE_SIZE;
return pa;
}
void
pmap_bootstrap(vaddr_t kva_start)
{
struct pmap *kpm;
int i;
vaddr_t kva;
pmap_pg_nx = (cpu_feature[2] & CPUID_NOX ? PTE_NX : 0);
virtual_avail = kva_start;
virtual_end = VM_MAX_KERNEL_ADDRESS;
protection_codes[VM_PROT_NONE] = pmap_pg_nx;
protection_codes[VM_PROT_EXECUTE] = PTE_X;
protection_codes[VM_PROT_READ] = pmap_pg_nx;
protection_codes[VM_PROT_READ|VM_PROT_EXECUTE] = PTE_X;
protection_codes[VM_PROT_WRITE] = PTE_W | pmap_pg_nx;
protection_codes[VM_PROT_WRITE|VM_PROT_EXECUTE] = PTE_W | PTE_X;
protection_codes[VM_PROT_WRITE|VM_PROT_READ] = PTE_W | pmap_pg_nx;
protection_codes[VM_PROT_ALL] = PTE_W | PTE_X;
kpm = pmap_kernel();
mutex_init(&kpm->pm_lock, MUTEX_DEFAULT, IPL_NONE);
rw_init(&kpm->pm_dummy_lock);
for (i = 0; i < PTP_LEVELS - 1; i++) {
uvm_obj_init(&kpm->pm_obj[i], &pmap_pager, false, 1);
uvm_obj_setlock(&kpm->pm_obj[i], &kpm->pm_dummy_lock);
kpm->pm_ptphint[i] = NULL;
}
memset(&kpm->pm_list, 0, sizeof(kpm->pm_list));
kpm->pm_pdir = (pd_entry_t *)bootspace.pdir;
for (i = 0; i < PDP_SIZE; i++)
kpm->pm_pdirpa[i] = PDPpaddr + PAGE_SIZE * i;
kpm->pm_stats.wired_count = kpm->pm_stats.resident_count =
x86_btop(kva_start - VM_MIN_KERNEL_ADDRESS);
kcpuset_create(&kpm->pm_cpus, true);
kcpuset_create(&kpm->pm_kernel_cpus, true);
kpm->pm_ldt = NULL;
kpm->pm_ldt_sel = GSYSSEL(GLDT_SEL, SEL_KPL);
#if !defined(XENPV)
#ifdef SVS
if (!svs_enabled && (cpu_feature[0] & CPUID_PGE)) {
#else
if (cpu_feature[0] & CPUID_PGE) {
#endif
pmap_pg_g = PTE_G;
pmap_remap_global();
}
#endif
#ifndef XENPV
if (cpu_feature[0] & CPUID_PSE) {
lcr4(rcr4() | CR4_PSE);
pmap_largepages = 1;
tlbflushg();
pmap_remap_largepages();
}
pmap_init_lapic();
#endif
#ifdef __HAVE_PCPU_AREA
pmap_init_pcpu();
#endif
#ifdef __HAVE_DIRECT_MAP
pmap_init_directmap(kpm);
#else
pmap_vpage_cpualloc(&cpu_info_primary);
if (VM_MIN_KERNEL_ADDRESS == KERNBASE) {
early_zerop = (void *)cpu_info_primary.vpage[VPAGE_ZER];
early_zero_pte = cpu_info_primary.vpage_pte[VPAGE_ZER];
} else {
#ifdef XENPV
#else
early_zerop = (void *)bootspace.spareva;
#endif
early_zero_pte = PTE_BASE + pl1_i((vaddr_t)early_zerop);
}
#endif
#if defined(XENPV) && defined(__x86_64__)
extern vaddr_t xen_dummy_page;
paddr_t xen_dummy_user_pgd;
xen_dummy_user_pgd = xen_dummy_page - KERNBASE;
memset(PAGE_ALIGNED(xen_dummy_user_pgd + KERNBASE), 0, PAGE_SIZE);
HYPERVISOR_update_va_mapping(xen_dummy_user_pgd + KERNBASE,
pmap_pa2pte(xen_dummy_user_pgd) | PTE_P | pmap_pg_nx,
UVMF_INVLPG);
xpq_queue_pin_l4_table(xpmap_ptom_masked(xen_dummy_user_pgd));
#endif
idt_vaddr = pmap_bootstrap_valloc(1);
idt_paddr = pmap_bootstrap_palloc(1);
gdt_vaddr = pmap_bootstrap_valloc(1);
gdt_paddr = pmap_bootstrap_palloc(1);
#ifdef __HAVE_PCPU_AREA
ldt_vaddr = (vaddr_t)&pcpuarea->ldt;
#else
ldt_vaddr = pmap_bootstrap_valloc(1);
#endif
ldt_paddr = pmap_bootstrap_palloc(1);
#if !defined(__x86_64__)
pentium_idt_vaddr = pmap_bootstrap_valloc(1);
#endif
#if defined(XENPVHVM)
extern paddr_t HYPERVISOR_shared_info_pa;
extern volatile struct xencons_interface *xencons_interface;
extern struct xenstore_domain_interface *xenstore_interface;
if (vm_guest == VM_GUEST_XENHVM) {
HYPERVISOR_shared_info = (void *) pmap_bootstrap_valloc(1);
HYPERVISOR_shared_info_pa = pmap_bootstrap_palloc(1);
}
xencons_interface = (void *) pmap_bootstrap_valloc(1);
xenstore_interface = (void *) pmap_bootstrap_valloc(1);
#endif
virtual_avail = reserve_dumppages(virtual_avail);
mutex_init(&pmaps_lock, MUTEX_DEFAULT, IPL_NONE);
LIST_INIT(&pmaps);
tlbflushg();
kva = VM_MIN_KERNEL_ADDRESS;
for (i = PTP_LEVELS - 1; i >= 1; i--) {
kva += nkptp[i] * nbpd[i];
}
pmap_maxkvaddr = kva;
}
#ifndef XENPV
static void
pmap_init_lapic(void)
{
local_apic_va = pmap_bootstrap_valloc(1);
local_apic_pa = pmap_bootstrap_palloc(1);
}
#endif
#ifdef __x86_64__
static size_t
pmap_pagetree_nentries_range(vaddr_t startva, vaddr_t endva, size_t pgsz)
{
size_t npages;
npages = (roundup(endva, pgsz) / pgsz) -
(rounddown(startva, pgsz) / pgsz);
return npages;
}
#endif
#if defined(__HAVE_DIRECT_MAP) || defined(KASAN) || defined(KMSAN)
static inline void
slotspace_copy(int type, pd_entry_t *dst, pd_entry_t *src)
{
size_t sslot = slotspace.area[type].sslot;
size_t nslot = slotspace.area[type].nslot;
memcpy(&dst[sslot], &src[sslot], nslot * sizeof(pd_entry_t));
}
#endif
#ifdef __x86_64__
vaddr_t
slotspace_rand(int type, size_t sz, size_t align, size_t randhole,
vaddr_t randva)
{
struct {
int start;
int end;
} holes[SLSPACE_NAREAS+1];
size_t i, nholes, hole;
size_t startsl, endsl, nslots, winsize;
vaddr_t startva, va;
sz = roundup(sz, align);
nslots = roundup(sz+NBPD_L4, NBPD_L4) / NBPD_L4;
nholes = 0;
size_t curslot = 0 + 256;
while (1) {
size_t minsslot = 512;
size_t minnslot = 0;
for (i = 0; i < SLSPACE_NAREAS; i++) {
if (!slotspace.area[i].active)
continue;
if (slotspace.area[i].sslot >= curslot &&
slotspace.area[i].sslot < minsslot) {
minsslot = slotspace.area[i].sslot;
minnslot = slotspace.area[i].nslot;
}
}
if (minsslot == 512) {
break;
}
if (minsslot - curslot >= nslots) {
holes[nholes].start = curslot;
holes[nholes].end = minsslot;
nholes++;
}
curslot = minsslot + minnslot;
}
if (nholes == 0) {
panic("%s: impossible", __func__);
}
hole = randhole;
#ifdef NO_X86_ASLR
hole = 0;
#endif
hole %= nholes;
startsl = holes[hole].start;
endsl = holes[hole].end;
startva = VA_SIGN_NEG(startsl * NBPD_L4);
va = randva;
#ifdef NO_X86_ASLR
va = 0;
#endif
winsize = ((endsl - startsl) * NBPD_L4) - sz;
va %= winsize;
va = rounddown(va, align);
va += startva;
slotspace.area[type].sslot = pl4_i(va);
slotspace.area[type].nslot =
pmap_pagetree_nentries_range(va, va+sz, NBPD_L4);
slotspace.area[type].active = true;
return va;
}
#endif
#ifdef __HAVE_PCPU_AREA
static void
pmap_init_pcpu(void)
{
const vaddr_t startva = PMAP_PCPU_BASE;
size_t nL4e, nL3e, nL2e, nL1e;
size_t L4e_idx, L3e_idx, L2e_idx, L1e_idx __diagused;
paddr_t pa;
vaddr_t endva;
vaddr_t tmpva;
pt_entry_t *pte;
size_t size;
int i;
const pd_entry_t pteflags = PTE_P | PTE_W | pmap_pg_nx;
size = sizeof(struct pcpu_area);
endva = startva + size;
tmpva = bootspace.spareva;
pte = PTE_BASE + pl1_i(tmpva);
L4e_idx = pl4_i(startva);
nL4e = pmap_pagetree_nentries_range(startva, endva, NBPD_L4);
KASSERT(nL4e == 1);
for (i = 0; i < nL4e; i++) {
KASSERT(L4_BASE[L4e_idx+i] == 0);
pa = pmap_bootstrap_palloc(1);
*pte = (pa & PTE_FRAME) | pteflags;
pmap_update_pg(tmpva);
memset(PAGE_ALIGNED(tmpva), 0, PAGE_SIZE);
L4_BASE[L4e_idx+i] = pa | pteflags | PTE_A;
}
L3e_idx = pl3_i(startva);
nL3e = pmap_pagetree_nentries_range(startva, endva, NBPD_L3);
for (i = 0; i < nL3e; i++) {
KASSERT(L3_BASE[L3e_idx+i] == 0);
pa = pmap_bootstrap_palloc(1);
*pte = (pa & PTE_FRAME) | pteflags;
pmap_update_pg(tmpva);
memset(PAGE_ALIGNED(tmpva), 0, PAGE_SIZE);
L3_BASE[L3e_idx+i] = pa | pteflags | PTE_A;
}
L2e_idx = pl2_i(startva);
nL2e = pmap_pagetree_nentries_range(startva, endva, NBPD_L2);
for (i = 0; i < nL2e; i++) {
KASSERT(L2_BASE[L2e_idx+i] == 0);
pa = pmap_bootstrap_palloc(1);
*pte = (pa & PTE_FRAME) | pteflags;
pmap_update_pg(tmpva);
memset(PAGE_ALIGNED(tmpva), 0, PAGE_SIZE);
L2_BASE[L2e_idx+i] = pa | pteflags | PTE_A;
}
L1e_idx = pl1_i(startva);
nL1e = pmap_pagetree_nentries_range(startva, endva, NBPD_L1);
for (i = 0; i < nL1e; i++) {
KASSERT(L1_BASE[L1e_idx+i] == 0);
}
*pte = 0;
pmap_update_pg(tmpva);
pcpuarea = (struct pcpu_area *)startva;
tlbflush();
}
#endif
#ifdef __HAVE_DIRECT_MAP
static void
randomize_hole(size_t *randholep, vaddr_t *randvap)
{
struct nist_hash_drbg drbg;
uint8_t seed[NIST_HASH_DRBG_SEEDLEN_BYTES];
const char p[] = "x86/directmap";
int error;
entropy_extract(seed, sizeof(seed), 0);
error = nist_hash_drbg_instantiate(&drbg, seed, sizeof(seed),
NULL, 0,
p, strlen(p));
KASSERTMSG(error == 0, "error=%d", error);
error = nist_hash_drbg_generate(&drbg, randholep, sizeof(*randholep),
NULL, 0);
KASSERTMSG(error == 0, "error=%d", error);
error = nist_hash_drbg_generate(&drbg, randvap, sizeof(*randvap),
NULL, 0);
KASSERTMSG(error == 0, "error=%d", error);
explicit_memset(seed, 0, sizeof(seed));
explicit_memset(&drbg, 0, sizeof(drbg));
}
static void
pmap_init_directmap(struct pmap *kpm)
{
extern phys_ram_seg_t mem_clusters[];
extern int mem_cluster_cnt;
vaddr_t startva;
size_t nL4e, nL3e, nL2e;
size_t L4e_idx, L3e_idx, L2e_idx;
size_t spahole, epahole;
paddr_t lastpa, pa;
vaddr_t endva;
vaddr_t tmpva;
pt_entry_t *pte;
phys_ram_seg_t *mc;
int i;
size_t randhole;
vaddr_t randva;
const pd_entry_t pteflags = PTE_P | PTE_W | pmap_pg_nx;
const pd_entry_t holepteflags = PTE_P | pmap_pg_nx;
CTASSERT(NL4_SLOT_DIRECT * NBPD_L4 == MAXPHYSMEM);
spahole = roundup(bootspace.head.pa, NBPD_L2);
epahole = rounddown(bootspace.boot.pa, NBPD_L2);
lastpa = 0;
for (i = 0; i < mem_cluster_cnt; i++) {
mc = &mem_clusters[i];
lastpa = MAX(lastpa, mc->start + mc->size);
}
if (lastpa > MAXPHYSMEM) {
panic("pmap_init_directmap: lastpa incorrect");
}
randomize_hole(&randhole, &randva);
startva = slotspace_rand(SLAREA_DMAP, lastpa, NBPD_L2,
randhole, randva);
endva = startva + lastpa;
tmpva = bootspace.spareva;
pte = PTE_BASE + pl1_i(tmpva);
L4e_idx = pl4_i(startva);
nL4e = pmap_pagetree_nentries_range(startva, endva, NBPD_L4);
KASSERT(nL4e <= NL4_SLOT_DIRECT);
for (i = 0; i < nL4e; i++) {
KASSERT(L4_BASE[L4e_idx+i] == 0);
pa = pmap_bootstrap_palloc(1);
*pte = (pa & PTE_FRAME) | pteflags;
pmap_update_pg(tmpva);
memset(PAGE_ALIGNED(tmpva), 0, PAGE_SIZE);
L4_BASE[L4e_idx+i] = pa | pteflags | PTE_A;
}
L3e_idx = pl3_i(startva);
nL3e = pmap_pagetree_nentries_range(startva, endva, NBPD_L3);
for (i = 0; i < nL3e; i++) {
KASSERT(L3_BASE[L3e_idx+i] == 0);
pa = pmap_bootstrap_palloc(1);
*pte = (pa & PTE_FRAME) | pteflags;
pmap_update_pg(tmpva);
memset(PAGE_ALIGNED(tmpva), 0, PAGE_SIZE);
L3_BASE[L3e_idx+i] = pa | pteflags | PTE_A;
}
L2e_idx = pl2_i(startva);
nL2e = pmap_pagetree_nentries_range(startva, endva, NBPD_L2);
for (i = 0; i < nL2e; i++) {
KASSERT(L2_BASE[L2e_idx+i] == 0);
pa = (paddr_t)(i * NBPD_L2);
if (spahole <= pa && pa < epahole) {
L2_BASE[L2e_idx+i] = pa | holepteflags | PTE_A |
PTE_PS | pmap_pg_g;
} else {
L2_BASE[L2e_idx+i] = pa | pteflags | PTE_A |
PTE_PS | pmap_pg_g;
}
}
*pte = 0;
pmap_update_pg(tmpva);
pmap_direct_base = startva;
pmap_direct_end = endva;
tlbflush();
}
#endif
#if !defined(XENPV)
static void
pmap_remap_global(void)
{
vaddr_t kva, kva_end;
unsigned long p1i;
size_t i;
kva = bootspace.head.va;
kva_end = kva + bootspace.head.sz;
for ( ; kva < kva_end; kva += PAGE_SIZE) {
p1i = pl1_i(kva);
if (pmap_valid_entry(PTE_BASE[p1i]))
PTE_BASE[p1i] |= pmap_pg_g;
}
for (i = 0; i < BTSPACE_NSEGS; i++) {
if (bootspace.segs[i].type == BTSEG_NONE) {
continue;
}
kva = bootspace.segs[i].va;
kva_end = kva + bootspace.segs[i].sz;
for ( ; kva < kva_end; kva += PAGE_SIZE) {
p1i = pl1_i(kva);
if (pmap_valid_entry(PTE_BASE[p1i]))
PTE_BASE[p1i] |= pmap_pg_g;
}
}
kva = bootspace.boot.va;
kva_end = kva + bootspace.boot.sz;
for ( ; kva < kva_end; kva += PAGE_SIZE) {
p1i = pl1_i(kva);
if (pmap_valid_entry(PTE_BASE[p1i]))
PTE_BASE[p1i] |= pmap_pg_g;
}
}
#endif
#ifndef XENPV
static void
pmap_remap_largepages(void)
{
pd_entry_t *pde;
vaddr_t kva, kva_end;
paddr_t pa;
size_t i;
for (i = 0; i < BTSPACE_NSEGS; i++) {
if (bootspace.segs[i].type != BTSEG_TEXT) {
continue;
}
kva = roundup(bootspace.segs[i].va, NBPD_L2);
if (kva < bootspace.segs[i].va) {
continue;
}
kva_end = rounddown(bootspace.segs[i].va +
bootspace.segs[i].sz, NBPD_L2);
pa = roundup(bootspace.segs[i].pa, NBPD_L2);
for (; kva < kva_end; kva += NBPD_L2, pa += NBPD_L2) {
pde = &L2_BASE[pl2_i(kva)];
*pde = pa | pmap_pg_g | PTE_PS | PTE_P;
tlbflushg();
}
}
for (i = 0; i < BTSPACE_NSEGS; i++) {
if (bootspace.segs[i].type != BTSEG_RODATA) {
continue;
}
kva = roundup(bootspace.segs[i].va, NBPD_L2);
if (kva < bootspace.segs[i].va) {
continue;
}
kva_end = rounddown(bootspace.segs[i].va +
bootspace.segs[i].sz, NBPD_L2);
pa = roundup(bootspace.segs[i].pa, NBPD_L2);
for (; kva < kva_end; kva += NBPD_L2, pa += NBPD_L2) {
pde = &L2_BASE[pl2_i(kva)];
*pde = pa | pmap_pg_g | PTE_PS | pmap_pg_nx | PTE_P;
tlbflushg();
}
}
for (i = 0; i < BTSPACE_NSEGS; i++) {
if (bootspace.segs[i].type != BTSEG_DATA) {
continue;
}
kva = roundup(bootspace.segs[i].va, NBPD_L2);
if (kva < bootspace.segs[i].va) {
continue;
}
kva_end = rounddown(bootspace.segs[i].va +
bootspace.segs[i].sz, NBPD_L2);
pa = roundup(bootspace.segs[i].pa, NBPD_L2);
for (; kva < kva_end; kva += NBPD_L2, pa += NBPD_L2) {
pde = &L2_BASE[pl2_i(kva)];
*pde = pa | pmap_pg_g | PTE_PS | pmap_pg_nx | PTE_W | PTE_P;
tlbflushg();
}
}
}
#endif
void
pmap_init(void)
{
int flags;
pool_cache_bootstrap(&pmap_cache, sizeof(struct pmap), COHERENCY_UNIT,
0, 0, "pmappl", NULL, IPL_NONE, pmap_ctor, pmap_dtor, NULL);
#ifdef XENPV
flags = PR_NOTOUCH;
#else
flags = 0;
#endif
#ifdef PAE
pool_init(&pmap_pdp_pool, PAGE_SIZE * PDP_SIZE, 0, 0, flags,
"pdppl", &pmap_pdp_allocator, IPL_NONE);
#else
pool_init(&pmap_pdp_pool, PAGE_SIZE, 0, 0, flags,
"pdppl", NULL, IPL_NONE);
#endif
pool_cache_bootstrap(&pmap_pvp_cache, PAGE_SIZE, PAGE_SIZE,
0, 0, "pvpage", &pool_allocator_kmem,
IPL_NONE, pmap_pvp_ctor, pmap_pvp_dtor, NULL);
pmap_tlb_init();
pmap_tlb_cpu_init(curcpu());
evcnt_attach_dynamic(&pmap_iobmp_evcnt, EVCNT_TYPE_MISC,
NULL, "x86", "io bitmap copy");
evcnt_attach_dynamic(&pmap_ldt_evcnt, EVCNT_TYPE_MISC,
NULL, "x86", "ldt sync");
rb_tree_init(&pmap_kernel_rb, &pmap_rbtree_ops);
pmap_initialized = true;
}
#ifndef XENPV
void
pmap_cpu_init_late(struct cpu_info *ci)
{
if (ci == &cpu_info_primary)
return;
#ifdef PAE
cpu_alloc_l3_page(ci);
#endif
}
#endif
#ifndef __HAVE_DIRECT_MAP
CTASSERT(CACHE_LINE_SIZE > sizeof(pt_entry_t));
CTASSERT(CACHE_LINE_SIZE % sizeof(pt_entry_t) == 0);
static void
pmap_vpage_cpualloc(struct cpu_info *ci)
{
bool primary = (ci == &cpu_info_primary);
size_t i, npages;
vaddr_t vabase;
vsize_t vrange;
npages = (CACHE_LINE_SIZE / sizeof(pt_entry_t));
KASSERT(npages >= VPAGE_MAX);
vrange = npages * PAGE_SIZE;
if (primary) {
while ((vabase = pmap_bootstrap_valloc(1)) % vrange != 0) {
}
pmap_bootstrap_valloc(npages - 1);
} else {
vabase = uvm_km_alloc(kernel_map, vrange, vrange,
UVM_KMF_VAONLY);
if (vabase == 0) {
panic("%s: failed to allocate tmp VA for CPU %d\n",
__func__, cpu_index(ci));
}
}
KASSERT((vaddr_t)&PTE_BASE[pl1_i(vabase)] % CACHE_LINE_SIZE == 0);
for (i = 0; i < VPAGE_MAX; i++) {
ci->vpage[i] = vabase + i * PAGE_SIZE;
ci->vpage_pte[i] = PTE_BASE + pl1_i(ci->vpage[i]);
}
}
void
pmap_vpage_cpu_init(struct cpu_info *ci)
{
if (ci == &cpu_info_primary) {
return;
}
pmap_vpage_cpualloc(ci);
}
#endif
static int
pmap_pvp_ctor(void *arg, void *obj, int flags)
{
struct pv_page *pvp = (struct pv_page *)obj;
struct pv_entry *pve = (struct pv_entry *)obj + 1;
struct pv_entry *maxpve = pve + PVE_PER_PVP;
KASSERT(sizeof(struct pv_page) <= sizeof(struct pv_entry));
KASSERT(trunc_page((vaddr_t)obj) == (vaddr_t)obj);
LIST_INIT(&pvp->pvp_pves);
pvp->pvp_nfree = PVE_PER_PVP;
pvp->pvp_pmap = NULL;
for (; pve < maxpve; pve++) {
LIST_INSERT_HEAD(&pvp->pvp_pves, pve, pve_list);
}
return 0;
}
static void
pmap_pvp_dtor(void *arg, void *obj)
{
struct pv_page *pvp __diagused = obj;
KASSERT(pvp->pvp_pmap == NULL);
KASSERT(pvp->pvp_nfree == PVE_PER_PVP);
}
static struct pv_entry *
pmap_alloc_pv(struct pmap *pmap)
{
struct pv_entry *pve;
struct pv_page *pvp;
KASSERT(mutex_owned(&pmap->pm_lock));
if (__predict_false((pvp = LIST_FIRST(&pmap->pm_pvp_part)) == NULL)) {
if ((pvp = LIST_FIRST(&pmap->pm_pvp_full)) != NULL) {
LIST_REMOVE(pvp, pvp_list);
} else {
pvp = pool_cache_get(&pmap_pvp_cache, PR_NOWAIT);
}
if (__predict_false(pvp == NULL)) {
return NULL;
}
LIST_INSERT_HEAD(&pmap->pm_pvp_part, pvp, pvp_list);
pvp->pvp_pmap = pmap;
}
KASSERT(pvp->pvp_pmap == pmap);
KASSERT(pvp->pvp_nfree > 0);
pve = LIST_FIRST(&pvp->pvp_pves);
LIST_REMOVE(pve, pve_list);
pvp->pvp_nfree--;
if (__predict_false(pvp->pvp_nfree == 0)) {
KASSERT(LIST_EMPTY(&pvp->pvp_pves));
LIST_REMOVE(pvp, pvp_list);
LIST_INSERT_HEAD(&pmap->pm_pvp_empty, pvp, pvp_list);
} else {
KASSERT(!LIST_EMPTY(&pvp->pvp_pves));
}
return pve;
}
static void
pmap_free_pv(struct pmap *pmap, struct pv_entry *pve)
{
struct pv_page *pvp = (struct pv_page *)trunc_page((vaddr_t)pve);
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(pvp->pvp_pmap == pmap);
KASSERT(pvp->pvp_nfree >= 0);
LIST_INSERT_HEAD(&pvp->pvp_pves, pve, pve_list);
pvp->pvp_nfree++;
if (__predict_false(pvp->pvp_nfree == 1)) {
LIST_REMOVE(pvp, pvp_list);
LIST_INSERT_HEAD(&pmap->pm_pvp_part, pvp, pvp_list);
} else if (__predict_false(pvp->pvp_nfree == PVE_PER_PVP)) {
LIST_REMOVE(pvp, pvp_list);
LIST_INSERT_HEAD(&pmap->pm_pvp_full, pvp, pvp_list);
}
}
static void
pmap_drain_pv(struct pmap *pmap)
{
struct pv_page *pvp;
KASSERT(mutex_owned(&pmap->pm_lock));
while ((pvp = LIST_FIRST(&pmap->pm_pvp_full)) != NULL) {
LIST_REMOVE(pvp, pvp_list);
KASSERT(pvp->pvp_pmap == pmap);
KASSERT(pvp->pvp_nfree == PVE_PER_PVP);
pvp->pvp_pmap = NULL;
pool_cache_put(&pmap_pvp_cache, pvp);
}
}
static void
pmap_check_pv(struct pmap *pmap, struct vm_page *ptp, struct pmap_page *pp,
vaddr_t va, bool tracked)
{
#ifdef DEBUG
struct pv_pte *pvpte;
PMAP_CHECK_PP(pp);
mutex_spin_enter(&pp->pp_lock);
for (pvpte = pv_pte_first(pp); pvpte; pvpte = pv_pte_next(pp, pvpte)) {
if (pvpte->pte_ptp == ptp && pvpte->pte_va == va) {
break;
}
}
mutex_spin_exit(&pp->pp_lock);
if (pvpte && !tracked) {
panic("pmap_check_pv: %p/%lx found on pp %p", ptp, va, pp);
} else if (!pvpte && tracked) {
panic("pmap_check_pv: %p/%lx missing on pp %p", ptp, va, pp);
}
#endif
}
static struct pv_entry *
pmap_treelookup_pv(const struct pmap *pmap, const struct vm_page *ptp,
const rb_tree_t *tree, const vaddr_t va)
{
struct pv_entry *pve;
rb_node_t *node;
for (node = tree->rbt_root;;) {
if (__predict_false(RB_SENTINEL_P(node))) {
return NULL;
}
pve = (struct pv_entry *)
((uintptr_t)node - offsetof(struct pv_entry, pve_rb));
if (pve->pve_pte.pte_va == va) {
KASSERT(pve->pve_pte.pte_ptp == ptp);
return pve;
}
node = node->rb_nodes[pve->pve_pte.pte_va < va];
}
}
static struct pv_entry *
pmap_lookup_pv(const struct pmap *pmap, const struct vm_page *ptp,
const struct pmap_page * const old_pp, const vaddr_t va)
{
struct pv_entry *pve;
const rb_tree_t *tree;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(ptp != NULL || pmap == pmap_kernel());
if (atomic_load_relaxed(&old_pp->pp_pte.pte_ptp) == ptp &&
atomic_load_relaxed(&old_pp->pp_pte.pte_va) == va) {
return NULL;
}
tree = (ptp != NULL ? &VM_PAGE_TO_PP(ptp)->pp_rb : &pmap_kernel_rb);
KASSERT(!RB_SENTINEL_P(tree->rbt_root));
pve = (struct pv_entry *)
((uintptr_t)tree->rbt_minmax[RB_DIR_LEFT] -
offsetof(struct pv_entry, pve_rb));
if (__predict_true(pve->pve_pte.pte_va == va)) {
KASSERT(pve->pve_pte.pte_ptp == ptp);
return pve;
}
return pmap_treelookup_pv(pmap, ptp, tree, va);
}
static int
pmap_enter_pv(struct pmap *pmap, struct pmap_page *pp, struct vm_page *ptp,
vaddr_t va, struct pv_entry **new_pve, struct pv_entry **old_pve,
bool *samepage, bool *new_embedded, rb_tree_t *tree)
{
struct pv_entry *pve;
int error;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(ptp_to_pmap(ptp) == pmap);
KASSERT(ptp == NULL || ptp->uobject != NULL);
KASSERT(ptp == NULL || ptp_va2o(va, 1) == ptp->offset);
PMAP_CHECK_PP(pp);
if (atomic_load_relaxed(&pp->pp_pte.pte_ptp) == ptp &&
atomic_load_relaxed(&pp->pp_pte.pte_va) == va) {
*samepage = true;
pmap_check_pv(pmap, ptp, pp, va, true);
return 0;
}
*old_pve = pmap_treelookup_pv(pmap, ptp, tree, va);
if (*old_pve != NULL && (*old_pve)->pve_pp == pp) {
*samepage = true;
pmap_check_pv(pmap, ptp, pp, va, true);
return 0;
}
if (pmap->pm_pve == NULL) {
pmap->pm_pve = pmap_alloc_pv(pmap);
}
error = 0;
pmap_check_pv(pmap, ptp, pp, va, false);
mutex_spin_enter(&pp->pp_lock);
if (!pv_pte_embedded(pp)) {
pp->pp_pte.pte_ptp = ptp;
pp->pp_pte.pte_va = va;
*new_embedded = true;
} else if (__predict_false(pmap->pm_pve == NULL)) {
error = ENOMEM;
} else {
pve = pmap->pm_pve;
pmap->pm_pve = NULL;
*new_pve = pve;
pve->pve_pte.pte_ptp = ptp;
pve->pve_pte.pte_va = va;
pve->pve_pp = pp;
LIST_INSERT_HEAD(&pp->pp_pvlist, pve, pve_list);
}
mutex_spin_exit(&pp->pp_lock);
if (error == 0) {
pmap_check_pv(pmap, ptp, pp, va, true);
}
return error;
}
static void
pmap_remove_pv(struct pmap *pmap, struct pmap_page *pp, struct vm_page *ptp,
vaddr_t va, struct pv_entry *pve, uint8_t oattrs)
{
rb_tree_t *tree = (ptp != NULL ?
&VM_PAGE_TO_PP(ptp)->pp_rb : &pmap_kernel_rb);
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(ptp_to_pmap(ptp) == pmap);
KASSERT(ptp == NULL || ptp->uobject != NULL);
KASSERT(ptp == NULL || ptp_va2o(va, 1) == ptp->offset);
KASSERT(ptp != NULL || pmap == pmap_kernel());
pmap_check_pv(pmap, ptp, pp, va, true);
if (pve == NULL) {
mutex_spin_enter(&pp->pp_lock);
KASSERT(pp->pp_pte.pte_ptp == ptp);
KASSERT(pp->pp_pte.pte_va == va);
pp->pp_attrs |= oattrs;
pp->pp_pte.pte_ptp = NULL;
pp->pp_pte.pte_va = 0;
mutex_spin_exit(&pp->pp_lock);
} else {
mutex_spin_enter(&pp->pp_lock);
KASSERT(pp->pp_pte.pte_ptp != ptp ||
pp->pp_pte.pte_va != va);
KASSERT(pve->pve_pte.pte_ptp == ptp);
KASSERT(pve->pve_pte.pte_va == va);
KASSERT(pve->pve_pp == pp);
pp->pp_attrs |= oattrs;
LIST_REMOVE(pve, pve_list);
mutex_spin_exit(&pp->pp_lock);
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == pve);
rb_tree_remove_node(tree, pve);
#ifdef DIAGNOSTIC
memset(pve, 0, sizeof(*pve));
#endif
pmap_free_pv(pmap, pve);
}
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == NULL);
pmap_check_pv(pmap, ptp, pp, va, false);
}
static struct vm_page *
pmap_find_ptp(struct pmap *pmap, vaddr_t va, int level)
{
int lidx = level - 1;
off_t off = ptp_va2o(va, level);
struct vm_page *pg;
KASSERT(mutex_owned(&pmap->pm_lock));
if (pmap->pm_ptphint[lidx] && off == pmap->pm_ptphint[lidx]->offset) {
KASSERT(pmap->pm_ptphint[lidx]->wire_count > 0);
pg = pmap->pm_ptphint[lidx];
PMAP_CHECK_PP(VM_PAGE_TO_PP(pg));
return pg;
}
PMAP_DUMMY_LOCK(pmap);
pg = uvm_pagelookup(&pmap->pm_obj[lidx], off);
PMAP_DUMMY_UNLOCK(pmap);
if (pg != NULL && __predict_false(pg->wire_count == 0)) {
pg = NULL;
}
if (pg != NULL) {
PMAP_CHECK_PP(VM_PAGE_TO_PP(pg));
}
pmap->pm_ptphint[lidx] = pg;
return pg;
}
static inline void
pmap_freepage(struct pmap *pmap, struct vm_page *ptp, int level)
{
int lidx;
KASSERT(ptp->wire_count <= 1);
PMAP_CHECK_PP(VM_PAGE_TO_PP(ptp));
lidx = level - 1;
pmap_stats_update(pmap, -ptp->wire_count, 0);
if (pmap->pm_ptphint[lidx] == ptp)
pmap->pm_ptphint[lidx] = NULL;
ptp->wire_count = 0;
ptp->uanon = NULL;
KASSERT(RB_TREE_MIN(&VM_PAGE_TO_PP(ptp)->pp_rb) == NULL);
LIST_INSERT_HEAD(&pmap->pm_gc_ptp, ptp, mdpage.mp_pp.pp_link);
}
static void
pmap_free_ptp(struct pmap *pmap, struct vm_page *ptp, vaddr_t va,
pt_entry_t *ptes, pd_entry_t * const *pdes)
{
unsigned long index;
int level;
vaddr_t invaladdr;
pd_entry_t opde;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
level = 1;
do {
index = pl_i(va, level + 1);
opde = pmap_pte_testset(&pdes[level - 1][index], 0);
#if defined(XENPV) && defined(__x86_64__)
if (level == PTP_LEVELS - 1) {
xen_kpm_sync(pmap, index);
}
#elif defined(SVS)
if (svs_enabled && level == PTP_LEVELS - 1 &&
pmap_is_user(pmap)) {
svs_pmap_sync(pmap, index);
}
#endif
invaladdr = level == 1 ? (vaddr_t)ptes :
(vaddr_t)pdes[level - 2];
pmap_tlb_shootdown(pmap, invaladdr + index * PAGE_SIZE,
opde, TLBSHOOT_FREE_PTP);
#if defined(XENPV)
pmap_tlb_shootnow();
#endif
pmap_freepage(pmap, ptp, level);
if (level < PTP_LEVELS - 1) {
ptp = pmap_find_ptp(pmap, va, level + 1);
ptp->wire_count--;
if (ptp->wire_count > 1)
break;
}
} while (++level < PTP_LEVELS);
pmap_pte_flush();
}
static int
pmap_get_ptp(struct pmap *pmap, struct pmap_ptparray *pt, vaddr_t va,
int flags, struct vm_page **resultp)
{
struct vm_page *ptp;
int i, aflags;
struct uvm_object *obj;
voff_t off;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
memset(pt, 0, sizeof(*pt));
aflags = ((flags & PMAP_CANFAIL) ? 0 : UVM_PGA_USERESERVE) |
UVM_PGA_ZERO;
for (i = PTP_LEVELS; i > 1; i--) {
obj = &pmap->pm_obj[i - 2];
off = ptp_va2o(va, i - 1);
PMAP_DUMMY_LOCK(pmap);
pt->pg[i] = uvm_pagelookup(obj, off);
if (pt->pg[i] == NULL) {
pt->pg[i] = uvm_pagealloc(obj, off, NULL, aflags);
pt->alloced[i] = (pt->pg[i] != NULL);
} else if (pt->pg[i]->wire_count == 0) {
LIST_REMOVE(pt->pg[i], mdpage.mp_pp.pp_link);
pt->alloced[i] = true;
}
PMAP_DUMMY_UNLOCK(pmap);
if (pt->pg[i] == NULL) {
pmap_unget_ptp(pmap, pt);
return ENOMEM;
} else if (pt->alloced[i]) {
pt->pg[i]->uanon = (struct vm_anon *)(vaddr_t)~0L;
rb_tree_init(&VM_PAGE_TO_PP(pt->pg[i])->pp_rb,
&pmap_rbtree_ops);
PMAP_CHECK_PP(VM_PAGE_TO_PP(pt->pg[i]));
}
}
ptp = pt->pg[2];
KASSERT(ptp != NULL);
*resultp = ptp;
pmap->pm_ptphint[0] = ptp;
return 0;
}
static void
pmap_install_ptp(struct pmap *pmap, struct pmap_ptparray *pt, vaddr_t va,
pd_entry_t * const *pdes)
{
struct vm_page *ptp;
unsigned long index;
pd_entry_t *pva;
paddr_t pa;
int i;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
for (i = PTP_LEVELS; i > 1; i--) {
index = pl_i(va, i);
pva = pdes[i - 2];
if (pmap_valid_entry(pva[index])) {
KASSERT(!pt->alloced[i]);
continue;
}
ptp = pt->pg[i];
ptp->flags &= ~PG_BUSY;
ptp->wire_count = 1;
pmap->pm_ptphint[i - 2] = ptp;
pa = VM_PAGE_TO_PHYS(ptp);
pmap_pte_set(&pva[index], (pd_entry_t)
(pmap_pa2pte(pa) | PTE_U | PTE_W | PTE_P));
#if defined(XENPV) && defined(__x86_64__)
if (i == PTP_LEVELS) {
xen_kpm_sync(pmap, index);
}
#elif defined(SVS)
if (svs_enabled && i == PTP_LEVELS &&
pmap_is_user(pmap)) {
svs_pmap_sync(pmap, index);
}
#endif
pmap_pte_flush();
pmap_stats_update(pmap, 1, 0);
if (i < PTP_LEVELS) {
pt->pg[i + 1]->wire_count++;
}
}
}
static void
pmap_unget_ptp(struct pmap *pmap, struct pmap_ptparray *pt)
{
int i;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
for (i = PTP_LEVELS; i > 1; i--) {
if (!pt->alloced[i]) {
continue;
}
KASSERT(pt->pg[i]->wire_count == 0);
PMAP_CHECK_PP(VM_PAGE_TO_PP(pt->pg[i]));
pmap_freepage(pmap, pt->pg[i], i - 1);
}
}
static void
pmap_pdp_init(pd_entry_t *pdir)
{
paddr_t pdirpa = 0;
vaddr_t object;
int i;
#if !defined(XENPV) || !defined(__x86_64__)
int npde;
#endif
#ifdef XENPV
int s;
#endif
memset(PAGE_ALIGNED(pdir), 0, PDP_SIZE * PAGE_SIZE);
#if defined(XENPV) && defined(__x86_64__)
(void)pmap_extract(pmap_kernel(), (vaddr_t)pdir, &pdirpa);
pdir[PDIR_SLOT_PTE] = pmap_pa2pte(pdirpa);
pdir[PDIR_SLOT_KERN + nkptp[PTP_LEVELS - 1] - 1] =
(pd_entry_t)-1 & PTE_FRAME;
#else
object = (vaddr_t)pdir;
for (i = 0; i < PDP_SIZE; i++, object += PAGE_SIZE) {
(void)pmap_extract(pmap_kernel(), object, &pdirpa);
pdir[PDIR_SLOT_PTE + i] = pmap_pa2pte(pdirpa) | PTE_P |
pmap_pg_nx;
#ifndef XENPV
pdir[PDIR_SLOT_PTE + i] |= PTE_W;
#endif
}
npde = nkptp[PTP_LEVELS - 1];
memcpy(&pdir[PDIR_SLOT_KERN], &PDP_BASE[PDIR_SLOT_KERN],
npde * sizeof(pd_entry_t));
if (VM_MIN_KERNEL_ADDRESS != KERNBASE) {
int idx = pl_i(KERNBASE, PTP_LEVELS);
pdir[idx] = PDP_BASE[idx];
}
#ifdef __HAVE_PCPU_AREA
pdir[PDIR_SLOT_PCPU] = PDP_BASE[PDIR_SLOT_PCPU];
#endif
#ifdef __HAVE_DIRECT_MAP
slotspace_copy(SLAREA_DMAP, pdir, PDP_BASE);
#endif
#ifdef KASAN
slotspace_copy(SLAREA_ASAN, pdir, PDP_BASE);
#endif
#ifdef KMSAN
slotspace_copy(SLAREA_MSAN, pdir, PDP_BASE);
#endif
#endif
#ifdef XENPV
s = splvm();
object = (vaddr_t)pdir;
pmap_protect(pmap_kernel(), object, object + (PAGE_SIZE * PDP_SIZE),
VM_PROT_READ);
pmap_update(pmap_kernel());
for (i = 0; i < PDP_SIZE; i++, object += PAGE_SIZE) {
#ifdef PAE
if (i == l2tol3(PDIR_SLOT_PTE))
continue;
#endif
(void) pmap_extract(pmap_kernel(), object, &pdirpa);
#ifdef __x86_64__
xpq_queue_pin_l4_table(xpmap_ptom_masked(pdirpa));
#else
xpq_queue_pin_l2_table(xpmap_ptom_masked(pdirpa));
#endif
}
#ifdef PAE
object = ((vaddr_t)pdir) + PAGE_SIZE * l2tol3(PDIR_SLOT_PTE);
(void)pmap_extract(pmap_kernel(), object, &pdirpa);
xpq_queue_pin_l2_table(xpmap_ptom_masked(pdirpa));
#endif
splx(s);
#endif
}
static void
pmap_pdp_fini(pd_entry_t *pdir)
{
#ifdef XENPV
paddr_t pdirpa = 0;
vaddr_t object = (vaddr_t)pdir;
int i;
int s = splvm();
pt_entry_t *pte;
for (i = 0; i < PDP_SIZE; i++, object += PAGE_SIZE) {
(void) pmap_extract(pmap_kernel(), object, &pdirpa);
xpq_queue_unpin_table(xpmap_ptom_masked(pdirpa));
}
object = (vaddr_t)pdir;
for (i = 0; i < PDP_SIZE; i++, object += PAGE_SIZE) {
pte = kvtopte(object);
pmap_pte_set(pte, *pte | PTE_W);
xen_bcast_invlpg((vaddr_t)object);
}
splx(s);
#endif
}
#ifdef PAE
static void *
pmap_pdp_alloc(struct pool *pp, int flags)
{
return (void *)uvm_km_alloc(kernel_map,
PAGE_SIZE * PDP_SIZE, PAGE_SIZE * PDP_SIZE,
((flags & PR_WAITOK) ? UVM_KMF_WAITVA
: UVM_KMF_NOWAIT | UVM_KMF_TRYLOCK) |
UVM_KMF_WIRED);
}
static void
pmap_pdp_free(struct pool *pp, void *v)
{
uvm_km_free(kernel_map, (vaddr_t)v, PAGE_SIZE * PDP_SIZE,
UVM_KMF_WIRED);
}
#endif
static int
pmap_ctor(void *arg, void *obj, int flags)
{
struct pmap *pmap = obj;
pt_entry_t p;
int i;
KASSERT((flags & PR_WAITOK) != 0);
mutex_init(&pmap->pm_lock, MUTEX_DEFAULT, IPL_NONE);
rw_init(&pmap->pm_dummy_lock);
kcpuset_create(&pmap->pm_cpus, true);
kcpuset_create(&pmap->pm_kernel_cpus, true);
#ifdef XENPV
kcpuset_create(&pmap->pm_xen_ptp_cpus, true);
#endif
LIST_INIT(&pmap->pm_gc_ptp);
pmap->pm_pve = NULL;
LIST_INIT(&pmap->pm_pvp_full);
LIST_INIT(&pmap->pm_pvp_part);
LIST_INIT(&pmap->pm_pvp_empty);
pmap->pm_pdir = pool_get(&pmap_pdp_pool, PR_WAITOK);
for (;;) {
pmap_pdp_init(pmap->pm_pdir);
mutex_enter(&pmaps_lock);
p = pmap->pm_pdir[PDIR_SLOT_KERN + nkptp[PTP_LEVELS - 1] - 1];
if (__predict_true(p != 0)) {
break;
}
mutex_exit(&pmaps_lock);
}
for (i = 0; i < PDP_SIZE; i++)
pmap->pm_pdirpa[i] =
pmap_pte2pa(pmap->pm_pdir[PDIR_SLOT_PTE + i]);
LIST_INSERT_HEAD(&pmaps, pmap, pm_list);
mutex_exit(&pmaps_lock);
return 0;
}
static void
pmap_dtor(void *arg, void *obj)
{
struct pmap *pmap = obj;
mutex_enter(&pmaps_lock);
LIST_REMOVE(pmap, pm_list);
mutex_exit(&pmaps_lock);
pmap_pdp_fini(pmap->pm_pdir);
pool_put(&pmap_pdp_pool, pmap->pm_pdir);
mutex_destroy(&pmap->pm_lock);
rw_destroy(&pmap->pm_dummy_lock);
kcpuset_destroy(pmap->pm_cpus);
kcpuset_destroy(pmap->pm_kernel_cpus);
#ifdef XENPV
kcpuset_destroy(pmap->pm_xen_ptp_cpus);
#endif
}
struct pmap *
pmap_create(void)
{
struct pmap *pmap;
int i;
pmap = pool_cache_get(&pmap_cache, PR_WAITOK);
for (i = 0; i < PTP_LEVELS - 1; i++) {
uvm_obj_init(&pmap->pm_obj[i], &pmap_pager, false, 1);
uvm_obj_setlock(&pmap->pm_obj[i], &pmap->pm_dummy_lock);
pmap->pm_ptphint[i] = NULL;
}
pmap->pm_stats.wired_count = 0;
pmap->pm_stats.resident_count = PDP_SIZE;
#if !defined(__x86_64__)
pmap->pm_hiexec = 0;
#endif
pmap->pm_enter = NULL;
pmap->pm_extract = NULL;
pmap->pm_remove = NULL;
pmap->pm_sync_pv = NULL;
pmap->pm_pp_remove_ent = NULL;
pmap->pm_write_protect = NULL;
pmap->pm_unwire = NULL;
pmap->pm_tlb_flush = NULL;
pmap->pm_data = NULL;
pmap->pm_ldt = NULL;
pmap->pm_ldt_sel = GSYSSEL(GLDT_SEL, SEL_KPL);
return pmap;
}
static void
pmap_check_ptps(struct pmap *pmap)
{
int i;
for (i = 0; i < PTP_LEVELS - 1; i++) {
KASSERTMSG(pmap->pm_obj[i].uo_npages == 0,
"pmap %p level %d still has %d pages",
pmap, i, (int)pmap->pm_obj[i].uo_npages);
}
}
static void
pmap_check_inuse(struct pmap *pmap)
{
#ifdef DEBUG
CPU_INFO_ITERATOR cii;
struct cpu_info *ci;
for (CPU_INFO_FOREACH(cii, ci)) {
if (ci->ci_pmap == pmap)
panic("destroying pmap being used");
#if defined(XENPV) && defined(__x86_64__)
for (int i = 0; i < PDIR_SLOT_USERLIM; i++) {
if (pmap->pm_pdir[i] != 0 &&
ci->ci_kpm_pdir[i] == pmap->pm_pdir[i]) {
printf("pmap_destroy(%p) pmap_kernel %p "
"curcpu %d cpu %d ci_pmap %p "
"ci->ci_kpm_pdir[%d]=%" PRIx64
" pmap->pm_pdir[%d]=%" PRIx64 "\n",
pmap, pmap_kernel(), curcpu()->ci_index,
ci->ci_index, ci->ci_pmap,
i, ci->ci_kpm_pdir[i],
i, pmap->pm_pdir[i]);
panic("%s: used pmap", __func__);
}
}
#endif
}
#endif
}
void
pmap_destroy(struct pmap *pmap)
{
int i;
if (atomic_dec_uint_nv(&pmap->pm_obj[0].uo_refs) > 0) {
return;
}
pmap_check_inuse(pmap);
mutex_enter(&pmap->pm_lock);
if (pmap->pm_pve != NULL) {
pmap_free_pv(pmap, pmap->pm_pve);
pmap->pm_pve = NULL;
}
pmap_drain_pv(pmap);
mutex_exit(&pmap->pm_lock);
pmap_update(pmap);
pmap_check_ptps(pmap);
KASSERT(LIST_EMPTY(&pmap->pm_gc_ptp));
#ifdef USER_LDT
if (pmap->pm_ldt != NULL) {
mutex_enter(&cpu_lock);
ldt_free(pmap->pm_ldt_sel);
mutex_exit(&cpu_lock);
uvm_km_free(kernel_map, (vaddr_t)pmap->pm_ldt,
MAX_USERLDT_SIZE, UVM_KMF_WIRED);
}
#endif
for (i = 0; i < PTP_LEVELS - 1; i++) {
uvm_obj_destroy(&pmap->pm_obj[i], false);
}
kcpuset_zero(pmap->pm_cpus);
kcpuset_zero(pmap->pm_kernel_cpus);
#ifdef XENPV
kcpuset_zero(pmap->pm_xen_ptp_cpus);
#endif
KASSERT(LIST_EMPTY(&pmap->pm_pvp_full));
KASSERT(LIST_EMPTY(&pmap->pm_pvp_part));
KASSERT(LIST_EMPTY(&pmap->pm_pvp_empty));
pmap_check_ptps(pmap);
if (__predict_false(pmap->pm_enter != NULL)) {
pool_cache_destruct_object(&pmap_cache, pmap);
} else {
pool_cache_put(&pmap_cache, pmap);
}
}
static void
pmap_zap_ptp(struct pmap *pmap, struct vm_page *ptp, pt_entry_t *pte,
vaddr_t startva, vaddr_t blkendva)
{
#ifndef XENPV
struct pv_entry *pve;
struct vm_page *pg;
struct pmap_page *pp;
pt_entry_t opte;
rb_tree_t *tree;
vaddr_t va;
int wired;
uint8_t oattrs;
u_int cnt;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
KASSERT(pmap != pmap_kernel());
KASSERT(ptp->wire_count > 1);
KASSERT(ptp->wire_count - 1 <= PAGE_SIZE / sizeof(pt_entry_t));
tree = &VM_PAGE_TO_PP(ptp)->pp_rb;
pve = RB_TREE_MIN(tree);
wired = 0;
va = (vaddr_t)ptp->uanon;
pte += ((va - startva) >> PAGE_SHIFT);
for (cnt = ptp->wire_count; cnt > 1; pte++, va += PAGE_SIZE) {
opte = *pte;
if (!pmap_valid_entry(opte)) {
continue;
}
cnt--;
wired -= (opte & PTE_WIRED);
if ((opte & PTE_PVLIST) == 0) {
#ifndef DOM0OPS
KASSERTMSG((PHYS_TO_VM_PAGE(pmap_pte2pa(opte)) == NULL),
"managed page without PTE_PVLIST for %#"
PRIxVADDR, va);
KASSERTMSG((pmap_pv_tracked(pmap_pte2pa(opte)) == NULL),
"pv-tracked page without PTE_PVLIST for %#"
PRIxVADDR, va);
#endif
KASSERT(pmap_treelookup_pv(pmap, ptp, (ptp != NULL ?
&VM_PAGE_TO_PP(ptp)->pp_rb : &pmap_kernel_rb),
va) == NULL);
continue;
}
oattrs = pmap_pte_to_pp_attrs(opte);
if (pve != NULL && pve->pve_pte.pte_va == va) {
pp = pve->pve_pp;
KASSERT(pve->pve_pte.pte_ptp == ptp);
KASSERT(pp->pp_pte.pte_ptp != ptp ||
pp->pp_pte.pte_va != va);
mutex_spin_enter(&pp->pp_lock);
pp->pp_attrs |= oattrs;
LIST_REMOVE(pve, pve_list);
mutex_spin_exit(&pp->pp_lock);
pmap_free_pv(pmap, pve);
pve = RB_TREE_NEXT(tree, pve);
continue;
}
if ((pg = PHYS_TO_VM_PAGE(pmap_pte2pa(opte))) != NULL) {
pp = VM_PAGE_TO_PP(pg);
} else if ((pp = pmap_pv_tracked(pmap_pte2pa(opte))) == NULL) {
paddr_t pa = pmap_pte2pa(opte);
panic("%s: PTE_PVLIST with pv-untracked page"
" va = %#"PRIxVADDR"pa = %#"PRIxPADDR
"(%#"PRIxPADDR")", __func__, va, pa, atop(pa));
}
mutex_spin_enter(&pp->pp_lock);
KASSERT(pp->pp_pte.pte_ptp == ptp);
KASSERT(pp->pp_pte.pte_va == va);
pp->pp_attrs |= oattrs;
pp->pp_pte.pte_ptp = NULL;
pp->pp_pte.pte_va = 0;
mutex_spin_exit(&pp->pp_lock);
}
pmap_stats_update(pmap, -(ptp->wire_count - 1), wired / PTE_WIRED);
ptp->wire_count = 1;
#ifdef DIAGNOSTIC
rb_tree_init(tree, &pmap_rbtree_ops);
#endif
#else
pmap_remove_ptes(pmap, ptp, (vaddr_t)pte, startva, blkendva);
#endif
}
bool
pmap_remove_all(struct pmap *pmap)
{
struct vm_page *ptps[32];
vaddr_t va, blkendva;
struct pmap *pmap2;
pt_entry_t *ptes;
pd_entry_t pde __diagused;
pd_entry_t * const *pdes;
int lvl __diagused, i, n;
if (pmap->pm_remove != NULL) {
return false;
}
for (;;) {
mutex_enter(&pmap->pm_lock);
n = radix_tree_gang_lookup_node(&pmap->pm_obj[0].uo_pages, 0,
(void **)ptps, __arraycount(ptps), false);
if (n == 0) {
mutex_exit(&pmap->pm_lock);
break;
}
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
for (i = 0; i < n; i++) {
if (ptps[i]->wire_count == 0) {
continue;
}
va = ptps[i]->offset * PAGE_SIZE / sizeof(pt_entry_t);
blkendva = x86_round_pdr(va + 1);
KASSERT(pmap_pdes_valid(va, pdes, &pde, &lvl));
KASSERT(lvl == 1);
KASSERT(pmap_find_ptp(pmap, va, 1) == ptps[i]);
pmap_zap_ptp(pmap, ptps[i], &ptes[pl1_i(va)], va,
blkendva);
KASSERT(ptps[i]->wire_count == 1);
pmap_free_ptp(pmap, ptps[i], va, ptes, pdes);
}
pmap_unmap_ptes(pmap, pmap2);
pmap_drain_pv(pmap);
pmap_tlb_shootdown(pmap, -1L, 0, TLBSHOOT_REMOVE_ALL);
mutex_exit(&pmap->pm_lock);
pmap_update(pmap);
preempt_point();
}
pmap_check_ptps(pmap);
KASSERTMSG(pmap->pm_stats.resident_count == PDP_SIZE,
"pmap %p not empty", pmap);
return true;
}
#if defined(PMAP_FORK)
void
pmap_fork(struct pmap *pmap1, struct pmap *pmap2)
{
#ifdef USER_LDT
union descriptor *new_ldt;
int sel;
if (__predict_true(pmap1->pm_ldt == NULL)) {
return;
}
retry:
if (pmap1->pm_ldt != NULL) {
new_ldt = (union descriptor *)uvm_km_alloc(kernel_map,
MAX_USERLDT_SIZE, 0, UVM_KMF_WIRED);
if (new_ldt == NULL) {
printf("WARNING: %s: unable to allocate LDT space\n",
__func__);
return;
}
mutex_enter(&cpu_lock);
sel = ldt_alloc(new_ldt, MAX_USERLDT_SIZE);
if (sel == -1) {
mutex_exit(&cpu_lock);
uvm_km_free(kernel_map, (vaddr_t)new_ldt,
MAX_USERLDT_SIZE, UVM_KMF_WIRED);
printf("WARNING: %s: unable to allocate LDT selector\n",
__func__);
return;
}
} else {
new_ldt = NULL;
sel = -1;
mutex_enter(&cpu_lock);
}
if (pmap1->pm_ldt != NULL) {
if (new_ldt == NULL) {
mutex_exit(&cpu_lock);
goto retry;
}
memcpy(new_ldt, pmap1->pm_ldt, MAX_USERLDT_SIZE);
pmap2->pm_ldt = new_ldt;
pmap2->pm_ldt_sel = sel;
mutex_exit(&cpu_lock);
} else {
if (new_ldt != NULL) {
ldt_free(sel);
mutex_exit(&cpu_lock);
uvm_km_free(kernel_map, (vaddr_t)new_ldt,
MAX_USERLDT_SIZE, UVM_KMF_WIRED);
return;
}
mutex_exit(&cpu_lock);
}
#endif
}
#endif
#ifdef USER_LDT
static void
pmap_ldt_xcall(void *arg1, void *arg2)
{
struct pmap *pm;
kpreempt_disable();
pm = arg1;
if (curcpu()->ci_pmap == pm) {
#if defined(SVS)
if (svs_enabled) {
svs_ldt_sync(pm);
} else
#endif
lldt(pm->pm_ldt_sel);
}
kpreempt_enable();
}
void
pmap_ldt_sync(struct pmap *pm)
{
uint64_t where;
KASSERT(mutex_owned(&cpu_lock));
pmap_ldt_evcnt.ev_count++;
where = xc_broadcast(0, pmap_ldt_xcall, pm, NULL);
xc_wait(where);
}
void
pmap_ldt_cleanup(struct lwp *l)
{
pmap_t pmap = l->l_proc->p_vmspace->vm_map.pmap;
union descriptor *ldt;
int sel;
if (__predict_true(pmap->pm_ldt == NULL)) {
return;
}
mutex_enter(&cpu_lock);
if (pmap->pm_ldt != NULL) {
sel = pmap->pm_ldt_sel;
ldt = pmap->pm_ldt;
pmap->pm_ldt_sel = GSYSSEL(GLDT_SEL, SEL_KPL);
pmap->pm_ldt = NULL;
pmap_ldt_sync(pmap);
ldt_free(sel);
uvm_km_free(kernel_map, (vaddr_t)ldt, MAX_USERLDT_SIZE,
UVM_KMF_WIRED);
}
mutex_exit(&cpu_lock);
}
#endif
void
pmap_activate(struct lwp *l)
{
struct cpu_info *ci;
struct pmap *pmap = vm_map_pmap(&l->l_proc->p_vmspace->vm_map);
KASSERT(kpreempt_disabled());
ci = curcpu();
if (l != ci->ci_curlwp)
return;
KASSERT(ci->ci_want_pmapload == 0);
KASSERT(ci->ci_tlbstate != TLBSTATE_VALID);
if (pmap == pmap_kernel()) {
ci->ci_want_pmapload = 0;
return;
}
ci->ci_want_pmapload = 1;
}
#if defined(XENPV) && defined(__x86_64__)
#define KASSERT_PDIRPA(pmap) \
KASSERT(pmap_pdirpa(pmap, 0) == ci->ci_xen_current_user_pgd || \
pmap == pmap_kernel())
#elif defined(PAE)
#define KASSERT_PDIRPA(pmap) \
KASSERT(pmap_pdirpa(pmap, 0) == pmap_pte2pa(ci->ci_pae_l3_pdir[0]))
#elif !defined(XENPV)
#define KASSERT_PDIRPA(pmap) \
KASSERT(pmap_pdirpa(pmap, 0) == pmap_pte2pa(rcr3()))
#else
#define KASSERT_PDIRPA(pmap) KASSERT(true)
#endif
static void
pmap_reactivate(struct pmap *pmap)
{
struct cpu_info * const ci = curcpu();
const cpuid_t cid = cpu_index(ci);
KASSERT(kpreempt_disabled());
KASSERT_PDIRPA(pmap);
ci->ci_tlbstate = TLBSTATE_VALID;
KASSERT(kcpuset_isset(pmap->pm_kernel_cpus, cid));
if (__predict_true(kcpuset_isset(pmap->pm_cpus, cid))) {
} else {
kcpuset_atomic_set(pmap->pm_cpus, cid);
tlbflush();
}
}
void
pmap_load(void)
{
struct cpu_info *ci;
struct pmap *pmap, *oldpmap;
struct lwp *l;
uint64_t pctr;
int ilevel __diagused;
u_long psl __diagused;
kpreempt_disable();
retry:
ci = curcpu();
if (!ci->ci_want_pmapload) {
kpreempt_enable();
return;
}
l = ci->ci_curlwp;
pctr = lwp_pctr();
__insn_barrier();
KASSERTMSG((ilevel = ci->ci_ilevel) < IPL_HIGH, "ilevel=%d", ilevel);
#ifdef XENPV
KASSERTMSG((psl = x86_read_psl()) == 0, "psl=0x%lx", psl);
#else
KASSERTMSG(((psl = x86_read_psl()) & PSL_I) != 0, "psl=0x%lx", psl);
#endif
KASSERT(l != NULL);
pmap = vm_map_pmap(&l->l_proc->p_vmspace->vm_map);
KASSERT(pmap != pmap_kernel());
oldpmap = ci->ci_pmap;
if (pmap == oldpmap) {
pmap_reactivate(pmap);
ci->ci_want_pmapload = 0;
kpreempt_enable();
return;
}
pmap_reference(pmap);
pmap_load1(l, pmap, oldpmap);
ci->ci_want_pmapload = 0;
pmap_destroy(oldpmap);
__insn_barrier();
if (lwp_pctr() != pctr) {
goto retry;
}
kpreempt_enable();
}
static void
pmap_load1(struct lwp *l, struct pmap *pmap, struct pmap *oldpmap)
{
struct cpu_info *ci;
struct pcb *pcb;
cpuid_t cid;
KASSERT(kpreempt_disabled());
pcb = lwp_getpcb(l);
ci = l->l_cpu;
cid = cpu_index(ci);
kcpuset_atomic_clear(oldpmap->pm_cpus, cid);
kcpuset_atomic_clear(oldpmap->pm_kernel_cpus, cid);
KASSERT_PDIRPA(oldpmap);
KASSERT(!kcpuset_isset(pmap->pm_cpus, cid));
KASSERT(!kcpuset_isset(pmap->pm_kernel_cpus, cid));
ci->ci_tlbstate = TLBSTATE_VALID;
kcpuset_atomic_set(pmap->pm_cpus, cid);
kcpuset_atomic_set(pmap->pm_kernel_cpus, cid);
ci->ci_pmap = pmap;
#ifdef PAE
pcb->pcb_cr3 = ci->ci_pae_l3_pdirpa;
#else
pcb->pcb_cr3 = pmap_pdirpa(pmap, 0);
#endif
#ifdef i386
#ifndef XENPV
ci->ci_tss->tss.tss_ldt = pmap->pm_ldt_sel;
ci->ci_tss->tss.tss_cr3 = pcb->pcb_cr3;
#endif
#endif
#if defined(SVS) && defined(USER_LDT)
if (svs_enabled) {
svs_ldt_sync(pmap);
} else
#endif
lldt(pmap->pm_ldt_sel);
cpu_load_pmap(pmap, oldpmap);
}
void
pmap_deactivate(struct lwp *l)
{
struct pmap *pmap;
struct cpu_info *ci;
KASSERT(kpreempt_disabled());
if (l != curlwp) {
return;
}
pmap_tlb_shootnow();
ci = curcpu();
if (ci->ci_want_pmapload) {
KASSERT(vm_map_pmap(&l->l_proc->p_vmspace->vm_map)
!= pmap_kernel());
KASSERT(vm_map_pmap(&l->l_proc->p_vmspace->vm_map)
!= ci->ci_pmap || ci->ci_tlbstate != TLBSTATE_VALID);
ci->ci_want_pmapload = 0;
return;
}
pmap = vm_map_pmap(&l->l_proc->p_vmspace->vm_map);
if (pmap == pmap_kernel()) {
return;
}
KASSERT_PDIRPA(pmap);
KASSERT(ci->ci_pmap == pmap);
KASSERT(ci->ci_tlbstate == TLBSTATE_VALID);
ci->ci_tlbstate = TLBSTATE_LAZY;
}
#ifdef EFI_RUNTIME
extern struct pmap *efi_runtime_pmap;
bool
pmap_is_user(struct pmap *pmap)
{
KASSERT(pmap != pmap_kernel());
return (pmap != efi_runtime_pmap);
}
void *
pmap_activate_sync(struct pmap *pmap)
{
struct cpu_info *ci = curcpu();
struct pmap *oldpmap = ci->ci_pmap;
unsigned cid = cpu_index(ci);
KASSERT(kpreempt_disabled());
KASSERT(pmap != pmap_kernel());
KASSERT(!kcpuset_isset(pmap->pm_cpus, cid));
KASSERT(!kcpuset_isset(pmap->pm_kernel_cpus, cid));
if (oldpmap) {
KASSERT_PDIRPA(oldpmap);
kcpuset_atomic_clear(oldpmap->pm_cpus, cid);
kcpuset_atomic_clear(oldpmap->pm_kernel_cpus, cid);
}
ci->ci_tlbstate = TLBSTATE_VALID;
kcpuset_atomic_set(pmap->pm_cpus, cid);
kcpuset_atomic_set(pmap->pm_kernel_cpus, cid);
ci->ci_pmap = pmap;
#if defined(SVS) && defined(USER_LDT)
if (svs_enabled) {
svs_ldt_sync(pmap);
} else
#endif
lldt(pmap->pm_ldt_sel);
cpu_load_pmap(pmap, oldpmap);
return oldpmap;
}
void
pmap_deactivate_sync(struct pmap *pmap, void *cookie)
{
struct cpu_info *ci = curcpu();
struct pmap *oldpmap = cookie;
unsigned cid = cpu_index(ci);
KASSERT(kpreempt_disabled());
KASSERT(pmap != pmap_kernel());
KASSERT(ci->ci_pmap == pmap);
KASSERT_PDIRPA(pmap);
KASSERT(kcpuset_isset(pmap->pm_cpus, cid));
KASSERT(kcpuset_isset(pmap->pm_kernel_cpus, cid));
pmap_tlb_shootnow();
kcpuset_atomic_clear(pmap->pm_cpus, cid);
kcpuset_atomic_clear(pmap->pm_kernel_cpus, cid);
ci->ci_tlbstate = TLBSTATE_VALID;
ci->ci_pmap = oldpmap;
if (oldpmap) {
kcpuset_atomic_set(oldpmap->pm_cpus, cid);
kcpuset_atomic_set(oldpmap->pm_kernel_cpus, cid);
#if defined(SVS) && defined(USER_LDT)
if (svs_enabled) {
svs_ldt_sync(oldpmap);
} else
#endif
lldt(oldpmap->pm_ldt_sel);
cpu_load_pmap(oldpmap, pmap);
} else {
lcr3(pmap_pdirpa(pmap_kernel(), 0));
}
}
#endif
bool
pmap_pdes_valid(vaddr_t va, pd_entry_t * const *pdes, pd_entry_t *lastpde,
int *lastlvl)
{
unsigned long index;
pd_entry_t pde;
int i;
for (i = PTP_LEVELS; i > 1; i--) {
index = pl_i(va, i);
pde = pdes[i - 2][index];
if ((pde & PTE_P) == 0) {
*lastlvl = i;
return false;
}
if (pde & PTE_PS)
break;
}
if (lastpde != NULL)
*lastpde = pde;
*lastlvl = i;
return true;
}
bool
pmap_extract(struct pmap *pmap, vaddr_t va, paddr_t *pap)
{
pt_entry_t *ptes, pte;
pd_entry_t pde;
pd_entry_t * const *pdes;
struct pmap *pmap2;
paddr_t pa;
bool rv;
int lvl;
if (__predict_false(pmap->pm_extract != NULL)) {
return (*pmap->pm_extract)(pmap, va, pap);
}
#ifdef __HAVE_DIRECT_MAP
if (va >= PMAP_DIRECT_BASE && va < PMAP_DIRECT_END) {
if (pap != NULL) {
*pap = PMAP_DIRECT_UNMAP(va);
}
return true;
}
#endif
rv = false;
pa = 0;
if (pmap != pmap_kernel()) {
mutex_enter(&pmap->pm_lock);
}
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
if (pmap_pdes_valid(va, pdes, &pde, &lvl)) {
if (lvl == 2) {
pa = (pde & PTE_LGFRAME) | (va & (NBPD_L2 - 1));
rv = true;
} else {
KASSERT(lvl == 1);
pte = ptes[pl1_i(va)];
if (__predict_true((pte & PTE_P) != 0)) {
pa = pmap_pte2pa(pte) | (va & (NBPD_L1 - 1));
rv = true;
}
}
}
pmap_unmap_ptes(pmap, pmap2);
if (pmap != pmap_kernel()) {
mutex_exit(&pmap->pm_lock);
}
if (pap != NULL) {
*pap = pa;
}
return rv;
}
paddr_t
vtophys(vaddr_t va)
{
paddr_t pa;
if (pmap_extract(pmap_kernel(), va, &pa) == true)
return pa;
return 0;
}
__strict_weak_alias(pmap_extract_ma, pmap_extract);
#ifdef XENPV
paddr_t
vtomach(vaddr_t va)
{
paddr_t pa;
if (pmap_extract_ma(pmap_kernel(), va, &pa) == true)
return pa;
return 0;
}
#endif
void
pmap_virtual_space(vaddr_t *startp, vaddr_t *endp)
{
*startp = virtual_avail;
*endp = virtual_end;
}
void
pmap_zero_page(paddr_t pa)
{
#if defined(__HAVE_DIRECT_MAP)
memset(PAGE_ALIGNED(PMAP_DIRECT_MAP(pa)), 0, PAGE_SIZE);
#else
#if defined(XENPV)
if (XEN_VERSION_SUPPORTED(3, 4)) {
xen_pagezero(pa);
return;
}
#endif
struct cpu_info *ci;
pt_entry_t *zpte;
vaddr_t zerova;
const pd_entry_t pteflags = PTE_P | PTE_W | pmap_pg_nx | PTE_D | PTE_A;
kpreempt_disable();
ci = curcpu();
zerova = ci->vpage[VPAGE_ZER];
zpte = ci->vpage_pte[VPAGE_ZER];
KASSERTMSG(!*zpte, "pmap_zero_page: lock botch");
pmap_pte_set(zpte, pmap_pa2pte(pa) | pteflags);
pmap_pte_flush();
pmap_update_pg(zerova);
memset(PAGE_ALIGNED(zerova), 0, PAGE_SIZE);
#if defined(DIAGNOSTIC) || defined(XENPV)
pmap_pte_set(zpte, 0);
pmap_pte_flush();
#endif
kpreempt_enable();
#endif
}
void
pmap_copy_page(paddr_t srcpa, paddr_t dstpa)
{
#if defined(__HAVE_DIRECT_MAP)
vaddr_t srcva = PMAP_DIRECT_MAP(srcpa);
vaddr_t dstva = PMAP_DIRECT_MAP(dstpa);
memcpy(PAGE_ALIGNED(dstva), PAGE_ALIGNED(srcva), PAGE_SIZE);
#else
#if defined(XENPV)
if (XEN_VERSION_SUPPORTED(3, 4)) {
xen_copy_page(srcpa, dstpa);
return;
}
#endif
struct cpu_info *ci;
pt_entry_t *srcpte, *dstpte;
vaddr_t srcva, dstva;
const pd_entry_t pteflags = PTE_P | PTE_W | pmap_pg_nx | PTE_A;
kpreempt_disable();
ci = curcpu();
srcva = ci->vpage[VPAGE_SRC];
dstva = ci->vpage[VPAGE_DST];
srcpte = ci->vpage_pte[VPAGE_SRC];
dstpte = ci->vpage_pte[VPAGE_DST];
KASSERT(*srcpte == 0 && *dstpte == 0);
pmap_pte_set(srcpte, pmap_pa2pte(srcpa) | pteflags);
pmap_pte_set(dstpte, pmap_pa2pte(dstpa) | pteflags | PTE_D);
pmap_pte_flush();
pmap_update_pg(srcva);
pmap_update_pg(dstva);
memcpy(PAGE_ALIGNED(dstva), PAGE_ALIGNED(srcva), PAGE_SIZE);
#if defined(DIAGNOSTIC) || defined(XENPV)
pmap_pte_set(srcpte, 0);
pmap_pte_set(dstpte, 0);
pmap_pte_flush();
#endif
kpreempt_enable();
#endif
}
static pt_entry_t *
pmap_map_ptp(struct vm_page *ptp)
{
#ifdef __HAVE_DIRECT_MAP
return (void *)PMAP_DIRECT_MAP(VM_PAGE_TO_PHYS(ptp));
#else
struct cpu_info *ci;
pt_entry_t *ptppte;
vaddr_t ptpva;
KASSERT(kpreempt_disabled());
#ifndef XENPV
const pd_entry_t pteflags = PTE_P | PTE_W | pmap_pg_nx | PTE_A | PTE_D;
#else
const pd_entry_t pteflags = PTE_P | pmap_pg_nx | PTE_A | PTE_D;
#endif
ci = curcpu();
ptpva = ci->vpage[VPAGE_PTP];
ptppte = ci->vpage_pte[VPAGE_PTP];
pmap_pte_set(ptppte, pmap_pa2pte(VM_PAGE_TO_PHYS(ptp)) | pteflags);
pmap_pte_flush();
pmap_update_pg(ptpva);
return (pt_entry_t *)ptpva;
#endif
}
static void
pmap_unmap_ptp(void)
{
#ifndef __HAVE_DIRECT_MAP
#if defined(DIAGNOSTIC) || defined(XENPV)
struct cpu_info *ci;
pt_entry_t *pte;
KASSERT(kpreempt_disabled());
ci = curcpu();
pte = ci->vpage_pte[VPAGE_PTP];
if (*pte != 0) {
pmap_pte_set(pte, 0);
pmap_pte_flush();
}
#endif
#endif
}
static pt_entry_t *
pmap_map_pte(struct pmap *pmap, struct vm_page *ptp, vaddr_t va)
{
KASSERT(kpreempt_disabled());
if (pmap_is_curpmap(pmap)) {
return &PTE_BASE[pl1_i(va)];
}
KASSERT(ptp != NULL);
return pmap_map_ptp(ptp) + pl1_pi(va);
}
static void
pmap_unmap_pte(void)
{
KASSERT(kpreempt_disabled());
pmap_unmap_ptp();
}
static void
pmap_remove_ptes(struct pmap *pmap, struct vm_page *ptp, vaddr_t ptpva,
vaddr_t startva, vaddr_t endva)
{
pt_entry_t *pte = (pt_entry_t *)ptpva;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
pmap_ptp_range_clip(ptp, &startva, &pte);
while (startva < endva && (ptp == NULL || ptp->wire_count > 1)) {
(void)pmap_remove_pte(pmap, ptp, pte, startva);
startva += PAGE_SIZE;
pte++;
}
}
static bool
pmap_remove_pte(struct pmap *pmap, struct vm_page *ptp, pt_entry_t *pte,
vaddr_t va)
{
struct pv_entry *pve;
struct vm_page *pg;
struct pmap_page *pp;
pt_entry_t opte;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
if (!pmap_valid_entry(*pte)) {
return false;
}
opte = pmap_pte_testset(pte, 0);
if (!pmap_valid_entry(opte)) {
return false;
}
pmap_exec_account(pmap, va, opte, 0);
pmap_stats_update_bypte(pmap, 0, opte);
if (ptp) {
ptp->wire_count--;
if (ptp->wire_count <= 1) {
opte |= PTE_A;
}
}
if ((opte & PTE_A) != 0) {
pmap_tlb_shootdown(pmap, va, opte, TLBSHOOT_REMOVE_PTE);
}
if ((opte & PTE_PVLIST) == 0) {
#ifndef DOM0OPS
KASSERTMSG((PHYS_TO_VM_PAGE(pmap_pte2pa(opte)) == NULL),
"managed page without PTE_PVLIST for %#"PRIxVADDR, va);
KASSERTMSG((pmap_pv_tracked(pmap_pte2pa(opte)) == NULL),
"pv-tracked page without PTE_PVLIST for %#"PRIxVADDR, va);
#endif
KASSERT(pmap_treelookup_pv(pmap, ptp, (ptp != NULL ?
&VM_PAGE_TO_PP(ptp)->pp_rb : &pmap_kernel_rb), va) == NULL);
return true;
}
if ((pg = PHYS_TO_VM_PAGE(pmap_pte2pa(opte))) != NULL) {
pp = VM_PAGE_TO_PP(pg);
} else if ((pp = pmap_pv_tracked(pmap_pte2pa(opte))) == NULL) {
paddr_t pa = pmap_pte2pa(opte);
panic("%s: PTE_PVLIST with pv-untracked page"
" va = %#"PRIxVADDR"pa = %#"PRIxPADDR" (%#"PRIxPADDR")",
__func__, va, pa, atop(pa));
}
pve = pmap_lookup_pv(pmap, ptp, pp, va);
pmap_remove_pv(pmap, pp, ptp, va, pve, pmap_pte_to_pp_attrs(opte));
return true;
}
static void
pmap_remove_locked(struct pmap *pmap, vaddr_t sva, vaddr_t eva)
{
pt_entry_t *ptes;
pd_entry_t pde;
pd_entry_t * const *pdes;
bool result;
vaddr_t blkendva, va = sva;
struct vm_page *ptp;
struct pmap *pmap2;
int lvl;
KASSERT(mutex_owned(&pmap->pm_lock));
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
if (va + PAGE_SIZE == eva) {
if (pmap_pdes_valid(va, pdes, &pde, &lvl)) {
KASSERT(lvl == 1);
if (pmap != pmap_kernel()) {
ptp = pmap_find_ptp(pmap, va, 1);
KASSERTMSG(ptp != NULL,
"%s: unmanaged PTP detected", __func__);
} else {
ptp = NULL;
}
result = pmap_remove_pte(pmap, ptp,
&ptes[pl1_i(va)], va);
if (result && ptp && ptp->wire_count <= 1)
pmap_free_ptp(pmap, ptp, va, ptes, pdes);
}
} else for ( ; va < eva ; va = blkendva) {
blkendva = x86_round_pdr(va+1);
if (blkendva > eva)
blkendva = eva;
if (!pmap_pdes_valid(va, pdes, &pde, &lvl)) {
blkendva = (va & ptp_frames[lvl - 1]) + nbpd[lvl - 1];
continue;
}
KASSERT(lvl == 1);
if (pmap != pmap_kernel()) {
ptp = pmap_find_ptp(pmap, va, 1);
KASSERTMSG(ptp != NULL, "%s: unmanaged PTP detected",
__func__);
} else {
ptp = NULL;
}
pmap_remove_ptes(pmap, ptp, (vaddr_t)&ptes[pl1_i(va)], va,
blkendva);
if (ptp && ptp->wire_count <= 1) {
pmap_free_ptp(pmap, ptp, va, ptes, pdes);
}
}
pmap_unmap_ptes(pmap, pmap2);
pmap_drain_pv(pmap);
}
void
pmap_remove(struct pmap *pmap, vaddr_t sva, vaddr_t eva)
{
if (__predict_false(pmap->pm_remove != NULL)) {
(*pmap->pm_remove)(pmap, sva, eva);
return;
}
mutex_enter(&pmap->pm_lock);
pmap_remove_locked(pmap, sva, eva);
mutex_exit(&pmap->pm_lock);
}
static int
pmap_sync_pv(struct pv_pte *pvpte, paddr_t pa, int clearbits, uint8_t *oattrs,
pt_entry_t *optep)
{
struct pmap *pmap;
struct vm_page *ptp;
vaddr_t va;
pt_entry_t *ptep;
pt_entry_t opte;
pt_entry_t npte;
pt_entry_t expect;
bool need_shootdown;
ptp = pvpte->pte_ptp;
va = pvpte->pte_va;
KASSERT(ptp == NULL || ptp->uobject != NULL);
KASSERT(ptp == NULL || ptp_va2o(va, 1) == ptp->offset);
pmap = ptp_to_pmap(ptp);
KASSERT(kpreempt_disabled());
if (__predict_false(pmap->pm_sync_pv != NULL)) {
return (*pmap->pm_sync_pv)(ptp, va, pa, clearbits, oattrs,
optep);
}
expect = pmap_pa2pte(pa) | PTE_P;
if (clearbits != ~0) {
KASSERT((clearbits & ~(PP_ATTRS_D|PP_ATTRS_A|PP_ATTRS_W)) == 0);
clearbits = pmap_pp_attrs_to_pte(clearbits);
}
ptep = pmap_map_pte(pmap, ptp, va);
do {
opte = *ptep;
KASSERT((opte & (PTE_D | PTE_A)) != PTE_D);
KASSERT((opte & (PTE_A | PTE_P)) != PTE_A);
KASSERT(opte == 0 || (opte & PTE_P) != 0);
if ((opte & (PTE_FRAME | PTE_P)) != expect) {
pmap_unmap_pte();
return EAGAIN;
}
if ((opte & clearbits) == 0) {
need_shootdown = false;
break;
}
need_shootdown = (opte & PTE_A) != 0 &&
!(clearbits == PTE_W && (opte & PTE_D) == 0);
npte = opte & ~clearbits;
if (need_shootdown) {
npte &= ~(PTE_A | PTE_D);
}
KASSERT((npte & (PTE_D | PTE_A)) != PTE_D);
KASSERT((npte & (PTE_A | PTE_P)) != PTE_A);
KASSERT(npte == 0 || (opte & PTE_P) != 0);
} while (pmap_pte_cas(ptep, opte, npte) != opte);
if (need_shootdown) {
pmap_tlb_shootdown(pmap, va, opte, TLBSHOOT_SYNC_PV);
}
pmap_unmap_pte();
*oattrs = pmap_pte_to_pp_attrs(opte);
if (optep != NULL)
*optep = opte;
return 0;
}
static void
pmap_pp_remove_ent(struct pmap *pmap, struct vm_page *ptp, pt_entry_t opte,
vaddr_t va)
{
struct pmap *pmap2;
pt_entry_t *ptes;
pd_entry_t * const *pdes;
KASSERT(mutex_owned(&pmap->pm_lock));
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
pmap_stats_update_bypte(pmap, 0, opte);
ptp->wire_count--;
if (ptp->wire_count <= 1) {
pmap_free_ptp(pmap, ptp, va, ptes, pdes);
}
pmap_unmap_ptes(pmap, pmap2);
}
static void
pmap_pp_remove(struct pmap_page *pp, paddr_t pa)
{
struct pv_pte *pvpte;
struct vm_page *ptp;
uintptr_t sum;
uint8_t oattrs;
bool locked;
sum = (uintptr_t)atomic_load_relaxed(&pp->pp_pte.pte_va);
sum |= (uintptr_t)atomic_load_relaxed(&pp->pp_pte.pte_ptp);
sum |= (uintptr_t)atomic_load_relaxed(&pp->pp_pvlist.lh_first);
if (sum == 0) {
return;
}
kpreempt_disable();
for (;;) {
struct pmap *pmap;
struct pv_entry *pve;
pt_entry_t opte;
vaddr_t va;
mutex_spin_enter(&pp->pp_lock);
if ((pvpte = pv_pte_first(pp)) == NULL) {
mutex_spin_exit(&pp->pp_lock);
break;
}
ptp = pvpte->pte_ptp;
pmap = ptp_to_pmap(ptp);
KASSERT(pmap->pm_obj[0].uo_refs > 0);
if (ptp != NULL) {
pmap_reference(pmap);
}
locked = mutex_tryenter(&pmap->pm_lock);
mutex_spin_exit(&pp->pp_lock);
if (!locked) {
mutex_enter(&pmap->pm_lock);
mutex_exit(&pmap->pm_lock);
if (ptp != NULL) {
pmap_destroy(pmap);
}
continue;
}
va = pvpte->pte_va;
KASSERTMSG(pmap->pm_stats.resident_count > PDP_SIZE,
"va %lx pmap %p ptp %p is empty", va, pmap, ptp);
KASSERTMSG(ptp == NULL || (ptp->flags & PG_FREE) == 0,
"va %lx pmap %p ptp %p is free", va, pmap, ptp);
KASSERTMSG(ptp == NULL || ptp->wire_count > 1,
"va %lx pmap %p ptp %p is empty", va, pmap, ptp);
#ifdef DEBUG
pmap_check_pv(pmap, ptp, pp, pvpte->pte_va, true);
rb_tree_t *tree = (ptp != NULL ?
&VM_PAGE_TO_PP(ptp)->pp_rb : &pmap_kernel_rb);
pve = pmap_treelookup_pv(pmap, ptp, tree, va);
if (pve == NULL) {
KASSERTMSG(&pp->pp_pte == pvpte,
"va %lx pmap %p ptp %p pvpte %p pve %p oops 1",
va, pmap, ptp, pvpte, pve);
} else {
KASSERTMSG(&pve->pve_pte == pvpte,
"va %lx pmap %p ptp %p pvpte %p pve %p oops 2",
va, pmap, ptp, pvpte, pve);
}
#endif
if (pmap_sync_pv(pvpte, pa, ~0, &oattrs, &opte)) {
panic("pmap_pp_remove: mapping not present");
}
pve = pmap_lookup_pv(pmap, ptp, pp, va);
pmap_remove_pv(pmap, pp, ptp, va, pve, oattrs);
if (ptp != NULL) {
KASSERT(pmap != pmap_kernel());
pmap_tlb_shootnow();
if (__predict_false(pmap->pm_pp_remove_ent != NULL)) {
(*pmap->pm_pp_remove_ent)(pmap, ptp, opte, va);
} else {
pmap_pp_remove_ent(pmap, ptp, opte, va);
}
} else {
KASSERT(pmap == pmap_kernel());
pmap_stats_update_bypte(pmap, 0, opte);
}
pmap_tlb_shootnow();
pmap_drain_pv(pmap);
mutex_exit(&pmap->pm_lock);
if (ptp != NULL) {
pmap_destroy(pmap);
}
}
kpreempt_enable();
}
void
pmap_page_remove(struct vm_page *pg)
{
struct pmap_page *pp;
paddr_t pa;
pp = VM_PAGE_TO_PP(pg);
pa = VM_PAGE_TO_PHYS(pg);
pmap_pp_remove(pp, pa);
}
void
pmap_pv_remove(paddr_t pa)
{
struct pmap_page *pp;
pp = pmap_pv_tracked(pa);
if (pp == NULL)
panic("%s: page not pv-tracked: %#"PRIxPADDR, __func__, pa);
pmap_pp_remove(pp, pa);
}
bool
pmap_test_attrs(struct vm_page *pg, unsigned testbits)
{
struct pmap_page *pp;
struct pv_pte *pvpte;
struct pmap *pmap;
uint8_t oattrs;
u_int result;
paddr_t pa;
pp = VM_PAGE_TO_PP(pg);
if ((pp->pp_attrs & testbits) != 0) {
return true;
}
pa = VM_PAGE_TO_PHYS(pg);
startover:
mutex_spin_enter(&pp->pp_lock);
for (pvpte = pv_pte_first(pp); pvpte; pvpte = pv_pte_next(pp, pvpte)) {
if ((pp->pp_attrs & testbits) != 0) {
break;
}
if (pmap_sync_pv(pvpte, pa, 0, &oattrs, NULL)) {
pmap = ptp_to_pmap(pvpte->pte_ptp);
pmap_reference(pmap);
mutex_spin_exit(&pp->pp_lock);
mutex_enter(&pmap->pm_lock);
mutex_exit(&pmap->pm_lock);
pmap_destroy(pmap);
goto startover;
}
pp->pp_attrs |= oattrs;
}
result = pp->pp_attrs & testbits;
mutex_spin_exit(&pp->pp_lock);
return result != 0;
}
static bool
pmap_pp_clear_attrs(struct pmap_page *pp, paddr_t pa, unsigned clearbits)
{
struct pv_pte *pvpte;
struct pmap *pmap;
uint8_t oattrs;
u_int result;
startover:
mutex_spin_enter(&pp->pp_lock);
for (pvpte = pv_pte_first(pp); pvpte; pvpte = pv_pte_next(pp, pvpte)) {
if (pmap_sync_pv(pvpte, pa, clearbits, &oattrs, NULL)) {
pmap = ptp_to_pmap(pvpte->pte_ptp);
pmap_reference(pmap);
mutex_spin_exit(&pp->pp_lock);
mutex_enter(&pmap->pm_lock);
mutex_exit(&pmap->pm_lock);
pmap_destroy(pmap);
goto startover;
}
pp->pp_attrs |= oattrs;
}
result = pp->pp_attrs & clearbits;
pp->pp_attrs &= ~clearbits;
pmap_tlb_shootnow();
mutex_spin_exit(&pp->pp_lock);
return result != 0;
}
bool
pmap_clear_attrs(struct vm_page *pg, unsigned clearbits)
{
struct pmap_page *pp;
paddr_t pa;
pp = VM_PAGE_TO_PP(pg);
pa = VM_PAGE_TO_PHYS(pg);
if ((pg->flags & PG_FAKE) != 0) {
KASSERT(atomic_load_relaxed(&pp->pp_pte.pte_va) == 0);
KASSERT(atomic_load_relaxed(&pp->pp_pte.pte_ptp) == NULL);
KASSERT(atomic_load_relaxed(&pp->pp_pvlist.lh_first) == NULL);
atomic_store_relaxed(&pp->pp_attrs, 0);
return false;
} else {
return pmap_pp_clear_attrs(pp, pa, clearbits);
}
}
bool
pmap_pv_clear_attrs(paddr_t pa, unsigned clearbits)
{
struct pmap_page *pp;
pp = pmap_pv_tracked(pa);
if (pp == NULL)
panic("%s: page not pv-tracked: %#"PRIxPADDR, __func__, pa);
return pmap_pp_clear_attrs(pp, pa, clearbits);
}
void
pmap_write_protect(struct pmap *pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot)
{
pt_entry_t bit_rem, bit_put;
pt_entry_t *ptes;
pt_entry_t * const *pdes;
struct pmap *pmap2;
vaddr_t blockend, va;
int lvl, i;
if (__predict_false(pmap->pm_write_protect != NULL)) {
(*pmap->pm_write_protect)(pmap, sva, eva, prot);
return;
}
bit_rem = 0;
if (!(prot & VM_PROT_WRITE))
bit_rem = PTE_W;
bit_put = 0;
if (!(prot & VM_PROT_EXECUTE))
bit_put = pmap_pg_nx;
sva &= ~PAGE_MASK;
eva &= ~PAGE_MASK;
if (pmap != pmap_kernel()) {
mutex_enter(&pmap->pm_lock);
}
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
for (va = sva ; va < eva; va = blockend) {
pt_entry_t *spte, *epte;
blockend = x86_round_pdr(va + 1);
if (blockend > eva)
blockend = eva;
if (!pmap_pdes_valid(va, pdes, NULL, &lvl)) {
continue;
}
KASSERT(va < VM_MAXUSER_ADDRESS || va >= VM_MAX_ADDRESS);
KASSERT(lvl == 1);
spte = &ptes[pl1_i(va)];
epte = &ptes[pl1_i(blockend)];
for (i = 0; spte < epte; spte++, i++) {
pt_entry_t opte, npte;
do {
opte = *spte;
if (!pmap_valid_entry(opte)) {
goto next;
}
npte = (opte & ~bit_rem) | bit_put;
} while (pmap_pte_cas(spte, opte, npte) != opte);
if ((opte & PTE_D) != 0) {
vaddr_t tva = va + x86_ptob(i);
pmap_tlb_shootdown(pmap, tva, opte,
TLBSHOOT_WRITE_PROTECT);
}
next:;
}
}
pmap_unmap_ptes(pmap, pmap2);
if (pmap != pmap_kernel()) {
mutex_exit(&pmap->pm_lock);
}
}
void
pmap_unwire(struct pmap *pmap, vaddr_t va)
{
pt_entry_t *ptes, *ptep, opte;
pd_entry_t * const *pdes;
struct pmap *pmap2;
int lvl;
if (__predict_false(pmap->pm_unwire != NULL)) {
(*pmap->pm_unwire)(pmap, va);
return;
}
mutex_enter(&pmap->pm_lock);
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
if (!pmap_pdes_valid(va, pdes, NULL, &lvl)) {
panic("%s: invalid PDE va=%#" PRIxVADDR, __func__, va);
}
KASSERT(lvl == 1);
ptep = &ptes[pl1_i(va)];
opte = *ptep;
KASSERT(pmap_valid_entry(opte));
if (opte & PTE_WIRED) {
pt_entry_t npte = opte & ~PTE_WIRED;
opte = pmap_pte_testset(ptep, npte);
pmap_stats_update_bypte(pmap, npte, opte);
} else {
printf("%s: wiring for pmap %p va %#" PRIxVADDR
" did not change!\n", __func__, pmap, va);
}
pmap_unmap_ptes(pmap, pmap2);
mutex_exit(&pmap->pm_lock);
}
__strict_weak_alias(pmap_enter, pmap_enter_default);
int
pmap_enter_default(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot,
u_int flags)
{
if (__predict_false(pmap->pm_enter != NULL)) {
return (*pmap->pm_enter)(pmap, va, pa, prot, flags);
}
return pmap_enter_ma(pmap, va, pa, pa, prot, flags, 0);
}
int
pmap_enter_ma(struct pmap *pmap, vaddr_t va, paddr_t ma, paddr_t pa,
vm_prot_t prot, u_int flags, int domid)
{
pt_entry_t *ptes, opte, npte;
pt_entry_t *ptep;
pd_entry_t * const *pdes;
struct vm_page *ptp;
struct vm_page *new_pg, *old_pg;
struct pmap_page *new_pp, *old_pp;
struct pv_entry *old_pve, *new_pve;
bool wired = (flags & PMAP_WIRED) != 0;
struct pmap *pmap2;
struct pmap_ptparray pt;
int error;
bool getptp, samepage, new_embedded;
rb_tree_t *tree;
KASSERT(pmap_initialized);
KASSERT(va < VM_MAX_KERNEL_ADDRESS);
KASSERTMSG(va != (vaddr_t)PDP_BASE, "%s: trying to map va=%#"
PRIxVADDR " over PDP!", __func__, va);
KASSERTMSG(va < VM_MIN_KERNEL_ADDRESS ||
pmap_valid_entry(pmap->pm_pdir[pl_i(va, PTP_LEVELS)]),
"%s: missing kernel PTP for va=%#" PRIxVADDR, __func__, va);
#ifdef XENPV
KASSERT(domid == DOMID_SELF || pa == 0);
#endif
npte = ma | protection_codes[prot] | PTE_P;
npte |= pmap_pat_flags(flags);
if (wired)
npte |= PTE_WIRED;
if (va < VM_MAXUSER_ADDRESS) {
KASSERTMSG(pmap != pmap_kernel(),
"entering user va %#"PRIxVADDR" into kernel pmap",
va);
if (pmap_is_user(pmap))
npte |= PTE_U;
}
if (pmap == pmap_kernel())
npte |= pmap_pg_g;
if (flags & VM_PROT_ALL) {
npte |= PTE_A;
if (flags & VM_PROT_WRITE) {
KASSERT((npte & PTE_W) != 0);
npte |= PTE_D;
}
}
#ifdef XENPV
if (domid != DOMID_SELF)
new_pg = NULL;
else
#endif
new_pg = PHYS_TO_VM_PAGE(pa);
if (new_pg != NULL) {
npte |= PTE_PVLIST;
new_pp = VM_PAGE_TO_PP(new_pg);
PMAP_CHECK_PP(new_pp);
} else if ((new_pp = pmap_pv_tracked(pa)) != NULL) {
npte |= PTE_PVLIST;
PMAP_CHECK_PP(new_pp);
} else {
new_pp = NULL;
}
mutex_enter(&pmap->pm_lock);
ptp = NULL;
getptp = false;
if (pmap != pmap_kernel()) {
ptp = pmap_find_ptp(pmap, va, 1);
if (ptp == NULL) {
getptp = true;
error = pmap_get_ptp(pmap, &pt, va, flags, &ptp);
if (error != 0) {
if (flags & PMAP_CANFAIL) {
mutex_exit(&pmap->pm_lock);
return error;
}
panic("%s: get ptp failed, error=%d", __func__,
error);
}
}
tree = &VM_PAGE_TO_PP(ptp)->pp_rb;
} else {
KASSERT(va != 0);
tree = &pmap_kernel_rb;
}
new_pve = NULL;
old_pve = NULL;
samepage = false;
new_embedded = false;
if (new_pp != NULL) {
error = pmap_enter_pv(pmap, new_pp, ptp, va, &new_pve,
&old_pve, &samepage, &new_embedded, tree);
if (error != 0) {
if (flags & PMAP_CANFAIL) {
if (getptp) {
pmap_unget_ptp(pmap, &pt);
}
mutex_exit(&pmap->pm_lock);
return error;
}
panic("%s: alloc pve failed", __func__);
}
} else {
old_pve = pmap_treelookup_pv(pmap, ptp, tree, va);
}
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
if (getptp) {
pmap_install_ptp(pmap, &pt, va, pdes);
}
ptep = &ptes[pl1_i(va)];
opte = *ptep;
bool have_oldpa = pmap_valid_entry(opte);
paddr_t oldpa = pmap_pte2pa(opte);
do {
opte = *ptep;
if (((opte ^ npte) & (PTE_FRAME | PTE_P)) == 0) {
npte |= opte & (PTE_A | PTE_D);
}
#if defined(XENPV)
if (domid != DOMID_SELF) {
int s = splvm();
if (opte != *ptep) {
splx(s);
continue;
}
error = xpq_update_foreign(
vtomach((vaddr_t)ptep), npte, domid, flags);
splx(s);
if (error) {
if (new_pp != NULL) {
mutex_spin_enter(&new_pp->pp_lock);
if (new_pve != NULL) {
LIST_REMOVE(new_pve, pve_list);
KASSERT(pmap->pm_pve == NULL);
pmap->pm_pve = new_pve;
} else if (new_embedded) {
new_pp->pp_pte.pte_ptp = NULL;
new_pp->pp_pte.pte_va = 0;
}
mutex_spin_exit(&new_pp->pp_lock);
}
pmap_unmap_ptes(pmap, pmap2);
if (ptp != NULL && ptp->wire_count <= 1) {
pmap_free_ptp(pmap, ptp, va, ptes,
pdes);
}
mutex_exit(&pmap->pm_lock);
return error;
}
break;
}
#endif
} while (pmap_pte_cas(ptep, opte, npte) != opte);
pmap_unmap_ptes(pmap, pmap2);
pmap_stats_update_bypte(pmap, npte, opte);
if (ptp != NULL) {
if (!have_oldpa) {
ptp->wire_count++;
}
pmap_ptp_range_set(ptp, va);
}
KASSERT(ptp == NULL || ptp->wire_count > 1);
if (((opte ^ npte) & (PTE_FRAME | PTE_P)) == 0) {
KASSERT(((opte ^ npte) & PTE_PVLIST) == 0);
if ((npte & PTE_PVLIST) != 0) {
KASSERT(samepage);
pmap_check_pv(pmap, ptp, new_pp, va, true);
}
goto same_pa;
} else if ((npte & PTE_PVLIST) != 0) {
KASSERT(!samepage);
}
if ((~opte & (PTE_P | PTE_PVLIST)) == 0) {
if ((old_pg = PHYS_TO_VM_PAGE(oldpa)) != NULL) {
old_pp = VM_PAGE_TO_PP(old_pg);
} else if ((old_pp = pmap_pv_tracked(oldpa)) == NULL) {
panic("%s: PTE_PVLIST with pv-untracked page"
" va = %#"PRIxVADDR
" pa = %#" PRIxPADDR " (%#" PRIxPADDR ")",
__func__, va, oldpa, atop(pa));
}
pmap_remove_pv(pmap, old_pp, ptp, va, old_pve,
pmap_pte_to_pp_attrs(opte));
} else {
KASSERT(old_pve == NULL);
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == NULL);
}
if (new_pve != NULL) {
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == NULL);
old_pve = rb_tree_insert_node(tree, new_pve);
KASSERT(old_pve == new_pve);
pmap_check_pv(pmap, ptp, new_pp, va, true);
}
same_pa:
if ((~opte & (PTE_P | PTE_A)) == 0 &&
((opte ^ npte) & (PTE_FRAME | PTE_W)) != 0) {
pmap_tlb_shootdown(pmap, va, opte, TLBSHOOT_ENTER);
}
pmap_drain_pv(pmap);
mutex_exit(&pmap->pm_lock);
return 0;
}
#if defined(XEN) && defined(DOM0OPS)
struct pmap_data_gnt {
SLIST_ENTRY(pmap_data_gnt) pd_gnt_list;
vaddr_t pd_gnt_sva;
vaddr_t pd_gnt_eva;
int pd_gnt_refs;
struct gnttab_map_grant_ref pd_gnt_ops[1];
};
SLIST_HEAD(pmap_data_gnt_head, pmap_data_gnt);
static void pmap_remove_gnt(struct pmap *, vaddr_t, vaddr_t);
static struct pmap_data_gnt *
pmap_find_gnt(struct pmap *pmap, vaddr_t sva, vaddr_t eva)
{
struct pmap_data_gnt_head *headp;
struct pmap_data_gnt *pgnt;
KASSERT(mutex_owned(&pmap->pm_lock));
headp = pmap->pm_data;
KASSERT(headp != NULL);
SLIST_FOREACH(pgnt, headp, pd_gnt_list) {
if (pgnt->pd_gnt_sva <= sva && eva <= pgnt->pd_gnt_eva)
return pgnt;
KASSERT(pgnt->pd_gnt_sva >= eva || pgnt->pd_gnt_eva <= sva);
}
return NULL;
}
static void
pmap_alloc_gnt(struct pmap *pmap, vaddr_t sva, int nentries,
const struct gnttab_map_grant_ref *ops)
{
struct pmap_data_gnt_head *headp;
struct pmap_data_gnt *pgnt;
vaddr_t eva = sva + nentries * PAGE_SIZE;
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(nentries >= 1);
if (pmap->pm_remove == NULL) {
pmap->pm_remove = pmap_remove_gnt;
KASSERT(pmap->pm_data == NULL);
headp = kmem_alloc(sizeof(*headp), KM_SLEEP);
SLIST_INIT(headp);
pmap->pm_data = headp;
} else {
KASSERT(pmap->pm_remove == pmap_remove_gnt);
KASSERT(pmap->pm_data != NULL);
headp = pmap->pm_data;
}
pgnt = pmap_find_gnt(pmap, sva, eva);
if (pgnt != NULL) {
KASSERT(pgnt->pd_gnt_sva == sva);
KASSERT(pgnt->pd_gnt_eva == eva);
return;
}
pgnt = kmem_alloc(sizeof(*pgnt) +
(nentries - 1) * sizeof(struct gnttab_map_grant_ref), KM_SLEEP);
pgnt->pd_gnt_sva = sva;
pgnt->pd_gnt_eva = eva;
pgnt->pd_gnt_refs = 0;
memcpy(pgnt->pd_gnt_ops, ops,
sizeof(struct gnttab_map_grant_ref) * nentries);
SLIST_INSERT_HEAD(headp, pgnt, pd_gnt_list);
}
static void
pmap_free_gnt(struct pmap *pmap, struct pmap_data_gnt *pgnt)
{
struct pmap_data_gnt_head *headp = pmap->pm_data;
int nentries = (pgnt->pd_gnt_eva - pgnt->pd_gnt_sva) / PAGE_SIZE;
KASSERT(nentries >= 1);
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(pgnt->pd_gnt_refs == 0);
SLIST_REMOVE(headp, pgnt, pmap_data_gnt, pd_gnt_list);
kmem_free(pgnt, sizeof(*pgnt) +
(nentries - 1) * sizeof(struct gnttab_map_grant_ref));
if (SLIST_EMPTY(headp)) {
kmem_free(headp, sizeof(*headp));
pmap->pm_data = NULL;
pmap->pm_remove = NULL;
}
}
int
pmap_enter_gnt(struct pmap *pmap, vaddr_t va, vaddr_t sva, int nentries,
const struct gnttab_map_grant_ref *oops)
{
struct pmap_data_gnt *pgnt;
pt_entry_t *ptes, opte;
#ifndef XENPV
pt_entry_t npte;
#endif
pt_entry_t *ptep;
pd_entry_t * const *pdes;
struct vm_page *ptp;
struct vm_page *old_pg;
struct pmap_page *old_pp;
struct pv_entry *old_pve;
struct pmap *pmap2;
struct pmap_ptparray pt;
int error;
bool getptp;
rb_tree_t *tree;
struct gnttab_map_grant_ref *op;
int ret;
int idx;
KASSERT(pmap_initialized);
KASSERT(va < VM_MAX_KERNEL_ADDRESS);
KASSERTMSG(va != (vaddr_t)PDP_BASE, "%s: trying to map va=%#"
PRIxVADDR " over PDP!", __func__, va);
KASSERT(pmap != pmap_kernel());
mutex_enter(&pmap->pm_lock);
pmap_alloc_gnt(pmap, sva, nentries, oops);
pgnt = pmap_find_gnt(pmap, va, va + PAGE_SIZE);
KASSERT(pgnt != NULL);
ptp = NULL;
getptp = false;
ptp = pmap_find_ptp(pmap, va, 1);
if (ptp == NULL) {
getptp = true;
error = pmap_get_ptp(pmap, &pt, va, PMAP_CANFAIL, &ptp);
if (error != 0) {
mutex_exit(&pmap->pm_lock);
return error;
}
}
tree = &VM_PAGE_TO_PP(ptp)->pp_rb;
old_pve = NULL;
old_pve = pmap_treelookup_pv(pmap, ptp, tree, va);
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
if (getptp) {
pmap_install_ptp(pmap, &pt, va, pdes);
}
ptep = &ptes[pl1_i(va)];
opte = *ptep;
bool have_oldpa = pmap_valid_entry(opte);
paddr_t oldpa = pmap_pte2pa(opte);
idx = (va - pgnt->pd_gnt_sva) / PAGE_SIZE;
op = &pgnt->pd_gnt_ops[idx];
#ifdef XENPV
KASSERT(op->flags & GNTMAP_contains_pte);
op->host_addr = xpmap_ptetomach(ptep);
#else
KASSERT((op->flags & GNTMAP_contains_pte) == 0);
KASSERT(op->flags != 0);
KASSERT(op->host_addr != 0);
#endif
op->dev_bus_addr = 0;
op->status = GNTST_general_error;
ret = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, op, 1);
if (__predict_false(ret)) {
printf("%s: GNTTABOP_map_grant_ref failed: %d\n",
__func__, ret);
op->status = GNTST_general_error;
}
for (int d = 0; d < 256 && op->status == GNTST_eagain; d++) {
kpause("gntmap", false, mstohz(1), NULL);
ret = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, op, 1);
if (__predict_false(ret)) {
printf("%s: GNTTABOP_map_grant_ref failed: %d\n",
__func__, ret);
op->status = GNTST_general_error;
}
}
if (__predict_false(op->status != GNTST_okay)) {
printf("%s: GNTTABOP_map_grant_ref status: %d\n",
__func__, op->status);
if (have_oldpa) {
ptp->wire_count--;
}
} else {
#ifndef XENPV
npte = op->host_addr | pmap_pg_nx | PTE_U | PTE_P;
if ((op->flags & GNTMAP_readonly) == 0)
npte |= PTE_W;
do {
opte = *ptep;
} while (pmap_pte_cas(ptep, opte, npte) != opte);
#endif
pgnt->pd_gnt_refs++;
if (!have_oldpa) {
ptp->wire_count++;
}
KASSERT(ptp->wire_count > 1);
pmap_ptp_range_set(ptp, va);
}
if (ptp->wire_count <= 1)
pmap_free_ptp(pmap, ptp, va, ptes, pdes);
pmap_unmap_ptes(pmap, pmap2);
pmap_stats_update_bypte(pmap, 0, opte);
if ((~opte & (PTE_P | PTE_PVLIST)) == 0) {
if ((old_pg = PHYS_TO_VM_PAGE(oldpa)) != NULL) {
old_pp = VM_PAGE_TO_PP(old_pg);
} else if ((old_pp = pmap_pv_tracked(oldpa)) == NULL) {
panic("%s: PTE_PVLIST with pv-untracked page"
" va = %#"PRIxVADDR " pa = %#" PRIxPADDR,
__func__, va, oldpa);
}
pmap_remove_pv(pmap, old_pp, ptp, va, old_pve,
pmap_pte_to_pp_attrs(opte));
} else {
KASSERT(old_pve == NULL);
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == NULL);
}
pmap_drain_pv(pmap);
mutex_exit(&pmap->pm_lock);
return op->status;
}
static void
pmap_remove_gnt(struct pmap *pmap, vaddr_t sva, vaddr_t eva)
{
struct pmap_data_gnt *pgnt;
pt_entry_t *ptes;
pd_entry_t pde;
pd_entry_t * const *pdes;
struct vm_page *ptp;
struct pmap *pmap2;
vaddr_t va;
int lvl;
int idx;
struct gnttab_map_grant_ref *op;
struct gnttab_unmap_grant_ref unmap_op;
int ret;
KASSERT(pmap != pmap_kernel());
KASSERT(pmap->pm_remove == pmap_remove_gnt);
mutex_enter(&pmap->pm_lock);
for (va = sva; va < eva; va += PAGE_SIZE) {
pgnt = pmap_find_gnt(pmap, va, va + PAGE_SIZE);
if (pgnt == NULL) {
pmap_remove_locked(pmap, sva, eva);
continue;
}
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
if (!pmap_pdes_valid(va, pdes, &pde, &lvl)) {
panic("pmap_remove_gnt pdes not valid");
}
idx = (va - pgnt->pd_gnt_sva) / PAGE_SIZE;
op = &pgnt->pd_gnt_ops[idx];
KASSERT(lvl == 1);
ptp = pmap_find_ptp(pmap, va, 1);
KASSERTMSG(ptp != NULL,
"%s: unmanaged PTP detected", __func__);
if (op->status == GNTST_okay) {
KASSERT(pmap_valid_entry(ptes[pl1_i(va)]));
#ifdef XENPV
unmap_op.host_addr = xpmap_ptetomach(&ptes[pl1_i(va)]);
#else
unmap_op.host_addr = op->host_addr;
pmap_pte_testset(&ptes[pl1_i(va)], 0);
#endif
unmap_op.handle = op->handle;
unmap_op.dev_bus_addr = 0;
ret = HYPERVISOR_grant_table_op(
GNTTABOP_unmap_grant_ref, &unmap_op, 1);
if (ret) {
printf("%s: GNTTABOP_unmap_grant_ref "
"failed: %d\n", __func__, ret);
}
ptp->wire_count--;
pgnt->pd_gnt_refs--;
}
if (pgnt->pd_gnt_refs == 0) {
pmap_free_gnt(pmap, pgnt);
}
if (ptp->wire_count <= 1)
pmap_free_ptp(pmap, ptp, va, ptes, pdes);
pmap_unmap_ptes(pmap, pmap2);
}
mutex_exit(&pmap->pm_lock);
}
#endif
paddr_t
pmap_get_physpage(void)
{
struct vm_page *ptp;
struct pmap *kpm = pmap_kernel();
paddr_t pa;
if (!uvm.page_init_done) {
if (!uvm_page_physget(&pa))
panic("%s: out of memory", __func__);
#if defined(__HAVE_DIRECT_MAP)
memset(PAGE_ALIGNED(PMAP_DIRECT_MAP(pa)), 0, PAGE_SIZE);
#else
#if defined(XENPV)
if (XEN_VERSION_SUPPORTED(3, 4)) {
xen_pagezero(pa);
return pa;
}
#endif
kpreempt_disable();
pmap_pte_set(early_zero_pte, pmap_pa2pte(pa) | PTE_P |
PTE_W | pmap_pg_nx);
pmap_pte_flush();
pmap_update_pg((vaddr_t)early_zerop);
memset(PAGE_ALIGNED(early_zerop), 0, PAGE_SIZE);
#if defined(DIAGNOSTIC) || defined(XENPV)
pmap_pte_set(early_zero_pte, 0);
pmap_pte_flush();
#endif
kpreempt_enable();
#endif
} else {
ptp = uvm_pagealloc(NULL, 0, NULL,
UVM_PGA_USERESERVE|UVM_PGA_ZERO);
if (ptp == NULL)
panic("%s: out of memory", __func__);
ptp->flags &= ~PG_BUSY;
ptp->wire_count = 1;
pa = VM_PAGE_TO_PHYS(ptp);
}
pmap_stats_update(kpm, 1, 0);
return pa;
}
static void
pmap_alloc_level(struct pmap *cpm, vaddr_t kva, long *needed_ptps)
{
unsigned long i;
paddr_t pa;
unsigned long index, endindex;
int level;
pd_entry_t *pdep;
#ifdef XENPV
int s = splvm();
#endif
for (level = PTP_LEVELS; level > 1; level--) {
if (level == PTP_LEVELS)
pdep = cpm->pm_pdir;
else
pdep = normal_pdes[level - 2];
index = pl_i_roundup(kva, level);
endindex = index + needed_ptps[level - 1] - 1;
for (i = index; i <= endindex; i++) {
pt_entry_t pte;
KASSERT(!pmap_valid_entry(pdep[i]));
pa = pmap_get_physpage();
pte = pmap_pa2pte(pa) | PTE_P | PTE_W;
#ifdef __x86_64__
pte |= pmap_pg_nx;
#endif
pmap_pte_set(&pdep[i], pte);
#ifdef XENPV
if (level == PTP_LEVELS && i >= PDIR_SLOT_KERN) {
if (__predict_true(
cpu_info_primary.ci_flags & CPUF_PRESENT)) {
xen_kpm_sync(pmap_kernel(), i);
} else {
#ifdef __x86_64__
pd_entry_t *cpu_pdep =
&cpu_info_primary.ci_kpm_pdir[i];
#else
pd_entry_t *cpu_pdep =
&cpu_info_primary.ci_kpm_pdir[l2tol2(i)];
#endif
pmap_pte_set(cpu_pdep, pte);
}
}
#endif
KASSERT(level != PTP_LEVELS || nkptp[level - 1] +
pl_i(VM_MIN_KERNEL_ADDRESS, level) == i);
nkptp[level - 1]++;
}
pmap_pte_flush();
}
#ifdef XENPV
splx(s);
#endif
}
vaddr_t
pmap_growkernel(vaddr_t maxkvaddr)
{
struct pmap *kpm = pmap_kernel();
struct pmap *cpm;
#if !defined(XENPV) || !defined(__x86_64__)
struct pmap *pm;
long old;
#endif
int s, i;
long needed_kptp[PTP_LEVELS], target_nptp;
bool invalidate = false;
s = splvm();
mutex_enter(&kpm->pm_lock);
if (maxkvaddr <= pmap_maxkvaddr) {
mutex_exit(&kpm->pm_lock);
splx(s);
return pmap_maxkvaddr;
}
maxkvaddr = x86_round_pdr(maxkvaddr);
#if !defined(XENPV) || !defined(__x86_64__)
old = nkptp[PTP_LEVELS - 1];
#endif
for (i = PTP_LEVELS - 1; i >= 1; i--) {
target_nptp = pl_i_roundup(maxkvaddr, i + 1) -
pl_i_roundup(VM_MIN_KERNEL_ADDRESS, i + 1);
if (target_nptp > nkptpmax[i])
panic("out of KVA space");
KASSERT(target_nptp >= nkptp[i]);
needed_kptp[i] = target_nptp - nkptp[i];
}
#ifdef XENPV
cpm = kpm;
#else
if (__predict_true(cpu_info_primary.ci_flags & CPUF_PRESENT)) {
cpm = curcpu()->ci_pmap;
} else {
cpm = kpm;
}
#endif
kasan_shadow_map((void *)pmap_maxkvaddr,
(size_t)(maxkvaddr - pmap_maxkvaddr));
kmsan_shadow_map((void *)pmap_maxkvaddr,
(size_t)(maxkvaddr - pmap_maxkvaddr));
pmap_alloc_level(cpm, pmap_maxkvaddr, needed_kptp);
if (needed_kptp[PTP_LEVELS - 1] != 0) {
#ifdef XENPV
#ifdef __x86_64__
#else
int pdkidx;
mutex_enter(&pmaps_lock);
LIST_FOREACH(pm, &pmaps, pm_list) {
for (pdkidx = PDIR_SLOT_KERN + old;
pdkidx < PDIR_SLOT_KERN + nkptp[PTP_LEVELS - 1];
pdkidx++) {
pmap_pte_set(&pm->pm_pdir[pdkidx],
kpm->pm_pdir[pdkidx]);
}
pmap_pte_flush();
}
mutex_exit(&pmaps_lock);
#endif
#else
size_t newpdes;
newpdes = nkptp[PTP_LEVELS - 1] - old;
if (cpm != kpm) {
memcpy(&kpm->pm_pdir[PDIR_SLOT_KERN + old],
&cpm->pm_pdir[PDIR_SLOT_KERN + old],
newpdes * sizeof(pd_entry_t));
}
mutex_enter(&pmaps_lock);
LIST_FOREACH(pm, &pmaps, pm_list) {
if (__predict_false(pm->pm_enter != NULL)) {
continue;
}
memcpy(&pm->pm_pdir[PDIR_SLOT_KERN + old],
&kpm->pm_pdir[PDIR_SLOT_KERN + old],
newpdes * sizeof(pd_entry_t));
}
mutex_exit(&pmaps_lock);
#endif
invalidate = true;
}
pmap_maxkvaddr = maxkvaddr;
mutex_exit(&kpm->pm_lock);
splx(s);
if (invalidate && pmap_initialized) {
pool_cache_invalidate(&pmap_cache);
}
return maxkvaddr;
}
#ifdef DEBUG
void pmap_dump(struct pmap *, vaddr_t, vaddr_t);
void
pmap_dump(struct pmap *pmap, vaddr_t sva, vaddr_t eva)
{
pt_entry_t *ptes, *pte;
pd_entry_t * const *pdes;
struct pmap *pmap2;
vaddr_t blkendva;
int lvl;
if (eva > VM_MAXUSER_ADDRESS || eva <= sva)
eva = VM_MAXUSER_ADDRESS;
mutex_enter(&pmap->pm_lock);
pmap_map_ptes(pmap, &pmap2, &ptes, &pdes);
for ( ; sva < eva ; sva = blkendva) {
blkendva = x86_round_pdr(sva+1);
if (blkendva > eva)
blkendva = eva;
if (!pmap_pdes_valid(sva, pdes, NULL, &lvl))
continue;
KASSERT(lvl == 1);
pte = &ptes[pl1_i(sva)];
for (; sva < blkendva ; sva += PAGE_SIZE, pte++) {
if (!pmap_valid_entry(*pte))
continue;
printf("va %#" PRIxVADDR " -> pa %#" PRIxPADDR
" (pte=%#" PRIxPADDR ")\n",
sva, (paddr_t)pmap_pte2pa(*pte), (paddr_t)*pte);
}
}
pmap_unmap_ptes(pmap, pmap2);
mutex_exit(&pmap->pm_lock);
}
#endif
void
pmap_update(struct pmap *pmap)
{
struct pmap_page *pp;
struct vm_page *ptp;
kpreempt_disable();
pmap_tlb_shootnow();
kpreempt_enable();
if (atomic_load_relaxed(&pmap->pm_gc_ptp.lh_first) == NULL) {
return;
}
mutex_enter(&pmap->pm_lock);
while ((ptp = LIST_FIRST(&pmap->pm_gc_ptp)) != NULL) {
KASSERT(ptp->wire_count == 0);
KASSERT(ptp->uanon == NULL);
LIST_REMOVE(ptp, mdpage.mp_pp.pp_link);
pp = VM_PAGE_TO_PP(ptp);
LIST_INIT(&pp->pp_pvlist);
pp->pp_attrs = 0;
pp->pp_pte.pte_ptp = NULL;
pp->pp_pte.pte_va = 0;
PMAP_CHECK_PP(VM_PAGE_TO_PP(ptp));
PMAP_DUMMY_LOCK(pmap);
uvm_pagerealloc(ptp, NULL, 0);
PMAP_DUMMY_UNLOCK(pmap);
uvm_pagefree(ptp);
}
mutex_exit(&pmap->pm_lock);
}
#if PTP_LEVELS > 4
#error "Unsupported number of page table mappings"
#endif
paddr_t
pmap_init_tmp_pgtbl(paddr_t pg)
{
static bool maps_loaded;
static const paddr_t x86_tmp_pml_paddr[] = {
4 * PAGE_SIZE,
5 * PAGE_SIZE,
6 * PAGE_SIZE,
7 * PAGE_SIZE
};
static vaddr_t x86_tmp_pml_vaddr[] = { 0, 0, 0, 0 };
pd_entry_t *tmp_pml, *kernel_pml;
int level;
if (!maps_loaded) {
for (level = 0; level < PTP_LEVELS; ++level) {
x86_tmp_pml_vaddr[level] =
uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
UVM_KMF_VAONLY);
if (x86_tmp_pml_vaddr[level] == 0)
panic("mapping of real mode PML failed\n");
pmap_kenter_pa(x86_tmp_pml_vaddr[level],
x86_tmp_pml_paddr[level],
VM_PROT_READ | VM_PROT_WRITE, 0);
}
pmap_update(pmap_kernel());
maps_loaded = true;
}
for (level = 0; level < PTP_LEVELS - 1; ++level) {
tmp_pml = (void *)x86_tmp_pml_vaddr[level];
memset(PAGE_ALIGNED(tmp_pml), 0, PAGE_SIZE);
}
kernel_pml = pmap_kernel()->pm_pdir;
tmp_pml = (void *)x86_tmp_pml_vaddr[PTP_LEVELS - 1];
memcpy(PAGE_ALIGNED(tmp_pml), PAGE_ALIGNED(kernel_pml), PAGE_SIZE);
#ifdef PAE
tmp_pml[508] = x86_tmp_pml_paddr[PTP_LEVELS - 1] | PTE_P;
tmp_pml[509] = 0;
tmp_pml[510] = 0;
tmp_pml[511] = pmap_pdirpa(pmap_kernel(), PDIR_SLOT_KERN) | PTE_P;
#endif
for (level = PTP_LEVELS - 1; level > 0; --level) {
tmp_pml = (void *)x86_tmp_pml_vaddr[level];
tmp_pml[pl_i(pg, level + 1)] =
(x86_tmp_pml_paddr[level - 1] & PTE_FRAME) | PTE_W | PTE_P;
}
tmp_pml = (void *)x86_tmp_pml_vaddr[0];
tmp_pml[pl_i(pg, 1)] = (pg & PTE_FRAME) | PTE_W | PTE_P;
#ifdef PAE
return x86_tmp_pml_paddr[PTP_LEVELS - 1] + 508 * sizeof(pd_entry_t);
#endif
return x86_tmp_pml_paddr[PTP_LEVELS - 1];
}
u_int
x86_mmap_flags(paddr_t mdpgno)
{
u_int nflag = (mdpgno >> X86_MMAP_FLAG_SHIFT) & X86_MMAP_FLAG_MASK;
u_int pflag = 0;
if (nflag & X86_MMAP_FLAG_PREFETCH)
pflag |= PMAP_WRITE_COMBINE;
return pflag;
}
#if defined(__HAVE_DIRECT_MAP) && defined(__x86_64__) && !defined(XENPV)
#define EPT_R __BIT(0)
#define EPT_W __BIT(1)
#define EPT_X __BIT(2)
#define EPT_T __BITS(5,3)
#define TYPE_UC 0
#define TYPE_WC 1
#define TYPE_WT 4
#define TYPE_WP 5
#define TYPE_WB 6
#define EPT_NOPAT __BIT(6)
#define EPT_L __BIT(7)
#define EPT_A __BIT(8)
#define EPT_D __BIT(9)
#define EPT_PVLIST __BIT(60)
#define EPT_WIRED __BIT(61)
#define pmap_ept_valid_entry(pte) (pte & EPT_R)
bool pmap_ept_has_ad __read_mostly;
static inline void
pmap_ept_stats_update_bypte(struct pmap *pmap, pt_entry_t npte, pt_entry_t opte)
{
int resid_diff = ((npte & EPT_R) ? 1 : 0) - ((opte & EPT_R) ? 1 : 0);
int wired_diff = ((npte & EPT_WIRED) ? 1 : 0) - ((opte & EPT_WIRED) ? 1 : 0);
KASSERT((npte & (EPT_R | EPT_WIRED)) != EPT_WIRED);
KASSERT((opte & (EPT_R | EPT_WIRED)) != EPT_WIRED);
pmap_stats_update(pmap, resid_diff, wired_diff);
}
static pt_entry_t
pmap_ept_type(u_int flags)
{
u_int cacheflags = (flags & PMAP_CACHE_MASK);
pt_entry_t ret;
switch (cacheflags) {
case PMAP_NOCACHE:
case PMAP_NOCACHE_OVR:
ret = __SHIFTIN(TYPE_UC, EPT_T);
break;
case PMAP_WRITE_COMBINE:
ret = __SHIFTIN(TYPE_WC, EPT_T);
break;
case PMAP_WRITE_BACK:
default:
ret = __SHIFTIN(TYPE_WB, EPT_T);
break;
}
ret |= EPT_NOPAT;
return ret;
}
static inline pt_entry_t
pmap_ept_prot(vm_prot_t prot)
{
pt_entry_t res = 0;
if (prot & VM_PROT_READ)
res |= EPT_R;
if (prot & VM_PROT_WRITE)
res |= EPT_W;
if (prot & VM_PROT_EXECUTE)
res |= EPT_X;
return res;
}
static inline uint8_t
pmap_ept_to_pp_attrs(pt_entry_t ept)
{
uint8_t ret = 0;
if (pmap_ept_has_ad) {
if (ept & EPT_D)
ret |= PP_ATTRS_D;
if (ept & EPT_A)
ret |= PP_ATTRS_A;
} else {
ret |= (PP_ATTRS_D|PP_ATTRS_A);
}
if (ept & EPT_W)
ret |= PP_ATTRS_W;
return ret;
}
static inline pt_entry_t
pmap_pp_attrs_to_ept(uint8_t attrs)
{
pt_entry_t ept = 0;
if (attrs & PP_ATTRS_D)
ept |= EPT_D;
if (attrs & PP_ATTRS_A)
ept |= EPT_A;
if (attrs & PP_ATTRS_W)
ept |= EPT_W;
return ept;
}
static void
pmap_ept_get_tree(struct pmap *pmap, vaddr_t va, pd_entry_t **tree)
{
pt_entry_t *pteva;
paddr_t ptepa;
int i, index;
ptepa = pmap->pm_pdirpa[0];
for (i = PTP_LEVELS; i > 1; i--) {
index = pl_pi(va, i);
pteva = (pt_entry_t *)PMAP_DIRECT_MAP(ptepa);
KASSERT(pmap_ept_valid_entry(pteva[index]));
tree[i - 2] = &pteva[index];
ptepa = pmap_pte2pa(pteva[index]);
}
}
static void
pmap_ept_free_ptp(struct pmap *pmap, struct vm_page *ptp, vaddr_t va)
{
pd_entry_t *tree[3];
int level;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
pmap_ept_get_tree(pmap, va, tree);
level = 1;
do {
(void)pmap_pte_testset(tree[level - 1], 0);
pmap_freepage(pmap, ptp, level);
if (level < PTP_LEVELS - 1) {
ptp = pmap_find_ptp(pmap, va, level + 1);
ptp->wire_count--;
if (ptp->wire_count > 1)
break;
}
} while (++level < PTP_LEVELS);
pmap_pte_flush();
}
static void
pmap_ept_install_ptp(struct pmap *pmap, struct pmap_ptparray *pt, vaddr_t va)
{
struct vm_page *ptp;
unsigned long index;
pd_entry_t *pteva;
paddr_t ptepa;
int i;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
ptepa = pmap->pm_pdirpa[0];
for (i = PTP_LEVELS; i > 1; i--) {
index = pl_pi(va, i);
pteva = (pt_entry_t *)PMAP_DIRECT_MAP(ptepa);
if (pmap_ept_valid_entry(pteva[index])) {
KASSERT(!pt->alloced[i]);
ptepa = pmap_pte2pa(pteva[index]);
continue;
}
ptp = pt->pg[i];
ptp->flags &= ~PG_BUSY;
ptp->wire_count = 1;
pmap->pm_ptphint[i - 2] = ptp;
ptepa = VM_PAGE_TO_PHYS(ptp);
pmap_pte_set(&pteva[index], ptepa | EPT_R | EPT_W | EPT_X);
pmap_pte_flush();
pmap_stats_update(pmap, 1, 0);
if (i < PTP_LEVELS) {
pt->pg[i + 1]->wire_count++;
}
}
}
static int
pmap_ept_enter(struct pmap *pmap, vaddr_t va, paddr_t pa, vm_prot_t prot,
u_int flags)
{
pt_entry_t *ptes, opte, npte;
pt_entry_t *ptep;
struct vm_page *ptp;
struct vm_page *new_pg, *old_pg;
struct pmap_page *new_pp, *old_pp;
struct pv_entry *old_pve, *new_pve;
bool wired = (flags & PMAP_WIRED) != 0;
bool accessed;
struct pmap_ptparray pt;
int error;
bool getptp, samepage, new_embedded;
rb_tree_t *tree;
KASSERT(pmap_initialized);
KASSERT(va < VM_MAXUSER_ADDRESS);
npte = pa | pmap_ept_prot(prot) | pmap_ept_type(flags);
if (wired)
npte |= EPT_WIRED;
if (flags & VM_PROT_ALL) {
npte |= EPT_A;
if (flags & VM_PROT_WRITE) {
KASSERT((npte & EPT_W) != 0);
npte |= EPT_D;
}
}
new_pg = PHYS_TO_VM_PAGE(pa);
if (new_pg != NULL) {
npte |= EPT_PVLIST;
new_pp = VM_PAGE_TO_PP(new_pg);
} else if ((new_pp = pmap_pv_tracked(pa)) != NULL) {
npte |= EPT_PVLIST;
} else {
new_pp = NULL;
}
mutex_enter(&pmap->pm_lock);
ptp = NULL;
getptp = false;
if (pmap != pmap_kernel()) {
ptp = pmap_find_ptp(pmap, va, 1);
if (ptp == NULL) {
getptp = true;
error = pmap_get_ptp(pmap, &pt, va, flags, &ptp);
if (error != 0) {
if (flags & PMAP_CANFAIL) {
mutex_exit(&pmap->pm_lock);
return error;
}
panic("%s: get ptp failed, error=%d", __func__,
error);
}
}
tree = &VM_PAGE_TO_PP(ptp)->pp_rb;
} else {
KASSERT(va != 0);
tree = &pmap_kernel_rb;
}
new_pve = NULL;
old_pve = NULL;
samepage = false;
new_embedded = false;
if (new_pp != NULL) {
error = pmap_enter_pv(pmap, new_pp, ptp, va, &new_pve,
&old_pve, &samepage, &new_embedded, tree);
if (error != 0) {
if (flags & PMAP_CANFAIL) {
if (getptp) {
pmap_unget_ptp(pmap, &pt);
}
mutex_exit(&pmap->pm_lock);
return error;
}
panic("%s: alloc pve failed", __func__);
}
} else {
old_pve = pmap_treelookup_pv(pmap, ptp, tree, va);
}
kpreempt_disable();
if (getptp) {
pmap_ept_install_ptp(pmap, &pt, va);
}
ptes = (pt_entry_t *)PMAP_DIRECT_MAP(VM_PAGE_TO_PHYS(ptp));
ptep = &ptes[pl1_pi(va)];
opte = *ptep;
bool have_oldpa = pmap_ept_valid_entry(opte);
paddr_t oldpa = pmap_pte2pa(opte);
do {
opte = *ptep;
if (((opte ^ npte) & (PTE_FRAME | EPT_R)) == 0) {
npte |= opte & (EPT_A | EPT_D);
}
} while (pmap_pte_cas(ptep, opte, npte) != opte);
kpreempt_enable();
pmap_ept_stats_update_bypte(pmap, npte, opte);
if (ptp != NULL) {
if (!have_oldpa) {
ptp->wire_count++;
}
pmap_ptp_range_set(ptp, va);
}
KASSERT(ptp == NULL || ptp->wire_count > 1);
if (((opte ^ npte) & (PTE_FRAME | EPT_R)) == 0) {
KASSERT(((opte ^ npte) & EPT_PVLIST) == 0);
if ((npte & EPT_PVLIST) != 0) {
KASSERT(samepage);
pmap_check_pv(pmap, ptp, new_pp, va, true);
}
goto same_pa;
} else if ((npte & EPT_PVLIST) != 0) {
KASSERT(!samepage);
}
if ((~opte & (EPT_R | EPT_PVLIST)) == 0) {
if ((old_pg = PHYS_TO_VM_PAGE(oldpa)) != NULL) {
old_pp = VM_PAGE_TO_PP(old_pg);
} else if ((old_pp = pmap_pv_tracked(oldpa)) == NULL) {
panic("%s: EPT_PVLIST with pv-untracked page"
" va = %#"PRIxVADDR
" pa = %#" PRIxPADDR " (%#" PRIxPADDR ")",
__func__, va, oldpa, atop(pa));
}
pmap_remove_pv(pmap, old_pp, ptp, va, old_pve,
pmap_ept_to_pp_attrs(opte));
} else {
KASSERT(old_pve == NULL);
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == NULL);
}
if (new_pve != NULL) {
KASSERT(pmap_treelookup_pv(pmap, ptp, tree, va) == NULL);
old_pve = rb_tree_insert_node(tree, new_pve);
KASSERT(old_pve == new_pve);
pmap_check_pv(pmap, ptp, new_pp, va, true);
}
same_pa:
if (pmap_ept_has_ad) {
accessed = (~opte & (EPT_R | EPT_A)) == 0;
} else {
accessed = (opte & EPT_R) != 0;
}
if (accessed && ((opte ^ npte) & (PTE_FRAME | EPT_W)) != 0) {
pmap_tlb_shootdown(pmap, va, 0, TLBSHOOT_ENTER);
}
pmap_drain_pv(pmap);
mutex_exit(&pmap->pm_lock);
return 0;
}
static int
pmap_ept_pdes_invalid(struct pmap *pmap, vaddr_t va, pd_entry_t *lastpde)
{
pt_entry_t *pteva;
paddr_t ptepa;
int i, index;
KASSERT(mutex_owned(&pmap->pm_lock));
ptepa = pmap->pm_pdirpa[0];
for (i = PTP_LEVELS; i > 1; i--) {
pteva = (pt_entry_t *)PMAP_DIRECT_MAP(ptepa);
index = pl_pi(va, i);
if (!pmap_ept_valid_entry(pteva[index]))
return i;
ptepa = pmap_pte2pa(pteva[index]);
}
if (lastpde != NULL) {
*lastpde = pteva[index];
}
return 0;
}
static bool
pmap_ept_extract(struct pmap *pmap, vaddr_t va, paddr_t *pap)
{
pt_entry_t *ptes, pte;
pd_entry_t pde;
paddr_t ptppa, pa;
bool rv;
#ifdef __HAVE_DIRECT_MAP
if (va >= PMAP_DIRECT_BASE && va < PMAP_DIRECT_END) {
if (pap != NULL) {
*pap = PMAP_DIRECT_UNMAP(va);
}
return true;
}
#endif
rv = false;
pa = 0;
mutex_enter(&pmap->pm_lock);
kpreempt_disable();
if (!pmap_ept_pdes_invalid(pmap, va, &pde)) {
ptppa = pmap_pte2pa(pde);
ptes = (pt_entry_t *)PMAP_DIRECT_MAP(ptppa);
pte = ptes[pl1_pi(va)];
if (__predict_true((pte & EPT_R) != 0)) {
pa = pmap_pte2pa(pte) | (va & (NBPD_L1 - 1));
rv = true;
}
}
kpreempt_enable();
mutex_exit(&pmap->pm_lock);
if (pap != NULL) {
*pap = pa;
}
return rv;
}
static bool
pmap_ept_remove_pte(struct pmap *pmap, struct vm_page *ptp, pt_entry_t *pte,
vaddr_t va)
{
struct pv_entry *pve;
struct vm_page *pg;
struct pmap_page *pp;
pt_entry_t opte;
bool accessed;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
if (!pmap_ept_valid_entry(*pte)) {
return false;
}
opte = pmap_pte_testset(pte, 0);
if (!pmap_ept_valid_entry(opte)) {
return false;
}
pmap_ept_stats_update_bypte(pmap, 0, opte);
if (ptp) {
ptp->wire_count--;
if (ptp->wire_count <= 1) {
opte |= EPT_A;
}
}
if (pmap_ept_has_ad) {
accessed = (opte & EPT_A) != 0;
} else {
accessed = true;
}
if (accessed) {
pmap_tlb_shootdown(pmap, va, 0, TLBSHOOT_REMOVE_PTE);
}
if ((opte & EPT_PVLIST) == 0) {
KASSERTMSG((PHYS_TO_VM_PAGE(pmap_pte2pa(opte)) == NULL),
"managed page without EPT_PVLIST for %#"PRIxVADDR, va);
KASSERTMSG((pmap_pv_tracked(pmap_pte2pa(opte)) == NULL),
"pv-tracked page without EPT_PVLIST for %#"PRIxVADDR, va);
KASSERT(pmap_treelookup_pv(pmap, ptp, (ptp != NULL ?
&VM_PAGE_TO_PP(ptp)->pp_rb : &pmap_kernel_rb), va) == NULL);
return true;
}
if ((pg = PHYS_TO_VM_PAGE(pmap_pte2pa(opte))) != NULL) {
pp = VM_PAGE_TO_PP(pg);
} else if ((pp = pmap_pv_tracked(pmap_pte2pa(opte))) == NULL) {
paddr_t pa = pmap_pte2pa(opte);
panic("%s: EPT_PVLIST with pv-untracked page"
" va = %#"PRIxVADDR"pa = %#"PRIxPADDR" (%#"PRIxPADDR")",
__func__, va, pa, atop(pa));
}
pve = pmap_lookup_pv(pmap, ptp, pp, va);
pmap_remove_pv(pmap, pp, ptp, va, pve, pmap_ept_to_pp_attrs(opte));
return true;
}
static void
pmap_ept_remove_ptes(struct pmap *pmap, struct vm_page *ptp, vaddr_t ptpva,
vaddr_t startva, vaddr_t endva)
{
pt_entry_t *pte = (pt_entry_t *)ptpva;
KASSERT(pmap != pmap_kernel());
KASSERT(mutex_owned(&pmap->pm_lock));
KASSERT(kpreempt_disabled());
pmap_ptp_range_clip(ptp, &startva, &pte);
while (startva < endva && (ptp == NULL || ptp->wire_count > 1)) {
(void)pmap_ept_remove_pte(pmap, ptp, pte, startva);
startva += PAGE_SIZE;
pte++;
}
}
static void
pmap_ept_remove(struct pmap *pmap, vaddr_t sva, vaddr_t eva)
{
pt_entry_t *ptes;
pd_entry_t pde;
paddr_t ptppa;
vaddr_t blkendva, va = sva;
struct vm_page *ptp;
mutex_enter(&pmap->pm_lock);
kpreempt_disable();
for ( ; va < eva ; va = blkendva) {
int lvl;
blkendva = x86_round_pdr(va+1);
if (blkendva > eva)
blkendva = eva;
lvl = pmap_ept_pdes_invalid(pmap, va, &pde);
if (lvl != 0) {
blkendva = (va & ptp_frames[lvl - 1]) + nbpd[lvl - 1];
continue;
}
ptppa = pmap_pte2pa(pde);
ptp = pmap_find_ptp(pmap, va, 1);
KASSERTMSG(ptp != NULL, "%s: unmanaged PTP detected",
__func__);
ptes = (pt_entry_t *)PMAP_DIRECT_MAP(ptppa);
pmap_ept_remove_ptes(pmap, ptp, (vaddr_t)&ptes[pl1_pi(va)], va,
blkendva);
if (ptp && ptp->wire_count <= 1) {
pmap_ept_free_ptp(pmap, ptp, va);
}
}
kpreempt_enable();
pmap_drain_pv(pmap);
mutex_exit(&pmap->pm_lock);
}
static int
pmap_ept_sync_pv(struct vm_page *ptp, vaddr_t va, paddr_t pa, int clearbits,
uint8_t *oattrs, pt_entry_t *optep)
{
struct pmap *pmap;
pt_entry_t *ptep;
pt_entry_t opte;
pt_entry_t npte;
pt_entry_t expect;
bool need_shootdown;
expect = pmap_pa2pte(pa) | EPT_R;
pmap = ptp_to_pmap(ptp);
if (clearbits != ~0) {
KASSERT((clearbits & ~(PP_ATTRS_D|PP_ATTRS_A|PP_ATTRS_W)) == 0);
clearbits = pmap_pp_attrs_to_ept(clearbits);
}
ptep = pmap_map_pte(pmap, ptp, va);
do {
opte = *ptep;
KASSERT((opte & (EPT_D | EPT_A)) != EPT_D);
KASSERT((opte & (EPT_A | EPT_R)) != EPT_A);
KASSERT(opte == 0 || (opte & EPT_R) != 0);
if ((opte & (PTE_FRAME | EPT_R)) != expect) {
pmap_unmap_pte();
return EAGAIN;
}
if ((opte & clearbits) == 0) {
need_shootdown = false;
break;
}
if (pmap_ept_has_ad) {
need_shootdown = (opte & EPT_A) != 0 &&
!(clearbits == EPT_W && (opte & EPT_D) == 0);
} else {
need_shootdown = true;
}
npte = opte & ~clearbits;
if (need_shootdown) {
npte &= ~(EPT_A | EPT_D);
}
KASSERT((npte & (EPT_D | EPT_A)) != EPT_D);
KASSERT((npte & (EPT_A | EPT_R)) != EPT_A);
KASSERT(npte == 0 || (opte & EPT_R) != 0);
} while (pmap_pte_cas(ptep, opte, npte) != opte);
if (need_shootdown) {
pmap_tlb_shootdown(pmap, va, 0, TLBSHOOT_SYNC_PV);
}
pmap_unmap_pte();
*oattrs = pmap_ept_to_pp_attrs(opte);
if (optep != NULL)
*optep = opte;
return 0;
}
static void
pmap_ept_pp_remove_ent(struct pmap *pmap, struct vm_page *ptp, pt_entry_t opte,
vaddr_t va)
{
KASSERT(mutex_owned(&pmap->pm_lock));
pmap_ept_stats_update_bypte(pmap, 0, opte);
ptp->wire_count--;
if (ptp->wire_count <= 1) {
pmap_ept_free_ptp(pmap, ptp, va);
}
}
static void
pmap_ept_write_protect(struct pmap *pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot)
{
pt_entry_t bit_rem;
pt_entry_t *ptes, *spte;
pt_entry_t opte, npte;
pd_entry_t pde;
paddr_t ptppa;
vaddr_t va;
bool modified;
bit_rem = 0;
if (!(prot & VM_PROT_WRITE))
bit_rem = EPT_W;
sva &= PTE_FRAME;
eva &= PTE_FRAME;
mutex_enter(&pmap->pm_lock);
kpreempt_disable();
for (va = sva; va < eva; va += PAGE_SIZE) {
if (pmap_ept_pdes_invalid(pmap, va, &pde)) {
continue;
}
ptppa = pmap_pte2pa(pde);
ptes = (pt_entry_t *)PMAP_DIRECT_MAP(ptppa);
spte = &ptes[pl1_pi(va)];
do {
opte = *spte;
if (!pmap_ept_valid_entry(opte)) {
goto next;
}
npte = (opte & ~bit_rem);
} while (pmap_pte_cas(spte, opte, npte) != opte);
if (pmap_ept_has_ad) {
modified = (opte & EPT_D) != 0;
} else {
modified = true;
}
if (modified) {
vaddr_t tva = x86_ptob(spte - ptes);
pmap_tlb_shootdown(pmap, tva, 0,
TLBSHOOT_WRITE_PROTECT);
}
next:;
}
kpreempt_enable();
mutex_exit(&pmap->pm_lock);
}
static void
pmap_ept_unwire(struct pmap *pmap, vaddr_t va)
{
pt_entry_t *ptes, *ptep, opte;
pd_entry_t pde;
paddr_t ptppa;
mutex_enter(&pmap->pm_lock);
kpreempt_disable();
if (pmap_ept_pdes_invalid(pmap, va, &pde)) {
panic("%s: invalid PDE va=%#" PRIxVADDR, __func__, va);
}
ptppa = pmap_pte2pa(pde);
ptes = (pt_entry_t *)PMAP_DIRECT_MAP(ptppa);
ptep = &ptes[pl1_pi(va)];
opte = *ptep;
KASSERT(pmap_ept_valid_entry(opte));
if (opte & EPT_WIRED) {
pt_entry_t npte = opte & ~EPT_WIRED;
opte = pmap_pte_testset(ptep, npte);
pmap_ept_stats_update_bypte(pmap, npte, opte);
} else {
printf("%s: wiring for pmap %p va %#" PRIxVADDR
"did not change!\n", __func__, pmap, va);
}
kpreempt_enable();
mutex_exit(&pmap->pm_lock);
}
void
pmap_ept_transform(struct pmap *pmap)
{
pmap->pm_enter = pmap_ept_enter;
pmap->pm_extract = pmap_ept_extract;
pmap->pm_remove = pmap_ept_remove;
pmap->pm_sync_pv = pmap_ept_sync_pv;
pmap->pm_pp_remove_ent = pmap_ept_pp_remove_ent;
pmap->pm_write_protect = pmap_ept_write_protect;
pmap->pm_unwire = pmap_ept_unwire;
memset(PAGE_ALIGNED(pmap->pm_pdir), 0, PAGE_SIZE);
}
#endif