/*	$NetBSD: pmap_machdep.c,v 1.40 2025/07/13 13:21:32 snj Exp $	*/

/*-
 * Copyright (c) 1998, 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center and by Chris G. Demetriou.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Systems Programming Group of the University of Utah Computer
 * Science Department and Ralph Campbell.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)pmap.c	8.4 (Berkeley) 1/26/94
 */

#include <sys/cdefs.h>

__KERNEL_RCSID(0, "$NetBSD: pmap_machdep.c,v 1.40 2025/07/13 13:21:32 snj Exp $");

/*
 *	Manages physical address maps.
 *
 *	In addition to hardware address maps, this
 *	module is called upon to provide software-use-only
 *	maps which may or may not be stored in the same
 *	form as hardware maps.  These pseudo-maps are
 *	used to store intermediate results from copy
 *	operations to and from address spaces.
 *
 *	Since the information managed by this module is
 *	also stored by the logical address mapping module,
 *	this module may throw away valid virtual-to-physical
 *	mappings at almost any time.  However, invalidations
 *	of virtual-to-physical mappings must be done as
 *	requested.
 *
 *	In order to cope with hardware architectures which
 *	make virtual-to-physical map invalidates expensive,
 *	this module may delay invalidate or reduced protection
 *	operations until such time as they are actually
 *	necessary.  This module is given full information as
 *	to which processors are currently using which maps,
 *	and to when physical maps must be made correct.
 */

/* XXX simonb 2002/02/26
 *
 * MIPS3_PLUS is used to conditionally compile the r4k MMU support.
 * This is bogus - for example, some IDT MIPS-II CPUs have r4k style
 * MMUs (and 32-bit ones at that).
 *
 * On the other hand, it's not likely that we'll ever support the R6000
 * (is it?), so maybe that can be an "if MIPS2 or greater" check.
 *
 * Also along these lines are using totally separate functions for
 * r3k-style and r4k-style MMUs and removing all the MIPS_HAS_R4K_MMU
 * checks in the current functions.
 *
 * These warnings probably applies to other files under sys/arch/mips.
 */

#include "opt_cputype.h"
#include "opt_mips_cache.h"
#include "opt_multiprocessor.h"
#include "opt_sysv.h"

#define __MUTEX_PRIVATE
#define __PMAP_PRIVATE

#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/buf.h>
#include <sys/cpu.h>
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <sys/pool.h>
#include <sys/proc.h>
#include <sys/systm.h>
#ifdef SYSVSHM
#include <sys/shm.h>
#endif

#include <uvm/uvm.h>
#include <uvm/uvm_physseg.h>

#include <mips/cache.h>
#include <mips/cpuregs.h>
#include <mips/locore.h>
#include <mips/pte.h>

CTASSERT(MIPS_KSEG0_START < 0);
CTASSERT((intptr_t)MIPS_PHYS_TO_KSEG0(0x1000) < 0);
CTASSERT(MIPS_KSEG1_START < 0);
CTASSERT((intptr_t)MIPS_PHYS_TO_KSEG1(0x1000) < 0);
CTASSERT(MIPS_KSEG2_START < 0);
CTASSERT(MIPS_MAX_MEM_ADDR < 0);
CTASSERT(MIPS_RESERVED_ADDR < 0);
CTASSERT((uint32_t)MIPS_KSEG0_START == 0x80000000);
CTASSERT((uint32_t)MIPS_KSEG1_START == 0xa0000000);
CTASSERT((uint32_t)MIPS_KSEG2_START == 0xc0000000);
CTASSERT((uint32_t)MIPS_MAX_MEM_ADDR == 0xbe000000);
CTASSERT((uint32_t)MIPS_RESERVED_ADDR == 0xbfc80000);
CTASSERT(MIPS_KSEG0_P(MIPS_PHYS_TO_KSEG0(0)));
CTASSERT(MIPS_KSEG1_P(MIPS_PHYS_TO_KSEG1(0)));
#ifdef _LP64
CTASSERT(VM_MIN_KERNEL_ADDRESS % NBXSEG == 0);
#else
CTASSERT(VM_MIN_KERNEL_ADDRESS % NBSEG == 0);
#endif

//PMAP_COUNTER(idlezeroed_pages, "pages idle zeroed");
PMAP_COUNTER(zeroed_pages, "pages zeroed");
PMAP_COUNTER(copied_pages, "pages copied");
extern struct evcnt pmap_evcnt_page_cache_evictions;

u_int pmap_page_cache_alias_mask;

#define pmap_md_cache_indexof(x)	(((vaddr_t)(x)) & pmap_page_cache_alias_mask)

