/* $NetBSD: pmap.c,v 1.38.8.1 2017/11/02 21:29:51 snj Exp $ */ /*- * Copyright (c) 1997, 1998, 2000 Ben Harris * 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. 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. */ /* * pmap.c - Interfacing the MEMC to the BSD VM system. */ /* * The MMU on ARM2 and ARM3 systems is an Acorn custom chip called MEMC * (Anna to her friends). Each MEMC can handle up to 4MB of local DRAM, * split into 128 pages. Thus, a system with 16MB of RAM will have four * MEMCs co-operating to run it. In addition the MMU, the master MEMC * in a system handles video and sound DMA, the system clocks and the * address decoding necessary to control accesses to the I/O system, ROMs * and VIDC. * * The memory layout provided by the MEMC is mostly fixed, with the bottom * 32 MB being logically-mapped RAM and the next 16 MB being physically- * mapped RAM. * * The MEMC provides some rudimentary access control over memory and I/O * devices. Code running in USR mode is allowed access to ROM and to those * parts of logical RAM to which it's been granted access. Code running in * SVC mode can access everything. The fact that SVC-mode code can't be * prevented from writing to RAM makes it very difficult to emulate modified * bits for pages that might be accessed from SVC mode. * * The page tables in the MEMC are implemented using content-addressable * memory, with one cell for each physical page. This means that it's * impossible to have one physical page mapped in two locations. For this * reason, we try to treat the MEMC as a TLB, and to keep enough information * around to quickly re-load mappings without troubling uvm_fault(). * Having multiple physical pages mapped to the same logical page is possible, * and we arrange to map all unused physical pages in the same place. * Accessing the logical page in question has undefined results, though, as * it may end up with multiple DRAMs trying to drive the data bus at once. * * The MEMC has variable page sizes, and moreover it's necessary to * change the page size depending on the amount of DRAM under the * MEMC's control. This code currently only handles 32k pages. * * The bottom 32k of logically-mapped RAM is zero page, and is mapped * in all address spaces (though it's not accessible in user mode). * This contains exception vectors. It doesn't appear in any pmap as * it's never unmapped. * * On an ARM3, the cache must be flushed whenever: * * 1 - We remove read access to a page, since we need to ensure reads * will abort. Write access doesn't matter since the cache is * write-through. * * 2 - We write to a page and need to be able to read it elsewhere. * Since the MEMC can only map each page once, this only occurs * without case one when one of the accesses is physical and the other * logical, which I think only happens when doing odd things with * /dev/mem. In all other cases, a physical page should only be * accessed through one of its mappings (virtual if it has one, * physical if it doesn't). pmap_find is useful for this. * * We assume the following rules apply: * * Accesses to memory managed by user pmaps will always be from USR * mode, or from LDR/STR with the T bit set (which looks the same to * the MEMC). * * Accesses to memory managed by the kernel pmap will always be from * SVC mode, and pmap_enter() will be explicitly told what to do to * the referenced/modified bits. * * Breaking these rules (especially the first) is likely to upset * referenced/modified emulation. */ #include "opt_ddb.h" #include "opt_uvmhist.h" #include "arcvideo.h" #include __KERNEL_RCSID(0, "$NetBSD: pmap.c,v 1.38.8.1 2017/11/02 21:29:51 snj Exp $"); #include /* for cold */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PMAP_DEBUG_MODIFIED #include #endif /* * We store all the mapping information in the pv_table, and the pmaps * just contain references to it. This is suggested in the Daemon * Book as a sensible thing to do on systems without real page tables. */ struct pv_entry { struct pv_entry *pv_next; pmap_t pv_pmap; int pv_ppn; int pv_lpn; uint32_t pv_activate; /* MEMC command to activate mapping */ uint32_t pv_deactivate;/* MEMC command to deactivate mapping */ vm_prot_t pv_prot; /* requested protection */ uint8_t pv_ppl; /* Actual PPL */ uint8_t pv_vflags; /* Per-mapping flags */ #define PV_WIRED 0x01 /* This is a wired mapping */ #define PV_UNMANAGED 0x02 /* Mapping was entered by pmap_kenter_*() */ /* From pv_pflags onwards is per-physical-page state. */ uint8_t pv_pflags; /* Per-physical-page flags */ #define PV_REFERENCED 0x01 #define PV_MODIFIED 0x02 #ifdef PMAP_DEBUG_MODIFIED unsigned char pv_md4sum[16]; #endif }; #define PM_NENTRIES 1024 struct pmap { int pm_count; /* Reference count */ int pm_flags; #define PM_ACTIVE 0x00000001 struct pmap_statistics pm_stats; struct pv_entry **pm_entries; }; /* * All physical pages must map to _some_ logical page, even though * they can have all access disabled. This means we need a logical * page reseved for this purpose. We use the last page of kernel VM. */ #define PMAP_DEAD_PAGE (atop(VM_MAX_KERNEL_ADDRESS) - 1) #define PMAP_UNMAP_ENTRY_32K(ppn) \ MEMC_TRANS_ENTRY_32K(ppn, PMAP_DEAD_PAGE, MEMC_PPL_NOACCESS) #if 0 {} /* shut up emacs */ #endif /* * Global array of pv_entries. Dynamically allocated at startup. */ struct pv_entry *pv_table; /* Kernel pmap -- statically allocated to make life slightly less odd. */ static struct pmap kernel_pmap_store; struct pmap *const kernel_pmap_ptr = &kernel_pmap_store; struct pv_entry *kernel_pmap_entries[PM_NENTRIES]; static bool pmap_initialised = false; static struct pool pmap_pool; static pmap_t active_pmap; UVMHIST_DECL(pmaphist); #ifdef UVMHIST static struct uvm_history_ent pmaphistbuf[100]; #endif void pmap_init2(void); static void pv_update(struct pv_entry *); static struct pv_entry *pv_alloc(void); static void pv_free(struct pv_entry *pv); static struct pv_entry *pv_get(pmap_t pmap, int ppn, int lpn); static void pv_release(pmap_t pmap, int ppn, int lpn); static int pmap_enter1(pmap_t, vaddr_t, paddr_t, vm_prot_t, u_int, int); static void *pmap_find(paddr_t); static void pmap_update_page(int); #ifdef PMAP_DEBUG_MODIFIED static void pmap_md4_page(unsigned char [16], paddr_t); #endif /* * No-one else wanted to take responsibility for the MEMC control register, * so it's ended up here. */ static register_t memc_control = 0; register_t update_memc(register_t mask, register_t value) { register_t old; int s; s = splhigh(); old = memc_control; memc_control &= ~mask; memc_control |= value & mask; MEMC_WRITE(memc_control); splx(s); return old; } /* * pmap_bootstrap: first-stage pmap initialisation * * This is called very early, and has to get the pmap system into a * state where pmap_virtual_space and pmap_kenter_pa at least will * work. If we need memory here, we have to work out how to get it * ourselves. */ void pmap_bootstrap(int npages, paddr_t zp_physaddr) { int i; struct pmap *pmap; size_t pv_table_size; UVMHIST_FUNC("pmap_bootstrap"); UVMHIST_INIT_STATIC(pmaphist, pmaphistbuf); UVMHIST_CALLED(pmaphist); /* Set up the bootstrap pv_table */ pv_table_size = round_page(physmem * sizeof(struct pv_entry)); pv_table = (struct pv_entry *)uvm_pageboot_alloc(pv_table_size); memset(pv_table, 0, pv_table_size); #ifdef PMAP_DEBUG_MODIFIED for (i = 0; i < physmem; i++) pv_table[i].pv_pflags |= PV_MODIFIED; #endif /* Set up the kernel's pmap */ pmap = pmap_kernel(); memset(pmap, 0, sizeof(*pmap)); pmap->pm_count = 1; pmap->pm_flags = PM_ACTIVE; /* Kernel pmap always is */ pmap->pm_entries = kernel_pmap_entries; memset(pmap->pm_entries, 0, sizeof(struct pv_entry *) * PM_NENTRIES); /* pmap_pinit(pmap); */ /* Clear the MEMC's page table */ /* XXX Maybe we should leave zero page alone? */ for (i=0; i < npages; i++) MEMC_WRITE(PMAP_UNMAP_ENTRY_32K(i)); /* TODO: Set up control register? */ update_memc(0xffffffff, MEMC_CONTROL | MEMC_CTL_PGSZ_32K | MEMC_CTL_LROMSPD_4N | MEMC_CTL_HROMSPD_4N | #if NARCVIDEO > 0 MEMC_CTL_RFRSH_FLYBACK | MEMC_CTL_VIDEODMA #else MEMC_CTL_RFRSH_CONTIN #endif ); /* Manually map zero page (it doesn't appear in pmaps) */ MEMC_WRITE(MEMC_TRANS_ENTRY_32K(atop(zp_physaddr), 0, MEMC_PPL_NOACCESS)); } vaddr_t pmap_steal_memory(vsize_t size, vaddr_t *vstartp, vaddr_t *vendp) { vaddr_t addr; uvm_physseg_t bank; UVMHIST_FUNC("pmap_steal_memory"); UVMHIST_CALLED(pmaphist); addr = 0; size = round_page(size); for (bank = uvm_physseg_get_first(); uvm_physseg_valid_p(bank); bank = uvm_physseg_get_next(bank)) { if (uvm_physseg_get_avail_start(bank) < uvm_physseg_get_avail_end(bank)) { paddr_t avail_start = uvm_physseg_get_avail_start(bank); addr = (vaddr_t) ((char*)MEMC_PHYS_BASE + ptoa(avail_start)); avail_start++; uvm_physseg_set_avail_start(bank, avail_start); break; } } return addr; } /* * pmap_init: second stage pmap initialisation. * * Sets up everything that will be needed for normal running. We can * allocate memory in page-rounded chunks with uvm_km_(z)alloc(), or * use the pool allocator. malloc is still taboo. */ void pmap_init(void) { UVMHIST_FUNC("pmap_init"); UVMHIST_CALLED(pmaphist); /* All deferred to pmap_create, because malloc() is nice. */ } /* * pmap_init2: third stage pmap initialisation. * * This is invoked the first time pmap_create is called. It sets up the pool * for allocating user pmaps, and frees some unnecessary memory. */ void pmap_init2(void) { struct pmap *pmap; struct pv_entry *new_pv_table, *old_pv_table; size_t pv_table_size; int lpn; UVMHIST_FUNC("pmap_init2"); UVMHIST_CALLED(pmaphist); /* We can now call malloc(). Rationalise our memory usage. */ pv_table_size = physmem * sizeof(struct pv_entry); new_pv_table = kmem_alloc(pv_table_size, KM_SLEEP); memcpy(new_pv_table, pv_table, pv_table_size); old_pv_table = pv_table; pv_table = new_pv_table; /* Fix up the kernel pmap to point at new pv_entries. */ pmap = pmap_kernel(); for (lpn = 0; lpn < 1024; lpn++) if (pmap->pm_entries[lpn] == old_pv_table + pmap->pm_entries[lpn]->pv_ppn) pmap->pm_entries[lpn] = pv_table + pmap->pm_entries[lpn]->pv_ppn; uvm_pagefree(PHYS_TO_VM_PAGE( PMAP_UNMAP_POOLPAGE((vaddr_t)old_pv_table))); /* Create pmap pool */ pool_init(&pmap_pool, sizeof(struct pmap), 0, 0, 0, "pmappool", NULL, IPL_NONE); pmap_initialised = 1; } struct pmap * pmap_create(void) { struct pmap *pmap; UVMHIST_FUNC("pmap_create"); UVMHIST_CALLED(pmaphist); if (!pmap_initialised) pmap_init2(); pmap = pool_get(&pmap_pool, PR_WAITOK); memset(pmap, 0, sizeof(*pmap)); pmap->pm_entries = (struct pv_entry **)kmem_zalloc( sizeof(struct pv_entry *) * PM_NENTRIES, KM_SLEEP); pmap->pm_count = 1; return pmap; } void pmap_destroy(pmap_t pmap) { #ifdef DIAGNOSTIC int i; #endif UVMHIST_FUNC("pmap_destroy"); UVMHIST_CALLED(pmaphist); if (--pmap->pm_count > 0) return; KASSERT((pmap->pm_flags & PM_ACTIVE) == 0); KASSERT(pmap->pm_stats.resident_count == 0); KASSERT(pmap->pm_stats.wired_count == 0); #ifdef DIAGNOSTIC for (i = 0; i < 1024; i++) if (pmap->pm_entries[i] != NULL) panic("pmap_destroy: pmap isn't empty"); #endif kmem_free((void *)pmap->pm_entries, sizeof(struct pv_entry *) * PM_NENTRIES); pool_put(&pmap_pool, pmap); } long _pmap_resident_count(pmap_t pmap) { return pmap->pm_stats.resident_count; } long _pmap_wired_count(pmap_t pmap) { return pmap->pm_stats.wired_count; } void pmap_activate(struct lwp *l) { struct proc *p = l->l_proc; pmap_t pmap; UVMHIST_FUNC("pmap_activate"); UVMHIST_CALLED(pmaphist); pmap = p->p_vmspace->vm_map.pmap; if (pmap == pmap_kernel()) return; /* kernel pmap is always active */ KASSERT(active_pmap == NULL); KASSERT((pmap->pm_flags & PM_ACTIVE) == 0); active_pmap = pmap; pmap->pm_flags |= PM_ACTIVE; } void pmap_deactivate(struct lwp *l) { struct proc *p = l->l_proc; pmap_t pmap; int i; UVMHIST_FUNC("pmap_deactivate"); UVMHIST_CALLED(pmaphist); pmap = p->p_vmspace->vm_map.pmap; if (pmap == pmap_kernel()) return; /* kernel pmap is always active */ KASSERT(pmap == active_pmap); KASSERT(pmap->pm_flags & PM_ACTIVE); active_pmap = NULL; pmap->pm_flags &=~ PM_ACTIVE; for (i = 0; i < 1024; i++) if (pmap->pm_entries[i] != NULL) MEMC_WRITE(pmap->pm_entries[i]->pv_deactivate); cpu_cache_flush(); } void pmap_unwire(pmap_t pmap, vaddr_t va) { struct pv_entry *pv; UVMHIST_FUNC("pmap_unwire"); UVMHIST_CALLED(pmaphist); if (pmap == NULL) return; pv = pmap->pm_entries[atop(va)]; if (pv == NULL) return; if ((pv->pv_vflags & PV_WIRED) == 0) return; pmap->pm_stats.wired_count--; pv->pv_vflags &= ~PV_WIRED; } void pmap_copy(pmap_t dst_pmap, pmap_t src_pmap, vaddr_t dst_addr, vsize_t len, vaddr_t src_addr) { UVMHIST_FUNC("pmap_copy"); UVMHIST_CALLED(pmaphist); /* This is allowed to be a no-op. */ } /* * Update the pv_activate and pv_deactivate fields in a pv_entry to * match the rest. */ static void pv_update(struct pv_entry *pv) { int ppl; int pflags; UVMHIST_FUNC("pv_update"); UVMHIST_CALLED(pmaphist); pflags = pv_table[pv->pv_ppn].pv_pflags; if (pv->pv_pmap == pmap_kernel()) /* * Referenced/modified emulation is almost impossible in * SVC mode. */ ppl = MEMC_PPL_NOACCESS; else { if ((pv->pv_prot & VM_PROT_WRITE) && (pflags & PV_REFERENCED) && (pflags & PV_MODIFIED)) ppl = MEMC_PPL_RDWR; else if ((pv->pv_prot & (VM_PROT_READ | VM_PROT_EXECUTE)) && (pflags & PV_REFERENCED)) ppl = MEMC_PPL_RDONLY; else ppl = MEMC_PPL_NOACCESS; } pv->pv_ppl = ppl; pv->pv_activate = MEMC_TRANS_ENTRY_32K(pv->pv_ppn, pv->pv_lpn, ppl); pv->pv_deactivate = PMAP_UNMAP_ENTRY_32K(pv->pv_ppn); } static struct pv_entry * pv_alloc(void) { return kmem_intr_zalloc(sizeof(struct pv_entry), KM_NOSLEEP); } static void pv_free(struct pv_entry *pv) { kmem_intr_free(pv, sizeof(struct pv_entry)); } static struct pv_entry * pv_get(pmap_t pmap, int ppn, int lpn) { struct pv_entry *pv; UVMHIST_FUNC("pv_get"); UVMHIST_CALLED(pmaphist); UVMHIST_LOG(pmaphist, "(pmap=%#jx, ppn=%jd, lpn=%jd)", (uintptr_t)pmap, ppn, lpn, 0); /* If the head entry's free use that. */ pv = &pv_table[ppn]; if (pv->pv_pmap == NULL) { UVMHIST_LOG(pmaphist, "<-- head (pv=%p)", pv, 0, 0, 0); pmap->pm_stats.resident_count++; return pv; } /* If this mapping exists already, use that. */ for (pv = pv; pv != NULL; pv = pv->pv_next) if (pv->pv_pmap == pmap && pv->pv_lpn == lpn) { UVMHIST_LOG(pmaphist, "<-- existing (pv=%#jx)", (uintptr_t)pv, 0, 0, 0); return pv; } /* Otherwise, allocate a new entry and link it in after the head. */ pv = pv_alloc(); if (pv == NULL) return NULL; pv->pv_next = pv_table[ppn].pv_next; pv_table[ppn].pv_next = pv; pmap->pm_stats.resident_count++; UVMHIST_LOG(pmaphist, "<-- new (pv=%#jx)", (uintptr_t)pv, 0, 0, 0); return pv; } /* * Release the pv_entry for a mapping. Code derived from hp300 pmap. */ static void pv_release(pmap_t pmap, int ppn, int lpn) { struct pv_entry *pv, *npv; UVMHIST_FUNC("pv_release"); UVMHIST_CALLED(pmaphist); UVMHIST_LOG(pmaphist, "(pmap=%#jx, ppn=%jd, lpn=%jd)", (uintptr_t)pmap, ppn, lpn, 0); pv = &pv_table[ppn]; /* * If it is the first entry on the list, it is actually * in the header and we must copy the following entry up * to the header. Otherwise we must search the list for * the entry. In either case we free the now unused entry. */ if (pmap == pv->pv_pmap && lpn == pv->pv_lpn) { npv = pv->pv_next; if (npv) { UVMHIST_LOG(pmaphist, "pv=%#jx; pull-up", (uintptr_t)pv, 0, 0, 0); /* Pull up first entry from chain. */ memcpy(pv, npv, offsetof(struct pv_entry, pv_pflags)); pv->pv_pmap->pm_entries[pv->pv_lpn] = pv; pv_free(npv); } else { UVMHIST_LOG(pmaphist, "pv=%#jx; empty", (uintptr_t)pv, 0, 0, 0); memset(pv, 0, offsetof(struct pv_entry, pv_pflags)); } } else { for (npv = pv->pv_next; npv; npv = npv->pv_next) { if (pmap == npv->pv_pmap && lpn == npv->pv_lpn) break; pv = npv; } KASSERT(npv != NULL); UVMHIST_LOG(pmaphist, "pv=%#jx; tail", (uintptr_t)pv, 0, 0, 0); pv->pv_next = npv->pv_next; pv_free(npv); } pmap->pm_entries[lpn] = NULL; pmap->pm_stats.resident_count--; } /* * Insert the given physical page (pa) at the specified virtual * address (va) in the target physical map with the protection * requested. * * NB: This is the only routine which MAY NOT lazy-evaluate or lose * information. That is, this routine must actually insert this page * into the given map NOW. */ int pmap_enter(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags) { UVMHIST_FUNC("pmap_enter"); UVMHIST_CALLED(pmaphist); return pmap_enter1(pmap, va, pa, prot, flags, 0); } static int pmap_enter1(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags, int unmanaged) { int ppn, lpn, s; struct pv_entry *pv, *ppv; UVMHIST_FUNC("pmap_enter"); UVMHIST_CALLED(pmaphist); ppn = atop(pa); lpn = atop(va); UVMHIST_LOG(pmaphist, "mapping ppn %jd at lpn %jd in pmap %#jx", ppn, lpn, (uintptr_t)pmap, 0); s = splvm(); /* Remove any existing mapping at this lpn */ if (pmap->pm_entries[lpn] != NULL && pmap->pm_entries[lpn]->pv_ppn != ppn) pmap_remove(pmap, va, va + PAGE_SIZE); /* Make a note */ pv = pv_get(pmap, ppn, lpn); if (pv == NULL) panic("pmap_enter1"); if (pv->pv_vflags & PV_WIRED) pmap->pm_stats.wired_count--; ppv = &pv_table[ppn]; pv->pv_pmap = pmap; pv->pv_ppn = ppn; pv->pv_lpn = lpn; pv->pv_prot = prot; pv->pv_vflags = 0; /* pv->pv_pflags = 0; */ if (flags & PMAP_WIRED) { pv->pv_vflags |= PV_WIRED; } if (unmanaged) pv->pv_vflags |= PV_UNMANAGED; else { /* According to pmap(9), unmanaged mappings don't track r/m */ if (flags & VM_PROT_WRITE) ppv->pv_pflags |= PV_REFERENCED | PV_MODIFIED; else if (flags & (VM_PROT_ALL)) ppv->pv_pflags |= PV_REFERENCED; } pmap_update_page(ppn); pmap->pm_entries[lpn] = pv; if (pv->pv_vflags & PV_WIRED) pmap->pm_stats.wired_count++; splx(s); /* Poke the MEMC */ if (pmap->pm_flags & PM_ACTIVE) MEMC_WRITE(pv->pv_activate); return 0; } /* Remove a range of virtual mappings from a pmap */ void pmap_remove(pmap_t pmap, vaddr_t sva, vaddr_t eva) { int slpn, elpn, lpn, s; struct pv_entry *pv; UVMHIST_FUNC("pmap_remove"); UVMHIST_CALLED(pmaphist); slpn = atop(sva); elpn = atop(eva); UVMHIST_LOG(pmaphist, "clearing from lpn %jd to lpn %jd in pmap %#jx", slpn, elpn - 1, (uintptr_t)pmap, 0); s = splvm(); for (lpn = slpn; lpn < elpn; lpn++) { pv = pmap->pm_entries[lpn]; if (pv != NULL) { if (pmap->pm_flags & PM_ACTIVE) { MEMC_WRITE(pv->pv_deactivate); cpu_cache_flush(); } pmap->pm_entries[lpn] = NULL; if (pv->pv_vflags & PV_WIRED) pmap->pm_stats.wired_count--; pv_release(pmap, pv->pv_ppn, lpn); } } splx(s); } bool pmap_extract(pmap_t pmap, vaddr_t va, paddr_t *ppa) { struct pv_entry *pv; UVMHIST_FUNC("pmap_extract"); UVMHIST_CALLED(pmaphist); pv = pmap->pm_entries[atop(va)]; if (pv == NULL) return false; *ppa = ptoa(pv->pv_ppn); return true; } void pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags) { UVMHIST_FUNC("pmap_kenter_pa"); UVMHIST_CALLED(pmaphist); pmap_enter1(pmap_kernel(), va, pa, prot, prot | PMAP_WIRED, 1); } void pmap_kremove(vaddr_t va, vsize_t len) { UVMHIST_FUNC("pmap_kremove"); UVMHIST_CALLED(pmaphist); pmap_remove(pmap_kernel(), va, va+len); } inline bool pmap_is_modified(struct vm_page *page) { int ppn; bool rv; #ifdef PMAP_DEBUG_MODIFIED unsigned char digest[16]; #endif UVMHIST_FUNC("pmap_is_modified"); UVMHIST_CALLED(pmaphist); ppn = atop(VM_PAGE_TO_PHYS(page)); rv = (pv_table[ppn].pv_pflags & PV_MODIFIED) != 0; #ifdef PMAP_DEBUG_MODIFIED if (!rv) { pmap_md4_page(digest, VM_PAGE_TO_PHYS(page)); if (memcmp(digest, pv_table[ppn].pv_md4sum, 16) != 0) { int i; printf("page %p modified bit wrong\n", page); printf("then = "); for (i = 0; i < 16; i++) printf("%02x", pv_table[ppn].pv_md4sum[i]); printf("\n now = "); for (i = 0; i < 16; i++) printf("%02x", digest[i]); printf("\n"); } } #endif return rv; } inline bool pmap_is_referenced(struct vm_page *page) { int ppn; UVMHIST_FUNC("pmap_is_referenced"); UVMHIST_CALLED(pmaphist); ppn = atop(VM_PAGE_TO_PHYS(page)); return (pv_table[ppn].pv_pflags & PV_REFERENCED) != 0; } static void pmap_update_page(int ppn) { struct pv_entry *pv; UVMHIST_FUNC("pmap_update_page"); UVMHIST_CALLED(pmaphist); for (pv = &pv_table[ppn]; pv != NULL; pv = pv->pv_next) if (pv->pv_pmap != NULL) { pv_update(pv); if (pv->pv_pmap->pm_flags & PM_ACTIVE) { if (pv->pv_activate) MEMC_WRITE(pv->pv_activate); else MEMC_WRITE(pv->pv_deactivate); } } } bool pmap_clear_modify(struct vm_page *page) { int ppn; bool rv; struct pv_entry *pv; UVMHIST_FUNC("pmap_clear_modify"); UVMHIST_CALLED(pmaphist); ppn = atop(VM_PAGE_TO_PHYS(page)); rv = pmap_is_modified(page); #ifdef PMAP_DEBUG_MODIFIED pmap_md4_page(pv_table[ppn].pv_md4sum, ptoa(ppn)); #endif if (rv) { for (pv = &pv_table[ppn]; pv != NULL; pv = pv->pv_next) if (pv->pv_pmap == pmap_kernel() && (pv->pv_prot & VM_PROT_WRITE)) return rv; pv_table[ppn].pv_pflags &= ~PV_MODIFIED; pmap_update_page(ppn); } return rv; } bool pmap_clear_reference(struct vm_page *page) { int ppn; bool rv; UVMHIST_FUNC("pmap_clear_reference"); UVMHIST_CALLED(pmaphist); ppn = atop(VM_PAGE_TO_PHYS(page)); rv = pmap_is_referenced(page); if (rv) { pv_table[ppn].pv_pflags &= ~PV_REFERENCED; pmap_update_page(ppn); } return rv; } /* * Work out if a given page fault was our fault (e.g. through * referenced/modified emulation). If it was, handle it and return * true. Otherwise, return false. */ bool pmap_fault(struct pmap *pmap, vaddr_t va, vm_prot_t atype) { int lpn, ppn; struct pv_entry *pv, *ppv; UVMHIST_FUNC("pmap_fault"); UVMHIST_CALLED(pmaphist); lpn = atop(va); pv = pmap->pm_entries[lpn]; if (pv == NULL) return false; ppn = pv->pv_ppn; ppv = &pv_table[ppn]; UVMHIST_LOG(pmaphist, "pmap = %#jx, lpn = %jd, ppn = %jd, atype = 0x%jx", (uintptr_t)pmap, lpn, ppn, atype); if (pmap != pmap_kernel()) { if ((ppv->pv_pflags & PV_REFERENCED) == 0) { ppv->pv_pflags |= PV_REFERENCED; pmap_update_page(ppn); return true; } if ((atype & VM_PROT_WRITE) && (pv->pv_prot & VM_PROT_WRITE) && (ppv->pv_pflags & PV_MODIFIED) == 0) { ppv->pv_pflags |= PV_MODIFIED; pmap_update_page(ppn); return true; } } /* * It wasn't a referenced/modified fault. * If it looks like the access should have been allowed, try pushing * the mapping back into the MEMC. */ if ((atype & ~pv->pv_prot) == 0) { MEMC_WRITE(pv->pv_activate); /* * If the new mapping is writable, we should flush the cache * so that stale data for an existing mapping doesn't get * reused. * XXX: Should this be done more lazily? */ if (pv->pv_prot & VM_PROT_WRITE) cpu_cache_flush(); return true; } return false; } /* * Change access permissions on a given physical page. * * Pages mapped using pmap_kenter_*() are exempt. */ void pmap_page_protect(struct vm_page *page, vm_prot_t prot) { int ppn; struct pv_entry *pv, *npv; UVMHIST_FUNC("pmap_page_protect"); UVMHIST_CALLED(pmaphist); ppn = atop(VM_PAGE_TO_PHYS(page)); if (prot == VM_PROT_NONE) { UVMHIST_LOG(pmaphist, "removing ppn %jd\n", ppn, 0, 0, 0); npv = pv = &pv_table[ppn]; while (pv != NULL && pv->pv_pmap != NULL) { if (pv->pv_vflags & PV_UNMANAGED) { pv = pv->pv_next; continue; } if (pv->pv_pmap->pm_flags & PM_ACTIVE) { MEMC_WRITE(pv->pv_deactivate); cpu_cache_flush(); } if (pv != &pv_table[ppn]) npv = pv->pv_next; pv->pv_pmap->pm_entries[pv->pv_lpn] = NULL; if (pv->pv_vflags & PV_WIRED) pv->pv_pmap->pm_stats.wired_count--; pv_release(pv->pv_pmap, ppn, pv->pv_lpn); pv = npv; } } else if (prot != VM_PROT_ALL) { for (pv = &pv_table[ppn]; pv != NULL; pv = pv->pv_next) if (pv->pv_pmap != NULL && (pv->pv_vflags & PV_UNMANAGED) == 0) { pv->pv_prot &= prot; pv_update(pv); if (pv->pv_pmap->pm_flags & PM_ACTIVE) MEMC_WRITE(pv->pv_activate); } } } paddr_t pmap_phys_address(paddr_t ppn) { panic("pmap_phys_address not implemented"); } void pmap_protect(pmap_t pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot) { struct pv_entry *pv; int s, slpn, elpn, lpn; UVMHIST_FUNC("pmap_protect"); UVMHIST_CALLED(pmaphist); if (prot == VM_PROT_NONE) { pmap_remove(pmap, sva, eva); return; } if (prot & VM_PROT_WRITE) return; /* apparently we're meant to */ if (pmap == pmap_kernel()) return; /* can't restrict kernel w/o unmapping. */ slpn = atop(sva); elpn = atop(eva); s = splvm(); for (lpn = slpn; lpn < elpn; lpn++) { pv = pmap->pm_entries[lpn]; if (pv != NULL) { pv->pv_prot &= prot; pv_update(pv); if (pv->pv_pmap->pm_flags & PM_ACTIVE) MEMC_WRITE(pv->pv_activate); } } splx(s); } void pmap_reference(pmap_t pmap) { UVMHIST_FUNC("pmap_reference"); UVMHIST_CALLED(pmaphist); pmap->pm_count++; } /* * If there were any actions we could delay committing to hardware, * this would be where they'd be committed. We should perhaps delay * cache flushes till here, but I've heard rumours that pmap_update() * isn't called as often as it should be, so I'll play it safe for * now. */ void pmap_update(struct pmap *pmap) { UVMHIST_FUNC("pmap_update"); UVMHIST_CALLED(pmaphist); /* Do nothing */ } /* * See if the given physical address has a logical mapping. Return * its address if so, otherwise return the logical address in MEMC * physical space. This means that we can safely write here without * flushing the cache. Only necessary on ARM3. */ static void * pmap_find(paddr_t pa) { #ifdef CPU_ARM3 struct pv_entry *pv; #endif UVMHIST_FUNC("pmap_find"); UVMHIST_CALLED(pmaphist); #ifdef CPU_ARM3 for (pv = &pv_table[atop(pa)]; pv != NULL; pv = pv->pv_next) if (pv->pv_pmap != NULL && (pv->pv_pmap->pm_flags & PM_ACTIVE)) return (void *)ptoa(pv->pv_lpn); #endif return (char*)MEMC_PHYS_BASE + pa; } void pmap_zero_page(paddr_t pa) { UVMHIST_FUNC("pmap_zero_page"); UVMHIST_CALLED(pmaphist); memset(pmap_find(pa), 0, PAGE_SIZE); } void pmap_copy_page(paddr_t src, paddr_t dest) { UVMHIST_FUNC("pmap_copy_page"); UVMHIST_CALLED(pmaphist); memcpy(pmap_find(dest), pmap_find(src), PAGE_SIZE); } #ifdef PMAP_DEBUG_MODIFIED static void pmap_md4_page(unsigned char digest[16], paddr_t pa) { MD4_CTX context; UVMHIST_FUNC("pmap_md4_page"); UVMHIST_CALLED(pmaphist); MD4Init(&context); MD4Update(&context, pmap_find(pa), PAGE_SIZE); MD4Final(digest, &context); } #endif /* * This is meant to return the range of kernel vm that is available * after loading the kernel. Since NetBSD/acorn26 runs the kernel from * physically-mapped space, we just return all of kernel vm. Oh, * except for the single page at the end where we map * otherwise-unmapped pages. */ void pmap_virtual_space(vaddr_t *vstartp, vaddr_t *vendp) { UVMHIST_FUNC("pmap_virtual_space"); UVMHIST_CALLED(pmaphist); if (vstartp != NULL) *vstartp = VM_MIN_KERNEL_ADDRESS; if (vendp != NULL) *vendp = VM_MAX_KERNEL_ADDRESS - PAGE_SIZE; } #ifdef DDB #include void pmap_dump(struct pmap *); void pmap_dump(struct pmap *pmap) { int i; struct pv_entry *pv; int pflags; db_printf("PMAP %p:\n", pmap); db_printf("\tcount = %d, flags = %d, " "resident_count = %ld, wired_count = %ld\n", pmap->pm_count, pmap->pm_flags, pmap->pm_stats.resident_count, pmap->pm_stats.wired_count); for (i = 0; i < 1024; i++) if ((pv = pmap->pm_entries[i]) != NULL) { db_printf("\t%03d->%p: ", i, pv); if (pv->pv_pmap != pmap) db_printf("pmap=%p!, ", pv->pv_pmap); db_printf("ppn=%d, lpn=%d, ", pv->pv_ppn, pv->pv_lpn); db_printf("act=%08x, deact=%08x,\n", pv->pv_activate, pv->pv_deactivate); db_printf("\t\tprot=%c%c%c, ", pv->pv_prot & VM_PROT_READ ? 'r' : '-', pv->pv_prot & VM_PROT_WRITE ? 'w' : '-', pv->pv_prot & VM_PROT_EXECUTE ? 'x' : '-'); db_printf("ppl="); switch(pv->pv_ppl) { case MEMC_PPL_RDWR: db_printf("rw, "); break; case MEMC_PPL_RDONLY: db_printf("r-, "); break; case MEMC_PPL_NOACCESS: db_printf("--, "); break; default: db_printf("0x%x, ", pv->pv_ppl); break; } db_printf("vflags=%c, ", pv->pv_vflags & PV_WIRED ? 'W' : '-'); pflags = pv_table[pv->pv_ppn].pv_pflags; db_printf("pflags=%c%c\n", pflags & PV_MODIFIED ? 'M' : '-', pflags & PV_REFERENCED ? 'R' : '-'); } } #endif