blob: 809a325ed7680ad94b7331b404f8aec4a56941db [file] [log] [blame]
/*
* 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 <arch/arch.h>
#include <atomic.h>
#include <env.h>
#include <error.h>
#include <event.h>
#include <kmalloc.h>
#include <ns.h>
#include <ros/fs.h>
#include <ros/procinfo.h>
#include <smp.h>
#include <stdio.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>
#if 0
void (*consdebug) (void) = NULL;
#endif
void (*screenputs)(const char *, int) = cputbuf;
struct queue *kbdq; /* unprocessed console input */
struct queue *lineq; /* processed console input */
struct queue *serialoq; /* serial console output */
struct queue *kprintoq; /* console output, for /dev/kprint */
atomic_t kprintinuse = 0; /* test and set whether /dev/kprint is open */
int iprintscreenputs = 1;
int keepbroken = 1;
static bool cons_has_init = FALSE;
struct queue *cons_q; /* Akaros cons input: keyboard, serial, etc */
spinlock_t cons_q_lock = SPINLOCK_INITIALIZER;
struct fdtap_slist cons_q_fd_taps = SLIST_HEAD_INITIALIZER(cons_q_fd_taps);
static uint8_t logbuffer[1 << 20];
static int index = 0;
static struct queue *logqueue = NULL;
static int reading_kmesg = 0;
typedef unsigned char uint8_t;
void logbuf(int c)
{
if (reading_kmesg)
return;
if (index > 1 << 20)
return;
logbuffer[index++] = c;
}
/*
* return true if current user is eve
*/
int iseve(void)
{
return strcmp(eve.name, current->user.name) == 0;
}
struct username eve = {.name = "eve", .name_lock = SPINLOCK_INITIALIZER};
char hostdomain[256] = "akaros.org";
static struct {
qlock_t qlock;
int raw; /* true if we shouldn't process input */
struct kref 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 */
spinlock_t 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;
int64_t fasthz;
static int readtime(uint32_t, char *, int);
static int readbintime(char *, int);
static int writetime(char *, int);
static int writebintime(char *, int);
static int hostownerwrite(char *, size_t);
static void killkid(void);
enum { CMbroken,
CMconsole,
CMhalt,
CMnobroken,
CMpanic,
CMreboot,
};
struct cmdtab rebootmsg[] = {
{CMbroken, "broken", 0}, {CMconsole, "console", 1},
{CMhalt, "halt", 1}, {CMnobroken, "nobroken", 0},
{CMpanic, "panic", 0}, {CMreboot, "reboot", 0},
};
void printinit(void)
{
lineq = qopen(2 * 1024, 0, NULL, NULL);
if (lineq == NULL)
panic("printinit");
qdropoverflow(lineq, 1);
}
int consactive(void)
{
if (serialoq)
return qlen(serialoq) > 0;
return 0;
}
void prflush(void)
{
long times = 0;
while (consactive())
if (times++ > 1000)
break;
}
/*
* Log console output so it can be retrieved via /dev/kmesg.
* This is good for catching boot-time messages after the fact.
*/
struct {
spinlock_t lk;
char buf[1 << 20];
unsigned int n;
} kmesg;
static void kmesgputs(char *str, int n)
{
unsigned int nn, d;
spin_lock_irqsave(&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;
spin_unlock_irqsave(&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 0
if (!islo())
usewrite = 0;
#endif
/*
* 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 != NULL && !qisclosed(kprintoq)) {
if (usewrite)
qwrite(kprintoq, str, n);
else
qiwrite(kprintoq, str, n);
} else if (screenputs != NULL)
screenputs(str, n);
if (serialoq == NULL) {
#if 0
uartputs(str, n);
#endif
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 = vsnprintf(buf, sizeof(buf), fmt, arg);
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 spinlock_t iprintlock;
static int iprintcanlock(spinlock_t *l)
{
int i;
for (i = 0; i < 1000; i++) {
if (spin_trylock(l))
return 1;
}
return 0;
}
int iprint(char *fmt, ...)
{
int8_t s = 0;
int n, locked;
va_list arg;
char buf[PRINTSIZE];
disable_irqsave(&s);
va_start(arg, fmt);
n = vsnprintf(buf, sizeof(buf), fmt, arg);
va_end(arg);
locked = iprintcanlock(&iprintlock);
if (screenputs != NULL && iprintscreenputs)
screenputs(buf, n);
#if 0
uartputs(buf, n);
#endif
if (locked)
spin_unlock(&iprintlock);
enable_irqsave(&s);
return n;
}
/* 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);
vsnprintf(err, sizeof err, fmt, arg);
va_end(arg);
panic("sysfatal: %s", err);
}
#if 0
int pprint(char *fmt, ...)
{
ERRSTACK(2);
int n;
struct chan *c;
va_list arg;
char buf[2 * PRINTSIZE];
if (up == NULL || current->fgrp == NULL)
return 0;
c = current->fgrp->fd[2];
if (c == 0 || (c->mode != O_WRITE && c->mode != O_RDWR))
return 0;
n = snprintf(buf, sizeof buf, "%s %lud: ", current->text, current->pid);
va_start(arg, fmt);
n = vsnprintf(buf + n, sizeof(buf), fmt, arg);
va_end(arg);
if (waserror())
return 0;
devtab[c->type]->write(c, buf, n, c->offset);
poperror();
spin_lock(&c->lock);
c->offset += n;
spin_unlock(&c->lock);
return n;
}
#endif
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;
char *e, *p;
if (n == 0)
return;
e = buf + n;
for (p = buf; p < e; p++) {
switch (*p) {
#if 0
case 0x10: /* ^P */
if (cpuserver && !kbd.ctlpoff) {
active.exiting = 1;
return;
}
break;
#endif
case 0x14: /* ^T */
ctrlt++;
if (ctrlt > 2)
ctrlt = 2;
continue;
}
if (ctrlt != 2)
continue;
/* ^T escapes */
ctrlt = 0;
switch (*p) {
#if 0
case 'S':{
int8_t x = 0;
disable_irqsave(&x);
dumpstack();
procdump();
enable_irqsave(&x);
return;
}
#endif
case 's':
dumpstack();
return;
#if 0
case 'x':
xsummary();
ixsummary();
mallocsummary();
memorysummary();
pagersummary();
return;
case 'd':
if (consdebug == NULL)
consdebug = rdb;
else
consdebug = NULL;
printd("consdebug now %#p\n", consdebug);
return;
case 'D':
if (consdebug == NULL)
consdebug = rdb;
consdebug();
return;
case 'p':
x = spllo();
procdump();
splx(x);
return;
case 'q':
scheddump();
return;
case 'k':
killbig("^t ^t k");
return;
#endif
case 'r':
exit(0);
return;
}
}
qwrite(kbdq, buf, n); /* was once qproduce, YMMV */
if (kbd.raw)
return;
kmesgputs(buf, n);
if (screenputs != NULL)
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(struct queue *ignored_queue, int ch)
{
char *next;
spin_lock_irqsave(&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;
}
spin_unlock_irqsave(&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(struct queue *unused_queue, int ch)
{
int i, n;
char buf[3];
// Akaros does not use Rune et al.
// Rune r;
int r;
char *next;
if (kbd.ir == NULL)
return 0; /* in case we're not inited yet */
spin_lock_irqsave(&kbd.lockputc); /* just a mutex */
r = ch;
// n = runetochar(buf, &r);
// Fake Rune support.
n = 1;
buf[0] = 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;
}
spin_unlock_irqsave(&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,
Qconfig,
Qcons,
Qconsctl,
Qcputime,
Qdrivers,
Qhostdomain,
Qhostowner,
Qklog,
Qkmesg,
Qkprint,
Qnull,
Qosversion,
Qpgrpid,
Qpid,
Qppid,
Qreboot,
Qstdin,
Qstdout,
Qstderr,
Qswap,
Qsysctl,
Qsysname,
Qsysstat,
Qtime,
Quser,
Qzero,
Qkillkid,
};
enum { VLNUMSIZE = 22,
DOMLEN = 256,
};
static struct dirtab consdir[] = {
{".", {Qdir, 0, QTDIR}, 0, DMDIR | 0555},
{"bintime", {Qbintime}, 24, 0664},
{"config", {Qconfig}, 0, 0444},
{"cons", {Qcons}, 0, 0660},
{"consctl", {Qconsctl}, 0, 0220},
// FIXME -- we don't have real permissions yet so we set it to 222, not 220
{"killkid", {Qkillkid}, 0, 0220 | /* BOGUS */ 2},
{"cputime", {Qcputime}, 6 * NUMSIZE, 0444},
{"drivers", {Qdrivers}, 0, 0444},
{"hostdomain", {Qhostdomain}, DOMLEN, 0664},
{"hostowner", {Qhostowner}, 0, 0664},
{"klog", {Qklog}, 0, 0440},
{"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},
{"reboot", {Qreboot}, 0, 0660},
{"stdin", {Qstdin}, 0, 0666},
{"stdout", {Qstdout}, 0, 0666},
{"stderr", {Qstderr}, 0, 0666},
{"swap", {Qswap}, 0, 0664},
{"sysctl", {Qsysctl}, 0, 0666},
{"sysname", {Qsysname}, 0, 0664},
{"sysstat", {Qsysstat}, 0, 0666},
{"time", {Qtime}, NUMSIZE + 3 * VLNUMSIZE, 0664},
{"user", {Quser}, 0, 0666},
{"zero", {Qzero}, 0, 0444},
};
int consreadnum(uint32_t off, char *buf, uint32_t n, uint32_t val, int size)
{
char tmp[64];
snprintf(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 consreadstr(uint32_t off, char *buf, uint32_t 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)
{
kstrdup(&sysname, "nanwan");
cons_q = qopen(256, 0, 0, 0);
#if 0
todinit();
#endif
/*
* at 115200 baud, the 1024 char buffer takes 56 ms to process,
* processing it every 22 ms should be fine
*/
#if 0
addclock0link(kbdputcclock, 22);
#endif
cmb(); /* single-core, just need previous instructions to be issued. */
cons_has_init = TRUE;
}
static char *devname(void);
static struct chan *consattach(char *spec)
{
return devattach(devname(), spec);
}
static struct walkqid *conswalk(struct chan *c, struct chan *nc, char **name,
unsigned int nname)
{
return devwalk(c, nc, name, nname, consdir, ARRAY_SIZE(consdir),
devgen);
}
static size_t consstat(struct chan *c, uint8_t *dp, size_t n)
{
struct dir dir;
struct dirtab *tab;
int perm;
switch ((uint32_t)c->qid.path) {
case Qstdin:
tab = &consdir[Qstdin];
perm = tab->perm;
perm |= qreadable(cons_q) ? DMREADABLE : 0;
devdir(c, tab->qid, tab->name, qlen(cons_q), eve.name, perm,
&dir);
return dev_make_stat(c, &dir, dp, n);
case Qnull:
tab = &consdir[Qnull];
perm = tab->perm | DMWRITABLE;
devdir(c, tab->qid, tab->name, 0, eve.name, perm, &dir);
return dev_make_stat(c, &dir, dp, n);
default:
return devstat(c, dp, n, consdir, ARRAY_SIZE(consdir), devgen);
}
}
static struct chan *consopen(struct chan *c, int omode)
{
c->aux = NULL;
c = devopen(c, omode, consdir, ARRAY_SIZE(consdir), devgen);
switch ((uint32_t)c->qid.path) {
case Qconsctl:
kref_get(&kbd.ctl, 1);
break;
case Qkprint:
if (atomic_swap(&kprintinuse, 1) != 0) {
c->flag &= ~COPEN;
error(EADDRINUSE, "kprintinuse lock failed");
}
if (kprintoq == NULL) {
kprintoq = qopen(8 * 1024, Qcoalesce, 0, 0);
if (kprintoq == NULL) {
c->flag &= ~COPEN;
error(ENOMEM, "Can't allocate kprintoq");
}
qdropoverflow(kprintoq, 1);
} else
qreopen(kprintoq);
c->iounit = qiomaxatomic;
break;
}
return c;
}
static void consclose(struct chan *c)
{
switch ((uint32_t)c->qid.path) {
/* last close of control file turns off raw */
case Qconsctl:
if (c->flag & COPEN) {
if (kref_put(&kbd.ctl) == 0)
kbd.raw = 0;
}
break;
/* close of kprint allows other opens */
case Qkprint:
if (c->flag & COPEN) {
kprintinuse = 0;
qhangup(kprintoq, NULL);
}
break;
}
}
static size_t consread(struct chan *c, void *buf, size_t n, off64_t off)
{
ERRSTACK(1);
uint32_t l;
#if 0
Mach *mp;
#endif
char *b, *bp, ch;
char tmp[256]; /* must be >= 18*NUMSIZE (Qswap) */
int i, k, id, send;
int64_t offset = off;
#if 0
extern char configfile[];
#endif
if (n <= 0)
return n;
switch ((uint32_t)c->qid.path) {
case Qdir:
return devdirread(c, buf, n, consdir, ARRAY_SIZE(consdir),
devgen);
case Qcons:
qlock(&(&kbd)->qlock);
if (waserror()) {
qunlock(&(&kbd)->qlock);
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)->qlock);
poperror();
return n;
#if 0
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 = current->time[i];
if (i == TReal)
l = MACHP(0)->ticks - l;
l = TK2MS(l);
consreadnum(0, tmp + NUMSIZE * i, NUMSIZE, l, NUMSIZE);
}
memmove(buf, tmp + k, n);
return n;
#endif
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 consreadnum((uint32_t)offset, buf, n,
current->pgrp->pgrpid, NUMSIZE);
case Qpid:
return consreadnum((uint32_t)offset, buf, n, current->pid,
NUMSIZE);
case Qppid:
return consreadnum((uint32_t)offset, buf, n, current->ppid,
NUMSIZE);
case Qtime:
return readtime((uint32_t)offset, buf, n);
case Qbintime:
return readbintime(buf, n);
case Qhostowner:
return consreadstr((uint32_t)offset, buf, n, eve.name);
case Qhostdomain:
return consreadstr((uint32_t)offset, buf, n, hostdomain);
case Quser:
return consreadstr((uint32_t)offset, buf, n,
current->user.name);
case Qnull:
return 0;
#if 0
case Qconfig:
return consreadstr((uint32_t) offset, buf, n, configfile);
case Qsysstat:
/* +1 for NUL */
b = kzmalloc(conf.nmach * (NUMSIZE * 11 + 1) + 1, 0);
bp = b;
for (id = 0; id < 32; id++) {
if (active.machs & (1 << id)) {
mp = MACHP(id);
consreadnum(0, bp, NUMSIZE, id, NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->syscall,
NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->pfault,
NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->tlbfault,
NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->tlbpurge,
NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE,
(mp->perf.avg_inidle * 100) /
mp->perf.period, NUMSIZE);
bp += NUMSIZE;
consreadnum(0, bp, NUMSIZE,
(mp->perf.avg_inintr * 100) /
mp->perf.period, NUMSIZE);
bp += NUMSIZE;
*bp++ = '\n';
}
}
if (waserror()) {
kfree(b);
nexterror();
}
n = consreadstr((uint32_t) offset, buf, n, b);
kfree(b);
poperror();
return n;
case Qswap:
snprintf(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 consreadstr((uint32_t) offset, buf, n, tmp);
#endif
case Qstdin:
if (c->flag & O_NONBLOCK)
return qread_nonblock(cons_q, buf, n);
else
return qread(cons_q, buf, n);
case Qsysname:
/* TODO: this is racy */
if (sysname == NULL)
return 0;
return consreadstr((uint32_t)offset, buf, n, sysname);
case Qdrivers:
b = kzmalloc(READSTR, 0);
if (b == NULL)
error(ENOMEM,
"allocation for /dev/drivers read failed");
k = 0;
for (int i = 0; &devtab[i] < __devtabend; i++)
k += snprintf(b + k, READSTR - k, "#%s\n",
devtab[i].name);
if (waserror()) {
kfree(b);
nexterror();
}
n = consreadstr((uint32_t)offset, buf, n, b);
kfree(b);
poperror();
return n;
case Qklog:
// return qread(klogq, buf, n);
/* the queue gives us some elasticity for log reading. */
if (!logqueue)
logqueue = qopen(1 << 20, 0, 0, 0);
if (logqueue) {
int ret;
/* atomic sets/gets are not that important in this case.
*/
reading_kmesg = 1;
qwrite(logqueue, logbuffer, index);
index = 0;
ret = qread(logqueue, buf, n);
reading_kmesg = 0;
return ret;
}
break;
case Qzero:
memset(buf, 0, n);
return n;
case Qosversion:
snprintf(tmp, sizeof tmp, "2000");
n = consreadstr((uint32_t)offset, buf, n, tmp);
return n;
default:
printd("consread %#llux\n", c->qid.path);
error(EINVAL, "bad QID in consread");
}
return -1; /* never reached */
}
static size_t conswrite(struct chan *c, void *va, size_t n, off64_t off)
{
ERRSTACK(1);
char buf[256], ch;
long l, bp;
char *a;
// Mach *mp;
int id, fd;
struct chan *swc;
uint32_t offset;
struct cmdbuf *cb;
struct cmdtab *ct;
int x;
uint64_t rip, rsp, cr3, flags, vcpu;
int ret;
a = va;
offset = off;
switch ((uint32_t)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;
/* TODO: have it take a command about just *how* to kill the kid? */
case Qkillkid:
killkid();
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, ' ')) != NULL)
a++;
}
break;
case Qtime:
if (!iseve())
error(EPERM, "Hodie Natus Est Radici Frater");
return writetime(a, n);
case Qbintime:
if (!iseve())
error(EPERM, ERROR_FIXME);
return writebintime(a, n);
case Qhostowner:
return hostownerwrite(a, n);
#if 0
case Qhostdomain:
return hostdomainwrite(a, n);
case Quser:
return userwrite(a, n);
#endif
case Qnull:
break;
case Qconfig:
error(EPERM, "Cannot write to config QID");
break;
case Qsysctl:
// if (!iseve()) error(EPERM, ERROR_FIXME);
cb = parsecmd(a, n);
if (cb->nf > 1)
printd("cons sysctl cmd %s\n", cb->f[0]);
case Qreboot:
if (!iseve())
error(EPERM, ERROR_FIXME);
cb = parsecmd(a, n);
if (waserror()) {
kfree(cb);
nexterror();
}
ct = lookupcmd(cb, rebootmsg, ARRAY_SIZE(rebootmsg));
switch (ct->index) {
case CMhalt:
cpu_halt();
break;
case CMbroken:
keepbroken = 1;
break;
case CMnobroken:
keepbroken = 0;
break;
case CMreboot:
reboot();
break;
case CMpanic:
*(uint32_t *)0 = 0;
panic("/dev/reboot");
break;
}
poperror();
kfree(cb);
break;
#if 0
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(EINVAL, "n is bigger than sizeof buf for Qswap");
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, ERROR_FIXME);
if (buf[0] < '0' || '9' < buf[0])
error(EINVAL, ERROR_FIXME);
fd = strtoul(buf, 0, 0);
swc = fdtochan(fd, -1, 1, 1);
setswapchan(swc);
break;
#endif
case Qstdout:
case Qstderr:
print_lock();
if (waserror()) {
print_unlock();
nexterror();
}
/* TODO: tty hack. they are sending us an escape sequence, and
* the keyboard would try to print it (which it can't do yet).
* The hack is even dirtier in that we only detect it if it is
* the first
* char, and we ignore everything else. \033 is 0x1b. */
if (((char *)va)[0] != '\033') {
n = MIN(n, 80);
cputbuf(va, n);
}
poperror();
print_unlock();
return n;
case Qsysname:
/* TODO: this is racy */
if (offset != 0)
error(EINVAL, ERROR_FIXME);
if (n <= 0 || n >= sizeof buf)
error(EINVAL, ERROR_FIXME);
strncpy(buf, a, n);
buf[n] = 0;
if (buf[n - 1] == '\n')
buf[n - 1] = 0;
kstrdup(&sysname, buf);
break;
default:
printd("conswrite: %#llux\n", c->qid.path);
error(EINVAL, "bad QID in conswrite");
}
return n;
}
static char *cons_chaninfo(struct chan *ch, char *ret, size_t ret_l)
{
switch ((uint32_t)ch->qid.path) {
case Qstdin:
snprintf(ret, ret_l, "qio len: %d", qlen(cons_q));
break;
default:
return devchaninfo(ch, ret, ret_l);
}
return ret;
}
static void __consq_fire_taps(uint32_t srcid, long a0, long a1, long a2)
{
struct fd_tap *tap_i;
int filter = a0;
spin_lock(&cons_q_lock);
SLIST_FOREACH (tap_i, &cons_q_fd_taps, link)
fire_tap(tap_i, filter);
spin_unlock(&cons_q_lock);
}
static void cons_q_wake_cb(struct queue *q, void *data, int filter)
{
/* TODO: taps can't fire from IRQ context, but the qiwrites for stdin
* come from IRQ context. So we need an RKM here. */
send_kernel_message(core_id(), __consq_fire_taps, filter, 0, 0,
KMSG_ROUTINE);
}
static int tap_stdin(struct chan *c, struct fd_tap *tap, int cmd)
{
int ret;
/* We don't actually support HANGUP, but epoll implies it. */
#define CONS_STDIN_TAPS (FDTAP_FILT_READABLE | FDTAP_FILT_HANGUP)
if (tap->filter & ~CONS_STDIN_TAPS) {
set_error(ENOSYS, "Unsupported #%s tap, must be %p", devname(),
CONS_STDIN_TAPS);
return -1;
}
spin_lock(&cons_q_lock);
switch (cmd) {
case FDTAP_CMD_ADD:
if (SLIST_EMPTY(&cons_q_fd_taps))
qio_set_wake_cb(cons_q, cons_q_wake_cb, 0);
SLIST_INSERT_HEAD(&cons_q_fd_taps, tap, link);
ret = 0;
break;
case FDTAP_CMD_REM:
SLIST_REMOVE(&cons_q_fd_taps, tap, fd_tap, link);
if (SLIST_EMPTY(&cons_q_fd_taps))
qio_set_wake_cb(cons_q, 0, 0);
ret = 0;
break;
default:
set_error(ENOSYS, "Unsupported #%s tap command %p", devname(),
cmd);
ret = -1;
}
spin_unlock(&cons_q_lock);
return ret;
}
/* Null isn't really tapable, since there are no edge events - it is always
* writable. However, users might want to tap their output files, regardless of
* whether or not it will generate an event. We'll pretend that we tapped it
* (or untapped, based on the command).
*
* Note that epoll will generate an event, since it will stat null and see its
* writable, so epoll will appear to have an event. */
static int tap_null(struct chan *c, struct fd_tap *tap, int cmd)
{
/* We don't actually support HANGUP, but epoll implies it. */
#define CONS_NULL_TAPS (FDTAP_FILT_WRITABLE | FDTAP_FILT_HANGUP)
if (tap->filter & ~CONS_NULL_TAPS) {
set_error(ENOSYS, "Unsupported #%s tap, must be %p", devname(),
CONS_NULL_TAPS);
return -1;
}
return 0;
}
static int cons_tapfd(struct chan *c, struct fd_tap *tap, int cmd)
{
switch ((uint32_t)c->qid.path) {
case Qstdin:
return tap_stdin(c, tap, cmd);
case Qnull:
return tap_null(c, tap, cmd);
default:
set_error(ENOSYS, "Can't tap #%s file type %d", devname(),
c->qid.path);
return -1;
}
}
struct dev consdevtab __devtab = {
.name = "cons",
.reset = devreset,
.init = consinit,
.shutdown = devshutdown,
.attach = consattach,
.walk = conswalk,
.stat = consstat,
.open = consopen,
.create = devcreate,
.close = consclose,
.read = consread,
.bread = devbread,
.write = conswrite,
.bwrite = devbwrite,
.remove = devremove,
.wstat = devwstat,
.power = devpower,
.chaninfo = cons_chaninfo,
.tapfd = cons_tapfd,
};
static char *devname(void)
{
return consdevtab.name;
}
static uint64_t uvorder = 0x0001020304050607ULL;
static uint8_t *le2int64_t(int64_t *to, uint8_t *f)
{
uint8_t *t, *o;
int i;
t = (uint8_t *)to;
o = (uint8_t *)&uvorder;
for (i = 0; i < sizeof(int64_t); i++)
t[o[i]] = f[i];
return f + sizeof(int64_t);
}
static uint8_t *int64_t2le(uint8_t *t, int64_t from)
{
uint8_t *f, *o;
int i;
f = (uint8_t *)&from;
o = (uint8_t *)&uvorder;
for (i = 0; i < sizeof(int64_t); i++)
t[i] = f[o[i]];
return t + sizeof(int64_t);
}
static long order = 0x00010203;
static uint8_t *le2long(long *to, uint8_t *f)
{
uint8_t *t, *o;
int i;
t = (uint8_t *)&to;
o = (uint8_t *)&order;
for (i = 0; i < sizeof(long); i++)
t[o[i]] = f[i];
return f + sizeof(long);
}
static uint8_t *long2le(uint8_t *t, long from)
{
uint8_t *f, *o;
int i;
f = (uint8_t *)&from;
o = (uint8_t *)&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(uint32_t off, char *buf, int n)
{
int64_t nsec, ticks;
long sec;
char str[7 * NUMSIZE];
if (fasthz == 0LL)
fasthz = __proc_global_info.tsc_freq;
#if 0
fastticks((uint64_t *) & fasthz);
nsec = todget(&ticks);
#endif
ticks = read_tsc();
nsec = tsc2nsec(ticks);
sec = nsec / 1000000000ULL;
snprintf(str, sizeof(str), "%*lud %*llud %*llud %*llud ", NUMSIZE - 1,
sec, VLNUMSIZE - 1, nsec, VLNUMSIZE - 1, ticks, VLNUMSIZE - 1,
fasthz);
return consreadstr(off, buf, n, str);
}
/*
* set the time in seconds
*/
static int writetime(char *buf, int n)
{
char b[13];
long i;
int64_t now;
if (n >= sizeof(b))
error(EINVAL, "bad size in writetime");
strncpy(b, buf, n);
b[n] = 0;
i = strtol(b, 0, 0);
if (i <= 0)
error(EINVAL, "Bad time in write");
now = i * 1000000000LL;
#if 0
todset(now, 0, 0);
#endif
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;
int64_t nsec, ticks;
uint8_t *b = (uint8_t *)buf;
i = 0;
if (fasthz == 0LL)
fasthz = __proc_global_info.tsc_freq;
#if 0
fastticks((uint64_t *) & fasthz);
nsec = todget(&ticks);
#endif
ticks = read_tsc();
nsec = tsc2nsec(ticks);
if (n >= 3 * sizeof(uint64_t)) {
int64_t2le(b + 2 * sizeof(uint64_t), fasthz);
i += sizeof(uint64_t);
}
if (n >= 2 * sizeof(uint64_t)) {
int64_t2le(b + sizeof(uint64_t), ticks);
i += sizeof(uint64_t);
}
if (n >= 8) {
int64_t2le(b, nsec);
i += sizeof(int64_t);
}
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)
{
uint8_t *p;
int64_t delta = 0;
long period = 0;
n--;
p = (uint8_t *)buf + 1;
switch (*buf) {
case 'n':
if (n < sizeof(int64_t))
error(EINVAL, ERROR_FIXME);
le2int64_t(&delta, p);
#if 0
todset(delta, 0, 0);
#endif
break;
case 'd':
if (n < sizeof(int64_t) + sizeof(long))
error(EINVAL, ERROR_FIXME);
p = le2int64_t(&delta, p);
le2long(&period, p);
#if 0
todset(-1, delta, period);
#endif
break;
case 'f':
if (n < sizeof(uint64_t))
error(EINVAL, ERROR_FIXME);
le2int64_t(&fasthz, p);
if (fasthz <= 0)
error(EINVAL, ERROR_FIXME);
#if 0
todsetfreq(fasthz);
#endif
break;
}
return n;
}
/*
* set the hostowner, but only if current is the hostowner and the new value
* isn't too long
*/
static int hostownerwrite(char *buf, size_t n)
{
ERRSTACK(1);
if (!iseve())
error(EPERM, "only hostowner can change hostowner");
if (strlen(buf) < 1)
error(EINVAL, "hostowner cannot be set to \"\"");
spin_lock(&eve.name_lock);
if (waserror()) {
spin_unlock(&eve.name_lock);
nexterror();
}
// value at boot is ""
if (eve.name[0] != 0)
error(EPERM, "hostowner can only be set once");
__set_username(&eve, buf);
poperror();
spin_unlock(&eve.name_lock);
// Set this username first so it gets set regardless of an error below
proc_set_username(current, buf);
// Update all other hostowner processes
// This is costly, but should only happen very early on
struct process_set pset;
proc_get_set(&pset);
if (waserror()) {
proc_free_set(&pset);
nexterror();
}
for (size_t i = 0; i < pset.num_processes; i++) {
// Won't update current again
// Note: if a name is being written to the user field as it is
// read
// here, it will appear as ""; but no other writes should
// occur as early as this should be run
if (strcmp(pset.procs[i]->user.name, "") == 0)
proc_set_username(pset.procs[i], buf);
}
poperror();
proc_free_set(&pset);
return n;
}
/*
* find_first_kid finds your immediate descendant. Not because we're
* trying to give it a check from the estate, mind you. We're here on
* much more unpleasant business. To make it easy to call this
* multiple times without lots of fussy checking, we allow it to be
* called with NULL pointers; hence find_first_kid(find_first_kid(x))
* works even if x is chaste and will return NULL.
*/
static struct proc *find_first_kid(struct proc *p)
{
struct proc *kid;
cv_lock(&p->child_wait);
kid = TAILQ_FIRST(&p->children);
if (kid) {
proc_incref(kid, 1);
proc_decref(p);
}
cv_unlock(&p->child_wait);
return kid;
}
/*
* killkid manages population issues by killing your kids. This is a
* particular temporary (we think) function for ssh. As such, we are
* going to make a Command Decision to never kill the top level
* kid. We start on grandkids, in other words. Hey, the parent/child
* terminology was Ken's idea; go yell at him. If you want, we can
* rename this to DrownCuteKittens.
*
* This was hard to get right so it's way more verbose than you might think we
* need. Sorry.
*/
void killkid(void)
{
struct proc *check, *kid, *grandkid, *victim;
/* find first grandkid */
proc_incref(current, 1);
kid = find_first_kid(current);
if (!kid) {
proc_decref(current);
return;
}
grandkid = find_first_kid(kid);
if (!grandkid) {
proc_decref(kid);
return;
}
victim = grandkid;
while (1) {
check = find_first_kid(victim);
if (!check)
break;
victim = check;
}
send_posix_signal(victim, SIGINT);
proc_decref(victim);
}
/* This can be called any time after arch_init()->cons_irq_init(). That can
* happen *before* the console device is initialized (devtab{reset,init}()).
*
* All other functions in #cons shouldn't be called until after cons_init(). */
void cons_add_char(char c)
{
if (!cons_has_init)
return;
qiwrite(cons_q, &c, sizeof(char));
}