static register_t
pmap_md_map_ephemeral_page(struct vm_page_md *mdpg, bool locked_p, int prot,
    pt_entry_t *old_pte_p)
{
	KASSERT(VM_PAGEMD_VMPAGE_P(mdpg));

	struct vm_page *pg = VM_MD_TO_PAGE(mdpg);
	const paddr_t pa = VM_PAGE_TO_PHYS(pg);
	pv_entry_t pv = &mdpg->mdpg_first;
	register_t va = 0;

	UVMHIST_FUNC(__func__);
	UVMHIST_CALLARGS(pmaphist, "(pg=%#jx, prot=%d, ptep=%#jx)",
	    (uintptr_t)pg, prot, (uintptr_t)old_pte_p, 0);

	KASSERT(!locked_p || VM_PAGEMD_PVLIST_LOCKED_P(mdpg));

	if (!MIPS_CACHE_VIRTUAL_ALIAS || !mips_cache_badalias(pv->pv_va, pa)) {
#ifdef _LP64
		va = MIPS_PHYS_TO_XKPHYS_CACHED(pa);
#else
		if (pa < MIPS_PHYS_MASK) {
			va = MIPS_PHYS_TO_KSEG0(pa);
		}
#endif
	}
	if (va == 0) {
		/*
		 * Make sure to use a congruent mapping to the last mapped
		 * address so we don't have to worry about virtual aliases.
		 */
		kpreempt_disable(); // paired with the one in unmap
		struct cpu_info * const ci = curcpu();
		if (MIPS_CACHE_VIRTUAL_ALIAS) {
			KASSERT(ci->ci_pmap_dstbase != 0);
			KASSERT(ci->ci_pmap_srcbase != 0);

			const u_int __diagused mask = pmap_page_cache_alias_mask;
			KASSERTMSG((ci->ci_pmap_dstbase & mask) == 0,
			    "%#"PRIxVADDR, ci->ci_pmap_dstbase);
			KASSERTMSG((ci->ci_pmap_srcbase & mask) == 0,
			    "%#"PRIxVADDR, ci->ci_pmap_srcbase);
		}
		vaddr_t nva = (prot & VM_PROT_WRITE
			? ci->ci_pmap_dstbase
			: ci->ci_pmap_srcbase)
		    + pmap_md_cache_indexof(MIPS_CACHE_VIRTUAL_ALIAS
			? pv->pv_va
			: pa);

		va = (intptr_t)nva;
		/*
		 * Now to make and write the new PTE to map the PA.
		 */
		const pt_entry_t npte = pte_make_kenter_pa(pa, mdpg, prot, 0);
		pt_entry_t * const ptep = pmap_pte_lookup(pmap_kernel(), va);
		*old_pte_p = *ptep;		// save
		bool rv __diagused;
		*ptep = npte;			// update page table

		// update the TLB directly making sure we force the new entry
		// into it.
		rv = tlb_update_addr(va, KERNEL_PID, npte, true);
		KASSERTMSG(rv == 1, "va %#"PRIxREGISTER" pte=%#"PRIxPTE" rv=%d",
		    va, pte_value(npte), rv);
	}
	if (MIPS_CACHE_VIRTUAL_ALIAS) {
		/*
		 * If we are forced to use an incompatible alias, flush the
		 * page from the cache so we will copy the correct contents.
		 */
		if (!locked_p)
			(void)VM_PAGEMD_PVLIST_READLOCK(mdpg);
		if (VM_PAGEMD_CACHED_P(mdpg)
		    && mips_cache_badalias(pv->pv_va, va)) {
			register_t ova = (intptr_t)trunc_page(pv->pv_va);
			mips_dcache_wbinv_range_index(ova, PAGE_SIZE);
			/*
			 * If there is no active mapping, remember this new one.
			 */
			if (pv->pv_pmap == NULL)
				pv->pv_va = va;
		}
		if (!locked_p)
			VM_PAGEMD_PVLIST_UNLOCK(mdpg);
	}

	UVMHIST_LOG(pmaphist, " <-- done (va=%#lx)", va, 0, 0, 0);

	return va;
}

static void
pmap_md_unmap_ephemeral_page(struct vm_page_md *mdpg, bool locked_p,
    register_t va, pt_entry_t old_pte)
{
	KASSERT(VM_PAGEMD_VMPAGE_P(mdpg));

	pv_entry_t pv = &mdpg->mdpg_first;

	UVMHIST_FUNC(__func__);
	UVMHIST_CALLARGS(pmaphist, "(pg=%#jx, va=%#lx, pte=%#"PRIxPTE")",
	    (uintptr_t)VM_MD_TO_PAGE(mdpg), va, pte_value(old_pte), 0);

	KASSERT(!locked_p || VM_PAGEMD_PVLIST_LOCKED_P(mdpg));

	if (MIPS_CACHE_VIRTUAL_ALIAS) {
		if (!locked_p)
			(void)VM_PAGEMD_PVLIST_READLOCK(mdpg);
		/*
		 * If this page was previously uncached or we had to use an
		 * incompatible alias, flush it from the cache.
		 */
		if (VM_PAGEMD_UNCACHED_P(mdpg)
		    || (pv->pv_pmap != NULL
			&& mips_cache_badalias(pv->pv_va, va))) {
			mips_dcache_wbinv_range(va, PAGE_SIZE);
		}
		if (!locked_p)
			VM_PAGEMD_PVLIST_UNLOCK(mdpg);
	}
	/*
	 * If we had to map using a page table entry, restore it now.
	 */
	if (!pmap_md_direct_mapped_vaddr_p(va)) {
		*pmap_pte_lookup(pmap_kernel(), va) = old_pte;
		if (pte_valid_p(old_pte)) {
			// Update the TLB with the old mapping.
			tlb_update_addr(va, KERNEL_PID, old_pte, 0);
		} else {
			// Invalidate TLB entry if the old pte wasn't valid.
			tlb_invalidate_addr(va, KERNEL_PID);
		}
		kpreempt_enable();	// Restore preemption
	}
	UVMHIST_LOG(pmaphist, " <-- done", 0, 0, 0, 0);
}

