shithub: riscv

Download patch

ref: dffbc1e45d61bb928ea6a9d0b1206d641daf24fe
parent: b5a6dc7849cbd9f1fd23183ba46f0d5deb24e81d
author: aiju <devnull@localhost>
date: Tue Jun 27 05:21:30 EDT 2017

vmx(1): I/O string instructions, incomplete support for IDE disks, misc fixes

--- a/sys/src/cmd/vmx/dat.h
+++ b/sys/src/cmd/vmx/dat.h
@@ -112,3 +112,32 @@
 	u32int chan;
 	VgaMode *next;
 };
+
+extern uchar cmos[0x30];
+
+extern void (*kconfig)(void);
+
+/* arguments for x86access */
+enum {
+	SEGCS,
+	SEGDS,
+	SEGES,
+	SEGFS,
+	SEGGS,
+	SEGSS,
+	SEGMAX,
+};
+
+enum {
+	ACCR,
+	ACCW,
+	ACCX,
+};
+
+/* used to speed up consecutive x86access calls */
+typedef struct TLB TLB;
+struct TLB {
+	int asz, seg, acc;
+	uintptr start, end;
+	uchar *base;
+};
--- a/sys/src/cmd/vmx/exith.c
+++ b/sys/src/cmd/vmx/exith.c
@@ -5,7 +5,7 @@
 #include "dat.h"
 #include "fns.h"
 
-int persist = 0;
+int persist = 1;
 
 typedef struct ExitInfo ExitInfo;
 struct ExitInfo {
@@ -67,43 +67,67 @@
 	return 0;
 }
 
