#include "opt_lockdebug.h"
#include "opt_sysv.h"
#include "opt_multiprocessor.h"
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pmap.c,v 1.309 2026/06/10 21:24:11 andvar Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/pool.h>
#include <sys/buf.h>
#include <sys/evcnt.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <uvm/uvm.h>
#if defined(MULTIPROCESSOR)
#include <machine/rpb.h>
#endif
#ifdef DEBUG
#define PDB_FOLLOW 0x0001
#define PDB_INIT 0x0002
#define PDB_ENTER 0x0004
#define PDB_REMOVE 0x0008
#define PDB_CREATE 0x0010
#define PDB_PTPAGE 0x0020
#define PDB_ASN 0x0040
#define PDB_BITS 0x0080
#define PDB_COLLECT 0x0100
#define PDB_PROTECT 0x0200
#define PDB_BOOTSTRAP 0x1000
#define PDB_PARANOIA 0x2000
#define PDB_WIRING 0x4000
#define PDB_PVDUMP 0x8000
int debugmap = 0;
int pmapdebug = PDB_PARANOIA;
#endif
#if defined(MULTIPROCESSOR)
#define PMAP_MP(x) x
#else
#define PMAP_MP(x) __nothing
#endif
#define pte_prot(m, p) (protection_codes[m == pmap_kernel() ? 0 : 1][p])
static int protection_codes[2][8] __read_mostly;
pt_entry_t *kernel_lev1map __read_mostly;
static pt_entry_t *VPT __read_mostly;
static struct {
struct pmap k_pmap;
} kernel_pmap_store __cacheline_aligned;
struct pmap *const kernel_pmap_ptr = &kernel_pmap_store.k_pmap;
paddr_t avail_start __read_mostly;
paddr_t avail_end __read_mostly;
static vaddr_t virtual_end __read_mostly;
static bool pmap_initialized __read_mostly;
u_long pmap_pages_stolen __read_mostly;
static u_long pmap_ncpuids __read_mostly;
#ifndef PMAP_PV_LOWAT
#define PMAP_PV_LOWAT 16
#endif
int pmap_pv_lowat __read_mostly = PMAP_PV_LOWAT;
static TAILQ_HEAD(, pmap) pmap_all_pmaps __cacheline_aligned;
static struct evcnt pmap_growkernel_evcnt __read_mostly;
static struct pool_cache pmap_pmap_cache __read_mostly;
static struct pool_cache pmap_l1pt_cache __read_mostly;
static struct pool_cache pmap_pv_cache __read_mostly;
CTASSERT(offsetof(struct pmap, pm_percpu[0]) == COHERENCY_UNIT);
CTASSERT(PMAP_SIZEOF(ALPHA_MAXPROCS) < ALPHA_PGBYTES);
CTASSERT(sizeof(struct pmap_percpu) == COHERENCY_UNIT);
static u_int pmap_max_asn __read_mostly;
static krwlock_t pmap_main_lock __cacheline_aligned;
static kmutex_t pmap_all_pmaps_lock __cacheline_aligned;
static krwlock_t pmap_growkernel_lock __cacheline_aligned;
#define PMAP_MAP_TO_HEAD_LOCK() rw_enter(&pmap_main_lock, RW_READER)
#define PMAP_MAP_TO_HEAD_UNLOCK() rw_exit(&pmap_main_lock)
#define PMAP_HEAD_TO_MAP_LOCK() rw_enter(&pmap_main_lock, RW_WRITER)
#define PMAP_HEAD_TO_MAP_UNLOCK() rw_exit(&pmap_main_lock)
static union {
kmutex_t lock;
uint8_t pad[COHERENCY_UNIT];
} pmap_pvh_locks[64] __cacheline_aligned;
#define PVH_LOCK_HASH(pg) \
((((uintptr_t)(pg)) >> 6) & 63)
static inline kmutex_t *
pmap_pvh_lock(struct vm_page *pg)
{
return &pmap_pvh_locks[PVH_LOCK_HASH(pg)].lock;
}
static union {
struct {
kmutex_t lock;
kmutex_t activation_lock;
} locks;
uint8_t pad[COHERENCY_UNIT];
} pmap_pmap_locks[64] __cacheline_aligned;
#define PMAP_LOCK_HASH(pm) \
((((uintptr_t)(pm)) >> 6) & 63)
static inline kmutex_t *
pmap_pmap_lock(pmap_t const pmap)
{
return &pmap_pmap_locks[PMAP_LOCK_HASH(pmap)].locks.lock;
}
static inline kmutex_t *
pmap_activation_lock(pmap_t const pmap)
{
return &pmap_pmap_locks[PMAP_LOCK_HASH(pmap)].locks.activation_lock;
}
#define PMAP_LOCK(pmap) mutex_enter(pmap_pmap_lock(pmap))
#define PMAP_UNLOCK(pmap) mutex_exit(pmap_pmap_lock(pmap))
#define PMAP_ACT_LOCK(pmap) mutex_spin_enter(pmap_activation_lock(pmap))
#define PMAP_ACT_TRYLOCK(pmap) mutex_tryenter(pmap_activation_lock(pmap))
#define PMAP_ACT_UNLOCK(pmap) mutex_spin_exit(pmap_activation_lock(pmap))
#if defined(MULTIPROCESSOR)
#define pmap_all_cpus() cpus_running
#else
#define pmap_all_cpus() ~0UL
#endif
#define TLB_CTX_MAXVA 8
#define TLB_CTX_ALLVA PAGE_MASK
struct pmap_tlb_context {
uintptr_t t_addrdata[TLB_CTX_MAXVA];
pmap_t t_pmap;
struct pmap_pagelist t_freeptq;
struct pmap_pvlist t_freepvq;
};
static void alpha_protection_init(void);
static pt_entry_t pmap_remove_mapping(pmap_t, vaddr_t, pt_entry_t *, bool,
pv_entry_t *,
struct pmap_tlb_context *);
static void pmap_changebit(struct vm_page *, pt_entry_t, pt_entry_t,
struct pmap_tlb_context *);
static int pmap_ptpage_alloc(pmap_t, pt_entry_t *, int);
static void pmap_ptpage_free(pmap_t, pt_entry_t *,
struct pmap_tlb_context *);
static void pmap_l3pt_delref(pmap_t, vaddr_t, pt_entry_t *,
struct pmap_tlb_context *);
static void pmap_l2pt_delref(pmap_t, pt_entry_t *, pt_entry_t *,
struct pmap_tlb_context *);
static void pmap_l1pt_delref(pmap_t, pt_entry_t *);
static void *pmap_l1pt_alloc(struct pool *, int);
static void pmap_l1pt_free(struct pool *, void *);
static struct pool_allocator pmap_l1pt_allocator = {
pmap_l1pt_alloc, pmap_l1pt_free, 0,
};
static int pmap_l1pt_ctor(void *, void *, int);
static int pmap_pv_enter(pmap_t, struct vm_page *, vaddr_t, pt_entry_t *,
bool, pv_entry_t);
static void pmap_pv_remove(pmap_t, struct vm_page *, vaddr_t, bool,
pv_entry_t *, struct pmap_tlb_context *);
static void *pmap_pv_page_alloc(struct pool *, int);
static void pmap_pv_page_free(struct pool *, void *);
static struct pool_allocator pmap_pv_page_allocator = {
pmap_pv_page_alloc, pmap_pv_page_free, 0,
};
#ifdef DEBUG
void pmap_pv_dump(paddr_t);
#endif
#define pmap_pv_alloc() pool_cache_get(&pmap_pv_cache, PR_NOWAIT)
#define pmap_pv_free(pv) pool_cache_put(&pmap_pv_cache, (pv))
static void
pmap_pagelist_free(struct pmap_pagelist * const list)
{
struct vm_page *pg;
while ((pg = LIST_FIRST(list)) != NULL) {
LIST_REMOVE(pg, pageq.list);
PHYSPAGE_REFCNT_SET(pg, 0);
uvm_pagefree(pg);
}
}
static void
pmap_pvlist_free(struct pmap_pvlist * const list)
{
pv_entry_t pv;
while ((pv = LIST_FIRST(list)) != NULL) {
LIST_REMOVE(pv, pv_link);
pmap_pv_free(pv);
}
}
#define TLB_CTX_F_ASM __BIT(0)
#define TLB_CTX_F_IMB __BIT(1)
#define TLB_CTX_F_KIMB __BIT(2)
#define TLB_CTX_F_PV __BIT(3)
#define TLB_CTX_F_MULTI __BIT(4)
#define TLB_CTX_COUNT(ctx) ((ctx)->t_addrdata[0] & PAGE_MASK)
#define TLB_CTX_INC_COUNT(ctx) (ctx)->t_addrdata[0]++
#define TLB_CTX_SET_ALLVA(ctx) (ctx)->t_addrdata[0] |= TLB_CTX_ALLVA
#define TLB_CTX_FLAGS(ctx) ((ctx)->t_addrdata[1] & PAGE_MASK)
#define TLB_CTX_SET_FLAG(ctx, f) (ctx)->t_addrdata[1] |= (f)
#define TLB_CTX_VA(ctx, i) ((ctx)->t_addrdata[(i)] & ~PAGE_MASK)
#define TLB_CTX_SETVA(ctx, i, va) \
(ctx)->t_addrdata[(i)] = (va) | ((ctx)->t_addrdata[(i)] & PAGE_MASK)
static struct {
kmutex_t lock;
struct evcnt events;
} tlb_shootdown __cacheline_aligned;
#define tlb_lock tlb_shootdown.lock
#define tlb_evcnt tlb_shootdown.events
#if defined(MULTIPROCESSOR)
static const struct pmap_tlb_context *tlb_context __cacheline_aligned;
static unsigned long tlb_pending __cacheline_aligned;
#endif
#if defined(TLB_STATS)
#define TLB_COUNT_DECL(cnt) static struct evcnt tlb_stat_##cnt
#define TLB_COUNT(cnt) atomic_inc_64(&tlb_stat_##cnt .ev_count)
#define TLB_COUNT_ATTACH(cnt) \
evcnt_attach_dynamic_nozero(&tlb_stat_##cnt, EVCNT_TYPE_MISC, \
NULL, "TLB", #cnt)
TLB_COUNT_DECL(invalidate_multi_tbia);
TLB_COUNT_DECL(invalidate_multi_tbiap);
TLB_COUNT_DECL(invalidate_multi_imb);
TLB_COUNT_DECL(invalidate_kern_tbia);
TLB_COUNT_DECL(invalidate_kern_tbis);
TLB_COUNT_DECL(invalidate_kern_imb);
TLB_COUNT_DECL(invalidate_user_not_current);
TLB_COUNT_DECL(invalidate_user_lazy_imb);
TLB_COUNT_DECL(invalidate_user_tbiap);
TLB_COUNT_DECL(invalidate_user_tbis);
TLB_COUNT_DECL(shootdown_kernel);
TLB_COUNT_DECL(shootdown_user);
TLB_COUNT_DECL(shootdown_imb);
TLB_COUNT_DECL(shootdown_kimb);
TLB_COUNT_DECL(shootdown_overflow);
TLB_COUNT_DECL(shootdown_all_user);
TLB_COUNT_DECL(shootdown_all_user_imb);
TLB_COUNT_DECL(shootdown_pv);
TLB_COUNT_DECL(shootdown_pv_multi);
TLB_COUNT_DECL(shootnow_over_notify);
TLB_COUNT_DECL(shootnow_remote);
TLB_COUNT_DECL(reason_remove_kernel);
TLB_COUNT_DECL(reason_remove_user);
TLB_COUNT_DECL(reason_remove_all_user);
TLB_COUNT_DECL(reason_page_protect_read);
TLB_COUNT_DECL(reason_page_protect_none);
TLB_COUNT_DECL(reason_protect);
TLB_COUNT_DECL(reason_enter_kernel);
TLB_COUNT_DECL(reason_enter_user);
TLB_COUNT_DECL(reason_kenter);
TLB_COUNT_DECL(reason_enter_l2pt_delref);
TLB_COUNT_DECL(reason_enter_l3pt_delref);
TLB_COUNT_DECL(reason_kremove);
TLB_COUNT_DECL(reason_clear_modify);
TLB_COUNT_DECL(reason_clear_reference);
TLB_COUNT_DECL(reason_emulate_reference);
TLB_COUNT_DECL(asn_reuse);
TLB_COUNT_DECL(asn_newgen);
TLB_COUNT_DECL(asn_assign);
TLB_COUNT_DECL(activate_both_change);
TLB_COUNT_DECL(activate_asn_change);
TLB_COUNT_DECL(activate_ptbr_change);
TLB_COUNT_DECL(activate_swpctx);
TLB_COUNT_DECL(activate_skip_swpctx);
#else
#define TLB_COUNT(cnt) __nothing
#define TLB_COUNT_ATTACH(cnt) __nothing
#endif
static void
pmap_tlb_init(void)
{
evcnt_attach_dynamic_nozero(&tlb_evcnt, EVCNT_TYPE_MISC,
NULL, "TLB", "shootdown");
TLB_COUNT_ATTACH(invalidate_multi_tbia);
TLB_COUNT_ATTACH(invalidate_multi_tbiap);
TLB_COUNT_ATTACH(invalidate_multi_imb);
TLB_COUNT_ATTACH(invalidate_kern_tbia);
TLB_COUNT_ATTACH(invalidate_kern_tbis);
TLB_COUNT_ATTACH(invalidate_kern_imb);
TLB_COUNT_ATTACH(invalidate_user_not_current);
TLB_COUNT_ATTACH(invalidate_user_lazy_imb);
TLB_COUNT_ATTACH(invalidate_user_tbiap);
TLB_COUNT_ATTACH(invalidate_user_tbis);
TLB_COUNT_ATTACH(shootdown_kernel);
TLB_COUNT_ATTACH(shootdown_user);
TLB_COUNT_ATTACH(shootdown_imb);
TLB_COUNT_ATTACH(shootdown_kimb);
TLB_COUNT_ATTACH(shootdown_overflow);
TLB_COUNT_ATTACH(shootdown_all_user);
TLB_COUNT_ATTACH(shootdown_all_user_imb);
TLB_COUNT_ATTACH(shootdown_pv);
TLB_COUNT_ATTACH(shootdown_pv_multi);
TLB_COUNT_ATTACH(shootnow_over_notify);
TLB_COUNT_ATTACH(shootnow_remote);
TLB_COUNT_ATTACH(reason_remove_kernel);
TLB_COUNT_ATTACH(reason_remove_user);
TLB_COUNT_ATTACH(reason_remove_all_user);
TLB_COUNT_ATTACH(reason_page_protect_read);
TLB_COUNT_ATTACH(reason_page_protect_none);
TLB_COUNT_ATTACH(reason_protect);
TLB_COUNT_ATTACH(reason_enter_kernel);
TLB_COUNT_ATTACH(reason_enter_user);
TLB_COUNT_ATTACH(reason_kenter);
TLB_COUNT_ATTACH(reason_enter_l2pt_delref);
TLB_COUNT_ATTACH(reason_enter_l3pt_delref);
TLB_COUNT_ATTACH(reason_kremove);
TLB_COUNT_ATTACH(reason_clear_modify);
TLB_COUNT_ATTACH(reason_clear_reference);
TLB_COUNT_ATTACH(asn_reuse);
TLB_COUNT_ATTACH(asn_newgen);
TLB_COUNT_ATTACH(asn_assign);
TLB_COUNT_ATTACH(activate_both_change);
TLB_COUNT_ATTACH(activate_asn_change);
TLB_COUNT_ATTACH(activate_ptbr_change);
TLB_COUNT_ATTACH(activate_swpctx);
TLB_COUNT_ATTACH(activate_skip_swpctx);
}
static inline void
pmap_tlb_context_init(struct pmap_tlb_context * const tlbctx, uintptr_t flags)
{
tlbctx->t_addrdata[0] = 0;
tlbctx->t_addrdata[1] = flags;
tlbctx->t_pmap = NULL;
LIST_INIT(&tlbctx->t_freeptq);
LIST_INIT(&tlbctx->t_freepvq);
}
static void
pmap_tlb_shootdown_internal(pmap_t const pmap, vaddr_t const va,
pt_entry_t const pte_bits, struct pmap_tlb_context * const tlbctx)
{
KASSERT(pmap != NULL);
KASSERT((va & PAGE_MASK) == 0);
if (pmap == pmap_kernel()) {
TLB_COUNT(shootdown_kernel);
KASSERT(pte_bits & PG_ASM);
TLB_CTX_SET_FLAG(tlbctx, TLB_CTX_F_ASM);
if (pte_bits & PG_EXEC) {
TLB_COUNT(shootdown_kimb);
TLB_CTX_SET_FLAG(tlbctx, TLB_CTX_F_KIMB);
}
} else {
TLB_COUNT(shootdown_user);
KASSERT((pte_bits & PG_ASM) == 0);
if (pte_bits & PG_EXEC) {
TLB_COUNT(shootdown_imb);
TLB_CTX_SET_FLAG(tlbctx, TLB_CTX_F_IMB);
}
}
KASSERT(tlbctx->t_pmap == NULL || tlbctx->t_pmap == pmap);
tlbctx->t_pmap = pmap;
const uintptr_t count = TLB_CTX_COUNT(tlbctx);
if (count > TLB_CTX_MAXVA) {
return;
}
if (count == TLB_CTX_MAXVA) {
TLB_COUNT(shootdown_overflow);
TLB_CTX_SET_ALLVA(tlbctx);
return;
}
TLB_CTX_SETVA(tlbctx, count, va);
TLB_CTX_INC_COUNT(tlbctx);
}
static void
pmap_tlb_shootdown(pmap_t const pmap, vaddr_t const va,
pt_entry_t const pte_bits, struct pmap_tlb_context * const tlbctx)
{
KASSERT((TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_PV) == 0);
pmap_tlb_shootdown_internal(pmap, va, pte_bits, tlbctx);
}
static void
pmap_tlb_shootdown_all_user(pmap_t const pmap, pt_entry_t const pte_bits,
struct pmap_tlb_context * const tlbctx)
{
KASSERT(pmap != pmap_kernel());
TLB_COUNT(shootdown_all_user);
if (pte_bits & PG_EXEC) {
TLB_COUNT(shootdown_all_user_imb);
TLB_CTX_SET_FLAG(tlbctx, TLB_CTX_F_IMB);
}
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_PV) {
if (tlbctx->t_pmap == NULL || tlbctx->t_pmap == pmap) {
if (tlbctx->t_pmap == NULL) {
pmap_reference(pmap);
tlbctx->t_pmap = pmap;
}
} else {
TLB_CTX_SET_FLAG(tlbctx, TLB_CTX_F_MULTI);
}
} else {
KASSERT(tlbctx->t_pmap == NULL || tlbctx->t_pmap == pmap);
tlbctx->t_pmap = pmap;
}
TLB_CTX_SET_ALLVA(tlbctx);
}
static void
pmap_tlb_shootdown_pv(pmap_t const pmap, vaddr_t const va,
pt_entry_t const pte_bits, struct pmap_tlb_context * const tlbctx)
{
KASSERT(TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_PV);
TLB_COUNT(shootdown_pv);
if (tlbctx->t_pmap == NULL || tlbctx->t_pmap == pmap) {
if (tlbctx->t_pmap == NULL) {
pmap_reference(pmap);
tlbctx->t_pmap = pmap;
}
pmap_tlb_shootdown_internal(pmap, va, pte_bits, tlbctx);
} else {
TLB_COUNT(shootdown_pv_multi);
uintptr_t flags = TLB_CTX_F_MULTI;
if (pmap == pmap_kernel()) {
KASSERT(pte_bits & PG_ASM);
flags |= TLB_CTX_F_ASM;
} else {
KASSERT((pte_bits & PG_ASM) == 0);
}
if (pte_bits & PG_EXEC) {
flags |= TLB_CTX_F_IMB;
}
TLB_CTX_SET_ALLVA(tlbctx);
TLB_CTX_SET_FLAG(tlbctx, flags);
}
}
static void
pmap_tlb_invalidate_multi(const struct pmap_tlb_context * const tlbctx)
{
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_ASM) {
TLB_COUNT(invalidate_multi_tbia);
ALPHA_TBIA();
} else {
TLB_COUNT(invalidate_multi_tbiap);
ALPHA_TBIAP();
}
if (TLB_CTX_FLAGS(tlbctx) & (TLB_CTX_F_IMB | TLB_CTX_F_KIMB)) {
TLB_COUNT(invalidate_multi_imb);
alpha_pal_imb();
}
}
static void
pmap_tlb_invalidate_kernel(const struct pmap_tlb_context * const tlbctx)
{
const uintptr_t count = TLB_CTX_COUNT(tlbctx);
if (count == TLB_CTX_ALLVA) {
TLB_COUNT(invalidate_kern_tbia);
ALPHA_TBIA();
} else {
TLB_COUNT(invalidate_kern_tbis);
for (uintptr_t i = 0; i < count; i++) {
ALPHA_TBIS(TLB_CTX_VA(tlbctx, i));
}
}
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_KIMB) {
TLB_COUNT(invalidate_kern_imb);
alpha_pal_imb();
}
}
static void
pmap_tlb_invalidate(const struct pmap_tlb_context * const tlbctx,
const struct cpu_info * const ci)
{
const uintptr_t count = TLB_CTX_COUNT(tlbctx);
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_MULTI) {
pmap_tlb_invalidate_multi(tlbctx);
return;
}
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_ASM) {
pmap_tlb_invalidate_kernel(tlbctx);
return;
}
KASSERT(kpreempt_disabled());
pmap_t const pmap = tlbctx->t_pmap;
KASSERT(pmap != NULL);
if (__predict_false(pmap != ci->ci_pmap)) {
TLB_COUNT(invalidate_user_not_current);
if (__predict_false(pmap_max_asn == 0)) {
return;
}
const u_long cpu_mask = 1UL << ci->ci_cpuid;
pmap->pm_percpu[ci->ci_cpuid].pmc_asngen = PMAP_ASNGEN_INVALID;
atomic_and_ulong(&pmap->pm_cpus, ~cpu_mask);
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_IMB) {
pmap->pm_percpu[ci->ci_cpuid].pmc_needisync = 1;
}
return;
}
if (TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_IMB) {
TLB_COUNT(invalidate_user_lazy_imb);
pmap->pm_percpu[ci->ci_cpuid].pmc_needisync = 1;
}
if (count == TLB_CTX_ALLVA) {
TLB_COUNT(invalidate_user_tbiap);
KASSERT((TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_ASM) == 0);
ALPHA_TBIAP();
} else {
TLB_COUNT(invalidate_user_tbis);
for (uintptr_t i = 0; i < count; i++) {
ALPHA_TBIS(TLB_CTX_VA(tlbctx, i));
}
}
}
static void
pmap_tlb_shootnow(const struct pmap_tlb_context * const tlbctx)
{
if (TLB_CTX_COUNT(tlbctx) == 0) {
return;
}
KASSERT(alpha_pal_rdps() < ALPHA_PSL_IPL_CLOCK);
mutex_spin_enter(&tlb_lock);
tlb_evcnt.ev_count++;
const struct cpu_info *ci = curcpu();
const u_long this_cpu = 1UL << ci->ci_cpuid;
u_long active_cpus;
bool activation_locked, activation_lock_tried;
if (TLB_CTX_FLAGS(tlbctx) & (TLB_CTX_F_ASM | TLB_CTX_F_MULTI)) {
active_cpus = pmap_all_cpus();
activation_locked = false;
activation_lock_tried = false;
} else {
KASSERT(tlbctx->t_pmap != NULL);
activation_locked = PMAP_ACT_TRYLOCK(tlbctx->t_pmap);
if (__predict_true(activation_locked)) {
active_cpus = tlbctx->t_pmap->pm_cpus;
} else {
TLB_COUNT(shootnow_over_notify);
active_cpus = pmap_all_cpus();
}
activation_lock_tried = true;
}
#if defined(MULTIPROCESSOR)
const u_long remote_cpus = active_cpus & ~this_cpu;
KASSERT(tlb_context == NULL);
if (remote_cpus) {
TLB_COUNT(shootnow_remote);
tlb_context = tlbctx;
tlb_pending = remote_cpus;
alpha_multicast_ipi(remote_cpus, ALPHA_IPI_SHOOTDOWN);
}
#endif
if (activation_lock_tried) {
if (activation_locked) {
KASSERT(tlbctx->t_pmap != NULL);
PMAP_ACT_UNLOCK(tlbctx->t_pmap);
}
alpha_pal_swpipl(IPL_VM);
}
if (active_cpus & this_cpu) {
pmap_tlb_invalidate(tlbctx, ci);
}
#if defined(MULTIPROCESSOR)
if (remote_cpus) {
int backoff = SPINLOCK_BACKOFF_MIN;
u_int spins = 0;
while (atomic_load_acquire(&tlb_context) != NULL) {
SPINLOCK_BACKOFF(backoff);
if (spins++ > 0x0fffffff) {
printf("TLB LOCAL MASK = 0x%016lx\n",
this_cpu);
printf("TLB REMOTE MASK = 0x%016lx\n",
remote_cpus);
printf("TLB REMOTE PENDING = 0x%016lx\n",
tlb_pending);
printf("TLB CONTEXT = %p\n", tlb_context);
printf("TLB LOCAL IPL = %lu\n",
alpha_pal_rdps());
panic("pmap_tlb_shootnow");
}
}
}
KASSERT(tlb_context == NULL);
#endif
mutex_spin_exit(&tlb_lock);
if (__predict_false(TLB_CTX_FLAGS(tlbctx) & TLB_CTX_F_PV)) {
KASSERT(tlbctx->t_pmap != NULL);
pmap_destroy(tlbctx->t_pmap);
}
}
#if defined(MULTIPROCESSOR)
void
pmap_tlb_shootdown_ipi(struct cpu_info * const ci,
struct trapframe * const tf __unused)
{
KASSERT(tlb_context != NULL);
pmap_tlb_invalidate(tlb_context, ci);
if (atomic_and_ulong_nv(&tlb_pending, ~(1UL << ci->ci_cpuid)) == 0) {
atomic_store_release(&tlb_context, NULL);
}
}
#endif
static inline void
pmap_tlb_context_drain(struct pmap_tlb_context * const tlbctx)
{
if (! LIST_EMPTY(&tlbctx->t_freeptq)) {
pmap_pagelist_free(&tlbctx->t_freeptq);
}
if (! LIST_EMPTY(&tlbctx->t_freepvq)) {
pmap_pvlist_free(&tlbctx->t_freepvq);
}
}
static u_int pmap_asn_alloc(pmap_t, struct cpu_info *);
static struct vm_page *pmap_physpage_alloc(int);
static void pmap_physpage_free(paddr_t);
static int pmap_physpage_addref(void *);
static int pmap_physpage_delref(void *);
static bool vtophys_internal(vaddr_t, paddr_t *p);
#ifdef DEBUG
#define PMAP_KERNEL_PTE(va) \
({ \
pt_entry_t *l1pte_, *l2pte_; \
\
l1pte_ = pmap_l1pte(kernel_lev1map, va); \
if (pmap_pte_v(l1pte_) == 0) { \
printf("kernel level 1 PTE not valid, va 0x%lx " \
"(line %d) pte=%p *pte=0x%016lx\n", (va), __LINE__, \
l1pte_, *l1pte_); \
panic("PMAP_KERNEL_PTE"); \
} \
l2pte_ = pmap_l2pte(kernel_lev1map, va, l1pte_); \
if (pmap_pte_v(l2pte_) == 0) { \
printf("kernel level 2 PTE not valid, va 0x%lx " \
"(line %d) pte=%p *pte=0x%016lx\n", (va), __LINE__, \
l2pte_, *l2pte_); \
panic("PMAP_KERNEL_PTE"); \
} \
pmap_l3pte(kernel_lev1map, va, l2pte_); \
})
#else
#define PMAP_KERNEL_PTE(va) (&VPT[VPT_INDEX((va))])
#endif
#define PMAP_STAT_INCR(s, v) atomic_add_long((unsigned long *)(&(s)), (v))
#define PMAP_STAT_DECR(s, v) atomic_add_long((unsigned long *)(&(s)), -(v))
void
pmap_init_cpu(struct cpu_info * const ci)
{
pmap_t const pmap = pmap_kernel();
atomic_or_ulong(&pmap->pm_cpus, 1UL << ci->ci_cpuid);
pmap_reference(pmap);
ci->ci_pmap = pmap;
ci->ci_next_asn = PMAP_ASN_FIRST_USER;
ci->ci_asn_gen = PMAP_ASNGEN_INITIAL;
}
void
pmap_bootstrap(paddr_t ptaddr, u_int maxasn, u_long ncpuids)
{
vsize_t lev2mapsize, lev3mapsize;
pt_entry_t *lev2map, *lev3map;
pt_entry_t pte;
vsize_t bufsz;
struct pcb *pcb;
int i;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_BOOTSTRAP))
printf("pmap_bootstrap(0x%lx, %u)\n", ptaddr, maxasn);
#endif
kmeminit_nkmempages();
bufsz = buf_memcalc();
buf_setvalimit(bufsz);
lev3mapsize =
(VM_PHYS_SIZE + (ubc_nwins << ubc_winshift) +
bufsz + 16 * NCARGS + pager_map_size) / PAGE_SIZE +
(maxproc * UPAGES) + nkmempages;
lev3mapsize = roundup(lev3mapsize, NPTEPG);
avail_start = ptoa(uvm_physseg_get_avail_start(uvm_physseg_get_first()));
avail_end = ptoa(uvm_physseg_get_avail_end(uvm_physseg_get_last()));
virtual_end = VM_MIN_KERNEL_ADDRESS + lev3mapsize * PAGE_SIZE;
#if 0
printf("avail_start = 0x%lx\n", avail_start);
printf("avail_end = 0x%lx\n", avail_end);
printf("virtual_end = 0x%lx\n", virtual_end);
#endif
kernel_lev1map = (pt_entry_t *)
uvm_pageboot_alloc(sizeof(pt_entry_t) * NPTEPG);
lev2mapsize = roundup(howmany(lev3mapsize, NPTEPG), NPTEPG);
lev2map = (pt_entry_t *)
uvm_pageboot_alloc(sizeof(pt_entry_t) * lev2mapsize);
lev3map = (pt_entry_t *)
uvm_pageboot_alloc(sizeof(pt_entry_t) * lev3mapsize);
for (i = 0; i < howmany(lev2mapsize, NPTEPG); i++) {
pte = (ALPHA_K0SEG_TO_PHYS(((vaddr_t)lev2map) +
(i*PAGE_SIZE)) >> PGSHIFT) << PG_SHIFT;
pte |= PG_V | PG_ASM | PG_KRE | PG_KWE | PG_WIRED;
kernel_lev1map[l1pte_index(VM_MIN_KERNEL_ADDRESS +
(i*PAGE_SIZE*NPTEPG*NPTEPG))] = pte;
}
pte = (ALPHA_K0SEG_TO_PHYS((vaddr_t)kernel_lev1map) >> PGSHIFT)
<< PG_SHIFT;
pte |= PG_V | PG_KRE | PG_KWE;
kernel_lev1map[l1pte_index(VPTBASE)] = pte;
VPT = (pt_entry_t *)VPTBASE;
for (i = 0; i < howmany(lev3mapsize, NPTEPG); i++) {
pte = (ALPHA_K0SEG_TO_PHYS(((vaddr_t)lev3map) +
(i*PAGE_SIZE)) >> PGSHIFT) << PG_SHIFT;
pte |= PG_V | PG_ASM | PG_KRE | PG_KWE | PG_WIRED;
lev2map[i] = pte;
}
rw_init(&pmap_growkernel_lock);
pmap_ncpuids = ncpuids;
pool_cache_bootstrap(&pmap_pmap_cache, PMAP_SIZEOF(pmap_ncpuids),
COHERENCY_UNIT, 0, 0, "pmap", NULL, IPL_NONE, NULL, NULL, NULL);
pool_cache_bootstrap(&pmap_l1pt_cache, PAGE_SIZE, 0, 0, 0, "pmapl1pt",
&pmap_l1pt_allocator, IPL_NONE, pmap_l1pt_ctor, NULL, NULL);
pool_cache_bootstrap(&pmap_pv_cache, sizeof(struct pv_entry), 0, 0,
PR_LARGECACHE, "pmappv", &pmap_pv_page_allocator, IPL_NONE, NULL,
NULL, NULL);
TAILQ_INIT(&pmap_all_pmaps);
pmap_max_asn = maxasn;
rw_init(&pmap_main_lock);
mutex_init(&pmap_all_pmaps_lock, MUTEX_DEFAULT, IPL_NONE);
for (i = 0; i < __arraycount(pmap_pvh_locks); i++) {
mutex_init(&pmap_pvh_locks[i].lock, MUTEX_DEFAULT, IPL_NONE);
}
for (i = 0; i < __arraycount(pmap_pvh_locks); i++) {
mutex_init(&pmap_pmap_locks[i].locks.lock,
MUTEX_DEFAULT, IPL_NONE);
mutex_init(&pmap_pmap_locks[i].locks.activation_lock,
MUTEX_SPIN, IPL_SCHED);
}
mutex_init(&tlb_lock, MUTEX_SPIN, IPL_VM);
memset(pmap_kernel(), 0, sizeof(struct pmap));
LIST_INIT(&pmap_kernel()->pm_ptpages);
LIST_INIT(&pmap_kernel()->pm_pvents);
atomic_store_relaxed(&pmap_kernel()->pm_count, 1);
TAILQ_INSERT_TAIL(&pmap_all_pmaps, pmap_kernel(), pm_list);
pcb = lwp_getpcb(&lwp0);
pcb->pcb_hw.apcb_ptbr =
ALPHA_K0SEG_TO_PHYS((vaddr_t)kernel_lev1map) >> PGSHIFT;
pcb->pcb_hw.apcb_asn = PMAP_ASN_KERNEL;
struct cpu_info * const ci = curcpu();
pmap_init_cpu(ci);
}
void
pmap_virtual_space(vaddr_t *vstartp, vaddr_t *vendp)
{
*vstartp = VM_MIN_KERNEL_ADDRESS;
*vendp = VM_MAX_KERNEL_ADDRESS;
}
vaddr_t
pmap_steal_memory(vsize_t size, vaddr_t *vstartp, vaddr_t *vendp)
{
int npgs;
vaddr_t va;
paddr_t pa;
uvm_physseg_t bank;
size = round_page(size);
npgs = atop(size);
#if 0
printf("PSM: size 0x%lx (npgs 0x%x)\n", size, npgs);
#endif
for (bank = uvm_physseg_get_first();
uvm_physseg_valid_p(bank);
bank = uvm_physseg_get_next(bank)) {
if (uvm.page_init_done == true)
panic("pmap_steal_memory: called _after_ bootstrap");
#if 0
printf(" bank %d: avail_start 0x%"PRIxPADDR", start 0x%"PRIxPADDR", "
"avail_end 0x%"PRIxPADDR"\n", bank, uvm_physseg_get_avail_start(bank),
uvm_physseg_get_start(bank), uvm_physseg_get_avail_end(bank));
#endif
if (uvm_physseg_get_avail_start(bank) != uvm_physseg_get_start(bank) ||
uvm_physseg_get_avail_start(bank) >= uvm_physseg_get_avail_end(bank))
continue;
#if 0
printf(" avail_end - avail_start = 0x%"PRIxPADDR"\n",
uvm_physseg_get_avail_end(bank) - uvm_physseg_get_avail_start(bank));
#endif
if (uvm_physseg_get_avail_end(bank) - uvm_physseg_get_avail_start(bank)
< npgs)
continue;
pa = ptoa(uvm_physseg_get_start(bank));
uvm_physseg_unplug(atop(pa), npgs);
va = ALPHA_PHYS_TO_K0SEG(pa);
memset((void *)va, 0, size);
pmap_pages_stolen += npgs;
return (va);
}
panic("pmap_steal_memory: no memory to steal");
}
void
pmap_init(void)
{
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_init()\n");
#endif
alpha_protection_init();
pmap_tlb_init();
evcnt_attach_dynamic_nozero(&pmap_growkernel_evcnt, EVCNT_TYPE_MISC,
NULL, "pmap", "growkernel");
pool_cache_setlowat(&pmap_pv_cache, pmap_pv_lowat);
pmap_initialized = true;
#if 0
for (uvm_physseg_t bank = uvm_physseg_get_first();
uvm_physseg_valid_p(bank);
bank = uvm_physseg_get_next(bank)) {
printf("bank %d\n", bank);
printf("\tstart = 0x%lx\n", ptoa(uvm_physseg_get_start(bank)));
printf("\tend = 0x%lx\n", ptoa(uvm_physseg_get_end(bank)));
printf("\tavail_start = 0x%lx\n",
ptoa(uvm_physseg_get_avail_start(bank)));
printf("\tavail_end = 0x%lx\n",
ptoa(uvm_physseg_get_avail_end(bank)));
}
#endif
}
pmap_t
pmap_create(void)
{
pmap_t pmap;
pt_entry_t *lev1map;
int i;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_CREATE))
printf("pmap_create()\n");
#endif
pmap = pool_cache_get(&pmap_pmap_cache, PR_WAITOK);
memset(pmap, 0, sizeof(*pmap));
LIST_INIT(&pmap->pm_ptpages);
LIST_INIT(&pmap->pm_pvents);
atomic_store_relaxed(&pmap->pm_count, 1);
try_again:
rw_enter(&pmap_growkernel_lock, RW_READER);
lev1map = pool_cache_get(&pmap_l1pt_cache, PR_NOWAIT);
if (__predict_false(lev1map == NULL)) {
rw_exit(&pmap_growkernel_lock);
(void) kpause("pmap_create", false, hz >> 2, NULL);
goto try_again;
}
for (i = 0; i < pmap_ncpuids; i++) {
pmap->pm_percpu[i].pmc_asn = PMAP_ASN_KERNEL;
pmap->pm_percpu[i].pmc_asngen = PMAP_ASNGEN_INVALID;
pmap->pm_percpu[i].pmc_lev1map = lev1map;
}
mutex_enter(&pmap_all_pmaps_lock);
TAILQ_INSERT_TAIL(&pmap_all_pmaps, pmap, pm_list);
mutex_exit(&pmap_all_pmaps_lock);
rw_exit(&pmap_growkernel_lock);
return (pmap);
}
void
pmap_destroy(pmap_t pmap)
{
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_destroy(%p)\n", pmap);
#endif
PMAP_MP(membar_release());
KASSERT(atomic_load_relaxed(&pmap->pm_count) > 0);
if (atomic_dec_uint_nv(&pmap->pm_count) > 0)
return;
PMAP_MP(membar_acquire());
pt_entry_t *lev1map = pmap_lev1map(pmap);
rw_enter(&pmap_growkernel_lock, RW_READER);
mutex_enter(&pmap_all_pmaps_lock);
TAILQ_REMOVE(&pmap_all_pmaps, pmap, pm_list);
mutex_exit(&pmap_all_pmaps_lock);
pool_cache_put(&pmap_l1pt_cache, lev1map);
#ifdef DIAGNOSTIC
int i;
for (i = 0; i < pmap_ncpuids; i++) {
pmap->pm_percpu[i].pmc_lev1map = (pt_entry_t *)0xdeadbeefUL;
}
#endif
rw_exit(&pmap_growkernel_lock);
pool_cache_put(&pmap_pmap_cache, pmap);
}
void
pmap_reference(pmap_t pmap)
{
unsigned int newcount __diagused;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_reference(%p)\n", pmap);
#endif
newcount = atomic_inc_uint_nv(&pmap->pm_count);
KASSERT(newcount != 0);
}
static void
pmap_remove_internal(pmap_t pmap, vaddr_t sva, vaddr_t eva,
struct pmap_tlb_context * const tlbctx)
{
pt_entry_t *l1pte, *l2pte, *l3pte;
pt_entry_t *saved_l2pte, *saved_l3pte;
vaddr_t l1eva, l2eva, l3vptva;
pt_entry_t pte_bits;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_REMOVE|PDB_PROTECT))
printf("pmap_remove(%p, %lx, %lx)\n", pmap, sva, eva);
#endif
if (pmap == pmap_kernel()) {
PMAP_MAP_TO_HEAD_LOCK();
PMAP_LOCK(pmap);
while (sva < eva) {
l3pte = PMAP_KERNEL_PTE(sva);
if (pmap_pte_v(l3pte)) {
pte_bits = pmap_remove_mapping(pmap, sva,
l3pte, true, NULL, tlbctx);
pmap_tlb_shootdown(pmap, sva, pte_bits,
tlbctx);
}
sva += PAGE_SIZE;
}
PMAP_MAP_TO_HEAD_UNLOCK();
PMAP_UNLOCK(pmap);
pmap_tlb_shootnow(tlbctx);
KASSERT(LIST_EMPTY(&tlbctx->t_freeptq));
pmap_tlb_context_drain(tlbctx);
TLB_COUNT(reason_remove_kernel);
return;
}
pt_entry_t * const lev1map = pmap_lev1map(pmap);
KASSERT(sva < VM_MAXUSER_ADDRESS);
KASSERT(eva <= VM_MAXUSER_ADDRESS);
KASSERT(lev1map != kernel_lev1map);
PMAP_MAP_TO_HEAD_LOCK();
PMAP_LOCK(pmap);
l1pte = pmap_l1pte(lev1map, sva);
for (; sva < eva; sva = l1eva, l1pte++) {
l1eva = alpha_trunc_l1seg(sva) + ALPHA_L1SEG_SIZE;
if (pmap_pte_v(l1pte)) {
saved_l2pte = l2pte = pmap_l2pte(lev1map, sva, l1pte);
pmap_physpage_addref(saved_l2pte);
for (; sva < l1eva && sva < eva; sva = l2eva, l2pte++) {
l2eva =
alpha_trunc_l2seg(sva) + ALPHA_L2SEG_SIZE;
if (pmap_pte_v(l2pte)) {
saved_l3pte = l3pte =
pmap_l3pte(lev1map, sva, l2pte);
pmap_physpage_addref(saved_l3pte);
l3vptva = sva;
for (; sva < l2eva && sva < eva;
sva += PAGE_SIZE, l3pte++) {
if (!pmap_pte_v(l3pte)) {
continue;
}
pte_bits =
pmap_remove_mapping(
pmap, sva,
l3pte, true,
NULL, tlbctx);
pmap_tlb_shootdown(pmap,
sva, pte_bits, tlbctx);
}
pmap_l3pt_delref(pmap, l3vptva,
saved_l3pte, tlbctx);
}
}
pmap_l2pt_delref(pmap, l1pte, saved_l2pte, tlbctx);
}
}
PMAP_MAP_TO_HEAD_UNLOCK();
PMAP_UNLOCK(pmap);
pmap_tlb_shootnow(tlbctx);
pmap_tlb_context_drain(tlbctx);
TLB_COUNT(reason_remove_user);
}
void
pmap_remove(pmap_t pmap, vaddr_t sva, vaddr_t eva)
{
struct pmap_tlb_context tlbctx;
pmap_tlb_context_init(&tlbctx, 0);
pmap_remove_internal(pmap, sva, eva, &tlbctx);
}
bool
pmap_remove_all(pmap_t pmap)
{
struct pmap_tlb_context tlbctx;
struct vm_page *pg;
pv_entry_t pv;
KASSERT(pmap != pmap_kernel());
pmap_tlb_context_init(&tlbctx, 0);
PMAP_MAP_TO_HEAD_LOCK();
PMAP_LOCK(pmap);
pt_entry_t * const lev1map = pmap_lev1map(pmap);
memset(lev1map, 0,
l1pte_index(VM_MAXUSER_ADDRESS) * sizeof(pt_entry_t));
LIST_MOVE(&pmap->pm_ptpages, &tlbctx.t_freeptq, pageq.list);
pg = PHYS_TO_VM_PAGE(ALPHA_K0SEG_TO_PHYS((vaddr_t)lev1map));
PHYSPAGE_REFCNT_SET(pg, 0);
while ((pv = LIST_FIRST(&pmap->pm_pvents)) != NULL) {
KASSERT(pv->pv_pmap == pmap);
pmap_pv_remove(pmap, PHYS_TO_VM_PAGE(pmap_pte_pa(pv->pv_pte)),
pv->pv_va, true, NULL, &tlbctx);
}
atomic_store_relaxed(&pmap->pm_stats.wired_count, 0);
atomic_store_relaxed(&pmap->pm_stats.resident_count, 0);
pmap_tlb_shootdown_all_user(pmap, PG_EXEC, &tlbctx);
PMAP_UNLOCK(pmap);
PMAP_MAP_TO_HEAD_UNLOCK();
pmap_tlb_shootnow(&tlbctx);
pmap_tlb_context_drain(&tlbctx);
TLB_COUNT(reason_remove_all_user);
return true;
}
void
pmap_page_protect(struct vm_page *pg, vm_prot_t prot)
{
pv_entry_t pv, nextpv;
pt_entry_t opte;
kmutex_t *lock;
struct pmap_tlb_context tlbctx;
#ifdef DEBUG
if ((pmapdebug & (PDB_FOLLOW|PDB_PROTECT)) ||
(prot == VM_PROT_NONE && (pmapdebug & PDB_REMOVE)))
printf("pmap_page_protect(%p, %x)\n", pg, prot);
#endif
pmap_tlb_context_init(&tlbctx, TLB_CTX_F_PV);
switch (prot) {
case VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE:
case VM_PROT_READ|VM_PROT_WRITE:
return;
case VM_PROT_READ|VM_PROT_EXECUTE:
case VM_PROT_READ:
PMAP_HEAD_TO_MAP_LOCK();
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
for (pv = VM_MDPAGE_PVS(pg); pv != NULL; pv = pv->pv_next) {
PMAP_LOCK(pv->pv_pmap);
opte = atomic_load_relaxed(pv->pv_pte);
if (opte & (PG_KWE | PG_UWE)) {
atomic_store_relaxed(pv->pv_pte,
opte & ~(PG_KWE | PG_UWE));
pmap_tlb_shootdown_pv(pv->pv_pmap, pv->pv_va,
opte, &tlbctx);
}
PMAP_UNLOCK(pv->pv_pmap);
}
mutex_exit(lock);
PMAP_HEAD_TO_MAP_UNLOCK();
pmap_tlb_shootnow(&tlbctx);
TLB_COUNT(reason_page_protect_read);
return;
default:
break;
}
PMAP_HEAD_TO_MAP_LOCK();
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
for (pv = VM_MDPAGE_PVS(pg); pv != NULL; pv = nextpv) {
pt_entry_t pte_bits;
pmap_t pmap;
vaddr_t va;
nextpv = pv->pv_next;
PMAP_LOCK(pv->pv_pmap);
pmap = pv->pv_pmap;
va = pv->pv_va;
pte_bits = pmap_remove_mapping(pmap, va, pv->pv_pte,
false, NULL, &tlbctx);
pmap_tlb_shootdown_pv(pmap, va, pte_bits, &tlbctx);
PMAP_UNLOCK(pv->pv_pmap);
}
mutex_exit(lock);
PMAP_HEAD_TO_MAP_UNLOCK();
pmap_tlb_shootnow(&tlbctx);
pmap_tlb_context_drain(&tlbctx);
TLB_COUNT(reason_page_protect_none);
}
void
pmap_protect(pmap_t pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot)
{
pt_entry_t *l1pte, *l2pte, *l3pte, opte;
vaddr_t l1eva, l2eva;
struct pmap_tlb_context tlbctx;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_PROTECT))
printf("pmap_protect(%p, %lx, %lx, %x)\n",
pmap, sva, eva, prot);
#endif
pmap_tlb_context_init(&tlbctx, 0);
if ((prot & VM_PROT_READ) == VM_PROT_NONE) {
pmap_remove_internal(pmap, sva, eva, &tlbctx);
return;
}
const pt_entry_t bits = pte_prot(pmap, prot);
pt_entry_t * const lev1map = pmap_lev1map(pmap);
PMAP_LOCK(pmap);
l1pte = pmap_l1pte(lev1map, sva);
for (; sva < eva; sva = l1eva, l1pte++) {
l1eva = alpha_trunc_l1seg(sva) + ALPHA_L1SEG_SIZE;
if (pmap_pte_v(l1pte)) {
l2pte = pmap_l2pte(lev1map, sva, l1pte);
for (; sva < l1eva && sva < eva; sva = l2eva, l2pte++) {
l2eva =
alpha_trunc_l2seg(sva) + ALPHA_L2SEG_SIZE;
if (pmap_pte_v(l2pte)) {
l3pte = pmap_l3pte(lev1map, sva, l2pte);
for (; sva < l2eva && sva < eva;
sva += PAGE_SIZE, l3pte++) {
if (pmap_pte_v(l3pte) &&
pmap_pte_prot_chg(l3pte,
bits)) {
opte = atomic_load_relaxed(l3pte);
pmap_pte_set_prot(l3pte,
bits);
pmap_tlb_shootdown(pmap,
sva, opte, &tlbctx);
}
}
}
}
}
}
PMAP_UNLOCK(pmap);
pmap_tlb_shootnow(&tlbctx);
TLB_COUNT(reason_protect);
}
static void __noinline
pmap_enter_tlb_shootdown(pmap_t const pmap, vaddr_t const va,
pt_entry_t const pte_bits, bool locked)
{
struct pmap_tlb_context tlbctx;
pmap_tlb_context_init(&tlbctx, 0);
pmap_tlb_shootdown(pmap, va, pte_bits, &tlbctx);
if (locked) {
PMAP_UNLOCK(pmap);
}
pmap_tlb_shootnow(&tlbctx);
}
static void __noinline
pmap_enter_l2pt_delref(pmap_t const pmap, pt_entry_t * const l1pte,
pt_entry_t * const l2pte)
{
struct pmap_tlb_context tlbctx;
pmap_tlb_context_init(&tlbctx, 0);
pmap_l2pt_delref(pmap, l1pte, l2pte, &tlbctx);
PMAP_UNLOCK(pmap);
pmap_tlb_shootnow(&tlbctx);
pmap_tlb_context_drain(&tlbctx);
TLB_COUNT(reason_enter_l2pt_delref);
}
static void __noinline
pmap_enter_l3pt_delref(pmap_t const pmap, vaddr_t const va,
pt_entry_t * const pte)
{
struct pmap_tlb_context tlbctx;
pmap_tlb_context_init(&tlbctx, 0);
pmap_l3pt_delref(pmap, va, pte, &tlbctx);
PMAP_UNLOCK(pmap);
pmap_tlb_shootnow(&tlbctx);
pmap_tlb_context_drain(&tlbctx);
TLB_COUNT(reason_enter_l3pt_delref);
}
int
pmap_enter(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags)
{
pt_entry_t *pte, npte, opte;
pv_entry_t opv = NULL;
paddr_t opa;
bool tflush = false;
int error = 0;
kmutex_t *lock;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ENTER))
printf("pmap_enter(%p, %lx, %lx, %x, %x)\n",
pmap, va, pa, prot, flags);
#endif
struct vm_page * const pg = PHYS_TO_VM_PAGE(pa);
const bool wired = (flags & PMAP_WIRED) != 0;
PMAP_MAP_TO_HEAD_LOCK();
PMAP_LOCK(pmap);
if (pmap == pmap_kernel()) {
KASSERT(va >= VM_MIN_KERNEL_ADDRESS);
pte = PMAP_KERNEL_PTE(va);
} else {
pt_entry_t *l1pte, *l2pte;
pt_entry_t * const lev1map = pmap_lev1map(pmap);
KASSERT(va < VM_MAXUSER_ADDRESS);
KASSERT(lev1map != kernel_lev1map);
l1pte = pmap_l1pte(lev1map, va);
if (pmap_pte_v(l1pte) == 0) {
pmap_physpage_addref(l1pte);
error = pmap_ptpage_alloc(pmap, l1pte, PGU_L2PT);
if (error) {
pmap_l1pt_delref(pmap, l1pte);
if (flags & PMAP_CANFAIL)
goto out;
panic("pmap_enter: unable to create L2 PT "
"page");
}
#ifdef DEBUG
if (pmapdebug & PDB_PTPAGE)
printf("pmap_enter: new level 2 table at "
"0x%lx\n", pmap_pte_pa(l1pte));
#endif
}
l2pte = pmap_l2pte(lev1map, va, l1pte);
if (pmap_pte_v(l2pte) == 0) {
pmap_physpage_addref(l2pte);
error = pmap_ptpage_alloc(pmap, l2pte, PGU_L3PT);
if (error) {
pmap_enter_l2pt_delref(pmap, l1pte, l2pte);
if (flags & PMAP_CANFAIL) {
PMAP_LOCK(pmap);
goto out;
}
panic("pmap_enter: unable to create L3 PT "
"page");
}
#ifdef DEBUG
if (pmapdebug & PDB_PTPAGE)
printf("pmap_enter: new level 3 table at "
"0x%lx\n", pmap_pte_pa(l2pte));
#endif
}
pte = pmap_l3pte(lev1map, va, l2pte);
}
opte = atomic_load_relaxed(pte);
if ((opte & PG_V) == 0) {
if (pmap != pmap_kernel()) {
pmap_physpage_addref(pte);
}
goto validate_enterpv;
}
opa = pmap_pte_pa(pte);
if (opa == pa) {
if (pmap_pte_w_chg(pte, wired ? PG_WIRED : 0)) {
#ifdef DEBUG
if (pmapdebug & PDB_ENTER)
printf("pmap_enter: wiring change -> %d\n",
wired);
#endif
if (wired)
PMAP_STAT_INCR(pmap->pm_stats.wired_count, 1);
else
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
}
goto validate;
}
#ifdef DEBUG
if (pmapdebug & PDB_ENTER)
printf("pmap_enter: removing old mapping 0x%lx\n", va);
#endif
if (pmap != pmap_kernel()) {
pmap_physpage_addref(pte);
}
(void) pmap_remove_mapping(pmap, va, pte, true, &opv, NULL);
validate_enterpv:
if (pg != NULL) {
error = pmap_pv_enter(pmap, pg, va, pte, true, opv);
if (error) {
KASSERT(opv == NULL);
pmap_enter_l3pt_delref(pmap, va, pte);
if (flags & PMAP_CANFAIL) {
PMAP_LOCK(pmap);
goto out;
}
panic("pmap_enter: unable to enter mapping in PV "
"table");
}
opv = NULL;
}
PMAP_STAT_INCR(pmap->pm_stats.resident_count, 1);
if (wired)
PMAP_STAT_INCR(pmap->pm_stats.wired_count, 1);
validate:
npte = ((pa >> PGSHIFT) << PG_SHIFT) | pte_prot(pmap, prot) | PG_V;
if (pg != NULL) {
struct vm_page_md * const md = VM_PAGE_TO_MD(pg);
uintptr_t attrs = 0;
KASSERT(((flags & VM_PROT_ALL) & ~prot) == 0);
if (flags & VM_PROT_WRITE)
attrs |= (PGA_REFERENCED|PGA_MODIFIED);
else if (flags & VM_PROT_ALL)
attrs |= PGA_REFERENCED;
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
attrs = (md->pvh_listx |= attrs);
mutex_exit(lock);
if ((attrs & PGA_REFERENCED) == 0)
npte |= PG_FOR | PG_FOW | PG_FOE;
else if ((attrs & PGA_MODIFIED) == 0)
npte |= PG_FOW;
npte |= PG_PVLIST;
}
if (wired)
npte |= PG_WIRED;
#ifdef DEBUG
if (pmapdebug & PDB_ENTER)
printf("pmap_enter: new pte = 0x%lx\n", npte);
#endif
if (opte & PG_V) {
tflush = PG_PALCODE(opte) != PG_PALCODE(npte);
}
atomic_store_relaxed(pte, npte);
out:
PMAP_MAP_TO_HEAD_UNLOCK();
if (tflush) {
pmap_enter_tlb_shootdown(pmap, va, opte, true);
if (pmap == pmap_kernel()) {
TLB_COUNT(reason_enter_kernel);
} else {
TLB_COUNT(reason_enter_user);
}
} else {
PMAP_UNLOCK(pmap);
}
if (opv)
pmap_pv_free(opv);
return error;
}
void
pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags)
{
pmap_t const pmap = pmap_kernel();
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ENTER))
printf("pmap_kenter_pa(%lx, %lx, %x)\n",
va, pa, prot);
#endif
KASSERT(va >= VM_MIN_KERNEL_ADDRESS);
pt_entry_t * const pte = PMAP_KERNEL_PTE(va);
const pt_entry_t npte =
((pa >> PGSHIFT) << PG_SHIFT) | pte_prot(pmap_kernel(), prot) |
PG_V | PG_WIRED;
const pt_entry_t opte = atomic_load_relaxed(pte);
atomic_store_relaxed(pte, npte);
PMAP_STAT_INCR(pmap->pm_stats.resident_count, 1);
PMAP_STAT_INCR(pmap->pm_stats.wired_count, 1);
if (__predict_false(opte & PG_V)) {
const pt_entry_t diff = npte ^ opte;
printf_nolog("%s: mapping already present\n", __func__);
PMAP_STAT_DECR(pmap->pm_stats.resident_count, 1);
if (diff & PG_WIRED)
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
if (diff & PG_PVLIST)
panic("pmap_kenter_pa: old mapping was managed");
pmap_enter_tlb_shootdown(pmap_kernel(), va, opte, false);
TLB_COUNT(reason_kenter);
}
}
void
pmap_kremove(vaddr_t va, vsize_t size)
{
pt_entry_t *pte, opte;
pmap_t const pmap = pmap_kernel();
struct pmap_tlb_context tlbctx;
int count = 0;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ENTER))
printf("pmap_kremove(%lx, %lx)\n",
va, size);
#endif
pmap_tlb_context_init(&tlbctx, 0);
KASSERT(va >= VM_MIN_KERNEL_ADDRESS);
for (; size != 0; size -= PAGE_SIZE, va += PAGE_SIZE) {
pte = PMAP_KERNEL_PTE(va);
opte = atomic_load_relaxed(pte);
if (opte & PG_V) {
KASSERT((opte & PG_PVLIST) == 0);
atomic_store_relaxed(pte, PG_NV);
pmap_tlb_shootdown(pmap, va, opte, &tlbctx);
count++;
}
}
if (__predict_true(count != 0)) {
PMAP_STAT_DECR(pmap->pm_stats.resident_count, count);
PMAP_STAT_DECR(pmap->pm_stats.wired_count, count);
}
pmap_tlb_shootnow(&tlbctx);
TLB_COUNT(reason_kremove);
}
void
pmap_unwire(pmap_t pmap, vaddr_t va)
{
pt_entry_t *pte;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_unwire(%p, %lx)\n", pmap, va);
#endif
PMAP_LOCK(pmap);
pte = pmap_l3pte(pmap_lev1map(pmap), va, NULL);
KASSERT(pte != NULL);
KASSERT(pmap_pte_v(pte));
if (pmap_pte_w_chg(pte, 0)) {
pmap_pte_set_w(pte, false);
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
}
#ifdef DEBUG
else {
printf("pmap_unwire: wiring for pmap %p va 0x%lx "
"didn't change!\n", pmap, va);
}
#endif
PMAP_UNLOCK(pmap);
}
bool
pmap_extract(pmap_t pmap, vaddr_t va, paddr_t *pap)
{
pt_entry_t *l1pte, *l2pte, *l3pte;
paddr_t pa;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_extract(%p, %lx) -> ", pmap, va);
#endif
if (__predict_true(pmap == pmap_kernel())) {
#ifdef DEBUG
bool address_is_valid = vtophys_internal(va, pap);
if (pmapdebug & PDB_FOLLOW) {
if (address_is_valid) {
printf("0x%lx (kernel vtophys)\n", *pap);
} else {
printf("failed (kernel vtophys)\n");
}
}
return address_is_valid;
#else
return vtophys_internal(va, pap);
#endif
}
pt_entry_t * const lev1map = pmap_lev1map(pmap);
PMAP_LOCK(pmap);
l1pte = pmap_l1pte(lev1map, va);
if (pmap_pte_v(l1pte) == 0)
goto out;
l2pte = pmap_l2pte(lev1map, va, l1pte);
if (pmap_pte_v(l2pte) == 0)
goto out;
l3pte = pmap_l3pte(lev1map, va, l2pte);
if (pmap_pte_v(l3pte) == 0)
goto out;
pa = pmap_pte_pa(l3pte) | (va & PGOFSET);
PMAP_UNLOCK(pmap);
if (pap != NULL)
*pap = pa;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("0x%lx\n", pa);
#endif
return (true);
out:
PMAP_UNLOCK(pmap);
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("failed\n");
#endif
return (false);
}
void
pmap_activate(struct lwp *l)
{
struct pmap * const pmap = l->l_proc->p_vmspace->vm_map.pmap;
struct pcb * const pcb = lwp_getpcb(l);
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_activate(%p)\n", l);
#endif
KASSERT(kpreempt_disabled());
struct cpu_info * const ci = curcpu();
KASSERT(l == ci->ci_curlwp);
u_long const old_ptbr = pcb->pcb_hw.apcb_ptbr;
u_int const old_asn = pcb->pcb_hw.apcb_asn;
if (pmap != pmap_kernel()) {
PMAP_ACT_LOCK(pmap);
pcb->pcb_hw.apcb_asn = pmap_asn_alloc(pmap, ci);
atomic_or_ulong(&pmap->pm_cpus, (1UL << ci->ci_cpuid));
} else {
pcb->pcb_hw.apcb_asn = PMAP_ASN_KERNEL;
}
pcb->pcb_hw.apcb_ptbr =
ALPHA_K0SEG_TO_PHYS((vaddr_t)pmap_lev1map(pmap)) >> PGSHIFT;
if (old_asn != pcb->pcb_hw.apcb_asn ||
old_ptbr != pcb->pcb_hw.apcb_ptbr) {
if (old_asn != pcb->pcb_hw.apcb_asn &&
old_ptbr != pcb->pcb_hw.apcb_ptbr) {
TLB_COUNT(activate_both_change);
} else if (old_asn != pcb->pcb_hw.apcb_asn) {
TLB_COUNT(activate_asn_change);
} else {
TLB_COUNT(activate_ptbr_change);
}
(void) alpha_pal_swpctx((u_long)l->l_md.md_pcbpaddr);
TLB_COUNT(activate_swpctx);
} else {
TLB_COUNT(activate_skip_swpctx);
}
pmap_reference(pmap);
ci->ci_pmap = pmap;
if (pmap != pmap_kernel()) {
PMAP_ACT_UNLOCK(pmap);
}
}
void
pmap_deactivate(struct lwp *l)
{
struct pmap * const pmap = l->l_proc->p_vmspace->vm_map.pmap;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_deactivate(%p)\n", l);
#endif
KASSERT(kpreempt_disabled());
struct cpu_info * const ci = curcpu();
KASSERT(l == ci->ci_curlwp);
KASSERT(pmap == ci->ci_pmap);
ci->ci_pmap = pmap_kernel();
KASSERT(atomic_load_relaxed(&pmap->pm_count) > 1);
pmap_destroy(pmap);
}
bool
pmap_pageidlezero(paddr_t pa)
{
u_long *ptr;
int i, cnt = PAGE_SIZE / sizeof(u_long);
for (i = 0, ptr = (u_long *) ALPHA_PHYS_TO_K0SEG(pa); i < cnt; i++) {
if (sched_curcpu_runnable_p()) {
return (false);
}
*ptr++ = 0;
}
return (true);
}
bool
pmap_clear_modify(struct vm_page *pg)
{
struct vm_page_md * const md = VM_PAGE_TO_MD(pg);
bool rv = false;
kmutex_t *lock;
struct pmap_tlb_context tlbctx;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_clear_modify(%p)\n", pg);
#endif
pmap_tlb_context_init(&tlbctx, TLB_CTX_F_PV);
PMAP_HEAD_TO_MAP_LOCK();
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
if (md->pvh_listx & PGA_MODIFIED) {
rv = true;
pmap_changebit(pg, PG_FOW, ~0UL, &tlbctx);
md->pvh_listx &= ~PGA_MODIFIED;
}
mutex_exit(lock);
PMAP_HEAD_TO_MAP_UNLOCK();
pmap_tlb_shootnow(&tlbctx);
TLB_COUNT(reason_clear_modify);
return (rv);
}
bool
pmap_clear_reference(struct vm_page *pg)
{
struct vm_page_md * const md = VM_PAGE_TO_MD(pg);
bool rv = false;
kmutex_t *lock;
struct pmap_tlb_context tlbctx;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_clear_reference(%p)\n", pg);
#endif
pmap_tlb_context_init(&tlbctx, TLB_CTX_F_PV);
PMAP_HEAD_TO_MAP_LOCK();
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
if (md->pvh_listx & PGA_REFERENCED) {
rv = true;
pmap_changebit(pg, PG_FOR | PG_FOW | PG_FOE, ~0UL, &tlbctx);
md->pvh_listx &= ~PGA_REFERENCED;
}
mutex_exit(lock);
PMAP_HEAD_TO_MAP_UNLOCK();
pmap_tlb_shootnow(&tlbctx);
TLB_COUNT(reason_clear_reference);
return (rv);
}
paddr_t
pmap_phys_address(paddr_t ppn)
{
return (alpha_ptob(ppn));
}
static void
alpha_protection_init(void)
{
int prot, *kp, *up;
kp = protection_codes[0];
up = protection_codes[1];
for (prot = 0; prot < 8; prot++) {
kp[prot] = PG_ASM;
up[prot] = 0;
if (prot & VM_PROT_READ) {
kp[prot] |= PG_KRE;
up[prot] |= PG_KRE | PG_URE;
}
if (prot & VM_PROT_WRITE) {
kp[prot] |= PG_KWE;
up[prot] |= PG_KWE | PG_UWE;
}
if (prot & VM_PROT_EXECUTE) {
kp[prot] |= PG_EXEC | PG_KRE;
up[prot] |= PG_EXEC | PG_KRE | PG_URE;
} else {
kp[prot] |= PG_FOE;
up[prot] |= PG_FOE;
}
}
}
static pt_entry_t
pmap_remove_mapping(pmap_t pmap, vaddr_t va, pt_entry_t *pte,
bool dolock, pv_entry_t *opvp, struct pmap_tlb_context * const tlbctx)
{
pt_entry_t opte;
paddr_t pa;
struct vm_page *pg;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_REMOVE|PDB_PROTECT))
printf("pmap_remove_mapping(%p, %lx, %p, %d, %p, %p)\n",
pmap, va, pte, dolock, opvp, tlbctx);
#endif
if (pte == NULL) {
pte = pmap_l3pte(pmap_lev1map(pmap), va, NULL);
if (pmap_pte_v(pte) == 0)
return 0;
}
opte = *pte;
pa = PG_PFNUM(opte) << PGSHIFT;
if (pmap_pte_w(pte))
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
PMAP_STAT_DECR(pmap->pm_stats.resident_count, 1);
#ifdef DEBUG
if (pmapdebug & PDB_REMOVE)
printf("remove: invalidating pte at %p\n", pte);
#endif
atomic_store_relaxed(pte, PG_NV);
if (pmap != pmap_kernel()) {
pmap_l3pt_delref(pmap, va, pte, tlbctx);
}
if (opte & PG_PVLIST) {
pg = PHYS_TO_VM_PAGE(pa);
KASSERT(pg != NULL);
pmap_pv_remove(pmap, pg, va, dolock, opvp, tlbctx);
KASSERT(opvp == NULL || *opvp != NULL);
}
return opte & (PG_V | PG_ASM | PG_EXEC);
}
static void
pmap_changebit(struct vm_page *pg, pt_entry_t set, pt_entry_t mask,
struct pmap_tlb_context * const tlbctx)
{
pv_entry_t pv;
pt_entry_t *pte, npte, opte;
#ifdef DEBUG
if (pmapdebug & PDB_BITS)
printf("pmap_changebit(%p, 0x%lx, 0x%lx)\n",
pg, set, mask);
#endif
for (pv = VM_MDPAGE_PVS(pg); pv != NULL; pv = pv->pv_next) {
PMAP_LOCK(pv->pv_pmap);
pte = pv->pv_pte;
opte = atomic_load_relaxed(pte);
npte = (opte | set) & mask;
if (npte != opte) {
atomic_store_relaxed(pte, npte);
pmap_tlb_shootdown_pv(pv->pv_pmap, pv->pv_va,
opte, tlbctx);
}
PMAP_UNLOCK(pv->pv_pmap);
}
}
int
pmap_emulate_reference(struct lwp *l, vaddr_t v, int user, int type)
{
struct pmap *pmap = l->l_proc->p_vmspace->vm_map.pmap;
pt_entry_t faultoff, *pte;
struct vm_page *pg;
paddr_t pa;
bool didlock = false;
bool exec = false;
kmutex_t *lock;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_emulate_reference: %p, 0x%lx, %d, %d\n",
l, v, user, type);
#endif
if (v >= VM_MIN_KERNEL_ADDRESS) {
if (user)
panic("pmap_emulate_reference: user ref to kernel");
pte = PMAP_KERNEL_PTE(v);
} else {
#ifdef DIAGNOSTIC
if (l == NULL)
panic("pmap_emulate_reference: bad proc");
if (l->l_proc->p_vmspace == NULL)
panic("pmap_emulate_reference: bad p_vmspace");
#endif
PMAP_LOCK(pmap);
didlock = true;
pte = pmap_l3pte(pmap_lev1map(pmap), v, NULL);
}
exec = pmap_pte_exec(pte);
if (!exec && type == ALPHA_MMCSR_FOE) {
if (didlock)
PMAP_UNLOCK(pmap);
return (1);
}
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW) {
printf("\tpte = %p, ", pte);
printf("*pte = 0x%lx\n", *pte);
}
#endif
pa = pmap_pte_pa(pte);
if (didlock)
PMAP_UNLOCK(pmap);
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("\tpa = 0x%lx\n", pa);
#endif
#ifdef DIAGNOSTIC
if (!uvm_pageismanaged(pa))
panic("pmap_emulate_reference(%p, 0x%lx, %d, %d): "
"pa 0x%lx not managed", l, v, user, type, pa);
#endif
pg = PHYS_TO_VM_PAGE(pa);
struct vm_page_md * const md = VM_PAGE_TO_MD(pg);
struct pmap_tlb_context tlbctx;
pmap_tlb_context_init(&tlbctx, TLB_CTX_F_PV);
PMAP_HEAD_TO_MAP_LOCK();
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
if (type == ALPHA_MMCSR_FOW) {
md->pvh_listx |= (PGA_REFERENCED|PGA_MODIFIED);
faultoff = PG_FOR | PG_FOW;
} else {
md->pvh_listx |= PGA_REFERENCED;
faultoff = PG_FOR;
if (exec) {
faultoff |= PG_FOE;
}
}
pmap_changebit(pg, 0, ~faultoff, &tlbctx);
mutex_exit(lock);
PMAP_HEAD_TO_MAP_UNLOCK();
pmap_tlb_shootnow(&tlbctx);
TLB_COUNT(reason_emulate_reference);
return (0);
}
#ifdef DEBUG
void
pmap_pv_dump(paddr_t pa)
{
struct vm_page *pg;
struct vm_page_md *md;
pv_entry_t pv;
kmutex_t *lock;
pg = PHYS_TO_VM_PAGE(pa);
md = VM_PAGE_TO_MD(pg);
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
printf("pa 0x%lx (attrs = 0x%lx):\n", pa, md->pvh_listx & PGA_ATTRS);
for (pv = VM_MDPAGE_PVS(pg); pv != NULL; pv = pv->pv_next)
printf(" pmap %p, va 0x%lx\n",
pv->pv_pmap, pv->pv_va);
printf("\n");
mutex_exit(lock);
}
#endif
static bool
vtophys_internal(vaddr_t const vaddr, paddr_t * const pap)
{
paddr_t pa;
KASSERT(vaddr >= ALPHA_K0SEG_BASE);
if (vaddr <= ALPHA_K0SEG_END) {
pa = ALPHA_K0SEG_TO_PHYS(vaddr);
} else {
pt_entry_t * const pte = PMAP_KERNEL_PTE(vaddr);
if (__predict_false(! pmap_pte_v(pte))) {
return false;
}
pa = pmap_pte_pa(pte) | (vaddr & PGOFSET);
}
if (pap != NULL) {
*pap = pa;
}
return true;
}
paddr_t
vtophys(vaddr_t const vaddr)
{
paddr_t pa;
if (__predict_false(! vtophys_internal(vaddr, &pa)))
pa = 0;
return pa;
}
static int
pmap_pv_enter(pmap_t pmap, struct vm_page *pg, vaddr_t va, pt_entry_t *pte,
bool dolock, pv_entry_t newpv)
{
struct vm_page_md * const md = VM_PAGE_TO_MD(pg);
kmutex_t *lock;
if (newpv == NULL) {
newpv = pmap_pv_alloc();
if (newpv == NULL)
return ENOMEM;
}
newpv->pv_va = va;
newpv->pv_pmap = pmap;
newpv->pv_pte = pte;
if (dolock) {
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
}
#ifdef DEBUG
{
pv_entry_t pv;
for (pv = VM_MDPAGE_PVS(pg); pv != NULL; pv = pv->pv_next) {
if (pmap == pv->pv_pmap && va == pv->pv_va) {
printf("pmap = %p, va = 0x%lx\n", pmap, va);
panic("pmap_pv_enter: already in pv table");
}
}
}
#endif
uintptr_t const attrs = md->pvh_listx & PGA_ATTRS;
newpv->pv_next = (struct pv_entry *)(md->pvh_listx & ~PGA_ATTRS);
md->pvh_listx = (uintptr_t)newpv | attrs;
LIST_INSERT_HEAD(&pmap->pm_pvents, newpv, pv_link);
if (dolock) {
mutex_exit(lock);
}
return 0;
}
static void
pmap_pv_remove(pmap_t pmap, struct vm_page *pg, vaddr_t va, bool dolock,
pv_entry_t *opvp, struct pmap_tlb_context * const tlbctx)
{
struct vm_page_md * const md = VM_PAGE_TO_MD(pg);
pv_entry_t pv, *pvp;
kmutex_t *lock;
if (dolock) {
lock = pmap_pvh_lock(pg);
mutex_enter(lock);
} else {
lock = NULL;
}
for (pvp = (struct pv_entry **)&md->pvh_listx, pv = VM_MDPAGE_PVS(pg);
pv != NULL; pvp = &pv->pv_next, pv = *pvp)
if (pmap == pv->pv_pmap && va == pv->pv_va)
break;
KASSERT(pv != NULL);
*pvp = (pv_entry_t)((uintptr_t)pv->pv_next |
(((uintptr_t)*pvp) & PGA_ATTRS));
LIST_REMOVE(pv, pv_link);
if (dolock) {
mutex_exit(lock);
}
if (opvp != NULL) {
*opvp = pv;
} else {
KASSERT(tlbctx != NULL);
LIST_INSERT_HEAD(&tlbctx->t_freepvq, pv, pv_link);
}
}
static void *
pmap_pv_page_alloc(struct pool *pp, int flags)
{
struct vm_page * const pg = pmap_physpage_alloc(PGU_PVENT);
if (__predict_false(pg == NULL)) {
return NULL;
}
return (void *)ALPHA_PHYS_TO_K0SEG(VM_PAGE_TO_PHYS(pg));
}
static void
pmap_pv_page_free(struct pool *pp, void *v)
{
pmap_physpage_free(ALPHA_K0SEG_TO_PHYS((vaddr_t)v));
}
static struct vm_page *
pmap_physpage_alloc(int usage)
{
struct vm_page *pg;
pg = uvm_pagealloc(NULL, 0, NULL, usage == PGU_L1PT ?
UVM_PGA_USERESERVE : UVM_PGA_USERESERVE|UVM_PGA_ZERO);
if (pg != NULL) {
KASSERT(PHYSPAGE_REFCNT(pg) == 0);
}
return pg;
}
static void
pmap_physpage_free(paddr_t pa)
{
struct vm_page *pg;
if ((pg = PHYS_TO_VM_PAGE(pa)) == NULL)
panic("pmap_physpage_free: bogus physical page address");
KASSERT(PHYSPAGE_REFCNT(pg) == 0);
uvm_pagefree(pg);
}
static int
pmap_physpage_addref(void *kva)
{
struct vm_page *pg;
paddr_t pa;
pa = ALPHA_K0SEG_TO_PHYS(trunc_page((vaddr_t)kva));
pg = PHYS_TO_VM_PAGE(pa);
KASSERT(PHYSPAGE_REFCNT(pg) < UINT32_MAX);
return PHYSPAGE_REFCNT_INC(pg);
}
static int
pmap_physpage_delref(void *kva)
{
struct vm_page *pg;
paddr_t pa;
pa = ALPHA_K0SEG_TO_PHYS(trunc_page((vaddr_t)kva));
pg = PHYS_TO_VM_PAGE(pa);
KASSERT(PHYSPAGE_REFCNT(pg) != 0);
return PHYSPAGE_REFCNT_DEC(pg);
}
static bool
pmap_kptpage_alloc(paddr_t *pap)
{
if (uvm.page_init_done == false) {
*pap = ALPHA_K0SEG_TO_PHYS(
pmap_steal_memory(PAGE_SIZE, NULL, NULL));
return true;
}
struct vm_page * const pg = pmap_physpage_alloc(PGU_NORMAL);
if (__predict_true(pg != NULL)) {
*pap = VM_PAGE_TO_PHYS(pg);
return true;
}
return false;
}
vaddr_t
pmap_growkernel(vaddr_t maxkvaddr)
{
struct pmap *pm;
paddr_t ptaddr;
pt_entry_t *l1pte, *l2pte, pte;
pt_entry_t *lev1map;
vaddr_t va;
int l1idx;
rw_enter(&pmap_growkernel_lock, RW_WRITER);
if (maxkvaddr <= virtual_end)
goto out;
pmap_growkernel_evcnt.ev_count++;
va = virtual_end;
while (va < maxkvaddr) {
l1pte = pmap_l1pte(kernel_lev1map, va);
if (pmap_pte_v(l1pte) == 0) {
if (!pmap_kptpage_alloc(&ptaddr))
goto die;
pte = (atop(ptaddr) << PG_SHIFT) |
PG_V | PG_ASM | PG_KRE | PG_KWE | PG_WIRED;
*l1pte = pte;
l1idx = l1pte_index(va);
mutex_enter(&pmap_all_pmaps_lock);
for (pm = TAILQ_FIRST(&pmap_all_pmaps);
pm != NULL; pm = TAILQ_NEXT(pm, pm_list)) {
if (pm == pmap_kernel())
continue;
lev1map = pmap_lev1map(pm);
KASSERT(lev1map != kernel_lev1map);
PMAP_LOCK(pm);
lev1map[l1idx] = pte;
PMAP_UNLOCK(pm);
}
mutex_exit(&pmap_all_pmaps_lock);
}
l2pte = pmap_l2pte(kernel_lev1map, va, l1pte);
KASSERT(pmap_pte_v(l2pte) == 0);
if (!pmap_kptpage_alloc(&ptaddr))
goto die;
*l2pte = (atop(ptaddr) << PG_SHIFT) |
PG_V | PG_ASM | PG_KRE | PG_KWE | PG_WIRED;
va += ALPHA_L2SEG_SIZE;
}
pool_cache_invalidate(&pmap_l1pt_cache);
virtual_end = va;
out:
rw_exit(&pmap_growkernel_lock);
return (virtual_end);
die:
panic("pmap_growkernel: out of memory");
}
static int
pmap_l1pt_ctor(void *arg, void *object, int flags)
{
pt_entry_t *l1pt = object, pte;
int i;
for (i = 0; i < l1pte_index(VM_MIN_KERNEL_ADDRESS); i++)
l1pt[i] = 0;
for (i = l1pte_index(VM_MIN_KERNEL_ADDRESS);
i <= l1pte_index(VM_MAX_KERNEL_ADDRESS); i++)
l1pt[i] = kernel_lev1map[i];
pte = ((ALPHA_K0SEG_TO_PHYS((vaddr_t) l1pt) >> PGSHIFT) << PG_SHIFT) |
PG_V | PG_KRE | PG_KWE;
l1pt[l1pte_index(VPTBASE)] = pte;
return (0);
}
static void *
pmap_l1pt_alloc(struct pool *pp, int flags)
{
struct vm_page * const pg = pmap_physpage_alloc(PGU_L1PT);
if (__predict_false(pg == NULL)) {
return NULL;
}
return (void *)ALPHA_PHYS_TO_K0SEG(VM_PAGE_TO_PHYS(pg));
}
static void
pmap_l1pt_free(struct pool *pp, void *v)
{
pmap_physpage_free(ALPHA_K0SEG_TO_PHYS((vaddr_t) v));
}
static int
pmap_ptpage_alloc(pmap_t pmap, pt_entry_t * const pte, int const usage)
{
struct vm_page * const pg = pmap_physpage_alloc(usage);
if (__predict_false(pg == NULL)) {
return ENOMEM;
}
LIST_INSERT_HEAD(&pmap->pm_ptpages, pg, pageq.list);
const pt_entry_t npte = ((VM_PAGE_TO_PHYS(pg) >> PGSHIFT) << PG_SHIFT) |
PG_V | PG_KRE | PG_KWE | PG_WIRED;
atomic_store_relaxed(pte, npte);
return (0);
}
static void
pmap_ptpage_free(pmap_t pmap, pt_entry_t * const pte,
struct pmap_tlb_context * const tlbctx)
{
const paddr_t ptpa = pmap_pte_pa(pte);
atomic_store_relaxed(pte, PG_NV);
struct vm_page * const pg = PHYS_TO_VM_PAGE(ptpa);
KASSERT(pg != NULL);
KASSERT(PHYSPAGE_REFCNT(pg) == 0);
#ifdef DEBUG
pmap_zero_page(ptpa);
#endif
LIST_REMOVE(pg, pageq.list);
LIST_INSERT_HEAD(&tlbctx->t_freeptq, pg, pageq.list);
}
static void
pmap_l3pt_delref(pmap_t pmap, vaddr_t va, pt_entry_t *l3pte,
struct pmap_tlb_context * const tlbctx)
{
pt_entry_t *l1pte, *l2pte;
pt_entry_t * const lev1map = pmap_lev1map(pmap);
l1pte = pmap_l1pte(lev1map, va);
l2pte = pmap_l2pte(lev1map, va, l1pte);
#ifdef DIAGNOSTIC
if (pmap == pmap_kernel())
panic("pmap_l3pt_delref: kernel pmap");
#endif
if (pmap_physpage_delref(l3pte) == 0) {
#ifdef DEBUG
if (pmapdebug & PDB_PTPAGE)
printf("pmap_l3pt_delref: freeing level 3 table at "
"0x%lx\n", pmap_pte_pa(l2pte));
#endif
KASSERT(tlbctx != NULL);
pmap_ptpage_free(pmap, l2pte, tlbctx);
pmap_tlb_shootdown_all_user(pmap, PG_V, tlbctx);
pmap_l2pt_delref(pmap, l1pte, l2pte, tlbctx);
}
}
static void
pmap_l2pt_delref(pmap_t pmap, pt_entry_t *l1pte, pt_entry_t *l2pte,
struct pmap_tlb_context * const tlbctx)
{
#ifdef DIAGNOSTIC
if (pmap == pmap_kernel())
panic("pmap_l2pt_delref: kernel pmap");
#endif
if (pmap_physpage_delref(l2pte) == 0) {
#ifdef DEBUG
if (pmapdebug & PDB_PTPAGE)
printf("pmap_l2pt_delref: freeing level 2 table at "
"0x%lx\n", pmap_pte_pa(l1pte));
#endif
KASSERT(tlbctx != NULL);
pmap_ptpage_free(pmap, l1pte, tlbctx);
pmap_tlb_shootdown_all_user(pmap, PG_V, tlbctx);
pmap_l1pt_delref(pmap, l1pte);
}
}
static void
pmap_l1pt_delref(pmap_t pmap, pt_entry_t *l1pte)
{
KASSERT(pmap != pmap_kernel());
(void)pmap_physpage_delref(l1pte);
}
static u_int
pmap_asn_alloc(pmap_t const pmap, struct cpu_info * const ci)
{
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ASN))
printf("pmap_asn_alloc(%p)\n", pmap);
#endif
KASSERT(pmap != pmap_kernel());
KASSERT(pmap->pm_percpu[ci->ci_cpuid].pmc_lev1map != kernel_lev1map);
KASSERT(kpreempt_disabled());
if (pmap_max_asn == 0)
return 0;
struct pmap_percpu * const pmc = &pmap->pm_percpu[ci->ci_cpuid];
if (pmc->pmc_asngen == ci->ci_asn_gen) {
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: same generation, keeping %u\n",
pmc->pmc_asn);
#endif
TLB_COUNT(asn_reuse);
return pmc->pmc_asn;
}
if (ci->ci_next_asn > pmap_max_asn) {
ALPHA_TBIAP();
alpha_pal_imb();
ci->ci_next_asn = PMAP_ASN_FIRST_USER;
ci->ci_asn_gen++;
TLB_COUNT(asn_newgen);
KASSERT(ci->ci_asn_gen != PMAP_ASNGEN_INVALID);
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: generation bumped to %lu\n",
ci->ci_asn_gen);
#endif
}
pmc->pmc_asn = ci->ci_next_asn++;
pmc->pmc_asngen = ci->ci_asn_gen;
TLB_COUNT(asn_assign);
pmc->pmc_needisync = 0;
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: assigning %u to pmap %p\n",
pmc->pmc_asn, pmap);
#endif
return pmc->pmc_asn;
}