static void
pmap_md_vca_page_wbinv(struct vm_page_md *mdpg, bool locked_p)
{
	UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
	pt_entry_t pte;

	const register_t va = pmap_md_map_ephemeral_page(mdpg, locked_p,
	    VM_PROT_READ, &pte);

	mips_dcache_wbinv_range(va, PAGE_SIZE);

	pmap_md_unmap_ephemeral_page(mdpg, locked_p, va, pte);
}

bool
pmap_md_ok_to_steal_p(const uvm_physseg_t bank, size_t npgs)
{
#ifndef _LP64
	if (uvm_physseg_get_avail_start(bank) + npgs >= atop(MIPS_PHYS_MASK + 1)) {
		aprint_debug("%s: seg not enough in KSEG0 for %zu pages\n",
		    __func__, npgs);
		return false;
	}
#endif
	return true;
}

/*
 *	Bootstrap the system enough to run with virtual memory.
 */
void
pmap_bootstrap(void)
{
	vsize_t bufsz;
	size_t sysmap_size;
	pt_entry_t *sysmap;

	if (MIPS_CACHE_VIRTUAL_ALIAS && uvmexp.ncolors) {
		pmap_page_colormask = (uvmexp.ncolors - 1) << PAGE_SHIFT;
		pmap_page_cache_alias_mask = uimax(
		    mips_cache_info.mci_cache_alias_mask,
		    mips_cache_info.mci_icache_alias_mask);
	}

#ifdef MULTIPROCESSOR
	pmap_t pm = pmap_kernel();
	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

	pmap_bootstrap_common();

	pmap_tlb_info_init(&pmap_tlb0_info);		/* init the lock */

	/*
	 * Compute the number of pages kmem_arena will have.
	 */
	kmeminit_nkmempages();

	/*
	 * Figure out how many PTE's are necessary to map the kernel.
	 * We also reserve space for kmem_alloc_pageable() for vm_fork().
	 */

	/* Get size of buffer cache and set an upper limit */
	buf_setvalimit((VM_MAX_KERNEL_ADDRESS - VM_MIN_KERNEL_ADDRESS) / 8);
	bufsz = buf_memcalc();
	buf_setvalimit(bufsz);

	sysmap_size = (VM_PHYS_SIZE + (ubc_nwins << ubc_winshift) +
	    bufsz + 16 * NCARGS + pager_map_size) / NBPG +
	    (maxproc * UPAGES) + nkmempages;

#ifdef SYSVSHM
	sysmap_size += shminfo.shmall;
#endif
#ifdef KSEG2IOBUFSIZE
	sysmap_size += (KSEG2IOBUFSIZE >> PGSHIFT);
#endif
#ifdef _LP64
	/*
	 * If we are using tmpfs, then we might want to use a great deal of
	 * our memory with it.  Make sure we have enough VM to do that.
	 */
	sysmap_size += physmem;
#else
	/* XXX: else runs out of space on 256MB sbmips!! */
	sysmap_size += 20000;
#endif
	/* Roundup to a even number of pte page tables */
	sysmap_size = (sysmap_size + NPTEPG - 1) & -NPTEPG;

	/*
	 * Initialize `FYI' variables.	Note we're relying on
	 * the fact that BSEARCH sorts the vm_physmem[] array
	 * for us.  Must do this before uvm_pageboot_alloc()
	 * can be called.
	 */
	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_end = pmap_limits.virtual_start + (vaddr_t)sysmap_size * NBPG;

#ifndef _LP64
	if (pmap_limits.virtual_end > VM_MAX_KERNEL_ADDRESS
	    || pmap_limits.virtual_end < VM_MIN_KERNEL_ADDRESS) {
		printf("%s: changing last kernel VA from %#"PRIxVADDR
		    " to %#"PRIxVADDR"\n", __func__,
		    pmap_limits.virtual_end, VM_MAX_KERNEL_ADDRESS);
		pmap_limits.virtual_end = VM_MAX_KERNEL_ADDRESS;
		sysmap_size =
		    (VM_MAX_KERNEL_ADDRESS - VM_MIN_KERNEL_ADDRESS) / NBPG;
	}
#endif
	pmap_pvlist_lock_init(mips_cache_info.mci_pdcache_line_size);

	/*
	 * Now actually allocate the kernel PTE array (must be done
	 * after pmap_limits.virtual_end is initialized).
	 */
	sysmap = (pt_entry_t *)
	    uvm_pageboot_alloc(sizeof(pt_entry_t) * sysmap_size);

	vaddr_t va = VM_MIN_KERNEL_ADDRESS;
#ifdef _LP64
	/*
	 * Do we need more than one XSEG's worth virtual address space?
	 * If so, we have to allocate the additional pmap_segtab_t's for them
	 * and insert them into the kernel's top level segtab.
	 */
	const size_t xsegs = (sysmap_size * NBPG + NBXSEG - 1) / NBXSEG;
	if (xsegs > 1) {
		printf("%s: %zu xsegs required for %zu pages\n",
		    __func__, xsegs, sysmap_size);
		pmap_segtab_t *stb = (pmap_segtab_t *)
		    uvm_pageboot_alloc(sizeof(pmap_segtab_t) * (xsegs - 1));
		for (size_t i = 1; i <= xsegs; i++, stb++) {
			pmap_kern_segtab.seg_seg[i] = stb;
		}
	}
	pmap_segtab_t ** const xstb = pmap_kern_segtab.seg_seg;
#else
	const size_t xsegs = 1;
	pmap_segtab_t * const stb = &pmap_kern_segtab;
#endif
	KASSERT(curcpu()->ci_pmap_kern_segtab == &pmap_kern_segtab);

	for (size_t k = 0, i = 0; k < xsegs; k++) {
#ifdef _LP64
		pmap_segtab_t * const stb =
		    xstb[(va >> XSEGSHIFT) & (NSEGPG - 1)];
#endif
		bool done = false;

		for (size_t j = (va >> SEGSHIFT) & (NSEGPG - 1);
		     !done && i < sysmap_size;
		     i += NPTEPG, j++, va += NBSEG) {
			/*
			 * Now set the page table pointer...
			 */
			stb->seg_ppg[j] = (pmap_ptpage_t *)&sysmap[i];
#ifdef _LP64
			/*
			 * If we are at end of this XSEG, terminate the loop
			 * so we advance to the next one.
			 */
			done = (j + 1 == NSEGPG);
#endif
		}
	}
	KASSERT(pmap_pte_lookup(pmap_kernel(), VM_MIN_KERNEL_ADDRESS) == sysmap);

	/* update the top of the kernel VM - pmap_growkernel not required */
	pmap_curmaxkvaddr = pmap_limits.virtual_end;
	/*
	 * Initialize the pools.
	 */
	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",
	    &pmap_pv_page_allocator, IPL_NONE);

	tlb_set_asid(KERNEL_PID, pmap_kernel());