-extern u32int io(int, u16int, u32int, int);
-
-u32int iodebug[32];
-
 static void
 iohandler(ExitInfo *ei)
 {
-	int port, len, isin;
+	int port, len, inc, isin;
+	int asz, seg;
+	uintptr addr;
 	u32int val;
-	u64int ax;
+	uvlong vval;
+	uintptr cx;
+	static int seglook[8] = {SEGES, SEGCS, SEGSS, SEGDS, SEGFS, SEGGS};
+	TLB tlb;
 	
 	port = ei->qual >> 16 & 0xffff;
 	len = (ei->qual & 7) + 1;
 	isin = (ei->qual & 8) != 0;
-	if((ei->qual & 1<<4) != 0){
-		vmerror("i/o string instruction not implemented");
-		postexc("#ud", NOERRC);
+	if((ei->qual & 1<<4) == 0){ /* not a string instruction */
+		if(isin){
+			val = io(1, port, 0, len);
+			rsetsz(RAX, val, len);
+		}else
+			io(0, port, rget(RAX), len);
+		skipinstr(ei);
 		return;
 	}
-	if(isin){
-		val = io(1, port, 0, len);
-		ax = rget(RAX);
-		if(len == 1) ax = ax & ~0xff | val & 0xff;
-		else if(len == 2) ax = ax & ~0xffff | val & 0xffff;
-		else ax = val;
-		rset(RAX, ax);
-	}else{
-		ax = rget(RAX);
-		if(len == 1) ax = (u8int) ax;
-		else if(len == 2) ax = (u16int) ax;
-		io(0, port, ax, len);
-		SET(val);
+	if((rget("flags") & 0x400) != 0) inc = -len;
+	else inc = len;
+	switch(ei->iinfo >> 7 & 7){
+	case 0: asz = 2; break;
+	default: asz = 4; break;
+	case 2: asz = 8; break;
 	}
-	if(port < 0x400 && (iodebug[port >> 5] >> (port & 31) & 1) != 0)
-		if(isin) vmdebug("in  %#.4ux <- %#ux", port, val);
-		else vmdebug("out %#.4ux <- %#ux", port, (int)ax);
+	if((ei->qual & 1<<5) != 0)
+		cx = rgetsz(RCX, asz);
+	else
+		cx = 1;
+	addr = isin ? rget(RDI) : rget(RSI);
+	if(isin)
+		seg = SEGES;
+	else
+		seg = seglook[ei->iinfo >> 15 & 7];
+	memset(&tlb, 0, sizeof(TLB));
+	for(; cx > 0; cx--){
+		if(isin){
+			vval = io(1, port, 0, len);
+			if(x86access(seg, addr, asz, &vval, len, ACCW, &tlb) < 0)
+				goto err;
+		}else{
+			if(x86access(seg, addr, asz, &vval, len, ACCR, &tlb) < 0)
+				goto err;
+			io(0, port, vval, len);
+		}
+		addr += inc;
+	}
 	skipinstr(ei);
+err:
+	if((ei->qual & 1<<5) != 0)
+		rsetsz(RCX, cx, asz);
+	if(isin)
+		rsetsz(RDI, addr, asz);
+	else
+		rsetsz(RSI, addr, asz);
 }
 
 typedef struct MemHandler MemHandler;
@@ -225,6 +249,8 @@
 	return nil;
 }
 
+int maxcpuid = 7;
+
 static void
 cpuid(ExitInfo *ei)
 {
@@ -237,7 +263,7 @@
 	if(cp == nil) cp = &def;
 	switch(ax){
 	case 0: /* highest register & GenuineIntel */
-		ax = 7;
+		ax = maxcpuid;
 		bx = cp->bx;
 		dx = cp->dx;
 		cx = cp->cx;
--- a/sys/src/cmd/vmx/fns.h
+++ b/sys/src/cmd/vmx/fns.h
@@ -3,6 +3,8 @@
 uvlong rget(char *);
 void rpoke(char *, uvlong, int);
 #define rset(a,b) rpoke(a,b,0)
+void rsetsz(char *, uvlong, int);
+uvlong rgetsz(char *, int);
 void processexit(char *);
 void pitadvance(void);
 void rtcadvance(void);
@@ -30,6 +32,7 @@
 void elcr(u16int);
 int mkvionet(char *);
 int mkvioblk(char *);
+int mkideblk(char *);
 char* rcflush(int);
 void i8042kick(void *);
 #define GET8(p,n) (*((u8int*)(p)+(n)))
@@ -44,3 +47,6 @@
 u32int vgagetpal(u8int);
 void vgasetpal(u8int, u32int);
 uintptr vmemread(void *, uintptr, uintptr);
+uintptr vmemwrite(void *, uintptr, uintptr);
+int x86access(int, uintptr, int, uvlong*, int, int, TLB *);
+u32int io(int, u16int, u32int, int);
--- /dev/null
+++ b/sys/src/cmd/vmx/ide.c
@@ -1,0 +1,591 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+typedef struct IDE IDE;
+typedef struct IDEIO IDEIO;
+
+struct IDEIO {
+	QLock;
+	Rendez;
+	u8int rbuf[8192];
+	u8int wbuf[1024];
+	u16int rbufrp, rbufwp;
+	u16int wbufrp, wbufwp;
+	vlong addr;
+	int cnt;
+	u8int err, scratched, wr;
+	enum {
+		IIOIDLE,
+		IIOBUSY,
+	} state;
+};
+
+struct IDE {
+	IDEIO io;
+	enum {
+		IDEPRESENT = 1,
+		IDEKEEPFEAT = 2,
+		IDEPIOREAD = 4,
+		IDEPIOWRITE = 8,
+	} flags;
+	u8int stat, err, irq;
+	u8int ctrl , feat;
+	u8int sec, cnt;
+	u16int cyl;
+	u8int head;
+	int fd;
+	vlong size;
+	int pcyl, phead, psec;
+	int lcyl, lhead, lsec;
+} ide[4];
+
+uchar ideint13[4*12];
+int idediskno;
+enum {
+	/* ctrl */
+	IDESRST = 4,
+	IDENIEN = 2,
+	/* stat */
+	IDEBUSY = 0x80,
+	IDEDRDY = 0x40,
+	IDEDSC = 0x10,
+	IDEDRQ = 0x08,
+	IDEERR = 0x01,
+	/* error */
+	IDEUNC = 0x40,
+	IDEIDNF = 0x10,
+	IDEABRT = 0x04,
+};
+
+static void
+idereset(IDE *d)
+{
+	d->ctrl &= IDESRST;
+	if((d->flags & IDEPRESENT) != 0){
+		qlock(&d->io);
+		while(d->io.state != IIOIDLE){
+			d->io.scratched = 1;
+			rwakeup(&d->io);
+			rsleep(&d->io);
+		}
+		d->io.rbufrp = d->io.rbufwp = 0;
+		d->io.scratched = 0;
+		qunlock(&d->io);
+		d->stat = IDEDRDY | IDEDSC;
+		d->err = 1;
+		d->flags &= IDEPRESENT | IDEKEEPFEAT;
+		d->sec = d->cnt = 1;
+		d->cyl = 0;
+		d->head = 0xa0;
+		d->feat = 0;
+	}
+}
+
+static void
+ideirq(IDE *d, int n)
+{
+	IDE *s;
+
+	if(n >= 0)
+		d->irq = n;
+	s = (d - ide & ~1) + (d->head >> 4 & 1) + ide;
+	irqline(14 + (d - ide)/2, s->irq && (s->ctrl & IDENIEN) == 0);
+}
+
+static vlong
+getlba(IDE *d)
+{
+	int he;
+
+	if((d->head & 0x40) != 0)
+		return d->sec | d->cyl << 8 | (d->head & 0xf) << 16;
+	if(d->sec == 0 || d->sec > d->lsec)
+		return -1;
+	he = d->head & 0xf;
+	if(d->cyl >= d->lcyl || he >= d->lhead)
+			return -1;
+	return d->sec - 1 + (he + d->cyl * d->lhead) * d->lsec;
+
+}
+
+static void
+idegoio(IDE *d, int wr)
+{
+	vlong addr;
+	
+	addr = getlba(d);
+	if(addr < 0){
+		vmerror("ide%d: access to invalid sector address (access to CHS=(%#.4ux,%#ux,%#.2ux); geometry is (%#.4ux,%#ux,%#.2ux)", d-ide, d->cyl, d->head&0xf, d->sec, d->lcyl, d->lhead, d->lsec);
+		postexc("#bp", NOERRC);
+		d->stat = IDEDRDY | IDEDSC | IDEDRQ | IDEERR;
+		d->err = IDEIDNF;
+		ideirq(d, 1);
+		return;
+	}
+	if(wr){
+		d->stat = IDEDRDY | IDEDRQ | IDEDSC;
+		d->flags |= IDEPIOWRITE;
+	}else{
+		d->stat = IDEDRDY | IDEBUSY | IDEDSC;
+		d->flags |= IDEPIOREAD;
+	}
+	qlock(&d->io);
+	while(d->io.state != IIOIDLE)
+		rsleep(&d->io);
+	d->io.addr = addr;
+	d->io.cnt = (d->cnt - 1 & 0xff) + 1;
+	d->io.err = 0;
+	d->io.wr = wr;
+	d->io.state = IIOBUSY;
+	rwakeup(&d->io);
+	qunlock(&d->io);
+}
+
+static void
+ideincaddr(IDE *d)
+{
+	if((d->head & 0x40) != 0){
+		if(d->sec++ == 255 && d->cyl++ == 65535)
+			d->head = d->head + 1 & 0xf | d->head & 0xf0;
+	}else{
+		if(d->sec++ == d->lsec){
+			d->sec = 1;
+			if((d->head & 0xf) == d->lhead-1){
+				d->head &= 0xf0;
+				if(d->cyl++ == d->lcyl)
+					d->cyl = 0;
+			}else
+				d->head++;
+		}
+	}
+}
+
+static void
+idesecrend(IDE *d)
+{
+	if((d->flags & IDEPIOREAD) == 0){
+		d->stat &= ~IDEDRQ;
+		return;
+	}
+	ideincaddr(d);
+	if(d->io.rbufwp == (u16int)(d->io.rbufrp + sizeof(d->io.rbuf) - 512))
+		rwakeup(&d->io);
+	if(--d->cnt == 0){
+		d->stat &= ~(IDEBUSY|IDEDRQ);
+		d->flags &= ~IDEPIOREAD;
+	}else if(d->io.rbufrp == d->io.rbufwp){
+		if(d->io.err != 0){
+			d->stat = d->stat | IDEERR;
+			d->err = d->io.err;
+			ideirq(d, 1);
+		}else
+			d->stat = d->stat & ~IDEDRQ | IDEBUSY;
+	}else
+		ideirq(d, 1);
+}
+
+static void
+idesecrdone(void *dp)
+{
+	IDE *d;
+	
+	d = dp;
+	qlock(&d->io);
+	d->stat = d->stat & ~IDEBUSY | IDEDRQ;
+	if(d->io.err != 0){
+		d->stat |= IDEERR;
+		d->err = d->io.err;
+	}
+	ideirq(d, 1);
+	qunlock(&d->io);
+}
+
+static void
+idesecwend(IDE *d)
+{
+	ideincaddr(d);
+	d->cnt--;
+	if((u16int)(d->io.wbufwp - 512) == d->io.wbufrp)
+		rwakeup(&d->io);
+	if(d->io.wbufwp == (u16int)(d->io.wbufrp + sizeof(d->io.wbuf))){
+		d->stat = d->stat & ~IDEDRQ | IDEBUSY;
+	}else{
+		if(d->cnt == 0)
+			d->stat = d->stat & ~(IDEDRQ|IDEBUSY);
+		ideirq(d, 1);
+	}
+}
+
+static void
+idesecwdone(void *dp)
+{
+	IDE *d;
+	
+	d = dp;
+	qlock(&d->io);
+	if(d->cnt == 0)
+		d->stat = d->stat & ~(IDEDRQ|IDEBUSY);
+	else
+		d->stat = d->stat & ~IDEBUSY | IDEDRQ;
+	ideirq(d, 1);
+	qunlock(&d->io);
+}
+
+typedef struct Sector Sector;
+struct Sector {
+	uchar data[512];
+	vlong addr;
+	Sector *next;
+};
+Sector *sectors;
+
+static int
+getsector(vlong a, uchar *p)
+{
+	Sector *s;
+	
+	for(s = sectors; s != nil; s = s->next)
+		if(s->addr == a){
+			vmdebug("reading updated sector %lld", a);
+			memmove(p, s->data, 512);
+			return 0;
+		}
+	return -1;
+}
+
+static void
+putsector(vlong a, uchar *dp)
+{
+	Sector *s, **p;
+	
+	for(p = &sectors; s = *p, s != nil; p = &s->next)
+		if(s->addr == a){
+			memmove(s->data, dp, 512);
+			return;
+		}
+	s = emalloc(sizeof(Sector));
+	s->addr = a;
+	memmove(s->data, dp, 512);
+	*p = s;
+}
+
+static void
+ideioproc(void *dp)
+{
+	IDE *d;
+	IDEIO *io;
+	vlong a;
+	uchar *p;
+	int i, n;
+	
+	d = dp;
+	io = &d->io;
+	threadsetname("ide");
+	for(;;){
+		qlock(io);
+		io->state = IIOIDLE;
+		rwakeup(io);
+		while(io->state == IIOIDLE)
+			rsleep(io);
+		a = io->addr;
+		n = io->cnt;
+		qunlock(io);
+		
+		if(io->wr){
+			for(i = 0; i < n; i++){
+				qlock(io);
+				while(!io->scratched && io->wbufrp == (io->wbufwp & ~511))
+					rsleep(io);
+				if(io->scratched){
+					qunlock(io);
+					break;
+				}
+				p = io->wbuf + (io->wbufrp & sizeof(io->wbuf) - 1);
+				qunlock(io);
+				putsector(a+i, p);
+				qlock(io);
+				if(io->wbufwp == (u16int)(io->wbufrp + sizeof(io->wbuf)))
+					sendnotif(idesecwdone, d);
+				io->wbufrp += 512;
+				qunlock(io);
+			}
+		}else{
+			for(i = 0; i < n; i++){
+				qlock(io);
+				while(!io->scratched && io->rbufwp == (u16int)((io->rbufrp & ~511) + sizeof(io->rbuf)))
+					rsleep(io);
+				if(io->scratched){
+					qunlock(io);
+					break;
+				}
+				p = io->rbuf + (io->rbufwp & sizeof(io->rbuf) - 1);
+				qunlock(io);
+				werrstr("eof");
+				if(getsector(a+i, p) < 0 && pread(d->fd, p, 512, (a+i)*512) < 512){
+					vmerror("ide%d: read: %r", d - ide);
+					qlock(io);
+					io->err = IDEUNC;
+					qunlock(io);
+					sendnotif(idesecrdone, d);
+					break;
+				}
+				qlock(io);
+				if(io->rbufrp == io->rbufwp)
+					sendnotif(idesecrdone, d);
+				io->rbufwp += 512;
+				qunlock(io);
+			}
+		}
+	}
+}
+
+static void
+idecmd(IDE *d, u8int cmd)
+{
+	u8int *p;
+	vlong vl;
+
+	if(cmd == 0)
+		return;
+	switch(cmd){
+	case 0x90:
+		break;
+	default:
+		if((d->flags & IDEPRESENT) == 0){
+			vmerror("ide%d: command %#ux issued to absent drive", d-ide, cmd);
+			return;
+		}
+	}
+	if(cmd >> 4 == 1 || cmd >> 4 == 7){
+		/* relibrate / seek */
+		d->stat = IDEDRDY|IDEDSC;
+		ideirq(d, 1);
+		return;
+	}
+	switch(cmd){
+	case 0x20: case 0x21: /* read (pio) */
+		idegoio(d, 0);
+		break;
+	case 0x30: case 0x31: /* write (pio) */
+		idegoio(d, 1);
+		break;
+	case 0x90: /* diagnostics */
+		d = (d - ide & ~1) + ide;
+		d[0].err = 0;
+		d[1].err = 0;
+		if((d->flags & IDEPRESENT) != 0){
+			d->stat = IDEDRDY|IDEDSC;
+			ideirq(d, 1);
+		}
+		break;
+	case 0x91: /* set translation mode */
+		d->lhead = (d->head & 0xf) + 1;
+		d->lsec = d->cnt;
+		if(d->cnt != 0){
+			vl = d->size / (d->lhead * d->lsec);
+			d->lcyl = vl >= 65535 ? 65535 : vl;
+		}
+		d->stat = IDEDRDY|IDEDSC;
+		ideirq(d, 1);
+		break;
+	case 0xec: /* identify */
+		qlock(&d->io);
+		while(d->io.state != IIOIDLE)
+			rsleep(&d->io);
+		p = d->io.rbuf + (d->io.rbufwp & sizeof(d->io.rbuf) - 1);
+		d->io.rbufwp += 512;
+		memset(p, 0, 512);
+		strcpy((char*)p+20, "13149562358579393248");
+		strcpy((char*)p+46, ".2.781  ");
+		sprint((char*)p+54, "%-40s", "jhidks s");
+		PUT16(p, 0, 0x40);
+		PUT16(p, 2, d->pcyl);
+		PUT16(p, 6, d->phead);
+		PUT16(p, 8, d->psec << 9);
+		PUT16(p, 10, 512);
+		PUT16(p, 12, d->psec);
+		PUT16(p, 98, 0x200);
+		if(d->lsec != 0){
+			PUT16(d, 106, 1);
+			PUT16(d, 108, d->lcyl);
+			PUT16(d, 110, d->lhead);
+			PUT16(d, 112, d->lsec);
+			PUT32(d, 114, d->lcyl * d->lhead * d->lsec);
+		}
+		PUT32(p, 120, d->size >= (u32int)-1 ? -1 : d->size);
+		PUT16(p, 160, 7);
+		qunlock(&d->io);
+		d->stat = IDEDRDY|IDEDSC|IDEDRQ;
+		ideirq(d, 1);
+		break;
+	case 0xef: /* set feature */
+		switch(d->feat){
+		case 1: case 0x81: /* enable/disable 8-bit transfers */
+		case 2: case 0x82: /* enable/disable cache */
+			break;
+		case 0x66: d->flags |= IDEKEEPFEAT; break; /* retain settings */
+		case 0xcc: d->flags &= ~IDEKEEPFEAT; break; /* revert to default on reset */
+		default:
+			vmerror("ide%d: unknown feature %#ux", d-ide, d->feat);
+			d->stat = IDEDRDY|IDEDSC|IDEERR;
+			d->err = IDEABRT;
+			return;
+		}
+		d->stat = IDEDRDY|IDEDSC;
+		break;
+	default:
+		vmerror("ide%d: unknown command %#ux", d-ide, cmd);
+		d->stat = IDEDRDY|IDEDSC|IDEERR;
+		d->err = IDEABRT;
+	}
+}
+
+u32int
+ideio(int isin, u16int port, u32int val, int sz, void *)
+{
+	IDE *d, *e;
+	u32int rc;
+
+	d = &ide[2 * ((port & 0x80) == 0)];
+	d += d->head >> 4 & 1;
+	e = (d - ide ^ 1) + ide;
+	if((port|0x80) != 0x1f0){
+		if(sz != 1)
+			vmerror("ide: access to port %#x with incorrect size %d", port, sz);
+		val = (u8int) val;
+	}
+	if(isin && (d->flags & IDEPRESENT) == 0)
+		return 0;
+	switch(isin << 16 | port | 0x80){
+	case 0x001f0:
+		if((d->flags & IDEPIOWRITE) == 0)
+			return 0;
+		qlock(&d->io);
+		PUT16(d->io.wbuf, d->io.wbufwp & sizeof(d->io.wbuf) - 1, (u16int)val);
+		d->io.wbufwp += 2;
+		if((d->io.wbufwp & 511) == 0)
+			idesecwend(d);
+		qunlock(&d->io);
+		if(sz == 4)
+			ideio(0, port, val >> 16, 2, nil);
+		return 0;
+	case 0x001f1: d->feat = e->feat = val; return 0;
+	case 0x001f2: d->cnt = e->cnt = val; return 0;
+	case 0x001f3: d->sec = e->sec = val; return 0;
+	case 0x001f4: d->cyl = d->cyl & 0xff00 | val; e->cyl = e->cyl & 0xff00 | val; return 0;
+	case 0x001f5: d->cyl = d->cyl & 0xff | val << 8; e->cyl = e->cyl & 0xff | val << 8; return 0;
+	case 0x001f6: d->head = e->head = val | 0xa0; return 0;
+	case 0x001f7: idecmd(d, val); return 0;
+	case 0x003f6:
+		d->ctrl = e->ctrl = val;
+		if((val & IDESRST) != 0){
+			idereset(d);
+			idereset(e);
+		}
+		ideirq(d, -1);
+		return 0;
+
+	case 0x101f0:
+		qlock(&d->io);
+		if(d->io.rbufrp != d->io.rbufwp){
+			rc = GET16(d->io.rbuf, d->io.rbufrp & sizeof(d->io.rbuf)-1);
+			d->io.rbufrp += 2;
+			if((d->io.rbufrp & 511) == 0)
+				idesecrend(d);
+		}else
+			rc = 0;
+		qunlock(&d->io);
+		if(sz == 4)
+			rc |= ideio(1, port, 0, 2, nil) << 16;
+		return rc;
+	case 0x101f1: return d->err;
+	case 0x101f2: return d->cnt;
+	case 0x101f3: return d->sec;
+	case 0x101f4: return (u8int)d->cyl;
+	case 0x101f5: return d->cyl >> 8;
+	case 0x101f6: return d->head;
+	case 0x101f7:
+		ideirq(d, 0);
+	case 0x103f6:
+		if((d->ctrl & IDESRST) != 0)
+			return IDEBUSY;
+		rc = d->stat;
+		/* stupid hack to work around different expectations of how DRQ behaves on error */
+		if((d->stat & IDEERR) != 0)
+			d->stat &= ~IDEDRQ;
+		return rc;
+	default: return iowhine(isin, port, val, sz, "ide");
+	}
+}
+
+static int
+idegeom(vlong vsz, int *cp, int *hp, int *sp)
+{
+	int sz, c, h, s, t;
+	int max;
+	
+	if(vsz >= 63*255*1023){
+		*cp = 1023;
+		*hp = 255;
+		*sp = 63;
+		return 0;
+	}
+	sz = vsz;
+	max = 0;
+	for(s = 63; s >= 1; s--)
+		for(h = 1; h <= 16; h++){
+			c = sz / (h * s);
+			if(c >= 1023) c = 1023;
+			t = c * h * s;
+			if(t > max){
+				max = t;
+				*cp = c;
+				*hp = h;
+				*sp = s;
+			}
+		}
+	return max == 0 ? -1 : 0;
+}
+
+int
+mkideblk(char *fn)
+{
+	int fd;
+	IDE *d;
+	uchar *p;
+	
+	if(idediskno >= 4){
+		werrstr("too many ide disks");
+		return -1;
+	}
+	d = &ide[idediskno];
+	d->io.Rendez.l = &d->io;
+	fd = open(fn, ORDWR);
+	if(fd < 0)
+		return -1;
+	d->size = seek(fd, 0, 2) >> 9;
+	if(idegeom(d->size, &d->pcyl, &d->phead, &d->psec) < 0){
+		werrstr("disk file too small");
+		return -1;
+	}
+	if(idediskno < 2){
+		cmos[0x12] |= 0xf << (1-idediskno) * 4;
+		cmos[0x19 + idediskno] = 47;
+	}
+	p = ideint13 + idediskno * 12;
+	PUT16(p, 0, 0x80 | idediskno);
+	PUT32(p, 2, d->pcyl);
+	PUT16(p, 6, d->psec);
+	PUT16(p, 8, d->phead);
+	PUT16(p, 10, 1);
+	d->flags |= IDEPRESENT;
+	d->fd = fd;
+	idereset(&ide[idediskno]);
+	idediskno++;
+	proccreate(ideioproc, d, 8192);
+	return 0;
+
+}
--- a/sys/src/cmd/vmx/io.c
+++ b/sys/src/cmd/vmx/io.c
@@ -7,7 +7,7 @@
 #include "dat.h"
 #include "fns.h"
 
-static uchar cmos[0x30] = {
+uchar cmos[0x30] = {
 	[1] 0xff, [3] 0xff, [5] 0xff, 
 	[0xa] 0x26,
 	[0xb] 1<<1,
@@ -727,6 +727,7 @@
 		case 0xee: keyputc(0xee); break; /* echo */
 		default:
 			vmerror("unknown kbd command %#ux", val);
+			keyputc(0xfe);
 		}
 	}
 	i8042kick(nil);
@@ -1124,14 +1125,6 @@
 	}
 }
 
-static u32int
-ideio(int, u16int port, u32int, int, void *)
-{
-	switch(port & 7){
-	case 7: return 0x71;
-	default: return -1;
-	}
-}
 
 /* floppy dummy controller */
 typedef struct Floppy Floppy;
@@ -1226,6 +1219,7 @@
 u32int vgaio(int, u16int, u32int, int, void *);
 u32int pciio(int, u16int, u32int, int, void *);
 u32int vesaio(int, u16int, u32int, int, void *);
+u32int ideio(int, u16int, u32int, int, void *);
 IOHandler handlers[] = {
 	0x20, 0x21, picio, nil,
 	0x40, 0x43, pitio, nil,
@@ -1268,8 +1262,8 @@
 	0xa79, 0xa79, nopio, nil, /* isa pnp */
 };
 
-u32int
-io(int dir, u16int port, u32int val, int size)
+static u32int
+io0(int dir, u16int port, u32int val, int size)
 {
 	IOHandler *h;
 	extern PCIBar iobars;
@@ -1282,4 +1276,29 @@
 		if(port >= p->addr && port < p->addr + p->length)
 			return p->io(dir, port - p->addr, val, size, p->aux);
 	return iowhine(dir, port, val, size, nil);
+}
+
+u32int iodebug[32];
+
+u32int
+io(int isin, u16int port, u32int val, int sz)
+{
+	int dbg;
+	
+	dbg = port < 0x400 && (iodebug[port >> 5] >> (port & 31) & 1) != 0;
+	if(isin){
+		val = io0(isin, port, val, sz);
+		if(sz == 1) val = (u8int)val;
+		else if(sz == 2) val = (u16int)val;
+		if(dbg)
+			vmdebug("in  %#.4ux <- %#.*ux", port, sz*2, val);
+		return val;
+	}else{
+		if(sz == 1) val = (u8int)val;
+		else if(sz == 2) val = (u16int)val;
+		io0(isin, port, val, sz);
+		if(dbg)
+			vmdebug("out %#.4ux <- %#.*ux", port, sz*2, val);
+		return 0;
+	}
 }
--- a/sys/src/cmd/vmx/ksetup.c
+++ b/sys/src/cmd/vmx/ksetup.c
@@ -244,7 +244,7 @@
 	if(sz == -1)
 		sz = elf64 ? 8 : 4;
 	if(*p + sz > e){
-		fprint(2, "out of bounds: %p > %p", *p + sz, e);
+		fprint(2, "out of bounds: %#p > %#p", *p + sz, e);
 		return 0;
 	}
 	switch(sz){
@@ -396,12 +396,12 @@
 		}
 		v = gptr(ph[i].paddr, ph[i].memsz);
 		if(v == nil)
-			sysfatal("invalid address %p (length=%p) in elf", (void*)ph[i].paddr, (void*)ph[i].memsz);
+			sysfatal("invalid address %#p (length=%#p) in elf", (void*)ph[i].paddr, (void*)ph[i].memsz);
 		if(ph[i].type == PT_OPENBSD_RANDOMIZE)
 			genrandom(v, ph[i].memsz);
 		else{
 			if(ph[i].filesz > ph[i].memsz)
-				sysfatal("elf: header entry shorter in memory than in the file (%p < %p)", (void*)ph[i].memsz, (void*)ph[i].filesz);
+				sysfatal("elf: header entry shorter in memory than in the file (%#p < %#p)", (void*)ph[i].memsz, (void*)ph[i].filesz);
 			if(ph[i].filesz != 0)
 				epreadn(v, ph[i].filesz, ph[i].offset, "elfdata");
 			if(ph[i].filesz < ph[i].memsz)
--- a/sys/src/cmd/vmx/mkfile
+++ b/sys/src/cmd/vmx/mkfile
@@ -8,6 +8,7 @@
 	ksetup.$O \
 	exith.$O \
 	io.$O \
+	ide.$O \
 	vga.$O \
 	pci.$O \
 	virtio.$O \
--- a/sys/src/cmd/vmx/pci.c
+++ b/sys/src/cmd/vmx/pci.c
@@ -280,7 +280,7 @@
 	int i;
 	
 	iop = 0x1000;
-	irqs = 1<<5|1<<7|1<<9|1<<10|1<<11|1<<14|1<<15;
+	irqs = 1<<5|1<<7|1<<9|1<<10|1<<11;
 	uirqs = 0;
 	irq = 0;
 	for(d = pcidevs; d != nil; d = d->next){
--- a/sys/src/cmd/vmx/vga.c
+++ b/sys/src/cmd/vmx/vga.c
@@ -40,6 +40,8 @@
 	u8int sidx; /* sequencer */
 	u16int rdidx, wdidx; /* bit 0-1: color */
 	u8int attr[32];
+	u8int seq[5];
+	u8int graph[9];
 	u32int pal[256];
 	Image *col[256];
 	Image *acol[16];
@@ -155,9 +157,9 @@
 
 	if(novga)
 		return 0;
-	if(port == 0x3d4 && sz == 2 && !isin){
-		vgaio(0, 0x3d4, (u8int)val, 1, nil);
-		return vgaio(0, 0x3d5, (u8int)(val >> 8), 1, nil);
+	if(port != 0x3df && sz == 2 && !isin){
+		vgaio(0, port, (u8int)val, 1, nil);
+		return vgaio(0, port+1, (u8int)(val >> 8), 1, nil);
 	}
 	if(sz != 1) vmdebug("vga: non-byte access to port %#ux, sz=%d", port, sz);
 	val = (u8int) val;
@@ -173,7 +175,12 @@
 		return 0;
 	case 0x3c2: vga.miscout = val; return 0;
 	case 0x3c4: vga.sidx = val; return 0;
-	case 0x3c5: vmerror("vga: write to unknown sequencer register %#ux (val=%#ux)", vga.sidx, val); return 0;
+	case 0x3c5:
+		switch(vga.sidx){
+		case 0: vga.seq[vga.sidx] = val & 3; return 0;
+		case 4: vga.seq[vga.sidx] = val & 0xe; return 0;
+		default: vmerror("vga: write to unknown sequencer register %#ux (val=%#ux)", vga.sidx, val); return 0;
+		}
 	case 0x3c6: return 0;
 	case 0x3c7: vga.rdidx = val << 2; return 0;
 	case 0x3c8: vga.wdidx = val << 2; return 0;
@@ -183,7 +190,14 @@
 		vga.wdidx = vga.wdidx + 1 + (vga.wdidx >> 1 & 1) & 0x3ff;
 		return 0;
 	case 0x3ce: vga.gidx = val; return 0;
-	case 0x3cf: vmerror("vga: write to unknown graphics register %#ux (val=%#ux)", vga.gidx, val); return 0;
+	case 0x3cf:
+		switch(vga.gidx){
+		case 4: vga.graph[vga.gidx] = val & 3; break;
+		case 8: vga.graph[vga.gidx] = val; break;
+		default:
+			vmerror("vga: write to unknown graphics register %#ux (val=%#ux)", vga.gidx, val);
+		}
+		return 0;
 	case 0x3d4: vga.cidx = val; return 0;
 	case 0x3d5:
 		switch(vga.cidx){
@@ -197,7 +211,13 @@
 	case 0x103c0: return vga.aidx & 0x3f;
 	case 0x103c1: return vga.attr[vga.aidx & 0x1f];
 	case 0x103c4: return vga.sidx;
-	case 0x103c5: vmerror("vga: read from unknown sequencer register %#ux (val=%#ux)", vga.sidx, val); return 0;
+	case 0x103c5:	
+		switch(vga.sidx){
+		case 0:
+		case 4:
+			return vga.seq[vga.sidx];
+		default: vmerror("vga: read from unknown sequencer register %#ux (val=%#ux)", vga.sidx, val); return 0;
+		}
 	case 0x103c6: return 0xff;
 	case 0x103c7: return vga.rdidx >> 2;
 	case 0x103c8: return vga.wdidx >> 2;
@@ -207,7 +227,15 @@
 		return m;
 	case 0x103cc: return vga.miscout;
 	case 0x103ce: return vga.gidx;
-	case 0x103cf: vmerror("vga: read from unknown graphics register %#ux", vga.gidx); return 0;
+	case 0x103cf:
+		switch(vga.gidx){
+		case 4:
+		case 8:
+			return vga.graph[vga.gidx];
+		default:
+			vmerror("vga: read from unknown graphics register %#ux", vga.gidx);
+			return 0;
+		}
 	case 0x103d4: return vga.cidx;
 	case 0x103d5:
 		switch(vga.cidx){
--- a/sys/src/cmd/vmx/virtio.c
+++ b/sys/src/cmd/vmx/virtio.c
@@ -145,7 +145,7 @@
 		b->flags = GET16(dp, 12);
 		b->p = gptr(b->addr, b->len);
 		if(b->p == nil){
-			vmerror("virtio device %#x: invalid buffer pointer %p in queue, ignoring descriptor", q->d->pci->bdf, (void*)b->addr);
+			vmerror("virtio device %#x: invalid buffer pointer %#p in queue, ignoring descriptor", q->d->pci->bdf, (void*)b->addr);
 			free(b);
 			break;
 		}
@@ -291,7 +291,7 @@
 	sz = sz1 + (-(-(8 * q->size + 6) & -4096));
 	p = gptr(addr, sz);
 	if(p == nil)
-		vmerror("virtio device %#x: attempt to set queue to invalid address %p", q->d->pci->bdf, (void *) addr);
+		vmerror("virtio device %#x: attempt to set queue to invalid address %#p", q->d->pci->bdf, (void *) addr);
 	qlock(q);
 	q->addr = addr;
 	if(p == nil){
--- a/sys/src/cmd/vmx/vmx.c
+++ b/sys/src/cmd/vmx/vmx.c
@@ -223,19 +223,52 @@
 		rcdirty[i>>6] |= 1ULL<<(i&63);
 }
 
+uvlong
+rgetsz(char *reg, int sz)
+{
+	switch(sz){
+	case 1: return (u8int)rget(reg);
+	case 2: return (u16int)rget(reg);
+	case 4: return (u32int)rget(reg);
+	case 8: return rget(reg);
+	default:
+		vmerror("invalid size operand for rgetsz");
+		assert(0);
+		return 0;
+	}
+}
+
+void
+rsetsz(char *reg, uvlong val, int sz)
+{
+	switch(sz){
+	case 1: rset(reg, (u8int)val | rget(reg) & ~0xffULL); break;
+	case 2: rset(reg, (u16int)val | rget(reg) & ~0xffffULL); break;
+	case 4: rset(reg, (u32int)val); break;
+	case 8: rset(reg, val); break;
+	default:
+		vmerror("invalid size operand for rsetsz");
+		assert(0);
+	}
+}
+
 Region *
 mkregion(u64int pa, u64int end, int type)
 {
-	Region *r, **rp;
+	Region *r, *s, **rp;
 
 	r = emalloc(sizeof(Region));
-	if(end < pa) sysfatal("end of region %p before start of region %p", (void*)end, (void*)pa);
-	if((pa & BY2PG-1) != 0 || (end & BY2PG-1) != 0) sysfatal("address %p not page aligned", (void*)pa);
+	if(end < pa) sysfatal("end of region %p before start of region %#p", (void*)end, (void*)pa);
+	if((pa & BY2PG-1) != 0 || (end & BY2PG-1) != 0) sysfatal("address %#p not page aligned", (void*)pa);
 	r->start = pa;
 	r->end = end;
 	r->type = type;
-	for(rp = &mmap; *rp != nil; rp = &(*rp)->next)
+	for(s = mmap; s != nil; s = s->next)
+		if(!(pa < s->start && end < s->end || pa >= s->start && pa >= s->end))
+			sysfatal("region %#p-%#p overlaps region %#p-%#p", pa, end, s->start, s->end);
+	for(rp = &mmap; (*rp) != nil && (*rp)->start < end; rp = &(*rp)->next)
 		;
+	r->next = *rp;
 	*rp = r;
 	return r;
 }
@@ -487,6 +520,39 @@
 }
 
 static void
+setiodebug(char *s)
+{
+	char *p;
+	int n, m, neg;
+	extern u32int iodebug[32];
+	
+	do{
+		if(neg = *s == '!')
+			s++;
+		n = strtoul(s, &p, 0);
+		if(s == p)
+no:			sysfatal("invalid iodebug argument (error at %#q)", s);
+		if(n >= sizeof(iodebug)*8)
+range:			sysfatal("out of iodebug range (0-%#ux)", sizeof(iodebug)*8-1);
+		s = p + 1;
+		if(*p == '-'){
+			m = strtoul(s, &p, 0);
+			if(m >= sizeof(iodebug)*8)
+				goto range;
+			if(s == p || m < n) goto no;
+			s = p + 1;
+		}else
+			m = n;
+		for(; n <= m; n++)
+			if(neg)
+				iodebug[n>>5] &= ~(1<<(n&31));
+			else
+				iodebug[n>>5] |= 1<<(n&31);
+	}while(*p == ',');
+	if(*p != 0) goto no;
+}
+
+static void
 usage(void)
 {
 	char *blanks, *p;
@@ -499,6 +565,8 @@
 	threadexitsall("usage");
 }
 
+void (*kconfig)(void);
+
 void
 threadmain(int argc, char **argv)
 {
@@ -537,9 +605,16 @@
 		break;
 	case 'd':
 		assert(edevn < nelem(edev));
-		edev[edevn] = mkvioblk;
-		edevt[edevn] = "virtio block";
-		edevaux[edevn++] = strdup(EARGF(usage()));
+		edevaux[edevn] = strdup(EARGF(usage()));
+		if(strncmp(edevaux[edevn], "ide:", 4) == 0){
+			edevaux[edevn] += 4;
+			edev[edevn] = mkideblk;
+			edevt[edevn] = "ide block";
+		}else{
+			edev[edevn] = mkvioblk;
+			edevt[edevn] = "virtio block";
+		}
+		edevn++;
 		break;
 	case 'M':
 		gmemsz = siparse(EARGF(usage()));
@@ -552,6 +627,9 @@
 		if(srvname != nil) usage();
 		srvname = EARGF(usage());
 		break;
+	case L'ι':
+		setiodebug(EARGF(usage()));
+		break;
 	default:
 		usage();
 	} ARGEND;
@@ -586,6 +664,7 @@
 	pcibusmap();
 	
 	if(srvname != nil) init9p(srvname);
+	if(kconfig != nil) kconfig();
 	runloop();
 	exits(nil);
 }
--- a/sys/src/cmd/vmx/vmxgdb.c
+++ b/sys/src/cmd/vmx/vmxgdb.c
@@ -162,6 +162,7 @@
 			pos += 4;
 		}
 		if(i == nelem(regname)) continue;
+		if(f[0][1] == 's' && f[0][2] == 0) v = 0;
 		l = 4;
 		while(l--){
 			sprint(tbuf, "%.2ux", (u8int)v);
--- a/sys/src/cmd/vmx/x86.c
+++ b/sys/src/cmd/vmx/x86.c
@@ -11,20 +11,22 @@
 	uintptr va, len;
 	void *buf;
 	uintptr rc;
+	int wr;
 };
 
 static uintptr
-translateflat(uintptr va, uintptr *pa, 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, uintptr cr4)
+translate32(uintptr va, uintptr *pa, int *perm)
 {
 	void *pd, *pt;
 	u32int pde, pte;
@@ -33,21 +35,23 @@
 	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 && (cr4 & Cr4Pse) != 0){
-		*pa = pde & (1<<22) - 1 | (uintptr)(pde & 0xfe000) << 19;
-		return (1<<22) - (va & (1<<22)-1);
+	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 *, uintptr)
+translatepae(uintptr, uintptr *, int *)
 {
 	vmerror("PAE translation not implemented");
 	return 0;
@@ -54,7 +58,7 @@
 }
 
 static uintptr
-translate64(uintptr, uintptr *, uintptr)
+translate64(uintptr, uintptr *, int *)
 {
 	vmerror("long mode translation not implemented");
 	return 0;	
@@ -61,7 +65,7 @@
 }
 
 static uintptr (*
-translator(uintptr *cr4p))(uintptr, uintptr *, uintptr)
+translator(void))(uintptr, uintptr *, int *)
 {
 	uintptr cr0, cr4, efer;
 	
@@ -72,7 +76,6 @@
 	if((efer & EferLme) != 0)
 		return translate64;
 	cr4 = rget("cr4real");
-	*cr4p = cr4;
 	if((cr4 & Cr4Pae) != 0)
 		return translatepae;
 	return translate32;
@@ -82,18 +85,20 @@
 vmemread0(void *aux)
 {
 	VMemReq *req;
-	uintptr va, pa, n, ok, pok, cr4;
+	uintptr va, pa, n, ok, pok;
 	void *v;
-	uintptr (*trans)(uintptr, uintptr *, uintptr);
+	uintptr (*trans)(uintptr, uintptr *, int *);
 	uchar *p;
+	int wr;
 	
 	req = aux;
 	va = req->va;
 	n = req->len;
 	p = req->buf;
-	trans = translator(&cr4);
+	wr = req->wr;
+	trans = translator();
 	while(n > 0){
-		ok = trans(va, &pa, cr4);
+		ok = trans(va, &pa, nil);
 		if(ok == 0) break;
 		if(ok > n) ok = n;
 		v = gptr(pa, 1);
@@ -100,7 +105,10 @@
 		if(v == nil) break;
 		pok = gavail(v);
 		if(ok > pok) ok = pok;
-		memmove(p, v, ok);
+		if(wr)
+			memmove(v, p, ok);
+		else
+			memmove(p, v, ok);
 		n -= ok;
 		p += ok;
 		va += ok;
@@ -115,6 +123,7 @@
 	VMemReq req;
 	
 	memset(&req, 0, sizeof(VMemReq));
+	req.wr = 0;
 	req.buf = buf;
 	req.len = len;
 	req.va = va;
@@ -122,4 +131,151 @@
 	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;
 }