#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/malloc.h>
#include <sys/pool.h>
#include <sys/user.h>
#include <sys/buf.h>
#include <sys/atomic.h>
#ifdef SYSVSHM
#include <sys/shm.h>
#endif
#include <uvm/uvm.h>
#include <machine/cpu.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|PDB_FOLLOW|PDB_ENTER;
#endif
#define pte_prot(m, p) (protection_codes[m == pmap_kernel() ? 0 : 1][p])
int protection_codes[2][8];
pt_entry_t *kernel_lev1map;
pt_entry_t *VPT;
struct pmap kernel_pmap_store
[(PMAP_SIZEOF(ALPHA_MAXPROCS) + sizeof(struct pmap) - 1)
/ sizeof(struct pmap)];
paddr_t avail_start;
paddr_t avail_end;
vaddr_t pmap_maxkvaddr;
boolean_t pmap_initialized;
u_long pmap_pages_stolen;
u_long pmap_ncpuids;
#ifndef PMAP_PV_LOWAT
#define PMAP_PV_LOWAT 16
#endif
int pmap_pv_lowat = PMAP_PV_LOWAT;
TAILQ_HEAD(, pmap) pmap_all_pmaps;
struct pool pmap_pmap_pool;
struct pool pmap_l1pt_pool;
struct pool pmap_pv_pool;
u_int pmap_max_asn;
struct pmap_asn_info pmap_asn_info[ALPHA_MAXPROCS];
struct mutex pmap_all_pmaps_mtx;
struct mutex pmap_growkernel_mtx;
#define PMAP_LOCK(pmap) mtx_enter(&pmap->pm_mtx)
#define PMAP_UNLOCK(pmap) mtx_leave(&pmap->pm_mtx)
#if defined(MULTIPROCESSOR)
struct pmap_tlb_shootdown_job {
unsigned int pj_state;
#define PJ_S_IDLE 0
#define PJ_S_PENDING 1
#define PJ_S_VALID 2
vaddr_t pj_va;
pmap_t pj_pmap;
pt_entry_t pj_pte;
} __aligned(64);
#define PMAP_TLB_SHOOTDOWN_MAXJOBS 8
struct pmap_tlb_shootdown_q {
unsigned long pq_pte;
uint64_t pq_globals;
uint64_t pq_jobruns;
struct pmap_tlb_shootdown_job pq_jobs[PMAP_TLB_SHOOTDOWN_MAXJOBS];
} pmap_tlb_shootdown_q[ALPHA_MAXPROCS];
#endif
#define PAGE_IS_MANAGED(pa) (vm_physseg_find(atop(pa), NULL) != -1)
void alpha_protection_init(void);
void pmap_do_remove(pmap_t, vaddr_t, vaddr_t);
boolean_t pmap_remove_mapping(pmap_t, vaddr_t, pt_entry_t *,
boolean_t, cpuid_t);
void pmap_changebit(struct vm_page *, pt_entry_t, pt_entry_t, cpuid_t);
int pmap_lev1map_create(pmap_t, cpuid_t);
void pmap_lev1map_destroy(pmap_t);
int pmap_ptpage_alloc(pmap_t, pt_entry_t *, int);
void pmap_ptpage_free(pmap_t, pt_entry_t *);
void pmap_l3pt_delref(pmap_t, vaddr_t, pt_entry_t *, cpuid_t);
void pmap_l2pt_delref(pmap_t, pt_entry_t *, pt_entry_t *);
void pmap_l1pt_delref(pmap_t, pt_entry_t *);
void *pmap_l1pt_alloc(struct pool *, int, int *);
void pmap_l1pt_free(struct pool *, void *);
struct pool_allocator pmap_l1pt_allocator = {
pmap_l1pt_alloc, pmap_l1pt_free, 0,
};
void pmap_l1pt_ctor(pt_entry_t *);
int pmap_pv_enter(pmap_t, struct vm_page *, vaddr_t, pt_entry_t *,
boolean_t);
void pmap_pv_remove(pmap_t, struct vm_page *, vaddr_t, boolean_t);
void *pmap_pv_page_alloc(struct pool *, int, int *);
void pmap_pv_page_free(struct pool *, void *);
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_get(&pmap_pv_pool, PR_NOWAIT)
#define pmap_pv_free(pv) pool_put(&pmap_pv_pool, (pv))
void pmap_asn_alloc(pmap_t, cpuid_t);
boolean_t pmap_physpage_alloc(int, paddr_t *);
void pmap_physpage_free(paddr_t);
int pmap_physpage_addref(void *);
int pmap_physpage_delref(void *);
#define PGU_NORMAL 0
#define PGU_PVENT 1
#define PGU_L1PT 2
#define PGU_L2PT 3
#define PGU_L3PT 4
#define PMAP_ISACTIVE_TEST(pm, cpu_id) \
(((pm)->pm_cpus & (1UL << (cpu_id))) != 0)
#if defined(DEBUG) && !defined(MULTIPROCESSOR)
#define PMAP_ISACTIVE(pm, cpu_id) \
({ \
\
int isactive_ = PMAP_ISACTIVE_TEST(pm, cpu_id); \
\
if (curproc != NULL && curproc->p_vmspace != NULL && \
(pm) != pmap_kernel() && \
(isactive_ ^ ((pm) == curproc->p_vmspace->vm_map.pmap))) \
panic("PMAP_ISACTIVE, isa: %d pm: %p curpm:%p", \
isactive_, (pm), curproc->p_vmspace->vm_map.pmap); \
(isactive_); \
})
#else
#define PMAP_ISACTIVE(pm, cpu_id) PMAP_ISACTIVE_TEST(pm, cpu_id)
#endif
#ifdef DEBUG
#define PMAP_ACTIVATE_ASN_SANITY(pmap, cpu_id) \
do { \
struct pmap_asn_info *__pma = &(pmap)->pm_asni[(cpu_id)]; \
struct pmap_asn_info *__cpma = &pmap_asn_info[(cpu_id)]; \
\
if ((pmap)->pm_lev1map == kernel_lev1map) { \
\
if (__pma->pma_asn != PMAP_ASN_RESERVED) { \
printf("kernel_lev1map with non-reserved ASN " \
"(line %d)\n", __LINE__); \
panic("PMAP_ACTIVATE_ASN_SANITY"); \
} \
} else { \
if (__pma->pma_asngen != __cpma->pma_asngen) { \
\
printf("pmap asngen %lu, current %lu " \
"(line %d)\n", \
__pma->pma_asngen, \
__cpma->pma_asngen, \
__LINE__); \
panic("PMAP_ACTIVATE_ASN_SANITY"); \
} \
if (__pma->pma_asn == PMAP_ASN_RESERVED) { \
\
printf("Using reserved ASN! (line %d)\n", \
__LINE__); \
panic("PMAP_ACTIVATE_ASN_SANITY"); \
} \
} \
} while (0)
#else
#define PMAP_ACTIVATE_ASN_SANITY(pmap, cpu_id)
#endif
#define PMAP_ACTIVATE(pmap, p, cpu_id) \
do { \
PMAP_ACTIVATE_ASN_SANITY(pmap, cpu_id); \
\
(p)->p_addr->u_pcb.pcb_hw.apcb_ptbr = \
ALPHA_K0SEG_TO_PHYS((vaddr_t)(pmap)->pm_lev1map) >> PGSHIFT; \
(p)->p_addr->u_pcb.pcb_hw.apcb_asn = \
(pmap)->pm_asni[(cpu_id)].pma_asn; \
\
if ((p) == curproc) { \
\
(void) alpha_pal_swpctx((u_long)p->p_md.md_pcbpaddr); \
} \
} while (0)
#define PMAP_SET_NEEDISYNC(pmap) (pmap)->pm_needisync = ~0UL
#if defined(MULTIPROCESSOR)
#define PMAP_SYNC_ISTREAM_KERNEL() \
do { \
alpha_pal_imb(); \
alpha_broadcast_ipi(ALPHA_IPI_IMB); \
} while (0)
#define PMAP_SYNC_ISTREAM_USER(pmap) \
do { \
alpha_multicast_ipi((pmap)->pm_cpus, ALPHA_IPI_AST); \
\
} while (0)
#else
#define PMAP_SYNC_ISTREAM_KERNEL() alpha_pal_imb()
#define PMAP_SYNC_ISTREAM_USER(pmap)
#endif
#define PMAP_SYNC_ISTREAM(pmap) \
do { \
if ((pmap) == pmap_kernel()) \
PMAP_SYNC_ISTREAM_KERNEL(); \
else \
PMAP_SYNC_ISTREAM_USER(pmap); \
} while (0)
#define PMAP_INVALIDATE_ASN(pmap, cpu_id) \
do { \
(pmap)->pm_asni[(cpu_id)].pma_asn = PMAP_ASN_RESERVED; \
} while (0)
#define PMAP_INVALIDATE_TLB(pmap, va, hadasm, isactive, cpu_id) \
do { \
if ((hadasm) || (isactive)) { \
\
ALPHA_TBIS((va)); \
} else if ((pmap)->pm_asni[(cpu_id)].pma_asngen == \
pmap_asn_info[(cpu_id)].pma_asngen) { \
\
PMAP_INVALIDATE_ASN((pmap), (cpu_id)); \
} \
\
} while (0)
#ifdef DEBUG
#define PMAP_KERNEL_PTE(va) \
({ \
pt_entry_t *l1pte_, *l2pte_; \
\
l1pte_ = pmap_l1pte(pmap_kernel(), va); \
if (pmap_pte_v(l1pte_) == 0) { \
printf("kernel level 1 PTE not valid, va 0x%lx " \
"(line %d)\n", (va), __LINE__); \
panic("PMAP_KERNEL_PTE"); \
} \
l2pte_ = pmap_l2pte(pmap_kernel(), va, l1pte_); \
if (pmap_pte_v(l2pte_) == 0) { \
printf("kernel level 2 PTE not valid, va 0x%lx " \
"(line %d)\n", (va), __LINE__); \
panic("PMAP_KERNEL_PTE"); \
} \
pmap_l3pte(pmap_kernel(), va, l2pte_); \
})
#else
#define PMAP_KERNEL_PTE(va) (&VPT[VPT_INDEX((va))])
#endif
#define PMAP_SET_PTE(ptep, val) *(ptep) = (val)
#define PMAP_STAT_INCR(s, v) atomic_add_ulong((unsigned long *)(&(s)), (v))
#define PMAP_STAT_DECR(s, v) atomic_sub_ulong((unsigned long *)(&(s)), (v))
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;
int i;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_BOOTSTRAP))
printf("pmap_bootstrap(0x%lx, %u)\n", ptaddr, maxasn);
#endif
kmeminit_nkmempages();
lev3mapsize = (VM_PHYS_SIZE + 16 * NCARGS + PAGER_MAP_SIZE) /
PAGE_SIZE + (maxthread * UPAGES) + nkmempages;
#ifdef SYSVSHM
lev3mapsize += shminfo.shmall;
#endif
lev3mapsize = roundup(lev3mapsize, NPTEPG);
kernel_lev1map = (pt_entry_t *)
pmap_steal_memory(sizeof(pt_entry_t) * NPTEPG, NULL, NULL);
lev2mapsize = roundup(howmany(lev3mapsize, NPTEPG), NPTEPG);
lev2map = (pt_entry_t *)
pmap_steal_memory(sizeof(pt_entry_t) * lev2mapsize, NULL, NULL);
lev3map = (pt_entry_t *)
pmap_steal_memory(sizeof(pt_entry_t) * lev3mapsize, NULL, NULL);
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[l2pte_index(VM_MIN_KERNEL_ADDRESS+
(i*PAGE_SIZE*NPTEPG))] = pte;
}
mtx_init(&pmap_growkernel_mtx, IPL_NONE);
avail_start = ptoa(vm_physmem[0].start);
avail_end = ptoa(vm_physmem[vm_nphysseg - 1].end);
pmap_maxkvaddr = 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);
#endif
pmap_ncpuids = ncpuids;
pool_init(&pmap_pmap_pool, PMAP_SIZEOF(pmap_ncpuids), 0, IPL_NONE, 0,
"pmappl", &pool_allocator_single);
pool_init(&pmap_l1pt_pool, PAGE_SIZE, 0, IPL_VM, 0,
"l1ptpl", &pmap_l1pt_allocator);
pool_init(&pmap_pv_pool, sizeof(struct pv_entry), 0, IPL_VM, 0,
"pvpl", &pmap_pv_page_allocator);
TAILQ_INIT(&pmap_all_pmaps);
pmap_max_asn = maxasn;
for (i = 0; i < ALPHA_MAXPROCS; i++) {
pmap_asn_info[i].pma_asn = 1;
pmap_asn_info[i].pma_asngen = 0;
}
mtx_init(&pmap_all_pmaps_mtx, IPL_NONE);
memset(pmap_kernel(), 0, sizeof(pmap_kernel()));
pmap_kernel()->pm_lev1map = kernel_lev1map;
pmap_kernel()->pm_count = 1;
for (i = 0; i < ALPHA_MAXPROCS; i++) {
pmap_kernel()->pm_asni[i].pma_asn = PMAP_ASN_RESERVED;
pmap_kernel()->pm_asni[i].pma_asngen =
pmap_asn_info[i].pma_asngen;
}
TAILQ_INSERT_TAIL(&pmap_all_pmaps, pmap_kernel(), pm_list);
mtx_init(&pmap_kernel()->pm_mtx, IPL_VM);
proc0.p_addr->u_pcb.pcb_hw.apcb_ptbr =
ALPHA_K0SEG_TO_PHYS((vaddr_t)kernel_lev1map) >> PGSHIFT;
proc0.p_addr->u_pcb.pcb_hw.apcb_asn =
pmap_kernel()->pm_asni[cpu_number()].pma_asn;
atomic_setbits_ulong(&pmap_kernel()->pm_cpus,
(1UL << cpu_number()));
}
vaddr_t
pmap_steal_memory(vsize_t size, vaddr_t *vstartp, vaddr_t *vendp)
{
int bank, npgs, x;
vaddr_t va;
paddr_t pa;
size = round_page(size);
npgs = atop(size);
#if 0
printf("PSM: size 0x%lx (npgs 0x%x)\n", size, npgs);
#endif
for (bank = 0; bank < vm_nphysseg; bank++) {
if (uvm.page_init_done == TRUE)
panic("pmap_steal_memory: called _after_ bootstrap");
#if 0
printf(" bank %d: avail_start 0x%lx, start 0x%lx, "
"avail_end 0x%lx\n", bank, vm_physmem[bank].avail_start,
vm_physmem[bank].start, vm_physmem[bank].avail_end);
#endif
if (vm_physmem[bank].avail_start != vm_physmem[bank].start ||
vm_physmem[bank].avail_start >= vm_physmem[bank].avail_end)
continue;
#if 0
printf(" avail_end - avail_start = 0x%lx\n",
vm_physmem[bank].avail_end - vm_physmem[bank].avail_start);
#endif
if ((vm_physmem[bank].avail_end - vm_physmem[bank].avail_start)
< npgs)
continue;
pa = ptoa(vm_physmem[bank].avail_start);
vm_physmem[bank].avail_start += npgs;
vm_physmem[bank].start += npgs;
if (vm_physmem[bank].avail_start == vm_physmem[bank].end) {
if (vm_nphysseg == 1)
panic("pmap_steal_memory: out of memory!");
vm_nphysseg--;
for (x = bank; x < vm_nphysseg; x++) {
vm_physmem[x] = vm_physmem[x + 1];
}
}
if (vstartp)
*vstartp = VM_MIN_KERNEL_ADDRESS;
if (vendp)
*vendp = VM_MAX_KERNEL_ADDRESS;
va = ALPHA_PHYS_TO_K0SEG(pa);
memset((caddr_t)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();
pool_setlowat(&pmap_pv_pool, pmap_pv_lowat);
pmap_initialized = TRUE;
#if 0
for (bank = 0; bank < vm_nphysseg; bank++) {
printf("bank %d\n", bank);
printf("\tstart = 0x%x\n", ptoa(vm_physmem[bank].start));
printf("\tend = 0x%x\n", ptoa(vm_physmem[bank].end));
printf("\tavail_start = 0x%x\n",
ptoa(vm_physmem[bank].avail_start));
printf("\tavail_end = 0x%x\n",
ptoa(vm_physmem[bank].avail_end));
}
#endif
}
pmap_t
pmap_create(void)
{
pmap_t pmap;
int i;
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_CREATE))
printf("pmap_create()\n");
#endif
pmap = pool_get(&pmap_pmap_pool, PR_WAITOK|PR_ZERO);
pmap->pm_count = 1;
for (i = 0; i < pmap_ncpuids; i++) {
pmap->pm_asni[i].pma_asn = PMAP_ASN_RESERVED;
pmap->pm_asni[i].pma_asngen = pmap_asn_info[i].pma_asngen;
}
mtx_init(&pmap->pm_mtx, IPL_VM);
for (;;) {
mtx_enter(&pmap_growkernel_mtx);
i = pmap_lev1map_create(pmap, cpu_number());
mtx_leave(&pmap_growkernel_mtx);
if (i == 0)
break;
uvm_wait(__func__);
}
mtx_enter(&pmap_all_pmaps_mtx);
TAILQ_INSERT_TAIL(&pmap_all_pmaps, pmap, pm_list);
mtx_leave(&pmap_all_pmaps_mtx);
return (pmap);
}
void
pmap_destroy(pmap_t pmap)
{
int refs;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_destroy(%p)\n", pmap);
#endif
refs = atomic_dec_int_nv(&pmap->pm_count);
if (refs > 0)
return;
mtx_enter(&pmap_all_pmaps_mtx);
TAILQ_REMOVE(&pmap_all_pmaps, pmap, pm_list);
mtx_leave(&pmap_all_pmaps_mtx);
mtx_enter(&pmap_growkernel_mtx);
pmap_lev1map_destroy(pmap);
mtx_leave(&pmap_growkernel_mtx);
pool_put(&pmap_pmap_pool, pmap);
}
void
pmap_reference(pmap_t pmap)
{
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_reference(%p)\n", pmap);
#endif
atomic_inc_int(&pmap->pm_count);
}
void
pmap_remove(pmap_t pmap, vaddr_t sva, vaddr_t eva)
{
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_REMOVE|PDB_PROTECT))
printf("pmap_remove(%p, %lx, %lx)\n", pmap, sva, eva);
#endif
pmap_do_remove(pmap, sva, eva);
}
void
pmap_do_remove(pmap_t pmap, vaddr_t sva, vaddr_t eva)
{
pt_entry_t *l1pte, *l2pte, *l3pte;
pt_entry_t *saved_l1pte, *saved_l2pte, *saved_l3pte;
vaddr_t l1eva, l2eva, vptva;
boolean_t needisync = FALSE;
cpuid_t cpu_id = cpu_number();
#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_LOCK(pmap);
while (sva < eva) {
l3pte = PMAP_KERNEL_PTE(sva);
if (pmap_pte_v(l3pte)) {
#ifdef DIAGNOSTIC
if (PAGE_IS_MANAGED(pmap_pte_pa(l3pte)) &&
pmap_pte_pv(l3pte) == 0)
panic("pmap_remove: managed page "
"without PG_PVLIST for 0x%lx",
sva);
#endif
needisync |= pmap_remove_mapping(pmap, sva,
l3pte, TRUE, cpu_id);
}
sva += PAGE_SIZE;
}
PMAP_UNLOCK(pmap);
if (needisync)
PMAP_SYNC_ISTREAM_KERNEL();
return;
}
#ifdef DIAGNOSTIC
if (sva > VM_MAXUSER_ADDRESS || eva > VM_MAXUSER_ADDRESS)
panic("pmap_remove: (0x%lx - 0x%lx) user pmap, kernel "
"address range", sva, eva);
#endif
PMAP_LOCK(pmap);
if (pmap->pm_lev1map == kernel_lev1map)
goto out;
saved_l1pte = l1pte = pmap_l1pte(pmap, sva);
pmap_physpage_addref(saved_l1pte);
for (; sva < eva; sva = l1eva, l1pte++) {
l1eva = alpha_trunc_l1seg(sva) + ALPHA_L1SEG_SIZE;
if (pmap_pte_v(l1pte)) {
saved_l2pte = l2pte = pmap_l2pte(pmap, 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(pmap, sva, l2pte);
pmap_physpage_addref(saved_l3pte);
vptva = sva;
for (; sva < l2eva && sva < eva;
sva += PAGE_SIZE, l3pte++) {
if (pmap_pte_v(l3pte)) {
needisync |=
pmap_remove_mapping(
pmap, sva,
l3pte, TRUE,
cpu_id);
}
}
pmap_l3pt_delref(pmap, vptva,
saved_l3pte, cpu_id);
}
}
pmap_l2pt_delref(pmap, l1pte, saved_l2pte);
}
}
pmap_l1pt_delref(pmap, saved_l1pte);
if (needisync)
PMAP_SYNC_ISTREAM_USER(pmap);
out:
PMAP_UNLOCK(pmap);
}
void
pmap_page_protect(struct vm_page *pg, vm_prot_t prot)
{
pmap_t pmap;
pv_entry_t pv;
boolean_t needkisync = FALSE;
cpuid_t cpu_id = cpu_number();
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if ((pmapdebug & (PDB_FOLLOW|PDB_PROTECT)) ||
(prot == PROT_NONE && (pmapdebug & PDB_REMOVE)))
printf("pmap_page_protect(%p, %x)\n", pg, prot);
#endif
switch (prot) {
case PROT_READ | PROT_WRITE | PROT_EXEC:
case PROT_READ | PROT_WRITE:
return;
case PROT_READ | PROT_EXEC:
case PROT_READ:
mtx_enter(&pg->mdpage.pvh_mtx);
for (pv = pg->mdpage.pvh_list; pv != NULL; pv = pv->pv_next) {
if (*pv->pv_pte & (PG_KWE | PG_UWE)) {
*pv->pv_pte &= ~(PG_KWE | PG_UWE);
PMAP_INVALIDATE_TLB(pv->pv_pmap, pv->pv_va,
pmap_pte_asm(pv->pv_pte),
PMAP_ISACTIVE(pv->pv_pmap, cpu_id), cpu_id);
PMAP_TLB_SHOOTDOWN(pv->pv_pmap, pv->pv_va,
pmap_pte_asm(pv->pv_pte));
}
}
mtx_leave(&pg->mdpage.pvh_mtx);
PMAP_TLB_SHOOTNOW();
return;
default:
break;
}
mtx_enter(&pg->mdpage.pvh_mtx);
while ((pv = pg->mdpage.pvh_list) != NULL) {
pmap_reference(pv->pv_pmap);
pmap = pv->pv_pmap;
mtx_leave(&pg->mdpage.pvh_mtx);
PMAP_LOCK(pmap);
mtx_enter(&pg->mdpage.pvh_mtx);
if ((pv = pg->mdpage.pvh_list) == NULL ||
pv->pv_pmap != pmap) {
mtx_leave(&pg->mdpage.pvh_mtx);
PMAP_UNLOCK(pmap);
pmap_destroy(pmap);
mtx_enter(&pg->mdpage.pvh_mtx);
continue;
}
#ifdef DEBUG
if (pmap_pte_v(pmap_l2pte(pv->pv_pmap, pv->pv_va, NULL)) == 0 ||
pmap_pte_pa(pv->pv_pte) != VM_PAGE_TO_PHYS(pg))
panic("pmap_page_protect: bad mapping");
#endif
if (pmap_remove_mapping(pmap, pv->pv_va, pv->pv_pte,
FALSE, cpu_id) == TRUE) {
if (pmap == pmap_kernel())
needkisync |= TRUE;
else
PMAP_SYNC_ISTREAM_USER(pmap);
}
mtx_leave(&pg->mdpage.pvh_mtx);
PMAP_UNLOCK(pmap);
pmap_destroy(pmap);
mtx_enter(&pg->mdpage.pvh_mtx);
}
mtx_leave(&pg->mdpage.pvh_mtx);
if (needkisync)
PMAP_SYNC_ISTREAM_KERNEL();
}
void
pmap_protect(pmap_t pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot)
{
pt_entry_t *l1pte, *l2pte, *l3pte, bits;
boolean_t isactive;
boolean_t hadasm;
vaddr_t l1eva, l2eva;
cpuid_t cpu_id = cpu_number();
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_PROTECT))
printf("pmap_protect(%p, %lx, %lx, %x)\n",
pmap, sva, eva, prot);
#endif
if ((prot & PROT_READ) == PROT_NONE) {
pmap_remove(pmap, sva, eva);
return;
}
PMAP_LOCK(pmap);
bits = pte_prot(pmap, prot);
isactive = PMAP_ISACTIVE(pmap, cpu_id);
l1pte = pmap_l1pte(pmap, sva);
for (; sva < eva; sva = l1eva, l1pte++) {
l1eva = alpha_trunc_l1seg(sva) + ALPHA_L1SEG_SIZE;
if (!pmap_pte_v(l1pte))
continue;
l2pte = pmap_l2pte(pmap, sva, l1pte);
for (; sva < l1eva && sva < eva; sva = l2eva, l2pte++) {
l2eva = alpha_trunc_l2seg(sva) + ALPHA_L2SEG_SIZE;
if (!pmap_pte_v(l2pte))
continue;
l3pte = pmap_l3pte(pmap, sva, l2pte);
for (; sva < l2eva && sva < eva;
sva += PAGE_SIZE, l3pte++) {
if (!pmap_pte_v(l3pte))
continue;
if (pmap_pte_prot_chg(l3pte, bits)) {
hadasm = (pmap_pte_asm(l3pte) != 0);
pmap_pte_set_prot(l3pte, bits);
PMAP_INVALIDATE_TLB(pmap, sva, hadasm,
isactive, cpu_id);
PMAP_TLB_SHOOTDOWN(pmap, sva,
hadasm ? PG_ASM : 0);
}
}
}
}
PMAP_TLB_SHOOTNOW();
if (prot & PROT_EXEC)
PMAP_SYNC_ISTREAM(pmap);
PMAP_UNLOCK(pmap);
}
int
pmap_enter(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot, int flags)
{
struct vm_page *pg;
pt_entry_t *pte, npte, opte;
paddr_t opa;
boolean_t tflush = TRUE;
boolean_t hadasm = FALSE;
boolean_t needisync = FALSE;
boolean_t setisync = FALSE;
boolean_t isactive;
boolean_t wired;
cpuid_t cpu_id = cpu_number();
int error = 0;
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ENTER))
printf("pmap_enter(%p, %lx, %lx, %x, %x)\n",
pmap, va, pa, prot, flags);
#endif
pg = PHYS_TO_VM_PAGE(pa);
isactive = PMAP_ISACTIVE(pmap, cpu_id);
wired = (flags & PMAP_WIRED) != 0;
if (prot & PROT_EXEC) {
if (pmap == pmap_kernel())
needisync = TRUE;
else {
setisync = TRUE;
needisync = (pmap->pm_cpus != 0);
}
}
PMAP_LOCK(pmap);
if (pmap == pmap_kernel()) {
#ifdef DIAGNOSTIC
if (va < VM_MIN_KERNEL_ADDRESS)
panic("pmap_enter: kernel pmap, invalid va 0x%lx", va);
#endif
pte = PMAP_KERNEL_PTE(va);
} else {
pt_entry_t *l1pte, *l2pte;
#ifdef DIAGNOSTIC
if (va >= VM_MAXUSER_ADDRESS)
panic("pmap_enter: user pmap, invalid va 0x%lx", va);
#endif
KASSERT(pmap->pm_lev1map != kernel_lev1map);
l1pte = pmap_l1pte(pmap, 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(pmap, va, l1pte);
if (pmap_pte_v(l2pte) == 0) {
pmap_physpage_addref(l2pte);
error = pmap_ptpage_alloc(pmap, l2pte, PGU_L3PT);
if (error) {
pmap_l2pt_delref(pmap, l1pte, l2pte);
if (flags & PMAP_CANFAIL)
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(pmap, va, l2pte);
}
opte = *pte;
if (pmap_pte_v(pte) == 0) {
tflush = FALSE;
setisync = needisync = FALSE;
if (pmap != pmap_kernel()) {
pmap_physpage_addref(pte);
}
goto validate_enterpv;
}
opa = pmap_pte_pa(pte);
hadasm = (pmap_pte_asm(pte) != 0);
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);
}
needisync |= pmap_remove_mapping(pmap, va, pte, TRUE, cpu_id);
validate_enterpv:
if (pg != NULL) {
error = pmap_pv_enter(pmap, pg, va, pte, TRUE);
if (error) {
pmap_l3pt_delref(pmap, va, pte, cpu_id);
if (flags & PMAP_CANFAIL)
goto out;
panic("pmap_enter: unable to enter mapping in PV "
"table");
}
}
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) {
int attrs;
#ifdef DIAGNOSTIC
if ((flags & PROT_MASK) & ~prot)
panic("pmap_enter: access type exceeds prot");
#endif
if (flags & PROT_WRITE)
atomic_setbits_int(&pg->pg_flags,
PG_PMAP_REF | PG_PMAP_MOD);
else if (flags & PROT_MASK)
atomic_setbits_int(&pg->pg_flags, PG_PMAP_REF);
attrs = pg->pg_flags;
if ((attrs & PG_PMAP_REF) == 0)
npte |= PG_FOR | PG_FOW | PG_FOE;
else if ((attrs & PG_PMAP_MOD) == 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 (PG_PALCODE(opte) == PG_PALCODE(npte))
tflush = FALSE;
PMAP_SET_PTE(pte, npte);
if (tflush) {
PMAP_INVALIDATE_TLB(pmap, va, hadasm, isactive, cpu_id);
PMAP_TLB_SHOOTDOWN(pmap, va, hadasm ? PG_ASM : 0);
PMAP_TLB_SHOOTNOW();
}
if (setisync)
PMAP_SET_NEEDISYNC(pmap);
if (needisync)
PMAP_SYNC_ISTREAM(pmap);
out:
PMAP_UNLOCK(pmap);
return error;
}
void
pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot)
{
pt_entry_t *pte, npte;
cpuid_t cpu_id = cpu_number();
boolean_t needisync = FALSE;
pmap_t pmap = pmap_kernel();
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ENTER))
printf("pmap_kenter_pa(%lx, %lx, %x)\n",
va, pa, prot);
#endif
#ifdef DIAGNOSTIC
if (va < VM_MIN_KERNEL_ADDRESS)
panic("pmap_kenter_pa: kernel pmap, invalid va 0x%lx", va);
#endif
pte = PMAP_KERNEL_PTE(va);
if (pmap_pte_v(pte) == 0)
PMAP_STAT_INCR(pmap->pm_stats.resident_count, 1);
if (pmap_pte_w(pte) == 0)
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
if ((prot & PROT_EXEC) != 0 || pmap_pte_exec(pte))
needisync = TRUE;
npte = ((pa >> PGSHIFT) << PG_SHIFT) | pte_prot(pmap_kernel(), prot) |
PG_V | PG_WIRED;
PMAP_SET_PTE(pte, npte);
#if defined(MULTIPROCESSOR)
alpha_mb();
#endif
PMAP_INVALIDATE_TLB(pmap, va, TRUE, TRUE, cpu_id);
PMAP_TLB_SHOOTDOWN(pmap, va, PG_ASM);
PMAP_TLB_SHOOTNOW();
if (needisync)
PMAP_SYNC_ISTREAM_KERNEL();
}
void
pmap_kremove(vaddr_t va, vsize_t size)
{
pt_entry_t *pte;
boolean_t needisync = FALSE;
cpuid_t cpu_id = cpu_number();
pmap_t pmap = pmap_kernel();
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ENTER))
printf("pmap_kremove(%lx, %lx)\n",
va, size);
#endif
#ifdef DIAGNOSTIC
if (va < VM_MIN_KERNEL_ADDRESS)
panic("pmap_kremove: user address");
#endif
for (; size != 0; size -= PAGE_SIZE, va += PAGE_SIZE) {
pte = PMAP_KERNEL_PTE(va);
if (pmap_pte_v(pte)) {
#ifdef DIAGNOSTIC
if (pmap_pte_pv(pte))
panic("pmap_kremove: PG_PVLIST mapping for "
"0x%lx", va);
#endif
if (pmap_pte_exec(pte))
needisync = TRUE;
PMAP_SET_PTE(pte, PG_NV);
#if defined(MULTIPROCESSOR)
alpha_mb();
#endif
PMAP_INVALIDATE_TLB(pmap, va, TRUE, TRUE, cpu_id);
PMAP_TLB_SHOOTDOWN(pmap, va, PG_ASM);
PMAP_STAT_DECR(pmap->pm_stats.resident_count, 1);
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
}
}
PMAP_TLB_SHOOTNOW();
if (needisync)
PMAP_SYNC_ISTREAM_KERNEL();
}
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, va, NULL);
#ifdef DIAGNOSTIC
if (pte == NULL || pmap_pte_v(pte) == 0)
panic("pmap_unwire");
#endif
if (pmap_pte_w_chg(pte, 0)) {
pmap_pte_set_w(pte, FALSE);
PMAP_STAT_DECR(pmap->pm_stats.wired_count, 1);
}
#ifdef DIAGNOSTIC
else {
printf("pmap_unwire: wiring for pmap %p va 0x%lx "
"didn't change!\n", pmap, va);
}
#endif
PMAP_UNLOCK(pmap);
}
boolean_t
pmap_extract(pmap_t pmap, vaddr_t va, paddr_t *pap)
{
pt_entry_t *l1pte, *l2pte, *l3pte;
boolean_t rv = FALSE;
paddr_t pa;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_extract(%p, %lx) -> ", pmap, va);
#endif
if (pmap == pmap_kernel()) {
if (va < ALPHA_K0SEG_BASE) {
} else if (va <= ALPHA_K0SEG_END) {
pa = ALPHA_K0SEG_TO_PHYS(va);
*pap = pa;
rv = TRUE;
} else {
l3pte = PMAP_KERNEL_PTE(va);
if (pmap_pte_v(l3pte)) {
pa = pmap_pte_pa(l3pte) | (va & PGOFSET);
*pap = pa;
rv = TRUE;
}
}
goto out_nolock;
}
PMAP_LOCK(pmap);
l1pte = pmap_l1pte(pmap, va);
if (pmap_pte_v(l1pte) == 0)
goto out;
l2pte = pmap_l2pte(pmap, va, l1pte);
if (pmap_pte_v(l2pte) == 0)
goto out;
l3pte = pmap_l3pte(pmap, va, l2pte);
if (pmap_pte_v(l3pte) == 0)
goto out;
pa = pmap_pte_pa(l3pte) | (va & PGOFSET);
*pap = pa;
rv = TRUE;
out:
PMAP_UNLOCK(pmap);
out_nolock:
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW) {
if (rv)
printf("0x%lx\n", pa);
else
printf("failed\n");
}
#endif
return (rv);
}
void
pmap_activate(struct proc *p)
{
struct pmap *pmap = p->p_vmspace->vm_map.pmap;
cpuid_t cpu_id = cpu_number();
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_activate(%p)\n", p);
#endif
atomic_setbits_ulong(&pmap->pm_cpus, (1UL << cpu_id));
pmap_asn_alloc(pmap, cpu_id);
PMAP_ACTIVATE(pmap, p, cpu_id);
}
void
pmap_deactivate(struct proc *p)
{
struct pmap *pmap = p->p_vmspace->vm_map.pmap;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_deactivate(%p)\n", p);
#endif
atomic_clearbits_ulong(&pmap->pm_cpus, (1UL << cpu_number()));
}
void
pmap_zero_page(struct vm_page *pg)
{
paddr_t phys = VM_PAGE_TO_PHYS(pg);
u_long *p0, *p1, *pend;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_zero_page(%lx)\n", phys);
#endif
p0 = (u_long *)ALPHA_PHYS_TO_K0SEG(phys);
p1 = NULL;
pend = (u_long *)((u_long)p0 + PAGE_SIZE);
do {
__asm volatile(
"# BEGIN loop body\n"
" addq %2, (8 * 8), %1 \n"
" stq $31, (0 * 8)(%0) \n"
" stq $31, (1 * 8)(%0) \n"
" stq $31, (2 * 8)(%0) \n"
" stq $31, (3 * 8)(%0) \n"
" stq $31, (4 * 8)(%0) \n"
" stq $31, (5 * 8)(%0) \n"
" stq $31, (6 * 8)(%0) \n"
" stq $31, (7 * 8)(%0) \n"
" \n"
" addq %3, (8 * 8), %0 \n"
" stq $31, (0 * 8)(%1) \n"
" stq $31, (1 * 8)(%1) \n"
" stq $31, (2 * 8)(%1) \n"
" stq $31, (3 * 8)(%1) \n"
" stq $31, (4 * 8)(%1) \n"
" stq $31, (5 * 8)(%1) \n"
" stq $31, (6 * 8)(%1) \n"
" stq $31, (7 * 8)(%1) \n"
" # END loop body"
: "=r" (p0), "=r" (p1)
: "0" (p0), "1" (p1)
: "memory");
} while (p0 < pend);
}
void
pmap_copy_page(struct vm_page *srcpg, struct vm_page *dstpg)
{
paddr_t src = VM_PAGE_TO_PHYS(srcpg);
paddr_t dst = VM_PAGE_TO_PHYS(dstpg);
caddr_t s, d;
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_copy_page(%lx, %lx)\n", src, dst);
#endif
s = (caddr_t)ALPHA_PHYS_TO_K0SEG(src);
d = (caddr_t)ALPHA_PHYS_TO_K0SEG(dst);
memcpy(d, s, PAGE_SIZE);
}
boolean_t
pmap_clear_modify(struct vm_page *pg)
{
boolean_t rv = FALSE;
cpuid_t cpu_id = cpu_number();
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_clear_modify(%p)\n", pg);
#endif
mtx_enter(&pg->mdpage.pvh_mtx);
if (pg->pg_flags & PG_PMAP_MOD) {
rv = TRUE;
pmap_changebit(pg, PG_FOW, ~0, cpu_id);
atomic_clearbits_int(&pg->pg_flags, PG_PMAP_MOD);
}
mtx_leave(&pg->mdpage.pvh_mtx);
return (rv);
}
boolean_t
pmap_clear_reference(struct vm_page *pg)
{
boolean_t rv = FALSE;
cpuid_t cpu_id = cpu_number();
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_clear_reference(%p)\n", pg);
#endif
mtx_enter(&pg->mdpage.pvh_mtx);
if (pg->pg_flags & PG_PMAP_REF) {
rv = TRUE;
pmap_changebit(pg, PG_FOR | PG_FOW | PG_FOE, ~0, cpu_id);
atomic_clearbits_int(&pg->pg_flags, PG_PMAP_REF);
}
mtx_leave(&pg->mdpage.pvh_mtx);
return (rv);
}
boolean_t
pmap_is_referenced(struct vm_page *pg)
{
boolean_t rv;
rv = ((pg->pg_flags & PG_PMAP_REF) != 0);
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW) {
printf("pmap_is_referenced(%p) -> %c\n", pg, "FT"[rv]);
}
#endif
return (rv);
}
boolean_t
pmap_is_modified(struct vm_page *pg)
{
boolean_t rv;
rv = ((pg->pg_flags & PG_PMAP_MOD) != 0);
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW) {
printf("pmap_is_modified(%p) -> %c\n", pg, "FT"[rv]);
}
#endif
return (rv);
}
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 & PROT_READ) {
kp[prot] |= PG_KRE;
up[prot] |= PG_KRE | PG_URE;
}
if (prot & PROT_WRITE) {
kp[prot] |= PG_KWE;
up[prot] |= PG_KWE | PG_UWE;
}
if (prot & PROT_EXEC) {
kp[prot] |= PG_EXEC | PG_KRE;
up[prot] |= PG_EXEC | PG_KRE | PG_URE;
} else {
kp[prot] |= PG_FOE;
up[prot] |= PG_FOE;
}
}
}
boolean_t
pmap_remove_mapping(pmap_t pmap, vaddr_t va, pt_entry_t *pte,
boolean_t dolock, cpuid_t cpu_id)
{
paddr_t pa;
struct vm_page *pg;
boolean_t onpv;
boolean_t hadasm;
boolean_t isactive;
boolean_t needisync = FALSE;
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_REMOVE|PDB_PROTECT))
printf("pmap_remove_mapping(%p, %lx, %p, %d, %ld)\n",
pmap, va, pte, dolock, cpu_id);
#endif
if (pte == PT_ENTRY_NULL) {
pte = pmap_l3pte(pmap, va, NULL);
if (pmap_pte_v(pte) == 0)
return (FALSE);
}
pa = pmap_pte_pa(pte);
onpv = (pmap_pte_pv(pte) != 0);
if (onpv) {
pg = PHYS_TO_VM_PAGE(pa);
KASSERT(pg != NULL);
pmap_pv_remove(pmap, pg, va, dolock);
}
hadasm = (pmap_pte_asm(pte) != 0);
isactive = PMAP_ISACTIVE(pmap, cpu_id);
if (pmap_pte_exec(pte)) {
if (pmap == pmap_kernel())
needisync = TRUE;
else {
PMAP_SET_NEEDISYNC(pmap);
needisync = (pmap->pm_cpus != 0);
}
}
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
PMAP_SET_PTE(pte, PG_NV);
PMAP_INVALIDATE_TLB(pmap, va, hadasm, isactive, cpu_id);
PMAP_TLB_SHOOTDOWN(pmap, va, hadasm ? PG_ASM : 0);
PMAP_TLB_SHOOTNOW();
if (pmap != pmap_kernel()) {
pmap_l3pt_delref(pmap, va, pte, cpu_id);
}
return (needisync);
}
void
pmap_changebit(struct vm_page *pg, u_long set, u_long mask, cpuid_t cpu_id)
{
pv_entry_t pv;
pt_entry_t *pte, npte;
vaddr_t va;
boolean_t hadasm, isactive;
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
#ifdef DEBUG
if (pmapdebug & PDB_BITS)
printf("pmap_changebit(0x%lx, 0x%lx, 0x%lx)\n",
VM_PAGE_TO_PHYS(pg), set, mask);
#endif
MUTEX_ASSERT_LOCKED(&pg->mdpage.pvh_mtx);
for (pv = pg->mdpage.pvh_list; pv != NULL; pv = pv->pv_next) {
va = pv->pv_va;
pte = pv->pv_pte;
npte = (*pte | set) & mask;
if (*pte != npte) {
hadasm = (pmap_pte_asm(pte) != 0);
isactive = PMAP_ISACTIVE(pv->pv_pmap, cpu_id);
PMAP_SET_PTE(pte, npte);
PMAP_INVALIDATE_TLB(pv->pv_pmap, va, hadasm, isactive,
cpu_id);
PMAP_TLB_SHOOTDOWN(pv->pv_pmap, va,
hadasm ? PG_ASM : 0);
}
}
PMAP_TLB_SHOOTNOW();
}
int
pmap_emulate_reference(struct proc *p, vaddr_t v, int user, int type)
{
struct pmap *pmap;
pt_entry_t faultoff, *pte;
struct vm_page *pg;
paddr_t pa;
boolean_t didlock = FALSE;
boolean_t exec = FALSE;
cpuid_t cpu_id = cpu_number();
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("pmap_emulate_reference: %p, 0x%lx, %d, %d\n",
p, 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 (p == NULL)
panic("pmap_emulate_reference: bad proc");
if (p->p_vmspace == NULL)
panic("pmap_emulate_reference: bad p_vmspace");
#endif
pmap = p->p_vmspace->vm_map.pmap;
PMAP_LOCK(pmap);
didlock = TRUE;
pte = pmap_l3pte(pmap, v, NULL);
}
if (pte == NULL || !pmap_pte_v(pte)) {
if (didlock)
PMAP_UNLOCK(pmap);
return (0);
}
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
#ifdef DEBUG
#ifndef MULTIPROCESSOR
if (type == ALPHA_MMCSR_FOW) {
if (!(*pte & (user ? PG_UWE : PG_UWE | PG_KWE))) {
panic("pmap_emulate_reference(%d,%d): "
"write but unwritable pte 0x%lx",
user, type, *pte);
}
if (!(*pte & PG_FOW)) {
panic("pmap_emulate_reference(%d,%d): "
"write but not FOW pte 0x%lx",
user, type, *pte);
}
} else {
if (!(*pte & (user ? PG_URE : PG_URE | PG_KRE))) {
panic("pmap_emulate_reference(%d,%d): "
"!write but unreadable pte 0x%lx",
user, type, *pte);
}
if (!(*pte & (PG_FOR | PG_FOE))) {
panic("pmap_emulate_reference(%d,%d): "
"!write but not FOR|FOE pte 0x%lx",
user, type, *pte);
}
}
#endif
#endif
pa = pmap_pte_pa(pte);
if (didlock)
PMAP_UNLOCK(pmap);
#ifdef DEBUG
if (pmapdebug & PDB_FOLLOW)
printf("\tpa = 0x%lx\n", pa);
#endif
pg = PHYS_TO_VM_PAGE(pa);
#ifdef DIAGNOSTIC
if (pg == NULL) {
panic("pmap_emulate_reference(%p, 0x%lx, %d, %d): "
"pa 0x%lx (pte %p 0x%08lx) not managed",
p, v, user, type, pa, pte, *pte);
}
#endif
mtx_enter(&pg->mdpage.pvh_mtx);
if (type == ALPHA_MMCSR_FOW) {
atomic_setbits_int(&pg->pg_flags, PG_PMAP_REF | PG_PMAP_MOD);
faultoff = PG_FOR | PG_FOW;
} else {
atomic_setbits_int(&pg->pg_flags, PG_PMAP_REF);
faultoff = PG_FOR;
if (exec) {
faultoff |= PG_FOE;
}
}
pmap_changebit(pg, 0, ~faultoff, cpu_id);
mtx_leave(&pg->mdpage.pvh_mtx);
return (0);
}
#ifdef DEBUG
void
pmap_pv_dump(paddr_t pa)
{
struct vm_page *pg;
pv_entry_t pv;
pg = PHYS_TO_VM_PAGE(pa);
printf("pa 0x%lx (attrs = 0x%x):\n",
pa, pg->pg_flags & (PG_PMAP_REF | PG_PMAP_MOD));
mtx_enter(&pg->mdpage.pvh_mtx);
for (pv = pg->mdpage.pvh_list; pv != NULL; pv = pv->pv_next)
printf(" pmap %p, va 0x%lx\n",
pv->pv_pmap, pv->pv_va);
mtx_leave(&pg->mdpage.pvh_mtx);
printf("\n");
}
#endif
paddr_t
vtophys(vaddr_t vaddr)
{
pt_entry_t *pte;
paddr_t paddr = 0;
if (vaddr < ALPHA_K0SEG_BASE)
printf("vtophys: invalid vaddr 0x%lx", vaddr);
else if (vaddr <= ALPHA_K0SEG_END)
paddr = ALPHA_K0SEG_TO_PHYS(vaddr);
else {
pte = PMAP_KERNEL_PTE(vaddr);
if (pmap_pte_v(pte))
paddr = pmap_pte_pa(pte) | (vaddr & PGOFSET);
}
#if 0
printf("vtophys(0x%lx) -> 0x%lx\n", vaddr, paddr);
#endif
return (paddr);
}
int
pmap_pv_enter(pmap_t pmap, struct vm_page *pg, vaddr_t va, pt_entry_t *pte,
boolean_t dolock)
{
pv_entry_t newpv;
newpv = pmap_pv_alloc();
if (newpv == NULL)
return (ENOMEM);
newpv->pv_va = va;
newpv->pv_pmap = pmap;
newpv->pv_pte = pte;
if (dolock)
mtx_enter(&pg->mdpage.pvh_mtx);
#ifdef DEBUG
{
pv_entry_t pv;
for (pv = pg->mdpage.pvh_list; 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
newpv->pv_next = pg->mdpage.pvh_list;
pg->mdpage.pvh_list = newpv;
if (dolock)
mtx_leave(&pg->mdpage.pvh_mtx);
return (0);
}
void
pmap_pv_remove(pmap_t pmap, struct vm_page *pg, vaddr_t va, boolean_t dolock)
{
pv_entry_t pv, *pvp;
if (dolock)
mtx_enter(&pg->mdpage.pvh_mtx);
for (pvp = &pg->mdpage.pvh_list, pv = *pvp;
pv != NULL; pvp = &pv->pv_next, pv = *pvp)
if (pmap == pv->pv_pmap && va == pv->pv_va)
break;
#ifdef DEBUG
if (pv == NULL)
panic("pmap_pv_remove: not in pv table");
#endif
*pvp = pv->pv_next;
if (dolock)
mtx_leave(&pg->mdpage.pvh_mtx);
pmap_pv_free(pv);
}
void *
pmap_pv_page_alloc(struct pool *pp, int flags, int *slowdown)
{
paddr_t pg;
*slowdown = 0;
if (pmap_physpage_alloc(PGU_PVENT, &pg))
return ((void *)ALPHA_PHYS_TO_K0SEG(pg));
return (NULL);
}
void
pmap_pv_page_free(struct pool *pp, void *v)
{
pmap_physpage_free(ALPHA_K0SEG_TO_PHYS((vaddr_t)v));
}
boolean_t
pmap_physpage_alloc(int usage, paddr_t *pap)
{
struct vm_page *pg;
paddr_t pa;
pg = uvm_pagealloc(NULL, 0, NULL, usage == PGU_L1PT ?
UVM_PGA_USERESERVE : UVM_PGA_USERESERVE|UVM_PGA_ZERO);
if (pg != NULL) {
pa = VM_PAGE_TO_PHYS(pg);
#ifdef DIAGNOSTIC
if (pg->wire_count != 0) {
printf("pmap_physpage_alloc: page 0x%lx has "
"%d references\n", pa, pg->wire_count);
panic("pmap_physpage_alloc");
}
#endif
*pap = pa;
return (TRUE);
}
return (FALSE);
}
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");
#ifdef DIAGNOSTIC
if (pg->wire_count != 0)
panic("pmap_physpage_free: page still has references");
#endif
uvm_pagefree(pg);
}
int
pmap_physpage_addref(void *kva)
{
struct vm_page *pg;
paddr_t pa;
int rval;
pa = ALPHA_K0SEG_TO_PHYS(trunc_page((vaddr_t)kva));
pg = PHYS_TO_VM_PAGE(pa);
rval = ++pg->wire_count;
return (rval);
}
int
pmap_physpage_delref(void *kva)
{
struct vm_page *pg;
paddr_t pa;
int rval;
pa = ALPHA_K0SEG_TO_PHYS(trunc_page((vaddr_t)kva));
pg = PHYS_TO_VM_PAGE(pa);
#ifdef DIAGNOSTIC
if (pg->wire_count == 0)
panic("pmap_physpage_delref: reference count already zero");
#endif
rval = --pg->wire_count;
return (rval);
}
vaddr_t
pmap_growkernel(vaddr_t maxkvaddr)
{
struct pmap *kpm = pmap_kernel(), *pm;
paddr_t ptaddr;
pt_entry_t *l1pte, *l2pte, pte;
vaddr_t va;
int l1idx;
mtx_enter(&pmap_growkernel_mtx);
if (maxkvaddr <= pmap_maxkvaddr)
goto out;
va = pmap_maxkvaddr;
while (va < maxkvaddr) {
l1pte = pmap_l1pte(kpm, va);
if (pmap_pte_v(l1pte) == 0) {
if (uvm.page_init_done == FALSE) {
ptaddr = ALPHA_K0SEG_TO_PHYS(
pmap_steal_memory(PAGE_SIZE, NULL, NULL));
} else if (pmap_physpage_alloc(PGU_NORMAL,
&ptaddr) == FALSE)
goto die;
pte = (atop(ptaddr) << PG_SHIFT) |
PG_V | PG_ASM | PG_KRE | PG_KWE | PG_WIRED;
*l1pte = pte;
l1idx = l1pte_index(va);
mtx_enter(&pmap_all_pmaps_mtx);
for (pm = TAILQ_FIRST(&pmap_all_pmaps);
pm != NULL; pm = TAILQ_NEXT(pm, pm_list)) {
if (pm == pmap_kernel())
continue;
PMAP_LOCK(pm);
KDASSERT(pm->pm_lev1map != kernel_lev1map);
pm->pm_lev1map[l1idx] = pte;
PMAP_UNLOCK(pm);
}
mtx_leave(&pmap_all_pmaps_mtx);
}
l2pte = pmap_l2pte(kpm, va, l1pte);
KASSERT(pmap_pte_v(l2pte) == 0);
if (uvm.page_init_done == FALSE) {
ptaddr = ALPHA_K0SEG_TO_PHYS(
pmap_steal_memory(PAGE_SIZE, NULL, NULL));
} else if (pmap_physpage_alloc(PGU_NORMAL, &ptaddr) == FALSE)
goto die;
*l2pte = (atop(ptaddr) << PG_SHIFT) |
PG_V | PG_ASM | PG_KRE | PG_KWE | PG_WIRED;
va += ALPHA_L2SEG_SIZE;
}
#if 0
pool_cache_invalidate(&pmap_l1pt_cache);
#endif
pmap_maxkvaddr = va;
out:
mtx_leave(&pmap_growkernel_mtx);
return (pmap_maxkvaddr);
die:
mtx_leave(&pmap_growkernel_mtx);
panic("pmap_growkernel: out of memory");
}
int
pmap_lev1map_create(pmap_t pmap, cpuid_t cpu_id)
{
pt_entry_t *l1pt;
KASSERT(pmap != pmap_kernel());
KASSERT(pmap->pm_asni[cpu_id].pma_asn == PMAP_ASN_RESERVED);
l1pt = pool_get(&pmap_l1pt_pool, PR_NOWAIT);
if (l1pt == NULL)
return (ENOMEM);
pmap_l1pt_ctor(l1pt);
pmap->pm_lev1map = l1pt;
return (0);
}
void
pmap_lev1map_destroy(pmap_t pmap)
{
pt_entry_t *l1pt = pmap->pm_lev1map;
KASSERT(pmap != pmap_kernel());
pmap->pm_lev1map = kernel_lev1map;
pool_put(&pmap_l1pt_pool, l1pt);
}
void
pmap_l1pt_ctor(pt_entry_t *l1pt)
{
pt_entry_t 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;
}
void *
pmap_l1pt_alloc(struct pool *pp, int flags, int *slowdown)
{
paddr_t ptpa;
*slowdown = 0;
if (pmap_physpage_alloc(PGU_L1PT, &ptpa) == FALSE)
return (NULL);
return ((void *) ALPHA_PHYS_TO_K0SEG(ptpa));
}
void
pmap_l1pt_free(struct pool *pp, void *v)
{
pmap_physpage_free(ALPHA_K0SEG_TO_PHYS((vaddr_t) v));
}
int
pmap_ptpage_alloc(pmap_t pmap, pt_entry_t *pte, int usage)
{
paddr_t ptpa;
if (pmap_physpage_alloc(usage, &ptpa) == FALSE)
return (ENOMEM);
PMAP_SET_PTE(pte, ((ptpa >> PGSHIFT) << PG_SHIFT) |
PG_V | PG_KRE | PG_KWE | PG_WIRED |
(pmap == pmap_kernel() ? PG_ASM : 0));
return (0);
}
void
pmap_ptpage_free(pmap_t pmap, pt_entry_t *pte)
{
paddr_t ptpa;
ptpa = pmap_pte_pa(pte);
PMAP_SET_PTE(pte, PG_NV);
#ifdef DEBUG
pmap_zero_page(PHYS_TO_VM_PAGE(ptpa));
#endif
pmap_physpage_free(ptpa);
}
void
pmap_l3pt_delref(pmap_t pmap, vaddr_t va, pt_entry_t *l3pte, cpuid_t cpu_id)
{
pt_entry_t *l1pte, *l2pte;
PMAP_TLB_SHOOTDOWN_CPUSET_DECL
l1pte = pmap_l1pte(pmap, va);
l2pte = pmap_l2pte(pmap, 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
pmap_ptpage_free(pmap, l2pte);
PMAP_INVALIDATE_TLB(pmap,
(vaddr_t)(&VPT[VPT_INDEX(va)]), FALSE,
PMAP_ISACTIVE(pmap, cpu_id), cpu_id);
PMAP_TLB_SHOOTDOWN(pmap,
(vaddr_t)(&VPT[VPT_INDEX(va)]), 0);
PMAP_TLB_SHOOTNOW();
pmap_l2pt_delref(pmap, l1pte, l2pte);
}
}
void
pmap_l2pt_delref(pmap_t pmap, pt_entry_t *l1pte, pt_entry_t *l2pte)
{
KASSERT(pmap != pmap_kernel());
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
pmap_ptpage_free(pmap, l1pte);
pmap_l1pt_delref(pmap, l1pte);
}
}
void
pmap_l1pt_delref(pmap_t pmap, pt_entry_t *l1pte)
{
KASSERT(pmap != pmap_kernel());
pmap_physpage_delref(l1pte);
}
void
pmap_asn_alloc(pmap_t pmap, cpuid_t cpu_id)
{
struct pmap_asn_info *pma = &pmap->pm_asni[cpu_id];
struct pmap_asn_info *cpma = &pmap_asn_info[cpu_id];
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ASN))
printf("pmap_asn_alloc(%p)\n", pmap);
#endif
if (pmap->pm_lev1map == kernel_lev1map) {
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: still references "
"kernel_lev1map\n");
#endif
#if defined(MULTIPROCESSOR)
pma->pma_asn = PMAP_ASN_RESERVED;
#else
KASSERT(pma->pma_asn == PMAP_ASN_RESERVED);
#endif
return;
}
if (pmap_max_asn == 0) {
pma->pma_asngen = cpma->pma_asngen;
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: no ASNs, using asngen %lu\n",
pma->pma_asngen);
#endif
return;
}
if (pma->pma_asn != PMAP_ASN_RESERVED &&
pma->pma_asngen == cpma->pma_asngen) {
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: same generation, keeping %u\n",
pma->pma_asn);
#endif
return;
}
if (cpma->pma_asn > pmap_max_asn) {
ALPHA_TBIAP();
alpha_pal_imb();
cpma->pma_asn = 1;
cpma->pma_asngen++;
#ifdef DIAGNOSTIC
if (cpma->pma_asngen == 0) {
panic("pmap_asn_alloc: too much uptime");
}
#endif
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: generation bumped to %lu\n",
cpma->pma_asngen);
#endif
}
pma->pma_asn = cpma->pma_asn++;
pma->pma_asngen = cpma->pma_asngen;
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_asn_alloc: assigning %u to pmap %p\n",
pma->pma_asn, pmap);
#endif
atomic_clearbits_ulong(&pmap->pm_needisync, (1UL << cpu_id));
}
#if defined(MULTIPROCESSOR)
static void
pmap_tlb_shootdown_job(struct pmap_tlb_shootdown_q *pq,
pmap_t pmap, vaddr_t va, pt_entry_t pte)
{
unsigned int i;
if (pq->pq_pte == 0) {
for (i = 0; i < nitems(pq->pq_jobs); i++) {
struct pmap_tlb_shootdown_job *pj = &pq->pq_jobs[i];
if (atomic_cas_uint(&pj->pj_state,
PJ_S_IDLE, PJ_S_PENDING) != PJ_S_IDLE)
continue;
pj->pj_pmap = pmap;
pj->pj_va = va;
pj->pj_pte = pte;
membar_producer();
pj->pj_state = PJ_S_VALID;
return;
}
}
atomic_setbits_ulong(&pq->pq_pte, (1UL << 32) | pte);
}
void
pmap_tlb_shootdown(pmap_t pmap, vaddr_t va, pt_entry_t pte, u_long *cpumaskp)
{
struct pmap_tlb_shootdown_q *pq;
struct cpu_info *ci, *self = curcpu();
u_long cpumask = 0;
CPU_INFO_ITERATOR cii;
CPU_INFO_FOREACH(cii, ci) {
if (ci == self)
continue;
if (pmap != pmap_kernel() &&
PMAP_ISACTIVE(pmap, ci->ci_cpuid) == 0) {
PMAP_INVALIDATE_ASN(pmap, ci->ci_cpuid);
continue;
}
cpumask |= 1UL << ci->ci_cpuid;
pq = &pmap_tlb_shootdown_q[ci->ci_cpuid];
pmap_tlb_shootdown_job(pq, pmap, va, pte);
}
*cpumaskp |= cpumask;
}
void
pmap_tlb_shootnow(u_long cpumask)
{
alpha_multicast_ipi(cpumask, ALPHA_IPI_SHOOTDOWN);
}
void
pmap_do_tlb_shootdown(struct cpu_info *ci, struct trapframe *framep)
{
u_long cpu_id = ci->ci_cpuid;
u_long cpu_mask = (1UL << cpu_id);
struct pmap_tlb_shootdown_q *pq = &pmap_tlb_shootdown_q[cpu_id];
unsigned int i;
unsigned long pte;
pte = atomic_swap_ulong(&pq->pq_pte, 0);
if (pte != 0) {
for (i = 0; i < nitems(pq->pq_jobs); i++) {
struct pmap_tlb_shootdown_job *pj = &pq->pq_jobs[i];
if (pj->pj_state != PJ_S_VALID)
continue;
pj->pj_state = PJ_S_IDLE;
}
if (pte & PG_ASM)
ALPHA_TBIA();
else
ALPHA_TBIAP();
pq->pq_globals++;
} else {
for (i = 0; i < nitems(pq->pq_jobs); i++) {
struct pmap_tlb_shootdown_job *pj = &pq->pq_jobs[i];
if (pj->pj_state != PJ_S_VALID)
continue;
membar_consumer();
PMAP_INVALIDATE_TLB(pj->pj_pmap, pj->pj_va,
pj->pj_pte & PG_ASM,
pj->pj_pmap->pm_cpus & cpu_mask, cpu_id);
pj->pj_state = PJ_S_IDLE;
pq->pq_jobruns++;
}
}
}
#endif