#ifdef MIPS3_PLUS	/* XXX mmu XXX */
	/*
	 * The R4?00 stores only one copy of the Global bit in the
	 * translation lookaside buffer for each 2 page entry.
	 * Thus invalid entries must have the Global bit set so
	 * when Entry LO and Entry HI G bits are anded together
	 * they will produce a global bit to store in the tlb.
	 */
	if (MIPS_HAS_R4K_MMU) {
		while (sysmap_size-- > 0) {
			*sysmap++ = MIPS3_PG_G;
		}
	}
#endif	/* MIPS3_PLUS */
}

void
pmap_md_alloc_ephemeral_address_space(struct cpu_info *ci)
{
	struct mips_cache_info * const mci = &mips_cache_info;

	/*
	 * If we have more memory than can be mapped by KSEG0, we need to
	 * allocate enough VA so we can map pages with the right color
	 * (to avoid cache alias problems).
	 */
	if (false
#ifndef _LP64
	    || pmap_limits.avail_end > MIPS_KSEG1_START - MIPS_KSEG0_START
#endif
	    || MIPS_CACHE_VIRTUAL_ALIAS
	    || MIPS_ICACHE_VIRTUAL_ALIAS) {
		vsize_t size = uimax(mci->mci_pdcache_way_size, mci->mci_picache_way_size);
		const u_int __diagused mask = pmap_page_cache_alias_mask;

		ci->ci_pmap_dstbase = uvm_km_alloc(kernel_map, size, size,
		    UVM_KMF_VAONLY);

		KASSERT(ci->ci_pmap_dstbase);
		KASSERT(!pmap_md_direct_mapped_vaddr_p(ci->ci_pmap_dstbase));
		KASSERTMSG((ci->ci_pmap_dstbase & mask) == 0, "%#"PRIxVADDR,
		    ci->ci_pmap_dstbase);

		ci->ci_pmap_srcbase = uvm_km_alloc(kernel_map, size, size,
		    UVM_KMF_VAONLY);
		KASSERT(ci->ci_pmap_srcbase);
		KASSERT(!pmap_md_direct_mapped_vaddr_p(ci->ci_pmap_srcbase));
		KASSERTMSG((ci->ci_pmap_srcbase & mask) == 0, "%#"PRIxVADDR,
		    ci->ci_pmap_srcbase);
	}
}

