/* $NetBSD: $ */

/*-
  * Copyright (c) 2009 UPMC/LIP6
  * All rights reserved.
  * This software is distributed under the following condiions
  * compliant with the NetBSD foundation policy.
  *
  * 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.
  */

#ifndef _TSARMIPS_PMAP_H_
#define _TSARMIPS_PMAP_H_

#include <uvm/uvm_object.h>
#include <machine/pte.h>
#include <machine/vcache.h>

/* special virtual addresses */
/*
 * page directory slots used to map a pmap's PTE2, and associated base address
 * We need 4 PDE1 to map the PTP2s covering the 4Gb space.
 * But we do use a trick here: To avoid having to lock the pmaps when
 * dealing with KVA mappings (for pmap_kenter_pa and pmap_extract),
 * these PDE1 covering the kernel's space PTP is the same for all pmaps.
 * Therefore the kernel VA space has to start on a 1GB boundary.
 */
#define L1_PTE2_SLOT 2040
/* base VA of mapped PTP: */
#define L1_PTE2SLOT_VADDR ((vaddr_t) L1_PTE2_SLOT << PDE1_SHIFT)
/* number of PDE entries to map all PTP covering the 4Gb space */
#define N_L1_PTE2SLOTS (PD1_NENT / PT2_NENT)
/* index in the (pte2_t*)L1_PTE2SLOT_VADDR array */
#define VADDR_TO_PTE2SLOTI(va) ((va & PTE2_FRAME) >> PTE2_SHIFT)
/* the same, but pointing at the start of the page table page */
#define VADDR_TO_PTE2BASEI(va) ((va & PTE1_FRAME) >> PTE2_SHIFT)
/* start of kernel space (from L1_PTE2_SLOT) */
#define L1_PTE2_KERNSLOT (VADDR_TO_PDE1I(VM_MIN_KERNEL_ADDRESS) / PT2_NENT)

#if defined(_KERNEL) && !defined(_LOCORE)

/*
 * pmap flags. These are passed in the low bits of page-aligned physical
 * addresses
 */
#define PMAP_NC	0x01	/* map non-cacheable */

/*
 * pmap data structures
 */

struct pmap;
typedef struct pmap *pmap_t;

/*
 * we maintain a list of all non-kernel pmaps
 */

LIST_HEAD(pmap_head, pmap); /* struct pmap_head: head of a pmap list */

struct pmap {
	struct uvm_object pm_obj;
#define pm_lock pm_obj.vmobjlock
	kmutex_t pm_obj_lock;
	LIST_ENTRY(pmap) pm_list;
	LIST_ENTRY(pmap) pm_freelist;
	/* page directory of this pmap */
	pde1_t	*pm_pd;
	paddr_t	pm_pdpa;
	struct pglist pm_pdlist;
	/* per-CPU data */
	/*
	 * We use per-CPU PTE2 tables to map a pmap's PTE2 in virtual
	 * space. When we want to map it we just make the 4
	 * L1_PTE2_SLOT slots in the page directory point to these 4
	 * pages.
	 * this means we have to keep in sync data in pm_pdp[] and in
	 * pm_pte2map[].
	 */
	pte2_t *pm_pte2map;
	paddr_t pm_pte2map_pa[N_L1_PTE2SLOTS];
	/* MI pmap statictics */
	struct pmap_statistics pm_stats;

	uint64_t pm_ncsw;		/* for assertions */
};

static inline void
itlbinval(vaddr_t va)
{
	tsar_vcache_write(VC_ITLB_INVAL, va);
}

static inline void
dtlbinval(vaddr_t va)
{
	tsar_vcache_write(VC_DTLB_INVAL, va);
}

static inline bool
pmap_pde_valid(struct pmap *pmap, vaddr_t va)
{
	return pde1_valid_entry(&pmap->pm_pd[VADDR_TO_PDE1I(va)]);
}

void pmap_bootstrap(vaddr_t *, vaddr_t *, paddr_t *, vaddr_t *, paddr_t, int);
void pmap_seccpus_bootstrap_add(vaddr_t);
void pmap_seccpus_bootstrap_remove(vaddr_t);

bool pmap_clear_attrs(struct vm_page *, unsigned);
bool pmap_test_attrs(struct vm_page *, unsigned);

void pmap_remove(struct pmap *, vaddr_t, vaddr_t);
void pmap_page_remove (struct vm_page *);
void pmap_write_protect(struct pmap *, vaddr_t, vaddr_t, vm_prot_t);


/* MI interfaces macro/inline */
#define pmap_phys_address(pa) (pa)

#define pmap_clear_modify(pg)	pmap_clear_attrs(pg, PTE2_D)
#define pmap_clear_reference(pg) pmap_clear_attrs(pg, PTE2_RL)
#define pmap_copy(DP,SP,D,L,S)	/* nothing */
#define pmap_is_modified(pg)	pmap_test_attrs(pg, PTE2_D)
#define pmap_is_referenced(pg)	pmap_test_attrs(pg, PTE2_RL)
#define pmap_resident_count(pmap) ((pmap)->pm_stats.resident_count)
#define pmap_wired_count(pmap)	((pmap)->pm_stats.wired_count)

extern paddr_t mips_avail_end;

/*
 * pmap_page_protect: change the protection of all recorded mappings
 *      of a managed page
 *
 * => this function is a frontend for pmap_page_remove/pmap_clear_attrs
 * => we only have to worry about making the page more protected.
 *      unprotecting a page is done on-demand at fault time.
 */

__inline static void __unused
pmap_page_protect(struct vm_page *pg, vm_prot_t prot)
{
	if ((prot & VM_PROT_WRITE) == 0) {
		if (prot & (VM_PROT_READ|VM_PROT_EXECUTE)) {
			(void) pmap_clear_attrs(pg, PTE2_W);
		} else {
			pmap_page_remove(pg);
		}
	}
}

/*
 * pmap_protect: change the protection of pages in a pmap
 *
 * => this function is a frontend for pmap_remove/pmap_write_protect
 * => we only have to worry about making the page more protected.
 *      unprotecting a page is done on-demand at fault time.
 */

__inline static void __unused
pmap_protect(struct pmap *pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot)
{
	if ((prot & VM_PROT_WRITE) == 0) {
		if (prot & (VM_PROT_READ|VM_PROT_EXECUTE)) {
			pmap_write_protect(pmap, sva, eva, prot);
		} else {
			pmap_remove(pmap, sva, eva);
		}
	}
}

extern vaddr_t pmap_avail_start; /* first available KVA address, set by mach_init() */
extern vaddr_t cp0_ebase;

#define PMAP_GROWKERNEL		/* turn on pmap_growkernel interface */

#endif /* _KERNEL */


#endif /* _TSARMIPS_PMAP_H_ */
