xref: /plan9-contrib/sys/src/cmd/aux/realemu/main.c (revision ccaec48a6a7d481d90233fb80c88e608b0a02604)
1 #include <u.h>
2 #include <libc.h>
3 #include "dat.h"
4 #include "fns.h"
5 
6 /* for fs */
7 #include <auth.h>
8 #include <fcall.h>
9 #include <thread.h>
10 #include <9p.h>
11 
12 #include "/386/include/ureg.h"
13 
14 enum {
15 	MEMSIZE = 0x100000,
16 
17 	RMBUF = 0x9000,
18 	RMCODE = 0x8000,
19 
20 	PITHZ = 1193182,
21 	PITNS = 1000000000/PITHZ,
22 };
23 
24 static Cpu cpu;
25 static uchar memory[MEMSIZE+4];
26 static uchar pageregtmp[0x10];
27 static int portfd[5];
28 static int realmemfd;
29 static int cputrace;
30 static int porttrace;
31 static Pit pit[3];
32 static uchar rtcaddr;
33 
34 static vlong pitclock;
35 
36 static void
startclock(void)37 startclock(void)
38 {
39 	pitclock = nsec();
40 }
41 
42 static void
runclock(void)43 runclock(void)
44 {
45 	vlong now, dt;
46 
47 	now = nsec();
48 	dt = now - pitclock;
49 	if(dt >= PITNS){
50 		clockpit(pit, dt/PITNS);
51 		pitclock = now;
52 	}
53 }
54 
55 static ulong
gw1(uchar * p)56 gw1(uchar *p)
57 {
58 	return p[0];
59 }
60 static ulong
gw2(uchar * p)61 gw2(uchar *p)
62 {
63 	return (ulong)p[0] | (ulong)p[1]<<8;
64 }
65 static ulong
gw4(uchar * p)66 gw4(uchar *p)
67 {
68 	return (ulong)p[0] | (ulong)p[1]<<8 | (ulong)p[2]<<16 | (ulong)p[3]<<24;
69 }
70 static ulong (*gw[5])(uchar *p) = {
71 	[1] gw1,
72 	[2] gw2,
73 	[4] gw4,
74 };
75 
76 static void
pw1(uchar * p,ulong w)77 pw1(uchar *p, ulong w)
78 {
79 	p[0] = w & 0xFF;
80 }
81 static void
pw2(uchar * p,ulong w)82 pw2(uchar *p, ulong w)
83 {
84 	p[0] = w & 0xFF;
85 	p[1] = (w>>8) & 0xFF;
86 }
87 static void
pw4(uchar * p,ulong w)88 pw4(uchar *p, ulong w)
89 {
90 	p[0] = w & 0xFF;
91 	p[1] = (w>>8) & 0xFF;
92 	p[2] = (w>>16) & 0xFF;
93 	p[3] = (w>>24) & 0xFF;
94 }
95 static void (*pw[5])(uchar *p, ulong w) = {
96 	[1] pw1,
97 	[2] pw2,
98 	[4] pw4,
99 };
100 
101 static ulong
rbad(void *,ulong off,int)102 rbad(void *, ulong off, int)
103 {
104 	fprint(2, "bad mem read %.5lux\n", off);
105 	trap(&cpu, EMEM);
106 
107 	/* not reached */
108 	return 0;
109 }
110 
111 static void
wbad(void *,ulong off,ulong,int)112 wbad(void *, ulong off, ulong, int)
113 {
114 	fprint(2, "bad mem write %.5lux\n", off);
115 	trap(&cpu, EMEM);
116 }
117 
118 static ulong
rmem(void *,ulong off,int len)119 rmem(void *, ulong off, int len)
120 {
121 	return gw[len](memory + off);
122 }
123 
124 static void
wmem(void *,ulong off,ulong w,int len)125 wmem(void *, ulong off, ulong w, int len)
126 {
127 	pw[len](memory + off, w);
128 }
129 
130 static ulong
rrealmem(void *,ulong off,int len)131 rrealmem(void *, ulong off, int len)
132 {
133 	uchar data[4];
134 
135 	if(pread(realmemfd, data, len, off) != len){
136 		fprint(2, "bad real mem read %.5lux: %r\n", off);
137 		trap(&cpu, EMEM);
138 	}
139 	return gw[len](data);
140 }
141 
142 static void
wrealmem(void *,ulong off,ulong w,int len)143 wrealmem(void *, ulong off, ulong w, int len)
144 {
145 	uchar data[4];
146 
147 	pw[len](data, w);
148 	if(pwrite(realmemfd, data, len, off) != len){
149 		fprint(2, "bad real mem write %.5lux: %r\n", off);
150 		trap(&cpu, EMEM);
151 	}
152 }
153 
154 
155 static ulong
rport(void *,ulong p,int len)156 rport(void *, ulong p, int len)
157 {
158 	uchar data[4];
159 	ulong w;
160 
161 	switch(p){
162 	case 0x20:	/* PIC 1 */
163 	case 0x21:
164 		w = 0;
165 		break;
166 	case 0x40:
167 	case 0x41:
168 	case 0x42:
169 	case 0x43:
170 		runclock();
171 		w = rpit(pit, p - 0x40);
172 		break;
173 	case 0x60:	/* keyboard data output buffer */
174 		w = 0;
175 		break;
176 	case 0x61:	/* keyboard controller port b */
177 		runclock();
178 		w = pit[2].out<<5 | pit[2].gate;
179 		break;
180 	case 0x62:	/* PPI (XT only) */
181 		runclock();
182 		w = pit[2].out<<5;
183 		break;
184 	case 0x63:	/* PPI (XT only) read dip switches */
185 		w = 0;
186 		break;
187 	case 0x65:	/* A20 gate */
188 		w = 1 << 2;
189 		break;
190 	case 0x70:	/* RTC addr */
191 		w = rtcaddr;
192 		break;
193 	case 0x71:	/* RTC data */
194 		w = 0xFF;
195 		break;
196 	case 0x80:	/* extra dma registers (temp) */
197 	case 0x84:
198 	case 0x85:
199 	case 0x86:
200 	case 0x88:
201 	case 0x8c:
202 	case 0x8d:
203 	case 0x8e:
204 		w = pageregtmp[p-0x80];
205 		break;
206 	case 0x92:	/* A20 gate (system control port a) */
207 		w = 1 << 1;
208 		break;
209 	case 0xa0:	/* PIC 2 */
210 	case 0xa1:
211 		w = 0;
212 		break;
213 	default:
214 		if(pread(portfd[len], data, len, p) != len){
215 			fprint(2, "bad %d bit port read %.4lux: %r\n", len*8, p);
216 			trap(&cpu, EIO);
217 		}
218 		w = gw[len](data);
219 	}
220 	if(porttrace)
221 		fprint(2, "rport %.4lux %.*lux\n", p, len<<1, w);
222 	return w;
223 }
224 
225 static void
wport(void *,ulong p,ulong w,int len)226 wport(void *, ulong p, ulong w, int len)
227 {
228 	uchar data[4];
229 
230 	if(porttrace)
231 		fprint(2, "wport %.4lux %.*lux\n", p, len<<1, w);
232 
233 	switch(p){
234 	case 0x20:	/* PIC 1 */
235 	case 0x21:
236 		break;
237 	case 0x40:
238 	case 0x41:
239 	case 0x42:
240 	case 0x43:
241 		runclock();
242 		wpit(pit, p - 0x40, w);
243 		break;
244 	case 0x60:	/* keyboard controller data port */
245 		break;
246 	case 0x61:	/* keyboard controller port B */
247 		setgate(&pit[2], w & 1);
248 		break;
249 	case 0x62:	/* PPI (XT only) */
250 	case 0x63:
251 	case 0x64:	/* KB controller input buffer (ISA, EISA) */
252 	case 0x65:	/* A20 gate (bit 2) */
253 		break;
254 	case 0x70:	/* RTC addr */
255 		rtcaddr = w & 0xFF;
256 		break;
257 	case 0x71:	/* RTC data */
258 		break;
259 	case 0x80:
260 	case 0x84:
261 	case 0x85:
262 	case 0x86:
263 	case 0x88:
264 	case 0x8c:
265 	case 0x8d:
266 	case 0x8e:
267 		pageregtmp[p-0x80] = w & 0xFF;
268 		break;
269 	case 0x92:	/* system control port a */
270 	case 0x94:	/* system port enable setup register */
271 	case 0x96:
272 		break;
273 	case 0xA0:	/* PIC 2 */
274 	case 0xA1:
275 		break;
276 
277 	default:
278 		pw[len](data, w);
279 		if(pwrite(portfd[len], data, len, p) != len){
280 			fprint(2, "bad %d bit port write %.4lux: %r\n", len*8, p);
281 			trap(&cpu, EIO);
282 		}
283 	}
284 }
285 
286 static Bus memio[] = {
287 	/* 0 */ memory, rmem, wmem,	/* RAM: IVT, BIOS data area */
288 	/* 1 */ memory,	rmem, wmem,	/* custom */
289 	/* 2 */ nil,	rbad, wbad,
290 	/* 3 */ nil,	rbad, wbad,
291 	/* 4 */ nil,	rbad, wbad,
292 	/* 5 */ nil,	rbad, wbad,
293 	/* 6 */ nil,	rbad, wbad,
294 	/* 7 */ nil,	rbad, wbad,
295 	/* 8 */ nil,	rbad, wbad,
296 	/* 9 */ memory,	rmem, wmem,	/* RAM: extended BIOS data area */
297 	/* A */ nil,	rrealmem, wrealmem,	/* RAM: VGA framebuffer */
298 	/* B */ nil,	rrealmem, wrealmem,	/* RAM: VGA framebuffer */
299 	/* C */ memory,	rmem, wmem,	/* ROM: VGA BIOS */
300 	/* D */ nil,	rbad, wbad,
301 	/* E */ memory,	rmem, wmem,	/* ROM: BIOS */
302 	/* F */ memory,	rmem, wbad,	/* ROM: BIOS */
303 };
304 
305 static Bus portio = {
306 	nil, rport, wport,
307 };
308 
309 static void
cpuinit(void)310 cpuinit(void)
311 {
312 	int i;
313 
314 	fmtinstall('I', instfmt);
315 	fmtinstall('J', flagfmt);
316 	fmtinstall('C', cpufmt);
317 
318 	if((portfd[1] = open("#P/iob", ORDWR)) < 0)
319 		sysfatal("open iob: %r");
320 	if((portfd[2] = open("#P/iow", ORDWR)) < 0)
321 		sysfatal("open iow: %r");
322 	if((portfd[4] = open("#P/iol", ORDWR)) < 0)
323 		sysfatal("open iol: %r");
324 
325 	if((realmemfd = open("#P/realmodemem", ORDWR)) < 0)
326 		sysfatal("open realmodemem: %r");
327 
328 	for(i=0; i<nelem(memio); i++){
329 		ulong off;
330 
331 		if(memio[i].r != rmem)
332 			continue;
333 
334 		off = (ulong)i << 16;
335 		seek(realmemfd, off, 0);
336 		if(readn(realmemfd, memory + off, 0x10000) != 0x10000)
337 			sysfatal("read real mem %lux: %r\n", off);
338 	}
339 
340 	cpu.ic = 0;
341 	cpu.mem = memio;
342 	cpu.port = &portio;
343 	cpu.alen = cpu.olen = cpu.slen = 2;
344 }
345 
346 static char Ebusy[] = "device is busy";
347 static char Eintr[] = "interrupted";
348 static char Eperm[] = "permission denied";
349 static char Eio[] = "i/o error";
350 static char Emem[] = "bad memory access";
351 static char Enonexist[] = "file does not exist";
352 static char Ebadspec[] = "bad attach specifier";
353 static char Ewalk[] = "walk in non directory";
354 static char Ebadureg[] = "write a Ureg";
355 static char Ebadoff[] = "invalid offset";
356 static char Ebadtrap[] = "bad trap";
357 
358 static char *trapstr[] = {
359 	[EDIV0] "division by zero",
360 	[EDEBUG] "debug exception",
361 	[ENMI] "not maskable interrupt",
362 	[EBRK] "breakpoint",
363 	[EINTO] "into overflow",
364 	[EBOUND] "bounds check",
365 	[EBADOP] "bad opcode",
366 	[ENOFPU] "no fpu installed",
367 	[EDBLF] "double fault",
368 	[EFPUSEG] "fpu segment overflow",
369 	[EBADTSS] "invalid task state segment",
370 	[ENP] "segment not present",
371 	[ESTACK] "stack fault",
372 	[EGPF] "general protection fault",
373 	[EPF] "page fault",
374 };
375 
376 static int flushed(void *);
377 
378 #define GETUREG(x)	gw[sizeof(u->x)]((uchar*)&u->x)
379 #define PUTUREG(x,y)	pw[sizeof(u->x)]((uchar*)&u->x,y)
380 
381 static char*
realmode(Cpu * cpu,struct Ureg * u,void * r)382 realmode(Cpu *cpu, struct Ureg *u, void *r)
383 {
384 	char *err;
385 	int i;
386 
387 	cpu->reg[RDI] = GETUREG(di);
388 	cpu->reg[RSI] = GETUREG(si);
389 	cpu->reg[RBP] = GETUREG(bp);
390 	cpu->reg[RBX] = GETUREG(bx);
391 	cpu->reg[RDX] = GETUREG(dx);
392 	cpu->reg[RCX] = GETUREG(cx);
393 	cpu->reg[RAX] = GETUREG(ax);
394 
395 	cpu->reg[RGS] = GETUREG(gs);
396 	cpu->reg[RFS] = GETUREG(fs);
397 	cpu->reg[RES] = GETUREG(es);
398 	cpu->reg[RDS] = GETUREG(ds);
399 
400 	cpu->reg[RFL] = GETUREG(flags);
401 
402 	if(i = GETUREG(trap)){
403 		cpu->reg[RSS] = 0x0000;
404 		cpu->reg[RSP] = 0x7C00;
405 		cpu->reg[RCS] = (RMCODE>>4)&0xF000;
406 		cpu->reg[RIP] = RMCODE & 0xFFFF;
407 		memory[RMCODE] = 0xf4;	/* HLT instruction */
408 		if(intr(cpu, i) < 0)
409 			return Ebadtrap;
410 	} else {
411 		cpu->reg[RSS] = GETUREG(ss);
412 		cpu->reg[RSP] = GETUREG(sp);
413 		cpu->reg[RCS] = GETUREG(cs);
414 		cpu->reg[RIP] = GETUREG(pc);
415 	}
416 
417 	startclock();
418 	for(;;){
419 		if(cputrace)
420 			fprint(2, "%C\n", cpu);
421 		switch(i = xec(cpu, (porttrace | cputrace) ? 1 : 100000)){
422 		case -1:
423 			if(flushed(r)){
424 				err = Eintr;
425 				break;
426 			}
427 			runclock();
428 			continue;
429 
430 		/* normal interrupts */
431 		default:
432 			if(intr(cpu, i) < 0){
433 				err = Ebadtrap;
434 				break;
435 			}
436 			continue;
437 
438 		/* pseudo-interrupts */
439 		case EHALT:
440 			err = nil;
441 			break;
442 		case EIO:
443 			err = Eio;
444 			break;
445 		case EMEM:
446 			err = Emem;
447 			break;
448 
449 		/* processor traps */
450 		case EDIV0:
451 		case EDEBUG:
452 		case ENMI:
453 		case EBRK:
454 		case EINTO:
455 		case EBOUND:
456 		case EBADOP:
457 		case ENOFPU:
458 		case EDBLF:
459 		case EFPUSEG:
460 		case EBADTSS:
461 		case ENP:
462 		case ESTACK:
463 		case EGPF:
464 		case EPF:
465 			PUTUREG(trap, i);
466 			err = trapstr[i];
467 			break;
468 		}
469 
470 		break;
471 	}
472 
473 	if(err)
474 		fprint(2, "%s\n%C\n", err, cpu);
475 
476 	PUTUREG(di, cpu->reg[RDI]);
477 	PUTUREG(si, cpu->reg[RSI]);
478 	PUTUREG(bp, cpu->reg[RBP]);
479 	PUTUREG(bx, cpu->reg[RBX]);
480 	PUTUREG(dx, cpu->reg[RDX]);
481 	PUTUREG(cx, cpu->reg[RCX]);
482 	PUTUREG(ax, cpu->reg[RAX]);
483 
484 	PUTUREG(gs, cpu->reg[RGS]);
485 	PUTUREG(fs, cpu->reg[RFS]);
486 	PUTUREG(es, cpu->reg[RES]);
487 	PUTUREG(ds, cpu->reg[RDS]);
488 
489 	PUTUREG(flags, cpu->reg[RFL]);
490 
491 	PUTUREG(pc, cpu->reg[RIP]);
492 	PUTUREG(cs, cpu->reg[RCS]);
493 	PUTUREG(sp, cpu->reg[RSP]);
494 	PUTUREG(ss, cpu->reg[RSS]);
495 
496 	return err;
497 }
498 
499 enum {
500 	Qroot,
501 	Qcall,
502 	Qmem,
503 	Nqid,
504 };
505 
506 static struct Qtab {
507 	char *name;
508 	int mode;
509 	int type;
510 	int length;
511 } qtab[Nqid] = {
512 	"/",
513 		DMDIR|0555,
514 		QTDIR,
515 		0,
516 
517 	"realmode",
518 		0666,
519 		0,
520 		0,
521 
522 	"realmodemem",
523 		0666,
524 		0,
525 		MEMSIZE,
526 };
527 
528 static int
fillstat(ulong qid,Dir * d)529 fillstat(ulong qid, Dir *d)
530 {
531 	struct Qtab *t;
532 
533 	memset(d, 0, sizeof(Dir));
534 	d->uid = "realemu";
535 	d->gid = "realemu";
536 	d->muid = "";
537 	d->qid = (Qid){qid, 0, 0};
538 	d->atime = time(0);
539 	t = qtab + qid;
540 	d->name = t->name;
541 	d->qid.type = t->type;
542 	d->mode = t->mode;
543 	d->length = t->length;
544 	return 1;
545 }
546 
547 static void
fsattach(Req * r)548 fsattach(Req *r)
549 {
550 	char *spec;
551 
552 	spec = r->ifcall.aname;
553 	if(spec && spec[0]){
554 		respond(r, Ebadspec);
555 		return;
556 	}
557 	r->fid->qid = (Qid){Qroot, 0, QTDIR};
558 	r->ofcall.qid = r->fid->qid;
559 	respond(r, nil);
560 }
561 
562 static void
fsstat(Req * r)563 fsstat(Req *r)
564 {
565 	fillstat((ulong)r->fid->qid.path, &r->d);
566 	r->d.name = estrdup9p(r->d.name);
567 	r->d.uid = estrdup9p(r->d.uid);
568 	r->d.gid = estrdup9p(r->d.gid);
569 	r->d.muid = estrdup9p(r->d.muid);
570 	respond(r, nil);
571 }
572 
573 static char*
fswalk1(Fid * fid,char * name,Qid * qid)574 fswalk1(Fid *fid, char *name, Qid *qid)
575 {
576 	int i;
577 	ulong path;
578 
579 	path = fid->qid.path;
580 	switch(path){
581 	case Qroot:
582 		if (strcmp(name, "..") == 0) {
583 			*qid = (Qid){Qroot, 0, QTDIR};
584 			fid->qid = *qid;
585 			return nil;
586 		}
587 		for(i = fid->qid.path; i<Nqid; i++){
588 			if(strcmp(name, qtab[i].name) != 0)
589 				continue;
590 			*qid = (Qid){i, 0, 0};
591 			fid->qid = *qid;
592 			return nil;
593 		}
594 		return Enonexist;
595 
596 	default:
597 		return Ewalk;
598 	}
599 }
600 
601 static void
fsopen(Req * r)602 fsopen(Req *r)
603 {
604 	static int need[4] = { 4, 2, 6, 1 };
605 	struct Qtab *t;
606 	int n;
607 
608 	t = qtab + r->fid->qid.path;
609 	n = need[r->ifcall.mode & 3];
610 	if((n & t->mode) != n)
611 		respond(r, Eperm);
612 	else
613 		respond(r, nil);
614 }
615 
616 static int
readtopdir(Fid *,uchar * buf,long off,int cnt,int blen)617 readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
618 {
619 	int i, m, n;
620 	long pos;
621 	Dir d;
622 
623 	n = 0;
624 	pos = 0;
625 	for (i = 1; i < Nqid; i++){
626 		fillstat(i, &d);
627 		m = convD2M(&d, &buf[n], blen-n);
628 		if(off <= pos){
629 			if(m <= BIT16SZ || m > cnt)
630 				break;
631 			n += m;
632 			cnt -= m;
633 		}
634 		pos += m;
635 	}
636 	return n;
637 }
638 
639 static Channel *reqchan;
640 
641 static void
cpuproc(void *)642 cpuproc(void *)
643 {
644 	static struct Ureg rmu;
645 	ulong path;
646 	vlong o;
647 	ulong n;
648 	char *p;
649 	Req *r;
650 
651 	threadsetname("cpuproc");
652 
653 	while(r = recvp(reqchan)){
654 		if(flushed(r)){
655 			respond(r, Eintr);
656 			continue;
657 		}
658 
659 		path = r->fid->qid.path;
660 
661 		p = r->ifcall.data;
662 		n = r->ifcall.count;
663 		o = r->ifcall.offset;
664 
665 		switch(((int)r->ifcall.type<<8)|path){
666 		case (Tread<<8) | Qmem:
667 			readbuf(r, memory, MEMSIZE);
668 			respond(r, nil);
669 			break;
670 
671 		case (Tread<<8) | Qcall:
672 			readbuf(r, &rmu, sizeof rmu);
673 			respond(r, nil);
674 			break;
675 
676 		case (Twrite<<8) | Qmem:
677 			if(o < 0 || o >= MEMSIZE || o+n > MEMSIZE){
678 				respond(r, Ebadoff);
679 				break;
680 			}
681 			memmove(memory + o, p, n);
682 			r->ofcall.count = n;
683 			respond(r, nil);
684 			break;
685 
686 		case (Twrite<<8) | Qcall:
687 			if(n != sizeof rmu){
688 				respond(r, Ebadureg);
689 				break;
690 			}
691 			memmove(&rmu, p, n);
692 			if(p = realmode(&cpu, &rmu, r)){
693 				respond(r, p);
694 				break;
695 			}
696 			r->ofcall.count = n;
697 			respond(r, nil);
698 			break;
699 		}
700 	}
701 }
702 
703 static Channel *flushchan;
704 
705 static int
flushed(void * r)706 flushed(void *r)
707 {
708 	return nbrecvp(flushchan) == r;
709 }
710 
711 static void
fsflush(Req * r)712 fsflush(Req *r)
713 {
714 	nbsendp(flushchan, r->oldreq);
715 	respond(r, nil);
716 }
717 
718 static void
dispatch(Req * r)719 dispatch(Req *r)
720 {
721 	if(!nbsendp(reqchan, r))
722 		respond(r, Ebusy);
723 }
724 
725 static void
fsread(Req * r)726 fsread(Req *r)
727 {
728 	switch((ulong)r->fid->qid.path){
729 	case Qroot:
730 		r->ofcall.count = readtopdir(r->fid, (void*)r->ofcall.data, r->ifcall.offset,
731 			r->ifcall.count, r->ifcall.count);
732 		respond(r, nil);
733 		break;
734 	default:
735 		dispatch(r);
736 	}
737 }
738 
739 static void
fsend(Srv *)740 fsend(Srv*)
741 {
742 	threadexitsall(nil);
743 }
744 
745 static Srv fs = {
746 	.attach=		fsattach,
747 	.walk1=			fswalk1,
748 	.open=			fsopen,
749 	.read=			fsread,
750 	.write=			dispatch,
751 	.stat=			fsstat,
752 	.flush=			fsflush,
753 	.end=			fsend,
754 };
755 
756 static void
usage(void)757 usage(void)
758 {
759 	fprint(2, "usgae:\t%s [-Dpt] [-s srvname] [-m mountpoint]\n", argv0);
760 	exits("usage");
761 }
762 
763 void
threadmain(int argc,char * argv[])764 threadmain(int argc, char *argv[])
765 {
766 	char *mnt = "/dev";
767 	char *srv = nil;
768 
769 	ARGBEGIN{
770 	case 'D':
771 		chatty9p++;
772 		break;
773 	case 'p':
774 		porttrace = 1;
775 		break;
776 	case 't':
777 		cputrace = 1;
778 		break;
779 	case 's':
780 		srv = EARGF(usage());
781 		mnt = nil;
782 		break;
783 	case 'm':
784 		mnt = EARGF(usage());
785 		break;
786 	default:
787 		usage();
788 	}ARGEND
789 
790 	cpuinit();
791 
792 	reqchan = chancreate(sizeof(Req*), 8);
793 	flushchan = chancreate(sizeof(Req*), 8);
794 	procrfork(cpuproc, nil, 16*1024, RFNAMEG|RFNOTEG);
795 	threadpostmountsrv(&fs, srv, mnt, MBEFORE);
796 }
797