void
pmap_md_init(void)
{
	pmap_md_alloc_ephemeral_address_space(curcpu());

#if defined(MIPS3) && 0
	if (MIPS_HAS_R4K_MMU) {
		/*
		 * XXX
		 * Disable sosend_loan() in src/sys/kern/uipc_socket.c
		 * on MIPS3 CPUs to avoid possible virtual cache aliases
		 * and uncached mappings in pmap_enter_pv().
		 *
		 * Ideally, read only shared mapping won't cause aliases
		 * so pmap_enter_pv() should handle any shared read only
		 * mappings without uncached ops like ARM pmap.
		 *
		 * On the other hand, R4000 and R4400 have the virtual
		 * coherency exceptions which will happen even on read only
		 * mappings, so we always have to disable sosend_loan()
		 * on such CPUs.
		 */
		sock_loan_thresh = -1;
	}
#endif
}

/*
 * XXXJRT -- need a version for each cache type.
 */
void
pmap_procwr(struct proc *p, vaddr_t va, size_t len)
{
	if (MIPS_HAS_R4K_MMU) {
		/*
		 * XXX
		 * shouldn't need to do this for physical d$?
		 * should need to do this for virtual i$ if prot == EXEC?
		 */
		if (p == curlwp->l_proc
		    && mips_cache_info.mci_pdcache_way_mask < PAGE_SIZE)
		    /* XXX check icache mask too? */
			mips_icache_sync_range((intptr_t)va, len);
		else
			mips_icache_sync_range_index((intptr_t)va, len);
	} else {
		pmap_t pmap = p->p_vmspace->vm_map.pmap;
		kpreempt_disable();
		pt_entry_t * const ptep = pmap_pte_lookup(pmap, va);
		pt_entry_t entry = (ptep != NULL ? *ptep : 0);
		kpreempt_enable();
		if (!pte_valid_p(entry))
			return;

		mips_icache_sync_range(
		    MIPS_PHYS_TO_KSEG0(pte_to_paddr(entry) + (va & PGOFSET)),
		    len);
	}
}

/*
 *	pmap_zero_page zeros the specified page.
 */
void
pmap_zero_page(paddr_t dst_pa)
{
	pt_entry_t dst_pte;

	UVMHIST_FUNC(__func__);
	UVMHIST_CALLARGS(pmaphist, "(pa=%#"PRIxPADDR")", dst_pa, 0, 0, 0);
	PMAP_COUNT(zeroed_pages);

	struct vm_page * const dst_pg = PHYS_TO_VM_PAGE(dst_pa);
	struct vm_page_md * const dst_mdpg = VM_PAGE_TO_MD(dst_pg);

	KASSERT(!VM_PAGEMD_EXECPAGE_P(dst_mdpg));

	const register_t dst_va = pmap_md_map_ephemeral_page(dst_mdpg, false,
	    VM_PROT_READ|VM_PROT_WRITE, &dst_pte);

	mips_pagezero(dst_va);

	pmap_md_unmap_ephemeral_page(dst_mdpg, false, dst_va, dst_pte);

	UVMHIST_LOG(pmaphist, " <-- done", 0, 0, 0, 0);
}

/*
 *	pmap_copy_page copies the specified page.
 */
void
pmap_copy_page(paddr_t src_pa, paddr_t dst_pa)
{
	pt_entry_t src_pte, dst_pte;

	UVMHIST_FUNC(__func__);
	UVMHIST_CALLARGS(pmaphist, "(src_pa=%#lx, dst_pa=%#lx)", src_pa, dst_pa,
	    0, 0);
	PMAP_COUNT(copied_pages);

	struct vm_page * const src_pg = PHYS_TO_VM_PAGE(src_pa);
	struct vm_page * const dst_pg = PHYS_TO_VM_PAGE(dst_pa);

	struct vm_page_md * const src_mdpg = VM_PAGE_TO_MD(src_pg);
	struct vm_page_md * const dst_mdpg = VM_PAGE_TO_MD(dst_pg);

	const register_t src_va = pmap_md_map_ephemeral_page(src_mdpg, false,
	    VM_PROT_READ, &src_pte);

	KASSERT(VM_PAGEMD_PVLIST_EMPTY_P(dst_mdpg));
	KASSERT(!VM_PAGEMD_EXECPAGE_P(dst_mdpg));
	const register_t dst_va = pmap_md_map_ephemeral_page(dst_mdpg, false,
	    VM_PROT_READ|VM_PROT_WRITE, &dst_pte);

	mips_pagecopy(dst_va, src_va);

	pmap_md_unmap_ephemeral_page(dst_mdpg, false, dst_va, dst_pte);
	pmap_md_unmap_ephemeral_page(src_mdpg, false, src_va, src_pte);

	UVMHIST_LOG(pmaphist, " <-- done", 0, 0, 0, 0);
}

