#include "opt_arm_debug.h"
#include "opt_efi.h"
#include "opt_multiprocessor.h"
#include "opt_uvmhist.h"
#define __PMAP_PRIVATE
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pmap_machdep.c,v 1.18 2026/06/13 12:12:47 skrll Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/buf.h>
#include <sys/cpu.h>
#include <sys/kernel.h>
#include <uvm/uvm.h>
#include <uvm/uvm_page.h>
#include <uvm/pmap/pmap_pvt.h>
#include <aarch64/cpufunc.h>
#include <arm/locore.h>
#ifdef VERBOSE_INIT_ARM
#define VPRINTF(...) printf(__VA_ARGS__)
#else
#define VPRINTF(...) __nothing
#endif
uint64_t pmap_attr_gp = 0;
vaddr_t virtual_avail;
vaddr_t virtual_end;
PMAP_COUNTER(fixup_referenced, "page reference emulations");
PMAP_COUNTER(fixup_modified, "page modification emulations");
paddr_t
vtophys(vaddr_t va)
{
paddr_t pa;
if (pmap_extract(pmap_kernel(), va, &pa) == false)
return 0;
return pa;
}
bool
pmap_extract_coherency(pmap_t pm, vaddr_t va, paddr_t *pap, bool *coherentp)
{
paddr_t pa;
bool coherency = false;
if (pm == pmap_kernel()) {
if (pmap_md_direct_mapped_vaddr_p(va)) {
pa = pmap_md_direct_mapped_vaddr_to_paddr(va);
goto done;
}
if (pmap_md_io_vaddr_p(va))
panic("pmap_extract: io address %#"PRIxVADDR"", va);
if (va >= pmap_limits.virtual_end)
panic("%s: illegal kernel mapped address %#"PRIxVADDR,
__func__, va);
}
kpreempt_disable();
const pt_entry_t * const ptep = pmap_pte_lookup(pm, va);
pt_entry_t pte;
if (ptep == NULL || !pte_valid_p(pte = *ptep)) {
kpreempt_enable();
return false;
}
kpreempt_enable();
pa = pte_to_paddr(pte) | (va & PGOFSET);
switch (pte & LX_BLKPAG_ATTR_MASK) {
case LX_BLKPAG_ATTR_NORMAL_NC:
case LX_BLKPAG_ATTR_DEVICE_MEM:
case LX_BLKPAG_ATTR_DEVICE_MEM_NP:
coherency = true;
break;
}
done:
if (pap != NULL) {
*pap = pa;
}
if (coherentp != NULL) {
*coherentp = coherency;
}
return true;
}
bool
pmap_fault_fixup(pmap_t pm, vaddr_t va, vm_prot_t ftype, bool user)
{
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
KASSERT(!user || (pm != pmap_kernel()));
kpreempt_disable();
struct pmap_asid_info * const pai = PMAP_PAI(pm, cpu_tlb_info(ci));
UVMHIST_LOG(pmaphist, " pm=%#jx, va=%#jx, ftype=%#jx, user=%jd",
(uintptr_t)pm, va, ftype, user);
UVMHIST_LOG(pmaphist, " ti=%#jx pai=%#jx asid=%#jx",
(uintptr_t)cpu_tlb_info(curcpu()),
(uintptr_t)pai, (uintptr_t)pai->pai_asid, 0);
bool fixed = false;
pt_entry_t * const ptep = pmap_pte_lookup(pm, va);
if (ptep == NULL) {
UVMHIST_LOG(pmaphist, "... no ptep", 0, 0, 0, 0);
goto done;
}
const pt_entry_t opte = atomic_load_relaxed(ptep);
if (!l3pte_valid(opte)) {
UVMHIST_LOG(pmaphist, "invalid pte: %016llx: va=%016lx",
opte, va, 0, 0);
goto done;
}
const paddr_t pa = l3pte_pa(opte);
struct vm_page * const pg = PHYS_TO_VM_PAGE(pa);
if (pg == NULL) {
UVMHIST_LOG(pmaphist, "pg not found: va=%016lx", va, 0, 0, 0);
goto done;
}
struct vm_page_md * const mdpg = VM_PAGE_TO_MD(pg);
UVMHIST_LOG(pmaphist, " pg=%#jx, opte=%#jx, ptep=%#jx", (uintptr_t)pg,
opte, (uintptr_t)ptep, 0);
if ((ftype & VM_PROT_WRITE) && (opte & LX_BLKPAG_OS_MODEMUL) != 0) {
pmap_page_set_attributes(mdpg, VM_PAGEMD_MODIFIED | VM_PAGEMD_REFERENCED);
const pt_entry_t npte =
(opte & ~LX_BLKPAG_AP) |
LX_BLKPAG_AF |
LX_BLKPAG_AP_RW;
atomic_swap_64(ptep, npte);
tlb_invalidate_addr(va, pai->pai_asid);
fixed = true;
PMAP_COUNT(fixup_modified);
UVMHIST_LOG(pmaphist, " <-- done (mod emul: changed pte "
"from %#jx to %#jx)", opte, npte, 0, 0);
} else if ((ftype & VM_PROT_READ) && (opte & LX_BLKPAG_AP) == LX_BLKPAG_AP_RO) {
KASSERT((opte & LX_BLKPAG_AF) == 0);
pmap_page_set_attributes(mdpg, VM_PAGEMD_REFERENCED);
const pt_entry_t npte = opte | LX_BLKPAG_AF;
atomic_swap_64(ptep, npte);
tlb_invalidate_addr(va, pai->pai_asid);
fixed = true;
PMAP_COUNT(fixup_referenced);
UVMHIST_LOG(pmaphist, " <-- done (ref emul: changed pte "
"from %#jx to %#jx)", opte, npte, 0, 0);
}
done:
kpreempt_enable();
return fixed;
}
void
pmap_icache_sync_range(pmap_t pm, vaddr_t sva, vaddr_t eva)
{
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pmaphist, "pm %#jx sva %#jx eva %#jx",
(uintptr_t)pm, sva, eva, 0);
KASSERT((sva & PAGE_MASK) == 0);
KASSERT((eva & PAGE_MASK) == 0);
pmap_lock(pm);
for (vaddr_t va = sva; va < eva; va += PAGE_SIZE) {
pt_entry_t * const ptep = pmap_pte_lookup(pm, va);
if (ptep == NULL)
continue;
pt_entry_t opte = *ptep;
if (!l3pte_valid(opte)) {
UVMHIST_LOG(pmaphist, "invalid pte: %016llx: va=%016lx",
opte, va, 0, 0);
goto done;
}
if (l3pte_readable(opte)) {
cpu_icache_sync_range(va, PAGE_SIZE);
} else {
struct pmap_asid_info * const pai = PMAP_PAI(pm,
cpu_tlb_info(ci));
atomic_swap_64(ptep, opte | LX_BLKPAG_AF);
tlb_invalidate_addr(va, pai->pai_asid);
cpu_icache_sync_range(va, PAGE_SIZE);
atomic_swap_64(ptep, opte);
tlb_invalidate_addr(va, pai->pai_asid);
}
}
done:
pmap_unlock(pm);
}
struct vm_page *
pmap_md_alloc_poolpage(int flags)
{
return uvm_pagealloc(NULL, 0, NULL, flags);
}
vaddr_t
pmap_md_map_poolpage(paddr_t pa, size_t len)
{
struct vm_page * const pg = PHYS_TO_VM_PAGE(pa);
const vaddr_t va = pmap_md_direct_map_paddr(pa);
KASSERT(cold || pg != NULL);
if (pg != NULL) {
struct vm_page_md * const mdpg = VM_PAGE_TO_MD(pg);
const pv_entry_t pv = &mdpg->mdpg_first;
const vaddr_t last_va = trunc_page(pv->pv_va);
KASSERT(len == PAGE_SIZE || last_va == pa);
KASSERT(pv->pv_pmap == NULL);
KASSERT(pv->pv_next == NULL);
KASSERT(!VM_PAGEMD_EXECPAGE_P(mdpg));
pv->pv_va = va;
}
return va;
}
paddr_t
pmap_md_unmap_poolpage(vaddr_t va, size_t len)
{
KASSERT(len == PAGE_SIZE);
KASSERT(pmap_md_direct_mapped_vaddr_p(va));
const paddr_t pa = pmap_md_direct_mapped_vaddr_to_paddr(va);
struct vm_page * const pg = PHYS_TO_VM_PAGE(pa);
KASSERT(pg);
struct vm_page_md * const mdpg = VM_PAGE_TO_MD(pg);
KASSERT(!VM_PAGEMD_EXECPAGE_P(mdpg));
const pv_entry_t pv = &mdpg->mdpg_first;
pv->pv_va = va;
KASSERT(pv->pv_pmap == NULL);
KASSERT(pv->pv_next == NULL);
return pa;
}
bool
pmap_md_kernel_vaddr_p(vaddr_t va)
{
extern char __kernel_text[];
extern char _end[];
extern long kernend_extra;
const vaddr_t kernstart = trunc_page((vaddr_t)__kernel_text);
const vaddr_t kernend = round_page((vaddr_t)_end);
const vaddr_t fva = L2_TRUNC_BLOCK(kernstart);
const vaddr_t lva = L2_ROUND_BLOCK(kernend + kernend_extra);
if (va >= fva && va < lva) {
return true;
}
return false;
}
paddr_t
pmap_md_kernel_vaddr_to_paddr(vaddr_t va)
{
if (pmap_md_kernel_vaddr_p(va)) {
return KERN_VTOPHYS(va);
}
panic("%s: va %#" PRIxVADDR " not direct mapped!", __func__, va);
}
bool
pmap_md_direct_mapped_vaddr_p(vaddr_t va)
{
if (!AARCH64_KVA_P(va))
return false;
paddr_t pa = AARCH64_KVA_TO_PA(va);
if (physical_start <= pa && pa < physical_end)
return true;
return false;
}
paddr_t
pmap_md_direct_mapped_vaddr_to_paddr(vaddr_t va)
{
return AARCH64_KVA_TO_PA(va);
}
vaddr_t
pmap_md_direct_map_paddr(paddr_t pa)
{
return AARCH64_PA_TO_KVA(pa);
}
bool
pmap_md_io_vaddr_p(vaddr_t va)
{
if (pmap_devmap_find_va(va, PAGE_SIZE)) {
return true;
}
return false;
}
static void
pmap_md_grow(pmap_pdetab_t *ptb, vaddr_t va, vsize_t vshift,
vsize_t *remaining)
{
KASSERT((va & (NBSEG - 1)) == 0);
const vaddr_t pdetab_mask = PMAP_PDETABSIZE - 1;
const vsize_t vinc = 1UL << vshift;
for (size_t i = (va >> vshift) & pdetab_mask;
i < PMAP_PDETABSIZE; i++, va += vinc) {
pd_entry_t * const pde_p =
&ptb->pde_pde[(va >> vshift) & pdetab_mask];
vaddr_t pdeva;
if (pte_pde_valid_p(*pde_p)) {
const paddr_t pa = pte_pde_to_paddr(*pde_p);
pdeva = pmap_md_direct_map_paddr(pa);
} else {
pdeva = uvm_pageboot_alloc(Ln_TABLE_SIZE);
paddr_t pdepa = AARCH64_KVA_TO_PA(pdeva);
*pde_p = pte_pde_pdetab(pdepa, true);
memset((void *)pdeva, 0, PAGE_SIZE);
}
if (vshift > SEGSHIFT) {
pmap_md_grow((pmap_pdetab_t *)pdeva, va,
vshift - SEGLENGTH, remaining);
} else {
if (*remaining > vinc)
*remaining -= vinc;
else
*remaining = 0;
}
if (*remaining == 0)
return;
}
}
void
pmap_bootstrap(vaddr_t vstart, vaddr_t vend)
{
pmap_t pm = pmap_kernel();
virtual_avail = vstart;
virtual_end = vend;
aarch64_tlbi_all();
pm->pm_l0_pa = __SHIFTOUT(reg_ttbr1_el1_read(), TTBR_BADDR);
pm->pm_pdetab = (pmap_pdetab_t *)AARCH64_PA_TO_KVA(pm->pm_l0_pa);
VPRINTF("common ");
pmap_bootstrap_common();
VPRINTF("tlb0 ");
pmap_tlb_info_init(&pmap_tlb0_info);
#ifdef MULTIPROCESSOR
VPRINTF("kcpusets ");
kcpuset_create(&pm->pm_onproc, true);
kcpuset_create(&pm->pm_active, true);
KASSERT(pm->pm_onproc != NULL);
KASSERT(pm->pm_active != NULL);
kcpuset_set(pm->pm_onproc, cpu_number());
kcpuset_set(pm->pm_active, cpu_number());
#endif
VPRINTF("nkmempages ");
kmeminit_nkmempages();
buf_setvalimit((VM_MAX_KERNEL_ADDRESS - VM_MIN_KERNEL_ADDRESS) / 8);
vsize_t bufsz = buf_memcalc();
buf_setvalimit(bufsz);
vsize_t kvmsize = (VM_PHYS_SIZE + (ubc_nwins << ubc_winshift) +
bufsz + 16 * NCARGS + pager_map_size) +
nkmempages * NBPG;
#ifdef SYSVSHM
kvmsize += shminfo.shmall;
#endif
kvmsize = roundup(kvmsize, NBSEG);
pmap_limits.avail_start = ptoa(uvm_physseg_get_start(uvm_physseg_get_first()));
pmap_limits.avail_end = ptoa(uvm_physseg_get_end(uvm_physseg_get_last()));
pmap_limits.virtual_start = vstart;
pmap_limits.virtual_end = vend;
VPRINTF("\nlimits: %" PRIxVADDR " - %" PRIxVADDR "\n", vstart, vend);
const vaddr_t kvmstart = vstart;
pmap_curmaxkvaddr = vstart + kvmsize;
VPRINTF("kva : %" PRIxVADDR " - %" PRIxVADDR "\n", kvmstart,
pmap_curmaxkvaddr);
pmap_md_grow(pmap_kernel()->pm_pdetab, kvmstart, XSEGSHIFT, &kvmsize);
#if defined(EFI_RUNTIME)
vaddr_t efi_l0va = uvm_pageboot_alloc(Ln_TABLE_SIZE);
KASSERT((efi_l0va & PAGE_MASK) == 0);
pmap_t efipm = pmap_efirt();
efipm->pm_l0_pa = AARCH64_KVA_TO_PA(efi_l0va);
efipm->pm_pdetab = (pmap_pdetab_t *)efi_l0va;
#endif
pool_init(&pmap_pmap_pool, PMAP_SIZE, 0, 0, 0, "pmappl",
&pool_allocator_nointr, IPL_NONE);
pool_init(&pmap_pv_pool, sizeof(struct pv_entry), 0, 0, 0, "pvpl",
#ifdef KASAN
NULL,
#else
&pmap_pv_page_allocator,
#endif
IPL_NONE);
pmap_pvlist_lock_init(CACHE_LINE_SIZE);
VPRINTF("done\n");
}
#if defined(EFI_RUNTIME)
void
pmap_md_activate_efirt(void)
{
kpreempt_disable();
struct pmap * const pm = pmap_efirt();
struct pmap_asid_info * const pai = PMAP_PAI(pm, cpu_tlb_info(ci));
pmap_md_asid_activate(pai->pai_asid, pmap_efirt(), NULL);
}
void
pmap_md_deactivate_efirt(void)
{
pmap_md_asid_deactivate(pmap_efirt());
KASSERT((reg_tcr_el1_read() & TCR_EPD0) != 0);
kpreempt_enable();
}
#endif
void
pmap_md_pdetab_init(struct pmap *pm)
{
KASSERT(pm != NULL);
pmap_extract(pmap_kernel(), (vaddr_t)pm->pm_pdetab, &pm->pm_l0_pa);
}
void
pmap_md_pdetab_fini(struct pmap *pm)
{
KASSERT(pm != NULL);
}
void
pmap_md_page_syncicache(struct vm_page_md *mdpg, const kcpuset_t *onproc)
{
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
struct vm_page * const pg = VM_MD_TO_PAGE(mdpg);
const paddr_t pa = VM_PAGE_TO_PHYS(pg);
const vaddr_t kva = pmap_md_direct_map_paddr(pa);
cpu_icache_sync_range(kva, PAGE_SIZE);
}
bool
pmap_md_ok_to_steal_p(const uvm_physseg_t bank, size_t npgs)
{
return true;
}
pd_entry_t *
pmap_l0table(struct pmap *pm)
{
return pm->pm_pdetab->pde_pde;
}
#define L1_BLK_MAPPABLE_P(va, pa, size) \
((((va) | (pa)) & L1_OFFSET) == 0 && (size) >= L1_SIZE)
#define L2_BLK_MAPPABLE_P(va, pa, size) \
((((va) | (pa)) & L2_OFFSET) == 0 && (size) >= L2_SIZE)
vsize_t
pmap_kenter_range(vaddr_t va, paddr_t pa, vsize_t size,
vm_prot_t prot, u_int flags)
{
pt_entry_t attr;
psize_t blocksize;
vsize_t resid = round_page(size);
vsize_t mapped = 0;
while (resid > 0) {
if (L1_BLK_MAPPABLE_P(va, pa, resid)) {
blocksize = L1_SIZE;
attr = L1_BLOCK;
} else if (L2_BLK_MAPPABLE_P(va, pa, resid)) {
blocksize = L2_SIZE;
attr = L2_BLOCK;
} else {
blocksize = L3_SIZE;
attr = L3_PAGE;
}
pt_entry_t pte = pte_make_kenter_pa(pa, NULL, prot, flags);
pte &= ~LX_TYPE;
attr |= pte;
pmapboot_enter(va, pa, blocksize, blocksize, attr, NULL);
va += blocksize;
pa += blocksize;
resid -= blocksize;
mapped += blocksize;
}
return mapped;
}
#ifdef MULTIPROCESSOR
void
pmap_md_tlb_info_attach(struct pmap_tlb_info *ti, struct cpu_info *ci)
{
}
#endif