Implement support for Address Space Numbers, greatly reducing the number

of TLB and I-cache flushes, significantly speeding up context switches.

Once again, many thanks to Chris Demetriou and Ross Harvey for code
review and debugging assistance!
This commit is contained in:
thorpej 1998-03-22 05:41:37 +00:00
parent a8d86e5a7c
commit c0cc1ed476

View File

@ -1,4 +1,4 @@
/* $NetBSD: pmap.old.c,v 1.64 1998/03/18 23:55:25 thorpej Exp $ */
/* $NetBSD: pmap.old.c,v 1.65 1998/03/22 05:41:37 thorpej Exp $ */
/*-
* Copyright (c) 1998 The NetBSD Foundation, Inc.
@ -98,6 +98,9 @@
* Support for the new UVM pmap interface was written by
* Jason R. Thorpe.
*
* Support for ASNs was written by Jason R. Thorpe, again
* with help from Chris Demetriou and Ross Harvey.
*
* Notes:
*
* All page table access is done via K0SEG. The one exception
@ -117,11 +120,26 @@
*
* Bugs/misfeatures:
*
* Does not currently support ASNs. Support for ASNs would
* eliminate a good deal of TLB invalidation.
* Does not currently support multiple processors. Problems:
*
* Does not currently support multiple processors. At the very
* least, TLB shootdown is needed.
* - There is no locking protocol to speak of.
*
* - pmap_activate() and pmap_deactivate() are called
* from a critical section in locore, and cannot lock
* the pmap (because doing so may sleep). Thus, they
* must be rewritten to use ldq_l and stq_c to update
* the CPU mask.
*
* - ASN logic needs minor adjustments for multiple processors:
*
* - pmap_next_asn and pmap_asn_generation need
* to be changed to arrays indexed by processor
* number.
*
* - A similar change needs to happen for the pmap
* structure's pm_asn and pm_asngen members.
*
* - TLB shootdown code needs to be written.
*/
/*
@ -155,7 +173,7 @@
#include <sys/cdefs.h> /* RCS ID & Copyright macro defns */
__KERNEL_RCSID(0, "$NetBSD: pmap.old.c,v 1.64 1998/03/18 23:55:25 thorpej Exp $");
__KERNEL_RCSID(0, "$NetBSD: pmap.old.c,v 1.65 1998/03/22 05:41:37 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -223,19 +241,20 @@ struct chgstats {
#endif
#ifdef DEBUG
#define PDB_FOLLOW 0x0001
#define PDB_INIT 0x0002
#define PDB_ENTER 0x0004
#define PDB_REMOVE 0x0008
#define PDB_CREATE 0x0010
#define PDB_PTPAGE 0x0020
#define PDB_BITS 0x0080
#define PDB_COLLECT 0x0100
#define PDB_PROTECT 0x0200
#define PDB_BOOTSTRAP 0x1000
#define PDB_PARANOIA 0x2000
#define PDB_WIRING 0x4000
#define PDB_PVDUMP 0x8000
#define PDB_FOLLOW 0x0001
#define PDB_INIT 0x0002
#define PDB_ENTER 0x0004
#define PDB_REMOVE 0x0008
#define PDB_CREATE 0x0010
#define PDB_PTPAGE 0x0020
#define PDB_ASN 0x0040
#define PDB_BITS 0x0080
#define PDB_COLLECT 0x0100
#define PDB_PROTECT 0x0200
#define PDB_BOOTSTRAP 0x1000
#define PDB_PARANOIA 0x2000
#define PDB_WIRING 0x4000
#define PDB_PVDUMP 0x8000
int debugmap = 0;
int pmapdebug = PDB_PARANOIA;
@ -319,6 +338,67 @@ int pv_nfree;
*/
LIST_HEAD(, pmap) pmap_all_pmaps;
/*
* Address Space Numbers.
*
* On many implementations of the Alpha architecture, the TLB entries and
* I-cache blocks are tagged with a unique number within an implementation-
* specified range. When a process context becomes active, the ASN is used
* to match TLB entries; if a TLB entry for a particular VA does not match
* the current ASN, it is ignored (one could think of the processor as
* having a collection of <max ASN> separate TLBs). This allows operating
* system software to skip the TLB flush that would otherwise be necessary
* at context switch time.
*
* Alpha PTEs have a bit in them (PG_ASM - Address Space Match) that
* causes TLB entries to match any ASN. The PALcode also provides
* a TBI (Translation Buffer Invalidate) operation that flushes all
* TLB entries that _do not_ have PG_ASM. We use this bit for kernel
* mappings, so that invalidation of all user mappings does not invalidate
* kernel mappings (which are consistent across all processes).
*
* pmap_next_asn always indicates to the next ASN to use. When
* pmap_next_asn exceeds pmap_max_asn, we start a new ASN generation.
*
* When a new ASN generation is created, the per-process (i.e. non-PG_ASM)
* TLB entries and the I-cache are flushed, the generation number is bumped,
* and pmap_next_asn is changed to indicate the first non-reserved ASN.
*
* We reserve ASN #0 for pmaps that use the global Lev1map. This prevents
* the following scenario:
*
* * New ASN generation starts, and process A is given ASN #0.
*
* * A new process B (and thus new pmap) is created. The ASN,
* for lack of a better value, is initialized to 0.
*
* * Process B runs. It is now using the TLB entries tagged
* by process A. *poof*
*
* In the scenario above, in addition to the processor using using incorrect
* TLB entires, the PALcode might use incorrect information to service a
* TLB miss. (The PALcode uses the recursively mapped Virtual Page Table
* to locate the PTE for a faulting address, and tagged TLB entires exist
* for the Virtual Page Table addresses in order to speed up this procedure,
* as well.)
*
* By reserving an ASN for Lev1map users, we are guaranteeing that
* new pmaps will initially run with no TLB entries for user addresses
* or VPT mappings that map user page tables. Since Lev1map only
* contains mappings for kernel addresses, and since those mappings
* are always made with PG_ASM, sharing an ASN for Lev1map users is
* safe (since PG_ASM mappings match any ASN).
*
* On processors that do not support ASNs, the PALcode invalidates
* the TLB and I-cache automatically on swpctx. We still still go
* through the motions of assigning an ASN (really, just refreshing
* the ASN generation in this particular case) to keep the logic sane
* in other parts of the code.
*/
u_int pmap_max_asn; /* max ASN supported by the system */
u_int pmap_next_asn; /* next free ASN to use */
u_long pmap_asn_generation; /* current ASN generation */
#define PAGE_IS_MANAGED(pa) (vm_physseg_find(atop(pa), NULL) != -1)
static __inline struct pv_head *pa_to_pvh __P((vm_offset_t));
@ -359,6 +439,11 @@ struct pv_entry *pmap_alloc_pv __P((void));
void pmap_free_pv __P((struct pv_entry *));
void pmap_collect_pv __P((void));
/*
* ASN management functions.
*/
void pmap_alloc_asn __P((pmap_t));
/*
* Misc. functions.
*/
@ -374,8 +459,78 @@ void pmap_pvdump __P((vm_offset_t));
*
* Check to see if a pmap is active on the current processor.
*/
#ifdef DEBUG
#define active_pmap(pm) \
({ \
int isactive_ = \
(((pm)->pm_cpus & (1UL << alpha_pal_whami())) != 0); \
\
if (isactive_) { \
if (curproc == NULL || \
(pm) != curproc->p_vmspace->vm_map.pmap) { \
printf("line %ld, false TRUE\n", __LINE__); \
panic("active_pmap"); \
} \
} else { \
if (curproc != NULL && \
(pm) == curproc->p_vmspace->vm_map.pmap) { \
printf("line %ld, false FALSE\n", __LINE__); \
panic("active_pmap"); \
} \
} \
\
(isactive_); \
})
#else
#define active_pmap(pm) \
(((pm)->pm_cpus & (1UL << alpha_pal_whami())) != 0)
#endif /* DEBUG */
/*
* PMAP_ACTIVATE_ASN_SANITY:
*
* DEBUG sanity checks for ASNs within PMAP_ACTIVATE.
*/
#ifdef DEBUG
#define PMAP_ACTIVATE_ASN_SANITY(pmap) \
do { \
if ((pmap)->pm_lev1map == Lev1map) { \
/* \
* This pmap implementation also ensures that \
* pmaps referencing Lev1map use a reserved ASN \
* to prevent the PALcode from servicing a TLB miss \
* with the wrong PTE. \
*/ \
if ((pmap)->pm_asn != PMAP_ASN_RESERVED) { \
printf("Lev1map with non-reserved ASN " \
"(line %ld)\n", __LINE__); \
panic("PMAP_ACTIVATE_ASN_SANITY"); \
} \
} else { \
if ((pmap)->pm_asngen != pmap_asn_generation) { \
/* \
* ASN generation number isn't valid! \
*/ \
printf("pmap asngen %lu, current %lu " \
"(line %ld)\n", \
(pmap)->pm_asngen, pmap_asn_generation, \
__LINE__); \
panic("PMAP_ACTIVATE_ASN_SANITY"); \
} \
if ((pmap)->pm_asn == PMAP_ASN_RESERVED) { \
/* \
* DANGER WILL ROBINSON! We're going to \
* pollute the VPT TLB entries! \
*/ \
printf("Using reserved ASN! (line %ld)\n", \
__LINE__); \
panic("PMAP_ACTIVATE_ASN_SANITY"); \
} \
} \
} while (0)
#else
#define PMAP_ACTIVATE_ASN_SANITY(pmap) /* nothing */
#endif
/*
* PMAP_ACTIVATE:
@ -383,11 +538,20 @@ void pmap_pvdump __P((vm_offset_t));
* This is essentially the guts of pmap_activate(), without
* ASN allocation. This is used by pmap_activate(),
* pmap_create_lev1map(), and pmap_destroy_lev1map().
*
* This is called only when it is known that a pmap is "active"
* on the current processor; the ASN must already be valid.
*
* NOTE: This must be written such that no locking of the pmap
* is necessary! See pmap_activate().
*/
#define PMAP_ACTIVATE(pmap, p) \
do { \
PMAP_ACTIVATE_ASN_SANITY(pmap); \
\
(p)->p_addr->u_pcb.pcb_hw.apcb_ptbr = \
ALPHA_K0SEG_TO_PHYS((vm_offset_t)(pmap)->pm_lev1map) >> PGSHIFT; \
(p)->p_addr->u_pcb.pcb_hw.apcb_asn = (pmap)->pm_asn; \
\
if ((p) == curproc) { \
/* \
@ -395,13 +559,27 @@ do { \
* our own context again so that it will take effect. \
*/ \
(void) alpha_pal_swpctx((u_long)p->p_md.md_pcbpaddr); \
\
/* XXX These go away if we use ASNs. */ \
ALPHA_TBIAP(); \
alpha_pal_imb(); \
} \
} while (0)
/*
* PMAP_INVALIDATE_ASN:
*
* Invalidate the specified pmap's ASN, so as to force allocation
* of a new one the next time pmap_alloc_asn() is called.
*
* NOTE: THIS MUST ONLY BE CALLED IF AT LEAST ONE OF THE FOLLOWING
* CONDITIONS ARE TRUE:
*
* (1) The pmap references the global Lev1map.
*
* (2) The pmap is not active on the current processor.
*/
#define PMAP_INVALIDATE_ASN(pmap) \
do { \
(pmap)->pm_asn = PMAP_ASN_RESERVED; \
} while (0)
/*
* PMAP_INVALIDATE_TLB:
*
@ -411,13 +589,26 @@ do { \
do { \
if ((hadasm) || (isactive)) { \
/* \
* Simply invalidating the TLB entry and I-stream \
* Simply invalidating the TLB entry and I-cache \
* works in this case. \
*/ \
ALPHA_TBIS((va)); \
if ((doimb)) \
alpha_pal_imb(); \
} else if ((pmap)->pm_asngen == pmap_asn_generation) { \
/* \
* We can't directly invalidate the TLB entry \
* in this case, so we have to force allocation \
* of a new ASN the next time this pmap becomes \
* active. \
*/ \
PMAP_INVALIDATE_ASN((pmap)); \
} \
/* \
* Nothing to do in this case; the next time the \
* pmap becomes active on this processor, a new \
* ASN will be allocated anyway. \
*/ \
} while (0)
/*
@ -578,18 +769,33 @@ pmap_bootstrap(ptaddr, maxasn)
LIST_INIT(&pmap_all_pmaps);
/*
* Initialize kernel pmap.
* Initialize the ASN logic.
*/
pmap_max_asn = maxasn;
pmap_next_asn = 1;
pmap_asn_generation = 0;
/*
* Initialize kernel pmap. Note that all kernel mappings
* have PG_ASM set, so the ASN doesn't really matter for
* the kernel pmap. Also, since the kernel pmap always
* references Lev1map, it always has an invalid ASN
* generation.
*/
pmap_kernel()->pm_lev1map = Lev1map;
pmap_kernel()->pm_count = 1;
pmap_kernel()->pm_asn = PMAP_ASN_RESERVED;
pmap_kernel()->pm_asngen = pmap_asn_generation;
simple_lock_init(&pmap_kernel()->pm_lock);
LIST_INSERT_HEAD(&pmap_all_pmaps, pmap_kernel(), pm_list);
/*
* Set up proc0's PCB such that the ptbr points to the right place.
* Set up proc0's PCB such that the ptbr points to the right place
* and has the kernel pmap's (really unused) ASN.
*/
proc0.p_addr->u_pcb.pcb_hw.apcb_ptbr =
ALPHA_K0SEG_TO_PHYS((vm_offset_t)Lev1map) >> PGSHIFT;
proc0.p_addr->u_pcb.pcb_hw.apcb_asn = pmap_kernel()->pm_asn;
}
#ifdef _PMAP_MAY_USE_PROM_CONSOLE
@ -809,6 +1015,8 @@ pmap_pinit(pmap)
pmap->pm_lev1map = Lev1map;
pmap->pm_count = 1;
pmap->pm_asn = PMAP_ASN_RESERVED;
pmap->pm_asngen = pmap_asn_generation;
simple_lock_init(&pmap->pm_lock);
LIST_INSERT_HEAD(&pmap_all_pmaps, pmap, pm_list);
}
@ -1736,6 +1944,11 @@ pmap_collect(pmap)
* Activate the pmap used by the specified process. This includes
* reloading the MMU context if the current process, and marking
* the pmap in use by the processor.
*
* NOTE: This is called from a critical section in locore. This
* means we cannot lock the pmap, because doing so may sleep!
* Instead, we have to ensure that operations performed in this
* function do not require locking!
*/
void
pmap_activate(p)
@ -1748,7 +1961,10 @@ pmap_activate(p)
printf("pmap_activate(%p)\n", p);
#endif
/* XXX Allocate an ASN. */
/*
* Allocate an ASN.
*/
pmap_alloc_asn(pmap);
/*
* Mark the pmap in use by this processor.
@ -1763,6 +1979,9 @@ pmap_activate(p)
*
* Mark that the pmap used by the specified process is no longer
* in use by the processor.
*
* The comment above pmap_activate() wrt. locking applies here,
* as well.
*/
void
pmap_deactivate(p)
@ -2191,7 +2410,9 @@ pmap_remove_mapping(pmap, va, pte)
* miss using the stale VPT TLB entry it entered
* behind our back to shortcut to the VA's PTE.
*/
ALPHA_TBIS((vm_offset_t)(&VPT[VPT_INDEX(va)]));
PMAP_INVALIDATE_TLB(pmap,
(vm_offset_t)(&VPT[VPT_INDEX(va)]), FALSE,
active_pmap(pmap), FALSE);
/*
* We've freed a level 3 table, so delete the
@ -2768,9 +2989,9 @@ pmap_alloc_physpage()
vm_offset_t pa;
#if defined(UVM)
if ((pg = uvm_pagealloc(NULL, 0, NULL)) == NULL) {
if ((pg = uvm_pagealloc(NULL, 0, NULL)) == NULL)
#else
if ((pg = vm_page_alloc1()) == NULL) {
if ((pg = vm_page_alloc1()) == NULL)
#endif
/*
* XXX This is lame. We can probably wait for the
@ -2778,7 +2999,6 @@ pmap_alloc_physpage()
* XXX page from another pmap.
*/
panic("pmap_alloc_physpage: no pages available");
}
pa = VM_PAGE_TO_PHYS(pg);
pmap_zero_page(pa);
@ -2831,6 +3051,9 @@ pmap_create_lev1map(pmap)
#ifdef DIAGNOSTIC
if (pmap == pmap_kernel())
panic("pmap_create_lev1map: got kernel pmap");
if (pmap->pm_asn != PMAP_ASN_RESERVED)
panic("pmap_create_lev1map: pmap uses non-reserved ASN");
#endif
/*
@ -2865,10 +3088,7 @@ pmap_create_lev1map(pmap)
* reactivate it.
*/
if (active_pmap(pmap)) {
#ifdef DIAGNOSTIC
if (curproc == NULL || pmap != curproc->p_vmspace->vm_map.pmap)
panic("pmap_create_lev1map: active inconsistency");
#endif
pmap_alloc_asn(pmap);
PMAP_ACTIVATE(pmap, curproc);
}
}
@ -2899,13 +3119,24 @@ pmap_destroy_lev1map(pmap)
/*
* The page table base has changed; if the pmap was active,
* reactivate it.
* reactivate it. Note that allocation of a new ASN is
* not necessary here:
*
* (1) We've gotten here because we've deleted all
* user mappings in the pmap, invalidating the
* TLB entries for them as we go.
*
* (2) Lev1map contains only kernel mappings, which
* were identical in the user pmap, and all of
* those mappings have PG_ASM, so the ASN doesn't
* matter.
*
* We do, however, ensure that the pmap is using the
* reserved ASN, to ensure that no two pmaps never have
* clashing TLB entries.
*/
if (active_pmap(pmap)) {
#ifdef DIAGNOSTIC
if (curproc == NULL || pmap != curproc->p_vmspace->vm_map.pmap)
panic("pmap_destroy_lev1map: active inconsistency");
#endif
PMAP_INVALIDATE_ASN(pmap);
PMAP_ACTIVATE(pmap, curproc);
}
@ -3061,3 +3292,136 @@ pmap_ptpage_delref(pte)
return (pvh->pvh_ptref);
}
/******************** Address Space Number management ********************/
/*
* pmap_alloc_asn:
*
* Allocate and assign an ASN to the specified pmap.
*/
void
pmap_alloc_asn(pmap)
pmap_t pmap;
{
#ifdef DEBUG
if (pmapdebug & (PDB_FOLLOW|PDB_ASN))
printf("pmap_alloc_asn(%p)\n", pmap);
#endif
#ifdef DIAGNOSTIC
if (pmap == pmap_kernel())
panic("pmap_alloc_asn: got kernel pmap");
#endif
/*
* If the pmap is still using the global Lev1map, there
* is no need to assign an ASN at this time, because only
* kernel mappings exist in that map, and all kernel mappings
* have PG_ASM set. If the pmap eventually gets its own
* lev1map, an ASN will be allocated at that time.
*/
if (pmap->pm_lev1map == Lev1map) {
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_alloc_asn: still references Lev1map\n");
#endif
#ifdef DIAGNOSTIC
if (pmap->pm_asn != PMAP_ASN_RESERVED)
panic("pmap_alloc_asn: Lev1map without "
"PMAP_ASN_RESERVED");
#endif
return;
}
/*
* On processors which do not implement ASNs, the swpctx PALcode
* operation will automatically invalidate the TLB and I-cache,
* so we don't need to do that here.
*/
if (pmap_max_asn == 0) {
/*
* Refresh the pmap's generation number, to
* simplify logic elsewhere.
*/
pmap->pm_asngen = pmap_asn_generation;
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_alloc_asn: no ASNs, using asngen %lu\n",
pmap->pm_asngen);
#endif
return;
}
/*
* Hopefully, we can continue using the one we have...
*/
if (pmap->pm_asn != PMAP_ASN_RESERVED &&
pmap->pm_asngen == pmap_asn_generation) {
/*
* ASN is still in the current generation; keep on using it.
*/
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_alloc_asn: same generation, keeping %u\n",
pmap->pm_asn);
#endif
return;
}
/*
* Need to assign a new ASN. Grab the next one, incrementing
* the generation number if we have to.
*/
if (pmap_next_asn > pmap_max_asn) {
/*
* Invalidate all non-PG_ASM TLB entries and the
* I-cache, and bump the generation number.
*/
ALPHA_TBIAP();
alpha_pal_imb();
pmap_next_asn = 1;
pmap_asn_generation++;
#ifdef DIAGNOSTIC
if (pmap_asn_generation == 0) {
/*
* The generation number has wrapped. We could
* handle this scenario by traversing all of
* the pmaps, and invaldating the generation
* number on those which are not currently
* in use by this processor.
*
* However... considering that we're using
* an unsigned 64-bit integer for generation
* numbers, on non-ASN CPUs, we won't wrap
* for approx. 585 million years, or 75 billion
* years on a 128-ASN CPU (assuming 1000 switch
* operations per second).
*
* So, we don't bother.
*/
panic("pmap_alloc_asn: too much uptime");
}
#endif
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_alloc_asn: generation bumped to %lu\n",
pmap_asn_generation);
#endif
}
/*
* Assign the new ASN and validate the generation number.
*/
pmap->pm_asn = pmap_next_asn++;
pmap->pm_asngen = pmap_asn_generation;
#ifdef DEBUG
if (pmapdebug & PDB_ASN)
printf("pmap_alloc_asn: assigning %u to pmap %p\n",
pmap->pm_asn, pmap);
#endif
}