void
pmap_md_page_syncicache(struct vm_page_md *mdpg, const kcpuset_t *onproc)
{
	UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
	struct mips_options * const opts = &mips_options;
	if (opts->mips_cpu_flags & CPU_MIPS_I_D_CACHE_COHERENT)
		return;

	/*
	 * If onproc is empty, we could do a
	 * pmap_page_protect(pg, VM_PROT_NONE) and remove all
	 * mappings of the page and clear its execness.  Then
	 * the next time page is faulted, it will get icache
	 * synched.  But this is easier. :)
	 */
	if (MIPS_HAS_R4K_MMU) {
		if (VM_PAGEMD_CACHED_P(mdpg)) {
			/* This was probably mapped cached by UBC so flush it */
			pt_entry_t pte;
			const register_t tva = pmap_md_map_ephemeral_page(mdpg,
			    false, VM_PROT_READ, &pte);

			UVMHIST_LOG(pmaphist, "  va %#"PRIxVADDR, tva, 0, 0, 0);
			mips_dcache_wbinv_range(tva, PAGE_SIZE);
			mips_icache_sync_range(tva, PAGE_SIZE);

			pmap_md_unmap_ephemeral_page(mdpg, false, tva, pte);
		}
	} else {
		KASSERT(VM_PAGEMD_VMPAGE_P(mdpg));

		struct vm_page *pg = VM_MD_TO_PAGE(mdpg);
		mips_icache_sync_range(MIPS_PHYS_TO_KSEG0(VM_PAGE_TO_PHYS(pg)),
		    PAGE_SIZE);
	}
#ifdef MULTIPROCESSOR
	pv_entry_t pv = &mdpg->mdpg_first;
	const register_t va = (intptr_t)trunc_page(pv->pv_va);
	pmap_tlb_syncicache(va, onproc);
#endif
}

struct vm_page *
pmap_md_alloc_poolpage(int flags)
{
	/*
	 * The VM_FREELIST used for pool pages is only set on 32bit
	 * kernels.  This is to make sure that we only allocate pages
	 * that can be mapped via KSEG0.  On 64bit kernels, all memory
	 * can be mapped via XKPHYS so just use the default freelist.
	 */
	if (mips_poolpage_vmfreelist != VM_FREELIST_DEFAULT)
		return uvm_pagealloc_strat(NULL, 0, NULL, flags,
		    UVM_PGA_STRAT_ONLY, mips_poolpage_vmfreelist);

	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);
	vaddr_t va = pmap_md_pool_phystov(pa);
	KASSERT(cold || pg != NULL);
	if (pg != NULL) {
		struct vm_page_md * const mdpg = VM_PAGE_TO_MD(pg);
		pv_entry_t pv = &mdpg->mdpg_first;
		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));

		/*
		 * If this page was last mapped with an address that
		 * might cause aliases, flush the page from the cache.
		 */
		if (MIPS_CACHE_VIRTUAL_ALIAS
		    && mips_cache_badalias(last_va, va)) {
			pmap_md_vca_page_wbinv(mdpg, false);
		}

		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_CACHED_P(mdpg));
	KASSERT(!VM_PAGEMD_EXECPAGE_P(mdpg));

	pv_entry_t pv = &mdpg->mdpg_first;

	/* Note last mapped address for future color check */
	pv->pv_va = va;

	KASSERT(pv->pv_pmap == NULL);
	KASSERT(pv->pv_next == NULL);

	return pa;
}

bool
pmap_md_direct_mapped_vaddr_p(register_t va)
{
#ifndef __mips_o32
	if (MIPS_XKPHYS_P(va))
		return true;
#endif
	return MIPS_KSEG0_P(va);
}

paddr_t
pmap_md_direct_mapped_vaddr_to_paddr(register_t va)
{
	if (MIPS_KSEG0_P(va)) {
		return MIPS_KSEG0_TO_PHYS(va);
	}
#ifndef __mips_o32
	if (MIPS_XKPHYS_P(va)) {
		return MIPS_XKPHYS_TO_PHYS(va);
	}
#endif
	panic("%s: va %#"PRIxREGISTER" not direct mapped!", __func__, va);
}

bool
pmap_md_io_vaddr_p(vaddr_t va)
{
#ifdef _LP64
	if (MIPS_XKPHYS_P(va)) {
		return MIPS_XKPHYS_TO_CCA(va) == CCA_UNCACHED;
	}
#endif
	return MIPS_KSEG1_P(va);
}

void
pmap_md_icache_sync_range_index(vaddr_t va, vsize_t len)
{
	UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
	mips_icache_sync_range_index(va, len);
}

void
pmap_md_icache_sync_all(void)
{
	UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
	mips_icache_sync_all();
}

#ifdef MULTIPROCESSOR
void
pmap_md_tlb_info_attach(struct pmap_tlb_info *ti, struct cpu_info *ci)
{
	if (ci->ci_index != 0)
		return;
	const u_int icache_way_pages =
	    mips_cache_info.mci_picache_way_size >> PGSHIFT;

	KASSERT(icache_way_pages <= 8*sizeof(pmap_tlb_synci_page_mask));
	pmap_tlb_synci_page_mask = icache_way_pages - 1;
	pmap_tlb_synci_map_mask = ~(~0 << icache_way_pages);
	printf("tlb0: synci page mask %#x and map mask %#x used for %u pages\n",
	    pmap_tlb_synci_page_mask, pmap_tlb_synci_map_mask, icache_way_pages);
}
#endif


