shithub: riscv

ref: 37e0d4ab29c8483aab727b76effaec009b650153
dir: /sys/src/cmd/vmx/x86.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include "dat.h"
#include "fns.h"
#include "x86.h"

typedef struct VMemReq VMemReq;
struct VMemReq {
	QLock;
	uintptr va, len;
	void *buf;
	uintptr rc;
	int wr;
};

static uintptr
translateflat(uintptr va, uintptr *pa, int *perm)
{
	if(sizeof(uintptr) != 4 && va >> 32 != 0) return 0;
	*pa = va;
	if(va == 0)
		return 0xFFFFFFFFUL;
	if(perm != 0) *perm = -1;
	return -va;
}

static uintptr
translate32(uintptr va, uintptr *pa, int *perm)
{
	void *pd, *pt;
	u32int pde, pte;

	if(sizeof(uintptr) != 4 && va >> 32 != 0) return -1;
	pd = gptr(rget("cr3") & ~0xfff, 4096);
	if(pd == nil) return 0;
	pde = GET32(pd, (va >> 22) * 4);
	if(perm != nil) *perm = pde;
	if((pde & 1) == 0) return 0;
	if((pde & 0x80) != 0 && (rget("cr4real") & Cr4Pse) != 0){
		*pa = pde & 0xffc00000 | (uintptr)(pde & 0x3fe000) << 19 | va & 0x3fffff;
		return 0x400000 - (va & 0x3fffff);
	}
	pt = gptr(pde & ~0xfff, 4096);
	if(pt == nil) return 0;
	pte = GET32(pt, va >> 10 & 0xffc);
	if((pte & 1) == 0) return 0;
	if(perm != nil) *perm &= pte;
	*pa = pte & ~0xfff | va & 0xfff;
	return 0x1000 - (va & 0xfff);
}

static uintptr
translatepae(uintptr, uintptr *, int *)
{
	vmerror("PAE translation not implemented");
	return 0;
}

static uintptr
translate64(uintptr, uintptr *, int *)
{
	vmerror("long mode translation not implemented");
	return 0;	
}

static uintptr (*
translator(void))(uintptr, uintptr *, int *)
{
	uintptr cr0, cr4, efer;
	
	cr0 = rget("cr0real");
	if((cr0 & Cr0Pg) == 0)
		return translateflat;
	efer = rget("efer");
	if((efer & EferLme) != 0)
		return translate64;
	cr4 = rget("cr4real");
	if((cr4 & Cr4Pae) != 0)
		return translatepae;
	return translate32;
}

static void
vmemread0(void *aux)
{
	VMemReq *req;
	uintptr va, pa, n, ok, pok;
	void *v;
	uintptr (*trans)(uintptr, uintptr *, int *);
	uchar *p;
	int wr;
	
	req = aux;
	va = req->va;
	n = req->len;
	p = req->buf;
	wr = req->wr;
	trans = translator();
	while(n > 0){
		ok = trans(va, &pa, nil);
		if(ok == 0) break;
		if(ok > n) ok = n;
		v = gptr(pa, 1);
		if(v == nil) break;
		pok = gavail(v);
		if(ok > pok) ok = pok;
		if(wr)
			memmove(v, p, ok);
		else
			memmove(p, v, ok);
		n -= ok;
		p += ok;
		va += ok;
	}
	req->rc = req->len - n;
	qunlock(req);
}

uintptr
vmemread(void *buf, uintptr len, uintptr va)
{
	VMemReq req;
	
	memset(&req, 0, sizeof(VMemReq));
	req.wr = 0;
	req.buf = buf;
	req.len = len;
	req.va = va;
	qlock(&req);
	sendnotif(vmemread0, &req);
	qlock(&req);
	return req.rc;
}

uintptr
vmemwrite(void *buf, uintptr len, uintptr va)
{
	VMemReq req;
	
	memset(&req, 0, sizeof(VMemReq));
	req.wr = 1;
	req.buf = buf;
	req.len = len;
	req.va = va;
	qlock(&req);
	sendnotif(vmemread0, &req);
	qlock(&req);
	return req.rc;
}

