
a portable SG_NOEXEC segment attribute was added to allow non-executable (physical) segments. which will set the PTENOEXEC bits for putmmu(). in the future, this can be used to make non-executable stack / bss segments. the SG_DEVICE attribute was added to distinguish between mmio regions and uncached memory. only matterns on arm64. on arm, theres the issue that PTEUNCACHED would have no bits set when using the hardware bit definitions. this is the reason bcm, kw, teg2 and omap kernels use arteficial PTE constants. on zynq, the XN bit was used as a hack to give PTEUNCACHED a non-zero value and when the bit is clear then cache attributes where added to the pte. to fix this, PTECACHED constant was added. the portable mmu code in fault.c will now explicitely set PTECACHED bits for cached memory and PTEUNCACHED for uncached memory. that way the hardware bit definitions can be used everywhere.
361 lines
6.8 KiB
C
361 lines
6.8 KiB
C
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
|
|
#include "arm.h"
|
|
|
|
#define FEXT(d, o, w) (((d)>>(o)) & ((1<<(w))-1))
|
|
#define L1X(va) FEXT((va), 20, 12)
|
|
#define L2X(va) FEXT((va), 12, 8)
|
|
#define L2AP(ap) l2ap(ap)
|
|
#define L1ptedramattrs soc.l1ptedramattrs
|
|
#define L2ptedramattrs soc.l2ptedramattrs
|
|
#define PTEDRAM (PHYSDRAM|Dom0|L1AP(Krw)|Section|L1ptedramattrs)
|
|
|
|
enum {
|
|
L1lo = UZERO/MiB, /* L1X(UZERO)? */
|
|
L1hi = (USTKTOP+MiB-1)/MiB, /* L1X(USTKTOP+MiB-1)? */
|
|
L2size = 256*sizeof(PTE),
|
|
};
|
|
|
|
/*
|
|
* Set up initial PTEs for cpu0 (called with mmu off)
|
|
*/
|
|
void
|
|
mmuinit(void *a)
|
|
{
|
|
PTE *l1, *l2;
|
|
uintptr pa, va;
|
|
|
|
l1 = (PTE*)a;
|
|
l2 = (PTE*)PADDR(L2);
|
|
|
|
/*
|
|
* map all of ram at KZERO
|
|
*/
|
|
va = KZERO;
|
|
for(pa = PHYSDRAM; pa < PHYSDRAM+soc.dramsize; pa += MiB){
|
|
l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section|L1ptedramattrs;
|
|
va += MiB;
|
|
}
|
|
|
|
/*
|
|
* identity map first MB of ram so mmu can be enabled
|
|
*/
|
|
l1[L1X(PHYSDRAM)] = PTEDRAM;
|
|
|
|
/*
|
|
* map i/o registers
|
|
*/
|
|
va = soc.virtio;
|
|
for(pa = soc.physio; pa < soc.physio+soc.iosize; pa += MiB){
|
|
l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section|L1noexec;
|
|
va += MiB;
|
|
}
|
|
pa = soc.armlocal;
|
|
if(pa)
|
|
l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section|L1noexec;
|
|
|
|
/*
|
|
* double map exception vectors near top of virtual memory
|
|
*/
|
|
va = HVECTORS;
|
|
l1[L1X(va)] = (uintptr)l2|Dom0|Coarse;
|
|
l2[L2X(va)] = PHYSDRAM|L2AP(Krw)|Small|L2ptedramattrs;
|
|
}
|
|
|
|
/*
|
|
* enable/disable identity map of first MB of ram
|
|
*/
|
|
void
|
|
mmuinit1(int on)
|
|
{
|
|
PTE *l1;
|
|
|
|
l1 = m->mmul1;
|
|
l1[L1X(PHYSDRAM)] = on? PTEDRAM: Fault;
|
|
cachedwbtlb(&l1[L1X(PHYSDRAM)], sizeof(PTE));
|
|
mmuinvalidateaddr(PHYSDRAM);
|
|
mmuinvalidate();
|
|
}
|
|
|
|
static void
|
|
mmul2empty(Proc* proc, int clear)
|
|
{
|
|
PTE *l1;
|
|
Page **l2, *page;
|
|
|
|
l1 = m->mmul1;
|
|
l2 = &proc->mmul2;
|
|
for(page = *l2; page != nil; page = page->next){
|
|
if(clear)
|
|
memset((void*)page->va, 0, L2size);
|
|
l1[page->daddr] = Fault;
|
|
l2 = &page->next;
|
|
}
|
|
coherence();
|
|
*l2 = proc->mmul2cache;
|
|
proc->mmul2cache = proc->mmul2;
|
|
proc->mmul2 = nil;
|
|
}
|
|
|
|
static void
|
|
mmul1empty(void)
|
|
{
|
|
PTE *l1;
|
|
|
|
/* clean out any user mappings still in l1 */
|
|
if(m->mmul1lo > 0){
|
|
if(m->mmul1lo == 1)
|
|
m->mmul1[L1lo] = Fault;
|
|
else
|
|
memset(&m->mmul1[L1lo], 0, m->mmul1lo*sizeof(PTE));
|
|
m->mmul1lo = 0;
|
|
}
|
|
if(m->mmul1hi > 0){
|
|
l1 = &m->mmul1[L1hi - m->mmul1hi];
|
|
if(m->mmul1hi == 1)
|
|
*l1 = Fault;
|
|
else
|
|
memset(l1, 0, m->mmul1hi*sizeof(PTE));
|
|
m->mmul1hi = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
mmuswitch(Proc* proc)
|
|
{
|
|
int x;
|
|
PTE *l1;
|
|
Page *page;
|
|
|
|
if(proc != nil && proc->newtlb){
|
|
mmul2empty(proc, 1);
|
|
proc->newtlb = 0;
|
|
}
|
|
|
|
mmul1empty();
|
|
|
|
/* move in new map */
|
|
l1 = m->mmul1;
|
|
if(proc != nil)
|
|
for(page = proc->mmul2; page != nil; page = page->next){
|
|
x = page->daddr;
|
|
l1[x] = PPN(page->pa)|Dom0|Coarse;
|
|
if(x >= L1lo + m->mmul1lo && x < L1hi - m->mmul1hi){
|
|
if(x+1 - L1lo < L1hi - x)
|
|
m->mmul1lo = x+1 - L1lo;
|
|
else
|
|
m->mmul1hi = L1hi - x;
|
|
}
|
|
}
|
|
|
|
/* make sure map is in memory */
|
|
/* could be smarter about how much? */
|
|
cachedwbtlb(&l1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
|
|
|
|
/* lose any possible stale tlb entries */
|
|
mmuinvalidate();
|
|
}
|
|
|
|
void
|
|
flushmmu(void)
|
|
{
|
|
int s;
|
|
|
|
s = splhi();
|
|
up->newtlb = 1;
|
|
mmuswitch(up);
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
mmurelease(Proc* proc)
|
|
{
|
|
Page *page, *next;
|
|
|
|
mmul2empty(proc, 0);
|
|
for(page = proc->mmul2cache; page != nil; page = next){
|
|
next = page->next;
|
|
if(--page->ref)
|
|
panic("mmurelease: page->ref %lud", page->ref);
|
|
pagechainhead(page);
|
|
}
|
|
if(proc->mmul2cache != nil)
|
|
pagechaindone();
|
|
proc->mmul2cache = nil;
|
|
|
|
mmul1empty();
|
|
|
|
/* make sure map is in memory */
|
|
/* could be smarter about how much? */
|
|
cachedwbtlb(&m->mmul1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
|
|
|
|
/* lose any possible stale tlb entries */
|
|
mmuinvalidate();
|
|
}
|
|
|
|
void
|
|
putmmu(uintptr va, uintptr pa, Page* page)
|
|
{
|
|
int x, s;
|
|
Page *pg;
|
|
PTE *l1, *pte;
|
|
|
|
/*
|
|
* disable interrupts to prevent flushmmu (called from hzclock)
|
|
* from clearing page tables while we are setting them
|
|
*/
|
|
s = splhi();
|
|
x = L1X(va);
|
|
l1 = &m->mmul1[x];
|
|
if(*l1 == Fault){
|
|
/* l2 pages only have 256 entries - wastes 3K per 1M of address space */
|
|
if(up->mmul2cache == nil){
|
|
spllo();
|
|
pg = newpage(1, 0, 0);
|
|
splhi();
|
|
/* if newpage slept, we might be on a different cpu */
|
|
l1 = &m->mmul1[x];
|
|
pg->va = VA(kmap(pg));
|
|
}else{
|
|
pg = up->mmul2cache;
|
|
up->mmul2cache = pg->next;
|
|
}
|
|
pg->daddr = x;
|
|
pg->next = up->mmul2;
|
|
up->mmul2 = pg;
|
|
|
|
/* force l2 page to memory (armv6) */
|
|
cachedwbtlb((void *)pg->va, L2size);
|
|
|
|
*l1 = PPN(pg->pa)|Dom0|Coarse;
|
|
cachedwbtlb(l1, sizeof *l1);
|
|
|
|
if(x >= L1lo + m->mmul1lo && x < L1hi - m->mmul1hi){
|
|
if(x+1 - L1lo < L1hi - x)
|
|
m->mmul1lo = x+1 - L1lo;
|
|
else
|
|
m->mmul1hi = L1hi - x;
|
|
}
|
|
}
|
|
pte = KADDR(PPN(*l1));
|
|
|
|
/* protection bits are
|
|
* PTERONLY|PTEVALID;
|
|
* PTEWRITE|PTEVALID;
|
|
* PTEWRITE|PTEUNCACHED|PTEVALID;
|
|
*/
|
|
x = Small;
|
|
if(!(pa & PTEUNCACHED))
|
|
x |= L2ptedramattrs;
|
|
if(pa & PTEWRITE)
|
|
x |= L2AP(Urw);
|
|
else
|
|
x |= L2AP(Uro);
|
|
if(pa & PTENOEXEC)
|
|
x |= L2noexec;
|
|
pte[L2X(va)] = PPN(pa)|x;
|
|
cachedwbtlb(&pte[L2X(va)], sizeof(PTE));
|
|
|
|
/* clear out the current entry */
|
|
mmuinvalidateaddr(PPN(va));
|
|
|
|
if((page->txtflush & (1<<m->machno)) != 0){
|
|
/* pio() sets PG_TXTFLUSH whenever a text pg has been written */
|
|
cachedwbse((void*)(page->pa|KZERO), BY2PG);
|
|
cacheiinvse((void*)page->va, BY2PG);
|
|
page->txtflush &= ~(1<<m->machno);
|
|
}
|
|
//checkmmu(va, PPN(pa));
|
|
splx(s);
|
|
}
|
|
|
|
void*
|
|
mmuuncache(void* v, usize size)
|
|
{
|
|
int x;
|
|
PTE *pte;
|
|
uintptr va;
|
|
|
|
/*
|
|
* Simple helper for ucalloc().
|
|
* Uncache a Section, must already be
|
|
* valid in the MMU.
|
|
*/
|
|
va = (uintptr)v;
|
|
assert(!(va & (1*MiB-1)) && size == 1*MiB);
|
|
|
|
x = L1X(va);
|
|
pte = &m->mmul1[x];
|
|
if((*pte & (Fine|Section|Coarse)) != Section)
|
|
return nil;
|
|
*pte &= ~L1ptedramattrs;
|
|
mmuinvalidateaddr(va);
|
|
cachedwbinvse(pte, 4);
|
|
|
|
return v;
|
|
}
|
|
|
|
/*
|
|
* Return the number of bytes that can be accessed via KADDR(pa).
|
|
* If pa is not a valid argument to KADDR, return 0.
|
|
*/
|
|
uintptr
|
|
cankaddr(uintptr pa)
|
|
{
|
|
if(pa < PHYSDRAM+soc.dramsize)
|
|
return ((uintptr)PHYSDRAM+soc.dramsize) - pa;
|
|
return 0;
|
|
}
|
|
|
|
uintptr
|
|
mmukmap(uintptr va, uintptr pa, usize size)
|
|
{
|
|
int o;
|
|
usize n;
|
|
PTE *pte, *pte0;
|
|
|
|
assert((va & (MiB-1)) == 0);
|
|
o = pa & (MiB-1);
|
|
pa -= o;
|
|
size += o;
|
|
pte = pte0 = &m->mmul1[L1X(va)];
|
|
for(n = 0; n < size; n += MiB)
|
|
if(*pte++ != Fault)
|
|
return 0;
|
|
pte = pte0;
|
|
for(n = 0; n < size; n += MiB){
|
|
*pte++ = (pa+n)|Dom0|L1AP(Krw)|Section|L1noexec;
|
|
mmuinvalidateaddr(va+n);
|
|
}
|
|
cachedwbtlb(pte0, (uintptr)pte - (uintptr)pte0);
|
|
return va + o;
|
|
}
|
|
|
|
void
|
|
checkmmu(uintptr va, uintptr pa)
|
|
{
|
|
int x;
|
|
PTE *l1, *pte;
|
|
|
|
x = L1X(va);
|
|
l1 = &m->mmul1[x];
|
|
if(*l1 == Fault){
|
|
iprint("checkmmu cpu%d va=%lux l1 %p=%ux\n", m->machno, va, l1, *l1);
|
|
return;
|
|
}
|
|
pte = KADDR(PPN(*l1));
|
|
pte += L2X(va);
|
|
if(pa == ~0 || (pa != 0 && PPN(*pte) != pa))
|
|
iprint("checkmmu va=%lux pa=%lux l1 %p=%ux pte %p=%ux\n", va, pa, l1, *l1, pte, *pte);
|
|
}
|
|
|
|
void
|
|
kunmap(KMap *k)
|
|
{
|
|
USED(k);
|
|
coherence();
|
|
}
|