bool
pmap_md_tlb_check_entry(void *ctx, vaddr_t va, tlb_asid_t asid, pt_entry_t pte)
{
	pmap_t pm = ctx;
	struct pmap_tlb_info * const ti = cpu_tlb_info(curcpu());
	struct pmap_asid_info * const pai = PMAP_PAI(pm, ti);

	if (asid != pai->pai_asid)
		return true;
	if (!pte_valid_p(pte)) {
		KASSERT(MIPS_HAS_R4K_MMU);
		KASSERTMSG(pte == MIPS3_PG_G, "va %#"PRIxVADDR" pte %#"PRIxPTE,
		    va, pte_value(pte));
		return true;
	}

	const pt_entry_t * const ptep = pmap_pte_lookup(pm, va);
	KASSERTMSG(ptep != NULL, "va %#"PRIxVADDR" asid %u pte %#"PRIxPTE,
	    va, asid, pte_value(pte));
	const pt_entry_t opte = *ptep;
	pt_entry_t xpte = opte;
	if (MIPS_HAS_R4K_MMU) {
		xpte &= ~(MIPS3_PG_WIRED|MIPS3_PG_RO);
	} else {
		xpte &= ~(MIPS1_PG_WIRED|MIPS1_PG_RO);
	}
	/*
	 * Don't check for differences in the TLB entry modified status for
	 * kernel mappings as they can be modified while the kernel pmap is
	 * being updated, see PR59518.
	 */
	if (pm == pmap_kernel()) {
		if (MIPS_HAS_R4K_MMU) {
			xpte ^= (pte ^ xpte) & MIPS3_PG_D;
		} else {
			xpte ^= (pte ^ xpte) & MIPS1_PG_D;
		}
	}

        KASSERTMSG(pte == xpte,
            "pmap=%p va=%#"PRIxVADDR" asid=%u: TLB pte (%#"PRIxPTE
	    ") != real pte (%#"PRIxPTE"/%#"PRIxPTE") @ %p",
            pm, va, asid, pte_value(pte), pte_value(xpte), pte_value(opte),
	    ptep);

        return true;
}

void
tlb_walk(void *ctx, tlb_walkfunc_t func)
{
	kpreempt_disable();
	for (size_t i = 0; i < mips_options.mips_num_tlb_entries; i++) {
		struct tlbmask tlbmask;
		tlb_asid_t asid;
		vaddr_t va;
		tlb_read_entry(i, &tlbmask);
		if (MIPS_HAS_R4K_MMU) {
			asid = __SHIFTOUT(tlbmask.tlb_hi, MIPS3_PG_ASID);
			va = tlbmask.tlb_hi & MIPS3_PG_HVPN;
		} else {
			asid = __SHIFTOUT(tlbmask.tlb_hi, MIPS1_TLB_PID);
			va = tlbmask.tlb_hi & MIPS1_PG_FRAME;
		}
		if ((pt_entry_t)tlbmask.tlb_lo0 != 0) {
			pt_entry_t pte = tlbmask.tlb_lo0;
			tlb_asid_t asid0 = (pte_global_p(pte) ? KERNEL_PID : asid);
			if (!(*func)(ctx, va, asid0, pte))
				break;
		}
#if (PGSHIFT & 1) == 0
		if (MIPS_HAS_R4K_MMU && (pt_entry_t)tlbmask.tlb_lo1 != 0) {
			pt_entry_t pte = tlbmask.tlb_lo1;
			tlb_asid_t asid1 = (pte_global_p(pte) ? KERNEL_PID : asid);
			if (!(*func)(ctx, va + MIPS3_PG_ODDPG, asid1, pte))
				break;
		}
#endif
	}
	kpreempt_enable();
}

