ref: fcb6d6ec65fb82c3b6b936dd148df72b9955994b
dir: /sys/src/9/port/devcons.c/
#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include <authsrv.h> void (*consdebug)(void) = nil; void (*screenputs)(char*, int) = nil; 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; char *sysname; vlong fasthz; static int readtime(ulong, char*, int); static int readbintime(char*, int); static int writetime(char*, int); static int writebintime(char*, int); enum { CMreboot, CMpanic, CMrdb, }; Cmdtab rebootmsg[] = { CMreboot, "reboot", 0, CMpanic, "panic", 0, CMrdb, "rdb", 0, }; enum { CMchdev, }; Cmdtab drivermsg[] = { CMchdev, "chdev", 0, }; void printinit(void) { } 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; } 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; int (*wq)(Queue*, void*, int); /* * 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. */ wq = usewrite && islo() ? qwrite : qiwrite; if(kprintoq != nil && !qisclosed(kprintoq)) (*wq)(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 != nil) { m = t-str; (*wq)(serialoq, str, m); (*wq)(serialoq, "\r\n", 2); n -= m+1; str = t+1; } else { (*wq)(serialoq, str, n); break; } } } void putstrn(char *str, int n) { putstrn0(str, n, 0); } int print(char *fmt, ...) { int n; va_list arg; char buf[PRINTSIZE]; 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; } _Noreturn void panic(char *fmt, ...) { int 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); vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg); va_end(arg); iprint("%s\n", buf); if(consdebug) (*consdebug)(); splx(s); prflush(); dumpstack(); /* reboot cpu servers and headless machines when not debugging */ if(getconf("*debug") == nil) if(cpuserver || !conf.monitor) exit(1); /* otherwise, just hang */ while(islo()) idlehands(); for(;;); } /* 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==nil || (c->flag&CMSG)!=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; } enum{ Qdir, Qbintime, Qcons, Qconsctl, Qcputime, Qdrivers, Qkmesg, Qkprint, Qhostdomain, Qhostowner, Qnull, Qosversion, Qpid, Qppid, Qrandom, Qreboot, Qsysname, Qsysstat, Qtime, Quser, Qzero, Qmordor, 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, 0666, "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, "pid", {Qpid}, NUMSIZE, 0444, "ppid", {Qppid}, NUMSIZE, 0444, "random", {Qrandom}, 0, 0444, "reboot", {Qreboot}, 0, 0220, "sysname", {Qsysname}, 0, 0664, "sysstat", {Qsysstat}, 0, 0664, "time", {Qtime}, NUMSIZE+3*VLNUMSIZE, 0664, "user", {Quser}, 0, 0666, "zero", {Qzero}, 0, 0444, "config", {Qconfig}, 0, 0444, "mordor", {Qmordor}, 0, 0666, }; 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(); } 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 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){ /* 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; char tmp[256]; int i, k, id; 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: error(Egreg); 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; readnum(0, tmp+NUMSIZE*i, NUMSIZE, tk2ms(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 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 < MAXMACH; id++) { if(active.machs[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; l = mp->perf.period; if(l == 0) l = 1; readnum(0, bp, NUMSIZE, (mp->perf.avg_inidle*100)/l, NUMSIZE); bp += NUMSIZE; readnum(0, bp, NUMSIZE, (mp->perf.avg_inintr*100)/l, NUMSIZE); bp += NUMSIZE; *bp++ = '\n'; } } if(waserror()){ free(b); nexterror(); } n = readstr((ulong)offset, buf, n, b); free(b); poperror(); return n; case Qsysname: if(sysname == nil) return 0; return readstr((ulong)offset, buf, n, sysname); case Qrandom: return randomread(buf, n); case Qdrivers: b = smalloc(READSTR); k = 0; rlock(&up->pgrp->ns); for(i = 0; devtab[i] != nil; i++){ if(up->pgrp->notallowed[i/(sizeof(u64int)*8)] & 1<<i%(sizeof(u64int)*8)) continue; k += snprint(b+k, READSTR-k, "#%C %s\n", devtab[i]->dc, devtab[i]->name); } runlock(&up->pgrp->ns); if(waserror()){ free(b); nexterror(); } n = readstr((ulong)offset, buf, n, b); poperror(); free(b); return n; case Qzero: memset(buf, 0, n); return n; case Qmordor: error("one does not simply read from mordor"); 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); } } static long conswrite(Chan *c, void *va, long n, vlong off) { char buf[256]; long l, bp; int invert; char *a; Mach *mp; int id; 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: error(Egreg); 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 Qdrivers: cb = parsecmd(a, n); if(waserror()) { free(cb); nexterror(); } ct = lookupcmd(cb, drivermsg, nelem(drivermsg)); if(ct == nil) error(Ebadarg); if(ct->index != CMchdev) error(Ebadarg); if(cb->nf == 1) error(Ebadarg); invert = 1; a = ""; switch(cb->f[1][0]){ case '&': if(cb->nf != 3) error(Ebadarg); a = cb->f[2]; if(cb->f[1][1] == '~') invert--; break; case '~': if(cb->nf != 2) error(Ebadarg); break; default: error(Ebadarg); break; } devmask(up->pgrp, invert, a); poperror(); free(cb); 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 CMreboot: rebootcmd(cb->nf-1, cb->f+1); break; case CMpanic: *(ulong*)0=0; panic("/dev/reboot"); case CMrdb: if(consdebug == nil) consdebug = rdb; consdebug(); break; } poperror(); free(cb); break; case Qsysstat: for(id = 0; id < MAXMACH; id++) { if(active.machs[id]) { mp = MACHP(id); mp->cs = 0; mp->intr = 0; mp->syscall = 0; mp->pfault = 0; mp->tlbfault = 0; mp->tlbpurge = 0; } } 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; case Qmordor: error("one does not simply write into mordor"); 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 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*)ℴ 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*)ℴ 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; if(--n <= 0) error(Ebadtimectl); 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+1; } void cpushutdown(void) { int ms, once; once = active.machs[m->machno]; active.machs[m->machno] = 0; active.exiting = 1; if(once) iprint("cpu%d: exiting\n", m->machno); /* wait for any other processors to shutdown */ spllo(); for(ms = 5*1000; ms > 0; ms -= TK2MS(2)){ delay(TK2MS(2)); if(memchr(active.machs, 1, MAXMACH) == nil && consactive() == 0) break; } }