shithub: n900

ref: e8dc10b3b573fb5b696d8667826cd16629983494
dir: /mmu.c/

View raw version
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"

static int debug = 0;

#define L1(va) (((va)>>20) & 0xfff)
#define L2(va) (((va)>>12) & 0xff)
#define L1AP(ap) (((ap) << 10) & 0xff) /* dont care about ap[2] */
#define L2AP(ap) (((ap) << 4) & 0xff)

enum {
	/* l1 descriptor type (arm v7 manual fig. 12.4) */
	L1coarse = 1,
	L1section = 2,
	L1fine = 3,

	/* l2 descriptor type (arm v7 manual fig. 12.5) */
	L2large = 1,
	L2small = 2,
	L2tiny = 3,

	Fnoexec = 1<<0,
	Fbuffered = 1<<2,
	Fcached = 1<<3,

	APkrw = 1, /* kernel read write */
	APuro = 2, /* user read only */
	APurw = 3, /* user read write */
};

void
mmudebugl2(PTE *l2, uintptr va)
{
	uintptr startpa, pa;
	uintptr startva, endva;
	int i, t;

	t = 0;
	startpa = 0;
	startva = endva = 0;
	for(i = 0; i < 256; i++) {
		pa = l2[i] & ~((4*KiB)-1);
		if(l2[i] == 0) {
			if(endva) {
				iprint("mmudebug: l2 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
				endva = 0;
			}
		} else {
			if(!endva) {
				startva = va;
				startpa = pa;
				t = l2[i] & (L2large | L2small | L2tiny);
			}

			endva = va + (4*KiB);
		}

		va += (4*KiB);
	}

	if(endva)
		iprint("mmudebug: l2 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
}

void
mmudebug(char *where)
{
	PTE *l1;
	uintptr pa, startpa;
	uintptr va, startva, endva;
	int i, t;

	if(!debug)
		return;

	iprint("mmudebug: %s pid %d\n", where, m->mmupid);

	t = 0;
	l1 = m->mmul1;
	startpa = 0;
	startva = endva = 0;
	for(va = i = 0; i < 4096; i++) {
		pa = l1[i] & ~(MiB-1);
		if(l1[i] == 0) {
			if(endva) {
				iprint("mmudebug: l1 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
				endva = 0;
			}
		} else {
			if(!endva) {
				startva = va;
				startpa = pa;
				t = l1[i] & (L1coarse|L1section|L1fine);
			}

			if(t == L1coarse) {
				mmudebugl2((PTE*) (l1[i] & ~(KiB-1)), startva);
				endva = 0;
			} else {
				endva = va + MB;
			}
		}

		va += MB;
	}

	if(endva)
		iprint("mmudebug: l1 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
}

void
mmuinit(void)
{
	m->mmul1 = (PTE*)MACHL1(m->machno);
}

static void
mmul1empty(void)
{
	memset(m->mmul1, 0, (ROUND(USTKTOP, MiB)/MiB)*sizeof(PTE));
}

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, BY2PG);

		l1[page->daddr] = 0;
		l2 = &page->next;
	}

	*l2 = proc->mmul2cache;
	proc->mmul2cache = proc->mmul2;
	proc->mmul2 = nil;
}

void
mmuswitch(Proc *proc)
{
	PTE *l1;
	Page *page;
	int l1x;

	if(m->mmupid == proc->pid && !proc->newtlb)
		return;
	m->mmupid = proc->pid;

	/* write back and invalidate caches */
	l1ucachewbinv();
	l2ucachewbinv();

	if(proc->newtlb) {
		mmul2empty(proc, 1);
		proc->newtlb = 0;
	}

	mmul1empty();

	/* switch to new map */
	l1 = m->mmul1;
	for(page = proc->mmul2; page != nil; page = page->next) {
		l1x = page->daddr;
		l1[l1x] = PPN(page->pa)|L1coarse;
	}

	/* FIXME: excessive invalidation */
	l1ucachewbinv();
	l2ucachewbinv();
	mmuinvalidate();
	mmudebug("mmuswitch");
}

void
mmurelease(Proc *proc)
{
	l1ucachewbinv();
	l2ucachewbinv();

	mmul2empty(proc, 0);

	freepages(proc->mmul2cache, nil, 0);
	proc->mmul2cache = nil;

	mmul1empty();

	/* FIXME: excessive invalidation */
	l1ucachewbinv();
	l2ucachewbinv();
	mmuinvalidate();
	mmudebug("mmurelease");
}

void*
mmuuncache(void *v, usize s)
{
	PTE *l1;
	uintptr va;

	assert(!((uintptr)v & (MiB-1)) && s == MiB);

	va = (uintptr)v;
	l1 = &m->mmul1[L1(va)];
	if((*l1 & (L1fine|L1section|L1coarse)) != L1section)
		return nil;

	*l1 &= ~(Fbuffered|Fcached);

	/* FIXME: excessive invalidation */
	l1ucachewbinv();
	l2ucachewbinv();
	mmuinvalidate();
	mmudebug("mmuuncache");

	return v;
}

void
putmmu(uintptr va, uintptr pa, Page *page)
{
	int l1x, s, x;
	PTE *l1, *l2;
	Page *pg;

	l1x = L1(va);
	l1 = &m->mmul1[l1x];

	/* put l1 for l2 table if needed */
	if(*l1 == 0) {
		if(up->mmul2cache == nil) {
			pg = newpage(1, 0, 0);
			pg->va = VA(kmap(pg));
		} else {
			pg = up->mmul2cache;
			up->mmul2cache = pg->next;
			memset((void*)pg->va, 0, BY2PG);
		}

		pg->daddr = l1x;
		pg->next = up->mmul2;
		up->mmul2 = pg;

		/* FIXME: excessive invalidation */
		s = splhi();
		*l1 = PPN(pg->pa)|L1coarse;
		l1ucachewbinv();
		l2ucachewbinv();
		splx(s);
	}

	/* put l2 entry */
	x = L2small;
	if(!(pa & PTEUNCACHED))
		x |= Fbuffered|Fcached;
	if(pa & PTENOEXEC)
		x |= Fnoexec;
	if(pa & PTEWRITE)
		x |= L2AP(APurw);
	else
		x |= L2AP(APuro);

	l2 = KADDR(PPN(*l1)); l2[L2(va)] = PPN(pa)|x;

	/* FIXME: excessive invalidation */
	s = splhi();
	l1ucachewbinv();
	l2ucachewbinv();
	if(needtxtflush(page)) {
		l1icacheinv();
		donetxtflush(page);
	}

	splx(s);
	mmuinvalidate();
	mmudebug("putmmu");
}

void
checkmmu(uintptr, uintptr)
{
	/* this page is intentionally left blank */
}

void
flushmmu(void)
{
	uint s;

	s = splhi();
	up->newtlb = 1; mmuswitch(up);
	splx(s);
}

uintptr
cankaddr(uintptr pa)
{
	if(pa >= PHYSMEM && pa < PHYSMEMEND)
		return PHYSMEMEND-pa;

	return 0;
}