Import the unmodified Plan 9 console driver.
authorDan Cross <crossd@gmail.com>
Thu, 15 Oct 2015 20:00:06 +0000 (16:00 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 17 Nov 2015 00:28:05 +0000 (16:28 -0800)
Step 1 in replacing the relatively less-functional Inferno
console driver with the Plan 9 driver from the UCB Plan 9
release. This is the original, unmodified source from the
Plan 9 distribution.

Signed-off-by: Dan Cross <crossd@gmail.com>
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/drivers/dev/devcons.c [new file with mode: 0644]

diff --git a/kern/drivers/dev/devcons.c b/kern/drivers/dev/devcons.c
new file mode 100644 (file)
index 0000000..3a225b6
--- /dev/null
@@ -0,0 +1,1362 @@
+/* 
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include       "u.h"
+#include       "../port/lib.h"
+#include       "mem.h"
+#include       "dat.h"
+#include       "fns.h"
+#include       "../port/error.h"
+#include       "pool.h"
+
+#include       <authsrv.h>
+
+void   (*consdebug)(void) = nil;
+void   (*screenputs)(char*, int) = nil;
+
+Queue* kbdq;                   /* unprocessed console input */
+Queue* lineq;                  /* processed console input */
+Queue* serialoq;               /* serial console output */
+Queue* kprintoq;               /* console output, for /dev/kprint */
+ulong  kprintinuse;            /* test and set whether /dev/kprint is open */
+int    iprintscreenputs = 1;
+
+int    panicking;
+
+static struct
+{
+       QLock;
+
+       int     raw;            /* true if we shouldn't process input */
+       Ref     ctl;            /* number of opens to the control file */
+       int     x;              /* index into line */
+       char    line[1024];     /* current input line */
+
+       int     count;
+       int     ctlpoff;
+
+       /* a place to save up characters at interrupt time before dumping them in the queue */
+       Lock    lockputc;
+       char    istage[1024];
+       char    *iw;
+       char    *ir;
+       char    *ie;
+} kbd = {
+       .iw     = kbd.istage,
+       .ir     = kbd.istage,
+       .ie     = kbd.istage + sizeof(kbd.istage),
+};
+
+char   *sysname;
+vlong  fasthz;
+
+static void    seedrand(void);
+static int     readtime(ulong, char*, int);
+static int     readbintime(char*, int);
+static int     writetime(char*, int);
+static int     writebintime(char*, int);
+
+enum
+{
+       CMhalt,
+       CMreboot,
+       CMpanic,
+};
+
+Cmdtab rebootmsg[] =
+{
+       CMhalt,         "halt",         1,
+       CMreboot,       "reboot",       0,
+       CMpanic,        "panic",        0,
+};
+
+void
+printinit(void)
+{
+       lineq = qopen(2*1024, 0, nil, nil);
+       if(lineq == nil)
+               panic("printinit");
+       qnoblock(lineq, 1);
+}
+
+int
+consactive(void)
+{
+       if(serialoq)
+               return qlen(serialoq) > 0;
+       return 0;
+}
+
+void
+prflush(void)
+{
+       ulong now;
+
+       now = m->ticks;
+       while(consactive())
+               if(m->ticks - now >= HZ)
+                       break;
+}
+
+/*
+ * Log console output so it can be retrieved via /dev/kmesg.
+ * This is good for catching boot-time messages after the fact.
+ */
+struct {
+       Lock lk;
+       char buf[KMESGSIZE];
+       uint n;
+} kmesg;
+
+static void
+kmesgputs(char *str, int n)
+{
+       uint nn, d;
+
+       ilock(&kmesg.lk);
+       /* take the tail of huge writes */
+       if(n > sizeof kmesg.buf){
+               d = n - sizeof kmesg.buf;
+               str += d;
+               n -= d;
+       }
+
+       /* slide the buffer down to make room */
+       nn = kmesg.n;
+       if(nn + n >= sizeof kmesg.buf){
+               d = nn + n - sizeof kmesg.buf;
+               if(d)
+                       memmove(kmesg.buf, kmesg.buf+d, sizeof kmesg.buf-d);
+               nn -= d;
+       }
+
+       /* copy the data in */
+       memmove(kmesg.buf+nn, str, n);
+       nn += n;
+       kmesg.n = nn;
+       iunlock(&kmesg.lk);
+}
+
+/*
+ *   Print a string on the console.  Convert \n to \r\n for serial
+ *   line consoles.  Locking of the queues is left up to the screen
+ *   or uart code.  Multi-line messages to serial consoles may get
+ *   interspersed with other messages.
+ */
+static void
+putstrn0(char *str, int n, int usewrite)
+{
+       int m;
+       char *t;
+
+       if(!islo())
+               usewrite = 0;
+
+       /*
+        *  how many different output devices do we need?
+        */
+       kmesgputs(str, n);
+
+       /*
+        *  if someone is reading /dev/kprint,
+        *  put the message there.
+        *  if not and there's an attached bit mapped display,
+        *  put the message there.
+        *
+        *  if there's a serial line being used as a console,
+        *  put the message there.
+        */
+       if(kprintoq != nil && !qisclosed(kprintoq)){
+               if(usewrite)
+                       qwrite(kprintoq, str, n);
+               else
+                       qiwrite(kprintoq, str, n);
+       }else if(screenputs != nil)
+               screenputs(str, n);
+
+       if(serialoq == nil){
+               uartputs(str, n);
+               return;
+       }
+
+       while(n > 0) {
+               t = memchr(str, '\n', n);
+               if(t && !kbd.raw) {
+                       m = t-str;
+                       if(usewrite){
+                               qwrite(serialoq, str, m);
+                               qwrite(serialoq, "\r\n", 2);
+                       } else {
+                               qiwrite(serialoq, str, m);
+                               qiwrite(serialoq, "\r\n", 2);
+                       }
+                       n -= m+1;
+                       str = t+1;
+               } else {
+                       if(usewrite)
+                               qwrite(serialoq, str, n);
+                       else
+                               qiwrite(serialoq, str, n);
+                       break;
+               }
+       }
+}
+
+void
+putstrn(char *str, int n)
+{
+       putstrn0(str, n, 0);
+}
+
+int noprint;
+
+int
+print(char *fmt, ...)
+{
+       int n;
+       va_list arg;
+       char buf[PRINTSIZE];
+
+       if(noprint)
+               return -1;
+
+       va_start(arg, fmt);
+       n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+       va_end(arg);
+       putstrn(buf, n);
+
+       return n;
+}
+
+/*
+ * Want to interlock iprints to avoid interlaced output on 
+ * multiprocessor, but don't want to deadlock if one processor
+ * dies during print and another has something important to say.
+ * Make a good faith effort.
+ */
+static Lock iprintlock;
+static int
+iprintcanlock(Lock *l)
+{
+       int i;
+       
+       for(i=0; i<1000; i++){
+               if(canlock(l))
+                       return 1;
+               if(l->m == MACHP(m->machno))
+                       return 0;
+               microdelay(100);
+       }
+       return 0;
+}
+
+int
+iprint(char *fmt, ...)
+{
+       int n, s, locked;
+       va_list arg;
+       char buf[PRINTSIZE];
+
+       s = splhi();
+       va_start(arg, fmt);
+       n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+       va_end(arg);
+       locked = iprintcanlock(&iprintlock);
+       if(screenputs != nil && iprintscreenputs)
+               screenputs(buf, n);
+       uartputs(buf, n);
+       if(locked)
+               unlock(&iprintlock);
+       splx(s);
+
+       return n;
+}
+
+void
+panic(char *fmt, ...)
+{
+       int n, s;
+       va_list arg;
+       char buf[PRINTSIZE];
+
+       kprintoq = nil; /* don't try to write to /dev/kprint */
+
+       if(panicking)
+               for(;;);
+       panicking = 1;
+
+       s = splhi();
+       strcpy(buf, "panic: ");
+       va_start(arg, fmt);
+       n = vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
+       va_end(arg);
+       iprint("%s\n", buf);
+       if(consdebug)
+               (*consdebug)();
+       splx(s);
+       prflush();
+       buf[n] = '\n';
+       putstrn(buf, n+1);
+       dumpstack();
+
+       exit(1);
+}
+
+/* libmp at least contains a few calls to sysfatal; simulate with panic */
+void
+sysfatal(char *fmt, ...)
+{
+       char err[256];
+       va_list arg;
+
+       va_start(arg, fmt);
+       vseprint(err, err + sizeof err, fmt, arg);
+       va_end(arg);
+       panic("sysfatal: %s", err);
+}
+
+void
+_assert(char *fmt)
+{
+       panic("assert failed at %#p: %s", getcallerpc(&fmt), fmt);
+}
+
+int
+pprint(char *fmt, ...)
+{
+       int n;
+       Chan *c;
+       va_list arg;
+       char buf[2*PRINTSIZE];
+
+       if(up == nil || up->fgrp == nil)
+               return 0;
+
+       c = up->fgrp->fd[2];
+       if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
+               return 0;
+       n = snprint(buf, sizeof buf, "%s %lud: ", up->text, up->pid);
+       va_start(arg, fmt);
+       n = vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
+       va_end(arg);
+
+       if(waserror())
+               return 0;
+       devtab[c->type]->write(c, buf, n, c->offset);
+       poperror();
+
+       lock(c);
+       c->offset += n;
+       unlock(c);
+
+       return n;
+}
+
+static void
+echoscreen(char *buf, int n)
+{
+       char *e, *p;
+       char ebuf[128];
+       int x;
+
+       p = ebuf;
+       e = ebuf + sizeof(ebuf) - 4;
+       while(n-- > 0){
+               if(p >= e){
+                       screenputs(ebuf, p - ebuf);
+                       p = ebuf;
+               }
+               x = *buf++;
+               if(x == 0x15){
+                       *p++ = '^';
+                       *p++ = 'U';
+                       *p++ = '\n';
+               } else
+                       *p++ = x;
+       }
+       if(p != ebuf)
+               screenputs(ebuf, p - ebuf);
+}
+
+static void
+echoserialoq(char *buf, int n)
+{
+       char *e, *p;
+       char ebuf[128];
+       int x;
+
+       p = ebuf;
+       e = ebuf + sizeof(ebuf) - 4;
+       while(n-- > 0){
+               if(p >= e){
+                       qiwrite(serialoq, ebuf, p - ebuf);
+                       p = ebuf;
+               }
+               x = *buf++;
+               if(x == '\n'){
+                       *p++ = '\r';
+                       *p++ = '\n';
+               } else if(x == 0x15){
+                       *p++ = '^';
+                       *p++ = 'U';
+                       *p++ = '\n';
+               } else
+                       *p++ = x;
+       }
+       if(p != ebuf)
+               qiwrite(serialoq, ebuf, p - ebuf);
+}
+
+static void
+echo(char *buf, int n)
+{
+       static int ctrlt, pid;
+       int x;
+       char *e, *p;
+
+       if(n == 0)
+               return;
+
+       e = buf+n;
+       for(p = buf; p < e; p++){
+               switch(*p){
+               case 0x10:      /* ^P */
+                       if(cpuserver && !kbd.ctlpoff){
+                               active.exiting = 1;
+                               return;
+                       }
+                       break;
+               case 0x14:      /* ^T */
+                       ctrlt++;
+                       if(ctrlt > 2)
+                               ctrlt = 2;
+                       continue;
+               }
+
+               if(ctrlt != 2)
+                       continue;
+
+               /* ^T escapes */
+               ctrlt = 0;
+               switch(*p){
+               case 'S':
+                       x = splhi();
+                       dumpstack();
+                       procdump();
+                       splx(x);
+                       return;
+               case 's':
+                       dumpstack();
+                       return;
+               case 'x':
+                       xsummary();
+                       ixsummary();
+                       mallocsummary();
+               //      memorysummary();
+                       pagersummary();
+                       return;
+               case 'd':
+                       if(consdebug == nil)
+                               consdebug = rdb;
+                       else
+                               consdebug = nil;
+                       print("consdebug now %#p\n", consdebug);
+                       return;
+               case 'D':
+                       if(consdebug == nil)
+                               consdebug = rdb;
+                       consdebug();
+                       return;
+               case 'p':
+                       x = spllo();
+                       procdump();
+                       splx(x);
+                       return;
+               case 'q':
+                       scheddump();
+                       return;
+               case 'k':
+                       killbig("^t ^t k");
+                       return;
+               case 'r':
+                       exit(0);
+                       return;
+               }
+       }
+
+       qproduce(kbdq, buf, n);
+       if(kbd.raw)
+               return;
+       kmesgputs(buf, n);
+       if(screenputs != nil)
+               echoscreen(buf, n);
+       if(serialoq)
+               echoserialoq(buf, n);
+}
+
+/*
+ *  Called by a uart interrupt for console input.
+ *
+ *  turn '\r' into '\n' before putting it into the queue.
+ */
+int
+kbdcr2nl(Queue*, int ch)
+{
+       char *next;
+
+       ilock(&kbd.lockputc);           /* just a mutex */
+       if(ch == '\r' && !kbd.raw)
+               ch = '\n';
+       next = kbd.iw+1;
+       if(next >= kbd.ie)
+               next = kbd.istage;
+       if(next != kbd.ir){
+               *kbd.iw = ch;
+               kbd.iw = next;
+       }
+       iunlock(&kbd.lockputc);
+       return 0;
+}
+
+/*
+ *  Put character, possibly a rune, into read queue at interrupt time.
+ *  Called at interrupt time to process a character.
+ */
+int
+kbdputc(Queue*, int ch)
+{
+       int i, n;
+       char buf[3];
+       Rune r;
+       char *next;
+
+       if(kbd.ir == nil)
+               return 0;               /* in case we're not inited yet */
+       
+       ilock(&kbd.lockputc);           /* just a mutex */
+       r = ch;
+       n = runetochar(buf, &r);
+       for(i = 0; i < n; i++){
+               next = kbd.iw+1;
+               if(next >= kbd.ie)
+                       next = kbd.istage;
+               if(next == kbd.ir)
+                       break;
+               *kbd.iw = buf[i];
+               kbd.iw = next;
+       }
+       iunlock(&kbd.lockputc);
+       return 0;
+}
+
+/*
+ *  we save up input characters till clock time to reduce
+ *  per character interrupt overhead.
+ */
+static void
+kbdputcclock(void)
+{
+       char *iw;
+
+       /* this amortizes cost of qproduce */
+       if(kbd.iw != kbd.ir){
+               iw = kbd.iw;
+               if(iw < kbd.ir){
+                       echo(kbd.ir, kbd.ie-kbd.ir);
+                       kbd.ir = kbd.istage;
+               }
+               if(kbd.ir != iw){
+                       echo(kbd.ir, iw-kbd.ir);
+                       kbd.ir = iw;
+               }
+       }
+}
+
+enum{
+       Qdir,
+       Qbintime,
+       Qcons,
+       Qconsctl,
+       Qcputime,
+       Qdrivers,
+       Qkmesg,
+       Qkprint,
+       Qhostdomain,
+       Qhostowner,
+       Qnull,
+       Qosversion,
+       Qpgrpid,
+       Qpid,
+       Qppid,
+       Qrandom,
+       Qreboot,
+       Qswap,
+       Qsysname,
+       Qsysstat,
+       Qtime,
+       Quser,
+       Qzero,
+       Qconfig,
+};
+
+enum
+{
+       VLNUMSIZE=      22,
+};
+
+static Dirtab consdir[]={
+       ".",    {Qdir, 0, QTDIR},       0,              DMDIR|0555,
+       "bintime",      {Qbintime},     24,             0664,
+       "cons",         {Qcons},        0,              0660,
+       "consctl",      {Qconsctl},     0,              0220,
+       "cputime",      {Qcputime},     6*NUMSIZE,      0444,
+       "drivers",      {Qdrivers},     0,              0444,
+       "hostdomain",   {Qhostdomain},  DOMLEN,         0664,
+       "hostowner",    {Qhostowner},   0,              0664,
+       "kmesg",        {Qkmesg},       0,              0440,
+       "kprint",       {Qkprint, 0, QTEXCL},   0,      DMEXCL|0440,
+       "null",         {Qnull},        0,              0666,
+       "osversion",    {Qosversion},   0,              0444,
+       "pgrpid",       {Qpgrpid},      NUMSIZE,        0444,
+       "pid",          {Qpid},         NUMSIZE,        0444,
+       "ppid",         {Qppid},        NUMSIZE,        0444,
+       "random",       {Qrandom},      0,              0444,
+       "reboot",       {Qreboot},      0,              0660,
+       "swap",         {Qswap},        0,              0664,
+       "sysname",      {Qsysname},     0,              0664,
+       "sysstat",      {Qsysstat},     0,              0666,
+       "time",         {Qtime},        NUMSIZE+3*VLNUMSIZE,    0664,
+       "user",         {Quser},        0,              0666,
+       "zero",         {Qzero},        0,              0444,
+       "config",       {Qconfig},      0,              0444,
+};
+
+int
+readnum(ulong off, char *buf, ulong n, ulong val, int size)
+{
+       char tmp[64];
+
+       snprint(tmp, sizeof(tmp), "%*lud", size-1, val);
+       tmp[size-1] = ' ';
+       if(off >= size)
+               return 0;
+       if(off+n > size)
+               n = size-off;
+       memmove(buf, tmp+off, n);
+       return n;
+}
+
+int
+readstr(ulong off, char *buf, ulong n, char *str)
+{
+       int size;
+
+       size = strlen(str);
+       if(off >= size)
+               return 0;
+       if(off+n > size)
+               n = size-off;
+       memmove(buf, str+off, n);
+       return n;
+}
+
+static void
+consinit(void)
+{
+       todinit();
+       randominit();
+       /*
+        * at 115200 baud, the 1024 char buffer takes 56 ms to process,
+        * processing it every 22 ms should be fine
+        */
+       addclock0link(kbdputcclock, 22);
+}
+
+static Chan*
+consattach(char *spec)
+{
+       return devattach('c', spec);
+}
+
+static Walkqid*
+conswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+       return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
+}
+
+static int
+consstat(Chan *c, uchar *dp, int n)
+{
+       return devstat(c, dp, n, consdir, nelem(consdir), devgen);
+}
+
+static Chan*
+consopen(Chan *c, int omode)
+{
+       c->aux = nil;
+       c = devopen(c, omode, consdir, nelem(consdir), devgen);
+       switch((ulong)c->qid.path){
+       case Qconsctl:
+               incref(&kbd.ctl);
+               break;
+
+       case Qkprint:
+               if(tas(&kprintinuse) != 0){
+                       c->flag &= ~COPEN;
+                       error(Einuse);
+               }
+               if(kprintoq == nil){
+                       kprintoq = qopen(8*1024, Qcoalesce, 0, 0);
+                       if(kprintoq == nil){
+                               c->flag &= ~COPEN;
+                               error(Enomem);
+                       }
+                       qnoblock(kprintoq, 1);
+               }else
+                       qreopen(kprintoq);
+               c->iounit = qiomaxatomic;
+               break;
+       }
+       return c;
+}
+
+static void
+consclose(Chan *c)
+{
+       switch((ulong)c->qid.path){
+       /* last close of control file turns off raw */
+       case Qconsctl:
+               if(c->flag&COPEN){
+                       if(decref(&kbd.ctl) == 0)
+                               kbd.raw = 0;
+               }
+               break;
+
+       /* close of kprint allows other opens */
+       case Qkprint:
+               if(c->flag & COPEN){
+                       kprintinuse = 0;
+                       qhangup(kprintoq, nil);
+               }
+               break;
+       }
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong off)
+{
+       ulong l;
+       Mach *mp;
+       char *b, *bp, ch;
+       char tmp[256];          /* must be >= 18*NUMSIZE (Qswap) */
+       int i, k, id, send;
+       vlong offset = off;
+       extern char configfile[];
+
+       if(n <= 0)
+               return n;
+
+       switch((ulong)c->qid.path){
+       case Qdir:
+               return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
+
+       case Qcons:
+               qlock(&kbd);
+               if(waserror()) {
+                       qunlock(&kbd);
+                       nexterror();
+               }
+               while(!qcanread(lineq)){
+                       if(qread(kbdq, &ch, 1) == 0)
+                               continue;
+                       send = 0;
+                       if(ch == 0){
+                               /* flush output on rawoff -> rawon */
+                               if(kbd.x > 0)
+                                       send = !qcanread(kbdq);
+                       }else if(kbd.raw){
+                               kbd.line[kbd.x++] = ch;
+                               send = !qcanread(kbdq);
+                       }else{
+                               switch(ch){
+                               case '\b':
+                                       if(kbd.x > 0)
+                                               kbd.x--;
+                                       break;
+                               case 0x15:      /* ^U */
+                                       kbd.x = 0;
+                                       break;
+                               case '\n':
+                               case 0x04:      /* ^D */
+                                       send = 1;
+                               default:
+                                       if(ch != 0x04)
+                                               kbd.line[kbd.x++] = ch;
+                                       break;
+                               }
+                       }
+                       if(send || kbd.x == sizeof kbd.line){
+                               qwrite(lineq, kbd.line, kbd.x);
+                               kbd.x = 0;
+                       }
+               }
+               n = qread(lineq, buf, n);
+               qunlock(&kbd);
+               poperror();
+               return n;
+
+       case Qcputime:
+               k = offset;
+               if(k >= 6*NUMSIZE)
+                       return 0;
+               if(k+n > 6*NUMSIZE)
+                       n = 6*NUMSIZE - k;
+               /* easiest to format in a separate buffer and copy out */
+               for(i=0; i<6 && NUMSIZE*i<k+n; i++){
+                       l = up->time[i];
+                       if(i == TReal)
+                               l = MACHP(0)->ticks - l;
+                       l = TK2MS(l);
+                       readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
+               }
+               memmove(buf, tmp+k, n);
+               return n;
+
+       case Qkmesg:
+               /*
+                * This is unlocked to avoid tying up a process
+                * that's writing to the buffer.  kmesg.n never 
+                * gets smaller, so worst case the reader will
+                * see a slurred buffer.
+                */
+               if(off >= kmesg.n)
+                       n = 0;
+               else{
+                       if(off+n > kmesg.n)
+                               n = kmesg.n - off;
+                       memmove(buf, kmesg.buf+off, n);
+               }
+               return n;
+               
+       case Qkprint:
+               return qread(kprintoq, buf, n);
+
+       case Qpgrpid:
+               return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);
+
+       case Qpid:
+               return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);
+
+       case Qppid:
+               return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);
+
+       case Qtime:
+               return readtime((ulong)offset, buf, n);
+
+       case Qbintime:
+               return readbintime(buf, n);
+
+       case Qhostowner:
+               return readstr((ulong)offset, buf, n, eve);
+
+       case Qhostdomain:
+               return readstr((ulong)offset, buf, n, hostdomain);
+
+       case Quser:
+               return readstr((ulong)offset, buf, n, up->user);
+
+       case Qnull:
+               return 0;
+
+       case Qconfig:
+               return readstr((ulong)offset, buf, n, configfile);
+
+       case Qsysstat:
+               b = smalloc(conf.nmach*(NUMSIZE*11+1) + 1);     /* +1 for NUL */
+               bp = b;
+               for(id = 0; id < 32; id++) {
+                       if(active.machs & (1<<id)) {
+                               mp = MACHP(id);
+                               readnum(0, bp, NUMSIZE, id, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE,
+                                       (mp->perf.avg_inidle*100)/mp->perf.period,
+                                       NUMSIZE);
+                               bp += NUMSIZE;
+                               readnum(0, bp, NUMSIZE,
+                                       (mp->perf.avg_inintr*100)/mp->perf.period,
+                                       NUMSIZE);
+                               bp += NUMSIZE;
+                               *bp++ = '\n';
+                       }
+               }
+               if(waserror()){
+                       free(b);
+                       nexterror();
+               }
+               n = readstr((ulong)offset, buf, n, b);
+               free(b);
+               poperror();
+               return n;
+
+       case Qswap:
+               snprint(tmp, sizeof tmp,
+                       "%lud memory\n"
+                       "%d pagesize\n"
+                       "%lud kernel\n"
+                       "%lud/%lud user\n"
+                       "%lud/%lud swap\n"
+                       "%lud/%lud kernel malloc\n"
+                       "%lud/%lud kernel draw\n",
+                       conf.npage*BY2PG,
+                       BY2PG,
+                       conf.npage-conf.upages,
+                       palloc.user-palloc.freecount, palloc.user,
+                       conf.nswap-swapalloc.free, conf.nswap,
+                       mainmem->cursize, mainmem->maxsize,
+                       imagmem->cursize, imagmem->maxsize);
+
+               return readstr((ulong)offset, buf, n, tmp);
+
+       case Qsysname:
+               if(sysname == nil)
+                       return 0;
+               return readstr((ulong)offset, buf, n, sysname);
+
+       case Qrandom:
+               return randomread(buf, n);
+
+       case Qdrivers:
+               b = malloc(READSTR);
+               if(b == nil)
+                       error(Enomem);
+               k = 0;
+               for(i = 0; devtab[i] != nil; i++)
+                       k += snprint(b+k, READSTR-k, "#%C %s\n",
+                               devtab[i]->dc, devtab[i]->name);
+               if(waserror()){
+                       free(b);
+                       nexterror();
+               }
+               n = readstr((ulong)offset, buf, n, b);
+               free(b);
+               poperror();
+               return n;
+
+       case Qzero:
+               memset(buf, 0, n);
+               return n;
+
+       case Qosversion:
+               snprint(tmp, sizeof tmp, "2000");
+               n = readstr((ulong)offset, buf, n, tmp);
+               return n;
+
+       default:
+               print("consread %#llux\n", c->qid.path);
+               error(Egreg);
+       }
+       return -1;              /* never reached */
+}
+
+static long
+conswrite(Chan *c, void *va, long n, vlong off)
+{
+       char buf[256], ch;
+       long l, bp;
+       char *a;
+       Mach *mp;
+       int id, fd;
+       Chan *swc;
+       ulong offset;
+       Cmdbuf *cb;
+       Cmdtab *ct;
+
+       a = va;
+       offset = off;
+
+       switch((ulong)c->qid.path){
+       case Qcons:
+               /*
+                * Can't page fault in putstrn, so copy the data locally.
+                */
+               l = n;
+               while(l > 0){
+                       bp = l;
+                       if(bp > sizeof buf)
+                               bp = sizeof buf;
+                       memmove(buf, a, bp);
+                       putstrn0(buf, bp, 1);
+                       a += bp;
+                       l -= bp;
+               }
+               break;
+
+       case Qconsctl:
+               if(n >= sizeof(buf))
+                       n = sizeof(buf)-1;
+               strncpy(buf, a, n);
+               buf[n] = 0;
+               for(a = buf; a;){
+                       if(strncmp(a, "rawon", 5) == 0){
+                               kbd.raw = 1;
+                               /* clumsy hack - wake up reader */
+                               ch = 0;
+                               qwrite(kbdq, &ch, 1);                   
+                       } else if(strncmp(a, "rawoff", 6) == 0){
+                               kbd.raw = 0;
+                       } else if(strncmp(a, "ctlpon", 6) == 0){
+                               kbd.ctlpoff = 0;
+                       } else if(strncmp(a, "ctlpoff", 7) == 0){
+                               kbd.ctlpoff = 1;
+                       }
+                       if(a = strchr(a, ' '))
+                               a++;
+               }
+               break;
+
+       case Qtime:
+               if(!iseve())
+                       error(Eperm);
+               return writetime(a, n);
+
+       case Qbintime:
+               if(!iseve())
+                       error(Eperm);
+               return writebintime(a, n);
+
+       case Qhostowner:
+               return hostownerwrite(a, n);
+
+       case Qhostdomain:
+               return hostdomainwrite(a, n);
+
+       case Quser:
+               return userwrite(a, n);
+
+       case Qnull:
+               break;
+
+       case Qconfig:
+               error(Eperm);
+               break;
+
+       case Qreboot:
+               if(!iseve())
+                       error(Eperm);
+               cb = parsecmd(a, n);
+
+               if(waserror()) {
+                       free(cb);
+                       nexterror();
+               }
+               ct = lookupcmd(cb, rebootmsg, nelem(rebootmsg));
+               switch(ct->index) {
+               case CMhalt:
+                       reboot(nil, 0, 0);
+                       break;
+               case CMreboot:
+                       rebootcmd(cb->nf-1, cb->f+1);
+                       break;
+               case CMpanic:
+                       *(ulong*)0=0;
+                       panic("/dev/reboot");
+               }
+               poperror();
+               free(cb);
+               break;
+
+       case Qsysstat:
+               for(id = 0; id < 32; id++) {
+                       if(active.machs & (1<<id)) {
+                               mp = MACHP(id);
+                               mp->cs = 0;
+                               mp->intr = 0;
+                               mp->syscall = 0;
+                               mp->pfault = 0;
+                               mp->tlbfault = 0;
+                               mp->tlbpurge = 0;
+                       }
+               }
+               break;
+
+       case Qswap:
+               if(n >= sizeof buf)
+                       error(Egreg);
+               memmove(buf, va, n);    /* so we can NUL-terminate */
+               buf[n] = 0;
+               /* start a pager if not already started */
+               if(strncmp(buf, "start", 5) == 0){
+                       kickpager();
+                       break;
+               }
+               if(!iseve())
+                       error(Eperm);
+               if(buf[0]<'0' || '9'<buf[0])
+                       error(Ebadarg);
+               fd = strtoul(buf, 0, 0);
+               swc = fdtochan(fd, -1, 1, 1);
+               setswapchan(swc);
+               break;
+
+       case Qsysname:
+               if(offset != 0)
+                       error(Ebadarg);
+               if(n <= 0 || n >= sizeof buf)
+                       error(Ebadarg);
+               strncpy(buf, a, n);
+               buf[n] = 0;
+               if(buf[n-1] == '\n')
+                       buf[n-1] = 0;
+               kstrdup(&sysname, buf);
+               break;
+
+       default:
+               print("conswrite: %#llux\n", c->qid.path);
+               error(Egreg);
+       }
+       return n;
+}
+
+Dev consdevtab = {
+       'c',
+       "cons",
+
+       devreset,
+       consinit,
+       devshutdown,
+       consattach,
+       conswalk,
+       consstat,
+       consopen,
+       devcreate,
+       consclose,
+       consread,
+       devbread,
+       conswrite,
+       devbwrite,
+       devremove,
+       devwstat,
+};
+
+static ulong   randn;
+
+static void
+seedrand(void)
+{
+       if(!waserror()){
+               randomread((void*)&randn, sizeof(randn));
+               poperror();
+       }
+}
+
+int
+nrand(int n)
+{
+       if(randn == 0)
+               seedrand();
+       randn = randn*1103515245 + 12345 + MACHP(0)->ticks;
+       return (randn>>16) % n;
+}
+
+int
+rand(void)
+{
+       nrand(1);
+       return randn;
+}
+
+static uvlong uvorder = 0x0001020304050607ULL;
+
+static uchar*
+le2vlong(vlong *to, uchar *f)
+{
+       uchar *t, *o;
+       int i;
+
+       t = (uchar*)to;
+       o = (uchar*)&uvorder;
+       for(i = 0; i < sizeof(vlong); i++)
+               t[o[i]] = f[i];
+       return f+sizeof(vlong);
+}
+
+static uchar*
+vlong2le(uchar *t, vlong from)
+{
+       uchar *f, *o;
+       int i;
+
+       f = (uchar*)&from;
+       o = (uchar*)&uvorder;
+       for(i = 0; i < sizeof(vlong); i++)
+               t[i] = f[o[i]];
+       return t+sizeof(vlong);
+}
+
+static long order = 0x00010203;
+
+static uchar*
+le2long(long *to, uchar *f)
+{
+       uchar *t, *o;
+       int i;
+
+       t = (uchar*)to;
+       o = (uchar*)&order;
+       for(i = 0; i < sizeof(long); i++)
+               t[o[i]] = f[i];
+       return f+sizeof(long);
+}
+
+static uchar*
+long2le(uchar *t, long from)
+{
+       uchar *f, *o;
+       int i;
+
+       f = (uchar*)&from;
+       o = (uchar*)&order;
+       for(i = 0; i < sizeof(long); i++)
+               t[i] = f[o[i]];
+       return t+sizeof(long);
+}
+
+char *Ebadtimectl = "bad time control";
+
+/*
+ *  like the old #c/time but with added info.  Return
+ *
+ *     secs    nanosecs        fastticks       fasthz
+ */
+static int
+readtime(ulong off, char *buf, int n)
+{
+       vlong   nsec, ticks;
+       long sec;
+       char str[7*NUMSIZE];
+
+       nsec = todget(&ticks);
+       if(fasthz == 0LL)
+               fastticks((uvlong*)&fasthz);
+       sec = nsec/1000000000ULL;
+       snprint(str, sizeof(str), "%*lud %*llud %*llud %*llud ",
+               NUMSIZE-1, sec,
+               VLNUMSIZE-1, nsec,
+               VLNUMSIZE-1, ticks,
+               VLNUMSIZE-1, fasthz);
+       return readstr(off, buf, n, str);
+}
+
+/*
+ *  set the time in seconds
+ */
+static int
+writetime(char *buf, int n)
+{
+       char b[13];
+       long i;
+       vlong now;
+
+       if(n >= sizeof(b))
+               error(Ebadtimectl);
+       strncpy(b, buf, n);
+       b[n] = 0;
+       i = strtol(b, 0, 0);
+       if(i <= 0)
+               error(Ebadtimectl);
+       now = i*1000000000LL;
+       todset(now, 0, 0);
+       return n;
+}
+
+/*
+ *  read binary time info.  all numbers are little endian.
+ *  ticks and nsec are syncronized.
+ */
+static int
+readbintime(char *buf, int n)
+{
+       int i;
+       vlong nsec, ticks;
+       uchar *b = (uchar*)buf;
+
+       i = 0;
+       if(fasthz == 0LL)
+               fastticks((uvlong*)&fasthz);
+       nsec = todget(&ticks);
+       if(n >= 3*sizeof(uvlong)){
+               vlong2le(b+2*sizeof(uvlong), fasthz);
+               i += sizeof(uvlong);
+       }
+       if(n >= 2*sizeof(uvlong)){
+               vlong2le(b+sizeof(uvlong), ticks);
+               i += sizeof(uvlong);
+       }
+       if(n >= 8){
+               vlong2le(b, nsec);
+               i += sizeof(vlong);
+       }
+       return i;
+}
+
+/*
+ *  set any of the following
+ *     - time in nsec
+ *     - nsec trim applied over some seconds
+ *     - clock frequency
+ */
+static int
+writebintime(char *buf, int n)
+{
+       uchar *p;
+       vlong delta;
+       long period;
+
+       n--;
+       p = (uchar*)buf + 1;
+       switch(*buf){
+       case 'n':
+               if(n < sizeof(vlong))
+                       error(Ebadtimectl);
+               le2vlong(&delta, p);
+               todset(delta, 0, 0);
+               break;
+       case 'd':
+               if(n < sizeof(vlong)+sizeof(long))
+                       error(Ebadtimectl);
+               p = le2vlong(&delta, p);
+               le2long(&period, p);
+               todset(-1, delta, period);
+               break;
+       case 'f':
+               if(n < sizeof(uvlong))
+                       error(Ebadtimectl);
+               le2vlong(&fasthz, p);
+               if(fasthz <= 0)
+                       error(Ebadtimectl);
+               todsetfreq(fasthz);
+               break;
+       }
+       return n;
+}