bool
pmap_md_vca_add(struct vm_page_md *mdpg, vaddr_t va, pt_entry_t *ptep)
{
	UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
	if (!MIPS_HAS_R4K_MMU || !MIPS_CACHE_VIRTUAL_ALIAS)
		return false;

	/*
	 * There is at least one other VA mapping this page.
	 * Check if they are cache index compatible.
	 */

	KASSERT(VM_PAGEMD_PVLIST_LOCKED_P(mdpg));
	pv_entry_t pv = &mdpg->mdpg_first;
#if defined(PMAP_NO_PV_UNCACHED)
	/*
	 * Instead of mapping uncached, which some platforms
	 * cannot support, remove incompatible mappings from others pmaps.
	 * When this address is touched again, the uvm will
	 * fault it in.  Because of this, each page will only
	 * be mapped with one index at any given time.
	 *
	 * We need to deal with all entries on the list - if the first is
	 * incompatible with the new mapping then they all will be.
	 */
	if (__predict_true(!mips_cache_badalias(pv->pv_va, va))) {
		return false;
	}
	KASSERT(pv->pv_pmap != NULL);
	bool ret = false;
	for (pv_entry_t npv = pv; npv && npv->pv_pmap;) {
		if (PV_ISKENTER_P(npv)) {
			npv = npv->pv_next;
			continue;
		}
		ret = true;
		vaddr_t nva = trunc_page(npv->pv_va);
		pmap_t npm = npv->pv_pmap;
		VM_PAGEMD_PVLIST_UNLOCK(mdpg);
		pmap_remove(npm, nva, nva + PAGE_SIZE);

		/*
		 * pmap_update is not required here as we're the pmap
		 * and we know that the invalidation happened or the
		 * asid has been released (and activation is deferred)
		 *
		 * A deferred activation should NOT occur here.
		 */
		(void)VM_PAGEMD_PVLIST_LOCK(mdpg);

		npv = pv;
	}
	KASSERT(ret == true);

	return ret;
#else	/* !PMAP_NO_PV_UNCACHED */
	if (VM_PAGEMD_CACHED_P(mdpg)) {
		/*
		 * If this page is cached, then all mappings
		 * have the same cache alias so we only need
		 * to check the first page to see if it's
		 * incompatible with the new mapping.
		 *
		 * If the mappings are incompatible, map this
		 * page as uncached and re-map all the current
		 * mapping as uncached until all pages can
		 * share the same cache index again.
		 */
		if (mips_cache_badalias(pv->pv_va, va)) {
			pmap_page_cache(mdpg, false);
			pmap_md_vca_page_wbinv(mdpg, true);
			*ptep = pte_cached_change(*ptep, false);
			PMAP_COUNT(page_cache_evictions);
		}
	} else {
		*ptep = pte_cached_change(*ptep, false);
		PMAP_COUNT(page_cache_evictions);
	}
	return false;
#endif	/* !PMAP_NO_PV_UNCACHED */
}

void
pmap_md_vca_clean(struct vm_page_md *mdpg, int op)
{
	UVMHIST_FUNC(__func__); UVMHIST_CALLED(pmaphist);
	if (!MIPS_HAS_R4K_MMU || !MIPS_CACHE_VIRTUAL_ALIAS)
		return;

	UVMHIST_LOG(pmaphist, "(mdpg=%#jx, op=%d)", (uintptr_t)mdpg, op, 0, 0);
	KASSERT(VM_PAGEMD_PVLIST_LOCKED_P(mdpg));

	if (op == PMAP_WB || op == PMAP_WBINV) {
		pmap_md_vca_page_wbinv(mdpg, true);
	} else if (op == PMAP_INV) {
		KASSERT(op == PMAP_INV && false);
		//mips_dcache_inv_range_index(va, PAGE_SIZE);
	}
}

/*
 * In the PMAP_NO_PV_CACHED case, all conflicts are resolved at mapping
 * so nothing needs to be done in removal.
 */
void
pmap_md_vca_remove(struct vm_page *pg, vaddr_t va, bool dirty, bool last)
{
#if !defined(PMAP_NO_PV_UNCACHED)
	struct vm_page_md * const mdpg = VM_PAGE_TO_MD(pg);
	if (!MIPS_HAS_R4K_MMU
	    || !MIPS_CACHE_VIRTUAL_ALIAS
	    || !VM_PAGEMD_UNCACHED_P(mdpg))
		return;

	KASSERT(kpreempt_disabled());
	KASSERT((va & PAGE_MASK) == 0);

	/*
	 * Page is currently uncached, check if alias mapping has been
	 * removed.  If it was, then reenable caching.
	 */
	(void)VM_PAGEMD_PVLIST_READLOCK(mdpg);
	pv_entry_t pv = &mdpg->mdpg_first;
	pv_entry_t pv0 = pv->pv_next;

	for (; pv0; pv0 = pv0->pv_next) {
		if (mips_cache_badalias(pv->pv_va, pv0->pv_va))
			break;
	}
	if (pv0 == NULL)
		pmap_page_cache(mdpg, true);
	VM_PAGEMD_PVLIST_UNLOCK(mdpg);
#endif
}

paddr_t
pmap_md_pool_vtophys(vaddr_t va)
{
#ifdef _LP64
	if (MIPS_XKPHYS_P(va))
		return MIPS_XKPHYS_TO_PHYS(va);
#endif
	KASSERT(MIPS_KSEG0_P(va));
	return MIPS_KSEG0_TO_PHYS(va);
}

vaddr_t
pmap_md_pool_phystov(paddr_t pa)
{
#ifdef _LP64
	KASSERT(mips_options.mips3_xkphys_cached);
	return MIPS_PHYS_TO_XKPHYS_CACHED(pa);
#else
	KASSERT((pa & ~MIPS_PHYS_MASK) == 0);
	return MIPS_PHYS_TO_KSEG0(pa);
#endif
}