int
x86access(int seg, uintptr addr0, int asz, uvlong *val, int sz, int acc, TLB *tlb)
{
	int cpl;
	static char *baser[] = {"csbase", "dsbase", "esbase", "fsbase", "gsbase", "ssbase"};
	static char *limitr[] = {"cslimit", "dslimit", "eslimit", "fslimit", "gslimit", "sslimit"};
	static char *permr[] = {"csperm", "dsperm", "esperm", "fsperm", "gsperm", "ssperm"};
	u32int limit, perm;
	uintptr addr, base, szmax;
	int pperm, wp, i;
	uintptr pa[8], pav;
	uintptr l;
	uchar *ptr;

	switch(asz){
	case 2: addr0 = (u16int)addr0; break;
	case 4: addr0 = (u32int)addr0; break;
	case 8: break;
	default:
		vmerror("invalid asz=%d in x86access", asz);
		assert(0);
	}
	assert(seg < SEGMAX && (uint)acc <= ACCX);
	addr = addr0;
	if(tlb != nil && tlb->asz == asz && tlb->seg == seg && tlb->acc == acc && addr >= tlb->start && addr + sz >= addr && addr + sz < tlb->end){
		ptr = tlb->base + addr;
		goto fast;
	}
	if(sizeof(uintptr) == 8 && asz == 8){
		if(seg == SEGFS || seg == SEGGS)
			addr += rget(baser[seg]);
		if((u16int)((addr >> 48) + 1) > 1){
		gpf:
			vmdebug("gpf");
			postexc("#gp", 0);
			return -1;
		}
		if((vlong)addr >= 0)
			szmax = (1ULL<<48) - addr;
		else
			szmax = -addr;
	}else{
		limit = rget(limitr[seg]);
		perm = rget(permr[seg]);
		if((perm & 0xc) == 0x4){
			if((u32int)(addr + sz - 1) < addr || addr <= limit)
				goto limfault;
			szmax = (u32int)-addr;
		}else{
			if((u64int)addr + sz - 1 >= limit){
			limfault:
				vmdebug("limit fault");
				postexc(seg == SEGSS ? "#ss" : "#gp", 0);
				return -1;
			}
			szmax = limit - addr + 1;
		}
		if((perm & 0x10080) != 0x80)
			goto gpf;
		switch(acc){
		case ACCR: if((perm & 0xa) == 8) goto gpf; break;
		case ACCW: if((perm & 0xa) != 2) goto gpf; break;
		case ACCX: if((perm & 8) == 0) goto gpf; break;
		}
		base = rget(baser[seg]);
		addr = (u32int)(addr + base);
	}
	cpl = rget("cs") & 3;
	wp = (rget("cr0real") & 1<<16) != 0;
	for(i = 0; i < sz; ){
		l = translator()(addr+i, &pav, &pperm);
		if(l == 0){
		pf:
			vmdebug("page fault @ %#p", addr+i);
			postexc("#pf", pperm & 1 | (acc == ACCW) << 1 | (cpl == 3) << 2 | (acc == ACCX) << 4);
			rset("cr2", addr+i);
			return -1;
		}
		if((cpl == 3 || wp) && acc == ACCW && (pperm & 2) == 0)
			goto pf;
		if(cpl == 3 && (pperm & 4) == 0)
			goto pf;
		if(i == 0 && l < szmax) szmax = l;
		while(i < sz && l-- > 0)
			pa[i++] = pav++;
	}
	if(szmax >= sz){
		ptr = gptr(pa[0], sz);
		if(ptr == nil) goto slow;
		if(tlb != nil){
			l = gavail(ptr);
			if(l < szmax) szmax = l;
			tlb->asz = asz;
			tlb->seg = seg;
			tlb->acc = acc;
			tlb->start = addr0;
			tlb->end = addr0 + szmax;
			tlb->base = ptr - addr0;
		}
	fast:
		if(acc == ACCW)
			switch(sz){
			case 1: PUT8(ptr, 0, *val); break;
			case 2: PUT16(ptr, 0, *val); break;
			case 4: PUT32(ptr, 0, *val); break;
			case 8: PUT64(ptr, 0, *val); break;
			}
		else
			switch(sz){
			case 1: *val = GET8(ptr, 0); break;
			case 2: *val = GET16(ptr, 0); break;
			case 4: *val = GET32(ptr, 0); break;
			case 8: *val = GET64(ptr, 0); break;
			}
	}else{
	slow:
		if(acc != ACCW)
			*val = 0;
		for(i = 0; i < sz; i++){
			ptr = gptr(pa[i], 1);
			if(ptr == nil)
				vmerror("x86access: access to unmapped address %#p", pa[i]);
			else if(acc == ACCW)
				*ptr = GET8(val, i);
			else
				PUT8(val, i, *ptr);	
		}
	}
	return 0;
}