1961 lines
55 KiB
C
1961 lines
55 KiB
C
/* $NetBSD: pmap.c,v 1.18 2005/12/24 20:07:04 perry Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2001, 2002 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Matthew Fredette.
|
|
*
|
|
* 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. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the NetBSD
|
|
* Foundation, Inc. and its contributors.
|
|
* 4. Neither the name of The NetBSD Foundation 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 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.
|
|
*/
|
|
|
|
/* $OpenBSD: pmap.c,v 1.46 2001/07/25 13:25:31 art Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1998-2001 Michael Shalayeff
|
|
* All rights reserved.
|
|
*
|
|
* 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. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Michael Shalayeff.
|
|
* 4. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 1996 1995 by Open Software Foundation, Inc.
|
|
* All Rights Reserved
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software and
|
|
* its documentation for any purpose and without fee is hereby granted,
|
|
* provided that the above copyright notice appears in all copies and
|
|
* that both the copyright notice and this permission notice appear in
|
|
* supporting documentation.
|
|
*
|
|
* OSF DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
|
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* IN NO EVENT SHALL OSF BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
|
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
|
|
* NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
|
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
/*
|
|
* Mach Operating System
|
|
* Copyright (c) 1990,1991,1992,1993,1994 The University of Utah and
|
|
* the Computer Systems Laboratory (CSL).
|
|
* Copyright (c) 1991,1987 Carnegie Mellon University.
|
|
* All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify and distribute this software and its
|
|
* documentation is hereby granted, provided that both the copyright
|
|
* notice and this permission notice appear in all copies of the
|
|
* software, derivative works or modified versions, and any portions
|
|
* thereof, and that both notices appear in supporting documentation,
|
|
* and that all advertising materials mentioning features or use of
|
|
* this software display the following acknowledgement: ``This product
|
|
* includes software developed by the Computer Systems Laboratory at
|
|
* the University of Utah.''
|
|
*
|
|
* CARNEGIE MELLON, THE UNIVERSITY OF UTAH AND CSL ALLOW FREE USE OF
|
|
* THIS SOFTWARE IN ITS "AS IS" CONDITION, AND DISCLAIM ANY LIABILITY
|
|
* OF ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF
|
|
* THIS SOFTWARE.
|
|
*
|
|
* CSL requests users of this software to return to csl-dist@cs.utah.edu any
|
|
* improvements that they make and grant CSL redistribution rights.
|
|
*
|
|
* Carnegie Mellon requests users of this software to return to
|
|
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
|
|
* School of Computer Science
|
|
* Carnegie Mellon University
|
|
* Pittsburgh PA 15213-3890
|
|
* any improvements or extensions that they make and grant Carnegie Mellon
|
|
* the rights to redistribute these changes.
|
|
*
|
|
* Utah $Hdr: pmap.c 1.49 94/12/15$
|
|
* Author: Mike Hibler, Bob Wheeler, University of Utah CSL, 10/90
|
|
*/
|
|
/*
|
|
* Manages physical address maps for hppa.
|
|
*
|
|
* 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 to
|
|
* when physical maps must be made correct.
|
|
*
|
|
*/
|
|
/*
|
|
* CAVEATS:
|
|
*
|
|
* Needs more work for MP support
|
|
* page maps are stored as linear linked lists, some
|
|
* improvement may be achieved should we use smth else
|
|
* protection id (pid) allocation should be done in a pid_t fashion
|
|
* (maybe just use the pid itself)
|
|
* some ppl say, block tlb entries should be maintained somewhere in uvm
|
|
* and be ready for reloads in the fault handler.
|
|
* usage of inline grows the code size by 100%, but hopefully
|
|
* makes it faster as well, since the functions are actually
|
|
* very small.
|
|
* retail: 8.1k -> 15.1K
|
|
* debug: 12.2k -> 22.1K
|
|
*
|
|
* References:
|
|
* 1. PA7100LC ERS, Hewlett-Packard, March 30 1999, Public version 1.0
|
|
* 2. PA7300LC ERS, Hewlett-Packard, March 18 1996, Version 1.0
|
|
*
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: pmap.c,v 1.18 2005/12/24 20:07:04 perry Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/proc.h>
|
|
|
|
#include <uvm/uvm.h>
|
|
|
|
#include <machine/reg.h>
|
|
#include <machine/psl.h>
|
|
#include <machine/cpu.h>
|
|
#include <machine/pmap.h>
|
|
#include <machine/pte.h>
|
|
#include <machine/cpufunc.h>
|
|
|
|
#include <hppa/hppa/hpt.h>
|
|
#include <hppa/hppa/machdep.h>
|
|
|
|
#define static /**/
|
|
#define inline /* */
|
|
|
|
#ifdef PMAPDEBUG
|
|
#define PDB_INIT 0x00000002
|
|
#define PDB_ENTER 0x00000004
|
|
#define PDB_REMOVE 0x00000008
|
|
#define PDB_KENTER 0x00000010
|
|
#define PDB_PMAP 0x00000020
|
|
#define PDB_CACHE 0x00000040
|
|
#define PDB_BITS 0x00000080
|
|
#define PDB_EXTRACT 0x00000100
|
|
#define PDB_PROTECT 0x00000200
|
|
#define PDB_PV_ALLOC 0x00000400
|
|
#define PDB_PV_ENTER 0x00000800
|
|
#define PDB_PV_REMOVE 0x00001000
|
|
#define PDB_PV_FIND_VA 0x00002000
|
|
#define PDB_WIRING 0x00004000
|
|
#define PDB_ZERO 0x00008000
|
|
#define PDB_STEAL 0x00010000
|
|
#define PDB_COPY 0x00020000
|
|
int pmapdebug = 0
|
|
#if 1
|
|
| PDB_ENTER
|
|
| PDB_REMOVE
|
|
| PDB_KENTER
|
|
| PDB_BITS
|
|
| PDB_PROTECT
|
|
| PDB_EXTRACT
|
|
| PDB_WIRING
|
|
| PDB_ZERO
|
|
| PDB_STEAL
|
|
| PDB_COPY
|
|
#endif
|
|
;
|
|
#define PMAP_PRINTF_MASK(m,v,x) do { \
|
|
if ((pmapdebug & (m)) == (v)) { \
|
|
printf("%s", __FUNCTION__); \
|
|
printf x; \
|
|
} \
|
|
} while(/* CONSTCOND */ 0)
|
|
#else
|
|
#define PMAP_PRINTF_MASK(m,v,x) do { } while(/* CONSTCOND */ 0)
|
|
#endif
|
|
#define PMAP_PRINTF(v,x) PMAP_PRINTF_MASK(v,v,x)
|
|
|
|
vaddr_t virtual_steal, virtual_start, virtual_end;
|
|
|
|
/* These two virtual pages are available for copying and zeroing. */
|
|
static vaddr_t tmp_vpages[2];
|
|
|
|
/* Free list of PV entries. */
|
|
static struct pv_entry *pv_free_list;
|
|
|
|
/* This is an array of struct pv_head, one per physical page. */
|
|
static struct pv_head *pv_head_tbl;
|
|
|
|
/*
|
|
* This is a bitmap of page-is-aliased bits.
|
|
* The magic 5 is log2(sizeof(u_int) * 8), and the magic 31 is 2^5 - 1.
|
|
*/
|
|
static u_int *page_aliased_bitmap;
|
|
#define _PAGE_ALIASED_WORD(pa) page_aliased_bitmap[((pa) >> PGSHIFT) >> 5]
|
|
#define _PAGE_ALIASED_BIT(pa) (1 << (((pa) >> PGSHIFT) & 31))
|
|
#define PAGE_IS_ALIASED(pa) (_PAGE_ALIASED_WORD(pa) & _PAGE_ALIASED_BIT(pa))
|
|
|
|
struct pmap kernel_pmap_store;
|
|
pmap_t kernel_pmap;
|
|
boolean_t pmap_initialized = FALSE;
|
|
|
|
TAILQ_HEAD(, pmap) pmap_freelist; /* list of free pmaps */
|
|
u_int pmap_nfree;
|
|
struct simplelock pmap_freelock; /* and lock */
|
|
|
|
struct simplelock pmap_lock; /* XXX this is all broken */
|
|
struct simplelock sid_pid_lock; /* pids */
|
|
|
|
u_int pages_per_vm_page;
|
|
u_int pid_counter;
|
|
|
|
#ifdef PMAPDEBUG
|
|
void pmap_hptdump(void);
|
|
#endif
|
|
|
|
u_int kern_prot[8], user_prot[8];
|
|
|
|
vaddr_t hpt_base;
|
|
vsize_t hpt_mask;
|
|
|
|
/*
|
|
* Page 3-6 of the "PA-RISC 1.1 Architecture and Instruction Set
|
|
* Reference Manual" (HP part number 09740-90039) defines equivalent
|
|
* and non-equivalent virtual addresses in the cache.
|
|
*
|
|
* This macro evaluates to TRUE iff the two space/virtual address
|
|
* combinations are non-equivalent aliases, and therefore will find
|
|
* two different locations in the cache.
|
|
*
|
|
* NB: currently, the CPU-specific desidhash() functions disable the
|
|
* use of the space in all cache hashing functions. This means that
|
|
* this macro definition is stricter than it has to be (because it
|
|
* takes space into account), but one day cache space hashing should
|
|
* be re-enabled. Cache space hashing should yield better performance
|
|
* through better utilization of the cache, assuming that most aliasing
|
|
* is the read-only kind, which we do allow in the cache.
|
|
*/
|
|
#define NON_EQUIVALENT_ALIAS(sp1, va1, sp2, va2) \
|
|
(((((va1) ^ (va2)) & ~HPPA_PGAMASK) != 0) || \
|
|
((((sp1) ^ (sp2)) & ~HPPA_SPAMASK) != 0))
|
|
|
|
/* Prototypes. */
|
|
void __pmap_pv_update(paddr_t, struct pv_entry *, u_int, u_int);
|
|
static inline void pmap_pv_remove(struct pv_entry *);
|
|
|
|
/*
|
|
* Given a directly-mapped region, this makes pv_entries out of it and
|
|
* adds them to the free list.
|
|
*/
|
|
static inline void pmap_pv_add(vaddr_t, vaddr_t);
|
|
static inline void
|
|
pmap_pv_add(vaddr_t pv_start, vaddr_t pv_end)
|
|
{
|
|
struct pv_entry *pv;
|
|
int s;
|
|
|
|
/* Align pv_start, then add the new pv_entries. */
|
|
pv_start = (pv_start + sizeof(*pv) - 1) & ~(sizeof(*pv) - 1);
|
|
pv = (struct pv_entry *) pv_start;
|
|
s = splvm();
|
|
while ((vaddr_t)(pv + 1) <= pv_end) {
|
|
pv->pv_next = pv_free_list;
|
|
pv_free_list = pv;
|
|
pv++;
|
|
}
|
|
splx(s);
|
|
|
|
PMAP_PRINTF(PDB_INIT, (": %d pv_entries @ %x allocated\n",
|
|
(pv - (struct pv_entry *) pv_start), (u_int)pv_start));
|
|
}
|
|
|
|
/*
|
|
* This allocates and returns a new struct pv_entry.
|
|
*
|
|
* If we run out of preallocated struct pv_entries, we have to forcibly
|
|
* free one. malloc() isn't an option, because a) we'll probably end
|
|
* up back here anyways when malloc() maps what it's trying to return to
|
|
* us, and b) even if malloc() did succeed, the TLB fault handlers run
|
|
* in physical mode and thus require that all pv_entries be directly
|
|
* mapped, a quality unlikely for malloc()-returned memory.
|
|
*/
|
|
static inline struct pv_entry *pmap_pv_alloc(void);
|
|
static inline struct pv_entry *
|
|
pmap_pv_alloc(void)
|
|
{
|
|
struct pv_entry *pv, *pv_fallback;
|
|
u_int hpt_index_first, hpt_index, hpt_size;
|
|
struct hpt_entry *hpt;
|
|
|
|
pv = pv_free_list;
|
|
if (pv == NULL) {
|
|
/*
|
|
* We need to find a struct pv_entry to forcibly
|
|
* free. It cannot be wired. We prefer to free
|
|
* mappings that aren't marked as referenced. We
|
|
* search the HPT for an entry to free, starting
|
|
* at a semirandom HPT index determined by the
|
|
* current value of the interval timer.
|
|
*/
|
|
hpt_size = hpt_mask / sizeof(*hpt);
|
|
mfctl(CR_ITMR, hpt_index_first);
|
|
hpt_index = hpt_index_first = hpt_index_first & hpt_size;
|
|
pv_fallback = NULL;
|
|
do {
|
|
hpt = ((struct hpt_entry *) hpt_base) + hpt_index;
|
|
for (pv = hpt->hpt_entry;
|
|
pv != NULL;
|
|
pv = pv->pv_hash) {
|
|
if (!(pv->pv_tlbprot & TLB_WIRED)) {
|
|
if (!(pv->pv_tlbprot & TLB_REF))
|
|
break;
|
|
pv_fallback = pv;
|
|
}
|
|
}
|
|
if (pv != NULL)
|
|
break;
|
|
if (pv_fallback != NULL) {
|
|
pv = pv_fallback;
|
|
break;
|
|
}
|
|
hpt_index = (hpt_index + 1) & hpt_size;
|
|
} while (hpt_index != hpt_index_first);
|
|
|
|
/* Remove the mapping. */
|
|
if (pv != NULL) {
|
|
KASSERT(pv->pv_pmap->pmap_stats.resident_count > 0);
|
|
pv->pv_pmap->pmap_stats.resident_count--;
|
|
pmap_pv_remove(pv);
|
|
pv = pv_free_list;
|
|
}
|
|
|
|
if (pv == NULL)
|
|
panic("out of pv_entries");
|
|
|
|
}
|
|
pv_free_list = pv->pv_next;
|
|
pv->pv_next = NULL;
|
|
|
|
PMAP_PRINTF(PDB_PV_ALLOC, ("() = %p\n", pv));
|
|
return pv;
|
|
}
|
|
|
|
/*
|
|
* Given a struct pv_entry allocated by pmap_pv_alloc, this frees it.
|
|
*/
|
|
static inline void pmap_pv_free(struct pv_entry *);
|
|
static inline void
|
|
pmap_pv_free(struct pv_entry *pv)
|
|
{
|
|
PMAP_PRINTF(PDB_PV_ALLOC, ("(%p)\n", pv));
|
|
|
|
pv->pv_next = pv_free_list;
|
|
pv_free_list = pv;
|
|
}
|
|
|
|
/*
|
|
* Given a VA, this hashes it into an HPT index.
|
|
*
|
|
* This hash function is the one used by the hardware TLB filler on
|
|
* the 7100LC, to index the hardware page table (HPT), which is sort
|
|
* of a cache of TLB entries.
|
|
*
|
|
* On other CPUs, locore.S has a software TLB filler that does exactly
|
|
* the same thing, right down to using this same hash function.
|
|
*
|
|
* This HPT is also used as a general VA->PA mapping store, with
|
|
* struct pv_entry chains hanging off of the HPT entries.
|
|
*/
|
|
static inline struct hpt_entry *pmap_hpt_hash(pa_space_t, vaddr_t);
|
|
static inline struct hpt_entry *
|
|
pmap_hpt_hash(pa_space_t sp, vaddr_t va)
|
|
{
|
|
struct hpt_entry *hpt;
|
|
__asm volatile (
|
|
"extru %2, 23, 20, %%r22\n\t" /* r22 = (va >> 8) */
|
|
"zdep %1, 22, 16, %%r23\n\t" /* r23 = (sp << 9) */
|
|
"dep %%r0, 31, 4, %%r22\n\t" /* r22 &= ~0xf */
|
|
"xor %%r22,%%r23, %%r23\n\t" /* r23 ^= r22 */
|
|
"mfctl %%cr24, %%r22\n\t" /* r22 = sizeof(HPT)-1 */
|
|
"and %%r22,%%r23, %%r23\n\t" /* r23 &= r22 */
|
|
"mfctl %%cr25, %%r22\n\t" /* r22 = addr of HPT table */
|
|
"or %%r23, %%r22, %0" /* %0 = HPT entry */
|
|
: "=r" (hpt) : "r" (sp), "r" (va) : "r22", "r23");
|
|
return hpt;
|
|
}
|
|
|
|
/*
|
|
* Given a PA, returns the table offset for it.
|
|
*/
|
|
static inline int pmap_table_find_pa(paddr_t);
|
|
static inline int
|
|
pmap_table_find_pa(paddr_t pa)
|
|
{
|
|
int off;
|
|
|
|
off = atop(pa);
|
|
return (off < totalphysmem) ? off : -1;
|
|
}
|
|
|
|
/*
|
|
* Given a PA, returns the first mapping for it.
|
|
*/
|
|
static inline struct pv_entry *pmap_pv_find_pa(paddr_t);
|
|
static inline struct pv_entry *
|
|
pmap_pv_find_pa(paddr_t pa)
|
|
{
|
|
int table_off;
|
|
|
|
table_off = pmap_table_find_pa(pa);
|
|
KASSERT(table_off >= 0);
|
|
return pv_head_tbl[table_off].pv_head_pvs;
|
|
}
|
|
|
|
/*
|
|
* Given a VA, this finds any mapping for it.
|
|
*/
|
|
static inline struct pv_entry *pmap_pv_find_va(pa_space_t, vaddr_t);
|
|
static inline struct pv_entry *
|
|
pmap_pv_find_va(pa_space_t space, vaddr_t va)
|
|
{
|
|
struct pv_entry *pv = pmap_hpt_hash(space, va)->hpt_entry;
|
|
|
|
while(pv && (pv->pv_va != va || pv->pv_space != space))
|
|
pv = pv->pv_hash;
|
|
|
|
PMAP_PRINTF(PDB_PV_FIND_VA, ("(0x%x:%p) = %p\n",
|
|
space, (caddr_t)va, pv));
|
|
return pv;
|
|
}
|
|
|
|
/*
|
|
* Given a page's PA, checks for non-equivalent aliasing,
|
|
* and stores and returns the result.
|
|
*/
|
|
static int pmap_pv_check_alias(paddr_t);
|
|
static int
|
|
pmap_pv_check_alias(paddr_t pa)
|
|
{
|
|
struct pv_entry *pv_outer, *pv;
|
|
pa_space_t space;
|
|
vaddr_t va;
|
|
int aliased;
|
|
u_int *aliased_word, aliased_bit;
|
|
|
|
/* By default we find no aliasing. */
|
|
aliased = FALSE;
|
|
|
|
/*
|
|
* We should never get called on I/O pages.
|
|
*/
|
|
KASSERT(pa < HPPA_IOSPACE);
|
|
|
|
/*
|
|
* Make an outer loop over the mappings, checking
|
|
* each following inner mapping for non-equivalent
|
|
* aliasing. If the non-equivalent alias relation
|
|
* is deemed transitive, this outer loop only needs
|
|
* one iteration.
|
|
*/
|
|
for (pv_outer = pmap_pv_find_pa(pa);
|
|
pv_outer != NULL;
|
|
pv_outer = pv_outer->pv_next) {
|
|
|
|
/* Load this outer mapping's space and address. */
|
|
space = pv_outer->pv_space;
|
|
va = pv_outer->pv_va;
|
|
|
|
/* Do the inner loop. */
|
|
for (pv = pv_outer->pv_next;
|
|
pv != NULL;
|
|
pv = pv->pv_next) {
|
|
if (NON_EQUIVALENT_ALIAS(space, va,
|
|
pv->pv_space, pv->pv_va)) {
|
|
aliased = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifndef NON_EQUIVALENT_ALIAS_TRANSITIVE
|
|
if (aliased)
|
|
#endif /* !NON_EQUIVALENT_ALIAS_TRANSITIVE */
|
|
break;
|
|
}
|
|
|
|
/* Store and return the result. */
|
|
aliased_word = &_PAGE_ALIASED_WORD(pa);
|
|
aliased_bit = _PAGE_ALIASED_BIT(pa);
|
|
*aliased_word = (*aliased_word & ~aliased_bit) |
|
|
(aliased ? aliased_bit : 0);
|
|
return aliased;
|
|
}
|
|
|
|
/*
|
|
* Given a VA->PA mapping and tlbprot bits to clear and set,
|
|
* this flushes the mapping from the TLB and cache, and changes
|
|
* the protection accordingly. This is used when a mapping is
|
|
* changing.
|
|
*/
|
|
static inline void _pmap_pv_update(paddr_t, struct pv_entry *, u_int, u_int);
|
|
static inline void
|
|
_pmap_pv_update(paddr_t pa, struct pv_entry *pv, u_int tlbprot_clear,
|
|
u_int tlbprot_set)
|
|
{
|
|
struct pv_entry *ppv;
|
|
int no_rw_alias;
|
|
|
|
/*
|
|
* We should never get called on I/O pages.
|
|
*/
|
|
KASSERT(pa < HPPA_IOSPACE);
|
|
|
|
/*
|
|
* If the TLB protection of this mapping is changing,
|
|
* check for a change in the no read-write alias state
|
|
* of the page.
|
|
*/
|
|
KASSERT((tlbprot_clear & TLB_AR_MASK) == 0 ||
|
|
(tlbprot_clear & TLB_AR_MASK) == TLB_AR_MASK);
|
|
if (tlbprot_clear & TLB_AR_MASK) {
|
|
|
|
/*
|
|
* Assume that no read-write aliasing
|
|
* exists. It does exist if this page is
|
|
* aliased and any mapping is writable.
|
|
*/
|
|
no_rw_alias = TLB_NO_RW_ALIAS;
|
|
if (PAGE_IS_ALIASED(pa)) {
|
|
for (ppv = pmap_pv_find_pa(pa);
|
|
ppv != NULL;
|
|
ppv = ppv->pv_next) {
|
|
if (TLB_AR_WRITABLE(ppv == pv ?
|
|
tlbprot_set :
|
|
ppv->pv_tlbprot)) {
|
|
no_rw_alias = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Note if the no read-write alias state has changed. */
|
|
if ((pv->pv_tlbprot & TLB_NO_RW_ALIAS) ^ no_rw_alias) {
|
|
tlbprot_clear |= TLB_NO_RW_ALIAS;
|
|
tlbprot_set |= no_rw_alias;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now call our asm helper function. At the very least,
|
|
* this will flush out the requested mapping and change
|
|
* its protection. If the changes touch any of TLB_REF,
|
|
* TLB_DIRTY, and TLB_NO_RW_ALIAS, all mappings of the
|
|
* page will be flushed and changed.
|
|
*/
|
|
__pmap_pv_update(pa, pv, tlbprot_clear, tlbprot_set);
|
|
}
|
|
#define pmap_pv_update(pv, tc, ts) \
|
|
_pmap_pv_update(tlbptob((pv)->pv_tlbpage), pv, tc, ts)
|
|
|
|
/*
|
|
* Given a pmap, a VA, a PA, and a TLB protection, this enters
|
|
* a new mapping and returns the new struct pv_entry.
|
|
*/
|
|
static inline struct pv_entry *pmap_pv_enter(pmap_t, pa_space_t, vaddr_t,
|
|
paddr_t, u_int);
|
|
static inline struct pv_entry *
|
|
pmap_pv_enter(pmap_t pmap, pa_space_t space, vaddr_t va, paddr_t pa,
|
|
u_int tlbprot)
|
|
{
|
|
struct hpt_entry *hpt = pmap_hpt_hash(space, va);
|
|
int table_off;
|
|
struct pv_head *hpv;
|
|
struct pv_entry *pv, *pv_other;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
/* Make sure this VA isn't already entered. */
|
|
for (pv = hpt->hpt_entry; pv != NULL; pv = pv->pv_hash)
|
|
if (pv->pv_va == va && pv->pv_space == space)
|
|
panic("pmap_pv_enter: VA already entered");
|
|
#endif /* DIAGNOSTIC */
|
|
|
|
/*
|
|
* Allocate a new pv_entry, fill it, and link it into the HPT.
|
|
*/
|
|
pv = pmap_pv_alloc();
|
|
pv->pv_va = va;
|
|
pv->pv_pmap = pmap;
|
|
pv->pv_space = space;
|
|
pv->pv_tlbprot = tlbprot;
|
|
pv->pv_tlbpage = tlbbtop(pa);
|
|
pv->pv_hpt = hpt;
|
|
pv->pv_hash = hpt->hpt_entry;
|
|
hpt->hpt_entry = pv;
|
|
|
|
/*
|
|
* If this mapping is for I/O space, mark the mapping
|
|
* uncacheable. (This is fine even on CPUs that don't
|
|
* support the U-bit; these CPUs don't cache references
|
|
* to I/O space.) Also mark this mapping as having
|
|
* no read/write aliasing, and we're done - we don't
|
|
* keep PA->VA lists for I/O space.
|
|
*/
|
|
if (pa >= HPPA_IOSPACE) {
|
|
KASSERT(tlbprot & TLB_UNMANAGED);
|
|
pv->pv_tlbprot |= TLB_UNCACHEABLE | TLB_NO_RW_ALIAS;
|
|
return pv;
|
|
}
|
|
|
|
/* Get the head of the PA->VA translation list. */
|
|
table_off = pmap_table_find_pa(pa);
|
|
KASSERT(table_off >= 0);
|
|
hpv = pv_head_tbl + table_off;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
/* Make sure this VA isn't already entered. */
|
|
for (pv_other = hpv->pv_head_pvs;
|
|
pv_other != NULL;
|
|
pv_other = pv_other->pv_next)
|
|
if (pmap == pv_other->pv_pmap && va == pv_other->pv_va)
|
|
panic("pmap_pv_enter: VA already in pv_tab");
|
|
#endif /* DIAGNOSTIC */
|
|
|
|
/*
|
|
* Link this mapping into the PA->VA list.
|
|
*/
|
|
pv_other = hpv->pv_head_pvs;
|
|
pv->pv_next = pv_other;
|
|
hpv->pv_head_pvs = pv;
|
|
|
|
/*
|
|
* If there are no other mappings of this page, this
|
|
* mapping has no read/write aliasing. Otherwise, give
|
|
* this mapping the same TLB_NO_RW_ALIAS status as the
|
|
* other mapping (all mappings of the same page must
|
|
* always be marked the same).
|
|
*/
|
|
pv->pv_tlbprot |= (pv_other == NULL ?
|
|
TLB_NO_RW_ALIAS :
|
|
(pv_other->pv_tlbprot & TLB_NO_RW_ALIAS));
|
|
|
|
/* Check for read-write aliasing. */
|
|
if (!PAGE_IS_ALIASED(pa))
|
|
pmap_pv_check_alias(pa);
|
|
_pmap_pv_update(pa, pv, TLB_AR_MASK, tlbprot & TLB_AR_MASK);
|
|
|
|
return pv;
|
|
}
|
|
|
|
/*
|
|
* Given a particular VA->PA mapping, this removes it.
|
|
*/
|
|
static inline void
|
|
pmap_pv_remove(struct pv_entry *pv)
|
|
{
|
|
paddr_t pa = tlbptob(pv->pv_tlbpage);
|
|
int table_off;
|
|
struct pv_head *hpv;
|
|
struct pv_entry **_pv;
|
|
|
|
PMAP_PRINTF(PDB_PV_REMOVE, ("(%p)\n", pv));
|
|
|
|
/* Unlink this pv_entry from the HPT. */
|
|
_pv = &pv->pv_hpt->hpt_entry;
|
|
while (*_pv != pv) {
|
|
KASSERT(*_pv != NULL);
|
|
_pv = &(*_pv)->pv_hash;
|
|
}
|
|
*_pv = pv->pv_hash;
|
|
|
|
/*
|
|
* If this mapping is for I/O space, simply flush the
|
|
* old mapping, free it, and we're done.
|
|
*/
|
|
if (pa >= HPPA_IOSPACE) {
|
|
__pmap_pv_update(pa, pv, 0, 0);
|
|
pmap_pv_free(pv);
|
|
return;
|
|
}
|
|
|
|
/* Get the head of the PA->VA translation list. */
|
|
table_off = pmap_table_find_pa(pa);
|
|
KASSERT(table_off >= 0);
|
|
hpv = pv_head_tbl + table_off;
|
|
|
|
/* Unlink this pv_entry from the PA->VA translation list. */
|
|
_pv = &hpv->pv_head_pvs;
|
|
while (*_pv != pv) {
|
|
KASSERT(*_pv != NULL);
|
|
_pv = &(*_pv)->pv_next;
|
|
}
|
|
*_pv = pv->pv_next;
|
|
|
|
/*
|
|
* Check for read-write aliasing. This will also flush
|
|
* the old mapping.
|
|
*/
|
|
if (PAGE_IS_ALIASED(pa))
|
|
pmap_pv_check_alias(pa);
|
|
_pmap_pv_update(pa, pv, TLB_AR_MASK, TLB_AR_KR);
|
|
|
|
/* Free this mapping. */
|
|
pmap_pv_free(pv);
|
|
}
|
|
|
|
/*
|
|
* Bootstrap the system enough to run with virtual memory.
|
|
* Map the kernel's code and data, and allocate the system page table.
|
|
* Called with mapping OFF.
|
|
*
|
|
* Parameters:
|
|
* vstart PA of first available physical page
|
|
* vend PA of last available physical page
|
|
*/
|
|
void
|
|
pmap_bootstrap(vaddr_t *vstart, vaddr_t *vend)
|
|
{
|
|
vaddr_t addr;
|
|
vsize_t size;
|
|
vaddr_t pv_region;
|
|
struct hpt_entry *hptp;
|
|
#define BTLB_SET_SIZE 16
|
|
vaddr_t btlb_entry_start[BTLB_SET_SIZE];
|
|
vsize_t btlb_entry_size[BTLB_SET_SIZE];
|
|
int btlb_entry_vm_prot[BTLB_SET_SIZE];
|
|
int btlb_i, btlb_j;
|
|
vsize_t btlb_entry_min, btlb_entry_max, btlb_entry_got;
|
|
extern int kernel_text, etext;
|
|
vaddr_t kernel_data;
|
|
paddr_t phys_start, phys_end;
|
|
|
|
uvm_setpagesize();
|
|
|
|
pages_per_vm_page = 1; /* XXX This should die */
|
|
|
|
kern_prot[VM_PROT_NONE | VM_PROT_NONE | VM_PROT_NONE] =TLB_AR_NA;
|
|
kern_prot[VM_PROT_READ | VM_PROT_NONE | VM_PROT_NONE] =TLB_AR_KR;
|
|
kern_prot[VM_PROT_NONE | VM_PROT_WRITE | VM_PROT_NONE] =TLB_AR_KRW;
|
|
kern_prot[VM_PROT_READ | VM_PROT_WRITE | VM_PROT_NONE] =TLB_AR_KRW;
|
|
kern_prot[VM_PROT_NONE | VM_PROT_NONE | VM_PROT_EXECUTE] =TLB_AR_KRX;
|
|
kern_prot[VM_PROT_READ | VM_PROT_NONE | VM_PROT_EXECUTE] =TLB_AR_KRX;
|
|
kern_prot[VM_PROT_NONE | VM_PROT_WRITE | VM_PROT_EXECUTE] =TLB_AR_KRWX;
|
|
kern_prot[VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE] =TLB_AR_KRWX;
|
|
|
|
user_prot[VM_PROT_NONE | VM_PROT_NONE | VM_PROT_NONE] =TLB_AR_NA;
|
|
user_prot[VM_PROT_READ | VM_PROT_NONE | VM_PROT_NONE] =TLB_AR_UR;
|
|
user_prot[VM_PROT_NONE | VM_PROT_WRITE | VM_PROT_NONE] =TLB_AR_URW;
|
|
user_prot[VM_PROT_READ | VM_PROT_WRITE | VM_PROT_NONE] =TLB_AR_URW;
|
|
user_prot[VM_PROT_NONE | VM_PROT_NONE | VM_PROT_EXECUTE] =TLB_AR_URX;
|
|
user_prot[VM_PROT_READ | VM_PROT_NONE | VM_PROT_EXECUTE] =TLB_AR_URX;
|
|
user_prot[VM_PROT_NONE | VM_PROT_WRITE | VM_PROT_EXECUTE] =TLB_AR_URWX;
|
|
user_prot[VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE] =TLB_AR_URWX;
|
|
|
|
/*
|
|
* Initialize kernel pmap
|
|
*/
|
|
kernel_pmap = &kernel_pmap_store;
|
|
#if NCPUS > 1
|
|
lock_init(&pmap_lock, FALSE, ETAP_VM_PMAP_SYS, ETAP_VM_PMAP_SYS_I);
|
|
#endif /* NCPUS > 1 */
|
|
simple_lock_init(&kernel_pmap->pmap_lock);
|
|
simple_lock_init(&pmap_freelock);
|
|
simple_lock_init(&sid_pid_lock);
|
|
|
|
kernel_pmap->pmap_refcnt = 1;
|
|
kernel_pmap->pmap_space = HPPA_SID_KERNEL;
|
|
kernel_pmap->pmap_pid = HPPA_PID_KERNEL;
|
|
|
|
/*
|
|
* Allocate various tables and structures.
|
|
*/
|
|
addr = hppa_round_page(*vstart);
|
|
virtual_end = *vend;
|
|
|
|
/*
|
|
* Figure out how big the HPT must be, and align
|
|
* addr to what will be its beginning. We don't
|
|
* waste the pages skipped for the alignment;
|
|
* they become struct pv_entry pages.
|
|
*/
|
|
pv_region = addr;
|
|
mfctl(CR_HPTMASK, size);
|
|
addr = (addr + size) & ~(size);
|
|
pv_free_list = NULL;
|
|
pmap_pv_add(pv_region, addr);
|
|
|
|
/* Allocate the HPT */
|
|
for (hptp = (struct hpt_entry *)addr;
|
|
((u_int)hptp - addr) <= size; hptp++) {
|
|
hptp->hpt_valid = 0;
|
|
hptp->hpt_vpn = 0;
|
|
hptp->hpt_space = -1;
|
|
hptp->hpt_tlbpage = 0;
|
|
hptp->hpt_tlbprot = 0;
|
|
hptp->hpt_entry = NULL;
|
|
}
|
|
#ifdef PMAPDEBUG
|
|
if (pmapdebug & PDB_INIT)
|
|
printf("hpt_table: 0x%lx @ %p\n", size + 1, (caddr_t)addr);
|
|
#endif
|
|
/* load cr25 with the address of the HPT table
|
|
NB: It sez CR_VTOP, but we (and the TLB handlers) know better ... */
|
|
mtctl(addr, CR_VTOP);
|
|
hpt_base = addr;
|
|
hpt_mask = size;
|
|
lwp0.l_md.md_regs->tf_hptm = size;
|
|
lwp0.l_md.md_regs->tf_vtop = addr;
|
|
addr += size + 1;
|
|
|
|
/* Allocate the struct pv_head array. */
|
|
addr = ALIGN(addr);
|
|
pv_head_tbl = (struct pv_head *) addr;
|
|
memset(pv_head_tbl, 0, sizeof(*pv_head_tbl) * totalphysmem);
|
|
addr = (vaddr_t) (pv_head_tbl + totalphysmem);
|
|
|
|
/* Allocate the page aliased bitmap. */
|
|
addr = ALIGN(addr);
|
|
page_aliased_bitmap = (u_int *) addr;
|
|
addr = (vaddr_t) (&_PAGE_ALIASED_WORD(totalphysmem) + 1);
|
|
memset(page_aliased_bitmap, 0, addr - (vaddr_t) page_aliased_bitmap);
|
|
|
|
/*
|
|
* Allocate the largest struct pv_entry region. The
|
|
* 6 is a magic constant, chosen to allow on average
|
|
* all physical pages to have 6 simultaneous mappings
|
|
* without having to reclaim any struct pv_entry.
|
|
*/
|
|
pv_region = addr;
|
|
addr += sizeof(struct pv_entry) * totalphysmem * 6;
|
|
pmap_pv_add(pv_region, addr);
|
|
|
|
/*
|
|
* Allocate the steal region. Because pmap_steal_memory
|
|
* must panic whenever an allocation cannot be fulfilled,
|
|
* we have to guess at the maximum amount of space that
|
|
* might be stolen. Overestimating is not really a problem,
|
|
* as it only leads to lost virtual space, not lost physical
|
|
* pages.
|
|
*/
|
|
addr = hppa_round_page(addr);
|
|
virtual_steal = addr;
|
|
addr += totalphysmem * sizeof(struct vm_page);
|
|
memset((caddr_t) virtual_steal, 0, addr - virtual_steal);
|
|
|
|
/*
|
|
* We now have a rough idea of where managed kernel virtual
|
|
* space will begin, and we can start mapping everything
|
|
* before that.
|
|
*/
|
|
addr = hppa_round_page(addr);
|
|
*vstart = addr;
|
|
|
|
/*
|
|
* In general, the virtual space below the kernel text is
|
|
* left unmapped, to allow detection of NULL dereferences.
|
|
* However, these tmp_vpages are two virtual pages right
|
|
* before the kernel text that can be mapped for page copying
|
|
* and zeroing.
|
|
*/
|
|
tmp_vpages[1] = hppa_trunc_page((vaddr_t) &kernel_text) - PAGE_SIZE;
|
|
tmp_vpages[0] = tmp_vpages[1] - PAGE_SIZE;
|
|
|
|
/*
|
|
* The kernel text, data, and bss must be direct-mapped,
|
|
* because the kernel often runs in physical mode, and
|
|
* anyways the loader loaded the kernel into physical
|
|
* memory exactly where it was linked.
|
|
*
|
|
* All memory already allocated after bss, either by
|
|
* our caller or by this function itself, must also be
|
|
* direct-mapped, because it's completely unmanaged
|
|
* and was allocated in physical mode.
|
|
*
|
|
* BTLB entries are used to do this direct mapping.
|
|
* BTLB entries have a minimum and maximum possible size,
|
|
* and MD code gives us these sizes in units of pages.
|
|
*/
|
|
btlb_entry_min = (vsize_t) hppa_btlb_size_min * PAGE_SIZE;
|
|
btlb_entry_max = (vsize_t) hppa_btlb_size_max * PAGE_SIZE;
|
|
|
|
/*
|
|
* We begin by making BTLB entries for the kernel text.
|
|
* To keep things simple, we insist that the kernel text
|
|
* be aligned to the minimum BTLB entry size.
|
|
*/
|
|
if (((vaddr_t) &kernel_text) & (btlb_entry_min - 1))
|
|
panic("kernel text not aligned to BTLB minimum size");
|
|
|
|
/*
|
|
* To try to conserve BTLB entries, take a hint from how
|
|
* the kernel was linked: take the kernel text start as
|
|
* our effective minimum BTLB entry size, assuming that
|
|
* the data segment was also aligned to that size.
|
|
*
|
|
* In practice, linking the kernel at 2MB, and aligning
|
|
* the data segment to a 2MB boundary, should control well
|
|
* how much of the BTLB the pmap uses. However, this code
|
|
* should not rely on this 2MB magic number, nor should
|
|
* it rely on the data segment being aligned at all. This
|
|
* is to allow (smaller) kernels (linked lower) to work fine.
|
|
*/
|
|
btlb_entry_min = (vaddr_t) &kernel_text;
|
|
__asm volatile (
|
|
" ldil L%%$global$, %0 \n"
|
|
" ldo R%%$global$(%0), %0 \n"
|
|
: "=r" (kernel_data));
|
|
|
|
/*
|
|
* Now make BTLB entries to direct-map the kernel text
|
|
* read- and execute-only as much as possible. Note that
|
|
* if the data segment isn't nicely aligned, the last
|
|
* BTLB entry for the kernel text may also cover some of
|
|
* the data segment, meaning it will have to allow writing.
|
|
*/
|
|
addr = (vaddr_t) &kernel_text;
|
|
btlb_j = 0;
|
|
while (addr < (vaddr_t) &etext) {
|
|
|
|
/* Set up the next BTLB entry. */
|
|
KASSERT(btlb_j < BTLB_SET_SIZE);
|
|
btlb_entry_start[btlb_j] = addr;
|
|
btlb_entry_size[btlb_j] = btlb_entry_min;
|
|
btlb_entry_vm_prot[btlb_j] = VM_PROT_READ | VM_PROT_EXECUTE;
|
|
if (addr + btlb_entry_min > kernel_data)
|
|
btlb_entry_vm_prot[btlb_j] |= VM_PROT_WRITE;
|
|
|
|
/* Coalesce BTLB entries whenever possible. */
|
|
while (btlb_j > 0 &&
|
|
btlb_entry_vm_prot[btlb_j] ==
|
|
btlb_entry_vm_prot[btlb_j - 1] &&
|
|
btlb_entry_size[btlb_j] ==
|
|
btlb_entry_size[btlb_j - 1] &&
|
|
!(btlb_entry_start[btlb_j - 1] &
|
|
((btlb_entry_size[btlb_j - 1] << 1) - 1)) &&
|
|
(btlb_entry_size[btlb_j - 1] << 1) <=
|
|
btlb_entry_max)
|
|
btlb_entry_size[--btlb_j] <<= 1;
|
|
|
|
/* Move on. */
|
|
addr = btlb_entry_start[btlb_j] + btlb_entry_size[btlb_j];
|
|
btlb_j++;
|
|
}
|
|
|
|
/*
|
|
* Now make BTLB entries to direct-map the kernel data,
|
|
* bss, and all of the preallocated space read-write.
|
|
*
|
|
* Note that, unlike above, we're not concerned with
|
|
* making these BTLB entries such that they finish as
|
|
* close as possible to the end of the space we need
|
|
* them to map. Instead, to minimize the number of BTLB
|
|
* entries we need, we make them as large as possible.
|
|
* The only thing this wastes is kernel virtual space,
|
|
* which is plentiful.
|
|
*/
|
|
while (addr < *vstart) {
|
|
|
|
/* Make the next BTLB entry. */
|
|
KASSERT(btlb_j < BTLB_SET_SIZE);
|
|
size = btlb_entry_min;
|
|
while ((addr + size) < *vstart &&
|
|
(size << 1) < btlb_entry_max &&
|
|
!(addr & ((size << 1) - 1)))
|
|
size <<= 1;
|
|
btlb_entry_start[btlb_j] = addr;
|
|
btlb_entry_size[btlb_j] = size;
|
|
btlb_entry_vm_prot[btlb_j] = VM_PROT_READ | VM_PROT_WRITE;
|
|
|
|
/* Move on. */
|
|
addr = btlb_entry_start[btlb_j] + btlb_entry_size[btlb_j];
|
|
btlb_j++;
|
|
}
|
|
|
|
/* Now insert all of the BTLB entries. */
|
|
for (btlb_i = 0; btlb_i < btlb_j; btlb_i++) {
|
|
btlb_entry_got = btlb_entry_size[btlb_i];
|
|
if (hppa_btlb_insert(kernel_pmap->pmap_space,
|
|
btlb_entry_start[btlb_i],
|
|
btlb_entry_start[btlb_i],
|
|
&btlb_entry_got,
|
|
kernel_pmap->pmap_pid |
|
|
pmap_prot(kernel_pmap,
|
|
btlb_entry_vm_prot[btlb_i])) < 0)
|
|
panic("pmap_bootstrap: cannot insert BTLB entry");
|
|
if (btlb_entry_got != btlb_entry_size[btlb_i])
|
|
panic("pmap_bootstrap: BTLB entry mapped wrong amount");
|
|
}
|
|
|
|
/*
|
|
* We now know the exact beginning of managed kernel
|
|
* virtual space.
|
|
*/
|
|
*vstart = btlb_entry_start[btlb_j - 1] + btlb_entry_size[btlb_j - 1];
|
|
virtual_start = *vstart;
|
|
|
|
/*
|
|
* Finally, load physical pages into UVM. There are
|
|
* three segments of pages.
|
|
*/
|
|
physmem = 0;
|
|
|
|
/* The first segment runs from [resvmem..kernel_text). */
|
|
phys_start = resvmem;
|
|
phys_end = atop(hppa_trunc_page(&kernel_text));
|
|
#ifdef DIAGNOSTIC
|
|
printf("phys segment: 0x%x 0x%x\n", (u_int)phys_start, (u_int)phys_end);
|
|
#endif
|
|
if (phys_end > phys_start) {
|
|
uvm_page_physload(phys_start, phys_end,
|
|
phys_start, phys_end, VM_FREELIST_DEFAULT);
|
|
physmem += phys_end - phys_start;
|
|
}
|
|
|
|
/* The second segment runs from [etext..kernel_data). */
|
|
phys_start = atop(hppa_round_page((vaddr_t) &etext));
|
|
phys_end = atop(hppa_trunc_page(kernel_data));
|
|
#ifdef DIAGNOSTIC
|
|
printf("phys segment: 0x%x 0x%x\n", (u_int)phys_start, (u_int)phys_end);
|
|
#endif
|
|
if (phys_end > phys_start) {
|
|
uvm_page_physload(phys_start, phys_end,
|
|
phys_start, phys_end, VM_FREELIST_DEFAULT);
|
|
physmem += phys_end - phys_start;
|
|
}
|
|
|
|
/* The third segment runs from [virtual_steal..totalphysmem). */
|
|
phys_start = atop(virtual_steal);
|
|
phys_end = totalphysmem;
|
|
#ifdef DIAGNOSTIC
|
|
printf("phys segment: 0x%x 0x%x\n", (u_int)phys_start, (u_int)phys_end);
|
|
#endif
|
|
if (phys_end > phys_start) {
|
|
uvm_page_physload(phys_start, phys_end,
|
|
phys_start, phys_end, VM_FREELIST_DEFAULT);
|
|
physmem += phys_end - phys_start;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* pmap_steal_memory(size, startp, endp)
|
|
* steals memory block of size `size' from directly mapped
|
|
* segment (mapped behind the scenes).
|
|
* directly mapped cannot be grown dynamically once allocated.
|
|
*/
|
|
vaddr_t
|
|
pmap_steal_memory(vsize_t size, vaddr_t *startp, vaddr_t *endp)
|
|
{
|
|
vaddr_t va;
|
|
int lcv;
|
|
|
|
PMAP_PRINTF(PDB_STEAL, ("(%lx, %p, %p)\n", size, startp, endp));
|
|
|
|
/* Remind the caller of the start and end of virtual space. */
|
|
if (startp)
|
|
*startp = virtual_start;
|
|
if (endp)
|
|
*endp = virtual_end;
|
|
|
|
/* Round the allocation up to a page. */
|
|
size = hppa_round_page(size);
|
|
|
|
/* We must panic if we cannot steal the memory. */
|
|
if (size > virtual_start - virtual_steal)
|
|
panic("pmap_steal_memory: out of memory");
|
|
|
|
/* Steal the memory. */
|
|
va = virtual_steal;
|
|
virtual_steal += size;
|
|
PMAP_PRINTF(PDB_STEAL, (": steal %ld bytes @%x\n", size, (u_int)va));
|
|
for (lcv = 0; lcv < vm_nphysseg ; lcv++)
|
|
if (vm_physmem[lcv].start == atop(va)) {
|
|
vm_physmem[lcv].start = atop(virtual_steal);
|
|
vm_physmem[lcv].avail_start = atop(virtual_steal);
|
|
break;
|
|
}
|
|
if (lcv == vm_nphysseg)
|
|
panic("pmap_steal_memory inconsistency");
|
|
|
|
return va;
|
|
}
|
|
|
|
/*
|
|
* How much virtual space does this kernel have?
|
|
* (After mapping kernel text, data, etc.)
|
|
*/
|
|
void
|
|
pmap_virtual_space(vaddr_t *vstartp, vaddr_t *vendp)
|
|
{
|
|
*vstartp = virtual_start;
|
|
*vendp = virtual_end;
|
|
}
|
|
|
|
/*
|
|
* Finishes the initialization of the pmap module.
|
|
* This procedure is called from vm_mem_init() in vm/vm_init.c
|
|
* to initialize any remaining data structures that the pmap module
|
|
* needs to map virtual memory (VM is already ON).
|
|
*/
|
|
void
|
|
pmap_init(void)
|
|
{
|
|
extern void gateway_page(void);
|
|
|
|
TAILQ_INIT(&pmap_freelist);
|
|
pid_counter = HPPA_PID_KERNEL + 2;
|
|
|
|
/*
|
|
* map SysCall gateways page once for everybody
|
|
* NB: we'll have to remap the phys memory
|
|
* if we have any at SYSCALLGATE address (;
|
|
*
|
|
* no spls since no interrupts
|
|
*/
|
|
pmap_pv_enter(pmap_kernel(), HPPA_SID_KERNEL, SYSCALLGATE,
|
|
(paddr_t)&gateway_page,
|
|
TLB_GATE_PROT | TLB_UNMANAGED | TLB_WIRED);
|
|
|
|
pmap_initialized = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Initialize a preallocated and zeroed pmap structure,
|
|
* such as one in a vmspace structure.
|
|
*/
|
|
static void pmap_pinit(pmap_t);
|
|
static void
|
|
pmap_pinit(pmap_t pmap)
|
|
{
|
|
u_int pid;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_PMAP, ("(%p), pid=%x\n", pmap, pmap->pmap_pid));
|
|
|
|
if (!(pid = pmap->pmap_pid)) {
|
|
|
|
/*
|
|
* Allocate space and protection IDs for the pmap.
|
|
* If all are allocated, there is nothing we can do.
|
|
*/
|
|
s = splvm();
|
|
if (pid_counter < HPPA_MAX_PID) {
|
|
pid = pid_counter;
|
|
pid_counter += 2;
|
|
} else
|
|
pid = 0;
|
|
splx(s);
|
|
|
|
if (pid == 0)
|
|
panic ("no more pmap ids\n");
|
|
|
|
simple_lock_init(&pmap->pmap_lock);
|
|
}
|
|
|
|
s = splvm();
|
|
pmap->pmap_pid = pid;
|
|
pmap->pmap_space = (pmap->pmap_pid >> 1) - 1;
|
|
pmap->pmap_refcnt = 1;
|
|
pmap->pmap_stats.resident_count = 0;
|
|
pmap->pmap_stats.wired_count = 0;
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* pmap_create()
|
|
*
|
|
* Create and return a physical map.
|
|
* the map is an actual physical map, and may be referenced by the hardware.
|
|
*/
|
|
pmap_t
|
|
pmap_create(void)
|
|
{
|
|
pmap_t pmap;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_PMAP, ("()"));
|
|
|
|
/*
|
|
* If there is a pmap in the pmap free list, reuse it.
|
|
*/
|
|
s = splvm();
|
|
if (pmap_nfree) {
|
|
pmap = pmap_freelist.tqh_first;
|
|
TAILQ_REMOVE(&pmap_freelist, pmap, pmap_list);
|
|
pmap_nfree--;
|
|
splx(s);
|
|
} else {
|
|
splx(s);
|
|
MALLOC(pmap, struct pmap *, sizeof(*pmap), M_VMMAP, M_NOWAIT);
|
|
if (pmap == NULL)
|
|
return NULL;
|
|
memset(pmap, 0, sizeof(*pmap));
|
|
}
|
|
|
|
pmap_pinit(pmap);
|
|
|
|
return(pmap);
|
|
}
|
|
|
|
/*
|
|
* pmap_destroy(pmap)
|
|
* Gives up a reference to the specified pmap. When the reference count
|
|
* reaches zero the pmap structure is added to the pmap free list.
|
|
* Should only be called if the map contains no valid mappings.
|
|
*/
|
|
void
|
|
pmap_destroy(pmap_t pmap)
|
|
{
|
|
int ref_count;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_PMAP, ("(%p)\n", pmap));
|
|
|
|
s = splvm();
|
|
|
|
ref_count = --pmap->pmap_refcnt;
|
|
|
|
if (ref_count < 0)
|
|
panic("pmap_destroy(): ref_count < 0");
|
|
if (!ref_count) {
|
|
KASSERT(pmap->pmap_stats.resident_count == 0);
|
|
KASSERT(pmap->pmap_stats.wired_count == 0);
|
|
|
|
/*
|
|
* Add the pmap to the pmap free list
|
|
* We cannot free() disposed pmaps because of
|
|
* PID shortage of 2^16
|
|
*/
|
|
TAILQ_INSERT_HEAD(&pmap_freelist, pmap, pmap_list);
|
|
pmap_nfree++;
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* pmap_activate(lwp)
|
|
* Activates the vmspace for the given LWP. This
|
|
* isn't necessarily the current process.
|
|
*/
|
|
void
|
|
pmap_activate(struct lwp *l)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
pmap_t pmap = p->p_vmspace->vm_map.pmap;
|
|
pa_space_t space = pmap->pmap_space;
|
|
struct trapframe *tf = l->l_md.md_regs;
|
|
|
|
/* space is cached for the copy{in,out}'s pleasure */
|
|
l->l_addr->u_pcb.pcb_space = space;
|
|
|
|
/* Load all of the user's space registers. */
|
|
tf->tf_sr0 = tf->tf_sr1 = tf->tf_sr2 = tf->tf_sr3 =
|
|
tf->tf_sr4 = tf->tf_sr5 = tf->tf_sr6 = space;
|
|
tf->tf_iisq_head = tf->tf_iisq_tail = space;
|
|
|
|
/*
|
|
* Load the protection registers. NB that
|
|
* if p *is* the current process, we set pidr2
|
|
* to the new space immediately, so any copyins
|
|
* or copyouts that happen before we return to
|
|
* userspace work.
|
|
*/
|
|
tf->tf_pidr1 = tf->tf_pidr2 = pmap->pmap_pid;
|
|
if (p == curproc)
|
|
mtctl(pmap->pmap_pid, CR_PIDR2);
|
|
}
|
|
|
|
/*
|
|
* pmap_enter(pmap, va, pa, prot, flags)
|
|
* Create a translation for the virtual address (va) to the physical
|
|
* address (pa) in the pmap with the protection requested. If the
|
|
* translation is wired then we can not allow a page fault to occur
|
|
* for this mapping.
|
|
*/
|
|
int
|
|
pmap_enter(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot, int flags)
|
|
{
|
|
struct pv_entry *pv;
|
|
u_int tlbpage, tlbprot;
|
|
pa_space_t space;
|
|
boolean_t waswired;
|
|
boolean_t wired = (flags & PMAP_WIRED) != 0;
|
|
int s;
|
|
|
|
/* Get a handle on the mapping we want to enter. */
|
|
space = pmap_sid(pmap, va);
|
|
va = hppa_trunc_page(va);
|
|
pa = hppa_trunc_page(pa);
|
|
tlbpage = tlbbtop(pa);
|
|
tlbprot = pmap_prot(pmap, prot) | pmap->pmap_pid;
|
|
if (wired)
|
|
tlbprot |= TLB_WIRED;
|
|
if (flags & VM_PROT_ALL) {
|
|
tlbprot |= TLB_REF;
|
|
if (flags & VM_PROT_WRITE)
|
|
tlbprot |= TLB_DIRTY;
|
|
}
|
|
|
|
#ifdef PMAPDEBUG
|
|
if (!pmap_initialized || (pmapdebug & PDB_ENTER))
|
|
PMAP_PRINTF(0, ("(%p, %p, %p, %x, %swired)\n",
|
|
pmap, (caddr_t)va, (caddr_t)pa,
|
|
prot, wired? "" : "un"));
|
|
#endif
|
|
|
|
s = splvm();
|
|
|
|
if (!(pv = pmap_pv_find_va(space, va))) {
|
|
/*
|
|
* Mapping for this virtual address doesn't exist.
|
|
* Enter a new mapping.
|
|
*/
|
|
pv = pmap_pv_enter(pmap, space, va, pa, tlbprot);
|
|
pmap->pmap_stats.resident_count++;
|
|
waswired = FALSE;
|
|
} else {
|
|
KASSERT((pv->pv_tlbprot & TLB_UNMANAGED) == 0);
|
|
waswired = pv->pv_tlbprot & TLB_WIRED;
|
|
|
|
/* see if we are remapping the page to another PA */
|
|
if (pv->pv_tlbpage != tlbpage) {
|
|
PMAP_PRINTF(PDB_ENTER, (": moving pa %x -> %x\n",
|
|
pv->pv_tlbpage, tlbpage));
|
|
/* update tlbprot to avoid extra subsequent fault */
|
|
pmap_pv_remove(pv);
|
|
pv = pmap_pv_enter(pmap, space, va, pa, tlbprot);
|
|
} else {
|
|
/* We are just changing the protection. */
|
|
#ifdef PMAPDEBUG
|
|
if (pmapdebug & PDB_ENTER) {
|
|
char buffer1[64];
|
|
char buffer2[64];
|
|
bitmask_snprintf(pv->pv_tlbprot, TLB_BITS,
|
|
buffer1, sizeof(buffer1));
|
|
bitmask_snprintf(tlbprot, TLB_BITS,
|
|
buffer2, sizeof(buffer2));
|
|
printf("pmap_enter: changing %s->%s\n",
|
|
buffer1, buffer2);
|
|
}
|
|
#endif
|
|
pmap_pv_update(pv, TLB_AR_MASK|TLB_PID_MASK|TLB_WIRED,
|
|
tlbprot);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Adjust statistics
|
|
*/
|
|
if (wired && !waswired) {
|
|
pmap->pmap_stats.wired_count++;
|
|
} else if (!wired && waswired) {
|
|
pmap->pmap_stats.wired_count--;
|
|
}
|
|
splx(s);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* pmap_remove(pmap, sva, eva)
|
|
* unmaps all virtual addresses v in the virtual address
|
|
* range determined by [sva, eva) and pmap.
|
|
* sva and eva must be on machine independent page boundaries and
|
|
* sva must be less than or equal to eva.
|
|
*/
|
|
void
|
|
pmap_remove(pmap_t pmap, vaddr_t sva, vaddr_t eva)
|
|
{
|
|
struct pv_entry *pv;
|
|
pa_space_t space;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_REMOVE, ("(%p, %p, %p)\n",
|
|
pmap, (caddr_t)sva, (caddr_t)eva));
|
|
|
|
sva = hppa_trunc_page(sva);
|
|
space = pmap_sid(pmap, sva);
|
|
|
|
s = splvm();
|
|
|
|
while (sva < eva) {
|
|
pv = pmap_pv_find_va(space, sva);
|
|
if (pv) {
|
|
KASSERT((pv->pv_tlbprot & TLB_UNMANAGED) == 0);
|
|
KASSERT(pmap->pmap_stats.resident_count > 0);
|
|
pmap->pmap_stats.resident_count--;
|
|
if (pv->pv_tlbprot & TLB_WIRED) {
|
|
KASSERT(pmap->pmap_stats.wired_count > 0);
|
|
pmap->pmap_stats.wired_count--;
|
|
}
|
|
pmap_pv_remove(pv);
|
|
PMAP_PRINTF(PDB_REMOVE, (": removed %p for 0x%x:%p\n",
|
|
pv, space, (caddr_t)sva));
|
|
}
|
|
sva += PAGE_SIZE;
|
|
}
|
|
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* pmap_page_protect(pa, prot)
|
|
*
|
|
* Lower the permission for all mappings to a given page.
|
|
*/
|
|
void
|
|
pmap_page_protect(struct vm_page *pg, vm_prot_t prot)
|
|
{
|
|
struct pv_entry *pv, *pv_next;
|
|
pmap_t pmap;
|
|
u_int tlbprot;
|
|
paddr_t pa = VM_PAGE_TO_PHYS(pg);
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_PROTECT, ("(%p, %x)\n", (caddr_t)pa, prot));
|
|
|
|
switch (prot) {
|
|
case VM_PROT_ALL:
|
|
return;
|
|
case VM_PROT_READ:
|
|
case VM_PROT_READ|VM_PROT_EXECUTE:
|
|
s = splvm();
|
|
for (pv = pmap_pv_find_pa(pa); pv; pv = pv->pv_next) {
|
|
/* Ignore unmanaged mappings. */
|
|
if (pv->pv_tlbprot & TLB_UNMANAGED)
|
|
continue;
|
|
/*
|
|
* Compare new protection with old to see if
|
|
* anything needs to be changed.
|
|
*/
|
|
tlbprot = pmap_prot(pv->pv_pmap, prot);
|
|
if ((pv->pv_tlbprot & TLB_AR_MASK) != tlbprot) {
|
|
pmap_pv_update(pv, TLB_AR_MASK, tlbprot);
|
|
}
|
|
}
|
|
splx(s);
|
|
break;
|
|
default:
|
|
s = splvm();
|
|
for (pv = pmap_pv_find_pa(pa); pv != NULL; pv = pv_next) {
|
|
pv_next = pv->pv_next;
|
|
/* Ignore unmanaged mappings. */
|
|
if (pv->pv_tlbprot & TLB_UNMANAGED)
|
|
continue;
|
|
#ifdef PMAPDEBUG
|
|
if (pmapdebug & PDB_PROTECT) {
|
|
char buffer[64];
|
|
bitmask_snprintf(pv->pv_tlbprot, TLB_BITS,
|
|
buffer, sizeof(buffer));
|
|
printf("pv={%p,%x:%x,%s,%x}->%p\n",
|
|
pv->pv_pmap, pv->pv_space, pv->pv_va,
|
|
buffer,
|
|
tlbptob(pv->pv_tlbpage), pv->pv_hash);
|
|
}
|
|
#endif
|
|
pmap = pv->pv_pmap;
|
|
if (pv->pv_tlbprot & TLB_WIRED) {
|
|
KASSERT(pmap->pmap_stats.wired_count > 0);
|
|
pmap->pmap_stats.wired_count--;
|
|
}
|
|
pmap_pv_remove(pv);
|
|
KASSERT(pmap->pmap_stats.resident_count > 0);
|
|
pmap->pmap_stats.resident_count--;
|
|
}
|
|
splx(s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* pmap_protect(pmap, s, e, prot)
|
|
* changes the protection on all virtual addresses v in the
|
|
* virtual address range determined by [s, e) and pmap to prot.
|
|
* s and e must be on machine independent page boundaries and
|
|
* s must be less than or equal to e.
|
|
*/
|
|
void
|
|
pmap_protect(pmap_t pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot)
|
|
{
|
|
struct pv_entry *pv;
|
|
u_int tlbprot;
|
|
pa_space_t space;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_PROTECT, ("(%p, %p, %p, %x)\n",
|
|
pmap, (caddr_t)sva, (caddr_t)eva, prot));
|
|
|
|
if (prot == VM_PROT_NONE) {
|
|
pmap_remove(pmap, sva, eva);
|
|
return;
|
|
}
|
|
|
|
sva = hppa_trunc_page(sva);
|
|
space = pmap_sid(pmap, sva);
|
|
tlbprot = pmap_prot(pmap, prot);
|
|
|
|
s = splvm();
|
|
for(; sva < eva; sva += PAGE_SIZE) {
|
|
if ((pv = pmap_pv_find_va(space, sva))) {
|
|
|
|
/*
|
|
* Compare new protection with old to see if
|
|
* anything needs to be changed.
|
|
*/
|
|
if ((pv->pv_tlbprot & TLB_AR_MASK) != tlbprot) {
|
|
pmap_pv_update(pv, TLB_AR_MASK, tlbprot);
|
|
}
|
|
}
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Routine: pmap_unwire
|
|
* Function: Change the wiring attribute for a map/virtual-address
|
|
* pair.
|
|
* In/out conditions:
|
|
* The mapping must already exist in the pmap.
|
|
*
|
|
* Change the wiring for a given virtual page. This routine currently is
|
|
* only used to unwire pages and hence the mapping entry will exist.
|
|
*/
|
|
void
|
|
pmap_unwire(pmap_t pmap, vaddr_t va)
|
|
{
|
|
struct pv_entry *pv;
|
|
int s;
|
|
|
|
va = hppa_trunc_page(va);
|
|
PMAP_PRINTF(PDB_WIRING, ("(%p, %p)\n", pmap, (caddr_t)va));
|
|
|
|
simple_lock(&pmap->pmap_lock);
|
|
|
|
s = splvm();
|
|
if ((pv = pmap_pv_find_va(pmap_sid(pmap, va), va)) == NULL)
|
|
panic("pmap_unwire: can't find mapping entry");
|
|
|
|
KASSERT((pv->pv_tlbprot & TLB_UNMANAGED) == 0);
|
|
if (pv->pv_tlbprot & TLB_WIRED) {
|
|
KASSERT(pmap->pmap_stats.wired_count > 0);
|
|
pv->pv_tlbprot &= ~TLB_WIRED;
|
|
pmap->pmap_stats.wired_count--;
|
|
}
|
|
splx(s);
|
|
simple_unlock(&pmap->pmap_lock);
|
|
}
|
|
|
|
/*
|
|
* pmap_extract(pmap, va, pap)
|
|
* fills in the physical address corrsponding to the
|
|
* virtual address specified by pmap and va into the
|
|
* storage pointed to by pap and returns TRUE if the
|
|
* virtual address is mapped. returns FALSE in not mapped.
|
|
*/
|
|
boolean_t
|
|
pmap_extract(pmap_t pmap, vaddr_t va, paddr_t *pap)
|
|
{
|
|
struct pv_entry *pv;
|
|
vaddr_t off;
|
|
int s;
|
|
|
|
off = va;
|
|
off -= (va = hppa_trunc_page(va));
|
|
|
|
s = splvm();
|
|
if ((pv = pmap_pv_find_va(pmap_sid(pmap, va), va))) {
|
|
if (pap != NULL)
|
|
*pap = tlbptob(pv->pv_tlbpage) + off;
|
|
PMAP_PRINTF(PDB_EXTRACT, ("(%p, %p) = %p\n",
|
|
pmap, (caddr_t)va,
|
|
(caddr_t)(tlbptob(pv->pv_tlbpage) + off)));
|
|
} else {
|
|
PMAP_PRINTF(PDB_EXTRACT, ("(%p, %p) unmapped\n",
|
|
pmap, (caddr_t)va));
|
|
}
|
|
splx(s);
|
|
return (pv != NULL);
|
|
}
|
|
|
|
/*
|
|
* pmap_zero_page(pa)
|
|
*
|
|
* Zeros the specified page.
|
|
*/
|
|
void
|
|
pmap_zero_page(paddr_t pa)
|
|
{
|
|
struct pv_entry *pv;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_ZERO, ("(%p)\n", (caddr_t)pa));
|
|
|
|
s = splvm(); /* XXX are we already that high? */
|
|
|
|
/* Map the physical page. */
|
|
pv = pmap_pv_enter(pmap_kernel(), HPPA_SID_KERNEL, tmp_vpages[1], pa,
|
|
TLB_AR_KRW | TLB_UNMANAGED | TLB_WIRED);
|
|
|
|
/* Zero it. */
|
|
memset((caddr_t)tmp_vpages[1], 0, PAGE_SIZE);
|
|
|
|
/* Unmap the physical page. */
|
|
pmap_pv_remove(pv);
|
|
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* pmap_copy_page(src, dst)
|
|
*
|
|
* pmap_copy_page copies the src page to the destination page. If a mapping
|
|
* can be found for the source, we use that virtual address. Otherwise, a
|
|
* slower physical page copy must be done. The destination is always a
|
|
* physical address sivnce there is usually no mapping for it.
|
|
*/
|
|
void
|
|
pmap_copy_page(paddr_t spa, paddr_t dpa)
|
|
{
|
|
struct pv_entry *spv, *dpv;
|
|
int s;
|
|
|
|
PMAP_PRINTF(PDB_COPY, ("(%p, %p)\n", (caddr_t)spa, (caddr_t)dpa));
|
|
|
|
s = splvm(); /* XXX are we already that high? */
|
|
|
|
/* Map the two pages. */
|
|
spv = pmap_pv_enter(pmap_kernel(), HPPA_SID_KERNEL, tmp_vpages[0], spa,
|
|
TLB_AR_KR | TLB_UNMANAGED | TLB_WIRED);
|
|
dpv = pmap_pv_enter(pmap_kernel(), HPPA_SID_KERNEL, tmp_vpages[1], dpa,
|
|
TLB_AR_KRW | TLB_UNMANAGED | TLB_WIRED);
|
|
|
|
/* Do the copy. */
|
|
memcpy((caddr_t)tmp_vpages[1], (const caddr_t)tmp_vpages[0], PAGE_SIZE);
|
|
|
|
/* Unmap the pages. */
|
|
pmap_pv_remove(spv);
|
|
pmap_pv_remove(dpv);
|
|
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Given a PA and a bit, this tests and clears that bit in
|
|
* the modref information for the PA.
|
|
*/
|
|
static inline boolean_t pmap_clear_bit(paddr_t, u_int);
|
|
static inline boolean_t
|
|
pmap_clear_bit(paddr_t pa, u_int tlbprot_bit)
|
|
{
|
|
int table_off;
|
|
struct pv_head *hpv;
|
|
u_int pv_head_bit;
|
|
boolean_t ret;
|
|
int s;
|
|
|
|
table_off = pmap_table_find_pa(pa);
|
|
KASSERT(table_off >= 0);
|
|
hpv = pv_head_tbl + table_off;
|
|
pv_head_bit = (tlbprot_bit == TLB_REF ? PV_HEAD_REF : PV_HEAD_DIRTY);
|
|
s = splvm();
|
|
_pmap_pv_update(pa, NULL, tlbprot_bit, 0);
|
|
ret = hpv->pv_head_writable_dirty_ref & pv_head_bit;
|
|
hpv->pv_head_writable_dirty_ref &= ~pv_head_bit;
|
|
splx(s);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Given a PA and a bit, this tests that bit in the modref
|
|
* information for the PA.
|
|
*/
|
|
static inline boolean_t pmap_test_bit(paddr_t, u_int);
|
|
static inline boolean_t
|
|
pmap_test_bit(paddr_t pa, u_int tlbprot_bit)
|
|
{
|
|
int table_off;
|
|
struct pv_head *hpv;
|
|
u_int pv_head_bit;
|
|
struct pv_entry *pv;
|
|
boolean_t ret;
|
|
int s;
|
|
|
|
table_off = pmap_table_find_pa(pa);
|
|
KASSERT(table_off >= 0);
|
|
hpv = pv_head_tbl + table_off;
|
|
pv_head_bit = (tlbprot_bit == TLB_REF ? PV_HEAD_REF : PV_HEAD_DIRTY);
|
|
s = splvm();
|
|
ret = (hpv->pv_head_writable_dirty_ref & pv_head_bit) != 0;
|
|
if (!ret) {
|
|
for (pv = hpv->pv_head_pvs;
|
|
pv != NULL;
|
|
pv = pv->pv_next) {
|
|
if ((pv->pv_tlbprot & (TLB_UNMANAGED | tlbprot_bit)) ==
|
|
tlbprot_bit) {
|
|
hpv->pv_head_writable_dirty_ref |= pv_head_bit;
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
splx(s);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* pmap_clear_modify(pa)
|
|
* clears the hardware modified ("dirty") bit for one
|
|
* machine independant page starting at the given
|
|
* physical address. phys must be aligned on a machine
|
|
* independant page boundary.
|
|
*/
|
|
boolean_t
|
|
pmap_clear_modify(struct vm_page *pg)
|
|
{
|
|
paddr_t pa = VM_PAGE_TO_PHYS(pg);
|
|
boolean_t ret = pmap_clear_bit(pa, TLB_DIRTY);
|
|
PMAP_PRINTF(PDB_BITS, ("(%p) = %d\n", (caddr_t)pa, ret));
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* pmap_is_modified(pa)
|
|
* returns TRUE if the given physical page has been modified
|
|
* since the last call to pmap_clear_modify().
|
|
*/
|
|
boolean_t
|
|
pmap_is_modified(struct vm_page *pg)
|
|
{
|
|
paddr_t pa = VM_PAGE_TO_PHYS(pg);
|
|
boolean_t ret = pmap_test_bit(pa, TLB_DIRTY);
|
|
PMAP_PRINTF(PDB_BITS, ("(%p) = %d\n", (caddr_t)pa, ret));
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* pmap_clear_reference(pa)
|
|
* clears the hardware referenced bit in the given machine
|
|
* independant physical page.
|
|
*
|
|
* Currently, we treat a TLB miss as a reference; i.e. to clear
|
|
* the reference bit we flush all mappings for pa from the TLBs.
|
|
*/
|
|
boolean_t
|
|
pmap_clear_reference(struct vm_page *pg)
|
|
{
|
|
paddr_t pa = VM_PAGE_TO_PHYS(pg);
|
|
boolean_t ret = pmap_clear_bit(pa, TLB_REF);
|
|
PMAP_PRINTF(PDB_BITS, ("(%p) = %d\n", (caddr_t)pa, ret));
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* pmap_is_referenced(pa)
|
|
* returns TRUE if the given physical page has been referenced
|
|
* since the last call to pmap_clear_reference().
|
|
*/
|
|
boolean_t
|
|
pmap_is_referenced(struct vm_page *pg)
|
|
{
|
|
paddr_t pa = VM_PAGE_TO_PHYS(pg);
|
|
boolean_t ret = pmap_test_bit(pa, TLB_REF);
|
|
PMAP_PRINTF(PDB_BITS, ("(%p) = %d\n", (caddr_t)pa, ret));
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot)
|
|
{
|
|
u_int tlbprot;
|
|
int s;
|
|
#ifdef PMAPDEBUG
|
|
int opmapdebug = pmapdebug;
|
|
|
|
/*
|
|
* If we're being told to map page zero, we can't
|
|
* call printf() at all, because doing so would
|
|
* lead to an infinite recursion on this call.
|
|
* (printf requires page zero to be mapped).
|
|
*/
|
|
if (va == 0)
|
|
pmapdebug = 0;
|
|
#endif /* PMAPDEBUG */
|
|
|
|
PMAP_PRINTF(PDB_KENTER, ("(%p, %p, %x)\n",
|
|
(caddr_t)va, (caddr_t)pa, prot));
|
|
va = hppa_trunc_page(va);
|
|
tlbprot = TLB_WIRED | TLB_UNMANAGED;
|
|
tlbprot |= (prot & PMAP_NC) ? TLB_UNCACHEABLE : 0;
|
|
tlbprot |= pmap_prot(pmap_kernel(), prot & VM_PROT_ALL);
|
|
s = splvm();
|
|
KASSERT(pmap_pv_find_va(HPPA_SID_KERNEL, va) == NULL);
|
|
pmap_pv_enter(pmap_kernel(), HPPA_SID_KERNEL, va, pa, tlbprot);
|
|
splx(s);
|
|
|
|
#ifdef PMAPDEBUG
|
|
pmapdebug = opmapdebug;
|
|
#endif /* PMAPDEBUG */
|
|
}
|
|
|
|
void
|
|
pmap_kremove(vaddr_t va, vsize_t size)
|
|
{
|
|
struct pv_entry *pv;
|
|
int s;
|
|
#ifdef PMAPDEBUG
|
|
int opmapdebug = pmapdebug;
|
|
|
|
/*
|
|
* If we're being told to unmap page zero, we can't
|
|
* call printf() at all, because doing so would
|
|
* lead to an infinite recursion on this call.
|
|
* (printf requires page zero to be mapped).
|
|
*/
|
|
if (va == 0)
|
|
pmapdebug = 0;
|
|
#endif /* PMAPDEBUG */
|
|
|
|
PMAP_PRINTF(PDB_KENTER, ("(%p, %x)\n",
|
|
(caddr_t)va, (u_int)size));
|
|
|
|
size += va;
|
|
va = hppa_trunc_page(va);
|
|
size -= va;
|
|
s = splvm();
|
|
for (size = hppa_round_page(size); size;
|
|
size -= PAGE_SIZE, va += PAGE_SIZE) {
|
|
pv = pmap_pv_find_va(HPPA_SID_KERNEL, va);
|
|
if (pv) {
|
|
KASSERT((pv->pv_tlbprot & TLB_UNMANAGED) != 0);
|
|
pmap_pv_remove(pv);
|
|
} else {
|
|
PMAP_PRINTF(PDB_REMOVE, (": no pv for %p\n",
|
|
(caddr_t)va));
|
|
}
|
|
}
|
|
splx(s);
|
|
#ifdef PMAPDEBUG
|
|
pmapdebug = opmapdebug;
|
|
#endif /* PMAPDEBUG */
|
|
}
|
|
|
|
/*
|
|
* pmap_redzone(sva, eva, create)
|
|
* creates or removes a red zone in already mapped and wired memory,
|
|
* from [sva, eva) in the kernel map.
|
|
*/
|
|
void
|
|
pmap_redzone(vaddr_t sva, vaddr_t eva, int create)
|
|
{
|
|
vaddr_t va;
|
|
struct pv_entry *pv;
|
|
u_int tlbprot;
|
|
int s;
|
|
|
|
sva = hppa_trunc_page(sva);
|
|
tlbprot = (create ? TLB_AR_NA : TLB_AR_KRW);
|
|
s = splvm();
|
|
for(va = sva; va < eva; va += PAGE_SIZE) {
|
|
pv = pmap_pv_find_va(HPPA_SID_KERNEL, va);
|
|
KASSERT(pv != NULL);
|
|
/*
|
|
* Compare new protection with old to see if
|
|
* anything needs to be changed.
|
|
*/
|
|
if ((pv->pv_tlbprot & TLB_AR_MASK) != tlbprot)
|
|
pmap_pv_update(pv, TLB_AR_MASK, tlbprot);
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
#if defined(PMAPDEBUG) && defined(DDB)
|
|
#include <ddb/db_output.h>
|
|
/*
|
|
* prints whole va->pa (aka HPT or HVT)
|
|
*/
|
|
void
|
|
pmap_hptdump(void)
|
|
{
|
|
struct hpt_entry *hpt, *ehpt;
|
|
struct pv_entry *pv;
|
|
|
|
mfctl(CR_HPTMASK, ehpt);
|
|
mfctl(CR_VTOP, hpt);
|
|
ehpt = (struct hpt_entry *)((int)hpt + (int)ehpt + 1);
|
|
db_printf("HPT dump %p-%p:\n", hpt, ehpt);
|
|
for (; hpt < ehpt; hpt++)
|
|
if (hpt->hpt_valid || hpt->hpt_entry) {
|
|
db_printf("hpt@%p: %x{%sv=%x:%x},%b,%x\n",
|
|
hpt, *(int *)hpt, (hpt->hpt_valid?"ok,":""),
|
|
hpt->hpt_space, hpt->hpt_vpn << 9,
|
|
hpt->hpt_tlbprot, TLB_BITS,
|
|
tlbptob(hpt->hpt_tlbpage));
|
|
for (pv = hpt->hpt_entry; pv; pv = pv->pv_hash)
|
|
db_printf(" pv={%p,%x:%x,%b,%x}->%p\n",
|
|
pv->pv_pmap, pv->pv_space, pv->pv_va,
|
|
pv->pv_tlbprot, TLB_BITS,
|
|
tlbptob(pv->pv_tlbpage), pv->pv_hash);
|
|
}
|
|
}
|
|
#endif
|