summaryrefslogtreecommitdiff
path: root/os/port
diff options
context:
space:
mode:
Diffstat (limited to 'os/port')
-rw-r--r--os/port/NOTICE18
-rw-r--r--os/port/alarm.c41
-rw-r--r--os/port/alloc.c963
-rw-r--r--os/port/allocb.c159
-rw-r--r--os/port/chan.c1431
-rw-r--r--os/port/cis.c539
-rw-r--r--os/port/dev.c434
-rw-r--r--os/port/devXXX.c147
-rw-r--r--os/port/devaudio.c947
-rw-r--r--os/port/devbench.c1165
-rw-r--r--os/port/devboot.c150
-rw-r--r--os/port/devbridge.c1206
-rw-r--r--os/port/devcap.c248
-rw-r--r--os/port/devcons.c1274
-rw-r--r--os/port/devdbg.c1000
-rw-r--r--os/port/devdraw.c2088
-rw-r--r--os/port/devds.c605
-rw-r--r--os/port/devdup.c151
-rw-r--r--os/port/devdynld.c365
-rw-r--r--os/port/devenv.c338
-rw-r--r--os/port/devflash.c641
-rw-r--r--os/port/devftl.c1365
-rw-r--r--os/port/devi2c.c227
-rw-r--r--os/port/devindir.c40
-rw-r--r--os/port/devkprof.c230
-rwxr-xr-xos/port/devlogfs.c1531
-rw-r--r--os/port/devloopback.c743
-rw-r--r--os/port/devmnt.c1204
-rw-r--r--os/port/devns16552.c1090
-rw-r--r--os/port/devpci.c227
-rw-r--r--os/port/devpipe.c464
-rw-r--r--os/port/devpointer.c279
-rw-r--r--os/port/devprof.c722
-rw-r--r--os/port/devprog.c1510
-rw-r--r--os/port/devroot.c153
-rw-r--r--os/port/devsd.c1474
-rw-r--r--os/port/devsign.c446
-rw-r--r--os/port/devsrv.c726
-rw-r--r--os/port/devssl.c1436
-rw-r--r--os/port/devtest.c125
-rw-r--r--os/port/devtinyfs.c915
-rw-r--r--os/port/devtk.c180
-rw-r--r--os/port/devuart.c748
-rw-r--r--os/port/dial.c417
-rw-r--r--os/port/dis.c1125
-rw-r--r--os/port/discall.c185
-rw-r--r--os/port/dynld.c75
-rw-r--r--os/port/edf.c626
-rw-r--r--os/port/edf.h53
-rw-r--r--os/port/error.h64
-rw-r--r--os/port/ethermii.c238
-rw-r--r--os/port/ethermii.h116
-rw-r--r--os/port/ethersink.c65
-rw-r--r--os/port/exception.c216
-rw-r--r--os/port/exportfs.c1325
-rw-r--r--os/port/flashamd29f0x0.c167
-rw-r--r--os/port/flashcfi16.c134
-rw-r--r--os/port/flashcfi8.c143
-rw-r--r--os/port/flashif.h147
-rw-r--r--os/port/flashintel179
-rw-r--r--os/port/flashnand.c337
-rw-r--r--os/port/fpi.c304
-rw-r--r--os/port/fpimem.c136
-rw-r--r--os/port/inferno.c1030
-rw-r--r--os/port/latin1.c76
-rw-r--r--os/port/latin1.h100
-rw-r--r--os/port/lib.h211
-rw-r--r--os/port/log.c186
-rw-r--r--os/port/master89
-rw-r--r--os/port/master.local10
-rw-r--r--os/port/mkdevc216
-rw-r--r--os/port/mkdevlist86
-rw-r--r--os/port/mkroot119
-rw-r--r--os/port/mul64fract.c39
-rw-r--r--os/port/netaux.c67
-rw-r--r--os/port/netif.c671
-rw-r--r--os/port/netif.h134
-rw-r--r--os/port/nocache.c49
-rw-r--r--os/port/nodynld.c48
-rw-r--r--os/port/noenv.c33
-rw-r--r--os/port/noscreen.c9
-rw-r--r--os/port/parse.c114
-rw-r--r--os/port/pgrp.c278
-rw-r--r--os/port/portbreak.c161
-rw-r--r--os/port/portclock.c277
-rw-r--r--os/port/portdat.h673
-rw-r--r--os/port/portfns.h319
-rw-r--r--os/port/portmkfile152
-rw-r--r--os/port/print.c31
-rw-r--r--os/port/proc.c788
-rw-r--r--os/port/qio.c1529
-rw-r--r--os/port/qlock.c111
-rw-r--r--os/port/random.c156
-rw-r--r--os/port/rdb.c112
-rw-r--r--os/port/sd.h132
-rw-r--r--os/port/swcursor.c358
-rw-r--r--os/port/sysfile.c1125
-rw-r--r--os/port/taslock.c126
-rw-r--r--os/port/tod.c283
-rw-r--r--os/port/uart.h104
-rw-r--r--os/port/xalloc.c246
101 files changed, 46045 insertions, 0 deletions
diff --git a/os/port/NOTICE b/os/port/NOTICE
new file mode 100644
index 00000000..3b325e40
--- /dev/null
+++ b/os/port/NOTICE
@@ -0,0 +1,18 @@
+The following files are subject to the Lucent Public License 1.02:
+
+devbridge.c
+devds.c
+devdup.c
+devloopback.c
+devpci.c (portions)
+devpnp.c
+devsd.c
+devuart.c
+edf.c
+edf.h
+ethermii.c
+ethermii.h
+log.c
+mul64fract.c
+rdb.c
+sd.h
diff --git a/os/port/alarm.c b/os/port/alarm.c
new file mode 100644
index 00000000..4f7662c3
--- /dev/null
+++ b/os/port/alarm.c
@@ -0,0 +1,41 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+Talarm talarm;
+
+/*
+ * called every clock tick
+ */
+void
+checkalarms(void)
+{
+ Proc *p;
+ ulong now;
+
+ now = MACHP(0)->ticks;
+
+ if(talarm.list == 0 || canlock(&talarm) == 0)
+ return;
+
+ for(;;) {
+ p = talarm.list;
+ if(p == 0)
+ break;
+
+ if(p->twhen == 0) {
+ talarm.list = p->tlink;
+ p->trend = 0;
+ continue;
+ }
+ if(now < p->twhen)
+ break;
+ wakeup(p->trend);
+ talarm.list = p->tlink;
+ p->trend = 0;
+ }
+
+ unlock(&talarm);
+}
diff --git a/os/port/alloc.c b/os/port/alloc.c
new file mode 100644
index 00000000..810560a4
--- /dev/null
+++ b/os/port/alloc.c
@@ -0,0 +1,963 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "interp.h"
+
+#define left u.s.bhl
+#define right u.s.bhr
+#define fwd u.s.bhf
+#define prev u.s.bhv
+#define parent u.s.bhp
+
+#define RESERVED 256*1024
+
+struct Pool
+{
+ char* name;
+ int pnum;
+ ulong maxsize;
+ int quanta;
+ int chunk;
+ ulong ressize;
+ ulong cursize;
+ ulong arenasize;
+ ulong hw;
+ Lock l;
+ Bhdr* root;
+ Bhdr* chain;
+ ulong nalloc;
+ ulong nfree;
+ int nbrk;
+ int lastfree;
+ int warn;
+ void (*move)(void*, void*);
+};
+
+static
+struct
+{
+ int n;
+ Pool pool[MAXPOOL];
+ // Lock l;
+} table = {
+ 3,
+ {
+ { "main", 0, 4*1024*1024, 31, 128*1024, 15*256*1024 },
+ { "heap", 1, 16*1024*1024, 31, 128*1024, 15*1024*1024 },
+ { "image", 2, 8*1024*1024, 31, 300*1024, 15*512*1024 },
+ }
+};
+
+Pool* mainmem = &table.pool[0];
+Pool* heapmem = &table.pool[1];
+Pool* imagmem = &table.pool[2];
+
+static void _auditmemloc(char *, void *);
+void (*auditmemloc)(char *, void *) = _auditmemloc;
+static void _poolfault(void *, char *, ulong);
+void (*poolfault)(void *, char *, ulong) = _poolfault;
+
+/* non tracing
+ *
+enum {
+ Npadlong = 0,
+ MallocOffset = 0,
+ ReallocOffset = 0
+};
+ *
+ */
+
+/* tracing */
+enum {
+ Npadlong = 2,
+ MallocOffset = 0,
+ ReallocOffset = 1
+};
+
+int
+memusehigh(void)
+{
+ return mainmem->cursize > mainmem->ressize ||
+ heapmem->cursize > heapmem->ressize ||
+ imagmem->cursize > imagmem->ressize;
+}
+
+void
+poolimmutable(void *v)
+{
+ Bhdr *b;
+
+ D2B(b, v);
+ b->magic = MAGIC_I;
+}
+
+void
+poolmutable(void *v)
+{
+ Bhdr *b;
+
+ D2B(b, v);
+ b->magic = MAGIC_A;
+ ((Heap*)v)->color = mutator;
+}
+
+char*
+poolname(Pool *p)
+{
+ return p->name;
+}
+
+Bhdr*
+poolchain(Pool *p)
+{
+ return p->chain;
+}
+
+void
+pooldel(Pool *p, Bhdr *t)
+{
+ Bhdr *s, *f, *rp, *q;
+
+ if(t->parent == nil && p->root != t) {
+ t->prev->fwd = t->fwd;
+ t->fwd->prev = t->prev;
+ return;
+ }
+
+ if(t->fwd != t) {
+ f = t->fwd;
+ s = t->parent;
+ f->parent = s;
+ if(s == nil)
+ p->root = f;
+ else {
+ if(s->left == t)
+ s->left = f;
+ else
+ s->right = f;
+ }
+
+ rp = t->left;
+ f->left = rp;
+ if(rp != nil)
+ rp->parent = f;
+ rp = t->right;
+ f->right = rp;
+ if(rp != nil)
+ rp->parent = f;
+
+ t->prev->fwd = t->fwd;
+ t->fwd->prev = t->prev;
+ return;
+ }
+
+ if(t->left == nil)
+ rp = t->right;
+ else {
+ if(t->right == nil)
+ rp = t->left;
+ else {
+ f = t;
+ rp = t->right;
+ s = rp->left;
+ while(s != nil) {
+ f = rp;
+ rp = s;
+ s = rp->left;
+ }
+ if(f != t) {
+ s = rp->right;
+ f->left = s;
+ if(s != nil)
+ s->parent = f;
+ s = t->right;
+ rp->right = s;
+ if(s != nil)
+ s->parent = rp;
+ }
+ s = t->left;
+ rp->left = s;
+ s->parent = rp;
+ }
+ }
+ q = t->parent;
+ if(q == nil)
+ p->root = rp;
+ else {
+ if(t == q->left)
+ q->left = rp;
+ else
+ q->right = rp;
+ }
+ if(rp != nil)
+ rp->parent = q;
+}
+
+void
+pooladd(Pool *p, Bhdr *q)
+{
+ int size;
+ Bhdr *tp, *t;
+
+ q->magic = MAGIC_F;
+
+ q->left = nil;
+ q->right = nil;
+ q->parent = nil;
+ q->fwd = q;
+ q->prev = q;
+
+ t = p->root;
+ if(t == nil) {
+ p->root = q;
+ return;
+ }
+
+ size = q->size;
+
+ tp = nil;
+ while(t != nil) {
+ if(size == t->size) {
+ q->prev = t->prev;
+ q->prev->fwd = q;
+ q->fwd = t;
+ t->prev = q;
+ return;
+ }
+ tp = t;
+ if(size < t->size)
+ t = t->left;
+ else
+ t = t->right;
+ }
+
+ q->parent = tp;
+ if(size < tp->size)
+ tp->left = q;
+ else
+ tp->right = q;
+}
+
+void
+poolsummary(void)
+{
+ int x = 0;
+ char buf[400];
+
+ print("\n");
+ print(" cursize maxsize hw nalloc nfree nbrk max name\n");
+
+ x=poolread( buf, sizeof buf - 1, x );
+ buf[x] = 0;
+ putstrn(buf, x);
+ print("\n");
+}
+
+void*
+poolalloc(Pool *p, ulong asize)
+{
+ Bhdr *q, *t;
+ int alloc, ldr, ns, frag;
+ int osize, size;
+ Prog *prog;
+
+ if(asize >= 1024*1024*1024) /* for sanity and to avoid overflow */
+ return nil;
+ if(p->cursize > p->ressize && (prog = currun()) != nil && prog->flags&Prestricted)
+ return nil;
+ size = asize;
+ osize = size;
+ size = (size + BHDRSIZE + p->quanta) & ~(p->quanta);
+
+ ilock(&p->l);
+ p->nalloc++;
+
+ t = p->root;
+ q = nil;
+ while(t) {
+ if(t->size == size) {
+ t = t->fwd;
+ pooldel(p, t);
+ t->magic = MAGIC_A;
+ p->cursize += t->size;
+ if(p->cursize > p->hw)
+ p->hw = p->cursize;
+ iunlock(&p->l);
+ return B2D(t);
+ }
+ if(size < t->size) {
+ q = t;
+ t = t->left;
+ }
+ else
+ t = t->right;
+ }
+ if(q != nil) {
+ pooldel(p, q);
+ q->magic = MAGIC_A;
+ frag = q->size - size;
+ if(frag < (size>>2) && frag < 0x8000) {
+ p->cursize += q->size;
+ if(p->cursize > p->hw)
+ p->hw = p->cursize;
+ iunlock(&p->l);
+ return B2D(q);
+ }
+ /* Split */
+ ns = q->size - size;
+ q->size = size;
+ B2T(q)->hdr = q;
+ t = B2NB(q);
+ t->size = ns;
+ B2T(t)->hdr = t;
+ pooladd(p, t);
+ p->cursize += q->size;
+ if(p->cursize > p->hw)
+ p->hw = p->cursize;
+ iunlock(&p->l);
+ return B2D(q);
+ }
+
+ ns = p->chunk;
+ if(size > ns)
+ ns = size;
+ ldr = p->quanta+1;
+
+ alloc = ns+ldr+ldr;
+ p->arenasize += alloc;
+ if(p->arenasize > p->maxsize) {
+ p->arenasize -= alloc;
+ ns = p->maxsize-p->arenasize-ldr-ldr;
+ ns &= ~p->quanta;
+ if (ns < size) {
+ if(poolcompact(p)) {
+ iunlock(&p->l);
+ return poolalloc(p, osize);
+ }
+
+ iunlock(&p->l);
+ if(p->warn)
+ return nil;
+ p->warn = 1;
+ if (p != mainmem || ns > 512)
+ print("arena too large: %s size %d cursize %lud arenasize %lud maxsize %lud, alloc = %d\n", p->name, osize, p->cursize, p->arenasize, p->maxsize, alloc);
+ return nil;
+ }
+ alloc = ns+ldr+ldr;
+ p->arenasize += alloc;
+ }
+
+ p->nbrk++;
+ t = xalloc(alloc);
+ if(t == nil) {
+ p->nbrk--;
+ iunlock(&p->l);
+ return nil;
+ }
+ /* Double alignment */
+ t = (Bhdr *)(((ulong)t + 7) & ~7);
+
+ /* TBS xmerge */
+ if(0 && p->chain != nil && (char*)t-(char*)B2LIMIT(p->chain)-ldr == 0){
+ /* can merge chains */
+ if(0)print("merging chains %p and %p in %s\n", p->chain, t, p->name);
+ q = B2LIMIT(p->chain);
+ q->magic = MAGIC_A;
+ q->size = alloc;
+ B2T(q)->hdr = q;
+ t = B2NB(q);
+ t->magic = MAGIC_E;
+ p->chain->csize += alloc;
+ p->cursize += alloc;
+ iunlock(&p->l);
+ poolfree(p, B2D(q)); /* for backward merge */
+ return poolalloc(p, osize);
+ }
+
+ t->magic = MAGIC_E; /* Make a leader */
+ t->size = ldr;
+ t->csize = ns+ldr;
+ t->clink = p->chain;
+ p->chain = t;
+ B2T(t)->hdr = t;
+ t = B2NB(t);
+
+ t->magic = MAGIC_A; /* Make the block we are going to return */
+ t->size = size;
+ B2T(t)->hdr = t;
+ q = t;
+
+ ns -= size; /* Free the rest */
+ if(ns > 0) {
+ q = B2NB(t);
+ q->size = ns;
+ B2T(q)->hdr = q;
+ pooladd(p, q);
+ }
+ B2NB(q)->magic = MAGIC_E; /* Mark the end of the chunk */
+
+ p->cursize += t->size;
+ if(p->cursize > p->hw)
+ p->hw = p->cursize;
+ iunlock(&p->l);
+ return B2D(t);
+}
+
+void
+poolfree(Pool *p, void *v)
+{
+ Bhdr *b, *c;
+ extern Bhdr *ptr;
+
+ D2B(b, v);
+
+ ilock(&p->l);
+ p->nfree++;
+ p->cursize -= b->size;
+
+ c = B2NB(b);
+ if(c->magic == MAGIC_F) { /* Join forward */
+ if(c == ptr)
+ ptr = b;
+ pooldel(p, c);
+ c->magic = 0;
+ b->size += c->size;
+ B2T(b)->hdr = b;
+ }
+
+ c = B2PT(b)->hdr;
+ if(c->magic == MAGIC_F) { /* Join backward */
+ if(b == ptr)
+ ptr = c;
+ pooldel(p, c);
+ b->magic = 0;
+ c->size += b->size;
+ b = c;
+ B2T(b)->hdr = b;
+ }
+
+ pooladd(p, b);
+ iunlock(&p->l);
+}
+
+void *
+poolrealloc(Pool *p, void *v, ulong size)
+{
+ Bhdr *b;
+ void *nv;
+ int osize;
+
+ if(size >= 1024*1024*1024) /* for sanity and to avoid overflow */
+ return nil;
+ if(size == 0){
+ poolfree(p, v);
+ return nil;
+ }
+ SET(osize);
+ if(v != nil){
+ ilock(&p->l);
+ D2B(b, v);
+ osize = b->size - BHDRSIZE;
+ iunlock(&p->l);
+ if(osize >= size)
+ return v;
+ }
+ nv = poolalloc(p, size);
+ if(nv != nil && v != nil){
+ memmove(nv, v, osize);
+ poolfree(p, v);
+ }
+ return nv;
+}
+
+ulong
+poolmsize(Pool *p, void *v)
+{
+ Bhdr *b;
+ ulong size;
+
+ if(v == nil)
+ return 0;
+ ilock(&p->l);
+ D2B(b, v);
+ size = b->size - BHDRSIZE;
+ iunlock(&p->l);
+ return size;
+}
+
+static ulong
+poolmax(Pool *p)
+{
+ Bhdr *t;
+ ulong size;
+
+ ilock(&p->l);
+ size = p->maxsize - p->cursize;
+ t = p->root;
+ if(t != nil) {
+ while(t->right != nil)
+ t = t->right;
+ if(size < t->size)
+ size = t->size;
+ }
+ if(size >= BHDRSIZE)
+ size -= BHDRSIZE;
+ iunlock(&p->l);
+ return size;
+}
+
+int
+poolread(char *va, int count, ulong offset)
+{
+ Pool *p;
+ int n, i, signed_off;
+
+ n = 0;
+ signed_off = offset;
+ for(i = 0; i < table.n; i++) {
+ p = &table.pool[i];
+ n += snprint(va+n, count-n, "%11lud %11lud %11lud %11lud %11lud %11d %11lud %s\n",
+ p->cursize,
+ p->maxsize,
+ p->hw,
+ p->nalloc,
+ p->nfree,
+ p->nbrk,
+ poolmax(p),
+ p->name);
+
+ if(signed_off > 0) {
+ signed_off -= n;
+ if(signed_off < 0) {
+ memmove(va, va+n+signed_off, -signed_off);
+ n = -signed_off;
+ }
+ else
+ n = 0;
+ }
+
+ }
+ return n;
+}
+
+void*
+malloc(ulong size)
+{
+ void *v;
+
+ v = poolalloc(mainmem, size+Npadlong*sizeof(ulong));
+ if(v != nil){
+ if(Npadlong){
+ v = (ulong*)v+Npadlong;
+ setmalloctag(v, getcallerpc(&size));
+ setrealloctag(v, 0);
+ }
+ memset(v, 0, size);
+ }
+ return v;
+}
+
+void*
+smalloc(ulong size)
+{
+ void *v;
+
+ for(;;) {
+ v = poolalloc(mainmem, size+Npadlong*sizeof(ulong));
+ if(v != nil)
+ break;
+ tsleep(&up->sleep, return0, 0, 100);
+ }
+ if(Npadlong){
+ v = (ulong*)v+Npadlong;
+ setmalloctag(v, getcallerpc(&size));
+ setrealloctag(v, 0);
+ }
+ memset(v, 0, size);
+ return v;
+}
+
+void*
+mallocz(ulong size, int clr)
+{
+ void *v;
+
+ v = poolalloc(mainmem, size+Npadlong*sizeof(ulong));
+ if(v != nil){
+ if(Npadlong){
+ v = (ulong*)v+Npadlong;
+ setmalloctag(v, getcallerpc(&size));
+ setrealloctag(v, 0);
+ }
+ if(clr)
+ memset(v, 0, size);
+ }
+ return v;
+}
+
+void
+free(void *v)
+{
+ Bhdr *b;
+
+ if(v != nil) {
+ if(Npadlong)
+ v = (ulong*)v-Npadlong;
+ D2B(b, v);
+ poolfree(mainmem, v);
+ }
+}
+
+void*
+realloc(void *v, ulong size)
+{
+ void *nv;
+
+ if(size == 0)
+ return malloc(size); /* temporary change until realloc calls can be checked */
+ if(v != nil)
+ v = (ulong*)v-Npadlong;
+ if(Npadlong!=0 && size!=0)
+ size += Npadlong*sizeof(ulong);
+ nv = poolrealloc(mainmem, v, size);
+ if(nv != nil) {
+ nv = (ulong*)nv+Npadlong;
+ setrealloctag(nv, getcallerpc(&v));
+ if(v == nil)
+ setmalloctag(v, getcallerpc(&v));
+ }
+ return nv;
+}
+
+void
+setmalloctag(void *v, ulong pc)
+{
+ ulong *u;
+
+ USED(v);
+ USED(pc);
+ if(Npadlong <= MallocOffset || v == nil)
+ return;
+ u = v;
+ u[-Npadlong+MallocOffset] = pc;
+}
+
+ulong
+getmalloctag(void *v)
+{
+ USED(v);
+ if(Npadlong <= MallocOffset)
+ return ~0;
+ return ((ulong*)v)[-Npadlong+MallocOffset];
+}
+
+void
+setrealloctag(void *v, ulong pc)
+{
+ ulong *u;
+
+ USED(v);
+ USED(pc);
+ if(Npadlong <= ReallocOffset || v == nil)
+ return;
+ u = v;
+ u[-Npadlong+ReallocOffset] = pc;
+}
+
+ulong
+getrealloctag(void *v)
+{
+ USED(v);
+ if(Npadlong <= ReallocOffset)
+ return ((ulong*)v)[-Npadlong+ReallocOffset];
+ return ~0;
+}
+
+ulong
+msize(void *v)
+{
+ if(v == nil)
+ return 0;
+ return poolmsize(mainmem, (ulong*)v-Npadlong)-Npadlong*sizeof(ulong);
+}
+
+void*
+calloc(ulong n, ulong szelem)
+{
+ return malloc(n*szelem);
+}
+
+void
+pooldump(Bhdr *b, int d, int c)
+{
+ Bhdr *t;
+
+ if(b == nil)
+ return;
+
+ print("%.8lux %.8lux %.8lux %c %4d %lud (f %.8lux p %.8lux)\n",
+ b, b->left, b->right, c, d, b->size, b->fwd, b->prev);
+ d++;
+ for(t = b->fwd; t != b; t = t->fwd)
+ print("\t%.8lux %.8lux %.8lux\n", t, t->prev, t->fwd);
+ pooldump(b->left, d, 'l');
+ pooldump(b->right, d, 'r');
+}
+
+void
+poolshow(void)
+{
+ int i;
+
+ for(i = 0; i < table.n; i++) {
+ print("Arena: %s root=%.8lux\n", table.pool[i].name, table.pool[i].root);
+ pooldump(table.pool[i].root, 0, 'R');
+ }
+}
+
+void
+poolsetcompact(Pool *p, void (*move)(void*, void*))
+{
+ p->move = move;
+}
+
+int
+poolcompact(Pool *pool)
+{
+ Bhdr *base, *limit, *ptr, *end, *next;
+ int compacted, nb;
+
+ if(pool->move == nil || pool->lastfree == pool->nfree)
+ return 0;
+
+ pool->lastfree = pool->nfree;
+
+ base = pool->chain;
+ ptr = B2NB(base); /* First Block in arena has clink */
+ limit = B2LIMIT(base);
+ compacted = 0;
+
+ pool->root = nil;
+ end = ptr;
+ while(base != nil) {
+ next = B2NB(ptr);
+ if(ptr->magic == MAGIC_A || ptr->magic == MAGIC_I) {
+ if(ptr != end) {
+ memmove(end, ptr, ptr->size);
+ pool->move(B2D(ptr), B2D(end));
+ compacted = 1;
+ }
+ end = B2NB(end);
+ }
+ if(next >= limit) {
+ nb = (uchar*)limit - (uchar*)end;
+ if(nb > 0){
+ if(nb < pool->quanta+1)
+ panic("poolcompact: leftover too small\n");
+ end->size = nb;
+ B2T(end)->hdr = end;
+ pooladd(pool, end);
+ }
+ base = base->clink;
+ if(base == nil)
+ break;
+ ptr = B2NB(base);
+ end = ptr; /* could do better by copying between chains */
+ limit = B2LIMIT(base);
+ } else
+ ptr = next;
+ }
+
+ return compacted;
+}
+
+void
+poolsize(Pool *p, int max, int contig)
+{
+ void *x;
+
+ p->maxsize = max;
+ if(max == 0)
+ p->ressize = max;
+ else if(max < RESERVED)
+ p->ressize = max;
+ else
+ p->ressize = max-RESERVED;
+ if (contig && max > 0) {
+ p->chunk = max-1024;
+ x = poolalloc(p, p->chunk);
+ if(x == nil)
+ panic("poolsize: don't have %d bytes\n", p->chunk);
+ poolfree(p, x);
+ p->hw = 0;
+ }
+}
+
+static void
+_poolfault(void *v, char *msg, ulong c)
+{
+ setpanic();
+ auditmemloc(msg, v);
+ panic("%s %lux (from %lux/%lux)", msg, v, getcallerpc(&v), c);
+}
+
+static void
+dumpvl(char *msg, ulong *v, int n)
+{
+ int i, l;
+
+ l = print("%s at %p: ", msg, v);
+ for(i = 0; i < n; i++) {
+ if(l >= 60) {
+ print("\n");
+ l = print(" %p: ", v);
+ }
+ l += print(" %lux", *v++);
+ }
+ print("\n");
+ USED(l);
+}
+
+static void
+corrupted(char *str, char *msg, Pool *p, Bhdr *b, void *v)
+{
+ print("%s(%p): pool %s CORRUPT: %s at %p'%lud(magic=%lux)\n",
+ str, v, p->name, msg, b, b->size, b->magic);
+ dumpvl("bad Bhdr", (ulong *)((ulong)b & ~3)-4, 10);
+}
+
+static void
+_auditmemloc(char *str, void *v)
+{
+ Pool *p;
+ Bhdr *bc, *ec, *b, *nb, *fb = nil;
+ char *fmsg, *msg;
+ ulong fsz;
+
+ SET(fsz, fmsg);
+ for (p = &table.pool[0]; p < &table.pool[nelem(table.pool)]; p++) {
+ ilock(&p->l);
+ for (bc = p->chain; bc != nil; bc = bc->clink) {
+ if (bc->magic != MAGIC_E) {
+ iunlock(&p->l);
+ corrupted(str, "chain hdr!=MAGIC_E", p, bc, v);
+ goto nextpool;
+ }
+ ec = B2LIMIT(bc);
+ if (((Bhdr*)v >= bc) && ((Bhdr*)v < ec))
+ goto found;
+ }
+ iunlock(&p->l);
+nextpool: ;
+ }
+ print("%s: %lux not in pools\n", str, v);
+ return;
+
+found:
+ for (b = bc; b < ec; b = nb) {
+ switch(b->magic) {
+ case MAGIC_F:
+ msg = "free blk";
+ break;
+ case MAGIC_I:
+ msg = "immutable block";
+ break;
+ case MAGIC_A:
+ msg = "block";
+ break;
+ default:
+ if (b == bc && b->magic == MAGIC_E) {
+ msg = "pool hdr";
+ break;
+ }
+ iunlock(&p->l);
+ corrupted(str, "bad magic", p, b, v);
+ goto badchunk;
+ }
+ if (b->size <= 0 || (b->size & p->quanta)) {
+ iunlock(&p->l);
+ corrupted(str, "bad size", p, b, v);
+ goto badchunk;
+ }
+ if (fb != nil)
+ break;
+ nb = B2NB(b);
+ if ((Bhdr*)v < nb) {
+ fb = b;
+ fsz = b->size;
+ fmsg = msg;
+ }
+ }
+ iunlock(&p->l);
+ if (b >= ec) {
+ if (b > ec)
+ corrupted(str, "chain size mismatch", p, b, v);
+ else if (b->magic != MAGIC_E)
+ corrupted(str, "chain end!=MAGIC_E", p, b, v);
+ }
+badchunk:
+ if (fb != nil) {
+ print("%s: %lux in %s:", str, v, p->name);
+ if (fb == v)
+ print(" is %s '%lux\n", fmsg, fsz);
+ else
+ print(" in %s at %lux'%lux\n", fmsg, fb, fsz);
+ dumpvl("area", (ulong *)((ulong)v & ~3)-4, 20);
+ }
+}
+
+char *
+poolaudit(char*(*audit)(int, Bhdr *))
+{
+ Pool *p;
+ Bhdr *bc, *ec, *b;
+ char *r = nil;
+
+ for (p = &table.pool[0]; p < &table.pool[nelem(table.pool)]; p++) {
+ ilock(&p->l);
+ for (bc = p->chain; bc != nil; bc = bc->clink) {
+ if (bc->magic != MAGIC_E) {
+ iunlock(&p->l);
+ return "bad chain hdr";
+ }
+ ec = B2LIMIT(bc);
+ for (b = bc; b < ec; b = B2NB(b)) {
+ if (b->size <= 0 || (b->size & p->quanta))
+ r = "bad size in bhdr";
+ else
+ switch(b->magic) {
+ case MAGIC_E:
+ if (b != bc) {
+ r = "unexpected MAGIC_E";
+ break;
+ }
+ case MAGIC_F:
+ case MAGIC_A:
+ case MAGIC_I:
+ r = audit(p->pnum, b);
+ break;
+ default:
+ r = "bad magic";
+ }
+ if (r != nil) {
+ iunlock(&p->l);
+ return r;
+ }
+ }
+ if (b != ec || b->magic != MAGIC_E) {
+ iunlock(&p->l);
+ return "bad chain ending";
+ }
+ }
+ iunlock(&p->l);
+ }
+ return r;
+}
+
+void
+poolinit(void)
+{
+ debugkey('m', "memory pools", poolsummary, 0);
+}
diff --git a/os/port/allocb.c b/os/port/allocb.c
new file mode 100644
index 00000000..6ab67d60
--- /dev/null
+++ b/os/port/allocb.c
@@ -0,0 +1,159 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+enum
+{
+ Hdrspc = 64, /* leave room for high-level headers */
+ Bdead = 0x51494F42, /* "QIOB" */
+};
+
+struct
+{
+ Lock;
+ ulong bytes;
+} ialloc;
+
+/*
+ * allocate blocks (round data base address to 64 bit boundary).
+ * if mallocz gives us more than we asked for, leave room at the front
+ * for header.
+ */
+Block*
+_allocb(int size)
+{
+ Block *b;
+ ulong addr;
+ int n;
+
+ b = mallocz(sizeof(Block)+size+Hdrspc+(BY2V-1), 0);
+ if(b == nil)
+ return nil;
+
+ b->next = nil;
+ b->list = nil;
+ b->free = nil;
+ b->flag = 0;
+
+ addr = (ulong)b;
+ addr = ROUND(addr + sizeof(Block), BY2V);
+ b->base = (uchar*)addr;
+ b->lim = ((uchar*)b) + msize(b);
+ b->rp = b->base;
+ n = b->lim - b->base - size;
+ b->rp += n & ~(BY2V-1);
+ b->wp = b->rp;
+
+ return b;
+}
+
+Block*
+allocb(int size)
+{
+ Block *b;
+
+ if(0 && up == nil)
+ panic("allocb outside process: %8.8lux", getcallerpc(&size));
+ b = _allocb(size);
+ if(b == 0)
+ exhausted("Blocks");
+ setmalloctag(b, getcallerpc(&size));
+ return b;
+}
+
+/*
+ * interrupt time allocation
+ */
+Block*
+iallocb(int size)
+{
+ Block *b;
+
+ if(ialloc.bytes > conf.ialloc){
+ //print("iallocb: limited %lud/%lud\n", ialloc.bytes, conf.ialloc);
+ return nil;
+ }
+
+ b = _allocb(size);
+ if(b == nil){
+ //print("iallocb: no memory %lud/%lud\n", ialloc.bytes, conf.ialloc);
+ return nil;
+ }
+ setmalloctag(b, getcallerpc(&size));
+ b->flag = BINTR;
+
+ ilock(&ialloc);
+ ialloc.bytes += b->lim - b->base;
+ iunlock(&ialloc);
+
+ return b;
+}
+
+void
+freeb(Block *b)
+{
+ void *dead = (void*)Bdead;
+
+ if(b == nil)
+ return;
+
+ /*
+ * drivers which perform non cache coherent DMA manage their own buffer
+ * pool of uncached buffers and provide their own free routine.
+ */
+ if(b->free) {
+ b->free(b);
+ return;
+ }
+ if(b->flag & BINTR) {
+ ilock(&ialloc);
+ ialloc.bytes -= b->lim - b->base;
+ iunlock(&ialloc);
+ }
+
+ /* poison the block in case someone is still holding onto it */
+ b->next = dead;
+ b->rp = dead;
+ b->wp = dead;
+ b->lim = dead;
+ b->base = dead;
+
+ free(b);
+}
+
+void
+checkb(Block *b, char *msg)
+{
+ void *dead = (void*)Bdead;
+
+ if(b == dead)
+ panic("checkb b %s %lux", msg, b);
+ if(b->base == dead || b->lim == dead || b->next == dead
+ || b->rp == dead || b->wp == dead){
+ print("checkb: base 0x%8.8luX lim 0x%8.8luX next 0x%8.8luX\n",
+ b->base, b->lim, b->next);
+ print("checkb: rp 0x%8.8luX wp 0x%8.8luX\n", b->rp, b->wp);
+ panic("checkb dead: %s\n", msg);
+ }
+
+ if(b->base > b->lim)
+ panic("checkb 0 %s %lux %lux", msg, b->base, b->lim);
+ if(b->rp < b->base)
+ panic("checkb 1 %s %lux %lux", msg, b->base, b->rp);
+ if(b->wp < b->base)
+ panic("checkb 2 %s %lux %lux", msg, b->base, b->wp);
+ if(b->rp > b->lim)
+ panic("checkb 3 %s %lux %lux", msg, b->rp, b->lim);
+ if(b->wp > b->lim)
+ panic("checkb 4 %s %lux %lux", msg, b->wp, b->lim);
+
+}
+
+void
+iallocsummary(void)
+{
+ print("ialloc %lud/%lud\n", ialloc.bytes, conf.ialloc);
+}
diff --git a/os/port/chan.c b/os/port/chan.c
new file mode 100644
index 00000000..a5d213aa
--- /dev/null
+++ b/os/port/chan.c
@@ -0,0 +1,1431 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+char*
+channame(Chan *c) /* DEBUGGING */
+{
+ if(c == nil)
+ return "<nil chan>";
+ if(c->name == nil)
+ return "<nil name>";
+ if(c->name->s == nil)
+ return "<nil name.s>";
+ return c->name->s;
+}
+
+enum
+{
+ CNAMESLOP = 20
+};
+
+struct
+{
+ Lock;
+ int fid;
+ Chan *free;
+ Chan *list;
+}chanalloc;
+
+typedef struct Elemlist Elemlist;
+
+struct Elemlist
+{
+ char *name; /* copy of name, so '/' can be overwritten */
+ int nelems;
+ char **elems;
+ int *off;
+ int mustbedir;
+};
+
+#define SEP(c) ((c) == 0 || (c) == '/')
+void cleancname(Cname*);
+
+int
+isdotdot(char *p)
+{
+ return p[0]=='.' && p[1]=='.' && p[2]=='\0';
+}
+
+int
+incref(Ref *r)
+{
+ int x;
+
+ lock(&r->l);
+ x = ++r->ref;
+ unlock(&r->l);
+ return x;
+}
+
+int
+decref(Ref *r)
+{
+ int x;
+
+ lock(&r->l);
+ x = --r->ref;
+ unlock(&r->l);
+ if(x < 0)
+ panic("decref, pc=0x%lux", getcallerpc(&r));
+
+ return x;
+}
+
+/*
+ * Rather than strncpy, which zeros the rest of the buffer, kstrcpy
+ * truncates if necessary, always zero terminates, does not zero fill,
+ * and puts ... at the end of the string if it's too long. Usually used to
+ * save a string in up->genbuf;
+ */
+void
+kstrcpy(char *s, char *t, int ns)
+{
+ int nt;
+
+ nt = strlen(t);
+ if(nt+1 <= ns){
+ memmove(s, t, nt+1);
+ return;
+ }
+ /* too long */
+ if(ns < 4){
+ /* but very short! */
+ strncpy(s, t, ns);
+ return;
+ }
+ /* truncate with ... at character boundary (very rare case) */
+ memmove(s, t, ns-4);
+ ns -= 4;
+ s[ns] = '\0';
+ /* look for first byte of UTF-8 sequence by skipping continuation bytes */
+ while(ns>0 && (s[--ns]&0xC0)==0x80)
+ ;
+ strcpy(s+ns, "...");
+}
+
+int
+emptystr(char *s)
+{
+ if(s == nil)
+ return 1;
+ if(s[0] == '\0')
+ return 1;
+ return 0;
+}
+
+/*
+ * Atomically replace *p with copy of s
+ */
+void
+kstrdup(char **p, char *s)
+{
+ int n;
+ char *t, *prev;
+
+ n = strlen(s)+1;
+ /* if it's a user, we can wait for memory; if not, something's very wrong */
+ if(up){
+ t = smalloc(n);
+ setmalloctag(t, getcallerpc(&p));
+ }else{
+ t = malloc(n);
+ if(t == nil)
+ panic("kstrdup: no memory");
+ }
+ memmove(t, s, n);
+ prev = *p;
+ *p = t;
+ free(prev);
+}
+
+void
+chandevreset(void)
+{
+ int i;
+
+ for(i=0; devtab[i] != nil; i++)
+ devtab[i]->reset();
+}
+
+void
+chandevinit(void)
+{
+ int i;
+
+ for(i=0; devtab[i] != nil; i++)
+ devtab[i]->init();
+}
+
+void
+chandevshutdown(void)
+{
+ int i;
+
+ /* shutdown in reverse order */
+ for(i=0; devtab[i] != nil; i++)
+ ;
+ for(i--; i >= 0; i--)
+ devtab[i]->shutdown();
+}
+
+Chan*
+newchan(void)
+{
+ Chan *c;
+
+ lock(&chanalloc);
+ c = chanalloc.free;
+ if(c != 0)
+ chanalloc.free = c->next;
+ unlock(&chanalloc);
+
+ if(c == nil) {
+ c = smalloc(sizeof(Chan));
+ lock(&chanalloc);
+ c->fid = ++chanalloc.fid;
+ c->link = chanalloc.list;
+ chanalloc.list = c;
+ unlock(&chanalloc);
+ }
+
+ /* if you get an error before associating with a dev,
+ close calls rootclose, a nop */
+ c->type = 0;
+ c->flag = 0;
+ c->ref = 1;
+ c->dev = 0;
+ c->offset = 0;
+ c->iounit = 0;
+ c->umh = 0;
+ c->uri = 0;
+ c->dri = 0;
+ c->aux = 0;
+ c->mchan = 0;
+ c->mcp = 0;
+ c->mux = 0;
+ c->mqid.path = 0;
+ c->mqid.vers = 0;
+ c->mqid.type = 0;
+ c->name = 0;
+ return c;
+}
+
+static Ref ncname;
+
+Cname*
+newcname(char *s)
+{
+ Cname *n;
+ int i;
+
+ n = smalloc(sizeof(Cname));
+ i = strlen(s);
+ n->len = i;
+ n->alen = i+CNAMESLOP;
+ n->s = smalloc(n->alen);
+ memmove(n->s, s, i+1);
+ n->ref = 1;
+ incref(&ncname);
+ return n;
+}
+
+void
+cnameclose(Cname *n)
+{
+ if(n == nil)
+ return;
+ if(decref(n))
+ return;
+ decref(&ncname);
+ free(n->s);
+ free(n);
+}
+
+Cname*
+addelem(Cname *n, char *s)
+{
+ int i, a;
+ char *t;
+ Cname *new;
+
+ if(s[0]=='.' && s[1]=='\0')
+ return n;
+
+ if(n->ref > 1){
+ /* copy on write */
+ new = newcname(n->s);
+ cnameclose(n);
+ n = new;
+ }
+
+ i = strlen(s);
+ if(n->len+1+i+1 > n->alen){
+ a = n->len+1+i+1 + CNAMESLOP;
+ t = smalloc(a);
+ memmove(t, n->s, n->len+1);
+ free(n->s);
+ n->s = t;
+ n->alen = a;
+ }
+ if(n->len>0 && n->s[n->len-1]!='/' && s[0]!='/') /* don't insert extra slash if one is present */
+ n->s[n->len++] = '/';
+ memmove(n->s+n->len, s, i+1);
+ n->len += i;
+ if(isdotdot(s))
+ cleancname(n);
+ return n;
+}
+
+void
+chanfree(Chan *c)
+{
+ c->flag = CFREE;
+
+ if(c->umh != nil){
+ putmhead(c->umh);
+ c->umh = nil;
+ }
+ if(c->umc != nil){
+ cclose(c->umc);
+ c->umc = nil;
+ }
+ if(c->mux != nil){
+ muxclose(c->mux);
+ c->mux = nil;
+ }
+ if(c->mchan != nil){
+ cclose(c->mchan);
+ c->mchan = nil;
+ }
+
+ cnameclose(c->name);
+
+ lock(&chanalloc);
+ c->next = chanalloc.free;
+ chanalloc.free = c;
+ unlock(&chanalloc);
+}
+
+void
+cclose(Chan *c)
+{
+ if(c == 0)
+ return;
+
+ if(c->flag&CFREE)
+ panic("cclose %lux", getcallerpc(&c));
+
+ if(decref(c))
+ return;
+
+ if(!waserror()){
+ devtab[c->type]->close(c);
+ poperror();
+ }
+ chanfree(c);
+}
+
+/*
+ * Make sure we have the only copy of c. (Copy on write.)
+ */
+Chan*
+cunique(Chan *c)
+{
+ Chan *nc;
+
+ if(c->ref != 1) {
+ nc = cclone(c);
+ cclose(c);
+ c = nc;
+ }
+
+ return c;
+}
+
+int
+eqqid(Qid a, Qid b)
+{
+ return a.path==b.path && a.vers==b.vers;
+}
+
+int
+eqchan(Chan *a, Chan *b, int pathonly)
+{
+ if(a->qid.path != b->qid.path)
+ return 0;
+ if(!pathonly && a->qid.vers!=b->qid.vers)
+ return 0;
+ if(a->type != b->type)
+ return 0;
+ if(a->dev != b->dev)
+ return 0;
+ return 1;
+}
+
+int
+eqchantdqid(Chan *a, int type, int dev, Qid qid, int pathonly)
+{
+ if(a->qid.path != qid.path)
+ return 0;
+ if(!pathonly && a->qid.vers!=qid.vers)
+ return 0;
+ if(a->type != type)
+ return 0;
+ if(a->dev != dev)
+ return 0;
+ return 1;
+}
+
+Mhead*
+newmhead(Chan *from)
+{
+ Mhead *mh;
+
+ mh = smalloc(sizeof(Mhead));
+ mh->ref = 1;
+ mh->from = from;
+ incref(from);
+
+/*
+ n = from->name->len;
+ if(n >= sizeof(mh->fromname))
+ n = sizeof(mh->fromname)-1;
+ memmove(mh->fromname, from->name->s, n);
+ mh->fromname[n] = 0;
+*/
+ return mh;
+}
+
+int
+cmount(Chan *new, Chan *old, int flag, char *spec)
+{
+ Pgrp *pg;
+ int order, flg;
+ Mhead *m, **l, *mh;
+ Mount *nm, *f, *um, **h;
+
+ if(QTDIR & (old->qid.type^new->qid.type))
+ error(Emount);
+
+if(old->umh)
+ print("cmount old extra umh\n");
+
+ order = flag&MORDER;
+
+ if((old->qid.type&QTDIR)==0 && order != MREPL)
+ error(Emount);
+
+ mh = new->umh;
+
+ /*
+ * Not allowed to bind when the old directory
+ * is itself a union. (Maybe it should be allowed, but I don't see
+ * what the semantics would be.)
+ *
+ * We need to check mh->mount->next to tell unions apart from
+ * simple mount points, so that things like
+ * mount -c fd /root
+ * bind -c /root /
+ * work. The check of mount->mflag catches things like
+ * mount fd /root
+ * bind -c /root /
+ *
+ * This is far more complicated than it should be, but I don't
+ * see an easier way at the moment. -rsc
+ */
+ if((flag&MCREATE) && mh && mh->mount
+ && (mh->mount->next || !(mh->mount->mflag&MCREATE)))
+ error(Emount);
+
+ pg = up->env->pgrp;
+ wlock(&pg->ns);
+
+ l = &MOUNTH(pg, old->qid);
+ for(m = *l; m; m = m->hash) {
+ if(eqchan(m->from, old, 1))
+ break;
+ l = &m->hash;
+ }
+
+ if(m == nil) {
+ /*
+ * nothing mounted here yet. create a mount
+ * head and add to the hash table.
+ */
+ m = newmhead(old);
+ *l = m;
+
+ /*
+ * if this is a union mount, add the old
+ * node to the mount chain.
+ */
+ if(order != MREPL)
+ m->mount = newmount(m, old, 0, 0);
+ }
+ wlock(&m->lock);
+ if(waserror()){
+ wunlock(&m->lock);
+ nexterror();
+ }
+ wunlock(&pg->ns);
+
+ nm = newmount(m, new, flag, spec);
+ if(mh != nil && mh->mount != nil) {
+ /*
+ * copy a union when binding it onto a directory
+ */
+ flg = order;
+ if(order == MREPL)
+ flg = MAFTER;
+ h = &nm->next;
+ um = mh->mount;
+ for(um = um->next; um; um = um->next) {
+ f = newmount(m, um->to, flg, um->spec);
+ *h = f;
+ h = &f->next;
+ }
+ }
+
+ if(m->mount && order == MREPL) {
+ mountfree(m->mount);
+ m->mount = 0;
+ }
+
+ if(flag & MCREATE)
+ nm->mflag |= MCREATE;
+
+ if(m->mount && order == MAFTER) {
+ for(f = m->mount; f->next; f = f->next)
+ ;
+ f->next = nm;
+ }
+ else {
+ for(f = nm; f->next; f = f->next)
+ ;
+ f->next = m->mount;
+ m->mount = nm;
+ }
+
+ wunlock(&m->lock);
+ poperror();
+ return nm->mountid;
+}
+
+void
+cunmount(Chan *mnt, Chan *mounted)
+{
+ Pgrp *pg;
+ Mhead *m, **l;
+ Mount *f, **p;
+
+ if(mnt->umh) /* should not happen */
+ print("cunmount newp extra umh %p has %p\n", mnt, mnt->umh);
+
+ /*
+ * It _can_ happen that mounted->umh is non-nil,
+ * because mounted is the result of namec(Aopen)
+ * (see sysfile.c:/^sysunmount).
+ * If we open a union directory, it will have a umh.
+ * Although surprising, this is okay, since the
+ * cclose will take care of freeing the umh.
+ */
+
+ pg = up->env->pgrp;
+ wlock(&pg->ns);
+
+ l = &MOUNTH(pg, mnt->qid);
+ for(m = *l; m; m = m->hash) {
+ if(eqchan(m->from, mnt, 1))
+ break;
+ l = &m->hash;
+ }
+
+ if(m == 0) {
+ wunlock(&pg->ns);
+ error(Eunmount);
+ }
+
+ wlock(&m->lock);
+ if(mounted == 0) {
+ *l = m->hash;
+ wunlock(&pg->ns);
+ mountfree(m->mount);
+ m->mount = nil;
+ cclose(m->from);
+ wunlock(&m->lock);
+ putmhead(m);
+ return;
+ }
+
+ p = &m->mount;
+ for(f = *p; f; f = f->next) {
+ /* BUG: Needs to be 2 pass */
+ if(eqchan(f->to, mounted, 1) ||
+ (f->to->mchan && eqchan(f->to->mchan, mounted, 1))) {
+ *p = f->next;
+ f->next = 0;
+ mountfree(f);
+ if(m->mount == nil) {
+ *l = m->hash;
+ cclose(m->from);
+ wunlock(&m->lock);
+ wunlock(&pg->ns);
+ putmhead(m);
+ return;
+ }
+ wunlock(&m->lock);
+ wunlock(&pg->ns);
+ return;
+ }
+ p = &f->next;
+ }
+ wunlock(&m->lock);
+ wunlock(&pg->ns);
+ error(Eunion);
+}
+
+Chan*
+cclone(Chan *c)
+{
+ Chan *nc;
+ Walkqid *wq;
+
+ wq = devtab[c->type]->walk(c, nil, nil, 0);
+ if(wq == nil)
+ error("clone failed");
+ nc = wq->clone;
+ free(wq);
+ nc->name = c->name;
+ if(c->name)
+ incref(c->name);
+ return nc;
+}
+
+int
+findmount(Chan **cp, Mhead **mp, int type, int dev, Qid qid)
+{
+ Pgrp *pg;
+ Mhead *m;
+
+ pg = up->env->pgrp;
+ rlock(&pg->ns);
+ for(m = MOUNTH(pg, qid); m; m = m->hash){
+ rlock(&m->lock);
+if(m->from == nil){
+ print("m %p m->from 0\n", m);
+ runlock(&m->lock);
+ continue;
+}
+ if(eqchantdqid(m->from, type, dev, qid, 1)) {
+ runlock(&pg->ns);
+ if(mp != nil){
+ incref(m);
+ if(*mp != nil)
+ putmhead(*mp);
+ *mp = m;
+ }
+ if(*cp != nil)
+ cclose(*cp);
+ incref(m->mount->to);
+ *cp = m->mount->to;
+ runlock(&m->lock);
+ return 1;
+ }
+ runlock(&m->lock);
+ }
+
+ runlock(&pg->ns);
+ return 0;
+}
+
+int
+domount(Chan **cp, Mhead **mp)
+{
+ return findmount(cp, mp, (*cp)->type, (*cp)->dev, (*cp)->qid);
+}
+
+Chan*
+undomount(Chan *c, Cname *name)
+{
+ Chan *nc;
+ Pgrp *pg;
+ Mount *t;
+ Mhead **h, **he, *f;
+
+ pg = up->env->pgrp;
+ rlock(&pg->ns);
+ if(waserror()) {
+ runlock(&pg->ns);
+ nexterror();
+ }
+
+ he = &pg->mnthash[MNTHASH];
+ for(h = pg->mnthash; h < he; h++) {
+ for(f = *h; f; f = f->hash) {
+ if(strcmp(f->from->name->s, name->s) != 0)
+ continue;
+ for(t = f->mount; t; t = t->next) {
+ if(eqchan(c, t->to, 1)) {
+ /*
+ * We want to come out on the left hand side of the mount
+ * point using the element of the union that we entered on.
+ * To do this, find the element that has a from name of
+ * c->name->s.
+ */
+ if(strcmp(t->head->from->name->s, name->s) != 0)
+ continue;
+ nc = t->head->from;
+ incref(nc);
+ cclose(c);
+ c = nc;
+ break;
+ }
+ }
+ }
+ }
+ poperror();
+ runlock(&pg->ns);
+ return c;
+}
+
+/*
+ * Either walks all the way or not at all. No partial results in *cp.
+ * *nerror is the number of names to display in an error message.
+ */
+static char Edoesnotexist[] = "does not exist";
+int
+walk(Chan **cp, char **names, int nnames, int nomount, int *nerror)
+{
+ int dev, dotdot, i, n, nhave, ntry, type;
+ Chan *c, *nc;
+ Cname *cname;
+ Mount *f;
+ Mhead *mh, *nmh;
+ Walkqid *wq;
+
+ c = *cp;
+ incref(c);
+ cname = c->name;
+ incref(cname);
+ mh = nil;
+
+ /*
+ * While we haven't gotten all the way down the path:
+ * 1. step through a mount point, if any
+ * 2. send a walk request for initial dotdot or initial prefix without dotdot
+ * 3. move to the first mountpoint along the way.
+ * 4. repeat.
+ *
+ * An invariant is that each time through the loop, c is on the undomount
+ * side of the mount point, and c's name is cname.
+ */
+ for(nhave=0; nhave<nnames; nhave+=n){
+ if((c->qid.type&QTDIR)==0){
+ if(nerror)
+ *nerror = nhave;
+ cnameclose(cname);
+ cclose(c);
+ strcpy(up->env->errstr, Enotdir);
+ if(mh != nil)
+ putmhead(mh);
+ return -1;
+ }
+ ntry = nnames - nhave;
+ if(ntry > MAXWELEM)
+ ntry = MAXWELEM;
+ dotdot = 0;
+ for(i=0; i<ntry; i++){
+ if(isdotdot(names[nhave+i])){
+ if(i==0) {
+ dotdot = 1;
+ ntry = 1;
+ } else
+ ntry = i;
+ break;
+ }
+ }
+
+ if(!dotdot && !nomount)
+ domount(&c, &mh);
+
+ type = c->type;
+ dev = c->dev;
+
+ if((wq = devtab[type]->walk(c, nil, names+nhave, ntry)) == nil){
+ /* try a union mount, if any */
+ if(mh && !nomount){
+ /*
+ * mh->mount == c, so start at mh->mount->next
+ */
+ rlock(&mh->lock);
+ for(f = mh->mount->next; f; f = f->next)
+ if((wq = devtab[f->to->type]->walk(f->to, nil, names+nhave, ntry)) != nil)
+ break;
+ runlock(&mh->lock);
+ if(f != nil){
+ type = f->to->type;
+ dev = f->to->dev;
+ }
+ }
+ if(wq == nil){
+ cclose(c);
+ cnameclose(cname);
+ if(nerror)
+ *nerror = nhave+1;
+ if(mh != nil)
+ putmhead(mh);
+ return -1;
+ }
+ }
+
+ nmh = nil;
+ if(dotdot) {
+ assert(wq->nqid == 1);
+ assert(wq->clone != nil);
+
+ cname = addelem(cname, "..");
+ nc = undomount(wq->clone, cname);
+ n = 1;
+ } else {
+ nc = nil;
+ if(!nomount)
+ for(i=0; i<wq->nqid && i<ntry-1; i++)
+ if(findmount(&nc, &nmh, type, dev, wq->qid[i]))
+ break;
+ if(nc == nil){ /* no mount points along path */
+ if(wq->clone == nil){
+ cclose(c);
+ cnameclose(cname);
+ if(wq->nqid==0 || (wq->qid[wq->nqid-1].type&QTDIR)){
+ if(nerror)
+ *nerror = nhave+wq->nqid+1;
+ strcpy(up->env->errstr, Edoesnotexist);
+ }else{
+ if(nerror)
+ *nerror = nhave+wq->nqid;
+ strcpy(up->env->errstr, Enotdir);
+ }
+ free(wq);
+ if(mh != nil)
+ putmhead(mh);
+ return -1;
+ }
+ n = wq->nqid;
+ nc = wq->clone;
+ }else{ /* stopped early, at a mount point */
+ if(wq->clone != nil){
+ cclose(wq->clone);
+ wq->clone = nil;
+ }
+ n = i+1;
+ }
+ for(i=0; i<n; i++)
+ cname = addelem(cname, names[nhave+i]);
+ }
+ cclose(c);
+ c = nc;
+ putmhead(mh);
+ mh = nmh;
+ free(wq);
+ }
+
+ putmhead(mh);
+
+ c = cunique(c);
+
+ if(c->umh != nil){ //BUG
+ print("walk umh\n");
+ putmhead(c->umh);
+ c->umh = nil;
+ }
+
+ cnameclose(c->name);
+ c->name = cname;
+
+ cclose(*cp);
+ *cp = c;
+ if(nerror)
+ *nerror = 0;
+ return 0;
+}
+
+/*
+ * c is a mounted non-creatable directory. find a creatable one.
+ */
+Chan*
+createdir(Chan *c, Mhead *m)
+{
+ Chan *nc;
+ Mount *f;
+
+ rlock(&m->lock);
+ if(waserror()) {
+ runlock(&m->lock);
+ nexterror();
+ }
+ for(f = m->mount; f; f = f->next) {
+ if(f->mflag&MCREATE) {
+ nc = cclone(f->to);
+ runlock(&m->lock);
+ poperror();
+ cclose(c);
+ return nc;
+ }
+ }
+ error(Enocreate);
+ return 0;
+}
+
+void
+saveregisters(void)
+{
+}
+
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ */
+void
+cleancname(Cname *n)
+{
+ char *p;
+
+ if(n->s[0] == '#'){
+ p = strchr(n->s, '/');
+ if(p == nil)
+ return;
+ cleanname(p);
+
+ /*
+ * The correct name is #i rather than #i/,
+ * but the correct name of #/ is #/.
+ */
+ if(strcmp(p, "/")==0 && n->s[1] != '/')
+ *p = '\0';
+ }else
+ cleanname(n->s);
+ n->len = strlen(n->s);
+}
+
+static void
+growparse(Elemlist *e)
+{
+ char **new;
+ int *inew;
+ enum { Delta = 8 };
+
+ if(e->nelems % Delta == 0){
+ new = smalloc((e->nelems+Delta) * sizeof(char*));
+ memmove(new, e->elems, e->nelems*sizeof(char*));
+ free(e->elems);
+ e->elems = new;
+ inew = smalloc((e->nelems+Delta+1) * sizeof(int));
+ memmove(inew, e->off, e->nelems*sizeof(int));
+ free(e->off);
+ e->off = inew;
+ }
+}
+
+/*
+ * The name is known to be valid.
+ * Copy the name so slashes can be overwritten.
+ * An empty string will set nelem=0.
+ * A path ending in / or /. or /.//./ etc. will have
+ * e.mustbedir = 1, so that we correctly
+ * reject, e.g., "/adm/users/." when /adm/users is a file
+ * rather than a directory.
+ */
+static void
+parsename(char *name, Elemlist *e)
+{
+ char *slash;
+
+ kstrdup(&e->name, name);
+ name = e->name;
+ e->nelems = 0;
+ e->elems = nil;
+ e->off = smalloc(sizeof(int));
+ e->off[0] = skipslash(name) - name;
+ for(;;){
+ name = skipslash(name);
+ if(*name=='\0'){
+ e->mustbedir = 1;
+ break;
+ }
+ growparse(e);
+
+ e->elems[e->nelems++] = name;
+ slash = utfrune(name, '/');
+ if(slash == nil){
+ e->off[e->nelems] = name+strlen(name) - e->name;
+ e->mustbedir = 0;
+ break;
+ }
+ e->off[e->nelems] = slash - e->name;
+ *slash++ = '\0';
+ name = slash;
+ }
+}
+
+void*
+memrchr(void *va, int c, long n)
+{
+ uchar *a, *e;
+
+ a = va;
+ for(e=a+n-1; e>a; e--)
+ if(*e == c)
+ return e;
+ return nil;
+}
+
+/*
+ * Turn a name into a channel.
+ * &name[0] is known to be a valid address. It may be a kernel address.
+ *
+ * Opening with amode Aopen, Acreate, or Aremove guarantees
+ * that the result will be the only reference to that particular fid.
+ * This is necessary since we might pass the result to
+ * devtab[]->remove().
+ *
+ * Opening Atodir, Amount, or Aaccess does not guarantee this.
+ *
+ * Opening Aaccess can, under certain conditions, return a
+ * correct Chan* but with an incorrect Cname attached.
+ * Since the functions that open Aaccess (sysstat, syswstat, sys_stat)
+ * do not use the Cname*, this avoids an unnecessary clone.
+ */
+Chan*
+namec(char *aname, int amode, int omode, ulong perm)
+{
+ int n, prefix, len, t, nomount, npath;
+ Chan *c, *cnew;
+ Cname *cname;
+ Elemlist e;
+ Rune r;
+ Mhead *m;
+ char *createerr, tmperrbuf[ERRMAX];
+ char *name;
+
+ name = aname;
+ if(name[0] == '\0')
+ error("empty file name");
+ validname(name, 1);
+
+ /*
+ * Find the starting off point (the current slash, the root of
+ * a device tree, or the current dot) as well as the name to
+ * evaluate starting there.
+ */
+ nomount = 0;
+ switch(name[0]){
+ case '/':
+ c = up->env->pgrp->slash;
+ incref(c);
+ break;
+
+ case '#':
+ nomount = 1;
+ up->genbuf[0] = '\0';
+ n = 0;
+ while(*name!='\0' && (*name != '/' || n < 2)){
+ if(n >= sizeof(up->genbuf)-1)
+ error(Efilename);
+ up->genbuf[n++] = *name++;
+ }
+ up->genbuf[n] = '\0';
+ n = chartorune(&r, up->genbuf+1)+1;
+ if(r == 'M')
+ error(Enoattach);
+ /*
+ * the nodevs exceptions are
+ * | it only gives access to pipes you create
+ * e this process's environment
+ * s private file2chan creation space
+ * D private secure sockets name space
+ * a private TLS name space
+ */
+ if(up->env->pgrp->nodevs &&
+ (utfrune("|esDa", r) == nil || r == 's' && up->genbuf[n]!='\0'))
+ error(Enoattach);
+ t = devno(r, 1);
+ if(t == -1)
+ error(Ebadsharp);
+ c = devtab[t]->attach(up->genbuf+n);
+ break;
+
+ default:
+ c = up->env->pgrp->dot;
+ incref(c);
+ break;
+ }
+ prefix = name - aname;
+
+ e.name = nil;
+ e.elems = nil;
+ e.off = nil;
+ e.nelems = 0;
+ if(waserror()){
+ cclose(c);
+ free(e.name);
+ free(e.elems);
+ free(e.off);
+//dumpmount();
+ nexterror();
+ }
+
+ /*
+ * Build a list of elements in the path.
+ */
+ parsename(name, &e);
+
+ /*
+ * On create, ....
+ */
+ if(amode == Acreate){
+ /* perm must have DMDIR if last element is / or /. */
+ if(e.mustbedir && !(perm&DMDIR)){
+ npath = e.nelems;
+ strcpy(tmperrbuf, "create without DMDIR");
+ goto NameError;
+ }
+
+ /* don't try to walk the last path element just yet. */
+ if(e.nelems == 0)
+ error(Eexist);
+ e.nelems--;
+ }
+
+ if(walk(&c, e.elems, e.nelems, nomount, &npath) < 0){
+ if(npath < 0 || npath > e.nelems){
+ print("namec %s walk error npath=%d\n", aname, npath);
+ nexterror();
+ }
+ strcpy(tmperrbuf, up->env->errstr);
+ NameError:
+ len = prefix+e.off[npath];
+ if(len < ERRMAX/3 || (name=memrchr(aname, '/', len))==nil || name==aname)
+ snprint(up->genbuf, sizeof up->genbuf, "%.*s", len, aname);
+ else
+ snprint(up->genbuf, sizeof up->genbuf, "...%.*s", (int)(len-(name-aname)), name);
+ snprint(up->env->errstr, ERRMAX, "%#q %s", up->genbuf, tmperrbuf);
+ nexterror();
+ }
+
+ if(e.mustbedir && !(c->qid.type&QTDIR)){
+ npath = e.nelems;
+ strcpy(tmperrbuf, "not a directory");
+ goto NameError;
+ }
+
+ if(amode == Aopen && (omode&3) == OEXEC && (c->qid.type&QTDIR)){
+ npath = e.nelems;
+ error("cannot exec directory");
+ }
+
+ switch(amode){
+ case Aaccess:
+ if(!nomount)
+ domount(&c, nil);
+ break;
+
+ case Abind:
+ m = nil;
+ if(!nomount)
+ domount(&c, &m);
+ if(c->umh != nil)
+ putmhead(c->umh);
+ c->umh = m;
+ break;
+
+ case Aremove:
+ case Aopen:
+ Open:
+ /* save the name; domount might change c */
+ cname = c->name;
+ incref(cname);
+ m = nil;
+ if(!nomount)
+ domount(&c, &m);
+
+ /* our own copy to open or remove */
+ c = cunique(c);
+
+ /* now it's our copy anyway, we can put the name back */
+ cnameclose(c->name);
+ c->name = cname;
+
+ switch(amode){
+ case Aremove:
+ putmhead(m);
+ break;
+
+ case Aopen:
+ case Acreate:
+if(c->umh != nil){
+ print("cunique umh\n");
+ putmhead(c->umh);
+ c->umh = nil;
+}
+
+ /* only save the mount head if it's a multiple element union */
+ if(m && m->mount && m->mount->next)
+ c->umh = m;
+ else
+ putmhead(m);
+
+ /* save registers else error() in open has wrong value of c saved */
+ saveregisters();
+
+ if(omode == OEXEC)
+ c->flag &= ~CCACHE;
+
+ c = devtab[c->type]->open(c, omode&~OCEXEC);
+
+ if(omode & OCEXEC)
+ c->flag |= CCEXEC;
+ if(omode & ORCLOSE)
+ c->flag |= CRCLOSE;
+ break;
+ }
+ break;
+
+ case Atodir:
+ /*
+ * Directories (e.g. for cd) are left before the mount point,
+ * so one may mount on / or . and see the effect.
+ */
+ if(!(c->qid.type & QTDIR))
+ error(Enotdir);
+ break;
+
+ case Amount:
+ /*
+ * When mounting on an already mounted upon directory,
+ * one wants subsequent mounts to be attached to the
+ * original directory, not the replacement. Don't domount.
+ */
+ break;
+
+ case Acreate:
+ /*
+ * We've already walked all but the last element.
+ * If the last exists, try to open it OTRUNC.
+ * If omode&OEXCL is set, just give up.
+ */
+ e.nelems++;
+ if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) == 0){
+ if(omode&OEXCL)
+ error(Eexist);
+ omode |= OTRUNC;
+ goto Open;
+ }
+
+ /*
+ * The semantics of the create(2) system call are that if the
+ * file exists and can be written, it is to be opened with truncation.
+ * On the other hand, the create(5) message fails if the file exists.
+ * If we get two create(2) calls happening simultaneously,
+ * they might both get here and send create(5) messages, but only
+ * one of the messages will succeed. To provide the expected create(2)
+ * semantics, the call with the failed message needs to try the above
+ * walk again, opening for truncation. This correctly solves the
+ * create/create race, in the sense that any observable outcome can
+ * be explained as one happening before the other.
+ * The create/create race is quite common. For example, it happens
+ * when two rc subshells simultaneously update the same
+ * environment variable.
+ *
+ * The implementation still admits a create/create/remove race:
+ * (A) walk to file, fails
+ * (B) walk to file, fails
+ * (A) create file, succeeds, returns
+ * (B) create file, fails
+ * (A) remove file, succeeds, returns
+ * (B) walk to file, return failure.
+ *
+ * This is hardly as common as the create/create race, and is really
+ * not too much worse than what might happen if (B) got a hold of a
+ * file descriptor and then the file was removed -- either way (B) can't do
+ * anything with the result of the create call. So we don't care about this race.
+ *
+ * Applications that care about more fine-grained decision of the races
+ * can use the OEXCL flag to get at the underlying create(5) semantics;
+ * by default we provide the common case.
+ *
+ * We need to stay behind the mount point in case we
+ * need to do the first walk again (should the create fail).
+ *
+ * We also need to cross the mount point and find the directory
+ * in the union in which we should be creating.
+ *
+ * The channel staying behind is c, the one moving forward is cnew.
+ */
+ m = nil;
+ cnew = nil; /* is this assignment necessary? */
+ if(!waserror()){ /* try create */
+ if(!nomount && findmount(&cnew, &m, c->type, c->dev, c->qid))
+ cnew = createdir(cnew, m);
+ else{
+ cnew = c;
+ incref(cnew);
+ }
+
+ /*
+ * We need our own copy of the Chan because we're
+ * about to send a create, which will move it. Once we have
+ * our own copy, we can fix the name, which might be wrong
+ * if findmount gave us a new Chan.
+ */
+ cnew = cunique(cnew);
+ cnameclose(cnew->name);
+ cnew->name = c->name;
+ incref(cnew->name);
+
+ devtab[cnew->type]->create(cnew, e.elems[e.nelems-1], omode&~(OEXCL|OCEXEC), perm);
+ poperror();
+ if(omode & OCEXEC)
+ cnew->flag |= CCEXEC;
+ if(omode & ORCLOSE)
+ cnew->flag |= CRCLOSE;
+ if(m)
+ putmhead(m);
+ cclose(c);
+ c = cnew;
+ c->name = addelem(c->name, e.elems[e.nelems-1]);
+ break;
+ }else{ /* create failed */
+ cclose(cnew);
+ if(m)
+ putmhead(m);
+ if(omode & OEXCL)
+ nexterror();
+ /* save error */
+ createerr = up->env->errstr;
+ up->env->errstr = tmperrbuf;
+ /* note: we depend that walk does not error */
+ if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) < 0){
+ up->env->errstr = createerr;
+ error(createerr); /* report true error */
+ }
+ up->env->errstr = createerr;
+ omode |= OTRUNC;
+ goto Open;
+ }
+ panic("namec: not reached");
+
+ default:
+ panic("unknown namec access %d\n", amode);
+ }
+
+ poperror();
+
+ /* place final element in genbuf for e.g. exec */
+ if(e.nelems > 0)
+ kstrcpy(up->genbuf, e.elems[e.nelems-1], sizeof up->genbuf);
+ else
+ kstrcpy(up->genbuf, ".", sizeof up->genbuf);
+ free(e.name);
+ free(e.elems);
+ free(e.off);
+
+ return c;
+}
+
+/*
+ * name is valid. skip leading / and ./ as much as possible
+ */
+char*
+skipslash(char *name)
+{
+ while(name[0]=='/' || (name[0]=='.' && (name[1]==0 || name[1]=='/')))
+ name++;
+ return name;
+}
+
+char isfrog[256]={
+ /*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ /*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1,
+ ['/'] 1,
+ [0x7f] 1,
+};
+
+/*
+ * Check that the name
+ * a) is in valid memory.
+ * b) is shorter than 2^16 bytes, so it can fit in a 9P string field.
+ * c) contains no frogs.
+ * The first byte is known to be addressible by the requester, so the
+ * routine works for kernel and user memory both.
+ * The parameter slashok flags whether a slash character is an error
+ * or a valid character.
+ */
+void
+validname(char *aname, int slashok)
+{
+ char *ename, *name;
+ int c;
+ Rune r;
+
+ name = aname;
+ ename = memchr(name, 0, (1<<16));
+
+ if(ename==nil || ename-name>=(1<<16))
+ error("name too long");
+
+ while(*name){
+ /* all characters above '~' are ok */
+ c = *(uchar*)name;
+ if(c >= Runeself)
+ name += chartorune(&r, name);
+ else{
+ if(isfrog[c])
+ if(!slashok || c!='/'){
+ snprint(up->genbuf, sizeof(up->genbuf), "%s: %q", Ebadchar, aname);
+ error(up->genbuf);
+ }
+ name++;
+ }
+ }
+}
+
+void
+isdir(Chan *c)
+{
+ if(c->qid.type & QTDIR)
+ return;
+ error(Enotdir);
+}
+
+/*
+ * This is necessary because there are many
+ * pointers to the top of a given mount list:
+ *
+ * - the mhead in the namespace hash table
+ * - the mhead in chans returned from findmount:
+ * used in namec and then by unionread.
+ * - the mhead in chans returned from createdir:
+ * used in the open/create race protect, which is gone.
+ *
+ * The RWlock in the Mhead protects the mount list it contains.
+ * The mount list is deleted when we cunmount.
+ * The RWlock ensures that nothing is using the mount list at that time.
+ *
+ * It is okay to replace c->mh with whatever you want as
+ * long as you are sure you have a unique reference to it.
+ *
+ * This comment might belong somewhere else.
+ */
+void
+putmhead(Mhead *m)
+{
+ if(m && decref(m) == 0){
+ m->mount = (Mount*)0xCafeBeef;
+ free(m);
+ }
+}
diff --git a/os/port/cis.c b/os/port/cis.c
new file mode 100644
index 00000000..368eb6d6
--- /dev/null
+++ b/os/port/cis.c
@@ -0,0 +1,539 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+
+enum{
+ Linktarget = 0x13,
+};
+
+/*
+ * read and crack the card information structure enough to set
+ * important parameters like power
+ */
+/* cis memory walking */
+typedef struct Cisdat {
+ uchar *cisbase;
+ int cispos;
+ int cisskip;
+ int cislen;
+} Cisdat;
+
+static void tcfig(PCMslot*, Cisdat*, int);
+static void tentry(PCMslot*, Cisdat*, int);
+static void tvers1(PCMslot*, Cisdat*, int);
+static void tlonglnkmfc(PCMslot*, Cisdat*, int);
+
+static int
+readc(Cisdat *cis, uchar *x)
+{
+ if(cis->cispos >= cis->cislen)
+ return 0;
+ *x = cis->cisbase[cis->cisskip*cis->cispos];
+ cis->cispos++;
+ return 1;
+}
+
+static int
+xcistuple(int slotno, int tuple, int subtuple, void *v, int nv, int attr)
+{
+ PCMmap *m;
+ Cisdat cis;
+ int i, l;
+ uchar *p;
+ uchar type, link, n, c;
+ int this, subtype;
+
+ m = pcmmap(slotno, 0, 0, attr);
+ if(m == 0)
+ return -1;
+
+ cis.cisbase = KADDR(m->isa);
+ cis.cispos = 0;
+ cis.cisskip = attr ? 2 : 1;
+ cis.cislen = m->len;
+
+ /* loop through all the tuples */
+ for(i = 0; i < 1000; i++){
+ this = cis.cispos;
+ if(readc(&cis, &type) != 1)
+ break;
+ if(type == 0xFF)
+ break;
+ if(readc(&cis, &link) != 1)
+ break;
+ if(link == 0xFF)
+ break;
+
+ n = link;
+ if(link > 1 && subtuple != -1){
+ if(readc(&cis, &c) != 1)
+ break;
+ subtype = c;
+ n--;
+ }else
+ subtype = -1;
+
+ if(type == tuple && subtype == subtuple){
+ p = v;
+ for(l=0; l<nv && l<n; l++)
+ if(readc(&cis, p++) != 1)
+ break;
+ pcmunmap(slotno, m);
+ return nv;
+ }
+ cis.cispos = this + (2+link);
+ }
+ pcmunmap(slotno, m);
+ return -1;
+}
+
+int
+pcmcistuple(int slotno, int tuple, int subtuple, void *v, int nv)
+{
+ int n;
+
+ /* try attribute space, then memory */
+ if((n = xcistuple(slotno, tuple, subtuple, v, nv, 1)) >= 0)
+ return n;
+ return xcistuple(slotno, tuple, subtuple, v, nv, 0);
+}
+
+void
+pcmcisread(PCMslot *pp)
+{
+ int this;
+ Cisdat cis;
+ PCMmap *m;
+ uchar type, link;
+
+ memset(pp->ctab, 0, sizeof(pp->ctab));
+ pp->ncfg = 0;
+ memset(pp->cfg, 0, sizeof(pp->cfg));
+ pp->configed = 0;
+ pp->nctab = 0;
+ pp->verstr[0] = 0;
+
+ /*
+ * Read all tuples in attribute space.
+ */
+ m = pcmmap(pp->slotno, 0, 0, 1);
+ if(m == 0)
+ return;
+
+ cis.cisbase = KADDR(m->isa);
+ cis.cispos = 0;
+ cis.cisskip = 2;
+ cis.cislen = m->len;
+
+ /* loop through all the tuples */
+ for(;;){
+ this = cis.cispos;
+ if(readc(&cis, &type) != 1)
+ break;
+ if(type == 0xFF)
+ break;
+ if(readc(&cis, &link) != 1)
+ break;
+
+ switch(type){
+ default:
+ break;
+ case 6:
+ tlonglnkmfc(pp, &cis, type);
+ break;
+ case 0x15:
+ tvers1(pp, &cis, type);
+ break;
+ case 0x1A:
+ tcfig(pp, &cis, type);
+ break;
+ case 0x1B:
+ tentry(pp, &cis, type);
+ break;
+ }
+
+ if(link == 0xFF)
+ break;
+ cis.cispos = this + (2+link);
+ }
+ pcmunmap(pp->slotno, m);
+}
+
+static ulong
+getlong(Cisdat *cis, int size)
+{
+ uchar c;
+ int i;
+ ulong x;
+
+ x = 0;
+ for(i = 0; i < size; i++){
+ if(readc(cis, &c) != 1)
+ break;
+ x |= c<<(i*8);
+ }
+ return x;
+}
+
+static void
+tcfig(PCMslot *pp, Cisdat *cis, int )
+{
+ uchar size, rasize, rmsize;
+ uchar last;
+
+ if(readc(cis, &size) != 1)
+ return;
+ rasize = (size&0x3) + 1;
+ rmsize = ((size>>2)&0xf) + 1;
+ if(readc(cis, &last) != 1)
+ return;
+
+ if(pp->ncfg >= 8){
+ print("tcfig: too many configuration registers\n");
+ return;
+ }
+
+ pp->cfg[pp->ncfg].caddr = getlong(cis, rasize);
+ pp->cfg[pp->ncfg].cpresent = getlong(cis, rmsize);
+ pp->ncfg++;
+}
+
+static ulong vexp[8] =
+{
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
+};
+static ulong vmant[16] =
+{
+ 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90,
+};
+
+static ulong
+microvolt(Cisdat *cis)
+{
+ uchar c;
+ ulong microvolts;
+ ulong exp;
+
+ if(readc(cis, &c) != 1)
+ return 0;
+ exp = vexp[c&0x7];
+ microvolts = vmant[(c>>3)&0xf]*exp;
+ while(c & 0x80){
+ if(readc(cis, &c) != 1)
+ return 0;
+ switch(c){
+ case 0x7d:
+ break; /* high impedence when sleeping */
+ case 0x7e:
+ case 0x7f:
+ microvolts = 0; /* no connection */
+ break;
+ default:
+ exp /= 10;
+ microvolts += exp*(c&0x7f);
+ }
+ }
+ return microvolts;
+}
+
+static ulong
+nanoamps(Cisdat *cis)
+{
+ uchar c;
+ ulong nanoamps;
+
+ if(readc(cis, &c) != 1)
+ return 0;
+ nanoamps = vexp[c&0x7]*vmant[(c>>3)&0xf];
+ while(c & 0x80){
+ if(readc(cis, &c) != 1)
+ return 0;
+ if(c == 0x7d || c == 0x7e || c == 0x7f)
+ nanoamps = 0;
+ }
+ return nanoamps;
+}
+
+/*
+ * only nominal voltage (feature 1) is important for config,
+ * other features must read card to stay in sync.
+ */
+static ulong
+power(Cisdat *cis)
+{
+ uchar feature;
+ ulong mv;
+
+ mv = 0;
+ if(readc(cis, &feature) != 1)
+ return 0;
+ if(feature & 1)
+ mv = microvolt(cis);
+ if(feature & 2)
+ microvolt(cis);
+ if(feature & 4)
+ microvolt(cis);
+ if(feature & 8)
+ nanoamps(cis);
+ if(feature & 0x10)
+ nanoamps(cis);
+ if(feature & 0x20)
+ nanoamps(cis);
+ if(feature & 0x40)
+ nanoamps(cis);
+ return mv/1000000;
+}
+
+static ulong mantissa[16] =
+{ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, };
+
+static ulong exponent[8] =
+{ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, };
+
+static ulong
+ttiming(Cisdat *cis, int scale)
+{
+ uchar unscaled;
+ ulong nanosecs;
+
+ if(readc(cis, &unscaled) != 1)
+ return 0;
+ nanosecs = (mantissa[(unscaled>>3)&0xf]*exponent[unscaled&7])/10;
+ nanosecs = nanosecs * vexp[scale];
+ return nanosecs;
+}
+
+static void
+timing(Cisdat *cis, PCMconftab *ct)
+{
+ uchar c, i;
+
+ if(readc(cis, &c) != 1)
+ return;
+ i = c&0x3;
+ if(i != 3)
+ ct->maxwait = ttiming(cis, i); /* max wait */
+ i = (c>>2)&0x7;
+ if(i != 7)
+ ct->readywait = ttiming(cis, i); /* max ready/busy wait */
+ i = (c>>5)&0x7;
+ if(i != 7)
+ ct->otherwait = ttiming(cis, i); /* reserved wait */
+}
+
+static void
+iospaces(Cisdat *cis, PCMconftab *ct)
+{
+ uchar c;
+ int i, nio;
+
+ ct->nio = 0;
+ if(readc(cis, &c) != 1)
+ return;
+
+ ct->bit16 = ((c>>5)&3) >= 2;
+ if(!(c & 0x80)){
+ ct->io[0].start = 0;
+ ct->io[0].len = 1<<(c&0x1f);
+ ct->nio = 1;
+ return;
+ }
+
+ if(readc(cis, &c) != 1)
+ return;
+
+ /*
+ * For each of the range descriptions read the
+ * start address and the length (value is length-1).
+ */
+ nio = (c&0xf)+1;
+ for(i = 0; i < nio; i++){
+ ct->io[i].start = getlong(cis, (c>>4)&0x3);
+ ct->io[i].len = getlong(cis, (c>>6)&0x3)+1;
+ }
+ ct->nio = nio;
+}
+
+static void
+irq(Cisdat *cis, PCMconftab *ct)
+{
+ uchar c;
+
+ if(readc(cis, &c) != 1)
+ return;
+ ct->irqtype = c & 0xe0;
+ if(c & 0x10)
+ ct->irqs = getlong(cis, 2);
+ else
+ ct->irqs = 1<<(c&0xf);
+ ct->irqs &= 0xDEB8; /* levels available to card */
+}
+
+static void
+memspace(Cisdat *cis, int asize, int lsize, int host)
+{
+ ulong haddress, address, len;
+
+ len = getlong(cis, lsize)*256;
+ address = getlong(cis, asize)*256;
+ USED(len, address);
+ if(host){
+ haddress = getlong(cis, asize)*256;
+ USED(haddress);
+ }
+}
+
+static void
+tentry(PCMslot *pp, Cisdat *cis, int )
+{
+ uchar c, i, feature;
+ PCMconftab *ct;
+
+ if(pp->nctab >= nelem(pp->ctab))
+ return;
+ if(readc(cis, &c) != 1)
+ return;
+ ct = &pp->ctab[pp->nctab++];
+
+ /* copy from last default config */
+ if(pp->def)
+ *ct = *pp->def;
+
+ ct->index = c & 0x3f;
+
+ /* is this the new default? */
+ if(c & 0x40)
+ pp->def = ct;
+
+ /* memory wait specified? */
+ if(c & 0x80){
+ if(readc(cis, &i) != 1)
+ return;
+ if(i&0x80)
+ ct->memwait = 1;
+ }
+
+ if(readc(cis, &feature) != 1)
+ return;
+ switch(feature&0x3){
+ case 1:
+ ct->vpp1 = ct->vpp2 = power(cis);
+ break;
+ case 2:
+ power(cis);
+ ct->vpp1 = ct->vpp2 = power(cis);
+ break;
+ case 3:
+ power(cis);
+ ct->vpp1 = power(cis);
+ ct->vpp2 = power(cis);
+ break;
+ default:
+ break;
+ }
+ if(feature&0x4)
+ timing(cis, ct);
+ if(feature&0x8)
+ iospaces(cis, ct);
+ if(feature&0x10)
+ irq(cis, ct);
+ switch((feature>>5)&0x3){
+ case 1:
+ memspace(cis, 0, 2, 0);
+ break;
+ case 2:
+ memspace(cis, 2, 2, 0);
+ break;
+ case 3:
+ if(readc(cis, &c) != 1)
+ return;
+ for(i = 0; i <= (c&0x7); i++)
+ memspace(cis, (c>>5)&0x3, (c>>3)&0x3, c&0x80);
+ break;
+ }
+ pp->configed++;
+}
+
+static void
+tvers1(PCMslot *pp, Cisdat *cis, int )
+{
+ uchar c, major, minor, last;
+ int i;
+
+ if(readc(cis, &major) != 1)
+ return;
+ if(readc(cis, &minor) != 1)
+ return;
+ last = 0;
+ for(i = 0; i < sizeof(pp->verstr)-1; i++){
+ if(readc(cis, &c) != 1)
+ return;
+ if(c == 0)
+ c = ';';
+ if(c == '\n')
+ c = ';';
+ if(c == 0xff)
+ break;
+ if(c == ';' && last == ';')
+ continue;
+ pp->verstr[i] = c;
+ last = c;
+ }
+ pp->verstr[i] = 0;
+}
+
+static void
+tlonglnkmfc(PCMslot *pp, Cisdat *cis, int)
+{
+ int i, npos, opos;
+ uchar nfn, space, expect, type, this, link;
+
+ readc(cis, &nfn);
+ for(i = 0; i < nfn; i++){
+ readc(cis, &space);
+ npos = getlong(cis, 4);
+ opos = cis->cispos;
+ cis->cispos = npos;
+ expect = Linktarget;
+
+ while(1){
+ this = cis->cispos;
+ if(readc(cis, &type) != 1)
+ break;
+ if(type == 0xFF)
+ break;
+ if(readc(cis, &link) != 1)
+ break;
+
+ if(expect && expect != type){
+ print("tlonglnkmfc: expected %X found %X\n",
+ expect, type);
+ break;
+ }
+ expect = 0;
+
+ switch(type){
+ default:
+ break;
+ case 0x15:
+ tvers1(pp, cis, type);
+ break;
+ case 0x1A:
+ tcfig(pp, cis, type);
+ break;
+ case 0x1B:
+ tentry(pp, cis, type);
+ break;
+ }
+
+ if(link == 0xFF)
+ break;
+ cis->cispos = this + (2+link);
+ }
+ cis->cispos = opos;
+ }
+}
diff --git a/os/port/dev.c b/os/port/dev.c
new file mode 100644
index 00000000..4e91b8d0
--- /dev/null
+++ b/os/port/dev.c
@@ -0,0 +1,434 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+extern ulong kerndate;
+
+void
+mkqid(Qid *q, vlong path, ulong vers, int type)
+{
+ q->type = type;
+ q->vers = vers;
+ q->path = path;
+}
+
+int
+devno(int c, int user)
+{
+ int i;
+
+ for(i = 0; devtab[i] != nil; i++) {
+ if(devtab[i]->dc == c)
+ return i;
+ }
+ if(user == 0)
+ panic("devno %C 0x%ux", c, c);
+
+ return -1;
+}
+
+void
+devdir(Chan *c, Qid qid, char *n, vlong length, char *user, long perm, Dir *db)
+{
+ db->name = n;
+ if(c->flag&CMSG)
+ qid.type |= QTMOUNT;
+ db->qid = qid;
+ db->type = devtab[c->type]->dc;
+ db->dev = c->dev;
+ db->mode = perm;
+ db->mode |= qid.type << 24;
+ db->atime = seconds();
+ db->mtime = kerndate;
+ db->length = length;
+ db->uid = user;
+ db->gid = eve;
+ db->muid = user;
+}
+
+/*
+ * the zeroth element of the table MUST be the directory itself for ..
+*/
+int
+devgen(Chan *c, char*, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ if(tab == 0)
+ return -1;
+ if(i != DEVDOTDOT){
+ /* skip over the first element, that for . itself */
+ i++;
+ if(i >= ntab)
+ return -1;
+ tab += i;
+ }
+ devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+}
+
+void
+devreset(void)
+{
+}
+
+void
+devinit(void)
+{
+}
+
+void
+devshutdown(void)
+{
+}
+
+Chan*
+devattach(int tc, char *spec)
+{
+ Chan *c;
+ char *buf;
+
+ c = newchan();
+ mkqid(&c->qid, 0, 0, QTDIR);
+ c->type = devno(tc, 0);
+ if(spec == nil)
+ spec = "";
+ buf = smalloc(4+strlen(spec)+1);
+ sprint(buf, "#%C%s", tc, spec);
+ c->name = newcname(buf);
+ free(buf);
+ return c;
+}
+
+
+Chan*
+devclone(Chan *c)
+{
+ Chan *nc;
+
+ if(c->flag & COPEN)
+ panic("clone of open file type %C\n", devtab[c->type]->dc);
+
+ nc = newchan();
+
+ nc->type = c->type;
+ nc->dev = c->dev;
+ nc->mode = c->mode;
+ nc->qid = c->qid;
+ nc->offset = c->offset;
+ nc->umh = nil;
+ nc->mountid = c->mountid;
+ nc->aux = c->aux;
+ nc->mqid = c->mqid;
+ nc->mcp = c->mcp;
+ return nc;
+}
+
+Walkqid*
+devwalk(Chan *c, Chan *nc, char **name, int nname, Dirtab *tab, int ntab, Devgen *gen)
+{
+ int i, j, alloc;
+ Walkqid *wq;
+ char *n;
+ Dir dir;
+
+ if(nname > 0)
+ isdir(c);
+
+ alloc = 0;
+ wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ if(waserror()){
+ if(alloc && wq->clone!=nil)
+ cclose(wq->clone);
+ free(wq);
+ return nil;
+ }
+ if(nc == nil){
+ nc = devclone(c);
+ nc->type = 0; /* device doesn't know about this channel yet */
+ alloc = 1;
+ }
+ wq->clone = nc;
+
+ for(j=0; j<nname; j++){
+ if(!(nc->qid.type&QTDIR)){
+ if(j==0)
+ error(Enotdir);
+ goto Done;
+ }
+ n = name[j];
+ if(strcmp(n, ".") == 0){
+ Accept:
+ wq->qid[wq->nqid++] = nc->qid;
+ continue;
+ }
+ if(strcmp(n, "..") == 0){
+ (*gen)(nc, nil, tab, ntab, DEVDOTDOT, &dir);
+ nc->qid = dir.qid;
+ goto Accept;
+ }
+ /*
+ * Ugly problem: If we're using devgen, make sure we're
+ * walking the directory itself, represented by the first
+ * entry in the table, and not trying to step into a sub-
+ * directory of the table, e.g. /net/net. Devgen itself
+ * should take care of the problem, but it doesn't have
+ * the necessary information (that we're doing a walk).
+ */
+ if(gen==devgen && nc->qid.path!=tab[0].qid.path)
+ goto Notfound;
+ for(i=0;; i++) {
+ switch((*gen)(nc, n, tab, ntab, i, &dir)){
+ case -1:
+ Notfound:
+ if(j == 0)
+ error(Enonexist);
+ kstrcpy(up->env->errstr, Enonexist, ERRMAX);
+ goto Done;
+ case 0:
+ continue;
+ case 1:
+ if(strcmp(n, dir.name) == 0){
+ nc->qid = dir.qid;
+ goto Accept;
+ }
+ continue;
+ }
+ }
+ }
+ /*
+ * We processed at least one name, so will return some data.
+ * If we didn't process all nname entries succesfully, we drop
+ * the cloned channel and return just the Qids of the walks.
+ */
+Done:
+ poperror();
+ if(wq->nqid < nname){
+ if(alloc)
+ cclose(wq->clone);
+ wq->clone = nil;
+ }else if(wq->clone){
+ /* attach cloned channel to same device */
+ wq->clone->type = c->type;
+ }
+ return wq;
+}
+
+int
+devstat(Chan *c, uchar *db, int n, Dirtab *tab, int ntab, Devgen *gen)
+{
+ int i;
+ Dir dir;
+ char *p, *elem;
+
+ for(i=0;; i++)
+ switch((*gen)(c, nil, tab, ntab, i, &dir)){
+ case -1:
+ if(c->qid.type & QTDIR){
+ if(c->name == nil)
+ elem = "???";
+ else if(strcmp(c->name->s, "/") == 0)
+ elem = "/";
+ else
+ for(elem=p=c->name->s; *p; p++)
+ if(*p == '/')
+ elem = p+1;
+ devdir(c, c->qid, elem, 0, eve, DMDIR|0555, &dir);
+ n = convD2M(&dir, db, n);
+ if(n == 0)
+ error(Ebadarg);
+ return n;
+ }
+ print("%s %s: devstat %C %llux\n",
+ up->text, up->env->user,
+ devtab[c->type]->dc, c->qid.path);
+
+ error(Enonexist);
+ case 0:
+ break;
+ case 1:
+ if(c->qid.path == dir.qid.path) {
+ if(c->flag&CMSG)
+ dir.mode |= DMMOUNT;
+ n = convD2M(&dir, db, n);
+ if(n == 0)
+ error(Ebadarg);
+ return n;
+ }
+ break;
+ }
+ error(Egreg); /* not reached? */
+ return -1;
+}
+
+long
+devdirread(Chan *c, char *d, long n, Dirtab *tab, int ntab, Devgen *gen)
+{
+ long m, dsz;
+ struct{
+ Dir;
+ char slop[100];
+ }dir;
+
+ for(m=0; m<n; c->dri++) {
+ switch((*gen)(c, nil, tab, ntab, c->dri, &dir)){
+ case -1:
+ return m;
+
+ case 0:
+ break;
+
+ case 1:
+ dsz = convD2M(&dir, (uchar*)d, n-m);
+ if(dsz <= BIT16SZ){ /* <= not < because this isn't stat; read is stuck */
+ if(m == 0)
+ error(Eshort);
+ return m;
+ }
+ m += dsz;
+ d += dsz;
+ break;
+ }
+ }
+
+ return m;
+}
+
+/*
+ * error(Eperm) if open permission not granted for up->env->user.
+ */
+void
+devpermcheck(char *fileuid, ulong perm, int omode)
+{
+ ulong t;
+ static int access[] = { 0400, 0200, 0600, 0100 };
+
+ if(strcmp(up->env->user, fileuid) == 0)
+ perm <<= 0;
+ else
+ if(strcmp(up->env->user, eve) == 0)
+ perm <<= 3;
+ else
+ perm <<= 6;
+
+ t = access[omode&3];
+ if((t&perm) != t)
+ error(Eperm);
+}
+
+Chan*
+devopen(Chan *c, int omode, Dirtab *tab, int ntab, Devgen *gen)
+{
+ int i;
+ Dir dir;
+
+ for(i=0;; i++) {
+ switch((*gen)(c, nil, tab, ntab, i, &dir)){
+ case -1:
+ goto Return;
+ case 0:
+ break;
+ case 1:
+ if(c->qid.path == dir.qid.path) {
+ devpermcheck(dir.uid, dir.mode, omode);
+ goto Return;
+ }
+ break;
+ }
+ }
+Return:
+ c->offset = 0;
+ if((c->qid.type&QTDIR) && omode!=OREAD)
+ error(Eperm);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ return c;
+}
+
+void
+devcreate(Chan*, char*, int, ulong)
+{
+ error(Eperm);
+}
+
+Block*
+devbread(Chan *c, long n, ulong offset)
+{
+ Block *bp;
+
+ bp = allocb(n);
+ if(bp == 0)
+ error(Enomem);
+ if(waserror()) {
+ freeb(bp);
+ nexterror();
+ }
+ bp->wp += devtab[c->type]->read(c, bp->wp, n, offset);
+ poperror();
+ return bp;
+}
+
+long
+devbwrite(Chan *c, Block *bp, ulong offset)
+{
+ long n;
+
+ if(waserror()) {
+ freeb(bp);
+ nexterror();
+ }
+ n = devtab[c->type]->write(c, bp->rp, BLEN(bp), offset);
+ poperror();
+ freeb(bp);
+
+ return n;
+}
+
+void
+devremove(Chan*)
+{
+ error(Eperm);
+}
+
+int
+devwstat(Chan*, uchar*, int)
+{
+ error(Eperm);
+ return 0;
+}
+
+void
+devpower(int)
+{
+ error(Eperm);
+}
+
+int
+devconfig(int, char *, DevConf *)
+{
+ error(Eperm);
+ return 0;
+}
+
+/*
+ * check that the name in a wstat is plausible
+ */
+void
+validwstatname(char *name)
+{
+ validname(name, 0);
+ if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ error(Efilename);
+}
+
+Dev*
+devbyname(char *name)
+{
+ int i;
+
+ for(i = 0; devtab[i] != nil; i++)
+ if(strcmp(devtab[i]->name, name) == 0)
+ return devtab[i];
+ return nil;
+}
diff --git a/os/port/devXXX.c b/os/port/devXXX.c
new file mode 100644
index 00000000..2c76abcd
--- /dev/null
+++ b/os/port/devXXX.c
@@ -0,0 +1,147 @@
+/*
+ * template for making a new device
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+
+enum{
+ Qdir,
+ Qdata,
+};
+
+static
+Dirtab XXXtab[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0555, /* entry for "." must be first if devgen used */
+ "data", {Qdata, 0}, 0, 0666,
+};
+
+static void
+XXXreset(void) /* default in dev.c */
+{
+}
+
+static void
+XXXinit(void) /* default in dev.c */
+{
+}
+
+static Chan*
+XXXattach(char* spec)
+{
+ return devattach('X', spec);
+}
+
+static Walkqid*
+XXXwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, XXXtab, nelem(XXXtab), devgen);
+}
+
+static int
+XXXstat(Chan* c, uchar *db, int n)
+{
+ return devstat(c, db, n, XXXtab, nelem(XXXtab), devgen);
+}
+
+static Chan*
+XXXopen(Chan* c, int omode)
+{
+ return devopen(c, omode, XXXtab, nelem(XXXtab), devgen);
+}
+
+static void
+XXXcreate(Chan* c, char* name, int omode, ulong perm) /* default in dev.c */
+{
+ USED(c, name, omode, perm);
+ error(Eperm);
+}
+
+static void
+XXXremove(Chan* c) /* default in dev.c */
+{
+ USED(c);
+ error(Eperm);
+}
+
+static int
+XXXwstat(Chan* c, uchar *dp, int n) /* default in dev.c */
+{
+ USED(c, dp);
+ error(Eperm);
+ return n;
+}
+
+static void
+XXXclose(Chan* c)
+{
+ USED(c);
+}
+
+static long
+XXXread(Chan* c, void* a, long n, vlong offset)
+{
+ USED(offset);
+
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, XXXtab, nelem(XXXtab), devgen);
+ case Qdata:
+ break;
+ default:
+ n=0;
+ break;
+ }
+ return n;
+}
+
+static Block*
+XXXbread(Chan* c, long n, ulong offset) /* default in dev.c */
+{
+ return devbread(c, n, offset);
+}
+
+static long
+XXXwrite(Chan* c, void* a, long n, vlong offset)
+{
+ USED(a, offset);
+
+ switch((ulong)c->qid.path){
+ case Qdata:
+ break;
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+static long
+XXXbwrite(Chan* c, Block* bp, ulong offset) /* default in dev.c */
+{
+ return devbwrite(c, bp, offset);
+}
+
+Dev XXXdevtab = { /* defaults in dev.c */
+ 'X',
+ "XXX",
+
+ XXXreset, /* devreset */
+ XXXinit, /* devinit */
+ devshutdown,
+ XXXattach,
+ XXXwalk,
+ XXXstat,
+ XXXopen,
+ XXXcreate, /* devcreate */
+ XXXclose,
+ XXXread,
+ XXXbread, /* devbread */
+ XXXwrite,
+ XXXbwrite, /* devbwrite */
+ XXXremove, /* devremove */
+ XXXwstat, /* devwstat */
+};
diff --git a/os/port/devaudio.c b/os/port/devaudio.c
new file mode 100644
index 00000000..b1441e03
--- /dev/null
+++ b/os/port/devaudio.c
@@ -0,0 +1,947 @@
+/*
+ * SB 16 driver
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+#include "audio.h"
+
+typedef struct AQueue AQueue;
+typedef struct Buf Buf;
+
+enum
+{
+ Qdir = 0,
+ Qaudio,
+ Qvolume,
+
+ Fmono = 1,
+ Fin = 2,
+ Fout = 4,
+
+ Aclosed = 0,
+ Aread,
+ Awrite,
+
+ Vaudio = 0,
+ Vsynth,
+ Vcd,
+ Vline,
+ Vmic,
+ Vspeaker,
+ Vtreb,
+ Vbass,
+ Vspeed,
+ Nvol,
+
+ Speed = 44100,
+ Ncmd = 50, /* max volume command words */
+};
+
+static
+Dirtab audiodir[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "audio", {Qaudio}, 0, 0666,
+ "volume", {Qvolume}, 0, 0666,
+};
+
+struct Buf
+{
+ uchar* virt;
+ ulong phys;
+ Buf* next;
+};
+struct AQueue
+{
+ Lock;
+ Buf* first;
+ Buf* last;
+};
+static struct
+{
+ QLock;
+ Rendez vous;
+ int bufinit; /* boolean if buffers allocated */
+ int curcount; /* how much data in current buffer */
+ int active; /* boolean dma running */
+ int intr; /* boolean an interrupt has happened */
+ int amode; /* Aclosed/Aread/Awrite for /audio */
+ int rivol[Nvol]; /* right/left input/output volumes */
+ int livol[Nvol];
+ int rovol[Nvol];
+ int lovol[Nvol];
+ int major; /* SB16 major version number (sb 4) */
+ int minor; /* SB16 minor version number */
+
+ Buf buf[Nbuf]; /* buffers and queues */
+ AQueue empty;
+ AQueue full;
+ Buf* current;
+ Buf* filling;
+} audio;
+
+static struct
+{
+ char* name;
+ int flag;
+ int ilval; /* initial values */
+ int irval;
+} volumes[] =
+{
+[Vaudio] "audio", Fout, 50, 50,
+[Vsynth] "synth", Fin|Fout, 0, 0,
+[Vcd] "cd", Fin|Fout, 0, 0,
+[Vline] "line", Fin|Fout, 0, 0,
+[Vmic] "mic", Fin|Fout|Fmono, 0, 0,
+[Vspeaker] "speaker", Fout|Fmono, 0, 0,
+
+[Vtreb] "treb", Fout, 50, 50,
+[Vbass] "bass", Fout, 50, 50,
+
+[Vspeed] "speed", Fin|Fout|Fmono, Speed, Speed,
+ 0
+};
+
+static struct
+{
+ Lock;
+ int reset; /* io ports to the sound blaster */
+ int read;
+ int write;
+ int wstatus;
+ int rstatus;
+ int mixaddr;
+ int mixdata;
+ int clri8;
+ int clri16;
+ int clri401;
+ int dma;
+} blaster;
+
+static void swab(uchar*);
+
+static char Emajor[] = "soundblaster not responding/wrong version";
+static char Emode[] = "illegal open mode";
+static char Evolume[] = "illegal volume specifier";
+
+static int
+sbcmd(int val)
+{
+ int i, s;
+
+ for(i=1<<16; i!=0; i--) {
+ s = inb(blaster.wstatus);
+ if((s & 0x80) == 0) {
+ outb(blaster.write, val);
+ return 0;
+ }
+ }
+/* print("#A: sbcmd (#%.2x) timeout\n", val); /**/
+ return 1;
+}
+
+static int
+sbread(void)
+{
+ int i, s;
+
+ for(i=1<<16; i!=0; i--) {
+ s = inb(blaster.rstatus);
+ if((s & 0x80) != 0) {
+ return inb(blaster.read);
+ }
+ }
+/* print("#A: sbread did not respond\n"); /**/
+ return 0xbb;
+}
+
+static int
+mxcmd(int addr, int val)
+{
+
+ outb(blaster.mixaddr, addr);
+ outb(blaster.mixdata, val);
+ return 1;
+}
+
+static int
+mxread(int addr)
+{
+ int s;
+
+ outb(blaster.mixaddr, addr);
+ s = inb(blaster.mixdata);
+ return s;
+}
+
+static void
+mxcmds(int s, int v)
+{
+
+ if(v > 100)
+ v = 100;
+ if(v < 0)
+ v = 0;
+ mxcmd(s, (v*255)/100);
+}
+
+static void
+mxcmdt(int s, int v)
+{
+
+ if(v > 100)
+ v = 100;
+ if(v <= 0)
+ mxcmd(s, 0);
+ else
+ mxcmd(s, 255-100+v);
+}
+
+static void
+mxcmdu(int s, int v)
+{
+
+ if(v > 100)
+ v = 100;
+ if(v <= 0)
+ v = 0;
+ mxcmd(s, 128-50+v);
+}
+
+static void
+mxvolume(void)
+{
+ int *left, *right;
+ int source;
+
+ if(audio.amode == Aread){
+ left = audio.livol;
+ right = audio.rivol;
+ }else{
+ left = audio.lovol;
+ right = audio.rovol;
+ }
+
+ ilock(&blaster);
+
+ mxcmd(0x30, 255); /* left master */
+ mxcmd(0x31, 255); /* right master */
+ mxcmd(0x3f, 0); /* left igain */
+ mxcmd(0x40, 0); /* right igain */
+ mxcmd(0x41, 0); /* left ogain */
+ mxcmd(0x42, 0); /* right ogain */
+
+ mxcmds(0x32, left[Vaudio]);
+ mxcmds(0x33, right[Vaudio]);
+
+ mxcmds(0x34, left[Vsynth]);
+ mxcmds(0x35, right[Vsynth]);
+
+ mxcmds(0x36, left[Vcd]);
+ mxcmds(0x37, right[Vcd]);
+
+ mxcmds(0x38, left[Vline]);
+ mxcmds(0x39, right[Vline]);
+
+ mxcmds(0x3a, left[Vmic]);
+ mxcmds(0x3b, left[Vspeaker]);
+
+ mxcmdu(0x44, left[Vtreb]);
+ mxcmdu(0x45, right[Vtreb]);
+
+ mxcmdu(0x46, left[Vbass]);
+ mxcmdu(0x47, right[Vbass]);
+
+ source = 0;
+ if(left[Vsynth])
+ source |= 1<<6;
+ if(right[Vsynth])
+ source |= 1<<5;
+ if(left[Vaudio])
+ source |= 1<<4;
+ if(right[Vaudio])
+ source |= 1<<3;
+ if(left[Vcd])
+ source |= 1<<2;
+ if(right[Vcd])
+ source |= 1<<1;
+ if(left[Vmic])
+ source |= 1<<0;
+ if(audio.amode == Aread)
+ mxcmd(0x3c, 0); /* output switch */
+ else
+ mxcmd(0x3c, source);
+ mxcmd(0x3d, source); /* input left switch */
+ mxcmd(0x3e, source); /* input right switch */
+ iunlock(&blaster);
+}
+
+static Buf*
+getbuf(AQueue *q)
+{
+ Buf *b;
+
+ ilock(q);
+ b = q->first;
+ if(b)
+ q->first = b->next;
+ iunlock(q);
+
+ return b;
+}
+
+static void
+putbuf(AQueue *q, Buf *b)
+{
+
+ ilock(q);
+ b->next = 0;
+ if(q->first)
+ q->last->next = b;
+ else
+ q->first = b;
+ q->last = b;
+ iunlock(q);
+}
+
+/*
+ * move the dma to the next buffer
+ */
+static void
+contindma(void)
+{
+ Buf *b;
+
+ if(!audio.active)
+ goto shutdown;
+
+ b = audio.current;
+ if(audio.amode == Aread) {
+ if(b) /* shouldnt happen */
+ putbuf(&audio.full, b);
+ b = getbuf(&audio.empty);
+ } else {
+ if(b) /* shouldnt happen */
+ putbuf(&audio.empty, b);
+ b = getbuf(&audio.full);
+ }
+ audio.current = b;
+ if(b == 0)
+ goto shutdown;
+
+ dmasetup(blaster.dma, b->virt, Bufsize, audio.amode == Aread);
+ return;
+
+shutdown:
+ dmaend(blaster.dma);
+ sbcmd(0xd9); /* exit at end of count */
+ sbcmd(0xd5); /* pause */
+ audio.curcount = 0;
+ audio.active = 0;
+}
+
+/*
+ * cause sb to get an interrupt per buffer.
+ * start first dma
+ */
+static void
+startdma(void)
+{
+ ulong count;
+ int speed;
+
+ ilock(&blaster);
+ dmaend(blaster.dma);
+ if(audio.amode == Aread) {
+ sbcmd(0x42); /* input sampling rate */
+ speed = audio.livol[Vspeed];
+ } else {
+ sbcmd(0x41); /* output sampling rate */
+ speed = audio.lovol[Vspeed];
+ }
+ sbcmd(speed>>8);
+ sbcmd(speed);
+
+ count = (Bufsize >> 1) - 1;
+ if(audio.amode == Aread)
+ sbcmd(0xbe); /* A/D, autoinit */
+ else
+ sbcmd(0xb6); /* D/A, autoinit */
+ sbcmd(0x30); /* stereo, 16 bit */
+ sbcmd(count);
+ sbcmd(count>>8);
+
+ audio.active = 1;
+ contindma();
+ iunlock(&blaster);
+}
+
+/*
+ * if audio is stopped,
+ * start it up again.
+ */
+static void
+pokeaudio(void)
+{
+ if(!audio.active)
+ startdma();
+}
+
+static void
+audiosbintr(void)
+{
+ int stat, dummy;
+
+ stat = mxread(0x82) & 7; /* get irq status */
+ if(stat) {
+ dummy = 0;
+ if(stat & 2) {
+ ilock(&blaster);
+ dummy = inb(blaster.clri16);
+ contindma();
+ iunlock(&blaster);
+ audio.intr = 1;
+ wakeup(&audio.vous);
+ }
+ if(stat & 1) {
+ dummy = inb(blaster.clri8);
+ }
+ if(stat & 4) {
+ dummy = inb(blaster.clri401);
+ }
+ USED(dummy);
+ }
+}
+
+static void
+pcaudiosbintr(Ureg*, void*)
+{
+/* print("#A: audio interrupt\n"); /**/
+ audiosbintr();
+}
+
+static void
+audiodmaintr(void)
+{
+/* print("#A: dma interrupt\n"); /**/
+}
+
+static int
+anybuf(void*)
+{
+ return audio.intr;
+}
+
+/*
+ * wait for some output to get
+ * empty buffers back.
+ */
+static void
+waitaudio(void)
+{
+
+ audio.intr = 0;
+ pokeaudio();
+ tsleep(&audio.vous, anybuf, 0, 10*1000);
+ if(audio.intr == 0) {
+/* print("#A: audio timeout\n"); /**/
+ audio.active = 0;
+ pokeaudio();
+ }
+}
+
+static void
+sbbufinit(void)
+{
+ int i;
+ void *p;
+
+ for(i=0; i<Nbuf; i++) {
+ p = xspanalloc(Bufsize, CACHELINESZ, 64*1024);
+ dcflush(p, Bufsize);
+ audio.buf[i].virt = UNCACHED(uchar, p);
+ audio.buf[i].phys = (ulong)PADDR(p);
+ }
+}
+
+static void
+setempty(void)
+{
+ int i;
+
+ ilock(&blaster);
+ audio.empty.first = 0;
+ audio.empty.last = 0;
+ audio.full.first = 0;
+ audio.full.last = 0;
+ audio.current = 0;
+ audio.filling = 0;
+ for(i=0; i<Nbuf; i++)
+ putbuf(&audio.empty, &audio.buf[i]);
+ iunlock(&blaster);
+}
+
+static void
+resetlevel(void)
+{
+ int i;
+
+ for(i=0; volumes[i].name; i++) {
+ audio.lovol[i] = volumes[i].ilval;
+ audio.rovol[i] = volumes[i].irval;
+ audio.livol[i] = volumes[i].ilval;
+ audio.rivol[i] = volumes[i].irval;
+ }
+}
+
+static void
+audioinit(void)
+{
+ ISAConf sbconf;
+ int i;
+
+ sbconf.port = 0x220;
+ sbconf.dma = Dma;
+ sbconf.irq = 7;
+ if(isaconfig("audio", 0, &sbconf) == 0)
+ return;
+ if(strcmp(sbconf.type, "sb16") != 0)
+ return;
+ switch(sbconf.port){
+ case 0x220:
+ case 0x240:
+ case 0x260:
+ case 0x280:
+ break;
+ default:
+ print("#A: bad port 0x%lx\n", sbconf.port);
+ return;
+ }
+ switch(sbconf.irq){
+ case 2:
+ case 5:
+ case 7:
+ case 10:
+ break;
+ default:
+ print("#A: bad irq %d\n", sbconf.irq);
+ return;
+ }
+
+ blaster.reset = sbconf.port + 0x6;
+ blaster.read = sbconf.port + 0xa;
+ blaster.write = sbconf.port + 0xc;
+ blaster.wstatus = sbconf.port + 0xc;
+ blaster.rstatus = sbconf.port + 0xe;
+ blaster.mixaddr = sbconf.port + 0x4;
+ blaster.mixdata = sbconf.port + 0x5;
+ blaster.clri8 = sbconf.port + 0xe;
+ blaster.clri16 = sbconf.port + 0xf;
+ blaster.clri401 = sbconf.port + 0x100;
+ blaster.dma = sbconf.dma;
+
+ seteisadma(blaster.dma, audiodmaintr);
+ setvec(Int0vec+sbconf.irq, pcaudiosbintr, 0);
+
+ audio.amode = Aclosed;
+ resetlevel();
+
+ outb(blaster.reset, 1);
+ delay(1); /* >3 υs */
+ outb(blaster.reset, 0);
+ delay(1);
+
+ i = sbread();
+ if(i != 0xaa) {
+ print("#A: no response #%.2x\n", i);
+ return;
+ }
+
+ sbcmd(0xe1); /* get version */
+ audio.major = sbread();
+ audio.minor = sbread();
+
+ if(audio.major != 4) {
+ print("#A: model #%.2x #%.2x; not SB 16\n", audio.major, audio.minor);
+ return;
+ }
+ /*
+ * initialize the mixer
+ */
+ mxcmd(0x00, 0); /* Reset mixer */
+ mxvolume();
+
+ /*
+ * set up irq/dma chans
+ */
+ mxcmd(0x80, /* irq */
+ (sbconf.irq==2)? 1:
+ (sbconf.irq==5)? 2:
+ (sbconf.irq==7)? 4:
+ (sbconf.irq==10)? 8:
+ 0);
+ mxcmd(0x81, 1<<blaster.dma); /* dma */
+}
+
+static Chan*
+audioattach(char *param)
+{
+ return devattach('A', param);
+}
+
+static Walkqid*
+audiowalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+}
+
+static int
+audiostat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
+}
+
+static Chan*
+audioopen(Chan *c, int omode)
+{
+ int amode;
+
+ if(audio.major != 4)
+ error(Emajor);
+
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ case Qdir:
+ break;
+
+ case Qaudio:
+ amode = Awrite;
+ if((omode&7) == OREAD)
+ amode = Aread;
+ qlock(&audio);
+ if(audio.amode != Aclosed){
+ qunlock(&audio);
+ error(Einuse);
+ }
+ if(audio.bufinit == 0) {
+ audio.bufinit = 1;
+ sbbufinit();
+ }
+ audio.amode = amode;
+ setempty();
+ audio.curcount = 0;
+ qunlock(&audio);
+ mxvolume();
+ break;
+ }
+ c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+
+ return c;
+}
+
+static void
+audioclose(Chan *c)
+{
+
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ case Qvolume:
+ break;
+
+ case Qaudio:
+ if(c->flag & COPEN) {
+ qlock(&audio);
+ audio.amode = Aclosed;
+ if(waserror()){
+ qunlock(&audio);
+ nexterror();
+ }
+ while(audio.active)
+ waitaudio();
+ setempty();
+ poperror();
+ qunlock(&audio);
+ }
+ break;
+ }
+}
+
+static long
+audioread(Chan *c, void *vp, long n, vlong offset)
+{
+ int liv, riv, lov, rov;
+ long m, n0;
+ char buf[300];
+ Buf *b;
+ int j;
+ char *a;
+
+ a = vp;
+ n0 = n;
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
+
+ case Qaudio:
+ if(audio.amode != Aread)
+ error(Emode);
+ qlock(&audio);
+ if(waserror()){
+ qunlock(&audio);
+ nexterror();
+ }
+ while(n > 0) {
+ b = audio.filling;
+ if(b == 0) {
+ b = getbuf(&audio.full);
+ if(b == 0) {
+ waitaudio();
+ continue;
+ }
+ audio.filling = b;
+ swab(b->virt);
+ audio.curcount = 0;
+ }
+ m = Bufsize-audio.curcount;
+ if(m > n)
+ m = n;
+ memmove(a, b->virt+audio.curcount, m);
+
+ audio.curcount += m;
+ n -= m;
+ a += m;
+ if(audio.curcount >= Bufsize) {
+ audio.filling = 0;
+ putbuf(&audio.empty, b);
+ }
+ }
+ poperror();
+ qunlock(&audio);
+ break;
+
+ case Qvolume:
+ j = 0;
+ buf[0] = 0;
+ for(m=0; volumes[m].name; m++){
+ liv = audio.livol[m];
+ riv = audio.rivol[m];
+ lov = audio.lovol[m];
+ rov = audio.rovol[m];
+ j += snprint(buf+j, sizeof(buf)-j, "%s", volumes[m].name);
+ if((volumes[m].flag & Fmono) || liv==riv && lov==rov){
+ if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) && liv==lov)
+ j += snprint(buf+j, sizeof(buf)-j, " %d", liv);
+ else{
+ if(volumes[m].flag & Fin)
+ j += snprint(buf+j, sizeof(buf)-j, " in %d", liv);
+ if(volumes[m].flag & Fout)
+ j += snprint(buf+j, sizeof(buf)-j, " out %d", lov);
+ }
+ }else{
+ if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) && liv==lov && riv==rov)
+ j += snprint(buf+j, sizeof(buf)-j, " left %d right %d",
+ liv, riv);
+ else{
+ if(volumes[m].flag & Fin)
+ j += snprint(buf+j, sizeof(buf)-j, " in left %d right %d",
+ liv, riv);
+ if(volumes[m].flag & Fout)
+ j += snprint(buf+j, sizeof(buf)-j, " out left %d right %d",
+ lov, rov);
+ }
+ }
+ j += snprint(buf+j, sizeof(buf)-j, "\n");
+ }
+
+ return readstr(offset, a, n, buf);
+ }
+ return n0-n;
+}
+
+static long
+audiowrite(Chan *c, void *vp, long n, vlong)
+{
+ long m, n0;
+ int i, nf, v, left, right, in, out;
+ char buf[255], *field[Ncmd];
+ Buf *b;
+ char *a;
+
+ a = vp;
+ n0 = n;
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ v = Vaudio;
+ left = 1;
+ right = 1;
+ in = 1;
+ out = 1;
+ if(n > sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = '\0';
+
+ nf = getfields(buf, field, Ncmd, 1, " \t\n");
+ for(i = 0; i < nf; i++){
+ /*
+ * a number is volume
+ */
+ if(field[i][0] >= '0' && field[i][0] <= '9') {
+ m = strtoul(field[i], 0, 10);
+ if(left && out)
+ audio.lovol[v] = m;
+ if(left && in)
+ audio.livol[v] = m;
+ if(right && out)
+ audio.rovol[v] = m;
+ if(right && in)
+ audio.rivol[v] = m;
+ mxvolume();
+ goto cont0;
+ }
+
+ for(m=0; volumes[m].name; m++) {
+ if(strcmp(field[i], volumes[m].name) == 0) {
+ v = m;
+ in = 1;
+ out = 1;
+ left = 1;
+ right = 1;
+ goto cont0;
+ }
+ }
+
+ if(strcmp(field[i], "reset") == 0) {
+ resetlevel();
+ mxvolume();
+ goto cont0;
+ }
+ if(strcmp(field[i], "in") == 0) {
+ in = 1;
+ out = 0;
+ goto cont0;
+ }
+ if(strcmp(field[i], "out") == 0) {
+ in = 0;
+ out = 1;
+ goto cont0;
+ }
+ if(strcmp(field[i], "left") == 0) {
+ left = 1;
+ right = 0;
+ goto cont0;
+ }
+ if(strcmp(field[i], "right") == 0) {
+ left = 0;
+ right = 1;
+ goto cont0;
+ }
+ error(Evolume);
+ break;
+ cont0:;
+ }
+ break;
+
+ case Qaudio:
+ if(audio.amode != Awrite)
+ error(Emode);
+ qlock(&audio);
+ if(waserror()){
+ qunlock(&audio);
+ nexterror();
+ }
+ while(n > 0) {
+ b = audio.filling;
+ if(b == 0) {
+ b = getbuf(&audio.empty);
+ if(b == 0) {
+ waitaudio();
+ continue;
+ }
+ audio.filling = b;
+ audio.curcount = 0;
+ }
+
+ m = Bufsize-audio.curcount;
+ if(m > n)
+ m = n;
+ memmove(b->virt+audio.curcount, a, m);
+
+ audio.curcount += m;
+ n -= m;
+ a += m;
+ if(audio.curcount >= Bufsize) {
+ audio.filling = 0;
+ swab(b->virt);
+ putbuf(&audio.full, b);
+ }
+ }
+ poperror();
+ qunlock(&audio);
+ break;
+ }
+ return n0 - n;
+}
+
+static void
+swab(uchar *a)
+{
+ ulong *p, *ep, b;
+
+ if(!SBswab)
+ return;
+ p = (ulong*)a;
+ ep = p + (Bufsize>>2);
+ while(p < ep) {
+ b = *p;
+ b = (b>>24) | (b<<24) |
+ ((b&0xff0000) >> 8) |
+ ((b&0x00ff00) << 8);
+ *p++ = b;
+ }
+}
+
+Dev audiodevtab = {
+ 'A',
+ "audio",
+
+ devreset,
+ audioinit,
+ devshutdown,
+ audioattach,
+ audiowalk,
+ audiostat,
+ audioopen,
+ devcreate,
+ audioclose,
+ audioread,
+ devbread,
+ audiowrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devbench.c b/os/port/devbench.c
new file mode 100644
index 00000000..22ab5e34
--- /dev/null
+++ b/os/port/devbench.c
@@ -0,0 +1,1165 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include <interp.h>
+#include "io.h"
+#include "../port/error.h"
+#include <isa.h>
+#include "kernel.h"
+
+/* Builtin module support */
+#include "bench.h"
+#include "benchmod.h"
+
+typedef enum { None, Calibrate, Base, Op, Intr, Dis, Gc, MS2T, xTest};
+static struct {
+ int inuse; /* reference count */
+ int test;
+ void* scratch;
+ char* buf;
+ int bufsz;
+ char* wpos;
+ void (*op)(void);
+ vlong tickstart;
+} bench;
+
+static void
+log(char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ bench.wpos = vseprint(bench.wpos, bench.buf+bench.bufsz, msg, ap);
+ va_end(ap);
+}
+
+void
+elog(char *msg, ...)
+{
+ va_list ap;
+
+ if(bench.buf == 0)
+ return;
+ va_start(ap, msg);
+ bench.wpos = vseprint(bench.wpos, bench.buf+bench.bufsz, msg, ap);
+ va_end(ap);
+}
+
+static void
+clear(void)
+{
+ bench.wpos = bench.buf;
+}
+
+static long
+rep(void *to, long n, ulong offset)
+{
+ long left = bench.wpos - bench.buf - offset;
+ if(left < 0)
+ left = 0;
+ if(n > left)
+ n = left;
+ memmove(to, bench.buf+offset, n);
+ return n;
+}
+
+static long
+notest(int report, void *va, long n, ulong offset)
+{
+ USED(report, va, n, offset);
+ if(report)
+ return rep(va, n, offset);
+ return 0;
+}
+
+// Calibration
+static long MS2TS = 0; // time stamps per millisec
+static long US2TS = 0; // time stamps per microsecond
+
+static long
+cal(int report, void *va, long n, ulong offset)
+{
+ int tot, i, lim, low, max, pl, mdelay;
+ ulong t;
+ if(report)
+ return rep(va, n, offset);
+ clear();
+ setpri(PriRealtime);
+ lim = 1000;
+ low = 64000000;
+ max = 0;
+ tot = 0;
+ mdelay = 1000;
+ for(i=0; i<lim; i++){
+ do{
+ pl = splhi();
+ t = archrdtsc32();
+ microdelay(mdelay);
+ t = archrdtsc32() - t;
+ splx(pl);
+ } while(t < 0);
+ if(t < low)
+ low = t;
+ if(t > max)
+ max = t;
+ tot += t;
+ }
+ MS2TS = tot/lim;
+ US2TS = MS2TS/1000;
+ if(va)
+ log("mdelay=%lud lim=%lud tot=%lud low=%lud max=%lud\n", mdelay, lim, tot, low, max);
+ setpri(PriNormal);
+ return n;
+}
+
+/*
+ * ticks to format string
+ */
+/*static*/ char *
+ts2str(vlong ticks)
+{
+#define Nbuf 5
+ static char b[Nbuf][40];
+ static int n=Nbuf-1;
+ char *fmt, *unit;
+ double d;
+
+ if(0){
+ print("ticks=%lld MS2TS=%ld\n", ticks, MS2TS);
+ d = (double)ticks;
+ print("1:%f\n", d);
+ d = (double)ticks*1000;
+ //print("2:%f\n", d);
+ d = ((double)ticks)/MS2TS;
+ //print("3:%f\n", d);
+ }
+ n = (n+1)%Nbuf;
+ if(ticks > MS2TS*1000) {
+ fmt = "%.2f %s";
+ unit = "s";
+ d = ((double)ticks/MS2TS) * 1000.0;
+ } else if(ticks > MS2TS) {
+ fmt = "%.2f %s";
+ unit = "ms";
+ d = (double)ticks/MS2TS;
+ } else if(ticks > MS2TS/1000) {
+ fmt = "%.2f %s";
+ unit = "us";
+ d = ((double)ticks*1000)/MS2TS;
+ } else {
+ fmt = "%.2f %s";
+ unit = "ns";
+ d = ((double)ticks*1000*1000)/MS2TS;
+ }
+ sprint(b[n], fmt, d, unit);
+ return b[n];
+}
+
+/*
+ * ticks to microseconds
+ */
+static double
+ts2us(vlong ticks)
+{
+ return ((double)ticks*1000)/MS2TS;
+}
+
+/*
+ * microseconds timestamp
+ */
+static vlong
+bus(int reset)
+{
+ vlong now;
+ if(US2TS == 0)
+ return 0;
+ if(reset) {
+ bench.tickstart = archrdtsc();
+ return 0;
+ }
+ now = archrdtsc();
+ return ((now-bench.tickstart))/US2TS;
+}
+
+// Base
+static long
+base(int report, void *va, long n, ulong offset)
+{
+ int tot, i, lim, low, max, pl;
+ ulong t;
+ char *bm;
+
+ if(report)
+ return rep(va, n, offset);
+ clear();
+ setpri(PriRealtime);
+ lim = 1000;
+ low = 64000000;
+ max = 0;
+ tot = 0;
+ for(i=0; i<lim; i++){
+ do {
+ pl = splhi();
+ t = archrdtsc32();
+ // do nothing
+ t = archrdtsc32() - t;
+ splx(pl);
+ } while(t < 0);
+ if(t < low)
+ low = t;
+ if(t > max)
+ max = t;
+ tot += t;
+ }
+ bm = ts2str(tot/lim);
+ log("%d %lud %lud %lud %lud (%s)\n", up->pid, lim, tot, low, max, bm);
+ setpri(PriNormal);
+ return n;
+}
+
+// Timeop
+
+typedef struct Psync Psync;
+
+enum {
+ Maxprocs=3,
+};
+
+struct Psync {
+ Rendez r;
+ int flag;
+ int id;
+ int awaken;
+};
+static Psync timesync[Maxprocs];
+static Ref nactive;
+static Ref nbusy;
+static RWlock sync;
+
+static void
+nilop(void)
+{
+}
+
+static int
+timev(void *a)
+{
+ return *(int*)a;
+}
+
+static void
+timeop0(void *ap)
+{
+ int tot, i, lim, low, max;
+ ulong t;
+ Psync *ps;
+ char *bm;
+
+ ps = ap;
+ setpri(PriRealtime);
+ incref(&nactive);
+ sleep(&ps->r, timev, &ps->flag);
+ rlock(&sync);
+ lim = 1000;
+ low = 64000000;
+ max = 0;
+ tot = 0;
+ for(i=0; i<lim; i++){
+ do{
+ t = archrdtsc32();
+ (*bench.op)();
+ t = archrdtsc32() - t;
+ }while(t < 0);
+ if(t < low)
+ low = t;
+ if(t > max)
+ max = t;
+ tot += t;
+ }
+ bm = ts2str(tot/lim);
+ log("%d %lud %lud %lud %lud (%s)\n", up->pid, lim, tot, low, max, bm);
+ runlock(&sync);
+ pexit("", 0);
+}
+
+static long
+timeop(int report, void *va, long n, ulong offset)
+{
+ int i, np, pl;
+
+ if(report)
+ return rep(va, n, offset);
+ clear();
+ bench.op = 0;
+ if(strncmp(va, "nil", 3) == 0)
+ bench.op = nilop;
+ else if(strncmp(va, "sched", 5) == 0)
+ bench.op = sched;
+ else
+ return 0;
+ for(np=1; np<=Maxprocs; np++) {
+ nactive.ref = 0;
+ wlock(&sync);
+ log("%d procs\n", np);
+ setpri(PriRealtime);
+ for(i=0; i<np; i++) {
+ timesync[i].id = i;
+ kproc("timeop", timeop0, &timesync[i], 0);
+ }
+ while(nactive.ref < np)
+ tsleep(&up->sleep, return0, 0, 20);
+ for(i=0; i<np; i++){
+ timesync[i].flag = 1;
+ wakeup(&timesync[i].r);
+ }
+ sched();
+ pl = splhi();
+ setpri(PriNormal);
+ wunlock(&sync);
+ // now they run
+ wlock(&sync); // wait for last reader
+ wunlock(&sync);
+ splx(pl);
+ }
+ return n;
+}
+
+typedef struct Ictr Ictr;
+struct Ictr {
+ ulong base;
+ ulong sleep;
+ ulong spllo;
+ ulong intr;
+ ulong isave;
+ ulong arrive;
+ ulong wakeup;
+ ulong awake;
+};
+static Ictr counters[5/*100*/], *curct;
+static int intrwant;
+static Rendez vous;
+int spltbl; /* set by spllo */
+int intrtbl; /* set by intrvec() */
+int isavetbl; /* set by intrvec() */
+
+static int ienable;
+
+
+static void
+intrwake(void)
+{
+ if(ienable == 0)
+ return;
+ ienable = 0;
+ if(spltbl == 0) // not used here
+ curct->spllo = curct->intr = curct->isave = archrdtsc32();
+ else {
+ curct->spllo = spltbl;
+ curct->intr = intrtbl;
+ curct->isave = isavetbl;
+ }
+ curct->arrive = archrdtsc32();
+ intrwant = 0;
+ wakeup(&vous);
+ curct->wakeup = archrdtsc32();
+}
+
+/*
+ * sleep calls intrtest with splhi (under lock):
+ * provoke the interrupt now, so that it is guaranteed
+ * not to happen until sleep has queued the process,
+ * forcing wakeup to do something.
+ */
+static int
+intrtest(void*)
+{
+ ienable = 1; /* enable recording on interrupt */
+ curct->sleep = archrdtsc32();
+ return intrwant==0;
+}
+
+static long
+intrtime(int report, void *va, long n, ulong offset)
+{
+ Ictr *ic;
+ long t;
+ int i;
+ char *bm;
+ if(report)
+ return rep(va, n, offset);
+ clear();
+
+ setpri(PriRealtime);
+ sched();
+ curct = counters;
+ ienable = 0;
+ addclock0link(intrwake, MS2HZ);
+ for(i=0; i<nelem(counters); i++){
+ curct = &counters[i];
+ intrwant = 1;
+ curct->base = archrdtsc32();
+ sleep(&vous, intrtest, nil);
+ curct->awake = archrdtsc32();
+ sched(); /* just to slow it down between trials */
+ }
+ log("interrupt\n");
+ for(i=0; i<nelem(counters); i++){
+ ic = &counters[i];
+ t = ic->awake - ic->base;
+ bm = ts2str(ic->awake - ic->arrive);
+ ic->awake -= ic->wakeup;
+ ic->wakeup -= ic->arrive;
+ ic->arrive -= ic->isave;
+ ic->isave -= ic->intr;
+ ic->intr -= ic->spllo;
+ ic->spllo -= ic->sleep;
+ ic->sleep -= ic->base;
+ log("%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld (%s)\n", ic->sleep, ic->spllo, ic->intr, ic->isave, ic->arrive, ic->wakeup, ic->awake, t, bm);
+ }
+ setpri(PriNormal);
+ return n;
+}
+
+
+/* DIS operation timing */
+
+typedef struct {
+ vlong n; /* count */
+ vlong min;
+ vlong max;
+ vlong sum;
+ vlong sumsq; /* sum of squares */
+} Stat;
+
+static void
+stat(enum { Reset, Inc } op, Stat *c, vlong val)
+{
+ switch(op) {
+ case Reset:
+ c->n = 0;
+ c->sum = 0;
+ c->sumsq = 0;
+ c->min = 0;
+ c->max = 0;
+ break;
+ case Inc:
+ c->n++;
+ c->sum += val;
+ c->sumsq += val*val;
+ break;
+ }
+ if(val < c->min || c->n == 1)
+ c->min = val;
+ if(val > c->max || c->n == 1)
+ c->max = val;
+}
+
+static void
+statinc(Stat *d, Stat *s)
+{
+ d->n += s->n;
+ d->sum += s->sum;
+ d->sumsq += s->sumsq;
+ if(s->min < d->min || d->n == s->n)
+ d->min = s->min;
+ if(s->max > d->max || d->n == s->n)
+ d->max = s->max;
+}
+
+enum
+{
+ HSIZE = 31,
+ MAXCOUNT = 100000000L,
+};
+
+typedef struct {
+ int op;
+ int pc;
+ long count;
+ Stat t; /* combined dec and execution time */
+} Istat;
+
+typedef struct Mstat Mstat;
+struct Mstat {
+ char* name;
+ char* path;
+ int ninst;
+ Istat* inst;
+ Inst* base;
+ Mstat* hash;
+ Mstat* link;
+};
+
+struct
+{
+ Mstat* hash[HSIZE];
+ Mstat* list;
+} vmstat;
+
+extern struct /* see ../../interp/tab.h:/keywds/ */
+{
+ char* name;
+ int op;
+ int terminal;
+}keywds[];
+
+static char *
+opname(int op)
+{
+ char *name;
+
+ if(op < 0 || op >= MAXDIS)
+ return "Unknown";
+ return keywds[op].name;
+ if(name == 0)
+ name = "<Noname>";
+ return name;
+}
+
+static void
+mreset(void)
+{
+ Mstat *mp, *link;
+
+ for(mp=vmstat.list; mp; mp=link) {
+ link = mp->link;
+ free(mp->inst);
+ free(mp);
+ }
+ vmstat.list = 0;
+ memset(vmstat.hash, 0, HSIZE*sizeof(Mstat*));
+}
+
+static ulong
+hash(void *s)
+{
+ ulong sum = 0;
+ uchar *a = s;
+
+ while(*a)
+ sum = (sum << 1) + *a++;
+ return sum%HSIZE;
+}
+
+static Mstat *
+mlookup(Module *mod)
+{
+ Mstat *m;
+ ulong h;
+
+ for(m=vmstat.hash[hash(mod->name)]; m; m=m->hash)
+ if(strcmp(m->name, mod->name) == 0
+ && strcmp(m->path, mod->path) == 0) {
+ return m;
+ }
+
+
+ m = malloc(sizeof(Mstat));
+ if(m == 0)
+ return 0;
+ kstrdup(&m->name, mod->name);
+ kstrdup(&m->path, mod->path);
+ m->ninst = mod->nprog;
+ m->inst = malloc(m->ninst*sizeof(Istat));
+ if(m->path == 0 || m->inst == 0)
+ return 0;
+ m->base = mod->prog;
+ m->link = vmstat.list;
+ vmstat.list = m;
+ h = hash(m->name);
+ m->hash = vmstat.hash[h];
+ vmstat.hash[h] = m;
+ return m;
+}
+
+/* interpreted code Dis timing */
+void
+bxec(Prog *p)
+{
+ int op, pc;
+ vlong t0, t;
+ Mstat* ms;
+ Istat* is;
+ Module *om;
+
+ R = p->R;
+ R.MP = R.M->MP;
+ R.IC = p->quanta;
+
+ if(p->kill != nil) {
+ char *m;
+ m = p->kill;
+ p->kill = nil;
+ error(m);
+ }
+
+ if(R.M->compiled)
+ comvec();
+ else {
+ om = 0;
+ ms = mlookup(R.M->m);
+ do {
+ op = R.PC->op;
+ pc = R.PC-R.M->prog;
+ if(om != R.M->m) {
+ om = R.M->m;
+ ms = mlookup(R.M->m);
+ }
+
+ t0 = archrdtsc();
+ dec[R.PC->add]();
+ R.PC++;
+ optab[op]();
+ t = archrdtsc();
+ if(ms) {
+ is = &ms->inst[pc];
+ if(is->count < MAXCOUNT) {
+ if(is->count++ == 0) {
+ is->op = op;
+ is->pc = pc;
+ }
+ stat(Inc, &is->t, t-t0);
+ }
+ }
+ if(op==ISPAWN || op==IMSPAWN) {
+ Prog *new = delruntail(Pdebug);
+ new->xec = bxec;
+ addrun(new);
+ }
+ } while(--R.IC != 0);
+ }
+
+ p->R = R;
+}
+
+/* compiled code Dis timing */
+
+static struct { /* compiled code timing */
+ int set;
+ int op, pc; /* Dis opcode and program counter */
+ vlong t0, t; /* time-in and time-out */
+ vlong base; /* cost of doing the timing */
+ Mstat *ms;
+ Module *om;
+ int timing; /* between "dis timer start" and stop */
+} C;
+
+enum { Nop = 0 }; /* opcode value for Dis NOP instruction */
+void
+dopostcomp(vlong t)
+{
+ Istat* is;
+
+ C.t = t;
+ C.set = 0;
+ if(C.ms != 0) {
+ is = &C.ms->inst[C.pc];
+ if(C.op == Nop) { /* NOP calibration */
+ vlong newbase = C.t - C.t0;
+ if(C.base == 0 || newbase < C.base)
+ C.base = newbase;
+ }
+ if(is->count < MAXCOUNT) {
+ if(is->count++ == 0) {
+ is->op = C.op;
+ is->pc = C.pc;
+ }
+ stat(Inc, &is->t, C.t-C.t0/*-C.base*/);
+ }
+ }
+}
+
+void
+postcomp(void)
+{
+ vlong t;
+
+ t = archrdtsc();
+ if(C.timing == 0 || C.set == 0)
+ return;
+ dopostcomp(t);
+}
+
+void
+precomp(void)
+{
+ vlong t;
+
+ t = archrdtsc();
+ if(C.timing == 0)
+ return;
+ if(C.set)
+ dopostcomp(t);
+ C.pc = *(ulong *)R.m;
+ C.op = *(ulong *)R.s;
+ if(C.om != R.M->m) {
+ C.om = R.M->m;
+ C.ms = mlookup(R.M->m);
+ }
+ C.set = 1;
+ C.t0 = archrdtsc();
+}
+
+/* standard deviation */
+static vlong
+sdev(Stat *s)
+{
+ extern double sqrt(double);
+ vlong var;
+ var = s->sum;
+ var *= var/s->n;
+ var = (s->sumsq - var)/s->n;
+ return (vlong)sqrt(var);
+}
+
+/*
+ * Use the sequence:
+ * 1. "timer startclr" or "timer start", then,
+ * 2. Any DIS operations, and,
+ * 3. "timer stop", to stop timing.
+ * 4. Read the results from the data file after:
+ * a) "timer report" to get module/pc level results, or
+ * b) "timer summary" to get opcode level results
+ */
+static long
+distime(int report, void *va, long n, ulong offset)
+{
+ Prog *p;
+ Mstat *mp;
+ Istat *ip, *ep;
+
+ if(report)
+ return rep(va, n, offset);
+ clear();
+ acquire();
+ p = currun();
+ if(strncmp(va, "timer startclr", 14) == 0) {
+ mreset();
+ memset(&C, 0, sizeof(C));
+ C.timing = 1;
+ p->xec = bxec;
+ } else if(strncmp(va, "timer start", 11) == 0) {
+ p->xec = bxec;
+ C.timing = 1;
+ } else if(strncmp(va, "timer stop", 10) == 0) {
+ p->xec = xec; /* bug: stop all xec threads */
+ C.timing = 0;
+ } else if(strncmp(va, "timer nilop", 11) == 0) {
+ } else if(strncmp(va, "timer report", 12) == 0) /* by address */
+ for(mp=vmstat.list; mp; mp=mp->link) {
+ ep = mp->inst + mp->ninst;
+ for(ip=mp->inst; ip<ep; ip++)
+ if(ip->count > 0) {
+ char *mean = ts2str(ip->t.sum/ip->count);
+ char *min = ts2str(ip->t.min);
+ char *max = ts2str(ip->t.max);
+ char *std = ts2str(sdev(&ip->t));
+ log("%s %d %s %ld %s %s %s %s\n", mp->path, ip->pc, opname(ip->op), ip->count, mean, min, max, std);
+ }
+ }
+ else if(strncmp(va, "timer summary", 13) == 0) { /* by opcode */
+ static Stat T[MAXDIS];
+ int i;
+
+ for(i=0; i<MAXDIS; i++)
+ stat(Reset, &T[i], 0);
+ for(mp=vmstat.list; mp; mp=mp->link) {
+ ep = mp->inst + mp->ninst;
+ for(ip=mp->inst; ip<ep; ip++)
+ if(ip->count > 0)
+ statinc(&T[ip->op], &ip->t);
+ }
+ for(i=0; i<MAXDIS; i++) {
+ Stat *t = &T[i];
+ char *mean = "0.00 ms";
+ char *min = "0.00 ms";
+ char *max = "0.00 ms";
+ char *std = "0.00 ms";
+ if(t->n > 0) {
+ mean = ts2str(t->sum/t->n);
+ min = ts2str(t->min);
+ max = ts2str(t->max);
+ std = ts2str(sdev(t));
+ }
+ log("%d %s %lld %s %s %s %s\n", i, opname(i), t->n, mean, min, max, std);
+ }
+ } else
+ n = 0;
+ R.IC = 1;
+ release();
+
+ return n;
+}
+
+/*
+ * Garbage collection
+ */
+static int nidle;
+
+int
+idlegc(void *p)
+{
+ int done;
+ Prog *head;
+ vlong t0, t1, tot;
+ USED(p);
+
+ head = progn(0); /* isched.head */
+ done = gccolor + 3;
+ tot = 0;
+ while(gccolor < done && gcruns()) {
+ if(tready(nil))
+ break;
+ t0 = archrdtsc();
+ rungc(head);
+ t1 = archrdtsc();
+ t1 -= t0;
+ tot += t1;
+// log(" %.2f", ts2us(t1));
+ }
+ log(" %.2f", ts2us(tot));
+ nidle--;
+ if(nidle == 0) {
+ log("\n");
+ return 1;
+ }
+ return 0;
+}
+
+static long
+gctime(int report, void *va, long n, ulong offset)
+{
+ int i;
+ vlong t0, t1;
+ Prog *head;
+
+ if(report)
+ return rep(va, n, offset);
+ clear();
+ acquire();
+ head = progn(0); /* isched.head */
+/*
+ if(strncmp(va, "idle", 4) == 0) {
+ nidle = 100;
+ log("GCIDLE:1l:Observation:n:Time:us");
+ atidle(idlegc, 0);
+ } else if(strncmp(va, "stop", 4) == 0) {
+ atidledont(idlegc, 0);
+ } else
+*/
+ if(strncmp(va, "sched", 5) == 0) {
+ log("GCSCHED:1l:Observation:n:Time:us");
+ for(i=0; i<1000; i++) {
+ t0 = archrdtsc();
+ rungc(head);
+ t1 = archrdtsc();
+ log(" %.2f", ts2us(t1-t0));
+ release();
+ acquire();
+ }
+ log("\n");
+ } else if(strncmp(va, "acquire", 7) == 0) {
+ log("GCACQUIRE:1l:Observation:n:Time:us");
+ for(i=0; i<1000; i++) {
+ t0 = archrdtsc();
+ release();
+ acquire();
+ head = progn(0); /* isched.head */
+ rungc(head);
+ release();
+ acquire();
+ t1 = archrdtsc();
+ log(" %.2f", ts2us(t1-t0));
+ }
+ log("\n");
+ }
+
+ release();
+
+ return n;
+}
+
+
+/*
+ * Request the number of time stamp ticks per millisecond
+ */
+static long
+ms2ts(int report, void *va, long n, ulong offset)
+{
+ if(report)
+ return rep(va, n, offset);
+ log("%.ld\n", MS2TS);
+ return n;
+}
+
+/*
+ * test
+ */
+
+static long
+test(int report, void *va, long n, ulong offset)
+{
+// vlong v;
+ double d;
+ if(report)
+ return rep(va, n, offset);
+// v = 5;
+// print("vlong %lld\n", v);
+// print("before cast\n");
+// d = (double)v;
+// print("after cast\n");
+// print("before assign\n");
+ d=100.0;
+ print("after assign\n");
+ print("double %f\n", d);
+// log("%lld %f\n", v, d);
+ return n;
+}
+
+/*
+ * $Bench builtin support
+ */
+void
+Bench_reset(void *)
+{
+ bus(1);
+}
+
+void
+Bench_microsec(void *fp)
+{
+ F_Bench_microsec *f;
+
+ f = fp;
+ *f->ret = bus(0);
+}
+
+void
+Bench_disablegc(void *)
+{
+ gclock();
+}
+
+void
+Bench_enablegc(void *)
+{
+ gcunlock();
+}
+
+
+#define fdchk(x) ((x) == (Bench_FD*)H ? -1 : (x)->fd)
+void
+Bench_read(void *fp)
+{
+ int n;
+ F_Bench_read *f;
+ vlong usrelease, uskread, usacquire, ussched;
+
+ f = fp;
+ n = f->n;
+ if(f->buf == (Array*)H) {
+ *f->ret = 0;
+ return;
+ }
+ if(n > f->buf->len)
+ n = f->buf->len;
+
+ bus(1);
+ release();
+ usrelease = bus(0);
+ *f->ret = kread(fdchk(f->fd), f->buf->data, n);
+ uskread = bus(0);
+ acquire();
+ usacquire = bus(0);
+ sched();
+ ussched = bus(0);
+ log("%lld %lld %lld %lud %lld\n", usrelease, uskread, usacquire, m->ticks, ussched);
+}
+
+
+/*
+ * driver support
+ */
+long (*Test[])(int report, void *va, long n, ulong offset) = {
+ [None] notest,
+ [Calibrate] cal,
+ [Base] base,
+ [Op] timeop,
+ [Intr] intrtime,
+ [Dis] distime,
+ [Gc] gctime,
+ [MS2T] ms2ts,
+ [xTest] test,
+};
+
+enum {
+ Benchdirqid,
+ Benchdataqid,
+ Benchctlqid,
+ Benchusqid,
+};
+#define Data 0
+static Dirtab benchtab[]={
+ ".", {Benchdirqid,0,QTDIR}, 0, 0555,
+ "bdata", {Benchdataqid}, 0, 0444,
+ "bctl", {Benchctlqid}, 0, 0660,
+ "busec", {Benchusqid}, 0, 0660,
+};
+
+static void
+benchreset(void)
+{
+ builtinmod("$Bench", Benchmodtab);
+}
+
+static Chan*
+benchattach(char *spec)
+{
+ bench.inuse++;
+ if(bench.inuse == 1) {
+ bench.bufsz = 100*READSTR;
+ bench.buf = xalloc(bench.bufsz);
+ bench.wpos = bench.buf;
+ if(bench.buf == 0)
+ error(Enomem);
+ bench.test = None;
+ cal(0, 0, 0, 0);
+ }
+ return devattach('x', spec);
+}
+
+void
+benchshutdown(void)
+{
+ bench.inuse--;
+ if(bench.inuse == 0)
+ xfree(bench.buf);
+}
+
+static Walkqid*
+benchwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, benchtab, nelem(benchtab), devgen);
+}
+
+static Chan*
+benchopen(Chan *c, int omode)
+{
+ if(c->qid.path == Benchdirqid){
+ if(omode != OREAD)
+ error(Eperm);
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static int
+benchstat(Chan *c, uchar *dp, int n)
+{
+ switch((ulong)c->qid.path){
+ case Benchdataqid:
+ benchtab[Data].length = bench.wpos - bench.buf;
+ }
+ return devstat(c, dp, n, benchtab, nelem(benchtab), devgen);
+}
+
+static void
+benchclose(Chan*)
+{
+}
+
+static long
+benchread(Chan *c, void *buf, long n, vlong offset)
+{
+ vlong us;
+ char tmp[64];
+
+ switch((ulong)c->qid.path){
+ case Benchdirqid:
+ return devdirread(c, buf, n, benchtab, nelem(benchtab), devgen);
+
+ case Benchdataqid:
+ return Test[bench.test](1, buf, n, offset);
+
+ case Benchusqid:
+ us = archrdtsc();
+ us /= US2TS;
+ snprint(tmp, sizeof(tmp), "%.lld", us);
+ return readstr(0, buf, n, tmp);
+ default:
+ n = 0;
+ break;
+ }
+ return n;
+}
+
+static long
+benchwrite(Chan *c, void *buf, long n, vlong offset)
+{
+ int argn = n;
+
+ switch((ulong)c->qid.path){
+ case Benchctlqid:
+ bench.test = None;
+ memset((char *)bench.buf, 0, bench.bufsz);
+ bench.wpos = bench.buf;
+ if(strncmp(buf, "test", 4) == 0)
+ bench.test = xTest;
+ else if(strncmp(buf, "calibrate", 9) == 0)
+ bench.test = Calibrate;
+ else if(strncmp(buf, "base", 4) == 0)
+ bench.test = Base;
+ else if(strncmp(buf, "intr", 4) == 0)
+ bench.test = Intr;
+ else if(strncmp(buf, "op ", 3) == 0) {
+ bench.test = Op;
+ buf = (char *)buf + 3;
+ argn -= 3;
+ } else if(strncmp(buf, "dis ", 4) == 0) {
+ bench.test = Dis;
+ buf = (char *)buf + 4;
+ argn -= 4;
+ } else if(strncmp(buf, "gc ", 3) == 0) {
+ bench.test = Gc;
+ buf = (char *)buf + 3;
+ argn -= 3;
+ } else if(strncmp(buf, "ms2ts", 5) == 0)
+ bench.test = MS2T;
+ else
+ error(Ebadctl);
+ Test[bench.test](0, buf, argn, offset);
+ break;
+ case Benchusqid:
+ bench.tickstart = archrdtsc();
+ break;
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+Dev benchdevtab = {
+ 'x',
+ "bench",
+
+ benchreset,
+ devinit,
+ benchshutdown,
+ benchattach,
+ benchwalk,
+ benchstat,
+ benchopen,
+ devcreate,
+ benchclose,
+ benchread,
+ devbread,
+ benchwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+
+};
diff --git a/os/port/devboot.c b/os/port/devboot.c
new file mode 100644
index 00000000..66babe38
--- /dev/null
+++ b/os/port/devboot.c
@@ -0,0 +1,150 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+enum{
+ Qdir,
+ Qboot,
+ Qmem,
+ Qkexec,
+
+ Maxkexec = 1536*1024,
+};
+
+static
+Dirtab bootdir[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "boot", {Qboot}, 0, 0220,
+ "mem", {Qmem}, 0, 0660,
+ "kexec", {Qkexec}, 0, 0220,
+};
+
+static Chan*
+bootattach(char *spec)
+{
+ return devattach('B', spec);
+}
+
+static Walkqid*
+bootwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, bootdir, nelem(bootdir), devgen);
+}
+
+static int
+bootstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, bootdir, nelem(bootdir), devgen);
+}
+
+static Chan*
+bootopen(Chan *c, int omode)
+{
+ if (c->qid.path == Qkexec) {
+ c->aux = malloc(Maxkexec);
+ print("kexec buffer: %lux\n", c->aux);
+ }
+ return devopen(c, omode, bootdir, nelem(bootdir), devgen);
+}
+
+static void
+bootclose(Chan *c)
+{
+ if(c->qid.path == Qkexec && c->aux != nil){
+ print("exec new kernel @%lux\n", (ulong)c->aux);
+ splhi();
+ segflush(c->aux, 64*1024);
+ gotopc((ulong)c->aux);
+ }
+}
+
+static long
+bootread(Chan *c, void *buf, long n, vlong offset)
+{
+ switch((ulong)c->qid.path){
+
+ case Qdir:
+ return devdirread(c, buf, n, bootdir, nelem(bootdir), devgen);
+
+ case Qmem:
+ /* kernel memory */
+ if(offset>=KZERO && offset<KZERO+conf.npage*BY2PG){
+ if(offset+n > KZERO+conf.npage*BY2PG)
+ n = KZERO+conf.npage*BY2PG - offset;
+ memmove(buf, (char*)offset, n);
+ return n;
+ }
+ error(Ebadarg);
+ }
+
+ error(Egreg);
+ return 0; /* not reached */
+}
+
+static long
+bootwrite(Chan *c, void *buf, long n, vlong offset)
+{
+ ulong pc;
+ uchar *p;
+
+ switch((ulong)c->qid.path){
+ case Qmem:
+ /* kernel memory */
+ if(offset>=KZERO && offset<KZERO+conf.npage*BY2PG){
+ if(offset+n > KZERO+conf.npage*BY2PG)
+ n = KZERO+conf.npage*BY2PG - offset;
+ memmove((char*)offset, buf, n);
+ segflush((void*)offset, n);
+ return n;
+ }
+ error(Ebadarg);
+
+ case Qboot:
+ p = (uchar*)buf;
+ pc = (((((p[0]<<8)|p[1])<<8)|p[2])<<8)|p[3];
+ if(pc < KZERO || pc >= KZERO+conf.npage*BY2PG)
+ error(Ebadarg);
+ splhi();
+ segflush((void*)pc, 64*1024);
+ gotopc(pc);
+
+ case Qkexec:
+ print(".");
+ if(c->aux != nil && offset <= Maxkexec){
+ if(offset+n > Maxkexec)
+ n = Maxkexec - offset;
+ memmove((char*)c->aux+offset, buf, n);
+ segflush((char*)c->aux+offset, n);
+ return n;
+ }
+ free(c->aux);
+ c->aux = nil;
+ error(Ebadarg);
+ }
+ error(Ebadarg);
+ return 0; /* not reached */
+}
+
+Dev bootdevtab = {
+ 'B',
+ "boot",
+
+ devreset,
+ devinit,
+ devshutdown,
+ bootattach,
+ bootwalk,
+ bootstat,
+ bootopen,
+ devcreate,
+ bootclose,
+ bootread,
+ devbread,
+ bootwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devbridge.c b/os/port/devbridge.c
new file mode 100644
index 00000000..9237728e
--- /dev/null
+++ b/os/port/devbridge.c
@@ -0,0 +1,1206 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/netif.h"
+#include "../port/error.h"
+
+typedef struct Bridge Bridge;
+typedef struct Port Port;
+typedef struct Centry Centry;
+typedef struct Iphdr Iphdr;
+typedef struct Tcphdr Tcphdr;
+
+enum
+{
+ Qtopdir= 1, /* top level directory */
+
+ Qbridgedir, /* bridge* directory */
+ Qbctl,
+ Qstats,
+ Qcache,
+ Qlog,
+
+ Qportdir, /* directory for a protocol */
+ Qpctl,
+ Qlocal,
+ Qstatus,
+
+ MaxQ,
+
+ Maxbridge= 4,
+ Maxport= 128, // power of 2
+ CacheHash= 257, // prime
+ CacheLook= 5, // how many cache entries to examine
+ CacheSize= (CacheHash+CacheLook-1),
+ CacheTimeout= 5*60, // timeout for cache entry in seconds
+
+ TcpMssMax = 1300, // max desirable Tcp MSS value
+ TunnelMtu = 1400,
+};
+
+static Dirtab bridgedirtab[]={
+ "ctl", {Qbctl}, 0, 0666,
+ "stats", {Qstats}, 0, 0444,
+ "cache", {Qcache}, 0, 0444,
+ "log", {Qlog}, 0, 0666,
+};
+
+static Dirtab portdirtab[]={
+ "ctl", {Qpctl}, 0, 0666,
+ "local", {Qlocal}, 0, 0444,
+ "status", {Qstatus}, 0, 0444,
+};
+
+enum {
+ Logcache= (1<<0),
+ Logmcast= (1<<1),
+};
+
+// types of interfaces
+enum
+{
+ Tether,
+ Ttun,
+};
+
+static Logflag logflags[] =
+{
+ { "cache", Logcache, },
+ { "multicast", Logmcast, },
+ { nil, 0, },
+};
+
+static Dirtab *dirtab[MaxQ];
+
+#define TYPE(x) (((ulong)(x).path) & 0xff)
+#define PORT(x) ((((ulong)(x).path) >> 8)&(Maxport-1))
+#define QID(x, y) (((x)<<8) | (y))
+
+struct Centry
+{
+ uchar d[Eaddrlen];
+ int port;
+ long expire; // entry expires this number of seconds after bootime
+ long src;
+ long dst;
+};
+
+struct Bridge
+{
+ QLock;
+ int nport;
+ Port *port[Maxport];
+ Centry cache[CacheSize];
+ ulong hit;
+ ulong miss;
+ ulong copy;
+ long delay0; // constant microsecond delay per packet
+ long delayn; // microsecond delay per byte
+ int tcpmss; // modify tcpmss value
+
+ Log;
+};
+
+struct Port
+{
+ int id;
+ Bridge *bridge;
+ int ref;
+ int closed;
+
+ Chan *data[2]; // channel to data
+
+ int mcast; // send multi cast packets
+
+ Proc *readp; // read proc
+
+ // the following uniquely identifies the port
+ int type;
+ char name[KNAMELEN];
+
+ // owner hash - avoids bind/unbind races
+ ulong ownhash;
+
+ // various stats
+ int in; // number of packets read
+ int inmulti; // multicast or broadcast
+ int inunknown; // unknown address
+ int out; // number of packets read
+ int outmulti; // multicast or broadcast
+ int outunknown; // unknown address
+ int outfrag; // fragmented the packet
+ int nentry; // number of cache entries for this port
+};
+
+enum {
+ IP_VER = 0x40, /* Using IP version 4 */
+ IP_HLEN = 0x05, /* Header length in characters */
+ IP_DF = 0x4000, /* Don't fragment */
+ IP_MF = 0x2000, /* More fragments */
+ IP_MAX = (32*1024), /* Maximum Internet packet size */
+ IP_TCPPROTO = 6,
+ EOLOPT = 0,
+ NOOPOPT = 1,
+ MSSOPT = 2,
+ MSS_LENGTH = 4, /* Mean segment size */
+ SYN = 0x02, /* Pkt. is synchronise */
+ IPHDR = 20, /* sizeof(Iphdr) */
+};
+
+struct Iphdr
+{
+ uchar vihl; /* Version and header length */
+ uchar tos; /* Type of service */
+ uchar length[2]; /* packet length */
+ uchar id[2]; /* ip->identification */
+ uchar frag[2]; /* Fragment information */
+ uchar ttl; /* Time to live */
+ uchar proto; /* Protocol */
+ uchar cksum[2]; /* Header checksum */
+ uchar src[4]; /* IP source */
+ uchar dst[4]; /* IP destination */
+};
+
+struct Tcphdr
+{
+ uchar sport[2];
+ uchar dport[2];
+ uchar seq[4];
+ uchar ack[4];
+ uchar flag[2];
+ uchar win[2];
+ uchar cksum[2];
+ uchar urg[2];
+};
+
+static Bridge bridgetab[Maxbridge];
+
+static int m2p[] = {
+ [OREAD] 4,
+ [OWRITE] 2,
+ [ORDWR] 6
+};
+
+static int bridgegen(Chan *c, char*, Dirtab*, int, int s, Dir *dp);
+static void portbind(Bridge *b, int argc, char *argv[]);
+static void portunbind(Bridge *b, int argc, char *argv[]);
+static void etherread(void *a);
+static char *cachedump(Bridge *b);
+static void portfree(Port *port);
+static void cacheflushport(Bridge *b, int port);
+static void etherwrite(Port *port, Block *bp);
+
+extern ulong parseip(uchar*, char*);
+extern ushort ipcsum(uchar *addr);
+
+static void
+bridgeinit(void)
+{
+ int i;
+ Dirtab *dt;
+ // setup dirtab with non directory entries
+ for(i=0; i<nelem(bridgedirtab); i++) {
+ dt = bridgedirtab + i;
+ dirtab[TYPE(dt->qid)] = dt;
+ }
+ for(i=0; i<nelem(portdirtab); i++) {
+ dt = portdirtab + i;
+ dirtab[TYPE(dt->qid)] = dt;
+ }
+}
+
+static Chan*
+bridgeattach(char* spec)
+{
+ Chan *c;
+ int dev;
+
+ dev = atoi(spec);
+ if(dev<0 || dev >= Maxbridge)
+ error("bad specification");
+
+ c = devattach('B', spec);
+ mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
+ c->dev = dev;
+
+ return c;
+}
+
+static Walkqid*
+bridgewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, (Dirtab*)0, 0, bridgegen);
+}
+
+static int
+bridgestat(Chan* c, uchar* db, int n)
+{
+ return devstat(c, db, n, (Dirtab *)0, 0L, bridgegen);
+}
+
+static Chan*
+bridgeopen(Chan* c, int omode)
+{
+ int perm;
+ Bridge *b;
+
+ omode &= 3;
+ perm = m2p[omode];
+ USED(perm);
+
+ b = bridgetab + c->dev;
+ USED(b);
+
+ switch(TYPE(c->qid)) {
+ default:
+ break;
+ case Qlog:
+ logopen(b);
+ break;
+ case Qcache:
+ c->aux = cachedump(b);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static void
+bridgeclose(Chan* c)
+{
+ Bridge *b = bridgetab + c->dev;
+
+ switch(TYPE(c->qid)) {
+ case Qcache:
+ if(c->flag & COPEN)
+ free(c->aux);
+ break;
+ case Qlog:
+ if(c->flag & COPEN)
+ logclose(b);
+ break;
+ }
+}
+
+static long
+bridgeread(Chan *c, void *a, long n, vlong off)
+{
+ char buf[256];
+ Bridge *b = bridgetab + c->dev;
+ Port *port;
+ int i, ingood, outgood;
+
+ USED(off);
+ switch(TYPE(c->qid)) {
+ default:
+ error(Eperm);
+ case Qtopdir:
+ case Qbridgedir:
+ case Qportdir:
+ return devdirread(c, a, n, 0, 0, bridgegen);
+ case Qlog:
+ return logread(b, a, off, n);
+ case Qstatus:
+ qlock(b);
+ port = b->port[PORT(c->qid)];
+ if(port == 0)
+ strcpy(buf, "unbound\n");
+ else {
+ i = 0;
+ switch(port->type) {
+ default: panic("bridgeread: unknown port type: %d", port->type);
+ case Tether:
+ i += snprint(buf+i, sizeof(buf)-i, "ether %s: ", port->name);
+ break;
+ case Ttun:
+ i += snprint(buf+i, sizeof(buf)-i, "tunnel %s: ", port->name);
+ break;
+ }
+ ingood = port->in-port->inmulti-port->inunknown;
+ outgood = port->out-port->outmulti-port->outunknown;
+ i += snprint(buf+i, sizeof(buf)-i, "in=%d(%d:%d:%d) out=%d(%d:%d:%d:%d)\n",
+ port->in, ingood, port->inmulti, port->inunknown,
+ port->out, outgood, port->outmulti, port->outunknown, port->outfrag);
+ USED(i);
+ }
+ n = readstr(off, a, n, buf);
+ qunlock(b);
+ return n;
+ case Qbctl:
+ snprint(buf, sizeof(buf), "%s tcpmss\ndelay %ld %ld\n", b->tcpmss ? "set" : "clear",
+ b->delay0, b->delayn);
+ n = readstr(off, a, n, buf);
+ return n;
+ case Qcache:
+ n = readstr(off, a, n, c->aux);
+ return n;
+ case Qstats:
+ snprint(buf, sizeof(buf), "hit=%uld miss=%uld copy=%uld\n",
+ b->hit, b->miss, b->copy);
+ n = readstr(off, a, n, buf);
+ return n;
+ }
+}
+
+static void
+bridgeoption(Bridge *b, char *option, int value)
+{
+ if(strcmp(option, "tcpmss") == 0)
+ b->tcpmss = value;
+ else
+ error("unknown bridge option");
+}
+
+
+static long
+bridgewrite(Chan *c, void *a, long n, vlong off)
+{
+ Bridge *b = bridgetab + c->dev;
+ Cmdbuf *cb;
+ char *arg0;
+ char *p;
+
+ USED(off);
+ switch(TYPE(c->qid)) {
+ default:
+ error(Eperm);
+ case Qbctl:
+ cb = parsecmd(a, n);
+ qlock(b);
+ if(waserror()) {
+ qunlock(b);
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf == 0)
+ error("short write");
+ arg0 = cb->f[0];
+ if(strcmp(arg0, "bind") == 0) {
+ portbind(b, cb->nf-1, cb->f+1);
+ } else if(strcmp(arg0, "unbind") == 0) {
+ portunbind(b, cb->nf-1, cb->f+1);
+ } else if(strcmp(arg0, "cacheflush") == 0) {
+ logb(b, Logcache, "cache flush\n");
+ memset(b->cache, 0, CacheSize*sizeof(Centry));
+ } else if(strcmp(arg0, "set") == 0) {
+ if(cb->nf != 2)
+ error("usage: set option");
+ bridgeoption(b, cb->f[1], 1);
+ } else if(strcmp(arg0, "clear") == 0) {
+ if(cb->nf != 2)
+ error("usage: clear option");
+ bridgeoption(b, cb->f[1], 0);
+ } else if(strcmp(arg0, "delay") == 0) {
+ if(cb->nf != 3)
+ error("usage: delay delay0 delayn");
+ b->delay0 = strtol(cb->f[1], nil, 10);
+ b->delayn = strtol(cb->f[2], nil, 10);
+ } else
+ error("unknown control request");
+ poperror();
+ qunlock(b);
+ free(cb);
+ return n;
+ case Qlog:
+ cb = parsecmd(a, n);
+ p = logctl(b, cb->nf, cb->f, logflags);
+ free(cb);
+ if(p != nil)
+ error(p);
+ return n;
+ }
+}
+
+static int
+bridgegen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
+{
+ Bridge *b = bridgetab + c->dev;
+ int type = TYPE(c->qid);
+ Dirtab *dt;
+ Qid qid;
+
+ if(s == DEVDOTDOT){
+ switch(TYPE(c->qid)){
+ case Qtopdir:
+ case Qbridgedir:
+ snprint(up->genbuf, sizeof(up->genbuf), "#B%ld", c->dev);
+ mkqid(&qid, Qtopdir, 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ break;
+ case Qportdir:
+ snprint(up->genbuf, sizeof(up->genbuf), "bridge%ld", c->dev);
+ mkqid(&qid, Qbridgedir, 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ break;
+ default:
+ panic("bridgewalk %llux", c->qid.path);
+ }
+ return 1;
+ }
+
+ switch(type) {
+ default:
+ // non directory entries end up here
+ if(c->qid.type & QTDIR)
+ panic("bridgegen: unexpected directory");
+ if(s != 0)
+ return -1;
+ dt = dirtab[TYPE(c->qid)];
+ if(dt == nil)
+ panic("bridgegen: unknown type: %lud", TYPE(c->qid));
+ devdir(c, c->qid, dt->name, dt->length, eve, dt->perm, dp);
+ return 1;
+ case Qtopdir:
+ if(s != 0)
+ return -1;
+ snprint(up->genbuf, sizeof(up->genbuf), "bridge%ld", c->dev);
+ mkqid(&qid, QID(0, Qbridgedir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ case Qbridgedir:
+ if(s<nelem(bridgedirtab)) {
+ dt = bridgedirtab+s;
+ devdir(c, dt->qid, dt->name, dt->length, eve, dt->perm, dp);
+ return 1;
+ }
+ s -= nelem(bridgedirtab);
+ if(s >= b->nport)
+ return -1;
+ mkqid(&qid, QID(s, Qportdir), 0, QTDIR);
+ snprint(up->genbuf, sizeof(up->genbuf), "%d", s);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ case Qportdir:
+ if(s>=nelem(portdirtab))
+ return -1;
+ dt = portdirtab+s;
+ mkqid(&qid, QID(PORT(c->qid),TYPE(dt->qid)), 0, QTFILE);
+ devdir(c, qid, dt->name, dt->length, eve, dt->perm, dp);
+ return 1;
+ }
+}
+
+// also in netif.c
+static int
+parseaddr(uchar *to, char *from, int alen)
+{
+ char nip[4];
+ char *p;
+ int i;
+
+ p = from;
+ for(i = 0; i < alen; i++){
+ if(*p == 0)
+ return -1;
+ nip[0] = *p++;
+ if(*p == 0)
+ return -1;
+ nip[1] = *p++;
+ nip[2] = 0;
+ to[i] = strtoul(nip, 0, 16);
+ if(*p == ':')
+ p++;
+ }
+ return 0;
+}
+
+// assumes b is locked
+static void
+portbind(Bridge *b, int argc, char *argv[])
+{
+ Port *port;
+ char path[8*KNAMELEN];
+ char buf[100];
+ char *dev, *dev2=nil, *p;
+ Chan *ctl;
+ int type=0, i, n;
+ char *usage = "usage: bind ether|tunnel name ownhash dev [dev2]";
+ char name[KNAMELEN];
+ ulong ownhash;
+
+ memset(name, 0, KNAMELEN);
+ if(argc < 4)
+ error(usage);
+ if(strcmp(argv[0], "ether") == 0) {
+ if(argc != 4)
+ error(usage);
+ type = Tether;
+ strncpy(name, argv[1], KNAMELEN);
+ name[KNAMELEN-1] = 0;
+// parseaddr(addr, argv[1], Eaddrlen);
+ } else if(strcmp(argv[0], "tunnel") == 0) {
+ if(argc != 5)
+ error(usage);
+ type = Ttun;
+ strncpy(name, argv[1], KNAMELEN);
+ name[KNAMELEN-1] = 0;
+// parseip(addr, argv[1]);
+ dev2 = argv[4];
+ } else
+ error(usage);
+ ownhash = atoi(argv[2]);
+ dev = argv[3];
+ for(i=0; i<b->nport; i++) {
+ port = b->port[i];
+ if(port != nil)
+ if(port->type == type)
+ if(memcmp(port->name, name, KNAMELEN) == 0)
+ error("port in use");
+ }
+ for(i=0; i<Maxport; i++)
+ if(b->port[i] == nil)
+ break;
+ if(i == Maxport)
+ error("no more ports");
+ port = smalloc(sizeof(Port));
+ port->ref = 1;
+ port->id = i;
+ port->ownhash = ownhash;
+
+ if(waserror()) {
+ portfree(port);
+ nexterror();
+ }
+ port->type = type;
+ memmove(port->name, name, KNAMELEN);
+ switch(port->type) {
+ default: panic("portbind: unknown port type: %d", type);
+ case Tether:
+ snprint(path, sizeof(path), "%s/clone", dev);
+ ctl = namec(path, Aopen, ORDWR, 0);
+ if(waserror()) {
+ cclose(ctl);
+ nexterror();
+ }
+ // check addr?
+
+ // get directory name
+ n = devtab[ctl->type]->read(ctl, buf, sizeof(buf), 0);
+ buf[n] = 0;
+ for(p = buf; *p == ' '; p++)
+ ;
+ snprint(path, sizeof(path), "%s/%lud/data", dev, strtoul(p, 0, 0));
+
+ // setup connection to be promiscuous
+ snprint(buf, sizeof(buf), "connect -1");
+ devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
+ snprint(buf, sizeof(buf), "promiscuous");
+ devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
+ snprint(buf, sizeof(buf), "bridge");
+ devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
+
+ // open data port
+ port->data[0] = namec(path, Aopen, ORDWR, 0);
+ // dup it
+ incref(port->data[0]);
+ port->data[1] = port->data[0];
+
+ poperror();
+ cclose(ctl);
+
+ break;
+ case Ttun:
+ port->data[0] = namec(dev, Aopen, OREAD, 0);
+ port->data[1] = namec(dev2, Aopen, OWRITE, 0);
+ break;
+ }
+
+ poperror();
+
+ // commited to binding port
+ b->port[port->id] = port;
+ port->bridge = b;
+ if(b->nport <= port->id)
+ b->nport = port->id+1;
+
+ // assumes kproc always succeeds
+ kproc("etherread", etherread, port, 0); // poperror must be next
+ port->ref++;
+}
+
+// assumes b is locked
+static void
+portunbind(Bridge *b, int argc, char *argv[])
+{
+ Port *port=nil;
+ int type=0, i;
+ char *usage = "usage: unbind ether|tunnel addr [ownhash]";
+ char name[KNAMELEN];
+ ulong ownhash;
+
+ memset(name, 0, KNAMELEN);
+ if(argc < 2 || argc > 3)
+ error(usage);
+ if(strcmp(argv[0], "ether") == 0) {
+ type = Tether;
+ strncpy(name, argv[1], KNAMELEN);
+ name[KNAMELEN-1] = 0;
+// parseaddr(addr, argv[1], Eaddrlen);
+ } else if(strcmp(argv[0], "tunnel") == 0) {
+ type = Ttun;
+ strncpy(name, argv[1], KNAMELEN);
+ name[KNAMELEN-1] = 0;
+// parseip(addr, argv[1]);
+ } else
+ error(usage);
+ if(argc == 3)
+ ownhash = atoi(argv[2]);
+ else
+ ownhash = 0;
+ for(i=0; i<b->nport; i++) {
+ port = b->port[i];
+ if(port != nil)
+ if(port->type == type)
+ if(memcmp(port->name, name, KNAMELEN) == 0)
+ break;
+ }
+ if(i == b->nport)
+ error("port not found");
+ if(ownhash != 0 && port->ownhash != 0 && ownhash != port->ownhash)
+ error("bad owner hash");
+
+ port->closed = 1;
+ b->port[i] = nil; // port is now unbound
+ cacheflushport(b, i);
+
+ // try and stop reader
+ if(port->readp)
+ postnote(port->readp, 1, "unbind", 0);
+ portfree(port);
+}
+
+// assumes b is locked
+static Centry *
+cachelookup(Bridge *b, uchar d[Eaddrlen])
+{
+ int i;
+ uint h;
+ Centry *p;
+ long sec;
+
+ // dont cache multicast or broadcast
+ if(d[0] & 1)
+ return 0;
+
+ h = 0;
+ for(i=0; i<Eaddrlen; i++) {
+ h *= 7;
+ h += d[i];
+ }
+ h %= CacheHash;
+ p = b->cache + h;
+ sec = TK2SEC(m->ticks);
+ for(i=0; i<CacheLook; i++,p++) {
+ if(memcmp(d, p->d, Eaddrlen) == 0) {
+ p->dst++;
+ if(sec >= p->expire) {
+ logb(b, Logcache, "expired cache entry: %E %d\n",
+ d, p->port);
+ return nil;
+ }
+ p->expire = sec + CacheTimeout;
+ return p;
+ }
+ }
+ logb(b, Logcache, "cache miss: %E\n", d);
+ return nil;
+}
+
+// assumes b is locked
+static void
+cacheupdate(Bridge *b, uchar d[Eaddrlen], int port)
+{
+ int i;
+ uint h;
+ Centry *p, *pp;
+ long sec;
+
+ // dont cache multicast or broadcast
+ if(d[0] & 1) {
+ logb(b, Logcache, "bad source address: %E\n", d);
+ return;
+ }
+
+ h = 0;
+ for(i=0; i<Eaddrlen; i++) {
+ h *= 7;
+ h += d[i];
+ }
+ h %= CacheHash;
+ p = b->cache + h;
+ pp = p;
+ sec = p->expire;
+
+ // look for oldest entry
+ for(i=0; i<CacheLook; i++,p++) {
+ if(memcmp(p->d, d, Eaddrlen) == 0) {
+ p->expire = TK2SEC(m->ticks) + CacheTimeout;
+ if(p->port != port) {
+ logb(b, Logcache, "NIC changed port %d->%d: %E\n",
+ p->port, port, d);
+ p->port = port;
+ }
+ p->src++;
+ return;
+ }
+ if(p->expire < sec) {
+ sec = p->expire;
+ pp = p;
+ }
+ }
+ if(pp->expire != 0)
+ logb(b, Logcache, "bumping from cache: %E %d\n", pp->d, pp->port);
+ pp->expire = TK2SEC(m->ticks) + CacheTimeout;
+ memmove(pp->d, d, Eaddrlen);
+ pp->port = port;
+ pp->src = 1;
+ pp->dst = 0;
+ logb(b, Logcache, "adding to cache: %E %d\n", pp->d, pp->port);
+}
+
+// assumes b is locked
+static void
+cacheflushport(Bridge *b, int port)
+{
+ Centry *ce;
+ int i;
+
+ ce = b->cache;
+ for(i=0; i<CacheSize; i++,ce++) {
+ if(ce->port != port)
+ continue;
+ memset(ce, 0, sizeof(Centry));
+ }
+}
+
+static char *
+cachedump(Bridge *b)
+{
+ int i, n;
+ long sec, off;
+ char *buf, *p, *ep;
+ Centry *ce;
+ char c;
+
+ qlock(b);
+ if(waserror()) {
+ qunlock(b);
+ nexterror();
+ }
+ sec = TK2SEC(m->ticks);
+ n = 0;
+ for(i=0; i<CacheSize; i++)
+ if(b->cache[i].expire != 0)
+ n++;
+
+ n *= 51; // change if print format is changed
+ n += 10; // some slop at the end
+ buf = malloc(n);
+ p = buf;
+ ep = buf + n;
+ ce = b->cache;
+ off = seconds() - sec;
+ for(i=0; i<CacheSize; i++,ce++) {
+ if(ce->expire == 0)
+ continue;
+ c = (sec < ce->expire)?'v':'e';
+ p += snprint(p, ep-p, "%E %2d %10ld %10ld %10ld %c\n", ce->d,
+ ce->port, ce->src, ce->dst, ce->expire+off, c);
+ }
+ *p = 0;
+ poperror();
+ qunlock(b);
+
+ return buf;
+}
+
+
+
+// assumes b is locked
+static void
+ethermultiwrite(Bridge *b, Block *bp, Port *port)
+{
+ Port *oport;
+ Block *bp2;
+ Etherpkt *ep;
+ int i, mcast, bcast;
+ static uchar bcastaddr[Eaddrlen] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+ if(waserror()) {
+ if(bp)
+ freeb(bp);
+ nexterror();
+ }
+
+ ep = (Etherpkt*)bp->rp;
+ mcast = ep->d[0] & 1;
+ if(mcast)
+ bcast = memcmp(ep->d, bcastaddr, Eaddrlen) == 0;
+ else
+ bcast = 0;
+
+ oport = nil;
+ for(i=0; i<b->nport; i++) {
+ if(i == port->id || b->port[i] == nil)
+ continue;
+ if(mcast && !bcast && !b->port[i]->mcast)
+ continue;
+ if(mcast)
+ b->port[i]->outmulti++;
+ else
+ b->port[i]->outunknown++;
+
+ // delay one so that the last write does not copy
+ if(oport != nil) {
+ b->copy++;
+ bp2 = copyblock(bp, blocklen(bp));
+ if(!waserror()) {
+ etherwrite(oport, bp2);
+ poperror();
+ }
+ }
+ oport = b->port[i];
+ }
+
+ // last write free block
+ if(oport) {
+ bp2 = bp; bp = nil; USED(bp);
+ if(!waserror()) {
+ etherwrite(oport, bp2);
+ poperror();
+ }
+ } else
+ freeb(bp);
+
+ poperror();
+}
+
+static void
+tcpmsshack(Etherpkt *epkt, int n)
+{
+ int hl;
+ Iphdr *iphdr;
+ Tcphdr *tcphdr;
+ ulong mss;
+ ulong cksum;
+ int optlen;
+ uchar *optr;
+
+ // check it is an ip packet
+ if(nhgets(epkt->type) != 0x800)
+ return;
+ iphdr = (Iphdr*)(epkt->data);
+ n -= ETHERHDRSIZE;
+ if(n < IPHDR)
+ return;
+
+ // check it is ok IP packet
+ if(iphdr->vihl != (IP_VER|IP_HLEN)) {
+ hl = (iphdr->vihl&0xF)<<2;
+ if((iphdr->vihl&0xF0) != IP_VER || hl < (IP_HLEN<<2))
+ return;
+ } else
+ hl = IP_HLEN<<2;
+
+ // check TCP
+ if(iphdr->proto != IP_TCPPROTO)
+ return;
+ n -= hl;
+ if(n < sizeof(Tcphdr))
+ return;
+ tcphdr = (Tcphdr*)((uchar*)(iphdr) + hl);
+ // MSS can only appear in SYN packet
+ if(!(tcphdr->flag[1] & SYN))
+ return;
+ hl = (tcphdr->flag[0] & 0xf0)>>2;
+ if(n < hl)
+ return;
+
+ // check for MSS option
+ optr = (uchar*)(tcphdr) + sizeof(Tcphdr);
+ n = hl - sizeof(Tcphdr);
+ for(;;) {
+ if(n <= 0 || *optr == EOLOPT)
+ return;
+ if(*optr == NOOPOPT) {
+ n--;
+ optr++;
+ continue;
+ }
+ optlen = optr[1];
+ if(optlen < 2 || optlen > n)
+ return;
+ if(*optr == MSSOPT && optlen == MSS_LENGTH)
+ break;
+ n -= optlen;
+ optr += optlen;
+ }
+
+ mss = nhgets(optr+2);
+ if(mss <= TcpMssMax)
+ return;
+ // fit checksum
+ cksum = nhgets(tcphdr->cksum);
+ if(optr-(uchar*)tcphdr & 1) {
+print("tcpmsshack: odd alignment!\n");
+ // odd alignments are a pain
+ cksum += nhgets(optr+1);
+ cksum -= (optr[1]<<8)|(TcpMssMax>>8);
+ cksum += (cksum>>16);
+ cksum &= 0xffff;
+ cksum += nhgets(optr+3);
+ cksum -= ((TcpMssMax&0xff)<<8)|optr[4];
+ cksum += (cksum>>16);
+ } else {
+ cksum += mss;
+ cksum -= TcpMssMax;
+ cksum += (cksum>>16);
+ }
+ hnputs(tcphdr->cksum, cksum);
+ hnputs(optr+2, TcpMssMax);
+}
+
+/*
+ * process to read from the ethernet
+ */
+static void
+etherread(void *a)
+{
+ Port *port = a;
+ Bridge *b = port->bridge;
+ Block *bp, *bp2;
+ Etherpkt *ep;
+ Centry *ce;
+ long md;
+
+ qlock(b);
+ port->readp = up; /* hide identity under a rock for unbind */
+
+ while(!port->closed){
+ // release lock to read - error means it is time to quit
+ qunlock(b);
+ if(waserror()) {
+print("etherread read error: %s\n", up->env->errstr);
+ qlock(b);
+ break;
+ }
+if(0)print("devbridge: etherread: reading\n");
+ bp = devtab[port->data[0]->type]->bread(port->data[0], ETHERMAXTU, 0);
+if(0)print("devbridge: etherread: blocklen = %d\n", blocklen(bp));
+ poperror();
+ qlock(b);
+ if(bp == nil || port->closed)
+ break;
+ if(waserror()) {
+//print("etherread bridge error\n");
+ if(bp)
+ freeb(bp);
+ continue;
+ }
+ if(blocklen(bp) < ETHERMINTU)
+ error("short packet");
+ port->in++;
+
+ ep = (Etherpkt*)bp->rp;
+ cacheupdate(b, ep->s, port->id);
+ if(b->tcpmss)
+ tcpmsshack(ep, BLEN(bp));
+
+ /*
+ * delay packets to simulate a slow link
+ */
+ if(b->delay0 || b->delayn){
+ md = b->delay0 + b->delayn * BLEN(bp);
+ if(md > 0)
+ microdelay(md);
+ }
+
+ if(ep->d[0] & 1) {
+ logb(b, Logmcast, "multicast: port=%d src=%E dst=%E type=%#.4ux\n",
+ port->id, ep->s, ep->d, (ep->type[0]<<8)|ep->type[1] );
+ port->inmulti++;
+ bp2 = bp; bp = nil;
+ ethermultiwrite(b, bp2, port);
+ } else {
+ ce = cachelookup(b, ep->d);
+ if(ce == nil) {
+ b->miss++;
+ port->inunknown++;
+ bp2 = bp; bp = nil;
+ ethermultiwrite(b, bp2, port);
+ }else if(ce->port != port->id){
+ b->hit++;
+ bp2 = bp; bp = nil;
+ etherwrite(b->port[ce->port], bp2);
+ }
+ }
+
+ poperror();
+ if(bp)
+ freeb(bp);
+ }
+//print("etherread: trying to exit\n");
+ port->readp = nil;
+ portfree(port);
+ qunlock(b);
+ pexit("hangup", 1);
+}
+
+static int
+fragment(Etherpkt *epkt, int n)
+{
+ Iphdr *iphdr;
+
+ if(n <= TunnelMtu)
+ return 0;
+
+ // check it is an ip packet
+ if(nhgets(epkt->type) != 0x800)
+ return 0;
+ iphdr = (Iphdr*)(epkt->data);
+ n -= ETHERHDRSIZE;
+ if(n < IPHDR)
+ return 0;
+
+ // check it is ok IP packet - I don't handle IP options for the momment
+ if(iphdr->vihl != (IP_VER|IP_HLEN))
+ return 0;
+
+ // check for don't fragment
+ if(iphdr->frag[0] & (IP_DF>>8))
+ return 0;
+
+ // check for short block
+ if(nhgets(iphdr->length) > n)
+ return 0;
+
+ return 1;
+}
+
+
+static void
+etherwrite(Port *port, Block *bp)
+{
+ Iphdr *eh, *feh;
+ Etherpkt *epkt;
+ int n, lid, len, seglen, chunk, dlen, blklen, offset, mf;
+ Block *xp, *nb;
+ ushort fragoff, frag;
+
+ port->out++;
+ epkt = (Etherpkt*)bp->rp;
+ n = blocklen(bp);
+ if(port->type != Ttun || !fragment(epkt, n)) {
+ devtab[port->data[1]->type]->bwrite(port->data[1], bp, 0);
+ return;
+ }
+ port->outfrag++;
+ if(waserror()){
+ freeblist(bp);
+ nexterror();
+ }
+
+ seglen = (TunnelMtu - ETHERHDRSIZE - IPHDR) & ~7;
+ eh = (Iphdr*)(epkt->data);
+ len = nhgets(eh->length);
+ frag = nhgets(eh->frag);
+ mf = frag & IP_MF;
+ frag <<= 3;
+ dlen = len - IPHDR;
+ xp = bp;
+ lid = nhgets(eh->id);
+ offset = ETHERHDRSIZE+IPHDR;
+ while(xp != nil && offset && offset >= BLEN(xp)) {
+ offset -= BLEN(xp);
+ xp = xp->next;
+ }
+ xp->rp += offset;
+
+if(0) print("seglen=%d, dlen=%d, mf=%x, frag=%d\n", seglen, dlen, mf, frag);
+ for(fragoff = 0; fragoff < dlen; fragoff += seglen) {
+ nb = allocb(ETHERHDRSIZE+IPHDR+seglen);
+
+ feh = (Iphdr*)(nb->wp+ETHERHDRSIZE);
+
+ memmove(nb->wp, epkt, ETHERHDRSIZE+IPHDR);
+ nb->wp += ETHERHDRSIZE+IPHDR;
+
+ if((fragoff + seglen) >= dlen) {
+ seglen = dlen - fragoff;
+ hnputs(feh->frag, (frag+fragoff)>>3 | mf);
+ }
+ else
+ hnputs(feh->frag, (frag+fragoff>>3) | IP_MF);
+
+ hnputs(feh->length, seglen + IPHDR);
+ hnputs(feh->id, lid);
+
+ /* Copy up the data area */
+ chunk = seglen;
+ while(chunk) {
+ blklen = chunk;
+ if(BLEN(xp) < chunk)
+ blklen = BLEN(xp);
+ memmove(nb->wp, xp->rp, blklen);
+ nb->wp += blklen;
+ xp->rp += blklen;
+ chunk -= blklen;
+ if(xp->rp == xp->wp)
+ xp = xp->next;
+ }
+
+ feh->cksum[0] = 0;
+ feh->cksum[1] = 0;
+ hnputs(feh->cksum, ipcsum(&feh->vihl));
+
+ // don't generate small packets
+ if(BLEN(nb) < ETHERMINTU)
+ nb->wp = nb->rp + ETHERMINTU;
+ devtab[port->data[1]->type]->bwrite(port->data[1], nb, 0);
+ }
+ poperror();
+ freeblist(bp);
+}
+
+// hold b lock
+static void
+portfree(Port *port)
+{
+ port->ref--;
+ if(port->ref < 0)
+ panic("portfree: bad ref");
+ if(port->ref > 0)
+ return;
+
+ if(port->data[0])
+ cclose(port->data[0]);
+ if(port->data[1])
+ cclose(port->data[1]);
+ memset(port, 0, sizeof(Port));
+ free(port);
+}
+
+Dev bridgedevtab = {
+ 'B',
+ "bridge",
+
+ devreset,
+ bridgeinit,
+ devshutdown,
+ bridgeattach,
+ bridgewalk,
+ bridgestat,
+ bridgeopen,
+ devcreate,
+ bridgeclose,
+ bridgeread,
+ devbread,
+ bridgewrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devcap.c b/os/port/devcap.c
new file mode 100644
index 00000000..64232c57
--- /dev/null
+++ b/os/port/devcap.c
@@ -0,0 +1,248 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "mp.h"
+#include "libsec.h"
+
+/*
+ * Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+ */
+
+enum {
+ Captimeout = 15, /* seconds until expiry */
+ Capidletime = 60 /* idle seconds before capwatch exits */
+};
+
+typedef struct Caps Caps;
+struct Caps
+{
+ uchar hash[SHA1dlen];
+ ulong time;
+ Caps* next;
+};
+
+struct {
+ QLock l;
+ Caps* caps;
+ int kpstarted;
+} allcaps;
+
+enum {
+ Qdir,
+ Qhash,
+ Quse
+};
+
+static Dirtab capdir[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "capuse", {Quse, 0}, 0, 0222,
+ "caphash", {Qhash, 0}, 0, 0200,
+};
+
+static int ncapdir = nelem(capdir);
+
+static void
+capwatch(void *a)
+{
+ Caps *c, **l;
+ int idletime;
+
+ USED(a);
+ idletime = 0;
+ for(;;){
+ tsleep(&up->sleep, return0, nil, 30*1000);
+ qlock(&allcaps.l);
+ for(l = &allcaps.caps; (c = *l) != nil;)
+ if(++c->time > Captimeout){
+ *l = c->next;
+ free(c);
+ }else
+ l = &c->next;
+ if(allcaps.caps == nil){
+ if(++idletime > Capidletime){
+ allcaps.kpstarted = 0;
+ qunlock(&allcaps.l);
+ pexit("", 0);
+ }
+ }else
+ idletime = 0;
+ qunlock(&allcaps.l);
+ }
+}
+
+static Chan *
+capattach(char *spec)
+{
+ return devattach(L'¤', spec);
+}
+
+static Walkqid*
+capwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, capdir, nelem(capdir), devgen);
+}
+
+static int
+capstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, capdir, nelem(capdir), devgen);
+}
+
+static Chan*
+capopen(Chan *c, int omode)
+{
+ if(c->qid.type & QTDIR) {
+ if(omode != OREAD)
+ error(Eisdir);
+ c->mode = omode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ if(c->qid.path == Qhash && !iseve())
+ error(Eperm);
+
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static void
+capclose(Chan *c)
+{
+ USED(c);
+}
+
+static long
+capread(Chan *c, void *va, long n, vlong vl)
+{
+ USED(vl);
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, va, n, capdir, ncapdir, devgen);
+
+ default:
+ error(Eperm);
+ break;
+ }
+ return n;
+}
+
+static int
+capwritehash(uchar *a, int l)
+{
+ Caps *c;
+
+ if(l != SHA1dlen)
+ return -1;
+ c = malloc(sizeof(*c));
+ if(c == nil)
+ return -1;
+ memmove(c->hash, a, l);
+ c->time = 0;
+ qlock(&allcaps.l);
+ c->next = allcaps.caps;
+ allcaps.caps = c;
+ if(!allcaps.kpstarted){
+ allcaps.kpstarted = 1;
+ kproc("capwatch", capwatch, 0, 0);
+ }
+ qunlock(&allcaps.l);
+ return 0;
+}
+
+static int
+capwriteuse(uchar *a, int len)
+{
+ int n;
+ uchar digest[SHA1dlen];
+ char buf[128], *p, *users[3];
+ Caps *c, **l;
+
+ if(len >= sizeof(buf)-1)
+ return -1;
+ memmove(buf, a, len);
+ buf[len] = 0;
+ p = strrchr(buf, '@');
+ if(p == nil)
+ return -1;
+ *p++ = 0;
+ len = strlen(p);
+ n = strlen(buf);
+ if(len == 0 || n == 0)
+ return -1;
+ hmac_sha1((uchar*)buf, n, (uchar*)p, len, digest, nil);
+ n = getfields(buf, users, nelem(users), 0, "@");
+ if(n == 1)
+ users[1] = users[0];
+ else if(n != 2)
+ return -1;
+ if(*users[0] == 0 || *users[1] == 0)
+ return -1;
+ qlock(&allcaps.l);
+ for(l = &allcaps.caps; (c = *l) != nil; l = &c->next)
+ if(memcmp(c->hash, digest, sizeof(c->hash)) == 0){
+ *l = c->next;
+ qunlock(&allcaps.l);
+ free(c);
+ if(n == 2 && strcmp(up->env->user, users[0]) != 0)
+ return -1;
+ kstrdup(&up->env->user, users[1]);
+ return 0;
+ }
+ qunlock(&allcaps.l);
+ return -1;
+}
+
+static long
+capwrite(Chan* c, void* buf, long n, vlong offset)
+{
+ USED(offset);
+ switch((ulong)c->qid.path){
+ case Qhash:
+ if(capwritehash(buf, n) < 0)
+ error(Ebadarg);
+ return n;
+ case Quse:
+ if(capwriteuse(buf, n) < 0)
+ error("invalid capability");
+ return n;
+ }
+ error(Ebadarg);
+ return 0;
+}
+
+static void
+capremove(Chan *c)
+{
+ if(c->qid.path != Qhash || !iseve())
+ error(Eperm);
+ ncapdir = nelem(capdir)-1;
+}
+
+Dev capdevtab = {
+ L'¤',
+ "cap",
+
+ devreset,
+ devinit,
+ devshutdown,
+ capattach,
+ capwalk,
+ capstat,
+ capopen,
+ devcreate,
+ capclose,
+ capread,
+ devbread,
+ capwrite,
+ devbwrite,
+ capremove,
+ devwstat
+};
diff --git a/os/port/devcons.c b/os/port/devcons.c
new file mode 100644
index 00000000..7460cffc
--- /dev/null
+++ b/os/port/devcons.c
@@ -0,0 +1,1274 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <version.h>
+#include "mp.h"
+#include "libsec.h"
+#include "keyboard.h"
+
+extern int cflag;
+extern int keepbroken;
+
+void (*serwrite)(char *, int);
+
+Queue* kscanq; /* keyboard raw scancodes (when needed) */
+char* kscanid; /* name of raw scan format (if defined) */
+Queue* kbdq; /* unprocessed console input */
+Queue* lineq; /* processed console input */
+Queue* printq; /* console output */
+Queue* klogq; /* kernel print (log) output */
+int iprintscreenputs;
+
+static struct
+{
+ RWlock;
+ Queue* q;
+} kprintq;
+
+static struct
+{
+ QLock;
+
+ int raw; /* true if we shouldn't process input */
+ int ctl; /* number of opens to the control file */
+ int kbdr; /* number of open reads to the keyboard */
+ int scan; /* true if reading raw scancodes */
+ int x; /* index into line */
+ char line[1024]; /* current input line */
+
+ char c;
+ int count;
+ int repeat;
+} kbd;
+
+char* sysname;
+char* eve;
+
+enum
+{
+ CMreboot,
+ CMhalt,
+ CMpanic,
+ CMbroken,
+ CMnobroken,
+ CMconsole,
+};
+
+static Cmdtab sysctlcmd[] =
+{
+ CMreboot, "reboot", 0,
+ CMhalt, "halt", 0,
+ CMpanic, "panic", 0,
+ CMconsole, "console", 1,
+ CMbroken, "broken", 0,
+ CMnobroken, "nobroken", 0,
+};
+
+void
+printinit(void)
+{
+ lineq = qopen(2*1024, 0, nil, nil);
+ if(lineq == nil)
+ panic("printinit");
+ qnoblock(lineq, 1);
+}
+
+/*
+ * return true if current user is eve
+ */
+int
+iseve(void)
+{
+ Osenv *o;
+
+ o = up->env;
+ return strcmp(eve, o->user) == 0;
+}
+
+static int
+consactive(void)
+{
+ if(printq)
+ return qlen(printq) > 0;
+ return 0;
+}
+
+static void
+prflush(void)
+{
+ ulong now;
+
+ now = m->ticks;
+ while(serwrite==nil && consactive())
+ if(m->ticks - now >= HZ)
+ break;
+}
+
+/*
+ * 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;
+ char buf[PRINTSIZE+2];
+
+ /*
+ * if kprint is open, put the message there, otherwise
+ * if there's an attached bit mapped display,
+ * put the message there.
+ */
+ m = consoleprint;
+ if(canrlock(&kprintq)){
+ if(kprintq.q != nil){
+ if(waserror()){
+ runlock(&kprintq);
+ nexterror();
+ }
+ if(usewrite)
+ qwrite(kprintq.q, str, n);
+ else
+ qiwrite(kprintq.q, str, n);
+ poperror();
+ m = 0;
+ }
+ runlock(&kprintq);
+ }
+ if(m && screenputs != nil)
+ screenputs(str, n);
+
+ /*
+ * if there's a serial line being used as a console,
+ * put the message there.
+ */
+ if(serwrite != nil) {
+ serwrite(str, n);
+ return;
+ }
+
+ if(printq == 0)
+ return;
+
+ while(n > 0) {
+ t = memchr(str, '\n', n);
+ if(t && !kbd.raw) {
+ m = t - str;
+ if(m > sizeof(buf)-2)
+ m = sizeof(buf)-2;
+ memmove(buf, str, m);
+ buf[m] = '\r';
+ buf[m+1] = '\n';
+ if(usewrite)
+ qwrite(printq, buf, m+2);
+ else
+ qiwrite(printq, buf, m+2);
+ str = t + 1;
+ n -= m + 1;
+ } else {
+ if(usewrite)
+ qwrite(printq, str, n);
+ else
+ qiwrite(printq, str, n);
+ break;
+ }
+ }
+}
+
+void
+putstrn(char *str, int n)
+{
+ putstrn0(str, n, 0);
+}
+
+int
+snprint(char *s, int n, char *fmt, ...)
+{
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = vseprint(s, s+n, fmt, arg) - s;
+ va_end(arg);
+
+ return n;
+}
+
+int
+sprint(char *s, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = vseprint(s, s+PRINTSIZE, fmt, arg) - s;
+ va_end(arg);
+
+ return n;
+}
+
+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;
+}
+
+int
+fprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ USED(fd);
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ putstrn(buf, n);
+
+ return n;
+}
+
+int
+kprint(char *fmt, ...)
+{
+ va_list arg;
+ char buf[PRINTSIZE];
+ int n;
+
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ if(qfull(klogq))
+ qflush(klogq);
+ return qproduce(klogq, buf, n);
+}
+
+int
+iprint(char *fmt, ...)
+{
+ int n, s;
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ s = splhi();
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ if(screenputs != nil && iprintscreenputs)
+ screenputs(buf, n);
+ uartputs(buf, n);
+ splx(s);
+
+ return n;
+}
+
+void
+panic(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ setpanic();
+ kprintq.q = nil;
+ strcpy(buf, "panic: ");
+ va_start(arg, fmt);
+ n = vseprint(buf+strlen(buf), buf+sizeof(buf)-1, fmt, arg) - buf;
+ va_end(arg);
+ buf[n] = '\n';
+ putstrn(buf, n+1);
+ spllo();
+ dumpstack();
+
+ exit(1);
+}
+
+void
+_assert(char *fmt)
+{
+ panic("assert failed: %s", fmt);
+}
+
+void
+sysfatal(char *fmt, ...)
+{
+ va_list arg;
+ char buf[64];
+
+ va_start(arg, fmt);
+ vsnprint(buf, sizeof(buf), fmt, arg);
+ va_end(arg);
+ panic("sysfatal: %s", buf);
+}
+
+int
+pprint(char *fmt, ...)
+{
+ int n;
+ Chan *c;
+ Osenv *o;
+ va_list arg;
+ char buf[2*PRINTSIZE];
+
+ n = sprint(buf, "%s %ld: ", up->text, up->pid);
+ va_start(arg, fmt);
+ n = vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+
+ o = up->env;
+ if(o->fgrp == 0) {
+ print("%s", buf);
+ return 0;
+ }
+ c = o->fgrp->fd[2];
+ if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR)) {
+ print("%s", buf);
+ return 0;
+ }
+
+ if(waserror()) {
+ print("%s", buf);
+ return 0;
+ }
+ devtab[c->type]->write(c, buf, n, c->offset);
+ poperror();
+
+ lock(c);
+ c->offset += n;
+ unlock(c);
+
+ return n;
+}
+
+void
+echo(Rune r, char *buf, int n)
+{
+ if(kbd.raw)
+ return;
+
+ if(r == '\n'){
+ if(printq)
+ qiwrite(printq, "\r", 1);
+ } else if(r == 0x15){
+ buf = "^U\n";
+ n = 3;
+ }
+ if(consoleprint && screenputs != nil)
+ screenputs(buf, n);
+ if(printq)
+ qiwrite(printq, buf, n);
+}
+
+/*
+ * Debug key support. Allows other parts of the kernel to register debug
+ * key handlers, instead of devcons.c having to know whatever's out there.
+ * A kproc is used to invoke most handlers, rather than tying up the CPU at
+ * splhi, which can choke some device drivers (eg softmodem).
+ */
+typedef struct {
+ Rune r;
+ char *m;
+ void (*f)(Rune);
+ int i; /* function called at interrupt time */
+} Dbgkey;
+
+static struct {
+ Rendez;
+ Dbgkey *work;
+ Dbgkey keys[50];
+ int nkeys;
+ int on;
+} dbg;
+
+static Dbgkey *
+finddbgkey(Rune r)
+{
+ int i;
+ Dbgkey *dp;
+
+ for(dp = dbg.keys, i = 0; i < dbg.nkeys; i++, dp++)
+ if(dp->r == r)
+ return dp;
+ return nil;
+}
+
+static int
+dbgwork(void *)
+{
+ return dbg.work != 0;
+}
+
+static void
+dbgproc(void *)
+{
+ Dbgkey *dp;
+
+ setpri(PriRealtime);
+ for(;;) {
+ do {
+ sleep(&dbg, dbgwork, 0);
+ dp = dbg.work;
+ } while(dp == nil);
+ dp->f(dp->r);
+ dbg.work = nil;
+ }
+}
+
+void
+debugkey(Rune r, char *msg, void (*fcn)(), int iflag)
+{
+ Dbgkey *dp;
+
+ if(dbg.nkeys >= nelem(dbg.keys))
+ return;
+ if(finddbgkey(r) != nil)
+ return;
+ for(dp = &dbg.keys[dbg.nkeys++] - 1; dp >= dbg.keys; dp--) {
+ if(strcmp(dp->m, msg) < 0)
+ break;
+ dp[1] = dp[0];
+ }
+ dp++;
+ dp->r = r;
+ dp->m = msg;
+ dp->f = fcn;
+ dp->i = iflag;
+}
+
+static int
+isdbgkey(Rune r)
+{
+ static int ctrlt;
+ Dbgkey *dp;
+ int echoctrlt = ctrlt;
+
+ /*
+ * ^t hack BUG
+ */
+ if(dbg.on || (ctrlt >= 2)) {
+ if(r == 0x14 || r == 0x05) {
+ ctrlt++;
+ return 0;
+ }
+ if(dp = finddbgkey(r)) {
+ if(dp->i || ctrlt > 2)
+ dp->f(r);
+ else {
+ dbg.work = dp;
+ wakeup(&dbg);
+ }
+ ctrlt = 0;
+ return 1;
+ }
+ ctrlt = 0;
+ }
+ else if(r == 0x14){
+ ctrlt++;
+ return 1;
+ }
+ else
+ ctrlt = 0;
+ if(echoctrlt){
+ char buf[3];
+
+ buf[0] = 0x14;
+ while(--echoctrlt >= 0){
+ echo(buf[0], buf, 1);
+ qproduce(kbdq, buf, 1);
+ }
+ }
+ return 0;
+}
+
+static void
+dbgtoggle(Rune)
+{
+ dbg.on = !dbg.on;
+ print("Debug keys %s\n", dbg.on ? "HOT" : "COLD");
+}
+
+static void
+dbghelp(void)
+{
+ int i;
+ Dbgkey *dp;
+ Dbgkey *dp2;
+ static char fmt[] = "%c: %-22s";
+
+ dp = dbg.keys;
+ dp2 = dp + (dbg.nkeys + 1)/2;
+ for(i = dbg.nkeys; i > 1; i -= 2, dp++, dp2++) {
+ print(fmt, dp->r, dp->m);
+ print(fmt, dp2->r, dp2->m);
+ print("\n");
+ }
+ if(i)
+ print(fmt, dp->r, dp->m);
+ print("\n");
+}
+
+static void
+debuginit(void)
+{
+ kproc("consdbg", dbgproc, nil, 0);
+ debugkey('|', "HOT|COLD keys", dbgtoggle, 0);
+ debugkey('?', "help", dbghelp, 0);
+}
+
+/*
+ * Called by a uart interrupt for console input.
+ *
+ * turn '\r' into '\n' before putting it into the queue.
+ */
+int
+kbdcr2nl(Queue *q, int ch)
+{
+ if(ch == '\r')
+ ch = '\n';
+ return kbdputc(q, ch);
+}
+
+/*
+ * Put character, possibly a rune, into read queue at interrupt time.
+ * Performs translation for compose sequences
+ * Called at interrupt time to process a character.
+ */
+int
+kbdputc(Queue *q, int ch)
+{
+ int n;
+ char buf[3];
+ Rune r;
+ static Rune kc[15];
+ static int nk, collecting = 0;
+
+ r = ch;
+ if(r == Latin) {
+ collecting = 1;
+ nk = 0;
+ return 0;
+ }
+ if(collecting) {
+ int c;
+ nk += runetochar((char*)&kc[nk], &r);
+ c = latin1(kc, nk);
+ if(c < -1) /* need more keystrokes */
+ return 0;
+ collecting = 0;
+ if(c == -1) { /* invalid sequence */
+ echo(kc[0], (char*)kc, nk);
+ qproduce(q, kc, nk);
+ return 0;
+ }
+ r = (Rune)c;
+ }
+ kbd.c = r;
+ n = runetochar(buf, &r);
+ if(n == 0)
+ return 0;
+ if(!isdbgkey(r)) {
+ echo(r, buf, n);
+ qproduce(q, buf, n);
+ }
+ return 0;
+}
+
+void
+kbdrepeat(int rep)
+{
+ kbd.repeat = rep;
+ kbd.count = 0;
+}
+
+void
+kbdclock(void)
+{
+ if(kbd.repeat == 0)
+ return;
+ if(kbd.repeat==1 && ++kbd.count>HZ){
+ kbd.repeat = 2;
+ kbd.count = 0;
+ return;
+ }
+ if(++kbd.count&1)
+ kbdputc(kbdq, kbd.c);
+}
+
+enum{
+ Qdir,
+ Qcons,
+ Qsysctl,
+ Qconsctl,
+ Qdrivers,
+ Qhostowner,
+ Qkeyboard,
+ Qklog,
+ Qkprint,
+ Qscancode,
+ Qmemory,
+ Qmsec,
+ Qnull,
+ Qpin,
+ Qrandom,
+ Qnotquiterandom,
+ Qsysname,
+ Qtime,
+ Quser,
+ Qjit,
+};
+
+static Dirtab consdir[]=
+{
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "cons", {Qcons}, 0, 0660,
+ "consctl", {Qconsctl}, 0, 0220,
+ "sysctl", {Qsysctl}, 0, 0644,
+ "drivers", {Qdrivers}, 0, 0444,
+ "hostowner", {Qhostowner}, 0, 0644,
+ "keyboard", {Qkeyboard}, 0, 0666,
+ "klog", {Qklog}, 0, 0444,
+ "kprint", {Qkprint}, 0, 0444,
+ "scancode", {Qscancode}, 0, 0444,
+ "memory", {Qmemory}, 0, 0444,
+ "msec", {Qmsec}, NUMSIZE, 0444,
+ "null", {Qnull}, 0, 0666,
+ "pin", {Qpin}, 0, 0666,
+ "random", {Qrandom}, 0, 0444,
+ "notquiterandom", {Qnotquiterandom}, 0, 0444,
+ "sysname", {Qsysname}, 0, 0664,
+ "time", {Qtime}, 0, 0664,
+ "user", {Quser}, 0, 0644,
+ "jit", {Qjit}, 0, 0666,
+};
+
+ulong boottime; /* seconds since epoch at boot */
+
+long
+seconds(void)
+{
+ return boottime + TK2SEC(MACHP(0)->ticks);
+}
+
+vlong
+mseconds(void)
+{
+ return ((vlong)boottime*1000)+((vlong)(TK2MS(MACHP(0)->ticks)));
+}
+
+vlong
+osusectime(void)
+{
+ return (((vlong)boottime*1000)+((vlong)(TK2MS(MACHP(0)->ticks)))*1000);
+}
+
+vlong
+nsec(void)
+{
+ return osusectime()*1000; /* TO DO */
+}
+
+int
+readnum(ulong off, char *buf, ulong n, ulong val, int size)
+{
+ char tmp[64];
+
+ if(size > 64) size = 64;
+
+ snprint(tmp, sizeof(tmp), "%*.0lud ", size, val);
+ 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;
+}
+
+void
+fddump()
+{
+ Proc *p;
+ Osenv *o;
+ int i;
+ Chan *c;
+
+ p = proctab(6);
+ o = p->env;
+ for(i = 0; i <= o->fgrp->maxfd; i++) {
+ if((c = o->fgrp->fd[i]) == nil)
+ continue;
+ print("%d: %s\n", i, c->name == nil? "???": c->name->s);
+ }
+}
+
+static void
+qpanic(Rune)
+{
+ panic("User requested panic.");
+}
+
+static void
+rexit(Rune)
+{
+ exit(0);
+}
+
+static void
+consinit(void)
+{
+ randominit();
+ debuginit();
+ debugkey('f', "files/6", fddump, 0);
+ debugkey('q', "panic", qpanic, 1);
+ debugkey('r', "exit", rexit, 1);
+ klogq = qopen(128*1024, 0, 0, 0);
+}
+
+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 void
+flushkbdline(Queue *q)
+{
+ if(kbd.x){
+ qwrite(q, kbd.line, kbd.x);
+ kbd.x = 0;
+ }
+}
+
+static Chan*
+consopen(Chan *c, int omode)
+{
+ c->aux = 0;
+ switch((ulong)c->qid.path){
+ case Qconsctl:
+ if(!iseve())
+ error(Eperm);
+ qlock(&kbd);
+ kbd.ctl++;
+ qunlock(&kbd);
+ break;
+
+ case Qkeyboard:
+ if((omode & 3) != OWRITE) {
+ qlock(&kbd);
+ kbd.kbdr++;
+ flushkbdline(kbdq);
+ kbd.raw = 1;
+ qunlock(&kbd);
+ }
+ break;
+
+ case Qscancode:
+ qlock(&kbd);
+ if(kscanq || !kscanid) {
+ qunlock(&kbd);
+ c->flag &= ~COPEN;
+ if(kscanq)
+ error(Einuse);
+ else
+ error(Ebadarg);
+ }
+ kscanq = qopen(256, 0, nil, nil);
+ qunlock(&kbd);
+ break;
+
+ case Qkprint:
+ if((omode & 3) != OWRITE) {
+ wlock(&kprintq);
+ if(kprintq.q != nil){
+ wunlock(&kprintq);
+ error(Einuse);
+ }
+ kprintq.q = qopen(32*1024, Qcoalesce, nil, nil);
+ if(kprintq.q == nil){
+ wunlock(&kprintq);
+ error(Enomem);
+ }
+ qnoblock(kprintq.q, 1);
+ wunlock(&kprintq);
+ c->iounit = qiomaxatomic;
+ }
+ break;
+ }
+ return devopen(c, omode, consdir, nelem(consdir), devgen);
+}
+
+static void
+consclose(Chan *c)
+{
+ if((c->flag&COPEN) == 0)
+ return;
+
+ switch((ulong)c->qid.path){
+ case Qconsctl:
+ /* last close of control file turns off raw */
+ qlock(&kbd);
+ if(--kbd.ctl == 0)
+ kbd.raw = 0;
+ qunlock(&kbd);
+ break;
+
+ case Qkeyboard:
+ if(c->mode != OWRITE) {
+ qlock(&kbd);
+ --kbd.kbdr;
+ qunlock(&kbd);
+ }
+ break;
+
+ case Qscancode:
+ qlock(&kbd);
+ if(kscanq) {
+ qfree(kscanq);
+ kscanq = 0;
+ }
+ qunlock(&kbd);
+ break;
+
+ case Qkprint:
+ wlock(&kprintq);
+ qfree(kprintq.q);
+ kprintq.q = nil;
+ wunlock(&kprintq);
+ break;
+ }
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong offset)
+{
+ int l;
+ Osenv *o;
+ int ch, eol, i;
+ char *p, tmp[128];
+ char *cbuf = buf;
+
+ if(n <= 0)
+ return n;
+ o = up->env;
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
+ case Qsysctl:
+ return readstr(offset, buf, n, VERSION);
+ case Qcons:
+ case Qkeyboard:
+ qlock(&kbd);
+ if(waserror()) {
+ qunlock(&kbd);
+ nexterror();
+ }
+ if(kbd.raw || kbd.kbdr) {
+ if(qcanread(lineq))
+ n = qread(lineq, buf, n);
+ else {
+ /* read as much as possible */
+ do {
+ i = qread(kbdq, cbuf, n);
+ cbuf += i;
+ n -= i;
+ } while(n>0 && qcanread(kbdq));
+ n = cbuf - (char*)buf;
+ }
+ } else {
+ while(!qcanread(lineq)) {
+ qread(kbdq, &kbd.line[kbd.x], 1);
+ ch = kbd.line[kbd.x];
+ eol = 0;
+ switch(ch){
+ case '\b':
+ if(kbd.x)
+ kbd.x--;
+ break;
+ case 0x15:
+ kbd.x = 0;
+ break;
+ case '\n':
+ case 0x04:
+ eol = 1;
+ default:
+ kbd.line[kbd.x++] = ch;
+ break;
+ }
+ if(kbd.x == sizeof(kbd.line) || eol) {
+ if(ch == 0x04)
+ kbd.x--;
+ qwrite(lineq, kbd.line, kbd.x);
+ kbd.x = 0;
+ }
+ }
+ n = qread(lineq, buf, n);
+ }
+ qunlock(&kbd);
+ poperror();
+ return n;
+
+ case Qscancode:
+ if(offset == 0)
+ return readstr(0, buf, n, kscanid);
+ else
+ return qread(kscanq, buf, n);
+
+ case Qpin:
+ p = "pin set";
+ if(up->env->pgrp->pin == Nopin)
+ p = "no pin";
+ return readstr(offset, buf, n, p);
+
+ case Qtime:
+ snprint(tmp, sizeof(tmp), "%.lld", (vlong)mseconds()*1000);
+ return readstr(offset, buf, n, tmp);
+
+ case Qhostowner:
+ return readstr(offset, buf, n, eve);
+
+ case Quser:
+ return readstr(offset, buf, n, o->user);
+
+ case Qjit:
+ snprint(tmp, sizeof(tmp), "%d", cflag);
+ return readstr(offset, buf, n, tmp);
+
+ case Qnull:
+ return 0;
+
+ case Qmsec:
+ return readnum(offset, buf, n, TK2MS(MACHP(0)->ticks), NUMSIZE);
+
+ case Qsysname:
+ if(sysname == nil)
+ return 0;
+ return readstr(offset, buf, n, sysname);
+
+ case Qnotquiterandom:
+ genrandom(buf, n);
+ return n;
+
+ case Qrandom:
+ return randomread(buf, n);
+
+ case Qmemory:
+ return poolread(buf, n, offset);
+
+ case Qdrivers:
+ p = malloc(READSTR);
+ if(p == nil)
+ error(Enomem);
+ l = 0;
+ for(i = 0; devtab[i] != nil; i++)
+ l += snprint(p+l, READSTR-l, "#%C %s\n", devtab[i]->dc, devtab[i]->name);
+ if(waserror()){
+ free(p);
+ nexterror();
+ }
+ n = readstr(offset, buf, n, p);
+ free(p);
+ poperror();
+ return n;
+
+ case Qklog:
+ return qread(klogq, buf, n);
+
+ case Qkprint:
+ rlock(&kprintq);
+ if(waserror()){
+ runlock(&kprintq);
+ nexterror();
+ }
+ n = qread(kprintq.q, buf, n);
+ poperror();
+ runlock(&kprintq);
+ return n;
+
+ default:
+ print("consread %llud\n", c->qid.path);
+ error(Egreg);
+ }
+ return -1; /* never reached */
+}
+
+static long
+conswrite(Chan *c, void *va, long n, vlong offset)
+{
+ vlong t;
+ long l, bp;
+ char *a = va;
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ char buf[256];
+ int x;
+
+ 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(a, 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){
+ qlock(&kbd);
+ flushkbdline(kbdq);
+ kbd.raw = 1;
+ qunlock(&kbd);
+ } else if(strncmp(a, "rawoff", 6) == 0){
+ qlock(&kbd);
+ kbd.raw = 0;
+ kbd.x = 0;
+ qunlock(&kbd);
+ }
+ if(a = strchr(a, ' '))
+ a++;
+ }
+ break;
+
+ case Qkeyboard:
+ for(x=0; x<n; ) {
+ Rune r;
+ x += chartorune(&r, &a[x]);
+ kbdputc(kbdq, r);
+ }
+ break;
+
+ case Qtime:
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ strncpy(buf, a, n);
+ buf[n] = 0;
+ t = strtoll(buf, 0, 0)/1000000;
+ boottime = t - TK2SEC(MACHP(0)->ticks);
+ break;
+
+ case Qhostowner:
+ if(!iseve())
+ error(Eperm);
+ if(offset != 0 || n >= sizeof(buf))
+ error(Ebadarg);
+ memmove(buf, a, n);
+ buf[n] = '\0';
+ if(n > 0 && buf[n-1] == '\n')
+ buf[--n] = 0;
+ if(n <= 0)
+ error(Ebadarg);
+ renameuser(eve, buf);
+ renameproguser(eve, buf);
+ kstrdup(&eve, buf);
+ kstrdup(&up->env->user, buf);
+ break;
+
+ case Quser:
+ if(!iseve())
+ error(Eperm);
+ 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(&up->env->user, buf);
+ break;
+
+ case Qjit:
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ strncpy(buf, va, n);
+ buf[n] = '\0';
+ x = atoi(buf);
+ if(x < 0 || x > 9)
+ error(Ebadarg);
+ cflag = x;
+ return n;
+
+ case Qnull:
+ break;
+
+ case Qpin:
+ if(up->env->pgrp->pin != Nopin)
+ error("pin already set");
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ strncpy(buf, va, n);
+ buf[n] = '\0';
+ up->env->pgrp->pin = atoi(buf);
+ return n;
+
+ 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 Qsysctl:
+ if(!iseve())
+ error(Eperm);
+ cb = parsecmd(a, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, sysctlcmd, nelem(sysctlcmd));
+ switch(ct->index){
+ case CMreboot:
+ reboot();
+ break;
+ case CMhalt:
+ halt();
+ break;
+ case CMpanic:
+ panic("sysctl");
+ case CMconsole:
+ consoleprint = strcmp(cb->f[1], "off") != 0;
+ break;
+ case CMbroken:
+ keepbroken = 1;
+ break;
+ case CMnobroken:
+ keepbroken = 0;
+ break;
+ }
+ poperror();
+ free(cb);
+ break;
+
+ default:
+ print("conswrite: %llud\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)
+{
+ randomread((void*)&randn, sizeof(randn));
+}
+
+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;
+}
+
+ulong
+truerand(void)
+{
+ ulong x;
+
+ randomread(&x, sizeof(x));
+ return x;
+}
+
+QLock grandomlk;
+
+void
+_genrandomqlock(void)
+{
+ qlock(&grandomlk);
+}
+
+
+void
+_genrandomqunlock(void)
+{
+ qunlock(&grandomlk);
+}
diff --git a/os/port/devdbg.c b/os/port/devdbg.c
new file mode 100644
index 00000000..daaf314c
--- /dev/null
+++ b/os/port/devdbg.c
@@ -0,0 +1,1000 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "ureg.h"
+#include "../port/error.h"
+#include "rdbg.h"
+
+#include <kernel.h>
+#include <interp.h>
+
+/*
+ * The following should be set in the config file to override
+ * the defaults.
+ */
+int dbgstart;
+char *dbgdata;
+char *dbgctl;
+char *dbgctlstart;
+char *dbgctlstop;
+char *dbgctlflush;
+
+//
+// Error messages sent to the remote debugger
+//
+static uchar Ereset[9] = { 'r', 'e', 's', 'e', 't' };
+static uchar Ecount[9] = { 'c', 'o', 'u', 'n', 't' };
+static uchar Eunk[9] = { 'u', 'n', 'k' };
+static uchar Einval[9] = { 'i', 'n', 'v', 'a', 'l' };
+static uchar Ebadpid[9] = {'p', 'i', 'd'};
+static uchar Eunsup[9] = { 'u', 'n', 's', 'u', 'p' };
+static uchar Enotstop[9] = { 'n', 'o', 't', 's', 't', 'o', 'p' };
+
+//
+// Error messages raised via call to error()
+//
+static char Erunning[] = "Not allowed while debugger is running";
+static char Enumarg[] = "Not enough args";
+static char Ebadcmd[] = "Unknown command";
+
+static int PROCREG;
+static struct {
+ Rendez;
+ Bkpt *b;
+} brk;
+
+static Queue *logq;
+
+int dbgchat = 0;
+
+typedef struct Debugger Debugger;
+struct Debugger {
+ RWlock;
+ int running;
+ char data[PRINTSIZE];
+ char ctl[PRINTSIZE];
+ char ctlstart[PRINTSIZE];
+ char ctlstop[PRINTSIZE];
+ char ctlflush[PRINTSIZE];
+};
+
+static Debugger debugger = {
+ .data= "#t/eia0",
+ .ctl= "#t/eia0ctl",
+ .ctlstart= "b19200",
+ .ctlstop= "h",
+ .ctlflush= "f",
+};
+
+enum {
+ BkptStackSize= 256,
+};
+
+typedef struct SkipArg SkipArg;
+struct SkipArg
+{
+ Bkpt *b;
+ Proc *p;
+};
+
+Bkpt *breakpoints;
+void freecondlist(BkptCond *l);
+
+static int
+getbreaks(ulong addr, Bkpt **a, int nb)
+{
+ Bkpt *b;
+ int n;
+
+ n = 0;
+ for(b = breakpoints; b != nil; b = b->next){
+ if(b->addr == addr){
+ a[n++] = b;
+ if(n == nb)
+ break;
+ }
+ }
+ return n;
+}
+
+Bkpt*
+newbreak(int id, ulong addr, BkptCond *conds, void(*handler)(Bkpt*), void *aux)
+{
+ Bkpt *b;
+
+ b = malloc(sizeof(*b));
+ if(b == nil)
+ error(Enomem);
+
+ b->id = id;
+ b->conditions = conds;
+ b->addr = addr;
+ b->handler = handler;
+ b->aux = aux;
+ b->next = nil;
+
+ return b;
+}
+
+void
+freebreak(Bkpt *b)
+{
+ freecondlist(b->conditions);
+ free(b);
+}
+
+BkptCond*
+newcondition(uchar cmd, ulong val)
+{
+ BkptCond *c;
+
+ c = mallocz(sizeof(*c), 0);
+ if(c == nil)
+ error(Enomem);
+
+ c->op = cmd;
+ c->val = val;
+ c->next = nil;
+
+ return c;
+}
+
+void
+freecondlist(BkptCond *l)
+{
+ BkptCond *next;
+
+ while(l != nil){
+ next = l->next;
+ free(l);
+ l = next;
+ }
+}
+
+
+void
+breakset(Bkpt *b)
+{
+ Bkpt *e[1];
+
+ if(getbreaks(b->addr, e, 1) != 0){
+ b->instr = e[0]->instr;
+ } else {
+ b->instr = machinstr(b->addr);
+ machbreakset(b->addr);
+ }
+
+ b->next = breakpoints;
+ breakpoints = b;
+}
+
+void
+breakrestore(Bkpt *b)
+{
+ b->next = breakpoints;
+ breakpoints = b;
+ machbreakset(b->addr);
+}
+
+Bkpt*
+breakclear(int id)
+{
+ Bkpt *b, *e, *p;
+
+ for(b=breakpoints, p=nil; b != nil && b->id != id; p = b, b = b->next)
+ ;
+
+ if(b != nil){
+ if(p == nil)
+ breakpoints = b->next;
+ else
+ p->next = b->next;
+
+ if(getbreaks(b->addr, &e, 1) == 0)
+ machbreakclear(b->addr, b->instr);
+ }
+
+ return b;
+}
+
+void
+breaknotify(Bkpt *b, Proc *p)
+{
+ p->dbgstop = 1; // stop running this process.
+ b->handler(b);
+}
+
+int
+breakmatch(BkptCond *cond, Ureg *ur, Proc *p)
+{
+ ulong a, b;
+ int pos;
+ ulong s[BkptStackSize];
+
+ memset(s, 0, sizeof(s));
+ pos = 0;
+
+ for(;cond != nil; cond = cond->next){
+ switch(cond->op){
+ default:
+ panic("breakmatch: unknown operator %c", cond->op);
+ break;
+ case 'k':
+ if(p == nil || p->pid != cond->val)
+ return 0;
+ s[pos++] = 1;
+ break;
+ case 'b':
+ if(ur->pc != cond->val)
+ return 0;
+ s[pos++] = 1;
+ break;
+ case 'p': s[pos++] = cond->val; break;
+ case '*': a = *(ulong*)s[--pos]; s[pos++] = a; break;
+ case '&': a = s[--pos]; b = s[--pos]; s[pos++] = a & b; break;
+ case '=': a = s[--pos]; b = s[--pos]; s[pos++] = a == b; break;
+ case '!': a = s[--pos]; b = s[--pos]; s[pos++] = a != b; break;
+ case 'a': a = s[--pos]; b = s[--pos]; s[pos++] = a && b; break;
+ case 'o': a = s[--pos]; b = s[--pos]; s[pos++] = a || b; break;
+ }
+ }
+
+ if(pos && s[pos-1])
+ return 1;
+ return 0;
+}
+
+void
+breakinit(void)
+{
+ machbreakinit();
+}
+
+static void
+dbglog(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);
+ qwrite(logq, buf, n);
+}
+
+static int
+get(int dbgfd, uchar *b)
+{
+ int i;
+ uchar c;
+
+ if(kread(dbgfd, &c, 1) < 0)
+ error(Eio);
+ for(i=0; i<9; i++){
+ if(kread(dbgfd, b++, 1) < 0)
+ error(Eio);
+ }
+ return c;
+}
+
+static void
+mesg(int dbgfd, int m, uchar *buf)
+{
+ int i;
+ uchar c;
+
+ c = m;
+ if(kwrite(dbgfd, &c, 1) < 0)
+ error(Eio);
+ for(i=0; i<9; i++){
+ if(kwrite(dbgfd, buf+i, 1) < 0)
+ error(Eio);
+ }
+}
+
+static ulong
+dbglong(uchar *s)
+{
+ return (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]<<0);
+}
+
+static Proc *
+dbgproc(ulong pid, int dbgok)
+{
+ int i;
+ Proc *p;
+
+ if(!dbgok && pid == up->pid)
+ return 0;
+ p = proctab(0);
+ for(i = 0; i < conf.nproc; i++){
+ if(p->pid == pid)
+ return p;
+ p++;
+ }
+ return 0;
+}
+
+static void*
+addr(uchar *s)
+{
+ ulong a;
+ Proc *p;
+ static Ureg ureg;
+
+ a = ((s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]<<0));
+ if(a < sizeof(Ureg)){
+ p = dbgproc(PROCREG, 0);
+ if(p == 0){
+ dbglog("dbg: invalid pid\n");
+ return 0;
+ }
+ if(p->dbgreg){
+ /* in trap(), registers are all on stack */
+ memmove(&ureg, p->dbgreg, sizeof(ureg));
+ }
+ else {
+ /* not in trap, only pc and sp are available */
+ memset(&ureg, 0, sizeof(ureg));
+ ureg.sp = p->sched.sp;
+ ureg.pc = p->sched.pc;
+ }
+ return (uchar*)&ureg+a;
+ }
+ return (void*)a;
+}
+
+
+static void
+dumpcmd(uchar cmd, uchar *min)
+{
+ char *s;
+ int n;
+
+ switch(cmd){
+ case Terr: s = "Terr"; break;
+ case Tmget: s = "Tmget"; break;
+ case Tmput: s = "Tmput"; break;
+ case Tspid: s = "Tspid"; break;
+ case Tproc: s = "Tproc"; break;
+ case Tstatus: s = "Tstatus"; break;
+ case Trnote: s = "Trnote"; break;
+ case Tstartstop: s = "Tstartstop"; break;
+ case Twaitstop: s = "Twaitstop"; break;
+ case Tstart: s = "Tstart"; break;
+ case Tstop: s = "Tstop"; break;
+ case Tkill: s = "Tkill"; break;
+ case Tcondbreak: s = "Tcondbreak"; break;
+ default: s = "<Unknown>"; break;
+ }
+ dbglog("%s: [%2.2ux]: ", s, cmd);
+ for(n = 0; n < 9; n++)
+ dbglog("%2.2ux", min[n]);
+ dbglog("\n");
+}
+
+static int
+brkpending(void *a)
+{
+ Proc *p;
+
+ p = a;
+ if(brk.b != nil) return 1;
+
+ p->dbgstop = 0; /* atomic */
+ if(p->state == Stopped)
+ ready(p);
+
+ return 0;
+}
+
+static void
+gotbreak(Bkpt *b)
+{
+ Bkpt *cur, *prev;
+
+ b->link = nil;
+
+ for(prev = nil, cur = brk.b; cur != nil; prev = cur, cur = cur->link)
+ ;
+ if(prev == nil)
+ brk.b = b;
+ else
+ prev->link = b;
+
+ wakeup(&brk);
+}
+
+static int
+startstop(Proc *p)
+{
+ int id;
+ int s;
+ Bkpt *b;
+
+ sleep(&brk, brkpending, p);
+
+ s = splhi();
+ b = brk.b;
+ brk.b = b->link;
+ splx(s);
+
+ id = b->id;
+
+ return id;
+}
+
+static int
+condbreak(char cmd, ulong val)
+{
+ BkptCond *c;
+ static BkptCond *head = nil;
+ static BkptCond *tail = nil;
+ static Proc *p = nil;
+ static int id = -1;
+ int s;
+
+ if(waserror()){
+ dbglog(up->env->errstr);
+ freecondlist(head);
+ head = tail = nil;
+ p = nil;
+ id = -1;
+ return 0;
+ }
+
+ switch(cmd){
+ case 'b': case 'p':
+ case '*': case '&': case '=':
+ case '!': case 'a': case 'o':
+ break;
+ case 'n':
+ id = val;
+ poperror();
+ return 1;
+ case 'k':
+ p = dbgproc(val, 0);
+ if(p == nil)
+ error("k: unknown pid");
+ break;
+ case 'd': {
+ Bkpt *b;
+
+ s = splhi();
+ b = breakclear(val);
+ if(b != nil){
+ Bkpt *cur, *prev;
+
+ prev = nil;
+ cur = brk.b;
+ while(cur != nil){
+ if(cur->id == b->id){
+ if(prev == nil)
+ brk.b = cur->link;
+ else
+ prev->link = cur->link;
+ break;
+ }
+ cur = cur->link;
+ }
+ freebreak(b);
+ }
+ splx(s);
+ poperror();
+ return 1;
+ }
+ default:
+ dbglog("condbreak(): unknown op %c %lux\n", cmd, val);
+ error("unknown op");
+ }
+
+ c = newcondition(cmd, val);
+
+ //
+ // the 'b' command comes last, (so we know we have reached the end
+ // of the condition list), but it should be the first thing
+ // checked, so put it at the head.
+ //
+ if(cmd == 'b'){
+ if(p == nil) error("no pid");
+ if(id == -1) error("no id");
+
+ c->next = head;
+ s = splhi();
+ breakset(newbreak(id, val, c, gotbreak, p));
+ splx(s);
+ head = tail = nil;
+ p = nil;
+ id = -1;
+ } else if(tail != nil){
+ tail->next = c;
+ tail = c;
+ } else
+ head = tail = c;
+
+ poperror();
+
+ return 1;
+}
+
+static void
+dbg(void*)
+{
+ Proc *p;
+ ulong val;
+ int n, cfd, dfd;
+ uchar cmd, *a, min[RDBMSGLEN-1], mout[RDBMSGLEN-1];
+
+ rlock(&debugger);
+
+ setpri(PriRealtime);
+
+ closefgrp(up->env->fgrp);
+ up->env->fgrp = newfgrp(nil);
+
+ if(waserror()){
+ dbglog("dbg: quits: %s\n", up->env->errstr);
+ runlock(&debugger);
+ wlock(&debugger);
+ debugger.running = 0;
+ wunlock(&debugger);
+ pexit("", 0);
+ }
+
+ dfd = kopen(debugger.data, ORDWR);
+ if(dfd < 0){
+ dbglog("dbg: can't open %s: %s\n",debugger.data, up->env->errstr);
+ error(Eio);
+ }
+ if(waserror()){
+ kclose(dfd);
+ nexterror();
+ }
+
+ if(debugger.ctl[0] != 0){
+ cfd = kopen(debugger.ctl, ORDWR);
+ if(cfd < 0){
+ dbglog("dbg: can't open %s: %s\n", debugger.ctl, up->env->errstr);
+ error(Eio);
+ }
+ if(kwrite(cfd, debugger.ctlstart, strlen(debugger.ctlstart)) < 0){
+ dbglog("dbg: write %s: %s\n", debugger.ctl, up->env->errstr);
+ error(Eio);
+ }
+ }else
+ cfd = -1;
+ if(waserror()){
+ if(cfd != -1){
+ kwrite(cfd, debugger.ctlflush, strlen(debugger.ctlflush));
+ kclose(cfd);
+ }
+ nexterror();
+ }
+
+ mesg(dfd, Rerr, Ereset);
+
+ for(;;){
+ memset(mout, 0, sizeof(mout));
+ cmd = get(dfd, min);
+ if(dbgchat)
+ dumpcmd(cmd, min);
+ switch(cmd){
+ case Tmget:
+ n = min[4];
+ if(n > 9){
+ mesg(dfd, Rerr, Ecount);
+ break;
+ }
+ a = addr(min+0);
+ if(!isvalid_va(a)){
+ mesg(dfd, Rerr, Einval);
+ break;
+ }
+ memmove(mout, a, n);
+ mesg(dfd, Rmget, mout);
+ break;
+ case Tmput:
+ n = min[4];
+ if(n > 4){
+ mesg(dfd, Rerr, Ecount);
+ break;
+ }
+ a = addr(min+0);
+ if(!isvalid_va(a)){
+ mesg(dfd, Rerr, Einval);
+ break;
+ }
+ memmove(a, min+5, n);
+ segflush(a, n);
+ mesg(dfd, Rmput, mout);
+ break;
+ case Tproc:
+ p = dbgproc(dbglong(min+0), 0);
+ if(p == 0){
+ mesg(dfd, Rerr, Ebadpid);
+ break;
+ }
+ PROCREG = p->pid; /* try this instead of Tspid */
+ sprint((char*)mout, "%8.8lux", p);
+ mesg(dfd, Rproc, mout);
+ break;
+ case Tstatus:
+ p = dbgproc(dbglong(min+0), 1);
+ if(p == 0){
+ mesg(dfd, Rerr, Ebadpid);
+ break;
+ }
+ if(p->state > Rendezvous || p->state < Dead)
+ sprint((char*)mout, "%8.8ux", p->state);
+ else if(p->dbgstop == 1)
+ strncpy((char*)mout, statename[Stopped], sizeof(mout));
+ else
+ strncpy((char*)mout, statename[p->state], sizeof(mout));
+ mesg(dfd, Rstatus, mout);
+ break;
+ case Trnote:
+ p = dbgproc(dbglong(min+0), 0);
+ if(p == 0){
+ mesg(dfd, Rerr, Ebadpid);
+ break;
+ }
+ mout[0] = 0; /* should be trap status, if any */
+ mesg(dfd, Rrnote, mout);
+ break;
+ case Tstop:
+ p = dbgproc(dbglong(min+0), 0);
+ if(p == 0){
+ mesg(dfd, Rerr, Ebadpid);
+ break;
+ }
+ p->dbgstop = 1; /* atomic */
+ mout[0] = 0;
+ mesg(dfd, Rstop, mout);
+ break;
+ case Tstart:
+ p = dbgproc(dbglong(min+0), 0);
+ if(p == 0){
+ mesg(dfd, Rerr, Ebadpid);
+ break;
+ }
+ p->dbgstop = 0; /* atomic */
+ if(p->state == Stopped)
+ ready(p);
+ mout[0] = 0;
+ mesg(dfd, Rstart, mout);
+ break;
+ case Tstartstop:
+ p = dbgproc(dbglong(min+0), 0);
+ if(p == 0){
+ mesg(dfd, Rerr, Ebadpid);
+ break;
+ }
+ if(!p->dbgstop){
+ mesg(dfd, Rerr, Enotstop);
+ break;
+ }
+ mout[0] = startstop(p);
+ mesg(dfd, Rstartstop, mout);
+ break;
+ case Tcondbreak:
+ val = dbglong(min+0);
+ if(!condbreak(min[4], val)){
+ mesg(dfd, Rerr, Eunk);
+ break;
+ }
+ mout[0] = 0;
+ mesg(dfd, Rcondbreak, mout);
+ break;
+ default:
+ dumpcmd(cmd, min);
+ mesg(dfd, Rerr, Eunk);
+ break;
+ }
+ }
+}
+
+static void
+dbgnote(Proc *p, Ureg *ur)
+{
+ if(p){
+ p->dbgreg = ur;
+ PROCREG = p->pid; /* acid can get the trap info from regs */
+ }
+}
+
+enum {
+ Qdir,
+ Qdbgctl,
+ Qdbglog,
+
+ DBGrun = 1,
+ DBGstop = 2,
+
+ Loglimit = 4096,
+};
+
+static Dirtab dbgdir[]=
+{
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "dbgctl", {Qdbgctl}, 0, 0660,
+ "dbglog", {Qdbglog}, 0, 0440,
+};
+
+static void
+start_debugger(void)
+{
+ breakinit();
+ dbglog("starting debugger\n");
+ debugger.running++;
+ kproc("dbg", dbg, 0, KPDUPPG);
+}
+
+static void
+dbginit(void)
+{
+
+ logq = qopen(Loglimit, 0, 0, 0);
+ if(logq == nil)
+ error(Enomem);
+ qnoblock(logq, 1);
+
+ wlock(&debugger);
+ if(waserror()){
+ wunlock(&debugger);
+ qfree(logq);
+ logq = nil;
+ nexterror();
+ }
+
+ if(dbgdata != nil){
+ strncpy(debugger.data, dbgdata, sizeof(debugger.data));
+ debugger.data[sizeof(debugger.data)-1] = 0;
+ }
+ if(dbgctl != nil){
+ strncpy(debugger.ctl, dbgctl, sizeof(debugger.ctl));
+ debugger.ctl[sizeof(debugger.ctl)-1] = 0;
+ }
+ if(dbgctlstart != nil){
+ strncpy(debugger.ctlstart, dbgctlstart, sizeof(debugger.ctlstart));
+ debugger.ctlstart[sizeof(debugger.ctlstart)-1] = 0;
+ }
+ if(dbgctlstop != nil){
+ strncpy(debugger.ctlstop, dbgctlstop, sizeof(debugger.ctlstop));
+ debugger.ctlstop[sizeof(debugger.ctlstop)-1] = 0;
+ }
+ if(dbgctlflush != nil){
+ strncpy(debugger.ctlflush, dbgctlflush, sizeof(debugger.ctlflush));
+ debugger.ctlflush[sizeof(debugger.ctlflush)-1] = 0;
+ }
+ if(dbgstart)
+ start_debugger();
+
+ poperror();
+ wunlock(&debugger);
+}
+
+static Chan*
+dbgattach(char *spec)
+{
+ return devattach('b', spec);
+}
+
+static Walkqid*
+dbgwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, dbgdir, nelem(dbgdir), devgen);
+}
+
+static int
+dbgstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, dbgdir, nelem(dbgdir), devgen);
+}
+
+static Chan*
+dbgopen(Chan *c, int omode)
+{
+ return devopen(c, omode, dbgdir, nelem(dbgdir), devgen);
+}
+
+static long
+dbgread(Chan *c, void *buf, long n, vlong offset)
+{
+ char *ctlstate;
+
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, buf, n, dbgdir, nelem(dbgdir), devgen);
+ case Qdbgctl:
+ rlock(&debugger);
+ ctlstate = smprint("%s data %s ctl %s ctlstart %s ctlstop %s ctlflush %s\n",
+ debugger.running ? "running" : "stopped",
+ debugger.data, debugger.ctl,
+ debugger.ctlstart, debugger.ctlstop, debugger.ctlflush);
+ runlock(&debugger);
+ if(ctlstate == nil)
+ error(Enomem);
+ if(waserror()){
+ free(ctlstate);
+ nexterror();
+ }
+ n = readstr(offset, buf, n, ctlstate);
+ poperror();
+ free(ctlstate);
+ return n;
+ case Qdbglog:
+ return qread(logq, buf, n);
+ default:
+ error(Egreg);
+ }
+ return -1; /* never reached */
+}
+
+static void
+ctl(Cmdbuf *cb)
+{
+ Debugger d;
+ int dbgstate = 0;
+ int i;
+ char *df;
+ int dfsize;
+ int setval;
+
+ memset(&d, 0, sizeof(d));
+ for(i=0; i < cb->nf; i++){
+ setval = 0;
+ df = nil;
+ dfsize = 0;
+ switch(cb->f[i][0]){
+ case 'd':
+ df = d.data;
+ dfsize = sizeof(d.data);
+ setval=1;
+ break;
+ case 'c':
+ df = d.ctl;
+ dfsize = sizeof(d.ctl);
+ setval=1;
+ break;
+ case 'i':
+ df = d.ctlstart;
+ dfsize = sizeof(d.ctlstart);
+ setval=1;
+ break;
+ case 'h':
+ df = d.ctlstop;
+ dfsize = sizeof(d.ctlstop);
+ setval=1;
+ break;
+ case 'f':
+ df = d.ctlflush;
+ dfsize = sizeof(d.ctlflush);
+ setval=1;
+ break;
+ case 'r':
+ dbgstate = DBGrun;
+ break;
+ case 's':
+ dbgstate = DBGstop;
+ break;
+ default:
+ error(Ebadcmd);
+ }
+ if(setval){
+ if(i+1 >= cb->nf)
+ cmderror(cb, Enumarg);
+ strncpy(df, cb->f[i+1], dfsize-1);
+ df[dfsize-1] = 0;
+ ++d.running;
+ ++i;
+ }
+ }
+
+ if(d.running){
+ wlock(&debugger);
+ if(debugger.running){
+ wunlock(&debugger);
+ error(Erunning);
+ }
+ if(d.data[0] != 0){
+ strcpy(debugger.data, d.data);
+ dbglog("data %s\n",debugger.data);
+ }
+ if(d.ctl[0] != 0){
+ strcpy(debugger.ctl, d.ctl);
+ dbglog("ctl %s\n",debugger.ctl);
+ }
+ if(d.ctlstart[0] != 0){
+ strcpy(debugger.ctlstart, d.ctlstart);
+ dbglog("ctlstart %s\n",debugger.ctlstart);
+ }
+ if(d.ctlstop[0] != 0){
+ strcpy(debugger.ctlstop, d.ctlstop);
+ dbglog("ctlstop %s\n",debugger.ctlstop);
+ }
+ wunlock(&debugger);
+ }
+
+ if(dbgstate == DBGrun){
+ if(!debugger.running){
+ wlock(&debugger);
+ if(waserror()){
+ wunlock(&debugger);
+ nexterror();
+ }
+ if(!debugger.running)
+ start_debugger();
+ else
+ dbglog("debugger already running\n");
+ poperror();
+ wunlock(&debugger);
+ } else
+ dbglog("debugger already running\n");
+ } else if(dbgstate == DBGstop){
+ if(debugger.running){
+ /* force hangup to stop the dbg process */
+ int cfd;
+ if(debugger.ctl[0] == 0)
+ return;
+ cfd = kopen(debugger.ctl, OWRITE);
+ if(cfd == -1)
+ error(up->env->errstr);
+ dbglog("stopping debugger\n");
+ if(kwrite(cfd, debugger.ctlstop, strlen(debugger.ctlstop)) == -1)
+ error(up->env->errstr);
+ kclose(cfd);
+ } else
+ dbglog("debugger not running\n");
+ }
+}
+
+static long
+dbgwrite(Chan *c, void *va, long n, vlong)
+{
+ Cmdbuf *cb;
+
+ switch((ulong)c->qid.path){
+ default:
+ error(Egreg);
+ break;
+ case Qdbgctl:
+ cb = parsecmd(va, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ ctl(cb);
+ poperror();
+ break;
+ }
+ return n;
+}
+
+static void
+dbgclose(Chan*)
+{
+}
+
+Dev dbgdevtab = {
+ 'b',
+ "dbg",
+
+ devreset,
+ dbginit,
+ devshutdown,
+ dbgattach,
+ dbgwalk,
+ dbgstat,
+ dbgopen,
+ devcreate,
+ dbgclose,
+ dbgread,
+ devbread,
+ dbgwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devdraw.c b/os/port/devdraw.c
new file mode 100644
index 00000000..a7c7151d
--- /dev/null
+++ b/os/port/devdraw.c
@@ -0,0 +1,2088 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#define Image IMAGE
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+#include <cursor.h>
+#include "screen.h"
+
+enum
+{
+ Qtopdir = 0,
+ Qnew,
+ Q3rd,
+ Q2nd,
+ Qcolormap,
+ Qctl,
+ Qdata,
+ Qrefresh,
+};
+
+/*
+ * Qid path is:
+ * 4 bits of file type (qids above)
+ * 24 bits of mux slot number +1; 0 means not attached to client
+ */
+#define QSHIFT 4 /* location in qid of client # */
+
+#define QID(q) ((((ulong)(q).path)&0x0000000F)>>0)
+#define CLIENTPATH(q) ((((ulong)q)&0x7FFFFFF0)>>QSHIFT)
+#define CLIENT(q) CLIENTPATH((q).path)
+
+#define NHASH (1<<5)
+#define HASHMASK (NHASH-1)
+#define IOUNIT (64*1024)
+
+typedef struct Client Client;
+typedef struct Draw Draw;
+typedef struct DImage DImage;
+typedef struct DScreen DScreen;
+typedef struct CScreen CScreen;
+typedef struct FChar FChar;
+typedef struct Refresh Refresh;
+typedef struct Refx Refx;
+typedef struct DName DName;
+
+ulong blanktime = 30; /* in minutes; a half hour */
+
+struct Draw
+{
+ QLock;
+ int clientid;
+ int nclient;
+ Client** client;
+ int nname;
+ DName* name;
+ int vers;
+ int softscreen;
+ int blanked; /* screen turned off */
+ ulong blanktime; /* time of last operation */
+ ulong savemap[3*256];
+};
+
+struct Client
+{
+ Ref r;
+ DImage* dimage[NHASH];
+ CScreen* cscreen;
+ Refresh* refresh;
+ Rendez refrend;
+ uchar* readdata;
+ int nreaddata;
+ int busy;
+ int clientid;
+ int slot;
+ int refreshme;
+ int infoid;
+ int op;
+};
+
+struct Refresh
+{
+ DImage* dimage;
+ Rectangle r;
+ Refresh* next;
+};
+
+struct Refx
+{
+ Client* client;
+ DImage* dimage;
+};
+
+struct DName
+{
+ char *name;
+ Client *client;
+ DImage* dimage;
+ int vers;
+};
+
+struct FChar
+{
+ int minx; /* left edge of bits */
+ int maxx; /* right edge of bits */
+ uchar miny; /* first non-zero scan-line */
+ uchar maxy; /* last non-zero scan-line + 1 */
+ schar left; /* offset of baseline */
+ uchar width; /* width of baseline */
+};
+
+/*
+ * Reference counts in DImages:
+ * one per open by original client
+ * one per screen image or fill
+ * one per image derived from this one by name
+ */
+struct DImage
+{
+ int id;
+ int ref;
+ char *name;
+ int vers;
+ Memimage* image;
+ int ascent;
+ int nfchar;
+ FChar* fchar;
+ DScreen* dscreen; /* 0 if not a window */
+ DImage* fromname; /* image this one is derived from, by name */
+ DImage* next;
+};
+
+struct CScreen
+{
+ DScreen* dscreen;
+ CScreen* next;
+};
+
+struct DScreen
+{
+ int id;
+ int public;
+ int ref;
+ DImage *dimage;
+ DImage *dfill;
+ Memscreen* screen;
+ Client* owner;
+ DScreen* next;
+};
+
+static Draw sdraw;
+static Memimage *screenimage;
+static Memdata screendata;
+static Rectangle flushrect;
+static int waste;
+static DScreen* dscreen;
+extern void flushmemscreen(Rectangle);
+ void drawmesg(Client*, void*, int);
+ void drawuninstall(Client*, int);
+ void drawfreedimage(DImage*);
+ Client* drawclientofpath(ulong);
+
+static char Enodrawimage[] = "unknown id for draw image";
+static char Enodrawscreen[] = "unknown id for draw screen";
+static char Eshortdraw[] = "short draw message";
+static char Eshortread[] = "draw read too short";
+static char Eimageexists[] = "image id in use";
+static char Escreenexists[] = "screen id in use";
+static char Edrawmem[] = "out of memory: image";
+static char Ereadoutside[] = "readimage outside image";
+static char Ewriteoutside[] = "writeimage outside image";
+static char Enotfont[] = "image not a font";
+static char Eindex[] = "character index out of range";
+static char Enoclient[] = "no such draw client";
+static char Enameused[] = "image name in use";
+static char Enoname[] = "no image with that name";
+static char Eoldname[] = "named image no longer valid";
+static char Enamed[] = "image already has name";
+static char Ewrongname[] = "wrong name for image";
+
+static int
+drawgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
+{
+ int t;
+ Qid q;
+ ulong path;
+ Client *cl;
+
+ q.vers = 0;
+
+ if(s == DEVDOTDOT){
+ switch(QID(c->qid)){
+ case Qtopdir:
+ case Q2nd:
+ mkqid(&q, Qtopdir, 0, QTDIR);
+ devdir(c, q, "#i", 0, eve, 0500, dp);
+ break;
+ case Q3rd:
+ cl = drawclientofpath(c->qid.path);
+ if(cl == nil)
+ strcpy(up->genbuf, "??");
+ else
+ sprint(up->genbuf, "%d", cl->clientid);
+ mkqid(&q, Q2nd, 0, QTDIR);
+ devdir(c, q, up->genbuf, 0, eve, 0500, dp);
+ break;
+ default:
+ panic("drawwalk %llux", c->qid.path);
+ }
+ return 1;
+ }
+
+ /*
+ * Top level directory contains the name of the device.
+ */
+ t = QID(c->qid);
+ if(t == Qtopdir){
+ switch(s){
+ case 0:
+ mkqid(&q, Q2nd, 0, QTDIR);
+ devdir(c, q, "draw", 0, eve, 0555, dp);
+ break;
+ default:
+ return -1;
+ }
+ return 1;
+ }
+
+ /*
+ * Second level contains "new" plus all the clients.
+ */
+ if(t == Q2nd || t == Qnew){
+ if(s == 0){
+ mkqid(&q, Qnew, 0, QTFILE);
+ devdir(c, q, "new", 0, eve, 0666, dp);
+ }
+ else if(s <= sdraw.nclient){
+ cl = sdraw.client[s-1];
+ if(cl == 0)
+ return 0;
+ sprint(up->genbuf, "%d", cl->clientid);
+ mkqid(&q, (s<<QSHIFT)|Q3rd, 0, QTDIR);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+ else
+ return -1;
+ return 1;
+ }
+
+ /*
+ * Third level.
+ */
+ path = c->qid.path&~((1<<QSHIFT)-1); /* slot component */
+ q.vers = c->qid.vers;
+ q.type = QTFILE;
+ switch(s){
+ case 0:
+ q.path = path|Qcolormap;
+ devdir(c, q, "colormap", 0, eve, 0600, dp);
+ break;
+ case 1:
+ q.path = path|Qctl;
+ devdir(c, q, "ctl", 0, eve, 0600, dp);
+ break;
+ case 2:
+ q.path = path|Qdata;
+ devdir(c, q, "data", 0, eve, 0600, dp);
+ break;
+ case 3:
+ q.path = path|Qrefresh;
+ devdir(c, q, "refresh", 0, eve, 0400, dp);
+ break;
+ default:
+ return -1;
+ }
+ return 1;
+}
+
+static
+int
+drawrefactive(void *a)
+{
+ Client *c;
+
+ c = a;
+ return c->refreshme || c->refresh!=0;
+}
+
+static
+void
+drawrefreshscreen(DImage *l, Client *client)
+{
+ while(l != nil && l->dscreen == nil)
+ l = l->fromname;
+ if(l != nil && l->dscreen->owner != client)
+ l->dscreen->owner->refreshme = 1;
+}
+
+static
+void
+drawrefresh(Memimage*, Rectangle r, void *v)
+{
+ Refx *x;
+ DImage *d;
+ Client *c;
+ Refresh *ref;
+
+ if(v == 0)
+ return;
+ x = v;
+ c = x->client;
+ d = x->dimage;
+ for(ref=c->refresh; ref; ref=ref->next)
+ if(ref->dimage == d){
+ combinerect(&ref->r, r);
+ return;
+ }
+ ref = malloc(sizeof(Refresh));
+ if(ref){
+ ref->dimage = d;
+ ref->r = r;
+ ref->next = c->refresh;
+ c->refresh = ref;
+ }
+}
+
+static void
+addflush(Rectangle r)
+{
+ int abb, ar, anbb;
+ Rectangle nbb;
+
+ if(sdraw.softscreen==0 || !rectclip(&r, screenimage->r))
+ return;
+
+ if(flushrect.min.x >= flushrect.max.x){
+ flushrect = r;
+ waste = 0;
+ return;
+ }
+ nbb = flushrect;
+ combinerect(&nbb, r);
+ ar = Dx(r)*Dy(r);
+ abb = Dx(flushrect)*Dy(flushrect);
+ anbb = Dx(nbb)*Dy(nbb);
+ /*
+ * Area of new waste is area of new bb minus area of old bb,
+ * less the area of the new segment, which we assume is not waste.
+ * This could be negative, but that's OK.
+ */
+ waste += anbb-abb - ar;
+ if(waste < 0)
+ waste = 0;
+ /*
+ * absorb if:
+ * total area is small
+ * waste is less than half total area
+ * rectangles touch
+ */
+ if(anbb<=1024 || waste*2<anbb || rectXrect(flushrect, r)){
+ flushrect = nbb;
+ return;
+ }
+ /* emit current state */
+ if(flushrect.min.x < flushrect.max.x)
+ flushmemscreen(flushrect);
+ flushrect = r;
+ waste = 0;
+}
+
+static
+void
+dstflush(Memimage *dst, Rectangle r)
+{
+ Memlayer *l;
+
+ if(dst == screenimage){
+ combinerect(&flushrect, r);
+ return;
+ }
+ l = dst->layer;
+ if(l == nil)
+ return;
+ do{
+ if(l->screen->image->data != screenimage->data)
+ return;
+ r = rectaddpt(r, l->delta);
+ l = l->screen->image->layer;
+ }while(l);
+ addflush(r);
+}
+
+void
+drawflush(void)
+{
+ if(flushrect.min.x < flushrect.max.x)
+ flushmemscreen(flushrect);
+ flushrect = Rect(10000, 10000, -10000, -10000);
+}
+
+static
+int
+drawcmp(char *a, char *b, int n)
+{
+ if(strlen(a) != n)
+ return 1;
+ return memcmp(a, b, n);
+}
+
+DName*
+drawlookupname(int n, char *str)
+{
+ DName *name, *ename;
+
+ name = sdraw.name;
+ ename = &name[sdraw.nname];
+ for(; name<ename; name++)
+ if(drawcmp(name->name, str, n) == 0)
+ return name;
+ return 0;
+}
+
+int
+drawgoodname(DImage *d)
+{
+ DName *n;
+
+ /* if window, validate the screen's own images */
+ if(d->dscreen)
+ if(drawgoodname(d->dscreen->dimage) == 0
+ || drawgoodname(d->dscreen->dfill) == 0)
+ return 0;
+ if(d->name == nil)
+ return 1;
+ n = drawlookupname(strlen(d->name), d->name);
+ if(n==nil || n->vers!=d->vers)
+ return 0;
+ return 1;
+}
+
+DImage*
+drawlookup(Client *client, int id, int checkname)
+{
+ DImage *d;
+
+ d = client->dimage[id&HASHMASK];
+ while(d){
+ if(d->id == id){
+ if(checkname && !drawgoodname(d))
+ error(Eoldname);
+ return d;
+ }
+ d = d->next;
+ }
+ return 0;
+}
+
+DScreen*
+drawlookupdscreen(int id)
+{
+ DScreen *s;
+
+ s = dscreen;
+ while(s){
+ if(s->id == id)
+ return s;
+ s = s->next;
+ }
+ return 0;
+}
+
+DScreen*
+drawlookupscreen(Client *client, int id, CScreen **cs)
+{
+ CScreen *s;
+
+ s = client->cscreen;
+ while(s){
+ if(s->dscreen->id == id){
+ *cs = s;
+ return s->dscreen;
+ }
+ s = s->next;
+ }
+ error(Enodrawscreen);
+ return 0;
+}
+
+Memimage*
+drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
+{
+ DImage *d;
+
+ d = malloc(sizeof(DImage));
+ if(d == 0)
+ return 0;
+ d->id = id;
+ d->ref = 1;
+ d->name = 0;
+ d->vers = 0;
+ d->image = i;
+ d->nfchar = 0;
+ d->fchar = 0;
+ d->fromname = 0;
+ d->dscreen = dscreen;
+ d->next = client->dimage[id&HASHMASK];
+ client->dimage[id&HASHMASK] = d;
+ return i;
+}
+
+Memscreen*
+drawinstallscreen(Client *client, DScreen *d, int id, DImage *dimage, DImage *dfill, int public)
+{
+ Memscreen *s;
+ CScreen *c;
+
+ c = malloc(sizeof(CScreen));
+ if(dimage && dimage->image && dimage->image->chan == 0)
+ panic("bad image %p in drawinstallscreen", dimage->image);
+
+ if(c == 0)
+ return 0;
+ if(d == 0){
+ d = malloc(sizeof(DScreen));
+ if(d == 0){
+ free(c);
+ return 0;
+ }
+ s = malloc(sizeof(Memscreen));
+ if(s == 0){
+ free(c);
+ free(d);
+ return 0;
+ }
+ s->frontmost = 0;
+ s->rearmost = 0;
+ d->dimage = dimage;
+ if(dimage){
+ s->image = dimage->image;
+ dimage->ref++;
+ }
+ d->dfill = dfill;
+ if(dfill){
+ s->fill = dfill->image;
+ dfill->ref++;
+ }
+ d->ref = 0;
+ d->id = id;
+ d->screen = s;
+ d->public = public;
+ d->next = dscreen;
+ d->owner = client;
+ dscreen = d;
+ }
+ c->dscreen = d;
+ d->ref++;
+ c->next = client->cscreen;
+ client->cscreen = c;
+ return d->screen;
+}
+
+void
+drawdelname(DName *name)
+{
+ int i;
+
+ i = name-sdraw.name;
+ memmove(name, name+1, (sdraw.nname-(i+1))*sizeof(DName));
+ sdraw.nname--;
+}
+
+void
+drawfreedscreen(DScreen *this)
+{
+ DScreen *ds, *next;
+
+ this->ref--;
+ if(this->ref < 0)
+ print("negative ref in drawfreedscreen\n");
+ if(this->ref > 0)
+ return;
+ ds = dscreen;
+ if(ds == this){
+ dscreen = this->next;
+ goto Found;
+ }
+ while(next = ds->next){ /* assign = */
+ if(next == this){
+ ds->next = this->next;
+ goto Found;
+ }
+ ds = next;
+ }
+ error(Enodrawimage);
+
+ Found:
+ if(this->dimage)
+ drawfreedimage(this->dimage);
+ if(this->dfill)
+ drawfreedimage(this->dfill);
+ free(this->screen);
+ free(this);
+}
+
+void
+drawfreedimage(DImage *dimage)
+{
+ int i;
+ Memimage *l;
+ DScreen *ds;
+
+ dimage->ref--;
+ if(dimage->ref < 0)
+ print("negative ref in drawfreedimage\n");
+ if(dimage->ref > 0)
+ return;
+
+ /* any names? */
+ for(i=0; i<sdraw.nname; )
+ if(sdraw.name[i].dimage == dimage)
+ drawdelname(sdraw.name+i);
+ else
+ i++;
+ if(dimage->fromname){ /* acquired by name; owned by someone else*/
+ drawfreedimage(dimage->fromname);
+ goto Return;
+ }
+ if(dimage->image == screenimage) /* don't free the display */
+ goto Return;
+ ds = dimage->dscreen;
+ if(ds){
+ l = dimage->image;
+ if(l->data == screenimage->data)
+ dstflush(l->layer->screen->image, l->layer->screenr);
+ if(l->layer->refreshfn == drawrefresh) /* else true owner will clean up */
+ free(l->layer->refreshptr);
+ l->layer->refreshptr = nil;
+ if(drawgoodname(dimage))
+ memldelete(l);
+ else
+ memlfree(l);
+ drawfreedscreen(ds);
+ }else
+ freememimage(dimage->image);
+ Return:
+ free(dimage->fchar);
+ free(dimage);
+}
+
+void
+drawuninstallscreen(Client *client, CScreen *this)
+{
+ CScreen *cs, *next;
+
+ cs = client->cscreen;
+ if(cs == this){
+ client->cscreen = this->next;
+ drawfreedscreen(this->dscreen);
+ free(this);
+ return;
+ }
+ while(next = cs->next){ /* assign = */
+ if(next == this){
+ cs->next = this->next;
+ drawfreedscreen(this->dscreen);
+ free(this);
+ return;
+ }
+ cs = next;
+ }
+}
+
+void
+drawuninstall(Client *client, int id)
+{
+ DImage *d, *next;
+
+ d = client->dimage[id&HASHMASK];
+ if(d == 0)
+ error(Enodrawimage);
+ if(d->id == id){
+ client->dimage[id&HASHMASK] = d->next;
+ drawfreedimage(d);
+ return;
+ }
+ while(next = d->next){ /* assign = */
+ if(next->id == id){
+ d->next = next->next;
+ drawfreedimage(next);
+ return;
+ }
+ d = next;
+ }
+ error(Enodrawimage);
+}
+
+void
+drawaddname(Client *client, DImage *di, int n, char *str)
+{
+ DName *name, *ename, *new, *t;
+
+ name = sdraw.name;
+ ename = &name[sdraw.nname];
+ for(; name<ename; name++)
+ if(drawcmp(name->name, str, n) == 0)
+ error(Enameused);
+ t = smalloc((sdraw.nname+1)*sizeof(DName));
+ memmove(t, sdraw.name, sdraw.nname*sizeof(DName));
+ free(sdraw.name);
+ sdraw.name = t;
+ new = &sdraw.name[sdraw.nname++];
+ new->name = smalloc(n+1);
+ memmove(new->name, str, n);
+ new->name[n] = 0;
+ new->dimage = di;
+ new->client = client;
+ new->vers = ++sdraw.vers;
+}
+
+Client*
+drawnewclient(void)
+{
+ Client *cl, **cp;
+ int i;
+
+ for(i=0; i<sdraw.nclient; i++){
+ cl = sdraw.client[i];
+ if(cl == 0)
+ break;
+ }
+ if(i == sdraw.nclient){
+ cp = malloc((sdraw.nclient+1)*sizeof(Client*));
+ if(cp == 0)
+ return 0;
+ memmove(cp, sdraw.client, sdraw.nclient*sizeof(Client*));
+ free(sdraw.client);
+ sdraw.client = cp;
+ sdraw.nclient++;
+ cp[i] = 0;
+ }
+ cl = malloc(sizeof(Client));
+ if(cl == 0)
+ return 0;
+ memset(cl, 0, sizeof(Client));
+ cl->slot = i;
+ cl->clientid = ++sdraw.clientid;
+ cl->op = SoverD;
+ sdraw.client[i] = cl;
+ return cl;
+}
+
+static int
+drawclientop(Client *cl)
+{
+ int op;
+
+ op = cl->op;
+ cl->op = SoverD;
+ return op;
+}
+
+int
+drawhasclients(void)
+{
+ /*
+ * if draw has ever been used, we can't resize the frame buffer,
+ * even if all clients have exited (nclients is cumulative); it's too
+ * hard to make work.
+ */
+ return sdraw.nclient != 0;
+}
+
+Client*
+drawclientofpath(ulong path)
+{
+ Client *cl;
+ int slot;
+
+ slot = CLIENTPATH(path);
+ if(slot == 0)
+ return nil;
+ cl = sdraw.client[slot-1];
+ if(cl==0 || cl->clientid==0)
+ return nil;
+ return cl;
+}
+
+
+Client*
+drawclient(Chan *c)
+{
+ Client *client;
+
+ client = drawclientofpath(c->qid.path);
+ if(client == nil)
+ error(Enoclient);
+ return client;
+}
+
+Memimage*
+drawimage(Client *client, uchar *a)
+{
+ DImage *d;
+
+ d = drawlookup(client, BGLONG(a), 1);
+ if(d == nil)
+ error(Enodrawimage);
+ return d->image;
+}
+
+void
+drawrectangle(Rectangle *r, uchar *a)
+{
+ r->min.x = BGLONG(a+0*4);
+ r->min.y = BGLONG(a+1*4);
+ r->max.x = BGLONG(a+2*4);
+ r->max.y = BGLONG(a+3*4);
+}
+
+void
+drawpoint(Point *p, uchar *a)
+{
+ p->x = BGLONG(a+0*4);
+ p->y = BGLONG(a+1*4);
+}
+
+Point
+drawchar(Memimage *dst, Point p, Memimage *src, Point *sp, DImage *font, int index, int op)
+{
+ FChar *fc;
+ Rectangle r;
+ Point sp1;
+
+ fc = &font->fchar[index];
+ r.min.x = p.x+fc->left;
+ r.min.y = p.y-(font->ascent-fc->miny);
+ r.max.x = r.min.x+(fc->maxx-fc->minx);
+ r.max.y = r.min.y+(fc->maxy-fc->miny);
+ sp1.x = sp->x+fc->left;
+ sp1.y = sp->y+fc->miny;
+ memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+ p.x += fc->width;
+ sp->x += fc->width;
+ return p;
+}
+
+static int
+initscreenimage(void)
+{
+ int width, depth;
+ ulong chan;
+ Rectangle r;
+
+ if(screenimage != nil)
+ return 1;
+
+ screendata.base = nil;
+ screendata.bdata = attachscreen(&r, &chan, &depth, &width, &sdraw.softscreen);
+ if(screendata.bdata == nil)
+ return 0;
+ screendata.ref = 1;
+
+ screenimage = allocmemimaged(r, chan, &screendata);
+ if(screenimage == nil){
+ /* RSC: BUG: detach screen */
+ return 0;
+ }
+
+ screenimage->width = width;
+ screenimage->clipr = r;
+ return 1;
+}
+
+void
+deletescreenimage(void)
+{
+ qlock(&sdraw);
+ /* RSC: BUG: detach screen */
+ if(screenimage)
+ freememimage(screenimage);
+ screenimage = nil;
+ qunlock(&sdraw);
+}
+
+static Chan*
+drawattach(char *spec)
+{
+ qlock(&sdraw);
+ if(!initscreenimage()){
+ qunlock(&sdraw);
+ error("no frame buffer");
+ }
+ qunlock(&sdraw);
+ return devattach('i', spec);
+}
+
+static Walkqid*
+drawwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ if(screendata.bdata == nil)
+ error("no frame buffer");
+ return devwalk(c, nc, name, nname, 0, 0, drawgen);
+}
+
+static int
+drawstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, 0, 0, drawgen);
+}
+
+static Chan*
+drawopen(Chan *c, int omode)
+{
+ Client *cl;
+
+ if(c->qid.type & QTDIR)
+ return devopen(c, omode, 0, 0, drawgen);
+
+ qlock(&sdraw);
+ if(waserror()){
+ qunlock(&sdraw);
+ nexterror();
+ }
+
+ if(QID(c->qid) == Qnew){
+ cl = drawnewclient();
+ if(cl == 0)
+ error(Enodev);
+ c->qid.path = Qctl|((cl->slot+1)<<QSHIFT);
+ }
+
+ switch(QID(c->qid)){
+ case Qnew:
+ break;
+
+ case Qctl:
+ cl = drawclient(c);
+ if(cl->busy)
+ error(Einuse);
+ cl->busy = 1;
+ flushrect = Rect(10000, 10000, -10000, -10000);
+ drawinstall(cl, 0, screenimage, 0);
+ incref(&cl->r);
+ break;
+ case Qcolormap:
+ case Qdata:
+ case Qrefresh:
+ cl = drawclient(c);
+ incref(&cl->r);
+ break;
+ }
+ qunlock(&sdraw);
+ poperror();
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = IOUNIT;
+ return c;
+}
+
+static void
+drawclose(Chan *c)
+{
+ int i;
+ DImage *d, **dp;
+ Client *cl;
+ Refresh *r;
+
+ if(QID(c->qid) < Qcolormap) /* Qtopdir, Qnew, Q3rd, Q2nd have no client */
+ return;
+ qlock(&sdraw);
+ if(waserror()){
+ qunlock(&sdraw);
+ nexterror();
+ }
+
+ cl = drawclient(c);
+ if(QID(c->qid) == Qctl)
+ cl->busy = 0;
+ if((c->flag&COPEN) && (decref(&cl->r)==0)){
+ while(r = cl->refresh){ /* assign = */
+ cl->refresh = r->next;
+ free(r);
+ }
+ /* free names */
+ for(i=0; i<sdraw.nname; )
+ if(sdraw.name[i].client == cl)
+ drawdelname(sdraw.name+i);
+ else
+ i++;
+ while(cl->cscreen)
+ drawuninstallscreen(cl, cl->cscreen);
+ /* all screens are freed, so now we can free images */
+ dp = cl->dimage;
+ for(i=0; i<NHASH; i++){
+ while((d = *dp) != nil){
+ *dp = d->next;
+ drawfreedimage(d);
+ }
+ dp++;
+ }
+ sdraw.client[cl->slot] = 0;
+ drawflush(); /* to erase visible, now dead windows */
+ free(cl);
+ }
+ qunlock(&sdraw);
+ poperror();
+}
+
+long
+drawread(Chan *c, void *a, long n, vlong off)
+{
+ int index, m;
+ ulong red, green, blue;
+ Client *cl;
+ uchar *p;
+ Refresh *r;
+ DImage *di;
+ Memimage *i;
+ ulong offset = off;
+ char buf[16];
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, drawgen);
+ cl = drawclient(c);
+ qlock(&sdraw);
+ if(waserror()){
+ qunlock(&sdraw);
+ nexterror();
+ }
+ switch(QID(c->qid)){
+ case Qctl:
+ if(n < 12*12)
+ error(Eshortread);
+ if(cl->infoid < 0)
+ error(Enodrawimage);
+ if(cl->infoid == 0){
+ i = screenimage;
+ if(i == nil)
+ error(Enodrawimage);
+ }else{
+ di = drawlookup(cl, cl->infoid, 1);
+ if(di == nil)
+ error(Enodrawimage);
+ i = di->image;
+ }
+ n = sprint(a, "%11d %11d %11s %11d %11d %11d %11d %11d %11d %11d %11d %11d ",
+ cl->clientid, cl->infoid, chantostr(buf, i->chan), (i->flags&Frepl)==Frepl,
+ i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y,
+ i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+ cl->infoid = -1;
+ break;
+
+ case Qcolormap:
+ drawactive(1); /* to restore map from backup */
+ p = malloc(4*12*256+1);
+ if(p == 0)
+ error(Enomem);
+ m = 0;
+ for(index = 0; index < 256; index++){
+ getcolor(index, &red, &green, &blue);
+ m += sprint((char*)p+m, "%11d %11lud %11lud %11lud\n", index, red>>24, green>>24, blue>>24);
+ }
+ n = readstr(offset, a, n, (char*)p);
+ free(p);
+ break;
+
+ case Qdata:
+ if(cl->readdata == nil)
+ error("no draw data");
+ if(n < cl->nreaddata)
+ error(Eshortread);
+ n = cl->nreaddata;
+ memmove(a, cl->readdata, cl->nreaddata);
+ free(cl->readdata);
+ cl->readdata = nil;
+ break;
+
+ case Qrefresh:
+ if(n < 5*4)
+ error(Ebadarg);
+ for(;;){
+ if(cl->refreshme || cl->refresh)
+ break;
+ qunlock(&sdraw);
+ if(waserror()){
+ qlock(&sdraw); /* restore lock for waserror() above */
+ nexterror();
+ }
+ sleep(&cl->refrend, drawrefactive, cl);
+ poperror();
+ qlock(&sdraw);
+ }
+ p = a;
+ while(cl->refresh && n>=5*4){
+ r = cl->refresh;
+ BPLONG(p+0*4, r->dimage->id);
+ BPLONG(p+1*4, r->r.min.x);
+ BPLONG(p+2*4, r->r.min.y);
+ BPLONG(p+3*4, r->r.max.x);
+ BPLONG(p+4*4, r->r.max.y);
+ cl->refresh = r->next;
+ free(r);
+ p += 5*4;
+ n -= 5*4;
+ }
+ cl->refreshme = 0;
+ n = p-(uchar*)a;
+ }
+ qunlock(&sdraw);
+ poperror();
+ return n;
+}
+
+void
+drawwakeall(void)
+{
+ Client *cl;
+ int i;
+
+ for(i=0; i<sdraw.nclient; i++){
+ cl = sdraw.client[i];
+ if(cl && (cl->refreshme || cl->refresh))
+ wakeup(&cl->refrend);
+ }
+}
+
+static long
+drawwrite(Chan *c, void *a, long n, vlong)
+{
+ char buf[128], *fields[4], *q;
+ Client *cl;
+ int i, m, red, green, blue, x;
+
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ cl = drawclient(c);
+ qlock(&sdraw);
+ if(waserror()){
+ drawwakeall();
+ qunlock(&sdraw);
+ nexterror();
+ }
+ switch(QID(c->qid)){
+ case Qctl:
+ if(n != 4)
+ error("unknown draw control request");
+ cl->infoid = BGLONG((uchar*)a);
+ break;
+
+ case Qcolormap:
+ drawactive(1); /* to restore map from backup */
+ m = n;
+ n = 0;
+ while(m > 0){
+ x = m;
+ if(x > sizeof(buf)-1)
+ x = sizeof(buf)-1;
+ q = memccpy(buf, a, '\n', x);
+ if(q == 0)
+ break;
+ i = q-buf;
+ n += i;
+ a = (char*)a + i;
+ m -= i;
+ *q = 0;
+ if(tokenize(buf, fields, nelem(fields)) != 4)
+ error(Ebadarg);
+ i = strtoul(fields[0], 0, 0);
+ red = strtoul(fields[1], 0, 0);
+ green = strtoul(fields[2], 0, 0);
+ blue = strtoul(fields[3], &q, 0);
+ if(fields[3] == q)
+ error(Ebadarg);
+ if(red>255 || green>255 || blue>255 || i<0 || i>255)
+ error(Ebadarg);
+ red |= red<<8;
+ red |= red<<16;
+ green |= green<<8;
+ green |= green<<16;
+ blue |= blue<<8;
+ blue |= blue<<16;
+ setcolor(i, red, green, blue);
+ }
+ break;
+
+ case Qdata:
+ drawmesg(cl, a, n);
+ drawwakeall();
+ break;
+
+ default:
+ error(Ebadusefd);
+ }
+ qunlock(&sdraw);
+ poperror();
+ return n;
+}
+
+uchar*
+drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
+{
+ int b, x;
+
+ if(p >= maxp)
+ error(Eshortdraw);
+ b = *p++;
+ x = b & 0x7F;
+ if(b & 0x80){
+ if(p+1 >= maxp)
+ error(Eshortdraw);
+ x |= *p++ << 7;
+ x |= *p++ << 15;
+ if(x & (1<<22))
+ x |= ~0<<23;
+ }else{
+ if(b & 0x40)
+ x |= ~0<<7;
+ x += oldx;
+ }
+ *newx = x;
+ return p;
+}
+
+static void
+printmesg(char *fmt, uchar *a, int plsprnt)
+{
+ char buf[256];
+ char *p, *q;
+ int s;
+
+ if(1|| plsprnt==0){
+ SET(s,q,p);
+ USED(fmt, a, buf, p, q, s);
+ return;
+ }
+ q = buf;
+ *q++ = *a++;
+ for(p=fmt; *p; p++){
+ switch(*p){
+ case 'l':
+ q += sprint(q, " %ld", (long)BGLONG(a));
+ a += 4;
+ break;
+ case 'L':
+ q += sprint(q, " %.8lux", (ulong)BGLONG(a));
+ a += 4;
+ break;
+ case 'R':
+ q += sprint(q, " [%d %d %d %d]", BGLONG(a), BGLONG(a+4), BGLONG(a+8), BGLONG(a+12));
+ a += 16;
+ break;
+ case 'P':
+ q += sprint(q, " [%d %d]", BGLONG(a), BGLONG(a+4));
+ a += 8;
+ break;
+ case 'b':
+ q += sprint(q, " %d", *a++);
+ break;
+ case 's':
+ q += sprint(q, " %d", BGSHORT(a));
+ a += 2;
+ break;
+ case 'S':
+ q += sprint(q, " %.4ux", BGSHORT(a));
+ a += 2;
+ break;
+ }
+ }
+ *q++ = '\n';
+ *q = 0;
+ iprint("%.*s", (int)(q-buf), buf);
+}
+
+void
+drawmesg(Client *client, void *av, int n)
+{
+ int c, op, repl, m, y, dstid, scrnid, ni, ci, j, nw, e0, e1, ox, oy, esize, oesize, doflush;
+ uchar *u, *a, refresh;
+ char *fmt;
+ ulong value, chan;
+ Rectangle r, clipr;
+ Point p, q, *pp, sp;
+ Memimage *i, *dst, *src, *mask;
+ Memimage *l, **lp;
+ Memscreen *scrn;
+ DImage *font, *ll, *di, *ddst, *dsrc;
+ DName *dn;
+ DScreen *dscrn;
+ FChar *fc;
+ Refx *refx;
+ CScreen *cs;
+ Refreshfn reffn;
+
+ a = av;
+ m = 0;
+ fmt = nil;
+ if(waserror()){
+ if(fmt) printmesg(fmt, a, 1);
+ /* iprint("error: %s\n", up->env->errstr); */
+ nexterror();
+ }
+ while((n-=m) > 0){
+ USED(fmt);
+ a += m;
+ switch(*a){
+ default:
+ error("bad draw command");
+ /* new allocate: 'b' id[4] screenid[4] refresh[1] chan[4] repl[1] R[4*4] clipR[4*4] rrggbbaa[4] */
+ case 'b':
+ printmesg(fmt="LLbLbRRL", a, 0);
+ m = 1+4+4+1+4+1+4*4+4*4+4;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ scrnid = BGSHORT(a+5);
+ refresh = a[9];
+ chan = BGLONG(a+10);
+ repl = a[14];
+ drawrectangle(&r, a+15);
+ drawrectangle(&clipr, a+31);
+ value = BGLONG(a+47);
+ if(drawlookup(client, dstid, 0))
+ error(Eimageexists);
+ if(scrnid){
+ dscrn = drawlookupscreen(client, scrnid, &cs);
+ scrn = dscrn->screen;
+ if(repl || chan!=scrn->image->chan)
+ error("image parameters incompatible with screen");
+ reffn = nil;
+ switch(refresh){
+ case Refbackup:
+ break;
+ case Refnone:
+ reffn = memlnorefresh;
+ break;
+ case Refmesg:
+ reffn = drawrefresh;
+ break;
+ default:
+ error("unknown refresh method");
+ }
+ l = memlalloc(scrn, r, reffn, 0, value);
+ if(l == 0)
+ error(Edrawmem);
+ dstflush(l->layer->screen->image, l->layer->screenr);
+ l->clipr = clipr;
+ rectclip(&l->clipr, r);
+ if(drawinstall(client, dstid, l, dscrn) == 0){
+ memldelete(l);
+ error(Edrawmem);
+ }
+ dscrn->ref++;
+ if(reffn){
+ refx = nil;
+ if(reffn == drawrefresh){
+ refx = malloc(sizeof(Refx));
+ if(refx == 0){
+ drawuninstall(client, dstid);
+ error(Edrawmem);
+ }
+ refx->client = client;
+ refx->dimage = drawlookup(client, dstid, 1);
+ }
+ memlsetrefresh(l, reffn, refx);
+ }
+ continue;
+ }
+ i = allocmemimage(r, chan);
+ if(i == 0)
+ error(Edrawmem);
+ if(repl)
+ i->flags |= Frepl;
+ i->clipr = clipr;
+ if(!repl)
+ rectclip(&i->clipr, r);
+ if(drawinstall(client, dstid, i, 0) == 0){
+ freememimage(i);
+ error(Edrawmem);
+ }
+ memfillcolor(i, value);
+ continue;
+
+ /* allocate screen: 'A' id[4] imageid[4] fillid[4] public[1] */
+ case 'A':
+ printmesg(fmt="LLLb", a, 1);
+ m = 1+4+4+4+1;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(dstid == 0)
+ error(Ebadarg);
+ if(drawlookupdscreen(dstid))
+ error(Escreenexists);
+ ddst = drawlookup(client, BGLONG(a+5), 1);
+ dsrc = drawlookup(client, BGLONG(a+9), 1);
+ if(ddst==0 || dsrc==0)
+ error(Enodrawimage);
+ if(drawinstallscreen(client, 0, dstid, ddst, dsrc, a[13]) == 0)
+ error(Edrawmem);
+ continue;
+
+ /* set repl and clip: 'c' dstid[4] repl[1] clipR[4*4] */
+ case 'c':
+ printmesg(fmt="LbR", a, 0);
+ m = 1+4+1+4*4;
+ if(n < m)
+ error(Eshortdraw);
+ ddst = drawlookup(client, BGLONG(a+1), 1);
+ if(ddst == nil)
+ error(Enodrawimage);
+ if(ddst->name)
+ error("cannot change repl/clipr of shared image");
+ dst = ddst->image;
+ if(a[5])
+ dst->flags |= Frepl;
+ drawrectangle(&dst->clipr, a+6);
+ continue;
+
+ /* draw: 'd' dstid[4] srcid[4] maskid[4] R[4*4] P[2*4] P[2*4] */
+ case 'd':
+ printmesg(fmt="LLLRPP", a, 0);
+ m = 1+4+4+4+4*4+2*4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ src = drawimage(client, a+5);
+ mask = drawimage(client, a+9);
+ drawrectangle(&r, a+13);
+ drawpoint(&p, a+29);
+ drawpoint(&q, a+37);
+ op = drawclientop(client);
+ memdraw(dst, r, src, p, mask, q, op);
+ dstflush(dst, r);
+ continue;
+
+ /* toggle debugging: 'D' val[1] */
+ case 'D':
+ printmesg(fmt="b", a, 0);
+ m = 1+1;
+ if(n < m)
+ error(Eshortdraw);
+ drawdebug = a[1];
+ continue;
+
+ /* ellipse: 'e' dstid[4] srcid[4] center[2*4] a[4] b[4] thick[4] sp[2*4] alpha[4] phi[4]*/
+ case 'e':
+ case 'E':
+ printmesg(fmt="LLPlllPll", a, 0);
+ m = 1+4+4+2*4+4+4+4+2*4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ src = drawimage(client, a+5);
+ drawpoint(&p, a+9);
+ e0 = BGLONG(a+17);
+ e1 = BGLONG(a+21);
+ if(e0<0 || e1<0)
+ error("invalid ellipse semidiameter");
+ j = BGLONG(a+25);
+ if(j < 0)
+ error("negative ellipse thickness");
+ drawpoint(&sp, a+29);
+ c = j;
+ if(*a == 'E')
+ c = -1;
+ ox = BGLONG(a+37);
+ oy = BGLONG(a+41);
+ op = drawclientop(client);
+ /* high bit indicates arc angles are present */
+ if(ox & (1<<31)){
+ if((ox & (1<<30)) == 0)
+ ox &= ~(1<<31);
+ memarc(dst, p, e0, e1, c, src, sp, ox, oy, op);
+ }else
+ memellipse(dst, p, e0, e1, c, src, sp, op);
+ dstflush(dst, Rect(p.x-e0-j, p.y-e1-j, p.x+e0+j+1, p.y+e1+j+1));
+ continue;
+
+ /* free: 'f' id[4] */
+ case 'f':
+ printmesg(fmt="L", a, 1);
+ m = 1+4;
+ if(n < m)
+ error(Eshortdraw);
+ ll = drawlookup(client, BGLONG(a+1), 0);
+ if(ll && ll->dscreen && ll->dscreen->owner != client)
+ ll->dscreen->owner->refreshme = 1;
+ drawuninstall(client, BGLONG(a+1));
+ continue;
+
+ /* free screen: 'F' id[4] */
+ case 'F':
+ printmesg(fmt="L", a, 1);
+ m = 1+4;
+ if(n < m)
+ error(Eshortdraw);
+ drawlookupscreen(client, BGLONG(a+1), &cs);
+ drawuninstallscreen(client, cs);
+ continue;
+
+ /* initialize font: 'i' fontid[4] nchars[4] ascent[1] */
+ case 'i':
+ printmesg(fmt="Llb", a, 1);
+ m = 1+4+4+1;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ dst = drawimage(client, a+1);
+ if(dstid == 0 || dst == screenimage)
+ error("cannot use display as font");
+ font = drawlookup(client, dstid, 1);
+ if(font == 0)
+ error(Enodrawimage);
+ if(font->image->layer)
+ error("cannot use window as font");
+ ni = BGLONG(a+5);
+ if(ni<=0 || ni>4096)
+ error("bad font size (4096 chars max)");
+ free(font->fchar); /* should we complain if non-zero? */
+ font->fchar = malloc(ni*sizeof(FChar));
+ if(font->fchar == 0)
+ error(Enomem);
+ memset(font->fchar, 0, ni*sizeof(FChar));
+ font->nfchar = ni;
+ font->ascent = a[9];
+ continue;
+
+ /* load character: 'l' fontid[4] srcid[4] index[2] R[4*4] P[2*4] left[1] width[1] */
+ case 'l':
+ printmesg(fmt="LLSRPbb", a, 0);
+ m = 1+4+4+2+4*4+2*4+1+1;
+ if(n < m)
+ error(Eshortdraw);
+ font = drawlookup(client, BGLONG(a+1), 1);
+ if(font == 0)
+ error(Enodrawimage);
+ if(font->nfchar == 0)
+ error(Enotfont);
+ src = drawimage(client, a+5);
+ ci = BGSHORT(a+9);
+ if(ci >= font->nfchar)
+ error(Eindex);
+ drawrectangle(&r, a+11);
+ drawpoint(&p, a+27);
+ memdraw(font->image, r, src, p, memopaque, p, S);
+ fc = &font->fchar[ci];
+ fc->minx = r.min.x;
+ fc->maxx = r.max.x;
+ fc->miny = r.min.y;
+ fc->maxy = r.max.y;
+ fc->left = a[35];
+ fc->width = a[36];
+ continue;
+
+ /* draw line: 'L' dstid[4] p0[2*4] p1[2*4] end0[4] end1[4] radius[4] srcid[4] sp[2*4] */
+ case 'L':
+ printmesg(fmt="LPPlllLP", a, 0);
+ m = 1+4+2*4+2*4+4+4+4+4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ drawpoint(&p, a+5);
+ drawpoint(&q, a+13);
+ e0 = BGLONG(a+21);
+ e1 = BGLONG(a+25);
+ j = BGLONG(a+29);
+ if(j < 0)
+ error("negative line width");
+ src = drawimage(client, a+33);
+ drawpoint(&sp, a+37);
+ op = drawclientop(client);
+ memline(dst, p, q, e0, e1, j, src, sp, op);
+ /* avoid memlinebbox if possible */
+ if(dst == screenimage || dst->layer!=nil){
+ /* BUG: this is terribly inefficient: update maximal containing rect*/
+ r = memlinebbox(p, q, e0, e1, j);
+ dstflush(dst, insetrect(r, -(1+1+j)));
+ }
+ continue;
+
+ /* create image mask: 'm' newid[4] id[4] */
+/*
+ *
+ case 'm':
+ printmesg("LL", a, 0);
+ m = 4+4;
+ if(n < m)
+ error(Eshortdraw);
+ break;
+ *
+ */
+
+ /* attach to a named image: 'n' dstid[4] j[1] name[j] */
+ case 'n':
+ printmesg(fmt="Lz", a, 0);
+ m = 1+4+1;
+ if(n < m)
+ error(Eshortdraw);
+ j = a[5];
+ if(j == 0) /* give me a non-empty name please */
+ error(Eshortdraw);
+ m += j;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(drawlookup(client, dstid, 0))
+ error(Eimageexists);
+ dn = drawlookupname(j, (char*)a+6);
+ if(dn == nil)
+ error(Enoname);
+ if(drawinstall(client, dstid, dn->dimage->image, 0) == 0)
+ error(Edrawmem);
+ di = drawlookup(client, dstid, 0);
+ if(di == 0)
+ error("draw: cannot happen");
+ di->vers = dn->vers;
+ di->name = smalloc(j+1);
+ di->fromname = dn->dimage;
+ di->fromname->ref++;
+ memmove(di->name, a+6, j);
+ di->name[j] = 0;
+ client->infoid = dstid;
+ continue;
+
+ /* name an image: 'N' dstid[4] in[1] j[1] name[j] */
+ case 'N':
+ printmesg(fmt="Lbz", a, 0);
+ m = 1+4+1+1;
+ if(n < m)
+ error(Eshortdraw);
+ c = a[5];
+ j = a[6];
+ if(j == 0) /* give me a non-empty name please */
+ error(Eshortdraw);
+ m += j;
+ if(n < m)
+ error(Eshortdraw);
+ di = drawlookup(client, BGLONG(a+1), 0);
+ if(di == 0)
+ error(Enodrawimage);
+ if(di->name)
+ error(Enamed);
+ if(c)
+ drawaddname(client, di, j, (char*)a+7);
+ else{
+ dn = drawlookupname(j, (char*)a+7);
+ if(dn == nil)
+ error(Enoname);
+ if(dn->dimage != di)
+ error(Ewrongname);
+ drawdelname(dn);
+ }
+ continue;
+
+ /* position window: 'o' id[4] r.min [2*4] screenr.min [2*4] */
+ case 'o':
+ printmesg(fmt="LPP", a, 0);
+ m = 1+4+2*4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ if(dst->layer){
+ drawpoint(&p, a+5);
+ drawpoint(&q, a+13);
+ r = dst->layer->screenr;
+ ni = memlorigin(dst, p, q);
+ if(ni < 0)
+ error("image origin failed");
+ if(ni > 0){
+ dstflush(dst->layer->screen->image, r);
+ dstflush(dst->layer->screen->image, dst->layer->screenr);
+ ll = drawlookup(client, BGLONG(a+1), 1);
+ drawrefreshscreen(ll, client);
+ }
+ }
+ continue;
+
+ /* set compositing operator for next draw operation: 'O' op */
+ case 'O':
+ printmesg(fmt="b", a, 0);
+ m = 1+1;
+ if(n < m)
+ error(Eshortdraw);
+ client->op = a[1];
+ continue;
+
+ /* filled polygon: 'P' dstid[4] n[2] wind[4] ignore[2*4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+ /* polygon: 'p' dstid[4] n[2] end0[4] end1[4] radius[4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+ case 'p':
+ case 'P':
+ printmesg(fmt="LslllLPP", a, 0);
+ m = 1+4+2+4+4+4+4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ ni = BGSHORT(a+5);
+ if(ni < 0)
+ error("negative count in polygon");
+ e0 = BGLONG(a+7);
+ e1 = BGLONG(a+11);
+ j = 0;
+ if(*a == 'p'){
+ j = BGLONG(a+15);
+ if(j < 0)
+ error("negative polygon line width");
+ }
+ src = drawimage(client, a+19);
+ drawpoint(&sp, a+23);
+ drawpoint(&p, a+31);
+ ni++;
+ pp = malloc(ni*sizeof(Point));
+ if(pp == nil)
+ error(Enomem);
+ doflush = 0;
+ if(dst == screenimage || (dst->layer && dst->layer->screen->image->data == screenimage->data))
+ doflush = 1; /* simplify test in loop */
+ ox = oy = 0;
+ esize = 0;
+ u = a+m;
+ for(y=0; y<ni; y++){
+ q = p;
+ oesize = esize;
+ u = drawcoord(u, a+n, ox, &p.x);
+ u = drawcoord(u, a+n, oy, &p.y);
+ ox = p.x;
+ oy = p.y;
+ if(doflush){
+ esize = j;
+ if(*a == 'p'){
+ if(y == 0){
+ c = memlineendsize(e0);
+ if(c > esize)
+ esize = c;
+ }
+ if(y == ni-1){
+ c = memlineendsize(e1);
+ if(c > esize)
+ esize = c;
+ }
+ }
+ if(*a=='P' && e0!=1 && e0 !=~0)
+ r = dst->clipr;
+ else if(y > 0){
+ r = Rect(q.x-oesize, q.y-oesize, q.x+oesize+1, q.y+oesize+1);
+ combinerect(&r, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+ }
+ if(rectclip(&r, dst->clipr)) /* should perhaps be an arg to dstflush */
+ dstflush(dst, r);
+ }
+ pp[y] = p;
+ }
+ if(y == 1)
+ dstflush(dst, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+ op = drawclientop(client);
+ if(*a == 'p')
+ mempoly(dst, pp, ni, e0, e1, j, src, sp, op);
+ else
+ memfillpoly(dst, pp, ni, e0, src, sp, op);
+ free(pp);
+ m = u-a;
+ continue;
+
+ /* read: 'r' id[4] R[4*4] */
+ case 'r':
+ printmesg(fmt="LR", a, 0);
+ m = 1+4+4*4;
+ if(n < m)
+ error(Eshortdraw);
+ i = drawimage(client, a+1);
+ drawrectangle(&r, a+5);
+ if(!rectinrect(r, i->r))
+ error(Ereadoutside);
+ c = bytesperline(r, i->depth);
+ c *= Dy(r);
+ free(client->readdata);
+ client->readdata = mallocz(c, 0);
+ if(client->readdata == nil)
+ error("readimage malloc failed");
+ client->nreaddata = memunload(i, r, client->readdata, c);
+ if(client->nreaddata < 0){
+ free(client->readdata);
+ client->readdata = nil;
+ error("bad readimage call");
+ }
+ continue;
+
+ /* string: 's' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] ni*(index[2]) */
+ /* stringbg: 'x' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] bgid[4] bgpt[2*4] ni*(index[2]) */
+ case 's':
+ case 'x':
+ printmesg(fmt="LLLPRPs", a, 0);
+ m = 1+4+4+4+2*4+4*4+2*4+2;
+ if(*a == 'x')
+ m += 4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+
+ dst = drawimage(client, a+1);
+ src = drawimage(client, a+5);
+ font = drawlookup(client, BGLONG(a+9), 1);
+ if(font == 0)
+ error(Enodrawimage);
+ if(font->nfchar == 0)
+ error(Enotfont);
+ drawpoint(&p, a+13);
+ drawrectangle(&r, a+21);
+ drawpoint(&sp, a+37);
+ ni = BGSHORT(a+45);
+ u = a+m;
+ m += ni*2;
+ if(n < m)
+ error(Eshortdraw);
+ clipr = dst->clipr;
+ dst->clipr = r;
+ op = drawclientop(client);
+ if(*a == 'x'){
+ /* paint background */
+ l = drawimage(client, a+47);
+ drawpoint(&q, a+51);
+ r.min.x = p.x;
+ r.min.y = p.y-font->ascent;
+ r.max.x = p.x;
+ r.max.y = r.min.y+Dy(font->image->r);
+ j = ni;
+ while(--j >= 0){
+ ci = BGSHORT(u);
+ if(ci<0 || ci>=font->nfchar){
+ dst->clipr = clipr;
+ error(Eindex);
+ }
+ r.max.x += font->fchar[ci].width;
+ u += 2;
+ }
+ memdraw(dst, r, l, q, memopaque, ZP, op);
+ u -= 2*ni;
+ }
+ q = p;
+ while(--ni >= 0){
+ ci = BGSHORT(u);
+ if(ci<0 || ci>=font->nfchar){
+ dst->clipr = clipr;
+ error(Eindex);
+ }
+ q = drawchar(dst, q, src, &sp, font, ci, op);
+ u += 2;
+ }
+ dst->clipr = clipr;
+ p.y -= font->ascent;
+ dstflush(dst, Rect(p.x, p.y, q.x, p.y+Dy(font->image->r)));
+ continue;
+
+ /* use public screen: 'S' id[4] chan[4] */
+ case 'S':
+ printmesg(fmt="Ll", a, 0);
+ m = 1+4+4;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(dstid == 0)
+ error(Ebadarg);
+ dscrn = drawlookupdscreen(dstid);
+ if(dscrn==0 || (dscrn->public==0 && dscrn->owner!=client))
+ error(Enodrawscreen);
+ if(dscrn->screen->image->chan != BGLONG(a+5))
+ error("inconsistent chan");
+ if(drawinstallscreen(client, dscrn, 0, 0, 0, 0) == 0)
+ error(Edrawmem);
+ continue;
+
+ /* top or bottom windows: 't' top[1] nw[2] n*id[4] */
+ case 't':
+ printmesg(fmt="bsL", a, 0);
+ m = 1+1+2;
+ if(n < m)
+ error(Eshortdraw);
+ nw = BGSHORT(a+2);
+ if(nw < 0)
+ error(Ebadarg);
+ if(nw == 0)
+ continue;
+ m += nw*4;
+ if(n < m)
+ error(Eshortdraw);
+ lp = malloc(nw*sizeof(Memimage*));
+ if(lp == 0)
+ error(Enomem);
+ if(waserror()){
+ free(lp);
+ nexterror();
+ }
+ for(j=0; j<nw; j++)
+ lp[j] = drawimage(client, a+1+1+2+j*4);
+ if(lp[0]->layer == 0)
+ error("images are not windows");
+ for(j=1; j<nw; j++)
+ if(lp[j]->layer->screen != lp[0]->layer->screen)
+ error("images not on same screen");
+ if(a[1])
+ memltofrontn(lp, nw);
+ else
+ memltorearn(lp, nw);
+ if(lp[0]->layer->screen->image->data == screenimage->data)
+ for(j=0; j<nw; j++)
+ dstflush(lp[j]->layer->screen->image, lp[j]->layer->screenr);
+ ll = drawlookup(client, BGLONG(a+1+1+2), 1);
+ drawrefreshscreen(ll, client);
+ poperror();
+ free(lp);
+ continue;
+
+ /* visible: 'v' */
+ case 'v':
+ printmesg(fmt="", a, 0);
+ m = 1;
+ drawflush();
+ continue;
+
+ /* write: 'y' id[4] R[4*4] data[x*1] */
+ /* write from compressed data: 'Y' id[4] R[4*4] data[x*1] */
+ case 'y':
+ case 'Y':
+ printmesg(fmt="LR", a, 0);
+ // iprint("load %c\n", *a);
+ m = 1+4+4*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ drawrectangle(&r, a+5);
+ if(!rectinrect(r, dst->r))
+ error(Ewriteoutside);
+ y = memload(dst, r, a+m, n-m, *a=='Y');
+ if(y < 0)
+ error("bad writeimage call");
+ dstflush(dst, r);
+ m += y;
+ continue;
+ }
+ }
+ poperror();
+}
+
+int
+drawlsetrefresh(ulong qidpath, int id, void *reffn, void *refx)
+{
+ DImage *d;
+ Memimage *i;
+ Client *client;
+
+ client = drawclientofpath(qidpath);
+ if(client == 0)
+ return 0;
+ d = drawlookup(client, id, 0);
+ if(d == nil)
+ return 0;
+ i = d->image;
+ if(i->layer == nil)
+ return 0;
+ return memlsetrefresh(i, reffn, refx);
+}
+
+Dev drawdevtab = {
+ 'i',
+ "draw",
+
+ devreset,
+ devinit,
+ devshutdown,
+ drawattach,
+ drawwalk,
+ drawstat,
+ drawopen,
+ devcreate,
+ drawclose,
+ drawread,
+ devbread,
+ drawwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
+/*
+ * On 8 bit displays, load the default color map
+ */
+void
+drawcmap(void)
+{
+ int r, g, b, cr, cg, cb, v;
+ int num, den;
+ int i, j;
+
+ drawactive(1); /* to restore map from backup */
+ for(r=0,i=0; r!=4; r++)
+ for(v=0; v!=4; v++,i+=16){
+ for(g=0,j=v-r; g!=4; g++)
+ for(b=0;b!=4;b++,j++){
+ den = r;
+ if(g > den)
+ den = g;
+ if(b > den)
+ den = b;
+ if(den == 0) /* divide check -- pick grey shades */
+ cr = cg = cb = v*17;
+ else{
+ num = 17*(4*den+v);
+ cr = r*num/den;
+ cg = g*num/den;
+ cb = b*num/den;
+ }
+ setcolor(i+(j&15),
+ cr*0x01010101, cg*0x01010101, cb*0x01010101);
+ }
+ }
+}
+
+void
+drawblankscreen(int blank)
+{
+ int i, nc;
+ ulong *p;
+
+ if(blank == sdraw.blanked)
+ return;
+ if(!canqlock(&sdraw))
+ return;
+ if(!initscreenimage()){
+ qunlock(&sdraw);
+ return;
+ }
+ p = sdraw.savemap;
+ nc = screenimage->depth > 8 ? 256 : 1<<screenimage->depth;
+
+ /*
+ * blankscreen uses the hardware to blank the screen
+ * when possible. to help in cases when it is not possible,
+ * we set the color map to be all black.
+ */
+ if(blank == 0){ /* turn screen on */
+ for(i=0; i<nc; i++, p+=3)
+ setcolor(i, p[0], p[1], p[2]);
+ blankscreen(0);
+ }else{ /* turn screen off */
+ blankscreen(1);
+ for(i=0; i<nc; i++, p+=3){
+ getcolor(i, &p[0], &p[1], &p[2]);
+ setcolor(i, 0, 0, 0);
+ }
+ }
+ sdraw.blanked = blank;
+ qunlock(&sdraw);
+}
+
+/*
+ * record activity on screen, changing blanking as appropriate
+ */
+void
+drawactive(int active)
+{
+ if(active){
+ drawblankscreen(0);
+ sdraw.blanktime = 0;
+ }else{
+ if(blanktime && TK2SEC(sdraw.blanktime)/60 >= blanktime)
+ drawblankscreen(1);
+ else
+ sdraw.blanktime++;
+ }
+}
+
+void
+interf(void)
+{
+ /* force it to load */
+ drawreplxy(0, 0, 0);
+}
+
+int
+drawidletime(void)
+{
+ return TK2SEC(MACHP(0)->ticks - sdraw.blanktime)/60;
+}
diff --git a/os/port/devds.c b/os/port/devds.c
new file mode 100644
index 00000000..6be12007
--- /dev/null
+++ b/os/port/devds.c
@@ -0,0 +1,605 @@
+/*
+ * (file system) device subsystems
+ * '#k'.
+ * Follows device config in Ken's file server.
+ * Builds mirrors, device cats, interleaving, and partition of devices out of
+ * other (inner) devices.
+ *
+ * This code is from Plan 9, and subject to the Lucent Public License 1.02.
+ * Only the name changed for Inferno (name clash).
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "../port/error.h"
+
+enum {
+ Fmirror, // mirror of others
+ Fcat, // catenation of others
+ Finter, // interleaving of others
+ Fpart, // part of others
+
+ Blksize = 8*1024, // for Finter only
+ Maxconf = 1024, // max length for config
+
+ Nfsdevs = 64,
+ Ndevs = 8,
+
+ Qtop = 0, // top dir (contains "ds")
+ Qdir = 1, // actual dir
+ Qctl = 2, // ctl file
+ Qfirst = 3, // first fs file
+};
+
+#define Cfgstr "fsdev:\n"
+
+typedef struct Fsdev Fsdev;
+
+struct Fsdev
+{
+ int type;
+ char *name; // name for this fsdev
+ vlong start; // start address (for Fpart)
+ vlong size; // min(idev sizes)
+ int ndevs; // number of inner devices
+ char *iname[Ndevs]; // inner device names
+ Chan *idev[Ndevs]; // inner devices
+ vlong isize[Ndevs]; // sizes for inneer devices
+};
+
+/*
+ * Once configured, a fsdev is never removed. The name of those
+ * configured is never nil. We have no locks here.
+ */
+static Fsdev fsdev[Nfsdevs];
+
+static Qid tqid = {Qtop, 0, QTDIR};
+static Qid dqid = {Qdir, 0, QTDIR};
+static Qid cqid = {Qctl, 0, 0};
+
+static Cmdtab configs[] = {
+ Fmirror,"mirror", 0,
+ Fcat, "cat", 0,
+ Finter, "inter", 0,
+ Fpart, "part", 5,
+};
+
+static char confstr[Maxconf];
+static int configed;
+
+
+static Fsdev*
+path2dev(int i, int mustexist)
+{
+ if (i < 0 || i >= nelem(fsdev))
+ error("bug: bad index in devfsdev");
+ if (mustexist && fsdev[i].name == nil)
+ error(Enonexist);
+
+ if (fsdev[i].name == nil)
+ return nil;
+ else
+ return &fsdev[i];
+}
+
+static Fsdev*
+devalloc(void)
+{
+ int i;
+
+ for (i = 0; i < nelem(fsdev); i++)
+ if (fsdev[i].name == nil)
+ break;
+ if (i == nelem(fsdev))
+ error(Enodev);
+
+ return &fsdev[i];
+}
+
+static void
+setdsize(Fsdev* mp)
+{
+ uchar buf[128]; /* old DIRLEN plus a little should be plenty */
+ int i;
+ Chan *mc;
+ Dir d;
+ long l;
+
+ if (mp->type != Fpart){
+ mp->start= 0;
+ mp->size = 0LL;
+ }
+ for (i = 0; i < mp->ndevs; i++){
+ mc = mp->idev[i];
+ l = devtab[mc->type]->stat(mc, buf, sizeof(buf));
+ convM2D(buf, l, &d, nil);
+ mp->isize[i] = d.length;
+ switch(mp->type){
+ case Fmirror:
+ if (mp->size == 0LL || mp->size > d.length)
+ mp->size = d.length;
+ break;
+ case Fcat:
+ mp->size += d.length;
+ break;
+ case Finter:
+ // truncate to multiple of Blksize
+ d.length = (d.length & ~(Blksize-1));
+ mp->isize[i] = d.length;
+ mp->size += d.length;
+ break;
+ case Fpart:
+ // should raise errors here?
+ if (mp->start > d.length)
+ mp->start = d.length;
+ if (d.length < mp->start + mp->size)
+ mp->size = d.length - mp->start;
+ break;
+ }
+ }
+}
+
+static void
+mpshut(Fsdev *mp)
+{
+ int i;
+ char *nm;
+
+ nm = mp->name;
+ mp->name = nil; // prevent others from using this.
+ if (nm)
+ free(nm);
+ for (i = 0; i < mp->ndevs; i++){
+ if (mp->idev[i] != nil)
+ cclose(mp->idev[i]);
+ if (mp->iname[i])
+ free(mp->iname[i]);
+ }
+ memset(mp, 0, sizeof(*mp));
+}
+
+
+static void
+mconfig(char* a, long n) // "name idev0 idev1"
+{
+ static QLock lck;
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ Fsdev *mp;
+ int i;
+ char *oldc;
+ char *c;
+ vlong size, start;
+
+ size = 0;
+ start = 0;
+ if (confstr[0] == 0)
+ seprint(confstr, confstr+sizeof(confstr), Cfgstr);
+ oldc = confstr + strlen(confstr);
+ qlock(&lck);
+ if (waserror()){
+ *oldc = 0;
+ qunlock(&lck);
+ nexterror();
+ }
+ cb = parsecmd(a, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ c = oldc;
+ for (i = 0; i < cb->nf; i++)
+ c = seprint(c, confstr+sizeof(confstr), "%s ", cb->f[i]);
+ *(c-1) = '\n';
+ ct = lookupcmd(cb, configs, nelem(configs));
+ cb->f++; // skip command
+ cb->nf--;
+ if (ct->index == Fpart){
+ size = strtoll(cb->f[3], nil, 10);
+ cb->nf--;
+ start = strtoll(cb->f[2], nil, 10);
+ cb->nf--;
+ }
+ for (i = 0; i < nelem(fsdev); i++)
+ if (fsdev[i].name != nil && strcmp(fsdev[i].name, cb->f[0])==0)
+ error(Eexist);
+ if (cb->nf - 1 > Ndevs)
+ error("too many devices; fix me");
+ for (i = 0; i < cb->nf; i++)
+ validname(cb->f[i], (i != 0));
+ mp = devalloc();
+ if(waserror()){
+ mpshut(mp);
+ nexterror();
+ }
+ mp->type = ct->index;
+ if (mp->type == Fpart){
+ mp->size = size;
+ mp->start = start;
+ }
+ kstrdup(&mp->name, cb->f[0]);
+ for (i = 1; i < cb->nf; i++){
+ kstrdup(&mp->iname[i-1], cb->f[i]);
+ mp->idev[i-1] = namec(mp->iname[i-1], Aopen, ORDWR, 0);
+ if (mp->idev[i-1] == nil)
+ error(Egreg);
+ mp->ndevs++;
+ }
+ setdsize(mp);
+ poperror();
+ free(cb);
+ poperror();
+ poperror();
+ configed = 1;
+ qunlock(&lck);
+
+}
+
+static void
+rdconf(void)
+{
+ int mustrd;
+ char *s;
+ char *c;
+ char *p;
+ char *e;
+ Chan *cc;
+
+ s = getconf("fsconfig");
+ if (s == nil){
+ mustrd = 0;
+ s = "/dev/sdC0/fscfg";
+ } else
+ mustrd = 1;
+ if (waserror()){
+ configed = 1;
+ if (!mustrd)
+ return;
+ nexterror();
+ }
+ cc = namec(s, Aopen, OREAD, 0);
+ if(waserror()){
+ cclose(cc);
+ nexterror();
+ }
+ devtab[cc->type]->read(cc, confstr, sizeof(confstr), 0);
+ poperror();
+ cclose(cc);
+ if (strncmp(confstr, Cfgstr, strlen(Cfgstr)) != 0)
+ error("config string must start with `fsdev:'");
+ kstrdup(&c, confstr + strlen(Cfgstr));
+ if(waserror()){
+ free(c);
+ nexterror();
+ }
+ memset(confstr, 0, sizeof(confstr));
+ for (p = c; p != nil && *p != 0; p = e){
+ e = strchr(p, '\n');
+ if (e == p){
+ e++;
+ continue;
+ }
+ if (e == nil)
+ e = p + strlen(p);
+ mconfig(p, e - p);
+ }
+ poperror();
+ poperror();
+}
+
+
+static int
+mgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
+{
+ Qid qid;
+ Fsdev *mp;
+
+ if (c->qid.path == Qtop){
+ switch(i){
+ case DEVDOTDOT:
+ devdir(c, tqid, "#k", 0, eve, DMDIR|0775, dp);
+ return 1;
+ case 0:
+ devdir(c, dqid, "ds", 0, eve, DMDIR|0775, dp);
+ return 1;
+ default:
+ return -1;
+ }
+ }
+ if (c->qid.path != Qdir){
+ switch(i){
+ case DEVDOTDOT:
+ devdir(c, dqid, "ds", 0, eve, DMDIR|0775, dp);
+ return 1;
+ default:
+ return -1;
+ }
+ }
+ switch(i){
+ case DEVDOTDOT:
+ devdir(c, tqid, "#k", 0, eve, DMDIR|0775, dp);
+ return 1;
+ case 0:
+ devdir(c, cqid, "ctl", 0, eve, 0664, dp);
+ return 1;
+ }
+ i--; // for ctl
+ qid.path = Qfirst + i;
+ qid.vers = 0;
+ qid.type = 0;
+ mp = path2dev(i, 0);
+ if (mp == nil)
+ return -1;
+ kstrcpy(up->genbuf, mp->name, sizeof(up->genbuf));
+ devdir(c, qid, up->genbuf, mp->size, eve, 0664, dp);
+ return 1;
+}
+
+static Chan*
+mattach(char *spec)
+{
+ *confstr = 0;
+ return devattach(L'k', spec);
+}
+
+static Walkqid*
+mwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ if (!configed)
+ rdconf();
+ return devwalk(c, nc, name, nname, 0, 0, mgen);
+}
+
+static int
+mstat(Chan *c, uchar *db, int n)
+{
+ Dir d;
+ Fsdev *mp;
+ int p;
+
+ p = c->qid.path;
+ memset(&d, 0, sizeof(d));
+ switch(p){
+ case Qtop:
+ devdir(c, tqid, "#k", 0, eve, DMDIR|0775, &d);
+ break;
+ case Qdir:
+ devdir(c, dqid, "ds", 0, eve, DMDIR|0775, &d);
+ break;
+ case Qctl:
+ devdir(c, cqid, "ctl", 0, eve, 0664, &d);
+ break;
+ default:
+ mp = path2dev(p - Qfirst, 1);
+ devdir(c, c->qid, mp->name, mp->size, eve, 0664, &d);
+ }
+ n = convD2M(&d, db, n);
+ if (n == 0)
+ error(Ebadarg);
+ return n;
+}
+
+static Chan*
+mopen(Chan *c, int omode)
+{
+ if((c->qid.type & QTDIR) && omode != OREAD)
+ error(Eperm);
+ if (omode & OTRUNC)
+ omode &= ~OTRUNC;
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static void
+mclose(Chan*)
+{
+ // that's easy
+}
+
+static long
+catio(Fsdev *mp, int isread, void *a, long n, vlong off)
+{
+ int i;
+ Chan* mc;
+ long l, wl, res;
+ //print("catio %d %p %ld %lld\n", isread, a, n, off);
+ res = n;
+ for (i = 0; n >= 0 && i < mp->ndevs ; i++){
+ mc = mp->idev[i];
+ if (off > mp->isize[i]){
+ off -= mp->isize[i];
+ continue;
+ }
+ if (off + n > mp->isize[i])
+ l = mp->isize[i] - off;
+ else
+ l = n;
+ //print("\tdev %d %p %ld %lld\n", i, a, l, off);
+
+ if (isread)
+ wl = devtab[mc->type]->read(mc, a, l, off);
+ else
+ wl = devtab[mc->type]->write(mc, a, l, off);
+ if (wl != l)
+ error("#k: write failed");
+ a = (char*)a + l;
+ off = 0;
+ n -= l;
+ }
+ //print("\tres %ld\n", res - n);
+ return res - n;
+}
+
+static long
+interio(Fsdev *mp, int isread, void *a, long n, vlong off)
+{
+ int i;
+ Chan* mc;
+ long l, wl, wsz;
+ vlong woff, blk, mblk;
+ long boff, res;
+
+ blk = off / Blksize;
+ boff = off % Blksize;
+ wsz = Blksize - boff;
+ res = n;
+ while(n > 0){
+ i = blk % mp->ndevs;
+ mc = mp->idev[i];
+ mblk = blk / mp->ndevs;
+ woff = mblk * Blksize + boff;
+ if (n > wsz)
+ l = wsz;
+ else
+ l = n;
+ if (isread)
+ wl = devtab[mc->type]->read(mc, a, l, woff);
+ else
+ wl = devtab[mc->type]->write(mc, a, l, woff);
+ if (wl != l || l == 0)
+ error(Eio);
+ a = (char*)a + l;
+ n -= l;
+ blk++;
+ boff = 0;
+ wsz = Blksize;
+ }
+ return res;
+}
+
+static long
+mread(Chan *c, void *a, long n, vlong off)
+{
+ int i;
+ Fsdev *mp;
+ Chan *mc;
+ long l;
+ long res;
+
+ if (c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, mgen);
+ if (c->qid.path == Qctl)
+ return readstr((long)off, a, n, confstr + strlen(Cfgstr));
+ i = c->qid.path - Qfirst;
+ mp = path2dev(i, 1);
+
+ if (off >= mp->size)
+ return 0;
+ if (off + n > mp->size)
+ n = mp->size - off;
+ if (n == 0)
+ return 0;
+
+ res = -1;
+ switch(mp->type){
+ case Fmirror:
+ for (i = 0; i < mp->ndevs; i++){
+ mc = mp->idev[i];
+ if (waserror()){
+ // if a read fails we let the user know and try
+ // another device.
+ print("#k: mread: (%llx %d): %s\n",
+ c->qid.path, i, up->env->errstr);
+ continue;
+ }
+ l = devtab[mc->type]->read(mc, a, n, off);
+ poperror();
+ if (l >=0){
+ res = l;
+ break;
+ }
+ }
+ if (i == mp->ndevs)
+ error(Eio);
+ break;
+ case Fcat:
+ res = catio(mp, 1, a, n, off);
+ break;
+ case Finter:
+ res = interio(mp, 1, a, n, off);
+ break;
+ case Fpart:
+ off += mp->start;
+ mc = mp->idev[0];
+ res = devtab[mc->type]->read(mc, a, n, off);
+ break;
+ }
+ return res;
+}
+
+static long
+mwrite(Chan *c, void *a, long n, vlong off)
+{
+ Fsdev *mp;
+ long l, res;
+ int i;
+ Chan *mc;
+
+ if (c->qid.type & QTDIR)
+ error(Eperm);
+ if (c->qid.path == Qctl){
+ mconfig(a, n);
+ return n;
+ }
+ mp = path2dev(c->qid.path - Qfirst, 1);
+
+ if (off >= mp->size)
+ return 0;
+ if (off + n > mp->size)
+ n = mp->size - off;
+ if (n == 0)
+ return 0;
+ res = n;
+ switch(mp->type){
+ case Fmirror:
+ for (i = mp->ndevs-1; i >=0; i--){
+ mc = mp->idev[i];
+ l = devtab[mc->type]->write(mc, a, n, off);
+ if (l < res)
+ res = l;
+ }
+ break;
+ case Fcat:
+ res = catio(mp, 0, a, n, off);
+ break;
+ case Finter:
+ res = interio(mp, 0, a, n, off);
+ break;
+ case Fpart:
+ mc = mp->idev[0];
+ off += mp->start;
+ l = devtab[mc->type]->write(mc, a, n, off);
+ if (l < res)
+ res = l;
+ break;
+ }
+ return res;
+}
+
+Dev dsdevtab = {
+ 'k',
+ "ds",
+
+ devreset,
+ devinit,
+ devshutdown,
+ mattach,
+ mwalk,
+ mstat,
+ mopen,
+ devcreate,
+ mclose,
+ mread,
+ devbread,
+ mwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devdup.c b/os/port/devdup.c
new file mode 100644
index 00000000..b176d6c0
--- /dev/null
+++ b/os/port/devdup.c
@@ -0,0 +1,151 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+/* Qid is (2*fd + (file is ctl))+1 */
+
+static int
+dupgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
+{
+ Fgrp *fgrp = up->env->fgrp;
+ Chan *f;
+ static int perm[] = { 0400, 0200, 0600, 0 };
+ int p;
+ Qid q;
+
+ if(s == DEVDOTDOT){
+ devdir(c, c->qid, ".", 0, eve, DMDIR|0555, dp);
+ return 1;
+ }
+ if(s == 0)
+ return 0;
+ s--;
+ if(s/2 > fgrp->maxfd)
+ return -1;
+ if((f=fgrp->fd[s/2]) == nil)
+ return 0;
+ if(s & 1){
+ p = 0400;
+ sprint(up->genbuf, "%dctl", s/2);
+ }else{
+ p = perm[f->mode&3];
+ sprint(up->genbuf, "%d", s/2);
+ }
+ mkqid(&q, s+1, 0, QTFILE);
+ devdir(c, q, up->genbuf, 0, eve, p, dp);
+ return 1;
+}
+
+static Chan*
+dupattach(char *spec)
+{
+ return devattach('d', spec);
+}
+
+static Walkqid*
+dupwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, dupgen);
+}
+
+static int
+dupstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, nil, 0, dupgen);
+}
+
+static Chan*
+dupopen(Chan *c, int omode)
+{
+ Chan *f;
+ int fd, twicefd;
+
+ if(c->qid.type & QTDIR){
+ if(omode != 0)
+ error(Eisdir);
+ c->mode = 0;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+ if(c->qid.type & QTAUTH)
+ error(Eperm);
+ twicefd = c->qid.path - 1;
+ fd = twicefd/2;
+ if((twicefd & 1)){
+ /* ctl file */
+ f = c;
+ f->mode = openmode(omode);
+ f->flag |= COPEN;
+ f->offset = 0;
+ }else{
+ /* fd file */
+ f = fdtochan(up->env->fgrp, fd, openmode(omode), 0, 1);
+ cclose(c);
+ }
+ if(omode & OCEXEC)
+ f->flag |= CCEXEC;
+ return f;
+}
+
+static void
+dupclose(Chan*)
+{
+}
+
+static long
+dupread(Chan *c, void *va, long n, vlong offset)
+{
+ char *a = va;
+ char buf[256];
+ int fd, twicefd;
+
+ if(c->qid.type == QTDIR)
+ return devdirread(c, a, n, nil, 0, dupgen);
+ twicefd = c->qid.path - 1;
+ fd = twicefd/2;
+ if(twicefd & 1){
+ c = fdtochan(up->env->fgrp, fd, -1, 0, 1);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ progfdprint(c, fd, 0, buf, sizeof buf);
+ poperror();
+ cclose(c);
+ return readstr((ulong)offset, va, n, buf);
+ }
+ panic("dupread");
+ return 0;
+}
+
+static long
+dupwrite(Chan*, void*, long, vlong)
+{
+ panic("dupwrite");
+ return 0; /* not reached */
+}
+
+Dev dupdevtab = {
+ 'd',
+ "dup",
+
+ devreset,
+ devinit,
+ devshutdown,
+ dupattach,
+ dupwalk,
+ dupstat,
+ dupopen,
+ devcreate,
+ dupclose,
+ dupread,
+ devbread,
+ dupwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devdynld.c b/os/port/devdynld.c
new file mode 100644
index 00000000..cc53308c
--- /dev/null
+++ b/os/port/devdynld.c
@@ -0,0 +1,365 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <a.out.h>
+#include <dynld.h>
+#include <kernel.h>
+
+/*
+ * TO DO
+ * - dynamic allocation of Dev.dc
+ * - inter-module dependencies
+ * - reference count on Dev to allow error("inuse") [or how else to do it]
+ * - is Dev.shutdown the right function to call? Dev.config perhaps?
+ */
+
+#define DBG if(1) print
+#define NATIVE
+
+
+extern ulong ndevs;
+
+enum
+{
+ Qdir,
+ Qdynld,
+ Qdynsyms,
+
+ DEVCHAR = 'L',
+};
+
+static Dirtab dltab[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "dynld", {Qdynld}, 0, 0644,
+ "dynsyms", {Qdynsyms}, 0, 0444,
+};
+
+typedef struct Dyndev Dyndev;
+
+struct Dyndev
+{
+ char* name; /* device name (eg, "dynld") */
+ char* tag; /* version tag (eg, MD5 or SHA1 hash of content) */
+ char* path; /* file from whence it came */
+ Dynobj* o;
+ Dev* dev;
+ Dyndev* next;
+};
+
+static Dyndev *loaded;
+static QLock dllock;
+
+static Dyndev** finddyndev(char*);
+static int matched(Dyndev*, char*, char*);
+
+extern Dynobj* kdynloadfd(int, Dynsym*, int, ulong);
+
+static void
+dlfree(Dyndev *l)
+{
+ if(l != nil){
+ free(l->tag);
+ free(l->name);
+ free(l->path);
+ dynobjfree(l->o);
+ free(l);
+ }
+}
+
+static Dyndev*
+dlload(char *path, Dynsym *tab, int ntab)
+{
+ Dyndev *l;
+ int fd;
+
+ /* in Plan 9, would probably use Chan* interface here */
+ fd = kopen(path, OREAD);
+ if(fd < 0)
+ error("cannot open");
+ if(waserror()){
+ kclose(fd);
+ nexterror();
+ }
+ l = mallocz(sizeof(Dyndev), 1);
+ if(l == nil)
+ error(Enomem);
+ if(waserror()){
+ dlfree(l);
+ nexterror();
+ }
+ l->path = strdup(path);
+ if(l->path == nil)
+ error(Enomem);
+ l->o = kdynloadfd(fd, tab, ntab, 0);
+ if(l->o == nil)
+ error(up->env->errstr);
+ poperror();
+ poperror();
+ kclose(fd);
+ return l;
+}
+
+static void
+devload(char *name, char *path, char *tag)
+{
+ int i;
+ Dyndev *l, **lp;
+ Dev *dev;
+ char tabname[32];
+
+ lp = finddyndev(name);
+ if(*lp != nil)
+ error("already loaded"); /* i'm assuming the name (eg, "cons") is to be unique */
+ l = dlload(path, _exporttab, dyntabsize(_exporttab));
+ if(waserror()){
+ dlfree(l);
+ nexterror();
+ }
+ snprint(tabname, sizeof(tabname), "%sdevtab", name);
+ dev = dynimport(l->o, tabname, signof(*dev));
+ if(dev == nil)
+ errorf("can't find %sdevtab in module", name);
+ kstrdup(&l->name, name);
+ kstrdup(&l->tag, tag != nil? tag: "");
+ if(dev->name == nil)
+ dev->name = l->name;
+ else if(strcmp(dev->name, l->name) != 0)
+ errorf("module file has device %s", dev->name);
+ /* TO DO: allocate dev->dc dynamically (cf. brucee's driver) */
+ if(devno(dev->dc, 1) >= 0)
+ errorf("devchar %C already used", dev->dc);
+ for(i = 0; devtab[i] != nil; i++)
+ ;
+ if(i >= ndevs || devtab[i+1] != nil)
+ error("device table full");
+#ifdef NATIVE
+ i = splhi();
+ dev->reset();
+ splx(i);
+#endif
+ dev->init();
+ l->dev = devtab[i] = dev;
+ l->next = loaded;
+ loaded = l; /* recently loaded ones first: good unload order? */
+ poperror();
+}
+
+static Dyndev**
+finddyndev(char *name)
+{
+ Dyndev *l, **lp;
+
+ for(lp = &loaded; (l = *lp) != nil; lp = &l->next)
+ if(strcmp(l->name, name) == 0)
+ break;
+ return lp;
+}
+
+static int
+matched(Dyndev *l, char *path, char *tag)
+{
+ if(path != nil && strcmp(l->path, path) != 0)
+ return 0;
+ if(tag != nil && strcmp(l->tag, tag) != 0)
+ return 0;
+ return 1;
+}
+
+static void
+devunload(char *name, char *path, char *tag)
+{
+ int i;
+ Dyndev *l, **lp;
+
+ lp = finddyndev(name);
+ l = *lp;
+ if(l == nil)
+ error("not loaded");
+ if(!matched(l, path, tag))
+ error("path/tag mismatch");
+ for(i = 0; i < ndevs; i++)
+ if(l->dev == devtab[i]){
+ devtab[i] = nil; /* TO DO: ensure driver is not currently in use */
+ break;
+ }
+#ifdef NATIVE
+ l->dev->shutdown();
+#endif
+ *lp = l->next;
+ dlfree(l);
+}
+
+static long
+readdynld(void *a, ulong n, ulong offset)
+{
+ char *p;
+ Dyndev *l;
+ int m, len;
+
+ m = 0;
+ for(l = loaded; l != nil; l = l->next)
+ m += 48 + strlen(l->name) + strlen(l->path) + strlen(l->tag);
+ p = malloc(m);
+ if(p == nil)
+ error(Enomem);
+ if(waserror()){
+ free(p);
+ nexterror();
+ }
+ *p = 0;
+ len = 0;
+ for(l = loaded; l != nil; l = l->next)
+ if(l->dev)
+ len += snprint(p+len, m-len, "#%C\t%.8p\t%.8lud\t%q\t%q\t%q\n",
+ l->dev->dc, l->o->base, l->o->size, l->name, l->path, l->tag);
+ n = readstr(offset, a, n, p);
+ poperror();
+ free(p);
+ return n;
+}
+
+static long
+readsyms(char *a, ulong n, ulong offset)
+{
+ char *p;
+ Dynsym *t;
+ long l, nr;
+
+ p = malloc(READSTR);
+ if(p == nil)
+ error(Enomem);
+ if(waserror()){
+ free(p);
+ nexterror();
+ }
+ nr = 0;
+ for(t = _exporttab; n > 0 && t->name != nil; t++){
+ l = snprint(p, READSTR, "%.8lux %.8lux %s\n", t->addr, t->sig, t->name);
+ if(offset >= l){
+ offset -= l;
+ continue;
+ }
+ l = readstr(offset, a, n, p);
+ offset = 0;
+ n -= l;
+ a += l;
+ nr += l;
+ }
+ poperror();
+ free(p);
+ return nr;
+}
+
+static Chan*
+dlattach(char *spec)
+{
+ return devattach(DEVCHAR, spec);
+}
+
+static Walkqid*
+dlwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, dltab, nelem(dltab), devgen);
+}
+
+static int
+dlstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, dltab, nelem(dltab), devgen);
+}
+
+static Chan*
+dlopen(Chan *c, int omode)
+{
+ return devopen(c, omode, dltab, nelem(dltab), devgen);
+}
+
+static void
+dlclose(Chan *c)
+{
+ USED(c);
+}
+
+static long
+dlread(Chan *c, void *a, long n, vlong voffset)
+{
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, dltab, nelem(dltab), devgen);
+ case Qdynld:
+ return readdynld(a, n, voffset);
+ case Qdynsyms:
+ return readsyms(a, n, voffset);
+ default:
+ error(Egreg);
+ }
+ return n;
+}
+
+static long
+dlwrite(Chan *c, void *a, long n, vlong voffset)
+{
+ Cmdbuf *cb;
+ char *name, *tag, *path;
+
+ USED(voffset);
+ switch((ulong)c->qid.path){
+ case Qdynld:
+ cb = parsecmd(a, n);
+ qlock(&dllock);
+ if(waserror()){
+ qunlock(&dllock);
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf < 3 || strcmp(cb->f[1], "dev") != 0) /* only do devices */
+ cmderror(cb, Ebadctl);
+ name = cb->f[2];
+ path = nil;
+ if(cb->nf > 3)
+ path = cb->f[3];
+ tag = nil;
+ if(cb->nf > 4)
+ tag = cb->f[4];
+ if(strcmp(cb->f[0], "load") == 0){
+ if(path == nil)
+ cmderror(cb, "missing load path");
+ devload(name, path, tag); /* TO DO: remaining parameters might be dependencies? */
+ }else if(strcmp(cb->f[0], "unload") == 0)
+ devunload(name, path, tag);
+ else
+ cmderror(cb, Ebadctl);
+ poperror();
+ qunlock(&dllock);
+ free(cb);
+ break;
+ default:
+ error(Egreg);
+ }
+ return n;
+}
+
+Dev dynlddevtab = {
+ DEVCHAR,
+ "dynld",
+
+ devreset,
+ devinit,
+ devshutdown, /* TO DO */
+ dlattach,
+ dlwalk,
+ dlstat,
+ dlopen,
+ devcreate,
+ dlclose,
+ dlread,
+ devbread,
+ dlwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devenv.c b/os/port/devenv.c
new file mode 100644
index 00000000..da6373a2
--- /dev/null
+++ b/os/port/devenv.c
@@ -0,0 +1,338 @@
+/*
+ * devenv - environment
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+static void envremove(Chan*);
+
+enum
+{
+ Maxenvsize = 16300,
+};
+
+static int
+envgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
+{
+ Egrp *eg;
+ Evalue *e;
+
+ if(s == DEVDOTDOT){
+ devdir(c, c->qid, "#e", 0, eve, DMDIR|0775, dp);
+ return 1;
+ }
+ eg = up->env->egrp;
+ qlock(eg);
+ for(e = eg->entries; e != nil && s != 0; e = e->next)
+ s--;
+ if(e == nil) {
+ qunlock(eg);
+ return -1;
+ }
+ /* make sure name string continues to exist after we release lock */
+ kstrcpy(up->genbuf, e->var, sizeof up->genbuf);
+ devdir(c, e->qid, up->genbuf, e->len, eve, 0666, dp);
+ qunlock(eg);
+ return 1;
+}
+
+static Chan*
+envattach(char *spec)
+{
+ if(up->env == nil || up->env->egrp == nil)
+ error(Enodev);
+ return devattach('e', spec);
+}
+
+static Walkqid*
+envwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, 0, 0, envgen);
+}
+
+static int
+envstat(Chan *c, uchar *db, int n)
+{
+ if(c->qid.type & QTDIR)
+ c->qid.vers = up->env->egrp->vers;
+ return devstat(c, db, n, 0, 0, envgen);
+}
+
+static Chan *
+envopen(Chan *c, int mode)
+{
+ Egrp *eg;
+ Evalue *e;
+
+ if(c->qid.type & QTDIR) {
+ if(mode != OREAD)
+ error(Eperm);
+ c->mode = mode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+ eg = up->env->egrp;
+ qlock(eg);
+ for(e = eg->entries; e != nil; e = e->next)
+ if(e->qid.path == c->qid.path)
+ break;
+ if(e == nil) {
+ qunlock(eg);
+ error(Enonexist);
+ }
+ if((mode & OTRUNC) && e->val) {
+ free(e->val);
+ e->val = 0;
+ e->len = 0;
+ e->qid.vers++;
+ }
+ qunlock(eg);
+ c->mode = openmode(mode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static void
+envcreate(Chan *c, char *name, int mode, ulong)
+{
+ Egrp *eg;
+ Evalue *e, **le;
+
+ if(c->qid.type != QTDIR)
+ error(Eperm);
+ if(strlen(name) >= sizeof(up->genbuf))
+ error("name too long"); /* needs to fit for stat */
+ mode = openmode(mode);
+ eg = up->env->egrp;
+ qlock(eg);
+ if(waserror()){
+ qunlock(eg);
+ nexterror();
+ }
+ for(le = &eg->entries; (e = *le) != nil; le = &e->next)
+ if(strcmp(e->var, name) == 0)
+ error(Eexist);
+ e = smalloc(sizeof(Evalue));
+ e->var = smalloc(strlen(name)+1);
+ strcpy(e->var, name);
+ e->val = 0;
+ e->len = 0;
+ e->qid.path = ++eg->path;
+ e->next = nil;
+ e->qid.vers = 0;
+ *le = e;
+ c->qid = e->qid;
+ eg->vers++;
+ poperror();
+ qunlock(eg);
+ c->offset = 0;
+ c->flag |= COPEN;
+ c->mode = mode;
+}
+
+static void
+envclose(Chan *c)
+{
+ if(c->flag & CRCLOSE)
+ envremove(c);
+}
+
+static long
+envread(Chan *c, void *a, long n, vlong offset)
+{
+ Egrp *eg;
+ Evalue *e;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, envgen);
+ eg = up->env->egrp;
+ qlock(eg);
+ if(waserror()){
+ qunlock(eg);
+ nexterror();
+ }
+ for(e = eg->entries; e != nil; e = e->next)
+ if(e->qid.path == c->qid.path)
+ break;
+ if(e == nil)
+ error(Enonexist);
+ if(offset > e->len) /* protects against overflow converting vlong to ulong */
+ n = 0;
+ else if(offset + n > e->len)
+ n = e->len - offset;
+ if(n <= 0)
+ n = 0;
+ else
+ memmove(a, e->val+offset, n);
+ poperror();
+ qunlock(eg);
+ return n;
+}
+
+static long
+envwrite(Chan *c, void *a, long n, vlong offset)
+{
+ char *s;
+ int ve;
+ Egrp *eg;
+ Evalue *e;
+
+ if(n <= 0)
+ return 0;
+ eg = up->env->egrp;
+ qlock(eg);
+ if(waserror()){
+ qunlock(eg);
+ nexterror();
+ }
+ for(e = eg->entries; e != nil; e = e->next)
+ if(e->qid.path == c->qid.path)
+ break;
+ if(e == nil)
+ error(Enonexist);
+ ve = offset+n;
+ if(ve > Maxenvsize)
+ error(Etoobig);
+ if(ve > e->len) {
+ s = smalloc(ve);
+ memmove(s, e->val, e->len);
+ if(e->val != nil)
+ free(e->val);
+ e->val = s;
+ e->len = ve;
+ }
+ memmove(e->val+offset, a, n);
+ e->qid.vers++;
+ poperror();
+ qunlock(eg);
+ return n;
+}
+
+static void
+envremove(Chan *c)
+{
+ Egrp *eg;
+ Evalue *e, **l;
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+ eg = up->env->egrp;
+ qlock(eg);
+ for(l = &eg->entries; (e = *l) != nil; l = &e->next)
+ if(e->qid.path == c->qid.path)
+ break;
+ if(e == nil) {
+ qunlock(eg);
+ error(Enonexist);
+ }
+ *l = e->next;
+ eg->vers++;
+ qunlock(eg);
+ free(e->var);
+ if(e->val != nil)
+ free(e->val);
+ free(e);
+}
+
+Dev envdevtab = {
+ 'e',
+ "env",
+
+ devreset,
+ devinit,
+ devshutdown,
+ envattach,
+ envwalk,
+ envstat,
+ envopen,
+ envcreate,
+ envclose,
+ envread,
+ devbread,
+ envwrite,
+ devbwrite,
+ envremove,
+ devwstat
+};
+
+/*
+ * kernel interface to environment variables
+ */
+Egrp*
+newegrp(void)
+{
+ Egrp *e;
+
+ e = smalloc(sizeof(Egrp));
+ e->ref = 1;
+ return e;
+}
+
+void
+closeegrp(Egrp *e)
+{
+ Evalue *el, *nl;
+
+ if(e == nil || decref(e) != 0)
+ return;
+ for (el = e->entries; el != nil; el = nl) {
+ free(el->var);
+ if (el->val)
+ free(el->val);
+ nl = el->next;
+ free(el);
+ }
+ free(e);
+}
+
+void
+egrpcpy(Egrp *to, Egrp *from)
+{
+ Evalue *e, *ne, **last;
+
+ if(from == nil)
+ return;
+ last = &to->entries;
+ qlock(from);
+ for (e = from->entries; e != nil; e = e->next) {
+ ne = smalloc(sizeof(Evalue));
+ ne->var = smalloc(strlen(e->var)+1);
+ strcpy(ne->var, e->var);
+ if (e->val) {
+ ne->val = smalloc(e->len);
+ memmove(ne->val, e->val, e->len);
+ ne->len = e->len;
+ }
+ ne->qid.path = ++to->path;
+ *last = ne;
+ last = &ne->next;
+ }
+ qunlock(from);
+}
+
+void
+ksetenv(char *var, char *val, int)
+{
+ Chan *c;
+ char buf[2*KNAMELEN];
+
+ snprint(buf, sizeof(buf), "#e/%s", var);
+ if(waserror())
+ return;
+ c = namec(buf, Acreate, OWRITE, 0600);
+ poperror();
+ if(!waserror()){
+ if(!waserror()){
+ devtab[c->type]->write(c, val, strlen(val), 0);
+ poperror();
+ }
+ poperror();
+ }
+ cclose(c);
+}
diff --git a/os/port/devflash.c b/os/port/devflash.c
new file mode 100644
index 00000000..a014fa46
--- /dev/null
+++ b/os/port/devflash.c
@@ -0,0 +1,641 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+/*
+ * flash memory
+ */
+
+#include "../port/flashif.h"
+
+typedef struct Flashtype Flashtype;
+struct Flashtype {
+ char* name;
+ int (*reset)(Flash*);
+ Flashtype* next;
+};
+
+enum {
+ Nbanks = 2,
+};
+
+static struct
+{
+ Flash* card[Nbanks]; /* actual card type, reset for access */
+ Flashtype* types; /* possible card types */
+}flash;
+
+enum{
+ Qtopdir,
+ Qflashdir,
+ Qdata,
+ Qctl,
+};
+
+#define TYPE(q) ((ulong)(q) & 0xFF)
+#define PART(q) ((ulong)(q)>>8)
+#define QID(p,t) (((p)<<8) | (t))
+
+static Flashregion* flashregion(Flash*, ulong);
+static char* flashnewpart(Flash*, char*, ulong, ulong);
+static ulong flashaddr(Flash*, Flashpart*, char*);
+static void protect(Flash*, ulong);
+static void eraseflash(Flash*, Flashregion*, ulong);
+static long readflash(Flash*, void*, long, int);
+static long writeflash(Flash*, long, void*,int);
+
+static char Eprotect[] = "flash region protected";
+
+static int
+flash2gen(Chan *c, ulong p, Dir *dp)
+{
+ Flashpart *fp;
+ Flash *f;
+ Qid q;
+ int mode;
+
+ f = flash.card[c->dev];
+ fp = &f->part[PART(p)];
+ if(fp->name == nil)
+ return 0;
+ mkqid(&q, p, 0, QTFILE);
+ switch(TYPE(p)){
+ case Qdata:
+ mode = 0660;
+ if(f->write == nil)
+ mode = 0440;
+ devdir(c, q, fp->name, fp->end-fp->start, eve, mode, dp);
+ return 1;
+ case Qctl:
+ snprint(up->genbuf, sizeof(up->genbuf), "%sctl", fp->name);
+ devdir(c, q, up->genbuf, 0, eve, 0660, dp);
+ return 1;
+ default:
+ return -1;
+ }
+}
+
+static int
+flashgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
+{
+ Qid q;
+ char *n;
+
+ if(s == DEVDOTDOT){
+ mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
+ n = "#F";
+ if(c->dev != 0){
+ sprint(up->genbuf, "#F%ld", c->dev);
+ n = up->genbuf;
+ }
+ devdir(c, q, n, 0, eve, 0555, dp);
+ return 1;
+ }
+ switch(TYPE(c->qid.path)){
+ case Qtopdir:
+ if(s != 0)
+ break;
+ mkqid(&q, QID(0, Qflashdir), 0, QTDIR);
+ n = "flash";
+ if(c->dev != 0){
+ sprint(up->genbuf, "flash%ld", c->dev);
+ n = up->genbuf;
+ }
+ devdir(c, q, n, 0, eve, 0555, dp);
+ return 1;
+ case Qflashdir:
+ if(s >= 2*nelem(flash.card[c->dev]->part))
+ return -1;
+ return flash2gen(c, QID(s>>1, s&1?Qctl:Qdata), dp);
+ case Qctl:
+ case Qdata:
+ return flash2gen(c, (ulong)c->qid.path, dp);
+ }
+ return -1;
+}
+
+static void
+flashreset(void)
+{
+ Flash *f;
+ Flashtype *t;
+ char *e;
+ int bank;
+
+ for(bank = 0; bank < Nbanks; bank++){
+ f = malloc(sizeof(*f));
+ if(f == nil){
+ print("#F%d: can't allocate Flash data\n", bank);
+ return;
+ }
+ f->cmask = ~(ulong)0;
+ if(archflashreset(bank, f) < 0 || f->type == nil || f->addr == nil){
+ free(f);
+ return;
+ }
+ for(t = flash.types; t != nil; t = t->next)
+ if(strcmp(f->type, t->name) == 0)
+ break;
+ if(t == nil){
+ iprint("#F%d: no flash driver for type %s (addr %p)\n", bank, f->type, f->addr);
+ free(f);
+ return;
+ }
+ f->reset = t->reset;
+ f->protect = 1;
+ if(f->reset(f) == 0){
+ flash.card[bank] = f;
+ iprint("#F%d: %s addr 0x%lux len %lud width %d interleave %d\n", bank, f->type, PADDR(f->addr), f->size, f->width, f->interleave);
+ e = flashnewpart(f, "flash", 0, f->size);
+ if(e != nil)
+ panic("#F%d: couldn't init table: %s\n", bank, e); /* shouldn't happen */
+ }else
+ iprint("#F%d: reset failed (%s)\n", bank, f->type);
+ }
+}
+
+static Chan*
+flashattach(char *spec)
+{
+ Flash *f;
+ int bank;
+ Chan *c;
+
+ bank = strtol(spec, nil, 0);
+ if(bank < 0 || bank >= Nbanks ||
+ (f = flash.card[bank]) == nil ||
+ f->attach != nil && f->attach(f) < 0)
+ error(Enodev);
+ c = devattach('F', spec);
+ c->dev = bank;
+ return c;
+}
+
+static Walkqid*
+flashwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, flashgen);
+}
+
+static int
+flashstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, nil, 0, flashgen);
+}
+
+static Chan*
+flashopen(Chan *c, int omode)
+{
+ omode = openmode(omode);
+ switch(TYPE(c->qid.path)){
+ case Qdata:
+ case Qctl:
+ if(flash.card[c->dev] == nil)
+ error(Enodev);
+ break;
+ }
+ return devopen(c, omode, nil, 0, flashgen);
+}
+
+static void
+flashclose(Chan*)
+{
+}
+
+static long
+flashread(Chan *c, void *buf, long n, vlong offset)
+{
+ Flash *f;
+ char *s, *o;
+ Flashpart *fp;
+ Flashregion *r;
+ int i;
+ ulong start, end;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, buf, n, nil, 0, flashgen);
+
+ f = flash.card[c->dev];
+ fp = &f->part[PART(c->qid.path)];
+ if(fp->name == nil)
+ error(Egreg);
+ switch(TYPE(c->qid.path)){
+ case Qdata:
+ offset += fp->start;
+ if(offset >= fp->end)
+ return 0;
+ if(offset+n > fp->end)
+ n = fp->end - offset;
+ n = readflash(f, buf, offset, n);
+ if(n < 0)
+ error(Eio);
+ return n;
+ case Qctl:
+ s = malloc(READSTR);
+ if(waserror()){
+ free(s);
+ nexterror();
+ }
+ o = seprint(s, s+READSTR, "%#2.2ux %#4.4ux %d %q\n",
+ f->id, f->devid, f->width, f->sort!=nil? f->sort: "nor");
+ for(i=0; i<f->nr; i++){
+ r = &f->regions[i];
+ if(r->start < fp->end && fp->start < r->end){
+ start = r->start;
+ if(fp->start > start)
+ start = fp->start;
+ end = r->end;
+ if(fp->end < end)
+ end = fp->end;
+ o = seprint(o, s+READSTR, "%#8.8lux %#8.8lux %#8.8lux", start, end, r->erasesize);
+ if(r->pagesize)
+ o = seprint(o, s+READSTR, " %#8.8lux", r->pagesize);
+ o = seprint(o, s+READSTR, "\n");
+ }
+ }
+ n = readstr(offset, buf, n, s);
+ poperror();
+ free(s);
+ return n;
+ }
+ error(Egreg);
+ return 0; /* not reached */
+}
+
+enum {
+ CMerase,
+ CMadd,
+ CMremove,
+ CMsync,
+ CMprotectboot,
+};
+
+static Cmdtab flashcmds[] = {
+ {CMerase, "erase", 2},
+ {CMadd, "add", 0},
+ {CMremove, "remove", 2},
+ {CMsync, "sync", 0},
+ {CMprotectboot, "protectboot", 0},
+};
+
+static long
+flashwrite(Chan *c, void *buf, long n, vlong offset)
+{
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ ulong addr, start, end;
+ char *e;
+ Flashpart *fp;
+ Flashregion *r;
+ Flash *f;
+
+ f = flash.card[c->dev];
+ fp = &f->part[PART(c->qid.path)];
+ if(fp->name == nil)
+ error(Egreg);
+ switch(TYPE(c->qid.path)){
+ case Qdata:
+ if(f->write == nil)
+ error(Eperm);
+ offset += fp->start;
+ if(offset >= fp->end)
+ return 0;
+ if(offset+n > fp->end)
+ n = fp->end - offset;
+ n = writeflash(f, offset, buf, n);
+ if(n < 0)
+ error(Eio);
+ return n;
+ case Qctl:
+ cb = parsecmd(buf, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, flashcmds, nelem(flashcmds));
+ switch(ct->index){
+ case CMerase:
+ if(strcmp(cb->f[1], "all") != 0){
+ addr = flashaddr(f, fp, cb->f[1]);
+ r = flashregion(f, addr);
+ if(r == nil)
+ error("nonexistent flash region");
+ if(addr%r->erasesize != 0)
+ error("invalid erase block address");
+ eraseflash(f, r, addr);
+ }else if(fp->start == 0 && fp->end == f->size && f->eraseall != nil){
+ eraseflash(f, nil, 0);
+ }else{
+ for(addr = fp->start; addr < fp->end; addr += r->erasesize){
+ r = flashregion(f, addr);
+ if(r == nil)
+ error("nonexistent flash region");
+ if(addr%r->erasesize != 0)
+ error("invalid erase block address");
+ eraseflash(f, r, addr);
+ }
+ }
+ break;
+ case CMadd:
+ if(cb->nf < 3)
+ error(Ebadarg);
+ start = flashaddr(f, fp, cb->f[2]);
+ if(cb->nf > 3 && strcmp(cb->f[3], "end") != 0)
+ end = flashaddr(f, fp, cb->f[3]);
+ else
+ end = fp->end;
+ if(start > end || start >= fp->end || end > fp->end)
+ error(Ebadarg);
+ e = flashnewpart(f, cb->f[1], start, end);
+ if(e != nil)
+ error(e);
+ break;
+ case CMremove:
+ /* TO DO */
+ break;
+ case CMprotectboot:
+ if(cb->nf > 1 && strcmp(cb->f[1], "off") == 0)
+ f->protect = 0;
+ else
+ f->protect = 1;
+ break;
+ case CMsync:
+ /* TO DO? */
+ break;
+ default:
+ error(Ebadarg);
+ }
+ poperror();
+ free(cb);
+ return n;
+ }
+ error(Egreg);
+ return 0; /* not reached */
+}
+
+static char*
+flashnewpart(Flash *f, char *name, ulong start, ulong end)
+{
+ Flashpart *fp, *empty;
+ int i;
+
+ empty = nil;
+ for(i = 0; i < nelem(f->part); i++){
+ fp = &f->part[i];
+ if(fp->name == nil){
+ if(empty == nil)
+ empty = fp;
+ }else if(strcmp(fp->name, name) == 0)
+ return Eexist;
+ }
+ if((fp = empty) == nil)
+ return "partition table full";
+ fp->name = strdup(name);
+ if(fp->name == nil)
+ return Enomem;
+ fp->start = start;
+ fp->end = end;
+ return nil;
+}
+
+static ulong
+flashaddr(Flash *f, Flashpart *fp, char *s)
+{
+ Flashregion *r;
+ ulong addr;
+
+ addr = strtoul(s, &s, 0);
+ if(*s)
+ error(Ebadarg);
+ if(fp->name == nil)
+ error("partition removed");
+ addr += fp->start;
+ r = flashregion(f, addr);
+ if(r != nil && addr%r->erasesize != 0)
+ error("invalid erase unit address");
+ if(addr < fp->start || addr > fp->end || addr > f->size)
+ error(Ebadarg);
+ return addr;
+}
+
+static Flashregion*
+flashregion(Flash *f, ulong a)
+{
+ int i;
+ Flashregion *r;
+
+ for(i=0; i<f->nr; i++){
+ r = &f->regions[i];
+ if(r->start <= a && a < r->end)
+ return r;
+ }
+ return nil;
+}
+
+Dev flashdevtab = {
+ 'F',
+ "flash",
+
+ flashreset,
+ devinit,
+ devshutdown,
+ flashattach,
+ flashwalk,
+ flashstat,
+ flashopen,
+ devcreate,
+ flashclose,
+ flashread,
+ devbread,
+ flashwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
+/*
+ * called by flash card types named in link section (eg, flashamd.c)
+ */
+void
+addflashcard(char *name, int (*reset)(Flash*))
+{
+ Flashtype *f, **l;
+
+ f = (Flashtype*)malloc(sizeof(*f));
+ f->name = name;
+ f->reset = reset;
+ f->next = nil;
+ for(l = &flash.types; *l != nil; l = &(*l)->next)
+ ;
+ *l = f;
+}
+
+static long
+readflash(Flash *f, void *buf, long offset, int n)
+{
+ int r, width, wmask;
+ uchar tmp[16];
+ uchar *p;
+ ulong o;
+
+ if(offset < 0 || offset+n > f->size)
+ error(Ebadarg);
+ qlock(f);
+ if(waserror()){
+ qunlock(f);
+ nexterror();
+ }
+ if(f->read != nil){
+ width = f->width;
+ wmask = width-1;
+ p = buf;
+ if(offset&wmask) {
+ o = offset & ~wmask;
+ if(f->read(f, o, (ulong*)tmp, width) < 0)
+ error(Eio);
+ memmove(tmp, (uchar*)f->addr+o, width);
+ for(; n > 0 && offset&wmask; n--)
+ *p++ = tmp[offset++&wmask];
+ }
+ r = n&wmask;
+ n &= ~wmask;
+ if(n){
+ if(f->read(f, offset, (ulong*)p, n) < 0)
+ error(Eio);
+ offset += n;
+ p += n;
+ }
+ if(r){
+ if(f->read(f, offset, (ulong*)tmp, width))
+ error(Eio);
+ memmove(p, tmp, r);
+ }
+ }else
+ memmove(buf, (uchar*)f->addr+offset, n); /* assumes hardware supports byte access */
+ poperror();
+ qunlock(f);
+ return n;
+}
+
+static long
+writeflash(Flash *f, long offset, void *buf, int n)
+{
+ uchar tmp[16];
+ uchar *p;
+ ulong o;
+ int r, width, wmask;
+ Flashregion *rg;
+
+ if(f->write == nil || offset < 0 || offset+n > f->size)
+ error(Ebadarg);
+ rg = flashregion(f, offset);
+ if(f->protect && rg != nil && rg->start == 0 && offset < rg->erasesize)
+ error(Eprotect);
+ width = f->width;
+ wmask = width-1;
+ qlock(f);
+ archflashwp(f, 0);
+ if(waserror()){
+ archflashwp(f, 1);
+ qunlock(f);
+ nexterror();
+ }
+ p = buf;
+ if(offset&wmask){
+ o = offset & ~wmask;
+ if(f->read != nil){
+ if(f->read(f, o, tmp, width) < 0)
+ error(Eio);
+ }else
+ memmove(tmp, (uchar*)f->addr+o, width);
+ for(; n > 0 && offset&wmask; n--)
+ tmp[offset++&wmask] = *p++;
+ if(f->write(f, o, tmp, width) < 0)
+ error(Eio);
+ }
+ r = n&wmask;
+ n &= ~wmask;
+ if(n){
+ if(f->write(f, offset, p, n) < 0)
+ error(Eio);
+ offset += n;
+ p += n;
+ }
+ if(r){
+ if(f->read != nil){
+ if(f->read(f, offset, tmp, width) < 0)
+ error(Eio);
+ }else
+ memmove(tmp, (uchar*)f->addr+offset, width);
+ memmove(tmp, p, r);
+ if(f->write(f, offset, tmp, width) < 0)
+ error(Eio);
+ }
+ poperror();
+ archflashwp(f, 1);
+ qunlock(f);
+ return n;
+}
+
+static void
+eraseflash(Flash *f, Flashregion *r, ulong addr)
+{
+ int rv;
+
+ if(f->protect && r != nil && r->start == 0 && addr < r->erasesize)
+ error(Eprotect);
+ qlock(f);
+ archflashwp(f, 0);
+ if(waserror()){
+ archflashwp(f, 1);
+ qunlock(f);
+ nexterror();
+ }
+ if(r == nil){
+ if(f->eraseall != nil)
+ rv = f->eraseall(f);
+ else
+ rv = -1;
+ }else
+ rv = f->erasezone(f, r, addr);
+ if(rv < 0)
+ error(Eio);
+ poperror();
+ archflashwp(f, 1);
+ qunlock(f);
+}
+
+/*
+ * flash access taking width and interleave into account
+ */
+int
+flashget(Flash *f, ulong a)
+{
+ switch(f->width){
+ default:
+ return ((uchar*)f->addr)[a<<f->bshift];
+ case 2:
+ return ((ushort*)f->addr)[a];
+ case 4:
+ return ((ulong*)f->addr)[a];
+ }
+}
+
+void
+flashput(Flash *f, ulong a, int v)
+{
+ switch(f->width){
+ default:
+ ((uchar*)f->addr)[a<<f->bshift] = v;
+ break;
+ case 2:
+ ((ushort*)f->addr)[a] = v;
+ break;
+ case 4:
+ ((ulong*)f->addr)[a] = v;
+ break;
+ }
+}
diff --git a/os/port/devftl.c b/os/port/devftl.c
new file mode 100644
index 00000000..ab24588a
--- /dev/null
+++ b/os/port/devftl.c
@@ -0,0 +1,1365 @@
+/*
+ * basic Flash Translation Layer driver
+ * see for instance the Intel technical paper
+ * ``Understanding the Flash Translation Layer (FTL) Specification''
+ * Order number 297816-001 (online at www.intel.com)
+ *
+ * a public driver by David Hinds, dhinds@allegro.stanford.edu
+ * further helps with some details.
+ *
+ * this driver uses the common simplification of never storing
+ * the VBM on the medium (a waste of precious flash!) but
+ * rather building it on the fly as the block maps are read.
+ *
+ * Plan 9 driver (c) 1997 by C H Forsyth (forsyth@terzarima.net)
+ * This driver may be used or adapted by anyone for any non-commercial purpose.
+ *
+ * adapted for Inferno 1998 by C H Forsyth, Vita Nuova Limited, York, England (forsyth@vitanuova.com)
+ *
+ * TO DO:
+ * check error handling details for get/put flash
+ * bad block handling
+ * reserved space in formatted size
+ * possibly block size as parameter
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+#include "kernel.h"
+
+#ifndef offsetof
+#define offsetof(s, m) (ulong)(&(((s*)0)->m))
+#endif
+
+typedef struct Ftl Ftl;
+typedef struct Merase Merase;
+typedef struct Terase Terase;
+
+enum {
+ Eshift = 18, /* 2^18=256k; log2(eraseunit) */
+ Flashseg = 1<<Eshift,
+ Bshift = 9, /* 2^9=512 */
+ Bsize = 1<<Bshift,
+ BAMoffset = 0x100,
+ Nolimit = ~0,
+ USABLEPCT = 95, /* release only this % to client */
+
+ FTLDEBUG = 0,
+
+ NPART = 4,
+};
+
+/* erase unit header (defined by FTL specification) */
+struct Merase {
+ uchar linktuple[5];
+ uchar orgtuple[10];
+ uchar nxfer;
+ uchar nerase[4];
+ uchar id[2];
+ uchar bshift;
+ uchar eshift;
+ uchar pstart[2];
+ uchar nunits[2];
+ uchar psize[4];
+ uchar vbmbase[4];
+ uchar nvbm[2];
+ uchar flags;
+ uchar code;
+ uchar serial[4];
+ uchar altoffset[4];
+ uchar bamoffset[4];
+ uchar rsv2[12];
+};
+#define ERASEHDRLEN 64
+
+enum {
+ /* special unit IDs */
+ XferID = 0xffff,
+ XferBusy = 0x7fff,
+
+ /* special BAM addresses */
+ Bfree = 0xffffffff,
+ Bwriting = 0xfffffffe,
+ Bdeleted = 0,
+
+ /* block types */
+ TypeShift = 7,
+ BlockType = (1<<TypeShift)-1,
+ ControlBlock = 0x30,
+ DataBlock = 0x40,
+ ReplacePage = 0x60,
+ BadBlock = 0x70,
+};
+
+#define BTYPE(b) ((b) & BlockType)
+#define BADDR(b) ((b) & ~BlockType)
+#define BNO(va) (((ulong)(va))>>Bshift)
+#define MKBAM(b,t) (((b)<<Bshift)|(t))
+
+struct Terase {
+ int x;
+ int id;
+ ulong offset;
+ ulong bamoffset;
+ ulong nbam;
+ ulong* bam;
+ ulong bamx;
+ ulong nfree;
+ ulong nused;
+ ulong ndead;
+ ulong nbad;
+ ulong nerase;
+};
+
+struct Ftl {
+ QLock;
+ Ref;
+
+ Chan* flash;
+ Chan* flashctl;
+ ulong base; /* base of flash region */
+ ulong size; /* size of flash region */
+ ulong segsize; /* size of flash segment (erase unit) */
+ int eshift; /* log2(erase-unit-size) */
+ int bshift; /* log2(bsize) */
+ int bsize;
+ int nunit; /* number of segments (erase units) */
+ Terase** unit;
+ int lastx; /* index in unit of last allocation */
+ int xfer; /* index in unit of current transfer unit (-1 if none) */
+ ulong nfree; /* total free space in blocks */
+ ulong nblock; /* total space in blocks */
+ ulong rwlimit; /* user-visible block limit (`formatted size') */
+ ulong* vbm; /* virtual block map */
+ ulong fstart; /* address of first block of data in a segment */
+ int trace; /* (debugging) trace of read/write actions */
+ int detach; /* free Ftl on last close */
+
+ /* scavenging variables */
+ QLock wantq;
+ Rendez wantr;
+ Rendez workr;
+ int needspace;
+ int hasproc;
+ int npart; /* over and above ftldata */
+ struct {
+ ulong start, size;
+ ulong rwlimit;
+ char *name; /* nil if slot unused */
+ } part[NPART];
+};
+
+enum {
+ /* Ftl.detach */
+ Detached = 1, /* detach on close */
+ Deferred /* scavenger must free it */
+};
+
+/* little endian */
+#define GET2(p) (((p)[1]<<8)|(p)[0])
+#define GET4(p) (((((((p)[3]<<8)|(p)[2])<<8)|(p)[1])<<8)|(p)[0])
+#define PUT2(p,v) (((p)[1]=(v)>>8),((p)[0]=(v)))
+#define PUT4(p,v) (((p)[3]=(v)>>24),((p)[2]=(v)>>16),((p)[1]=(v)>>8),((p)[0]=(v)))
+
+static Lock ftllock;
+static Ftl *ftls;
+static int ftlpct = USABLEPCT;
+
+static ulong allocblk(Ftl*);
+static int erasedetect(Ftl *ftl, ulong base, ulong size, ushort *pstart, ushort *nunits);
+static void eraseflash(Ftl*, ulong);
+static void erasefree(Terase*);
+static void eraseinit(Ftl*, ulong, int, int);
+static Terase* eraseload(Ftl*, int, ulong);
+static void ftlfree(Ftl*);
+static void getflash(Ftl*, void*, ulong, long);
+static int mapblk(Ftl*, ulong, Terase**, ulong*);
+static Ftl* mkftl(char*, ulong, ulong, int, char*);
+static void putbam(Ftl*, Terase*, int, ulong);
+static void putflash(Ftl*, ulong, void*, long);
+static int scavenge(Ftl*);
+
+enum {
+ Qdir,
+ Qctl,
+ Qdata,
+};
+
+#define DATAQID(q) ((q) >= Qdata && (q) <= Qdata + NPART)
+
+static void
+ftlpartcmd(Ftl *ftl, char **fields, int nfields)
+{
+ ulong start, end;
+ char *p;
+ int n, newn;
+
+ /* name start [end] */
+ if(nfields < 2 || nfields > 3)
+ error(Ebadarg);
+ if(ftl->npart >= NPART)
+ error("table full");
+ if(strcmp(fields[0], "ctl") == 0 || strcmp(fields[0], "data") == 0)
+ error(Ebadarg);
+ newn = -1;
+ for(n = 0; n < NPART; n++){
+ if(ftl->part[n].name == nil){
+ if(newn < 0)
+ newn = n;
+ continue;
+ }
+ if(strcmp(fields[0], ftl->part[n].name + 3) == 0)
+ error(Ebadarg);
+ }
+ start = strtoul(fields[1], 0, 0);
+ if(nfields > 2)
+ end = strtoul(fields[2], 0, 0);
+ else
+ end = ftl->rwlimit * Bsize;
+ if(start >= end || start % Bsize || end % Bsize)
+ error(Ebadarg);
+ ftl->part[newn].start = start;
+ ftl->part[newn].size = end - start;
+ ftl->part[newn].rwlimit = end / Bsize;
+ free(ftl->part[newn].name);
+ p = malloc(strlen(fields[0]) + 3 + 1);
+ strcpy(p, "ftl");
+ strcat(p, fields[0]);
+ ftl->part[newn].name = p;
+ ftl->npart++;
+}
+
+static void
+ftldelpartcmd(Ftl *ftl, char **fields, int nfields)
+{
+ int n;
+ // name
+ if(nfields != 1)
+ error(Ebadarg);
+ for(n = 0; n < NPART; n++)
+ if(strcmp(fields[0], ftl->part[n].name + 3) == 0){
+ free(ftl->part[n].name);
+ ftl->part[n].name = nil;
+ ftl->npart--;
+ return;
+ }
+ error(Ebadarg);
+}
+
+static int
+ftlgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
+{
+ int n;
+ switch(i){
+ case DEVDOTDOT:
+ devdir(c, (Qid){Qdir, 0, QTDIR}, "#X", 0, eve, 0555, dp);
+ break;
+ case 0:
+ devdir(c, (Qid){Qctl, 0, QTFILE}, "ftlctl", 0, eve, 0660, dp);
+ break;
+ case 1:
+ devdir(c, (Qid){Qdata, 0, QTFILE}, "ftldata", ftls ? ftls->rwlimit * Bsize : 0, eve, 0660, dp);
+ break;
+ default:
+ if(ftls == nil)
+ return -1;
+ i -= 2;
+ if(i >= ftls->npart)
+ return -1;
+ for(n = 0; n < NPART; n++)
+ if(ftls->part[n].name != nil){
+ if(i == 0)
+ break;
+ i--;
+ }
+ if(i != 0){
+ print("wierd\n");
+ return -1;
+ }
+ devdir(c, (Qid){Qdata + 1 + n, 0, QTFILE}, ftls->part[n].name, ftls->part[n].size, eve, 0660, dp);
+ }
+ return 1;
+}
+
+static Ftl *
+ftlget(void)
+{
+ Ftl *ftl;
+
+ lock(&ftllock);
+ ftl = ftls;
+ if(ftl != nil)
+ incref(ftl);
+ unlock(&ftllock);
+ return ftl;
+}
+
+static void
+ftlput(Ftl *ftl)
+{
+ if(ftl != nil){
+ lock(&ftllock);
+ if(decref(ftl) == 0 && ftl->detach == Detached){
+ ftls = nil;
+ if(ftl->hasproc){ /* no lock needed: can't change if ftl->ref==0 */
+ ftl->detach = Deferred;
+ wakeup(&ftl->workr);
+ }else
+ ftlfree(ftl);
+ }
+ unlock(&ftllock);
+ }
+}
+
+static Chan *
+ftlattach(char *spec)
+{
+ return devattach('X', spec);
+}
+
+static Walkqid*
+ftlwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+
+ wq = devwalk(c, nc, name, nname, 0, 0, ftlgen);
+ if(wq != nil && wq->clone != nil && wq->clone != c)
+ if(DATAQID(wq->clone->qid.path))
+ wq->clone->aux = ftlget();
+ return wq;
+}
+
+static int
+ftlstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, 0, 0, ftlgen);
+}
+
+static Chan*
+ftlopen(Chan *c, int omode)
+{
+ Ftl *ftl;
+ omode = openmode(omode);
+ if(DATAQID(c->qid.path)){
+ ftl = ftls;
+ if(ftl == nil)
+ error(Enodev);
+ if(strcmp(up->env->user, eve)!=0)
+ error(Eperm);
+ }
+ else if(c->qid.path == Qctl){
+ if(strcmp(up->env->user, eve)!=0)
+ error(Eperm);
+ }
+ c = devopen(c, omode, 0, 0, ftlgen);
+ if(DATAQID(c->qid.path)){
+ c->aux = ftlget();
+ if(c->aux == nil)
+ error(Enodev);
+ }
+ return c;
+}
+
+static void
+ftlclose(Chan *c)
+{
+ if(DATAQID(c->qid.path) && (c->flag&COPEN) != 0)
+ ftlput((Ftl*)c->aux);
+}
+
+static long
+ftlread(Chan *c, void *buf, long n, vlong offset)
+{
+ Ftl *ftl;
+ Terase *e;
+ int nb;
+ uchar *a;
+ ulong pb;
+ if(c->qid.type & QTDIR)
+ return devdirread(c, buf, n, 0, 0, ftlgen);
+
+ if(DATAQID(c->qid.path)){
+ ulong rwlimit;
+
+ if(n <= 0 || n%Bsize || offset%Bsize)
+ error(Eio);
+ ftl = c->aux;
+ if(c->qid.path > Qdata){
+ int p = c->qid.path - Qdata - 1;
+ offset += ftl->part[p].start;
+ rwlimit = ftl->part[p].rwlimit;
+ }
+ else
+ rwlimit = ftl->rwlimit;
+ nb = n/Bsize;
+ offset /= Bsize;
+ if(offset >= rwlimit)
+ return 0;
+ if(offset+nb > rwlimit)
+ nb = rwlimit - offset;
+ a = buf;
+ for(n = 0; n < nb; n++){
+ qlock(ftl);
+ if(waserror()){
+ qunlock(ftl);
+ nexterror();
+ }
+ if(mapblk(ftl, offset+n, &e, &pb))
+ getflash(ftl, a, e->offset + pb*Bsize, Bsize);
+ else
+ memset(a, 0, Bsize);
+ poperror();
+ qunlock(ftl);
+ a += Bsize;
+ }
+ return a-(uchar*)buf;
+ }
+
+ if(c->qid.path != Qctl)
+ error(Egreg);
+
+ return 0;
+}
+
+static long
+ftlwrite(Chan *c, void *buf, long n, vlong offset)
+{
+ char cmd[64], *fields[6];
+ int ns, i, k, nb;
+ uchar *a;
+ Terase *e, *oe;
+ ulong ob, v, base, size, segsize;
+ Ftl *ftl;
+
+ if(n <= 0)
+ return 0;
+
+ if(DATAQID(c->qid.path)){
+ ulong rwlimit;
+ ftl = c->aux;
+ if(n <= 0 || n%Bsize || offset%Bsize)
+ error(Eio);
+ if(c->qid.path > Qdata){
+ int p = c->qid.path - Qdata - 1;
+ offset += ftl->part[p].start;
+ rwlimit = ftl->part[p].rwlimit;
+ }
+ else
+ rwlimit = ftl->rwlimit;
+ nb = n/Bsize;
+ offset /= Bsize;
+ if(offset >= rwlimit)
+ return 0;
+ if(offset+nb > rwlimit)
+ nb = rwlimit - offset;
+ a = buf;
+ for(n = 0; n < nb; n++){
+ ns = 0;
+ while((v = allocblk(ftl)) == 0)
+ if(!scavenge(ftl) || ++ns > 3){
+ static int stop;
+
+ if(stop < 3){
+ stop++;
+ print("ftl: flash memory full\n");
+ }
+ error("flash memory full");
+ }
+ qlock(ftl);
+ if(waserror()){
+ qunlock(ftl);
+ nexterror();
+ }
+ if(!mapblk(ftl, offset+n, &oe, &ob))
+ oe = nil;
+ e = ftl->unit[v>>16];
+ v &= 0xffff;
+ putflash(ftl, e->offset + v*Bsize, a, Bsize);
+ putbam(ftl, e, v, MKBAM(offset+n, DataBlock));
+ /* both old and new block references exist in this window (can't be closed?) */
+ ftl->vbm[offset+n] = (e->x<<16) | v;
+ if(oe != nil){
+ putbam(ftl, oe, ob, Bdeleted);
+ oe->ndead++;
+ }
+ poperror();
+ qunlock(ftl);
+ a += Bsize;
+ }
+ return a-(uchar*)buf;
+ }
+ else if(c->qid.path == Qctl){
+ if(n > sizeof(cmd)-1)
+ n = sizeof(cmd)-1;
+ memmove(cmd, buf, n);
+ cmd[n] = 0;
+ i = getfields(cmd, fields, 6, 1, " \t\n");
+ if(i <= 0)
+ error(Ebadarg);
+ if(i >= 2 && (strcmp(fields[0], "init") == 0 || strcmp(fields[0], "format") == 0)){
+ if(i > 2)
+ base = strtoul(fields[2], nil, 0);
+ else
+ base = 0; /* TO DO: hunt for signature */
+ if(i > 3)
+ size = strtoul(fields[3], nil, 0);
+ else
+ size = Nolimit;
+ if(i > 4)
+ segsize = strtoul(fields[4], nil, 0);
+ else
+ segsize = 0;
+ /* segsize must be power of two and size and base must be multiples of it
+ * if segsize is zero, then use whatever the device returns
+ */
+ if(segsize != 0 && (segsize > size
+ || segsize&(segsize-1)
+ || (base != Nolimit && base&(segsize-1))
+ || size == 0
+ || (size != Nolimit && size&(segsize-1))))
+ error(Ebadarg);
+ if(segsize == 0)
+ k = 0;
+ else {
+ for(k=0; k<32 && (1<<k) != segsize; k++)
+ ;
+ }
+ if(ftls != nil)
+ error(Einuse);
+ ftls = mkftl(fields[1], base, size, k, fields[0]);
+ }else if(strcmp(fields[0], "scavenge") == 0){
+ if(ftls != nil)
+ print("scavenge %d\n", scavenge(ftls));
+ }else if(strcmp(fields[0], "trace") == 0){
+ if(ftls != nil)
+ ftls->trace = i>1? strtol(fields[1], nil, 0): 1;
+ }else if(strcmp(fields[0], "detach") == 0){
+ if((ftl = ftlget()) != nil){
+ if(ftl->ref > 1){
+ ftlput(ftl);
+ error(Einuse);
+ }
+ ftl->detach = Detached;
+ ftlput(ftl);
+ }else
+ error(Enodev);
+ }else if(strcmp(fields[0], "part")==0){
+ if((ftl = ftlget()) != nil){
+ if(ftl->ref > 1){
+ ftlput(ftl);
+ error(Einuse);
+ }
+ if(waserror()){
+ ftlput(ftl);
+ nexterror();
+ }
+ ftlpartcmd(ftl, fields + 1, i - 1);
+ poperror();
+ ftlput(ftl);
+ }else
+ error(Enodev);
+ }else if(strcmp(fields[0], "delpart")==0){
+ if((ftl = ftlget()) != nil){
+ if(ftl->ref > 1){
+ ftlput(ftl);
+ error(Einuse);
+ }
+ if(waserror()){
+ ftlput(ftl);
+ nexterror();
+ }
+ ftldelpartcmd(ftls, fields + 1, i - 1);
+ poperror();
+ ftlput(ftl);
+ }else
+ error(Enodev);
+ }else if(i >= 2 && strcmp(fields[0], "pct")==0){
+ v = strtoul(fields[1], nil, 0);
+ if(v >= 50)
+ ftlpct = v;
+ }else
+ error(Ebadarg);
+ return n;
+ }
+ error(Egreg);
+ return 0; /* not reached */
+}
+
+static Chan *
+ftlkopen(char *name, char *suffix, int mode)
+{
+ Chan *c;
+ char *fn;
+ int fd;
+
+ if(suffix != nil && *suffix){
+ fn = smalloc(strlen(name)+strlen(suffix)+1);
+ if(fn == nil)
+ return nil;
+ strcpy(fn, name);
+ strcat(fn, suffix);
+ fd = kopen(fn, mode);
+ free(fn);
+ }else
+ fd = kopen(name, mode);
+ if(fd < 0)
+ return nil;
+ c = fdtochan(up->env->fgrp, fd, mode, 0, 1);
+ kclose(fd);
+ return c;
+}
+
+static ulong
+ftlfsize(Chan *c)
+{
+ uchar dbuf[STATFIXLEN+32*4];
+ Dir d;
+ int n;
+
+ n = devtab[c->type]->stat(c, dbuf, sizeof(dbuf));
+ if(convM2D(dbuf, n, &d, nil) == 0)
+ return 0;
+ return d.length;
+}
+
+static Ftl *
+mkftl(char *fname, ulong base, ulong size, int eshift, char *op)
+{
+ int i, j, nov, segblocks, n, badseg, old, valid;
+ ulong limit;
+ Terase *e;
+ Ftl *ftl;
+ char buf[64], *fields[8];
+ ulong ea;
+ Chan *statfd;
+
+ ftl = malloc(sizeof(*ftl));
+ if(ftl == nil)
+ error(Enomem);
+ e = nil;
+ if(waserror()){
+ ftlfree(ftl);
+ if(e)
+ free(e);
+ nexterror();
+ }
+ ftl->lastx = 0;
+ ftl->trace = 0;
+ ftl->flash = ftlkopen(fname, "", ORDWR);
+ if(ftl->flash == nil)
+ error(up->env->errstr);
+ ftl->flashctl = ftlkopen(fname, "ctl", ORDWR);
+ if(ftl->flashctl == nil)
+ error(up->env->errstr);
+ old = 1;
+ statfd = ftlkopen(fname, "stat", OREAD); /* old scheme */
+ if(statfd == nil){
+ statfd = ftl->flashctl; /* new just uses ctl */
+ old = 0;
+ }
+ statfd->offset = 0;
+ if((n = kchanio(statfd, buf, sizeof(buf), OREAD)) <= 0){
+ print("ftl: read stat/ctl failed: %s\n", up->env->errstr);
+ error(up->env->errstr);
+ }
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ buf[n] = 0;
+ if(statfd != ftl->flashctl)
+ cclose(statfd);
+
+ n = getfields(buf, fields, nelem(fields), 1, " \t\n");
+ ea = 0;
+ if(old){
+ if(n >= 4)
+ if((ea = strtoul(fields[3], nil, 16)) < 8*1024)
+ ea = 0; /* assume the format is wrong */
+ }else{
+ if(n >= 7)
+ if((ea = strtoul(fields[6], nil, 0)) < 2*1024)
+ ea = 0; /* assume the format is wrong */
+ }
+ if(ea != 0){
+ for(i=0; i < 32 && (1<<i) != ea; i++)
+ ;
+ if(eshift && i != eshift)
+ print("ftl: overriding erasesize %d with %d\n", 1 << eshift, 1 << i);
+ eshift = i;
+ if(FTLDEBUG)
+ print("ftl: e=%lud eshift=%d\n", ea, i);
+ }
+ if(eshift == 0)
+ error("no erasesize");
+
+ limit = ftlfsize(ftl->flash);
+ if(limit == 0)
+ error("no space for flash translation");
+ ftl->segsize = 1 << eshift;
+ if(base == Nolimit){
+ ushort pstart, nunits;
+ erasedetect(ftl, 0, limit, &pstart, &nunits);
+ base = pstart * ftl->segsize;
+ size = nunits * ftl->segsize;
+ print("ftl: partition in %s at 0x%.8lux, length 0x%.8lux\n", fname, base, size);
+ } else if(size == Nolimit)
+ size = limit-base;
+ if(base >= limit || size > limit || base+size > limit || eshift < 8 || (1<<eshift) > size){
+ print("ftl: bad: base=%#lux limit=%#lux size=%ld eshift=%d\n", base, limit, size, eshift);
+ error("bad flash space parameters");
+ }
+ if(FTLDEBUG)
+ print("%s flash %s #%lux:#%lux limit #%lux\n", op, fname, base, size, limit);
+ ftl->base = base;
+ ftl->size = size;
+ ftl->bshift = Bshift;
+ ftl->bsize = Bsize;
+ ftl->eshift = eshift;
+ ftl->nunit = size>>eshift;
+ nov = ((ftl->segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; /* number of overhead blocks per segment (header, and BAM itself) */
+ ftl->fstart = nov;
+ segblocks = ftl->segsize/Bsize - nov;
+ ftl->nblock = ftl->nunit*segblocks;
+ if(ftl->nblock > 0x10000){
+ /* oops - too many blocks */
+ ftl->nunit = 0x10000 / segblocks;
+ ftl->nblock = ftl->nunit * segblocks;
+ size = ftl->nunit * ftl->segsize;
+ ftl->size = size;
+ print("ftl: too many blocks - limiting to %ld bytes %d units %lud blocks\n",
+ size, ftl->nunit, ftl->nblock);
+ }
+ ftl->vbm = malloc(ftl->nblock*sizeof(*ftl->vbm));
+ ftl->unit = malloc(ftl->nunit*sizeof(*ftl->unit));
+ if(ftl->vbm == nil || ftl->unit == nil)
+ error(Enomem);
+ if(strcmp(op, "format") == 0){
+ for(i=0; i<ftl->nunit-1; i++)
+ eraseinit(ftl, i*ftl->segsize, i, 1);
+ eraseinit(ftl, i*ftl->segsize, XferID, 1);
+ }
+ badseg = -1;
+ ftl->xfer = -1;
+ valid = 0;
+ for(i=0; i<ftl->nunit; i++){
+ e = eraseload(ftl, i, i*ftl->segsize);
+ if(e == nil){
+ print("ftl: logical segment %d: bad format\n", i);
+ if(badseg == -1)
+ badseg = i;
+ else
+ badseg = -2;
+ continue;
+ }
+ if(e->id == XferBusy){
+ e->nerase++;
+ eraseinit(ftl, e->offset, XferID, e->nerase);
+ e->id = XferID;
+ }
+ for(j=0; j<ftl->nunit; j++)
+ if(ftl->unit[j] != nil && ftl->unit[j]->id == e->id){
+ print("ftl: duplicate erase unit #%x\n", e->id);
+ erasefree(e);
+ e = nil;
+ break;
+ }
+ if(e){
+ valid++;
+ ftl->unit[e->x] = e;
+ if(e->id == XferID)
+ ftl->xfer = e->x;
+ if(FTLDEBUG)
+ print("ftl: unit %d:#%x used %lud free %lud dead %lud bad %lud nerase %lud\n",
+ e->x, e->id, e->nused, e->nfree, e->ndead, e->nbad, e->nerase);
+ e = nil;
+ USED(e);
+ }
+ }
+ if(badseg >= 0){
+ if(ftl->xfer >= 0)
+ error("invalid ftl format");
+ i = badseg;
+ eraseinit(ftl, i*ftl->segsize, XferID, 1);
+ e = eraseload(ftl, i, i*ftl->segsize);
+ if(e == nil)
+ error("bad ftl format");
+ ftl->unit[e->x] = e;
+ ftl->xfer = e->x;
+ print("ftl: recovered transfer unit %d\n", e->x);
+ valid++;
+ e = nil;
+ USED(e);
+ }
+ if(ftl->xfer < 0 && valid <= 0 || ftl->xfer >= 0 && valid <= 1)
+ error("no valid flash data units");
+ if(ftl->xfer < 0)
+ error("ftl: no transfer unit: device is WORM\n");
+ else
+ ftl->nblock -= segblocks; /* discount transfer segment */
+ if(ftl->nblock >= 1000)
+ ftl->rwlimit = ftl->nblock-100; /* TO DO: variable reserve */
+ else
+ ftl->rwlimit = ftl->nblock*ftlpct/100;
+ poperror();
+ return ftl;
+}
+
+static void
+ftlfree(Ftl *ftl)
+{
+ int i, n;
+
+ if(ftl != nil){
+ if(ftl->flashctl != nil)
+ cclose(ftl->flashctl);
+ if(ftl->flash != nil)
+ cclose(ftl->flash);
+
+ if(ftl->unit){
+ for(i = 0; i < ftl->nunit; i++)
+ erasefree(ftl->unit[i]);
+ free(ftl->unit);
+ }
+ free(ftl->vbm);
+ for(n = 0; n < NPART; n++)
+ free(ftl->part[n].name);
+ free(ftl);
+ }
+}
+
+/*
+ * this simple greedy algorithm weighted by nerase does seem to lead
+ * to even wear of erase units (cf. the eNVy file system)
+ */
+static Terase *
+bestcopy(Ftl *ftl)
+{
+ Terase *e, *be;
+ int i;
+
+ be = nil;
+ for(i=0; i<ftl->nunit; i++)
+ if((e = ftl->unit[i]) != nil && e->id != XferID && e->id != XferBusy && e->ndead+e->nbad &&
+ (be == nil || e->nerase <= be->nerase && e->ndead >= be->ndead))
+ be = e;
+ return be;
+}
+
+static int
+copyunit(Ftl *ftl, Terase *from, Terase *to)
+{
+ int i, nb;
+ uchar id[2];
+ ulong *bam;
+ uchar *buf;
+ ulong v, bno;
+
+ if(FTLDEBUG || ftl->trace)
+ print("ftl: copying %d (#%lux) to #%lux\n", from->id, from->offset, to->offset);
+ to->nbam = 0;
+ free(to->bam);
+ to->bam = nil;
+ bam = nil;
+ buf = malloc(Bsize);
+ if(buf == nil)
+ return 0;
+ if(waserror()){
+ free(buf);
+ free(bam);
+ return 0;
+ }
+ PUT2(id, XferBusy);
+ putflash(ftl, to->offset+offsetof(Merase,id[0]), id, 2);
+ /* make new BAM */
+ nb = from->nbam*sizeof(*to->bam);
+ bam = malloc(nb);
+ if(bam == nil)
+ error(Enomem);
+ memmove(bam, from->bam, nb);
+ to->nused = 0;
+ to->nbad = 0;
+ to->nfree = 0;
+ to->ndead = 0;
+ for(i = 0; i < from->nbam; i++)
+ switch(bam[i]){
+ case Bwriting:
+ case Bdeleted:
+ case Bfree:
+ bam[i] = Bfree;
+ to->nfree++;
+ break;
+ default:
+ switch(bam[i]&BlockType){
+ default:
+ case BadBlock: /* it isn't necessarily bad in this unit */
+ to->nfree++;
+ bam[i] = Bfree;
+ break;
+ case DataBlock:
+ case ReplacePage:
+ v = bam[i];
+ bno = BNO(v & ~BlockType);
+ if(i < ftl->fstart || bno >= ftl->nblock){
+ print("ftl: unit %d:#%x bad bam[%d]=#%lux\n", from->x, from->id, i, v);
+ to->nfree++;
+ bam[i] = Bfree;
+ break;
+ }
+ getflash(ftl, buf, from->offset+i*Bsize, Bsize);
+ putflash(ftl, to->offset+i*Bsize, buf, Bsize);
+ to->nused++;
+ break;
+ case ControlBlock:
+ to->nused++;
+ break;
+ }
+ }
+ for(i=0; i<from->nbam; i++){
+ uchar *p = (uchar*)&bam[i];
+ v = bam[i];
+ if(v != Bfree && ftl->trace > 1)
+ print("to[%d]=#%lux\n", i, v);
+ PUT4(p, v);
+ }
+ putflash(ftl, to->bamoffset, bam, nb); /* BUG: PUT4 */
+ for(i=0; i<from->nbam; i++){
+ uchar *p = (uchar*)&bam[i];
+ v = bam[i];
+ PUT4(p, v);
+ }
+ to->id = from->id;
+ PUT2(id, to->id);
+ putflash(ftl, to->offset+offsetof(Merase,id[0]), id, 2);
+ to->nbam = from->nbam;
+ to->bam = bam;
+ ftl->nfree += to->nfree - from->nfree;
+ poperror();
+ free(buf);
+ return 1;
+}
+
+static int
+mustscavenge(void *a)
+{
+ return ((Ftl*)a)->needspace || ((Ftl*)a)->detach == Deferred;
+}
+
+static int
+donescavenge(void *a)
+{
+ return ((Ftl*)a)->needspace == 0;
+}
+
+static void
+scavengeproc(void *arg)
+{
+ Ftl *ftl;
+ int i;
+ Terase *e, *ne;
+
+ ftl = arg;
+ if(waserror()){
+ print("ftl: kproc noted\n");
+ pexit("ftldeath", 0);
+ }
+ for(;;){
+ sleep(&ftl->workr, mustscavenge, ftl);
+ if(ftl->detach == Deferred){
+ ftlfree(ftl);
+ pexit("", 0);
+ }
+ if(FTLDEBUG || ftl->trace)
+ print("ftl: scavenge %ld\n", ftl->nfree);
+ qlock(ftl);
+ if(waserror()){
+ qunlock(ftl);
+ nexterror();
+ }
+ e = bestcopy(ftl);
+ if(e == nil || ftl->xfer < 0 || (ne = ftl->unit[ftl->xfer]) == nil || ne->id != XferID || e == ne)
+ goto Fail;
+ if(copyunit(ftl, e, ne)){
+ i = ne->x; ne->x = e->x; e->x = i;
+ ftl->unit[ne->x] = ne;
+ ftl->unit[e->x] = e;
+ ftl->xfer = e->x;
+ e->id = XferID;
+ e->nbam = 0;
+ free(e->bam);
+ e->bam = nil;
+ e->bamx = 0;
+ e->nerase++;
+ eraseinit(ftl, e->offset, XferID, e->nerase);
+ }
+ Fail:
+ if(FTLDEBUG || ftl->trace)
+ print("ftl: end scavenge %ld\n", ftl->nfree);
+ ftl->needspace = 0;
+ wakeup(&ftl->wantr);
+ poperror();
+ qunlock(ftl);
+ }
+}
+
+static int
+scavenge(Ftl *ftl)
+{
+ if(ftl->xfer < 0 || bestcopy(ftl) == nil)
+ return 0; /* you worm! */
+
+ qlock(ftl);
+ if(waserror()){
+ qunlock(ftl);
+ return 0;
+ }
+ if(!ftl->hasproc){
+ ftl->hasproc = 1;
+ kproc("ftl.scavenge", scavengeproc, ftl, 0);
+ }
+ ftl->needspace = 1;
+ wakeup(&ftl->workr);
+ poperror();
+ qunlock(ftl);
+
+ qlock(&ftl->wantq);
+ if(waserror()){
+ qunlock(&ftl->wantq);
+ nexterror();
+ }
+ while(ftl->needspace)
+ sleep(&ftl->wantr, donescavenge, ftl);
+ poperror();
+ qunlock(&ftl->wantq);
+
+ return ftl->nfree;
+}
+
+static void
+putbam(Ftl *ftl, Terase *e, int n, ulong entry)
+{
+ uchar b[4];
+
+ e->bam[n] = entry;
+ PUT4(b, entry);
+ putflash(ftl, e->bamoffset + n*4, b, 4);
+}
+
+static ulong
+allocblk(Ftl *ftl)
+{
+ Terase *e;
+ int i, j;
+
+ qlock(ftl);
+ i = ftl->lastx;
+ do{
+ e = ftl->unit[i];
+ if(e != nil && e->id != XferID && e->nfree){
+ ftl->lastx = i;
+ for(j=e->bamx; j<e->nbam; j++)
+ if(e->bam[j] == Bfree){
+ putbam(ftl, e, j, Bwriting);
+ ftl->nfree--;
+ e->nfree--;
+ e->bamx = j+1;
+ qunlock(ftl);
+ return (e->x<<16) | j;
+ }
+ e->nfree = 0;
+ qunlock(ftl);
+ print("ftl: unit %d:#%x nfree %ld but not free in BAM\n", e->x, e->id, e->nfree);
+ qlock(ftl);
+ }
+ if(++i >= ftl->nunit)
+ i = 0;
+ }while(i != ftl->lastx);
+ qunlock(ftl);
+ return 0;
+}
+
+static int
+mapblk(Ftl *ftl, ulong bno, Terase **ep, ulong *bp)
+{
+ ulong v;
+ int x;
+
+ if(bno < ftl->nblock){
+ v = ftl->vbm[bno];
+ if(v == 0 || v == ~0)
+ return 0;
+ x = v>>16;
+ if(x >= ftl->nunit || x == ftl->xfer || ftl->unit[x] == nil){
+ print("ftl: corrupt format: bad block mapping %lud -> unit #%x\n", bno, x);
+ return 0;
+ }
+ *ep = ftl->unit[x];
+ *bp = v & 0xFFFF;
+ return 1;
+ }
+ return 0;
+}
+
+static void
+eraseinit(Ftl *ftl, ulong offset, int id, int nerase)
+{
+ union {
+ Merase;
+ uchar block[ERASEHDRLEN];
+ } *m;
+ uchar *bam, *p;
+ int i, nov;
+
+ nov = ((ftl->segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; /* number of overhead blocks (header, and BAM itself) */
+ if(nov*Bsize >= ftl->segsize)
+ error("ftl -- too small for files");
+ eraseflash(ftl, offset);
+ m = malloc(sizeof(*m));
+ if(m == nil)
+ error(Enomem);
+ memset(m, 0xFF, sizeof(*m));
+ m->linktuple[0] = 0x13;
+ m->linktuple[1] = 0x3;
+ memmove(m->linktuple+2, "CIS", 3);
+ m->orgtuple[0] = 0x46;
+ m->orgtuple[1] = 0x57;
+ m->orgtuple[2] = 0x00;
+ memmove(m->orgtuple+3, "FTL100", 7);
+ m->nxfer = 1;
+ PUT4(m->nerase, nerase);
+ PUT2(m->id, id);
+ m->bshift = ftl->bshift;
+ m->eshift = ftl->eshift;
+ PUT2(m->pstart, ftl->base >> ftl->eshift);
+ PUT2(m->nunits, ftl->nunit);
+ PUT4(m->psize, ftl->size - nov*Bsize*ftl->nunit);
+ PUT4(m->vbmbase, 0xffffffff); /* we always calculate the VBM */
+ PUT2(m->nvbm, 0);
+ m->flags = 0;
+ m->code = 0xFF;
+ memmove(m->serial, "Inf1", 4);
+ PUT4(m->altoffset, 0);
+ PUT4(m->bamoffset, BAMoffset);
+ putflash(ftl, offset, m, ERASEHDRLEN);
+ free(m);
+ if(id == XferID)
+ return;
+ nov *= 4; /* now bytes of BAM */
+ bam = malloc(nov);
+ if(bam == nil)
+ error(Enomem);
+ for(i=0; i<nov; i += 4){
+ p = bam+i;
+ PUT4(p, ControlBlock); /* reserve them */
+ }
+ putflash(ftl, offset+BAMoffset, bam, nov);
+ free(bam);
+}
+
+static int
+erasedetect(Ftl *ftl, ulong base, ulong size, ushort *pstart, ushort *nunits)
+{
+ ulong o;
+ int rv;
+
+ union {
+ Merase;
+ uchar block[ERASEHDRLEN];
+ } *m;
+ m = malloc(sizeof(*m));
+ if(m == nil)
+ error(Enomem);
+ rv = 0;
+ for(o = base; o < base + size; o += ftl->segsize){
+ if(waserror())
+ continue;
+ getflash(ftl, m, o, ERASEHDRLEN);
+ poperror();
+ if(memcmp(m->orgtuple + 3, "FTL100", 7) == 0
+ && memcmp(m->serial, "Inf1", 4) == 0){
+ *pstart = GET2(m->pstart);
+ *nunits = GET2(m->nunits);
+ rv = 1;
+ break;
+ }
+ }
+ free(m);
+ return rv;
+}
+
+static Terase *
+eraseload(Ftl *ftl, int x, ulong offset)
+{
+ union {
+ Merase;
+ uchar block[ERASEHDRLEN];
+ } *m;
+ Terase *e;
+ uchar *p;
+ int i, nbam;
+ ulong bno, v;
+
+ m = malloc(sizeof(*m));
+ if(m == nil)
+ error(Enomem);
+ if(waserror()){
+ free(m);
+ return nil;
+ }
+ getflash(ftl, m, offset, ERASEHDRLEN);
+ poperror();
+ if(memcmp(m->orgtuple+3, "FTL100", 7) != 0 ||
+ memcmp(m->serial, "Inf1", 4) != 0){
+ free(m);
+print("%8.8lux: bad sig\n", offset);
+ return nil;
+ }
+ e = malloc(sizeof(*e));
+ if(e == nil){
+ free(m);
+ error(Enomem);
+ }
+ e->x = x;
+ e->id = GET2(m->id);
+ e->offset = offset;
+ e->bamoffset = GET4(m->bamoffset);
+ e->nerase = GET4(m->nerase);
+ free(m);
+ m = nil;
+ USED(m);
+ if(e->bamoffset != BAMoffset){
+ free(e);
+print("%8.8lux: bad bamoffset %8.8lux\n", offset, e->bamoffset);
+ return nil;
+ }
+ e->bamoffset += offset;
+ if(e->id == XferID || e->id == XferBusy){
+ e->bam = nil;
+ e->nbam = 0;
+ return e;
+ }
+ nbam = ftl->segsize/Bsize;
+ e->bam = malloc(nbam*sizeof(*e->bam));
+ e->nbam = nbam;
+ if(waserror()){
+ free(e);
+ nexterror();
+ }
+ getflash(ftl, e->bam, e->bamoffset, nbam*4);
+ poperror();
+ /* scan BAM to build VBM */
+ e->bamx = 0;
+ for(i=0; i<nbam; i++){
+ p = (uchar*)&e->bam[i];
+ e->bam[i] = v = GET4(p);
+ if(v == Bwriting || v == Bdeleted)
+ e->ndead++;
+ else if(v == Bfree){
+ if(e->bamx == 0)
+ e->bamx = i;
+ e->nfree++;
+ ftl->nfree++;
+ }else{
+ switch(v & BlockType){
+ case ControlBlock:
+ break;
+ case DataBlock:
+ /* add to VBM */
+ if(v & (1<<31))
+ break; /* negative => VBM page, ignored */
+ bno = BNO(v & ~BlockType);
+ if(i < ftl->fstart || bno >= ftl->nblock){
+ print("ftl: unit %d:#%x bad bam[%d]=#%lux\n", e->x, e->id, i, v);
+ e->nbad++;
+ break;
+ }
+ ftl->vbm[bno] = (e->x<<16) | i;
+ e->nused++;
+ break;
+ case ReplacePage:
+ /* replacement VBM page; ignored */
+ break;
+ default:
+ print("ftl: unit %d:#%x bad bam[%d]=%lux\n", e->x, e->id, i, v);
+ case BadBlock:
+ e->nbad++;
+ break;
+ }
+ }
+ }
+ return e;
+}
+
+static void
+erasefree(Terase *e)
+{
+ if(e){
+ free(e->bam);
+ free(e);
+ }
+}
+
+static void
+eraseflash(Ftl *ftl, ulong offset)
+{
+ char cmd[40];
+
+ offset += ftl->base;
+ if(FTLDEBUG || ftl->trace)
+ print("ftl: erase seg @#%lux\n", offset);
+ snprint(cmd, sizeof(cmd), "erase 0x%8.8lux", offset);
+ if(kchanio(ftl->flashctl, cmd, strlen(cmd), OWRITE) <= 0){
+ print("ftl: erase failed: %s\n", up->env->errstr);
+ error(up->env->errstr);
+ }
+}
+
+static void
+putflash(Ftl *ftl, ulong offset, void *buf, long n)
+{
+ offset += ftl->base;
+ if(ftl->trace)
+ print("ftl: write(#%lux, %ld)\n", offset, n);
+ ftl->flash->offset = offset;
+ if(kchanio(ftl->flash, buf, n, OWRITE) != n){
+ print("ftl: flash write error: %s\n", up->env->errstr);
+ error(up->env->errstr);
+ }
+}
+
+static void
+getflash(Ftl *ftl, void *buf, ulong offset, long n)
+{
+ offset += ftl->base;
+ if(ftl->trace)
+ print("ftl: read(#%lux, %ld)\n", offset, n);
+ ftl->flash->offset = offset;
+ if(kchanio(ftl->flash, buf, n, OREAD) != n){
+ print("ftl: flash read error %s\n", up->env->errstr);
+ error(up->env->errstr);
+ }
+}
+
+Dev ftldevtab = {
+ 'X', /* TO DO */
+ "ftl",
+
+ devreset,
+ devinit,
+ devshutdown,
+ ftlattach,
+ ftlwalk,
+ ftlstat,
+ ftlopen,
+ devcreate,
+ ftlclose,
+ ftlread,
+ devbread,
+ ftlwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devi2c.c b/os/port/devi2c.c
new file mode 100644
index 00000000..50512248
--- /dev/null
+++ b/os/port/devi2c.c
@@ -0,0 +1,227 @@
+/*
+ * i2c
+ *
+ * Copyright © 1998, 2003 Vita Nuova Limited.
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+typedef struct I2Cdir I2Cdir;
+
+enum{
+ Qdir,
+ Qdata,
+ Qctl,
+};
+
+static
+Dirtab i2ctab[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "i2cdata", {Qdata, 0}, 256, 0660,
+ "i2cctl", {Qctl, 0}, 0, 0660,
+};
+
+struct I2Cdir {
+ Ref;
+ I2Cdev;
+ Dirtab tab[nelem(i2ctab)];
+};
+
+static void
+i2creset(void)
+{
+ i2csetup(0);
+}
+
+static Chan*
+i2cattach(char* spec)
+{
+ char *s;
+ ulong addr;
+ I2Cdir *d;
+ Chan *c;
+
+ addr = strtoul(spec, &s, 16);
+ if(*spec == 0 || *s || addr >= (1<<10))
+ error("invalid i2c address");
+ d = malloc(sizeof(I2Cdir));
+ if(d == nil)
+ error(Enomem);
+ d->ref = 1;
+ d->addr = addr;
+ d->salen = 0;
+ d->tenbit = addr >= 128;
+ memmove(d->tab, i2ctab, sizeof(d->tab));
+ sprint(d->tab[1].name, "i2c.%lux.data", addr);
+ sprint(d->tab[2].name, "i2c.%lux.ctl", addr);
+
+ c = devattach('J', spec);
+ c->aux = d;
+ return c;
+}
+
+static Walkqid*
+i2cwalk(Chan* c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+ I2Cdir *d;
+
+ d = c->aux;
+ wq = devwalk(c, nc, name, nname, d->tab, nelem(d->tab), devgen);
+ if(wq != nil && wq->clone != nil && wq->clone != c)
+ incref(d);
+ return wq;
+}
+
+static int
+i2cstat(Chan* c, uchar *dp, int n)
+{
+ I2Cdir *d;
+
+ d = c->aux;
+ return devstat(c, dp, n, d->tab, nelem(d->tab), devgen);
+}
+
+static Chan*
+i2copen(Chan* c, int omode)
+{
+ I2Cdir *d;
+
+ d = c->aux;
+ return devopen(c, omode, d->tab, nelem(d->tab), devgen);
+}
+
+static void
+i2cclose(Chan *c)
+{
+ I2Cdir *d;
+
+ d = c->aux;
+ if(decref(d) == 0)
+ free(d);
+}
+
+static long
+i2cread(Chan *c, void *a, long n, vlong offset)
+{
+ I2Cdir *d;
+ char *s, *e;
+ ulong len;
+
+ d = c->aux;
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, d->tab, nelem(d->tab), devgen);
+ case Qdata:
+ len = d->tab[1].length;
+ if(offset+n >= len){
+ n = len - offset;
+ if(n <= 0)
+ return 0;
+ }
+ n = i2crecv(d, a, n, offset);
+ break;
+ case Qctl:
+ s = smalloc(READSTR);
+ if(waserror()){
+ free(s);
+ nexterror();
+ }
+ e = seprint(s, s+READSTR, "size %lud\n", (ulong)d->tab[1].length);
+ if(d->salen)
+ e = seprint(e, s+READSTR, "subaddress %d\n", d->salen);
+ if(d->tenbit)
+ seprint(e, s+READSTR, "a10\n");
+ n = readstr(offset, a, n, s);
+ poperror();
+ free(s);
+ return n;
+ default:
+ n=0;
+ break;
+ }
+ return n;
+}
+
+static long
+i2cwrite(Chan *c, void *a, long n, vlong offset)
+{
+ I2Cdir *d;
+ long len;
+ Cmdbuf *cb;
+
+ USED(offset);
+ switch((ulong)c->qid.path){
+ case Qdata:
+ d = c->aux;
+ len = d->tab[1].length;
+ if(offset+n >= len){
+ n = len - offset;
+ if(n <= 0)
+ return 0;
+ }
+ n = i2csend(d, a, n, offset);
+ break;
+ case Qctl:
+ cb = parsecmd(a, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf < 1)
+ error(Ebadctl);
+ d = c->aux;
+ if(strcmp(cb->f[0], "subaddress") == 0){
+ if(cb->nf > 1){
+ len = strtol(cb->f[1], nil, 0);
+ if(len <= 0)
+ len = 0;
+ if(len > 4)
+ cmderror(cb, "subaddress too long");
+ }else
+ len = 1;
+ d->salen = len;
+ }else if(cb->nf > 1 && strcmp(cb->f[0], "size") == 0){
+ len = strtol(cb->f[1], nil, 0);
+ if(len < 0)
+ cmderror(cb, "size is negative");
+ d->tab[1].length = len;
+ }else if(strcmp(cb->f[0], "a10") == 0)
+ d->tenbit = 1;
+ else
+ cmderror(cb, "unknown control request");
+ poperror();
+ free(cb);
+ break;
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+Dev i2cdevtab = {
+ 'J',
+ "i2c",
+
+ i2creset,
+ devinit,
+ devshutdown,
+ i2cattach,
+ i2cwalk,
+ i2cstat,
+ i2copen,
+ devcreate,
+ i2cclose,
+ i2cread,
+ devbread,
+ i2cwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devindir.c b/os/port/devindir.c
new file mode 100644
index 00000000..9f713bbc
--- /dev/null
+++ b/os/port/devindir.c
@@ -0,0 +1,40 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+static Chan *
+indirattach(char *spec)
+{
+ char *p;
+ Dev *d;
+
+ if(*spec == 0)
+ error(Ebadspec);
+ p = strrchr(spec, '!');
+ if(p == nil)
+ p = "";
+ else
+ *p++ = 0;
+ d = devbyname(spec);
+ if(d == nil || d->dc == '*'){
+ snprint(up->env->errstr, ERRMAX, "unknown device: %s", spec);
+ error(up->env->errstr);
+ }
+ if(up->env->pgrp->nodevs &&
+ (utfrune("|esDa", d->dc) == nil || d->dc == 's' && *p!='\0'))
+ error(Enoattach);
+ return d->attach(p);
+}
+
+Dev indirdevtab = {
+ '*',
+ "indir",
+
+ devreset,
+ devinit,
+ devshutdown,
+ indirattach,
+};
diff --git a/os/port/devkprof.c b/os/port/devkprof.c
new file mode 100644
index 00000000..134f346a
--- /dev/null
+++ b/os/port/devkprof.c
@@ -0,0 +1,230 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#ifndef LRES
+#define LRES 3 /* log of PC resolution */
+#endif
+
+#define SZ 4 /* sizeof of count cell; well known as 4 */
+
+enum {
+ SpecialTotalTicks,
+ SpecialOutsideTicks,
+ SpecialMicroSecondsPerTick,
+ SpecialSamples,
+ SpecialSampleSize,
+ SpecialSampleLogBucketSize,
+ SpecialMax
+};
+
+struct
+{
+ int minpc;
+ int maxpc;
+ int nbuf;
+ int time;
+ ulong *buf;
+}kprof;
+
+enum{
+ Qdir,
+ Qdata,
+ Qctl,
+ Kprofmaxqid,
+};
+
+Dirtab kproftab[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0500,
+ "kpdata", {Qdata}, 0, 0600,
+ "kpctl", {Qctl}, 0, 0600,
+};
+
+void kproftimer(ulong);
+void (*kproftick)(ulong);
+
+static void
+kprofinit(void)
+{
+ if(SZ != sizeof kprof.buf[0])
+ panic("kprof size");
+}
+
+static void
+kprofbufinit(void)
+{
+ kprof.buf[SpecialMicroSecondsPerTick] = archkprofmicrosecondspertick();
+ kprof.buf[SpecialSamples] = kprof.nbuf;
+ kprof.buf[SpecialSampleSize] = SZ;
+ kprof.buf[SpecialSampleLogBucketSize] = LRES;
+}
+
+static Chan *
+kprofattach(char *spec)
+{
+ ulong n;
+
+ /* allocate when first used */
+ kproftick = kproftimer;
+ kprof.minpc = KTZERO;
+ kprof.maxpc = (ulong)etext;
+ kprof.nbuf = (kprof.maxpc-kprof.minpc) >> LRES;
+ n = kprof.nbuf*SZ;
+ if(kprof.buf == 0) {
+ kprof.buf = xalloc(n);
+ if(kprof.buf == 0)
+ error(Enomem);
+ }
+ kproftab[0].length = n;
+ kprofbufinit();
+ return devattach('K', spec);
+}
+
+static Walkqid*
+kprofwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, kproftab, nelem(kproftab), devgen);
+}
+
+static int
+kprofstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, kproftab, nelem(kproftab), devgen);
+}
+
+static Chan *
+kprofopen(Chan *c, int omode)
+{
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Eperm);
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+void
+kprofclose(Chan*)
+{
+}
+
+static long
+kprofread(Chan *c, void *va, long n, vlong offset)
+{
+ ulong tabend;
+ ulong w, *bp;
+ uchar *a, *ea;
+
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, va, n, kproftab, nelem(kproftab), devgen);
+
+ case Qdata:
+ tabend = kprof.nbuf*SZ;
+ if(offset & (SZ-1))
+ error(Ebadarg);
+ if(offset >= tabend){
+ n = 0;
+ break;
+ }
+ if(offset+n > tabend)
+ n = tabend-offset;
+ n &= ~(SZ-1);
+ a = va;
+ ea = a + n;
+ bp = kprof.buf + offset/SZ;
+ while(a < ea){
+ w = *bp++;
+ *a++ = w>>24;
+ *a++ = w>>16;
+ *a++ = w>>8;
+ *a++ = w>>0;
+ }
+ break;
+
+ default:
+ n = 0;
+ break;
+ }
+ return n;
+}
+
+static long
+kprofwrite(Chan *c, void *vp, long n, vlong offset)
+{
+ char *a;
+ USED(offset);
+
+ a = vp;
+ switch((ulong)c->qid.path){
+ case Qctl:
+ if(strncmp(a, "startclr", 8) == 0){
+ memset((char *)kprof.buf, 0, kprof.nbuf*SZ);
+ kprofbufinit();
+ archkprofenable(1);
+ kprof.time = 1;
+ }else if(strncmp(a, "start", 5) == 0) {
+ archkprofenable(1);
+ kprof.time = 1;
+ }
+ else if(strncmp(a, "stop", 4) == 0) {
+ archkprofenable(0);
+ kprof.time = 0;
+ }
+ else
+ error(Ebadctl);
+ break;
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+void
+kproftimer(ulong pc)
+{
+ extern void spldone(void);
+
+ if(kprof.time == 0)
+ return;
+ /*
+ * if the pc is coming out of spllo or splx,
+ * use the pc saved when we went splhi.
+ */
+// if(pc>=(ulong)splx && pc<=(ulong)spldone)
+// pc = m->splpc;
+
+ kprof.buf[SpecialTotalTicks]++;
+ if(kprof.minpc + (SpecialMax << LRES) <= pc && pc < kprof.maxpc){
+ pc -= kprof.minpc;
+ pc >>= LRES;
+ kprof.buf[pc]++;
+ }else
+ kprof.buf[SpecialOutsideTicks]++;
+}
+
+Dev kprofdevtab = {
+ 'K',
+ "kprof",
+
+ devreset,
+ kprofinit,
+ devshutdown,
+ kprofattach,
+ kprofwalk,
+ kprofstat,
+ kprofopen,
+ devcreate,
+ kprofclose,
+ kprofread,
+ devbread,
+ kprofwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devlogfs.c b/os/port/devlogfs.c
new file mode 100755
index 00000000..630d3eba
--- /dev/null
+++ b/os/port/devlogfs.c
@@ -0,0 +1,1531 @@
+#ifndef EMU
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#else
+#include "error.h"
+#endif
+#include "dat.h"
+#include "fns.h"
+#include "kernel.h"
+#include "logfs.h"
+#include "nandfs.h"
+
+#ifndef EMU
+#define Sleep sleep
+#define Wakeup wakeup
+#endif
+
+#ifndef offsetof
+#define offsetof(T,X) ((ulong)&(((T*)0)->X))
+#endif
+
+typedef struct Devlogfs Devlogfs;
+typedef struct DevlogfsSession DevlogfsSession;
+
+//#define CALLTRACE
+
+enum {
+ DEVLOGFSDEBUG = 0,
+ DEVLOGFSIODEBUG = 0,
+ DEVLOGFSBAD = 1,
+};
+
+enum {
+ Qdir,
+ Qctl,
+ Qusers,
+ Qdump,
+ Qfs,
+ Qfsboot,
+ Qend,
+};
+
+typedef enum DevlogfsServerState { Closed, BootOpen, NeedVersion, NeedAttach, Attached, Hungup } DevlogfsServerState;
+
+struct Devlogfs {
+ QLock qlock;
+ QLock rlock;
+ QLock wlock;
+ Ref ref;
+ int instance;
+ int trace; /* (debugging) trace of read/write actions */
+ int nand;
+ char *name;
+ char *device;
+ char *filename[Qend - Qfs];
+ LogfsLowLevel *ll;
+ Chan *flash, *flashctl;
+ QLock bootqlock;
+ int logfstrace;
+ LogfsBoot *lb;
+ /* stuff for server */
+ ulong openflags;
+ Fcall in;
+ Fcall out;
+ int reading;
+ DevlogfsServerState state;
+ Rendez readrendez;
+ Rendez writerendez;
+ uint readcount;
+ ulong readbufsize;
+ uchar *readbuf;
+ uchar *readp;
+ LogfsServer *server;
+ Devlogfs *next;
+};
+
+#define MAXMSIZE 8192
+
+static struct {
+ RWlock rwlock; /* rlock when walking, wlock when changing */
+ QLock configqlock; /* serialises addition of new configurations */
+ Devlogfs *head;
+ char *defname;
+} devlogfslist;
+
+static LogfsIdentityStore *is;
+
+#ifndef EMU
+char Eunknown[] = "unknown user or group id";
+#endif
+
+static void devlogfsfree(Devlogfs*);
+
+#define SPLITPATH(path, qtype, instance, qid, qt) { instance = path >> 4; qid = path & 0xf; qt = qtype & QTDIR; }
+#define DATAQID(q, qt) (!(qt) && (q) >= Qfs && (q) < Qend)
+#define MKPATH(instance, qid) ((instance << 4) | qid)
+
+#define PREFIX "logfs"
+
+static char *devlogfsprefix = PREFIX;
+static char *devlogfsctlname = PREFIX "ctl";
+static char *devlogfsusersname = PREFIX "users";
+static char *devlogfsdumpname = PREFIX "dump";
+static char *devlogfsbootsuffix = "boot";
+static char *devlogfs9pversion = "9P2000";
+
+static void
+errorany(char *errmsg)
+{
+ if (errmsg)
+ error(errmsg);
+}
+
+static void *
+emalloc(ulong size)
+{
+ void *p;
+ p = logfsrealloc(nil, size);
+ if (p == nil)
+ error(Enomem);
+ return p;
+}
+
+static char *
+estrdup(char *q)
+{
+ void *p;
+ if (q == nil)
+ return nil;
+ p = logfsrealloc(nil, strlen(q) + 1);
+ if (p == nil)
+ error(Enomem);
+ return strcpy(p, q);
+}
+
+static char *
+estrconcat(char *a, ...)
+{
+ va_list l;
+ char *p, *r;
+ int t;
+
+ t = strlen(a);
+ va_start(l, a);
+ while ((p = va_arg(l, char *)) != nil)
+ t += strlen(p);
+
+ r = logfsrealloc(nil, t + 1);
+ if (r == nil)
+ error(Enomem);
+
+ strcpy(r, a);
+ va_start(l, a);
+ while ((p = va_arg(l, char *)) != nil)
+ strcat(r, p);
+
+ va_end(l);
+
+ return r;
+}
+
+static int
+gen(Chan *c, int i, Dir *dp, int lockit)
+{
+ Devlogfs *l;
+ long size;
+ Qid qid;
+ qid.vers = 0;
+ qid.type = 0;
+
+ if (i + Qctl < Qfs) {
+ switch (i + Qctl) {
+ case Qctl:
+ qid.path = Qctl;
+ devdir(c, qid, devlogfsctlname, 0, eve, 0666, dp);
+ return 1;
+ case Qusers:
+ qid.path = Qusers;
+ devdir(c, qid, devlogfsusersname, 0, eve, 0444, dp);
+ return 1;
+ case Qdump:
+ qid.path = Qdump;
+ devdir(c, qid, devlogfsdumpname, 0, eve, 0444, dp);
+ return 1;
+ }
+ }
+
+ i -= Qfs - Qctl;
+
+ if (lockit)
+ rlock(&devlogfslist.rwlock);
+
+ if (waserror()) {
+ if (lockit)
+ runlock(&devlogfslist.rwlock);
+ nexterror();
+ }
+
+ for (l = devlogfslist.head; l; l = l->next) {
+ if (i < Qend - Qfs)
+ break;
+ i -= Qend - Qfs;
+ }
+
+ if (l == nil) {
+ poperror();
+ if (lockit)
+ runlock(&devlogfslist.rwlock);
+ return -1;
+ }
+
+ switch (Qfs + i) {
+ case Qfsboot:
+ size = l->lb ? logfsbootgetsize(l->lb) : 0;
+ break;
+ default:
+ size = 0;
+ break;
+ }
+ /* perhaps the user id should come from the underlying file */
+ qid.path = MKPATH(l->instance, Qfs + i);
+ devdir(c, qid, l->filename[i], size, eve, 0666, dp);
+
+ poperror();
+ if (lockit)
+ runlock(&devlogfslist.rwlock);
+
+ return 1;
+}
+
+static int
+devlogfsgen(Chan *c, char *n, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ USED(n);
+ USED(tab);
+ USED(ntab);
+ return gen(c, i, dp, 1);
+}
+
+static int
+devlogfsgennolock(Chan *c, char *n, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ USED(n);
+ USED(tab);
+ USED(ntab);
+ return gen(c, i, dp, 0);
+}
+
+/* called under lock */
+static Devlogfs *
+devlogfsfind(int instance)
+{
+ Devlogfs *l;
+
+ for (l = devlogfslist.head; l; l = l->next)
+ if (l->instance == instance)
+ break;
+ return l;
+}
+
+static Devlogfs *
+devlogfsget(int instance)
+{
+ Devlogfs *l;
+ rlock(&devlogfslist.rwlock);
+ for (l = devlogfslist.head; l; l = l->next)
+ if (l->instance == instance)
+ break;
+ if (l)
+ incref(&l->ref);
+ runlock(&devlogfslist.rwlock);
+ return l;
+}
+
+static Devlogfs *
+devlogfsfindbyname(char *name)
+{
+ Devlogfs *l;
+
+ rlock(&devlogfslist.rwlock);
+ for (l = devlogfslist.head; l; l = l->next)
+ if (strcmp(l->name, name) == 0)
+ break;
+ runlock(&devlogfslist.rwlock);
+ return l;
+}
+
+static Devlogfs *
+devlogfssetdefname(char *name)
+{
+ Devlogfs *l;
+ char *searchname;
+ wlock(&devlogfslist.rwlock);
+ if (waserror()) {
+ wunlock(&devlogfslist.rwlock);
+ nexterror();
+ }
+ if (name == nil)
+ searchname = devlogfslist.defname;
+ else
+ searchname = name;
+ for (l = devlogfslist.head; l; l = l->next)
+ if (strcmp(l->name, searchname) == 0)
+ break;
+ if (l == nil) {
+ logfsfreemem(devlogfslist.defname);
+ devlogfslist.defname = nil;
+ }
+ else if (name) {
+ if (devlogfslist.defname) {
+ logfsfreemem(devlogfslist.defname);
+ devlogfslist.defname = nil;
+ }
+ devlogfslist.defname = estrdup(name);
+ }
+ poperror();
+ wunlock(&devlogfslist.rwlock);
+ return l;
+}
+
+static Chan *
+devlogfskopen(char *name, char *suffix, int mode)
+{
+ Chan *c;
+ char *fn;
+ int fd;
+
+ fn = estrconcat(name, suffix, 0);
+ fd = kopen(fn, mode);
+ logfsfreemem(fn);
+ if (fd < 0)
+ error(up->env->errstr);
+ c = fdtochan(up->env->fgrp, fd, mode, 0, 1);
+ kclose(fd);
+ return c;
+}
+
+static char *
+xread(void *a, void *buf, long nbytes, ulong offset)
+{
+ Devlogfs *l = a;
+ long rv;
+
+ if (DEVLOGFSIODEBUG || l->trace)
+ print("devlogfs: %s: read(0x%lux, %ld)\n", l->device, offset, nbytes);
+ l->flash->offset = offset;
+ rv = kchanio(l->flash, buf, nbytes, OREAD);
+ if (rv < 0) {
+ print("devlogfs: %s: flash read error: %s\n", l->device, up->env->errstr);
+ return up->env->errstr;
+ }
+ if (rv != nbytes) {
+ print("devlogfs: %s: short flash read: offset %lud, %ld not %ld\n", l->device, offset, rv, nbytes);
+ return "short read";
+ }
+ return nil;
+}
+
+static char *
+xwrite(void *a, void *buf, long nbytes, ulong offset)
+{
+ Devlogfs *l = a;
+ long rv;
+
+ if (DEVLOGFSIODEBUG || l->trace)
+ print("devlogfs: %s: write(0x%lux, %ld)\n", l->device, offset, nbytes);
+ l->flash->offset = offset;
+ rv = kchanio(l->flash, buf, nbytes, OWRITE);
+ if (rv < 0) {
+ print("devlogfs: %s: flash write error: %s\n", l->device, up->env->errstr);
+ return up->env->errstr;
+ }
+ if (rv != nbytes) {
+ print("devlogfs: %s: short flash write: offset %lud, %ld not %ld\n", l->device, offset, rv, nbytes);
+ return "short write";
+ }
+ return nil;
+}
+
+static char *
+xerase(void *a, long address)
+{
+ Devlogfs *l = a;
+ char cmd[40];
+
+ if (DEVLOGFSIODEBUG || l->trace)
+ print("devlogfs: %s: erase(0x%lux)\n", l->device, address);
+ snprint(cmd, sizeof(cmd), "erase 0x%8.8lux", address);
+ if (kchanio(l->flashctl, cmd, strlen(cmd), OWRITE) <= 0) {
+ print("devlogfs: %s: flash erase error: %s\n", l->device, up->env->errstr);
+ return up->env->errstr;
+ }
+ return nil;
+}
+
+static char *
+xsync(void *a)
+{
+ Devlogfs *l = a;
+
+ if (DEVLOGFSIODEBUG || l->trace)
+ print("devlogfs: %s: sync()\n", l->device);
+ if (kchanio(l->flashctl, "sync", 4, OWRITE) <= 0){
+ print("devlogfs: %s: flash sync error: %s\n", l->device, up->env->errstr);
+ return up->env->errstr;
+ }
+ return nil;
+}
+
+//#define LEAKHUNT
+#ifdef LEAKHUNT
+#define MAXLIVE 2000
+typedef struct Live {
+ void *p;
+ int freed;
+ ulong callerpc;
+} Live;
+
+static Live livemem[MAXLIVE];
+
+static void
+leakalloc(void *p, ulong callerpc)
+{
+ int x;
+ int use = -1;
+ for (x = 0; x < MAXLIVE; x++) {
+ if (livemem[x].p == p) {
+ if (!livemem[x].freed)
+ print("leakalloc: unexpected realloc of 0x%.8lux from 0x%.8lux\n", p, callerpc);
+// else
+// print("leakalloc: reusing address 0x%.8lux from 0x%.8lux\n", p, callerpc);
+ livemem[x].freed = 0;
+ livemem[x].callerpc = callerpc;
+ return;
+ }
+ else if (use < 0 && livemem[x].p == 0)
+ use = x;
+ }
+ if (use < 0)
+ panic("leakalloc: too many live entries");
+ livemem[use].p = p;
+ livemem[use].freed = 0;
+ livemem[use].callerpc = callerpc;
+}
+
+static void
+leakaudit(void)
+{
+ int x;
+ for (x = 0; x < MAXLIVE; x++) {
+ if (livemem[x].p && !livemem[x].freed)
+ print("leakaudit: 0x%.8lux from 0x%.8lux\n", livemem[x].p, livemem[x].callerpc);
+ }
+}
+
+static void
+leakfree(void *p, ulong callerpc)
+{
+ int x;
+ if (p == nil)
+ return;
+ for (x = 0; x < MAXLIVE; x++) {
+ if (livemem[x].p == p) {
+ if (livemem[x].freed)
+ print("leakfree: double free of 0x%.8lux from 0x%.8lux, originally by 0x%.8lux\n",
+ p, callerpc, livemem[x].callerpc);
+ livemem[x].freed = 1;
+ livemem[x].callerpc = callerpc;
+ return;
+ }
+ }
+ print("leakfree: free of unalloced address 0x%.8lux from 0x%.8lux\n", p, callerpc);
+ leakaudit();
+}
+
+static void
+leakrealloc(void *newp, void *oldp, ulong callerpc)
+{
+ leakfree(oldp, callerpc);
+ leakalloc(newp, callerpc);
+}
+#endif
+
+
+#ifdef LEAKHUNT
+static void *_realloc(void *p, ulong size, ulong callerpc)
+#else
+void *
+logfsrealloc(void *p, ulong size)
+#endif
+{
+ void *q;
+ ulong osize;
+ if (waserror()) {
+ print("wobbly thrown in memory allocator: %s\n", up->env->errstr);
+ nexterror();
+ }
+ if (p == nil) {
+ q = smalloc(size);
+ poperror();
+#ifdef LEAKHUNT
+ leakrealloc(q, nil, callerpc);
+#endif
+ return q;
+ }
+ q = realloc(p, size);
+ if (q) {
+ poperror();
+#ifdef LEAKHUNT
+ leakrealloc(q, p, callerpc);
+#endif
+ return q;
+ }
+ q = smalloc(size);
+ osize = msize(p);
+ if (osize > size)
+ osize = size;
+ memmove(q, p, osize);
+ free(p);
+ poperror();
+#ifdef LEAKHUNT
+ leakrealloc(q, p, callerpc);
+#endif
+ return q;
+}
+
+#ifdef LEAKHUNT
+void *
+logfsrealloc(void *p, ulong size)
+{
+ return _realloc(p, size, getcallerpc(&p));
+}
+
+void *
+nandfsrealloc(void *p, ulong size)
+{
+ return _realloc(p, size, getcallerpc(&p));
+}
+#else
+void *
+nandfsrealloc(void *p, ulong size)
+{
+ return logfsrealloc(p, size);
+}
+#endif
+
+void
+logfsfreemem(void *p)
+{
+#ifdef LEAKHUNT
+ leakfree(p, getcallerpc(&p));
+#endif
+ free(p);
+}
+
+void
+nandfsfreemem(void *p)
+{
+#ifdef LEAKHUNT
+ leakfree(p, getcallerpc(&p));
+#endif
+ free(p);
+}
+
+static Devlogfs *
+devlogfsconfig(char *name, char *device)
+{
+ Devlogfs *newl, *l;
+ int i;
+ int n;
+ char buf[100], *fields[12];
+ long rawblocksize, rawsize;
+
+ newl = nil;
+
+ qlock(&devlogfslist.configqlock);
+
+ if (waserror()) {
+ qunlock(&devlogfslist.configqlock);
+ devlogfsfree(newl);
+ nexterror();
+ }
+
+ rlock(&devlogfslist.rwlock);
+ for (l = devlogfslist.head; l; l = l->next)
+ if (strcmp(l->name, name) == 0) {
+ runlock(&devlogfslist.rwlock);
+ error(Einuse);
+ }
+
+ /* horrid n^2 solution to finding a unique instance number */
+
+ for (i = 0;; i++) {
+ for (l = devlogfslist.head; l; l = l->next)
+ if (l->instance == i)
+ break;
+ if (l == nil)
+ break;
+ }
+ runlock(&devlogfslist.rwlock);
+
+ newl = emalloc(sizeof(Devlogfs));
+ newl->instance = i;
+ newl->name = estrdup(name);
+ newl->device = estrdup(device);
+ newl->filename[Qfs - Qfs] = estrconcat(devlogfsprefix, name, nil);
+ newl->filename[Qfsboot - Qfs] = estrconcat(devlogfsprefix, name, devlogfsbootsuffix, nil);
+ newl->flash = devlogfskopen(device, nil, ORDWR);
+ newl->flashctl = devlogfskopen(device, "ctl", ORDWR);
+ newl->flashctl->offset = 0;
+ if ((n = kchanio(newl->flashctl, buf, sizeof(buf), OREAD)) <= 0) {
+ print("devlogfsconfig: read ctl failed: %s\n", up->env->errstr);
+ error(up->env->errstr);
+ }
+
+ if (n >= sizeof(buf))
+ n = sizeof(buf) - 1;
+ buf[n] = 0;
+ n = tokenize(buf, fields, nelem(fields));
+ if(n < 7)
+ error("unexpected flashctl format");
+ newl->nand = strcmp(fields[3], "nand") == 0;
+ rawblocksize = strtol(fields[6], nil, 0);
+ rawsize = strtol(fields[5], nil, 0)-strtol(fields[4], nil, 0);
+ if(newl->nand == 0)
+ error("only NAND supported at the moment");
+ errorany(nandfsinit(newl, rawsize, rawblocksize, xread, xwrite, xerase, xsync, &newl->ll));
+ wlock(&devlogfslist.rwlock);
+ newl->next = devlogfslist.head;
+ devlogfslist.head = newl;
+ logfsfreemem(devlogfslist.defname);
+ devlogfslist.defname = nil;
+ if (!waserror()){
+ devlogfslist.defname = estrdup(name);
+ poperror();
+ }
+ wunlock(&devlogfslist.rwlock);
+ poperror();
+ qunlock(&devlogfslist.configqlock);
+ return newl;
+}
+
+static void
+devlogfsunconfig(Devlogfs *devlogfs)
+{
+ Devlogfs **lp;
+
+ qlock(&devlogfslist.configqlock);
+
+ if (waserror()) {
+ qunlock(&devlogfslist.configqlock);
+ nexterror();
+ }
+
+ wlock(&devlogfslist.rwlock);
+
+ if (waserror()) {
+ wunlock(&devlogfslist.rwlock);
+ nexterror();
+ }
+
+ for (lp = &devlogfslist.head; *lp && (*lp) != devlogfs; lp = &(*lp)->next)
+ ;
+ if (*lp == nil) {
+ if (DEVLOGFSBAD)
+ print("devlogfsunconfig: not in list\n");
+ }
+ else
+ *lp = devlogfs->next;
+
+ poperror();
+ wunlock(&devlogfslist.rwlock);
+
+ /* now invisible to the naked eye */
+ devlogfsfree(devlogfs);
+ poperror();
+ qunlock(&devlogfslist.configqlock);
+}
+
+static void
+devlogfsllopen(Devlogfs *l)
+{
+ qlock(&l->qlock);
+ if (waserror()) {
+ qunlock(&l->qlock);
+ nexterror();
+ }
+ if (l->lb == nil)
+ errorany(logfsbootopen(l->ll, 0, 0, l->logfstrace, 1, &l->lb));
+ l->state = BootOpen;
+ poperror();
+ qunlock(&l->qlock);
+}
+
+static void
+devlogfsllformat(Devlogfs *l, long bootsize)
+{
+ qlock(&l->qlock);
+ if (waserror()) {
+ qunlock(&l->qlock);
+ nexterror();
+ }
+ if (l->lb == nil)
+ errorany(logfsformat(l->ll, 0, 0, bootsize, l->logfstrace));
+ poperror();
+ qunlock(&l->qlock);
+}
+
+static Chan *
+devlogfsattach(char *spec)
+{
+ Chan *c;
+#ifdef CALLTRACE
+ print("devlogfsattach(spec = %s) - start\n", spec);
+#endif
+ /* create the identity store on first attach */
+ if (is == nil)
+ errorany(logfsisnew(&is));
+ c = devattach(0x29f, spec);
+// c = devattach(L'ʟ', spec);
+#ifdef CALLTRACE
+ print("devlogfsattach(spec = %s) - return %.8lux\n", spec, (ulong)c);
+#endif
+ return c;
+}
+
+static Walkqid*
+devlogfswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ int instance, qid, qt, clone;
+ Walkqid *wq;
+
+#ifdef CALLTRACE
+ print("devlogfswalk(c = 0x%.8lux, nc = 0x%.8lux, name = 0x%.8lux, nname = %d) - start\n",
+ (ulong)c, (ulong)nc, (ulong)name, nname);
+#endif
+ clone = 0;
+ if(nc == nil){
+ nc = devclone(c);
+ nc->type = 0;
+ SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
+ if(DATAQID(qid, qt))
+ nc->aux = devlogfsget(instance);
+ clone = 1;
+ }
+ wq = devwalk(c, nc, name, nname, 0, 0, devlogfsgen);
+ if (wq == nil || wq->nqid < nname) {
+ if(clone)
+ cclose(nc);
+ }
+ else if (clone) {
+ wq->clone = nc;
+ nc->type = c->type;
+ }
+#ifdef CALLTRACE
+ print("devlogfswalk(c = 0x%.8lux, nc = 0x%.8lux, name = 0x%.8lux, nname = %d) - return\n",
+ (ulong)c, (ulong)nc, (ulong)name, nname);
+#endif
+ return wq;
+}
+
+static int
+devlogfsstat(Chan *c, uchar *dp, int n)
+{
+#ifdef CALLTRACE
+ print("devlogfsstat(c = 0x%.8lux, dp = 0x%.8lux n= %d)\n",
+ (ulong)c, (ulong)dp, n);
+#endif
+ return devstat(c, dp, n, 0, 0, devlogfsgen);
+}
+
+static Chan*
+devlogfsopen(Chan *c, int omode)
+{
+ int instance, qid, qt;
+
+ omode = openmode(omode);
+ SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
+#ifdef CALLTRACE
+ print("devlogfsopen(c = 0x%.8lux, omode = %o, instance = %d, qid = %d, qt = %d)\n",
+ (ulong)c, omode, instance, qid, qt);
+#endif
+
+
+ rlock(&devlogfslist.rwlock);
+ if (waserror()) {
+ runlock(&devlogfslist.rwlock);
+#ifdef CALLTRACE
+ print("devlogfsopen(c = 0x%.8lux, omode = %o) - error %s\n", (ulong)c, omode, up->env->errstr);
+#endif
+ nexterror();
+ }
+
+ if (DATAQID(qid, qt)) {
+ Devlogfs *d;
+ d = devlogfsfind(instance);
+ if (d == nil)
+ error(Enodev);
+ if (strcmp(up->env->user, eve) != 0)
+ error(Eperm);
+ if (qid == Qfs && d->state != BootOpen)
+ error(Eperm);
+ if (d->server == nil) {
+ errorany(logfsservernew(d->lb, d->ll, is, d->openflags, d->logfstrace, &d->server));
+ d->state = NeedVersion;
+ }
+ c = devopen(c, omode, 0, 0, devlogfsgennolock);
+ incref(&d->ref);
+ c->aux = d;
+ }
+ else if (qid == Qctl || qid == Qusers) {
+ if (strcmp(up->env->user, eve) != 0)
+ error(Eperm);
+ c = devopen(c, omode, 0, 0, devlogfsgennolock);
+ }
+ else
+ c = devopen(c, omode, 0, 0, devlogfsgennolock);
+ poperror();
+ runlock(&devlogfslist.rwlock);
+#ifdef CALLTRACE
+ print("devlogfsopen(c = 0x%.8lux, omode = %o) - return\n", (ulong)c, omode);
+#endif
+ return c;
+}
+
+static void
+devlogfsclose(Chan *c)
+{
+ int instance, qid, qt;
+#ifdef CALLTRACE
+ print("devlogfsclose(c = 0x%.8lux)\n", (ulong)c);
+#endif
+ SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
+ USED(instance);
+ if(DATAQID(qid, qt) && (c->flag & COPEN) != 0) {
+ Devlogfs *d;
+ d = c->aux;
+ qlock(&d->qlock);
+ if (qid == Qfs && d->state == Attached) {
+ logfsserverflush(d->server);
+ logfsserverfree(&d->server);
+ d->state = BootOpen;
+ }
+ qunlock(&d->qlock);
+ decref(&d->ref);
+ }
+#ifdef CALLTRACE
+ print("devlogfsclose(c = 0x%.8lux) - return\n", (ulong)c);
+#endif
+}
+
+typedef char *(SMARTIOFN)(void *magic, void *buf, long n, ulong offset, int write);
+
+static void
+smartio(SMARTIOFN *io, void *magic, void *buf, long n, ulong offset, long blocksize, int write)
+{
+ void *tmp = nil;
+ ulong blocks, toread;
+
+ if (waserror()) {
+ logfsfreemem(tmp);
+ nexterror();
+ }
+ if (offset % blocksize) {
+ ulong aoffset;
+ int tmpoffset;
+ int tocopy;
+
+ if (tmp == nil)
+ tmp = emalloc(blocksize);
+ aoffset = offset / blocksize;
+ aoffset *= blocksize;
+ errorany((*io)(magic, tmp, blocksize, aoffset, 0));
+ tmpoffset = offset - aoffset;
+ tocopy = blocksize - tmpoffset;
+ if (tocopy > n)
+ tocopy = n;
+ if (write) {
+ memmove((uchar *)tmp + tmpoffset, buf, tocopy);
+ errorany((*io)(magic, tmp, blocksize, aoffset, 1));
+ }
+ else
+ memmove(buf, (uchar *)tmp + tmpoffset, tocopy);
+ buf = (uchar *)buf + tocopy;
+ n -= tocopy;
+ offset = aoffset + blocksize;
+ }
+ blocks = n / blocksize;
+ toread = blocks * blocksize;
+ errorany((*io)(magic, buf, toread, offset, write));
+ buf = (uchar *)buf + toread;
+ n -= toread;
+ offset += toread;
+ if (n) {
+ if (tmp == nil)
+ tmp = emalloc(blocksize);
+ errorany((*io)(magic, tmp, blocksize, offset, 0));
+ if (write) {
+ memmove(tmp, buf, n);
+ errorany((*io)(magic, tmp, blocksize, offset, 1));
+ }
+ memmove(buf, tmp, n);
+ }
+ poperror();
+ logfsfreemem(tmp);
+}
+
+static int
+readok(void *a)
+{
+ Devlogfs *d = a;
+ return d->reading;
+}
+
+static int
+writeok(void *a)
+{
+ Devlogfs *d = a;
+ return !d->reading;
+}
+
+static long
+lfsrvread(Devlogfs *d, void *buf, long n)
+{
+ qlock(&d->rlock);
+ if(waserror()){
+ qunlock(&d->rlock);
+ nexterror();
+ }
+ if (d->state == Hungup)
+ error(Ehungup);
+ Sleep(&d->readrendez, readok, d);
+ if (n > d->readcount)
+ n = d->readcount;
+ memmove(buf, d->readp, n);
+ d->readp += n;
+ d->readcount -= n;
+ if (d->readcount == 0) {
+ d->reading = 0;
+ Wakeup(&d->writerendez);
+ }
+ poperror();
+ qunlock(&d->rlock);
+ return n;
+}
+
+static void
+reply(Devlogfs *d)
+{
+ d->readp = d->readbuf;
+ d->readcount = convS2M(&d->out, d->readp, d->readbufsize);
+//print("reply is %d bytes\n", d->readcount);
+ if (d->readcount == 0)
+ panic("logfs: reply: did not fit\n");
+ d->reading = 1;
+ Wakeup(&d->readrendez);
+}
+
+static void
+rerror(Devlogfs *d, char *ename)
+{
+ d->out.type = Rerror;
+ d->out.ename = ename;
+ reply(d);
+}
+
+static struct {
+ QLock qlock;
+ int (*read)(void *magic, Devlogfs *d, int line, char *buf, int buflen);
+ void *magic;
+ Devlogfs *d;
+ int line;
+} dump;
+
+static void *
+extentdumpinit(Devlogfs *d, int argc, char **argv)
+{
+ int *p;
+ ulong path;
+ u32int flashaddr, length;
+ long block;
+ int page, offset;
+
+ if (argc != 1)
+ error(Ebadarg);
+ path = strtoul(argv[0], 0, 0);
+ errorany(logfsserverreadpathextent(d->server, path, 0, &flashaddr, &length, &block, &page, &offset));
+ p = emalloc(sizeof(ulong));
+ *p = path;
+ return p;
+}
+
+static int
+extentdumpread(void *magic, Devlogfs *d, int line, char *buf, int buflen)
+{
+ ulong *p = magic;
+ u32int flashaddr, length;
+ long block;
+ int page, offset;
+ USED(d);
+ errorany(logfsserverreadpathextent(d->server, *p, line, &flashaddr, &length, &block, &page, &offset));
+ if (length == 0)
+ return 0;
+ return snprint(buf, buflen, "%.8ux %ud %ld %d %d\n", flashaddr, length, block, page, offset);
+}
+
+static void
+devlogfsdumpinit(Devlogfs *d,
+ void *(*init)(Devlogfs *d, int argc, char **argv),
+ int (*read)(void *magic, Devlogfs *d, int line, char *buf, int buflen), int argc, char **argv)
+{
+ qlock(&dump.qlock);
+ if (waserror()) {
+ qunlock(&dump.qlock);
+ nexterror();
+ }
+ if (d) {
+ if (d->state < NeedVersion)
+ error("not mounted");
+ qlock(&d->qlock);
+ if (waserror()) {
+ qunlock(&d->qlock);
+ nexterror();
+ }
+ }
+ if (dump.magic) {
+ logfsfreemem(dump.magic);
+ dump.magic = nil;
+ }
+ dump.d = d;
+ dump.magic = (*init)(d, argc, argv);
+ dump.read = read;
+ dump.line = 0;
+ if (d) {
+ poperror();
+ qunlock(&d->qlock);
+ }
+ poperror();
+ qunlock(&dump.qlock);
+}
+
+static long
+devlogfsdumpread(char *buf, int buflen)
+{
+ char *tmp = nil;
+ long n;
+ qlock(&dump.qlock);
+ if (waserror()) {
+ logfsfreemem(tmp);
+ qunlock(&dump.qlock);
+ nexterror();
+ }
+ if (dump.magic == nil)
+ error(Eio);
+ tmp = emalloc(READSTR);
+ if (dump.d) {
+ if (dump.d->state < NeedVersion)
+ error("not mounted");
+ qlock(&dump.d->qlock);
+ if (waserror()) {
+ qunlock(&dump.d->qlock);
+ nexterror();
+ }
+ }
+ n = (*dump.read)(dump.magic, dump.d, dump.line, tmp, READSTR);
+ if (n) {
+ dump.line++;
+ n = readstr(0, buf, buflen, tmp);
+ }
+ if (dump.d) {
+ poperror();
+ qunlock(&dump.d->qlock);
+ }
+ logfsfreemem(tmp);
+ poperror();
+ qunlock(&dump.qlock);
+ return n;
+}
+
+static void
+devlogfsserverlogsweep(Devlogfs *d, int justone)
+{
+ int didsomething;
+ if (d->state < NeedVersion)
+ error("not mounted");
+ qlock(&d->qlock);
+ if (waserror()) {
+ qunlock(&d->qlock);
+ nexterror();
+ }
+ errorany(logfsserverlogsweep(d->server, justone, &didsomething));
+ poperror();
+ qunlock(&d->qlock);
+}
+
+static void
+devlogfsserversync(Devlogfs *d)
+{
+ if (d->state < NeedVersion)
+ return;
+ qlock(&d->qlock);
+ if (waserror()) {
+ qunlock(&d->qlock);
+ nexterror();
+ }
+ errorany(logfsserverflush(d->server));
+ poperror();
+ qunlock(&d->qlock);
+}
+
+static void
+lfssrvwrite(Devlogfs *d, void *buf, long n)
+{
+ volatile int locked = 0;
+
+ qlock(&d->wlock);
+ if(waserror()){
+ qunlock(&d->wlock);
+ nexterror();
+ }
+ if (d->state == Hungup)
+ error(Ehungup);
+ Sleep(&d->writerendez, writeok, d);
+ if (convM2S(buf, n, &d->in) != n) {
+ /*
+ * someone is writing drivel; have nothing to do with them anymore
+ * most common cause; trying to mount authenticated
+ */
+ d->state = Hungup;
+ error(Ehungup);
+ }
+ d->out.tag = d->in.tag;
+ d->out.fid = d->in.fid;
+ d->out.type = d->in.type + 1;
+ if (waserror()) {
+ if (locked)
+ qunlock(&d->qlock);
+ rerror(d, up->env->errstr);
+ goto Replied;
+ }
+ if (d->in.type != Tversion && d->in.type != Tattach) {
+ if (d->state != Attached)
+ error("must be attached");
+ qlock(&d->qlock);
+ locked = 1;
+ }
+ switch (d->in.type) {
+ case Tauth:
+ error("no authentication needed");
+ case Tversion: {
+ char *rversion;
+ if (d->state != NeedVersion)
+ error("unexpected Tversion");
+ if (d->in.tag != NOTAG)
+ error("protocol botch");
+ /*
+ * check the version string
+ */
+ if (strcmp(d->in.version, devlogfs9pversion) != 0)
+ rversion = "unknown";
+ else
+ rversion = devlogfs9pversion;
+ /*
+ * allocate the reply buffer
+ */
+ d->readbufsize = d->in.msize;
+ if (d->readbufsize > MAXMSIZE)
+ d->readbufsize = MAXMSIZE;
+ d->readbuf = emalloc(d->readbufsize);
+ /*
+ * compose the Rversion
+ */
+ d->out.msize = d->readbufsize;
+ d->out.version = rversion;
+ d->state = NeedAttach;
+ break;
+ }
+ case Tattach:
+ if (d->state != NeedAttach)
+ error("unexpected attach");
+ if (d->in.afid != NOFID)
+ error("unexpected afid");
+ errorany(logfsserverattach(d->server, d->in.fid, d->in.uname, &d->out.qid));
+ d->state = Attached;
+ break;
+ case Tclunk:
+ errorany(logfsserverclunk(d->server, d->in.fid));
+ break;
+ case Tcreate:
+ errorany(logfsservercreate(d->server, d->in.fid, d->in.name, d->in.perm, d->in.mode, &d->out.qid));
+ d->out.iounit = d->readbufsize - 11;
+ break;
+ case Tflush:
+ break;
+ case Topen:
+ errorany(logfsserveropen(d->server, d->in.fid, d->in.mode, &d->out.qid));
+ d->out.iounit = d->readbufsize - 11;
+ break;
+ case Tread:
+ d->out.data = (char *)d->readbuf + 11;
+ /* TODO - avoid memmove */
+ errorany(logfsserverread(d->server, d->in.fid, d->in.offset, d->in.count, (uchar *)d->out.data,
+ d->readbufsize - 11, &d->out.count));
+ break;
+ case Tremove:
+ errorany(logfsserverremove(d->server, d->in.fid));
+ break;
+ case Tstat:
+ d->out.stat = d->readbuf + 9;
+ /* TODO - avoid memmove */
+ errorany(logfsserverstat(d->server, d->in.fid, d->out.stat, d->readbufsize - 9, &d->out.nstat));
+// print("nstat %d\n", d->out.nstat);
+ break;
+ case Twalk:
+ errorany(logfsserverwalk(d->server, d->in.fid, d->in.newfid,
+ d->in.nwname, d->in.wname, &d->out.nwqid, d->out.wqid));
+ break;
+ case Twrite:
+ errorany(logfsserverwrite(d->server, d->in.fid, d->in.offset, d->in.count, (uchar *)d->in.data,
+ &d->out.count));
+ break;
+ case Twstat:
+ errorany(logfsserverwstat(d->server, d->in.fid, d->in.stat, d->in.nstat));
+ break;
+ default:
+ print("lfssrvwrite: msg %d unimplemented\n", d->in.type);
+ error("unimplemented");
+ }
+ poperror();
+ if (locked)
+ qunlock(&d->qlock);
+ reply(d);
+Replied:
+ poperror();
+ qunlock(&d->wlock);
+}
+
+static long
+devlogfsread(Chan *c, void *buf, long n, vlong off)
+{
+ int instance, qid, qt;
+
+ SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
+ USED(instance);
+#ifdef CALLTRACE
+ print("devlogfsread(c = 0x%.8lux, buf = 0x%.8lux, n = %ld, instance = %d, qid = %d, qt = %d) - start\n",
+ (ulong)c, (ulong)buf, n, instance, qid, qt);
+#endif
+ if(qt & QTDIR) {
+#ifdef CALLTRACE
+ print("devlogfsread(c = 0x%.8lux, buf = 0x%.8lux, n = %ld, instance = %d, qid = %d, qt = %d) - calling devdirread\n",
+ (ulong)c, (ulong)buf, n, instance, qid, qt);
+#endif
+ return devdirread(c, buf, n, 0, 0, devlogfsgen);
+ }
+
+ if(DATAQID(qid, qt)) {
+ if (qid == Qfsboot) {
+ Devlogfs *l = c->aux;
+ qlock(&l->bootqlock);
+ if (waserror()) {
+ qunlock(&l->bootqlock);
+ nexterror();
+ }
+ smartio((SMARTIOFN *)logfsbootio, l->lb, buf, n, off, logfsbootgetiosize(l->lb), 0);
+ poperror();
+ qunlock(&l->bootqlock);
+ return n;
+ }
+ else if (qid == Qfs) {
+ Devlogfs *d = c->aux;
+ return lfsrvread(d, buf, n);
+ }
+ error(Eio);
+ }
+
+ if (qid == Qusers) {
+ long nr;
+ errorany(logfsisusersread(is, buf, n, (ulong)off, &nr));
+ return nr;
+ }
+ else if (qid == Qdump)
+ return devlogfsdumpread(buf, n);
+
+ if (qid != Qctl)
+ error(Egreg);
+
+ return 0;
+}
+
+enum {
+ CMconfig,
+ CMformat,
+ CMopen,
+ CMsweep,
+ CMtrace,
+ CMunconfig,
+ CMextent,
+ CMsweepone,
+ CMtest,
+ CMleakaudit,
+ CMsync
+};
+
+static Cmdtab fscmds[] = {
+ {CMconfig, "config", 2},
+ {CMformat, "format", 2},
+ {CMopen, "open", 0},
+ {CMsweep, "sweep", 1},
+ {CMsweepone, "sweepone", 1},
+ {CMtrace, "trace", 0},
+ {CMunconfig, "unconfig", 1},
+ {CMextent, "extent", 0},
+ {CMtest, "test", 0},
+ {CMleakaudit, "leakaudit", 1},
+ {CMsync, "sync", 1},
+};
+
+static long
+devlogfswrite(Chan *c, void *buf, long n, vlong off)
+{
+ int instance, qid, qt, i;
+ Cmdbuf *cmd;
+ Cmdtab *ct;
+
+ if(n <= 0)
+ return 0;
+ SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
+#ifdef CALLTRACE
+ print("devlogfswrite(c = 0x%.8lux, buf = 0x%.8lux, n = %ld, instance = %d, qid = %d, qt = %d) - start\n",
+ (ulong)c, (ulong)buf, n, instance, qid, qt);
+#endif
+ USED(instance);
+ if(DATAQID(qid, qt)){
+ if (qid == Qfsboot) {
+ Devlogfs *l = c->aux;
+ qlock(&l->bootqlock);
+ if (waserror()) {
+ qunlock(&l->bootqlock);
+ nexterror();
+ }
+ smartio((SMARTIOFN *)logfsbootio, l->lb, buf, n, off, logfsbootgetiosize(l->lb), 1);
+ poperror();
+ qunlock(&l->bootqlock);
+ return n;
+ }
+ else if (qid == Qfs) {
+ Devlogfs *d = c->aux;
+ lfssrvwrite(d, buf, n);
+ return n;
+ }
+ error(Eio);
+ }
+ else if (qid == Qctl) {
+ Devlogfs *l = nil;
+ int a;
+
+ cmd = parsecmd(buf, n);
+ if(waserror()){
+ free(cmd);
+ nexterror();
+ }
+ i = cmd->nf;
+ if(0){print("i=%d", i); for(i=0; i<cmd->nf; i++)print(" %q", cmd->f[i]); print("\n");}
+ if (i <= 0)
+ error(Ebadarg);
+ if (i == 3 && strcmp(cmd->f[0], "uname") == 0) {
+ switch (cmd->f[2][0]) {
+ default:
+ errorany(logfsisgroupcreate(is, cmd->f[1], cmd->f[2]));
+ break;
+ case ':':
+ errorany(logfsisgroupcreate(is, cmd->f[1], cmd->f[2] + 1));
+ break;
+ case '%':
+ errorany(logfsisgrouprename(is, cmd->f[1], cmd->f[2] + 1));
+ break;
+ case '=':
+ errorany(logfsisgroupsetleader(is, cmd->f[1], cmd->f[2] + 1));
+ break;
+ case '+':
+ errorany(logfsisgroupaddmember(is, cmd->f[1], cmd->f[2] + 1));
+ break;
+ case '-':
+ errorany(logfsisgroupremovemember(is, cmd->f[1], cmd->f[2] + 1));
+ break;
+ }
+ i = 0;
+ }
+ if (i == 4 && strcmp(cmd->f[0], "fsys") == 0 && strcmp(cmd->f[2], "config") == 0) {
+ l = devlogfsconfig(cmd->f[1], cmd->f[3]);
+ i = 0;
+ }
+ else if (i >= 2 && strcmp(cmd->f[0], "fsys") == 0) {
+ l = devlogfssetdefname(cmd->f[1]);
+ if (l == nil)
+ errorf("file system %q not configured", cmd->f[1]);
+ i -= 2;
+ cmd->f += 2;
+ cmd->nf = i;
+ }
+ if (i != 0) {
+ ct = lookupcmd(cmd, fscmds, nelem(fscmds));
+ if (l == nil)
+ l = devlogfssetdefname(nil);
+ if(l == nil && ct->index != CMleakaudit)
+ error("file system not configured");
+ switch(ct->index){
+ case CMopen:
+ for (a = 1; a < i; a++)
+ if (cmd->f[a][0] == '-')
+ switch (cmd->f[a][1]) {
+ case 'P':
+ l->openflags |= LogfsOpenFlagNoPerm;
+ break;
+ case 'W':
+ l->openflags |= LogfsOpenFlagWstatAllow;
+ break;
+ default:
+ error(Ebadarg);
+ }
+ devlogfsllopen(l);
+ break;
+ case CMformat:
+ devlogfsllformat(l, strtol(cmd->f[1], nil, 0));
+ break;
+ case CMsweep:
+ devlogfsserverlogsweep(l, 0);
+ break;
+ case CMsweepone:
+ devlogfsserverlogsweep(l, 1);
+ break;
+ case CMtrace:
+ l->logfstrace = i > 1 ? strtol(cmd->f[1], nil, 0) : 0;
+ if (l->server)
+ logfsservertrace(l->server, l->logfstrace);
+ if (l->lb)
+ logfsboottrace(l->lb, l->logfstrace);
+ break;
+ case CMunconfig:
+ if (l->ref.ref > 0)
+ error(Einuse);
+ devlogfsunconfig(l);
+ break;
+ case CMextent:
+ if(i < 2)
+ error(Ebadarg);
+ devlogfsdumpinit(l, extentdumpinit, extentdumpread, i - 1, cmd->f + 1);
+ break;
+ case CMtest:
+ if(i < 2)
+ error(Ebadarg);
+ errorany(logfsservertestcmd(l->server, i - 1, cmd->f + 1));
+ break;
+ case CMleakaudit:
+#ifdef LEAKHUNT
+ leakaudit();
+#endif
+ break;
+ case CMsync:
+ devlogfsserversync(l);
+ break;
+ default:
+ error(Ebadarg);
+ }
+ }
+ poperror();
+ free(cmd);
+ return n;
+ }
+ error(Egreg);
+ return 0; /* not reached */
+}
+
+static void
+devlogfsfree(Devlogfs *devlogfs)
+{
+ if (devlogfs != nil) {
+ int i;
+ logfsfreemem(devlogfs->device);
+ logfsfreemem(devlogfs->name);
+ for (i = 0; i < Qend - Qfs; i++)
+ logfsfreemem(devlogfs->filename[i]);
+ cclose(devlogfs->flash);
+ cclose(devlogfs->flashctl);
+ qlock(&devlogfs->qlock);
+ logfsserverfree(&devlogfs->server);
+ logfsbootfree(devlogfs->lb);
+ if (devlogfs->ll)
+ (*devlogfs->ll->free)(devlogfs->ll);
+ logfsfreemem(devlogfs->readbuf);
+ qunlock(&devlogfs->qlock);
+ logfsfreemem(devlogfs);
+ }
+}
+
+#ifdef EMU
+ulong
+logfsnow(void)
+{
+ extern vlong timeoffset;
+ return (timeoffset + osusectime()) / 1000000;
+}
+#endif
+
+Dev logfsdevtab = {
+ 0x29f,
+// L'ʟ',
+ "logfs",
+
+#ifndef EMU
+ devreset,
+#endif
+ devinit,
+#ifndef EMU
+ devshutdown,
+#endif
+ devlogfsattach,
+ devlogfswalk,
+ devlogfsstat,
+ devlogfsopen,
+ devcreate,
+ devlogfsclose,
+ devlogfsread,
+ devbread,
+ devlogfswrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devloopback.c b/os/port/devloopback.c
new file mode 100644
index 00000000..e6d6508d
--- /dev/null
+++ b/os/port/devloopback.c
@@ -0,0 +1,743 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+typedef struct Link Link;
+typedef struct Loop Loop;
+
+struct Link
+{
+ Lock;
+
+ int ref;
+
+ long packets; /* total number of packets sent */
+ long bytes; /* total number of bytes sent */
+ int indrop; /* enable dropping on iq overflow */
+ long soverflows; /* packets dropped because iq overflowed */
+ long droprate; /* drop 1/droprate packets in tq */
+ long drops; /* packets deliberately dropped */
+
+ vlong delay0ns; /* nanosec of delay in the link */
+ long delaynns; /* nanosec of delay per byte */
+
+ Block *tq; /* transmission queue */
+ Block *tqtail;
+ vlong tout; /* time the last packet in tq is really out */
+ vlong tin; /* time the head packet in tq enters the remote side */
+
+ long limit; /* queue buffering limit */
+ Queue *oq; /* output queue from other side & packets in the link */
+ Queue *iq;
+
+ Timer ci; /* time to move packets from next packet from oq */
+};
+
+struct Loop
+{
+ QLock;
+ int ref;
+ int minmtu; /* smallest block transmittable */
+ Loop *next;
+ ulong path;
+ Link link[2];
+};
+
+static struct
+{
+ Lock;
+ ulong path;
+} loopbackalloc;
+
+enum
+{
+ Qtopdir= 1, /* top level directory */
+
+ Qloopdir, /* loopback* directory */
+
+ Qportdir, /* directory each end of the loop */
+ Qctl,
+ Qstatus,
+ Qstats,
+ Qdata,
+
+ MaxQ,
+
+ Nloopbacks = 5,
+
+ Statelen = 23*1024, /* status buffer size */
+
+ Tmsize = 8,
+ Delayn = 10000, /* default delays in ns */
+ Delay0 = 2500000,
+
+ Loopqlim = 32*1024, /* default size of queues */
+};
+
+static Dirtab loopportdir[] =
+{
+ "ctl", {Qctl}, 0, 0222,
+ "status", {Qstatus}, 0, 0444,
+ "stats", {Qstats}, 0, 0444,
+ "data", {Qdata}, 0, 0666,
+};
+static Dirtab loopdirs[MaxQ];
+
+static Loop loopbacks[Nloopbacks];
+
+#define TYPE(x) (((ulong)(x))&0xff)
+#define ID(x) (((ulong)(x))>>8)
+#define QID(x,y) ((((ulong)(x))<<8)|((ulong)(y)))
+
+static void looper(Loop *lb);
+static long loopoput(Loop *lb, Link *link, Block *bp);
+static void ptime(uchar *p, vlong t);
+static vlong gtime(uchar *p);
+static void closelink(Link *link, int dofree);
+static void pushlink(Link *link, vlong now);
+static void freelb(Loop *lb);
+static void linkintr(Ureg*, Timer *ci);
+
+static void
+loopbackinit(void)
+{
+ int i;
+
+ for(i = 0; i < Nloopbacks; i++)
+ loopbacks[i].path = i;
+
+ /* invert directory tables for non-directory entries */
+ for(i=0; i<nelem(loopportdir); i++)
+ loopdirs[loopportdir[i].qid.path] = loopportdir[i];
+}
+
+static Chan*
+loopbackattach(char *spec)
+{
+ Loop *volatile lb;
+ Queue *q;
+ Chan *c;
+ int chan;
+ int dev;
+
+ dev = 0;
+ if(spec != nil){
+ dev = atoi(spec);
+ if(dev >= Nloopbacks)
+ error(Ebadspec);
+ }
+
+ c = devattach('X', spec);
+ lb = &loopbacks[dev];
+
+ qlock(lb);
+ if(waserror()){
+ lb->ref--;
+ qunlock(lb);
+ nexterror();
+ }
+
+ lb->ref++;
+ if(lb->ref == 1){
+ for(chan = 0; chan < 2; chan++){
+ lb->link[chan].ci.tmode = Tabsolute;
+ lb->link[chan].ci.ta = &lb->link[chan];
+ lb->link[chan].ci.tf = linkintr;
+ lb->link[chan].limit = Loopqlim;
+ q = qopen(lb->link[chan].limit, 0, 0, 0);
+ lb->link[chan].iq = q;
+ if(q == nil){
+ freelb(lb);
+ exhausted("memory");
+ }
+ q = qopen(lb->link[chan].limit, 0, 0, 0);
+ lb->link[chan].oq = q;
+ if(q == nil){
+ freelb(lb);
+ exhausted("memory");
+ }
+ lb->link[chan].indrop = 1;
+
+ lb->link[chan].delaynns = Delayn;
+ lb->link[chan].delay0ns = Delay0;
+ }
+ }
+ poperror();
+ qunlock(lb);
+
+ mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
+ c->aux = lb;
+ c->dev = dev;
+ return c;
+}
+
+static int
+loopbackgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
+{
+ Dirtab *tab;
+ int len, type;
+ Qid qid;
+
+ type = TYPE(c->qid.path);
+ if(i == DEVDOTDOT){
+ switch(type){
+ case Qtopdir:
+ case Qloopdir:
+ snprint(up->genbuf, sizeof(up->genbuf), "#X%ld", c->dev);
+ mkqid(&qid, QID(0, Qtopdir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ break;
+ case Qportdir:
+ snprint(up->genbuf, sizeof(up->genbuf), "loopback%ld", c->dev);
+ mkqid(&qid, QID(0, Qloopdir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ break;
+ default:
+ panic("loopbackgen %llux", c->qid.path);
+ }
+ return 1;
+ }
+
+ switch(type){
+ case Qtopdir:
+ if(i != 0)
+ return -1;
+ snprint(up->genbuf, sizeof(up->genbuf), "loopback%ld", c->dev);
+ mkqid(&qid, QID(0, Qloopdir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ case Qloopdir:
+ if(i >= 2)
+ return -1;
+ snprint(up->genbuf, sizeof(up->genbuf), "%d", i);
+ mkqid(&qid, QID(i, QID(0, Qportdir)), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ case Qportdir:
+ if(i >= nelem(loopportdir))
+ return -1;
+ tab = &loopportdir[i];
+ mkqid(&qid, QID(ID(c->qid.path), tab->qid.path), 0, QTFILE);
+ devdir(c, qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+ default:
+ /* non directory entries end up here; must be in lowest level */
+ if(c->qid.type & QTDIR)
+ panic("loopbackgen: unexpected directory");
+ if(i != 0)
+ return -1;
+ tab = &loopdirs[type];
+ if(tab == nil)
+ panic("loopbackgen: unknown type: %d", type);
+ len = tab->length;
+ devdir(c, c->qid, tab->name, len, eve, tab->perm, dp);
+ return 1;
+ }
+}
+
+
+static Walkqid*
+loopbackwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+ Loop *lb;
+
+ wq = devwalk(c, nc, name, nname, nil, 0, loopbackgen);
+ if(wq != nil && wq->clone != nil && wq->clone != c){
+ lb = c->aux;
+ qlock(lb);
+ lb->ref++;
+ if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata)
+ lb->link[ID(c->qid.path)].ref++;
+ qunlock(lb);
+ }
+ return wq;
+}
+
+static int
+loopbackstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, nil, 0, loopbackgen);
+}
+
+/*
+ * if the stream doesn't exist, create it
+ */
+static Chan*
+loopbackopen(Chan *c, int omode)
+{
+ Loop *lb;
+
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Ebadarg);
+ c->mode = omode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ lb = c->aux;
+ qlock(lb);
+ if(TYPE(c->qid.path) == Qdata){
+ if(lb->link[ID(c->qid.path)].ref){
+ qunlock(lb);
+ error(Einuse);
+ }
+ lb->link[ID(c->qid.path)].ref++;
+ }
+ qunlock(lb);
+
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static void
+loopbackclose(Chan *c)
+{
+ Loop *lb;
+ int ref, chan;
+
+ lb = c->aux;
+
+ qlock(lb);
+
+ /*
+ * closing either side hangs up the stream
+ */
+ if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata){
+ chan = ID(c->qid.path);
+ if(--lb->link[chan].ref == 0){
+ qhangup(lb->link[chan ^ 1].oq, nil);
+ looper(lb);
+ }
+ }
+
+
+ /*
+ * if both sides are closed, they are reusable
+ */
+ if(lb->link[0].ref == 0 && lb->link[1].ref == 0){
+ for(chan = 0; chan < 2; chan++){
+ closelink(&lb->link[chan], 0);
+ qreopen(lb->link[chan].iq);
+ qreopen(lb->link[chan].oq);
+ qsetlimit(lb->link[chan].oq, lb->link[chan].limit);
+ qsetlimit(lb->link[chan].iq, lb->link[chan].limit);
+ }
+ }
+ ref = --lb->ref;
+ if(ref == 0)
+ freelb(lb);
+ qunlock(lb);
+}
+
+static void
+freelb(Loop *lb)
+{
+ int chan;
+
+ for(chan = 0; chan < 2; chan++)
+ closelink(&lb->link[chan], 1);
+}
+
+/*
+ * called with the Loop qlocked,
+ * so only pushlink can mess with the queues
+ */
+static void
+closelink(Link *link, int dofree)
+{
+ Queue *iq, *oq;
+ Block *bp;
+
+ ilock(link);
+ iq = link->iq;
+ oq = link->oq;
+ bp = link->tq;
+ link->tq = nil;
+ link->tqtail = nil;
+ link->tout = 0;
+ link->tin = 0;
+ timerdel(&link->ci);
+ iunlock(link);
+ if(iq != nil){
+ qclose(iq);
+ if(dofree){
+ ilock(link);
+ free(iq);
+ link->iq = nil;
+ iunlock(link);
+ }
+ }
+ if(oq != nil){
+ qclose(oq);
+ if(dofree){
+ ilock(link);
+ free(oq);
+ link->oq = nil;
+ iunlock(link);
+ }
+ }
+ freeblist(bp);
+}
+
+static long
+loopbackread(Chan *c, void *va, long n, vlong offset)
+{
+ Loop *lb;
+ Link *link;
+ char *buf;
+ long rv;
+
+ lb = c->aux;
+ switch(TYPE(c->qid.path)){
+ default:
+ error(Eperm);
+ return -1; /* not reached */
+ case Qtopdir:
+ case Qloopdir:
+ case Qportdir:
+ return devdirread(c, va, n, nil, 0, loopbackgen);
+ case Qdata:
+ return qread(lb->link[ID(c->qid.path)].iq, va, n);
+ case Qstatus:
+ link = &lb->link[ID(c->qid.path)];
+ buf = smalloc(Statelen);
+ rv = snprint(buf, Statelen, "delay %lld %ld\n", link->delay0ns, link->delaynns);
+ rv += snprint(buf+rv, Statelen-rv, "limit %ld\n", link->limit);
+ rv += snprint(buf+rv, Statelen-rv, "indrop %d\n", link->indrop);
+ snprint(buf+rv, Statelen-rv, "droprate %ld\n", link->droprate);
+ rv = readstr(offset, va, n, buf);
+ free(buf);
+ break;
+ case Qstats:
+ link = &lb->link[ID(c->qid.path)];
+ buf = smalloc(Statelen);
+ rv = snprint(buf, Statelen, "packets: %ld\n", link->packets);
+ rv += snprint(buf+rv, Statelen-rv, "bytes: %ld\n", link->bytes);
+ rv += snprint(buf+rv, Statelen-rv, "dropped: %ld\n", link->drops);
+ snprint(buf+rv, Statelen-rv, "soft overflows: %ld\n", link->soverflows);
+ rv = readstr(offset, va, n, buf);
+ free(buf);
+ break;
+ }
+ return rv;
+}
+
+static Block*
+loopbackbread(Chan *c, long n, ulong offset)
+{
+ Loop *lb;
+
+ lb = c->aux;
+ if(TYPE(c->qid.path) == Qdata)
+ return qbread(lb->link[ID(c->qid.path)].iq, n);
+
+ return devbread(c, n, offset);
+}
+
+static long
+loopbackbwrite(Chan *c, Block *bp, ulong off)
+{
+ Loop *lb;
+
+ lb = c->aux;
+ if(TYPE(c->qid.path) == Qdata)
+ return loopoput(lb, &lb->link[ID(c->qid.path) ^ 1], bp);
+ return devbwrite(c, bp, off);
+}
+
+static long
+loopbackwrite(Chan *c, void *va, long n, vlong off)
+{
+ Loop *lb;
+ Link *link;
+ Cmdbuf *volatile cb;
+ Block *volatile bp;
+ vlong d0ns;
+ long dnns;
+
+ switch(TYPE(c->qid.path)){
+ case Qdata:
+ bp = allocb(n);
+ if(waserror()){
+ freeb(bp);
+ nexterror();
+ }
+ memmove(bp->wp, va, n);
+ poperror();
+ bp->wp += n;
+ return loopbackbwrite(c, bp, off);
+ case Qctl:
+ lb = c->aux;
+ link = &lb->link[ID(c->qid.path)];
+ cb = parsecmd(va, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf < 1)
+ error("short control request");
+ if(strcmp(cb->f[0], "delay") == 0){
+ if(cb->nf != 3)
+ error("usage: delay latency bytedelay");
+ d0ns = strtoll(cb->f[1], nil, 10);
+ dnns = strtol(cb->f[2], nil, 10);
+
+ /*
+ * it takes about 20000 cycles on a pentium ii
+ * to run pushlink; perhaps this should be accounted.
+ */
+
+ ilock(link);
+ link->delay0ns = d0ns;
+ link->delaynns = dnns;
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "indrop") == 0){
+ if(cb->nf != 2)
+ error("usage: indrop [01]");
+ ilock(link);
+ link->indrop = strtol(cb->f[1], nil, 0) != 0;
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "droprate") == 0){
+ if(cb->nf != 2)
+ error("usage: droprate ofn");
+ ilock(link);
+ link->droprate = strtol(cb->f[1], nil, 0);
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "limit") == 0){
+ if(cb->nf != 2)
+ error("usage: limit maxqsize");
+ ilock(link);
+ link->limit = strtol(cb->f[1], nil, 0);
+ qsetlimit(link->oq, link->limit);
+ qsetlimit(link->iq, link->limit);
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "reset") == 0){
+ if(cb->nf != 1)
+ error("usage: reset");
+ ilock(link);
+ link->packets = 0;
+ link->bytes = 0;
+ link->indrop = 0;
+ link->soverflows = 0;
+ link->drops = 0;
+ iunlock(link);
+ }else
+ error("unknown control request");
+ poperror();
+ free(cb);
+ break;
+ default:
+ error(Eperm);
+ }
+
+ return n;
+}
+
+static long
+loopoput(Loop *lb, Link *link, Block *volatile bp)
+{
+ long n;
+
+ n = BLEN(bp);
+
+ /* make it a single block with space for the loopback timing header */
+ if(waserror()){
+ freeb(bp);
+ nexterror();
+ }
+ bp = padblock(bp, Tmsize);
+ if(bp->next)
+ bp = concatblock(bp);
+ if(BLEN(bp) < lb->minmtu)
+ bp = adjustblock(bp, lb->minmtu);
+ poperror();
+ ptime(bp->rp, todget(nil));
+
+ link->packets++;
+ link->bytes += n;
+
+ qbwrite(link->oq, bp);
+
+ looper(lb);
+ return n;
+}
+
+static void
+looper(Loop *lb)
+{
+ vlong t;
+ int chan;
+
+ t = todget(nil);
+ for(chan = 0; chan < 2; chan++)
+ pushlink(&lb->link[chan], t);
+}
+
+static void
+linkintr(Ureg*, Timer *ci)
+{
+ Link *link;
+
+ link = ci->ta;
+ pushlink(link, ci->tns);
+}
+
+/*
+ * move blocks between queues if they are ready.
+ * schedule an interrupt for the next interesting time.
+ *
+ * must be called with the link ilocked.
+ */
+static void
+pushlink(Link *link, vlong now)
+{
+ Block *bp;
+ vlong tout, tin;
+
+ /*
+ * put another block in the link queue
+ */
+ ilock(link);
+ if(link->iq == nil || link->oq == nil){
+ iunlock(link);
+ return;
+
+ }
+ timerdel(&link->ci);
+
+ /*
+ * put more blocks into the xmit queue
+ * use the time the last packet was supposed to go out
+ * as the start time for the next packet, rather than
+ * the current time. this more closely models a network
+ * device which can queue multiple output packets.
+ */
+ tout = link->tout;
+ if(!tout)
+ tout = now;
+ while(tout <= now){
+ bp = qget(link->oq);
+ if(bp == nil){
+ tout = 0;
+ break;
+ }
+
+ /*
+ * can't send the packet before it gets queued
+ */
+ tin = gtime(bp->rp);
+ if(tin > tout)
+ tout = tin;
+ tout = tout + (BLEN(bp) - Tmsize) * link->delaynns;
+
+ /*
+ * drop packets
+ */
+ if(link->droprate && nrand(link->droprate) == 0)
+ link->drops++;
+ else{
+ ptime(bp->rp, tout + link->delay0ns);
+ if(link->tq == nil)
+ link->tq = bp;
+ else
+ link->tqtail->next = bp;
+ link->tqtail = bp;
+ }
+ }
+
+ /*
+ * record the next time a packet can be sent,
+ * but don't schedule an interrupt if none is waiting
+ */
+ link->tout = tout;
+ if(!qcanread(link->oq))
+ tout = 0;
+
+ /*
+ * put more blocks into the receive queue
+ */
+ tin = 0;
+ while(bp = link->tq){
+ tin = gtime(bp->rp);
+ if(tin > now)
+ break;
+ bp->rp += Tmsize;
+ link->tq = bp->next;
+ bp->next = nil;
+ if(!link->indrop)
+ qpassnolim(link->iq, bp);
+ else if(qpass(link->iq, bp) < 0)
+ link->soverflows++;
+ tin = 0;
+ }
+ if(bp == nil && qisclosed(link->oq) && !qcanread(link->oq) && !qisclosed(link->iq))
+ qhangup(link->iq, nil);
+ link->tin = tin;
+ if(!tin || tin > tout && tout)
+ tin = tout;
+
+ link->ci.tns = tin;
+ if(tin){
+ if(tin < now)
+ panic("loopback unfinished business");
+ timeradd(&link->ci);
+ }
+ iunlock(link);
+}
+
+static void
+ptime(uchar *p, vlong t)
+{
+ ulong tt;
+
+ tt = t >> 32;
+ p[0] = tt >> 24;
+ p[1] = tt >> 16;
+ p[2] = tt >> 8;
+ p[3] = tt;
+ tt = t;
+ p[4] = tt >> 24;
+ p[5] = tt >> 16;
+ p[6] = tt >> 8;
+ p[7] = tt;
+}
+
+static vlong
+gtime(uchar *p)
+{
+ ulong t1, t2;
+
+ t1 = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+ t2 = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
+ return ((vlong)t1 << 32) | t2;
+}
+
+Dev loopbackdevtab = {
+ 'X',
+ "loopback",
+
+ devreset,
+ loopbackinit,
+ devshutdown,
+ loopbackattach,
+ loopbackwalk,
+ loopbackstat,
+ loopbackopen,
+ devcreate,
+ loopbackclose,
+ loopbackread,
+ loopbackbread,
+ loopbackwrite,
+ loopbackbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devmnt.c b/os/port/devmnt.c
new file mode 100644
index 00000000..6fd3ec6b
--- /dev/null
+++ b/os/port/devmnt.c
@@ -0,0 +1,1204 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+/*
+ * References are managed as follows:
+ * The channel to the server - a network connection or pipe - has one
+ * reference for every Chan open on the server. The server channel has
+ * c->mux set to the Mnt used for muxing control to that server. Mnts
+ * have no reference count; they go away when c goes away.
+ * Each channel derived from the mount point has mchan set to c,
+ * and increfs/decrefs mchan to manage references on the server
+ * connection.
+ */
+
+#define MAXRPC (IOHDRSZ+8192)
+
+struct Mntrpc
+{
+ Chan* c; /* Channel for whom we are working */
+ Mntrpc* list; /* Free/pending list */
+ Fcall request; /* Outgoing file system protocol message */
+ Fcall reply; /* Incoming reply */
+ Mnt* m; /* Mount device during rpc */
+ Rendez r; /* Place to hang out */
+ uchar* rpc; /* I/O Data buffer */
+ uint rpclen; /* len of buffer */
+ Block *b; /* reply blocks */
+ char done; /* Rpc completed */
+ uvlong stime; /* start time for mnt statistics */
+ ulong reqlen; /* request length for mnt statistics */
+ ulong replen; /* reply length for mnt statistics */
+ Mntrpc* flushed; /* message this one flushes */
+};
+
+enum
+{
+ TAGSHIFT = 5, /* ulong has to be 32 bits */
+ TAGMASK = (1<<TAGSHIFT)-1,
+ NMASK = (64*1024)>>TAGSHIFT,
+};
+
+struct Mntalloc
+{
+ Lock;
+ Mnt* list; /* Mount devices in use */
+ Mnt* mntfree; /* Free list */
+ Mntrpc* rpcfree;
+ int nrpcfree;
+ int nrpcused;
+ ulong id;
+ ulong tagmask[NMASK];
+}mntalloc;
+
+void mattach(Mnt*, Chan*, char*);
+Mnt* mntchk(Chan*);
+void mntdirfix(uchar*, Chan*);
+Mntrpc* mntflushalloc(Mntrpc*, ulong);
+void mntflushfree(Mnt*, Mntrpc*);
+void mntfree(Mntrpc*);
+void mntgate(Mnt*);
+void mntpntfree(Mnt*);
+void mntqrm(Mnt*, Mntrpc*);
+Mntrpc* mntralloc(Chan*, ulong);
+long mntrdwr(int, Chan*, void*, long, vlong);
+int mntrpcread(Mnt*, Mntrpc*);
+void mountio(Mnt*, Mntrpc*);
+void mountmux(Mnt*, Mntrpc*);
+void mountrpc(Mnt*, Mntrpc*);
+int rpcattn(void*);
+Chan* mntchan(void);
+
+char Esbadstat[] = "invalid directory entry received from server";
+char Enoversion[] = "version not established for mount channel";
+
+
+void (*mntstats)(int, Chan*, uvlong, ulong);
+
+static void
+mntreset(void)
+{
+ mntalloc.id = 1;
+ mntalloc.tagmask[0] = 1; /* don't allow 0 as a tag */
+ mntalloc.tagmask[NMASK-1] = 0x80000000UL; /* don't allow NOTAG */
+ fmtinstall('F', fcallfmt);
+/* fmtinstall('D', dirfmt); */
+/* fmtinstall('M', dirmodefmt); */
+
+ cinit();
+}
+
+/*
+ * Version is not multiplexed: message sent only once per connection.
+ */
+long
+mntversion(Chan *c, char *version, int msize, int returnlen)
+{
+ Fcall f;
+ uchar *msg;
+ Mnt *m;
+ char *v;
+ long k, l;
+ uvlong oo;
+ char buf[128];
+
+ qlock(&c->umqlock); /* make sure no one else does this until we've established ourselves */
+ if(waserror()){
+ qunlock(&c->umqlock);
+ nexterror();
+ }
+
+ /* defaults */
+ if(msize == 0)
+ msize = MAXRPC;
+ if(msize > c->iounit && c->iounit != 0)
+ msize = c->iounit;
+ v = version;
+ if(v == nil || v[0] == '\0')
+ v = VERSION9P;
+
+ /* validity */
+ if(msize < 0)
+ error("bad iounit in version call");
+ if(strncmp(v, VERSION9P, strlen(VERSION9P)) != 0)
+ error("bad 9P version specification");
+
+ m = c->mux;
+
+ if(m != nil){
+ qunlock(&c->umqlock);
+ poperror();
+
+ strecpy(buf, buf+sizeof buf, m->version);
+ k = strlen(buf);
+ if(strncmp(buf, v, k) != 0){
+ snprint(buf, sizeof buf, "incompatible 9P versions %s %s", m->version, v);
+ error(buf);
+ }
+ if(returnlen > 0){
+ if(returnlen < k)
+ error(Eshort);
+ memmove(version, buf, k);
+ }
+ return k;
+ }
+
+ f.type = Tversion;
+ f.tag = NOTAG;
+ f.msize = msize;
+ f.version = v;
+ msg = malloc(8192+IOHDRSZ);
+ if(msg == nil)
+ exhausted("version memory");
+ if(waserror()){
+ free(msg);
+ nexterror();
+ }
+ k = convS2M(&f, msg, 8192+IOHDRSZ);
+ if(k == 0)
+ error("bad fversion conversion on send");
+
+ lock(c);
+ oo = c->offset;
+ c->offset += k;
+ unlock(c);
+
+ l = devtab[c->type]->write(c, msg, k, oo);
+
+ if(l < k){
+ lock(c);
+ c->offset -= k - l;
+ unlock(c);
+ error("short write in fversion");
+ }
+
+ /* message sent; receive and decode reply */
+ k = devtab[c->type]->read(c, msg, 8192+IOHDRSZ, c->offset);
+ if(k <= 0)
+ error("EOF receiving fversion reply");
+
+ lock(c);
+ c->offset += k;
+ unlock(c);
+
+ l = convM2S(msg, k, &f);
+ if(l != k)
+ error("bad fversion conversion on reply");
+ if(f.type != Rversion){
+ if(f.type == Rerror)
+ error(f.ename);
+ error("unexpected reply type in fversion");
+ }
+ if(f.msize > msize)
+ error("server tries to increase msize in fversion");
+ if(f.msize<256 || f.msize>1024*1024)
+ error("nonsense value of msize in fversion");
+ if(strncmp(f.version, v, strlen(f.version)) != 0)
+ error("bad 9P version returned from server");
+
+ /* now build Mnt associated with this connection */
+ lock(&mntalloc);
+ m = mntalloc.mntfree;
+ if(m != 0)
+ mntalloc.mntfree = m->list;
+ else {
+ m = malloc(sizeof(Mnt));
+ if(m == 0) {
+ unlock(&mntalloc);
+ exhausted("mount devices");
+ }
+ }
+ m->list = mntalloc.list;
+ mntalloc.list = m;
+ m->version = nil;
+ kstrdup(&m->version, f.version);
+ m->id = mntalloc.id++;
+ m->q = qopen(10*MAXRPC, 0, nil, nil);
+ m->msize = f.msize;
+ unlock(&mntalloc);
+
+ poperror(); /* msg */
+ free(msg);
+
+ lock(m);
+ m->queue = 0;
+ m->rip = 0;
+
+ c->flag |= CMSG;
+ c->mux = m;
+ m->c = c;
+ unlock(m);
+
+ poperror(); /* c */
+ qunlock(&c->umqlock);
+
+ k = strlen(f.version);
+ if(returnlen > 0){
+ if(returnlen < k)
+ error(Eshort);
+ memmove(version, f.version, k);
+ }
+
+ return k;
+}
+
+Chan*
+mntauth(Chan *c, char *spec)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = c->mux;
+
+ if(m == nil){
+ mntversion(c, VERSION9P, MAXRPC, 0);
+ m = c->mux;
+ if(m == nil)
+ error(Enoversion);
+ }
+
+ c = mntchan();
+ if(waserror()) {
+ /* Close must not be called since it will
+ * call mnt recursively
+ */
+ chanfree(c);
+ nexterror();
+ }
+
+ r = mntralloc(0, m->msize);
+
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+
+ r->request.type = Tauth;
+ r->request.afid = c->fid;
+ r->request.uname = up->env->user;
+ r->request.aname = spec;
+ mountrpc(m, r);
+
+ c->qid = r->reply.aqid;
+ c->mchan = m->c;
+ incref(m->c);
+ c->mqid = c->qid;
+ c->mode = ORDWR;
+
+ poperror(); /* r */
+ mntfree(r);
+
+ poperror(); /* c */
+
+ return c;
+
+}
+
+static Chan*
+mntattach(char *muxattach)
+{
+ Mnt *m;
+ Chan *c;
+ Mntrpc *r;
+ struct bogus{
+ Chan *chan;
+ Chan *authchan;
+ char *spec;
+ int flags;
+ }bogus;
+
+ bogus = *((struct bogus *)muxattach);
+ c = bogus.chan;
+
+ m = c->mux;
+
+ if(m == nil){
+ mntversion(c, nil, 0, 0);
+ m = c->mux;
+ if(m == nil)
+ error(Enoversion);
+ }
+
+ c = mntchan();
+ if(waserror()) {
+ /* Close must not be called since it will
+ * call mnt recursively
+ */
+ chanfree(c);
+ nexterror();
+ }
+
+ r = mntralloc(0, m->msize);
+
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+
+ r->request.type = Tattach;
+ r->request.fid = c->fid;
+ if(bogus.authchan == nil)
+ r->request.afid = NOFID;
+ else
+ r->request.afid = bogus.authchan->fid;
+ r->request.uname = up->env->user;
+ r->request.aname = bogus.spec;
+ mountrpc(m, r);
+
+ c->qid = r->reply.qid;
+ c->mchan = m->c;
+ incref(m->c);
+ c->mqid = c->qid;
+
+ poperror(); /* r */
+ mntfree(r);
+
+ poperror(); /* c */
+
+ if(bogus.flags&MCACHE)
+ c->flag |= CCACHE;
+ return c;
+}
+
+Chan*
+mntchan(void)
+{
+ Chan *c;
+
+ c = devattach('M', 0);
+ lock(&mntalloc);
+ c->dev = mntalloc.id++;
+ unlock(&mntalloc);
+
+ if(c->mchan)
+ panic("mntchan non-zero %p", c->mchan);
+ return c;
+}
+
+static Walkqid*
+mntwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ int i, alloc;
+ Mnt *m;
+ Mntrpc *r;
+ Walkqid *wq;
+
+ if(nc != nil)
+ print("mntwalk: nc != nil\n");
+ if(nname > MAXWELEM)
+ error("devmnt: too many name elements");
+ alloc = 0;
+ wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ if(waserror()){
+ if(alloc && wq->clone!=nil)
+ cclose(wq->clone);
+ free(wq);
+ return nil;
+ }
+
+ alloc = 0;
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(nc == nil){
+ nc = devclone(c);
+ /*
+ * Until the other side accepts this fid, we can't mntclose it.
+ * Therefore set type to 0 for now; rootclose is known to be safe.
+ */
+ nc->type = 0;
+ alloc = 1;
+ }
+ wq->clone = nc;
+
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = Twalk;
+ r->request.fid = c->fid;
+ r->request.newfid = nc->fid;
+ r->request.nwname = nname;
+ memmove(r->request.wname, name, nname*sizeof(char*));
+
+ mountrpc(m, r);
+
+ if(r->reply.nwqid > nname)
+ error("too many QIDs returned by walk");
+ if(r->reply.nwqid < nname){
+ if(alloc)
+ cclose(nc);
+ wq->clone = nil;
+ if(r->reply.nwqid == 0){
+ free(wq);
+ wq = nil;
+ goto Return;
+ }
+ }
+
+ /* move new fid onto mnt device and update its qid */
+ if(wq->clone != nil){
+ if(wq->clone != c){
+ wq->clone->type = c->type;
+ wq->clone->mchan = c->mchan;
+ incref(c->mchan);
+ }
+ if(r->reply.nwqid > 0)
+ wq->clone->qid = r->reply.wqid[r->reply.nwqid-1];
+ }
+ wq->nqid = r->reply.nwqid;
+ for(i=0; i<wq->nqid; i++)
+ wq->qid[i] = r->reply.wqid[i];
+
+ Return:
+ poperror();
+ mntfree(r);
+ poperror();
+ return wq;
+}
+
+static int
+mntstat(Chan *c, uchar *dp, int n)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ if(n < BIT16SZ)
+ error(Eshortstat);
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = Tstat;
+ r->request.fid = c->fid;
+ mountrpc(m, r);
+
+ if(r->reply.nstat > n){
+ /* doesn't fit; just patch the count and return */
+ PBIT16((uchar*)dp, r->reply.nstat);
+ n = BIT16SZ;
+ }else{
+ n = r->reply.nstat;
+ memmove(dp, r->reply.stat, n);
+ validstat(dp, n);
+ mntdirfix(dp, c);
+ }
+ poperror();
+ mntfree(r);
+ return n;
+}
+
+static Chan*
+mntopencreate(int type, Chan *c, char *name, int omode, ulong perm)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = type;
+ r->request.fid = c->fid;
+ r->request.mode = omode;
+ if(type == Tcreate){
+ r->request.perm = perm;
+ r->request.name = name;
+ }
+ mountrpc(m, r);
+
+ c->qid = r->reply.qid;
+ c->offset = 0;
+ c->mode = openmode(omode);
+ c->iounit = r->reply.iounit;
+ if(c->iounit == 0 || c->iounit > m->msize-IOHDRSZ)
+ c->iounit = m->msize-IOHDRSZ;
+ c->flag |= COPEN;
+ poperror();
+ mntfree(r);
+
+ if(c->flag & CCACHE)
+ copen(c);
+
+ return c;
+}
+
+static Chan*
+mntopen(Chan *c, int omode)
+{
+ return mntopencreate(Topen, c, nil, omode, 0);
+}
+
+static void
+mntcreate(Chan *c, char *name, int omode, ulong perm)
+{
+ mntopencreate(Tcreate, c, name, omode, perm);
+}
+
+static void
+mntclunk(Chan *c, int t)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()){
+ mntfree(r);
+ nexterror();
+ }
+
+ r->request.type = t;
+ r->request.fid = c->fid;
+ mountrpc(m, r);
+ mntfree(r);
+ poperror();
+}
+
+void
+muxclose(Mnt *m)
+{
+ Mntrpc *q, *r;
+
+ for(q = m->queue; q; q = r) {
+ r = q->list;
+ mntfree(q);
+ }
+ m->id = 0;
+ free(m->version);
+ m->version = nil;
+ mntpntfree(m);
+}
+
+void
+mntpntfree(Mnt *m)
+{
+ Mnt *f, **l;
+ Queue *q;
+
+ lock(&mntalloc);
+ l = &mntalloc.list;
+ for(f = *l; f; f = f->list) {
+ if(f == m) {
+ *l = m->list;
+ break;
+ }
+ l = &f->list;
+ }
+ m->list = mntalloc.mntfree;
+ mntalloc.mntfree = m;
+ q = m->q;
+ unlock(&mntalloc);
+
+ qfree(q);
+}
+
+static void
+mntclose(Chan *c)
+{
+ mntclunk(c, Tclunk);
+}
+
+static void
+mntremove(Chan *c)
+{
+ mntclunk(c, Tremove);
+}
+
+static int
+mntwstat(Chan *c, uchar *dp, int n)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = Twstat;
+ r->request.fid = c->fid;
+ r->request.nstat = n;
+ r->request.stat = dp;
+ mountrpc(m, r);
+ poperror();
+ mntfree(r);
+ return n;
+}
+
+static long
+mntread(Chan *c, void *buf, long n, vlong off)
+{
+ uchar *p, *e;
+ int nc, cache, isdir, dirlen;
+
+ isdir = 0;
+ cache = c->flag & CCACHE;
+ if(c->qid.type & QTDIR) {
+ cache = 0;
+ isdir = 1;
+ }
+
+ p = buf;
+ if(cache) {
+ nc = cread(c, buf, n, off);
+ if(nc > 0) {
+ n -= nc;
+ if(n == 0)
+ return nc;
+ p += nc;
+ off += nc;
+ }
+ n = mntrdwr(Tread, c, p, n, off);
+ cupdate(c, p, n, off);
+ return n + nc;
+ }
+
+ n = mntrdwr(Tread, c, buf, n, off);
+ if(isdir) {
+ for(e = &p[n]; p+BIT16SZ < e; p += dirlen){
+ dirlen = BIT16SZ+GBIT16(p);
+ if(p+dirlen > e)
+ break;
+ validstat(p, dirlen);
+ mntdirfix(p, c);
+ }
+ if(p != e)
+ error(Esbadstat);
+ }
+ return n;
+}
+
+static long
+mntwrite(Chan *c, void *buf, long n, vlong off)
+{
+ return mntrdwr(Twrite, c, buf, n, off);
+}
+
+long
+mntrdwr(int type, Chan *c, void *buf, long n, vlong off)
+{
+ Mnt *m;
+ Mntrpc *r;
+ char *uba;
+ int cache;
+ ulong cnt, nr, nreq;
+
+ m = mntchk(c);
+ uba = buf;
+ cnt = 0;
+ cache = c->flag & CCACHE;
+ if(c->qid.type & QTDIR)
+ cache = 0;
+ for(;;) {
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = type;
+ r->request.fid = c->fid;
+ r->request.offset = off;
+ r->request.data = uba;
+ nr = n;
+ if(nr > m->msize-IOHDRSZ)
+ nr = m->msize-IOHDRSZ;
+ r->request.count = nr;
+ mountrpc(m, r);
+ nreq = r->request.count;
+ nr = r->reply.count;
+ if(nr > nreq)
+ nr = nreq;
+
+ if(type == Tread)
+ r->b = bl2mem((uchar*)uba, r->b, nr);
+ else if(cache)
+ cwrite(c, (uchar*)uba, nr, off);
+
+ poperror();
+ mntfree(r);
+ off += nr;
+ uba += nr;
+ cnt += nr;
+ n -= nr;
+ if(nr != nreq || n == 0 || up->killed)
+ break;
+ }
+ return cnt;
+}
+
+void
+mountrpc(Mnt *m, Mntrpc *r)
+{
+ char *sn, *cn;
+ int t;
+
+ r->reply.tag = 0;
+ r->reply.type = Tmax; /* can't ever be a valid message type */
+
+ mountio(m, r);
+
+ t = r->reply.type;
+ switch(t) {
+ case Rerror:
+ error(r->reply.ename);
+ case Rflush:
+ error(Eintr);
+ default:
+ if(t == r->request.type+1)
+ break;
+ sn = "?";
+ if(m->c->name != nil)
+ sn = m->c->name->s;
+ cn = "?";
+ if(r->c != nil && r->c->name != nil)
+ cn = r->c->name->s;
+ print("mnt: proc %s %lud: mismatch from %s %s rep 0x%lux tag %d fid %d T%d R%d rp %d\n",
+ up->text, up->pid, sn, cn,
+ r, r->request.tag, r->request.fid, r->request.type,
+ r->reply.type, r->reply.tag);
+ error(Emountrpc);
+ }
+}
+
+void
+mountio(Mnt *m, Mntrpc *r)
+{
+ int n;
+
+ while(waserror()) {
+ if(m->rip == up)
+ mntgate(m);
+ if(strcmp(up->env->errstr, Eintr) != 0){
+ mntflushfree(m, r);
+ nexterror();
+ }
+ r = mntflushalloc(r, m->msize);
+ }
+
+ lock(m);
+ r->m = m;
+ r->list = m->queue;
+ m->queue = r;
+ unlock(m);
+
+ /* Transmit a file system rpc */
+ if(m->msize == 0)
+ panic("msize");
+ n = convS2M(&r->request, r->rpc, m->msize);
+ if(n < 0)
+ panic("bad message type in mountio");
+ if(devtab[m->c->type]->write(m->c, r->rpc, n, 0) != n)
+ error(Emountrpc);
+/* r->stime = fastticks(nil); */
+ r->reqlen = n;
+
+ /* Gate readers onto the mount point one at a time */
+ for(;;) {
+ lock(m);
+ if(m->rip == 0)
+ break;
+ unlock(m);
+ sleep(&r->r, rpcattn, r);
+ if(r->done){
+ poperror();
+ mntflushfree(m, r);
+ return;
+ }
+ }
+ m->rip = up;
+ unlock(m);
+ while(r->done == 0) {
+ if(mntrpcread(m, r) < 0)
+ error(Emountrpc);
+ mountmux(m, r);
+ }
+ mntgate(m);
+ poperror();
+ mntflushfree(m, r);
+}
+
+static int
+doread(Mnt *m, int len)
+{
+ Block *b;
+
+ while(qlen(m->q) < len){
+ b = devtab[m->c->type]->bread(m->c, m->msize, 0);
+ if(b == nil)
+ return -1;
+ if(blocklen(b) == 0){
+ freeblist(b);
+ return -1;
+ }
+ qaddlist(m->q, b);
+ }
+ return 0;
+}
+
+int
+mntrpcread(Mnt *m, Mntrpc *r)
+{
+ int i, t, len, hlen;
+ Block *b, **l, *nb;
+
+ r->reply.type = 0;
+ r->reply.tag = 0;
+
+ /* read at least length, type, and tag and pullup to a single block */
+ if(doread(m, BIT32SZ+BIT8SZ+BIT16SZ) < 0)
+ return -1;
+ nb = pullupqueue(m->q, BIT32SZ+BIT8SZ+BIT16SZ);
+
+ /* read in the rest of the message, avoid ridiculous (for now) message sizes */
+ len = GBIT32(nb->rp);
+ if(len > m->msize){
+ qdiscard(m->q, qlen(m->q));
+ return -1;
+ }
+ if(doread(m, len) < 0)
+ return -1;
+
+ /* pullup the header (i.e. everything except data) */
+ t = nb->rp[BIT32SZ];
+ switch(t){
+ case Rread:
+ hlen = BIT32SZ+BIT8SZ+BIT16SZ+BIT32SZ;
+ break;
+ default:
+ hlen = len;
+ break;
+ }
+ nb = pullupqueue(m->q, hlen);
+
+ if(convM2S(nb->rp, len, &r->reply) <= 0){
+ /* bad message, dump it */
+ print("mntrpcread: convM2S failed\n");
+ qdiscard(m->q, len);
+ return -1;
+ }
+
+ /* hang the data off of the fcall struct */
+ l = &r->b;
+ *l = nil;
+ do {
+ b = qremove(m->q);
+ if(hlen > 0){
+ b->rp += hlen;
+ len -= hlen;
+ hlen = 0;
+ }
+ i = BLEN(b);
+ if(i <= len){
+ len -= i;
+ *l = b;
+ l = &(b->next);
+ } else {
+ /* split block and put unused bit back */
+ nb = allocb(i-len);
+ memmove(nb->wp, b->rp+len, i-len);
+ b->wp = b->rp+len;
+ nb->wp += i-len;
+ qputback(m->q, nb);
+ *l = b;
+ return 0;
+ }
+ }while(len > 0);
+
+ return 0;
+}
+
+void
+mntgate(Mnt *m)
+{
+ Mntrpc *q;
+
+ lock(m);
+ m->rip = 0;
+ for(q = m->queue; q; q = q->list) {
+ if(q->done == 0)
+ if(wakeup(&q->r))
+ break;
+ }
+ unlock(m);
+}
+
+void
+mountmux(Mnt *m, Mntrpc *r)
+{
+ Mntrpc **l, *q;
+
+ lock(m);
+ l = &m->queue;
+ for(q = *l; q; q = q->list) {
+ /* look for a reply to a message */
+ if(q->request.tag == r->reply.tag) {
+ *l = q->list;
+ if(q != r) {
+ /*
+ * Completed someone else.
+ * Trade pointers to receive buffer.
+ */
+ q->reply = r->reply;
+ q->b = r->b;
+ r->b = nil;
+ }
+ q->done = 1;
+ unlock(m);
+ if(mntstats != nil)
+ (*mntstats)(q->request.type,
+ m->c, q->stime,
+ q->reqlen + r->replen);
+ if(q != r)
+ wakeup(&q->r);
+ return;
+ }
+ l = &q->list;
+ }
+ unlock(m);
+ print("unexpected reply tag %ud; type %d\n", r->reply.tag, r->reply.type);
+}
+
+/*
+ * Create a new flush request and chain the previous
+ * requests from it
+ */
+Mntrpc*
+mntflushalloc(Mntrpc *r, ulong iounit)
+{
+ Mntrpc *fr;
+
+ fr = mntralloc(0, iounit);
+
+ fr->request.type = Tflush;
+ if(r->request.type == Tflush)
+ fr->request.oldtag = r->request.oldtag;
+ else
+ fr->request.oldtag = r->request.tag;
+ fr->flushed = r;
+
+ return fr;
+}
+
+/*
+ * Free a chain of flushes. Remove each unanswered
+ * flush and the original message from the unanswered
+ * request queue. Mark the original message as done
+ * and if it hasn't been answered set the reply to to
+ * Rflush.
+ */
+void
+mntflushfree(Mnt *m, Mntrpc *r)
+{
+ Mntrpc *fr;
+
+ while(r){
+ fr = r->flushed;
+ if(!r->done){
+ r->reply.type = Rflush;
+ mntqrm(m, r);
+ }
+ if(fr)
+ mntfree(r);
+ r = fr;
+ }
+}
+
+static int
+alloctag(void)
+{
+ int i, j;
+ ulong v;
+
+ for(i = 0; i < NMASK; i++){
+ v = mntalloc.tagmask[i];
+ if(v == ~0UL)
+ continue;
+ for(j = 0; j < 1<<TAGSHIFT; j++)
+ if((v & (1<<j)) == 0){
+ mntalloc.tagmask[i] |= 1<<j;
+ return (i<<TAGSHIFT) + j;
+ }
+ }
+ /* panic("no devmnt tags left"); */
+ return NOTAG;
+}
+
+static void
+freetag(int t)
+{
+ mntalloc.tagmask[t>>TAGSHIFT] &= ~(1<<(t&TAGMASK));
+}
+
+Mntrpc*
+mntralloc(Chan *c, ulong msize)
+{
+ Mntrpc *new;
+
+ lock(&mntalloc);
+ new = mntalloc.rpcfree;
+ if(new == nil){
+ new = malloc(sizeof(Mntrpc));
+ if(new == nil) {
+ unlock(&mntalloc);
+ exhausted("mount rpc header");
+ }
+ /*
+ * The header is split from the data buffer as
+ * mountmux may swap the buffer with another header.
+ */
+ new->rpc = mallocz(msize, 0);
+ if(new->rpc == nil){
+ free(new);
+ unlock(&mntalloc);
+ exhausted("mount rpc buffer");
+ }
+ new->rpclen = msize;
+ new->request.tag = alloctag();
+ if(new->request.tag == NOTAG){
+ free(new);
+ unlock(&mntalloc);
+ exhausted("rpc tags");
+ }
+ }
+ else {
+ mntalloc.rpcfree = new->list;
+ mntalloc.nrpcfree--;
+ if(new->rpclen < msize){
+ free(new->rpc);
+ new->rpc = mallocz(msize, 0);
+ if(new->rpc == nil){
+ free(new);
+ mntalloc.nrpcused--;
+ unlock(&mntalloc);
+ exhausted("mount rpc buffer");
+ }
+ new->rpclen = msize;
+ }
+ }
+ mntalloc.nrpcused++;
+ unlock(&mntalloc);
+ new->c = c;
+ new->done = 0;
+ new->flushed = nil;
+ new->b = nil;
+ return new;
+}
+
+void
+mntfree(Mntrpc *r)
+{
+ if(r->b != nil)
+ freeblist(r->b);
+ lock(&mntalloc);
+ if(mntalloc.nrpcfree >= 10){
+ free(r->rpc);
+ free(r);
+ freetag(r->request.tag);
+ }
+ else{
+ r->list = mntalloc.rpcfree;
+ mntalloc.rpcfree = r;
+ mntalloc.nrpcfree++;
+ }
+ mntalloc.nrpcused--;
+ unlock(&mntalloc);
+}
+
+void
+mntqrm(Mnt *m, Mntrpc *r)
+{
+ Mntrpc **l, *f;
+
+ lock(m);
+ r->done = 1;
+
+ l = &m->queue;
+ for(f = *l; f; f = f->list) {
+ if(f == r) {
+ *l = r->list;
+ break;
+ }
+ l = &f->list;
+ }
+ unlock(m);
+}
+
+Mnt*
+mntchk(Chan *c)
+{
+ Mnt *m;
+
+ /* This routine is mostly vestiges of prior lives; now it's just sanity checking */
+
+ if(c->mchan == nil)
+ panic("mntchk 1: nil mchan c %s\n", channame(c));
+
+ m = c->mchan->mux;
+
+ if(m == nil)
+ print("mntchk 2: nil mux c %s c->mchan %s \n", channame(c), channame(c->mchan));
+
+ /*
+ * Was it closed and reused (was error(Eshutdown); now, it can't happen)
+ */
+ if(m->id == 0 || m->id >= c->dev)
+ panic("mntchk 3: can't happen");
+
+ return m;
+}
+
+/*
+ * Rewrite channel type and dev for in-flight data to
+ * reflect local values. These entries are known to be
+ * the first two in the Dir encoding after the count.
+ */
+void
+mntdirfix(uchar *dirbuf, Chan *c)
+{
+ uint r;
+
+ r = devtab[c->type]->dc;
+ dirbuf += BIT16SZ; /* skip count */
+ PBIT16(dirbuf, r);
+ dirbuf += BIT16SZ;
+ PBIT32(dirbuf, c->dev);
+}
+
+int
+rpcattn(void *v)
+{
+ Mntrpc *r;
+
+ r = v;
+ return r->done || r->m->rip == 0;
+}
+
+Dev mntdevtab = {
+ 'M',
+ "mnt",
+
+ mntreset,
+ devinit,
+ devshutdown,
+ mntattach,
+ mntwalk,
+ mntstat,
+ mntopen,
+ mntcreate,
+ mntclose,
+ mntread,
+ devbread,
+ mntwrite,
+ devbwrite,
+ mntremove,
+ mntwstat,
+};
diff --git a/os/port/devns16552.c b/os/port/devns16552.c
new file mode 100644
index 00000000..b260eec8
--- /dev/null
+++ b/os/port/devns16552.c
@@ -0,0 +1,1090 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "../port/netif.h"
+
+/*
+ * Driver for the ns16552.
+ */
+enum
+{
+ /*
+ * register numbers
+ */
+ Data= 0, /* xmit/rcv buffer */
+ Iena= 1, /* interrupt enable */
+ Ircv= (1<<0), /* for char rcv'd */
+ Ixmt= (1<<1), /* for xmit buffer empty */
+ Irstat=(1<<2), /* for change in rcv'er status */
+ Imstat=(1<<3), /* for change in modem status */
+ Istat= 2, /* interrupt flag (read) */
+ Ipend= 1, /* interrupt pending (not) */
+ Fenabd=(3<<6), /* on if fifo's enabled */
+ Fifoctl=2, /* fifo control (write) */
+ Fena= (1<<0), /* enable xmit/rcv fifos */
+ Ftrig= (1<<6), /* trigger after 4 input characters */
+ Fclear=(3<<1), /* clear xmit & rcv fifos */
+ Format= 3, /* byte format */
+ Bits8= (3<<0), /* 8 bits/byte */
+ Stop2= (1<<2), /* 2 stop bits */
+ Pena= (1<<3), /* generate parity */
+ Peven= (1<<4), /* even parity */
+ Pforce=(1<<5), /* force parity */
+ Break= (1<<6), /* generate a break */
+ Dra= (1<<7), /* address the divisor */
+ Mctl= 4, /* modem control */
+ Dtr= (1<<0), /* data terminal ready */
+ Rts= (1<<1), /* request to send */
+ Ri= (1<<2), /* ring */
+ Inton= (1<<3), /* turn on interrupts */
+ Loop= (1<<4), /* loop back */
+ Lstat= 5, /* line status */
+ Inready=(1<<0), /* receive buffer full */
+ Oerror=(1<<1), /* receiver overrun */
+ Perror=(1<<2), /* receiver parity error */
+ Ferror=(1<<3), /* rcv framing error */
+ Berror=(1<<4), /* break alarm */
+ Outready=(1<<5), /* output buffer full */
+ Mstat= 6, /* modem status */
+ Ctsc= (1<<0), /* clear to send changed */
+ Dsrc= (1<<1), /* data set ready changed */
+ Rire= (1<<2), /* rising edge of ring indicator */
+ Dcdc= (1<<3), /* data carrier detect changed */
+ Cts= (1<<4), /* complement of clear to send line */
+ Dsr= (1<<5), /* complement of data set ready line */
+ Ring= (1<<6), /* complement of ring indicator line */
+ Dcd= (1<<7), /* complement of data carrier detect line */
+ Scratch=7, /* scratchpad */
+ Dlsb= 0, /* divisor lsb */
+ Dmsb= 1, /* divisor msb */
+
+ CTLS= 023,
+ CTLQ= 021,
+
+ Stagesize= 1024,
+ Nuart= 32, /* max per machine */
+};
+
+typedef struct Uart Uart;
+struct Uart
+{
+ QLock;
+ int opens;
+
+ int enabled;
+ Uart *elist; /* next enabled interface */
+ char name[KNAMELEN];
+
+ uchar sticky[8]; /* sticky write register values */
+ ulong port;
+ ulong freq; /* clock frequency */
+ uchar mask; /* bits/char */
+ int dev;
+ int baud; /* baud rate */
+
+ uchar istat; /* last istat read */
+ int frame; /* framing errors */
+ int overrun; /* rcvr overruns */
+
+ /* buffers */
+ int (*putc)(Queue*, int);
+ Queue *iq;
+ Queue *oq;
+
+ Lock flock; /* fifo */
+ uchar fifoon; /* fifo's enabled */
+ uchar nofifo; /* earlier chip version with nofifo */
+
+ Lock rlock; /* receive */
+ uchar istage[Stagesize];
+ uchar *ip;
+ uchar *ie;
+
+ int haveinput;
+
+ Lock tlock; /* transmit */
+ uchar ostage[Stagesize];
+ uchar *op;
+ uchar *oe;
+
+ int modem; /* hardware flow control on */
+ int xonoff; /* software flow control on */
+ int blocked;
+ int cts, dsr, dcd; /* keep track of modem status */
+ int ctsbackoff;
+ int hup_dsr, hup_dcd; /* send hangup upstream? */
+ int dohup;
+
+ Rendez r;
+};
+
+static Uart *uart[Nuart];
+static int nuart;
+static Uart *consuart;
+
+struct Uartalloc {
+ Lock;
+ Uart *elist; /* list of enabled interfaces */
+} uartalloc;
+
+void ns16552intr(int);
+
+/*
+ * pick up architecture specific routines and definitions
+ */
+#include "ns16552.h"
+
+/*
+ * set the baud rate by calculating and setting the baudrate
+ * generator constant. This will work with fairly non-standard
+ * baud rates.
+ */
+static void
+ns16552setbaud(Uart *p, int rate)
+{
+ ulong brconst;
+
+ if(rate <= 0)
+ return;
+
+ brconst = (p->freq+8*rate-1)/(16*rate);
+
+ uartwrreg(p, Format, Dra);
+ outb(p->port + Dmsb, (brconst>>8) & 0xff);
+ outb(p->port + Dlsb, brconst & 0xff);
+ uartwrreg(p, Format, 0);
+
+ p->baud = rate;
+}
+
+/*
+ * decide if we should hangup when dsr or dcd drops.
+ */
+static void
+ns16552dsrhup(Uart *p, int n)
+{
+ p->hup_dsr = n;
+}
+
+static void
+ns16552dcdhup(Uart *p, int n)
+{
+ p->hup_dcd = n;
+}
+
+static void
+ns16552parity(Uart *p, char type)
+{
+ switch(type){
+ case 'e':
+ p->sticky[Format] |= Pena|Peven;
+ break;
+ case 'o':
+ p->sticky[Format] &= ~Peven;
+ p->sticky[Format] |= Pena;
+ break;
+ default:
+ p->sticky[Format] &= ~(Pena|Peven);
+ break;
+ }
+ uartwrreg(p, Format, 0);
+}
+
+/*
+ * set bits/character, default 8
+ */
+void
+ns16552bits(Uart *p, int bits)
+{
+ if(bits < 5 || bits > 8)
+ error(Ebadarg);
+
+ p->sticky[Format] &= ~3;
+ p->sticky[Format] |= bits-5;
+
+ uartwrreg(p, Format, 0);
+}
+
+
+/*
+ * toggle DTR
+ */
+void
+ns16552dtr(Uart *p, int n)
+{
+ if(n)
+ p->sticky[Mctl] |= Dtr;
+ else
+ p->sticky[Mctl] &= ~Dtr;
+
+ uartwrreg(p, Mctl, 0);
+}
+
+/*
+ * toggle RTS
+ */
+void
+ns16552rts(Uart *p, int n)
+{
+ if(n)
+ p->sticky[Mctl] |= Rts;
+ else
+ p->sticky[Mctl] &= ~Rts;
+
+ uartwrreg(p, Mctl, 0);
+}
+
+/*
+ * send break
+ */
+static void
+ns16552break(Uart *p, int ms)
+{
+ if(ms == 0)
+ ms = 200;
+
+ uartwrreg(p, Format, Break);
+ tsleep(&up->sleep, return0, 0, ms);
+ uartwrreg(p, Format, 0);
+}
+
+static void
+ns16552fifoon(Uart *p)
+{
+ ulong i, x;
+
+ if(p->nofifo || uartrdreg(p, Istat) & Fenabd)
+ return;
+
+ x = splhi();
+
+ /* reset fifos */
+ p->sticky[Fifoctl] = 0;
+ uartwrreg(p, Fifoctl, Fclear);
+
+ /* empty buffer and interrupt conditions */
+ for(i = 0; i < 16; i++){
+ if(uartrdreg(p, Istat)){
+ /* nothing to do */
+ }
+ if(uartrdreg(p, Data)){
+ /* nothing to do */
+ }
+ }
+
+ /* turn on fifo */
+ p->fifoon = 1;
+ p->sticky[Fifoctl] = Fena|Ftrig;
+ uartwrreg(p, Fifoctl, 0);
+ p->istat = uartrdreg(p, Istat);
+ if((p->istat & Fenabd) == 0) {
+ /* didn't work, must be an earlier chip type */
+ p->nofifo = 1;
+ }
+
+ splx(x);
+}
+
+/*
+ * modem flow control on/off (rts/cts)
+ */
+static void
+ns16552mflow(Uart *p, int n)
+{
+ ilock(&p->tlock);
+ if(n){
+ p->sticky[Iena] |= Imstat;
+ uartwrreg(p, Iena, 0);
+ p->modem = 1;
+ p->cts = uartrdreg(p, Mstat) & Cts;
+ } else {
+ p->sticky[Iena] &= ~Imstat;
+ uartwrreg(p, Iena, 0);
+ p->modem = 0;
+ p->cts = 1;
+ }
+ iunlock(&p->tlock);
+
+// ilock(&p->flock);
+// if(1)
+// /* turn on fifo's */
+// ns16552fifoon(p);
+// else {
+// /* turn off fifo's */
+// p->fifoon = 0;
+// p->sticky[Fifoctl] = 0;
+// uartwrreg(p, Fifoctl, Fclear);
+// }
+// iunlock(&p->flock);
+}
+
+/*
+ * turn on a port's interrupts. set DTR and RTS
+ */
+static void
+ns16552enable(Uart *p)
+{
+ Uart **l;
+
+ if(p->enabled)
+ return;
+
+ uartpower(p->dev, 1);
+
+ p->hup_dsr = p->hup_dcd = 0;
+ p->cts = p->dsr = p->dcd = 0;
+
+ /*
+ * turn on interrupts
+ */
+ p->sticky[Iena] = Ircv | Ixmt | Irstat;
+ uartwrreg(p, Iena, 0);
+
+ /*
+ * turn on DTR and RTS
+ */
+ ns16552dtr(p, 1);
+ ns16552rts(p, 1);
+
+ ns16552fifoon(p);
+
+ /*
+ * assume we can send
+ */
+ ilock(&p->tlock);
+ p->cts = 1;
+ p->blocked = 0;
+ iunlock(&p->tlock);
+
+ /*
+ * set baud rate to the last used
+ */
+ ns16552setbaud(p, p->baud);
+
+ lock(&uartalloc);
+ for(l = &uartalloc.elist; *l; l = &(*l)->elist){
+ if(*l == p)
+ break;
+ }
+ if(*l == 0){
+ p->elist = uartalloc.elist;
+ uartalloc.elist = p;
+ }
+ p->enabled = 1;
+ unlock(&uartalloc);
+}
+
+/*
+ * turn off a port's interrupts. reset DTR and RTS
+ */
+static void
+ns16552disable(Uart *p)
+{
+ Uart **l;
+
+ /*
+ * turn off interrupts
+ */
+ p->sticky[Iena] = 0;
+ uartwrreg(p, Iena, 0);
+
+ /*
+ * revert to default settings
+ */
+ p->sticky[Format] = Bits8;
+ uartwrreg(p, Format, 0);
+
+ /*
+ * turn off DTR, RTS, hardware flow control & fifo's
+ */
+ ns16552dtr(p, 0);
+ ns16552rts(p, 0);
+ ns16552mflow(p, 0);
+ ilock(&p->tlock);
+ p->xonoff = p->blocked = 0;
+ iunlock(&p->tlock);
+
+ uartpower(p->dev, 0);
+
+ lock(&uartalloc);
+ for(l = &uartalloc.elist; *l; l = &(*l)->elist){
+ if(*l == p){
+ *l = p->elist;
+ break;
+ }
+ }
+ p->enabled = 0;
+ unlock(&uartalloc);
+}
+
+/*
+ * put some bytes into the local queue to avoid calling
+ * qconsume for every character
+ */
+static int
+stageoutput(Uart *p)
+{
+ int n;
+
+ n = qconsume(p->oq, p->ostage, Stagesize);
+ if(n <= 0)
+ return 0;
+ p->op = p->ostage;
+ p->oe = p->ostage + n;
+ return n;
+}
+
+/*
+ * (re)start output
+ */
+static void
+ns16552kick0(Uart *p)
+{
+ int i;
+ if((p->modem && (p->cts == 0)) || p->blocked)
+ return;
+
+ /*
+ * 128 here is an arbitrary limit to make sure
+ * we don't stay in this loop too long. If the
+ * chips output queue is longer than 128, too
+ * bad -- presotto
+ */
+ for(i = 0; i < 128; i++){
+ if(!(uartrdreg(p, Lstat) & Outready))
+ break;
+ if(p->op >= p->oe && stageoutput(p) == 0)
+ break;
+ outb(p->port + Data, *(p->op++));
+ }
+}
+
+static void
+ns16552kick(void *v)
+{
+ Uart *p;
+
+ p = v;
+ ilock(&p->tlock);
+ ns16552kick0(p);
+ iunlock(&p->tlock);
+}
+
+/*
+ * restart input if it's off
+ */
+static void
+ns16552flow(void *v)
+{
+ Uart *p;
+
+ p = v;
+ if(p->modem)
+ ns16552rts(p, 1);
+ ilock(&p->rlock);
+ p->haveinput = 1;
+ iunlock(&p->rlock);
+}
+
+/*
+ * default is 9600 baud, 1 stop bit, 8 bit chars, no interrupts,
+ * transmit and receive enabled, interrupts disabled.
+ */
+static void
+ns16552setup0(Uart *p)
+{
+ memset(p->sticky, 0, sizeof(p->sticky));
+ /*
+ * set rate to 9600 baud.
+ * 8 bits/character.
+ * 1 stop bit.
+ * interrupts enabled.
+ */
+ p->sticky[Format] = Bits8;
+ uartwrreg(p, Format, 0);
+ p->sticky[Mctl] |= Inton;
+ uartwrreg(p, Mctl, 0x0);
+
+ ns16552setbaud(p, 9600);
+
+ p->iq = qopen(4*1024, 0, ns16552flow, p);
+ p->oq = qopen(4*1024, 0, ns16552kick, p);
+ if(p->iq == nil || p->oq == nil)
+ panic("ns16552setup0");
+
+ p->ip = p->istage;
+ p->ie = &p->istage[Stagesize];
+ p->op = p->ostage;
+ p->oe = p->ostage;
+}
+
+/*
+ * called by main() to create a new duart
+ */
+void
+ns16552setup(ulong port, ulong freq, char *name)
+{
+ Uart *p;
+
+ if(nuart >= Nuart)
+ return;
+
+ p = xalloc(sizeof(Uart));
+ uart[nuart] = p;
+ strcpy(p->name, name);
+ p->dev = nuart;
+ nuart++;
+ p->port = port;
+ p->freq = freq;
+ ns16552setup0(p);
+}
+
+/*
+ * called by main() to configure a duart port as a console or a mouse
+ */
+void
+ns16552special(int port, int baud, Queue **in, Queue **out, int (*putc)(Queue*, int))
+{
+ Uart *p = uart[port];
+ ns16552enable(p);
+ if(baud)
+ ns16552setbaud(p, baud);
+ p->putc = putc;
+ if(in)
+ *in = p->iq;
+ if(out)
+ *out = p->oq;
+ p->opens++;
+}
+
+/*
+ * handle an interrupt to a single uart
+ */
+void
+ns16552intr(int dev)
+{
+ uchar ch;
+ int s, l;
+ Uart *p = uart[dev];
+
+ for (s = uartrdreg(p, Istat); !(s&Ipend); s = uartrdreg(p, Istat)) {
+ switch(s&0x3f){
+ case 4: /* received data available */
+ case 6: /* receiver line status (alarm or error) */
+ case 12: /* character timeout indication */
+ while ((l = uartrdreg(p, Lstat)) & Inready) {
+ if(l & Ferror)
+ p->frame++;
+ if(l & Oerror)
+ p->overrun++;
+ ch = uartrdreg(p, Data) & 0xff;
+ if (l & (Berror|Perror|Ferror)) {
+ /* ch came with break, parity or framing error - consume */
+ continue;
+ }
+ if (ch == CTLS || ch == CTLQ) {
+ ilock(&p->tlock);
+ if(p->xonoff){
+ if(ch == CTLS)
+ p->blocked = 1;
+ else
+ p->blocked = 0; /* clock gets output going again */
+ }
+ iunlock(&p->tlock);
+ }
+ if(p->putc)
+ p->putc(p->iq, ch);
+ else {
+ ilock(&p->rlock);
+ if(p->ip < p->ie)
+ *p->ip++ = ch;
+ else
+ p->overrun++;
+ p->haveinput = 1;
+ iunlock(&p->rlock);
+ }
+ }
+ break;
+
+ case 2: /* transmitter not full */
+ ns16552kick(p);
+ break;
+
+ case 0: /* modem status */
+ ch = uartrdreg(p, Mstat);
+ if(ch & Ctsc){
+ ilock(&p->tlock);
+ l = p->cts;
+ p->cts = ch & Cts;
+ if(l == 0 && p->cts)
+ p->ctsbackoff = 2; /* clock gets output going again */
+ iunlock(&p->tlock);
+ }
+ if (ch & Dsrc) {
+ l = ch & Dsr;
+ if(p->hup_dsr && p->dsr && !l){
+ ilock(&p->rlock);
+ p->dohup = 1;
+ iunlock(&p->rlock);
+ }
+ p->dsr = l;
+ }
+ if (ch & Dcdc) {
+ l = ch & Dcd;
+ if(p->hup_dcd && p->dcd && !l){
+ ilock(&p->rlock);
+ p->dohup = 1;
+ iunlock(&p->rlock);
+ }
+ p->dcd = l;
+ }
+ break;
+
+ default:
+ print("weird uart interrupt #%2.2ux\n", s);
+ break;
+ }
+ }
+ p->istat = s;
+}
+
+/*
+ * we save up input characters till clock time
+ *
+ * There's also a bit of code to get a stalled print going.
+ * It shouldn't happen, but it does. Obviously I don't
+ * understand something. Since it was there, I bundled a
+ * restart after flow control with it to give some hysteresis
+ * to the hardware flow control. This makes compressing
+ * modems happier but will probably bother something else.
+ * -- presotto
+ */
+void
+uartclock(void)
+{
+ int n;
+ Uart *p;
+
+ for(p = uartalloc.elist; p; p = p->elist){
+
+ /* this amortizes cost of qproduce to many chars */
+ if(p->haveinput){
+ ilock(&p->rlock);
+ if(p->haveinput){
+ n = p->ip - p->istage;
+ if(n > 0 && p->iq){
+ if(n > Stagesize)
+ panic("uartclock");
+ if(qproduce(p->iq, p->istage, n) < 0)
+ ns16552rts(p, 0);
+ else
+ p->ip = p->istage;
+ }
+ p->haveinput = 0;
+ }
+ iunlock(&p->rlock);
+ }
+ if(p->dohup){
+ ilock(&p->rlock);
+ if(p->dohup){
+ qhangup(p->iq, 0);
+ qhangup(p->oq, 0);
+ }
+ p->dohup = 0;
+ iunlock(&p->rlock);
+ }
+
+ /* this adds hysteresis to hardware flow control */
+ if(p->ctsbackoff){
+ ilock(&p->tlock);
+ if(p->ctsbackoff){
+ if(--(p->ctsbackoff) == 0)
+ ns16552kick0(p);
+ }
+ iunlock(&p->tlock);
+ }
+ }
+}
+
+Dirtab *ns16552dir;
+int ndir;
+
+static void
+setlength(int i)
+{
+ Uart *p;
+
+ if(i > 0){
+ p = uart[i];
+ if(p && p->opens && p->iq)
+ ns16552dir[1+3*i].length = qlen(p->iq);
+ } else for(i = 0; i < nuart; i++){
+ p = uart[i];
+ if(p && p->opens && p->iq)
+ ns16552dir[1+3*i].length = qlen(p->iq);
+ }
+}
+
+/*
+ * all uarts must be ns16552setup() by this point or inside of ns16552install()
+ */
+static void
+ns16552reset(void)
+{
+ int i;
+ Dirtab *dp;
+ ns16552install(); /* architecture specific */
+
+ ndir = 1+3*nuart;
+ ns16552dir = xalloc(ndir * sizeof(Dirtab));
+ dp = ns16552dir;
+ strcpy(dp->name, ".");
+ mkqid(&dp->qid, 0, 0, QTDIR);
+ dp->length = 0;
+ dp->perm = DMDIR|0555;
+ dp++;
+ for(i = 0; i < nuart; i++){
+ /* 3 directory entries per port */
+ strcpy(dp->name, uart[i]->name);
+ dp->qid.path = NETQID(i, Ndataqid);
+ dp->perm = 0666;
+ dp++;
+ sprint(dp->name, "%sctl", uart[i]->name);
+ dp->qid.path = NETQID(i, Nctlqid);
+ dp->perm = 0666;
+ dp++;
+ sprint(dp->name, "%sstatus", uart[i]->name);
+ dp->qid.path = NETQID(i, Nstatqid);
+ dp->perm = 0444;
+ dp++;
+ }
+}
+
+static Chan*
+ns16552attach(char *spec)
+{
+ return devattach('t', spec);
+}
+
+static Walkqid*
+ns16552walk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, ns16552dir, ndir, devgen);
+}
+
+static int
+ns16552stat(Chan *c, uchar *dp, int n)
+{
+ if(NETTYPE(c->qid.path) == Ndataqid)
+ setlength(NETID(c->qid.path));
+ return devstat(c, dp, n, ns16552dir, ndir, devgen);
+}
+
+static Chan*
+ns16552open(Chan *c, int omode)
+{
+ Uart *p;
+
+ c = devopen(c, omode, ns16552dir, ndir, devgen);
+
+ switch(NETTYPE(c->qid.path)){
+ case Nctlqid:
+ case Ndataqid:
+ p = uart[NETID(c->qid.path)];
+ qlock(p);
+ if(p->opens++ == 0){
+ ns16552enable(p);
+ qreopen(p->iq);
+ qreopen(p->oq);
+ }
+ qunlock(p);
+ break;
+ }
+
+ return c;
+}
+
+static void
+ns16552close(Chan *c)
+{
+ Uart *p;
+
+ if(c->qid.type & QTDIR)
+ return;
+ if((c->flag & COPEN) == 0)
+ return;
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ case Nctlqid:
+ p = uart[NETID(c->qid.path)];
+ qlock(p);
+ if(--(p->opens) == 0){
+ ns16552disable(p);
+ qclose(p->iq);
+ qclose(p->oq);
+ p->ip = p->istage;
+ p->dcd = p->dsr = p->dohup = 0;
+ }
+ qunlock(p);
+ break;
+ }
+}
+
+static long
+uartstatus(Chan*, Uart *p, void *buf, long n, long offset)
+{
+ uchar mstat, fstat, istat, tstat;
+ char str[256];
+
+ str[0] = 0;
+ tstat = p->sticky[Mctl];
+ mstat = uartrdreg(p, Mstat);
+ istat = p->sticky[Iena];
+ fstat = p->sticky[Format];
+ snprint(str, sizeof str,
+ "b%d c%d d%d e%d l%d m%d p%c r%d s%d\n"
+ "%d %d %d%s%s%s%s%s\n",
+
+ p->baud,
+ p->hup_dcd,
+ (tstat & Dtr) != 0,
+ p->hup_dsr,
+ (fstat & Bits8) + 5,
+ (istat & Imstat) != 0,
+ (fstat & Pena) ? ((fstat & Peven) ? 'e' : 'o') : 'n',
+ (tstat & Rts) != 0,
+ (fstat & Stop2) ? 2 : 1,
+
+ p->dev,
+ p->frame,
+ p->overrun,
+ uartrdreg(p, Istat) & Fenabd ? " fifo" : "",
+ (mstat & Cts) ? " cts" : "",
+ (mstat & Dsr) ? " dsr" : "",
+ (mstat & Dcd) ? " dcd" : "",
+ (mstat & Ring) ? " ring" : ""
+ );
+ return readstr(offset, buf, n, str);
+}
+
+static long
+ns16552read(Chan *c, void *buf, long n, vlong off)
+{
+ Uart *p;
+ ulong offset = off;
+
+ if(c->qid.type & QTDIR){
+ setlength(-1);
+ return devdirread(c, buf, n, ns16552dir, ndir, devgen);
+ }
+
+ p = uart[NETID(c->qid.path)];
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ return qread(p->iq, buf, n);
+ case Nctlqid:
+ return readnum(offset, buf, n, NETID(c->qid.path), NUMSIZE);
+ case Nstatqid:
+ return uartstatus(c, p, buf, n, offset);
+ }
+
+ return 0;
+}
+
+static void
+ns16552ctl(Uart *p, char *cmd)
+{
+ int i, n;
+
+ /* let output drain for a while */
+ for(i = 0; i < 16 && qlen(p->oq); i++)
+ tsleep(&p->r, (int(*)(void*))qlen, p->oq, 125);
+
+ if(strncmp(cmd, "break", 5) == 0){
+ ns16552break(p, 0);
+ return;
+ }
+
+
+ n = atoi(cmd+1);
+ switch(*cmd){
+ case 'B':
+ case 'b':
+ ns16552setbaud(p, n);
+ break;
+ case 'C':
+ case 'c':
+ ns16552dcdhup(p, n);
+ break;
+ case 'D':
+ case 'd':
+ ns16552dtr(p, n);
+ break;
+ case 'E':
+ case 'e':
+ ns16552dsrhup(p, n);
+ break;
+ case 'f':
+ case 'F':
+ qflush(p->oq);
+ break;
+ case 'H':
+ case 'h':
+ qhangup(p->iq, 0);
+ qhangup(p->oq, 0);
+ break;
+ case 'L':
+ case 'l':
+ ns16552bits(p, n);
+ break;
+ case 'm':
+ case 'M':
+ ns16552mflow(p, n);
+ break;
+ case 'n':
+ case 'N':
+ qnoblock(p->oq, n);
+ break;
+ case 'P':
+ case 'p':
+ ns16552parity(p, *(cmd+1));
+ break;
+ case 'K':
+ case 'k':
+ ns16552break(p, n);
+ break;
+ case 'R':
+ case 'r':
+ ns16552rts(p, n);
+ break;
+ case 'Q':
+ case 'q':
+ qsetlimit(p->iq, n);
+ qsetlimit(p->oq, n);
+ break;
+ case 'W':
+ case 'w':
+ /* obsolete */
+ break;
+ case 'X':
+ case 'x':
+ ilock(&p->tlock);
+ p->xonoff = n;
+ iunlock(&p->tlock);
+ break;
+ }
+}
+
+static long
+ns16552write(Chan *c, void *buf, long n, vlong)
+{
+ Uart *p;
+ char cmd[32];
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+
+ p = uart[NETID(c->qid.path)];
+
+ /*
+ * The fifo's turn themselves off sometimes.
+ * It must be something I don't understand. -- presotto
+ */
+ lock(&p->flock);
+ if((p->istat & Fenabd) == 0 && p->fifoon && p->nofifo == 0)
+ ns16552fifoon(p);
+ unlock(&p->flock);
+
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ return qwrite(p->oq, buf, n);
+ case Nctlqid:
+ if(n >= sizeof(cmd))
+ n = sizeof(cmd)-1;
+ memmove(cmd, buf, n);
+ cmd[n] = 0;
+ ns16552ctl(p, cmd);
+ return n;
+ }
+}
+
+static int
+ns16552wstat(Chan *c, uchar *dp, int n)
+{
+ Dir d;
+ Dirtab *dt;
+
+ if(!iseve())
+ error(Eperm);
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+ if(NETTYPE(c->qid.path) == Nstatqid)
+ error(Eperm);
+
+ dt = &ns16552dir[1+3 * NETID(c->qid.path)];
+ n = convM2D(dp, n, &d, nil);
+ if(n == 0)
+ error(Eshortstat);
+ if(d.mode != ~0UL){
+ d.mode &= 0666;
+ dt[0].perm = dt[1].perm = d.mode;
+ }
+ return n;
+}
+
+Dev ns16552devtab = {
+ 't',
+ "ns16552",
+
+ ns16552reset,
+ devinit,
+ devshutdown,
+ ns16552attach,
+ ns16552walk,
+ ns16552stat,
+ ns16552open,
+ devcreate,
+ ns16552close,
+ ns16552read,
+ devbread,
+ ns16552write,
+ devbwrite,
+ devremove,
+ ns16552wstat,
+};
+
+void
+uartputc(int c)
+{
+ Uart *p;
+ int i;
+
+ p = consuart;
+ if(p == nil)
+ return;
+ for(i = 0; !(uartrdreg(p, Lstat)&Outready) && i < 128; i++)
+ delay(1);
+ outb(p->port+Data, c);
+ for(i = 0; !(uartrdreg(p, Lstat)&Outready) && i < 128; i++)
+ delay(1);
+}
+
+void
+uartputs(char *s, int n)
+{
+ char *e;
+
+ if(consuart == nil)
+ return;
+ e = s+n;
+ for(; s < e; s++){
+ if(*s == '\n')
+ uartputc('\r');
+ uartputc(*s);
+ }
+}
diff --git a/os/port/devpci.c b/os/port/devpci.c
new file mode 100644
index 00000000..90967e49
--- /dev/null
+++ b/os/port/devpci.c
@@ -0,0 +1,227 @@
+/*
+ * access to PCI configuration space (devpnp.c without PNP)
+ *
+ * TODO
+ * - extend PCI raw access to configuration space (writes, byte/short access?)
+ * - implement PCI access to memory/io space/BIOS ROM
+ * - use c->aux instead of performing lookup on each read/write?
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#define DPRINT if(0) print
+#define XPRINT if(1) print
+
+enum {
+ Qtopdir = 0,
+
+ Qpcidir,
+ Qpcictl,
+ Qpciraw,
+};
+
+#define TYPE(q) ((ulong)(q).path & 0x0F)
+#define QID(c, t) (((c)<<4)|(t))
+
+static Dirtab topdir[] = {
+ ".", { Qtopdir, 0, QTDIR }, 0, 0555,
+ "pci", { Qpcidir, 0, QTDIR }, 0, 0555,
+};
+
+extern Dev pcidevtab;
+
+static int
+pcigen2(Chan *c, int t, int tbdf, Dir *dp)
+{
+ Qid q;
+
+ q = (Qid){BUSBDF(tbdf)|t, 0, 0};
+ switch(t) {
+ case Qpcictl:
+ sprint(up->genbuf, "%d.%d.%dctl", BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf));
+ devdir(c, q, up->genbuf, 0, eve, 0444, dp);
+ return 1;
+ case Qpciraw:
+ sprint(up->genbuf, "%d.%d.%draw", BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf));
+ devdir(c, q, up->genbuf, 128, eve, 0444, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static int
+pcigen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
+{
+ Qid q;
+ Pcidev *p;
+ int tbdf;
+
+ switch(TYPE(c->qid)){
+ case Qtopdir:
+ if(s == DEVDOTDOT){
+ q = (Qid){QID(0, Qtopdir), 0, QTDIR};
+ sprint(up->genbuf, "#%C", pcidevtab.dc);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+ return devgen(c, nil, topdir, nelem(topdir), s, dp);
+ case Qpcidir:
+ if(s == DEVDOTDOT){
+ q = (Qid){QID(0, Qtopdir), 0, QTDIR};
+ sprint(up->genbuf, "#%C", pcidevtab.dc);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+ p = pcimatch(nil, 0, 0);
+ while(s >= 2 && p != nil) {
+ p = pcimatch(p, 0, 0);
+ s -= 2;
+ }
+ if(p == nil)
+ return -1;
+ return pcigen2(c, s+Qpcictl, p->tbdf, dp);
+ case Qpcictl:
+ case Qpciraw:
+ tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
+ p = pcimatchtbdf(tbdf);
+ if(p == nil)
+ return -1;
+ return pcigen2(c, TYPE(c->qid), tbdf, dp);
+ default:
+ break;
+ }
+ return -1;
+}
+
+static Chan*
+pciattach(char *spec)
+{
+ return devattach(pcidevtab.dc, spec);
+}
+
+Walkqid*
+pciwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, pcigen);
+}
+
+static int
+pcistat(Chan* c, uchar* dp, int n)
+{
+ return devstat(c, dp, n, nil, 0, pcigen);
+}
+
+static Chan*
+pciopen(Chan *c, int omode)
+{
+ return devopen(c, omode, nil, 0, pcigen);
+}
+
+static void
+pciclose(Chan*)
+{
+}
+
+static long
+pciread(Chan *c, void *va, long n, vlong offset)
+{
+ ulong x;
+ Pcidev *p;
+ char buf[256], *ebuf, *w;
+ char *a = va;
+ int i, tbdf, r;
+
+ switch(TYPE(c->qid)){
+ case Qtopdir:
+ case Qpcidir:
+ return devdirread(c, a, n, nil, 0, pcigen);
+ case Qpcictl:
+ tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
+ p = pcimatchtbdf(tbdf);
+ if(p == nil)
+ error(Egreg);
+ ebuf = buf+sizeof buf-1; /* -1 for newline */
+ w = seprint(buf, ebuf, "%.2x.%.2x.%.2x %.4x/%.4x %3d",
+ p->ccrb, p->ccru, p->ccrp, p->vid, p->did, p->intl);
+ for(i=0; i<nelem(p->mem); i++){
+ if(p->mem[i].size == 0)
+ continue;
+ w = seprint(w, ebuf, " %d:%.8lux %d", i, p->mem[i].bar, p->mem[i].size);
+ }
+ *w++ = '\n';
+ *w = '\0';
+ return readstr(offset, a, n, buf);
+ case Qpciraw:
+ tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
+ p = pcimatchtbdf(tbdf);
+ if(p == nil)
+ error(Egreg);
+ if(offset > 256)
+ return 0;
+ if(n+offset > 256)
+ n = 256-offset;
+ if(offset%4)
+ error(Ebadarg);
+ r = offset;
+ for(i = 0; i+4 <= n; i+=4) {
+ x = pcicfgr32(p, r);
+ a[0] = x;
+ a[1] = (x>>8);
+ a[2] = (x>>16);
+ a[3] = (x>>24);
+ a += 4;
+ r += 4;
+ }
+ return i;
+ default:
+ error(Egreg);
+ }
+ return n;
+}
+
+static long
+pciwrite(Chan *c, void *a, long n, vlong)
+{
+ char buf[256];
+
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ strncpy(buf, a, n);
+ buf[n] = 0;
+
+ switch(TYPE(c->qid)){
+ case Qpcictl:
+ case Qpciraw:
+ error(Eperm);
+ default:
+ error(Egreg);
+ }
+ return n;
+}
+
+
+Dev pcidevtab = {
+ '$',
+ "pci",
+
+ devreset,
+ devinit,
+ devshutdown,
+ pciattach,
+ pciwalk,
+ pcistat,
+ pciopen,
+ devcreate,
+ pciclose,
+ pciread,
+ devbread,
+ pciwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devpipe.c b/os/port/devpipe.c
new file mode 100644
index 00000000..2aa799aa
--- /dev/null
+++ b/os/port/devpipe.c
@@ -0,0 +1,464 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "interp.h"
+
+#define NETTYPE(x) ((ulong)(x)&0x1f)
+#define NETID(x) (((ulong)(x))>>5)
+#define NETQID(i,t) (((i)<<5)|(t))
+
+typedef struct Pipe Pipe;
+struct Pipe
+{
+ QLock;
+ Pipe* next;
+ int ref;
+ ulong path;
+ Queue* q[2];
+ int qref[2];
+ Dirtab *pipedir;
+ char* user;
+};
+
+static struct
+{
+ Lock;
+ ulong path;
+ int pipeqsize;
+} pipealloc;
+
+enum
+{
+ Qdir,
+ Qdata0,
+ Qdata1,
+};
+
+static
+Dirtab pipedir[] =
+{
+ ".", {Qdir,0,QTDIR}, 0, DMDIR|0500,
+ "data", {Qdata0}, 0, 0660,
+ "data1", {Qdata1}, 0, 0660,
+};
+
+static void
+freepipe(Pipe *p)
+{
+ if(p != nil){
+ free(p->user);
+ free(p->q[0]);
+ free(p->q[1]);
+ free(p->pipedir);
+ free(p);
+ }
+}
+
+static void
+pipeinit(void)
+{
+ pipealloc.pipeqsize = 32*1024;
+}
+
+/*
+ * create a pipe, no streams are created until an open
+ */
+static Chan*
+pipeattach(char *spec)
+{
+ Pipe *p;
+ Chan *c;
+
+ c = devattach('|', spec);
+ p = malloc(sizeof(Pipe));
+ if(p == 0)
+ error(Enomem);
+ if(waserror()){
+ freepipe(p);
+ nexterror();
+ }
+ p->pipedir = malloc(sizeof(pipedir));
+ if (p->pipedir == 0)
+ error(Enomem);
+ memmove(p->pipedir, pipedir, sizeof(pipedir));
+ kstrdup(&p->user, up->env->user);
+ p->ref = 1;
+
+ p->q[0] = qopen(pipealloc.pipeqsize, 0, 0, 0);
+ if(p->q[0] == 0)
+ error(Enomem);
+ p->q[1] = qopen(pipealloc.pipeqsize, 0, 0, 0);
+ if(p->q[1] == 0)
+ error(Enomem);
+ poperror();
+
+ lock(&pipealloc);
+ p->path = ++pipealloc.path;
+ unlock(&pipealloc);
+
+ c->qid.path = NETQID(2*p->path, Qdir);
+ c->qid.vers = 0;
+ c->qid.type = QTDIR;
+ c->aux = p;
+ c->dev = 0;
+ return c;
+}
+
+static int
+pipegen(Chan *c, char *, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ int id, len;
+ Qid qid;
+ Pipe *p;
+
+ if(i == DEVDOTDOT){
+ devdir(c, c->qid, "#|", 0, eve, 0555, dp);
+ return 1;
+ }
+ i++; /* skip . */
+ if(tab==0 || i>=ntab)
+ return -1;
+ tab += i;
+ p = c->aux;
+ switch(NETTYPE(tab->qid.path)){
+ case Qdata0:
+ len = qlen(p->q[0]);
+ break;
+ case Qdata1:
+ len = qlen(p->q[1]);
+ break;
+ default:
+ len = tab->length;
+ break;
+ }
+ id = NETID(c->qid.path);
+ qid.path = NETQID(id, tab->qid.path);
+ qid.vers = 0;
+ qid.type = QTFILE;
+ devdir(c, qid, tab->name, len, eve, tab->perm, dp);
+ return 1;
+}
+
+
+static Walkqid*
+pipewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+ Pipe *p;
+
+ p = c->aux;
+ wq = devwalk(c, nc, name, nname, p->pipedir, nelem(pipedir), pipegen);
+ if(wq != nil && wq->clone != nil && wq->clone != c){
+ qlock(p);
+ p->ref++;
+ if(c->flag & COPEN){
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ p->qref[0]++;
+ break;
+ case Qdata1:
+ p->qref[1]++;
+ break;
+ }
+ }
+ qunlock(p);
+ }
+ return wq;
+}
+
+static int
+pipestat(Chan *c, uchar *db, int n)
+{
+ Pipe *p;
+ Dir dir;
+ Dirtab *tab;
+
+ p = c->aux;
+ tab = p->pipedir;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdir:
+ devdir(c, c->qid, ".", 0, eve, DMDIR|0555, &dir);
+ break;
+ case Qdata0:
+ devdir(c, c->qid, tab[1].name, qlen(p->q[0]), eve, tab[1].perm, &dir);
+ break;
+ case Qdata1:
+ devdir(c, c->qid, tab[2].name, qlen(p->q[1]), eve, tab[2].perm, &dir);
+ break;
+ default:
+ panic("pipestat");
+ }
+ n = convD2M(&dir, db, n);
+ if(n < BIT16SZ)
+ error(Eshortstat);
+ return n;
+}
+
+/*
+ * if the stream doesn't exist, create it
+ */
+static Chan*
+pipeopen(Chan *c, int omode)
+{
+ Pipe *p;
+
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Ebadarg);
+ c->mode = omode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ openmode(omode); /* check it */
+
+ p = c->aux;
+ qlock(p);
+ if(waserror()){
+ qunlock(p);
+ nexterror();
+ }
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ devpermcheck(p->user, p->pipedir[1].perm, omode);
+ p->qref[0]++;
+ break;
+ case Qdata1:
+ devpermcheck(p->user, p->pipedir[2].perm, omode);
+ p->qref[1]++;
+ break;
+ }
+ poperror();
+ qunlock(p);
+
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static void
+pipeclose(Chan *c)
+{
+ Pipe *p;
+
+ p = c->aux;
+ qlock(p);
+
+ if(c->flag & COPEN){
+ /*
+ * closing either side hangs up the stream
+ */
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ p->qref[0]--;
+ if(p->qref[0] == 0){
+ qhangup(p->q[1], 0);
+ qclose(p->q[0]);
+ }
+ break;
+ case Qdata1:
+ p->qref[1]--;
+ if(p->qref[1] == 0){
+ qhangup(p->q[0], 0);
+ qclose(p->q[1]);
+ }
+ break;
+ }
+ }
+
+
+ /*
+ * if both sides are closed, they are reusable
+ */
+ if(p->qref[0] == 0 && p->qref[1] == 0){
+ qreopen(p->q[0]);
+ qreopen(p->q[1]);
+ }
+
+ /*
+ * free the structure on last close
+ */
+ p->ref--;
+ if(p->ref == 0){
+ qunlock(p);
+ freepipe(p);
+ } else
+ qunlock(p);
+}
+
+static long
+piperead(Chan *c, void *va, long n, vlong)
+{
+ Pipe *p;
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdir:
+ return devdirread(c, va, n, p->pipedir, nelem(pipedir), pipegen);
+ case Qdata0:
+ return qread(p->q[0], va, n);
+ case Qdata1:
+ return qread(p->q[1], va, n);
+ default:
+ panic("piperead");
+ }
+ return -1; /* not reached */
+}
+
+static Block*
+pipebread(Chan *c, long n, ulong offset)
+{
+ Pipe *p;
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ return qbread(p->q[0], n);
+ case Qdata1:
+ return qbread(p->q[1], n);
+ }
+
+ return devbread(c, n, offset);
+}
+
+/*
+ * a write to a closed pipe causes an exception to be sent to
+ * the prog.
+ */
+static long
+pipewrite(Chan *c, void *va, long n, vlong)
+{
+ Pipe *p;
+ Prog *r;
+
+ if(waserror()) {
+ /* avoid exceptions when pipe is a mounted queue */
+ if((c->flag & CMSG) == 0) {
+ r = up->iprog;
+ if(r != nil && r->kill == nil)
+ r->kill = "write on closed pipe";
+ }
+ nexterror();
+ }
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ n = qwrite(p->q[1], va, n);
+ break;
+
+ case Qdata1:
+ n = qwrite(p->q[0], va, n);
+ break;
+
+ default:
+ panic("pipewrite");
+ }
+
+ poperror();
+ return n;
+}
+
+static long
+pipebwrite(Chan *c, Block *bp, ulong junk)
+{
+ long n;
+ Pipe *p;
+ Prog *r;
+
+ USED(junk);
+ if(waserror()) {
+ /* avoid exceptions when pipe is a mounted queue */
+ if((c->flag & CMSG) == 0) {
+ r = up->iprog;
+ if(r != nil && r->kill == nil)
+ r->kill = "write on closed pipe";
+ }
+ nexterror();
+ }
+
+ p = c->aux;
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ n = qbwrite(p->q[1], bp);
+ break;
+
+ case Qdata1:
+ n = qbwrite(p->q[0], bp);
+ break;
+
+ default:
+ n = 0;
+ panic("pipebwrite");
+ }
+
+ poperror();
+ return n;
+}
+
+static int
+pipewstat(Chan *c, uchar *dp, int n)
+{
+ Dir *d;
+ Pipe *p;
+ int d1;
+
+ if (c->qid.type&QTDIR)
+ error(Eperm);
+ p = c->aux;
+ if(strcmp(up->env->user, p->user) != 0)
+ error(Eperm);
+ d = smalloc(sizeof(*d)+n);
+ if(waserror()){
+ free(d);
+ nexterror();
+ }
+ n = convM2D(dp, n, d, (char*)&d[1]);
+ if(n == 0)
+ error(Eshortstat);
+ d1 = NETTYPE(c->qid.path) == Qdata1;
+ if(!emptystr(d->name)){
+ validwstatname(d->name);
+ if(strlen(d->name) >= KNAMELEN)
+ error(Efilename);
+ if(strcmp(p->pipedir[1+!d1].name, d->name) == 0)
+ error(Eexist);
+ kstrcpy(p->pipedir[1+d1].name, d->name, KNAMELEN);
+ }
+ if(d->mode != ~0UL)
+ p->pipedir[d1 + 1].perm = d->mode & 0777;
+ poperror();
+ free(d);
+ return n;
+}
+
+Dev pipedevtab = {
+ '|',
+ "pipe",
+
+ devreset,
+ pipeinit,
+ devshutdown,
+ pipeattach,
+ pipewalk,
+ pipestat,
+ pipeopen,
+ devcreate,
+ pipeclose,
+ piperead,
+ pipebread,
+ pipewrite,
+ pipebwrite,
+ devremove,
+ pipewstat,
+};
diff --git a/os/port/devpointer.c b/os/port/devpointer.c
new file mode 100644
index 00000000..7b069397
--- /dev/null
+++ b/os/port/devpointer.c
@@ -0,0 +1,279 @@
+/*
+ * mouse or stylus
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#include <draw.h>
+#include <memdraw.h>
+#include <cursor.h>
+#include "screen.h"
+
+enum{
+ Qdir,
+ Qpointer,
+ Qcursor,
+};
+
+typedef struct Pointer Pointer;
+
+struct Pointer {
+ int x;
+ int y;
+ int b;
+ ulong msec;
+};
+
+static struct
+{
+ Pointer;
+ int modify;
+ int lastb;
+ Rendez r;
+ Ref ref;
+ QLock q;
+} mouse;
+
+static
+Dirtab pointertab[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "pointer", {Qpointer}, 0, 0666,
+ "cursor", {Qcursor}, 0, 0222,
+};
+
+enum {
+ Nevent = 16 /* enough for some */
+};
+
+static struct {
+ int rd;
+ int wr;
+ Pointer clicks[Nevent];
+ Rendez r;
+ int full;
+ int put;
+ int get;
+} ptrq;
+
+/*
+ * called by any source of pointer data
+ */
+void
+mousetrack(int b, int x, int y, int isdelta)
+{
+ int lastb;
+ ulong msec;
+ Pointer e;
+
+ if(isdelta){
+ x += mouse.x;
+ y += mouse.y;
+ }
+ msec = TK2MS(MACHP(0)->ticks);
+ if(b && (mouse.b ^ b)&0x1f){
+ if(msec - mouse.msec < 300 && mouse.lastb == b
+ && abs(mouse.x - x) < 12 && abs(mouse.y - y) < 12)
+ b |= 1<<8;
+ mouse.lastb = b & 0x1f;
+ mouse.msec = msec;
+ }
+ if(x == mouse.x && y == mouse.y && mouse.b == b)
+ return;
+ lastb = mouse.b;
+ mouse.x = x;
+ mouse.y = y;
+ mouse.b = b;
+ mouse.msec = msec;
+ if(!ptrq.full && lastb != b){
+ e = mouse.Pointer;
+ ptrq.clicks[ptrq.wr] = e;
+ if(++ptrq.wr >= Nevent)
+ ptrq.wr = 0;
+ if(ptrq.wr == ptrq.rd)
+ ptrq.full = 1;
+ }
+ mouse.modify = 1;
+ ptrq.put++;
+ wakeup(&ptrq.r);
+ drawactive(1);
+ /* TO DO: cursor update */
+}
+
+static int
+ptrqnotempty(void*)
+{
+ return ptrq.full || ptrq.put != ptrq.get;
+}
+
+static Pointer
+mouseconsume(void)
+{
+ Pointer e;
+
+ sleep(&ptrq.r, ptrqnotempty, 0);
+ ptrq.full = 0;
+ ptrq.get = ptrq.put;
+ if(ptrq.rd != ptrq.wr){
+ e = ptrq.clicks[ptrq.rd];
+ if(++ptrq.rd >= Nevent)
+ ptrq.rd = 0;
+ }else
+ e = mouse.Pointer;
+ return e;
+}
+
+Point
+mousexy(void)
+{
+ return Pt(mouse.x, mouse.y);
+}
+
+
+static Chan*
+pointerattach(char* spec)
+{
+ return devattach('m', spec);
+}
+
+static Walkqid*
+pointerwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+
+ wq = devwalk(c, nc, name, nname, pointertab, nelem(pointertab), devgen);
+ if(wq != nil && wq->clone != c && wq->clone != nil && (ulong)c->qid.path == Qpointer)
+ incref(&mouse.ref); /* can this happen? */
+ return wq;
+}
+
+static int
+pointerstat(Chan* c, uchar *db, int n)
+{
+ return devstat(c, db, n, pointertab, nelem(pointertab), devgen);
+}
+
+static Chan*
+pointeropen(Chan* c, int omode)
+{
+ c = devopen(c, omode, pointertab, nelem(pointertab), devgen);
+ if((ulong)c->qid.path == Qpointer){
+ if(waserror()){
+ c->flag &= ~COPEN;
+ nexterror();
+ }
+ if(!canqlock(&mouse.q))
+ error(Einuse);
+ if(incref(&mouse.ref) != 1){
+ qunlock(&mouse.q);
+ error(Einuse);
+ }
+ cursorenable();
+ qunlock(&mouse.q);
+ poperror();
+ }
+ return c;
+}
+
+static void
+pointerclose(Chan* c)
+{
+ if((c->flag & COPEN) == 0)
+ return;
+ switch((ulong)c->qid.path){
+ case Qpointer:
+ qlock(&mouse.q);
+ if(decref(&mouse.ref) == 0)
+ cursordisable();
+ qunlock(&mouse.q);
+ break;
+ }
+}
+
+static long
+pointerread(Chan* c, void* a, long n, vlong)
+{
+ Pointer mt;
+ char tmp[128];
+ int l;
+
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, pointertab, nelem(pointertab), devgen);
+ case Qpointer:
+ qlock(&mouse.q);
+ if(waserror()) {
+ qunlock(&mouse.q);
+ nexterror();
+ }
+ mt = mouseconsume();
+ poperror();
+ qunlock(&mouse.q);
+ l = sprint(tmp, "m%11d %11d %11d %11lud ", mt.x, mt.y, mt.b, mt.msec);
+ if(l < n)
+ n = l;
+ memmove(a, tmp, n);
+ break;
+ case Qcursor:
+ /* TO DO: interpret data written as Image; give to drawcursor() */
+ break;
+ default:
+ n=0;
+ break;
+ }
+ return n;
+}
+
+static long
+pointerwrite(Chan* c, void* va, long n, vlong)
+{
+ char *a = va;
+ char buf[128];
+ int b, x, y;
+
+ switch((ulong)c->qid.path){
+ case Qpointer:
+ if(n > sizeof buf-1)
+ n = sizeof buf -1;
+ memmove(buf, va, n);
+ buf[n] = 0;
+ x = strtoul(buf+1, &a, 0);
+ if(*a == 0)
+ error(Eshort);
+ y = strtoul(a, &a, 0);
+ if(*a != 0)
+ b = strtoul(a, 0, 0);
+ else
+ b = mouse.b;
+ mousetrack(b, x, y, 0);
+ break;
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+Dev pointerdevtab = {
+ 'm',
+ "pointer",
+
+ devreset,
+ devinit,
+ devshutdown,
+ pointerattach,
+ pointerwalk,
+ pointerstat,
+ pointeropen,
+ devcreate,
+ pointerclose,
+ pointerread,
+ devbread,
+ pointerwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devprof.c b/os/port/devprof.c
new file mode 100644
index 00000000..f0ee3ba8
--- /dev/null
+++ b/os/port/devprof.c
@@ -0,0 +1,722 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <interp.h>
+#include <isa.h>
+#include "runt.h"
+
+static void cpxec(Prog *);
+static void memprof(int, Heap*, ulong);
+
+extern Inst* pc2dispc(Inst*, Module*);
+
+static int interval = 100; /* Sampling interval in milliseconds */
+
+enum
+{
+ HSIZE = 32,
+};
+
+#define HASH(m) ((m)%HSIZE)
+
+/* cope with multiple profilers some day */
+
+typedef struct Record Record;
+struct Record
+{
+ int id;
+ char* name;
+ char* path;
+ Inst* base;
+ int size;
+ /*Module* m; */
+ ulong mtime;
+ Qid qid;
+ Record* hash;
+ Record* link;
+ ulong bucket[1];
+};
+
+struct
+{
+ Lock l;
+ vlong time;
+ Record* hash[HSIZE];
+ Record* list;
+} profile;
+
+typedef struct Pmod Pmod;
+struct Pmod
+{
+ char* name;
+ Pmod* link;
+} *pmods;
+
+#define QSHIFT 4
+#define QID(q) ((ulong)(q).path&0xf)
+#define QPID(pid) ((pid)<<QSHIFT)
+#define PID(q) ((q).vers)
+#define PATH(q) ((ulong)(q).path&~((1<<QSHIFT)-1))
+
+enum
+{
+ Qdir,
+ Qname,
+ Qpath,
+ Qhist,
+ Qpctl,
+ Qctl,
+};
+
+Dirtab profdir[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "name", {Qname}, 0, 0444,
+ "path", {Qpath}, 0, 0444,
+ "histogram", {Qhist}, 0, 0444,
+ "pctl", {Qpctl}, 0, 0222,
+ "ctl", {Qctl}, 0, 0222,
+};
+
+enum{
+ Pnil, /* null profiler */
+ Psam, /* sampling profiler */
+ Pcov, /* coverage profiler */
+ Pmem, /* memory profiler */
+};
+
+static int profiler = Pnil;
+
+static int ids;
+static int samplefn;
+
+static void sampler(void*);
+
+static Record*
+getrec(int id)
+{
+ Record *r;
+
+ for(r = profile.list; r != nil; r = r->link)
+ if(r->id == id)
+ break;
+ return r;
+}
+
+static void
+addpmod(char *m)
+{
+ Pmod *p = malloc(sizeof(Pmod));
+
+ if(p == nil)
+ return;
+ p->name = malloc(strlen(m)+1);
+ if(p->name == nil){
+ free(p);
+ return;
+ }
+ strcpy(p->name, m);
+ p->link = pmods;
+ pmods = p;
+}
+
+static void
+freepmods(void)
+{
+ Pmod *p, *np;
+
+ for(p = pmods; p != nil; p = np){
+ free(p->name);
+ np = p->link;
+ free(p);
+ }
+ pmods = nil;
+}
+
+static int
+inpmods(char *m)
+{
+ Pmod *p;
+
+ for(p = pmods; p != nil; p = p->link)
+ if(strcmp(p->name, m) == 0)
+ return 1;
+ return 0;
+}
+
+static void
+freeprof(void)
+{
+ int i;
+ Record *r, *nr;
+
+ ids = 0;
+ profiler = Pnil;
+ freepmods();
+ for(r = profile.list; r != nil; r = nr){
+ free(r->path);
+ nr = r->link;
+ free(r);
+ }
+ profile.list = nil;
+ profile.time = 0;
+ for(i = 0; i < HSIZE; i++)
+ profile.hash[i] = nil;
+}
+
+static int
+profgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp)
+{
+ Qid qid;
+ Record *r;
+ ulong path, perm, len;
+ Dirtab *tab;
+
+ USED(name);
+ USED(d);
+ USED(nd);
+
+ if(s == DEVDOTDOT) {
+ mkqid(&qid, Qdir, 0, QTDIR);
+ devdir(c, qid, "#P", 0, eve, 0555, dp);
+ return 1;
+ }
+
+ if(c->qid.path == Qdir && c->qid.type & QTDIR) {
+ acquire();
+ if(s-- == 0){
+ tab = &profdir[Qctl];
+ mkqid(&qid, PATH(c->qid)|tab->qid.path, c->qid.vers, QTFILE);
+ devdir(c, qid, tab->name, tab->length, eve, tab->perm, dp);
+ release();
+ return 1;
+ }
+ r = profile.list;
+ while(s-- && r != nil)
+ r = r->link;
+ if(r == nil) {
+ release();
+ return -1;
+ }
+ sprint(up->genbuf, "%.8lux", (ulong)r->id);
+ mkqid(&qid, (r->id<<QSHIFT), r->id, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, DMDIR|0555, dp);
+ release();
+ return 1;
+ }
+ if(s >= nelem(profdir)-1)
+ error(Enonexist); /* was return -1; */
+ tab = &profdir[s];
+ path = PATH(c->qid);
+
+ acquire();
+ r = getrec(PID(c->qid));
+ if(r == nil) {
+ release();
+ error(Enonexist); /* was return -1; */
+ }
+
+ perm = tab->perm;
+ len = tab->length;
+ mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE);
+ devdir(c, qid, tab->name, len, eve, perm, dp);
+ release();
+ return 1;
+}
+
+static Chan*
+profattach(char *spec)
+{
+ return devattach('P', spec);
+}
+
+static Walkqid*
+profwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, 0, 0, profgen);
+}
+
+static int
+profstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, 0, 0, profgen);
+}
+
+static Chan*
+profopen(Chan *c, int omode)
+{
+ int qid;
+ Record *r;
+
+ if(c->qid.type & QTDIR) {
+ if(omode != OREAD)
+ error(Eisdir);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ if(omode&OTRUNC)
+ error(Eperm);
+
+ qid = QID(c->qid);
+ if(qid == Qctl || qid == Qpctl){
+ if (omode != OWRITE)
+ error(Eperm);
+ }
+ else{
+ if(omode != OREAD)
+ error(Eperm);
+ }
+
+ if(qid != Qctl){
+ acquire();
+ r = getrec(PID(c->qid));
+ release();
+ if(r == nil)
+ error(Ethread);
+ }
+
+ c->offset = 0;
+ c->flag |= COPEN;
+ c->mode = openmode(omode);
+ if(QID(c->qid) == Qhist)
+ c->aux = nil;
+ return c;
+}
+
+static int
+profwstat(Chan *c, uchar *dp, int n)
+{
+ Dir d;
+ Record *r;
+
+ if(strcmp(up->env->user, eve))
+ error(Eperm);
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+ acquire();
+ r = getrec(PID(c->qid));
+ release();
+ if(r == nil)
+ error(Ethread);
+ n = convM2D(dp, n, &d, nil);
+ if(n == 0)
+ error(Eshortstat);
+ d.mode &= 0777;
+ /* TO DO: copy to c->aux->perm, once that exists */
+ return n;
+}
+
+static void
+profclose(Chan *c)
+{
+ USED(c);
+}
+
+static long
+profread(Chan *c, void *va, long n, vlong offset)
+{
+ int i;
+ Record *r;
+ char *a = va;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, profgen);
+ acquire();
+ r = getrec(PID(c->qid));
+ release();
+ if(r == nil)
+ error(Ethread);
+ switch(QID(c->qid)){
+ case Qname:
+ return readstr(offset, va, n, r->name);
+ case Qpath:
+ return readstr(offset, va, n, r->path);
+ case Qhist:
+ i = (int)c->aux;
+ while(i < r->size && r->bucket[i] == 0)
+ i++;
+ if(i >= r->size)
+ return 0;
+ c->aux = (void*)(i+1);
+ if(n < 20)
+ error(Etoosmall);
+ return sprint(a, "%d %lud", i, r->bucket[i]);
+ case Qctl:
+ error(Eperm);
+ }
+ return 0;
+}
+
+static long
+profwrite(Chan *c, void *va, long n, vlong offset)
+{
+ int i;
+ char *a = va;
+ char buf[128], *fields[128];
+
+ USED(va);
+ USED(n);
+ USED(offset);
+
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ switch(QID(c->qid)){
+ case Qctl:
+ if(n > sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = 0;
+ i = getfields(buf, fields, nelem(fields), 1, " \t\n");
+ if(i > 0 && strcmp(fields[0], "module") == 0){
+ freepmods();
+ while(--i > 0)
+ addpmod(fields[i]);
+ return n;
+ }
+ if(i == 1){
+ if(strcmp(fields[0], "start") == 0){
+ if(profiler == Pnil) {
+ profiler = Psam;
+ if(!samplefn){
+ samplefn = 1;
+ kproc("prof", sampler, 0, 0);
+ }
+ }
+ }
+ else if(strcmp(fields[0], "startmp") == 0){
+ if(profiler == Pnil){
+ profiler = Pmem;
+ heapmonitor = memprof;
+ }
+ }
+ else if(strcmp(fields[0], "stop") == 0)
+ profiler = Pnil;
+ else if(strcmp(fields[0], "end") == 0){
+ profiler = Pnil;
+ freeprof();
+ interval = 100;
+ }
+ else
+ error(Ebadarg);
+ }
+ else if (i == 2){
+ if(strcmp(fields[0], "interval") == 0)
+ interval = strtoul(fields[1], nil, 0);
+ else if(strcmp(fields[0], "startcp") == 0){
+ Prog *p;
+
+ acquire();
+ p = progpid(strtoul(fields[1], nil, 0));
+ if(p == nil){
+ release();
+ return -1;
+ }
+ if(profiler == Pnil){
+ profiler = Pcov;
+ p->xec = cpxec;
+ }
+ release();
+ }
+ else
+ error(Ebadarg);
+ }
+ else
+ error(Ebadarg);
+ return n;
+ default:
+ error(Eperm);
+ }
+ return 0;
+}
+
+static Record*
+newmodule(Module *m, int vm, int scale, int origin)
+{
+ int dsize;
+ Record *r, **l;
+
+ if(!vm)
+ acquire();
+ if((m->compiled && m->pctab == nil) || m->prog == nil) {
+ if(!vm)
+ release();
+ return nil;
+ }
+/* print("newmodule %x %s %s %d %d %d\n", m, m->name, m->path, m->mtime, m->qid.path, m->qid.vers); */
+ if(m->compiled)
+ dsize = m->nprog * sizeof(r->bucket[0]);
+ else
+ dsize = (msize(m->prog)/sizeof(Inst)) * sizeof(r->bucket[0]);
+ dsize *= scale;
+ dsize += origin;
+ r = malloc(sizeof(Record)+dsize);
+ if(r == nil) {
+ if(!vm)
+ release();
+ return nil;
+ }
+
+ r->id = ++ids;
+ if(ids == (1<<8)-1)
+ ids = 0;
+ kstrdup(&r->name, m->name);
+ kstrdup(&r->path, m->path);
+ r->base = m->prog;
+ r->size = dsize/sizeof(r->bucket[0]);
+ // r->m = m;
+ r->mtime = m->mtime;
+ r->qid.path = m->qid.path;
+ r->qid.vers = m->qid.vers;
+ memset(r->bucket, 0, dsize);
+ r->link = profile.list;
+ profile.list = r;
+
+ l = &profile.hash[HASH(m->mtime)];
+ r->hash = *l;
+ *l = r;
+
+ if(!vm)
+ release();
+ return r;
+}
+
+static Record*
+mlook(Module *m, int vm, int scale, int origin)
+{
+ Record *r;
+
+ for(r = profile.hash[HASH(m->mtime)]; r; r = r->hash){
+ /* if(r->m == m){ /* bug - r->m could be old exited module */
+ if(r->mtime == m->mtime && r->qid.path == m->qid.path && r->qid.vers == m->qid.vers && strcmp(r->name, m->name) == 0 && strcmp(r->path, m->path) == 0){
+ r->base = m->prog;
+ return r;
+ }
+ }
+ if(pmods == nil || inpmods(m->name) || inpmods(m->path)){
+ if(0 && profiler == Pmem)
+ heapmonitor = nil;
+ r = newmodule(m, vm, scale, origin);
+ if(0 && profiler == Pmem)
+ heapmonitor = memprof;
+ return r;
+ }
+ return nil;
+}
+
+static void
+sampler(void* a)
+{
+ int i;
+ Module *m;
+ Record *r;
+ Inst *p;
+
+ USED(a);
+ for(;;) {
+ tsleep(&up->sleep, return0, nil, interval);
+ if(profiler != Psam)
+ break;
+ lock(&profile.l);
+ profile.time += interval;
+ if(R.M == H || (m = R.M->m) == nil){
+ unlock(&profile.l);
+ continue;
+ }
+ p = R.PC;
+ r = mlook(m, 0, 1, 0);
+ if(r == nil){
+ unlock(&profile.l);
+ continue;
+ }
+ if(m->compiled && m->pctab != nil)
+ p = pc2dispc(p, m);
+ if((i = p-r->base) >= 0 && i < r->size)
+ r->bucket[i]++;
+ unlock(&profile.l);
+ }
+ samplefn = 0;
+ pexit("", 0);
+}
+
+/*
+ * coverage profiling
+ */
+
+static void
+cpxec(Prog *p)
+{
+ int op, i;
+ Module *m;
+ Record *r;
+ Prog *n;
+
+ R = p->R;
+ R.MP = R.M->MP;
+ R.IC = p->quanta;
+
+ if(p->kill != nil){
+ char *m;
+ m = p->kill;
+ p->kill = nil;
+ error(m);
+ }
+
+ if(R.M->compiled)
+ comvec();
+ else{
+ m = R.M->m;
+ r = profiler == Pcov ? mlook(m, 1, 1, 0) : nil;
+ do{
+ dec[R.PC->add]();
+ op = R.PC->op;
+ if(r != nil){
+ i = R.PC-r->base;
+ if(i >= 0 && i < r->size)
+ r->bucket[i]++;
+ }
+ R.PC++;
+ optab[op]();
+ if(op == ISPAWN || op == IMSPAWN){
+ n = delruntail(Pdebug); // any state will do
+ n->xec = cpxec;
+ addrun(n);
+ }
+ if(m != R.M->m){
+ m = R.M->m;
+ r = profiler == Pcov ? mlook(m, 1, 1, 0) : nil;
+ }
+ }while(--R.IC != 0);
+ }
+
+ p->R = R;
+}
+
+/* memory profiling */
+
+enum{
+ Halloc,
+ Hfree,
+ Hgcfree,
+};
+
+#define MPAD sizeof(double)
+
+static void
+memprof(int c, Heap *h, ulong n)
+{
+ int i, j, k;
+ ulong kk, *b;
+ Module *m;
+ Record *r;
+ Inst *p;
+
+/* print("%d %x %uld\n", c, h, n); */
+ USED(h);
+ if(profiler != Pmem){
+ heapmonitor = nil;
+ return;
+ }
+ lock(&profile.l);
+ m = nil;
+ if(c != Hgcfree && (R.M == H || (m = R.M->m) == nil)){
+ unlock(&profile.l);
+ return;
+ }
+ j = n;
+ if(c == 0 || c == 4){ /* heap or main allocation */
+ p = R.PC;
+ if(m->compiled && m->pctab != nil)
+ p = pc2dispc(p, m);
+ if((r = mlook(m, 1, 2, 2)) == nil){
+ unlock(&profile.l);
+ return;
+ }
+ i = p-r->base;
+ k = (r->id<<24) | i;
+ if(c == 0){
+ h->pad = k;
+ k = sizeof(Heap);
+ }
+ else{
+ *(ulong*)h = k;
+ k = MPAD;
+ }
+ /* 31 is pool quanta - dependency on alloc.c */
+ j = ((j+k+BHDRSIZE+31)&~31) - (k+BHDRSIZE);
+ }
+ else{
+ /* c == 1 is ref count free */
+ /* c == 2 is gc free */
+ /* c == 3 is main free */
+ if(c == 3)
+ k = *(ulong*)h;
+ else
+ k = h->pad;
+ if((r = getrec(k>>24)) == nil){
+ unlock(&profile.l);
+ return;
+ }
+ i = k&0xffffff;
+ if(c == 3)
+ j = msize(h)-MPAD;
+ else
+ j = hmsize(h)-sizeof(Heap);
+ j = -j;
+ }
+ i = 2*(i+1);
+ b = r->bucket;
+ if(i >= 0 && i < r->size){
+ if(0){
+ if(c == 1){
+ b[0] -= j;
+ b[i] -= j;
+ }
+ else if(c == 2){
+ b[1] -= j;
+ b[i+1] -= j;
+ }
+ }
+ else{
+ b[0] += j;
+ if((int)b[0] < 0)
+ b[0] = 0;
+ b[i] += j;
+ if((int)b[i] < 0)
+ b[i] = 0;
+ if(j > 0){
+ if((kk = b[0]) > b[1])
+ b[1] = kk;
+ if((kk = b[i]) > b[i+1])
+ b[i+1] = kk;
+ }
+ }
+ }
+ unlock(&profile.l);
+}
+
+Dev profdevtab = {
+ 'P',
+ "prof",
+
+ devreset,
+ devinit,
+ devshutdown,
+ profattach,
+ profwalk,
+ profstat,
+ profopen,
+ devcreate,
+ profclose,
+ profread,
+ devbread,
+ profwrite,
+ devbwrite,
+ devremove,
+ profwstat
+};
diff --git a/os/port/devprog.c b/os/port/devprog.c
new file mode 100644
index 00000000..c6c97efc
--- /dev/null
+++ b/os/port/devprog.c
@@ -0,0 +1,1510 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include <interp.h>
+#include <isa.h>
+#include "runt.h"
+
+/*
+ * Enable the heap device for environments that allow debugging =>
+ * Must be 1 for a production environment.
+ */
+int SECURE = 0;
+
+enum
+{
+ Qdir,
+ Qctl,
+ Qdbgctl,
+ Qheap,
+ Qns,
+ Qnsgrp,
+ Qpgrp,
+ Qstack,
+ Qstatus,
+ Qtext,
+ Qwait,
+ Qfd,
+ Qexception,
+};
+
+/*
+ * must be in same order as enum
+ */
+Dirtab progdir[] =
+{
+ "ctl", {Qctl}, 0, 0200,
+ "dbgctl", {Qdbgctl}, 0, 0600,
+ "heap", {Qheap}, 0, 0600,
+ "ns", {Qns}, 0, 0400,
+ "nsgrp", {Qnsgrp}, 0, 0444,
+ "pgrp", {Qpgrp}, 0, 0444,
+ "stack", {Qstack}, 0, 0400,
+ "status", {Qstatus}, 0, 0444,
+ "text", {Qtext}, 0, 0000,
+ "wait", {Qwait}, 0, 0400,
+ "fd", {Qfd}, 0, 0400,
+ "exception", {Qexception}, 0, 0400,
+};
+
+enum
+{
+ CMkill,
+ CMkillgrp,
+ CMrestricted,
+ CMexceptions,
+ CMprivate
+};
+
+static
+Cmdtab progcmd[] = {
+ CMkill, "kill", 1,
+ CMkillgrp, "killgrp", 1,
+ CMrestricted, "restricted", 1,
+ CMexceptions, "exceptions", 2,
+ CMprivate, "private", 1,
+};
+
+enum
+{
+ CDstep,
+ CDtoret,
+ CDcont,
+ CDstart,
+ CDstop,
+ CDunstop,
+ CDmaim,
+ CDbpt
+};
+
+static
+Cmdtab progdbgcmd[] = {
+ CDstep, "step", 0, /* known below to be first, to cope with stepN */
+ CDtoret, "toret", 1,
+ CDcont, "cont", 1,
+ CDstart, "start", 1,
+ CDstop, "stop", 1,
+ CDunstop, "unstop", 1,
+ CDmaim, "maim", 1,
+ CDbpt, "bpt", 4,
+};
+
+typedef struct Heapqry Heapqry;
+struct Heapqry
+{
+ char fmt;
+ ulong addr;
+ ulong module;
+ int count;
+};
+
+typedef struct Bpt Bpt;
+
+struct Bpt
+{
+ Bpt *next;
+ int pc;
+ char *file;
+ char path[1];
+};
+
+typedef struct Progctl Progctl;
+struct Progctl
+{
+ Rendez r;
+ int ref;
+ Proc *debugger; /* waiting for dbgxec */
+ char *msg; /* reply from dbgxec */
+ int step; /* instructions to try */
+ int stop; /* stop running the program */
+ Bpt* bpts; /* active breakpoints */
+ Queue* q; /* status queue */
+};
+
+#define QSHIFT 4 /* location in qid of pid */
+#define QID(q) (((ulong)(q).path&0x0000000F)>>0)
+#define QPID(pid) (((pid)<<QSHIFT))
+#define PID(q) ((q).vers)
+#define PATH(q) ((ulong)(q).path&~((1<<QSHIFT)-1))
+
+static char *progstate[] = /* must correspond to include/interp.h */
+{
+ "alt", /* blocked in alt instruction */
+ "send", /* waiting to send */
+ "recv", /* waiting to recv */
+ "debug", /* debugged */
+ "ready", /* ready to be scheduled */
+ "release", /* interpreter released */
+ "exiting", /* exit because of kill or error */
+ "broken", /* thread crashed */
+};
+
+static void dbgstep(Progctl*, Prog*, int);
+static void dbgstart(Prog*);
+static void freebpts(Bpt*);
+static Bpt* delbpt(Bpt*, char*, int);
+static Bpt* setbpt(Bpt*, char*, int);
+static void mntscan(Mntwalk*, Pgrp*);
+extern Type *Trdchan;
+extern Type *Twrchan;
+extern Module* modules;
+static char Emisalign[] = "misaligned address";
+
+static int
+proggen(Chan *c, char *name, Dirtab *tab, int, int s, Dir *dp)
+{
+ Qid qid;
+ Prog *p;
+ char *e;
+ Osenv *o;
+ ulong pid, path, perm, len;
+
+ if(s == DEVDOTDOT){
+ mkqid(&qid, Qdir, 0, QTDIR);
+ devdir(c, qid, "#p", 0, eve, DMDIR|0555, dp);
+ return 1;
+ }
+
+ if((ulong)c->qid.path == Qdir) {
+ if(name != nil){
+ /* ignore s and use name to find pid */
+ pid = strtoul(name, &e, 0);
+ if(pid == 0 || *e != '\0')
+ return -1;
+ acquire();
+ p = progpid(pid);
+ if(p == nil){
+ release();
+ return -1;
+ }
+ }else{
+ acquire();
+ p = progn(s);
+ if(p == nil) {
+ release();
+ return -1;
+ }
+ pid = p->pid;
+ }
+ o = p->osenv;
+ sprint(up->genbuf, "%lud", pid);
+ if(name != nil && strcmp(name, up->genbuf) != 0){
+ release();
+ return -1;
+ }
+ mkqid(&qid, pid<<QSHIFT, pid, QTDIR);
+ devdir(c, qid, up->genbuf, 0, o->user, DMDIR|0555, dp);
+ release();
+ return 1;
+ }
+
+ if(s >= nelem(progdir))
+ return -1;
+ tab = &progdir[s];
+ path = PATH(c->qid);
+
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil) {
+ release();
+ return -1;
+ }
+
+ o = p->osenv;
+
+ perm = tab->perm;
+ if((perm & 7) == 0)
+ perm = (perm|(perm>>3)|(perm>>6)) & o->pgrp->progmode;
+
+ len = tab->length;
+ mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE);
+ devdir(c, qid, tab->name, len, o->user, perm, dp);
+ release();
+ return 1;
+}
+
+static Chan*
+progattach(char *spec)
+{
+ return devattach('p', spec);
+}
+
+static Walkqid*
+progwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, 0, 0, proggen);
+}
+
+static int
+progstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, 0, 0, proggen);
+}
+
+static Chan*
+progopen(Chan *c, int omode)
+{
+ Prog *p;
+ Osenv *o;
+ Progctl *ctl;
+ int perm;
+
+ if(c->qid.type & QTDIR)
+ return devopen(c, omode, 0, 0, proggen);
+
+ acquire();
+ if (waserror()) {
+ release();
+ nexterror();
+ }
+ p = progpid(PID(c->qid));
+ if(p == nil)
+ error(Ethread);
+
+ o = p->osenv;
+ perm = progdir[QID(c->qid)-1].perm;
+ if((perm & 7) == 0)
+ perm = (perm|(perm>>3)|(perm>>6)) & o->pgrp->progmode;
+ devpermcheck(o->user, perm, omode);
+ omode = openmode(omode);
+
+ switch(QID(c->qid)){
+ default:
+ error(Egreg);
+ case Qnsgrp:
+ case Qpgrp:
+ case Qtext:
+ case Qstatus:
+ case Qstack:
+ case Qctl:
+ case Qfd:
+ case Qexception:
+ break;
+ case Qwait:
+ c->aux = qopen(1024, Qmsg, nil, nil);
+ if(c->aux == nil)
+ error(Enomem);
+ o->childq = c->aux;
+ break;
+ case Qns:
+ c->aux = malloc(sizeof(Mntwalk));
+ if(c->aux == nil)
+ error(Enomem);
+ break;
+ case Qheap:
+ if(SECURE || o->pgrp->privatemem || omode != ORDWR)
+ error(Eperm);
+ c->aux = malloc(sizeof(Heapqry));
+ if(c->aux == nil)
+ error(Enomem);
+ break;
+ case Qdbgctl:
+ if(SECURE || o->pgrp->privatemem || omode != ORDWR)
+ error(Eperm);
+ ctl = malloc(sizeof(Progctl));
+ if(ctl == nil)
+ error(Enomem);
+ ctl->q = qopen(1024, Qmsg, nil, nil);
+ if(ctl->q == nil) {
+ free(ctl);
+ error(Enomem);
+ }
+ ctl->bpts = nil;
+ ctl->ref = 1;
+ c->aux = ctl;
+ break;
+ }
+ if(p->state != Pexiting)
+ c->qid.vers = p->pid;
+
+ poperror();
+ release();
+ c->offset = 0;
+ c->mode = omode;
+ c->flag |= COPEN;
+ return c;
+}
+
+static int
+progwstat(Chan *c, uchar *db, int n)
+{
+ Dir d;
+ Prog *p;
+ char *u;
+ Osenv *o;
+
+ if(c->qid.type&QTDIR)
+ error(Eperm);
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil) {
+ release();
+ error(Ethread);
+ }
+
+ u = up->env->user;
+ o = p->osenv;
+ if(strcmp(u, o->user) != 0 && strcmp(u, eve) != 0) {
+ release();
+ error(Eperm);
+ }
+
+ n = convM2D(db, n, &d, nil);
+ if(n == 0){
+ release();
+ error(Eshortstat);
+ }
+ if(d.mode != ~0UL)
+ o->pgrp->progmode = d.mode&0777;
+ release();
+ return n;
+}
+
+static void
+closedbgctl(Progctl *ctl, Prog *p)
+{
+ Osenv *o;
+
+ if(ctl->ref-- > 1)
+ return;
+ freebpts(ctl->bpts);
+ if(p != nil){
+ o = p->osenv;
+ if(o->debug == ctl){
+ o->debug = nil;
+ p->xec = xec;
+ }
+ }
+ qfree(ctl->q);
+ free(ctl);
+}
+
+static void
+progclose(Chan *c)
+{
+ int i;
+ Prog *f;
+ Osenv *o;
+ Progctl *ctl;
+
+ switch(QID(c->qid)) {
+ case Qns:
+ case Qheap:
+ free(c->aux);
+ break;
+ case Qdbgctl:
+ if((c->flag & COPEN) == 0)
+ return;
+ ctl = c->aux;
+ acquire();
+ closedbgctl(ctl, progpid(PID(c->qid)));
+ release();
+ break;
+ case Qwait:
+ acquire();
+ i = 0;
+ for(;;) {
+ f = progn(i++);
+ if(f == nil)
+ break;
+ o = f->osenv;
+ if(o->waitq == c->aux)
+ o->waitq = nil;
+ if(o->childq == c->aux)
+ o->childq = nil;
+ }
+ release();
+ qfree(c->aux);
+ }
+}
+
+static int
+progsize(Prog *p)
+{
+ int size;
+ Frame *f;
+ uchar *fp;
+ Modlink *m;
+
+ m = p->R.M;
+ size = 0;
+ if(m->MP != H)
+ size += hmsize(D2H(m->MP));
+ if(m->prog != nil)
+ size += msize(m->prog);
+
+ fp = p->R.FP;
+ while(fp != nil) {
+ f = (Frame*)fp;
+ fp = f->fp;
+ if(f->mr != nil) {
+ if(f->mr->MP != H)
+ size += hmsize(D2H(f->mr->MP));
+ if(f->mr->prog != nil)
+ size += msize(f->mr->prog);
+ }
+ if(f->t == nil)
+ size += msize(SEXTYPE(f));
+ }
+ return size/1024;
+}
+
+static long
+progoffset(long offset, char *va, int *np)
+{
+ if(offset > 0) {
+ offset -= *np;
+ if(offset < 0) {
+ memmove(va, va+*np+offset, -offset);
+ *np = -offset;
+ }
+ else
+ *np = 0;
+ }
+ return offset;
+}
+
+static int
+progqidwidth(Chan *c)
+{
+ char buf[32];
+
+ return sprint(buf, "%lud", c->qid.vers);
+}
+
+int
+progfdprint(Chan *c, int fd, int w, char *s, int ns)
+{
+ int n;
+
+ if(w == 0)
+ w = progqidwidth(c);
+ n = snprint(s, ns, "%3d %.2s %C %4ld (%.16llux %*lud %.2ux) %5ld %8lld %s\n",
+ fd,
+ &"r w rw"[(c->mode&3)<<1],
+ devtab[c->type]->dc, c->dev,
+ c->qid.path, w, c->qid.vers, c->qid.type,
+ c->iounit, c->offset, c->name->s);
+ return n;
+}
+
+static int
+progfds(Osenv *o, char *va, int count, long offset)
+{
+ Fgrp *f;
+ Chan *c;
+ int n, i, w, ww;
+
+ f = o->fgrp; /* f is not locked because we've acquired */
+ n = readstr(0, va, count, o->pgrp->dot->name->s);
+ n += snprint(va+n, count-n, "\n");
+ offset = progoffset(offset, va, &n);
+ /* compute width of qid.path */
+ w = 0;
+ for(i = 0; i <= f->maxfd; i++) {
+ c = f->fd[i];
+ if(c == nil)
+ continue;
+ ww = progqidwidth(c);
+ if(ww > w)
+ w = ww;
+ }
+ for(i = 0; i <= f->maxfd; i++) {
+ c = f->fd[i];
+ if(c == nil)
+ continue;
+ n += progfdprint(c, i, w, va+n, count-n);
+ offset = progoffset(offset, va, &n);
+ }
+ return n;
+}
+
+Inst *
+pc2dispc(Inst *pc, Module *mod)
+{
+ ulong l, u, m, v;
+ ulong *tab = mod->pctab;
+
+ v = (ulong)pc - (ulong)mod->prog;
+ l = 0;
+ u = mod->nprog-1;
+ while(l < u){
+ m = (l+u+1)/2;
+ if(tab[m] < v)
+ l = m;
+ else if(tab[m] > v)
+ u = m-1;
+ else
+ l = u = m;
+ }
+ if(l == u && tab[u] <= v && u != mod->nprog-1 && tab[u+1] > v)
+ return &mod->prog[u];
+ return 0;
+}
+
+static int
+progstack(REG *reg, int state, char *va, int count, long offset)
+{
+ int n;
+ Frame *f;
+ Inst *pc;
+ uchar *fp;
+ Modlink *m;
+
+ n = 0;
+ m = reg->M;
+ fp = reg->FP;
+ pc = reg->PC;
+
+ /*
+ * all states other than debug and ready block,
+ * but interp has already advanced the PC
+ */
+ if(!m->compiled && state != Pready && state != Pdebug && pc > m->prog)
+ pc--;
+ if(m->compiled && m->m->pctab != nil)
+ pc = pc2dispc(pc, m->m);
+
+ while(fp != nil) {
+ f = (Frame*)fp;
+ n += snprint(va+n, count-n, "%.8lux %.8lux %.8lux %.8lux %d %s\n",
+ (ulong)f, /* FP */
+ (ulong)(pc - m->prog), /* PC in dis instructions */
+ (ulong)m->MP, /* MP */
+ (ulong)m->prog, /* Code for module */
+ m->compiled && m->m->pctab == nil, /* True if native assembler: fool stack utility for now */
+ m->m->path); /* File system path */
+
+ if(offset > 0) {
+ offset -= n;
+ if(offset < 0) {
+ memmove(va, va+n+offset, -offset);
+ n = -offset;
+ }
+ else
+ n = 0;
+ }
+
+ pc = f->lr;
+ fp = f->fp;
+ if(f->mr != nil)
+ m = f->mr;
+ if(!m->compiled)
+ pc--;
+ else if(m->m->pctab != nil)
+ pc = pc2dispc(pc, m->m)-1;
+ }
+ return n;
+}
+
+static int
+calldepth(REG *reg)
+{
+ int n;
+ uchar *fp;
+
+ n = 0;
+ for(fp = reg->FP; fp != nil; fp = ((Frame*)fp)->fp)
+ n++;
+ return n;
+}
+
+static int
+progheap(Heapqry *hq, char *va, int count, ulong offset)
+{
+ WORD *w;
+ void *p;
+ List *hd;
+ Array *a;
+ char *fmt, *str;
+ Module *m;
+ Modlink *ml;
+ Channel *c;
+ ulong addr;
+ String *ss;
+ union { REAL r; LONG l; WORD w[2]; } rock;
+ int i, s, n, len, signed_off;
+ Type *t;
+
+ n = 0;
+ s = 0;
+ signed_off = offset;
+ addr = hq->addr;
+ for(i = 0; i < hq->count; i++) {
+ switch(hq->fmt) {
+ case 'W':
+ if(addr & 3)
+ return -1;
+ n += snprint(va+n, count-n, "%d\n", *(WORD*)addr);
+ s = sizeof(WORD);
+ break;
+ case 'B':
+ n += snprint(va+n, count-n, "%d\n", *(BYTE*)addr);
+ s = sizeof(BYTE);
+ break;
+ case 'V':
+ if(addr & 3)
+ return -1;
+ w = (WORD*)addr;
+ rock.w[0] = w[0];
+ rock.w[1] = w[1];
+ n += snprint(va+n, count-n, "%lld\n", rock.l);
+ s = sizeof(LONG);
+ break;
+ case 'R':
+ if(addr & 3)
+ return -1;
+ w = (WORD*)addr;
+ rock.w[0] = w[0];
+ rock.w[1] = w[1];
+ n += snprint(va+n, count-n, "%g\n", rock.r);
+ s = sizeof(REAL);
+ break;
+ case 'I':
+ if(addr & 3)
+ return -1;
+ for(m = modules; m != nil; m = m->link)
+ if(m == (Module*)hq->module)
+ break;
+ if(m == nil)
+ error(Ebadctl);
+ addr = (ulong)(m->prog+addr);
+ n += snprint(va+n, count-n, "%D\n", (Inst*)addr);
+ s = sizeof(Inst);
+ break;
+ case 'P':
+ if(addr & 3)
+ return -1;
+ p = *(void**)addr;
+ fmt = "nil\n";
+ if(p != H)
+ fmt = "%lux\n";
+ n += snprint(va+n, count-n, fmt, p);
+ s = sizeof(WORD);
+ break;
+ case 'L':
+ if(addr & 3)
+ return -1;
+ hd = *(List**)addr;
+ if(hd == H || D2H(hd)->t != &Tlist)
+ return -1;
+ n += snprint(va+n, count-n, "%lux.%lux\n", (ulong)&hd->tail, (ulong)hd->data);
+ s = sizeof(WORD);
+ break;
+ case 'A':
+ if(addr & 3)
+ return -1;
+ a = *(Array**)addr;
+ if(a == H)
+ n += snprint(va+n, count-n, "nil\n");
+ else {
+ if(D2H(a)->t != &Tarray)
+ return -1;
+ n += snprint(va+n, count-n, "%d.%lux\n", a->len, (ulong)a->data);
+ }
+ s = sizeof(WORD);
+ break;
+ case 'C':
+ if(addr & 3)
+ return -1;
+ ss = *(String**)addr;
+ if(ss == H)
+ ss = &snil;
+ else
+ if(D2H(ss)->t != &Tstring)
+ return -1;
+ n += snprint(va+n, count-n, "%d.", abs(ss->len));
+ str = string2c(ss);
+ len = strlen(str);
+ if(count-n < len)
+ len = count-n;
+ if(len > 0) {
+ memmove(va+n, str, len);
+ n += len;
+ }
+ break;
+ case 'M':
+ if(addr & 3)
+ return -1;
+ ml = *(Modlink**)addr;
+ fmt = ml == H ? "nil\n" : "%lux\n";
+ n += snprint(va+n, count-n, fmt, ml->MP);
+ s = sizeof(WORD);
+ break;
+ case 'c':
+ if(addr & 3)
+ return -1;
+ c = *(Channel**)addr;
+ if(c == H)
+ n += snprint(va+n, count-n, "nil\n");
+ else{
+ t = D2H(c)->t;
+ if(t != &Tchannel && t != Trdchan && t != Twrchan)
+ return -1;
+ if(c->buf == H)
+ n += snprint(va+n, count-n, "0.%lux\n", (ulong)c);
+ else
+ n += snprint(va+n, count-n, "%d.%lux.%d.%d\n", c->buf->len, (ulong)c->buf->data, c->front, c->size);
+ }
+ break;
+
+ }
+ addr += s;
+ if(signed_off > 0) {
+ signed_off -= n;
+ if(signed_off < 0) {
+ memmove(va, va+n+signed_off, -signed_off);
+ n = -signed_off;
+ }
+ else
+ n = 0;
+ }
+ }
+ return n;
+}
+
+WORD
+modstatus(REG *r, char *ptr, int len)
+{
+ Inst *PC;
+ Frame *f;
+
+ if(r->M->m->name[0] == '$') {
+ f = (Frame*)r->FP;
+ snprint(ptr, len, "%s[%s]", f->mr->m->name, r->M->m->name);
+ if(f->mr->compiled)
+ return (WORD)f->lr;
+ return f->lr - f->mr->prog;
+ }
+ memmove(ptr, r->M->m->name, len);
+ if(r->M->compiled)
+ return (WORD)r->PC;
+ PC = r->PC;
+ /* should really check for blocked states */
+ if(PC > r->M->prog)
+ PC--;
+ return PC - r->M->prog;
+}
+
+static void
+int2flag(int flag, char *s)
+{
+ if(flag == 0){
+ *s = '\0';
+ return;
+ }
+ *s++ = '-';
+ if(flag & MAFTER)
+ *s++ = 'a';
+ if(flag & MBEFORE)
+ *s++ = 'b';
+ if(flag & MCREATE)
+ *s++ = 'c';
+ if(flag & MCACHE)
+ *s++ = 'C';
+ *s = '\0';
+}
+
+static char*
+progtime(ulong msec, char *buf, char *ebuf)
+{
+ int tenths, sec;
+
+ tenths = msec/100;
+ sec = tenths/10;
+ seprint(buf, ebuf, "%4d:%2.2d.%d", sec/60, sec%60, tenths%10);
+ return buf;
+}
+
+static long
+progread(Chan *c, void *va, long n, vlong offset)
+{
+ int i;
+ Prog *p;
+ Osenv *o;
+ Mntwalk *mw;
+ ulong grpid;
+ char *a = va;
+ Progctl *ctl;
+ char mbuf[64], timebuf[12];
+ char flag[10];
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, proggen);
+
+ switch(QID(c->qid)){
+ case Qdbgctl:
+ ctl = c->aux;
+ return qread(ctl->q, va, n);
+ case Qstatus:
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil || p->state == Pexiting || p->R.M == H) {
+ release();
+ snprint(up->genbuf, sizeof(up->genbuf), "%8lud %8d %10s %s %10s %5dK %s",
+ PID(c->qid),
+ 0,
+ eve,
+ progtime(0, timebuf, timebuf+sizeof(timebuf)),
+ progstate[Pexiting],
+ 0,
+ "[$Sys]");
+ return readstr(offset, va, n, up->genbuf);
+ }
+ modstatus(&p->R, mbuf, sizeof(mbuf));
+ o = p->osenv;
+ snprint(up->genbuf, sizeof(up->genbuf), "%8d %8d %10s %s %10s %5dK %s",
+ p->pid,
+ p->group!=nil? p->group->id: 0,
+ o->user,
+ progtime(TK2MS(p->ticks), timebuf, timebuf+sizeof(timebuf)),
+ progstate[p->state],
+ progsize(p),
+ mbuf);
+ release();
+ return readstr(offset, va, n, up->genbuf);
+ case Qwait:
+ return qread(c->aux, va, n);
+ case Qns:
+ acquire();
+ if(waserror()){
+ release();
+ nexterror();
+ }
+ p = progpid(PID(c->qid));
+ if(p == nil)
+ error(Ethread);
+ mw = c->aux;
+ if(mw->cddone){
+ poperror();
+ release();
+ return 0;
+ }
+ o = p->osenv;
+ mntscan(mw, o->pgrp);
+ if(mw->mh == 0) {
+ mw->cddone = 1;
+ i = snprint(a, n, "cd %s\n", o->pgrp->dot->name->s);
+ poperror();
+ release();
+ return i;
+ }
+ int2flag(mw->cm->mflag, flag);
+ if(strcmp(mw->cm->to->name->s, "#M") == 0){
+ i = snprint(a, n, "mount %s %s %s %s\n", flag,
+ mw->cm->to->mchan->name->s,
+ mw->mh->from->name->s, mw->cm->spec? mw->cm->spec : "");
+ }else
+ i = snprint(a, n, "bind %s %s %s\n", flag,
+ mw->cm->to->name->s, mw->mh->from->name->s);
+ poperror();
+ release();
+ return i;
+ case Qnsgrp:
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil) {
+ release();
+ error(Ethread);
+ }
+ grpid = ((Osenv *)p->osenv)->pgrp->pgrpid;
+ release();
+ return readnum(offset, va, n, grpid, NUMSIZE);
+ case Qpgrp:
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil) {
+ release();
+ error(Ethread);
+ }
+ grpid = p->group!=nil? p->group->id: 0;
+ release();
+ return readnum(offset, va, n, grpid, NUMSIZE);
+ case Qstack:
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil || p->state == Pexiting) {
+ release();
+ error(Ethread);
+ }
+ if(p->state == Pready) {
+ release();
+ error(Estopped);
+ }
+ n = progstack(&p->R, p->state, va, n, offset);
+ release();
+ return n;
+ case Qheap:
+ acquire();
+ if(waserror()){
+ release();
+ nexterror();
+ }
+ n = progheap(c->aux, va, n, offset);
+ if(n == -1)
+ error(Emisalign);
+ poperror();
+ release();
+ return n;
+ case Qfd:
+ acquire();
+ if(waserror()) {
+ release();
+ nexterror();
+ }
+ p = progpid(PID(c->qid));
+ if(p == nil)
+ error(Ethread);
+ o = p->osenv;
+ n = progfds(o, va, n, offset);
+ poperror();
+ release();
+ return n;
+ case Qexception:
+ acquire();
+ p = progpid(PID(c->qid));
+ if(p == nil) {
+ release();
+ error(Ethread);
+ }
+ if(p->exstr == nil)
+ up->genbuf[0] = 0;
+ else
+ snprint(up->genbuf, sizeof(up->genbuf), p->exstr);
+ release();
+ return readstr(offset, va, n, up->genbuf);
+ }
+ error(Egreg);
+ return 0;
+}
+
+static void
+mntscan(Mntwalk *mw, Pgrp *pg)
+{
+ Mount *t;
+ Mhead *f;
+ int nxt, i;
+ ulong last, bestmid;
+
+ rlock(&pg->ns);
+
+ nxt = 0;
+ bestmid = ~0;
+
+ last = 0;
+ if(mw->mh)
+ last = mw->cm->mountid;
+
+ for(i = 0; i < MNTHASH; i++) {
+ for(f = pg->mnthash[i]; f; f = f->hash) {
+ for(t = f->mount; t; t = t->next) {
+ if(mw->mh == 0 ||
+ (t->mountid > last && t->mountid < bestmid)) {
+ mw->cm = t;
+ mw->mh = f;
+ bestmid = mw->cm->mountid;
+ nxt = 1;
+ }
+ }
+ }
+ }
+ if(nxt == 0)
+ mw->mh = 0;
+
+ runlock(&pg->ns);
+}
+
+static long
+progwrite(Chan *c, void *va, long n, vlong offset)
+{
+ Prog *p, *f;
+ Heapqry *hq;
+ char buf[128];
+ Progctl *ctl;
+ char *b;
+ int i, pc;
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ Osenv *o;
+
+ USED(offset);
+ USED(va);
+
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+
+ acquire();
+ if(waserror()) {
+ release();
+ nexterror();
+ }
+ p = progpid(PID(c->qid));
+ if(p == nil)
+ error(Ethread);
+
+ switch(QID(c->qid)){
+ case Qctl:
+ cb = parsecmd(va, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, progcmd, nelem(progcmd));
+ switch(ct->index){
+ case CMkillgrp:
+ killgrp(p, "killed");
+ break;
+ case CMkill:
+ killprog(p, "killed");
+ break;
+ case CMrestricted:
+ p->flags |= Prestrict;
+ break;
+ case CMexceptions:
+ if(p->group->id != p->pid)
+ error(Eperm);
+ if(strcmp(cb->f[1], "propagate") == 0)
+ p->flags |= Ppropagate;
+ else if(strcmp(cb->f[1], "notifyleader") == 0)
+ p->flags |= Pnotifyleader;
+ else
+ error(Ebadctl);
+ break;
+ case CMprivate:
+ o = p->osenv;
+ o->pgrp->privatemem = 1;
+ break;
+ }
+ poperror();
+ free(cb);
+ break;
+ case Qdbgctl:
+ cb = parsecmd(va, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf == 1 && strncmp(cb->f[0], "step", 4) == 0)
+ ct = progdbgcmd;
+ else
+ ct = lookupcmd(cb, progdbgcmd, nelem(progdbgcmd));
+ switch(ct->index){
+ case CDstep:
+ if(cb->nf == 1)
+ i = strtoul(cb->f[0]+4, nil, 0);
+ else
+ i = strtoul(cb->f[1], nil, 0);
+ dbgstep(c->aux, p, i);
+ break;
+ case CDtoret:
+ f = currun();
+ i = calldepth(&p->R);
+ while(f->kill == nil) {
+ dbgstep(c->aux, p, 1024);
+ if(i > calldepth(&p->R))
+ break;
+ }
+ break;
+ case CDcont:
+ f = currun();
+ while(f->kill == nil)
+ dbgstep(c->aux, p, 1024);
+ break;
+ case CDstart:
+ dbgstart(p);
+ break;
+ case CDstop:
+ ctl = c->aux;
+ ctl->stop = 1;
+ break;
+ case CDunstop:
+ ctl = c->aux;
+ ctl->stop = 0;
+ break;
+ case CDbpt:
+ pc = strtoul(cb->f[3], nil, 10);
+ ctl = c->aux;
+ if(strcmp(cb->f[1], "set") == 0)
+ ctl->bpts = setbpt(ctl->bpts, cb->f[2], pc);
+ else if(strcmp(cb->f[1], "del") == 0)
+ ctl->bpts = delbpt(ctl->bpts, cb->f[2], pc);
+ else
+ error(Ebadctl);
+ break;
+ case CDmaim:
+ p->kill = "maim";
+ break;
+ }
+ poperror();
+ free(cb);
+ break;
+ case Qheap:
+ /*
+ * Heap query:
+ * addr.Fn
+ * pc+module.In
+ */
+ i = n;
+ if(i > sizeof(buf)-1)
+ i = sizeof(buf)-1;
+ memmove(buf, va, i);
+ buf[i] = '\0';
+ hq = c->aux;
+ hq->addr = strtoul(buf, &b, 0);
+ if(*b == '+')
+ hq->module = strtoul(b, &b, 0);
+ if(*b++ != '.')
+ error(Ebadctl);
+ hq->fmt = *b++;
+ hq->count = strtoul(b, nil, 0);
+ break;
+ default:
+ print("unknown qid in procwrite\n");
+ error(Egreg);
+ }
+ poperror();
+ release();
+ return n;
+}
+
+static Bpt*
+setbpt(Bpt *bpts, char *path, int pc)
+{
+ int n;
+ Bpt *b;
+
+ n = strlen(path);
+ b = mallocz(sizeof *b + n, 0);
+ if(b == nil)
+ return bpts;
+ b->pc = pc;
+ memmove(b->path, path, n+1);
+ b->file = b->path;
+ path = strrchr(b->path, '/');
+ if(path != nil)
+ b->file = path + 1;
+ b->next = bpts;
+ return b;
+}
+
+static Bpt*
+delbpt(Bpt *bpts, char *path, int pc)
+{
+ Bpt *b, **last;
+
+ last = &bpts;
+ for(b = bpts; b != nil; b = b->next){
+ if(b->pc == pc && strcmp(b->path, path) == 0) {
+ *last = b->next;
+ free(b);
+ break;
+ }
+ last = &b->next;
+ }
+ return bpts;
+}
+
+static void
+freebpts(Bpt *b)
+{
+ Bpt *next;
+
+ for(; b != nil; b = next){
+ next = b->next;
+ free(b);
+ }
+}
+
+static void
+telldbg(Progctl *ctl, char *msg)
+{
+ kstrcpy(ctl->msg, msg, ERRMAX);
+ ctl->debugger = nil;
+ wakeup(&ctl->r);
+}
+
+static void
+dbgstart(Prog *p)
+{
+ Osenv *o;
+ Progctl *ctl;
+
+ o = p->osenv;
+ ctl = o->debug;
+ if(ctl != nil && ctl->debugger != nil)
+ error("prog debugged");
+ if(p->state == Pdebug)
+ addrun(p);
+ o->debug = nil;
+ p->xec = xec;
+}
+
+static int
+xecdone(void *vc)
+{
+ Progctl *ctl = vc;
+
+ return ctl->debugger == nil;
+}
+
+static void
+dbgstep(Progctl *vctl, Prog *p, int n)
+{
+ Osenv *o;
+ Progctl *ctl;
+ char buf[ERRMAX+20], *msg;
+
+ if(p == currun())
+ error("cannot step yourself");
+
+ if(p->state == Pbroken)
+ error("prog broken");
+
+ ctl = vctl;
+ if(ctl->debugger != nil)
+ error("prog already debugged");
+
+ o = p->osenv;
+ if(o->debug == nil) {
+ o->debug = ctl;
+ p->xec = dbgxec;
+ }else if(o->debug != ctl)
+ error("prog already debugged");
+
+ ctl->step = n;
+ if(p->state == Pdebug)
+ addrun(p);
+ ctl->debugger = up;
+ strcpy(buf, "child: ");
+ msg = buf+7;
+ ctl->msg = msg;
+
+ /*
+ * wait for reply from dbgxec; release is okay because prog is now
+ * debugged, cannot exit.
+ */
+ if(waserror()){
+ acquire();
+ ctl->debugger = nil;
+ ctl->msg = nil;
+ o->debug = nil;
+ p->xec = xec;
+ nexterror();
+ }
+ release();
+ sleep(&ctl->r, xecdone, ctl);
+ poperror();
+ acquire();
+ if(msg[0] != '\0')
+ error(buf);
+}
+
+void
+dbgexit(Prog *kid, int broken, char *estr)
+{
+ int n;
+ Osenv *e;
+ Progctl *ctl;
+ char buf[ERRMAX+20];
+
+ e = kid->osenv;
+ ctl = e->debug;
+ e->debug = nil;
+ kid->xec = xec;
+
+ if(broken)
+ n = snprint(buf, sizeof(buf), "broken: %s", estr);
+ else
+ n = snprint(buf, sizeof(buf), "exited");
+ if(ctl->debugger)
+ telldbg(ctl, buf);
+ qproduce(ctl->q, buf, n);
+}
+
+static void
+dbgaddrun(Prog *p)
+{
+ Osenv *o;
+
+ p->state = Pdebug;
+ p->addrun = nil;
+ o = p->osenv;
+ if(o->rend != nil)
+ wakeup(o->rend);
+ o->rend = nil;
+}
+
+static int
+bdone(void *vp)
+{
+ Prog *p = vp;
+
+ return p->addrun == nil;
+}
+
+static void
+dbgblock(Prog *p)
+{
+ Osenv *o;
+ Progctl *ctl;
+
+ o = p->osenv;
+ ctl = o->debug;
+ qproduce(ctl->q, progstate[p->state], strlen(progstate[p->state]));
+ pushrun(p);
+ p->addrun = dbgaddrun;
+ o->rend = &up->sleep;
+
+ /*
+ * bdone(p) is safe after release because p is being debugged,
+ * so cannot exit.
+ */
+ if(waserror()){
+ acquire();
+ nexterror();
+ }
+ release();
+ if(o->rend != nil)
+ sleep(o->rend, bdone, p);
+ poperror();
+ acquire();
+ if(p->kill != nil)
+ error(p->kill);
+ ctl = o->debug;
+ if(ctl != nil)
+ qproduce(ctl->q, "run", 3);
+}
+
+void
+dbgxec(Prog *p)
+{
+ Bpt *b;
+ Prog *kid;
+ Osenv *env;
+ Progctl *ctl;
+ int op, pc, n;
+ char buf[ERRMAX+10];
+ extern void (*dec[])(void);
+
+ env = p->osenv;
+ ctl = env->debug;
+ ctl->ref++;
+ if(waserror()){
+ closedbgctl(ctl, p);
+ nexterror();
+ }
+
+ R = p->R;
+ R.MP = R.M->MP;
+
+ R.IC = p->quanta;
+ if((ulong)R.IC > ctl->step)
+ R.IC = ctl->step;
+ if(ctl->stop)
+ R.IC = 0;
+
+
+ buf[0] = '\0';
+
+ if(R.IC != 0 && R.M->compiled) {
+ comvec();
+ if(p != currun())
+ dbgblock(p);
+ goto save;
+ }
+
+ while(R.IC != 0) {
+ if(0)
+ print("step: %lux: %s %4ld %D\n",
+ (ulong)p, R.M->m->name, R.PC-R.M->prog, R.PC);
+
+ dec[R.PC->add]();
+ op = R.PC->op;
+ R.PC++;
+ optab[op]();
+
+ /*
+ * check notification about new progs
+ */
+ if(op == ISPAWN || op == IMSPAWN) {
+ /* pick up the kid from the end of the run queue */
+ kid = delruntail(Pdebug);
+ n = snprint(buf, sizeof buf, "new %d", kid->pid);
+ qproduce(ctl->q, buf, n);
+ buf[0] = '\0';
+ }
+ if(op == ILOAD) {
+ n = snprint(buf, sizeof buf, "load %s", string2c(*(String**)R.s));
+ qproduce(ctl->q, buf, n);
+ buf[0] = '\0';
+ }
+
+ /*
+ * check for returns at big steps
+ */
+ if(op == IRET)
+ R.IC = 1;
+
+ /*
+ * check for blocked progs
+ */
+ if(R.IC == 1 && p != currun())
+ dbgblock(p);
+ if(ctl->stop)
+ R.IC = 1;
+ R.IC--;
+ if(ctl->bpts == nil)
+ continue;
+ pc = R.PC - R.M->prog;
+ for(b = ctl->bpts; b != nil; b = b->next) {
+ if(pc == b->pc &&
+ (strcmp(R.M->m->path, b->path) == 0 ||
+ strcmp(R.M->m->path, b->file) == 0)) {
+ strcpy(buf, "breakpoint");
+ goto save;
+ }
+ }
+ }
+
+save:
+ if(ctl->stop)
+ strcpy(buf, "stopped");
+
+ p->R = R;
+
+ if(env->debug == nil) {
+ poperror();
+ return;
+ }
+
+ if(p == currun())
+ delrun(Pdebug);
+
+ telldbg(env->debug, buf);
+ poperror();
+ closedbgctl(env->debug, p);
+}
+
+Dev progdevtab = {
+ 'p',
+ "prog",
+
+ devreset,
+ devinit,
+ devshutdown,
+ progattach,
+ progwalk,
+ progstat,
+ progopen,
+ devcreate,
+ progclose,
+ progread,
+ devbread,
+ progwrite,
+ devbwrite,
+ devremove,
+ progwstat,
+};
diff --git a/os/port/devroot.c b/os/port/devroot.c
new file mode 100644
index 00000000..02fd8833
--- /dev/null
+++ b/os/port/devroot.c
@@ -0,0 +1,153 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+extern Rootdata rootdata[];
+extern Dirtab roottab[];
+extern int rootmaxq;
+
+static Chan*
+rootattach(char *spec)
+{
+ int i;
+ ulong len;
+ Rootdata *r;
+
+ if(*spec)
+ error(Ebadspec);
+ for (i = 0; i < rootmaxq; i++){
+ r = &rootdata[i];
+ if (r->sizep){
+ len = *r->sizep;
+ r->size = len;
+ roottab[i].length = len;
+ }
+ }
+ return devattach('/', spec);
+}
+
+static int
+rootgen(Chan *c, char *name, Dirtab *tab, int nd, int s, Dir *dp)
+{
+ int p, i;
+ Rootdata *r;
+
+ if(s == DEVDOTDOT){
+ p = rootdata[c->qid.path].dotdot;
+ c->qid.path = p;
+ c->qid.type = QTDIR;
+ name = "#/";
+ if(p != 0){
+ for(i = 0; i < rootmaxq; i++)
+ if(roottab[i].qid.path == c->qid.path){
+ name = roottab[i].name;
+ break;
+ }
+ }
+ devdir(c, c->qid, name, 0, eve, 0555, dp);
+ return 1;
+ }
+ if(name != nil){
+ isdir(c);
+ r = &rootdata[(int)c->qid.path];
+ tab = r->ptr;
+ for(i=0; i<r->size; i++, tab++)
+ if(strcmp(tab->name, name) == 0){
+ devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+ }
+ return -1;
+ }
+ if(s >= nd)
+ return -1;
+ tab += s;
+ devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+}
+
+static Walkqid*
+rootwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ ulong p;
+
+ p = c->qid.path;
+ if(nname == 0)
+ p = rootdata[p].dotdot;
+ return devwalk(c, nc, name, nname, rootdata[p].ptr, rootdata[p].size, rootgen);
+}
+
+static int
+rootstat(Chan *c, uchar *dp, int n)
+{
+ int p;
+
+ p = rootdata[c->qid.path].dotdot;
+ return devstat(c, dp, n, rootdata[p].ptr, rootdata[p].size, rootgen);
+}
+
+static Chan*
+rootopen(Chan *c, int omode)
+{
+ int p;
+
+ p = rootdata[c->qid.path].dotdot;
+ return devopen(c, omode, rootdata[p].ptr, rootdata[p].size, rootgen);
+}
+
+/*
+ * sysremove() knows this is a nop
+ */
+static void
+rootclose(Chan*)
+{
+}
+
+static long
+rootread(Chan *c, void *buf, long n, vlong offset)
+{
+ ulong p, len;
+ uchar *data;
+
+ p = c->qid.path;
+ if(c->qid.type & QTDIR)
+ return devdirread(c, buf, n, rootdata[p].ptr, rootdata[p].size, rootgen);
+ len = rootdata[p].size;
+ if(offset < 0 || offset >= len)
+ return 0;
+ if(offset+n > len)
+ n = len - offset;
+ data = rootdata[p].ptr;
+ memmove(buf, data+offset, n);
+ return n;
+}
+
+static long
+rootwrite(Chan*, void*, long, vlong)
+{
+ error(Eperm);
+ return 0;
+}
+
+Dev rootdevtab = {
+ '/',
+ "root",
+
+ devreset,
+ devinit,
+ devshutdown,
+ rootattach,
+ rootwalk,
+ rootstat,
+ rootopen,
+ devcreate,
+ rootclose,
+ rootread,
+ devbread,
+ rootwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devsd.c b/os/port/devsd.c
new file mode 100644
index 00000000..5bdc4048
--- /dev/null
+++ b/os/port/devsd.c
@@ -0,0 +1,1474 @@
+/*
+ * Storage Device.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "../port/error.h"
+
+#include "../port/sd.h"
+
+extern Dev sddevtab;
+extern SDifc* sdifc[];
+
+typedef struct SDevgrp {
+ SDev* dev;
+ int nunits; /* num units in dev */
+} SDevgrp;
+
+static SDevgrp* devs; /* all devices */
+static QLock devslock; /* insertion and removal of devices */
+static int ndevs; /* total number of devices in the system */
+
+enum {
+ Rawcmd,
+ Rawdata,
+ Rawstatus,
+};
+
+enum {
+ Qtopdir = 1, /* top level directory */
+ Qtopbase,
+ Qtopctl = Qtopbase,
+ Qtopstat,
+
+ Qunitdir, /* directory per unit */
+ Qunitbase,
+ Qctl = Qunitbase,
+ Qraw,
+ Qpart,
+
+ TypeLOG = 4,
+ NType = (1<<TypeLOG),
+ TypeMASK = (NType-1),
+ TypeSHIFT = 0,
+
+ PartLOG = 8,
+ NPart = (1<<PartLOG),
+ PartMASK = (NPart-1),
+ PartSHIFT = TypeLOG,
+
+ UnitLOG = 8,
+ NUnit = (1<<UnitLOG),
+ UnitMASK = (NUnit-1),
+ UnitSHIFT = (PartLOG+TypeLOG),
+
+ DevLOG = 8,
+ NDev = (1 << DevLOG),
+ DevMASK = (NDev-1),
+ DevSHIFT = (UnitLOG+PartLOG+TypeLOG),
+
+ Ncmd = 20,
+};
+
+#define TYPE(q) ((((ulong)(q).path)>>TypeSHIFT) & TypeMASK)
+#define PART(q) ((((ulong)(q).path)>>PartSHIFT) & PartMASK)
+#define UNIT(q) ((((ulong)(q).path)>>UnitSHIFT) & UnitMASK)
+#define DEV(q) ((((ulong)(q).path)>>DevSHIFT) & DevMASK)
+#define QID(d,u, p, t) (((d)<<DevSHIFT)|((u)<<UnitSHIFT)|\
+ ((p)<<PartSHIFT)|((t)<<TypeSHIFT))
+
+
+static void
+sdaddpart(SDunit* unit, char* name, ulong start, ulong end)
+{
+ SDpart *pp;
+ int i, partno;
+
+ /*
+ * Check name not already used
+ * and look for a free slot.
+ */
+ if(unit->part != nil){
+ partno = -1;
+ for(i = 0; i < unit->npart; i++){
+ pp = &unit->part[i];
+ if(!pp->valid){
+ if(partno == -1)
+ partno = i;
+ break;
+ }
+ if(strcmp(name, pp->name) == 0){
+ if(pp->start == start && pp->end == end)
+ return;
+ error(Ebadctl);
+ }
+ }
+ }
+ else{
+ if((unit->part = malloc(sizeof(SDpart)*SDnpart)) == nil)
+ error(Enomem);
+ unit->npart = SDnpart;
+ partno = 0;
+ }
+
+ /*
+ * If no free slot found then increase the
+ * array size (can't get here with unit->part == nil).
+ */
+ if(partno == -1){
+ if(unit->npart >= NPart)
+ error(Enomem);
+ if((pp = malloc(sizeof(SDpart)*(unit->npart+SDnpart))) == nil)
+ error(Enomem);
+ memmove(pp, unit->part, sizeof(SDpart)*unit->npart);
+ free(unit->part);
+ unit->part = pp;
+ partno = unit->npart;
+ unit->npart += SDnpart;
+ }
+
+ /*
+ * Check size and extent are valid.
+ */
+ if(start > end || end > unit->sectors)
+ error(Eio);
+ pp = &unit->part[partno];
+ pp->start = start;
+ pp->end = end;
+ kstrdup(&pp->name, name);
+ kstrdup(&pp->user, eve);
+ pp->perm = 0640;
+ pp->valid = 1;
+}
+
+static void
+sddelpart(SDunit* unit, char* name)
+{
+ int i;
+ SDpart *pp;
+
+ /*
+ * Look for the partition to delete.
+ * Can't delete if someone still has it open.
+ */
+ pp = unit->part;
+ for(i = 0; i < unit->npart; i++){
+ if(strcmp(name, pp->name) == 0)
+ break;
+ pp++;
+ }
+ if(i >= unit->npart)
+ error(Ebadctl);
+ if(strcmp(up->env->user, pp->user) && !iseve())
+ error(Eperm);
+ pp->valid = 0;
+ pp->vers++;
+}
+
+static int
+sdinitpart(SDunit* unit)
+{
+ int i, nf;
+ ulong start, end;
+ char *f[4], *p, *q, buf[10];
+
+ unit->vers++;
+ unit->sectors = unit->secsize = 0;
+ if(unit->part){
+ for(i = 0; i < unit->npart; i++){
+ unit->part[i].valid = 0;
+ unit->part[i].vers++;
+ }
+ }
+
+ if(unit->inquiry[0] & 0xC0)
+ return 0;
+ switch(unit->inquiry[0] & 0x1F){
+ case 0x00: /* DA */
+ case 0x04: /* WORM */
+ case 0x05: /* CD-ROM */
+ case 0x07: /* MO */
+ break;
+ default:
+ return 0;
+ }
+
+ if(unit->dev->ifc->online)
+ unit->dev->ifc->online(unit);
+ if(unit->sectors){
+ sdaddpart(unit, "data", 0, unit->sectors);
+
+ /*
+ * Use partitions passed from boot program,
+ * e.g.
+ * sdC0part=dos 63 123123/plan9 123123 456456
+ * This happens before /boot sets hostname so the
+ * partitions will have the null-string for user.
+ * The gen functions patch it up.
+ */
+ snprint(buf, sizeof buf, "%spart", unit->name);
+ for(p = getconf(buf); p != nil; p = q){
+ if(q = strchr(p, '/'))
+ *q++ = '\0';
+ nf = tokenize(p, f, nelem(f));
+ if(nf < 3)
+ continue;
+
+ start = strtoul(f[1], 0, 0);
+ end = strtoul(f[2], 0, 0);
+ if(!waserror()){
+ sdaddpart(unit, f[0], start, end);
+ poperror();
+ }
+ }
+ }
+
+ return 1;
+}
+
+static SDev*
+sdgetdev(int idno)
+{
+ SDev *sdev;
+ int i;
+
+ qlock(&devslock);
+ for(i = 0; i != ndevs; i++)
+ if(devs[i].dev->idno == idno)
+ break;
+
+ if(i == ndevs)
+ sdev = nil;
+ else{
+ sdev = devs[i].dev;
+ incref(&sdev->r);
+ }
+ qunlock(&devslock);
+ return sdev;
+}
+
+static SDunit*
+sdgetunit(SDev* sdev, int subno)
+{
+ SDunit *unit;
+ char buf[32];
+
+ /*
+ * Associate a unit with a given device and sub-unit
+ * number on that device.
+ * The device will be probed if it has not already been
+ * successfully accessed.
+ */
+ qlock(&sdev->unitlock);
+ if(subno > sdev->nunit){
+ qunlock(&sdev->unitlock);
+ return nil;
+ }
+
+ unit = sdev->unit[subno];
+ if(unit == nil){
+ /*
+ * Probe the unit only once. This decision
+ * may be a little severe and reviewed later.
+ */
+ if(sdev->unitflg[subno]){
+ qunlock(&sdev->unitlock);
+ return nil;
+ }
+ if((unit = malloc(sizeof(SDunit))) == nil){
+ qunlock(&sdev->unitlock);
+ return nil;
+ }
+ sdev->unitflg[subno] = 1;
+
+ snprint(buf, sizeof(buf), "%s%d", sdev->name, subno);
+ kstrdup(&unit->name, buf);
+ kstrdup(&unit->user, eve);
+ unit->perm = 0555;
+ unit->subno = subno;
+ unit->dev = sdev;
+
+ if(sdev->enabled == 0 && sdev->ifc->enable)
+ sdev->ifc->enable(sdev);
+ sdev->enabled = 1;
+
+ /*
+ * No need to lock anything here as this is only
+ * called before the unit is made available in the
+ * sdunit[] array.
+ */
+ if(unit->dev->ifc->verify(unit) == 0){
+ qunlock(&sdev->unitlock);
+ free(unit);
+ return nil;
+ }
+ sdev->unit[subno] = unit;
+ }
+ qunlock(&sdev->unitlock);
+ return unit;
+}
+
+static void
+sdreset(void)
+{
+ int i;
+ SDev *sdev, *tail, *sdlist;
+
+ /*
+ * Probe all configured controllers and make a list
+ * of devices found, accumulating a possible maximum number
+ * of units attached and marking each device with an index
+ * into the linear top-level directory array of units.
+ */
+ tail = sdlist = nil;
+ for(i = 0; sdifc[i] != nil; i++){
+ if(sdifc[i]->pnp == nil || (sdev = sdifc[i]->pnp()) == nil)
+ continue;
+ if(sdlist != nil)
+ tail->next = sdev;
+ else
+ sdlist = sdev;
+ for(tail = sdev; tail->next != nil; tail = tail->next){
+ tail->unit = (SDunit**)malloc(tail->nunit * sizeof(SDunit*));
+ tail->unitflg = (int*)malloc(tail->nunit * sizeof(int));
+ assert(tail->unit && tail->unitflg);
+ ndevs++;
+ }
+ tail->unit = (SDunit**)malloc(tail->nunit * sizeof(SDunit*));
+ tail->unitflg = (int*)malloc(tail->nunit * sizeof(int));
+ ndevs++;
+ }
+
+ /*
+ * Legacy and option code goes here. This will be hard...
+ */
+
+ /*
+ * The maximum number of possible units is known, allocate
+ * placeholders for their datastructures; the units will be
+ * probed and structures allocated when attached.
+ * Allocate controller names for the different types.
+ */
+ if(ndevs == 0)
+ return;
+ for(i = 0; sdifc[i] != nil; i++){
+ /*
+ * BUG: no check is made here or later when a
+ * unit is attached that the id and name are set.
+ */
+ if(sdifc[i]->id)
+ sdifc[i]->id(sdlist);
+ }
+
+ /*
+ * The IDs have been set, unlink the sdlist and copy the spec to
+ * the devtab.
+ */
+ devs = (SDevgrp*)malloc(ndevs * sizeof(SDevgrp));
+ memset(devs, 0, ndevs * sizeof(SDevgrp));
+ i = 0;
+ while(sdlist != nil){
+ devs[i].dev = sdlist;
+ devs[i].nunits = sdlist->nunit;
+ sdlist = sdlist->next;
+ devs[i].dev->next = nil;
+ i++;
+ }
+}
+
+static int
+sd2gen(Chan* c, int i, Dir* dp)
+{
+ Qid q;
+ vlong l;
+ SDpart *pp;
+ SDperm *perm;
+ SDunit *unit;
+ SDev *sdev;
+ int rv;
+
+ sdev = sdgetdev(DEV(c->qid));
+ assert(sdev);
+ unit = sdev->unit[UNIT(c->qid)];
+
+ rv = -1;
+ switch(i){
+ case Qctl:
+ mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qctl),
+ unit->vers, QTFILE);
+ perm = &unit->ctlperm;
+ if(emptystr(perm->user)){
+ kstrdup(&perm->user, eve);
+ perm->perm = 0640;
+ }
+ devdir(c, q, "ctl", 0, perm->user, perm->perm, dp);
+ rv = 1;
+ break;
+
+ case Qraw:
+ mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qraw),
+ unit->vers, QTFILE);
+ perm = &unit->rawperm;
+ if(emptystr(perm->user)){
+ kstrdup(&perm->user, eve);
+ perm->perm = DMEXCL|0600;
+ }
+ devdir(c, q, "raw", 0, perm->user, perm->perm, dp);
+ rv = 1;
+ break;
+
+ case Qpart:
+ pp = &unit->part[PART(c->qid)];
+ l = (pp->end - pp->start) * (vlong)unit->secsize;
+ mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qpart),
+ unit->vers+pp->vers, QTFILE);
+ if(emptystr(pp->user))
+ kstrdup(&pp->user, eve);
+ devdir(c, q, pp->name, l, pp->user, pp->perm, dp);
+ rv = 1;
+ break;
+ }
+
+ decref(&sdev->r);
+ return rv;
+}
+
+static int
+sd1gen(Chan* c, int i, Dir* dp)
+{
+ Qid q;
+
+ switch(i){
+ case Qtopctl:
+ mkqid(&q, QID(0, 0, 0, Qtopctl), 0, QTFILE);
+ devdir(c, q, "sdctl", 0, eve, 0640, dp);
+ return 1;
+ case Qtopstat:
+ mkqid(&q, QID(0, 0, 0, Qtopstat), 0, QTFILE);
+ devdir(c, q, "sdstat", 0, eve, 0640, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static int
+sdgen(Chan* c, char*, Dirtab*, int, int s, Dir* dp)
+{
+ Qid q;
+ vlong l;
+ int i, r;
+ SDpart *pp;
+ SDunit *unit;
+ SDev *sdev;
+
+ switch(TYPE(c->qid)){
+ case Qtopdir:
+ if(s == DEVDOTDOT){
+ mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR);
+ sprint(up->genbuf, "#%C", sddevtab.dc);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+
+ if(s == 0 || s == 1)
+ return sd1gen(c, s + Qtopbase, dp);
+ s -= 2;
+
+ qlock(&devslock);
+ for(i = 0; i != ndevs; i++){
+ if(s < devs[i].nunits)
+ break;
+ s -= devs[i].nunits;
+ }
+
+ if(i == ndevs){
+ /* Run of the end of the list */
+ qunlock(&devslock);
+ return -1;
+ }
+
+ if((sdev = devs[i].dev) == nil){
+ qunlock(&devslock);
+ return 0;
+ }
+
+ incref(&sdev->r);
+ qunlock(&devslock);
+
+ if((unit = sdev->unit[s]) == nil)
+ if((unit = sdgetunit(sdev, s)) == nil){
+ decref(&sdev->r);
+ return 0;
+ }
+
+ mkqid(&q, QID(sdev->idno, s, 0, Qunitdir), 0, QTDIR);
+ if(emptystr(unit->user))
+ kstrdup(&unit->user, eve);
+ devdir(c, q, unit->name, 0, unit->user, unit->perm, dp);
+ decref(&sdev->r);
+ return 1;
+
+ case Qunitdir:
+ if(s == DEVDOTDOT){
+ mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR);
+ sprint(up->genbuf, "#%C", sddevtab.dc);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+
+ if((sdev = sdgetdev(DEV(c->qid))) == nil){
+ devdir(c, q, "unavailable", 0, eve, 0, dp);
+ return 1;
+ }
+
+ unit = sdev->unit[UNIT(c->qid)];
+ qlock(&unit->ctl);
+
+ /*
+ * Check for media change.
+ * If one has already been detected, sectors will be zero.
+ * If there is one waiting to be detected, online
+ * will return > 1.
+ * Online is a bit of a large hammer but does the job.
+ */
+ if(unit->sectors == 0
+ || (unit->dev->ifc->online && unit->dev->ifc->online(unit) > 1))
+ sdinitpart(unit);
+
+ i = s+Qunitbase;
+ if(i < Qpart){
+ r = sd2gen(c, i, dp);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ return r;
+ }
+ i -= Qpart;
+ if(unit->part == nil || i >= unit->npart){
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ break;
+ }
+ pp = &unit->part[i];
+ if(!pp->valid){
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ return 0;
+ }
+ l = (pp->end - pp->start) * (vlong)unit->secsize;
+ mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), i, Qpart),
+ unit->vers+pp->vers, QTFILE);
+ if(emptystr(pp->user))
+ kstrdup(&pp->user, eve);
+ devdir(c, q, pp->name, l, pp->user, pp->perm, dp);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ return 1;
+ case Qraw:
+ case Qctl:
+ case Qpart:
+ if((sdev = sdgetdev(DEV(c->qid))) == nil){
+ devdir(c, q, "unavailable", 0, eve, 0, dp);
+ return 1;
+ }
+ unit = sdev->unit[UNIT(c->qid)];
+ qlock(&unit->ctl);
+ r = sd2gen(c, TYPE(c->qid), dp);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ return r;
+ case Qtopctl:
+ case Qtopstat:
+ return sd1gen(c, TYPE(c->qid), dp);
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+static Chan*
+sdattach(char* spec)
+{
+ Chan *c;
+ char *p;
+ SDev *sdev;
+ int idno, subno, i;
+
+ if(ndevs == 0 || *spec == '\0'){
+ c = devattach(sddevtab.dc, spec);
+ mkqid(&c->qid, QID(0, 0, 0, Qtopdir), 0, QTDIR);
+ return c;
+ }
+
+ if(spec[0] != 's' || spec[1] != 'd')
+ error(Ebadspec);
+ idno = spec[2];
+ subno = strtol(&spec[3], &p, 0);
+ if(p == &spec[3])
+ error(Ebadspec);
+
+ qlock(&devslock);
+ for (sdev = nil, i = 0; i != ndevs; i++)
+ if((sdev = devs[i].dev) != nil && sdev->idno == idno)
+ break;
+
+ if(i == ndevs || subno >= sdev->nunit || sdgetunit(sdev, subno) == nil){
+ qunlock(&devslock);
+ error(Enonexist);
+ }
+ incref(&sdev->r);
+ qunlock(&devslock);
+
+ c = devattach(sddevtab.dc, spec);
+ mkqid(&c->qid, QID(sdev->idno, subno, 0, Qunitdir), 0, QTDIR);
+ c->dev = (sdev->idno << UnitLOG) + subno;
+ decref(&sdev->r);
+ return c;
+}
+
+static Walkqid*
+sdwalk(Chan* c, Chan* nc, char** name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, sdgen);
+}
+
+static int
+sdstat(Chan* c, uchar* db, int n)
+{
+ return devstat(c, db, n, nil, 0, sdgen);
+}
+
+static Chan*
+sdopen(Chan* c, int omode)
+{
+ SDpart *pp;
+ SDunit *unit;
+ SDev *sdev;
+ uchar tp;
+
+ c = devopen(c, omode, 0, 0, sdgen);
+ if((tp = TYPE(c->qid)) != Qctl && tp != Qraw && tp != Qpart)
+ return c;
+
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+ unit = sdev->unit[UNIT(c->qid)];
+
+ switch(TYPE(c->qid)){
+ case Qctl:
+ c->qid.vers = unit->vers;
+ break;
+ case Qraw:
+ c->qid.vers = unit->vers;
+ if(_tas(&unit->rawinuse) != 0){
+ c->flag &= ~COPEN;
+ error(Einuse);
+ }
+ unit->state = Rawcmd;
+ break;
+ case Qpart:
+ qlock(&unit->ctl);
+ if(waserror()){
+ qunlock(&unit->ctl);
+ c->flag &= ~COPEN;
+ nexterror();
+ }
+ pp = &unit->part[PART(c->qid)];
+ c->qid.vers = unit->vers+pp->vers;
+ qunlock(&unit->ctl);
+ poperror();
+ break;
+ }
+ decref(&sdev->r);
+ return c;
+}
+
+static void
+sdclose(Chan* c)
+{
+ SDunit *unit;
+ SDev *sdev;
+
+ if(c->qid.type & QTDIR)
+ return;
+ if(!(c->flag & COPEN))
+ return;
+
+ switch(TYPE(c->qid)){
+ default:
+ break;
+ case Qraw:
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev){
+ unit = sdev->unit[UNIT(c->qid)];
+ unit->rawinuse = 0;
+ decref(&sdev->r);
+ }
+ break;
+ }
+}
+
+static long
+sdbio(Chan* c, int write, char* a, long len, vlong off)
+{
+ int nchange;
+ long l;
+ uchar *b;
+ SDpart *pp;
+ SDunit *unit;
+ SDev *sdev;
+ ulong bno, max, nb, offset;
+
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+ unit = sdev->unit[UNIT(c->qid)];
+ if(unit == nil)
+ error(Enonexist);
+
+ nchange = 0;
+ qlock(&unit->ctl);
+ while(waserror()){
+ /* notification of media change; go around again */
+ if(strcmp(up->env->errstr, Eio) == 0 && unit->sectors == 0 && nchange++ == 0){
+ sdinitpart(unit);
+ continue;
+ }
+
+ /* other errors; give up */
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ nexterror();
+ }
+ pp = &unit->part[PART(c->qid)];
+ if(unit->vers+pp->vers != c->qid.vers)
+ error(Eio);
+
+ /*
+ * Check the request is within bounds.
+ * Removeable drives are locked throughout the I/O
+ * in case the media changes unexpectedly.
+ * Non-removeable drives are not locked during the I/O
+ * to allow the hardware to optimise if it can; this is
+ * a little fast and loose.
+ * It's assumed that non-removeable media parameters
+ * (sectors, secsize) can't change once the drive has
+ * been brought online.
+ */
+ bno = (off/unit->secsize) + pp->start;
+ nb = ((off+len+unit->secsize-1)/unit->secsize) + pp->start - bno;
+ max = SDmaxio/unit->secsize;
+ if(nb > max)
+ nb = max;
+ if(bno+nb > pp->end)
+ nb = pp->end - bno;
+ if(bno >= pp->end || nb == 0){
+ if(write)
+ error(Eio);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ poperror();
+ return 0;
+ }
+ if(!(unit->inquiry[1] & 0x80)){
+ qunlock(&unit->ctl);
+ poperror();
+ }
+
+ b = sdmalloc(nb*unit->secsize);
+ if(b == nil)
+ error(Enomem);
+ if(waserror()){
+ sdfree(b);
+ if(!(unit->inquiry[1] & 0x80))
+ decref(&sdev->r); /* gadverdamme! */
+ nexterror();
+ }
+
+ offset = off%unit->secsize;
+ if(offset+len > nb*unit->secsize)
+ len = nb*unit->secsize - offset;
+ if(write){
+ if(offset || (len%unit->secsize)){
+ l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
+ if(l < 0)
+ error(Eio);
+ if(l < (nb*unit->secsize)){
+ nb = l/unit->secsize;
+ l = nb*unit->secsize - offset;
+ if(len > l)
+ len = l;
+ }
+ }
+ memmove(b+offset, a, len);
+ l = unit->dev->ifc->bio(unit, 0, 1, b, nb, bno);
+ if(l < 0)
+ error(Eio);
+ if(l < offset)
+ len = 0;
+ else if(len > l - offset)
+ len = l - offset;
+ }
+ else{
+ l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
+ if(l < 0)
+ error(Eio);
+ if(l < offset)
+ len = 0;
+ else if(len > l - offset)
+ len = l - offset;
+ memmove(a, b+offset, len);
+ }
+ sdfree(b);
+ poperror();
+
+ if(unit->inquiry[1] & 0x80){
+ qunlock(&unit->ctl);
+ poperror();
+ }
+
+ decref(&sdev->r);
+ return len;
+}
+
+static long
+sdrio(SDreq* r, void* a, long n)
+{
+ void *data;
+
+ if(n >= SDmaxio || n < 0)
+ error(Etoobig);
+
+ data = nil;
+ if(n){
+ if((data = sdmalloc(n)) == nil)
+ error(Enomem);
+ if(r->write)
+ memmove(data, a, n);
+ }
+ r->data = data;
+ r->dlen = n;
+
+ if(waserror()){
+ if(data != nil){
+ sdfree(data);
+ r->data = nil;
+ }
+ nexterror();
+ }
+
+ if(r->unit->dev->ifc->rio(r) != SDok)
+ error(Eio);
+
+ if(!r->write && r->rlen > 0)
+ memmove(a, data, r->rlen);
+ if(data != nil){
+ sdfree(data);
+ r->data = nil;
+ }
+ poperror();
+
+ return r->rlen;
+}
+
+static long
+sdread(Chan *c, void *a, long n, vlong off)
+{
+ char *p, *e, *buf;
+ SDpart *pp;
+ SDunit *unit;
+ SDev *sdev;
+ ulong offset;
+ int i, l, status;
+
+ offset = off;
+ switch(TYPE(c->qid)){
+ default:
+ error(Eperm);
+ case Qtopstat:
+ p = buf = malloc(READSTR);
+ assert(p);
+ e = p + READSTR;
+ qlock(&devslock);
+ for(i = 0; i != ndevs; i++){
+ SDev *sdev = devs[i].dev;
+
+ if(sdev->ifc->stat)
+ p = sdev->ifc->stat(sdev, p, e);
+ else
+ p = seprint(e, "%s; no statistics available\n", sdev->name);
+ }
+ qunlock(&devslock);
+ n = readstr(off, a, n, buf);
+ free(buf);
+ return n;
+
+ case Qtopdir:
+ case Qunitdir:
+ return devdirread(c, a, n, 0, 0, sdgen);
+
+ case Qctl:
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+
+ unit = sdev->unit[UNIT(c->qid)];
+ p = malloc(READSTR);
+ l = snprint(p, READSTR, "inquiry %.48s\n",
+ (char*)unit->inquiry+8);
+ qlock(&unit->ctl);
+ /*
+ * If there's a device specific routine it must
+ * provide all information pertaining to night geometry
+ * and the garscadden trains.
+ */
+ if(unit->dev->ifc->rctl)
+ l += unit->dev->ifc->rctl(unit, p+l, READSTR-l);
+ if(unit->sectors == 0)
+ sdinitpart(unit);
+ if(unit->sectors){
+ if(unit->dev->ifc->rctl == nil)
+ l += snprint(p+l, READSTR-l,
+ "geometry %ld %ld\n",
+ unit->sectors, unit->secsize);
+ pp = unit->part;
+ for(i = 0; i < unit->npart; i++){
+ if(pp->valid)
+ l += snprint(p+l, READSTR-l,
+ "part %s %lud %lud\n",
+ pp->name, pp->start, pp->end);
+ pp++;
+ }
+ }
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ l = readstr(offset, a, n, p);
+ free(p);
+ return l;
+
+ case Qraw:
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+
+ unit = sdev->unit[UNIT(c->qid)];
+ qlock(&unit->raw);
+ if(waserror()){
+ qunlock(&unit->raw);
+ decref(&sdev->r);
+ nexterror();
+ }
+ if(unit->state == Rawdata){
+ unit->state = Rawstatus;
+ i = sdrio(unit->req, a, n);
+ }
+ else if(unit->state == Rawstatus){
+ status = unit->req->status;
+ unit->state = Rawcmd;
+ free(unit->req);
+ unit->req = nil;
+ i = readnum(0, a, n, status, NUMSIZE);
+ } else
+ i = 0;
+ qunlock(&unit->raw);
+ decref(&sdev->r);
+ poperror();
+ return i;
+
+ case Qpart:
+ return sdbio(c, 0, a, n, off);
+ }
+
+ return 0;
+}
+
+typedef struct Confdata Confdata;
+struct Confdata {
+ int on;
+ char* spec;
+ DevConf cf;
+};
+
+static void
+parseswitch(Confdata* cd, char* option)
+{
+ if(!strcmp("on", option))
+ cd->on = 1;
+ else if(!strcmp("off", option))
+ cd->on = 0;
+ else
+ error(Ebadarg);
+}
+
+static void
+parsespec(Confdata* cd, char* option)
+{
+ if(strlen(option) > 1)
+ error(Ebadarg);
+ cd->spec = option;
+}
+
+static Devport*
+getnewport(DevConf* dc)
+{
+ Devport *p;
+
+ p = (Devport *)malloc((dc->nports + 1) * sizeof(Devport));
+ if(dc->nports > 0){
+ memmove(p, dc->ports, dc->nports * sizeof(Devport));
+ free(dc->ports);
+ }
+ dc->ports = p;
+ p = &dc->ports[dc->nports++];
+ p->size = -1;
+ p->port = (ulong)-1;
+ return p;
+}
+
+static void
+parseport(Confdata* cd, char* option)
+{
+ char *e;
+ Devport *p;
+
+ if(cd->cf.nports == 0 || cd->cf.ports[cd->cf.nports-1].port != (ulong)-1)
+ p = getnewport(&cd->cf);
+ else
+ p = &cd->cf.ports[cd->cf.nports-1];
+ p->port = strtol(option, &e, 0);
+ if(e == nil || *e != '\0')
+ error(Ebadarg);
+}
+
+static void
+parsesize(Confdata* cd, char* option)
+{
+ char *e;
+ Devport *p;
+
+ if(cd->cf.nports == 0 || cd->cf.ports[cd->cf.nports-1].size != -1)
+ p = getnewport(&cd->cf);
+ else
+ p = &cd->cf.ports[cd->cf.nports-1];
+ p->size = (int)strtol(option, &e, 0);
+ if(e == nil || *e != '\0')
+ error(Ebadarg);
+}
+
+static void
+parseirq(Confdata* cd, char* option)
+{
+ char *e;
+
+ cd->cf.intnum = strtoul(option, &e, 0);
+ if(e == nil || *e != '\0')
+ error(Ebadarg);
+}
+
+static void
+parsetype(Confdata* cd, char* option)
+{
+ cd->cf.type = option;
+}
+
+static struct {
+ char *option;
+ void (*parse)(Confdata*, char*);
+} options[] = {
+ { "switch", parseswitch, },
+ { "spec", parsespec, },
+ { "port", parseport, },
+ { "size", parsesize, },
+ { "irq", parseirq, },
+ { "type", parsetype, },
+};
+
+static long
+sdwrite(Chan* c, void* a, long n, vlong off)
+{
+ Cmdbuf *cb;
+ SDreq *req;
+ SDunit *unit;
+ SDev *sdev;
+ ulong end, start;
+
+ switch(TYPE(c->qid)){
+ default:
+ error(Eperm);
+ case Qtopctl: {
+ Confdata cd;
+ char buf[256], *field[Ncmd];
+ int nf, i, j;
+
+ memset(&cd, 0, sizeof(Confdata));
+ if(n > sizeof(buf)-1) n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = '\0';
+
+ cd.on = -1;
+ cd.spec = '\0';
+ memset(&cd.cf, 0, sizeof(DevConf));
+
+ nf = tokenize(buf, field, Ncmd);
+ for(i = 0; i < nf; i++){
+ char *opt = field[i++];
+ if(i >= nf)
+ error(Ebadarg);
+ for(j = 0; j != nelem(options); j++)
+ if(!strcmp(opt, options[j].option))
+ break;
+
+ if(j == nelem(options))
+ error(Ebadarg);
+ options[j].parse(&cd, field[i]);
+ }
+
+ if(cd.on < 0)
+ error(Ebadarg);
+
+ if(cd.on){
+ if(cd.spec == '\0' || cd.cf.nports == 0 ||
+ cd.cf.intnum == 0 || cd.cf.type == nil)
+ error(Ebadarg);
+ }
+ else{
+ if(cd.spec == '\0')
+ error(Ebadarg);
+ }
+
+ if(sddevtab.config == nil)
+ error("No configuration function");
+ sddevtab.config(cd.on, cd.spec, &cd.cf);
+ break;
+ }
+ case Qctl:
+ cb = parsecmd(a, n);
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+ unit = sdev->unit[UNIT(c->qid)];
+
+ qlock(&unit->ctl);
+ if(waserror()){
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ free(cb);
+ nexterror();
+ }
+ if(unit->vers != c->qid.vers)
+ error(Eio);
+
+ if(cb->nf < 1)
+ error(Ebadctl);
+ if(strcmp(cb->f[0], "part") == 0){
+ if(cb->nf != 4)
+ error(Ebadctl);
+ if(unit->sectors == 0 && !sdinitpart(unit))
+ error(Eio);
+ start = strtoul(cb->f[2], 0, 0);
+ end = strtoul(cb->f[3], 0, 0);
+ sdaddpart(unit, cb->f[1], start, end);
+ }
+ else if(strcmp(cb->f[0], "delpart") == 0){
+ if(cb->nf != 2 || unit->part == nil)
+ error(Ebadctl);
+ sddelpart(unit, cb->f[1]);
+ }
+ else if(unit->dev->ifc->wctl)
+ unit->dev->ifc->wctl(unit, cb);
+ else
+ error(Ebadctl);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ poperror();
+ free(cb);
+ break;
+
+ case Qraw:
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+ unit = sdev->unit[UNIT(c->qid)];
+ qlock(&unit->raw);
+ if(waserror()){
+ qunlock(&unit->raw);
+ decref(&sdev->r);
+ nexterror();
+ }
+ switch(unit->state){
+ case Rawcmd:
+ if(n < 6 || n > sizeof(req->cmd))
+ error(Ebadarg);
+ if((req = malloc(sizeof(SDreq))) == nil)
+ error(Enomem);
+ req->unit = unit;
+ memmove(req->cmd, a, n);
+ req->clen = n;
+ req->flags = SDnosense;
+ req->status = ~0;
+
+ unit->req = req;
+ unit->state = Rawdata;
+ break;
+
+ case Rawstatus:
+ unit->state = Rawcmd;
+ free(unit->req);
+ unit->req = nil;
+ error(Ebadusefd);
+
+ case Rawdata:
+ if(unit->state != Rawdata)
+ error(Ebadusefd);
+ unit->state = Rawstatus;
+
+ unit->req->write = 1;
+ n = sdrio(unit->req, a, n);
+ }
+ qunlock(&unit->raw);
+ decref(&sdev->r);
+ poperror();
+ break;
+ case Qpart:
+ return sdbio(c, 1, a, n, off);
+ }
+
+ return n;
+}
+
+static int
+sdwstat(Chan* c, uchar* dp, int n)
+{
+ Dir *d;
+ SDpart *pp;
+ SDperm *perm;
+ SDunit *unit;
+ SDev *sdev;
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+
+ sdev = sdgetdev(DEV(c->qid));
+ if(sdev == nil)
+ error(Enonexist);
+ unit = sdev->unit[UNIT(c->qid)];
+ qlock(&unit->ctl);
+ d = nil;
+ if(waserror()){
+ free(d);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ nexterror();
+ }
+
+ switch(TYPE(c->qid)){
+ default:
+ error(Eperm);
+ case Qctl:
+ perm = &unit->ctlperm;
+ break;
+ case Qraw:
+ perm = &unit->rawperm;
+ break;
+ case Qpart:
+ pp = &unit->part[PART(c->qid)];
+ if(unit->vers+pp->vers != c->qid.vers)
+ error(Enonexist);
+ perm = &pp->SDperm;
+ break;
+ }
+
+ if(strcmp(up->env->user, perm->user) && !iseve())
+ error(Eperm);
+
+ d = smalloc(sizeof(Dir)+n);
+ n = convM2D(dp, n, &d[0], (char*)&d[1]);
+ if(n == 0)
+ error(Eshortstat);
+ if(!emptystr(d[0].uid))
+ kstrdup(&perm->user, d[0].uid);
+ if(d[0].mode != ~0UL)
+ perm->perm = (perm->perm & ~0777) | (d[0].mode & 0777);
+
+ free(d);
+ qunlock(&unit->ctl);
+ decref(&sdev->r);
+ poperror();
+ return n;
+}
+
+static char
+getspec(char base)
+{
+ while(1){
+ int i;
+ SDev *sdev;
+
+ for(i = 0; i != ndevs; i++)
+ if((sdev = devs[i].dev) != nil && (char)sdev->idno == base)
+ break;
+
+ if(i == ndevs)
+ return base;
+ base++;
+ }
+ return '\0';
+}
+
+static int
+configure(char* spec, DevConf* cf)
+{
+ ISAConf isa;
+ SDevgrp *tmpdevs;
+ SDev *tail, *sdev, *(*probe)(DevConf*);
+ char *p, name[32];
+ int i, nnew;
+
+ if((p = strchr(cf->type, '/')) != nil)
+ *p++ = '\0';
+
+ for(i = 0; sdifc[i] != nil; i++)
+ if(!strcmp(sdifc[i]->name, cf->type))
+ break;
+
+ if(sdifc[i] == nil)
+ error("type not found");
+
+ if((probe = sdifc[i]->probe) == nil)
+ error("No probe function");
+
+ if(p){
+ /* Try to find the card on the ISA bus. This code really belongs
+ in sdata and I'll move it later. Really! */
+ memset(&isa, 0, sizeof(isa));
+ isa.port = cf->ports[0].port;
+ isa.irq = cf->intnum;
+
+ if(pcmspecial(p, &isa) < 0)
+ error("Cannot find controller");
+ }
+
+ qlock(&devslock);
+ if(waserror()){
+ qunlock(&devslock);
+ nexterror();
+ }
+
+ for(i = 0; i != ndevs; i++)
+ if((sdev = devs[i].dev) != nil && sdev->idno == *spec)
+ break;
+ if(i != ndevs)
+ error(Eexist);
+
+ if((sdev = (*probe)(cf)) == nil)
+ error("Cannot probe controller");
+ poperror();
+
+ nnew = 0;
+ tail = sdev;
+ while(tail){
+ nnew++;
+ tail = tail->next;
+ }
+
+ tmpdevs = (SDevgrp*)malloc((ndevs + nnew) * sizeof(SDevgrp));
+ memmove(tmpdevs, devs, ndevs * sizeof(SDevgrp));
+ free(devs);
+ devs = tmpdevs;
+
+ while(sdev){
+ /* Assign `spec' to the device */
+ *spec = getspec(*spec);
+ snprint(name, sizeof(name), "sd%c", *spec);
+ kstrdup(&sdev->name, name);
+ sdev->idno = *spec;
+ sdev->unit = (SDunit **)malloc(sdev->nunit * sizeof(SDunit*));
+ sdev->unitflg = (int *)malloc(sdev->nunit * sizeof(int));
+ assert(sdev->unit && sdev->unitflg);
+
+ devs[ndevs].dev = sdev;
+ devs[ndevs].nunits = sdev->nunit;
+ sdev = sdev->next;
+ devs[ndevs].dev->next = nil;
+ ndevs++;
+ }
+
+ qunlock(&devslock);
+ return 0;
+}
+
+static int
+unconfigure(char* spec)
+{
+ int i;
+ SDev *sdev;
+
+ qlock(&devslock);
+ if(waserror()){
+ qunlock(&devslock);
+ nexterror();
+ }
+
+ sdev = nil;
+ for(i = 0; i != ndevs; i++)
+ if((sdev = devs[i].dev) != nil && sdev->idno == *spec)
+ break;
+
+ if(i == ndevs)
+ error(Enonexist);
+
+ if(sdev->r.ref)
+ error(Einuse);
+
+ /* make sure no interrupts arrive anymore before removing resources */
+ if(sdev->enabled && sdev->ifc->disable)
+ sdev->ifc->disable(sdev);
+
+ /* we're alone and the device tab is locked; make the device unavailable */
+ memmove(&devs[i], &devs[ndevs - 1], sizeof(SDevgrp));
+ memset(&devs[ndevs - 1], 0, sizeof(SDevgrp));
+ ndevs--;
+
+ qunlock(&devslock);
+ poperror();
+
+ for(i = 0; i != sdev->nunit; i++)
+ if(sdev->unit[i]){
+ SDunit *unit = sdev->unit[i];
+
+ free(unit->name);
+ free(unit->user);
+ free(unit);
+ }
+
+ if(sdev->ifc->clear)
+ sdev->ifc->clear(sdev);
+ return 0;
+}
+
+static int
+sdconfig(int on, char* spec, DevConf* cf)
+{
+ if(on)
+ return configure(spec, cf);
+ return unconfigure(spec);
+}
+
+Dev sddevtab = {
+ 'S',
+ "sd",
+
+ sdreset,
+ devinit,
+ devshutdown,
+ sdattach,
+ sdwalk,
+ sdstat,
+ sdopen,
+ devcreate,
+ sdclose,
+ sdread,
+ devbread,
+ sdwrite,
+ devbwrite,
+ devremove,
+ sdwstat,
+ devpower,
+ sdconfig,
+};
diff --git a/os/port/devsign.c b/os/port/devsign.c
new file mode 100644
index 00000000..4fe8edb3
--- /dev/null
+++ b/os/port/devsign.c
@@ -0,0 +1,446 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#include "interp.h"
+#include <isa.h>
+#include "runt.h"
+#include "mp.h"
+#include "libsec.h"
+#include "../../libkeyring/keys.h"
+
+/*
+ * experimental version of signed modules
+ */
+
+enum
+{
+ Qdir,
+ Qkey,
+ Qctl,
+
+ Maxkey = 2048
+};
+
+static Dirtab signdir[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "signerkey", {Qkey}, 0, 0644,
+ "signerctl", {Qctl}, 0, 0600,
+};
+
+typedef struct Get Get;
+struct Get {
+ uchar* p;
+ uchar* ep;
+};
+
+#define G32(b) ((b[0]<<24)|(b[1]<<16)|(b[2]<<8)|b[3])
+
+static int vc(Get*);
+static int vs(void*, int, Get*, int);
+static Signerkey* findsignerkey(Skeyset*, char*, int, char*);
+extern vlong osusectime(void);
+
+int
+verifysigner(uchar *sign, int len, uchar *data, ulong ndata)
+{
+ Get sig;
+ int alg;
+ ulong issued, expires, now;
+ int footprint, r, n;
+ uchar buf[128], digest[SHA1dlen];
+ DigestState *ds;
+ volatile struct {BigInt b;} b;
+ volatile struct {BigInt s;} s;
+ SigAlgVec *sa;
+ Signerkey *key;
+ Skeyset *sigs;
+
+ /* alg[1] issued[4] expires[4] footprint[2] signer[n] sig[m] */
+ sigs = up->env->sigs;
+ if(sigs == nil)
+ return 1; /* not enforcing signed modules */
+ sig.p = sign;
+ sig.ep = sign+len;
+ alg = vc(&sig);
+ if(alg != 2)
+ return 0; /* we do only SHA1/RSA */
+ sa = findsigalg("rsa");
+ if(sa == nil)
+ return 0;
+ if(vs(buf, sizeof(buf), &sig, 4) < 0)
+ return 0;
+ now = osusectime()/1000000;
+ issued = G32(buf);
+ if(vs(buf, sizeof(buf), &sig, 4) < 0)
+ return 0;
+ if(issued != 0 && now < issued)
+ return 0;
+ expires = G32(buf);
+ if(expires != 0 && now >= expires)
+ return 0;
+ footprint = vc(&sig) << 8;
+ footprint |= vc(&sig);
+ if(footprint < 0)
+ return 0;
+ r = 0;
+ b.b = nil;
+ s.s = nil;
+ qlock(sigs);
+ if(waserror())
+ goto out;
+ if((n = vs(buf, sizeof(buf)-NUMSIZE-1, &sig, -1)) < 0) /* owner */
+ goto out;
+ buf[n] = 0;
+ key = findsignerkey(sigs, sa->name, footprint, (char*)buf);
+ if(key == nil)
+ goto out;
+ n += snprint((char*)buf+n, NUMSIZE, " %lud", expires);
+ ds = sha1(buf, n, nil, nil);
+ sha1(data, ndata, digest, ds);
+ b.b = betomp(digest, SHA1dlen, nil);
+ if(b.b == nil)
+ goto out;
+ s.s = betomp(sig.p, sig.ep-sig.p, nil);
+ if(s.s == nil)
+ goto out;
+ r = (*sa->verify)(b.b, s.s, key->pk);
+out:
+ qunlock(sigs);
+ if(b.b != nil)
+ mpfree(b.b);
+ if(s.s != nil)
+ mpfree(s.s);
+ return r;
+}
+
+int
+mustbesigned(char *path, uchar*, ulong, Dir *dir)
+{
+ USED(path);
+if(0)print("load %s: %d %C\n", path, up->env->sigs!=nil, dir==nil?'?':dir->type);
+ /* allow only signed modules and those in #/; already loaded modules are reloaded from cache */
+ return up->env->sigs != nil && (dir == nil || dir->type != '/');
+}
+
+static int
+vc(Get *g)
+{
+ return g->p < g->ep? *g->p++: -1;
+}
+
+static int
+vs(void *s, int lim, Get *g, int n)
+{
+ int nr;
+
+ if(n < 0){
+ if(g->p >= g->ep)
+ return -1;
+ n = *g->p++;
+ lim--;
+ }
+ if(n > lim)
+ return -1;
+ nr = g->ep - g->p;
+ if(n > nr)
+ return -1;
+ if(s != nil)
+ memmove(s, g->p, n);
+ g->p += n;
+ return n;
+}
+
+static char*
+cstring(char *str, char **strp)
+{
+ char *p, *s;
+ int n;
+
+ p = strchr(str, '\n');
+ if(p == 0)
+ p = str + strlen(str);
+ n = p - str;
+ s = malloc(n+1);
+ if(s == nil)
+ return nil;
+ memmove(s, str, n);
+ s[n] = 0;
+
+ if(strp){
+ if(*p)
+ p++;
+ *strp = p;
+ }
+
+ return s;
+}
+
+static SigAlgVec*
+cstrtoalg(char *str, char **strp)
+{
+ int n;
+ char *p, name[KNAMELEN];
+
+ p = strchr(str, '\n');
+ if(p == 0){
+ p = str + strlen(str);
+ if(strp)
+ *strp = p;
+ } else {
+ if(strp)
+ *strp = p+1;
+ }
+
+ n = p - str;
+ if(n >= sizeof(name))
+ return nil;
+ strncpy(name, str, n);
+ name[n] = 0;
+ return findsigalg(name);
+}
+
+static Signerkey*
+strtopk(char *buf)
+{
+ SigAlgVec *sa;
+ char *p;
+ Signerkey *key;
+
+ key = malloc(sizeof(*key));
+ if(key == nil)
+ return nil;
+ key->ref = 1;
+ sa = cstrtoalg(buf, &p);
+ if(sa == nil){
+ free(key);
+ return nil;
+ }
+ key->alg = sa;
+ key->pkfree = sa->pkfree;
+ key->owner = cstring(p, &p);
+ if(key->owner == nil){
+ free(key);
+ return nil;
+ }
+ key->pk = (*sa->str2pk)(p, &p);
+ if(key->pk == nil){
+ free(key->owner);
+ free(key);
+ return nil;
+ }
+ return key;
+}
+
+static Signerkey*
+findsignerkey(Skeyset *sigs, char *alg, int footprint, char *owner)
+{
+ int i;
+ Signerkey *key;
+
+ for(i=0; i<sigs->nkey; i++){
+ key = sigs->keys[i];
+ if(key->footprint == footprint &&
+ strcmp(alg, ((SigAlgVec*)key->alg)->name) == 0 &&
+ strcmp(key->owner, owner) == 0)
+ return key;
+ }
+ return nil;
+}
+
+static Chan*
+signattach(char *spec)
+{
+ return devattach(L'Σ', spec);
+}
+
+static Walkqid*
+signwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, signdir, nelem(signdir), devgen);
+}
+
+static int
+signstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, signdir, nelem(signdir), devgen);
+}
+
+static Chan*
+signopen(Chan *c, int omode)
+{
+ if(c->qid.type & QTDIR) {
+ if(omode != OREAD)
+ error(Eisdir);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ switch((ulong)c->qid.path){
+ case Qctl:
+ if(!iseve())
+ error(Eperm);
+ break;
+
+ case Qkey:
+ if(omode != OREAD && !iseve())
+ error(Eperm);
+ break;
+ }
+
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static void
+signclose(Chan *c)
+{
+ USED(c);
+}
+
+static long
+signread(Chan *c, void *va, long n, vlong offset)
+{
+ char *buf, *p;
+ SigAlgVec *sa;
+ Skeyset *sigs;
+ Signerkey *key;
+ int i;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, va, n, signdir, nelem(signdir), devgen);
+ sigs = up->env->sigs;
+ if(sigs == nil)
+ return 0;
+ switch((ulong)c->qid.path){
+ case Qkey:
+ buf = smalloc(Maxkey);
+ if(waserror()){
+ free(buf);
+ nexterror();
+ }
+ qlock(sigs);
+ if(waserror()){
+ qunlock(sigs);
+ nexterror();
+ }
+ p = buf;
+ for(i=0; i<sigs->nkey; i++){
+ key = sigs->keys[i];
+ sa = key->alg;
+ p = seprint(p, buf+Maxkey, "owner=%s alg=%s footprint=%ud expires=%lud\n",
+ key->owner, sa->name, key->footprint, key->expires);
+ }
+ poperror();
+ qunlock(sigs);
+ n = readstr(offset, va, n, buf);
+ poperror();
+ free(buf);
+ return n;
+
+ case Qctl:
+ return readnum(offset, va, n, sigs->nkey, NUMSIZE);
+ }
+ return 0;
+}
+
+static long
+signwrite(Chan *c, void *va, long n, vlong offset)
+{
+ char *buf;
+ Skeyset *sigs;
+ Signerkey *okey, *key;
+ int i;
+
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ USED(offset);
+ switch((ulong)c->qid.path){
+ case Qkey:
+ if(n >= Maxkey)
+ error(Etoobig);
+ buf = smalloc(Maxkey);
+ if(waserror()){
+ free(buf);
+ nexterror();
+ }
+ memmove(buf, va, n);
+ buf[n] = 0;
+
+ key = strtopk(buf);
+ if(key == nil)
+ error("bad key syntax");
+ poperror();
+ free(buf);
+
+ if(waserror()){
+ freeskey(key);
+ nexterror();
+ }
+ sigs = up->env->sigs;
+ if(sigs == nil){
+ sigs = malloc(sizeof(*sigs));
+ if(sigs == nil)
+ error(Enomem);
+ sigs->ref = 1;
+ up->env->sigs = sigs;
+ }
+ qlock(sigs);
+ if(waserror()){
+ qunlock(sigs);
+ nexterror();
+ }
+ for(i=0; i<sigs->nkey; i++){
+ okey = sigs->keys[i];
+ if(strcmp(okey->owner, key->owner) == 0){
+ /* replace existing key */
+ sigs->keys[i] = key;
+ freeskey(okey);
+ break;
+ }
+ }
+ if(i >= sigs->nkey){
+ if(sigs->nkey >= nelem(sigs->keys))
+ error("too many keys");
+ sigs->keys[sigs->nkey++] = key;
+ }
+ poperror();
+ qunlock(sigs);
+ poperror(); /* key */
+
+ return n;
+ case Qctl:
+ error(Ebadctl);
+ break;
+ }
+ return 0;
+}
+
+Dev signdevtab = {
+ L'Σ',
+ "sign",
+
+ devreset,
+ devinit,
+ devshutdown,
+ signattach,
+ signwalk,
+ signstat,
+ signopen,
+ devcreate,
+ signclose,
+ signread,
+ devbread,
+ signwrite,
+ devbwrite,
+ devremove,
+ devwstat
+};
diff --git a/os/port/devsrv.c b/os/port/devsrv.c
new file mode 100644
index 00000000..a80a3984
--- /dev/null
+++ b/os/port/devsrv.c
@@ -0,0 +1,726 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "interp.h"
+#include "isa.h"
+#include "runt.h"
+
+typedef struct SrvFile SrvFile;
+struct SrvFile
+{
+ char* spec;
+ char* name;
+ char* user;
+ ulong perm;
+ vlong length;
+ Qid qid;
+ int ref;
+ int opens;
+ int flags;
+ Channel* read;
+ Channel* write;
+ SrvFile* entry;
+ SrvFile* dir;
+ SrvFile* devlist;
+};
+
+enum
+{
+ SORCLOSE = (1<<0),
+ SRDCLOSE = (1<<1),
+ SWRCLOSE = (1<<2),
+ SREMOVED = (1<<3),
+};
+
+typedef struct SrvDev SrvDev;
+struct SrvDev
+{
+ Type* Rread;
+ Type* Rwrite;
+ QLock l;
+ ulong pathgen;
+ SrvFile* devices;
+};
+
+static SrvDev dev;
+
+void freechan(Heap*, int);
+static void freerdchan(Heap*, int);
+static void freewrchan(Heap*, int);
+
+Type *Trdchan;
+Type *Twrchan;
+
+static int
+srvgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
+{
+ SrvFile *f;
+
+ if(s == DEVDOTDOT){
+ devdir(c, c->qid, "#s", 0, eve, 0555, dp);
+ return 1;
+ }
+ f = c->aux;
+ if((c->qid.type & QTDIR) == 0){
+ if(s > 0)
+ return -1;
+ devdir(c, f->qid, f->name, f->length, f->user, f->perm, dp);
+ return 1;
+ }
+
+ for(f = f->entry; f != nil; f = f->entry){
+ if(s-- == 0)
+ break;
+ }
+ if(f == nil)
+ return -1;
+
+ devdir(c, f->qid, f->name, f->length, f->user, f->perm, dp);
+ return 1;
+}
+
+static void
+srvinit(void)
+{
+ static uchar rmap[] = Sys_Rread_map;
+ static uchar wmap[] = Sys_Rwrite_map;
+
+ Trdchan = dtype(freerdchan, sizeof(Channel), Tchannel.map, Tchannel.np);
+ Twrchan = dtype(freewrchan, sizeof(Channel), Tchannel.map, Tchannel.np);
+
+ dev.pathgen = 1;
+ dev.Rread = dtype(freeheap, Sys_Rread_size, rmap, sizeof(rmap));
+ dev.Rwrite = dtype(freeheap, Sys_Rwrite_size, wmap, sizeof(wmap));
+}
+
+static int
+srvchkattach(SrvFile *d)
+{
+ if(strcmp(d->user, up->env->user) == 0)
+ return 1;
+
+ /*
+ * Need write permission in other to allow attaches if
+ * we are not the owner
+ */
+ if(d->perm & 2)
+ return 1;
+
+ return 0;
+}
+
+static Chan*
+srvattach(char *spec)
+{
+ Chan *c;
+ SrvFile *d;
+
+ if(spec[0] != '\0'){
+ qlock(&dev.l);
+ for(d = dev.devices; d != nil; d = d->devlist){
+ if(strcmp(spec, d->spec) == 0){
+ if(srvchkattach(d) == 0){
+ qunlock(&dev.l);
+ error(Eperm);
+ }
+ d->ref++;
+ break;
+ }
+ }
+ qunlock(&dev.l);
+
+ if(d != nil){
+ c = devattach('s', spec);
+ c->aux = d;
+ c->qid = d->qid;
+ return c;
+ }
+ }
+
+ d = malloc(sizeof(SrvFile));
+ if(d == nil)
+ error(Enomem);
+
+ c = devattach('s', spec);
+
+ d->ref = 1;
+ kstrdup(&d->spec, spec);
+ kstrdup(&d->user, up->env->user);
+ snprint(up->genbuf, sizeof(up->genbuf), "srv%ld", up->env->pgrp->pgrpid);
+ kstrdup(&d->name, up->genbuf);
+ d->perm = DMDIR|0770;
+
+ qlock(&dev.l);
+ mkqid(&d->qid, dev.pathgen++, 0, QTDIR);
+ d->devlist = dev.devices;
+ dev.devices = d;
+ qunlock(&dev.l);
+
+ c->aux = d;
+ c->qid = d->qid;
+
+ return c;
+}
+
+static Walkqid*
+srvwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ SrvFile *d, *pd;
+ Walkqid *w;
+
+ pd = c->aux;
+ qlock(&dev.l);
+ if(waserror()){
+ qunlock(&dev.l);
+ nexterror();
+ }
+
+ w = devwalk(c, nc, name, nname, nil, 0, srvgen);
+ if(w != nil && w->clone != nil){
+ if(nname != 0){
+ for(d = pd->entry; d != nil; d = d->entry)
+ if(d->qid.path == w->clone->qid.path)
+ break;
+ if(d == nil)
+ panic("srvwalk");
+ if(w->clone == c)
+ pd->ref--;
+ }else
+ d = pd;
+ w->clone->aux = d;
+ d->ref++;
+ }
+ poperror();
+ qunlock(&dev.l);
+ return w;
+}
+
+static int
+srvstat(Chan *c, uchar *db, int n)
+{
+ qlock(&dev.l);
+ if(waserror()){
+ qunlock(&dev.l);
+ nexterror();
+ }
+ n = devstat(c, db, n, 0, 0, srvgen);
+ poperror();
+ qunlock(&dev.l);
+ return n;
+}
+
+static Chan*
+srvopen(Chan *c, int omode)
+{
+ SrvFile *sf;
+
+ openmode(omode); /* check it */
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Eisdir);
+ c->mode = omode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ sf = c->aux;
+
+ qlock(&dev.l);
+ if(waserror()){
+ qunlock(&dev.l);
+ nexterror();
+ }
+ devpermcheck(sf->user, sf->perm, omode);
+ if(omode&ORCLOSE && strcmp(sf->user, up->env->user) != 0)
+ error(Eperm);
+ if(sf->perm & DMEXCL && sf->opens != 0)
+ error(Einuse);
+ sf->opens++;
+ if(omode&ORCLOSE)
+ sf->flags |= SORCLOSE;
+ poperror();
+ qunlock(&dev.l);
+
+ c->offset = 0;
+ c->flag |= COPEN;
+ c->mode = openmode(omode);
+
+ return c;
+}
+
+static int
+srvwstat(Chan *c, uchar *dp, int n)
+{
+ Dir *d;
+ SrvFile *sf, *f;
+
+ sf = c->aux;
+ if(strcmp(up->env->user, sf->user) != 0)
+ error(Eperm);
+
+ d = smalloc(sizeof(*d)+n);
+ if(waserror()){
+ free(d);
+ nexterror();
+ }
+ n = convM2D(dp, n, d, (char*)&d[1]);
+ if(n == 0)
+ error(Eshortstat);
+ if(!emptystr(d->name)){
+ if(sf->dir == nil)
+ error(Eperm);
+ validwstatname(d->name);
+ qlock(&dev.l);
+ for(f = sf->dir; f != nil; f = f->entry)
+ if(strcmp(f->name, d->name) == 0){
+ qunlock(&dev.l);
+ error(Eexist);
+ }
+ kstrdup(&sf->name, d->name);
+ qunlock(&dev.l);
+ }
+ if(d->mode != ~0UL)
+ sf->perm = d->mode & (DMEXCL|DMAPPEND|0777);
+ if(d->length != (vlong)-1)
+ sf->length = d->length;
+ poperror();
+ free(d);
+ return n;
+}
+
+static void
+srvputdir(SrvFile *sf)
+{
+ SrvFile **l, *d;
+
+ sf->ref--;
+ if(sf->ref != 0)
+ return;
+
+ for(l = &dev.devices; (d = *l) != nil; l = &d->devlist)
+ if(d == sf){
+ *l = d->devlist;
+ break;
+ }
+ free(sf->spec);
+ free(sf->user);
+ free(sf->name);
+ free(sf);
+}
+
+static void
+srvunblock(SrvFile *sf, int fid)
+{
+ Channel *d;
+ Sys_FileIO_read rreq;
+ Sys_FileIO_write wreq;
+
+ acquire();
+ if(waserror()){
+ release();
+ nexterror();
+ }
+ d = sf->read;
+ if(d != H){
+ rreq.t0 = 0;
+ rreq.t1 = 0;
+ rreq.t2 = fid;
+ rreq.t3 = H;
+ csendalt(d, &rreq, d->mid.t, -1);
+ }
+
+ d = sf->write;
+ if(d != H){
+ wreq.t0 = 0;
+ wreq.t1 = H;
+ wreq.t2 = fid;
+ wreq.t3 = H;
+ csendalt(d, &wreq, d->mid.t, -1);
+ }
+ poperror();
+ release();
+}
+
+static void
+srvdecr(SrvFile *sf, int remove)
+{
+ SrvFile *f, **l;
+
+ if(remove){
+ l = &sf->dir->entry;
+ for(f = *l; f != nil; f = f->entry){
+ if(sf == f){
+ *l = f->entry;
+ break;
+ }
+ l = &f->entry;
+ }
+ sf->ref--;
+ sf->flags |= SREMOVED;
+ }
+
+ if(sf->ref != 0)
+ return;
+
+ if(sf->dir != nil)
+ srvputdir(sf->dir);
+
+ free(sf->spec);
+ free(sf->user);
+ free(sf->name);
+ free(sf);
+}
+
+static void
+srvfree(SrvFile *sf, int flag)
+{
+ sf->flags |= flag;
+ if((sf->flags & (SRDCLOSE | SWRCLOSE)) == (SRDCLOSE | SWRCLOSE)){
+ sf->ref--;
+ srvdecr(sf, (sf->flags & SREMOVED) == 0);
+ }
+}
+
+static void
+freerdchan(Heap *h, int swept)
+{
+ SrvFile *sf;
+
+ release();
+ qlock(&dev.l);
+ sf = H2D(Channel*, h)->aux;
+ sf->read = H;
+ srvfree(sf, SRDCLOSE);
+ qunlock(&dev.l);
+ acquire();
+ freechan(h, swept);
+}
+
+static void
+freewrchan(Heap *h, int swept)
+{
+ SrvFile *sf;
+
+ release();
+ qlock(&dev.l);
+ sf = H2D(Channel*, h)->aux;
+ sf->write = H;
+ srvfree(sf, SWRCLOSE);
+ qunlock(&dev.l);
+ acquire();
+ freechan(h, swept);
+}
+
+static void
+srvclunk(Chan *c, int remove)
+{
+ int opens, noperm;
+ SrvFile *sf;
+
+ sf = c->aux;
+ qlock(&dev.l);
+ if(c->qid.type & QTDIR){
+ srvputdir(sf);
+ qunlock(&dev.l);
+ if(remove)
+ error(Eperm);
+ return;
+ }
+
+ opens = 0;
+ if(c->flag & COPEN){
+ opens = sf->opens--;
+ if (sf->read != H || sf->write != H)
+ srvunblock(sf, c->fid);
+ }
+
+ sf->ref--;
+ if(opens == 1){
+ if((sf->flags & (SORCLOSE | SREMOVED)) == SORCLOSE)
+ remove = 1;
+ }
+
+ noperm = 0;
+ if(remove && strcmp(sf->dir->user, up->env->user) != 0){
+ noperm = 1;
+ remove = 0;
+ }
+
+ srvdecr(sf, remove);
+ qunlock(&dev.l);
+
+ if(noperm)
+ error(Eperm);
+}
+
+static void
+srvclose(Chan *c)
+{
+ srvclunk(c, 0);
+}
+
+static void
+srvremove(Chan *c)
+{
+ srvclunk(c, 1);
+}
+
+static long
+srvread(Chan *c, void *va, long count, vlong offset)
+{
+ int l;
+ Heap * volatile h;
+ Array *a;
+ SrvFile *sp;
+ Channel *rc;
+ Channel *rd;
+ Sys_Rread * volatile r;
+ Sys_FileIO_read req;
+
+ if(c->qid.type & QTDIR){
+ qlock(&dev.l);
+ if(waserror()){
+ qunlock(&dev.l);
+ nexterror();
+ }
+ l = devdirread(c, va, count, 0, 0, srvgen);
+ poperror();
+ qunlock(&dev.l);
+ return l;
+ }
+
+ sp = c->aux;
+
+ acquire();
+ if(waserror()){
+ release();
+ nexterror();
+ }
+
+ rd = sp->read;
+ if(rd == H)
+ error(Eshutdown);
+
+ rc = cnewc(dev.Rread, movtmp, 1);
+ ptradd(D2H(rc));
+ if(waserror()){
+ ptrdel(D2H(rc));
+ destroy(rc);
+ nexterror();
+ }
+
+ req.t0 = offset;
+ req.t1 = count;
+ req.t2 = c->fid;
+ req.t3 = rc;
+ csend(rd, &req);
+
+ h = heap(dev.Rread);
+ r = H2D(Sys_Rread *, h);
+ ptradd(h);
+ if(waserror()){
+ ptrdel(h);
+ destroy(r);
+ nexterror();
+ }
+
+ crecv(rc, r);
+ if(r->t1 != H)
+ error(string2c(r->t1));
+
+ a = r->t0;
+ l = 0;
+ if(a != H){
+ l = a->len;
+ if(l > count)
+ l = count;
+ memmove(va, a->data, l);
+ }
+
+ poperror();
+ ptrdel(h);
+ destroy(r);
+
+ poperror();
+ ptrdel(D2H(rc));
+ destroy(rc);
+
+ poperror();
+ release();
+
+ return l;
+}
+
+static long
+srvwrite(Chan *c, void *va, long count, vlong offset)
+{
+ long l;
+ Heap * volatile h;
+ SrvFile *sp;
+ Channel *wc;
+ Channel *wr;
+ Sys_Rwrite * volatile w;
+ Sys_FileIO_write req;
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+
+ acquire();
+ if(waserror()){
+ release();
+ nexterror();
+ }
+
+ sp = c->aux;
+ wr = sp->write;
+ if(wr == H)
+ error(Eshutdown);
+
+ wc = cnewc(dev.Rwrite, movtmp, 1);
+ ptradd(D2H(wc));
+ if(waserror()){
+ ptrdel(D2H(wc));
+ destroy(wc);
+ nexterror();
+ }
+
+ req.t0 = offset;
+ req.t1 = mem2array(va, count);
+ req.t2 = c->fid;
+ req.t3 = wc;
+
+ ptradd(D2H(req.t1));
+ if(waserror()){
+ ptrdel(D2H(req.t1));
+ destroy(req.t1);
+ nexterror();
+ }
+
+ csend(wr, &req);
+
+ poperror();
+ ptrdel(D2H(req.t1));
+ destroy(req.t1);
+
+ poperror();
+ ptrdel(D2H(wc));
+ destroy(wc);
+
+ h = heap(dev.Rwrite);
+ w = H2D(Sys_Rwrite *, h);
+ ptradd(h);
+ if(waserror()){
+ ptrdel(h);
+ destroy(w);
+ nexterror();
+ }
+ crecv(wc, w);
+ if(w->t1 != H)
+ error(string2c(w->t1));
+ poperror();
+ ptrdel(h);
+ l = w->t0;
+ destroy(w);
+
+ poperror();
+ release();
+ if(l < 0)
+ l = 0;
+ return l;
+}
+
+static void
+srvretype(Channel *c, SrvFile *f, Type *t)
+{
+ Heap *h;
+
+ h = D2H(c);
+ h->t->ref--;
+ h->t = t;
+ t->ref++;
+ c->aux = f;
+}
+
+int
+srvf2c(char *dir, char *file, Sys_FileIO *io)
+{
+ SrvFile *s, *f;
+ volatile struct { Chan *c; } c;
+
+ c.c = nil;
+ if(waserror()){
+ cclose(c.c);
+ return -1;
+ }
+
+ if(strchr(file, '/') != nil || strlen(file) >= 64 || strcmp(file, ".") == 0 || strcmp(file, "..") == 0)
+ error(Efilename);
+
+ c.c = namec(dir, Aaccess, 0, 0);
+ if((c.c->qid.type&QTDIR) == 0 || devtab[c.c->type]->dc != 's')
+ error("directory not a srv device");
+
+ s = c.c->aux;
+
+ qlock(&dev.l);
+ for(f = s->entry; f != nil; f = f->entry){
+ if(strcmp(f->name, file) == 0){
+ qunlock(&dev.l);
+ error(Eexist);
+ }
+ }
+
+ f = malloc(sizeof(SrvFile));
+ if(f == nil){
+ qunlock(&dev.l);
+ error(Enomem);
+ }
+
+ srvretype(io->read, f, Trdchan);
+ srvretype(io->write, f, Twrchan);
+ f->read = io->read;
+ f->write = io->write;
+
+ kstrdup(&f->name, file);
+ kstrdup(&f->user, up->env->user);
+ f->perm = 0666 & (~0666 | (s->perm & 0666));
+ f->length = 0;
+ f->ref = 2;
+ mkqid(&f->qid, dev.pathgen++, 0, QTFILE);
+
+ f->entry = s->entry;
+ s->entry = f;
+ s->ref++;
+ f->dir = s;
+ qunlock(&dev.l);
+
+ cclose(c.c);
+ poperror();
+
+ return 0;
+}
+
+Dev srvdevtab = {
+ 's',
+ "srv",
+
+ devreset,
+ srvinit,
+ devshutdown,
+ srvattach,
+ srvwalk,
+ srvstat,
+ srvopen,
+ devcreate,
+ srvclose,
+ srvread,
+ devbread,
+ srvwrite,
+ devbwrite,
+ srvremove,
+ srvwstat
+};
diff --git a/os/port/devssl.c b/os/port/devssl.c
new file mode 100644
index 00000000..23c3fec5
--- /dev/null
+++ b/os/port/devssl.c
@@ -0,0 +1,1436 @@
+/*
+ * devssl - secure sockets layer
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "kernel.h"
+
+#include "mp.h"
+#include "libsec.h"
+
+typedef struct OneWay OneWay;
+struct OneWay
+{
+ QLock q;
+ QLock ctlq;
+
+ void *state; /* encryption state */
+ int slen; /* secret data length */
+ uchar *secret; /* secret */
+ ulong mid; /* message id */
+};
+
+enum
+{
+ /* connection states */
+ Sincomplete= 0,
+ Sclear= 1,
+ Sencrypting= 2,
+ Sdigesting= 4,
+ Sdigenc= Sencrypting|Sdigesting,
+
+ /* encryption algorithms */
+ Noencryption= 0,
+ DESCBC= 1,
+ DESECB= 2,
+ RC4= 3,
+ IDEACBC= 4,
+ IDEAECB= 5
+};
+
+typedef struct Dstate Dstate;
+struct Dstate
+{
+ Chan *c; /* io channel */
+ uchar state; /* state of connection */
+ int ref; /* serialized by dslock for atomic destroy */
+
+ uchar encryptalg; /* encryption algorithm */
+ ushort blocklen; /* blocking length */
+
+ ushort diglen; /* length of digest */
+ DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*); /* hash func */
+
+ /* for SSL format */
+ int max; /* maximum unpadded data per msg */
+ int maxpad; /* maximum padded data per msg */
+
+ /* input side */
+ OneWay in;
+ Block *processed;
+ Block *unprocessed;
+
+ /* output side */
+ OneWay out;
+
+ /* protections */
+ char* user;
+ int perm;
+};
+
+Lock dslock;
+int dshiwat;
+int maxdstate = 20;
+Dstate** dstate;
+char** dsname;
+
+enum
+{
+ Maxdmsg= 1<<16,
+ Maxdstate= 1<<10,
+};
+
+enum{
+ Qtopdir = 1, /* top level directory */
+ Qclonus,
+ Qconvdir, /* directory for a conversation */
+ Qdata,
+ Qctl,
+ Qsecretin,
+ Qsecretout,
+ Qencalgs,
+ Qhashalgs
+};
+
+#define TYPE(x) ((ulong)(x).path & 0xf)
+#define CONV(x) (((ulong)(x).path >> 4)&(Maxdstate-1))
+#define QID(c, y) (((c)<<4) | (y))
+
+/* for generating random fill */
+ulong badlong;
+uchar *badarray = (uchar*)&badlong;
+static char* encalgs;
+static char* hashalgs;
+
+void producerand(void);
+
+static void alglistinit(void);
+static void ensure(Dstate*, Block**, int);
+static void consume(Block**, uchar*, int);
+static void setsecret(OneWay*, uchar*, int);
+static Block* encryptb(Dstate*, Block*, int);
+static Block* decryptb(Dstate*, Block*);
+static Block* digestb(Dstate*, Block*, int);
+static void checkdigestb(Dstate*, Block*);
+static Chan* buftochan(char*);
+static void sslhangup(Dstate*);
+static Dstate* dsclone(Chan *c);
+static void dsnew(Chan *c, Dstate **);
+
+static int
+sslgen(Chan *c, char*, Dirtab *d, int nd, int s, Dir *dp)
+{
+ Qid q;
+ Dstate *ds;
+ char name[16], *p, *nm;
+
+ USED(nd);
+ USED(d);
+ q.type = QTFILE;
+ q.vers = 0;
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qtopdir);
+ q.type = QTDIR;
+ devdir(c, q, "#D", 0, eve, 0555, dp);
+ return 1;
+ }
+ switch(TYPE(c->qid)) {
+ case Qtopdir:
+ if(s < dshiwat) {
+ q.path = QID(s, Qconvdir);
+ q.type = QTDIR;
+ ds = dstate[s];
+ if(ds != 0)
+ nm = ds->user;
+ else
+ nm = eve;
+ if(dsname[s] == nil){
+ sprint(name, "%d", s);
+ kstrdup(&dsname[s], name);
+ }
+ devdir(c, q, dsname[s], 0, nm, DMDIR|0555, dp);
+ return 1;
+ }
+ if(s > dshiwat)
+ return -1;
+ /* fall through */
+ case Qclonus:
+ q.path = QID(0, Qclonus);
+ devdir(c, q, "clone", 0, eve, 0666, dp);
+ return 1;
+ case Qconvdir:
+ ds = dstate[CONV(c->qid)];
+ if(ds != 0)
+ nm = ds->user;
+ else
+ nm = eve;
+ switch(s) {
+ default:
+ return -1;
+ case 0:
+ q.path = QID(CONV(c->qid), Qctl);
+ p = "ctl";
+ break;
+ case 1:
+ q.path = QID(CONV(c->qid), Qdata);
+ p = "data";
+ break;
+ case 2:
+ q.path = QID(CONV(c->qid), Qsecretin);
+ p = "secretin";
+ break;
+ case 3:
+ q.path = QID(CONV(c->qid), Qsecretout);
+ p = "secretout";
+ break;
+ case 4:
+ q.path = QID(CONV(c->qid), Qencalgs);
+ p = "encalgs";
+ break;
+ case 5:
+ q.path = QID(CONV(c->qid), Qhashalgs);
+ p = "hashalgs";
+ break;
+ }
+ devdir(c, q, p, 0, nm, 0660, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static void
+sslinit(void)
+{
+ if((dstate = malloc(sizeof(Dstate*) * maxdstate)) == 0)
+ panic("sslinit");
+ if((dsname = malloc(sizeof(*dsname) * maxdstate)) == 0)
+ panic("sslinit");
+ alglistinit();
+}
+
+static Chan *
+sslattach(char *spec)
+{
+ Chan *c;
+
+ c = devattach('D', spec);
+ c->qid.path = QID(0, Qtopdir);
+ c->qid.vers = 0;
+ c->qid.type = QTDIR;
+ return c;
+}
+
+static Walkqid*
+sslwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, 0, 0, sslgen);
+}
+
+static int
+sslstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, 0, 0, sslgen);
+}
+
+static Chan*
+sslopen(Chan *c, int omode)
+{
+ Dstate *s, **pp;
+ int perm;
+
+ perm = 0;
+ omode &= 3;
+ switch(omode) {
+ case OREAD:
+ perm = 4;
+ break;
+ case OWRITE:
+ perm = 2;
+ break;
+ case ORDWR:
+ perm = 6;
+ break;
+ }
+
+ switch(TYPE(c->qid)) {
+ default:
+ panic("sslopen");
+ case Qtopdir:
+ case Qconvdir:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ case Qclonus:
+ s = dsclone(c);
+ if(s == 0)
+ error(Enodev);
+ break;
+ case Qctl:
+ case Qdata:
+ case Qsecretin:
+ case Qsecretout:
+ if(waserror()) {
+ unlock(&dslock);
+ nexterror();
+ }
+ lock(&dslock);
+ pp = &dstate[CONV(c->qid)];
+ s = *pp;
+ if(s == 0)
+ dsnew(c, pp);
+ else {
+ if((perm & (s->perm>>6)) != perm
+ && (strcmp(up->env->user, s->user) != 0
+ || (perm & s->perm) != perm))
+ error(Eperm);
+
+ s->ref++;
+ }
+ unlock(&dslock);
+ poperror();
+ break;
+ case Qencalgs:
+ case Qhashalgs:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static int
+sslwstat(Chan *c, uchar *dp, int n)
+{
+ Dir d;
+ Dstate *s;
+
+ n = convM2D(dp, n, &d, nil);
+ if(n == 0)
+ error(Eshortstat);
+ s = dstate[CONV(c->qid)];
+ if(s == 0)
+ error(Ebadusefd);
+ if(strcmp(s->user, up->env->user) != 0)
+ error(Eperm);
+ if(!emptystr(d.uid))
+ kstrdup(&s->user, d.uid);
+ if(d.mode != ~0UL)
+ s->perm = d.mode;
+ return n;
+}
+
+static void
+sslclose(Chan *c)
+{
+ Dstate *s;
+
+ switch(TYPE(c->qid)) {
+ case Qctl:
+ case Qdata:
+ case Qsecretin:
+ case Qsecretout:
+ if((c->flag & COPEN) == 0)
+ break;
+
+ s = dstate[CONV(c->qid)];
+ if(s == 0)
+ break;
+
+ lock(&dslock);
+ if(--s->ref > 0) {
+ unlock(&dslock);
+ break;
+ }
+ dstate[CONV(c->qid)] = 0;
+ unlock(&dslock);
+
+ sslhangup(s);
+ if(s->c)
+ cclose(s->c);
+ free(s->user);
+ free(s->in.secret);
+ free(s->out.secret);
+ free(s->in.state);
+ free(s->out.state);
+ free(s);
+ }
+}
+
+/*
+ * make sure we have at least 'n' bytes in list 'l'
+ */
+static void
+ensure(Dstate *s, Block **l, int n)
+{
+ int sofar, i;
+ Block *b, *bl;
+
+ sofar = 0;
+ for(b = *l; b; b = b->next){
+ sofar += BLEN(b);
+ if(sofar >= n)
+ return;
+ l = &b->next;
+ }
+
+ while(sofar < n){
+ bl = devtab[s->c->type]->bread(s->c, Maxdmsg, 0);
+ if(bl == 0)
+ error(Ehungup);
+ *l = bl;
+ i = 0;
+ for(b = bl; b; b = b->next){
+ i += BLEN(b);
+ l = &b->next;
+ }
+ if(i == 0)
+ error(Ehungup);
+
+ sofar += i;
+ }
+}
+
+/*
+ * copy 'n' bytes from 'l' into 'p' and free
+ * the bytes in 'l'
+ */
+static void
+consume(Block **l, uchar *p, int n)
+{
+ Block *b;
+ int i;
+
+ for(; *l && n > 0; n -= i){
+ b = *l;
+ i = BLEN(b);
+ if(i > n)
+ i = n;
+ memmove(p, b->rp, i);
+ b->rp += i;
+ p += i;
+ if(BLEN(b) < 0)
+ panic("consume");
+ if(BLEN(b))
+ break;
+ *l = b->next;
+ freeb(b);
+ }
+}
+
+/*
+ * remove at most n bytes from the queue, if discard is set
+ * dump the remainder
+ */
+static Block*
+qtake(Block **l, int n, int discard)
+{
+ Block *nb, *b, *first;
+ int i;
+
+ first = *l;
+ for(b = first; b; b = b->next){
+ i = BLEN(b);
+ if(i == n){
+ if(discard){
+ freeblist(b->next);
+ *l = 0;
+ } else
+ *l = b->next;
+ b->next = 0;
+ return first;
+ } else if(i > n){
+ i -= n;
+ if(discard){
+ freeblist(b->next);
+ *l = 0;
+ } else {
+ nb = allocb(i);
+ memmove(nb->wp, b->rp+n, i);
+ nb->wp += i;
+ nb->next = b->next;
+ *l = nb;
+ }
+ b->wp -= i;
+ b->next = 0;
+ if(BLEN(b) < 0)
+ panic("qtake");
+ return first;
+ } else
+ n -= i;
+ if(BLEN(b) < 0)
+ panic("qtake");
+ }
+ *l = 0;
+ return first;
+}
+
+static Block*
+sslbread(Chan *c, long n, ulong offset)
+{
+ volatile struct { Dstate *s; } s;
+ Block *b;
+ uchar count[2];
+ int len, pad;
+
+ USED(offset);
+
+ s.s = dstate[CONV(c->qid)];
+ if(s.s == 0)
+ panic("sslbread");
+ if(s.s->state == Sincomplete)
+ error(Ebadusefd);
+
+ if(waserror()){
+ qunlock(&s.s->in.q);
+ sslhangup(s.s);
+ nexterror();
+ }
+ qlock(&s.s->in.q);
+
+ if(s.s->processed == 0){
+ /* read in the whole message */
+ ensure(s.s, &s.s->unprocessed, 2);
+ consume(&s.s->unprocessed, count, 2);
+ if(count[0] & 0x80){
+ len = ((count[0] & 0x7f)<<8) | count[1];
+ ensure(s.s, &s.s->unprocessed, len);
+ pad = 0;
+ } else {
+ len = ((count[0] & 0x3f)<<8) | count[1];
+ ensure(s.s, &s.s->unprocessed, len+1);
+ consume(&s.s->unprocessed, count, 1);
+ pad = count[0];
+ if(pad > len){
+ print("pad %d buf len %d\n", pad, len);
+ error("bad pad in ssl message");
+ }
+ }
+
+ /* put extra on unprocessed queue */
+ s.s->processed = qtake(&s.s->unprocessed, len, 0);
+
+ if(waserror()){
+ qunlock(&s.s->in.ctlq);
+ nexterror();
+ }
+ qlock(&s.s->in.ctlq);
+ switch(s.s->state){
+ case Sencrypting:
+ s.s->processed = decryptb(s.s, s.s->processed);
+ break;
+ case Sdigesting:
+ s.s->processed = pullupblock(s.s->processed, s.s->diglen);
+ if(s.s->processed == 0)
+ error("ssl message too short");
+ checkdigestb(s.s, s.s->processed);
+ s.s->processed->rp += s.s->diglen;
+ break;
+ case Sdigenc:
+ s.s->processed = decryptb(s.s, s.s->processed);
+ s.s->processed = pullupblock(s.s->processed, s.s->diglen);
+ if(s.s->processed == 0)
+ error("ssl message too short");
+ checkdigestb(s.s, s.s->processed);
+ s.s->processed->rp += s.s->diglen;
+ len -= s.s->diglen;
+ break;
+ }
+ s.s->in.mid++;
+ qunlock(&s.s->in.ctlq);
+ poperror();
+
+ /* remove pad */
+ if(pad)
+ s.s->processed = qtake(&s.s->processed, len - pad, 1);
+ }
+
+ /* return at most what was asked for */
+ b = qtake(&s.s->processed, n, 0);
+
+ qunlock(&s.s->in.q);
+ poperror();
+
+ return b;
+}
+
+static long
+sslread(Chan *c, void *a, long n, vlong offset)
+{
+ volatile struct { Block *b; } b;
+ Block *nb;
+ uchar *va;
+ int i;
+ char buf[128];
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, sslgen);
+
+ switch(TYPE(c->qid)) {
+ default:
+ error(Ebadusefd);
+ case Qctl:
+ sprint(buf, "%ld", CONV(c->qid));
+ return readstr(offset, a, n, buf);
+ case Qdata:
+ b.b = sslbread(c, n, offset);
+ break;
+ case Qencalgs:
+ return readstr(offset, a, n, encalgs);
+ case Qhashalgs:
+ return readstr(offset, a, n, hashalgs);
+ }
+
+ n = 0;
+ va = a;
+ for(nb = b.b; nb; nb = nb->next){
+ i = BLEN(nb);
+ memmove(va+n, nb->rp, i);
+ n += i;
+ }
+
+ freeblist(b.b);
+
+ return n;
+}
+
+/*
+ * this algorithm doesn't have to be great since we're just
+ * trying to obscure the block fill
+ */
+static void
+randfill(uchar *buf, int len)
+{
+ while(len-- > 0)
+ *buf++ = nrand(256);
+}
+
+/*
+ * use SSL record format, add in count and digest or encrypt
+ */
+static long
+sslbwrite(Chan *c, Block *b, ulong offset)
+{
+ volatile struct { Dstate *s; } s;
+ volatile struct { Block *b; } bb;
+ Block *nb;
+ int h, n, m, pad, rv;
+ uchar *p;
+
+ bb.b = b;
+
+ s.s = dstate[CONV(c->qid)];
+ if(s.s == 0)
+ panic("sslbwrite");
+ if(s.s->state == Sincomplete){
+ freeb(b);
+ error(Ebadusefd);
+ }
+
+ if(waserror()){
+ qunlock(&s.s->out.q);
+ if(bb.b)
+ freeb(bb.b);
+ sslhangup(s.s);
+ nexterror();
+ }
+ qlock(&s.s->out.q);
+
+ rv = 0;
+ while(bb.b){
+ m = n = BLEN(bb.b);
+ h = s.s->diglen + 2;
+
+ /* trim to maximum block size */
+ pad = 0;
+ if(m > s.s->max){
+ m = s.s->max;
+ } else if(s.s->blocklen != 1){
+ pad = (m + s.s->diglen)%s.s->blocklen;
+ if(pad){
+ if(m > s.s->maxpad){
+ pad = 0;
+ m = s.s->maxpad;
+ } else {
+ pad = s.s->blocklen - pad;
+ h++;
+ }
+ }
+ }
+
+ rv += m;
+ if(m != n){
+ nb = allocb(m + h + pad);
+ memmove(nb->wp + h, bb.b->rp, m);
+ nb->wp += m + h;
+ bb.b->rp += m;
+ } else {
+ /* add header space */
+ nb = padblock(bb.b, h);
+ bb.b = 0;
+ }
+ m += s.s->diglen;
+
+ /* SSLv2 style count */
+ if(pad){
+ nb = padblock(nb, -pad);
+ randfill(nb->wp, pad);
+ nb->wp += pad;
+ m += pad;
+
+ p = nb->rp;
+ p[0] = (m>>8);
+ p[1] = m;
+ p[2] = pad;
+ offset = 3;
+ } else {
+ p = nb->rp;
+ p[0] = (m>>8) | 0x80;
+ p[1] = m;
+ offset = 2;
+ }
+
+ switch(s.s->state){
+ case Sencrypting:
+ nb = encryptb(s.s, nb, offset);
+ break;
+ case Sdigesting:
+ nb = digestb(s.s, nb, offset);
+ break;
+ case Sdigenc:
+ nb = digestb(s.s, nb, offset);
+ nb = encryptb(s.s, nb, offset);
+ break;
+ }
+
+ s.s->out.mid++;
+
+ m = BLEN(nb);
+ devtab[s.s->c->type]->bwrite(s.s->c, nb, s.s->c->offset);
+ s.s->c->offset += m;
+ }
+ qunlock(&s.s->out.q);
+ poperror();
+
+ return rv;
+}
+
+static void
+setsecret(OneWay *w, uchar *secret, int n)
+{
+ free(w->secret);
+ w->secret = mallocz(n, 0);
+ if(w->secret == nil)
+ error(Enomem);
+ memmove(w->secret, secret, n);
+ w->slen = n;
+}
+
+static void
+initIDEAkey(OneWay *w)
+{
+ free(w->state);
+ w->state = malloc(sizeof(IDEAstate));
+ if(w->state == nil)
+ error(Enomem);
+ if(w->slen >= 24)
+ setupIDEAstate(w->state, w->secret, w->secret+16);
+ else if(w->slen >= 16)
+ setupIDEAstate(w->state, w->secret, 0);
+ else
+ error("secret too short");
+}
+
+static void
+initDESkey(OneWay *w)
+{
+ free(w->state);
+ w->state = malloc(sizeof(DESstate));
+ if (!w->state)
+ error(Enomem);
+ if(w->slen >= 16)
+ setupDESstate(w->state, w->secret, w->secret+8);
+ else if(w->slen >= 8)
+ setupDESstate(w->state, w->secret, 0);
+ else
+ error("secret too short");
+}
+
+/*
+ * 40 bit DES is the same as 56 bit DES. However,
+ * 16 bits of the key are masked to zero.
+ */
+static void
+initDESkey_40(OneWay *w)
+{
+ uchar key[8];
+
+ if(w->slen >= 8) {
+ memmove(key, w->secret, 8);
+ key[0] &= 0x0f;
+ key[2] &= 0x0f;
+ key[4] &= 0x0f;
+ key[6] &= 0x0f;
+ }
+
+ free(w->state);
+ w->state = malloc(sizeof(DESstate));
+ if (!w->state)
+ error(Enomem);
+ if(w->slen >= 16)
+ setupDESstate(w->state, key, w->secret+8);
+ else if(w->slen >= 8)
+ setupDESstate(w->state, key, 0);
+ else
+ error("secret too short");
+}
+
+static void
+initRC4key(OneWay *w)
+{
+ free(w->state);
+ w->state = malloc(sizeof(RC4state));
+ if (!w->state)
+ error(Enomem);
+ setupRC4state(w->state, w->secret, w->slen);
+}
+
+/*
+ * 40 bit RC4 is the same as n-bit RC4. However,
+ * we ignore all but the first 40 bits of the key.
+ */
+static void
+initRC4key_40(OneWay *w)
+{
+ int slen = w->slen;
+
+ if(slen > 5)
+ slen = 5;
+
+ free(w->state);
+ w->state = malloc(sizeof(RC4state));
+ if (!w->state)
+ error(Enomem);
+ setupRC4state(w->state, w->secret, slen);
+}
+
+/*
+ * 128 bit RC4 is the same as n-bit RC4. However,
+ * we ignore all but the first 128 bits of the key.
+ */
+static void
+initRC4key_128(OneWay *w)
+{
+ int slen = w->slen;
+
+ if(slen > 16)
+ slen = 16;
+
+ free(w->state);
+ w->state = malloc(sizeof(RC4state));
+ if (!w->state)
+ error(Enomem);
+ setupRC4state(w->state, w->secret, slen);
+}
+
+typedef struct Hashalg Hashalg;
+struct Hashalg
+{
+ char *name;
+ int diglen;
+ DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*);
+};
+
+Hashalg hashtab[] =
+{
+ { "md4", MD4dlen, md4, },
+ { "md5", MD5dlen, md5, },
+ { "sha1", SHA1dlen, sha1, },
+ { "sha", SHA1dlen, sha1, },
+ { 0 }
+};
+
+static int
+parsehashalg(char *p, Dstate *s)
+{
+ Hashalg *ha;
+
+ for(ha = hashtab; ha->name; ha++){
+ if(strcmp(p, ha->name) == 0){
+ s->hf = ha->hf;
+ s->diglen = ha->diglen;
+ s->state &= ~Sclear;
+ s->state |= Sdigesting;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+typedef struct Encalg Encalg;
+struct Encalg
+{
+ char *name;
+ int blocklen;
+ int alg;
+ void (*keyinit)(OneWay*);
+};
+
+Encalg encrypttab[] =
+{
+ { "descbc", 8, DESCBC, initDESkey, }, /* DEPRECATED -- use des_56_cbc */
+ { "desecb", 8, DESECB, initDESkey, }, /* DEPRECATED -- use des_56_ecb */
+ { "des_56_cbc", 8, DESCBC, initDESkey, },
+ { "des_56_ecb", 8, DESECB, initDESkey, },
+ { "des_40_cbc", 8, DESCBC, initDESkey_40, },
+ { "des_40_ecb", 8, DESECB, initDESkey_40, },
+ { "rc4", 1, RC4, initRC4key_40, }, /* DEPRECATED -- use rc4_X */
+ { "rc4_256", 1, RC4, initRC4key, },
+ { "rc4_128", 1, RC4, initRC4key_128, },
+ { "rc4_40", 1, RC4, initRC4key_40, },
+ { "ideacbc", 8, IDEACBC, initIDEAkey, },
+ { "ideaecb", 8, IDEAECB, initIDEAkey, },
+ { 0 }
+};
+
+static int
+parseencryptalg(char *p, Dstate *s)
+{
+ Encalg *ea;
+
+ for(ea = encrypttab; ea->name; ea++){
+ if(strcmp(p, ea->name) == 0){
+ s->encryptalg = ea->alg;
+ s->blocklen = ea->blocklen;
+ (*ea->keyinit)(&s->in);
+ (*ea->keyinit)(&s->out);
+ s->state &= ~Sclear;
+ s->state |= Sencrypting;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void
+alglistinit(void)
+{
+ Hashalg *h;
+ Encalg *e;
+ int n;
+
+ n = 1;
+ for(e = encrypttab; e->name != nil; e++)
+ n += strlen(e->name) + 1;
+ encalgs = malloc(n);
+ if(encalgs == nil)
+ panic("sslinit");
+ n = 0;
+ for(e = encrypttab; e->name != nil; e++){
+ strcpy(encalgs+n, e->name);
+ n += strlen(e->name);
+ if(e[1].name == nil)
+ break;
+ encalgs[n++] = ' ';
+ }
+ encalgs[n] = 0;
+
+ n = 1;
+ for(h = hashtab; h->name != nil; h++)
+ n += strlen(h->name) + 1;
+ hashalgs = malloc(n);
+ if(hashalgs == nil)
+ panic("sslinit");
+ n = 0;
+ for(h = hashtab; h->name != nil; h++){
+ strcpy(hashalgs+n, h->name);
+ n += strlen(h->name);
+ if(h[1].name == nil)
+ break;
+ hashalgs[n++] = ' ';
+ }
+ hashalgs[n] = 0;
+}
+
+static long
+sslwrite(Chan *c, void *a, long n, vlong offset)
+{
+ volatile struct { Dstate *s; } s;
+ volatile struct { Block *b; } b;
+ int m, t;
+ char *p, *np, *e, buf[32];
+ uchar *x;
+
+ s.s = dstate[CONV(c->qid)];
+ if(s.s == 0)
+ panic("sslwrite");
+
+ t = TYPE(c->qid);
+ if(t == Qdata){
+ if(s.s->state == Sincomplete)
+ error(Ebadusefd);
+
+ p = a;
+ e = p + n;
+ do {
+ m = e - p;
+ if(m > s.s->max)
+ m = s.s->max;
+
+ b.b = allocb(m);
+ memmove(b.b->wp, p, m);
+ b.b->wp += m;
+
+ sslbwrite(c, b.b, offset);
+
+ p += m;
+ } while(p < e);
+ return n;
+ }
+
+ /* mutex with operations using what we're about to change */
+ if(waserror()){
+ qunlock(&s.s->in.ctlq);
+ qunlock(&s.s->out.q);
+ nexterror();
+ }
+ qlock(&s.s->in.ctlq);
+ qlock(&s.s->out.q);
+
+ switch(t){
+ default:
+ panic("sslwrite");
+ case Qsecretin:
+ setsecret(&s.s->in, a, n);
+ goto out;
+ case Qsecretout:
+ setsecret(&s.s->out, a, n);
+ goto out;
+ case Qctl:
+ break;
+ }
+
+ if(n >= sizeof(buf))
+ error(Ebadarg);
+ strncpy(buf, a, n);
+ buf[n] = 0;
+ p = strchr(buf, '\n');
+ if(p)
+ *p = 0;
+ p = strchr(buf, ' ');
+ if(p)
+ *p++ = 0;
+
+ if(strcmp(buf, "fd") == 0){
+ s.s->c = buftochan(p);
+
+ /* default is clear (msg delimiters only) */
+ s.s->state = Sclear;
+ s.s->blocklen = 1;
+ s.s->diglen = 0;
+ s.s->maxpad = s.s->max = (1<<15) - s.s->diglen - 1;
+ s.s->in.mid = 0;
+ s.s->out.mid = 0;
+ } else if(strcmp(buf, "alg") == 0 && p != 0){
+ s.s->blocklen = 1;
+ s.s->diglen = 0;
+
+ if(s.s->c == 0)
+ error("must set fd before algorithm");
+
+ if(strcmp(p, "clear") == 0){
+ s.s->state = Sclear;
+ s.s->maxpad = s.s->max = (1<<15) - s.s->diglen - 1;
+ goto out;
+ }
+
+ if(s.s->in.secret && s.s->out.secret == 0)
+ setsecret(&s.s->out, s.s->in.secret, s.s->in.slen);
+ if(s.s->out.secret && s.s->in.secret == 0)
+ setsecret(&s.s->in, s.s->out.secret, s.s->out.slen);
+ if(s.s->in.secret == 0 || s.s->out.secret == 0)
+ error("algorithm but no secret");
+
+ s.s->hf = 0;
+ s.s->encryptalg = Noencryption;
+ s.s->blocklen = 1;
+
+ for(;;){
+ np = strchr(p, ' ');
+ if(np)
+ *np++ = 0;
+ else{
+ np = strchr(p, '/');
+ if(np)
+ *np++ = 0;
+ }
+ if(parsehashalg(p, s.s) < 0)
+ if(parseencryptalg(p, s.s) < 0)
+ error(Ebadarg);
+
+ if(np == 0)
+ break;
+ p = np;
+ }
+
+ if(s.s->hf == 0 && s.s->encryptalg == Noencryption)
+ error(Ebadarg);
+
+ if(s.s->blocklen != 1){
+ /* make multiple of blocklen */
+ s.s->max = (1<<15) - s.s->diglen - 1;
+ s.s->max -= s.s->max % s.s->blocklen;
+ s.s->maxpad = (1<<14) - s.s->diglen - 1;
+ s.s->maxpad -= s.s->maxpad % s.s->blocklen;
+ } else
+ s.s->maxpad = s.s->max = (1<<15) - s.s->diglen - 1;
+ } else if(strcmp(buf, "secretin") == 0 && p != 0) {
+ m = (strlen(p)*3)/2;
+ x = smalloc(m);
+ if(waserror()){
+ free(x);
+ nexterror();
+ }
+ t = dec64(x, m, p, strlen(p));
+ setsecret(&s.s->in, x, t);
+ poperror();
+ free(x);
+ } else if(strcmp(buf, "secretout") == 0 && p != 0) {
+ m = (strlen(p)*3)/2;
+ x = smalloc(m);
+ if(waserror()){
+ free(x);
+ nexterror();
+ }
+ t = dec64(x, m, p, strlen(p));
+ setsecret(&s.s->out, x, t);
+ poperror();
+ free(x);
+ } else
+ error(Ebadarg);
+
+out:
+ qunlock(&s.s->in.ctlq);
+ qunlock(&s.s->out.q);
+ poperror();
+ return n;
+}
+
+Dev ssldevtab = {
+ 'D',
+ "ssl",
+
+ devreset,
+ sslinit,
+ devshutdown,
+ sslattach,
+ sslwalk,
+ sslstat,
+ sslopen,
+ devcreate,
+ sslclose,
+ sslread,
+ sslbread,
+ sslwrite,
+ sslbwrite,
+ devremove,
+ sslwstat,
+};
+
+static Block*
+encryptb(Dstate *s, Block *b, int offset)
+{
+ uchar *p, *ep, *p2, *ip, *eip;
+ DESstate *ds;
+ IDEAstate *is;
+
+ switch(s->encryptalg){
+ case DESECB:
+ ds = s->out.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp + offset; p < ep; p += 8)
+ block_cipher(ds->expanded, p, 0);
+ break;
+ case DESCBC:
+ ds = s->out.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp + offset; p < ep; p += 8){
+ p2 = p;
+ ip = ds->ivec;
+ for(eip = ip+8; ip < eip; )
+ *p2++ ^= *ip++;
+ block_cipher(ds->expanded, p, 0);
+ memmove(ds->ivec, p, 8);
+ }
+ break;
+ case IDEAECB:
+ is = s->out.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp + offset; p < ep; p += 8)
+ idea_cipher(is->edkey, p, 0);
+ break;
+ case IDEACBC:
+ is = s->out.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp + offset; p < ep; p += 8){
+ p2 = p;
+ ip = is->ivec;
+ for(eip = ip+8; ip < eip; )
+ *p2++ ^= *ip++;
+ idea_cipher(is->edkey, p, 0);
+ memmove(is->ivec, p, 8);
+ }
+ break;
+ case RC4:
+ rc4(s->out.state, b->rp + offset, BLEN(b) - offset);
+ break;
+ }
+ return b;
+}
+
+static Block*
+decryptb(Dstate *s, Block *inb)
+{
+ Block *b, **l;
+ uchar *p, *ep, *tp, *ip, *eip;
+ DESstate *ds;
+ IDEAstate *is;
+ uchar tmp[8];
+ int i;
+
+ l = &inb;
+ for(b = inb; b; b = b->next){
+ /* make sure we have a multiple of s->blocklen */
+ if(s->blocklen > 1){
+ i = BLEN(b);
+ if(i % s->blocklen){
+ *l = b = pullupblock(b, i + s->blocklen - (i%s->blocklen));
+ if(b == 0)
+ error("ssl encrypted message too short");
+ }
+ }
+ l = &b->next;
+
+ /* decrypt */
+ switch(s->encryptalg){
+ case DESECB:
+ ds = s->in.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp; p < ep; p += 8)
+ block_cipher(ds->expanded, p, 1);
+ break;
+ case DESCBC:
+ ds = s->in.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp; p < ep;){
+ memmove(tmp, p, 8);
+ block_cipher(ds->expanded, p, 1);
+ tp = tmp;
+ ip = ds->ivec;
+ for(eip = ip+8; ip < eip; ){
+ *p++ ^= *ip;
+ *ip++ = *tp++;
+ }
+ }
+ break;
+ case IDEAECB:
+ is = s->in.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp; p < ep; p += 8)
+ idea_cipher(is->edkey, p, 1);
+ break;
+ case IDEACBC:
+ is = s->in.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp; p < ep;){
+ memmove(tmp, p, 8);
+ idea_cipher(is->edkey, p, 1);
+ tp = tmp;
+ ip = is->ivec;
+ for(eip = ip+8; ip < eip; ){
+ *p++ ^= *ip;
+ *ip++ = *tp++;
+ }
+ }
+ break;
+ case RC4:
+ rc4(s->in.state, b->rp, BLEN(b));
+ break;
+ }
+ }
+ return inb;
+}
+
+static Block*
+digestb(Dstate *s, Block *b, int offset)
+{
+ uchar *p;
+ DigestState ss;
+ uchar msgid[4];
+ ulong n, h;
+ OneWay *w;
+
+ w = &s->out;
+
+ memset(&ss, 0, sizeof(ss));
+ h = s->diglen + offset;
+ n = BLEN(b) - h;
+
+ /* hash secret + message */
+ (*s->hf)(w->secret, w->slen, 0, &ss);
+ (*s->hf)(b->rp + h, n, 0, &ss);
+
+ /* hash message id */
+ p = msgid;
+ n = w->mid;
+ *p++ = n>>24;
+ *p++ = n>>16;
+ *p++ = n>>8;
+ *p = n;
+ (*s->hf)(msgid, 4, b->rp + offset, &ss);
+
+ return b;
+}
+
+static void
+checkdigestb(Dstate *s, Block *inb)
+{
+ uchar *p;
+ DigestState ss;
+ uchar msgid[4];
+ int n, h;
+ OneWay *w;
+ uchar digest[128];
+ Block *b;
+
+ w = &s->in;
+
+ memset(&ss, 0, sizeof(ss));
+
+ /* hash secret */
+ (*s->hf)(w->secret, w->slen, 0, &ss);
+
+ /* hash message */
+ h = s->diglen;
+ for(b = inb; b; b = b->next){
+ n = BLEN(b) - h;
+ if(n < 0)
+ panic("checkdigestb");
+ (*s->hf)(b->rp + h, n, 0, &ss);
+ h = 0;
+ }
+
+ /* hash message id */
+ p = msgid;
+ n = w->mid;
+ *p++ = n>>24;
+ *p++ = n>>16;
+ *p++ = n>>8;
+ *p = n;
+ (*s->hf)(msgid, 4, digest, &ss);
+
+ /* requires pullupblock */
+ if(memcmp(digest, inb->rp, s->diglen) != 0)
+ error("bad digest");
+}
+
+/* get channel associated with an fd */
+static Chan*
+buftochan(char *p)
+{
+ Chan *c;
+ int fd;
+
+ if(p == 0)
+ error(Ebadarg);
+ fd = strtoul(p, 0, 0);
+ if(fd < 0)
+ error(Ebadarg);
+ c = fdtochan(up->env->fgrp, fd, -1, 0, 1); /* error check and inc ref */
+ return c;
+}
+
+/* hang up a digest connection */
+static void
+sslhangup(Dstate *s)
+{
+ qlock(&s->in.q);
+ freeblist(s->processed);
+ s->processed = 0;
+ freeblist(s->unprocessed);
+ s->unprocessed = 0;
+ s->state = Sincomplete;
+ qunlock(&s->in.q);
+}
+
+extern void rbcheck(char*);
+
+static Dstate*
+dsclone(Chan *ch)
+{
+ Dstate **pp, **ep, **np;
+ int newmax;
+
+ if(waserror()) {
+ unlock(&dslock);
+ nexterror();
+ }
+ lock(&dslock);
+ ep = &dstate[maxdstate];
+ for(pp = dstate; pp < ep; pp++) {
+ if(*pp == 0) {
+ dsnew(ch, pp);
+ break;
+ }
+ }
+ if(pp >= ep) {
+ if(maxdstate >= Maxdstate) {
+ unlock(&dslock);
+ poperror();
+ return 0;
+ }
+ newmax = 2 * maxdstate;
+ if(newmax > Maxdstate)
+ newmax = Maxdstate;
+ np = realloc(dstate, sizeof(Dstate*) * newmax);
+ if(np == 0)
+ error(Enomem);
+ dstate = np;
+ pp = &dstate[maxdstate];
+ memset(pp, 0, sizeof(Dstate*)*(newmax - maxdstate));
+ maxdstate = newmax;
+ dsnew(ch, pp);
+ }
+ unlock(&dslock);
+ poperror();
+ return *pp;
+}
+
+static void
+dsnew(Chan *ch, Dstate **pp)
+{
+ Dstate *s;
+ int t;
+
+ *pp = s = malloc(sizeof(*s));
+ if(!s)
+ error(Enomem);
+ if(pp - dstate >= dshiwat)
+ dshiwat++;
+ s->state = Sincomplete;
+ s->ref = 1;
+ kstrdup(&s->user, up->env->user);
+ s->perm = 0660;
+ t = TYPE(ch->qid);
+ if(t == Qclonus)
+ t = Qctl;
+ ch->qid.path = QID(pp - dstate, t);
+ ch->qid.vers = 0;
+ ch->qid.type = QTFILE;
+}
diff --git a/os/port/devtest.c b/os/port/devtest.c
new file mode 100644
index 00000000..2c935014
--- /dev/null
+++ b/os/port/devtest.c
@@ -0,0 +1,125 @@
+/*
+ * Test device
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "libcrypt.h"
+
+#include <kernel.h>
+
+#define DEBUG 0
+
+extern void _startup(void);
+
+enum{
+ Qdir,
+ Qkt5sum,
+ Qkerndate,
+};
+
+static
+Dirtab testtab[]={
+ ".", { Qdir, 0, QTDIR}, 0, 0555,
+ "kt5sum", { Qkt5sum }, 0, 0444,
+ "kerndate", { Qkerndate }, 0, 0444,
+};
+
+
+void ktsum(char *digest)
+{
+ uchar rawdigest[MD5dlen+1];
+ int i;
+ void *start = _startup;
+ ulong size = (ulong)etext - (ulong) start;
+ md5(start, size, rawdigest, nil);
+ for (i=0; i<MD5dlen; i++)
+ sprint(&digest[2*i], "%2.2x", rawdigest[i]);
+ digest[MD5dlen*2] = 0;
+ strcat(digest, "\n");
+}
+
+static Chan*
+testattach(char *spec)
+{
+ return devattach('Z', spec);
+}
+
+static Walkqid*
+testwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, testtab, nelem(testtab), devgen);
+}
+
+static int
+teststat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, testtab, nelem(testtab), devgen);
+}
+
+static Chan*
+testopen(Chan *c, int omode)
+{
+ return devopen(c, omode, testtab, nelem(testtab), devgen);
+}
+
+static void
+testclose(Chan *)
+{
+}
+
+extern ulong kerndate;
+
+static long
+testread(Chan* c, void* a, long n, vlong offset)
+{
+ char digest[MD5dlen*2+1];
+ switch ((ulong)c->qid.path) {
+ case Qdir:
+ return devdirread(c, a, n, testtab, nelem(testtab), devgen);
+ case Qkt5sum:
+ ktsum(digest);
+ return readstr(offset, a, n, digest);
+ case Qkerndate:
+ sprint(digest, "%ld\n", kerndate);
+ return readstr(offset, a, n, digest);
+ default:
+ n = 0;
+ break;
+ }
+ return n;
+}
+
+
+static long
+testwrite(Chan*, void*, long, vlong)
+{
+ error(Ebadusefd);
+ return 0;
+}
+
+Dev testdevtab = {
+ 'Z',
+ "test",
+
+ devreset,
+ devinit,
+ devshutdown,
+ testattach,
+ testwalk,
+ teststat,
+ testopen,
+ devcreate,
+ testclose,
+ testread,
+ devbread,
+ testwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
diff --git a/os/port/devtinyfs.c b/os/port/devtinyfs.c
new file mode 100644
index 00000000..b63caf3f
--- /dev/null
+++ b/os/port/devtinyfs.c
@@ -0,0 +1,915 @@
+/*
+ * a pity the code isn't also tiny...
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+enum{
+ Qdir,
+ Qmedium,
+
+ Maxfs= 10, /* max file systems */
+
+ Blen= 48, /* block length */
+ Nlen= 28, /* name length */
+ Dlen= Blen - 4,
+
+ Tagdir= 'd',
+ Tagdata= 'D',
+ Tagend= 'e',
+ Tagfree= 'f',
+
+ Notapin= 0xffff,
+ Notabno= 0xffff,
+
+ Fcreating= 1,
+ Frmonclose= 2
+};
+
+/* representation of a Tdir on medium */
+typedef struct Mdir Mdir;
+struct Mdir {
+ uchar type;
+ uchar bno[2];
+ uchar pin[2];
+ char name[Nlen];
+ char pad[Blen - Nlen - 6];
+ uchar sum;
+};
+
+/* representation of a Tdata/Tend on medium */
+typedef struct Mdata Mdata;
+struct Mdata {
+ uchar type;
+ uchar bno[2];
+ uchar data[Dlen];
+ uchar sum;
+};
+
+typedef struct Tfile Tfile;
+struct Tfile {
+ int r;
+ char name[Nlen];
+ ushort bno;
+ ushort dbno;
+ ushort pin;
+ uchar flag;
+ ulong length;
+
+ /* hint to avoid egregious reading */
+ ushort fbno;
+ ulong finger;
+};
+
+typedef struct Tfs Tfs;
+struct Tfs {
+ QLock ql;
+ int r;
+ Chan *c;
+ uchar *map;
+ int nblocks;
+ Tfile *f;
+ int nf;
+ int fsize;
+};
+
+static struct {
+ Tfs fs[Maxfs];
+} tinyfs;
+
+#define GETS(x) ((x)[0]|((x)[1]<<8))
+#define PUTS(x, v) {(x)[0] = (v);(x)[1] = ((v)>>8);}
+
+#define GETL(x) (GETS(x)|(GETS(x+2)<<16))
+#define PUTL(x, v) {PUTS(x, v);PUTS(x+2, (v)>>16)};
+
+static uchar
+checksum(uchar *p)
+{
+ uchar *e;
+ uchar s;
+
+ s = 0;
+ for(e = p + Blen; p < e; p++)
+ s += *p;
+ return s;
+}
+
+static void
+mapclr(Tfs *fs, ulong bno)
+{
+ fs->map[bno>>3] &= ~(1<<(bno&7));
+}
+
+static void
+mapset(Tfs *fs, ulong bno)
+{
+ fs->map[bno>>3] |= 1<<(bno&7);
+}
+
+static int
+isalloced(Tfs *fs, ulong bno)
+{
+ return fs->map[bno>>3] & (1<<(bno&7));
+}
+
+static int
+mapalloc(Tfs *fs)
+{
+ int i, j, lim;
+ uchar x;
+
+ lim = (fs->nblocks + 8 - 1)/8;
+ for(i = 0; i < lim; i++){
+ x = fs->map[i];
+ if(x == 0xff)
+ continue;
+ for(j = 0; j < 8; j++)
+ if((x & (1<<j)) == 0){
+ fs->map[i] = x|(1<<j);
+ return i*8 + j;
+ }
+ }
+
+ return Notabno;
+}
+
+static Mdir*
+validdir(Tfs *fs, uchar *p)
+{
+ Mdir *md;
+ ulong x;
+
+ if(checksum(p) != 0)
+ return 0;
+ if(p[0] != Tagdir)
+ return 0;
+ md = (Mdir*)p;
+ x = GETS(md->bno);
+ if(x >= fs->nblocks)
+ return 0;
+ return md;
+}
+
+static Mdata*
+validdata(Tfs *fs, uchar *p, int *lenp)
+{
+ Mdata *md;
+ ulong x;
+
+ if(checksum(p) != 0)
+ return 0;
+ md = (Mdata*)p;
+ switch(md->type){
+ case Tagdata:
+ x = GETS(md->bno);
+ if(x >= fs->nblocks)
+ return 0;
+ if(lenp)
+ *lenp = Dlen;
+ break;
+ case Tagend:
+ x = GETS(md->bno);
+ if(x > Dlen)
+ return 0;
+ if(lenp)
+ *lenp = x;
+ break;
+ default:
+ return 0;
+ }
+ return md;
+}
+
+static Mdata*
+readdata(Tfs *fs, ulong bno, uchar *buf, int *lenp)
+{
+ if(bno >= fs->nblocks)
+ return 0;
+ if(devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno) != Blen)
+ error(Eio);
+ return validdata(fs, buf, lenp);
+}
+
+static void
+writedata(Tfs *fs, ulong bno, ulong next, uchar *buf, int len, int last)
+{
+ Mdata md;
+
+ if(bno >= fs->nblocks)
+ error(Eio);
+ if(len > Dlen)
+ len = Dlen;
+ if(len < 0)
+ error(Eio);
+ memset(&md, 0, sizeof(md));
+ if(last){
+ md.type = Tagend;
+ PUTS(md.bno, len);
+ } else {
+ md.type = Tagdata;
+ PUTS(md.bno, next);
+ }
+ memmove(md.data, buf, len);
+ md.sum = 0 - checksum((uchar*)&md);
+
+ if(devtab[fs->c->type]->write(fs->c, &md, Blen, Blen*bno) != Blen)
+ error(Eio);
+}
+
+static void
+writedir(Tfs *fs, Tfile *f)
+{
+ Mdir *md;
+ uchar buf[Blen];
+
+ if(f->bno == Notabno)
+ return;
+
+ md = (Mdir*)buf;
+ memset(buf, 0, Blen);
+ md->type = Tagdir;
+ strncpy(md->name, f->name, sizeof(md->name)-1);
+ PUTS(md->bno, f->dbno);
+ PUTS(md->pin, f->pin);
+ md->sum = 0 - checksum(buf);
+
+ if(devtab[fs->c->type]->write(fs->c, buf, Blen, Blen*f->bno) != Blen)
+ error(Eio);
+}
+
+static void
+freeblocks(Tfs *fs, ulong bno, ulong bend)
+{
+ uchar buf[Blen];
+ Mdata *md;
+
+ if(waserror())
+ return;
+
+ while(bno != bend && bno != Notabno){
+ mapclr(fs, bno);
+ if(devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno) != Blen)
+ break;
+ md = validdata(fs, buf, 0);
+ if(md == 0)
+ break;
+ if(md->type == Tagend)
+ break;
+ bno = GETS(md->bno);
+ }
+
+ poperror();
+}
+
+static void
+freefile(Tfs *fs, Tfile *f, ulong bend)
+{
+ uchar buf[Blen];
+
+ /* remove blocks from map */
+ freeblocks(fs, f->dbno, bend);
+
+ /* change file type to free on medium */
+ if(f->bno != Notabno){
+ memset(buf, 0x55, Blen);
+ devtab[fs->c->type]->write(fs->c, buf, Blen, Blen*f->bno);
+ mapclr(fs, f->bno);
+ }
+
+ /* forget we ever knew about it */
+ memset(f, 0, sizeof(*f));
+}
+
+static void
+expand(Tfs *fs)
+{
+ Tfile *f;
+
+ fs->fsize += 8;
+ f = malloc(fs->fsize*sizeof(*f));
+ if(f == nil)
+ error(Enomem);
+
+ if(fs->f){
+ memmove(f, fs->f, fs->nf*sizeof(*f));
+ free(fs->f);
+ }
+ fs->f = f;
+}
+
+static Tfile*
+newfile(Tfs *fs, char *name)
+{
+ int i;
+ volatile struct {
+ Tfile *f;
+ Tfs *fs;
+ } rock;
+
+ /* find free entry in file table */
+ rock.f = 0;
+ rock.fs = fs;
+ for(;;) {
+ for(i = 0; i < rock.fs->fsize; i++){
+ rock.f = &rock.fs->f[i];
+ if(rock.f->name[0] == 0){
+ strncpy(rock.f->name, name, sizeof(rock.f->name)-1);
+ break;
+ }
+ }
+
+ if(i < rock.fs->fsize){
+ if(i >= rock.fs->nf)
+ rock.fs->nf = i+1;
+ break;
+ }
+
+ expand(rock.fs);
+ }
+
+ rock.f->flag = Fcreating;
+ rock.f->dbno = Notabno;
+ rock.f->bno = mapalloc(rock.fs);
+ rock.f->fbno = Notabno;
+ rock.f->r = 1;
+ rock.f->pin = up->env->pgrp->pin;
+
+ /* write directory block */
+ if(waserror()){
+ freefile(rock.fs, rock.f, Notabno);
+ nexterror();
+ }
+ if(rock.f->bno == Notabno)
+ error("out of space");
+ writedir(rock.fs, rock.f);
+ poperror();
+
+ return rock.f;
+}
+
+/*
+ * Read the whole medium and build a file table and used
+ * block bitmap. Inconsistent files are purged. The medium
+ * had better be small or this could take a while.
+ */
+static void
+tfsinit(Tfs *fs)
+{
+ uchar dbuf[STATFIXLEN+4*KNAMELEN];
+ Dir d;
+ uchar buf[Blen];
+ ulong x, bno;
+ int n, done;
+ Tfile *f;
+ Mdir *mdir;
+ Mdata *mdata;
+
+ n = devtab[fs->c->type]->stat(fs->c, dbuf, sizeof(dbuf));
+ if(n == 0)
+ error(Eshortstat);
+ n = convM2D(dbuf, n, &d, nil);
+ if(n == 0)
+ error(Eshortstat);
+ fs->nblocks = d.length/Blen;
+ if(fs->nblocks < 3)
+ error("tinyfs medium too small");
+
+ /* bitmap for block usage */
+ x = (fs->nblocks + 8 - 1)/8;
+ fs->map = malloc(x);
+ if(fs->map == nil)
+ error(Enomem);
+ memset(fs->map, 0x0, x);
+ for(bno = fs->nblocks; bno < x*8; bno++)
+ mapset(fs, bno);
+
+ /* find files */
+ for(bno = 0; bno < fs->nblocks; bno++){
+ n = devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno);
+ if(n != Blen)
+ break;
+
+ mdir = validdir(fs, buf);
+ if(mdir == 0)
+ continue;
+
+ if(fs->nf >= fs->fsize)
+ expand(fs);
+
+ f = &fs->f[fs->nf++];
+
+ x = GETS(mdir->bno);
+ mapset(fs, bno);
+ strncpy(f->name, mdir->name, sizeof(f->name));
+ f->pin = GETS(mdir->pin);
+ f->bno = bno;
+ f->dbno = x;
+ f->fbno = Notabno;
+ }
+
+ /* follow files */
+ for(f = fs->f; f < &(fs->f[fs->nf]); f++){
+ bno = f->dbno;
+ for(done = 0; !done;) {
+ if(isalloced(fs, bno)){
+ freefile(fs, f, bno);
+ break;
+ }
+ n = devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno);
+ if(n != Blen){
+ freefile(fs, f, bno);
+ break;
+ }
+ mdata = validdata(fs, buf, 0);
+ if(mdata == 0){
+ freefile(fs, f, bno);
+ break;
+ }
+ mapset(fs, bno);
+ switch(mdata->type){
+ case Tagdata:
+ bno = GETS(mdata->bno);
+ f->length += Dlen;
+ break;
+ case Tagend:
+ f->length += GETS(mdata->bno);
+ done = 1;
+ break;
+ }
+ if(done)
+ f->flag &= ~Fcreating;
+ }
+ }
+}
+
+/*
+ * single directory
+ */
+static int
+tinyfsgen(Chan *c, char*, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ Tfs *fs;
+ Tfile *f;
+ Qid qid;
+
+ USED(ntab);
+ USED(tab);
+
+ fs = &tinyfs.fs[c->dev];
+ if(i >= fs->nf)
+ return -1;
+ if(i == DEVDOTDOT){
+ mkqid(&qid, Qdir, 0, QTDIR);
+ devdir(c, qid, ".", 0, eve, 0555, dp);
+ return 1;
+ }
+ f = &fs->f[i];
+ if(f->name[0] == 0)
+ return 0;
+ mkqid(&qid, i, 0, QTFILE);
+ devdir(c, qid, f->name, f->length, eve, 0664, dp);
+ return 1;
+}
+
+static void
+tinyfsinit(void)
+{
+ if(Nlen > KNAMELEN)
+ panic("tinyfsinit");
+}
+
+/*
+ * specifier is the name of a device in /dev
+ */
+static Chan*
+tinyfsattach(char *spec)
+{
+ Tfs *fs;
+ Chan *c;
+ volatile struct { Chan *cc; } rock;
+ int i;
+ char buf[KNAMELEN*3];
+
+ if(*spec == 0)
+ error("bad specifier");
+
+ snprint(buf, sizeof(buf), "/dev/%s", spec);
+ rock.cc = namec(buf, Aopen, ORDWR, 0);
+ if(waserror()){
+ cclose(rock.cc);
+ nexterror();
+ }
+
+ fs = 0;
+ for(i = 0; i < Maxfs; i++){
+ fs = &tinyfs.fs[i];
+ qlock(&fs->ql);
+ if(fs->r && eqchan(rock.cc, fs->c, 1))
+ break;
+ qunlock(&fs->ql);
+ }
+ if(i < Maxfs){
+ fs->r++;
+ qunlock(&fs->ql);
+ cclose(rock.cc);
+ } else {
+ for(fs = tinyfs.fs; fs < &tinyfs.fs[Maxfs]; fs++){
+ qlock(&fs->ql);
+ if(fs->r == 0)
+ break;
+ qunlock(&fs->ql);
+ }
+ if(fs == &tinyfs.fs[Maxfs])
+ error("too many tinyfs's");
+ fs->c = rock.cc;
+ fs->r = 1;
+ fs->f = 0;
+ fs->nf = 0;
+ fs->fsize = 0;
+ tfsinit(fs);
+ qunlock(&fs->ql);
+ }
+ poperror();
+
+ c = devattach('F', spec);
+ c->dev = fs - tinyfs.fs;
+ c->qid.path = Qdir;
+ c->qid.type = QTDIR;
+ c->qid.vers = 0;
+
+ return c;
+}
+
+static Walkqid*
+tinyfswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Tfs *fs;
+ Walkqid *w;
+
+ fs = &tinyfs.fs[c->dev];
+
+ qlock(&fs->ql);
+ if(waserror()){
+ qunlock(&fs->ql);
+ nexterror();
+ }
+ w = devwalk(c, nc, name, nname, 0, 0, tinyfsgen);
+ if(w != nil && w->clone!=nil && w->clone->qid.type != QTDIR){
+ fs = &tinyfs.fs[w->clone->dev];
+ fs->r++;
+ fs->f[(ulong)w->clone->qid.path].r++;
+ }
+ poperror();
+ qunlock(&fs->ql);
+ return w;
+}
+
+static int
+tinyfsstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, 0, 0, tinyfsgen);
+}
+
+static Chan*
+tinyfsopen(Chan *c, int omode)
+{
+ Tfile *f;
+ volatile struct { Tfs *fs; } rock;
+
+ rock.fs = &tinyfs.fs[c->dev];
+
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Eperm);
+ } else {
+ qlock(&rock.fs->ql);
+ if(waserror()){
+ qunlock(&rock.fs->ql);
+ nexterror();
+ }
+ switch(omode){
+ case OTRUNC|ORDWR:
+ case OTRUNC|OWRITE:
+ f = newfile(rock.fs, rock.fs->f[c->qid.path].name);
+ rock.fs->f[c->qid.path].r--;
+ c->qid.path = f - rock.fs->f;
+ break;
+ case OREAD:
+ break;
+ default:
+ error(Eperm);
+ }
+ qunlock(&rock.fs->ql);
+ poperror();
+ }
+
+ return devopen(c, omode, 0, 0, tinyfsgen);
+}
+
+static void
+tinyfscreate(Chan *c, char *name, int omode, ulong perm)
+{
+ volatile struct { Tfs *fs; } rock;
+ Tfile *f;
+
+ USED(perm);
+
+ rock.fs = &tinyfs.fs[c->dev];
+
+ qlock(&rock.fs->ql);
+ if(waserror()){
+ qunlock(&rock.fs->ql);
+ nexterror();
+ }
+ f = newfile(rock.fs, name);
+ qunlock(&rock.fs->ql);
+ poperror();
+
+ c->qid.path = f - rock.fs->f;
+ c->qid.vers = 0;
+ c->mode = openmode(omode);
+}
+
+static void
+tinyfsremove(Chan *c)
+{
+ Tfs *fs;
+ Tfile *f;
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+ fs = &tinyfs.fs[c->dev];
+ f = &fs->f[c->qid.path];
+ qlock(&fs->ql);
+ freefile(fs, f, Notabno);
+ qunlock(&fs->ql);
+}
+
+static void
+tinyfsclose(Chan *c)
+{
+ volatile struct { Tfs *fs; } rock;
+ Tfile *f, *nf;
+ int i;
+
+ rock.fs = &tinyfs.fs[c->dev];
+
+ qlock(&rock.fs->ql);
+
+ /* dereference file and remove old versions */
+ if(!waserror()){
+ if(c->qid.path != Qdir){
+ f = &rock.fs->f[c->qid.path];
+ f->r--;
+ if(f->r == 0){
+ if(f->flag & Frmonclose)
+ freefile(rock.fs, f, Notabno);
+ else if(f->flag & Fcreating){
+ /* remove all other files with this name */
+ for(i = 0; i < rock.fs->fsize; i++){
+ nf = &rock.fs->f[i];
+ if(f == nf)
+ continue;
+ if(strcmp(nf->name, f->name) == 0){
+ if(nf->r)
+ nf->flag |= Frmonclose;
+ else
+ freefile(rock.fs, nf, Notabno);
+ }
+ }
+ f->flag &= ~Fcreating;
+ }
+ }
+ }
+ poperror();
+ }
+
+ /* dereference rock.fs and remove on zero refs */
+ rock.fs->r--;
+ if(rock.fs->r == 0){
+ if(rock.fs->f)
+ free(rock.fs->f);
+ rock.fs->f = 0;
+ rock.fs->nf = 0;
+ rock.fs->fsize = 0;
+ if(rock.fs->map)
+ free(rock.fs->map);
+ rock.fs->map = 0;
+ cclose(rock.fs->c);
+ rock.fs->c = 0;
+ }
+ qunlock(&rock.fs->ql);
+}
+
+static long
+tinyfsread(Chan *c, void *a, long n, vlong offset)
+{
+ volatile struct { Tfs *fs; } rock;
+ Tfile *f;
+ int sofar, i, off;
+ ulong bno;
+ Mdata *md;
+ uchar buf[Blen];
+ uchar *p;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, tinyfsgen);
+
+ p = a;
+ rock.fs = &tinyfs.fs[c->dev];
+ f = &rock.fs->f[c->qid.path];
+ if(offset >= f->length)
+ return 0;
+
+ qlock(&rock.fs->ql);
+ if(waserror()){
+ qunlock(&rock.fs->ql);
+ nexterror();
+ }
+ if(n + offset >= f->length)
+ n = f->length - offset;
+
+ /* walk to starting data block */
+ if(0 && f->finger <= offset && f->fbno != Notabno){
+ sofar = f->finger;
+ bno = f->fbno;
+ } else {
+ sofar = 0;
+ bno = f->dbno;
+ }
+ for(; sofar + Dlen <= offset; sofar += Dlen){
+ md = readdata(rock.fs, bno, buf, 0);
+ if(md == 0)
+ error(Eio);
+ bno = GETS(md->bno);
+ }
+
+ /* read data */
+ off = offset%Dlen;
+ offset -= off;
+ for(sofar = 0; sofar < n; sofar += i){
+ md = readdata(rock.fs, bno, buf, &i);
+ if(md == 0)
+ error(Eio);
+
+ /* update finger for successful read */
+ f->finger = offset;
+ f->fbno = bno;
+ offset += Dlen;
+
+ i -= off;
+ if(i > n - sofar)
+ i = n - sofar;
+ memmove(p, md->data+off, i);
+ p += i;
+ bno = GETS(md->bno);
+ off = 0;
+ }
+ qunlock(&rock.fs->ql);
+ poperror();
+
+ return sofar;
+}
+
+/*
+ * if we get a write error in this routine, blocks will
+ * be lost. They should be recovered next fsinit.
+ */
+static long
+tinyfswrite(Chan *c, void *a, long n, vlong offset)
+{
+ Tfile *f;
+ int last, next, i, finger, off, used;
+ ulong bno, fbno;
+ Mdata *md;
+ uchar buf[Blen];
+ uchar *p;
+ volatile struct {
+ Tfs *fs;
+ ulong dbno;
+ } rock;
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+
+ if(n == 0)
+ return 0;
+
+ p = a;
+ rock.fs = &tinyfs.fs[c->dev];
+ f = &rock.fs->f[c->qid.path];
+
+ qlock(&rock.fs->ql);
+ rock.dbno = Notabno;
+ if(waserror()){
+ freeblocks(rock.fs, rock.dbno, Notabno);
+ qunlock(&rock.fs->ql);
+ nexterror();
+ }
+
+ /* files are append only, anything else is illegal */
+ if(offset != f->length)
+ error("append only");
+
+ /* write blocks backwards */
+ p += n;
+ last = offset + n;
+ fbno = Notabno;
+ finger = 0;
+ off = offset; /* so we have something signed to compare against */
+ for(next = ((last-1)/Dlen)*Dlen; next >= off; next -= Dlen){
+ bno = mapalloc(rock.fs);
+ if(bno == Notabno)
+ error("out of space");
+ i = last - next;
+ p -= i;
+ if(last == n+offset){
+ writedata(rock.fs, bno, rock.dbno, p, i, 1);
+ finger = next; /* remember for later */
+ fbno = bno;
+ } else {
+ writedata(rock.fs, bno, rock.dbno, p, i, 0);
+ }
+ rock.dbno = bno;
+ last = next;
+ }
+
+ /* walk to last data block */
+ md = (Mdata*)buf;
+ if(0 && f->finger < offset && f->fbno != Notabno){
+ next = f->finger;
+ bno = f->fbno;
+ } else {
+ next = 0;
+ bno = f->dbno;
+ }
+
+ used = 0;
+ while(bno != Notabno){
+ md = readdata(rock.fs, bno, buf, &used);
+ if(md == 0)
+ error(Eio);
+ if(md->type == Tagend){
+ if(next + Dlen < offset)
+ panic("devtinyfs1");
+ break;
+ }
+ next += Dlen;
+ if(next > offset)
+ panic("devtinyfs1");
+ bno = GETS(md->bno);
+ }
+
+ /* point to new blocks */
+ if(offset == 0){
+ /* first block in a file */
+ f->dbno = rock.dbno;
+ writedir(rock.fs, f);
+ } else {
+ /* updating a current block */
+ i = last - offset;
+ if(i > 0){
+ p -= i;
+ memmove(md->data + used, p, i);
+ used += i;
+ }
+ writedata(rock.fs, bno, rock.dbno, md->data, used, last == n+offset);
+ }
+ f->length += n;
+
+ /* update finger */
+ if(fbno != Notabno){
+ f->finger = finger;
+ f->fbno = fbno;
+ }
+ poperror();
+ qunlock(&rock.fs->ql);
+
+ return n;
+}
+
+Dev tinyfsdevtab = {
+ 'F',
+ "tinyfs",
+
+ devreset,
+ tinyfsinit,
+ devshutdown,
+ tinyfsattach,
+ tinyfswalk,
+ tinyfsstat,
+ tinyfsopen,
+ tinyfscreate,
+ tinyfsclose,
+ tinyfsread,
+ devbread,
+ tinyfswrite,
+ devbwrite,
+ tinyfsremove,
+ devwstat,
+};
diff --git a/os/port/devtk.c b/os/port/devtk.c
new file mode 100644
index 00000000..61afd667
--- /dev/null
+++ b/os/port/devtk.c
@@ -0,0 +1,180 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+#include <interp.h>
+
+#include "draw.h"
+
+enum{
+ Qdir,
+ Qtkevents
+};
+
+static
+Dirtab tkdirtab[]={
+ {".", {Qdir,0,QTDIR}, 0, 0500},
+ {"tkevents", {Qtkevents, 0}, 0, 0600},
+};
+
+static struct {
+ QLock l;
+ Queue* eq;
+ Ref inuse;
+} tkevents;
+
+static void
+tkwiretapper(void *top, char *cmd, char *result, void *image, Rectangle *rp)
+{
+ Block *b;
+ int n;
+ char *s, *e;
+
+ n = 12;
+ if(cmd != nil)
+ n += strlen(cmd)+2+1;
+ if(result != nil)
+ n += strlen(result)+2+1;
+ if(image != nil)
+ n += 12;
+ if(rp != nil)
+ n += 4*20;
+ n++;
+ b = allocb(n);
+ if(b != nil){
+ s = (char*)b->wp;
+ e = s+n;
+ s += snprint(s, e-s, "%p", top);
+ if(cmd != nil){
+ *s++ = ' ';
+ *s++ = '[';
+ n = strlen(cmd);
+ memmove(s, cmd, n);
+ s += n;
+ *s++ = ']';
+ }
+ /* ignore result for now */
+ if(image != nil)
+ s += snprint(s, e-s, " %p", image);
+ if(rp != nil)
+ s += snprint(s, e-s, " %d %d %d %d", rp->min.x, rp->min.y, rp->max.x, rp->max.y);
+ *s++ = '\n';
+ b->wp = (uchar*)s;
+ release();
+ qlock(&tkevents.l);
+ if(waserror()){
+ qunlock(&tkevents.l);
+ acquire();
+ return;
+ }
+ if(tkevents.eq != nil)
+ qbwrite(tkevents.eq, b);
+ poperror();
+ qunlock(&tkevents.l);
+ acquire();
+ }
+}
+
+void (*tkwiretap)(void*, char*, char*, void*, Rectangle*);
+
+static Chan*
+tkattach(char* spec)
+{
+ return devattach(L'τ', spec);
+}
+
+static Walkqid*
+tkwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, tkdirtab, nelem(tkdirtab), devgen);
+}
+
+static int
+tkstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, tkdirtab, nelem(tkdirtab), devgen);
+}
+
+static Chan*
+tkopen(Chan* c, int omode)
+{
+ if(c->qid.type & QTDIR)
+ return devopen(c, omode, tkdirtab, nelem(tkdirtab), devgen);
+ switch((ulong)c->qid.path){
+ case Qtkevents:
+ c = devopen(c, omode, tkdirtab, nelem(tkdirtab), devgen);
+ qlock(&tkevents.l);
+ if(incref(&tkevents.inuse) != 1){
+ qunlock(&tkevents.l);
+ error(Einuse);
+ }
+ if(tkevents.eq == nil)
+ tkevents.eq = qopen(256*1024, 0, nil, nil);
+ else
+ qreopen(tkevents.eq);
+ tkwiretap = tkwiretapper;
+ qunlock(&tkevents.l);
+ break;
+ }
+ return c;
+}
+
+static void
+tkclose(Chan* c)
+{
+ if(c->qid.type & QTDIR || (c->flag & COPEN) == 0)
+ return;
+ qlock(&tkevents.l);
+ if(decref(&tkevents.inuse) == 0){
+ tkwiretap = nil;
+ qclose(tkevents.eq);
+ }
+ qunlock(&tkevents.l);
+}
+
+static long
+tkread(Chan* c, void* a, long n, vlong offset)
+{
+ USED(offset);
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, tkdirtab, nelem(tkdirtab), devgen);
+ case Qtkevents:
+ return qread(tkevents.eq, a, n);
+ default:
+ n=0;
+ break;
+ }
+ return n;
+}
+
+static long
+tkwrite(Chan*, void*, long, vlong)
+{
+ error(Ebadusefd);
+ return 0;
+}
+
+Dev tkdevtab = {
+ L'τ',
+ "tk",
+
+ devreset,
+ devinit,
+ devshutdown,
+ tkattach,
+ tkwalk,
+ tkstat,
+ tkopen,
+ devcreate,
+ tkclose,
+ tkread,
+ devbread,
+ tkwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/os/port/devuart.c b/os/port/devuart.c
new file mode 100644
index 00000000..52e1841f
--- /dev/null
+++ b/os/port/devuart.c
@@ -0,0 +1,748 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "../port/netif.h"
+
+#include "../port/uart.h"
+
+enum
+{
+ /* soft flow control chars */
+ CTLS= 023,
+ CTLQ= 021,
+};
+
+extern Dev uartdevtab;
+extern PhysUart* physuart[];
+
+static Uart* uartlist;
+static Uart** uart;
+static int uartnuart;
+static Dirtab *uartdir;
+static int uartndir;
+
+struct Uartalloc {
+ Lock;
+ Uart *elist; /* list of enabled interfaces */
+} uartalloc;
+
+static void uartclock(void);
+static void uartflow(void*);
+
+/*
+ * enable/disable uart and add/remove to list of enabled uarts
+ */
+static Uart*
+uartenable(Uart *p)
+{
+ Uart **l;
+
+ if(p->iq == nil){
+ if((p->iq = qopen(4*1024, 0, uartflow, p)) == nil)
+ return nil;
+ }
+ else
+ qreopen(p->iq);
+ if(p->oq == nil){
+ if((p->oq = qopen(4*1024, 0, uartkick, p)) == nil){
+ qfree(p->iq);
+ p->iq = nil;
+ return nil;
+ }
+ }
+ else
+ qreopen(p->oq);
+
+ p->ir = p->istage;
+ p->iw = p->istage;
+ p->ie = &p->istage[Stagesize];
+ p->op = p->ostage;
+ p->oe = p->ostage;
+
+ p->hup_dsr = p->hup_dcd = 0;
+ p->dsr = p->dcd = 0;
+
+ /* assume we can send */
+ p->cts = 1;
+ p->ctsbackoff = 0;
+
+ if(p->bits == 0)
+ uartctl(p, "l8");
+ if(p->stop == 0)
+ uartctl(p, "s1");
+ if(p->parity == 0)
+ uartctl(p, "pn");
+ if(p->baud == 0)
+ uartctl(p, "b9600");
+ (*p->phys->enable)(p, 1);
+
+ lock(&uartalloc);
+ for(l = &uartalloc.elist; *l; l = &(*l)->elist){
+ if(*l == p)
+ break;
+ }
+ if(*l == 0){
+ p->elist = uartalloc.elist;
+ uartalloc.elist = p;
+ }
+ p->enabled = 1;
+ unlock(&uartalloc);
+
+ return p;
+}
+
+static void
+uartdisable(Uart *p)
+{
+ Uart **l;
+
+ (*p->phys->disable)(p);
+
+ lock(&uartalloc);
+ for(l = &uartalloc.elist; *l; l = &(*l)->elist){
+ if(*l == p){
+ *l = p->elist;
+ break;
+ }
+ }
+ p->enabled = 0;
+ unlock(&uartalloc);
+}
+
+void
+uartmouse(Uart* p, int (*putc)(Queue*, int), int setb1200)
+{
+ qlock(p);
+ if(p->opens++ == 0 && uartenable(p) == nil){
+ qunlock(p);
+ error(Enodev);
+ }
+ if(setb1200)
+ uartctl(p, "b1200");
+ p->putc = putc;
+ p->special = 1;
+ qunlock(p);
+}
+
+void
+uartsetmouseputc(Uart* p, int (*putc)(Queue*, int))
+{
+ qlock(p);
+ if(p->opens == 0 || p->special == 0){
+ qunlock(p);
+ error(Enodev);
+ }
+ p->putc = putc;
+ qunlock(p);
+}
+
+static void
+setlength(int i)
+{
+ Uart *p;
+
+ if(i > 0){
+ p = uart[i];
+ if(p && p->opens && p->iq)
+ uartdir[1+3*i].length = qlen(p->iq);
+ } else for(i = 0; i < uartnuart; i++){
+ p = uart[i];
+ if(p && p->opens && p->iq)
+ uartdir[1+3*i].length = qlen(p->iq);
+ }
+}
+
+/*
+ * set up the '#t' directory
+ */
+static void
+uartreset(void)
+{
+ int i;
+ Dirtab *dp;
+ Uart *p, *tail;
+
+ tail = nil;
+ for(i = 0; physuart[i] != nil; i++){
+ if(physuart[i]->pnp == nil)
+ continue;
+ if((p = physuart[i]->pnp()) == nil)
+ continue;
+ if(uartlist != nil)
+ tail->next = p;
+ else
+ uartlist = p;
+ for(tail = p; tail->next != nil; tail = tail->next)
+ uartnuart++;
+ uartnuart++;
+ }
+
+ if(uartnuart)
+ uart = xalloc(uartnuart*sizeof(Uart*));
+
+ uartndir = 1 + 3*uartnuart;
+ uartdir = xalloc(uartndir * sizeof(Dirtab));
+ dp = uartdir;
+ strcpy(dp->name, ".");
+ mkqid(&dp->qid, 0, 0, QTDIR);
+ dp->length = 0;
+ dp->perm = DMDIR|0555;
+ dp++;
+ p = uartlist;
+ for(i = 0; i < uartnuart; i++){
+ /* 3 directory entries per port */
+ sprint(dp->name, "eia%d", i);
+ dp->qid.path = NETQID(i, Ndataqid);
+ dp->perm = 0660;
+ dp++;
+ sprint(dp->name, "eia%dctl", i);
+ dp->qid.path = NETQID(i, Nctlqid);
+ dp->perm = 0660;
+ dp++;
+ sprint(dp->name, "eia%dstatus", i);
+ dp->qid.path = NETQID(i, Nstatqid);
+ dp->perm = 0444;
+ dp++;
+
+ uart[i] = p;
+ p->dev = i;
+ if(p->console || p->special){
+ if(uartenable(p) != nil){
+ if(p->console){
+ kbdq = p->iq;
+ printq = p->oq;
+ p->putc = kbdcr2nl;
+ }
+ p->opens++;
+ }
+ }
+ p = p->next;
+ }
+
+ if(uartnuart){
+ /*
+ * at 115200 baud, the 1024 char buffer takes 56 ms to process,
+ * processing it every 22 ms should be fine
+ */
+ addclock0link(uartclock, 22);
+ }
+}
+
+
+static Chan*
+uartattach(char *spec)
+{
+ return devattach('t', spec);
+}
+
+static Walkqid*
+uartwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, uartdir, uartndir, devgen);
+}
+
+static int
+uartstat(Chan *c, uchar *dp, int n)
+{
+ if(NETTYPE(c->qid.path) == Ndataqid)
+ setlength(NETID(c->qid.path));
+ return devstat(c, dp, n, uartdir, uartndir, devgen);
+}
+
+static Chan*
+uartopen(Chan *c, int omode)
+{
+ Uart *p;
+
+ c = devopen(c, omode, uartdir, uartndir, devgen);
+
+ switch(NETTYPE(c->qid.path)){
+ case Nctlqid:
+ case Ndataqid:
+ p = uart[NETID(c->qid.path)];
+ qlock(p);
+ if(p->opens++ == 0 && uartenable(p) == nil){
+ qunlock(p);
+ c->flag &= ~COPEN;
+ error(Enodev);
+ }
+ qunlock(p);
+ break;
+ }
+
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static int
+uartdrained(void* arg)
+{
+ Uart *p;
+
+ p = arg;
+ return qlen(p->oq) == 0 && p->op == p->oe;
+}
+
+static void
+uartdrainoutput(Uart *p)
+{
+ if(!p->enabled)
+ return;
+
+ p->drain = 1;
+ if(waserror()){
+ p->drain = 0;
+ nexterror();
+ }
+ sleep(&p->r, uartdrained, p);
+ poperror();
+}
+
+static void
+uartclose(Chan *c)
+{
+ Uart *p;
+
+ if(c->qid.type & QTDIR)
+ return;
+ if((c->flag & COPEN) == 0)
+ return;
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ case Nctlqid:
+ p = uart[NETID(c->qid.path)];
+ qlock(p);
+ if(--(p->opens) == 0){
+ qclose(p->iq);
+ p->ir = p->iw = p->istage;
+
+ /*
+ */
+ qhangup(p->oq, nil);
+ if(!waserror()){
+ uartdrainoutput(p);
+ poperror();
+ }
+ qclose(p->oq);
+ uartdisable(p);
+ p->dcd = p->dsr = p->dohup = 0;
+ }
+ qunlock(p);
+ break;
+ }
+}
+
+static long
+uartread(Chan *c, void *buf, long n, vlong off)
+{
+ Uart *p;
+ ulong offset = off;
+
+ if(c->qid.type & QTDIR){
+ setlength(-1);
+ return devdirread(c, buf, n, uartdir, uartndir, devgen);
+ }
+
+ p = uart[NETID(c->qid.path)];
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ return qread(p->iq, buf, n);
+ case Nctlqid:
+ return readnum(offset, buf, n, NETID(c->qid.path), NUMSIZE);
+ case Nstatqid:
+ return (*p->phys->status)(p, buf, n, offset);
+ }
+
+ return 0;
+}
+
+int
+uartctl(Uart *p, char *cmd)
+{
+ char *f[16];
+ int i, n, nf;
+
+ nf = tokenize(cmd, f, nelem(f));
+ for(i = 0; i < nf; i++){
+ if(strncmp(f[i], "break", 5) == 0){
+ (*p->phys->dobreak)(p, 0);
+ continue;
+ }
+
+ n = atoi(f[i]+1);
+ switch(*f[i]){
+ case 'B':
+ case 'b':
+ uartdrainoutput(p);
+ if((*p->phys->baud)(p, n) < 0)
+ return -1;
+ break;
+ case 'C':
+ case 'c':
+ p->hup_dcd = n;
+ break;
+ case 'D':
+ case 'd':
+ uartdrainoutput(p);
+ (*p->phys->dtr)(p, n);
+ break;
+ case 'E':
+ case 'e':
+ p->hup_dsr = n;
+ break;
+ case 'f':
+ case 'F':
+ if(p->oq != nil)
+ qflush(p->oq);
+ break;
+ case 'H':
+ case 'h':
+ if(p->iq != nil)
+ qhangup(p->iq, 0);
+ if(p->oq != nil)
+ qhangup(p->oq, 0);
+ break;
+ case 'i':
+ case 'I':
+ uartdrainoutput(p);
+ (*p->phys->fifo)(p, n);
+ break;
+ case 'K':
+ case 'k':
+ uartdrainoutput(p);
+ (*p->phys->dobreak)(p, n);
+ break;
+ case 'L':
+ case 'l':
+ uartdrainoutput(p);
+ if((*p->phys->bits)(p, n) < 0)
+ return -1;
+ break;
+ case 'm':
+ case 'M':
+ uartdrainoutput(p);
+ (*p->phys->modemctl)(p, n);
+ break;
+ case 'n':
+ case 'N':
+ if(p->oq != nil)
+ qnoblock(p->oq, n);
+ break;
+ case 'P':
+ case 'p':
+ uartdrainoutput(p);
+ if((*p->phys->parity)(p, *(f[i]+1)) < 0)
+ return -1;
+ break;
+ case 'Q':
+ case 'q':
+ if(p->iq != nil)
+ qsetlimit(p->iq, n);
+ if(p->oq != nil)
+ qsetlimit(p->oq, n);
+ break;
+ case 'R':
+ case 'r':
+ uartdrainoutput(p);
+ (*p->phys->rts)(p, n);
+ break;
+ case 'S':
+ case 's':
+ uartdrainoutput(p);
+ if((*p->phys->stop)(p, n) < 0)
+ return -1;
+ break;
+ case 'T':
+ case 't':
+ p->dcdts = n;
+ break;
+ case 'W':
+ case 'w':
+ /* obsolete */
+ break;
+ case 'X':
+ case 'x':
+ if(p->enabled){
+ ilock(&p->tlock);
+ p->xonoff = n;
+ iunlock(&p->tlock);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+static long
+uartwrite(Chan *c, void *buf, long n, vlong)
+{
+ Uart *p;
+ char *cmd;
+
+ if(c->qid.type & QTDIR)
+ error(Eperm);
+
+ p = uart[NETID(c->qid.path)];
+
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ qlock(p);
+ if(waserror()){
+ qunlock(p);
+ nexterror();
+ }
+
+ n = qwrite(p->oq, buf, n);
+
+ qunlock(p);
+ poperror();
+ break;
+ case Nctlqid:
+ cmd = malloc(n+1);
+ memmove(cmd, buf, n);
+ cmd[n] = 0;
+ qlock(p);
+ if(waserror()){
+ qunlock(p);
+ free(cmd);
+ nexterror();
+ }
+
+ /* let output drain */
+ if(uartctl(p, cmd) < 0)
+ error(Ebadarg);
+
+ qunlock(p);
+ poperror();
+ free(cmd);
+ break;
+ }
+
+ return n;
+}
+
+static int
+uartwstat(Chan *c, uchar *dp, int n)
+{
+ Dir d;
+ Dirtab *dt;
+
+ if(!iseve())
+ error(Eperm);
+ if(QTDIR & c->qid.type)
+ error(Eperm);
+ if(NETTYPE(c->qid.path) == Nstatqid)
+ error(Eperm);
+
+ dt = &uartdir[1 + 3 * NETID(c->qid.path)];
+ n = convM2D(dp, n, &d, nil);
+ if(n == 0)
+ error(Eshortstat);
+ if(d.mode != ~0UL)
+ dt[0].perm = dt[1].perm = d.mode;
+ return n;
+}
+
+void
+uartpower(int on)
+{
+ Uart *p;
+
+ for(p = uartlist; p != nil; p = p->next) {
+ if(p->phys->power)
+ (*p->phys->power)(p, on);
+ }
+}
+
+Dev uartdevtab = {
+ 't',
+ "uart",
+
+ uartreset,
+ devinit,
+ devshutdown,
+ uartattach,
+ uartwalk,
+ uartstat,
+ uartopen,
+ devcreate,
+ uartclose,
+ uartread,
+ devbread,
+ uartwrite,
+ devbwrite,
+ devremove,
+ uartwstat,
+ uartpower,
+};
+
+/*
+ * restart input if it's off
+ */
+static void
+uartflow(void *v)
+{
+ Uart *p;
+
+ p = v;
+ if(p->modem)
+ (*p->phys->rts)(p, 1);
+}
+
+/*
+ * put some bytes into the local queue to avoid calling
+ * qconsume for every character
+ */
+int
+uartstageoutput(Uart *p)
+{
+ int n;
+
+ n = qconsume(p->oq, p->ostage, Stagesize);
+ if(n <= 0)
+ return 0;
+ p->op = p->ostage;
+ p->oe = p->ostage + n;
+ return n;
+}
+
+/*
+ * restart output
+ */
+void
+uartkick(void *v)
+{
+ Uart *p = v;
+
+ if(p->blocked)
+ return;
+
+ ilock(&p->tlock);
+ (*p->phys->kick)(p);
+ iunlock(&p->tlock);
+
+ if(p->drain && uartdrained(p)){
+ p->drain = 0;
+ wakeup(&p->r);
+ }
+}
+
+/*
+ * receive a character at interrupt time
+ */
+void
+uartrecv(Uart *p, char ch)
+{
+ uchar *next;
+
+ /* software flow control */
+ if(p->xonoff){
+ if(ch == CTLS){
+ p->blocked = 1;
+ }else if(ch == CTLQ){
+ p->blocked = 0;
+ p->ctsbackoff = 2; /* clock gets output going again */
+ }
+ }
+
+ /* receive the character */
+ if(p->putc)
+ p->putc(p->iq, ch);
+ else{
+ next = p->iw + 1;
+ if(next == p->ie)
+ next = p->istage;
+ if(next != p->ir){
+ *p->iw = ch;
+ p->iw = next;
+ }
+ }
+}
+
+/*
+ * we save up input characters till clock time to reduce
+ * per character interrupt overhead.
+ */
+static void
+uartclock(void)
+{
+ Uart *p;
+ uchar *iw;
+
+ for(p = uartalloc.elist; p; p = p->elist){
+
+ /* this amortizes cost of qproduce to many chars */
+ if(p->iw != p->ir){
+ iw = p->iw;
+ if(iw < p->ir){
+ if(qproduce(p->iq, p->ir, p->ie-p->ir) < 0)
+ (*p->phys->rts)(p, 0);
+ p->ir = p->istage;
+ }
+ if(iw > p->ir)
+ if(qproduce(p->iq, p->ir, iw-p->ir) < 0)
+ (*p->phys->rts)(p, 0);
+ p->ir = iw;
+ }
+
+ /* hang up if requested */
+ if(p->dohup){
+ qhangup(p->iq, 0);
+ qhangup(p->oq, 0);
+ p->dohup = 0;
+ }
+
+ /* this adds hysteresis to hardware/software flow control */
+ if(p->ctsbackoff){
+ ilock(&p->tlock);
+ if(p->ctsbackoff){
+ if(--(p->ctsbackoff) == 0)
+ (*p->phys->kick)(p);
+ }
+ iunlock(&p->tlock);
+ }
+ }
+}
+
+/*
+ * polling console input, output
+ */
+
+Uart* consuart;
+
+int
+uartgetc(void)
+{
+ if(consuart == nil || consuart->phys->getc == nil)
+ return -1;
+ return consuart->phys->getc(consuart);
+}
+
+void
+uartputc(int c)
+{
+ if(consuart == nil || consuart->phys->putc == nil)
+ return;
+ consuart->phys->putc(consuart, c);
+}
+
+void
+uartputs(char *s, int n)
+{
+ char *e;
+
+ if(consuart == nil || consuart->phys->putc == nil)
+ return;
+
+ e = s+n;
+ for(; s<e; s++){
+ if(*s == '\n')
+ consuart->phys->putc(consuart, '\r');
+ consuart->phys->putc(consuart, *s);
+ }
+}
diff --git a/os/port/dial.c b/os/port/dial.c
new file mode 100644
index 00000000..d07671dc
--- /dev/null
+++ b/os/port/dial.c
@@ -0,0 +1,417 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "kernel.h"
+
+typedef struct DS DS;
+
+static int call(char*, char*, DS*);
+static int csdial(DS*);
+static void _dial_string_parse(char*, DS*);
+static int nettrans(char*, char*, int na, char*, int);
+
+enum
+{
+ Maxstring= 128,
+ Maxpath= 100
+};
+
+struct DS
+{
+ char buf[Maxstring]; /* dist string */
+ char *netdir;
+ char *proto;
+ char *rem;
+ char *local; /* other args */
+ char *dir;
+ int *cfdp;
+};
+
+/*
+ * the dialstring is of the form '[/net/]proto!dest'
+ */
+int
+kdial(char *dest, char *local, char *dir, int *cfdp)
+{
+ DS ds;
+ int rv;
+ char err[ERRMAX], alterr[ERRMAX];
+
+ ds.local = local;
+ ds.dir = dir;
+ ds.cfdp = cfdp;
+
+ _dial_string_parse(dest, &ds);
+ if(ds.netdir)
+ return csdial(&ds);
+
+ ds.netdir = "/net";
+ rv = csdial(&ds);
+ if(rv >= 0)
+ return rv;
+
+ err[0] = 0;
+ kerrstr(err, sizeof err);
+ if(strstr(err, "refused") != 0){
+ kerrstr(err, sizeof err);
+ return rv;
+ }
+
+ ds.netdir = "/net.alt";
+ rv = csdial(&ds);
+ if(rv >= 0)
+ return rv;
+
+ alterr[0] = 0;
+ kerrstr(alterr, sizeof err);
+
+ if(strstr(alterr, "translate") || strstr(alterr, "does not exist"))
+ kerrstr(err, sizeof err);
+ else
+ kerrstr(alterr, sizeof alterr);
+ return rv;
+}
+
+static int
+csdial(DS *ds)
+{
+ int n, fd, rv;
+ char *p, buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX];
+
+ /*
+ * open connection server
+ */
+ snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
+ fd = kopen(buf, ORDWR);
+ if(fd < 0){
+ /* no connection server, don't translate */
+ snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
+ return call(clone, ds->rem, ds);
+ }
+
+ /*
+ * ask connection server to translate
+ */
+ sprint(buf, "%s!%s", ds->proto, ds->rem);
+ if(kwrite(fd, buf, strlen(buf)) < 0){
+ kerrstr(err, sizeof err);
+ kclose(fd);
+ kwerrstr("%s (%s)", err, buf);
+ return -1;
+ }
+
+ /*
+ * loop through each address from the connection server till
+ * we get one that works.
+ */
+ *besterr = 0;
+ strcpy(err, Egreg);
+ rv = -1;
+ kseek(fd, 0, 0);
+ while((n = kread(fd, buf, sizeof(buf) - 1)) > 0){
+ buf[n] = 0;
+ p = strchr(buf, ' ');
+ if(p == 0)
+ continue;
+ *p++ = 0;
+ rv = call(buf, p, ds);
+ if(rv >= 0)
+ break;
+ err[0] = 0;
+ kerrstr(err, sizeof err);
+ if(strstr(err, "does not exist") == 0)
+ memmove(besterr, err, sizeof besterr);
+ }
+ kclose(fd);
+
+ if(rv < 0 && *besterr)
+ kerrstr(besterr, sizeof besterr);
+ else
+ kerrstr(err, sizeof err);
+ return rv;
+}
+
+static int
+call(char *clone, char *dest, DS *ds)
+{
+ int fd, cfd, n;
+ char name[Maxpath], data[Maxpath], err[ERRMAX], *p;
+
+ cfd = kopen(clone, ORDWR);
+ if(cfd < 0){
+ kerrstr(err, sizeof err);
+ kwerrstr("%s (%s)", err, clone);
+ return -1;
+ }
+
+ /* get directory name */
+ n = kread(cfd, name, sizeof(name)-1);
+ if(n < 0){
+ kerrstr(err, sizeof err);
+ kclose(cfd);
+ kwerrstr("read %s: %s", clone, err);
+ return -1;
+ }
+ name[n] = 0;
+ for(p = name; *p == ' '; p++)
+ ;
+ sprint(name, "%ld", strtoul(p, 0, 0));
+ p = strrchr(clone, '/');
+ *p = 0;
+ if(ds->dir)
+ snprint(ds->dir, NETPATHLEN, "%s/%s", clone, name);
+ snprint(data, sizeof(data), "%s/%s/data", clone, name);
+
+ /* connect */
+ if(ds->local)
+ snprint(name, sizeof(name), "connect %s %s", dest, ds->local);
+ else
+ snprint(name, sizeof(name), "connect %s", dest);
+ if(kwrite(cfd, name, strlen(name)) < 0){
+ err[0] = 0;
+ kerrstr(err, sizeof err);
+ kclose(cfd);
+ kwerrstr("%s (%s)", err, name);
+ return -1;
+ }
+
+ /* open data connection */
+ fd = kopen(data, ORDWR);
+ if(fd < 0){
+ err[0] = 0;
+ kerrstr(err, sizeof err);
+ kwerrstr("%s (%s)", err, data);
+ kclose(cfd);
+ return -1;
+ }
+ if(ds->cfdp)
+ *ds->cfdp = cfd;
+ else
+ kclose(cfd);
+
+ return fd;
+}
+
+/*
+ * parse a dial string
+ */
+static void
+_dial_string_parse(char *str, DS *ds)
+{
+ char *p, *p2;
+
+ strncpy(ds->buf, str, Maxstring);
+ ds->buf[Maxstring-1] = 0;
+
+ p = strchr(ds->buf, '!');
+ if(p == 0) {
+ ds->netdir = 0;
+ ds->proto = "net";
+ ds->rem = ds->buf;
+ } else {
+ if(*ds->buf != '/' && *ds->buf != '#'){
+ ds->netdir = 0;
+ ds->proto = ds->buf;
+ } else {
+ for(p2 = p; *p2 != '/'; p2--)
+ ;
+ *p2++ = 0;
+ ds->netdir = ds->buf;
+ ds->proto = p2;
+ }
+ *p = 0;
+ ds->rem = p + 1;
+ }
+}
+
+/*
+ * announce a network service.
+ */
+int
+kannounce(char *addr, char *dir)
+{
+ int ctl, n, m;
+ char buf[NETPATHLEN];
+ char buf2[Maxpath];
+ char netdir[NETPATHLEN];
+ char naddr[Maxpath];
+ char *cp;
+
+ /*
+ * translate the address
+ */
+ if(nettrans(addr, naddr, sizeof(naddr), netdir, sizeof(netdir)) < 0)
+ return -1;
+
+ /*
+ * get a control channel
+ */
+ ctl = kopen(netdir, ORDWR);
+ if(ctl<0)
+ return -1;
+ cp = strrchr(netdir, '/');
+ *cp = 0;
+
+ /*
+ * find out which line we have
+ */
+ n = sprint(buf, "%.*s/", sizeof buf, netdir);
+ m = kread(ctl, &buf[n], sizeof(buf)-n-1);
+ if(m <= 0){
+ kclose(ctl);
+ return -1;
+ }
+ buf[n+m] = 0;
+
+ /*
+ * make the call
+ */
+ n = snprint(buf2, sizeof buf2, "announce %s", naddr);
+ if(kwrite(ctl, buf2, n)!=n){
+ kclose(ctl);
+ return -1;
+ }
+
+ /*
+ * return directory etc.
+ */
+ if(dir)
+ strcpy(dir, buf);
+ return ctl;
+}
+
+/*
+ * listen for an incoming call
+ */
+int
+klisten(char *dir, char *newdir)
+{
+ int ctl, n, m;
+ char buf[NETPATHLEN];
+ char *cp;
+
+ /*
+ * open listen, wait for a call
+ */
+ snprint(buf, sizeof buf, "%s/listen", dir);
+ ctl = kopen(buf, ORDWR);
+ if(ctl < 0)
+ return -1;
+
+ /*
+ * find out which line we have
+ */
+ strcpy(buf, dir);
+ cp = strrchr(buf, '/');
+ *++cp = 0;
+ n = cp-buf;
+ m = kread(ctl, cp, sizeof(buf) - n - 1);
+ if(m <= 0){
+ kclose(ctl);
+ return -1;
+ }
+ buf[n+m] = 0;
+
+ /*
+ * return directory etc.
+ */
+ if(newdir)
+ strcpy(newdir, buf);
+ return ctl;
+
+}
+
+/*
+ * perform the identity translation (in case we can't reach cs)
+ */
+static int
+identtrans(char *netdir, char *addr, char *naddr, int na, char *file, int nf)
+{
+ char proto[Maxpath];
+ char *p;
+
+ USED(nf);
+
+ /* parse the protocol */
+ strncpy(proto, addr, sizeof(proto));
+ proto[sizeof(proto)-1] = 0;
+ p = strchr(proto, '!');
+ if(p)
+ *p++ = 0;
+
+ snprint(file, nf, "%s/%s/clone", netdir, proto);
+ strncpy(naddr, p, na);
+ naddr[na-1] = 0;
+
+ return 1;
+}
+
+/*
+ * call up the connection server and get a translation
+ */
+static int
+nettrans(char *addr, char *naddr, int na, char *file, int nf)
+{
+ int i, fd;
+ char buf[Maxpath];
+ char netdir[NETPATHLEN];
+ char *p, *p2;
+ long n;
+
+ /*
+ * parse, get network directory
+ */
+ p = strchr(addr, '!');
+ if(p == 0){
+ kwerrstr("bad dial string: %s", addr);
+ return -1;
+ }
+ if(*addr != '/'){
+ strcpy(netdir, "/net");
+ } else {
+ for(p2 = p; *p2 != '/'; p2--)
+ ;
+ i = p2 - addr;
+ if(i == 0 || i >= sizeof(netdir)){
+ kwerrstr("bad dial string: %s", addr);
+ return -1;
+ }
+ strncpy(netdir, addr, i);
+ netdir[i] = 0;
+ addr = p2 + 1;
+ }
+
+ /*
+ * ask the connection server
+ */
+ sprint(buf, "%s/cs", netdir);
+ fd = kopen(buf, ORDWR);
+ if(fd < 0)
+ return identtrans(netdir, addr, naddr, na, file, nf);
+ if(kwrite(fd, addr, strlen(addr)) < 0){
+ kclose(fd);
+ return -1;
+ }
+ kseek(fd, 0, 0);
+ n = kread(fd, buf, sizeof(buf)-1);
+ kclose(fd);
+ if(n <= 0)
+ return -1;
+ buf[n] = 0;
+
+ /*
+ * parse the reply
+ */
+ p = strchr(buf, ' ');
+ if(p == 0)
+ return -1;
+ *p++ = 0;
+ strncpy(naddr, p, na);
+ naddr[na-1] = 0;
+ strncpy(file, buf, nf);
+ file[nf-1] = 0;
+ return 0;
+}
diff --git a/os/port/dis.c b/os/port/dis.c
new file mode 100644
index 00000000..2b5ac937
--- /dev/null
+++ b/os/port/dis.c
@@ -0,0 +1,1125 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <isa.h>
+#include <interp.h>
+#include <kernel.h>
+#include "raise.h"
+
+ void vmachine(void*);
+
+struct
+{
+ Lock l;
+ Prog* runhd;
+ Prog* runtl;
+ Prog* head;
+ Prog* tail;
+ Rendez irend;
+ int idle;
+ int nyield;
+ int creating;
+ Proc* vmq; /* queue of procs wanting vm */
+ Proc* vmqt;
+ Proc* idlevmq; /* queue of procs wanting work */
+ Atidle* idletasks;
+} isched;
+
+int bflag;
+int cflag;
+uvlong gcbusy;
+uvlong gcidle;
+uvlong gcidlepass;
+uvlong gcpartial;
+int keepbroken = 1;
+static Prog* proghash[64];
+
+static Progs* delgrp(Prog*);
+static void addgrp(Prog*, Prog*);
+void printgrp(Prog*, char*);
+
+static Prog**
+pidlook(int pid)
+{
+ ulong h;
+ Prog **l;
+
+ h = (ulong)pid % nelem(proghash);
+ for(l = &proghash[h]; *l != nil && (*l)->pid != pid; l = &(*l)->pidlink)
+ ;
+ return l;
+}
+
+int
+tready(void *a)
+{
+ USED(a);
+ return isched.runhd != nil || isched.vmq != nil;
+}
+
+Prog*
+progpid(int pid)
+{
+ return *pidlook(pid);
+}
+
+Prog*
+progn(int n)
+{
+ Prog *p;
+
+ for(p = isched.head; p && n--; p = p->next)
+ ;
+ return p;
+}
+
+int
+nprog(void)
+{
+ int n;
+ Prog *p;
+
+ n = 0;
+ for(p = isched.head; p; p = p->next)
+ n++;
+ return n;
+}
+
+static void
+accountant(void)
+{
+ Prog *p;
+
+ p = isched.runhd;
+ if(p != nil)
+ p->ticks++;
+}
+
+static void
+execatidle(void)
+{
+ int done;
+
+ if(tready(nil))
+ return;
+
+ gcidle++;
+ up->type = IdleGC;
+ up->iprog = nil;
+ addrun(up->prog);
+
+ done = gccolor+3;
+ while(gccolor < done && gcruns()) {
+ if(isched.vmq != nil || isched.runhd != isched.runtl) {
+ gcpartial++;
+ break;
+ }
+ rungc(isched.head);
+ gcidlepass++;
+ sched();
+ }
+
+ up->type = Interp;
+ delrunq(up->prog);
+}
+
+Prog*
+newprog(Prog *p, Modlink *m)
+{
+ Heap *h;
+ Prog *n, **ph;
+ Osenv *on, *op;
+ static int pidnum;
+
+ n = malloc(sizeof(Prog)+sizeof(Osenv));
+ if(n == 0){
+ if(p == nil)
+ panic("no memory");
+ else
+ error(exNomem);
+ }
+
+ n->pid = ++pidnum;
+ if(n->pid <= 0)
+ panic("no pids");
+ n->group = nil;
+
+ if(isched.tail != nil) {
+ n->prev = isched.tail;
+ isched.tail->next = n;
+ }
+ else {
+ isched.head = n;
+ n->prev = nil;
+ }
+ isched.tail = n;
+
+ ph = pidlook(n->pid);
+ if(*ph != nil)
+ panic("dup pid");
+ n->pidlink = nil;
+ *ph = n;
+
+ n->osenv = (Osenv*)((uchar*)n + sizeof(Prog));
+ n->xec = xec;
+ n->quanta = PQUANTA;
+ n->flags = 0;
+ n->exval = H;
+
+ h = D2H(m);
+ h->ref++;
+ Setmark(h);
+ n->R.M = m;
+ n->R.MP = m->MP;
+ if(m->MP != H)
+ Setmark(D2H(m->MP));
+ addrun(n);
+
+ if(p == nil){
+ newgrp(n);
+ return n;
+ }
+
+ addgrp(n, p);
+ n->flags = p->flags;
+ if(p->flags & Prestrict)
+ n->flags |= Prestricted;
+ memmove(n->osenv, p->osenv, sizeof(Osenv));
+ op = p->osenv;
+ on = n->osenv;
+ on->waitq = op->childq;
+ on->childq = nil;
+ on->debug = nil;
+ incref(on->pgrp);
+ incref(on->fgrp);
+ if(on->egrp != nil)
+ incref(on->egrp);
+ if(on->sigs != nil)
+ incref(on->sigs);
+ on->user = nil;
+ kstrdup(&on->user, op->user);
+ on->errstr = on->errbuf0;
+ on->syserrstr = on->errbuf1;
+
+ return n;
+}
+
+void
+delprog(Prog *p, char *msg)
+{
+ Osenv *o;
+ Prog **ph;
+
+ tellsomeone(p, msg); /* call before being removed from prog list */
+
+ o = p->osenv;
+ release();
+ closepgrp(o->pgrp);
+ closefgrp(o->fgrp);
+ closeegrp(o->egrp);
+ closesigs(o->sigs);
+ acquire();
+
+ delgrp(p);
+
+ if(p->prev)
+ p->prev->next = p->next;
+ else
+ isched.head = p->next;
+
+ if(p->next)
+ p->next->prev = p->prev;
+ else
+ isched.tail = p->prev;
+
+ ph = pidlook(p->pid);
+ if(*ph == nil)
+ panic("lost pid");
+ *ph = p->pidlink;
+
+ if(p == isched.runhd) {
+ isched.runhd = p->link;
+ if(p->link == nil)
+ isched.runtl = nil;
+ }
+ p->state = 0xdeadbeef;
+ free(o->user);
+ free(p->killstr);
+ free(p->exstr);
+ free(p);
+}
+
+void
+renameproguser(char *old, char *new)
+{
+ Prog *p;
+ Osenv *o;
+
+ acquire();
+ for(p = isched.head; p; p = p->next){
+ o = p->osenv;
+ if(o->user != nil && strcmp(o->user, old) == 0)
+ kstrdup(&o->user, new);
+ }
+ release();
+}
+
+void
+tellsomeone(Prog *p, char *buf)
+{
+ Osenv *o;
+
+ if(waserror())
+ return;
+ o = p->osenv;
+ if(o->childq != nil)
+ qproduce(o->childq, buf, strlen(buf));
+ if(o->waitq != nil)
+ qproduce(o->waitq, buf, strlen(buf));
+ poperror();
+}
+
+static void
+swiprog(Prog *p)
+{
+ Proc *q, *eq;
+
+ q = proctab(0);
+ for(eq = q+conf.nproc; q < eq; q++) {
+ if(q->iprog == p) {
+ swiproc(q, 1);
+ return;
+ }
+ }
+ /*print("didn't find\n");*/
+}
+
+static Prog*
+grpleader(Prog *p)
+{
+ Progs *g;
+ Prog *l;
+
+ g = p->group;
+ if(g != nil && (l = g->head) != nil && l->pid == g->id)
+ return l;
+ return nil;
+}
+
+int
+exprog(Prog *p, char *exc)
+{
+ switch(p->state) {
+ case Palt:
+ altdone(p->R.s, p, nil, -1);
+ break;
+ case Psend:
+ cqdelp(&p->chan->send, p);
+ break;
+ case Precv:
+ cqdelp(&p->chan->recv, p);
+ break;
+ case Pready:
+ break;
+ case Prelease:
+ swiprog(p);
+ break;
+ case Pexiting:
+ case Pbroken:
+ case Pdebug:
+ return 0;
+ default:
+ panic("exprog - bad state 0x%x\n", p->state);
+ }
+ if(p->state != Pready && p->state != Prelease)
+ addrun(p);
+ if(p->kill == nil){
+ if(p->killstr == nil){
+ p->killstr = malloc(ERRMAX);
+ if(p->killstr == nil){
+ p->kill = Enomem;
+ return 1;
+ }
+ }
+ kstrcpy(p->killstr, exc, ERRMAX);
+ p->kill = p->killstr;
+ }
+ return 1;
+}
+
+static void
+propex(Prog *p, char *estr)
+{
+ Prog *f, *nf, *pgl;
+
+ if(!(p->flags & (Ppropagate|Pnotifyleader)) || p->group == nil)
+ return;
+ if(*estr == 0){
+ if((p->flags & Pkilled) == 0)
+ return;
+ estr = "killed";
+ }
+ pgl = grpleader(p);
+ if(pgl == nil)
+ pgl = p;
+ if(!(pgl->flags & (Ppropagate|Pnotifyleader)))
+ return; /* exceptions are local; don't propagate */
+ for(f = p->group->head; f != nil; f = nf){
+ nf = f->grpnext;
+ if(f != p && f != pgl){
+ if(pgl->flags & Ppropagate)
+ exprog(f, estr);
+ else{
+ f->flags &= ~(Ppropagate|Pnotifyleader); /* prevent recursion */
+ killprog(f, "killed");
+ }
+ }
+ }
+ if(p != pgl)
+ exprog(pgl, estr);
+}
+
+int
+killprog(Prog *p, char *cause)
+{
+ Osenv *env;
+ char msg[ERRMAX+2*KNAMELEN];
+
+ if(p == isched.runhd) {
+ p->kill = "";
+ p->flags |= Pkilled;
+ p->state = Pexiting;
+ return 0;
+ }
+
+ switch(p->state) {
+ case Palt:
+ altdone(p->R.s, p, nil, -1);
+ break;
+ case Psend:
+ cqdelp(&p->chan->send, p);
+ break;
+ case Precv:
+ cqdelp(&p->chan->recv, p);
+ break;
+ case Pready:
+ delrunq(p);
+ break;
+ case Prelease:
+ p->kill = "";
+ p->flags |= Pkilled;
+ p->state = Pexiting;
+ swiprog(p);
+ /* No break */
+ case Pexiting:
+ return 0;
+ case Pbroken:
+ case Pdebug:
+ break;
+ default:
+ panic("killprog - bad state 0x%x\n", p->state);
+ }
+
+ if(p->addrun != nil) {
+ p->kill = "";
+ p->flags |= Pkilled;
+ p->addrun(p);
+ p->addrun = nil;
+ return 0;
+ }
+
+ env = p->osenv;
+ if(env->debug != nil) {
+ p->state = Pbroken;
+ dbgexit(p, 0, cause);
+ return 0;
+ }
+
+ propex(p, "killed");
+
+ snprint(msg, sizeof(msg), "%d \"%s\":%s", p->pid, p->R.M->m->name, cause);
+
+ p->state = Pexiting;
+ gclock();
+ destroystack(&p->R);
+ delprog(p, msg);
+ gcunlock();
+
+ return 1;
+}
+
+void
+newgrp(Prog *p)
+{
+ Progs *pg, *g;
+
+ if(p->group != nil && p->group->id == p->pid)
+ return;
+ g = malloc(sizeof(*g));
+ if(g == nil)
+ error(Enomem);
+ p->flags &= ~(Ppropagate|Pnotifyleader);
+ g->id = p->pid;
+ g->flags = 0;
+ g->child = nil;
+ pg = delgrp(p);
+ g->head = g->tail = p;
+ p->group = g;
+ if(pg != nil){
+ g->sib = pg->child;
+ pg->child = g;
+ }
+ g->parent = pg;
+}
+
+static void
+addgrp(Prog *n, Prog *p)
+{
+ Progs *g;
+
+ n->group = p->group;
+ if((g = n->group) != nil){
+ n->grpnext = nil;
+ if(g->head != nil){
+ n->grpprev = g->tail;
+ g->tail->grpnext = n;
+ }else{
+ n->grpprev = nil;
+ g->head = n;
+ }
+ g->tail = n;
+ }
+}
+
+static Progs*
+delgrp(Prog *p)
+{
+ Progs *g, *pg, *cg, **l;
+
+ g = p->group;
+ if(g == nil)
+ return nil;
+ if(p->grpprev)
+ p->grpprev->grpnext = p->grpnext;
+ else
+ g->head = p->grpnext;
+ if(p->grpnext)
+ p->grpnext->grpprev = p->grpprev;
+ else
+ g->tail = p->grpprev;
+ p->grpprev = p->grpnext = nil;
+ p->group = nil;
+
+ if(g->head == nil){
+ /* move up, giving subgroups of groups with no Progs to their parents */
+ do{
+ if((pg = g->parent) != nil){
+ pg = g->parent;
+ for(l = &pg->child; *l != nil && *l != g; l = &(*l)->sib)
+ ;
+ *l = g->sib;
+ }
+ /* put subgroups in new parent group */
+ while((cg = g->child) != nil){
+ g->child = cg->sib;
+ cg->parent = pg;
+ if(pg != nil){
+ cg->sib = pg->child;
+ pg->child = cg;
+ }
+ }
+ free(g);
+ }while((g = pg) != nil && g->head == nil);
+ }
+ return g;
+}
+
+void
+printgrp(Prog *p, char *v)
+{
+ Progs *g;
+ Prog *q;
+
+ g = p->group;
+ print("%s pid %d grp %d pgrp %d: [pid", v, p->pid, g->id, g->parent!=nil?g->parent->id:0);
+ for(q = g->head; q != nil; q = q->grpnext)
+ print(" %d", q->pid);
+ print(" subgrp");
+ for(g = g->child; g != nil; g = g->sib)
+ print(" %d", g->id);
+ print("]\n");
+}
+
+int
+killgrp(Prog *p, char *msg)
+{
+ int i, npid, *pids;
+ Prog *f;
+ Progs *g;
+
+ /* interpreter has been acquired */
+ g = p->group;
+ if(g == nil || g->head == nil)
+ return 0;
+ npid = 0;
+ for(f = g->head; f != nil; f = f->grpnext)
+ if(f->group != g)
+ panic("killgrp");
+ else
+ npid++;
+ /* use pids not Prog* because state can change during killprog */
+ pids = malloc(npid*sizeof(int));
+ if(pids == nil)
+ error(Enomem);
+ npid = 0;
+ for(f = g->head; f != nil; f = f->grpnext)
+ pids[npid++] = f->pid;
+ if(waserror()) {
+ free(pids);
+ nexterror();
+ }
+ for(i = 0; i < npid; i++) {
+ f = progpid(pids[i]);
+ if(f != nil && f != currun())
+ killprog(f, msg);
+ }
+ poperror();
+ free(pids);
+ return 1;
+}
+
+char changup[] = "channel hangup";
+
+void
+killcomm(Progq **q)
+{
+ Prog *p;
+ Progq *f;
+
+ for (f = *q; f != nil; f = *q) {
+ *q = f->next;
+ p = f->prog;
+ free(f);
+ if(p == nil)
+ return;
+ p->ptr = nil;
+ switch(p->state) {
+ case Prelease:
+ swiprog(p);
+ break;
+ case Psend:
+ case Precv:
+ p->kill = changup;
+ addrun(p);
+ break;
+ case Palt:
+ altgone(p);
+ break;
+ }
+ }
+}
+
+void
+addprog(Proc *p)
+{
+ Prog *n;
+
+ if((n = p->prog) == nil) {
+ n = malloc(sizeof(Prog));
+ if(n == nil)
+ panic("no memory");
+ p->prog = n;
+ } else
+ memset(n, 0, sizeof(Prog));
+ n->osenv = p->env;
+}
+
+static void
+cwakeme(Prog *p)
+{
+ Osenv *o;
+
+ p->addrun = nil;
+ o = p->osenv;
+ wakeup(o->rend);
+}
+
+static int
+cdone(void *vp)
+{
+ Prog *p = vp;
+
+ return p->addrun == nil || p->kill != nil;
+}
+
+void
+cblock(Prog *p)
+{
+ Osenv *o;
+
+ p->addrun = cwakeme;
+ o = p->osenv;
+ o->rend = &up->sleep;
+ release();
+
+ /*
+ * To allow cdone(p) safely after release,
+ * p must be currun before the release.
+ * Exits in the error case with the vm acquired.
+ */
+ if(waserror()) {
+ acquire();
+ p->addrun = nil;
+ nexterror();
+ }
+ sleep(o->rend, cdone, p);
+ if (p->kill != nil)
+ error(Eintr);
+ poperror();
+ acquire();
+}
+
+void
+addrun(Prog *p)
+{
+ if(p->addrun != 0) {
+ p->addrun(p);
+ return;
+ }
+ if(p->state == Pready && p != (Prog *)up->prog)
+ panic("addrun of ready prog %8.8p by %8.8lux\n", p, getcallerpc(&p));
+ p->state = Pready;
+ p->link = nil;
+ if(isched.runhd == nil)
+ isched.runhd = p;
+ else
+ isched.runtl->link = p;
+
+ isched.runtl = p;
+}
+
+Prog*
+delrun(int state)
+{
+ Prog *p;
+
+ p = isched.runhd;
+ p->state = state;
+ isched.runhd = p->link;
+ if(p->link == nil)
+ isched.runtl = nil;
+
+ return p;
+}
+
+void
+delrunq(Prog *p)
+{
+ Prog *prev, *f;
+
+ prev = nil;
+ for(f = isched.runhd; f; f = f->link) {
+ if(f == p)
+ break;
+ prev = f;
+ }
+ if(f == nil)
+ return;
+ if(prev == nil)
+ isched.runhd = p->link;
+ else
+ prev->link = p->link;
+ if(p == isched.runtl)
+ isched.runtl = prev;
+}
+
+Prog*
+delruntail(int state)
+{
+ Prog *p;
+
+ p = isched.runtl;
+ delrunq(isched.runtl);
+ p->state = state;
+ return p;
+}
+
+Prog*
+currun(void)
+{
+ return isched.runhd;
+}
+
+Prog*
+schedmod(Module *m)
+{
+ Heap *h;
+ Type *t;
+ Prog *p;
+ Modlink *ml;
+ Frame f, *fp;
+
+ ml = mklinkmod(m, 0);
+
+ if(m->origmp != H && m->ntype > 0) {
+ t = m->type[0];
+ h = nheap(t->size);
+ h->t = t;
+ t->ref++;
+ ml->MP = H2D(uchar*, h);
+ newmp(ml->MP, m->origmp, t);
+ }
+
+ p = newprog(nil, ml);
+ h = D2H(ml);
+ h->ref--;
+ p->R.PC = m->entry;
+ fp = &f;
+ R.s = &fp;
+ f.t = m->entryt;
+ newstack(p);
+ initmem(m->entryt, p->R.FP);
+
+ return p;
+}
+
+void
+acquire(void)
+{
+ int empty;
+ Prog *p;
+
+ lock(&isched.l);
+ if(isched.idle) {
+ isched.idle = 0;
+ unlock(&isched.l);
+ }
+ else {
+ up->qnext = nil;
+ if(isched.vmq != nil){
+ empty = 0;
+ isched.vmqt->qnext = up;
+ }else{
+ isched.vmq = up;
+ empty = 1;
+ }
+ isched.vmqt = up;
+
+ up->state = Queueing;
+ up->pc = getcallerpc(&empty);
+ unlock(&isched.l);
+ if(empty)
+ wakeup(&isched.irend);
+ sched();
+ }
+
+ if(up->type == Interp) {
+ p = up->iprog;
+ up->iprog = nil;
+ irestore(p);
+ }
+ else
+ p = up->prog;
+
+ p->state = Pready;
+ p->link = isched.runhd;
+ isched.runhd = p;
+ if(p->link == nil)
+ isched.runtl = p;
+}
+
+void
+release(void)
+{
+ Proc *p, **pq;
+ int f;
+
+ if(up->type == Interp){
+ if(up->iprog != nil)
+ panic("Double release (Interp)?");
+ up->iprog = isave();
+ }else{
+ if(((Prog *)up->prog)->state != Pready) panic("double release (GC)?");
+ delrun(Prelease);
+ }
+
+ lock(&isched.l);
+ if(*(pq = &isched.vmq) == nil && *(pq = &isched.idlevmq) == nil) {
+ isched.idle = 1;
+ f = isched.creating;
+ isched.creating = 1;
+ unlock(&isched.l);
+ if(f == 0)
+ kproc("dis", vmachine, nil, 0);
+ return;
+ }
+ p = *pq;
+ *pq = p->qnext;
+ unlock(&isched.l);
+
+ ready(p);
+}
+
+void
+iyield(void)
+{
+ Proc *p;
+
+ lock(&isched.l);
+ p = isched.vmq;
+ if(p == nil) {
+ unlock(&isched.l);
+ return;
+ }
+ isched.nyield++;
+ isched.vmq = p->qnext;
+
+ if(up->iprog != nil)
+ panic("iyield but iprog, type %d", up->type);
+ if(up->type != Interp){
+ static int once;
+ if(!once++)
+ print("tell charles: #%p->type==%d\n", up, up->type);
+ }
+ up->qnext = isched.idlevmq;
+ isched.idlevmq = up;
+
+ up->state = Queueing;
+ up->pc = getcallerpc(&p);
+ unlock(&isched.l);
+ ready(p);
+ sched();
+}
+
+void
+startup(void)
+{
+ int x;
+
+ up->type = Interp;
+ up->iprog = nil;
+
+ lock(&isched.l);
+ isched.creating = 0;
+ if(isched.idle) {
+ isched.idle = 0;
+ unlock(&isched.l);
+ return;
+ }
+ up->qnext = isched.idlevmq;
+ isched.idlevmq = up;
+ up->state = Queueing;
+ up->pc = getcallerpc(&x);
+ unlock(&isched.l);
+ sched();
+}
+
+void
+progexit(void)
+{
+ Prog *r;
+ Module *m;
+ int broken;
+ char *estr, msg[ERRMAX+2*KNAMELEN];
+
+ estr = up->env->errstr;
+ broken = 0;
+ if(estr[0] != '\0' && strcmp(estr, Eintr) != 0 && strncmp(estr, "fail:", 5) != 0)
+ broken = 1;
+
+ r = up->iprog;
+ if(r != nil)
+ acquire();
+ else
+ r = currun();
+
+ if(*estr == '\0' && r->flags & Pkilled)
+ estr = "killed";
+
+ m = R.M->m;
+ if(broken)
+ print("[%s] Broken: \"%s\"\n", m->name, estr);
+
+ snprint(msg, sizeof(msg), "%d \"%s\":%s", r->pid, m->name, estr);
+
+ if(up->env->debug != nil) {
+ dbgexit(r, broken, estr);
+ broken = 1;
+ /* must force it to break if in debug */
+ }else if(broken && (!keepbroken || strncmp(estr, "out of memory", 13)==0 || memusehigh()))
+ broken = 0; /* don't want them or short of memory */
+
+ if(broken){
+ tellsomeone(r, msg);
+ r = isave();
+ r->state = Pbroken;
+ return;
+ }
+
+ gclock();
+ destroystack(&R);
+ delprog(r, msg);
+ gcunlock();
+}
+
+void
+disfault(void *reg, char *msg)
+{
+ Prog *p;
+
+ USED(reg);
+
+ if(strncmp(msg, Eintr, 6) == 0 || up == nil) {
+ print("EMU: faults: %s\n", msg);
+ panic("disfault");
+ }
+ if(up->type != Interp) {
+ print("SYS: process %s faults: %s\n", up->text, msg);
+ panic("disfault");
+ }
+
+ if(up->iprog != nil)
+ acquire();
+
+ p = currun();
+ if(p == nil)
+ panic("Interp faults with no dis prog");
+
+ /* cause an exception in the dis prog. */
+ error(msg);
+}
+
+void
+vmachine(void*)
+{
+ Prog *r;
+ Osenv *o;
+ int cycles;
+
+ startup();
+
+ while(waserror()) {
+ if(up->type != Interp)
+ panic("vmachine: non-interp kproc");
+ if(up->iprog != nil)
+ acquire();
+ if(handler(up->env->errstr) == 0) {
+ propex(currun(), up->env->errstr);
+ progexit();
+ }
+ up->env = &up->defenv;
+ }
+
+ cycles = 0;
+ for(;;) {
+ if(tready(nil) == 0) {
+ execatidle();
+ sleep(&isched.irend, tready, 0);
+ }
+
+ if(isched.vmq != nil && (isched.runhd == nil || ++cycles > 2)){
+ iyield();
+ cycles = 0;
+ }
+
+ r = isched.runhd;
+ if(r != nil) {
+ o = r->osenv;
+ up->env = o;
+
+ FPrestore(&o->fpu);
+ r->xec(r);
+ FPsave(&o->fpu);
+
+ if(isched.runhd != nil)
+ if(r == isched.runhd)
+ if(isched.runhd != isched.runtl) {
+ isched.runhd = r->link;
+ r->link = nil;
+ isched.runtl->link = r;
+ isched.runtl = r;
+ }
+ up->env = &up->defenv;
+ if(isched.runhd != nil)
+ if (up->iprog == nil) {
+ up->type = BusyGC;
+ pushrun(up->prog);
+ rungc(isched.head);
+ up->type = Interp;
+ delrunq(up->prog);
+ } else
+ print("up->iprog not nil (%lux)\n", up->iprog);
+ }
+ }
+}
+
+void
+disinit(void *a)
+{
+ Prog *p;
+ Osenv *o;
+ Module *root;
+ char *initmod = a;
+
+ if(waserror())
+ panic("disinit error: %r");
+
+ print("Initial Dis: \"%s\"\n", initmod);
+
+ fmtinstall('D', Dconv);
+
+ addclock0link(accountant, MS2HZ);
+
+ FPinit();
+ FPsave(&up->env->fpu);
+
+ opinit();
+ modinit();
+ excinit();
+
+ root = load(initmod);
+ if(root == 0) {
+ kgerrstr(up->genbuf, sizeof up->genbuf);
+ panic("loading \"%s\": %s", initmod, up->genbuf);
+ }
+
+ p = schedmod(root);
+
+ memmove(p->osenv, up->env, sizeof(Osenv));
+ o = p->osenv;
+ incref(o->pgrp);
+ incref(o->fgrp);
+ if(o->egrp != nil)
+ incref(o->egrp);
+ if(o->sigs != nil)
+ incref(o->sigs);
+ o->user = nil;
+ kstrdup(&o->user, up->env->user);
+ o->errstr = o->errbuf0;
+ o->syserrstr = o->errbuf1;
+
+ isched.idle = 1;
+
+ if(kopen("#c/cons", OREAD) != 0)
+ panic("failed to make fd0 from #c/cons");
+ kopen("#c/cons", OWRITE);
+ kopen("#c/cons", OWRITE);
+
+ poperror();
+ vmachine(nil);
+}
+
+void
+pushrun(Prog *p)
+{
+ if(p->addrun != nil)
+ panic("pushrun addrun");
+ p->state = Pready;
+ p->link = isched.runhd;
+ isched.runhd = p;
+ if(p->link == nil)
+ isched.runtl = p;
+}
diff --git a/os/port/discall.c b/os/port/discall.c
new file mode 100644
index 00000000..c5db5d79
--- /dev/null
+++ b/os/port/discall.c
@@ -0,0 +1,185 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <isa.h>
+#include <interp.h>
+#include "kernel.h"
+
+#define QP(l) (Prog**)((char*)(l)+sizeof(QLock))
+
+void*
+libqlowner(void *l)
+{
+ return *QP(l);
+}
+
+void
+libqlock(void *l)
+{
+ Prog *p;
+ QLock *q;
+
+ q = l;
+ p = currun();
+ if(p == nil)
+ panic("libqlock");
+
+ if(!canqlock(q)) {
+ release();
+ qlock(q);
+ acquire();
+ }
+ *QP(l) = p;
+}
+
+void
+libqunlock(void *l)
+{
+ Prog *p;
+ QLock *q;
+
+ q = l;
+ p = currun();
+ if(p == nil)
+ panic("libqunlock 1");
+ if(*QP(l) != p)
+ panic("libqunlock 2");
+
+ *QP(l) = nil;
+ qunlock(q);
+}
+
+void*
+libqlalloc(void)
+{
+ QLock *q;
+
+ q = mallocz(sizeof(QLock)+sizeof(Prog*), 1);
+ return q;
+}
+
+void
+libqlfree(void *l)
+{
+ free(l);
+}
+
+vlong
+libseek(int fd, vlong off, int whence)
+{
+ release();
+ off = kseek(fd, off, whence);
+ acquire();
+ return off;
+}
+
+int
+libread(int fd, void *buf, int n)
+{
+ release();
+ n = kread(fd, buf, n);
+ acquire();
+ return n;
+}
+
+int
+libreadn(int fd, void *av, long n)
+{
+ char *a;
+ long m, t;
+
+ a = av;
+ t = 0;
+ release();
+ while(t < n){
+ m = kread(fd, a+t, n-t);
+ if(m <= 0){
+ if(t == 0){
+ acquire();
+ return m;
+ }
+ break;
+ }
+ t += m;
+ }
+ acquire();
+ return t;
+}
+
+int
+libwrite(int fd, void *buf, int n)
+{
+ release();
+ n = kwrite(fd, buf, n);
+ acquire();
+ return n;
+}
+
+int
+libopen(char *name, int omode)
+{
+ int fd;
+
+ release();
+ fd = kopen(name, omode);
+ acquire();
+ return fd;
+}
+
+int
+libclose(int fd)
+{
+ release();
+ fd = kclose(fd);
+ acquire();
+ return fd;
+}
+
+Dir*
+libdirfstat(int fd)
+{
+ Dir *d;
+
+ release();
+ d = kdirfstat(fd);
+ acquire();
+ return d;
+}
+
+int
+libbind(char *s, char *t, int f)
+{
+ int n;
+
+ release();
+ n = kbind(s, t, f);
+ acquire();
+ return n;
+}
+
+void
+libchanclose(void *chan)
+{
+ release();
+ cclose(chan);
+ acquire();
+}
+
+void*
+libfdtochan(int fd, int mode)
+{
+ Chan *c;
+
+ release();
+ if(waserror()) {
+ acquire();
+ return nil;
+ }
+ c = fdtochan(up->env->fgrp, fd, mode, 0, 1);
+ poperror();
+ acquire();
+ return c;
+}
diff --git a/os/port/dynld.c b/os/port/dynld.c
new file mode 100644
index 00000000..77f74230
--- /dev/null
+++ b/os/port/dynld.c
@@ -0,0 +1,75 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include <a.out.h>
+#include <dynld.h>
+#include <kernel.h>
+
+/*
+ * kernel interface to dynld, for use by devdynld.c,
+ * libinterp/dlm.c, and possibly others
+ */
+
+typedef struct Fd Fd;
+struct Fd {
+ int fd;
+};
+
+static long
+readfd(void *a, void *buf, long nbytes)
+{
+ return kread(((Fd*)a)->fd, buf, nbytes);
+}
+
+static vlong
+seekfd(void *a, vlong off, int t)
+{
+ return kseek(((Fd*)a)->fd, off, t);
+}
+
+static void
+errfd(char *s)
+{
+ kstrcpy(up->env->errstr, s, ERRMAX);
+}
+
+Dynobj*
+kdynloadfd(int fd, Dynsym *tab, int ntab)
+{
+ Fd f;
+
+ f.fd = fd;
+ return dynloadgen(&f, readfd, seekfd, errfd, tab, ntab, 0);
+}
+
+int
+kdynloadable(int fd)
+{
+ Fd f;
+
+ f.fd = fd;
+ return dynloadable(&f, readfd, seekfd);
+}
+
+/* auxiliary routines for dynamic loading of C modules */
+
+Dynobj*
+dynld(int fd)
+{
+ Fd f;
+
+ f.fd = fd;
+ return dynloadgen(&f, readfd, seekfd, errfd, _exporttab, dyntabsize(_exporttab), 0);
+}
+
+int
+dynldable(int fd)
+{
+ Fd f;
+
+ f.fd = fd;
+ return dynloadable(&f, readfd, seekfd);
+}
diff --git a/os/port/edf.c b/os/port/edf.c
new file mode 100644
index 00000000..c406f4cc
--- /dev/null
+++ b/os/port/edf.c
@@ -0,0 +1,626 @@
+/* EDF scheduling */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "../port/edf.h"
+#include <trace.h>
+
+/* debugging */
+int edfprint = 0;
+#define DPRINT if(edfprint)print
+
+static vlong now;
+extern ulong delayedscheds;
+extern Schedq runq[Nrq];
+extern int nrdy;
+extern ulong runvec;
+
+/* Statistics stuff */
+ulong nilcount;
+ulong scheds;
+vlong edfruntime;
+ulong edfnrun;
+int misseddeadlines;
+
+/* Edfschedlock protects modification of admission params */
+int edfinited;
+QLock edfschedlock;
+static Lock thelock;
+
+enum{
+ Dl, /* Invariant for schedulability test: Dl < Rl */
+ Rl,
+};
+
+static char *testschedulability(Proc*);
+static Proc *qschedulability;
+
+enum {
+ Onemicrosecond = 1000ULL,
+ Onemillisecond = 1000000ULL,
+ Onesecond = 1000000000ULL,
+ OneRound = Onemillisecond/2LL,
+ MilliRound = Onemicrosecond/2LL,
+};
+
+static int
+timeconv(Fmt *f)
+{
+ char buf[128], *sign;
+ vlong t;
+
+ buf[0] = 0;
+ switch(f->r) {
+ case 'U':
+ t = va_arg(f->args, uvlong);
+ break;
+ case 't': // vlong in nanoseconds
+ t = va_arg(f->args, vlong);
+ break;
+ default:
+ return fmtstrcpy(f, "(timeconv)");
+ }
+ if (t < 0) {
+ sign = "-";
+ t = -t;
+ }
+ else
+ sign = "";
+ if (t > Onesecond){
+ t += OneRound;
+ sprint(buf, "%s%d.%.3ds", sign, (int)(t / Onesecond), (int)(t % Onesecond)/1000000);
+ }else if (t > Onemillisecond){
+ t += MilliRound;
+ sprint(buf, "%s%d.%.3dms", sign, (int)(t / Onemillisecond), (int)(t % Onemillisecond)/1000);
+ }else if (t > Onemicrosecond)
+ sprint(buf, "%s%d.%.3dµs", sign, (int)(t / Onemicrosecond), (int)(t % Onemicrosecond));
+ else
+ sprint(buf, "%s%dns", sign, (int)t);
+ return fmtstrcpy(f, buf);
+}
+
+Edf*
+edflock(Proc *p)
+{
+ Edf *e;
+
+ if (p->edf == nil)
+ return nil;
+ ilock(&thelock);
+ if ((e = p->edf) && (e->flags & Admitted)){
+ now = todget(nil);
+ return e;
+ }
+ iunlock(&thelock);
+ return nil;
+}
+
+void
+edfunlock(void)
+{
+ edfruntime += todget(nil) - now;
+ edfnrun++;
+ iunlock(&thelock);
+}
+
+void
+edfinit(Proc*p)
+{
+ if(!edfinited){
+ fmtinstall('t', timeconv);
+ edfinited++;
+ }
+ now = todget(nil);
+ DPRINT("%t edfinit %lud[%s]\n", now, p->pid, statename[p->state]);
+ p->edf = malloc(sizeof(Edf));
+ if(p->edf == nil)
+ error(Enomem);
+ return;
+}
+
+static void
+deadlineintr(Ureg*, Timer *t)
+{
+ /* Proc reached deadline */
+ extern int panicking;
+ Proc *p;
+ void (*pt)(Proc*, int, vlong);
+
+ if(panicking || active.exiting)
+ return;
+
+ p = t->ta;
+
+ DPRINT("%t deadlineintr %lud[%s]\n", todget(nil), p->pid, statename[p->state]);
+ /* If we're interrupting something other than the proc pointed to by t->a,
+ * we've already achieved recheduling, so we need not do anything
+ * Otherwise, we must cause a reschedule, but if we call sched()
+ * here directly, the timer interrupt routine will not finish its business
+ * Instead, we cause the resched to happen when the interrupted proc
+ * returns to user space
+ */
+ if (p == up){
+ pt = proctrace;
+ if(up->trace && pt)
+ pt(up, SInts, 0);
+ up->delaysched++;
+ delayedscheds++;
+ }
+}
+
+static void
+release(Proc *p)
+{
+ /* Called with edflock held */
+ Edf *e;
+ void (*pt)(Proc*, int, vlong);
+
+ e = p->edf;
+ e->flags &= ~Yield;
+ if (e->d < now){
+ e->periods++;
+ e->r = now;
+ if ((e->flags & Sporadic) == 0){
+ /* Non sporadic processes stay true to their period;
+ * calculate next release time
+ */
+ while(e->t <= now)
+ e->t += e->T;
+ }else{
+ /* Sporadic processes may not be released earlier than
+ * one period after this release
+ */
+ e->t = e->r + e->T;
+ }
+ e->d = e->r + e->D;
+ e->S = e->C;
+ DPRINT("%t release %lud[%s], r=%t, d=%t, t=%t, S=%t\n",
+ now, p->pid, statename[p->state], e->r, e->d, e->t, e->S);
+ if (pt = proctrace){
+ pt(p, SRelease, e->r);
+ pt(p, SDeadline, e->d);
+ }
+ }else{
+ DPRINT("%t release %lud[%s], too late t=%t, called from 0x%lux\n",
+ now, p->pid, statename[p->state], e->t, getcallerpc(&p));
+ }
+}
+
+static void
+releaseintr(Ureg*, Timer *t)
+{
+ Proc *p;
+ extern int panicking;
+ Schedq *rq;
+
+ if(panicking || active.exiting)
+ return;
+
+ p = t->ta;
+ if((edflock(p)) == nil)
+ return;
+ DPRINT("%t releaseintr %lud[%s]\n", now, p->pid, statename[p->state]);
+ switch(p->state){
+ default:
+ edfunlock();
+ return;
+ case Ready:
+ /* remove proc from current runq */
+ rq = &runq[p->pri];
+ if (dequeueproc(rq, p) != p){
+ DPRINT("releaseintr: can't find proc or lock race\n");
+ release(p); /* It'll start best effort */
+ edfunlock();
+ return;
+ }
+ p->state = Waitrelease;
+ /* fall through */
+ case Waitrelease:
+ release(p);
+ edfunlock();
+ ready(p);
+ if (up){
+ up->delaysched++;
+ delayedscheds++;
+ }
+ return;
+ case Running:
+ release(p);
+ edfrun(p, 1);
+ break;
+ case Wakeme:
+ release(p);
+ edfunlock();
+ if (p->trend)
+ wakeup(p->trend);
+ p->trend = nil;
+ if (up){
+ up->delaysched++;
+ delayedscheds++;
+ }
+ return;
+ }
+ edfunlock();
+}
+
+void
+edfrecord(Proc *p)
+{
+ vlong used;
+ Edf *e;
+ void (*pt)(Proc*, int, vlong);
+
+ if((e = edflock(p)) == nil)
+ return;
+ used = now - e->s;
+ if (e->d <= now)
+ e->edfused += used;
+ else
+ e->extraused += used;
+ if (e->S > 0){
+ if (e->S <= used){
+ if(pt = proctrace)
+ pt(p, SSlice, now);
+ DPRINT("%t edfrecord slice used up\n", now);
+ e->d = now;
+ e->S = 0;
+ }else
+ e->S -= used;
+ }
+ e->s = now;
+ edfunlock();
+}
+
+void
+edfrun(Proc *p, int edfpri)
+{
+ Edf *e;
+ void (*pt)(Proc*, int, vlong);
+
+ e = p->edf;
+ /* Called with edflock held */
+ if(edfpri){
+ if (e->d <= now || e->S == 0){
+ /* Deadline reached or resources exhausted,
+ * deschedule forthwith
+ */
+ p->delaysched++;
+ delayedscheds++;
+ e->s = now;
+ return;
+ }
+ e->tns = now + e->S;
+ if (e->d < e->tns)
+ e->tns = e->d;
+ if(e->tt == nil || e->tf != deadlineintr){
+ DPRINT("%t edfrun, deadline=%t\n", now, e->tns);
+ }else{
+ DPRINT("v");
+ }
+ if(p->trace && (pt = proctrace))
+ pt(p, SInte, e->tns);
+ e->tmode = Tabsolute;
+ e->tf = deadlineintr;
+ e->ta = p;
+ timeradd(e);
+ }else{
+ DPRINT("<");
+ }
+ e->s = now;
+}
+
+char *
+edfadmit(Proc *p)
+{
+ char *err;
+ Edf *e;
+ int i;
+ Proc *r;
+ void (*pt)(Proc*, int, vlong);
+
+ e = p->edf;
+ if (e->flags & Admitted)
+ return "task state"; /* should never happen */
+
+ /* simple sanity checks */
+ if (e->T == 0)
+ return "T not set";
+ if (e->C == 0)
+ return "C not set";
+ if (e->D > e->T)
+ return "D > T";
+ if (e->D == 0) /* if D is not set, set it to T */
+ e->D = e->T;
+ if (e->C > e->D)
+ return "C > D";
+
+ qlock(&edfschedlock);
+ if (err = testschedulability(p)){
+ qunlock(&edfschedlock);
+ return err;
+ }
+ e->flags |= Admitted;
+
+ edflock(p);
+
+ if(pt = proctrace)
+ pt(p, SAdmit, now);
+
+ /* Look for another proc with the same period to synchronize to */
+ SET(r);
+ for(i=0; i<conf.nproc; i++) {
+ r = proctab(i);
+ if(r->state == Dead || r == p)
+ continue;
+ if (r->edf == nil || (r->edf->flags & Admitted) == 0)
+ continue;
+ if (r->edf->T == e->T)
+ break;
+ }
+ if (i == conf.nproc){
+ /* Can't synchronize to another proc, release now */
+ e->t = now;
+ e->d = 0;
+ release(p);
+ if (p == up){
+ DPRINT("%t edfadmit self %lud[%s], release now: r=%t d=%t t=%t\n",
+ now, p->pid, statename[p->state], e->r, e->d, e->t);
+ /* We're already running */
+ edfrun(p, 1);
+ }else{
+ /* We're releasing another proc */
+ DPRINT("%t edfadmit other %lud[%s], release now: r=%t d=%t t=%t\n",
+ now, p->pid, statename[p->state], e->r, e->d, e->t);
+ p->ta = p;
+ edfunlock();
+ qunlock(&edfschedlock);
+ releaseintr(nil, p);
+ return nil;
+ }
+ }else{
+ /* Release in synch to something else */
+ e->t = r->edf->t;
+ if (p == up){
+ DPRINT("%t edfadmit self %lud[%s], release at %t\n",
+ now, p->pid, statename[p->state], e->t);
+ edfunlock();
+ qunlock(&edfschedlock);
+ return nil;
+ }else{
+ DPRINT("%t edfadmit other %lud[%s], release at %t\n",
+ now, p->pid, statename[p->state], e->t);
+ if(e->tt == nil){
+ e->tf = releaseintr;
+ e->ta = p;
+ e->tns = e->t;
+ e->tmode = Tabsolute;
+ timeradd(e);
+ }
+ }
+ }
+ edfunlock();
+ qunlock(&edfschedlock);
+ return nil;
+}
+
+void
+edfstop(Proc *p)
+{
+ Edf *e;
+ void (*pt)(Proc*, int, vlong);
+
+ if (e = edflock(p)){
+ DPRINT("%t edfstop %lud[%s]\n", now, p->pid, statename[p->state]);
+ if(pt = proctrace)
+ pt(p, SExpel, now);
+ e->flags &= ~Admitted;
+ if (e->tt)
+ timerdel(e);
+ edfunlock();
+ }
+}
+
+static int
+yfn(void *)
+{
+ return up->trend == nil || todget(nil) >= up->edf->r;
+}
+
+void
+edfyield(void)
+{
+ /* sleep until next release */
+ Edf *e;
+ void (*pt)(Proc*, int, vlong);
+
+ if((e = edflock(up)) == nil)
+ return;
+ if(pt = proctrace)
+ pt(up, SYield, now);
+ while(e->t < now)
+ e->t += e->T;
+ e->r = e->t;
+ e->flags |= Yield;
+ e->d = now;
+ if (up->tt == nil){
+ up->tns = e->t;
+ up->tf = releaseintr;
+ up->tmode = Tabsolute;
+ up->ta = up;
+ up->trend = &up->sleep;
+ timeradd(up);
+ }else if(up->tf != releaseintr)
+ print("edfyield: surprise! 0x%lux\n", up->tf);
+ edfunlock();
+ sleep(&up->sleep, yfn, nil);
+}
+
+int
+edfready(Proc *p)
+{
+ Edf *e;
+ Schedq *rq;
+ Proc *l, *pp;
+ void (*pt)(Proc*, int, vlong);
+
+ if((e = edflock(p)) == nil)
+ return 0;
+ if (e->d <= now){
+ /* past deadline, arrange for next release */
+ if ((e->flags & Sporadic) == 0){
+ /* Non sporadic processes stay true to their period, calculate next release time */
+ while(e->t < now)
+ e->t += e->T;
+ }
+ if (now < e->t){
+ /* Next release is in the future, schedule it */
+ if (e->tt == nil || e->tf != releaseintr){
+ e->tns = e->t;
+ e->tmode = Tabsolute;
+ e->tf = releaseintr;
+ e->ta = p;
+ timeradd(e);
+ DPRINT("%t edfready %lud[%s], release=%t\n",
+ now, p->pid, statename[p->state], e->t);
+ }
+ if(p->state == Running && (e->flags & (Yield|Yieldonblock)) == 0 && (e->flags & Extratime)){
+ /* If we were running, we've overrun our CPU allocation
+ * or missed the deadline, continue running best-effort at low priority
+ * Otherwise we were blocked. If we don't yield on block, we continue
+ * best effort
+ */
+ DPRINT(">");
+ p->pri = PriExtra;
+ edfunlock();
+ return 0; /* Stick on runq[PriExtra] */
+ }
+ DPRINT("%t edfready %lud[%s] wait release at %t\n",
+ now, p->pid, statename[p->state], e->t);
+ p->state = Waitrelease;
+ edfunlock();
+ return 1; /* Make runnable later */
+ }
+ DPRINT("%t edfready %lud %s release now\n", now, p->pid, statename[p->state]);
+ /* release now */
+ release(p);
+ }
+ edfunlock();
+ DPRINT("^");
+ rq = &runq[PriEdf];
+ /* insert in queue in earliest deadline order */
+ lock(runq);
+ l = nil;
+ for(pp = rq->head; pp; pp = pp->rnext){
+ if(pp->edf->d > e->d)
+ break;
+ l = pp;
+ }
+ p->rnext = pp;
+ if (l == nil)
+ rq->head = p;
+ else
+ l->rnext = p;
+ if(pp == nil)
+ rq->tail = p;
+ rq->n++;
+ nrdy++;
+ runvec |= 1 << PriEdf;
+ p->pri = PriEdf;
+ p->readytime = m->ticks;
+ p->state = Ready;
+ unlock(runq);
+ if(pt = proctrace)
+ pt(p, SReady, now);
+ return 1;
+}
+
+
+static void
+testenq(Proc *p)
+{
+ Proc *xp, **xpp;
+ Edf *e;
+
+ e = p->edf;
+ e->testnext = nil;
+ if (qschedulability == nil) {
+ qschedulability = p;
+ return;
+ }
+ SET(xp);
+ for (xpp = &qschedulability; *xpp; xpp = &xp->edf->testnext) {
+ xp = *xpp;
+ if (e->testtime < xp->edf->testtime
+ || (e->testtime == xp->edf->testtime && e->testtype < xp->edf->testtype)){
+ e->testnext = xp;
+ *xpp = p;
+ return;
+ }
+ }
+ assert(xp->edf->testnext == nil);
+ xp->edf->testnext = p;
+}
+
+static char *
+testschedulability(Proc *theproc)
+{
+ Proc *p;
+ vlong H, G, Cb, ticks;
+ int steps, i;
+
+ /* initialize */
+ DPRINT("schedulability test %lud\n", theproc->pid);
+ qschedulability = nil;
+ for(i=0; i<conf.nproc; i++) {
+ p = proctab(i);
+ if(p->state == Dead)
+ continue;
+ if ((p->edf == nil || (p->edf->flags & Admitted) == 0) && p != theproc)
+ continue;
+ p->edf->testtype = Rl;
+ p->edf->testtime = 0;
+ DPRINT("\tInit: edfenqueue %lud\n", p->pid);
+ testenq(p);
+ }
+ H=0;
+ G=0;
+ for(steps = 0; steps < Maxsteps; steps++){
+ p = qschedulability;
+ qschedulability = p->edf->testnext;
+ ticks = p->edf->testtime;
+ switch (p->edf->testtype){
+ case Dl:
+ H += p->edf->C;
+ Cb = 0;
+ DPRINT("\tStep %3d, Ticks %t, pid %lud, deadline, H += %t → %t, Cb = %t\n",
+ steps, ticks, p->pid, p->edf->C, H, Cb);
+ if (H+Cb>ticks){
+ DPRINT("not schedulable\n");
+ return "not schedulable";
+ }
+ p->edf->testtime += p->edf->T - p->edf->D;
+ p->edf->testtype = Rl;
+ testenq(p);
+ break;
+ case Rl:
+ DPRINT("\tStep %3d, Ticks %t, pid %lud, release, G %t, C%t\n",
+ steps, ticks, p->pid, p->edf->C, G);
+ if(ticks && G <= ticks){
+ DPRINT("schedulable\n");
+ return nil;
+ }
+ G += p->edf->C;
+ p->edf->testtime += p->edf->D;
+ p->edf->testtype = Dl;
+ testenq(p);
+ break;
+ default:
+ assert(0);
+ }
+ }
+ DPRINT("probably not schedulable\n");
+ return "probably not schedulable";
+}
diff --git a/os/port/edf.h b/os/port/edf.h
new file mode 100644
index 00000000..dfd2b4a9
--- /dev/null
+++ b/os/port/edf.h
@@ -0,0 +1,53 @@
+enum {
+ Maxsteps = 200 * 100 * 2, /* 100 periods of 200 procs */
+
+ /* Edf.flags field */
+ Admitted = 0x01,
+ Sporadic = 0x02,
+ Yieldonblock = 0x04,
+ Sendnotes = 0x08,
+ Deadline = 0x10,
+ Yield = 0x20,
+ Extratime = 0x40,
+
+ Infinity = ~0ULL,
+};
+
+typedef struct Edf Edf;
+
+struct Edf {
+ /* time intervals */
+ vlong D; /* Deadline */
+ vlong Delta; /* Inherited deadline */
+ vlong T; /* period */
+ vlong C; /* Cost */
+ vlong S; /* Slice: time remaining in this period */
+ /* times */
+ vlong r; /* (this) release time */
+ vlong d; /* (this) deadline */
+ vlong t; /* Start of next period, t += T at release */
+ vlong s; /* Time at which this proc was last scheduled */
+ /* for schedulability testing */
+ vlong testDelta;
+ int testtype; /* Release or Deadline */
+ vlong testtime;
+ Proc *testnext;
+ /* other */
+ ushort flags;
+ Timer;
+ /* Stats */
+ vlong edfused;
+ vlong extraused;
+ vlong aged;
+ ulong periods;
+ ulong missed;
+};
+
+extern Lock edftestlock; /* for atomic admitting/expelling */
+
+#pragma varargck type "t" vlong
+#pragma varargck type "U" uvlong
+
+/* Interface: */
+Edf* edflock(Proc*);
+void edfunlock(void);
diff --git a/os/port/error.h b/os/port/error.h
new file mode 100644
index 00000000..5d5c2f66
--- /dev/null
+++ b/os/port/error.h
@@ -0,0 +1,64 @@
+extern char Enoerror[]; /* no error */
+extern char Emount[]; /* inconsistent mount */
+extern char Eunmount[]; /* not mounted */
+extern char Eunion[]; /* not in union */
+extern char Emountrpc[]; /* mount rpc error */
+extern char Eshutdown[]; /* mounted device shut down */
+extern char Enocreate[]; /* mounted directory forbids creation */
+extern char Enonexist[]; /* file does not exist */
+extern char Eexist[]; /* file already exists */
+extern char Ebadsharp[]; /* unknown device in # filename */
+extern char Enotdir[]; /* not a directory */
+extern char Eisdir[]; /* file is a directory */
+extern char Ebadchar[]; /* bad character in file name */
+extern char Efilename[]; /* file name syntax */
+extern char Eperm[]; /* permission denied */
+extern char Ebadusefd[]; /* inappropriate use of fd */
+extern char Ebadarg[]; /* bad arg in system call */
+extern char Einuse[]; /* device or object already in use */
+extern char Eio[]; /* i/o error */
+extern char Etoobig[]; /* read or write too large */
+extern char Etoosmall[]; /* read or write too small */
+extern char Enetaddr[]; /* bad network address */
+extern char Emsgsize[]; /* message is too big for protocol */
+extern char Enetbusy[]; /* network device is busy or allocated */
+extern char Enoproto[]; /* network protocol not supported */
+extern char Enoport[]; /* network port not available */
+extern char Enoifc[]; /* bad interface or no free interface slots */
+extern char Enolisten[]; /* not announced */
+extern char Ehungup[]; /* i/o on hungup channel */
+extern char Ebadctl[]; /* bad process or channel control request */
+extern char Enodev[]; /* no free devices */
+extern char Enoenv[]; /* no free environment resources */
+extern char Emuxshutdown[]; /* mux server shut down */
+extern char Emuxbusy[]; /* all mux channels busy */
+extern char Emuxmsg[]; /* bad mux message format or mismatch */
+extern char Ethread[]; /* thread exited */
+extern char Estopped[]; /* thread must be stopped */
+extern char Enochild[]; /* no living children */
+extern char Eioload[]; /* i/o error in demand load */
+extern char Enovmem[]; /* out of memory: virtual memory */
+extern char Ebadld[]; /* illegal line discipline */
+extern char Ebadfd[]; /* fd out of range or not open */
+extern char Eisstream[]; /* seek on a stream */
+extern char Ebadexec[]; /* exec header invalid */
+extern char Etimedout[]; /* connection timed out */
+extern char Econrefused[]; /* connection refused */
+extern char Econinuse[]; /* connection in use */
+extern char Eintr[]; /* interrupted */
+extern char Eneedservice[]; /* service required for tcp/udp/il calls */
+extern char Enomem[]; /* out of memory: kernel */
+extern char Esfnotcached[]; /* subfont not cached */
+extern char Esoverlap[]; /* segments overlap */
+extern char Emouseset[]; /* mouse type already set */
+extern char Erecover[]; /* failed to recover fd */
+extern char Eshort[]; /* i/o count too small */
+extern char Enobitstore[]; /* out of screen memory */
+extern char Egreg[]; /* jim'll fix it */
+extern char Ebadspec[]; /* bad attach specifier */
+extern char Enoattach[]; /* mount/attach disallowed */
+extern char Eshortstat[]; /* stat buffer too small */
+extern char Enegoff[]; /* negative i/o offset */
+extern char Ecmdargs[]; /* wrong #args in control message */
+extern char Ebadstat[]; /* malformed stat buffer */
+extern char Enofd[]; /* no free file descriptors */
diff --git a/os/port/ethermii.c b/os/port/ethermii.c
new file mode 100644
index 00000000..584cdf08
--- /dev/null
+++ b/os/port/ethermii.c
@@ -0,0 +1,238 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+#include "ethermii.h"
+
+int
+mii(Mii* mii, int mask)
+{
+ MiiPhy *miiphy;
+ int bit, oui, phyno, r, rmask;
+
+ /*
+ * Probe through mii for PHYs in mask;
+ * return the mask of those found in the current probe.
+ * If the PHY has not already been probed, update
+ * the Mii information.
+ */
+ rmask = 0;
+ for(phyno = 0; phyno < NMiiPhy; phyno++){
+ bit = 1<<phyno;
+ if(!(mask & bit))
+ continue;
+ if(mii->mask & bit){
+ rmask |= bit;
+ continue;
+ }
+ if(mii->mir(mii, phyno, Bmsr) == -1)
+ continue;
+ r = mii->mir(mii, phyno, Phyidr1);
+ oui = (r & 0x3FFF)<<6;
+ r = mii->mir(mii, phyno, Phyidr2);
+ oui |= r>>10;
+ if(oui == 0xFFFFF || oui == 0)
+ continue;
+
+ if((miiphy = malloc(sizeof(MiiPhy))) == nil)
+ continue;
+
+ miiphy->mii = mii;
+ miiphy->oui = oui;
+ miiphy->phyno = phyno;
+
+ miiphy->anar = ~0;
+ miiphy->fc = ~0;
+ miiphy->mscr = ~0;
+
+ mii->phy[phyno] = miiphy;
+ if(mii->curphy == nil)
+ mii->curphy = miiphy;
+ mii->mask |= bit;
+ mii->nphy++;
+
+ rmask |= bit;
+ }
+ return rmask;
+}
+
+int
+miimir(Mii* mii, int r)
+{
+ if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+ return -1;
+ return mii->mir(mii, mii->curphy->phyno, r);
+}
+
+int
+miimiw(Mii* mii, int r, int data)
+{
+ if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+ return -1;
+ return mii->miw(mii, mii->curphy->phyno, r, data);
+}
+
+int
+miireset(Mii* mii)
+{
+ int bmcr;
+
+ if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+ return -1;
+ bmcr = mii->mir(mii, mii->curphy->phyno, Bmcr);
+ bmcr |= BmcrR;
+ mii->miw(mii, mii->curphy->phyno, Bmcr, bmcr);
+/* microdelay(1);*/
+ microdelay(500); /* DP83847, at least */
+
+ return 0;
+}
+
+int
+miiane(Mii* mii, int a, int p, int e)
+{
+ int anar, bmsr, mscr, r, phyno;
+
+ if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+ return -1;
+ phyno = mii->curphy->phyno;
+
+ bmsr = mii->mir(mii, phyno, Bmsr);
+ if(!(bmsr & BmsrAna))
+ return -1;
+
+ if(a != ~0)
+ anar = (AnaTXFD|AnaTXHD|Ana10FD|Ana10HD) & a;
+ else if(mii->curphy->anar != ~0)
+ anar = mii->curphy->anar;
+ else{
+ anar = mii->mir(mii, phyno, Anar);
+ anar &= ~(AnaAP|AnaP|AnaT4|AnaTXFD|AnaTXHD|Ana10FD|Ana10HD);
+ if(bmsr & Bmsr10THD)
+ anar |= Ana10HD;
+ if(bmsr & Bmsr10TFD)
+ anar |= Ana10FD;
+ if(bmsr & Bmsr100TXHD)
+ anar |= AnaTXHD;
+ if(bmsr & Bmsr100TXFD)
+ anar |= AnaTXFD;
+ }
+ mii->curphy->anar = anar;
+
+ if(p != ~0)
+ anar |= (AnaAP|AnaP) & p;
+ else if(mii->curphy->fc != ~0)
+ anar |= mii->curphy->fc;
+ mii->curphy->fc = (AnaAP|AnaP) & anar;
+
+ if(bmsr & BmsrEs){
+ mscr = mii->mir(mii, phyno, Mscr);
+ mscr &= ~(Mscr1000TFD|Mscr1000THD);
+ if(e != ~0)
+ mscr |= (Mscr1000TFD|Mscr1000THD) & e;
+ else if(mii->curphy->mscr != ~0)
+ mscr = mii->curphy->mscr;
+ else{
+ r = mii->mir(mii, phyno, Esr);
+ if(r & Esr1000THD)
+ mscr |= Mscr1000THD;
+ if(r & Esr1000TFD)
+ mscr |= Mscr1000TFD;
+ }
+ mii->curphy->mscr = mscr;
+ mii->miw(mii, phyno, Mscr, mscr);
+ }
+ mii->miw(mii, phyno, Anar, anar);
+
+ r = mii->mir(mii, phyno, Bmcr);
+ if(!(r & BmcrR)){
+ r |= BmcrAne|BmcrRan;
+ mii->miw(mii, phyno, Bmcr, r);
+ }
+
+ return 0;
+}
+
+int
+miistatus(Mii* mii)
+{
+ MiiPhy *phy;
+ int anlpar, bmsr, p, r, phyno;
+
+ if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+ return -1;
+ phy = mii->curphy;
+ phyno = phy->phyno;
+
+ /*
+ * Check Auto-Negotiation is complete and link is up.
+ * (Read status twice as the Ls bit is sticky).
+ */
+ bmsr = mii->mir(mii, phyno, Bmsr);
+ if(!(bmsr & (BmsrAnc|BmsrAna)))
+{
+print("miistatus 1\n");
+ return -1;
+}
+
+ bmsr = mii->mir(mii, phyno, Bmsr);
+ if(!(bmsr & BmsrLs)){
+print("miistatus 2\n");
+ phy->link = 0;
+ return -1;
+ }
+
+ phy->speed = phy->fd = phy->rfc = phy->tfc = 0;
+ if(phy->mscr){
+ r = mii->mir(mii, phyno, Mssr);
+ if((phy->mscr & Mscr1000TFD) && (r & Mssr1000TFD)){
+ phy->speed = 1000;
+ phy->fd = 1;
+ }
+ else if((phy->mscr & Mscr1000THD) && (r & Mssr1000THD))
+ phy->speed = 1000;
+ }
+
+ anlpar = mii->mir(mii, phyno, Anlpar);
+ if(phy->speed == 0){
+ r = phy->anar & anlpar;
+ if(r & AnaTXFD){
+ phy->speed = 100;
+ phy->fd = 1;
+ }
+ else if(r & AnaTXHD)
+ phy->speed = 100;
+ else if(r & Ana10FD){
+ phy->speed = 10;
+ phy->fd = 1;
+ }
+ else if(r & Ana10HD)
+ phy->speed = 10;
+ }
+ if(phy->speed == 0)
+{
+print("miistatus 3\n");
+ return -1;
+}
+
+ if(phy->fd){
+ p = phy->fc;
+ r = anlpar & (AnaAP|AnaP);
+ if(p == AnaAP && r == (AnaAP|AnaP))
+ phy->tfc = 1;
+ else if(p == (AnaAP|AnaP) && r == AnaAP)
+ phy->rfc = 1;
+ else if((p & AnaP) && (r & AnaP))
+ phy->rfc = phy->tfc = 1;
+ }
+
+ phy->link = 1;
+
+ return 0;
+}
diff --git a/os/port/ethermii.h b/os/port/ethermii.h
new file mode 100644
index 00000000..02a45ee5
--- /dev/null
+++ b/os/port/ethermii.h
@@ -0,0 +1,116 @@
+typedef struct Mii Mii;
+typedef struct MiiPhy MiiPhy;
+
+enum { /* registers */
+ Bmcr = 0x00, /* Basic Mode Control */
+ Bmsr = 0x01, /* Basic Mode Status */
+ Phyidr1 = 0x02, /* PHY Identifier #1 */
+ Phyidr2 = 0x03, /* PHY Identifier #2 */
+ Anar = 0x04, /* Auto-Negotiation Advertisement */
+ Anlpar = 0x05, /* AN Link Partner Ability */
+ Aner = 0x06, /* AN Expansion */
+ Annptr = 0x07, /* AN Next Page TX */
+ Annprr = 0x08, /* AN Next Page RX */
+ Mscr = 0x09, /* MASTER-SLAVE Control */
+ Mssr = 0x0A, /* MASTER-SLAVE Status */
+ Esr = 0x0F, /* Extended Status */
+
+ NMiiPhyr = 32,
+ NMiiPhy = 32,
+};
+
+enum { /* Bmcr */
+ BmcrSs1 = 0x0040, /* Speed Select[1] */
+ BmcrCte = 0x0080, /* Collision Test Enable */
+ BmcrDm = 0x0100, /* Duplex Mode */
+ BmcrRan = 0x0200, /* Restart Auto-Negotiation */
+ BmcrI = 0x0400, /* Isolate */
+ BmcrPd = 0x0800, /* Power Down */
+ BmcrAne = 0x1000, /* Auto-Negotiation Enable */
+ BmcrSs0 = 0x2000, /* Speed Select[0] */
+ BmcrLe = 0x4000, /* Loopback Enable */
+ BmcrR = 0x8000, /* Reset */
+};
+
+enum { /* Bmsr */
+ BmsrEc = 0x0001, /* Extended Capability */
+ BmsrJd = 0x0002, /* Jabber Detect */
+ BmsrLs = 0x0004, /* Link Status */
+ BmsrAna = 0x0008, /* Auto-Negotiation Ability */
+ BmsrRf = 0x0010, /* Remote Fault */
+ BmsrAnc = 0x0020, /* Auto-Negotiation Complete */
+ BmsrPs = 0x0040, /* Preamble Suppression Capable */
+ BmsrEs = 0x0100, /* Extended Status */
+ Bmsr100T2HD = 0x0200, /* 100BASE-T2 HD Capable */
+ Bmsr100T2FD = 0x0400, /* 100BASE-T2 FD Capable */
+ Bmsr10THD = 0x0800, /* 10BASE-T HD Capable */
+ Bmsr10TFD = 0x1000, /* 10BASE-T FD Capable */
+ Bmsr100TXHD = 0x2000, /* 100BASE-TX HD Capable */
+ Bmsr100TXFD = 0x4000, /* 100BASE-TX FD Capable */
+ Bmsr100T4 = 0x8000, /* 100BASE-T4 Capable */
+};
+
+enum { /* Anar/Anlpar */
+ Ana10HD = 0x0020, /* Advertise 10BASE-T */
+ Ana10FD = 0x0040, /* Advertise 10BASE-T FD */
+ AnaTXHD = 0x0080, /* Advertise 100BASE-TX */
+ AnaTXFD = 0x0100, /* Advertise 100BASE-TX FD */
+ AnaT4 = 0x0200, /* Advertise 100BASE-T4 */
+ AnaP = 0x0400, /* Pause */
+ AnaAP = 0x0800, /* Asymmetrical Pause */
+ AnaRf = 0x2000, /* Remote Fault */
+ AnaAck = 0x4000, /* Acknowledge */
+ AnaNp = 0x8000, /* Next Page Indication */
+};
+
+enum { /* Mscr */
+ Mscr1000THD = 0x0100, /* Advertise 1000BASE-T HD */
+ Mscr1000TFD = 0x0200, /* Advertise 1000BASE-T FD */
+};
+
+enum { /* Mssr */
+ Mssr1000THD = 0x0400, /* Link Partner 1000BASE-T HD able */
+ Mssr1000TFD = 0x0800, /* Link Partner 1000BASE-T FD able */
+};
+
+enum { /* Esr */
+ Esr1000THD = 0x1000, /* 1000BASE-T HD Capable */
+ Esr1000TFD = 0x2000, /* 1000BASE-T FD Capable */
+ Esr1000XHD = 0x4000, /* 1000BASE-X HD Capable */
+ Esr1000XFD = 0x8000, /* 1000BASE-X FD Capable */
+};
+
+typedef struct Mii {
+ Lock;
+ int nphy;
+ int mask;
+ MiiPhy* phy[NMiiPhy];
+ MiiPhy* curphy;
+
+ void* ctlr;
+ int (*mir)(Mii*, int, int);
+ int (*miw)(Mii*, int, int, int);
+} Mii;
+
+typedef struct MiiPhy {
+ Mii* mii;
+ int oui;
+ int phyno;
+
+ int anar;
+ int fc;
+ int mscr;
+
+ int link;
+ int speed;
+ int fd;
+ int rfc;
+ int tfc;
+};
+
+extern int mii(Mii*, int);
+extern int miiane(Mii*, int, int, int);
+extern int miimir(Mii*, int);
+extern int miimiw(Mii*, int, int);
+extern int miireset(Mii*);
+extern int miistatus(Mii*);
diff --git a/os/port/ethersink.c b/os/port/ethersink.c
new file mode 100644
index 00000000..dd701804
--- /dev/null
+++ b/os/port/ethersink.c
@@ -0,0 +1,65 @@
+/*
+ * An ethernet /dev/null.
+ * Useful as a bridging target with ethernet-based VPN.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "etherif.h"
+
+static long
+ctl(Ether *ether, void *buf, long n)
+{
+ uchar ea[Eaddrlen];
+ Cmdbuf *cb;
+
+ cb = parsecmd(buf, n);
+ if(cb->nf >= 2
+ && strcmp(cb->f[0], "ea")==0
+ && parseether(ea, cb->f[1]) == 0){
+ free(cb);
+ memmove(ether->ea, ea, Eaddrlen);
+ memmove(ether->addr, ether->ea, Eaddrlen);
+ return 0;
+ }
+ free(cb);
+ error(Ebadctl);
+ return -1; /* not reached */
+}
+
+static void
+nop(Ether*)
+{
+}
+
+static int
+reset(Ether* ether)
+{
+ uchar ea[Eaddrlen];
+
+ if(ether->type==nil)
+ return -1;
+ memset(ea, 0, sizeof ea);
+ ether->mbps = 1000;
+ ether->attach = nop;
+ ether->transmit = nop;
+ ether->irq = -1;
+ ether->interrupt = nil;
+ ether->ifstat = nil;
+ ether->ctl = ctl;
+ ether->promiscuous = nil;
+ ether->multicast = nil;
+ ether->arg = ether;
+ return 0;
+}
+
+void
+ethersinklink(void)
+{
+ addethercard("sink", reset);
+}
diff --git a/os/port/exception.c b/os/port/exception.c
new file mode 100644
index 00000000..84a74711
--- /dev/null
+++ b/os/port/exception.c
@@ -0,0 +1,216 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "interp.h"
+#include "isa.h"
+#include "runt.h"
+#include "kernel.h"
+#include "raise.h"
+
+static int
+ematch(char *pat, char *exp)
+{
+ int l;
+
+ if(strcmp(pat, exp) == 0)
+ return 1;
+
+ l = strlen(pat);
+ if(l == 0)
+ return 0;
+ if(pat[l-1] == '*') {
+ if(l == 1)
+ return 1;
+ if(strncmp(pat, exp, l-1) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static void
+setstr(String *s, char *p)
+{
+ if(s == H)
+ return;
+ if(s->len < 0 || s->max < 4)
+ return;
+ kstrcpy(s->Sascii, p, s->max); /* TO DO: we are assuming they aren't runes */
+ s->len = strlen(s->Sascii);
+}
+
+static String *exstr;
+
+void
+excinit(void)
+{
+ exstr = newstring(ERRMAX);
+ poolimmutable(D2H(exstr));
+}
+
+static String*
+newestring(char *estr)
+{
+ String *s;
+
+ if(waserror()){
+ setstr(exstr, estr);
+ D2H(exstr)->ref++;
+ return exstr;
+ }
+ s = c2string(estr, strlen(estr));
+ poperror();
+ return s;
+}
+
+#define NOPC 0xffffffff
+
+#define FRTYPE(f) ((f)->t == nil ? SEXTYPE(f)->reg.TR : (f)->t)
+
+/*
+ * clear up an uncalled frame
+ */
+static void
+freeframe(uchar *fp, int setsp)
+{
+ Frame *f;
+
+ f = (Frame*)fp;
+ if(f->t == nil)
+ unextend(f);
+ else if(f->t->np)
+ freeptrs(f, f->t);
+ if(setsp)
+ R.SP = fp;
+}
+
+int
+handler(char *estr)
+{
+ Prog *p;
+ Modlink *m, *mr;
+ int str, ne;
+ ulong pc, newpc;
+ long eoff;
+ uchar *fp, **eadr;
+ Frame *f;
+ Type *t, *zt;
+ Handler *h;
+ Except *e;
+ void *v;
+
+ p = currun();
+ if(*estr == 0 || p == nil)
+ return 0;
+ str = p->exval == H || D2H(p->exval)->t == &Tstring;
+ m = R.M;
+ if(m->compiled)
+ pc = (ulong)R.PC-(ulong)m->prog;
+ else
+ pc = R.PC-m->prog;
+ pc--;
+ fp = R.FP;
+ while(fp != nil){ /* look for a handler */
+ if((h = m->m->htab) != nil){
+ for( ; h->etab != nil; h++){
+ if(pc < h->pc1 || pc >= h->pc2)
+ continue;
+ eoff = h->eoff;
+ zt = h->t;
+ for(e = h->etab, ne = h->ne; e->s != nil; e++, ne--){
+ if(ematch(e->s, estr) && (str && ne <= 0 || !str && ne > 0)){
+ newpc = e->pc;
+ goto found;
+ }
+ }
+ newpc = e->pc;
+ if(newpc != NOPC)
+ goto found;
+ }
+ }
+ if(!str && fp != R.FP){ /* becomes a string exception in immediate caller */
+ v = p->exval;
+ p->exval = *(String**)v;
+ D2H(p->exval)->ref++;
+ destroy(v);
+ str = 1;
+ continue;
+ }
+ f = (Frame*)fp;
+ if(f->mr != nil)
+ m = f->mr;
+ if(m->compiled)
+ pc = (ulong)f->lr-(ulong)m->prog;
+ else
+ pc = f->lr-m->prog;
+ pc--;
+ fp = f->fp;
+ }
+ destroy(p->exval);
+ p->exval = H;
+ return 0;
+found:
+ {
+ int n;
+ char name[3*KNAMELEN];
+
+ pc = modstatus(&R, name, sizeof(name));
+ n = 10+1+strlen(name)+1+strlen(estr)+1;
+ p->exstr = realloc(p->exstr, n);
+ if(p->exstr != nil)
+ snprint(p->exstr, n, "%lud %s %s", pc, name, estr);
+ }
+
+ /*
+ * there may be an uncalled frame at the top of the stack
+ */
+ f = (Frame*)R.FP;
+ t = FRTYPE(f);
+ if(R.FP < R.EX || R.FP >= R.TS)
+ freeframe(R.EX+OA(Stkext, reg.tos.fr), 0);
+ else if(R.FP+t->size < R.SP)
+ freeframe(R.FP+t->size, 1);
+
+ m = R.M;
+ while(R.FP != fp){
+ f = (Frame*)R.FP;
+ R.PC = f->lr;
+ R.FP = f->fp;
+ R.SP = (uchar*)f;
+ mr = f->mr;
+ if(f->t == nil)
+ unextend(f);
+ else if(f->t->np)
+ freeptrs(f, f->t);
+ if(mr != nil){
+ m = mr;
+ destroy(R.M);
+ R.M = m;
+ R.MP = m->MP;
+ }
+ }
+ if(zt != nil){
+ freeptrs(fp, zt);
+ initmem(zt, fp);
+ }
+ eadr = (uchar**)(fp+eoff);
+ destroy(*eadr);
+ *eadr = H;
+ if(p->exval == H)
+ *eadr = (uchar*)newestring(estr); /* might fail */
+ else{
+ D2H(p->exval)->ref++;
+ *eadr = p->exval;
+ }
+ if(m->compiled)
+ R.PC = (Inst*)((ulong)m->prog+newpc);
+ else
+ R.PC = m->prog+newpc;
+ memmove(&p->R, &R, sizeof(R));
+ p->kill = nil;
+ destroy(p->exval);
+ p->exval = H;
+ return 1;
+}
diff --git a/os/port/exportfs.c b/os/port/exportfs.c
new file mode 100644
index 00000000..354574ef
--- /dev/null
+++ b/os/port/exportfs.c
@@ -0,0 +1,1325 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "kernel.h"
+
+typedef struct Fid Fid;
+typedef struct Export Export;
+typedef struct Exq Exq;
+typedef struct Uqid Uqid;
+
+enum
+{
+ Nfidhash = 32,
+ Nqidhash = 32,
+ QIDMASK = ((vlong)1<<48)-1,
+ MAXFDATA = 8192,
+ MAXRPCDEF = IOHDRSZ+MAXFDATA, /* initial/default */
+ MAXRPCMAX = IOHDRSZ+64*1024, /* most every allowed */
+ MSGHDRSZ = BIT32SZ+BIT8SZ+BIT16SZ
+};
+
+struct Export
+{
+ Lock;
+ Ref r;
+ Exq* work;
+ Lock fidlock;
+ Fid* fid[Nfidhash];
+ QLock qidlock;
+ Uqid* qids[Nqidhash];
+ ulong pathgen;
+ Chan* io;
+ Chan* root;
+ Pgrp* pgrp;
+ Egrp* egrp;
+ Fgrp* fgrp;
+ int async;
+ int readonly;
+ int msize;
+ char* user;
+};
+
+struct Fid
+{
+ Fid* next;
+ Fid** last;
+ Chan* chan;
+ int fid;
+ int ref; /* fcalls using the fid; locked by Export.Lock */
+ vlong offset; /* last offset used (within directory) */
+ int attached; /* fid attached or cloned but not clunked */
+ Uqid* qid; /* generated qid */
+};
+
+struct Uqid
+{
+ Ref;
+ int type;
+ int dev;
+ vlong oldpath;
+ vlong newpath;
+ Uqid* next;
+};
+
+struct Exq
+{
+ Lock;
+ int busy; /* fcall in progress */
+ int finished; /* will do no more work on this request or flushes */
+ Exq* next;
+ int shut; /* has been noted for shutdown */
+ Exq* flush; /* queued flush requests */
+ Exq* flusht; /* tail of flush queue */
+ Export* export;
+ Proc* slave;
+ Fcall in, out;
+ uchar* buf;
+ int bsize;
+};
+
+struct
+{
+ Lock l;
+ QLock qwait;
+ Rendez rwait;
+ Exq *head; /* work waiting for a slave */
+ Exq *tail;
+}exq;
+
+static void exshutdown(Export*);
+static int exflushed(Export*, Exq*);
+static void exslave(void*);
+static void exfree(Export*);
+static void exfreeq(Exq*);
+static void exportproc(void*);
+static void exreply(Exq*, char*);
+static int exisroot(Export*, Chan*);
+static Uqid* uqidalloc(Export*, Chan*);
+static void freeuqid(Export*, Uqid*);
+
+static char* Exversion(Export*, Fcall*, Fcall*);
+static char* Exauth(Export*, Fcall*, Fcall*);
+static char* Exattach(Export*, Fcall*, Fcall*);
+static char* Exclunk(Export*, Fcall*, Fcall*);
+static char* Excreate(Export*, Fcall*, Fcall*);
+static char* Exopen(Export*, Fcall*, Fcall*);
+static char* Exread(Export*, Fcall*, Fcall*);
+static char* Exremove(Export*, Fcall*, Fcall*);
+static char* Exstat(Export*, Fcall*, Fcall*);
+static char* Exwalk(Export*, Fcall*, Fcall*);
+static char* Exwrite(Export*, Fcall*, Fcall*);
+static char* Exwstat(Export*, Fcall*, Fcall*);
+
+static char *(*fcalls[Tmax])(Export*, Fcall*, Fcall*);
+
+static char Enofid[] = "no such fid";
+static char Eseekdir[] = "can't seek on a directory";
+static char Eopen[] = "walk of open fid";
+static char Emode[] = "open/create -- unknown mode";
+static char Edupfid[] = "fid in use";
+static char Eaccess[] = "read/write -- not open in suitable mode";
+static char Ecount[] = "read/write -- count too big";
+int exdebug = 0;
+
+int
+export(int fd, char *dir, int async)
+{
+ Chan *c, *dc;
+ Pgrp *pg;
+ Egrp *eg;
+ Export *fs;
+
+ if(waserror())
+ return -1;
+ c = fdtochan(up->env->fgrp, fd, ORDWR, 1, 1);
+ poperror();
+
+ if(waserror()){
+ cclose(c);
+ return -1;
+ }
+ dc = namec(dir, Atodir, 0, 0);
+ poperror();
+
+ fs = malloc(sizeof(Export));
+ if(fs == nil){
+ cclose(c);
+ cclose(dc);
+ error(Enomem);
+ }
+
+ fs->r.ref = 1;
+ pg = up->env->pgrp;
+ fs->pgrp = pg;
+ incref(pg);
+ eg = up->env->egrp;
+ fs->egrp = eg;
+ if(eg != nil)
+ incref(eg);
+ fs->fgrp = newfgrp(nil);
+ kstrdup(&fs->user, up->env->user);
+ fs->root = dc;
+ fs->io = c;
+ fs->pathgen = 0;
+ fs->msize = 0;
+ c->flag |= CMSG;
+ fs->async = async;
+
+ if(async){
+ if(waserror())
+ return -1;
+ kproc("exportfs", exportproc, fs, 0);
+ poperror();
+ }else
+ exportproc(fs);
+
+ return 0;
+}
+
+static void
+exportinit(void)
+{
+ lock(&exq.l);
+ if(fcalls[Tversion] != nil) {
+ unlock(&exq.l);
+ return;
+ }
+ fcalls[Tversion] = Exversion;
+ fcalls[Tauth] = Exauth;
+ fcalls[Tattach] = Exattach;
+ fcalls[Twalk] = Exwalk;
+ fcalls[Topen] = Exopen;
+ fcalls[Tcreate] = Excreate;
+ fcalls[Tread] = Exread;
+ fcalls[Twrite] = Exwrite;
+ fcalls[Tclunk] = Exclunk;
+ fcalls[Tremove] = Exremove;
+ fcalls[Tstat] = Exstat;
+ fcalls[Twstat] = Exwstat;
+ unlock(&exq.l);
+}
+
+static int
+exisroot(Export *fs, Chan *c)
+{
+ return eqchan(fs->root, c, 1);
+}
+
+static int
+exreadn(Chan *c, void *buf, int n)
+{
+ int nr, t;
+
+ if(waserror())
+ return -1;
+ for(nr = 0; nr < n;){
+ t = devtab[c->type]->read(c, (char*)buf+nr, n-nr, 0);
+ if(t <= 0)
+ break;
+ nr += t;
+ }
+ poperror();
+ return nr;
+}
+
+static int
+exreadmsg(Chan *c, void *a, uint n)
+{
+ int m, len;
+ uchar *buf;
+
+ buf = a;
+ m = exreadn(c, buf, BIT32SZ);
+ if(m < BIT32SZ){
+ if(m < 0)
+ return -1;
+ return 0;
+ }
+ len = GBIT32(buf);
+ if(len <= BIT32SZ || len > n){
+ kwerrstr("bad length in Styx message header");
+ return -1;
+ }
+ len -= BIT32SZ;
+ m = exreadn(c, buf+BIT32SZ, len);
+ if(m < len){
+ if(m < 0)
+ return -1;
+ return 0;
+ }
+ return BIT32SZ+m;
+}
+
+static void
+exportproc(void *a)
+{
+ Exq *q;
+ int async, msize;
+ int n, type;
+ Export *fs = a;
+
+ exportinit();
+
+ for(;;){
+
+ msize = fs->msize;
+ if(msize == 0)
+ msize = MAXRPCDEF;
+ for(n=0;; n++){ /* we don't use smalloc, to avoid memset */
+ q = mallocz(sizeof(*q)+msize, 0);
+ if(q != nil || n > 6000)
+ break;
+ if(n%600 == 0)
+ print("exportproc %ld: waiting for memory (%d) for request\n", up->pid, msize);
+ tsleep(&up->sleep, return0, nil, 100);
+ }
+ if(q == nil){
+ kwerrstr("out of memory: read request");
+ n = -1;
+ break;
+ }
+ memset(q, 0, sizeof(*q));
+ q->buf = (uchar*)q + sizeof(*q);
+ q->bsize = msize;
+
+ n = exreadmsg(fs->io, q->buf, msize); /* TO DO: avoid copy */
+ if(n <= 0)
+ break;
+ if(convM2S(q->buf, n, &q->in) != n){
+ kwerrstr("bad T-message");
+ n = -1;
+ break;
+ }
+ type = q->in.type;
+ if(type < Tversion || type >= Tmax || type&1 || type == Terror){
+ kwerrstr("invalid T-message type %d", type);
+ n = -1;
+ break;
+ }
+
+ if(exdebug)
+ print("export %ld <- %F\n", up->pid, &q->in);
+
+ q->out.type = type+1;
+ q->out.tag = q->in.tag;
+
+ q->export = fs;
+ incref(&fs->r);
+
+ if(fs->readonly){
+ switch(type){
+ case Topen:
+ if((q->in.mode & (ORCLOSE|OTRUNC|3)) == OREAD)
+ break;
+ /* FALL THROUGH */
+ case Tcreate:
+ case Twrite:
+ case Tremove:
+ case Twstat:
+ q->out.type = Rerror;
+ q->out.ename = "file system read only";
+ exreply(q, "exportproc");
+ exfreeq(q);
+ continue;
+ }
+ }
+
+ if(q->in.type == Tflush){
+ if(exflushed(fs, q)){
+ /* not yet started or not found (flush arrived after reply); we reply */
+ if(exdebug)
+ print("export: flush %d\n", q->in.oldtag);
+ exreply(q, "exportproc");
+ exfreeq(q);
+ }
+ continue;
+ }
+
+ lock(&exq.l);
+ if(exq.head == nil)
+ exq.head = q;
+ else
+ exq.tail->next = q;
+ q->next = nil;
+ exq.tail = q;
+ unlock(&exq.l);
+ if(exq.qwait.head == nil)
+ kproc("exslave", exslave, nil, 0);
+ wakeup(&exq.rwait);
+ }
+
+ if(exdebug){
+ if(n < 0)
+ print("exportproc %ld shut down: %s\n", up->pid, up->env->errstr);
+ else
+ print("exportproc %ld shut down\n", up->pid);
+ }
+
+ free(q);
+ exshutdown(fs);
+ async = fs->async;
+ exfree(fs);
+
+ if(async)
+ pexit("mount shut down", 0);
+}
+
+static int
+exflushed(Export *fs, Exq *fq)
+{
+ Exq *q, **last;
+ ulong pid;
+
+ /* not yet started? */
+ lock(&exq.l);
+ for(last = &exq.head; (q = *last) != nil; last = &q->next)
+ if(q->export == fs && q->in.tag == fq->in.oldtag){
+ *last = q->next;
+ unlock(&exq.l);
+ /* not yet started: discard, and Rflush */
+ exfreeq(q);
+ return 1;
+ }
+ unlock(&exq.l);
+
+ /* tricky case: in progress */
+ lock(fs);
+ for(q = fs->work; q != nil; q = q->next)
+ if(q->in.tag == fq->in.oldtag){
+ pid = 0;
+ lock(q);
+ if(q->finished){
+ /* slave replied and emptied its flush queue; we can Rflush now */
+ unlock(q);
+ return 1;
+ }
+ /* append to slave's flush queue */
+ fq->next = nil;
+ if(q->flush != nil)
+ q->flusht->next = fq;
+ else
+ q->flush = fq;
+ q->flusht = fq;
+ if(q->busy){
+ pid = q->slave->pid;
+ swiproc(q->slave, 0);
+ }
+ unlock(q);
+ unlock(fs);
+ if(exdebug && pid)
+ print("export: swiproc %ld to flush %d\n", pid, fq->in.oldtag);
+ return 0;
+ }
+ unlock(fs);
+
+ /* not found */
+ return 1;
+}
+
+static void
+exfreeq(Exq *q)
+{
+ Exq *fq;
+
+ while((fq = q->flush) != nil){
+ q->flush = fq->next;
+ exfree(fq->export);
+ free(fq);
+ }
+ exfree(q->export);
+ free(q);
+}
+
+static void
+exshutdown(Export *fs)
+{
+ Exq *q, **last;
+
+ /* work not started */
+ lock(&exq.l);
+ for(last = &exq.head; (q = *last) != nil;)
+ if(q->export == fs){
+ *last = q->next;
+ exfreeq(q);
+ }else
+ last = &q->next;
+ unlock(&exq.l);
+
+ /* tell slaves to abandon work in progress */
+ lock(fs);
+ while((q = fs->work) != nil){
+ fs->work = q->next;
+ lock(q);
+ q->shut = 1;
+ swiproc(q->slave, 0); /* whether busy or not */
+ unlock(q);
+ }
+ unlock(fs);
+}
+
+static void
+exfreefids(Export *fs)
+{
+ Fid *f, *n;
+ int i;
+
+ for(i = 0; i < Nfidhash; i++){
+ for(f = fs->fid[i]; f != nil; f = n){
+ n = f->next;
+ f->attached = 0;
+ if(f->ref == 0) {
+ if(f->chan != nil)
+ cclose(f->chan);
+ freeuqid(fs, f->qid);
+ free(f);
+ } else
+ print("exfreefids: busy fid\n");
+ }
+ }
+}
+
+static void
+exfree(Export *fs)
+{
+ if(exdebug)
+ print("export p/s %ld free %p ref %ld\n", up->pid, fs, fs->r.ref);
+ if(decref(&fs->r) != 0)
+ return;
+ closepgrp(fs->pgrp);
+ closeegrp(fs->egrp);
+ closefgrp(fs->fgrp);
+ cclose(fs->root);
+ cclose(fs->io);
+ exfreefids(fs);
+ free(fs->user);
+ free(fs);
+}
+
+static int
+exwork(void*)
+{
+ return exq.head != nil;
+}
+
+static void
+exslave(void*)
+{
+ Export *fs;
+ Exq *q, *t, *fq, **last;
+ char *err;
+
+ for(;;){
+ qlock(&exq.qwait);
+ if(waserror()){
+ qunlock(&exq.qwait);
+ continue;
+ }
+ sleep(&exq.rwait, exwork, nil);
+ poperror();
+
+ lock(&exq.l);
+ q = exq.head;
+ if(q == nil) {
+ unlock(&exq.l);
+ qunlock(&exq.qwait);
+ continue;
+ }
+ exq.head = q->next;
+
+ qunlock(&exq.qwait);
+
+ /*
+ * put the job on the work queue before it's
+ * visible as off of the head queue, so it's always
+ * findable for flushes and shutdown
+ */
+ notkilled();
+ q->slave = up;
+ q->busy = 1; /* fcall in progress: interruptible */
+ fs = q->export;
+ lock(fs);
+ q->next = fs->work;
+ fs->work = q;
+ unlock(fs);
+ unlock(&exq.l);
+
+ up->env->pgrp = q->export->pgrp;
+ up->env->egrp = q->export->egrp;
+ up->env->fgrp = q->export->fgrp;
+ kstrdup(&up->env->user, q->export->user);
+
+ if(exdebug > 1)
+ print("exslave %ld dispatch %F\n", up->pid, &q->in);
+
+ if(waserror()){
+ print("exslave %ld err %s\n", up->pid, up->env->errstr); /* shouldn't happen */
+ err = up->env->errstr;
+ }else{
+ if(q->in.type >= Tmax || !fcalls[q->in.type]){
+ snprint(up->genbuf, sizeof(up->genbuf), "unknown message: %F", &q->in);
+ err = up->genbuf;
+ }else{
+ switch(q->in.type){
+ case Tread:
+ q->out.data = (char*)q->buf + IOHDRSZ;
+ break;
+ case Tstat:
+ q->out.stat = q->buf + MSGHDRSZ + BIT16SZ; /* leaves it just where we want it */
+ q->out.nstat = q->bsize-(MSGHDRSZ+BIT16SZ);
+ break;
+ }
+ err = (*fcalls[q->in.type])(fs, &q->in, &q->out);
+ }
+ poperror();
+ }
+
+ /*
+ * if the fcall completed without error we must reply,
+ * even if a flush is pending (because the underlying server
+ * might have changed state), unless the export has shut down completely.
+ * must also reply to each flush in order, and only after the original reply (if sent).
+ */
+ lock(q);
+ notkilled();
+ q->busy = 0; /* operation complete */
+ if(!q->shut){
+ if(q->flush == nil || err == nil){
+ unlock(q);
+ q->out.type = q->in.type+1;
+ q->out.tag = q->in.tag;
+ if(err){
+ q->out.type = Rerror;
+ q->out.ename = err;
+ }
+ exreply(q, "exslave");
+ lock(q);
+ }
+ while((fq = q->flush) != nil && !q->shut){
+ q->flush = fq->next;
+ unlock(q);
+ exreply(fq, "exslave");
+ exfreeq(fq);
+ lock(q);
+ }
+ }
+ q->finished = 1; /* promise not to send any more */
+ unlock(q);
+
+ lock(fs);
+ for(last = &fs->work; (t = *last) != nil; last = &t->next)
+ if(t == q){
+ *last = q->next;
+ break;
+ }
+ unlock(fs);
+
+ notkilled();
+ exfreeq(q);
+ }
+ print("exslave %ld shut down", up->pid); /* not reached */
+ pexit("exslave shut down", 0);
+}
+
+static void
+exreply(Exq *q, char *who)
+{
+ Export *fs;
+ Fcall *r;
+ int n;
+
+ fs = q->export;
+ r = &q->out;
+
+ n = convS2M(r, q->buf, q->bsize);
+ if(n == 0){
+ r->type = Rerror;
+ if(fs->msize == 0)
+ r->ename = "Tversion not seen";
+ else
+ r->ename = "failed to convert R-message";
+ n = convS2M(r, q->buf, q->bsize);
+ }
+
+ if(exdebug)
+ print("%s %ld -> %F\n", who, up->pid, r);
+
+ if(!waserror()){
+ devtab[fs->io->type]->write(fs->io, q->buf, n, 0);
+ poperror();
+ }
+}
+
+static int
+exiounit(Export *fs, Chan *c)
+{
+ int iounit;
+
+ iounit = fs->msize-IOHDRSZ;
+ if(c->iounit != 0 && c->iounit < fs->msize)
+ iounit = c->iounit;
+ return iounit;
+}
+
+static Qid
+Exrmtqid(Chan *c, Uqid *qid)
+{
+ Qid q;
+
+ q.path = qid->newpath;
+ q.vers = c->qid.vers;
+ q.type = c->qid.type;
+ return q;
+}
+
+static Fid*
+Exmkfid(Export *fs, ulong fid)
+{
+ ulong h;
+ Fid *f, *nf;
+
+ nf = malloc(sizeof(Fid));
+ if(nf == nil)
+ return nil;
+ lock(&fs->fidlock);
+ h = fid % Nfidhash;
+ for(f = fs->fid[h]; f != nil; f = f->next){
+ if(f->fid == fid){
+ unlock(&fs->fidlock);
+ free(nf);
+ return nil;
+ }
+ }
+
+ nf->next = fs->fid[h];
+ if(nf->next != nil)
+ nf->next->last = &nf->next;
+ nf->last = &fs->fid[h];
+ fs->fid[h] = nf;
+
+ nf->fid = fid;
+ nf->ref = 1;
+ nf->attached = 1;
+ nf->offset = 0;
+ nf->chan = nil;
+ nf->qid = nil;
+ unlock(&fs->fidlock);
+ return nf;
+}
+
+static Fid*
+Exgetfid(Export *fs, ulong fid)
+{
+ Fid *f;
+ ulong h;
+
+ lock(&fs->fidlock);
+ h = fid % Nfidhash;
+ for(f = fs->fid[h]; f; f = f->next) {
+ if(f->fid == fid){
+ if(f->attached == 0)
+ break;
+ f->ref++;
+ unlock(&fs->fidlock);
+ return f;
+ }
+ }
+ unlock(&fs->fidlock);
+ return nil;
+}
+
+static void
+Exputfid(Export *fs, Fid *f)
+{
+ Chan *c;
+
+ lock(&fs->fidlock);
+ f->ref--;
+ if(f->ref == 0 && f->attached == 0){
+ c = f->chan;
+ f->chan = nil;
+ *f->last = f->next;
+ if(f->next != nil)
+ f->next->last = f->last;
+ unlock(&fs->fidlock);
+ if(c != nil)
+ cclose(c);
+ freeuqid(fs, f->qid);
+ free(f);
+ return;
+ }
+ unlock(&fs->fidlock);
+}
+
+static Chan*
+exmount(Chan *c, Mhead **mp, int doname)
+{
+ Chan *nc;
+ Cname *oname;
+
+ nc = nil;
+ if((c->flag & COPEN) == 0 && findmount(&nc, mp, c->type, c->dev, c->qid)){
+ if(waserror()){
+ cclose(nc);
+ nexterror();
+ }
+ nc = cunique(nc);
+ poperror();
+ if(doname){
+ oname = c->name;
+ incref(oname);
+ cnameclose(nc->name);
+ nc->name = oname;
+ }
+ return nc;
+ }
+ incref(c);
+ return c;
+}
+
+static char*
+Exversion(Export *fs, Fcall *t, Fcall *r)
+{
+ char *p;
+ static char version[] = VERSION9P;
+ int iounit;
+
+ r->msize = t->msize;
+ if(r->msize > MAXRPCMAX)
+ r->msize = MAXRPCMAX;
+ iounit = fs->io->iounit;
+ if(iounit != 0 && iounit > 64 && iounit < r->msize)
+ r->msize = iounit;
+ if(r->msize < 64)
+ return "message size too small";
+ if((p = strchr(t->version, '.')) != nil)
+ *p = 0;
+ if(strncmp(t->version, "9P", 2) ==0 && strcmp(version, t->version) <= 0){
+ r->version = version;
+ fs->msize = r->msize;
+ }else
+ r->version = "unknown";
+ return nil;
+}
+
+static char*
+Exauth(Export *fs, Fcall *t, Fcall *r)
+{
+ USED(fs);
+ USED(t);
+ USED(r);
+ return "authentication not required";
+}
+
+static char*
+Exattach(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+
+ f = Exmkfid(fs, t->fid);
+ if(f == nil)
+ return Edupfid;
+ if(waserror()){
+ f->attached = 0;
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ f->chan = cclone(fs->root);
+ f->qid = uqidalloc(fs, f->chan);
+ poperror();
+ r->qid = Exrmtqid(f->chan, f->qid);
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exclunk(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+
+ USED(r);
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ f->attached = 0;
+ Exputfid(fs, f);
+ return nil;
+}
+
+static int
+safewalk(Chan **cp, char **names, int nnames, int nomount, int *nerror)
+{
+ int r;
+
+ /* walk can raise error */
+ if(waserror())
+ return -1;
+ r = walk(cp, names, nnames, nomount, nerror);
+ poperror();
+ return r;
+}
+
+static char*
+Exwalk(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f, *nf;
+ Chan *c;
+ char *name;
+ Uqid *qid;
+ int i;
+
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ if(f->chan->flag & COPEN){
+ Exputfid(fs, f);
+ return Eopen;
+ }
+
+ if(waserror())
+ return up->env->errstr;
+ c = cclone(f->chan);
+ poperror();
+ qid = f->qid;
+ incref(qid);
+ r->nwqid = 0;
+ if(t->nwname > 0){
+ for(i=0; i<t->nwname; i++){
+ name = t->wname[i];
+ if(!exisroot(fs, c) || *name != '\0' && strcmp(name, "..") != 0){
+ if(safewalk(&c, &name, 1, 0, nil) < 0){
+ /* leave the original state on error */
+ cclose(c);
+ freeuqid(fs, qid);
+ Exputfid(fs, f);
+ if(i == 0)
+ return up->env->errstr;
+ return nil;
+ }
+ freeuqid(fs, qid);
+ qid = uqidalloc(fs, c);
+ }
+ r->wqid[r->nwqid++] = Exrmtqid(c, qid);
+ }
+ }
+
+ if(t->newfid != t->fid){
+ nf = Exmkfid(fs, t->newfid);
+ if(nf == nil){
+ cclose(c);
+ freeuqid(fs, qid);
+ Exputfid(fs, f);
+ return Edupfid;
+ }
+ nf->chan = c;
+ nf->qid = qid;
+ Exputfid(fs, nf);
+ }else{
+ cclose(f->chan);
+ f->chan = c;
+ freeuqid(fs, f->qid);
+ f->qid = qid;
+ }
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exopen(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ Chan *c;
+ Uqid *qid;
+ Mhead *m;
+
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ if(f->chan->flag & COPEN){
+ Exputfid(fs, f);
+ return Emode;
+ }
+ m = nil;
+ c = exmount(f->chan, &m, 1);
+ if(waserror()){
+ cclose(c);
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+
+ /* only save the mount head if it's a multiple element union */
+ if(m && m->mount && m->mount->next)
+ c->umh = m;
+ else
+ putmhead(m);
+
+ c = devtab[c->type]->open(c, t->mode);
+ if(t->mode & ORCLOSE)
+ c->flag |= CRCLOSE;
+
+ qid = uqidalloc(fs, c);
+ poperror();
+ freeuqid(fs, f->qid);
+ cclose(f->chan);
+ f->chan = c;
+ f->qid = qid;
+ f->offset = 0;
+ r->qid = Exrmtqid(c, f->qid);
+ r->iounit = exiounit(fs, c);
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Excreate(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ volatile struct {Chan *c;} c, dc;
+ Cname *oname;
+ Uqid *qid;
+ Mhead *m;
+
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ if(f->chan->flag & COPEN){
+ Exputfid(fs, f);
+ return Emode;
+ }
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ validname(t->name, 0);
+ if(t->name[0] == '.' && (t->name[1] == '\0' || t->name[1] == '.' && t->name[2] == '\0'))
+ error(Efilename); /* underlying server should check, but stop it here */
+
+ m = nil;
+ c.c = exmount(f->chan, &m, 1);
+ if(waserror()){
+ cclose(c.c);
+ if(m != nil)
+ putmhead(m);
+ nexterror();
+ }
+ if(m != nil){
+ oname = c.c->name;
+ incref(oname);
+ if(waserror()){
+ cnameclose(oname);
+ nexterror();
+ }
+ dc.c = createdir(c.c, m);
+ if(waserror()){
+ cclose(dc.c);
+ nexterror();
+ }
+ c.c = cunique(dc.c);
+ poperror();
+ cnameclose(c.c->name);
+ poperror();
+ c.c->name = oname;
+ }
+ devtab[c.c->type]->create(c.c, t->name, t->mode, t->perm);
+ c.c->name = addelem(c.c->name, t->name);
+ if(t->mode & ORCLOSE)
+ c.c->flag |= CRCLOSE;
+ qid = uqidalloc(fs, c.c);
+ poperror();
+ if(m != nil)
+ putmhead(m);
+
+ poperror();
+ cclose(f->chan);
+ f->chan = c.c;
+ freeuqid(fs, f->qid);
+ f->qid = qid;
+ r->qid = Exrmtqid(c.c, f->qid);
+ r->iounit = exiounit(fs, c.c);
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exread(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ Chan *c;
+ long off;
+ int dir, n, seek;
+
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+
+ if(waserror()) {
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ c = f->chan;
+ if((c->flag & COPEN) == 0)
+ error(Emode);
+ if(c->mode != OREAD && c->mode != ORDWR)
+ error(Eaccess);
+ if(t->count < 0 || t->count > fs->msize-IOHDRSZ)
+ error(Ecount);
+ if(t->offset < 0)
+ error(Enegoff);
+ dir = c->qid.type & QTDIR;
+ if(dir && t->offset != f->offset){
+ if(t->offset != 0)
+ error(Eseekdir);
+ f->offset = 0;
+ c->uri = 0;
+ c->dri = 0;
+ }
+
+ for(;;){
+ n = t->count;
+ seek = 0;
+ off = t->offset;
+ if(dir && f->offset != off){
+ off = f->offset;
+ n = t->offset - off;
+ if(n > MAXFDATA)
+ n = MAXFDATA;
+ seek = 1;
+ }
+ if(dir && c->umh != nil){
+ if(0)
+ print("union read %d uri %d dri %d\n", seek, c->uri, c->dri);
+ n = unionread(c, r->data, n);
+ }
+ else{
+ c->offset = off;
+ n = devtab[c->type]->read(c, r->data, n, off);
+ lock(c);
+ c->offset += n;
+ unlock(c);
+ }
+ f->offset = off + n;
+ if(n == 0 || !seek)
+ break;
+ }
+ r->count = n;
+
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exwrite(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ c = f->chan;
+ if((c->flag & COPEN) == 0)
+ error(Emode);
+ if(c->mode != OWRITE && c->mode != ORDWR)
+ error(Eaccess);
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ if(t->count < 0 || t->count > fs->msize-IOHDRSZ)
+ error(Ecount);
+ if(t->offset < 0)
+ error(Enegoff);
+ r->count = devtab[c->type]->write(c, t->data, t->count, t->offset);
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exstat(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ Chan *c;
+ int n;
+
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ c = exmount(f->chan, nil, 1);
+ if(waserror()){
+ cclose(c);
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ n = devtab[c->type]->stat(c, r->stat, r->nstat);
+ if(n <= BIT16SZ)
+ error(Eshortstat);
+ r->nstat = n;
+ poperror();
+ cclose(c);
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exwstat(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ Chan *c;
+
+ USED(r);
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ validstat(t->stat, t->nstat); /* check name */
+
+ c = exmount(f->chan, nil, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ devtab[c->type]->wstat(c, t->stat, t->nstat);
+ poperror();
+
+ cclose(c);
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+static char*
+Exremove(Export *fs, Fcall *t, Fcall *r)
+{
+ Fid *f;
+ Chan *c;
+
+ USED(r);
+ f = Exgetfid(fs, t->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ f->attached = 0;
+ Exputfid(fs, f);
+ return up->env->errstr;
+ }
+ c = exmount(f->chan, nil, 0);
+ if(waserror()){
+ c->type = 0; /* see below */
+ cclose(c);
+ nexterror();
+ }
+ devtab[c->type]->remove(c);
+ poperror();
+ poperror();
+
+ /*
+ * chan is already clunked by remove.
+ * however, we need to recover the chan,
+ * and follow sysremove's lead in making it point to root.
+ */
+ c->type = 0;
+
+ cclose(c);
+ f->attached = 0;
+ Exputfid(fs, f);
+ return nil;
+}
+
+/*
+ * unique path generation
+ */
+
+static int
+uqidhash(vlong path)
+{
+ ulong p;
+ p = (ulong)path;
+ return ((p>>16) ^ (p>>8) ^ p) & (Nqidhash-1);
+}
+
+static Uqid **
+uqidlook(Uqid **tab, Chan *c, vlong path)
+{
+ Uqid **hp, *q;
+
+ for(hp = &tab[uqidhash(path)]; (q = *hp) != nil; hp = &q->next)
+ if(q->type == c->type && q->dev == c->dev && q->oldpath == path)
+ break;
+ return hp;
+}
+
+static int
+uqidexists(Uqid **tab, vlong path)
+{
+ int i;
+ Uqid *q;
+
+ for(i=0; i<Nqidhash; i++)
+ for(q = tab[i]; q != nil; q = q->next)
+ if(q->newpath == path)
+ return 1;
+ return 0;
+}
+
+static Uqid *
+uqidalloc(Export *fs, Chan *c)
+{
+ Uqid **hp, *q;
+
+ qlock(&fs->qidlock);
+ hp = uqidlook(fs->qids, c, c->qid.path);
+ if((q = *hp) != nil){
+ incref(q);
+ qunlock(&fs->qidlock);
+ return q;
+ }
+ q = mallocz(sizeof(*q), 1);
+ if(q == nil){
+ qunlock(&fs->qidlock);
+ error(Enomem);
+ }
+ q->ref = 1;
+ q->type = c->type;
+ q->dev = c->dev;
+ q->oldpath = c->qid.path;
+ q->newpath = c->qid.path;
+ while(uqidexists(fs->qids, q->newpath)){
+ if(++fs->pathgen >= (1<<16))
+ fs->pathgen = 1;
+ q->newpath = ((vlong)fs->pathgen<<48) | (q->newpath & QIDMASK);
+ }
+ q->next = nil;
+ *hp = q;
+ qunlock(&fs->qidlock);
+ return q;
+}
+
+static void
+freeuqid(Export *fs, Uqid *q)
+{
+ Uqid **hp;
+
+ if(q == nil)
+ return;
+ qlock(&fs->qidlock);
+ if(decref(q) == 0){
+ hp = &fs->qids[uqidhash(q->oldpath)];
+ for(; *hp != nil; hp = &(*hp)->next)
+ if(*hp == q){
+ *hp = q->next;
+ free(q);
+ break;
+ }
+ }
+ qunlock(&fs->qidlock);
+}
diff --git a/os/port/flashamd29f0x0.c b/os/port/flashamd29f0x0.c
new file mode 100644
index 00000000..06798d05
--- /dev/null
+++ b/os/port/flashamd29f0x0.c
@@ -0,0 +1,167 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "../port/flashif.h"
+
+/*
+ * AMD29F0x0 with 4 interleaved to give 32 bits
+ */
+
+enum {
+ DQ7 = 0x80808080,
+ DQ6 = 0x40404040,
+ DQ5 = 0x20202020,
+ DQ3 = 0x08080808,
+ DQ2 = 0x04040404,
+};
+
+#define DPRINT if(0)print
+#define EPRINT if(1)print
+
+static char*
+amdwait(ulong *p, ulong ticks)
+{
+ ulong v0, v1;
+
+ ticks += m->ticks+1;
+ v0 = *p;
+ for(;;){
+ sched();
+ v1 = *p;
+ if((v1 & DQ6) == (v0 & DQ6))
+ break;
+ if((v1 & DQ5) == DQ5){
+ v0 = *p;
+ v1 = *p;
+ if((v1 & DQ6) == (v0 & DQ6))
+ break;
+ EPRINT("flash: DQ5 error: %8.8lux %8.8lux\n", v0, v1);
+ return "flash write error";
+ }
+ if(m->ticks >= ticks){
+ EPRINT("flash: timed out: %8.8lux\n", *p);
+ return "flash write timed out";
+ }
+ v0 = v1;
+ }
+ return nil;
+}
+
+static int
+eraseall(Flash *f)
+{
+ ulong *p;
+ int s;
+ char *e;
+
+ DPRINT("flash: erase all\n");
+ p = (ulong*)f->addr;
+ s = splhi();
+ *(p+0x555) = 0xAAAAAAAA;
+ *(p+0x2AA) = 0x55555555;
+ *(p+0x555) = 0x80808080;
+ *(p+0x555) = 0xAAAAAAAA;
+ *(p+0x2AA) = 0x55555555;
+ *(p+0x555) = 0x10101010; /* chip erase */
+ splx(s);
+ e = amdwait(p, MS2TK(64*1000));
+ *p = 0xF0F0F0F0; /* reset */
+ if(e != nil)
+ error(e);
+ return 0;
+}
+
+static int
+erasezone(Flash *f, Flashregion *r, ulong addr)
+{
+ ulong *p;
+ int s;
+ char *e;
+
+ DPRINT("flash: erase %8.8lux\n", addr);
+ if(addr & (r->erasesize-1))
+ return -1; /* bad zone */
+ p = (ulong*)f->addr;
+ s = splhi();
+ *(p+0x555) = 0xAAAAAAAA;
+ *(p+0x2AA) = 0x55555555;
+ *(p+0x555) = 0x80808080;
+ *(p+0x555) = 0xAAAAAAAA;
+ *(p+0x2AA) = 0x55555555;
+ p += addr>>2;
+ *p = 0x30303030; /* sector erase */
+ splx(s);
+ e = amdwait(p, MS2TK(8*1000));
+ *p = 0xF0F0F0F0; /* reset */
+ if(e != nil)
+ error(e);
+ return 0;
+}
+
+static int
+write4(Flash *f, ulong offset, void *buf, long n)
+{
+ ulong *p, *a, *v, w;
+ int s;
+ char *e;
+
+ p = (ulong*)f->addr;
+ if(((ulong)p|offset|n)&3)
+ return -1;
+ n >>= 2;
+ a = p + (offset>>2);
+ v = buf;
+ for(; --n >= 0; v++, a++){
+ w = *a;
+ DPRINT("flash: write %lux %lux -> %lux\n", (ulong)a, w, *v);
+ if(w == *v)
+ continue; /* already set */
+ if(~w & *v)
+ error("flash not erased");
+ s = splhi();
+ *(p+0x555) = 0xAAAAAAAA;
+ *(p+0x2AA) = 0x55555555;
+ *(p+0x555) = 0xA0A0A0A0; /* program */
+ *a = *v;
+ splx(s);
+ microdelay(8);
+ if(*a != *v){
+ microdelay(8);
+ while(*a != *v){
+ e = amdwait(a, 1);
+ if(e != nil)
+ error(e);
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+reset(Flash *f)
+{
+ f->id = 0x01; /* can't use autoselect: might be running in flash */
+ f->devid = 0;
+ f->write = write4;
+ f->eraseall = eraseall;
+ f->erasezone = erasezone;
+ f->suspend = nil;
+ f->resume = nil;
+ f->width = 4;
+ f->interleave = 0; /* TO DO */
+ f->nr = 1;
+ f->regions[0] = (Flashregion){f->size/(4*64*1024), 0, f->size, 4*64*1024, 0};
+ *(ulong*)f->addr = 0xF0F0F0F0; /* reset (just in case) */
+ return 0;
+}
+
+void
+flashamd29f0x0link(void)
+{
+ addflashcard("AMD29F0x0", reset);
+}
diff --git a/os/port/flashcfi16.c b/os/port/flashcfi16.c
new file mode 100644
index 00000000..3c22f475
--- /dev/null
+++ b/os/port/flashcfi16.c
@@ -0,0 +1,134 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "../port/flashif.h"
+
+/*
+ * Common Flash Interface (1x16 and 2x16)
+ */
+
+/* interleaved flash has chips at even and odd word addresses */
+#define I(x) (((x)<<24)|((x)<<16)|((x)<<8)|(x))
+
+enum {
+ ReadArray = I(0xFF),
+ ReadQuery = I(0x98),
+};
+
+#include "flashintel"
+
+static int
+cfiget1(Flash *f, ulong a)
+{
+ ulong v;
+
+ v = flashget(f, a);
+//iprint("%.8lux->%.4ux\n", a, v);
+ if(f->width == 2 && v == 0xFFFF)
+ return 0; /* get this on old, partially-conforming CFI */
+ return v & 0xFF;
+}
+
+static int
+cfiget2(Flash *f, ulong i)
+{
+ return (cfiget1(f, i+1)<<8) | cfiget1(f, i);
+}
+
+static int
+cfiquery(Flash *f)
+{
+ Flashregion *r;
+ ulong addr;
+ int i;
+
+ flashput(f, 0x55, ReadQuery);
+ if(!(cfiget1(f, 0x10) == 'Q' && cfiget1(f, 0x11) == 'R' && cfiget1(f, 0x12) == 'Y')) /* TO DO: detect interleave */
+ return 0;
+ f->alg = cfiget2(f, 0x13);
+ i = cfiget1(f, 0x27);
+ if(i > 0 && i < 32)
+ i = 1<<i;
+ else
+ i = 0;
+ f->devsize = i;
+ f->size = f->devsize;
+ if(f->interleave)
+ f->size *= 2;
+ i = cfiget2(f, 0x2A);
+ if(i > 0 && i < 32)
+ i = 1<<i;
+ else
+ i = 0;
+ f->maxwb = i;
+ f->nr = cfiget1(f, 0x2C);
+ if(f->nr != 0){
+ addr = 0;
+ for(i=0; i<f->nr; i++){
+ r = &f->regions[i];
+ r->n = cfiget2(f, 0x2D+4*i)+1;
+ r->erasesize = cfiget2(f, 0x2D+2+4*i)*256;
+ if(r->erasesize == 0)
+ r->erasesize = 128;
+ if(f->interleave)
+ r->erasesize *= 2; /* TO DO */
+ r->start = addr;
+ r->end = r->start + r->n*r->erasesize;
+ }
+ if(1){
+ iprint("cfi: devsize=%lud maxwb=%d\n", f->devsize, f->maxwb);
+ for(i=0; i<f->nr; i++){
+ r = &f->regions[i];
+ iprint("flash %d: %d %lud %8.8lux %8.8lux\n", i, r->n, r->erasesize, r->start, r->end);
+ }
+ }
+ }else{
+ f->nr = 1;
+ f->regions[0] = (Flashregion){1, 0, f->devsize, f->devsize, 0};
+ }
+ return 1;
+}
+
+static int
+reset(Flash *f)
+{
+ if(f->xip)
+ return -1; /* can't use this interface if executing from flash */
+ if(f->width == 0)
+ f->width = 2;
+ if(!cfiquery(f) || f->alg != 1 && f->alg != 3){
+ /* apparently not CFI: try to reset to read mode before return */
+ flashput(f, 0x55, ClearStatus);
+ flashput(f, 0x55, ReadArray);
+ return -1;
+ }
+ f->cmask = 0x00FF00FF;
+ flashput(f, 0x55, ClearStatus);
+ flashput(f, 0x55, ReadID);
+ f->id = cfiget1(f, 0x00);
+ f->devid = cfiget1(f, 0x01);
+ flashput(f, 0x55, ClearStatus);
+ flashput(f, 0x55, ReadArray);
+ if(f->width == 2){
+ f->cmask = 0x00FF;
+ f->write = intelwrite2;
+ }else{
+ f->cmask = 0x00FF00FF;
+ f->write = intelwrite4;
+ }
+ f->erasezone = intelerase;
+ f->suspend = nil;
+ f->resume = nil;
+ return 0;
+}
+
+void
+flashcfi16link(void)
+{
+ addflashcard("cfi16", reset);
+}
diff --git a/os/port/flashcfi8.c b/os/port/flashcfi8.c
new file mode 100644
index 00000000..099267b6
--- /dev/null
+++ b/os/port/flashcfi8.c
@@ -0,0 +1,143 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "../port/flashif.h"
+
+/*
+ * Common Flash Interface (1x8 and 2x8)
+ */
+
+/* interleaved flash has chips at even and odd word addresses */
+#define I(x) (((x)<<24)|((x)<<16)|((x)<<8)|(x))
+
+enum {
+ ReadArray = I(0xFF),
+ ReadQuery = I(0x98),
+};
+
+/* TO DO: flash amd */
+#include "flashintel"
+
+static int
+cfiget1(Flash *f, ulong a)
+{
+ ulong v;
+
+ v = flashget(f, a);
+//iprint("%.8lux->%.4ux\n", a, v);
+ return v & 0xFF;
+}
+
+static int
+cfiget2(Flash *f, ulong i)
+{
+ return (cfiget1(f, i+1)<<8) | cfiget1(f, i);
+}
+
+static int
+cfiquery(Flash *f)
+{
+ Flashregion *r;
+ ulong addr;
+ int i;
+
+ flashput(f, 0x55, ReadQuery);
+ if(!(cfiget1(f, 0x10) == 'Q' && cfiget1(f, 0x11) == 'R' && cfiget1(f, 0x12) == 'Y')) /* TO DO: detect interleave */
+ return 0;
+ f->alg = cfiget2(f, 0x13);
+ i = cfiget1(f, 0x27);
+ if(i > 0 && i < 32)
+ i = 1<<i;
+ else
+ i = 0;
+ f->devsize = i;
+ f->size = f->devsize;
+ if(f->interleave)
+ f->size *= 2;
+ i = cfiget2(f, 0x2A);
+ if(i > 0 && i < 32)
+ i = 1<<i;
+ else
+ i = 0;
+ f->maxwb = i;
+ f->nr = cfiget1(f, 0x2C);
+ if(f->nr != 0){
+ addr = 0;
+ for(i=0; i<f->nr; i++){
+ r = &f->regions[i];
+ r->n = cfiget2(f, 0x2D+4*i)+1;
+ r->erasesize = cfiget2(f, 0x2D+2+4*i)*256;
+ if(r->erasesize == 0)
+ r->erasesize = 128;
+ if(f->interleave)
+ r->erasesize *= 2; /* TO DO */
+ r->start = addr;
+ r->end = r->start + r->n*r->erasesize;
+ }
+ if(1){
+ iprint("cfi: devsize=%lud maxwb=%d\n", f->devsize, f->maxwb);
+ for(i=0; i<f->nr; i++){
+ r = &f->regions[i];
+ iprint("flash %d: %d %lud %8.8lux %8.8lux\n", i, r->n, r->erasesize, r->start, r->end);
+ }
+ }
+ }else{
+ f->nr = 1;
+ f->regions[0] = (Flashregion){1, 0, f->devsize, f->devsize, 0};
+ }
+ return 1;
+}
+
+static int
+reset(Flash *f)
+{
+ if(f->xip)
+ return -1; /* can't use this interface if executing from flash */
+ if(f->width == 0)
+ f->width = 1;
+ if(!cfiquery(f) && f->bshift==0)
+ f->bshift = 1; /* try this */
+ if(!cfiquery(f) || f->alg != 1 && f->alg != 3){
+ if(f->alg == 2){
+ print("amd algorithm\n");
+ goto ok;
+ }
+ /* apparently not CFI: try to reset to read mode before return */
+ flashput(f, 0x55, ClearStatus);
+ flashput(f, 0x55, ReadArray);
+ return -1;
+ }
+ok:
+ switch(f->width){
+ case 1:
+ f->cmask = 0x00FF;
+ break;
+ case 2:
+ f->cmask = 0xFFFF;
+ break;
+ case 4:
+ f->cmask = 0xFFFFFFFF;
+ break;
+ }
+ flashput(f, 0x55, ClearStatus);
+ flashput(f, 0x55, ReadID);
+ f->id = cfiget1(f, 0x00);
+ f->devid = cfiget1(f, 0x01);
+ flashput(f, 0x55, ClearStatus);
+ flashput(f, 0x55, ReadArray);
+ f->erasezone = nil;
+ f->suspend = nil;
+ f->resume = nil;
+ return 0;
+}
+
+void
+flashcfi8link(void)
+{
+ addflashcard("cfi8", reset);
+}
diff --git a/os/port/flashif.h b/os/port/flashif.h
new file mode 100644
index 00000000..cc013760
--- /dev/null
+++ b/os/port/flashif.h
@@ -0,0 +1,147 @@
+typedef struct Flash Flash;
+typedef struct Flashchip Flashchip;
+typedef struct Flashpart Flashpart;
+typedef struct Flashregion Flashregion;
+
+/*
+ * logical partitions
+ */
+enum {
+ Maxflashpart = 8
+};
+
+struct Flashpart {
+ char* name;
+ ulong start;
+ ulong end;
+};
+
+enum {
+ Maxflashregion = 4
+};
+
+/*
+ * physical erase block regions
+ */
+struct Flashregion {
+ int n; /* number of blocks in region */
+ ulong start; /* physical base address (allowing for banks) */
+ ulong end;
+ ulong erasesize;
+ ulong pagesize; /* if non-zero, the size of pages within the erase block */
+};
+
+/*
+ * one of a set of chips in a given region
+ */
+struct Flashchip {
+ int nr;
+ Flashregion regions[Maxflashregion];
+
+ uchar id; /* flash manufacturer ID */
+ ushort devid; /* flash device ID */
+ int width; /* bytes per flash line */
+ int maxwb; /* max write buffer size */
+ ulong devsize; /* physical device size */
+ int alg; /* programming algorithm (if CFI) */
+ int protect; /* software protection */
+};
+
+/*
+ * structure defining a contiguous region of flash memory
+ */
+struct Flash {
+ QLock; /* interlock on flash operations */
+ Flash* next;
+
+ /* the following are filled in before calling Flash.reset */
+ char* type;
+ void* addr;
+ ulong size;
+ int xip; /* executing in place: don't query */
+ int (*reset)(Flash*);
+
+ /* the following are filled in by the reset routine */
+ int (*eraseall)(Flash*);
+ int (*erasezone)(Flash*, Flashregion*, ulong);
+ int (*read)(Flash*, ulong, void*, long); /* (optional) reads of correct width and alignment */
+ int (*write)(Flash*, ulong, void*, long); /* writes of correct width and alignment */
+ int (*suspend)(Flash*);
+ int (*resume)(Flash*);
+ int (*attach)(Flash*);
+
+ /* the following might be filled in by either archflashreset or the reset routine */
+ int nr;
+ Flashregion regions[Maxflashregion];
+
+ uchar id; /* flash manufacturer ID */
+ ushort devid; /* flash device ID */
+ int width; /* bytes per flash line */
+ int interleave; /* addresses are interleaved across set of chips */
+ int bshift; /* byte addresses are shifted */
+ ulong cmask; /* command mask for interleaving */
+ int maxwb; /* max write buffer size */
+ ulong devsize; /* physical device size */
+ int alg; /* programming algorithm (if CFI) */
+ void* data; /* flash type routines' private storage, or nil */
+ Flashpart part[Maxflashpart]; /* logical partitions */
+ int protect; /* software protection */
+ char* sort; /* "nand", "nor", "serial", nil (unspecified) */
+};
+
+/*
+ * called by link routine of driver for specific flash type: arguments are
+ * conventional name for card type/model, and card driver's reset routine.
+ */
+void addflashcard(char*, int (*)(Flash*));
+
+/*
+ * called by devflash.c:/^flashreset; if flash exists,
+ * sets type, address, and size in bytes of flash
+ * and returns 0; returns -1 if flash doesn't exist
+ */
+int archflashreset(int, Flash*);
+
+/*
+ * enable/disable write protect
+ */
+void archflashwp(Flash*, int);
+
+/*
+ * flash access taking width and interleave into account
+ */
+int flashget(Flash*, ulong);
+void flashput(Flash*, ulong, int);
+
+/*
+ * Architecture specific routines for managing nand devices
+ */
+
+/*
+ * do any device spcific initialisation
+ */
+void archnand_init(Flash*);
+
+/*
+ * if claim is 1, claim device exclusively, and enable it (power it up)
+ * if claim is 0, release, and disable it (power it down)
+ * claiming may be as simple as a qlock per device
+ */
+void archnand_claim(Flash*, int claim);
+
+/*
+ * set command latch enable (CLE) and address latch enable (ALE)
+ * appropriately
+ */
+void archnand_setCLEandALE(Flash*, int cle, int ale);
+
+/*
+ * write a sequence of bytes to the device
+ */
+void archnand_write(Flash*, void *buf, int len);
+
+/*
+ * read a sequence of bytes from the device
+ * if buf is 0, throw away the data
+ */
+void archnand_read(Flash*, void *buf, int len);
diff --git a/os/port/flashintel b/os/port/flashintel
new file mode 100644
index 00000000..f0093acf
--- /dev/null
+++ b/os/port/flashintel
@@ -0,0 +1,179 @@
+
+enum {
+ DQ7 = I(0x80),
+ DQ6 = I(0x40),
+ DQ5 = I(0x20),
+ DQ4 = I(0x10),
+ DQ3 = I(0x08),
+ DQ2 = I(0x04),
+ DQ1 = I(0x02),
+ DQ0 = I(0x01),
+};
+
+/*
+ * intel algorithm
+ */
+
+enum {
+ ReadID = I(0x90),
+ ClearStatus = I(0x50),
+ ReadStatus = I(0x70),
+ Program = I(0x40),
+ BlockErase = I(0x20),
+ Confirm = I(0xD0),
+};
+
+#define DPRINT if(0)print
+#define EPRINT if(1)print
+
+static char*
+intelwait(Flash *f, void *p, ulong ticks)
+{
+ ulong csr, mask;
+
+ ticks += m->ticks+1;
+ mask = f->cmask;
+ for(;;){
+ if(f->width == 2)
+ csr = *(ushort*)p;
+ else
+ csr = *(ulong*)p;
+ if((csr & mask) == (DQ7 & mask))
+ break;
+ sched();
+ if(m->ticks >= ticks)
+ return "flash write timed out";
+ }
+ if(csr & (DQ5|DQ4|DQ3|DQ1)){
+ EPRINT("flash: error: %8.8lux %8.8lux\n", p, csr);
+ if(csr & DQ1)
+ return "flash block locked";
+ if(csr & DQ3)
+ return "low flash programming voltage";
+ return Eio;
+ }
+ return nil;
+}
+
+static int
+intelerase(Flash *f, Flashregion *r, ulong addr)
+{
+ int s;
+ char *e;
+
+ DPRINT("flash: erase zone %8.8lux\n", addr);
+ if(addr & (r->erasesize-1))
+ return -1; /* bad zone */
+ if(f->width == 2){
+ ushort *p = (ushort*)((ulong)f->addr + addr);
+ s = splhi();
+ *p = BlockErase & f->cmask;
+ *p = Confirm & f->cmask;
+ splx(s);
+ e = intelwait(f, p, MS2TK(8*1000));
+ *p = ClearStatus & f->cmask;
+ *p = ReadArray & f->cmask;
+ }else{
+ ulong *p = (ulong*)((ulong)f->addr + addr);
+ s = splhi();
+ *p = BlockErase & f->cmask;
+ *p = Confirm & f->cmask;
+ splx(s);
+ e = intelwait(f, p, MS2TK(8*1000));
+ *p = ClearStatus & f->cmask;
+ *p = ReadArray & f->cmask;
+ }
+ if(e != nil)
+ error(e);
+ return 0;
+}
+
+static int
+intelwrite2(Flash *f, ulong offset, void *buf, long n)
+{
+ ushort *a, *v;
+ ulong w;
+ int s;
+ char *e;
+
+ if(((ulong)f->addr|offset|n)&(f->width-1))
+ return -1;
+ a = (ushort*)((ulong)f->addr + offset);
+ n /= f->width;
+ v = buf;
+ if(waserror()){
+ *a = ClearStatus & f->cmask;
+ *a = ReadArray & f->cmask;
+ nexterror();
+ }
+ for(; --n >= 0; v++, a++){
+ w = *a;
+ DPRINT("flash: write %p %#ulx -> %#lux\n", a, w, (ulong)*v);
+ if(w == *v)
+ continue; /* already set */
+ if(~w & *v)
+ error("flash not erased");
+ s = splhi();
+ *a = Program & f->cmask; /* program */
+ *a = *v;
+ splx(s);
+ microdelay(8);
+ e = intelwait(f, a, 5);
+ *a = ClearStatus & f->cmask;
+ *a = ReadArray & f->cmask;
+ if(e != nil)
+ error(e);
+ w = *a;
+ if(w != *v){
+ EPRINT("flash: write %p %#8.8lux -> %#8.8lux failed\n", a, w, (ulong)*v);
+ error(Eio);
+ }
+ }
+ poperror();
+ return 0;
+}
+
+static int
+intelwrite4(Flash *f, ulong offset, void *buf, long n)
+{
+ ulong *a, *v;
+ ulong w;
+ int s;
+ char *e;
+
+ if(((ulong)f->addr|offset|n)&(f->width-1))
+ return -1;
+ a = (ulong*)((ulong)f->addr + offset);
+ n /= f->width;
+ v = buf;
+ if(waserror()){
+ *a = ClearStatus & f->cmask;
+ *a = ReadArray & f->cmask;
+ nexterror();
+ }
+ for(; --n >= 0; v++, a++){
+ w = *a;
+ DPRINT("flash: write %p %#ulx -> %#lux\n", a, w, (ulong)*v);
+ if(w == *v)
+ continue; /* already set */
+ if(~w & *v)
+ error("flash not erased");
+ s = splhi();
+ *a = Program; /* program */
+ *a = *v;
+ splx(s);
+ microdelay(8);
+ e = intelwait(f, a, 5);
+ *a = ClearStatus & f->cmask;
+ *a = ReadArray & f->cmask;
+ if(e != nil)
+ error(e);
+ w = *a;
+ if(w != *v){
+ EPRINT("flash: write %p %#8.8lux -> %#8.8lux failed\n", a, w, *v);
+ error(Eio);
+ }
+ }
+ poperror();
+ return 0;
+}
diff --git a/os/port/flashnand.c b/os/port/flashnand.c
new file mode 100644
index 00000000..e58c5a63
--- /dev/null
+++ b/os/port/flashnand.c
@@ -0,0 +1,337 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "flashif.h"
+
+typedef struct Nandtab Nandtab;
+
+struct Nandtab {
+ short manufacturer;
+ uchar id;
+ uchar l2bytesperpage;
+ ushort pagesperblock;
+ ushort blocks;
+ uchar tPROGms;
+ ushort tBERASEms;
+ uchar tRus;
+};
+
+static Nandtab nandtab[] = {
+ { 0xec, 0xe6, 9, 16, 1024, 1, 4, 7 }, /* Samsung KM29U64000T */
+
+ { 0x98, 0xe6, 9, 16, 1024, 1, 4, 25 }, /* Toshiba TC58V64AFT */
+ { 0x98, 0x73, 9, 32, 1024, 1, 10, 25}, /* Toshiba TC56V128AFT */
+ /* Generic entries which take timings from Toshiba SMIL example code */
+ { -1, 0xea, 8, 16, 512, 20, 400, 100 },
+ { -1, 0xe3, 9, 16, 512, 20, 400, 100 },
+ { -1, 0xe5, 9, 16, 512, 20, 400, 100 },
+ { -1, 0x73, 9, 32, 1024, 20, 400, 100 },
+ { -1, 0x75, 9, 32, 2048, 20, 400, 100 },
+ { -1, 0x76, 9, 32, 4096, 20, 400, 100 },
+};
+
+enum {
+ ReadMode1 = 0x00,
+ ReadMode2 = 0x01,
+ Program = 0x10,
+ ReadMode3 = 0x50,
+ Erase1 = 0x60,
+ ReadStatus = 0x70,
+ Write = 0x80,
+ Identify = 0x90,
+ Erase2 = 0xd0,
+
+ StatusReady = 0x40,
+ StatusFail = 0x01,
+};
+
+/*
+ * NAND flash driver
+ */
+
+#define DPRINT if(0)print
+#define EPRINT if(1)print
+
+static int idchip(Flash *f);
+
+static void
+nand_writebyte(Flash *f, uchar b)
+{
+ archnand_write(f, &b, 1);
+}
+
+static uchar
+nand_readbyte(Flash *f)
+{
+ uchar b;
+ archnand_read(f, &b, 1);
+ return b;
+}
+
+static int
+idchip(Flash *f)
+{
+ int x;
+ uchar maker, device;
+
+ f->id = 0;
+ f->devid = 0;
+ f->width = 1;
+ archnand_claim(f, 1);
+ archnand_setCLEandALE(f, 1, 0);
+ nand_writebyte(f, Identify);
+ archnand_setCLEandALE(f, 0, 1);
+ nand_writebyte(f, 0);
+ archnand_setCLEandALE(f, 0, 0);
+ maker = nand_readbyte(f);
+ device = nand_readbyte(f);
+ archnand_claim(f, 0);
+ iprint("man=%#ux device=%#ux\n", maker, device);
+ for(x = 0; x < sizeof(nandtab) / sizeof(nandtab[0]); x++){
+ if(nandtab[x].id == (device & 0xff)
+ && (nandtab[x].manufacturer == maker || nandtab[x].manufacturer == -1)){
+ ulong bpp;
+ f->id = maker;
+ f->devid = device;
+ f->nr = 1;
+ bpp = 1 << nandtab[x].l2bytesperpage;
+ bpp |= bpp >> 5;
+ f->regions[0].erasesize = bpp * nandtab[x].pagesperblock;
+ f->size = f->regions[0].erasesize * nandtab[x].blocks;
+ f->regions[0].n = nandtab[x].blocks;
+ f->regions[0].start = 0;
+ f->regions[0].end = f->size;
+ f->regions[0].pagesize = bpp;
+ f->data = &nandtab[x];
+ return 0;
+ }
+ }
+ print("nand: device %#.2ux/%#.2ux not recognised\n", maker, device);
+ return -1;
+}
+
+static int
+erasezone(Flash *f, Flashregion *r, ulong byteaddr)
+{
+ Nandtab *nt = f->data;
+ int paddress;
+ uchar val;
+ int rv;
+ uchar addr[2];
+
+ if(byteaddr%r->erasesize || byteaddr >= f->size)
+ return -1; /* bad zone */
+ paddress = byteaddr/r->erasesize * nt->pagesperblock; /* can simplify ... */
+//print("erasezone(%.8lux) page %d %.8lux\n", byteaddr, paddress, r->erasesize);
+ archnand_claim(f, 1);
+ archnand_setCLEandALE(f, 1, 0); // command mode
+ nand_writebyte(f, Erase1);
+ archnand_setCLEandALE(f, 0, 1); // address mode
+ addr[0] = paddress;
+ addr[1] = paddress >> 8;
+ archnand_write(f, addr, 2);
+ archnand_setCLEandALE(f, 1, 0); // command mode
+ nand_writebyte(f, Erase2);
+ nand_writebyte(f, ReadStatus);
+ archnand_setCLEandALE(f, 0, 0); // data mode
+
+ do {
+ val = nand_readbyte(f);
+ } while((val & StatusReady) != StatusReady);
+
+ if((val & StatusFail) != 0){
+ print("erasezone failed: %.2ux\n", val);
+ rv = -1;
+ }
+ else
+ rv = 0;
+ archnand_claim(f, 0); // turn off chip
+ return rv;
+}
+
+static int
+writepage(Flash *f, ulong page, ushort addr, void *buf, long n)
+{
+ uchar cmd;
+ uchar val;
+ int rv;
+ uchar cmdbuf[3];
+
+//print("writepage(%ld, %d, %ld)\n", page, addr, n);
+ // Fake a read to set the pointer
+ if(addr < 256)
+ cmd = ReadMode1;
+ else if(addr < 512){
+ cmd = ReadMode2;
+ addr -= 256;
+ }else{
+ cmd = ReadMode3;
+ addr -= 512;
+ }
+ archnand_claim(f, 1);
+ archnand_setCLEandALE(f, 1, 0); // command mode
+ nand_writebyte(f, cmd);
+ nand_writebyte(f, Write);
+ archnand_setCLEandALE(f, 0, 1); // address mode
+ cmdbuf[0] = addr;
+ cmdbuf[1] = page;
+ cmdbuf[2] = page >> 8;
+ archnand_write(f, cmdbuf, 3);
+ archnand_setCLEandALE(f, 0, 0); // data mode
+ archnand_write(f, buf, n);
+ archnand_setCLEandALE(f, 1, 0); // command mode
+ nand_writebyte(f, Program);
+ nand_writebyte(f, ReadStatus);
+ archnand_setCLEandALE(f, 0, 0); // data mode
+
+ do {
+ val = nand_readbyte(f);
+ }while((val & StatusReady) != StatusReady);
+
+ if((val & StatusFail) != 0){
+ print("writepage failed: %.2ux\n", val);
+ rv = -1;
+ }else
+ rv = 0;
+
+ archnand_claim(f, 0);
+ return rv;
+}
+
+static int
+write(Flash *f, ulong offset, void *buf, long n)
+{
+ Nandtab *nt = f->data;
+ ulong page;
+ ulong addr;
+ ulong xbpp;
+
+//print("write(%ld, %ld)\n", offset, n);
+
+ xbpp = (1 << nt->l2bytesperpage);
+ xbpp |= (xbpp >> 5);
+ page = offset / xbpp;
+ addr = offset % xbpp;
+
+ while(n > 0){
+ int count;
+ count = xbpp - addr;
+ if(count > n)
+ count = n;
+ if(writepage(f, page, addr, buf, count) < 0)
+ return -1;
+ offset += count;
+ n -= count;
+ buf = (uchar *)buf + count;
+ addr = 0;
+ }
+//print("write done\n");
+ return 0;
+}
+
+static int
+read(Flash *f, ulong offset, void *buf, long n)
+{
+ Nandtab *nt = f->data;
+ uchar cmd;
+ ulong page;
+ ulong addr;
+ ushort bytesperpage, xbytesperpage, skip, partialaddr;
+ uchar cmdbuf[3];
+ int toread;
+
+//print("read(%ld, %.8lux, %ld)\n", offset, buf, n);
+
+ bytesperpage = (1 << nt->l2bytesperpage);
+ xbytesperpage = bytesperpage;
+ xbytesperpage += bytesperpage >> 5; // 512 => 16, 256 => 8
+ page = offset / xbytesperpage;
+ partialaddr = offset % xbytesperpage;
+ skip = 0;
+ if(partialaddr >= bytesperpage && xbytesperpage - partialaddr < n){
+ // cannot start read in extended area, and then chain into main area,
+ // so start on last byte of main area, and skip the extra bytes
+ // stupid chip design this one
+ skip = partialaddr - bytesperpage + 1;
+ n += skip;
+ partialaddr = bytesperpage - 1;
+ }
+ addr = partialaddr;
+ if(addr >= bytesperpage){
+ cmd = ReadMode3;
+ addr -= bytesperpage;
+ }else if(addr >= 256){
+ cmd = ReadMode2;
+ addr -= 256;
+ }else
+ cmd = ReadMode1;
+
+//print("cmd %.2x page %.4lux addr %.8lux partialaddr %d skip %d\n", cmd, page, addr, partialaddr, skip);
+ // Read first page
+ archnand_claim(f, 1);
+ archnand_setCLEandALE(f, 1, 0);
+ nand_writebyte(f, cmd);
+ archnand_setCLEandALE(f, 0, 1);
+ cmdbuf[0] = addr;
+ cmdbuf[1] = page;
+ cmdbuf[2] = page >> 8;
+ archnand_write(f, cmdbuf, 3);
+ archnand_setCLEandALE(f, 0, 0);
+ if(partialaddr){
+ // partial first page
+ microdelay(nt->tRus);
+ toread = partialaddr < xbytesperpage ? xbytesperpage - partialaddr : 0;
+ if(toread > n)
+ toread = n;
+ if(skip){
+ archnand_read(f, 0, skip);
+ toread -= skip;
+ n -= skip;
+// partialaddr += skip;
+ }
+ archnand_read(f, buf, toread);
+ n -= toread;
+// partialaddr += toread;
+ buf = (uchar *)buf + toread;
+ }
+ while(n){
+ microdelay(nt->tRus);
+ toread = xbytesperpage;
+ if(n < toread)
+ toread = n;
+ archnand_read(f, buf, toread);
+ n -= toread;
+ buf = (uchar *)buf + toread;
+ }
+ archnand_claim(f, 0);
+//print("readn done\n");
+ return 0;
+}
+
+static int
+reset(Flash *f)
+{
+//iprint("nandreset\n");
+ if(f->data != nil)
+ return 1;
+ f->write = write;
+ f->read = read;
+ f->eraseall = nil;
+ f->erasezone = erasezone;
+ f->suspend = nil;
+ f->resume = nil;
+ f->sort = "nand";
+ archnand_init(f);
+ return idchip(f);
+}
+
+void
+flashnandlink(void)
+{
+ addflashcard("nand", reset);
+}
diff --git a/os/port/fpi.c b/os/port/fpi.c
new file mode 100644
index 00000000..fc6d4e80
--- /dev/null
+++ b/os/port/fpi.c
@@ -0,0 +1,304 @@
+/*
+ * Floating Point Interpreter.
+ * shamelessly stolen from an original by ark.
+ */
+#include "fpi.h"
+
+void
+fpiround(Internal *i)
+{
+ unsigned long guard;
+
+ guard = i->l & GuardMask;
+ i->l &= ~GuardMask;
+ if(guard > (LsBit>>1) || (guard == (LsBit>>1) && (i->l & LsBit))){
+ i->l += LsBit;
+ if(i->l & CarryBit){
+ i->l &= ~CarryBit;
+ i->h++;
+ if(i->h & CarryBit){
+ if (i->h & 0x01)
+ i->l |= CarryBit;
+ i->l >>= 1;
+ i->h >>= 1;
+ i->e++;
+ }
+ }
+ }
+}
+
+static void
+matchexponents(Internal *x, Internal *y)
+{
+ int count;
+
+ count = y->e - x->e;
+ x->e = y->e;
+ if(count >= 2*FractBits){
+ x->l = x->l || x->h;
+ x->h = 0;
+ return;
+ }
+ if(count >= FractBits){
+ count -= FractBits;
+ x->l = x->h|(x->l != 0);
+ x->h = 0;
+ }
+ while(count > 0){
+ count--;
+ if(x->h & 0x01)
+ x->l |= CarryBit;
+ if(x->l & 0x01)
+ x->l |= 2;
+ x->l >>= 1;
+ x->h >>= 1;
+ }
+}
+
+static void
+shift(Internal *i)
+{
+ i->e--;
+ i->h <<= 1;
+ i->l <<= 1;
+ if(i->l & CarryBit){
+ i->l &= ~CarryBit;
+ i->h |= 0x01;
+ }
+}
+
+static void
+normalise(Internal *i)
+{
+ while((i->h & HiddenBit) == 0)
+ shift(i);
+}
+
+static void
+renormalise(Internal *i)
+{
+ if(i->e < -2 * FractBits)
+ i->e = -2 * FractBits;
+ while(i->e < 1){
+ i->e++;
+ if(i->h & 0x01)
+ i->l |= CarryBit;
+ i->h >>= 1;
+ i->l = (i->l>>1)|(i->l & 0x01);
+ }
+ if(i->e >= ExpInfinity)
+ SetInfinity(i);
+}
+
+void
+fpinormalise(Internal *x)
+{
+ if(!IsWeird(x) && !IsZero(x))
+ normalise(x);
+}
+
+void
+fpiadd(Internal *x, Internal *y, Internal *i)
+{
+ Internal *t;
+
+ i->s = x->s;
+ if(IsWeird(x) || IsWeird(y)){
+ if(IsNaN(x) || IsNaN(y))
+ SetQNaN(i);
+ else
+ SetInfinity(i);
+ return;
+ }
+ if(x->e > y->e){
+ t = x;
+ x = y;
+ y = t;
+ }
+ matchexponents(x, y);
+ i->e = x->e;
+ i->h = x->h + y->h;
+ i->l = x->l + y->l;
+ if(i->l & CarryBit){
+ i->h++;
+ i->l &= ~CarryBit;
+ }
+ if(i->h & (HiddenBit<<1)){
+ if(i->h & 0x01)
+ i->l |= CarryBit;
+ i->l = (i->l>>1)|(i->l & 0x01);
+ i->h >>= 1;
+ i->e++;
+ }
+ if(IsWeird(i))
+ SetInfinity(i);
+}
+
+void
+fpisub(Internal *x, Internal *y, Internal *i)
+{
+ Internal *t;
+
+ if(y->e < x->e
+ || (y->e == x->e && (y->h < x->h || (y->h == x->h && y->l < x->l)))){
+ t = x;
+ x = y;
+ y = t;
+ }
+ i->s = y->s;
+ if(IsNaN(y)){
+ SetQNaN(i);
+ return;
+ }
+ if(IsInfinity(y)){
+ if(IsInfinity(x))
+ SetQNaN(i);
+ else
+ SetInfinity(i);
+ return;
+ }
+ matchexponents(x, y);
+ i->e = y->e;
+ i->h = y->h - x->h;
+ i->l = y->l - x->l;
+ if(i->l < 0){
+ i->l += CarryBit;
+ i->h--;
+ }
+ if(i->h == 0 && i->l == 0)
+ SetZero(i);
+ else while(i->e > 1 && (i->h & HiddenBit) == 0)
+ shift(i);
+}
+
+#define CHUNK (FractBits/2)
+#define CMASK ((1<<CHUNK)-1)
+#define HI(x) ((short)((x)>>CHUNK) & CMASK)
+#define LO(x) ((short)(x) & CMASK)
+#define SPILL(x) ((x)>>CHUNK)
+#define M(x, y) ((long)a[x]*(long)b[y])
+#define C(h, l) (((long)((h) & CMASK)<<CHUNK)|((l) & CMASK))
+
+void
+fpimul(Internal *x, Internal *y, Internal *i)
+{
+ long a[4], b[4], c[7], f[4];
+
+ i->s = x->s^y->s;
+ if(IsWeird(x) || IsWeird(y)){
+ if(IsNaN(x) || IsNaN(y) || IsZero(x) || IsZero(y))
+ SetQNaN(i);
+ else
+ SetInfinity(i);
+ return;
+ }
+ else if(IsZero(x) || IsZero(y)){
+ SetZero(i);
+ return;
+ }
+ normalise(x);
+ normalise(y);
+ i->e = x->e + y->e - (ExpBias - 1);
+
+ a[0] = HI(x->h); b[0] = HI(y->h);
+ a[1] = LO(x->h); b[1] = LO(y->h);
+ a[2] = HI(x->l); b[2] = HI(y->l);
+ a[3] = LO(x->l); b[3] = LO(y->l);
+
+ c[6] = M(3, 3);
+ c[5] = M(2, 3) + M(3, 2) + SPILL(c[6]);
+ c[4] = M(1, 3) + M(2, 2) + M(3, 1) + SPILL(c[5]);
+ c[3] = M(0, 3) + M(1, 2) + M(2, 1) + M(3, 0) + SPILL(c[4]);
+ c[2] = M(0, 2) + M(1, 1) + M(2, 0) + SPILL(c[3]);
+ c[1] = M(0, 1) + M(1, 0) + SPILL(c[2]);
+ c[0] = M(0, 0) + SPILL(c[1]);
+
+ f[0] = c[0];
+ f[1] = C(c[1], c[2]);
+ f[2] = C(c[3], c[4]);
+ f[3] = C(c[5], c[6]);
+
+ if((f[0] & HiddenBit) == 0){
+ f[0] <<= 1;
+ f[1] <<= 1;
+ f[2] <<= 1;
+ f[3] <<= 1;
+ if(f[1] & CarryBit){
+ f[0] |= 1;
+ f[1] &= ~CarryBit;
+ }
+ if(f[2] & CarryBit){
+ f[1] |= 1;
+ f[2] &= ~CarryBit;
+ }
+ if(f[3] & CarryBit){
+ f[2] |= 1;
+ f[3] &= ~CarryBit;
+ }
+ i->e--;
+ }
+ i->h = f[0];
+ i->l = f[1];
+ if(f[2] || f[3])
+ i->l |= 1;
+ renormalise(i);
+}
+
+void
+fpidiv(Internal *x, Internal *y, Internal *i)
+{
+ i->s = x->s^y->s;
+ if(IsNaN(x) || IsNaN(y)
+ || (IsInfinity(x) && IsInfinity(y)) || (IsZero(x) && IsZero(y))){
+ SetQNaN(i);
+ return;
+ }
+ else if(IsZero(x) || IsInfinity(y)){
+ SetInfinity(i);
+ return;
+ }
+ else if(IsInfinity(x) || IsZero(y)){
+ SetZero(i);
+ return;
+ }
+ normalise(x);
+ normalise(y);
+ i->h = 0;
+ i->l = 0;
+ i->e = y->e - x->e + (ExpBias + 2*FractBits - 1);
+ do{
+ if(y->h > x->h || (y->h == x->h && y->l >= x->l)){
+ i->l |= 0x01;
+ y->h -= x->h;
+ y->l -= x->l;
+ if(y->l < 0){
+ y->l += CarryBit;
+ y->h--;
+ }
+ }
+ shift(y);
+ shift(i);
+ }while ((i->h & HiddenBit) == 0);
+/*
+ if(y->h > x->h || (y->h == x->h && y->l >= x->l))
+ i->l |= 0x01;
+*/
+ if(y->h || y->l)
+ i->l |= 0x01;
+ renormalise(i);
+}
+
+int
+fpicmp(Internal *x, Internal *y)
+{
+ if(IsNaN(x) && IsNaN(y))
+ return 0;
+ if(IsInfinity(x) && IsInfinity(y))
+ return y->s - x->s;
+ if(x->e == y->e && x->h == y->h && x->l == y->l)
+ return y->s - x->s;
+ if(x->e < y->e
+ || (x->e == y->e && (x->h < y->h || (x->h == y->h && x->l < y->l))))
+ return y->s ? 1: -1;
+ return x->s ? -1: 1;
+}
diff --git a/os/port/fpimem.c b/os/port/fpimem.c
new file mode 100644
index 00000000..4799d0a5
--- /dev/null
+++ b/os/port/fpimem.c
@@ -0,0 +1,136 @@
+#include "fpi.h"
+
+/*
+ * the following routines depend on memory format, not the machine
+ */
+
+void
+fpis2i(Internal *i, void *v)
+{
+ Single *s = v;
+
+ i->s = (*s & 0x80000000) ? 1: 0;
+ if((*s & ~0x80000000) == 0){
+ SetZero(i);
+ return;
+ }
+ i->e = ((*s>>23) & 0x00FF) - SingleExpBias + ExpBias;
+ i->h = (*s & 0x007FFFFF)<<(1+NGuardBits);
+ i->l = 0;
+ if(i->e)
+ i->h |= HiddenBit;
+ else
+ i->e++;
+}
+
+void
+fpid2i(Internal *i, void *v)
+{
+ Double *d = v;
+
+ i->s = (d->h & 0x80000000) ? 1: 0;
+ i->e = (d->h>>20) & 0x07FF;
+ i->h = ((d->h & 0x000FFFFF)<<(4+NGuardBits))|((d->l>>25) & 0x7F);
+ i->l = (d->l & 0x01FFFFFF)<<NGuardBits;
+ if(i->e)
+ i->h |= HiddenBit;
+ else
+ i->e++;
+}
+
+void
+fpiw2i(Internal *i, void *v)
+{
+ Word w, word = *(Word*)v;
+ int e;
+
+ if(word < 0){
+ i->s = 1;
+ word = -word;
+ }
+ else
+ i->s = 0;
+ if(word == 0){
+ SetZero(i);
+ return;
+ }
+ if(word > 0){
+ for (e = 0, w = word; w; w >>= 1, e++)
+ ;
+ } else
+ e = 32;
+ if(e > FractBits){
+ i->h = word>>(e - FractBits);
+ i->l = (word & ((1<<(e - FractBits)) - 1))<<(2*FractBits - e);
+ }
+ else {
+ i->h = word<<(FractBits - e);
+ i->l = 0;
+ }
+ i->e = (e - 1) + ExpBias;
+}
+
+void
+fpii2s(void *v, Internal *i)
+{
+ int e;
+ Single *s = (Single*)v;
+
+ fpiround(i);
+ if(i->h & HiddenBit)
+ i->h &= ~HiddenBit;
+ else
+ i->e--;
+ *s = i->s ? 0x80000000: 0;
+ e = i->e;
+ if(e < ExpBias){
+ if(e <= (ExpBias - SingleExpBias))
+ return;
+ e = SingleExpBias - (ExpBias - e);
+ }
+ else if(e >= (ExpBias + (SingleExpMax-SingleExpBias))){
+ *s |= SingleExpMax<<23;
+ return;
+ }
+ else
+ e = SingleExpBias + (e - ExpBias);
+ *s |= (e<<23)|(i->h>>(1+NGuardBits));
+}
+
+void
+fpii2d(void *v, Internal *i)
+{
+ Double *d = (Double*)v;
+
+ fpiround(i);
+ if(i->h & HiddenBit)
+ i->h &= ~HiddenBit;
+ else
+ i->e--;
+ i->l = ((i->h & GuardMask)<<25)|(i->l>>NGuardBits);
+ i->h >>= NGuardBits;
+ d->h = i->s ? 0x80000000: 0;
+ d->h |= (i->e<<20)|((i->h & 0x00FFFFFF)>>4);
+ d->l = (i->h<<28)|i->l;
+}
+
+void
+fpii2w(Word *word, Internal *i)
+{
+ Word w;
+ int e;
+
+ fpiround(i);
+ e = (i->e - ExpBias) + 1;
+ if(e <= 0)
+ w = 0;
+ else if(e > 31)
+ w = 0x7FFFFFFF;
+ else if(e > FractBits)
+ w = (i->h<<(e - FractBits))|(i->l>>(2*FractBits - e));
+ else
+ w = i->h>>(FractBits-e);
+ if(i->s)
+ w = -w;
+ *word = w;
+}
diff --git a/os/port/inferno.c b/os/port/inferno.c
new file mode 100644
index 00000000..de9ea600
--- /dev/null
+++ b/os/port/inferno.c
@@ -0,0 +1,1030 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "isa.h"
+#include "interp.h"
+#include "runt.h"
+#include "kernel.h"
+
+/*
+ * here because Sys_FileIO is not public
+ */
+extern int srvf2c(char*, char*, Sys_FileIO*);
+
+/*
+ * System types connected to gc
+ */
+uchar FDmap[] = Sys_FD_map;
+uchar FileIOmap[] = Sys_FileIO_map;
+void freeFD(Heap*, int);
+void freeFileIO(Heap*, int);
+Type* TFD;
+Type* TFileIO;
+
+static uchar rmap[] = Sys_FileIO_read_map;
+static uchar wmap[] = Sys_FileIO_write_map;
+static Type* FioTread;
+static Type* FioTwrite;
+static uchar dmap[] = Sys_Dir_map;
+static Type* Tdir;
+
+typedef struct FD FD;
+struct FD
+{
+ Sys_FD fd;
+ Fgrp* grp;
+};
+
+void
+sysinit(void)
+{
+ TFD = dtype(freeFD, sizeof(FD), FDmap, sizeof(FDmap));
+ TFileIO = dtype(freeFileIO, Sys_FileIO_size, FileIOmap, sizeof(FileIOmap));
+
+ /* Support for devsrv.c */
+ FioTread = dtype(freeheap, Sys_FileIO_read_size, rmap, sizeof(rmap));
+ FioTwrite = dtype(freeheap, Sys_FileIO_write_size, wmap, sizeof(wmap));
+
+ /* Support for dirread */
+ Tdir = dtype(freeheap, Sys_Dir_size, dmap, sizeof(dmap));
+}
+
+void
+freeFD(Heap *h, int swept)
+{
+ FD *handle;
+
+ USED(swept);
+
+ handle = H2D(FD*, h);
+
+ release();
+ kfgrpclose(handle->grp, handle->fd.fd);
+ closefgrp(handle->grp);
+ acquire();
+}
+
+void
+freeFileIO(Heap *h, int swept)
+{
+ Sys_FileIO *fio;
+
+ if(swept)
+ return;
+
+ fio = H2D(Sys_FileIO*, h);
+ destroy(fio->read);
+ destroy(fio->write);
+}
+
+Sys_FD*
+mkfd(int fd)
+{
+ Heap *h;
+ Fgrp *fg;
+ FD *handle;
+
+ h = heap(TFD);
+ handle = H2D(FD*, h);
+ handle->fd.fd = fd;
+ fg = up->env->fgrp;
+ handle->grp = fg;
+ incref(fg);
+ return (Sys_FD*)handle;
+}
+#define fdchk(x) ((x) == (Sys_FD*)H ? -1 : (x)->fd)
+
+void
+seterror(char *err, ...)
+{
+ char *estr;
+ va_list arg;
+
+ estr = up->env->errstr;
+ va_start(arg, err);
+ vseprint(estr, estr+ERRMAX, err, arg);
+ va_end(arg);
+}
+
+char*
+syserr(char *s, char *es, Prog *p)
+{
+ Osenv *o;
+
+ o = p->osenv;
+ kstrcpy(s, o->errstr, es - s);
+ return s + strlen(s);
+}
+
+void
+Sys_millisec(void *fp)
+{
+ F_Sys_millisec *f;
+
+ f = fp;
+ *f->ret = TK2MS(MACHP(0)->ticks);
+}
+
+void
+Sys_open(void *fp)
+{
+ int fd;
+ F_Sys_open *f;
+
+ f = fp;
+ destroy(*f->ret);
+ *f->ret = H;
+ release();
+ fd = kopen(string2c(f->s), f->mode);
+ acquire();
+ if(fd == -1)
+ return;
+
+ *f->ret = mkfd(fd);
+}
+
+void
+Sys_pipe(void *fp)
+{
+ Array *a;
+ int fd[2];
+ Sys_FD **sfd;
+ F_Sys_pipe *f;
+
+ f = fp;
+ *f->ret = -1;
+
+ a = f->fds;
+ if(a->len < 2)
+ return;
+ if(kpipe(fd) < 0)
+ return;
+
+ sfd = (Sys_FD**)a->data;
+ destroy(sfd[0]);
+ destroy(sfd[1]);
+ sfd[0] = H;
+ sfd[1] = H;
+ sfd[0] = mkfd(fd[0]);
+ sfd[1] = mkfd(fd[1]);
+ *f->ret = 0;
+}
+
+void
+Sys_fildes(void *fp)
+{
+ F_Sys_fildes *f;
+ int fd;
+
+ f = fp;
+ destroy(*f->ret);
+ *f->ret = H;
+ release();
+ fd = kdup(f->fd, -1);
+ acquire();
+ if(fd == -1)
+ return;
+ *f->ret = mkfd(fd);
+}
+
+void
+Sys_dup(void *fp)
+{
+ F_Sys_dup *f;
+
+ f = fp;
+ release();
+ *f->ret = kdup(f->old, f->new);
+ acquire();
+}
+
+void
+Sys_create(void *fp)
+{
+ int fd;
+ F_Sys_create *f;
+
+ f = fp;
+ destroy(*f->ret);
+ *f->ret = H;
+ release();
+ fd = kcreate(string2c(f->s), f->mode, f->perm);
+ acquire();
+ if(fd == -1)
+ return;
+
+ *f->ret = mkfd(fd);
+}
+
+void
+Sys_remove(void *fp)
+{
+ F_Sys_remove *f;
+
+ f = fp;
+ release();
+ *f->ret = kremove(string2c(f->s));
+ acquire();
+}
+
+void
+Sys_seek(void *fp)
+{
+ F_Sys_seek *f;
+
+ f = fp;
+ release();
+ *f->ret = kseek(fdchk(f->fd), f->off, f->start);
+ acquire();
+}
+
+void
+Sys_unmount(void *fp)
+{
+ F_Sys_unmount *f;
+
+ f = fp;
+ release();
+ *f->ret = kunmount(string2c(f->s1), string2c(f->s2));
+ acquire();
+}
+
+void
+Sys_read(void *fp)
+{
+ int n;
+ F_Sys_read *f;
+
+ f = fp;
+ n = f->n;
+ if(f->buf == (Array*)H || n < 0) {
+ *f->ret = 0;
+ return;
+ }
+ if(n > f->buf->len)
+ n = f->buf->len;
+
+ release();
+ *f->ret = kread(fdchk(f->fd), f->buf->data, n);
+ acquire();
+}
+
+void
+Sys_pread(void *fp)
+{
+ int n;
+ F_Sys_pread *f;
+
+ f = fp;
+ n = f->n;
+ if(f->buf == (Array*)H || n < 0) {
+ *f->ret = 0;
+ return;
+ }
+ if(n > f->buf->len)
+ n = f->buf->len;
+
+ release();
+ *f->ret = kpread(fdchk(f->fd), f->buf->data, n, f->off);
+ acquire();
+}
+
+void
+Sys_chdir(void *fp)
+{
+ F_Sys_chdir *f;
+
+ f = fp;
+ release();
+ *f->ret = kchdir(string2c(f->path));
+ acquire();
+}
+
+void
+Sys_write(void *fp)
+{
+ int n;
+ F_Sys_write *f;
+
+ f = fp;
+ n = f->n;
+ if(f->buf == (Array*)H || n < 0) {
+ *f->ret = 0;
+ return;
+ }
+ if(n > f->buf->len)
+ n = f->buf->len;
+
+ release();
+ *f->ret = kwrite(fdchk(f->fd), f->buf->data, n);
+ acquire();
+}
+
+void
+Sys_pwrite(void *fp)
+{
+ int n;
+ F_Sys_pwrite *f;
+
+ f = fp;
+ n = f->n;
+ if(f->buf == (Array*)H || n < 0) {
+ *f->ret = 0;
+ return;
+ }
+ if(n > f->buf->len)
+ n = f->buf->len;
+
+ release();
+ *f->ret = kpwrite(fdchk(f->fd), f->buf->data, n, f->off);
+ acquire();
+}
+
+static void
+unpackdir(Dir *d, Sys_Dir *sd)
+{
+ retstr(d->name, &sd->name);
+ retstr(d->uid, &sd->uid);
+ retstr(d->gid, &sd->gid);
+ retstr(d->muid, &sd->muid);
+ sd->qid.path = d->qid.path;
+ sd->qid.vers = d->qid.vers;
+ sd->qid.qtype = d->qid.type;
+ sd->mode = d->mode;
+ sd->atime = d->atime;
+ sd->mtime = d->mtime;
+ sd->length = d->length;
+ sd->dtype = d->type;
+ sd->dev = d->dev;
+}
+
+static Dir*
+packdir(Sys_Dir *sd)
+{
+ char *nm[4], *p;
+ int i, n;
+ Dir *d;
+
+ nm[0] = string2c(sd->name);
+ nm[1] = string2c(sd->uid);
+ nm[2] = string2c(sd->gid);
+ nm[3] = string2c(sd->muid);
+ n = 0;
+ for(i=0; i<4; i++)
+ n += strlen(nm[i])+1;
+ d = smalloc(sizeof(*d)+n);
+ p = (char*)d+sizeof(*d);
+ for(i=0; i<4; i++){
+ n = strlen(nm[i])+1;
+ memmove(p, nm[i], n);
+ p += n;
+ }
+ d->name = nm[0];
+ d->uid = nm[1];
+ d->gid = nm[2];
+ d->muid = nm[3];
+ d->qid.path = sd->qid.path;
+ d->qid.vers = sd->qid.vers;
+ d->qid.type = sd->qid.qtype;
+ d->mode = sd->mode;
+ d->atime = sd->atime;
+ d->mtime = sd->mtime;
+ d->length = sd->length;
+ d->type = sd->dtype;
+ d->dev = sd->dev;
+ return d;
+}
+
+void
+Sys_fstat(void *fp)
+{
+ Dir *d;
+ F_Sys_fstat *f;
+
+ f = fp;
+ f->ret->t0 = -1;
+ release();
+ d = kdirfstat(fdchk(f->fd));
+ acquire();
+ if(d == nil)
+ return;
+ if(waserror() == 0){
+ unpackdir(d, &f->ret->t1);
+ f->ret->t0 = 0;
+ poperror();
+ }
+ free(d);
+}
+
+void
+Sys_stat(void *fp)
+{
+ Dir *d;
+ F_Sys_stat *f;
+
+ f = fp;
+ f->ret->t0 = -1;
+ release();
+ d = kdirstat(string2c(f->s));
+ acquire();
+ if(d == nil)
+ return;
+ if(waserror() == 0){
+ unpackdir(d, &f->ret->t1);
+ f->ret->t0 = 0;
+ poperror();
+ }
+ free(d);
+}
+
+void
+Sys_fd2path(void *fp)
+{
+ F_Sys_fd2path *f;
+ char *s;
+ void *r;
+
+ f = fp;
+ r = *f->ret;
+ *f->ret = H;
+ destroy(r);
+ release();
+ s = kfd2path(fdchk(f->fd));
+ acquire();
+ if(waserror() == 0){
+ retstr(s, f->ret);
+ poperror();
+ }
+ free(s);
+}
+
+void
+Sys_mount(void *fp)
+{
+ F_Sys_mount *f;
+
+ f = fp;
+ release();
+ *f->ret = kmount(fdchk(f->fd), fdchk(f->afd), string2c(f->on), f->flags, string2c(f->spec));
+ acquire();
+}
+
+void
+Sys_bind(void *fp)
+{
+ F_Sys_bind *f;
+
+ f = fp;
+ release();
+ *f->ret = kbind(string2c(f->s), string2c(f->on), f->flags);
+ acquire();
+}
+
+void
+Sys_wstat(void *fp)
+{
+ Dir *d;
+ F_Sys_wstat *f;
+
+ f = fp;
+ d = packdir(&f->d);
+ release();
+ *f->ret = kdirwstat(string2c(f->s), d);
+ acquire();
+ free(d);
+}
+
+void
+Sys_fwstat(void *fp)
+{
+ Dir *d;
+ F_Sys_fwstat *f;
+
+ f = fp;
+ d = packdir(&f->d);
+ release();
+ *f->ret = kdirfwstat(fdchk(f->fd), d);
+ acquire();
+ free(d);
+}
+
+void
+Sys_print(void *fp)
+{
+ int n;
+ Prog *p;
+ Chan *c;
+ char buf[1024], *b = buf;
+ F_Sys_print *f;
+ f = fp;
+ c = up->env->fgrp->fd[1];
+ if(c == nil)
+ return;
+ p = currun();
+
+ release();
+ n = xprint(p, f, &f->vargs, f->s, buf, sizeof(buf));
+ if (n >= sizeof(buf)-UTFmax-2)
+ n = bigxprint(p, f, &f->vargs, f->s, &b, sizeof(buf));
+ *f->ret = kwrite(1, b, n);
+ if (b != buf)
+ free(b);
+ acquire();
+}
+
+void
+Sys_fprint(void *fp)
+{
+ int n;
+ Prog *p;
+ char buf[1024], *b = buf;
+ F_Sys_fprint *f;
+
+ f = fp;
+ p = currun();
+ release();
+ n = xprint(p, f, &f->vargs, f->s, buf, sizeof(buf));
+ if (n >= sizeof(buf)-UTFmax-2)
+ n = bigxprint(p, f, &f->vargs, f->s, &b, sizeof(buf));
+ *f->ret = kwrite(fdchk(f->fd), b, n);
+ if (b != buf)
+ free(b);
+ acquire();
+}
+
+void
+Sys_werrstr(void *fp)
+{
+ F_Sys_werrstr *f;
+
+ f = fp;
+ *f->ret = 0;
+ kstrcpy(up->env->errstr, string2c(f->s), ERRMAX);
+}
+
+void
+Sys_dial(void *fp)
+{
+ int cfd;
+ char dir[NETPATHLEN], *a, *l;
+ F_Sys_dial *f;
+
+ f = fp;
+ a = string2c(f->addr);
+ l = string2c(f->local);
+ release();
+ f->ret->t0 = kdial(a, l, dir, &cfd);
+ acquire();
+ destroy(f->ret->t1.dfd);
+ f->ret->t1.dfd = H;
+ destroy(f->ret->t1.cfd);
+ f->ret->t1.cfd = H;
+ if(f->ret->t0 == -1)
+ return;
+
+ f->ret->t1.dfd = mkfd(f->ret->t0);
+ f->ret->t1.cfd = mkfd(cfd);
+ retstr(dir, &f->ret->t1.dir);
+}
+
+void
+Sys_announce(void *fp)
+{
+ char dir[NETPATHLEN], *a;
+ F_Sys_announce *f;
+
+ f = fp;
+ a = string2c(f->addr);
+ release();
+ f->ret->t0 = kannounce(a, dir);
+ acquire();
+ destroy(f->ret->t1.dfd);
+ f->ret->t1.dfd = H;
+ destroy(f->ret->t1.cfd);
+ f->ret->t1.cfd = H;
+ if(f->ret->t0 == -1)
+ return;
+
+ f->ret->t1.cfd = mkfd(f->ret->t0);
+ retstr(dir, &f->ret->t1.dir);
+}
+
+void
+Sys_listen(void *fp)
+{
+ F_Sys_listen *f;
+ char dir[NETPATHLEN], *d;
+
+ f = fp;
+ d = string2c(f->c.dir);
+ release();
+ f->ret->t0 = klisten(d, dir);
+ acquire();
+
+ destroy(f->ret->t1.dfd);
+ f->ret->t1.dfd = H;
+ destroy(f->ret->t1.cfd);
+ f->ret->t1.cfd = H;
+ if(f->ret->t0 == -1)
+ return;
+
+ f->ret->t1.cfd = mkfd(f->ret->t0);
+ retstr(dir, &f->ret->t1.dir);
+}
+
+void
+Sys_sleep(void *fp)
+{
+ F_Sys_sleep *f;
+
+ f = fp;
+ release();
+ if(f->period > 0){
+ if(waserror()){
+ acquire();
+ error("");
+ }
+ tsleep(&up->sleep, return0, 0, f->period);
+ poperror();
+ }
+ acquire();
+ *f->ret = 0;
+}
+
+void
+Sys_stream(void *fp)
+{
+ Prog *p;
+ uchar *buf;
+ int src, dst;
+ F_Sys_stream *f;
+ int nbytes, t, n;
+
+ f = fp;
+ buf = malloc(f->bufsiz);
+ if(buf == nil) {
+ kwerrstr(Enomem);
+ *f->ret = -1;
+ return;
+ }
+
+ src = fdchk(f->src);
+ dst = fdchk(f->dst);
+
+ p = currun();
+
+ release();
+ t = 0;
+ nbytes = 0;
+ while(p->kill == nil) {
+ n = kread(src, buf+t, f->bufsiz-t);
+ if(n <= 0)
+ break;
+ t += n;
+ if(t >= f->bufsiz) {
+ if(kwrite(dst, buf, t) != t) {
+ t = 0;
+ break;
+ }
+
+ nbytes += t;
+ t = 0;
+ }
+ }
+ if(t != 0) {
+ kwrite(dst, buf, t);
+ nbytes += t;
+ }
+ acquire();
+ free(buf);
+ *f->ret = nbytes;
+}
+
+void
+Sys_export(void *fp)
+{
+ F_Sys_export *f;
+
+ f = fp;
+ release();
+ *f->ret = export(fdchk(f->c), string2c(f->dir), f->flag&Sys_EXPASYNC);
+ acquire();
+}
+
+void
+Sys_file2chan(void *fp)
+{
+ int r;
+ Heap *h;
+ Channel *c;
+ Sys_FileIO *fio;
+ F_Sys_file2chan *f;
+ void *sv;
+
+ h = heap(TFileIO);
+
+ fio = H2D(Sys_FileIO*, h);
+
+ c = cnewc(FioTread, movtmp, 16);
+ fio->read = c;
+
+ c = cnewc(FioTwrite, movtmp, 16);
+ fio->write = c;
+
+ f = fp;
+ sv = *f->ret;
+ *f->ret = fio;
+ destroy(sv);
+
+ release();
+ r = srvf2c(string2c(f->dir), string2c(f->file), fio);
+ acquire();
+ if(r == -1) {
+ *f->ret = H;
+ destroy(fio);
+ }
+}
+
+enum
+{
+ /* the following pctl calls can block and must release the virtual machine */
+ BlockingPctl= Sys_NEWFD|Sys_FORKFD|Sys_NEWNS|Sys_FORKNS|Sys_NEWENV|Sys_FORKENV
+};
+
+void
+Sys_pctl(void *fp)
+{
+ int fd;
+ Prog *p;
+ List *l;
+ Chan *c;
+ volatile struct {Pgrp *np;} np;
+ Pgrp *opg;
+ Chan *dot;
+ Osenv *o;
+ F_Sys_pctl *f;
+ Fgrp *fg, *ofg, *nfg;
+ volatile struct {Egrp *ne;} ne;
+ Egrp *oe;
+
+ f = fp;
+
+ p = currun();
+ if(f->flags & BlockingPctl)
+ release();
+
+ np.np = nil;
+ ne.ne = nil;
+ if(waserror()) {
+ closepgrp(np.np);
+ closeegrp(ne.ne);
+ if(f->flags & BlockingPctl)
+ acquire();
+ *f->ret = -1;
+ return;
+ }
+
+ o = p->osenv;
+ if(f->flags & Sys_NEWFD) {
+ ofg = o->fgrp;
+ nfg = newfgrp(ofg);
+ lock(ofg);
+ /* file descriptors to preserve */
+ for(l = f->movefd; l != H; l = l->tail) {
+ fd = *(int*)l->data;
+ if(fd >= 0 && fd <= ofg->maxfd) {
+ c = ofg->fd[fd];
+ if(c != nil && fd < nfg->nfd && nfg->fd[fd] == nil) {
+ incref(c);
+ nfg->fd[fd] = c;
+ if(nfg->maxfd < fd)
+ nfg->maxfd = fd;
+ }
+ }
+ }
+ unlock(ofg);
+ o->fgrp = nfg;
+ closefgrp(ofg);
+ }
+ else
+ if(f->flags & Sys_FORKFD) {
+ ofg = o->fgrp;
+ fg = dupfgrp(ofg);
+ /* file descriptors to close */
+ for(l = f->movefd; l != H; l = l->tail)
+ kclose(*(int*)l->data);
+ o->fgrp = fg;
+ closefgrp(ofg);
+ }
+
+ if(f->flags & Sys_NEWNS) {
+ np.np = newpgrp();
+ dot = o->pgrp->dot;
+ np.np->dot = cclone(dot);
+ np.np->slash = cclone(dot);
+ cnameclose(np.np->slash->name);
+ np.np->slash->name = newcname("/");
+ np.np->pin = o->pgrp->pin; /* pin is ALWAYS inherited */
+ np.np->nodevs = o->pgrp->nodevs;
+ opg = o->pgrp;
+ o->pgrp = np.np;
+ np.np = nil;
+ closepgrp(opg);
+ }
+ else
+ if(f->flags & Sys_FORKNS) {
+ np.np = newpgrp();
+ pgrpcpy(np.np, o->pgrp);
+ opg = o->pgrp;
+ o->pgrp = np.np;
+ np.np = nil;
+ closepgrp(opg);
+ }
+
+ if(f->flags & Sys_NEWENV) {
+ oe = o->egrp;
+ o->egrp = newegrp();
+ closeegrp(oe);
+ }
+ else
+ if(f->flags & Sys_FORKENV) {
+ ne.ne = newegrp();
+ egrpcpy(ne.ne, o->egrp);
+ oe = o->egrp;
+ o->egrp = ne.ne;
+ ne.ne = nil;
+ closeegrp(oe);
+ }
+
+ if(f->flags & Sys_NEWPGRP)
+ newgrp(p);
+
+ if(f->flags & Sys_NODEVS)
+ o->pgrp->nodevs = 1;
+
+ poperror();
+
+ if(f->flags & BlockingPctl)
+ acquire();
+
+ *f->ret = p->pid;
+}
+
+void
+Sys_dirread(void *fp)
+{
+
+ Dir *b;
+ int i, n;
+ Heap *h;
+ uchar *d;
+ void *r;
+ F_Sys_dirread *f;
+
+ f = fp;
+ f->ret->t0 = -1;
+ r = f->ret->t1;
+ f->ret->t1 = H;
+ destroy(r);
+ release();
+ n = kdirread(fdchk(f->fd), &b);
+ acquire();
+ if(n <= 0) {
+ f->ret->t0 = n;
+ free(b);
+ return;
+ }
+ if(waserror()){
+ free(b);
+ return;
+ }
+ h = heaparray(Tdir, n);
+ poperror();
+ d = H2D(Array*, h)->data;
+ for(i = 0; i < n; i++) {
+ unpackdir(b+i, (Sys_Dir*)d);
+ d += Sys_Dir_size;
+ }
+ f->ret->t0 = n;
+ f->ret->t1 = H2D(Array*, h);
+ free(b);
+}
+
+void
+Sys_fauth(void *fp)
+{
+
+ int fd;
+ F_Sys_fauth *f;
+ void *r;
+
+ f = fp;
+ r = *f->ret;
+ *f->ret = H;
+ destroy(r);
+ release();
+ fd = kfauth(fdchk(f->fd), string2c(f->aname));
+ acquire();
+ if(fd >= 0)
+ *f->ret = mkfd(fd);
+}
+
+void
+Sys_fversion(void *fp)
+{
+ void *r;
+ F_Sys_fversion *f;
+ int n;
+ char buf[20], *s;
+
+ f = fp;
+ f->ret->t0 = -1;
+ r = f->ret->t1;
+ f->ret->t1 = H;
+ destroy(r);
+ s = string2c(f->version);
+ n = strlen(s);
+ if(n >= sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, s, n);
+ buf[n] = 0;
+ release();
+ n = kfversion(fdchk(f->fd), f->msize, buf, sizeof(buf));
+ acquire();
+ if(n >= 0){
+ f->ret->t0 = f->msize;
+ retnstr(buf, n, &f->ret->t1);
+ }
+}
+
+void
+Sys_iounit(void *fp)
+{
+ F_Sys_iounit *f;
+
+ f = fp;
+ release();
+ *f->ret = kiounit(fdchk(f->fd));
+ acquire();
+}
+
+void
+ccom(Progq **cl, Prog *p)
+{
+ volatile struct {Progq **cl;} vcl;
+
+ cqadd(cl, p);
+ vcl.cl = cl;
+ if(waserror()) {
+ if(p->ptr != nil) { /* no killcomm */
+ cqdelp(vcl.cl, p);
+ p->ptr = nil;
+ }
+ nexterror();
+ }
+ cblock(p);
+ poperror();
+}
+
+void
+crecv(Channel *c, void *ip)
+{
+ Prog *p;
+ REG rsav;
+
+ if(c->send->prog == nil && c->size == 0) {
+ p = currun();
+ p->ptr = ip;
+ ccom(&c->recv, p);
+ return;
+ }
+
+ rsav = R;
+ R.s = &c;
+ R.d = ip;
+ irecv();
+ R = rsav;
+}
+
+void
+csend(Channel *c, void *ip)
+{
+ Prog *p;
+ REG rsav;
+
+ if(c->recv->prog == nil && (c->buf == H || c->size == c->buf->len)) {
+ p = currun();
+ p->ptr = ip;
+ ccom(&c->send, p);
+ return;
+ }
+
+ rsav = R;
+ R.s = ip;
+ R.d = &c;
+ isend();
+ R = rsav;
+}
diff --git a/os/port/latin1.c b/os/port/latin1.c
new file mode 100644
index 00000000..8dcb0d7e
--- /dev/null
+++ b/os/port/latin1.c
@@ -0,0 +1,76 @@
+#include <u.h>
+
+/*
+ * The code makes two assumptions: strlen(ld) is 1 or 2; latintab[i].ld can be a
+ * prefix of latintab[j].ld only when j<i.
+ */
+struct cvlist
+{
+ char *ld; /* must be seen before using this conversion */
+ char *si; /* options for last input characters */
+ Rune *so; /* the corresponding Rune for each si entry */
+} latintab[] = {
+#include "../port/latin1.h"
+ 0, 0, 0
+};
+
+/*
+ * Given 5 characters k[0]..k[4], find the rune or return -1 for failure.
+ */
+long
+unicode(Rune *k)
+{
+ long i, c;
+
+ k++; /* skip 'X' */
+ c = 0;
+ for(i=0; i<4; i++,k++){
+ c <<= 4;
+ if('0'<=*k && *k<='9')
+ c += *k-'0';
+ else if('a'<=*k && *k<='f')
+ c += 10 + *k-'a';
+ else if('A'<=*k && *k<='F')
+ c += 10 + *k-'A';
+ else
+ return -1;
+ }
+ return c;
+}
+
+/*
+ * Given n characters k[0]..k[n-1], find the corresponding rune or return -1 for
+ * failure, or something < -1 if n is too small. In the latter case, the result
+ * is minus the required n.
+ */
+long
+latin1(Rune *k, int n)
+{
+ struct cvlist *l;
+ int c;
+ char* p;
+
+ if(k[0] == 'X')
+ if(n>=5)
+ return unicode(k);
+ else
+ return -5;
+ for(l=latintab; l->ld!=0; l++)
+ if(k[0] == l->ld[0]){
+ if(n == 1)
+ return -2;
+ if(l->ld[1] == 0)
+ c = k[1];
+ else if(l->ld[1] != k[1])
+ continue;
+ else if(n == 2)
+ return -3;
+ else
+ c = k[2];
+ for(p=l->si; *p!=0; p++)
+ if(*p == c)
+ return l->so[p - l->si];
+ return -1;
+ }
+ return -1;
+}
diff --git a/os/port/latin1.h b/os/port/latin1.h
new file mode 100644
index 00000000..382df2bf
--- /dev/null
+++ b/os/port/latin1.h
@@ -0,0 +1,100 @@
+ " ", " i", L"␣ı",
+ "!~", "-=~", L"≄≇≉",
+ "!", "!<=>?bmp", L"¡≮≠≯‽⊄∉⊅",
+ "\"*", "IUiu", L"ΪΫϊϋ",
+ "\"", "\"AEIOUYaeiouy", L"¨ÄËÏÖÜŸäëïöüÿ",
+ "$*", "fhk", L"ϕϑϰ",
+ "$", "BEFHILMRVaefglopv", L"ℬℰℱℋℐℒℳℛƲɑℯƒℊℓℴ℘ʋ",
+ "\'\"", "Uu", L"Ǘǘ",
+ "\'", "\'ACEILNORSUYZacegilnorsuyz", L"´ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź",
+ "*", "*ABCDEFGHIKLMNOPQRSTUWXYZabcdefghiklmnopqrstuwxyz", L"∗ΑΒΞΔΕΦΓΘΙΚΛΜΝΟΠΨΡΣΤΥΩΧΗΖαβξδεφγθικλμνοπψρστυωχηζ",
+ "+", "-O", L"±⊕",
+ ",", ",ACEGIKLNORSTUacegiklnorstu", L"¸ĄÇĘĢĮĶĻŅǪŖŞŢŲąçęģįķļņǫŗşţų",
+ "-*", "l", L"ƛ",
+ "-", "+-2:>DGHILOTZbdghiltuz~", L"∓­ƻ÷→ÐǤĦƗŁ⊖ŦƵƀðǥℏɨłŧʉƶ≂",
+ ".", ".CEGILOZceglz", L"·ĊĖĠİĿ⊙Żċėġŀż",
+ "/", "Oo", L"Øø",
+ "1", "234568", L"½⅓¼⅕⅙⅛",
+ "2", "-35", L"ƻ⅔⅖",
+ "3", "458", L"¾⅗⅜",
+ "4", "5", L"⅘",
+ "5", "68", L"⅚⅝",
+ "7", "8", L"⅞",
+ ":", "()-=", L"☹☺÷≔",
+ "<!", "=~", L"≨⋦",
+ "<", "-<=>~", L"←«≤≶≲",
+ "=", ":<=>OV", L"≕⋜≡⋝⊜⇒",
+ ">!", "=~", L"≩⋧",
+ ">", "<=>~", L"≷≥»≳",
+ "?", "!?", L"‽¿",
+ "@\'", "\'", L"ъ",
+ "@@", "\'EKSTYZekstyz", L"ьЕКСТЫЗекстыз",
+ "@C", "Hh", L"ЧЧ",
+ "@E", "Hh", L"ЭЭ",
+ "@K", "Hh", L"ХХ",
+ "@S", "CHch", L"ЩШЩШ",
+ "@T", "Ss", L"ЦЦ",
+ "@Y", "AEOUaeou", L"ЯЕЁЮЯЕЁЮ",
+ "@Z", "Hh", L"ЖЖ",
+ "@c", "h", L"ч",
+ "@e", "h", L"э",
+ "@k", "h", L"х",
+ "@s", "ch", L"щш",
+ "@t", "s", L"ц",
+ "@y", "aeou", L"яеёю",
+ "@z", "h", L"ж",
+ "@", "ABDFGIJLMNOPRUVXabdfgijlmnopruvx", L"АБДФГИЙЛМНОПРУВХабдфгийлмнопрувх",
+ "A", "E", L"Æ",
+ "C", "ACU", L"⋂ℂ⋃",
+ "Dv", "Zz", L"DŽDž",
+ "D", "-e", L"Ð∆",
+ "G", "-", L"Ǥ",
+ "H", "-H", L"Ħℍ",
+ "I", "-J", L"ƗIJ",
+ "L", "&-Jj|", L"⋀ŁLJLj⋁",
+ "M", "#48bs", L"♮♩♪♭♯",
+ "N", "JNj", L"NJℕNj",
+ "O", "*+-./=EIcoprx", L"⊛⊕⊖⊙⊘⊜ŒƢ©⊚℗®⊗",
+ "P", "P", L"ℙ",
+ "Q", "Q", L"ℚ",
+ "R", "R", L"ℝ",
+ "S", "123S", L"¹²³§",
+ "T", "-u", L"Ŧ⊨",
+ "V", "=", L"⇐",
+ "Y", "R", L"Ʀ",
+ "Z", "-ACSZ", L"Ƶℤ",
+ "^", "ACEGHIJOSUWYaceghijosuwy", L"ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ",
+ "_\"", "AUau", L"ǞǕǟǖ",
+ "_,", "Oo", L"Ǭǭ",
+ "_.", "Aa", L"Ǡǡ",
+ "_", "AEIOU_aeiou", L"ĀĒĪŌŪ¯āēīōū",
+ "`\"", "Uu", L"Ǜǜ",
+ "`", "AEIOUaeiou", L"ÀÈÌÒÙàèìòù",
+ "a", "ben", L"↔æ∠",
+ "b", "()+-0123456789=bknpqru", L"₍₎₊₋₀₁₂₃₄₅₆₇₈₉₌♝♚♞♟♛♜•",
+ "c", "$Oagu", L"¢©∩≅∪",
+ "dv", "z", L"dž",
+ "d", "-adegz", L"ð↓‡°†ʣ",
+ "e", "$lmns", L"€⋯—–∅",
+ "f", "a", L"∀",
+ "g", "$-r", L"¤ǥ∇",
+ "h", "-v", L"ℏƕ",
+ "i", "-bfjps", L"ɨ⊆∞ij⊇∫",
+ "l", "\"$&\'-jz|", L"“£∧‘łlj⋄∨",
+ "m", "iou", L"µ∈×",
+ "n", "jo", L"nj¬",
+ "o", "AOUaeiu", L"Å⊚Ůåœƣů",
+ "p", "Odgrt", L"℗∂¶∏∝",
+ "r", "\"\'O", L"”’®",
+ "s", "()+-0123456789=abnoprstu", L"⁽⁾⁺⁻⁰ⁱ⁲⁳⁴⁵⁶⁷⁸⁹⁼ª⊂ⁿº⊃√ß∍∑",
+ "t", "-efmsu", L"ŧ∃∴™ς⊢",
+ "u", "-AEGIOUaegiou", L"ʉĂĔĞĬŎŬ↑ĕğĭŏŭ",
+ "v\"", "Uu", L"Ǚǚ",
+ "v", "ACDEGIKLNORSTUZacdegijklnorstuz", L"ǍČĎĚǦǏǨĽŇǑŘŠŤǓŽǎčďěǧǐǰǩľňǒřšťǔž",
+ "w", "bknpqr", L"♗♔♘♙♕♖",
+ "x", "O", L"⊗",
+ "y", "$", L"¥",
+ "z", "-", L"ƶ",
+ "|", "Pp|", L"Þþ¦",
+ "~!", "=", L"≆",
+ "~", "-=AINOUainou~", L"≃≅ÃĨÑÕŨãĩñõũ≈",
diff --git a/os/port/lib.h b/os/port/lib.h
new file mode 100644
index 00000000..0233dc72
--- /dev/null
+++ b/os/port/lib.h
@@ -0,0 +1,211 @@
+
+/*
+ * functions (possibly) linked in, complete, from libc.
+ */
+
+/*
+ * mem routines
+ */
+extern void* memccpy(void*, void*, int, ulong);
+extern void* memset(void*, int, ulong);
+extern int memcmp(void*, void*, ulong);
+extern void* memmove(void*, void*, ulong);
+extern void* memchr(void*, int, ulong);
+
+/*
+ * string routines
+ */
+extern char* strcat(char*, char*);
+extern char* strchr(char*, int);
+extern char* strrchr(char*, int);
+extern int strcmp(char*, char*);
+extern char* strcpy(char*, char*);
+extern char* strecpy(char*, char*, char*);
+extern char* strncat(char*, char*, long);
+extern char* strncpy(char*, char*, long);
+extern int strncmp(char*, char*, long);
+extern long strlen(char*);
+extern char* strdup(char*);
+extern char* strstr(char*, char*);
+extern int cistrncmp(char*, char*, int);
+extern int cistrcmp(char*, char*);
+extern char* cistrstr(char*, char*);
+extern int atoi(char*);
+extern int fullrune(char*, int);
+
+enum
+{
+ UTFmax = 3, /* maximum bytes per rune */
+ Runesync = 0x80, /* cannot represent part of a UTF sequence */
+ Runeself = 0x80, /* rune and UTF sequences are the same (<) */
+ Runeerror = 0x80, /* decoding error in UTF */
+};
+
+/*
+ * rune routines
+ */
+extern int runetochar(char*, Rune*);
+extern int chartorune(Rune*, char*);
+extern char* utfrune(char*, long);
+extern int utflen(char*);
+extern int runelen(long);
+
+extern int abs(int);
+
+/*
+ * print routines
+ */
+typedef struct Fmt Fmt;
+typedef int (*Fmts)(Fmt*);
+struct Fmt{
+ uchar runes; /* output buffer is runes or chars? */
+ void *start; /* of buffer */
+ void *to; /* current place in the buffer */
+ void *stop; /* end of the buffer; overwritten if flush fails */
+ int (*flush)(Fmt *); /* called when to == stop */
+ void *farg; /* to make flush a closure */
+ int nfmt; /* num chars formatted so far */
+ va_list args; /* args passed to dofmt */
+ int r; /* % format Rune */
+ int width;
+ int prec;
+ ulong flags;
+};
+extern int print(char*, ...);
+extern char* seprint(char*, char*, char*, ...);
+extern char* vseprint(char*, char*, char*, va_list);
+extern char* smprint(char*, ...);
+extern char* vsmprint(char*, va_list);
+extern int snprint(char*, int, char*, ...);
+extern int vsnprint(char*, int, char*, va_list);
+extern int sprint(char*, char*, ...);
+
+extern int fmtinstall(int, int (*)(Fmt*));
+extern void quotefmtinstall(void);
+extern int fmtprint(Fmt*, char*, ...);
+extern int fmtstrcpy(Fmt*, char*);
+
+#pragma varargck argpos fmtprint 2
+#pragma varargck argpos print 1
+#pragma varargck argpos seprint 3
+#pragma varargck argpos snprint 3
+#pragma varargck argpos sprint 2
+#pragma varargck argpos vseprint 3
+#pragma varargck argpos vsnprint 3
+
+extern int fltconv(Fmt*);
+
+/*
+ * math
+ */
+extern int isNaN(double);
+extern int isInf(double, int);
+extern double floor(double);
+extern double frexp(double, int*);
+extern double pow10(int);
+
+/*
+ * one-of-a-kind
+ */
+extern char* cleanname(char*);
+extern ulong getcallerpc(void*);
+
+extern long strtol(char*, char**, int);
+extern ulong strtoul(char*, char**, int);
+extern vlong strtoll(char*, char**, int);
+extern uvlong strtoull(char*, char**, int);
+extern char etext[];
+extern char edata[];
+extern char end[];
+extern int getfields(char*, char**, int, int, char*);
+extern int tokenize(char*, char**, int);
+extern int dec64(uchar*, int, char*, int);
+
+extern int toupper(int);
+extern char* netmkaddr(char*, char*, char*);
+extern int myetheraddr(uchar*, char*);
+extern int parseether(uchar*, char*);
+
+/*
+ * network dialling
+ */
+#define NETPATHLEN 40
+
+/*
+ * Syscall data structures
+ */
+#define MORDER 0x0003 /* mask for bits defining order of mounting */
+#define MREPL 0x0000 /* mount replaces object */
+#define MBEFORE 0x0001 /* mount goes before others in union directory */
+#define MAFTER 0x0002 /* mount goes after others in union directory */
+#define MCREATE 0x0004 /* permit creation in mounted directory */
+#define MCACHE 0x0010 /* cache some data */
+#define MMASK 0x0017 /* all bits on */
+
+#define OREAD 0 /* open for read */
+#define OWRITE 1 /* write */
+#define ORDWR 2 /* read and write */
+#define OEXEC 3 /* execute, == read but check execute permission */
+#define OTRUNC 16 /* or'ed in (except for exec), truncate file first */
+#define OCEXEC 32 /* or'ed in, close on exec */
+#define ORCLOSE 64 /* or'ed in, remove on close */
+#define OEXCL 0x1000 /* or'ed in, exclusive create */
+
+#define NCONT 0 /* continue after note */
+#define NDFLT 1 /* terminate after note */
+#define NSAVE 2 /* clear note but hold state */
+#define NRSTR 3 /* restore saved state */
+
+typedef struct Qid Qid;
+typedef struct Dir Dir;
+typedef struct Waitmsg Waitmsg;
+
+#define ERRMAX 128 /* max length of error string */
+#define KNAMELEN 28 /* max length of name held in kernel */
+
+/* bits in Qid.type */
+#define QTDIR 0x80 /* type bit for directories */
+#define QTAPPEND 0x40 /* type bit for append only files */
+#define QTEXCL 0x20 /* type bit for exclusive use files */
+#define QTMOUNT 0x10 /* type bit for mounted channel */
+#define QTAUTH 0x08 /* type bit for authentication file */
+#define QTFILE 0x00 /* plain file */
+
+/* bits in Dir.mode */
+#define DMDIR 0x80000000 /* mode bit for directories */
+#define DMAPPEND 0x40000000 /* mode bit for append only files */
+#define DMEXCL 0x20000000 /* mode bit for exclusive use files */
+#define DMMOUNT 0x10000000 /* mode bit for mounted channel */
+#define DMREAD 0x4 /* mode bit for read permission */
+#define DMWRITE 0x2 /* mode bit for write permission */
+#define DMEXEC 0x1 /* mode bit for execute permission */
+
+struct Qid
+{
+ uvlong path;
+ ulong vers;
+ uchar type;
+};
+
+struct Dir {
+ /* system-modified data */
+ ushort type; /* server type */
+ uint dev; /* server subtype */
+ /* file data */
+ Qid qid; /* unique id from server */
+ ulong mode; /* permissions */
+ ulong atime; /* last read time */
+ ulong mtime; /* last write time */
+ vlong length; /* file length: see <u.h> */
+ char *name; /* last element of path */
+ char *uid; /* owner name */
+ char *gid; /* group name */
+ char *muid; /* last modifier name */
+};
+
+struct Waitmsg
+{
+ int pid; /* of loved one */
+ ulong time[3]; /* of loved one and descendants */
+ char msg[ERRMAX]; /* actually variable-size in user mode */
+};
diff --git a/os/port/log.c b/os/port/log.c
new file mode 100644
index 00000000..14708233
--- /dev/null
+++ b/os/port/log.c
@@ -0,0 +1,186 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+static char Ebadlogctl[] = "unknown log ctl message";
+
+void
+logopen(Log *alog)
+{
+ lock(alog);
+ if(waserror()){
+ unlock(alog);
+ nexterror();
+ }
+ if(alog->opens == 0){
+ if(alog->nlog == 0)
+ alog->nlog = 4*1024;
+ if(alog->minread == 0)
+ alog->minread = 1;
+ if(alog->buf == nil)
+ alog->buf = malloc(alog->nlog);
+ alog->rptr = alog->buf;
+ alog->end = alog->buf + alog->nlog;
+ alog->len = 0;
+ }
+ alog->opens++;
+ unlock(alog);
+ poperror();
+}
+
+void
+logclose(Log *alog)
+{
+ lock(alog);
+ alog->opens--;
+ if(alog->opens == 0){
+ free(alog->buf);
+ alog->buf = nil;
+ }
+ unlock(alog);
+}
+
+static int
+logready(void *a)
+{
+ Log *alog = a;
+
+ return alog->len >= alog->minread;
+}
+
+long
+logread(Log *alog, void *a, ulong, long n)
+{
+ int i, d;
+ char *p, *rptr;
+
+ qlock(&alog->readq);
+ if(waserror()){
+ qunlock(&alog->readq);
+ nexterror();
+ }
+
+ for(;;){
+ lock(alog);
+ if(alog->len >= alog->minread || alog->len >= n){
+ if(n > alog->len)
+ n = alog->len;
+ d = 0;
+ rptr = alog->rptr;
+ alog->rptr += n;
+ if(alog->rptr >= alog->end){
+ d = alog->rptr - alog->end;
+ alog->rptr = alog->buf + d;
+ }
+ alog->len -= n;
+ unlock(alog);
+
+ i = n-d;
+ p = a;
+ memmove(p, rptr, i);
+ memmove(p+i, alog->buf, d);
+ break;
+ }
+ else
+ unlock(alog);
+
+ sleep(&alog->readr, logready, alog);
+ }
+
+ qunlock(&alog->readq);
+ poperror();
+
+ return n;
+}
+
+char*
+logctl(Log *alog, int argc, char *argv[], Logflag *flags)
+{
+ int i, set;
+ Logflag *fp;
+
+ if(argc < 2)
+ return Ebadlogctl;
+
+ if(strcmp("set", argv[0]) == 0)
+ set = 1;
+ else if(strcmp("clear", argv[0]) == 0)
+ set = 0;
+ else
+ return Ebadlogctl;
+
+ for(i = 1; i < argc; i++){
+ for(fp = flags; fp->name; fp++)
+ if(strcmp(fp->name, argv[i]) == 0)
+ break;
+ if(fp->name == nil)
+ continue;
+ if(set)
+ alog->logmask |= fp->mask;
+ else
+ alog->logmask &= ~fp->mask;
+ }
+
+ return nil;
+}
+
+void
+logn(Log *alog, int mask, void *buf, int n)
+{
+ char *fp, *t;
+ int dowake, i;
+
+ if(!(alog->logmask & mask))
+ return;
+
+ if(alog->opens == 0)
+ return;
+
+ if(n > alog->nlog)
+ return;
+
+ lock(alog);
+ i = alog->len + n - alog->nlog;
+ if(i > 0){
+ alog->len -= i;
+ alog->rptr += i;
+ if(alog->rptr >= alog->end)
+ alog->rptr = alog->buf + (alog->rptr - alog->end);
+ }
+ t = alog->rptr + alog->len;
+ fp = buf;
+ alog->len += n;
+ while(n-- > 0){
+ if(t >= alog->end)
+ t = alog->buf + (t - alog->end);
+ *t++ = *fp++;
+ }
+ dowake = alog->len >= alog->minread;
+ unlock(alog);
+
+ if(dowake)
+ wakeup(&alog->readr);
+}
+
+void
+logb(Log *alog, int mask, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[128];
+
+ if(!(alog->logmask & mask))
+ return;
+
+ if(alog->opens == 0)
+ return;
+
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+
+ logn(alog, mask, buf, n);
+}
diff --git a/os/port/master b/os/port/master
new file mode 100644
index 00000000..fe8ffa00
--- /dev/null
+++ b/os/port/master
@@ -0,0 +1,89 @@
+/ root
+A audio
+A beep
+B boot
+B bridge
+D ssl
+E mpeg
+F flash
+F tinyfs
+G fpga
+G gpio
+H ata
+I ip
+J i2c
+J isdn
+K kprof
+L ds1620
+L lm78
+L lpt
+M mnt
+O omap
+O sm # altivec soft modem
+P prof
+P tad
+P telephony
+Q ssfdc
+R cdrom
+R msg
+S scsi
+T kprof # old letter
+T pri
+T touch
+U usb
+V tv
+V voice
+W flash
+X XXX
+X ftl
+Y pbus
+Z doc
+Z sadb
+Z test
+Z zt5512
+a atm
+a beep
+b dbg
+c cons
+d dup
+e env
+f floppy
+i draw
+k kprof
+k fs
+l ether
+l lance
+m midi
+m mouse
+m pointer
+p prog
+r rtc
+s srv
+t duart
+t ns16552
+t uart
+t z8530
+v vid
+v vsc
+w sd
+x bench
+y i82365
+y pcmcia
+y sapcm
+¤ cap
+Û isp1161
+Û usbh
+Σ sign
+τ tk
+↓ power
+
+α local use
+β local use
+γ local use
+δ local use
+ε local use
+ζ local use
+η local use
+θ local use
+ι local use
+κ local use
diff --git a/os/port/master.local b/os/port/master.local
new file mode 100644
index 00000000..5d9438d3
--- /dev/null
+++ b/os/port/master.local
@@ -0,0 +1,10 @@
+α local use
+β local use
+γ local use
+δ local use
+ε local use
+ζ local use
+η local use
+θ local use
+ι local use
+κ local use
diff --git a/os/port/mkdevc b/os/port/mkdevc
new file mode 100644
index 00000000..afb7caa7
--- /dev/null
+++ b/os/port/mkdevc
@@ -0,0 +1,216 @@
+$AWK -v 'objtype='$OBJTYPE '
+BEGIN{
+ if(ARGC < 2)
+ exit
+}
+
+/^$/{
+ next;
+}
+/^#/{
+ next;
+}
+collect && /^[^ \t]/{
+ collect = 0;
+}
+collect && section ~ "dev"{
+ dev[ndev++] = $1;
+ if($1 ~ "vga")
+ devvga = 1;
+}
+collect && section ~ "ip"{
+ ip[nip++] = $1;
+}
+collect && (section ~ "ether" || section ~ "link") {
+ link[nlink++] = $1;
+}
+collect && section ~ "mod"{
+ mod[nmod++] = $1;
+}
+collect && section ~ "vga"{
+ option = 0;
+ for(i = 2; i < NF; i++){
+ if($i ~ "[+]hwgc"){
+ hwgc[nhwgc++] = $1;
+ option = 1;
+ } else if($i ~ "[+=]hwgc"){
+ hwgc[nhwgc++] = $1;
+ if(option == 0)
+ option = 2;
+ }
+ }
+ if(option < 2)
+ vga[nvga++] = $1;
+}
+collect && section ~ "misc"{
+ misc[nmisc++] = $1;
+ if($1 ~ "^arch.*")
+ arch[narch++] = $1;
+ else if($1 ~ "^sd.*")
+ sdifc[nsdifc++] = $1;
+ else if($1 ~ "^uart.*")
+ physuart[nphysuart++] = substr($1, 5, length($1)-4) "physuart";
+ else if($1 ~ "^vga.*"){
+ if(NF == 1)
+ vgadev[nvgadev++] = $1;
+ else for(i = 2; i <= NF; i++){
+ if($i ~ "[+]cur")
+ vgadev[nvgadev++] = $1;
+ if($i ~ "[+=]cur")
+ vgacur[nvgacur++] = $1;
+ }
+ }
+ else if($1 ~ ".*\.root"){
+ x = substr($1, 1, index($1, ".")-1);
+ if(x ~ "(dossrv|kfs)")
+ x = "fs";
+ fs[nfs++] = x;
+ }
+}
+collect && section ~ "port"{
+ port[nport++] = $0;
+}
+collect && section ~ "code"{
+ code[ncode++] = $0;
+}
+$0 ~ /^[^ \t]/{
+ if($0 ~ "(code|dev|ether|ip|lib|link|mod|misc|port|root|vga)"){
+ section = $0;
+ collect = 1;
+ }
+ next;
+}
+
+END{
+ if(ARGC < 2)
+ exit "usage"
+
+ printf "#include \"u.h\"\n"
+ printf "#include \"../port/lib.h\"\n"
+ printf "#include \"mem.h\"\n"
+ printf "#include \"dat.h\"\n"
+ printf "#include \"fns.h\"\n"
+ printf "#include \"io.h\"\n"
+ if(nphysuart)
+ printf "#include \"../port/uart.h\"\n"
+ printf "#include \"../port/error.h\"\n"
+ printf "#include \"interp.h\"\n\n"
+ printf "#include \"%s.root.h\"\n\n", ARGV[1];
+
+ printf "ulong ndevs = %d;\n", ndev+8
+ for(i = 0; i < ndev; i++)
+ printf "extern Dev %sdevtab;\n", dev[i];
+ printf "Dev* devtab[%d]={\n", ndev+8
+ for(i = 0; i < ndev; i++)
+ printf "\t&%sdevtab,\n", dev[i];
+ printf "\tnil,\n};\n\n";
+
+
+ for(i = 0; i < nfs; i++){
+ printf "extern uchar %scode[];\n", fs[i];
+ printf "extern ulong %slen;\n", fs[i];
+ }
+ for(i = 0; i < nlink; i++)
+ printf "extern void %slink(void);\n", link[i];
+
+ printf "void links(void){\n";
+ for(i = 0; i < nfs; i++)
+ printf "\taddrootfile(\"%s\", %scode, %slen);\n", fs[i], fs[i], fs[i];
+ for(i = 0; i < nlink; i++)
+ printf "\t%slink();\n", link[i];
+ printf "}\n\n";
+
+ for(i = 0; i < nmod; i++)
+ printf "extern void %smodinit(void);\n", mod[i];
+ printf "void modinit(void){\n";
+ for(i = 0; i < nmod; i++)
+ printf("\t%smodinit();\n",mod[i]);
+ printf("}\n\n");
+
+ if(narch || objtype ~ "386"){
+ for(i = 0; i < narch; i++)
+ printf "extern PCArch %s;\n", arch[i];
+ printf "PCArch* knownarch[] = {\n";
+ for(i = 0; i < narch; i++)
+ printf "\t&%s,\n", arch[i];
+ printf "\tnil,\n};\n\n";
+ }
+
+ if(nsdifc){
+ printf "#include \"../port/sd.h\"\n";
+ for(i = 0; i < nsdifc; i++)
+ printf "extern SDifc %sifc;\n", sdifc[i];
+ printf "SDifc* sdifc[] = {\n";
+ for(i = 0; i < nsdifc; i++)
+ printf "\t&%sifc,\n", sdifc[i];
+ printf "\tnil,\n};\n\n";
+ }
+
+ if(nphysuart){
+ for(i = 0; i < nphysuart; i++)
+ printf "extern PhysUart %s;\n", physuart[i];
+ printf "PhysUart* physuart[] = {\n";
+ for(i = 0; i < nphysuart; i++)
+ printf "\t&%s,\n", physuart[i];
+ printf "\tnil,\n};\n\n";
+ }
+
+ if(devvga || nvga || nvgadev){
+ printf "#include <draw.h>\n"
+ printf "#include <memdraw.h>\n"
+
+ if(nvga){
+ printf "#include \"vga.h\"\n"
+ for(i = 0; i < nvga; i++)
+ printf "extern Vgac %s;\n", vga[i];
+ printf "Vgac* knownvga[] = {\n";
+ for(i = 0; i < nvga; i++)
+ printf "\t&%s,\n", vga[i];
+ printf "\tnil,\n};\n\n";
+
+ if(nhwgc){
+ for(i = 0; i < nhwgc; i++)
+ printf "extern Hwgc %shwgc;\n", hwgc[i];
+ printf "Hwgc* knownhwgc[] = {\n";
+ for(i = 0; i < nhwgc; i++)
+ printf "\t&%shwgc,\n", hwgc[i];
+ printf "\tnil,\n};\n\n";
+ }
+ }
+
+ if(nvgadev){
+ printf "#include \"screen.h\"\n";
+ for(i = 0; i < nvgadev; i++)
+ printf "extern VGAdev %sdev;\n", vgadev[i];
+ printf "VGAdev* vgadev[] = {\n";
+ for(i = 0; i < nvgadev; i++)
+ printf "\t&%sdev,\n", vgadev[i];
+ printf "\tnil,\n};\n\n";
+
+ for(i = 0; i < nvgacur; i++)
+ printf "extern VGAcur %scur;\n", vgacur[i];
+ printf "VGAcur* vgacur[] = {\n";
+ for(i = 0; i < nvgacur; i++)
+ printf "\t&%scur,\n", vgacur[i];
+ printf "\tnil,\n};\n\n";
+ }
+ }
+
+ if(nip){
+ printf "#include \"../ip/ip.h\"\n";
+ for(i = 0; i < nip; i++)
+ printf "extern void %sinit(Fs*);\n", ip[i];
+ printf "void (*ipprotoinit[])(Fs*) = {\n";
+ for(i = 0; i < nip; i++)
+ printf "\t%sinit,\n", ip[i];
+ printf "\tnil,\n};\n\n";
+ }
+
+ for(i = 0; i < ncode; i++)
+ printf "%s\n", code[i];
+
+ printf "char* conffile = \"%s\";\n", ARGV[1];
+ printf "ulong kerndate = KERNDATE;\n";
+
+ exit
+}' $*
diff --git a/os/port/mkdevlist b/os/port/mkdevlist
new file mode 100644
index 00000000..6fdc6866
--- /dev/null
+++ b/os/port/mkdevlist
@@ -0,0 +1,86 @@
+$AWK '
+BEGIN{
+ var["dev"] = "DEVS=";
+ var["vga"] = "VGAS=";
+ var["ether"] = "ETHERS=";
+ var["init"] = "INIT=";
+ var["ip"] = "IP=";
+ var["port"] = "PORT=";
+ var["misc"] = "MISC=";
+ var["lib"] = "LIBS=";
+ var["link"] = "LINKS=";
+ var["root"] = "ROOTFILES=";
+ infernoroot = ENVIRON["ROOT"];
+}
+/^$/{ next;
+}
+/^#/{ next;
+}
+/^env/{
+ inenv = 1;
+ next;
+}
+inenv != 0 && /^[ ]/{
+ sub("^[ ]*", "", $0)
+ printf "%s\n", $0
+ next
+}
+/^(code|dev|ether|init|ip|lib|link|mod|misc|port|root|vga)/{
+ inenv = 0;
+ if(current != "")
+ print current;
+ current = var[$1];
+ type = $1;
+ next;
+}
+/^[^ ]/ {
+ inenv = 0;
+ if(current != "")
+ print current;
+ current = "";
+}
+current && /^[ ]/{
+ if(type == "dev")
+ file = "dev" $1;
+ else if(type == "root"){
+ if (NF > 1)
+ file = $2;
+ else if ($1 == "/osinit.dis")
+ next; # handled via explicit dependency
+ else
+ file = $1;
+ if(have[file] == 0){
+ current = current " " infernoroot file;
+ have[file]++;
+ }
+ next;
+ }
+ else
+ file = $1;
+ if(type == "init" || type == "lib")
+ current = current " " file;
+ else if(have[file] == 0){
+ if(type == "lib")
+ current = current " " file;
+ else
+ current = current " " file "'.$O'";
+ have[file]++;
+ }
+ for(i = 2; i <= NF; i++){
+ if($i !~ "^[+=-].*"){
+ if(have[$i] == 0){
+ others[++nothers] = $i;
+ have[$i]++;
+ }
+ }
+ }
+ next;
+}
+END{
+ if(current != "")
+ print current;
+ for(i = 1; i <= nothers; i++)
+ x = x " " others[i] "'.$O' ";
+ if(x)
+ printf("OTHERS=%s\n", x);
+}' $*
diff --git a/os/port/mkroot b/os/port/mkroot
new file mode 100644
index 00000000..9bcf3598
--- /dev/null
+++ b/os/port/mkroot
@@ -0,0 +1,119 @@
+$AWK '
+BEGIN{
+ if (ARGC < 2)
+ exit "usage";
+
+ conf = ARGV[1];
+ infernoroot = ENVIRON["ROOT"];
+ init = ENVIRON["INIT"];
+ data2s = ENVIRON["DATA2S"];
+ nroot = 0;
+}
+/^$/{
+ next;
+}
+/^#/{
+ next;
+}
+collect && /^[^ \t]/{
+ collect = 0;
+}
+collect && section ~ "root"{
+ dst[nroot] = $1;
+ if (NF > 1)
+ src[nroot] = infernoroot $2;
+ else if (dst[nroot] == "/osinit.dis")
+ src[nroot] = infernoroot "/os/init/" init ".dis";
+ else
+ src[nroot] = infernoroot $1;
+ for(i=0; i<nroot; i++)
+ if(dst[i] == dst[nroot])
+ break;
+ if(i == nroot)
+ nroot++;
+}
+$0 ~ /^[^ \t]/{
+ if($0 ~ "(code|dev|ether|ip|lib|link|mod|misc|port|root|vga)"){
+ section = $0;
+ collect = 1;
+ }
+ next;
+}
+END{
+ rootdata = conf ".root.s";
+ system("rm -f " rootdata);
+ print("/* Generated by /os/port/mkroot */") >rootdata;
+ close(rootdata);
+ isdir[0] = 1;
+ dotdot[0] = 0;
+ qid = 1;
+ for (i = 0; i < nroot; i++) {
+ ncomp = split(dst[i], comp, "/");
+ if (comp[1] != "" || ncomp < 2)
+ continue;
+ q = 0;
+ for (j = 2; j <= ncomp; j++) {
+ key = q "/" comp[j];
+ if (walk[key] == 0) {
+ walk[key] = qid;
+ dotdot[qid] = q;
+ q = qid++;
+ name[q] = comp[j];
+ if (j < ncomp)
+ isdir[q] = 1;
+ }
+ else
+ q = walk[key];
+ }
+ if (system("test -d " src[i]) == 0)
+ isdir[q] = 1;
+ else {
+ if (system(data2s " root" q " <" src[i] " >>" rootdata) != 0)
+ exit 1;
+ print("extern unsigned char root" q "code[];");
+ print("extern int root" q "len;");
+ }
+ }
+
+ x = 1;
+ sort[0] = 0;
+ unsort[0] = 0;
+ for (q = 0; q < qid; q++) {
+ if (isdir[q]) {
+ nchild[q] = 0;
+ for (q2 = 1; q2 < qid; q2++) {
+ if (dotdot[q2] == q) {
+ if (nchild[q]++ == 0)
+ child0[q] = x;
+ sort[q2] = x++;
+ unsort[sort[q2]] = q2;
+ }
+ }
+ }
+ }
+
+ print("int rootmaxq = " qid ";");
+
+ print("Dirtab roottab[" qid "] = {");
+ for (oq = 0; oq < qid; oq++) {
+ q = unsort[oq];
+ if (!isdir[q])
+ print("\t\"" name[q] "\",\t{" oq ", 0, QTFILE},\t", "0,\t0444,");
+ else
+ print("\t\"" name[q] "\",\t{" oq ", 0, QTDIR},\t", "0,\t0555,");
+ }
+ print("};");
+
+ print("Rootdata rootdata[" qid "] = {");
+ for (oq = 0; oq < qid; oq++) {
+ q = unsort[oq];
+ if (!isdir[q])
+ print("\t" sort[dotdot[q]] ",\t", "root" q "code,\t", "0,\t", "&root" q "len,");
+ else if (nchild[q])
+ print("\t" sort[dotdot[q]] ",\t", "&roottab[" child0[q] "],\t", nchild[q] ",\tnil,");
+ else
+ print("\t" sort[dotdot[q]] ",\t", "nil,\t", "0,\t", "nil,");
+ }
+ print("};");
+}
+' $1 >$1.root.h
diff --git a/os/port/mul64fract.c b/os/port/mul64fract.c
new file mode 100644
index 00000000..ca627073
--- /dev/null
+++ b/os/port/mul64fract.c
@@ -0,0 +1,39 @@
+#include <u.h>
+
+/* mul64fract(uvlong*r, uvlong a, uvlong b)
+ *
+ * Multiply two 64 numbers and return the middle 64 bits of the 128 bit result.
+ *
+ * The assumption is that one of the numbers is a
+ * fixed point number with the integer portion in the
+ * high word and the fraction in the low word.
+ *
+ * There should be an assembler version of this routine
+ * for each architecture. This one is intended to
+ * make ports easier.
+ *
+ * ignored r0 = lo(a0*b0)
+ * lsw of result r1 = hi(a0*b0) +lo(a0*b1) +lo(a1*b0)
+ * msw of result r2 = hi(a0*b1) +hi(a1*b0) +lo(a1*b1)
+ * ignored r3 = hi(a1*b1)
+ */
+
+void
+mul64fract(uvlong *r, uvlong a, uvlong b)
+{
+ uvlong bh, bl;
+ uvlong ah, al;
+ uvlong res;
+
+ bl = b & 0xffffffffULL;
+ bh = b >> 32;
+ al = a & 0xffffffffULL;
+ ah = a >> 32;
+
+ res = (al*bl)>>32;
+ res += (al*bh);
+ res += (ah*bl);
+ res += (ah*bh)<<32;
+
+ *r = res;
+}
diff --git a/os/port/netaux.c b/os/port/netaux.c
new file mode 100644
index 00000000..0c5d6a55
--- /dev/null
+++ b/os/port/netaux.c
@@ -0,0 +1,67 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+
+void
+hnputv(void *p, vlong v)
+{
+ uchar *a;
+
+ a = p;
+ hnputl(a, v>>32);
+ hnputl(a+4, v);
+}
+
+void
+hnputl(void *p, ulong v)
+{
+ uchar *a;
+
+ a = p;
+ a[0] = v>>24;
+ a[1] = v>>16;
+ a[2] = v>>8;
+ a[3] = v;
+}
+
+void
+hnputs(void *p, ushort v)
+{
+ uchar *a;
+
+ a = p;
+ a[0] = v>>8;
+ a[1] = v;
+}
+
+vlong
+nhgetv(void *p)
+{
+ uchar *a;
+
+ a = p;
+ return ((vlong)nhgetl(a) << 32) | nhgetl(a+4);
+}
+
+ulong
+nhgetl(void *p)
+{
+ uchar *a;
+
+ a = p;
+ return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
+}
+
+ushort
+nhgets(void *p)
+{
+ uchar *a;
+
+ a = p;
+ return (a[0]<<8)|(a[1]<<0);
+}
diff --git a/os/port/netif.c b/os/port/netif.c
new file mode 100644
index 00000000..60567f5e
--- /dev/null
+++ b/os/port/netif.c
@@ -0,0 +1,671 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+static int netown(Netfile*, char*, int);
+static int openfile(Netif*, int);
+static char* matchtoken(char*, char*);
+static char* netmulti(Netif*, Netfile*, uchar*, int);
+static int parseaddr(uchar*, char*, int);
+
+/*
+ * set up a new network interface
+ */
+void
+netifinit(Netif *nif, char *name, int nfile, ulong limit)
+{
+ strncpy(nif->name, name, KNAMELEN-1);
+ nif->name[KNAMELEN-1] = 0;
+ nif->nfile = nfile;
+ nif->f = malloc(nfile*sizeof(Netfile*));
+ if(nif->f)
+ memset(nif->f, 0, nfile*sizeof(Netfile*));
+ else
+ nif->nfile = 0;
+ nif->limit = limit;
+}
+
+/*
+ * generate a 3 level directory
+ */
+static int
+netifgen(Chan *c, char*, Dirtab *vp, int, int i, Dir *dp)
+{
+ Qid q;
+ Netif *nif = (Netif*)vp;
+ Netfile *f;
+ int t;
+ int perm;
+ char *o;
+
+ q.type = QTFILE;
+ q.vers = 0;
+
+ /* top level directory contains the name of the network */
+ if(c->qid.path == 0){
+ switch(i){
+ case DEVDOTDOT:
+ q.path = 0;
+ q.type = QTDIR;
+ devdir(c, q, ".", 0, eve, 0555, dp);
+ break;
+ case 0:
+ q.path = N2ndqid;
+ q.type = QTDIR;
+ strcpy(up->genbuf, nif->name);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ break;
+ default:
+ return -1;
+ }
+ return 1;
+ }
+
+ /* second level contains clone plus all the conversations */
+ t = NETTYPE(c->qid.path);
+ if(t == N2ndqid || t == Ncloneqid || t == Naddrqid){
+ switch(i) {
+ case DEVDOTDOT:
+ q.type = QTDIR;
+ q.path = 0;
+ devdir(c, q, ".", 0, eve, DMDIR|0555, dp);
+ break;
+ case 0:
+ q.path = Ncloneqid;
+ devdir(c, q, "clone", 0, eve, 0666, dp);
+ break;
+ case 1:
+ q.path = Naddrqid;
+ devdir(c, q, "addr", 0, eve, 0666, dp);
+ break;
+ case 2:
+ q.path = Nstatqid;
+ devdir(c, q, "stats", 0, eve, 0444, dp);
+ break;
+ case 3:
+ q.path = Nifstatqid;
+ devdir(c, q, "ifstats", 0, eve, 0444, dp);
+ break;
+ default:
+ i -= 4;
+ if(i >= nif->nfile)
+ return -1;
+ if(nif->f[i] == 0)
+ return 0;
+ q.type = QTDIR;
+ q.path = NETQID(i, N3rdqid);
+ sprint(up->genbuf, "%d", i);
+ devdir(c, q, up->genbuf, 0, eve, DMDIR|0555, dp);
+ break;
+ }
+ return 1;
+ }
+
+ /* third level */
+ f = nif->f[NETID(c->qid.path)];
+ if(f == 0)
+ return 0;
+ if(*f->owner){
+ o = f->owner;
+ perm = f->mode;
+ } else {
+ o = eve;
+ perm = 0666;
+ }
+ switch(i){
+ case DEVDOTDOT:
+ q.type = QTDIR;
+ q.path = N2ndqid;
+ strcpy(up->genbuf, nif->name);
+ devdir(c, q, up->genbuf, 0, eve, DMDIR|0555, dp);
+ break;
+ case 0:
+ q.path = NETQID(NETID(c->qid.path), Ndataqid);
+ devdir(c, q, "data", 0, o, perm, dp);
+ break;
+ case 1:
+ q.path = NETQID(NETID(c->qid.path), Nctlqid);
+ devdir(c, q, "ctl", 0, o, perm, dp);
+ break;
+ case 2:
+ q.path = NETQID(NETID(c->qid.path), Nstatqid);
+ devdir(c, q, "stats", 0, eve, 0444, dp);
+ break;
+ case 3:
+ q.path = NETQID(NETID(c->qid.path), Ntypeqid);
+ devdir(c, q, "type", 0, eve, 0444, dp);
+ break;
+ case 4:
+ q.path = NETQID(NETID(c->qid.path), Nifstatqid);
+ devdir(c, q, "ifstats", 0, eve, 0444, dp);
+ break;
+ default:
+ return -1;
+ }
+ return 1;
+}
+
+Walkqid*
+netifwalk(Netif *nif, Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, (Dirtab *)nif, 0, netifgen);
+}
+
+Chan*
+netifopen(Netif *nif, Chan *c, int omode)
+{
+ int id;
+ Netfile *f;
+
+ id = 0;
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Eperm);
+ } else {
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ case Nctlqid:
+ id = NETID(c->qid.path);
+ openfile(nif, id);
+ break;
+ case Ncloneqid:
+ id = openfile(nif, -1);
+ c->qid.path = NETQID(id, Nctlqid);
+ break;
+ default:
+ if(omode != OREAD)
+ error(Ebadarg);
+ }
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ case Nctlqid:
+ f = nif->f[id];
+ if(netown(f, up->env->user, omode&7) < 0)
+ error(Eperm);
+ break;
+ }
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+long
+netifread(Netif *nif, Chan *c, void *a, long n, ulong offset)
+{
+ int i, j;
+ Netfile *f;
+ char *p;
+
+ if(c->qid.type&QTDIR)
+ return devdirread(c, a, n, (Dirtab*)nif, 0, netifgen);
+
+ switch(NETTYPE(c->qid.path)){
+ case Ndataqid:
+ f = nif->f[NETID(c->qid.path)];
+ return qread(f->in, a, n);
+ case Nctlqid:
+ return readnum(offset, a, n, NETID(c->qid.path), NUMSIZE);
+ case Nstatqid:
+ p = malloc(READSTR);
+ if(p == nil)
+ return 0;
+ j = snprint(p, READSTR, "in: %d\n", nif->inpackets);
+ j += snprint(p+j, READSTR-j, "link: %d\n", nif->link);
+ j += snprint(p+j, READSTR-j, "out: %d\n", nif->outpackets);
+ j += snprint(p+j, READSTR-j, "crc errs: %d\n", nif->crcs);
+ j += snprint(p+j, READSTR-j, "overflows: %d\n", nif->overflows);
+ j += snprint(p+j, READSTR-j, "soft overflows: %d\n", nif->soverflows);
+ j += snprint(p+j, READSTR-j, "framing errs: %d\n", nif->frames);
+ j += snprint(p+j, READSTR-j, "buffer errs: %d\n", nif->buffs);
+ j += snprint(p+j, READSTR-j, "output errs: %d\n", nif->oerrs);
+ j += snprint(p+j, READSTR-j, "prom: %d\n", nif->prom);
+ j += snprint(p+j, READSTR-j, "mbps: %d\n", nif->mbps);
+ j += snprint(p+j, READSTR-j, "addr: ");
+ for(i = 0; i < nif->alen; i++)
+ j += snprint(p+j, READSTR-j, "%2.2ux", nif->addr[i]);
+ snprint(p+j, READSTR-j, "\n");
+ n = readstr(offset, a, n, p);
+ free(p);
+ return n;
+ case Naddrqid:
+ p = malloc(READSTR);
+ if(p == nil)
+ return 0;
+ j = 0;
+ for(i = 0; i < nif->alen; i++)
+ j += snprint(p+j, READSTR-j, "%2.2ux", nif->addr[i]);
+ n = readstr(offset, a, n, p);
+ free(p);
+ return n;
+ case Ntypeqid:
+ f = nif->f[NETID(c->qid.path)];
+ return readnum(offset, a, n, f->type, NUMSIZE);
+ case Nifstatqid:
+ return 0;
+ }
+ error(Ebadarg);
+ return -1; /* not reached */
+}
+
+Block*
+netifbread(Netif *nif, Chan *c, long n, ulong offset)
+{
+ if((c->qid.type & QTDIR) || NETTYPE(c->qid.path) != Ndataqid)
+ return devbread(c, n, offset);
+
+ return qbread(nif->f[NETID(c->qid.path)]->in, n);
+}
+
+/*
+ * make sure this type isn't already in use on this device
+ */
+static int
+typeinuse(Netif *nif, int type)
+{
+ Netfile *f, **fp, **efp;
+
+ if(type <= 0)
+ return 0;
+
+ efp = &nif->f[nif->nfile];
+ for(fp = nif->f; fp < efp; fp++){
+ f = *fp;
+ if(f == 0)
+ continue;
+ if(f->type == type)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * the devxxx.c that calls us handles writing data, it knows best
+ */
+long
+netifwrite(Netif *nif, Chan *c, void *a, long n)
+{
+ Netfile *f;
+ int type;
+ char *p, buf[64];
+ uchar binaddr[Nmaxaddr];
+
+ if(NETTYPE(c->qid.path) != Nctlqid)
+ error(Eperm);
+
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = 0;
+
+ if(waserror()){
+ qunlock(nif);
+ nexterror();
+ }
+
+ qlock(nif);
+ f = nif->f[NETID(c->qid.path)];
+ if((p = matchtoken(buf, "connect")) != 0){
+ type = atoi(p);
+ if(typeinuse(nif, type))
+ error(Einuse);
+ f->type = type;
+ if(f->type < 0)
+ nif->all++;
+ } else if(matchtoken(buf, "promiscuous")){
+ if(f->prom == 0){
+ if(nif->prom == 0 && nif->promiscuous != nil)
+ nif->promiscuous(nif->arg, 1);
+ f->prom = 1;
+ nif->prom++;
+ }
+ } else if((p = matchtoken(buf, "scanbs")) != 0){
+ /* scan for base stations */
+ if(f->scan == 0){
+ type = atoi(p);
+ if(type < 5)
+ type = 5;
+ if(nif->scanbs != nil)
+ nif->scanbs(nif->arg, type);
+ f->scan = type;
+ nif->scan++;
+ }
+ } else if(matchtoken(buf, "bridge")){
+ f->bridge = 1;
+ } else if(matchtoken(buf, "headersonly")){
+ f->headersonly = 1;
+ } else if((p = matchtoken(buf, "addmulti")) != 0){
+ if(parseaddr(binaddr, p, nif->alen) < 0)
+ error("bad address");
+ p = netmulti(nif, f, binaddr, 1);
+ if(p)
+ error(p);
+ } else if((p = matchtoken(buf, "remmulti")) != 0){
+ if(parseaddr(binaddr, p, nif->alen) < 0)
+ error("bad address");
+ p = netmulti(nif, f, binaddr, 0);
+ if(p)
+ error(p);
+ } else
+ n = -1;
+ qunlock(nif);
+ poperror();
+ return n;
+}
+
+int
+netifwstat(Netif *nif, Chan *c, uchar *db, int n)
+{
+ Dir *dir;
+ Netfile *f;
+ int m;
+
+ f = nif->f[NETID(c->qid.path)];
+ if(f == 0)
+ error(Enonexist);
+
+ if(netown(f, up->env->user, OWRITE) < 0)
+ error(Eperm);
+
+ dir = smalloc(sizeof(Dir)+n);
+ m = convM2D(db, n, &dir[0], (char*)&dir[1]);
+ if(m == 0){
+ free(dir);
+ error(Eshortstat);
+ }
+ if(!emptystr(dir[0].uid))
+ strncpy(f->owner, dir[0].uid, KNAMELEN);
+ if(dir[0].mode != ~0UL)
+ f->mode = dir[0].mode;
+ free(dir);
+ return m;
+}
+
+int
+netifstat(Netif *nif, Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, (Dirtab *)nif, 0, netifgen);
+}
+
+void
+netifclose(Netif *nif, Chan *c)
+{
+ Netfile *f;
+ int t;
+ Netaddr *ap;
+
+ if((c->flag & COPEN) == 0)
+ return;
+
+ t = NETTYPE(c->qid.path);
+ if(t != Ndataqid && t != Nctlqid)
+ return;
+
+ f = nif->f[NETID(c->qid.path)];
+ qlock(f);
+ if(--(f->inuse) == 0){
+ if(f->prom){
+ qlock(nif);
+ if(--(nif->prom) == 0 && nif->promiscuous != nil)
+ nif->promiscuous(nif->arg, 0);
+ qunlock(nif);
+ f->prom = 0;
+ }
+ if(f->scan){
+ qlock(nif);
+ if(--(nif->scan) == 0 && nif->scanbs != nil)
+ nif->scanbs(nif->arg, 0);
+ qunlock(nif);
+ f->prom = 0;
+ f->scan = 0;
+ }
+ if(f->nmaddr){
+ qlock(nif);
+ t = 0;
+ for(ap = nif->maddr; ap; ap = ap->next){
+ if(f->maddr[t/8] & (1<<(t%8)))
+ netmulti(nif, f, ap->addr, 0);
+ }
+ qunlock(nif);
+ f->nmaddr = 0;
+ }
+ if(f->type < 0){
+ qlock(nif);
+ --(nif->all);
+ qunlock(nif);
+ }
+ f->owner[0] = 0;
+ f->type = 0;
+ f->bridge = 0;
+ f->headersonly = 0;
+ qclose(f->in);
+ }
+ qunlock(f);
+}
+
+Lock netlock;
+
+static int
+netown(Netfile *p, char *o, int omode)
+{
+ static int access[] = { 0400, 0200, 0600, 0100 };
+ int mode;
+ int t;
+
+ lock(&netlock);
+ if(*p->owner){
+ if(strncmp(o, p->owner, KNAMELEN) == 0) /* User */
+ mode = p->mode;
+ else if(strncmp(o, eve, KNAMELEN) == 0) /* Bootes is group */
+ mode = p->mode<<3;
+ else
+ mode = p->mode<<6; /* Other */
+
+ t = access[omode&3];
+ if((t & mode) == t){
+ unlock(&netlock);
+ return 0;
+ } else {
+ unlock(&netlock);
+ return -1;
+ }
+ }
+ strncpy(p->owner, o, KNAMELEN);
+ p->mode = 0660;
+ unlock(&netlock);
+ return 0;
+}
+
+/*
+ * Increment the reference count of a network device.
+ * If id < 0, return an unused ether device.
+ */
+static int
+openfile(Netif *nif, int id)
+{
+ Netfile *f, **fp, **efp;
+
+ if(id >= 0){
+ f = nif->f[id];
+ if(f == 0)
+ error(Enodev);
+ qlock(f);
+ qreopen(f->in);
+ f->inuse++;
+ qunlock(f);
+ return id;
+ }
+
+ qlock(nif);
+ if(waserror()){
+ qunlock(nif);
+ nexterror();
+ }
+ efp = &nif->f[nif->nfile];
+ for(fp = nif->f; fp < efp; fp++){
+ f = *fp;
+ if(f == 0){
+ f = malloc(sizeof(Netfile));
+ if(f == 0)
+ exhausted("memory");
+ f->in = qopen(nif->limit, Qmsg, 0, 0);
+ if(f->in == nil){
+ free(f);
+ exhausted("memory");
+ }
+ *fp = f;
+ qlock(f);
+ } else {
+ qlock(f);
+ if(f->inuse){
+ qunlock(f);
+ continue;
+ }
+ }
+ f->inuse = 1;
+ qreopen(f->in);
+ netown(f, up->env->user, 0);
+ qunlock(f);
+ qunlock(nif);
+ poperror();
+ return fp - nif->f;
+ }
+ error(Enodev);
+ return -1; /* not reached */
+}
+
+/*
+ * look for a token starting a string,
+ * return a pointer to first non-space char after it
+ */
+static char*
+matchtoken(char *p, char *token)
+{
+ int n;
+
+ n = strlen(token);
+ if(strncmp(p, token, n))
+ return 0;
+ p += n;
+ if(*p == 0)
+ return p;
+ if(*p != ' ' && *p != '\t' && *p != '\n')
+ return 0;
+ while(*p == ' ' || *p == '\t' || *p == '\n')
+ p++;
+ return p;
+}
+
+static ulong
+hash(uchar *a, int len)
+{
+ ulong sum = 0;
+
+ while(len-- > 0)
+ sum = (sum << 1) + *a++;
+ return sum%Nmhash;
+}
+
+int
+activemulti(Netif *nif, uchar *addr, int alen)
+{
+ Netaddr *hp;
+
+ for(hp = nif->mhash[hash(addr, alen)]; hp; hp = hp->hnext)
+ if(memcmp(addr, hp->addr, alen) == 0){
+ if(hp->ref)
+ return 1;
+ else
+ break;
+ }
+ return 0;
+}
+
+static int
+parseaddr(uchar *to, char *from, int alen)
+{
+ char nip[4];
+ char *p;
+ int i;
+
+ p = from;
+ for(i = 0; i < alen; i++){
+ if(*p == 0)
+ return -1;
+ nip[0] = *p++;
+ if(*p == 0)
+ return -1;
+ nip[1] = *p++;
+ nip[2] = 0;
+ to[i] = strtoul(nip, 0, 16);
+ if(*p == ':')
+ p++;
+ }
+ return 0;
+}
+
+/*
+ * keep track of multicast addresses
+ */
+static char*
+netmulti(Netif *nif, Netfile *f, uchar *addr, int add)
+{
+ Netaddr **l, *ap;
+ int i;
+ ulong h;
+
+ if(nif->multicast == nil)
+ return "interface does not support multicast";
+
+ l = &nif->maddr;
+ i = 0;
+ for(ap = *l; ap; ap = *l){
+ if(memcmp(addr, ap->addr, nif->alen) == 0)
+ break;
+ i++;
+ l = &ap->next;
+ }
+
+ if(add){
+ if(ap == 0){
+ *l = ap = smalloc(sizeof(*ap));
+ memmove(ap->addr, addr, nif->alen);
+ ap->next = 0;
+ ap->ref = 1;
+ h = hash(addr, nif->alen);
+ ap->hnext = nif->mhash[h];
+ nif->mhash[h] = ap;
+ } else {
+ ap->ref++;
+ }
+ if(ap->ref == 1){
+ nif->nmaddr++;
+ nif->multicast(nif->arg, addr, 1);
+ }
+ if(i < 8*sizeof(f->maddr)){
+ if((f->maddr[i/8] & (1<<(i%8))) == 0)
+ f->nmaddr++;
+ f->maddr[i/8] |= 1<<(i%8);
+ }
+ } else {
+ if(ap == 0 || ap->ref == 0)
+ return 0;
+ ap->ref--;
+ if(ap->ref == 0){
+ nif->nmaddr--;
+ nif->multicast(nif->arg, addr, 0);
+ }
+ if(i < 8*sizeof(f->maddr)){
+ if((f->maddr[i/8] & (1<<(i%8))) != 0)
+ f->nmaddr--;
+ f->maddr[i/8] &= ~(1<<(i%8));
+ }
+ }
+ return 0;
+}
diff --git a/os/port/netif.h b/os/port/netif.h
new file mode 100644
index 00000000..6fbe6b82
--- /dev/null
+++ b/os/port/netif.h
@@ -0,0 +1,134 @@
+typedef struct Etherpkt Etherpkt;
+typedef struct Netaddr Netaddr;
+typedef struct Netfile Netfile;
+typedef struct Netif Netif;
+
+enum
+{
+ Nmaxaddr= 64,
+ Nmhash= 31,
+
+ Ncloneqid= 1,
+ Naddrqid,
+ N2ndqid,
+ N3rdqid,
+ Ndataqid,
+ Nctlqid,
+ Nstatqid,
+ Ntypeqid,
+ Nifstatqid,
+};
+
+/*
+ * Macros to manage Qid's used for multiplexed devices
+ */
+#define NETTYPE(x) (((ulong)x)&0x1f)
+#define NETID(x) ((((ulong)x))>>5)
+#define NETQID(i,t) ((((ulong)i)<<5)|(t))
+
+/*
+ * one per multiplexed connection
+ */
+struct Netfile
+{
+ QLock;
+
+ int inuse;
+ ulong mode;
+ char owner[KNAMELEN];
+
+ int type; /* multiplexor type */
+ int prom; /* promiscuous mode */
+ int scan; /* base station scanning interval */
+ int bridge; /* bridge mode */
+ int headersonly; /* headers only - no data */
+ uchar maddr[8]; /* bitmask of multicast addresses requested */
+ int nmaddr; /* number of multicast addresses */
+
+ Queue *in; /* input buffer */
+};
+
+/*
+ * a network address
+ */
+struct Netaddr
+{
+ Netaddr *next; /* allocation chain */
+ Netaddr *hnext;
+ uchar addr[Nmaxaddr];
+ int ref;
+};
+
+/*
+ * a network interface
+ */
+struct Netif
+{
+ QLock;
+
+ /* multiplexing */
+ char name[KNAMELEN]; /* for top level directory */
+ int nfile; /* max number of Netfiles */
+ Netfile **f;
+
+ /* about net */
+ int limit; /* flow control */
+ int alen; /* address length */
+ int mbps; /* megabits per sec */
+ int link; /* link status */
+ uchar addr[Nmaxaddr];
+ uchar bcast[Nmaxaddr];
+ Netaddr *maddr; /* known multicast addresses */
+ int nmaddr; /* number of known multicast addresses */
+ Netaddr *mhash[Nmhash]; /* hash table of multicast addresses */
+ int prom; /* number of promiscuous opens */
+ int scan; /* number of base station scanners */
+ int all; /* number of -1 multiplexors */
+
+ /* statistics */
+ int misses;
+ int inpackets;
+ int outpackets;
+ int crcs; /* input crc errors */
+ int oerrs; /* output errors */
+ int frames; /* framing errors */
+ int overflows; /* packet overflows */
+ int buffs; /* buffering errors */
+ int soverflows; /* software overflow */
+
+ /* routines for touching the hardware */
+ void *arg;
+ void (*promiscuous)(void*, int);
+ void (*multicast)(void*, uchar*, int);
+ void (*scanbs)(void*, uint); /* scan for base stations */
+};
+
+void netifinit(Netif*, char*, int, ulong);
+Walkqid* netifwalk(Netif*, Chan*, Chan*, char **, int);
+Chan* netifopen(Netif*, Chan*, int);
+void netifclose(Netif*, Chan*);
+long netifread(Netif*, Chan*, void*, long, ulong);
+Block* netifbread(Netif*, Chan*, long, ulong);
+long netifwrite(Netif*, Chan*, void*, long);
+int netifwstat(Netif*, Chan*, uchar*, int);
+int netifstat(Netif*, Chan*, uchar*, int);
+int activemulti(Netif*, uchar*, int);
+
+/*
+ * Ethernet specific
+ */
+enum
+{
+ Eaddrlen= 6,
+ ETHERMINTU = 60, /* minimum transmit size */
+ ETHERMAXTU = 1514, /* maximum transmit size */
+ ETHERHDRSIZE = 14, /* size of an ethernet header */
+};
+
+struct Etherpkt
+{
+ uchar d[Eaddrlen];
+ uchar s[Eaddrlen];
+ uchar type[2];
+ uchar data[1500];
+};
diff --git a/os/port/nocache.c b/os/port/nocache.c
new file mode 100644
index 00000000..0eed67e1
--- /dev/null
+++ b/os/port/nocache.c
@@ -0,0 +1,49 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+/*
+ * stubs when no devmnt cache
+ */
+void
+cinit(void)
+{
+}
+
+void
+copen(Chan *c)
+{
+ c->flag &= ~CCACHE;
+}
+
+int
+cread(Chan *c, uchar *b, int n, vlong off)
+{
+ USED(c);
+ USED(b);
+ USED(n);
+ USED(off);
+ return 0;
+}
+
+void
+cwrite(Chan *c, uchar *buf, int n, vlong off)
+{
+ USED(c);
+ USED(buf);
+ USED(n);
+ USED(off);
+}
+
+void
+cupdate(Chan *c, uchar *buf, int n, vlong off)
+{
+ USED(c);
+ USED(buf);
+ USED(n);
+ USED(off);
+}
+
diff --git a/os/port/nodynld.c b/os/port/nodynld.c
new file mode 100644
index 00000000..2ba8c7c2
--- /dev/null
+++ b/os/port/nodynld.c
@@ -0,0 +1,48 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include <a.out.h>
+#include <dynld.h>
+
+/*
+ * null kernel interface to dynld, to stop libinterp moaning
+ */
+
+void*
+dynimport(Dynobj*, char*, ulong)
+{
+ return nil;
+}
+
+void
+dynobjfree(Dynobj*)
+{
+}
+
+Dynobj*
+kdynloadfd(int fd, Dynsym *tab, int ntab)
+{
+ USED(fd, tab, ntab);
+ return nil;
+}
+
+int
+kdynloadable(int)
+{
+ return 0;
+}
+
+Dynobj*
+dynld(int)
+{
+ return nil;
+}
+
+int
+dynldable(int)
+{
+ return 0;
+}
diff --git a/os/port/noenv.c b/os/port/noenv.c
new file mode 100644
index 00000000..446945ac
--- /dev/null
+++ b/os/port/noenv.c
@@ -0,0 +1,33 @@
+/*
+ * use this when devenv.c not used
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+/*
+ * null kernel interface
+ */
+Egrp*
+newegrp(void)
+{
+ return nil;
+}
+
+void
+closeegrp(Egrp*)
+{
+}
+
+void
+egrpcpy(Egrp*, Egrp*)
+{
+}
+
+void
+ksetenv(char*, char*, int)
+{
+}
diff --git a/os/port/noscreen.c b/os/port/noscreen.c
new file mode 100644
index 00000000..af76178e
--- /dev/null
+++ b/os/port/noscreen.c
@@ -0,0 +1,9 @@
+void
+screeninit(void)
+{
+}
+
+void
+screenrotate(int)
+{
+}
diff --git a/os/port/parse.c b/os/port/parse.c
new file mode 100644
index 00000000..9d59b567
--- /dev/null
+++ b/os/port/parse.c
@@ -0,0 +1,114 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+/*
+ * Generous estimate of number of fields, including terminal nil pointer
+ */
+static int
+ncmdfield(char *p, int n)
+{
+ int white, nwhite;
+ char *ep;
+ int nf;
+
+ if(p == nil)
+ return 1;
+
+ nf = 0;
+ ep = p+n;
+ white = 1; /* first text will start field */
+ while(p < ep){
+ nwhite = (strchr(" \t\r\n", *p++ & 0xFF) != 0); /* UTF is irrelevant */
+ if(white && !nwhite) /* beginning of field */
+ nf++;
+ white = nwhite;
+ }
+ return nf+1; /* +1 for nil */
+}
+
+/*
+ * parse a command written to a device
+ */
+Cmdbuf*
+parsecmd(char *p, int n)
+{
+ Cmdbuf *volatile cb;
+ int nf;
+ char *sp;
+
+ nf = ncmdfield(p, n);
+
+ /* allocate Cmdbuf plus string pointers plus copy of string including \0 */
+ sp = smalloc(sizeof(*cb) + nf * sizeof(char*) + n + 1);
+ cb = (Cmdbuf*)sp;
+ cb->f = (char**)(&cb[1]);
+ cb->buf = (char*)(&cb->f[nf]);
+
+ if(up!=nil && waserror()){
+ free(cb);
+ nexterror();
+ }
+ memmove(cb->buf, p, n);
+ if(up != nil)
+ poperror();
+
+ /* dump new line and null terminate */
+ if(n > 0 && cb->buf[n-1] == '\n')
+ n--;
+ cb->buf[n] = '\0';
+
+ cb->nf = tokenize(cb->buf, cb->f, nf-1);
+ cb->f[cb->nf] = nil;
+
+ return cb;
+}
+
+/*
+ * Reconstruct original message, for error diagnostic
+ */
+void
+cmderror(Cmdbuf *cb, char *s)
+{
+ int i;
+ char *p, *e;
+
+ p = up->genbuf;
+ e = p+ERRMAX-10;
+ p = seprint(p, e, "%s \"", s);
+ for(i=0; i<cb->nf; i++){
+ if(i > 0)
+ p = seprint(p, e, " ");
+ p = seprint(p, e, "%q", cb->f[i]);
+ }
+ strcpy(p, "\"");
+ error(up->genbuf);
+}
+
+/*
+ * Look up entry in table
+ */
+Cmdtab*
+lookupcmd(Cmdbuf *cb, Cmdtab *ctab, int nctab)
+{
+ int i;
+ Cmdtab *ct;
+
+ if(cb->nf == 0)
+ error("empty control message");
+
+ for(ct = ctab, i=0; i<nctab; i++, ct++){
+ if(strcmp(ct->cmd, "*") !=0) /* wildcard always matches */
+ if(strcmp(ct->cmd, cb->f[0]) != 0)
+ continue;
+ if(ct->narg != 0 && ct->narg != cb->nf)
+ cmderror(cb, Ecmdargs);
+ return ct;
+ }
+
+ cmderror(cb, "unknown control message");
+ return nil;
+}
diff --git a/os/port/pgrp.c b/os/port/pgrp.c
new file mode 100644
index 00000000..6b2d7d5c
--- /dev/null
+++ b/os/port/pgrp.c
@@ -0,0 +1,278 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+static Ref pgrpid;
+static Ref mountid;
+
+Pgrp*
+newpgrp(void)
+{
+ Pgrp *p;
+
+ p = smalloc(sizeof(Pgrp));
+ p->ref = 1;
+ p->pgrpid = incref(&pgrpid);
+ p->pin = Nopin;
+ p->progmode = 0644;
+ p->privatemem = 0;
+ return p;
+}
+
+void
+closepgrp(Pgrp *p)
+{
+ Mhead **h, **e, *f, *next;
+
+ if(p == nil || decref(p) != 0)
+ return;
+
+ wlock(&p->ns);
+ p->pgrpid = -1;
+
+ e = &p->mnthash[MNTHASH];
+ for(h = p->mnthash; h < e; h++) {
+ for(f = *h; f; f = next) {
+ wlock(&f->lock);
+ cclose(f->from);
+ mountfree(f->mount);
+ f->mount = nil;
+ next = f->hash;
+ wunlock(&f->lock);
+ putmhead(f);
+ }
+ }
+ wunlock(&p->ns);
+ cclose(p->dot);
+ cclose(p->slash);
+ free(p);
+}
+
+void
+pgrpinsert(Mount **order, Mount *m)
+{
+ Mount *f;
+
+ m->order = 0;
+ if(*order == 0) {
+ *order = m;
+ return;
+ }
+ for(f = *order; f; f = f->order) {
+ if(m->mountid < f->mountid) {
+ m->order = f;
+ *order = m;
+ return;
+ }
+ order = &f->order;
+ }
+ *order = m;
+}
+
+/*
+ * pgrpcpy MUST preserve the mountid allocation order of the parent group
+ */
+void
+pgrpcpy(Pgrp *to, Pgrp *from)
+{
+ int i;
+ Mount *n, *m, **link, *order;
+ Mhead *f, **tom, **l, *mh;
+
+ wlock(&from->ns);
+ order = 0;
+ tom = to->mnthash;
+ for(i = 0; i < MNTHASH; i++) {
+ l = tom++;
+ for(f = from->mnthash[i]; f; f = f->hash) {
+ rlock(&f->lock);
+ mh = malloc(sizeof(Mhead));
+ if(mh == nil) {
+ runlock(&f->lock);
+ wunlock(&from->ns);
+ error(Enomem);
+ }
+ mh->from = f->from;
+ mh->ref = 1;
+ incref(mh->from);
+ *l = mh;
+ l = &mh->hash;
+ link = &mh->mount;
+ for(m = f->mount; m; m = m->next) {
+ n = newmount(mh, m->to, m->mflag, m->spec);
+ if(n == nil) {
+ runlock(&f->lock);
+ wunlock(&from->ns);
+ error(Enomem);
+ }
+ m->copy = n;
+ pgrpinsert(&order, m);
+ *link = n;
+ link = &n->next;
+ }
+ runlock(&f->lock);
+ }
+ }
+ /*
+ * Allocate mount ids in the same sequence as the parent group
+ */
+ lock(&mountid.l);
+ for(m = order; m; m = m->order)
+ m->copy->mountid = mountid.ref++;
+ unlock(&mountid.l);
+
+ to->pin = from->pin;
+
+ to->slash = cclone(from->slash);
+ to->dot = cclone(from->dot);
+ to->nodevs = from->nodevs;
+
+ wunlock(&from->ns);
+}
+
+Fgrp*
+newfgrp(Fgrp *old)
+{
+ Fgrp *new;
+ int n;
+
+ new = smalloc(sizeof(Fgrp));
+ new->ref = 1;
+ n = DELTAFD;
+ if(old != nil){
+ lock(old);
+ if(old->maxfd >= n)
+ n = (old->maxfd+1 + DELTAFD-1)/DELTAFD * DELTAFD;
+ new->maxfd = old->maxfd;
+ unlock(old);
+ }
+ new->nfd = n;
+ new->fd = smalloc(n*sizeof(Chan*));
+ return new;
+}
+
+Fgrp*
+dupfgrp(Fgrp *f)
+{
+ int i;
+ Chan *c;
+ Fgrp *new;
+ int n;
+
+ new = smalloc(sizeof(Fgrp));
+ new->ref = 1;
+ lock(f);
+ n = DELTAFD;
+ if(f->maxfd >= n)
+ n = (f->maxfd+1 + DELTAFD-1)/DELTAFD * DELTAFD;
+ new->nfd = n;
+ new->fd = malloc(n*sizeof(Chan*));
+ if(new->fd == nil){
+ unlock(f);
+ free(new);
+ error(Enomem);
+ }
+ new->maxfd = f->maxfd;
+ new->minfd = f->minfd;
+ for(i = 0; i <= f->maxfd; i++) {
+ if(c = f->fd[i]){
+ incref(c);
+ new->fd[i] = c;
+ }
+ }
+ unlock(f);
+
+ return new;
+}
+
+void
+closefgrp(Fgrp *f)
+{
+ int i;
+ Chan *c;
+
+ if(f == nil || decref(f) != 0)
+ return;
+
+ for(i = 0; i <= f->maxfd; i++)
+ if(c = f->fd[i])
+ cclose(c);
+
+ free(f->fd);
+ free(f);
+}
+
+Mount*
+newmount(Mhead *mh, Chan *to, int flag, char *spec)
+{
+ Mount *m;
+
+ m = smalloc(sizeof(Mount));
+ m->to = to;
+ m->head = mh;
+ incref(to);
+ m->mountid = incref(&mountid);
+ m->mflag = flag;
+ if(spec != 0)
+ kstrdup(&m->spec, spec);
+
+ return m;
+}
+
+void
+mountfree(Mount *m)
+{
+ Mount *f;
+
+ while(m) {
+ f = m->next;
+ cclose(m->to);
+ m->mountid = 0;
+ free(m->spec);
+ free(m);
+ m = f;
+ }
+}
+
+void
+resrcwait(char *reason)
+{
+ char *p;
+
+ if(up == 0)
+ panic("resrcwait");
+
+ p = up->psstate;
+ if(reason) {
+ up->psstate = reason;
+ print("%s\n", reason);
+ }
+
+ tsleep(&up->sleep, return0, 0, 300);
+ up->psstate = p;
+}
+
+void
+closesigs(Skeyset *s)
+{
+ int i;
+
+ if(s == nil || decref(s) != 0)
+ return;
+ for(i=0; i<s->nkey; i++)
+ freeskey(s->keys[i]);
+ free(s);
+}
+
+void
+freeskey(Signerkey *key)
+{
+ if(key == nil || decref(key) != 0)
+ return;
+ free(key->owner);
+ (*key->pkfree)(key->pk);
+ free(key);
+}
diff --git a/os/port/portbreak.c b/os/port/portbreak.c
new file mode 100644
index 00000000..d64f40f6
--- /dev/null
+++ b/os/port/portbreak.c
@@ -0,0 +1,161 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "portfns.h"
+#include "ureg.h"
+#include "../port/error.h"
+
+//
+// These bits used to be in port/devdbg but were removed in
+// order to allow for using hardware debug features on certain
+// architectures
+//
+
+extern void breakset(Bkpt *b);
+extern void breakrestore(Bkpt *b);
+extern Bkpt* breakclear(int id);
+extern void breaknotify(Bkpt *b, Proc *p);
+extern int breakmatch(BkptCond *cond, Ureg *ur, Proc *p);
+
+void skipfree(Bkpt *b);
+Bkpt*newskip(ulong addr, Bkpt *skipb, Proc *skipp);
+Bkpt *skipalloc;
+extern Bkpt *breakpoints;
+typedef struct SkipArg SkipArg;
+struct SkipArg
+{
+ Bkpt *b;
+ Proc *p;
+};
+
+void
+skiphandler(Bkpt *b)
+{
+ SkipArg *a = b->aux;
+ Bkpt *l;
+
+ if(breakclear(b->id) == nil)
+ panic("skiphandler: breakclear() failed");
+ breakrestore(a->b);
+ l = a->b->link;
+ while(l != nil) {
+ breakrestore(l);
+ l = l->link;
+ }
+ skipfree(b);
+ a->p->dbgstop = 0; // Whoo!
+ if(a->p->state == Stopped)
+ ready(a->p);
+}
+
+Bkpt*
+newskip(ulong addr, Bkpt *skipb, Proc *skipp)
+{
+ Bkpt *b;
+ SkipArg *a;
+
+ b = skipalloc;
+ if(b == nil)
+ panic("newskip(): no free skips\n");
+ skipalloc = b->next;
+
+ b->addr = addr;
+ b->conditions->val = addr;
+ b->link = nil;
+ a = b->aux;
+ a->b = skipb;
+ a->p = skipp;
+
+ return b;
+}
+
+void
+skipfree(Bkpt *b)
+{
+ b->next = skipalloc;
+ skipalloc = b;
+}
+
+//
+// Called from the exception handler when a breakpoint instruction has been
+// hit. This cannot not be called unless at least one breakpoint with this
+// address is in the list of breakpoints. (All breakpoint notifications must
+// previously have been set via setbreak())
+//
+// foreach breakpoint in list
+// if breakpoint matches conditions
+// notify the break handler
+// if no breakpoints matched the conditions
+// pick a random breakpoint set to this address
+//
+// set a breakpoint at the next instruction to be executed,
+// and pass the current breakpoint to the "skiphandler"
+//
+// clear the current breakpoint
+//
+// Tell the scheduler to stop scheduling, so the caller is
+// guaranteed to execute the instruction, followed by the
+// added breakpoint.
+//
+//
+int
+breakhit(Ureg *ur, Proc *p)
+{
+ Bkpt *b;
+ int nmatched;
+ Bkpt *skip;
+
+ nmatched = 0;
+ for(b = breakpoints; b != nil; b = b->next) {
+ if(breakmatch(b->conditions, ur, p)) {
+ breaknotify(b, p);
+ ++nmatched;
+ }
+ }
+
+ if(nmatched)
+ return BrkSched;
+
+ skip = nil;
+ for(b = breakpoints; b != nil; b = b->next) {
+ if(b->addr == ur->pc) {
+ if(breakclear(b->id) == nil)
+ panic("breakhit: breakclear() failed");
+
+ if(skip == nil)
+ skip = newskip(machnextaddr(ur), b, p);
+ else {
+ b->link = skip->link;
+ skip->link = b;
+ }
+ }
+ }
+ if(skip == nil)
+ return BrkSched;
+ breakset(skip);
+ return BrkNoSched;
+}
+
+void
+portbreakinit(void)
+{
+ Bkpt *b;
+ int i;
+
+ skipalloc = mallocz(conf.nproc*(sizeof(Bkpt)+sizeof(BkptCond)+sizeof(SkipArg)), 1);
+ if(skipalloc == nil)
+ error(Enomem);
+
+ b = skipalloc;
+ for(i=0; i < conf.nproc-1; i++) {
+ b->id = -(i+1);
+ b->conditions = (BkptCond*)((uchar*)b + sizeof(Bkpt));
+ b->conditions->op = 'b';
+ b->handler = skiphandler;
+ b->aux = (SkipArg*)((uchar*)b+sizeof(Bkpt)+sizeof(BkptCond));
+ b->next = (Bkpt*)((uchar*)b+sizeof(Bkpt)+sizeof(BkptCond)+sizeof(SkipArg));
+ b = b->next;
+ }
+ b->next = nil;
+}
diff --git a/os/port/portclock.c b/os/port/portclock.c
new file mode 100644
index 00000000..04d1b110
--- /dev/null
+++ b/os/port/portclock.c
@@ -0,0 +1,277 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+
+struct Timers
+{
+ Lock;
+ Timer *head;
+};
+
+static Timers timers[MAXMACH];
+
+ulong intrcount[MAXMACH];
+ulong fcallcount[MAXMACH];
+
+static uvlong
+tadd(Timers *tt, Timer *nt)
+{
+ Timer *t, **last;
+
+ /* Called with tt locked */
+ assert(nt->tt == nil);
+ switch(nt->tmode){
+ default:
+ panic("timer");
+ break;
+ case Trelative:
+ assert(nt->tns > 0);
+ nt->twhen = fastticks(nil) + ns2fastticks(nt->tns);
+ break;
+ case Tabsolute:
+ nt->twhen = tod2fastticks(nt->tns);
+ break;
+ case Tperiodic:
+ assert(nt->tns >= 100000); /* At least 100 µs period */
+ if(nt->twhen == 0){
+ /* look for another timer at same frequency for combining */
+ for(t = tt->head; t; t = t->tnext){
+ if(t->tmode == Tperiodic && t->tns == nt->tns)
+ break;
+ }
+ if (t)
+ nt->twhen = t->twhen;
+ else
+ nt->twhen = fastticks(nil);
+ }
+ nt->twhen += ns2fastticks(nt->tns);
+ break;
+ }
+
+ for(last = &tt->head; t = *last; last = &t->tnext){
+ if(t->twhen > nt->twhen)
+ break;
+ }
+ nt->tnext = *last;
+ *last = nt;
+ nt->tt = tt;
+ if(last == &tt->head)
+ return nt->twhen;
+ return 0;
+}
+
+static uvlong
+tdel(Timer *dt)
+{
+
+ Timer *t, **last;
+ Timers *tt;
+
+ tt = dt->tt;
+ if (tt == nil)
+ return 0;
+ for(last = &tt->head; t = *last; last = &t->tnext){
+ if(t == dt){
+ assert(dt->tt);
+ dt->tt = nil;
+ *last = t->tnext;
+ break;
+ }
+ }
+ if(last == &tt->head && tt->head)
+ return tt->head->twhen;
+ return 0;
+}
+
+/* add or modify a timer */
+void
+timeradd(Timer *nt)
+{
+ Timers *tt;
+ vlong when;
+
+ if (nt->tmode == Tabsolute){
+ when = todget(nil);
+ if (nt->tns <= when){
+ // if (nt->tns + MS2NS(10) <= when) /* small deviations will happen */
+ // print("timeradd (%lld %lld) %lld too early 0x%lux\n",
+ // when, nt->tns, when - nt->tns, getcallerpc(&nt));
+ nt->tns = when;
+ }
+ }
+ /* Must lock Timer struct before Timers struct */
+ ilock(nt);
+ if(tt = nt->tt){
+ ilock(tt);
+ tdel(nt);
+ iunlock(tt);
+ }
+ tt = &timers[m->machno];
+ ilock(tt);
+ when = tadd(tt, nt);
+ if(when)
+ timerset(when);
+ iunlock(tt);
+ iunlock(nt);
+}
+
+
+void
+timerdel(Timer *dt)
+{
+ Timers *tt;
+ uvlong when;
+
+ ilock(dt);
+ if(tt = dt->tt){
+ ilock(tt);
+ when = tdel(dt);
+ if(when && tt == &timers[m->machno])
+ timerset(tt->head->twhen);
+ iunlock(tt);
+ }
+ iunlock(dt);
+}
+
+void
+hzclock(Ureg *ur)
+{
+ m->ticks++;
+ if(m->proc)
+ m->proc->pc = ur->pc;
+
+ kmapinval();
+
+ if(kproftick != nil)
+ kproftick(ur->pc);
+
+ if((active.machs&(1<<m->machno)) == 0)
+ return;
+
+ if(active.exiting) {
+ print("someone's exiting\n");
+ exit(0);
+ }
+
+ checkalarms();
+
+ if(up && up->state == Running){
+ if(anyready()){
+ sched();
+ splhi();
+ }
+ }
+}
+
+void
+timerintr(Ureg *u, uvlong)
+{
+ Timer *t;
+ Timers *tt;
+ uvlong when, now;
+ int callhzclock;
+ static int sofar;
+
+ intrcount[m->machno]++;
+ callhzclock = 0;
+ tt = &timers[m->machno];
+ now = fastticks(nil);
+ ilock(tt);
+ while(t = tt->head){
+ /*
+ * No need to ilock t here: any manipulation of t
+ * requires tdel(t) and this must be done with a
+ * lock to tt held. We have tt, so the tdel will
+ * wait until we're done
+ */
+ when = t->twhen;
+ if(when > now){
+ timerset(when);
+ iunlock(tt);
+ if(callhzclock)
+ hzclock(u);
+ return;
+ }
+ tt->head = t->tnext;
+ assert(t->tt == tt);
+ t->tt = nil;
+ fcallcount[m->machno]++;
+ iunlock(tt);
+ if(t->tf)
+ (*t->tf)(u, t);
+ else
+ callhzclock++;
+ ilock(tt);
+ if(t->tmode == Tperiodic)
+ tadd(tt, t);
+ }
+ iunlock(tt);
+}
+
+void
+timersinit(void)
+{
+ Timer *t;
+
+ todinit();
+ t = malloc(sizeof(*t));
+ t->tmode = Tperiodic;
+ t->tt = nil;
+ t->tns = 1000000000/HZ;
+ t->tf = nil;
+ timeradd(t);
+}
+
+Timer*
+addclock0link(void (*f)(void), int ms)
+{
+ Timer *nt;
+ uvlong when;
+
+ /* Synchronize to hztimer if ms is 0 */
+ nt = malloc(sizeof(Timer));
+ if(ms == 0)
+ ms = 1000/HZ;
+ nt->tns = (vlong)ms*1000000LL;
+ nt->tmode = Tperiodic;
+ nt->tt = nil;
+ nt->tf = (void (*)(Ureg*, Timer*))f;
+
+ ilock(&timers[0]);
+ when = tadd(&timers[0], nt);
+ if(when)
+ timerset(when);
+ iunlock(&timers[0]);
+ return nt;
+}
+
+/*
+ * This tk2ms avoids overflows that the macro version is prone to.
+ * It is a LOT slower so shouldn't be used if you're just converting
+ * a delta.
+ */
+ulong
+tk2ms(ulong ticks)
+{
+ uvlong t, hz;
+
+ t = ticks;
+ hz = HZ;
+ t *= 1000L;
+ t = t/hz;
+ ticks = t;
+ return ticks;
+}
+
+ulong
+ms2tk(ulong ms)
+{
+ /* avoid overflows at the cost of precision */
+ if(ms >= 1000000000/HZ)
+ return (ms/1000)*HZ;
+ return (ms*HZ+500)/1000;
+}
diff --git a/os/port/portdat.h b/os/port/portdat.h
new file mode 100644
index 00000000..103961e0
--- /dev/null
+++ b/os/port/portdat.h
@@ -0,0 +1,673 @@
+typedef struct Alarms Alarms;
+typedef struct Block Block;
+typedef struct Bkpt Bkpt;
+typedef struct BkptCond BkptCond;
+typedef struct Chan Chan;
+typedef struct Cmdbuf Cmdbuf;
+typedef struct Cmdtab Cmdtab;
+typedef struct Cname Cname;
+typedef struct Crypt Crypt;
+typedef struct Dev Dev;
+typedef struct DevConf DevConf;
+typedef struct Dirtab Dirtab;
+typedef struct Edf Edf;
+typedef struct Egrp Egrp;
+typedef struct Evalue Evalue;
+typedef struct Fgrp Fgrp;
+typedef struct List List;
+typedef struct Log Log;
+typedef struct Logflag Logflag;
+typedef struct Mntcache Mntcache;
+typedef struct Mntparam Mntparam;
+typedef struct Mount Mount;
+typedef struct Mntrpc Mntrpc;
+typedef struct Mntwalk Mntwalk;
+typedef struct Mnt Mnt;
+typedef struct Mhead Mhead;
+typedef struct Osenv Osenv;
+typedef struct Pgrp Pgrp;
+typedef struct Proc Proc;
+typedef struct QLock QLock;
+typedef struct Queue Queue;
+typedef struct Ref Ref;
+typedef struct Rendez Rendez;
+typedef struct Rept Rept;
+typedef struct Rootdata Rootdata;
+typedef struct RWlock RWlock;
+typedef struct Signerkey Signerkey;
+typedef struct Skeyset Skeyset;
+typedef struct Talarm Talarm;
+typedef struct Timer Timer;
+typedef struct Timers Timers;
+typedef struct Uart Uart;
+typedef struct Walkqid Walkqid;
+typedef int Devgen(Chan*, char*, Dirtab*, int, int, Dir*);
+
+#pragma incomplete DevConf
+#pragma incomplete Edf
+#pragma incomplete Mntcache
+#pragma incomplete Mntrpc
+#pragma incomplete Queue
+#pragma incomplete Timers
+
+#include "fcall.h"
+#include <pool.h>
+
+#define nelem(n) (sizeof(n)/sizeof(n[0]))
+
+struct Ref
+{
+ Lock l;
+ long ref;
+};
+
+struct Rendez
+{
+ Lock;
+ Proc *p;
+};
+
+struct Rept
+{
+ Lock l;
+ Rendez r;
+ void *o;
+ int t;
+ int (*active)(void*);
+ int (*ck)(void*, int);
+ void (*f)(void*); /* called with VM acquire()'d */
+};
+
+struct Osenv
+{
+ char *syserrstr; /* last error from a system call, errbuf0 or 1 */
+ char *errstr; /* reason we're unwinding the error stack, errbuf1 or 0 */
+ char errbuf0[ERRMAX];
+ char errbuf1[ERRMAX];
+ Pgrp* pgrp; /* Ref to namespace, working dir and root */
+ Fgrp* fgrp; /* Ref to file descriptors */
+ Egrp* egrp; /* Environment vars */
+ Skeyset* sigs; /* Signed module keys */
+ Rendez* rend; /* Synchro point */
+ Queue* waitq; /* Info about dead children */
+ Queue* childq; /* Info about children for debuggers */
+ void* debug; /* Debugging master */
+ int uid; /* Numeric user id for system */
+ int gid; /* Numeric group id for system */
+ char* user; /* Inferno user name */
+ FPenv fpu; /* Floating point thread state */
+};
+
+enum
+{
+ Nopin = -1
+};
+
+struct QLock
+{
+ Lock use; /* to access Qlock structure */
+ Proc *head; /* next process waiting for object */
+ Proc *tail; /* last process waiting for object */
+ int locked; /* flag */
+};
+
+struct RWlock
+{
+ Lock; /* Lock modify lock */
+ QLock x; /* Mutual exclusion lock */
+ QLock k; /* Lock for waiting writers */
+ int readers; /* Count of readers in lock */
+};
+
+struct Talarm
+{
+ Lock;
+ Proc* list;
+};
+
+struct Alarms
+{
+ QLock;
+ Proc* head;
+};
+
+struct Rootdata
+{
+ int dotdot;
+ void *ptr;
+ int size;
+ int *sizep;
+};
+
+/*
+ * Access types in namec & channel flags
+ */
+enum
+{
+ Aaccess, /* as in stat, wstat */
+ Abind, /* for left-hand-side of bind */
+ Atodir, /* as in chdir */
+ Aopen, /* for i/o */
+ Amount, /* to be mounted or mounted upon */
+ Acreate, /* is to be created */
+ Aremove, /* will be removed by caller */
+
+ COPEN = 0x0001, /* for i/o */
+ CMSG = 0x0002, /* the message channel for a mount */
+ CCEXEC = 0x0008, /* close on exec */
+ CFREE = 0x0010, /* not in use */
+ CRCLOSE = 0x0020, /* remove on close */
+ CCACHE = 0x0080, /* client cache */
+};
+
+enum
+{
+ BINTR = (1<<0),
+ BFREE = (1<<1),
+ Bipck = (1<<2), /* ip checksum */
+ Budpck = (1<<3), /* udp checksum */
+ Btcpck = (1<<4), /* tcp checksum */
+ Bpktck = (1<<5), /* packet checksum */
+};
+
+struct Block
+{
+ Block* next;
+ Block* list;
+ uchar* rp; /* first unconsumed byte */
+ uchar* wp; /* first empty byte */
+ uchar* lim; /* 1 past the end of the buffer */
+ uchar* base; /* start of the buffer */
+ void (*free)(Block*);
+ ushort flag;
+ ushort checksum; /* IP checksum of complete packet (minus media header) */
+};
+#define BLEN(s) ((s)->wp - (s)->rp)
+#define BALLOC(s) ((s)->lim - (s)->base)
+
+struct Chan
+{
+ Lock;
+ Ref;
+ Chan* next; /* allocation */
+ Chan* link;
+ vlong offset; /* in file */
+ ushort type;
+ ulong dev;
+ ushort mode; /* read/write */
+ ushort flag;
+ Qid qid;
+ int fid; /* for devmnt */
+ ulong iounit; /* chunk size for i/o; 0==default */
+ Mhead* umh; /* mount point that derived Chan; used in unionread */
+ Chan* umc; /* channel in union; held for union read */
+ QLock umqlock; /* serialize unionreads */
+ int uri; /* union read index */
+ int dri; /* devdirread index */
+ ulong mountid;
+ Mntcache *mcp; /* Mount cache pointer */
+ Mnt *mux; /* Mnt for clients using me for messages */
+ union {
+ void* aux;
+ char tag[4]; /* for iproute */
+ };
+ Chan* mchan; /* channel to mounted server */
+ Qid mqid; /* qid of root of mount point */
+ Cname *name;
+};
+
+struct Cname
+{
+ Ref;
+ int alen; /* allocated length */
+ int len; /* strlen(s) */
+ char *s;
+};
+
+struct Dev
+{
+ int dc;
+ char* name;
+
+ void (*reset)(void);
+ void (*init)(void);
+ void (*shutdown)(void);
+ Chan* (*attach)(char*);
+ Walkqid* (*walk)(Chan*, Chan*, char**, int);
+ int (*stat)(Chan*, uchar*, int);
+ Chan* (*open)(Chan*, int);
+ void (*create)(Chan*, char*, int, ulong);
+ void (*close)(Chan*);
+ long (*read)(Chan*, void*, long, vlong);
+ Block* (*bread)(Chan*, long, ulong);
+ long (*write)(Chan*, void*, long, vlong);
+ long (*bwrite)(Chan*, Block*, ulong);
+ void (*remove)(Chan*);
+ int (*wstat)(Chan*, uchar*, int);
+ void (*power)(int); /* power mgt: power(1) → on, power (0) → off */
+ int (*config)(int, char*, DevConf*);
+};
+
+struct Dirtab
+{
+ char name[KNAMELEN];
+ Qid qid;
+ vlong length;
+ long perm;
+};
+
+struct Walkqid
+{
+ Chan *clone;
+ int nqid;
+ Qid qid[1];
+};
+
+enum
+{
+ NSMAX = 1000,
+ NSLOG = 7,
+ NSCACHE = (1<<NSLOG),
+};
+
+struct Mntwalk /* state for /proc/#/ns */
+{
+ int cddone;
+ ulong id;
+ Mhead* mh;
+ Mount* cm;
+};
+
+struct Mount
+{
+ ulong mountid;
+ Mount* next;
+ Mhead* head;
+ Mount* copy;
+ Mount* order;
+ Chan* to; /* channel replacing channel */
+ int mflag;
+ char *spec;
+};
+
+struct Mhead
+{
+ Ref;
+ RWlock lock;
+ Chan* from; /* channel mounted upon */
+ Mount* mount; /* what's mounted upon it */
+ Mhead* hash; /* Hash chain */
+};
+
+struct Mnt
+{
+ Lock;
+ /* references are counted using c->ref; channels on this mount point incref(c->mchan) == Mnt.c */
+ Chan *c; /* Channel to file service */
+ Proc *rip; /* Reader in progress */
+ Mntrpc *queue; /* Queue of pending requests on this channel */
+ ulong id; /* Multiplexer id for channel check */
+ Mnt *list; /* Free list */
+ int flags; /* cache */
+ int msize; /* data + IOHDRSZ */
+ char *version; /* 9P version */
+ Queue *q; /* input queue */
+};
+
+enum
+{
+ RENDLOG = 5,
+ RENDHASH = 1<<RENDLOG, /* Hash to lookup rendezvous tags */
+ MNTLOG = 5,
+ MNTHASH = 1<<MNTLOG, /* Hash to walk mount table */
+ DELTAFD= 20, /* allocation quantum for process file descriptors */
+ MAXNFD = 4000, /* max per process file descriptors */
+ MAXKEY = 8, /* keys for signed modules */
+};
+#define MOUNTH(p,qid) ((p)->mnthash[(qid).path&((1<<MNTLOG)-1)])
+
+struct Mntparam {
+ Chan* chan;
+ Chan* authchan;
+ char* spec;
+ int flags;
+};
+
+struct Pgrp
+{
+ Ref; /* also used as a lock when mounting */
+ ulong pgrpid;
+ QLock debug; /* single access via devproc.c */
+ RWlock ns; /* Namespace n read/one write lock */
+ QLock nsh;
+ Mhead* mnthash[MNTHASH];
+ int progmode;
+ int privatemem; /* deny access to /prog by debuggers */
+ Chan* dot;
+ Chan* slash;
+ int nodevs;
+ int pin;
+};
+
+struct Fgrp
+{
+ Lock;
+ Ref;
+ Chan** fd;
+ int nfd; /* number of fd slots */
+ int maxfd; /* highest fd in use */
+ int minfd; /* lower bound on free fd */
+};
+
+struct Evalue
+{
+ char *var;
+ char *val;
+ int len;
+ Qid qid;
+ Evalue *next;
+};
+
+struct Egrp
+{
+ Ref;
+ QLock;
+ Evalue *entries;
+ ulong path; /* qid.path of next Evalue to be allocated */
+ ulong vers; /* of Egrp */
+};
+
+struct Signerkey
+{
+ Ref;
+ char* owner;
+ ushort footprint;
+ ulong expires;
+ void* alg;
+ void* pk;
+ void (*pkfree)(void*);
+};
+
+struct Skeyset
+{
+ Ref;
+ QLock;
+ ulong flags;
+ char* devs;
+ int nkey;
+ Signerkey *keys[MAXKEY];
+};
+
+/*
+ * fasttick timer interrupts
+ */
+enum {
+ /* Mode */
+ Trelative, /* timer programmed in ns from now */
+ Tabsolute, /* timer programmed in ns since epoch */
+ Tperiodic, /* periodic timer, period in ns */
+};
+
+struct Timer
+{
+ /* Public interface */
+ int tmode; /* See above */
+ vlong tns; /* meaning defined by mode */
+ void (*tf)(Ureg*, Timer*);
+ void *ta;
+ /* Internal */
+ Lock;
+ Timers *tt; /* Timers queue this timer runs on */
+ vlong twhen; /* ns represented in fastticks */
+ Timer *tnext;
+};
+
+enum
+{
+ Dead = 0, /* Process states */
+ Moribund,
+ Ready,
+ Scheding,
+ Running,
+ Queueing,
+ Wakeme,
+ Broken,
+ Stopped,
+ Rendezvous,
+ Waitrelease,
+
+ Proc_stopme = 1, /* devproc requests */
+ Proc_exitme,
+ Proc_traceme,
+ Proc_exitbig,
+
+ NERR = 30,
+
+ Unknown = 0,
+ IdleGC,
+ Interp,
+ BusyGC,
+
+ PriLock = 0, /* Holding Spin lock */
+ PriEdf, /* active edf processes */
+ PriRelease, /* released edf processes */
+ PriRealtime, /* Video telephony */
+ PriHicodec, /* MPEG codec */
+ PriLocodec, /* Audio codec */
+ PriHi, /* Important task */
+ PriNormal,
+ PriLo,
+ PriBackground,
+ PriExtra, /* edf processes we don't care about */
+ Nrq
+};
+
+struct Proc
+{
+ Label sched; /* known to l.s */
+ char* kstack; /* known to l.s */
+ Mach* mach; /* machine running this proc */
+ char text[KNAMELEN];
+ Proc* rnext; /* next process in run queue */
+ Proc* qnext; /* next process on queue for a QLock */
+ QLock* qlock; /* addrof qlock being queued for DEBUG */
+ int state;
+ int type;
+ void* prog; /* Dummy Prog for interp release */
+ void* iprog;
+ Osenv* env;
+ Osenv defenv;
+ int swipend; /* software interrupt pending for Prog */
+ Lock sysio; /* note handler lock */
+ char* psstate; /* What /proc/#/status reports */
+ ulong pid;
+ int fpstate;
+ int procctl; /* Control for /proc debugging */
+ ulong pc; /* DEBUG only */
+ Lock rlock; /* sync between sleep/swiproc for r */
+ Rendez* r; /* rendezvous point slept on */
+ Rendez sleep; /* place for syssleep/debug */
+ int killed; /* by swiproc */
+ int kp; /* true if a kernel process */
+ ulong alarm; /* Time of call */
+ int pri; /* scheduler priority */
+ ulong twhen;
+ Rendez* trend;
+ Proc* tlink;
+ int (*tfn)(void*);
+ void (*kpfun)(void*);
+ void* arg;
+ FPU fpsave;
+ int scallnr;
+ int nerrlab;
+ Label errlab[NERR];
+ char genbuf[128]; /* buffer used e.g. for last name element from namec */
+ Mach* mp; /* machine this process last ran on */
+ Mach* wired;
+ ulong movetime; /* next time process should switch processors */
+ ulong delaysched;
+ int preempted; /* process yielding in interrupt */
+ ulong qpc; /* last call that blocked in qlock */
+ void* dbgreg; /* User registers for devproc */
+ int dbgstop; /* don't run this kproc */
+ Edf* edf; /* if non-null, real-time proc, edf contains scheduling params */
+};
+
+enum
+{
+ /* kproc flags */
+ KPDUPPG = (1<<0),
+ KPDUPFDG = (1<<1),
+ KPDUPENVG = (1<<2),
+ KPDUP = KPDUPPG | KPDUPFDG | KPDUPENVG
+};
+
+enum {
+ BrkSched,
+ BrkNoSched,
+};
+
+struct BkptCond
+{
+ uchar op;
+ ulong val;
+ BkptCond *next;
+};
+
+struct Bkpt
+{
+ int id;
+ ulong addr;
+ BkptCond *conditions;
+ Instr instr;
+ void (*handler)(Bkpt*);
+ void *aux;
+ Bkpt *next;
+ Bkpt *link;
+};
+
+enum
+{
+ PRINTSIZE = 256,
+ NUMSIZE = 12, /* size of formatted number */
+ MB = (1024*1024),
+ READSTR = 1000, /* temporary buffer size for device reads */
+};
+
+extern Conf conf;
+extern char* conffile;
+extern int consoleprint;
+extern Dev* devtab[];
+extern char* eve;
+extern int hwcurs;
+extern FPU initfp;
+extern Queue *kbdq;
+extern Queue *kscanq;
+extern Ref noteidalloc;
+extern Queue *printq;
+extern uint qiomaxatomic;
+extern char* statename[];
+extern char* sysname;
+extern Talarm talarm;
+
+/*
+ * action log
+ */
+struct Log {
+ Lock;
+ int opens;
+ char* buf;
+ char *end;
+ char *rptr;
+ int len;
+ int nlog;
+ int minread;
+
+ int logmask; /* mask of things to debug */
+
+ QLock readq;
+ Rendez readr;
+};
+
+struct Logflag {
+ char* name;
+ int mask;
+};
+
+struct Cmdbuf
+{
+ char *buf;
+ char **f;
+ int nf;
+};
+
+struct Cmdtab
+{
+ int index; /* used by client to switch on result */
+ char *cmd; /* command name */
+ int narg; /* expected #args; 0 ==> variadic */
+};
+
+enum
+{
+ MAXPOOL = 8,
+};
+
+extern Pool* mainmem;
+extern Pool* heapmem;
+extern Pool* imagmem;
+
+/* queue state bits, Qmsg, Qcoalesce, and Qkick can be set in qopen */
+enum
+{
+ /* Queue.state */
+ Qstarve = (1<<0), /* consumer starved */
+ Qmsg = (1<<1), /* message stream */
+ Qclosed = (1<<2), /* queue has been closed/hungup */
+ Qflow = (1<<3), /* producer flow controlled */
+ Qcoalesce = (1<<4), /* coallesce packets on read */
+ Qkick = (1<<5), /* always call the kick routine after qwrite */
+};
+
+#define DEVDOTDOT -1
+
+#pragma varargck argpos print 1
+#pragma varargck argpos snprint 3
+#pragma varargck argpos seprint 3
+#pragma varargck argpos sprint 2
+#pragma varargck argpos fprint 2
+#pragma varargck argpos iprint 1
+#pragma varargck argpos panic 1
+#pragma varargck argpos kwerrstr 1
+#pragma varargck argpos kprint 1
+
+#pragma varargck type "lld" vlong
+#pragma varargck type "llx" vlong
+#pragma varargck type "lld" uvlong
+#pragma varargck type "llx" uvlong
+#pragma varargck type "lx" void*
+#pragma varargck type "ld" long
+#pragma varargck type "lx" long
+#pragma varargck type "ld" ulong
+#pragma varargck type "lx" ulong
+#pragma varargck type "d" int
+#pragma varargck type "x" int
+#pragma varargck type "c" int
+#pragma varargck type "C" int
+#pragma varargck type "d" uint
+#pragma varargck type "x" uint
+#pragma varargck type "c" uint
+#pragma varargck type "C" uint
+#pragma varargck type "f" double
+#pragma varargck type "e" double
+#pragma varargck type "g" double
+#pragma varargck type "s" char*
+#pragma varargck type "S" Rune*
+#pragma varargck type "r" void
+#pragma varargck type "%" void
+#pragma varargck type "I" uchar*
+#pragma varargck type "V" uchar*
+#pragma varargck type "E" uchar*
+#pragma varargck type "M" uchar*
+#pragma varargck type "p" void*
+#pragma varargck type "q" char*
diff --git a/os/port/portfns.h b/os/port/portfns.h
new file mode 100644
index 00000000..d6bcdcf0
--- /dev/null
+++ b/os/port/portfns.h
@@ -0,0 +1,319 @@
+#define FPinit() fpinit() /* remove this if math lib is linked */
+void FPrestore(void*);
+void FPsave(void*);
+Timer* addclock0link(void (*)(void), int);
+Cname* addelem(Cname*, char*);
+void addprog(Proc*);
+void addrootfile(char*, uchar*, ulong);
+Block* adjustblock(Block*, int);
+Block* allocb(int);
+int anyhigher(void);
+int anyready(void);
+#define assert(x) if(x){}else _assert("assert(x) failed")
+void _assert(char*);
+Block* bl2mem(uchar*, Block*, int);
+int blocklen(Block*);
+int breakhit(Ureg *ur, Proc*);
+void callwithureg(void(*)(Ureg*));
+char* channame(Chan*);
+int canlock(Lock*);
+int canqlock(QLock*);
+void cclose(Chan*);
+int canrlock(RWlock*);
+void chandevinit(void);
+void chandevreset(void);
+void chandevshutdown(void);
+Dir* chandirstat(Chan*);
+void chanfree(Chan*);
+void chanrec(Mnt*);
+void checkalarms(void);
+void checkb(Block*, char*);
+void cinit(void);
+Chan* cclone(Chan*);
+void cclose(Chan*);
+void closeegrp(Egrp*);
+void closefgrp(Fgrp*);
+void closemount(Mount*);
+void closepgrp(Pgrp*);
+void closesigs(Skeyset*);
+void cmderror(Cmdbuf*, char*);
+int cmount(Chan*, Chan*, int, char*);
+void cnameclose(Cname*);
+Block* concatblock(Block*);
+void confinit(void);
+void copen(Chan*);
+Block* copyblock(Block*, int);
+int cread(Chan*, uchar*, int, vlong);
+Chan* cunique(Chan*);
+Chan* createdir(Chan*, Mhead*);
+void cunmount(Chan*, Chan*);
+void cupdate(Chan*, uchar*, int, vlong);
+void cursorenable(void);
+void cursordisable(void);
+int cursoron(int);
+void cursoroff(int);
+void cwrite(Chan*, uchar*, int, vlong);
+void debugkey(Rune, char *, void(*)(), int);
+int decref(Ref*);
+Chan* devattach(int, char*);
+Block* devbread(Chan*, long, ulong);
+long devbwrite(Chan*, Block*, ulong);
+Chan* devclone(Chan*);
+void devcreate(Chan*, char*, int, ulong);
+void devdir(Chan*, Qid, char*, vlong, char*, long, Dir*);
+long devdirread(Chan*, char*, long, Dirtab*, int, Devgen*);
+Devgen devgen;
+void devinit(void);
+int devno(int, int);
+void devpower(int);
+Dev* devbyname(char*);
+Chan* devopen(Chan*, int, Dirtab*, int, Devgen*);
+void devpermcheck(char*, ulong, int);
+void devremove(Chan*);
+void devreset(void);
+void devshutdown(void);
+int devstat(Chan*, uchar*, int, Dirtab*, int, Devgen*);
+Walkqid* devwalk(Chan*, Chan*, char**, int, Dirtab*, int, Devgen*);
+int devwstat(Chan*, uchar*, int);
+void disinit(void*);
+void disfault(void*, char*);
+int domount(Chan**, Mhead**);
+void drawactive(int);
+void drawcmap(void);
+void dumpstack(void);
+Fgrp* dupfgrp(Fgrp*);
+void egrpcpy(Egrp*, Egrp*);
+int emptystr(char*);
+int eqchan(Chan*, Chan*, int);
+int eqqid(Qid, Qid);
+void error(char*);
+void errorf(char*, ...);
+#pragma varargck argpos errorf 1
+void errstr(char*, int);
+void excinit(void);
+void exhausted(char*);
+void exit(int);
+void reboot(void);
+void halt(void);
+int export(int, char*, int);
+uvlong fastticks(uvlong*);
+uvlong fastticks2ns(uvlong);
+void fdclose(Fgrp*, int);
+Chan* fdtochan(Fgrp*, int, int, int, int);
+int findmount(Chan**, Mhead**, int, int, Qid);
+void free(void*);
+void freeb(Block*);
+void freeblist(Block*);
+void freeskey(Signerkey*);
+void getcolor(ulong, ulong*, ulong*, ulong*);
+ulong getmalloctag(void*);
+ulong getrealloctag(void*);
+void gotolabel(Label*);
+void hnputl(void*, ulong);
+void hnputs(void*, ushort);
+Block* iallocb(int);
+void iallocsummary(void);
+void ilock(Lock*);
+int incref(Ref*);
+int iprint(char*, ...);
+#pragma varargck argpos iprint 1
+void isdir(Chan*);
+int iseve(void);
+int islo(void);
+void iunlock(Lock*);
+void ixsummary(void);
+void kbdclock(void);
+int kbdcr2nl(Queue*, int);
+int kbdputc(Queue*, int);
+void kbdrepeat(int);
+void kproc(char*, void(*)(void*), void*, int);
+int kfgrpclose(Fgrp*, int);
+void kprocchild(Proc*, void (*)(void*), void*);
+int kprint(char*, ...);
+void (*kproftick)(ulong);
+void ksetenv(char*, char*, int);
+void kstrcpy(char*, char*, int);
+void kstrdup(char**, char*);
+long latin1(Rune*, int);
+void lock(Lock*);
+void logopen(Log*);
+void logclose(Log*);
+char* logctl(Log*, int, char**, Logflag*);
+void logn(Log*, int, void*, int);
+long logread(Log*, void*, ulong, long);
+void logb(Log*, int, char*, ...);
+#define pragma varargck argpos logb 3
+Cmdtab* lookupcmd(Cmdbuf*, Cmdtab*, int);
+void machinit(void);
+extern void machbreakinit(void);
+extern Instr machinstr(ulong addr);
+extern void machbreakset(ulong addr);
+extern void machbreakclear(ulong addr, Instr i);
+extern ulong machnextaddr(Ureg *ur);
+void* malloc(ulong);
+void* mallocz(ulong, int);
+Block* mem2bl(uchar*, int);
+int memusehigh(void);
+void microdelay(int);
+uvlong mk64fract(uvlong, uvlong);
+void mkqid(Qid*, vlong, ulong, int);
+void modinit(void);
+Chan* mntauth(Chan*, char*);
+long mntversion(Chan*, char*, int, int);
+void mountfree(Mount*);
+void mousetrack(int, int, int, int);
+uvlong ms2fastticks(ulong);
+ulong msize(void*);
+void mul64fract(uvlong*, uvlong, uvlong);
+void muxclose(Mnt*);
+Chan* namec(char*, int, int, ulong);
+Chan* newchan(void);
+Egrp* newegrp(void);
+Fgrp* newfgrp(Fgrp*);
+Mount* newmount(Mhead*, Chan*, int, char*);
+Pgrp* newpgrp(void);
+Proc* newproc(void);
+char* nextelem(char*, char*);
+void nexterror(void);
+Cname* newcname(char*);
+int notify(Ureg*);
+void notkilled(void);
+int nrand(int);
+uvlong ns2fastticks(uvlong);
+int okaddr(ulong, ulong, int);
+int openmode(ulong);
+Block* packblock(Block*);
+Block* padblock(Block*, int);
+void panic(char*, ...);
+Cmdbuf* parsecmd(char*, int);
+void pexit(char*, int);
+void pgrpcpy(Pgrp*, Pgrp*);
+#define poperror() up->nerrlab--
+int poolread(char*, int, ulong);
+void poolsize(Pool *, int, int);
+int postnote(Proc *, int, char *, int);
+int pprint(char*, ...);
+int preemption(int);
+void printinit(void);
+void procctl(Proc*);
+void procdump(void);
+void procinit(void);
+Proc* proctab(int);
+void (*proctrace)(Proc*, int, vlong);
+int progfdprint(Chan*, int, int, char*, int);
+int pullblock(Block**, int);
+Block* pullupblock(Block*, int);
+Block* pullupqueue(Queue*, int);
+void putmhead(Mhead*);
+void putstrn(char*, int);
+void qaddlist(Queue*, Block*);
+Block* qbread(Queue*, int);
+long qbwrite(Queue*, Block*);
+Queue* qbypass(void (*)(void*, Block*), void*);
+int qcanread(Queue*);
+void qclose(Queue*);
+int qconsume(Queue*, void*, int);
+Block* qcopy(Queue*, int, ulong);
+int qdiscard(Queue*, int);
+void qflush(Queue*);
+void qfree(Queue*);
+int qfull(Queue*);
+Block* qget(Queue*);
+void qhangup(Queue*, char*);
+int qisclosed(Queue*);
+int qiwrite(Queue*, void*, int);
+int qlen(Queue*);
+void qlock(QLock*);
+void qnoblock(Queue*, int);
+Queue* qopen(int, int, void (*)(void*), void*);
+int qpass(Queue*, Block*);
+int qpassnolim(Queue*, Block*);
+int qproduce(Queue*, void*, int);
+void qputback(Queue*, Block*);
+long qread(Queue*, void*, int);
+Block* qremove(Queue*);
+void qreopen(Queue*);
+void qsetlimit(Queue*, int);
+void qunlock(QLock*);
+int qwindow(Queue*);
+int qwrite(Queue*, void*, int);
+void randominit(void);
+ulong randomread(void*, ulong);
+void* realloc(void*, ulong);
+int readnum(ulong, char*, ulong, ulong, int);
+int readnum_vlong(ulong, char*, ulong, vlong, int);
+int readstr(ulong, char*, ulong, char*);
+void ready(Proc*);
+void renameproguser(char*, char*);
+void renameuser(char*, char*);
+void resrcwait(char*);
+int return0(void*);
+void rlock(RWlock*);
+void runlock(RWlock*);
+Proc* runproc(void);
+void sched(void);
+void schedinit(void);
+long seconds(void);
+void (*serwrite)(char*, int);
+int setcolor(ulong, ulong, ulong, ulong);
+int setlabel(Label*);
+void setmalloctag(void*, ulong);
+int setpri(int);
+void setrealloctag(void*, ulong);
+char* skipslash(char*);
+void sleep(Rendez*, int(*)(void*), void*);
+void* smalloc(ulong);
+int splhi(void);
+int spllo(void);
+void splx(int);
+void splxpc(int);
+void swiproc(Proc*, int);
+ulong _tas(ulong*);
+void timeradd(Timer*);
+void timerdel(Timer*);
+void timersinit(void);
+void timerintr(Ureg*, uvlong);
+void timerset(uvlong);
+ulong tk2ms(ulong);
+#define TK2MS(x) ((x)*(1000/HZ))
+uvlong tod2fastticks(vlong);
+vlong todget(vlong*);
+void todfix(void);
+void todsetfreq(vlong);
+void todinit(void);
+void todset(vlong, vlong, int);
+int tready(void*);
+Block* trimblock(Block*, int, int);
+void tsleep(Rendez*, int (*)(void*), void*, int);
+int uartgetc(void);
+void uartputc(int);
+void uartputs(char*, int);
+long unionread(Chan*, void*, long);
+void unlock(Lock*);
+void userinit(void);
+ulong userpc(void);
+void validname(char*, int);
+void validstat(uchar*, int);
+void validwstatname(char*);
+int wakeup(Rendez*);
+int walk(Chan**, char**, int, int, int*);
+void werrstr(char*, ...);
+void wlock(RWlock*);
+void wunlock(RWlock*);
+void* xalloc(ulong);
+void* xallocz(ulong, int);
+void xfree(void*);
+void xhole(ulong, ulong);
+void xinit(void);
+int xmerge(void*, void*);
+void* xspanalloc(ulong, int, ulong);
+void xsummary(void);
+
+void validaddr(void*, ulong, int);
+void* vmemchr(void*, int, int);
+void hnputv(void*, vlong);
+void hnputl(void*, ulong);
+void hnputs(void*, ushort);
+vlong nhgetv(void*);
+ulong nhgetl(void*);
+ushort nhgets(void*);
diff --git a/os/port/portmkfile b/os/port/portmkfile
new file mode 100644
index 00000000..9ee9a7f7
--- /dev/null
+++ b/os/port/portmkfile
@@ -0,0 +1,152 @@
+PORTHFILES=\
+ ../port/error.h\
+ ../port/lib.h\
+ ../port/portdat.h\
+ ../port/portfns.h\
+
+LIBFILES=${LIBS:%=$ROOT/Inferno/$OBJTYPE/lib/lib%.a}
+
+CLEANEXTRA=
+
+%.$O: %.s
+ $AS $stem.s
+
+%.$O: %.c
+ $CC $CFLAGS $stem.c
+
+%.$O: ../port/%.c
+ $CC $CFLAGS -I. ../port/$stem.c
+
+%.$O: ../ip/%.c
+ $CC $CFLAGS -I. ../ip/$stem.c
+
+%.$O: ../kfs/%.c
+ $CC $CFLAGS -I. ../kfs/$stem.c
+
+&.$O: $HFILES $PORTHFILES
+
+$INSTALLDIR/%: %
+ cp $stem $INSTALLDIR/$stem
+
+installall:V: install-$SHELLTYPE
+all:V: default-$SHELLTYPE
+
+acid:V: i$CONF.acid
+i$CONF.acid:V: $SHELLTYPE-i$CONF.acid
+
+LIBHDIRS= -I$ROOT/libmp/port -I$ROOT/libsec/port
+
+
+rc-i$CONF.acid nt-i$CONF.acid:V: i$CONF
+ {
+ x=i$CONF; test -e i$CONF.p9 && x=i$CONF.p9
+ for (i in `{srclist -ec -r $ROOT/ $x}) {
+ echo '//FILE: ' $i
+ $CC -I. $CFLAGS $LIBHDIRS '-DKERNDATE='$KERNDATE -a $i
+ }
+ echo 'include ("inferno");'
+ } >i$CONF.acid
+
+sh-i$CONF.acid:V: i$CONF
+ x=i$CONF; test -e i$CONF.p9 && x=i$CONF.p9
+ for i in `srclist -ec -r $ROOT/ $x`
+ do
+ echo '//FILE: ' $i
+ $CC -I. $CFLAGS $LIBHDIRS '-DKERNDATE='$KERNDATE -a $i
+ done >i$CONF.acid
+ echo 'include ("inferno");' >> i$CONF.acid
+
+lib%.a:V: $SHELLTYPE-lib%.a
+
+rc-lib%.a nt-lib%.a:VQ:
+ echo '@{builtin cd' $ROOT/lib$stem ';' mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE install'}'
+ @{builtin cd $ROOT/lib$stem; mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE install}
+
+sh-lib%.a:VQ:
+ echo "(cd $ROOT/lib$stem ; mk SHELLTYPE=$SHELLTYPE SYSTARG=$SYSTARG OBJTYPE=$OBJTYPE install)"
+ (cd $ROOT/lib$stem; mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE install)
+
+%-rc %-nt:V:
+ for(i in $CONFLIST)
+ mk 'CONF='$i $stem
+
+%-sh:V:
+ for i in $CONFLIST
+ do
+ mk 'CONF='$i $stem
+ done
+
+clean:V: cleanconf-$SHELLTYPE
+ rm -f *.[$OS] *.root.[sh] errstr.h *.out $CLEANEXTRA
+
+cleanconf-sh:V:
+ for i in $CONFLIST $CLEANCONFLIST
+ do
+ rm -f $i.c i$i i$i.* $i.ver
+ done
+
+cleanconf-rc cleanconf-nt:V:
+ for(i in $CONFLIST $CLEANCONFLIST)
+ rm -f $i.c i$i i$i.* $i.ver
+
+nuke-sh:QV:
+ for i in $LIBDIRS
+ do
+ echo "(cd $ROOT/lib$i ; mk SHELLTYPE=$SHELLTYPE SYSTARG=$SYSTARG OBJTYPE=$OBJTYPE nuke)"
+ (cd $ROOT/lib$i; mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE nuke)
+ done
+
+nuke-rc nuke-nt:QV:
+ for (i in $LIBDIRS)
+ {
+ echo '@{cd $ROOT/lib$i ; mk SHELLTYPE=$SHELLTYPE SYSTARG=$SYSTARG OBJTYPE=$OBJTYPE nuke}'
+ @{cd $ROOT/lib$i; mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE nuke}
+ }
+
+nuke:V: clean nuke-$SHELLTYPE
+
+$CONF.c: ../port/mkdevc $CONF
+ $SHELLNAME ../port/mkdevc $CONF > $CONF.c
+
+errstr.h: ../port/error.h
+ sed 's/extern //;s,;.*/\* , = ",;s, \*/,";,' < ../port/error.h > errstr.h
+
+../init/%.dis: ../init/%.b
+ cd ../init; mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE $stem.dis
+
+$ROOT/libinterp/runt.h:
+ cd $ROOT/libinterp
+ mk 'SHELLTYPE='$SHELLTYPE 'SYSTARG='$SYSTARG 'OBJTYPE='$OBJTYPE runt.h
+
+RUNT=$ROOT/libinterp/runt.h # for culling dependencies
+INTERP=$ROOT/include/interp.h
+
+alloc.$O: $INTERP
+devdbg.$O: $INTERP
+
+devmnt.$O: $ROOT/include/fcall.h
+devns16552.$O: ../port/netif.h
+devns16552.$O: ns16552.h
+devpipe.$O: $INTERP
+devprof.$O: $RUNT $INTERP
+devprog.$O: $RUNT $INTERP
+devroot.$O: errstr.h
+devsign.$O: $RUNT $INTERP
+devsrv.$O: $RUNT $INTERP
+dis.$O: $INTERP
+discall.$O: $INTERP
+exception.$O: $RUNT $INTERP
+inferno.$O: $RUNT $INTERP
+latin1.$O: ../port/latin1.h
+main.$O: ../port/error.h
+netif.$O: ../port/netif.h
+proc.$O: errstr.h $INTERP
+screen.$O: screen.h
+trap.$O: $ROOT/Inferno/$OBJTYPE/include/ureg.h
+
+devroot.$O: $CONF.root.h
+$CONF.$O: $CONF.root.h
+$CONF.root.s $CONF.root.h: $CONF ../init/$INIT.dis ../port/mkroot $ROOTFILES
+ $SHELLNAME ../port/mkroot $CONF
+
+%.$O: $ROOT/Inferno/$OBJTYPE/include/u.h ../port/lib.h mem.h dat.h fns.h io.h ../port/error.h ../port/portdat.h ../port/portfns.h
diff --git a/os/port/print.c b/os/port/print.c
new file mode 100644
index 00000000..8a7a86fd
--- /dev/null
+++ b/os/port/print.c
@@ -0,0 +1,31 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+static Lock fmtl;
+
+void
+_fmtlock(void)
+{
+ lock(&fmtl);
+}
+
+void
+_fmtunlock(void)
+{
+ unlock(&fmtl);
+}
+
+int
+_efgfmt(Fmt*)
+{
+ return -1;
+}
+
+int
+errfmt(Fmt*)
+{
+ return -1;
+}
diff --git a/os/port/proc.c b/os/port/proc.c
new file mode 100644
index 00000000..a652669a
--- /dev/null
+++ b/os/port/proc.c
@@ -0,0 +1,788 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include <interp.h>
+
+Ref pidalloc;
+
+struct
+{
+ Lock;
+ Proc* arena;
+ Proc* free;
+}procalloc;
+
+typedef struct
+{
+ Lock;
+ Proc* head;
+ Proc* tail;
+}Schedq;
+
+static Schedq runq[Nrq];
+static ulong occupied;
+int nrdy;
+
+char *statename[] =
+{ /* BUG: generate automatically */
+ "Dead",
+ "Moribund",
+ "Ready",
+ "Scheding",
+ "Running",
+ "Queueing",
+ "Wakeme",
+ "Broken",
+ "Stopped",
+ "Rendez",
+};
+
+/*
+ * Always splhi()'ed.
+ */
+void
+schedinit(void) /* never returns */
+{
+ setlabel(&m->sched);
+ if(up) {
+/*
+ if((e = up->edf) && (e->flags & Admitted))
+ edfrecord(up);
+*/
+ m->proc = nil;
+ switch(up->state) {
+ case Running:
+ ready(up);
+ break;
+ case Moribund:
+ up->state = Dead;
+/*
+ edfstop(up);
+ if(up->edf){
+ free(up->edf);
+ up->edf = nil;
+ }
+*/
+ /*
+ * Holding locks from pexit:
+ * procalloc
+ */
+ up->qnext = procalloc.free;
+ procalloc.free = up;
+ unlock(&procalloc);
+ break;
+ }
+ up->mach = nil;
+ up = nil;
+ }
+ sched();
+}
+
+void
+sched(void)
+{
+ if(up) {
+ splhi();
+ procsave(up);
+ if(setlabel(&up->sched)) {
+ /* procrestore(up); */
+ spllo();
+ return;
+ }
+ gotolabel(&m->sched);
+ }
+ up = runproc();
+ up->state = Running;
+ up->mach = MACHP(m->machno); /* m might be a fixed address; use MACHP */
+ m->proc = up;
+ gotolabel(&up->sched);
+}
+
+void
+ready(Proc *p)
+{
+ int s;
+ Schedq *rq;
+
+ s = splhi();
+/*
+ if(edfready(p)){
+ splx(s);
+ return;
+ }
+*/
+ rq = &runq[p->pri];
+ lock(runq);
+ p->rnext = 0;
+ if(rq->tail)
+ rq->tail->rnext = p;
+ else
+ rq->head = p;
+ rq->tail = p;
+
+ nrdy++;
+ occupied |= 1<<p->pri;
+ p->state = Ready;
+ unlock(runq);
+ splx(s);
+}
+
+int
+anyready(void)
+{
+ /* same priority only */
+ return occupied & (1<<up->pri);
+}
+
+int
+anyhigher(void)
+{
+ return occupied & ((1<<up->pri)-1);
+}
+
+int
+preemption(int tick)
+{
+ if(up != nil && up->state == Running && !up->preempted &&
+ (anyhigher() || tick && anyready())){
+ up->preempted = 1;
+ sched();
+ splhi();
+ up->preempted = 0;
+ return 1;
+ }
+ return 0;
+}
+
+Proc*
+runproc(void)
+{
+ Proc *p, *l;
+ Schedq *rq, *erq;
+
+ erq = runq + Nrq - 1;
+loop:
+ splhi();
+ for(rq = runq; rq->head == 0; rq++)
+ if(rq >= erq) {
+ idlehands();
+ spllo();
+ goto loop;
+ }
+
+ if(!canlock(runq))
+ goto loop;
+ /* choose first one we last ran on this processor at this level or hasn't moved recently */
+ l = nil;
+ for(p = rq->head; p != nil; p = p->rnext)
+ if(p->mp == nil || p->mp == MACHP(m->machno) || p->movetime < MACHP(0)->ticks)
+ break;
+ if(p == nil)
+ p = rq->head;
+ /* p->mach==0 only when process state is saved */
+ if(p == 0 || p->mach) {
+ unlock(runq);
+ goto loop;
+ }
+ if(p->rnext == nil)
+ rq->tail = l;
+ if(l)
+ l->rnext = p->rnext;
+ else
+ rq->head = p->rnext;
+ if(rq->head == nil){
+ rq->tail = nil;
+ occupied &= ~(1<<p->pri);
+ }
+ nrdy--;
+ if(p->dbgstop){
+ p->state = Stopped;
+ unlock(runq);
+ goto loop;
+ }
+ if(p->state != Ready)
+ print("runproc %s %lud %s\n", p->text, p->pid, statename[p->state]);
+ unlock(runq);
+ p->state = Scheding;
+ if(p->mp != MACHP(m->machno))
+ p->movetime = MACHP(0)->ticks + HZ/10;
+ p->mp = MACHP(m->machno);
+
+/*
+ if(edflock(p)){
+ edfrun(p, rq == &runq[PriEdf]); // start deadline timer and do admin
+ edfunlock();
+ }
+*/
+ return p;
+}
+
+int
+setpri(int pri)
+{
+ int p;
+
+ /* called by up so not on run queue */
+ p = up->pri;
+ up->pri = pri;
+ if(up->state == Running && anyhigher())
+ sched();
+ return p;
+}
+
+Proc*
+newproc(void)
+{
+ Proc *p;
+
+ lock(&procalloc);
+ for(;;) {
+ if(p = procalloc.free)
+ break;
+
+ unlock(&procalloc);
+ resrcwait("no procs");
+ lock(&procalloc);
+ }
+ procalloc.free = p->qnext;
+ unlock(&procalloc);
+
+ p->type = Unknown;
+ p->state = Scheding;
+ p->pri = PriNormal;
+ p->psstate = "New";
+ p->mach = 0;
+ p->qnext = 0;
+ p->fpstate = FPINIT;
+ p->kp = 0;
+ p->killed = 0;
+ p->swipend = 0;
+ p->mp = 0;
+ p->movetime = 0;
+ p->delaysched = 0;
+ p->edf = nil;
+ memset(&p->defenv, 0, sizeof(p->defenv));
+ p->env = &p->defenv;
+ p->dbgreg = 0;
+ kstrdup(&p->env->user, "*nouser");
+ p->env->errstr = p->env->errbuf0;
+ p->env->syserrstr = p->env->errbuf1;
+
+ p->pid = incref(&pidalloc);
+ if(p->pid == 0)
+ panic("pidalloc");
+ if(p->kstack == 0)
+ p->kstack = smalloc(KSTACK);
+ addprog(p);
+
+ return p;
+}
+
+void
+procinit(void)
+{
+ Proc *p;
+ int i;
+
+ procalloc.free = xalloc(conf.nproc*sizeof(Proc));
+ procalloc.arena = procalloc.free;
+
+ p = procalloc.free;
+ for(i=0; i<conf.nproc-1; i++,p++)
+ p->qnext = p+1;
+ p->qnext = 0;
+
+ debugkey('p', "processes", procdump, 0);
+}
+
+void
+sleep(Rendez *r, int (*f)(void*), void *arg)
+{
+ int s;
+
+ if(up == nil)
+ panic("sleep() not in process (%lux)", getcallerpc(&r));
+ /*
+ * spl is to allow lock to be called
+ * at interrupt time. lock is mutual exclusion
+ */
+ s = splhi();
+
+ lock(&up->rlock);
+ lock(r);
+
+ /*
+ * if killed or condition happened, never mind
+ */
+ if(up->killed || f(arg)){
+ unlock(r);
+ }else{
+
+ /*
+ * now we are committed to
+ * change state and call scheduler
+ */
+ if(r->p != nil) {
+ print("double sleep pc=0x%lux %lud %lud r=0x%lux\n", getcallerpc(&r), r->p->pid, up->pid, r);
+ dumpstack();
+ panic("sleep");
+ }
+ up->state = Wakeme;
+ r->p = up;
+ unlock(r);
+ up->swipend = 0;
+ up->r = r; /* for swiproc */
+ unlock(&up->rlock);
+
+ sched();
+ splhi(); /* sched does spllo */
+
+ lock(&up->rlock);
+ up->r = nil;
+ }
+
+ if(up->killed || up->swipend) {
+ up->killed = 0;
+ up->swipend = 0;
+ unlock(&up->rlock);
+ splx(s);
+ error(Eintr);
+ }
+ unlock(&up->rlock);
+ splx(s);
+}
+
+int
+tfn(void *arg)
+{
+ return MACHP(0)->ticks >= up->twhen || (*up->tfn)(arg);
+}
+
+void
+tsleep(Rendez *r, int (*fn)(void*), void *arg, int ms)
+{
+ ulong when;
+ Proc *f, **l;
+
+ if(up == nil)
+ panic("tsleep() not in process (0x%lux)", getcallerpc(&r));
+
+ when = MS2TK(ms)+MACHP(0)->ticks;
+ lock(&talarm);
+ /* take out of list if checkalarm didn't */
+ if(up->trend) {
+ l = &talarm.list;
+ for(f = *l; f; f = f->tlink) {
+ if(f == up) {
+ *l = up->tlink;
+ break;
+ }
+ l = &f->tlink;
+ }
+ }
+ /* insert in increasing time order */
+ l = &talarm.list;
+ for(f = *l; f; f = f->tlink) {
+ if(f->twhen >= when)
+ break;
+ l = &f->tlink;
+ }
+ up->trend = r;
+ up->twhen = when;
+ up->tfn = fn;
+ up->tlink = *l;
+ *l = up;
+ unlock(&talarm);
+
+ if(waserror()){
+ up->twhen = 0;
+ nexterror();
+ }
+ sleep(r, tfn, arg);
+ up->twhen = 0;
+ poperror();
+}
+
+int
+wakeup(Rendez *r)
+{
+ Proc *p;
+ int s;
+
+ s = splhi();
+ lock(r);
+ p = r->p;
+ if(p){
+ r->p = nil;
+ if(p->state != Wakeme)
+ panic("wakeup: state");
+ ready(p);
+ }
+ unlock(r);
+ splx(s);
+ return p != nil;
+}
+
+void
+swiproc(Proc *p, int interp)
+{
+ ulong s;
+ Rendez *r;
+
+ if(p == nil)
+ return;
+
+ s = splhi();
+ lock(&p->rlock);
+ if(!interp)
+ p->killed = 1;
+ r = p->r;
+ if(r != nil) {
+ lock(r);
+ if(r->p == p){
+ p->swipend = 1;
+ r->p = nil;
+ ready(p);
+ }
+ unlock(r);
+ }
+ unlock(&p->rlock);
+ splx(s);
+}
+
+void
+notkilled(void)
+{
+ lock(&up->rlock);
+ up->killed = 0;
+ unlock(&up->rlock);
+}
+
+void
+pexit(char*, int)
+{
+ Osenv *o;
+
+ up->alarm = 0;
+
+ o = up->env;
+ if(o != nil){
+ closefgrp(o->fgrp);
+ closepgrp(o->pgrp);
+ closeegrp(o->egrp);
+ closesigs(o->sigs);
+ }
+
+ /* Sched must not loop for this lock */
+ lock(&procalloc);
+
+/*
+ edfstop(up);
+*/
+ up->state = Moribund;
+ sched();
+ panic("pexit");
+}
+
+Proc*
+proctab(int i)
+{
+ return &procalloc.arena[i];
+}
+
+void
+procdump(void)
+{
+ int i;
+ char *s;
+ Proc *p;
+ char tmp[14];
+
+ for(i=0; i<conf.nproc; i++) {
+ p = &procalloc.arena[i];
+ if(p->state == Dead)
+ continue;
+
+ s = p->psstate;
+ if(s == nil)
+ s = "kproc";
+ if(p->state == Wakeme)
+ snprint(tmp, sizeof(tmp), " /%.8lux", p->r);
+ else
+ *tmp = '\0';
+ print("%lux:%3lud:%14s pc %.8lux %s/%s qpc %.8lux pri %d%s\n",
+ p, p->pid, p->text, p->pc, s, statename[p->state], p->qpc, p->pri, tmp);
+ }
+}
+
+void
+kproc(char *name, void (*func)(void *), void *arg, int flags)
+{
+ Proc *p;
+ Pgrp *pg;
+ Fgrp *fg;
+ Egrp *eg;
+
+ p = newproc();
+ p->psstate = 0;
+ p->kp = 1;
+
+ p->fpsave = up->fpsave;
+ p->scallnr = up->scallnr;
+ p->nerrlab = 0;
+
+ kstrdup(&p->env->user, up->env->user);
+ if(flags & KPDUPPG) {
+ pg = up->env->pgrp;
+ incref(pg);
+ p->env->pgrp = pg;
+ }
+ if(flags & KPDUPFDG) {
+ fg = up->env->fgrp;
+ incref(fg);
+ p->env->fgrp = fg;
+ }
+ if(flags & KPDUPENVG) {
+ eg = up->env->egrp;
+ if(eg != nil)
+ incref(eg);
+ p->env->egrp = eg;
+ }
+
+ kprocchild(p, func, arg);
+
+ strcpy(p->text, name);
+
+ ready(p);
+}
+
+void
+errorf(char *fmt, ...)
+{
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ error(buf);
+}
+
+void
+error(char *err)
+{
+ if(up == nil)
+ panic("error(%s) not in a process", err);
+ spllo();
+ if(up->nerrlab > NERR)
+ panic("error stack too deep");
+ if(err != up->env->errstr)
+ kstrcpy(up->env->errstr, err, ERRMAX);
+ setlabel(&up->errlab[NERR-1]);
+ nexterror();
+}
+
+#include "errstr.h"
+
+/* Set kernel error string */
+void
+kerrstr(char *err, uint size)
+{
+
+ char tmp[ERRMAX];
+
+ kstrcpy(tmp, up->env->errstr, sizeof(tmp));
+ kstrcpy(up->env->errstr, err, ERRMAX);
+ kstrcpy(err, tmp, size);
+}
+
+/* Get kernel error string */
+void
+kgerrstr(char *err, uint size)
+{
+ char tmp[ERRMAX];
+
+ kstrcpy(tmp, up->env->errstr, sizeof(tmp));
+ kstrcpy(up->env->errstr, err, ERRMAX);
+ kstrcpy(err, tmp, size);
+}
+
+/* Set kernel error string, using formatted print */
+void
+kwerrstr(char *fmt, ...)
+{
+ va_list arg;
+ char buf[ERRMAX];
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ kstrcpy(up->env->errstr, buf, ERRMAX);
+}
+
+void
+werrstr(char *fmt, ...)
+{
+ va_list arg;
+ char buf[ERRMAX];
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ kstrcpy(up->env->errstr, buf, ERRMAX);
+}
+
+void
+nexterror(void)
+{
+ gotolabel(&up->errlab[--up->nerrlab]);
+}
+
+/* for dynamic modules - functions not macros */
+
+void*
+waserr(void)
+{
+ up->nerrlab++;
+ return &up->errlab[up->nerrlab-1];
+}
+
+void
+poperr(void)
+{
+ up->nerrlab--;
+}
+
+char*
+enverror(void)
+{
+ return up->env->errstr;
+}
+
+void
+exhausted(char *resource)
+{
+ char buf[64];
+
+ snprint(buf, sizeof(buf), "no free %s", resource);
+ iprint("%s\n", buf);
+ error(buf);
+}
+
+/*
+ * change ownership to 'new' of all processes owned by 'old'. Used when
+ * eve changes.
+ */
+void
+renameuser(char *old, char *new)
+{
+ Proc *p, *ep;
+ Osenv *o;
+
+ ep = procalloc.arena+conf.nproc;
+ for(p = procalloc.arena; p < ep; p++) {
+ o = &p->defenv;
+ if(o->user != nil && strcmp(o->user, old) == 0)
+ kstrdup(&o->user, new);
+ }
+}
+
+int
+return0(void*)
+{
+ return 0;
+}
+
+void
+setid(char *name, int owner)
+{
+ if(!owner || iseve())
+ kstrdup(&up->env->user, name);
+}
+
+void
+rptwakeup(void *o, void *ar)
+{
+ Rept *r;
+
+ r = ar;
+ if(r == nil)
+ return;
+ lock(&r->l);
+ r->o = o;
+ unlock(&r->l);
+ wakeup(&r->r);
+}
+
+static int
+rptactive(void *a)
+{
+ Rept *r = a;
+ int i;
+ lock(&r->l);
+ i = r->active(r->o);
+ unlock(&r->l);
+ return i;
+}
+
+static void
+rproc(void *a)
+{
+ long now, then;
+ ulong t;
+ int i;
+ void *o;
+ Rept *r;
+
+ r = a;
+ t = r->t;
+
+Wait:
+ sleep(&r->r, rptactive, r);
+ lock(&r->l);
+ o = r->o;
+ unlock(&r->l);
+ then = TK2MS(MACHP(0)->ticks);
+ for(;;){
+ tsleep(&up->sleep, return0, nil, t);
+ now = TK2MS(MACHP(0)->ticks);
+ if(waserror())
+ break;
+ i = r->ck(o, now-then);
+ poperror();
+ if(i == -1)
+ goto Wait;
+ if(i == 0)
+ continue;
+ then = now;
+ acquire();
+ if(waserror()) {
+ release();
+ break;
+ }
+ r->f(o);
+ poperror();
+ release();
+ }
+ pexit("", 0);
+}
+
+void*
+rptproc(char *s, int t, void *o, int (*active)(void*), int (*ck)(void*, int), void (*f)(void*))
+{
+ Rept *r;
+
+ r = mallocz(sizeof(Rept), 1);
+ if(r == nil)
+ return nil;
+ r->t = t;
+ r->active = active;
+ r->ck = ck;
+ r->f = f;
+ r->o = o;
+ kproc(s, rproc, r, KPDUP);
+ return r;
+}
diff --git a/os/port/qio.c b/os/port/qio.c
new file mode 100644
index 00000000..d44ea6b8
--- /dev/null
+++ b/os/port/qio.c
@@ -0,0 +1,1529 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+static ulong padblockcnt;
+static ulong concatblockcnt;
+static ulong pullupblockcnt;
+static ulong copyblockcnt;
+static ulong consumecnt;
+static ulong producecnt;
+static ulong qcopycnt;
+
+static int debugging;
+
+#define QDEBUG if(0)
+
+/*
+ * IO queues
+ */
+typedef struct Queue Queue;
+
+struct Queue
+{
+ Lock;
+
+ Block* bfirst; /* buffer */
+ Block* blast;
+
+ int len; /* bytes allocated to queue */
+ int dlen; /* data bytes in queue */
+ int limit; /* max bytes in queue */
+ int inilim; /* initial limit */
+ int state;
+ int noblock; /* true if writes return immediately when q full */
+ int eof; /* number of eofs read by user */
+
+ void (*kick)(void*); /* restart output */
+ void (*bypass)(void*, Block*); /* bypass queue altogether */
+ void* arg; /* argument to kick */
+
+ QLock rlock; /* mutex for reading processes */
+ Rendez rr; /* process waiting to read */
+ QLock wlock; /* mutex for writing processes */
+ Rendez wr; /* process waiting to write */
+
+ char err[ERRMAX];
+};
+
+enum
+{
+ Maxatomic = 64*1024,
+};
+
+uint qiomaxatomic = Maxatomic;
+
+void
+ixsummary(void)
+{
+ debugging ^= 1;
+ iallocsummary();
+ print("pad %lud, concat %lud, pullup %lud, copy %lud\n",
+ padblockcnt, concatblockcnt, pullupblockcnt, copyblockcnt);
+ print("consume %lud, produce %lud, qcopy %lud\n",
+ consumecnt, producecnt, qcopycnt);
+}
+
+/*
+ * free a list of blocks
+ */
+void
+freeblist(Block *b)
+{
+ Block *next;
+
+ for(; b != 0; b = next){
+ next = b->next;
+ b->next = 0;
+ freeb(b);
+ }
+}
+
+/*
+ * pad a block to the front (or the back if size is negative)
+ */
+Block*
+padblock(Block *bp, int size)
+{
+ int n;
+ Block *nbp;
+
+ QDEBUG checkb(bp, "padblock 1");
+ if(size >= 0){
+ if(bp->rp - bp->base >= size){
+ bp->rp -= size;
+ return bp;
+ }
+
+ if(bp->next)
+ panic("padblock 0x%luX", getcallerpc(&bp));
+ n = BLEN(bp);
+ padblockcnt++;
+ nbp = allocb(size+n);
+ nbp->rp += size;
+ nbp->wp = nbp->rp;
+ memmove(nbp->wp, bp->rp, n);
+ nbp->wp += n;
+ freeb(bp);
+ nbp->rp -= size;
+ } else {
+ size = -size;
+
+ if(bp->next)
+ panic("padblock 0x%luX", getcallerpc(&bp));
+
+ if(bp->lim - bp->wp >= size)
+ return bp;
+
+ n = BLEN(bp);
+ padblockcnt++;
+ nbp = allocb(size+n);
+ memmove(nbp->wp, bp->rp, n);
+ nbp->wp += n;
+ freeb(bp);
+ }
+ QDEBUG checkb(nbp, "padblock 1");
+ return nbp;
+}
+
+/*
+ * return count of bytes in a string of blocks
+ */
+int
+blocklen(Block *bp)
+{
+ int len;
+
+ len = 0;
+ while(bp) {
+ len += BLEN(bp);
+ bp = bp->next;
+ }
+ return len;
+}
+
+/*
+ * return count of space in blocks
+ */
+int
+blockalloclen(Block *bp)
+{
+ int len;
+
+ len = 0;
+ while(bp) {
+ len += BALLOC(bp);
+ bp = bp->next;
+ }
+ return len;
+}
+
+/*
+ * copy the string of blocks into
+ * a single block and free the string
+ */
+Block*
+concatblock(Block *bp)
+{
+ int len;
+ Block *nb, *f;
+
+ if(bp->next == 0)
+ return bp;
+
+ nb = allocb(blocklen(bp));
+ for(f = bp; f; f = f->next) {
+ len = BLEN(f);
+ memmove(nb->wp, f->rp, len);
+ nb->wp += len;
+ }
+ concatblockcnt += BLEN(nb);
+ freeblist(bp);
+ QDEBUG checkb(nb, "concatblock 1");
+ return nb;
+}
+
+/*
+ * make sure the first block has at least n bytes
+ */
+Block*
+pullupblock(Block *bp, int n)
+{
+ int i;
+ Block *nbp;
+
+ /*
+ * this should almost always be true, it's
+ * just to avoid every caller checking.
+ */
+ if(BLEN(bp) >= n)
+ return bp;
+
+ /*
+ * if not enough room in the first block,
+ * add another to the front of the list.
+ */
+ if(bp->lim - bp->rp < n){
+ nbp = allocb(n);
+ nbp->next = bp;
+ bp = nbp;
+ }
+
+ /*
+ * copy bytes from the trailing blocks into the first
+ */
+ n -= BLEN(bp);
+ while(nbp = bp->next){
+ i = BLEN(nbp);
+ if(i > n) {
+ memmove(bp->wp, nbp->rp, n);
+ pullupblockcnt++;
+ bp->wp += n;
+ nbp->rp += n;
+ QDEBUG checkb(bp, "pullupblock 1");
+ return bp;
+ }
+ else {
+ memmove(bp->wp, nbp->rp, i);
+ pullupblockcnt++;
+ bp->wp += i;
+ bp->next = nbp->next;
+ nbp->next = 0;
+ freeb(nbp);
+ n -= i;
+ if(n == 0){
+ QDEBUG checkb(bp, "pullupblock 2");
+ return bp;
+ }
+ }
+ }
+ freeb(bp);
+ return 0;
+}
+
+/*
+ * make sure the first block has at least n bytes
+ */
+Block*
+pullupqueue(Queue *q, int n)
+{
+ Block *b;
+
+ if(BLEN(q->bfirst) >= n)
+ return q->bfirst;
+ q->bfirst = pullupblock(q->bfirst, n);
+ for(b = q->bfirst; b != nil && b->next != nil; b = b->next)
+ ;
+ q->blast = b;
+ return q->bfirst;
+}
+
+/*
+ * trim to len bytes starting at offset
+ */
+Block *
+trimblock(Block *bp, int offset, int len)
+{
+ ulong l;
+ Block *nb, *startb;
+
+ QDEBUG checkb(bp, "trimblock 1");
+ if(blocklen(bp) < offset+len) {
+ freeblist(bp);
+ return nil;
+ }
+
+ while((l = BLEN(bp)) < offset) {
+ offset -= l;
+ nb = bp->next;
+ bp->next = nil;
+ freeb(bp);
+ bp = nb;
+ }
+
+ startb = bp;
+ bp->rp += offset;
+
+ while((l = BLEN(bp)) < len) {
+ len -= l;
+ bp = bp->next;
+ }
+
+ bp->wp -= (BLEN(bp) - len);
+
+ if(bp->next) {
+ freeblist(bp->next);
+ bp->next = nil;
+ }
+
+ return startb;
+}
+
+/*
+ * copy 'count' bytes into a new block
+ */
+Block*
+copyblock(Block *bp, int count)
+{
+ int l;
+ Block *nbp;
+
+ QDEBUG checkb(bp, "copyblock 0");
+ nbp = allocb(count);
+ for(; count > 0 && bp != 0; bp = bp->next){
+ l = BLEN(bp);
+ if(l > count)
+ l = count;
+ memmove(nbp->wp, bp->rp, l);
+ nbp->wp += l;
+ count -= l;
+ }
+ if(count > 0){
+ memset(nbp->wp, 0, count);
+ nbp->wp += count;
+ }
+ copyblockcnt++;
+ QDEBUG checkb(nbp, "copyblock 1");
+
+ return nbp;
+}
+
+Block*
+adjustblock(Block* bp, int len)
+{
+ int n;
+ Block *nbp;
+
+ if(len < 0){
+ freeb(bp);
+ return nil;
+ }
+
+ if(bp->rp+len > bp->lim){
+ nbp = copyblock(bp, len);
+ freeblist(bp);
+ QDEBUG checkb(nbp, "adjustblock 1");
+
+ return nbp;
+ }
+
+ n = BLEN(bp);
+ if(len > n)
+ memset(bp->wp, 0, len-n);
+ bp->wp = bp->rp+len;
+ QDEBUG checkb(bp, "adjustblock 2");
+
+ return bp;
+}
+
+
+/*
+ * throw away up to count bytes from a
+ * list of blocks. Return count of bytes
+ * thrown away.
+ */
+int
+pullblock(Block **bph, int count)
+{
+ Block *bp;
+ int n, bytes;
+
+ bytes = 0;
+ if(bph == nil)
+ return 0;
+
+ while(*bph != nil && count != 0) {
+ bp = *bph;
+ n = BLEN(bp);
+ if(count < n)
+ n = count;
+ bytes += n;
+ count -= n;
+ bp->rp += n;
+ QDEBUG checkb(bp, "pullblock ");
+ if(BLEN(bp) == 0) {
+ *bph = bp->next;
+ bp->next = nil;
+ freeb(bp);
+ }
+ }
+ return bytes;
+}
+
+/*
+ * get next block from a queue, return null if nothing there
+ */
+Block*
+qget(Queue *q)
+{
+ int dowakeup;
+ Block *b;
+
+ /* sync with qwrite */
+ ilock(q);
+
+ b = q->bfirst;
+ if(b == nil){
+ q->state |= Qstarve;
+ iunlock(q);
+ return nil;
+ }
+ q->bfirst = b->next;
+ b->next = 0;
+ q->len -= BALLOC(b);
+ q->dlen -= BLEN(b);
+ QDEBUG checkb(b, "qget");
+
+ /* if writer flow controlled, restart */
+ if((q->state & Qflow) && q->len < q->limit/2){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ } else
+ dowakeup = 0;
+
+ iunlock(q);
+
+ if(dowakeup)
+ wakeup(&q->wr);
+
+ return b;
+}
+
+/*
+ * throw away the next 'len' bytes in the queue
+ * returning the number actually discarded
+ */
+int
+qdiscard(Queue *q, int len)
+{
+ Block *b;
+ int dowakeup, n, sofar;
+
+ ilock(q);
+ for(sofar = 0; sofar < len; sofar += n){
+ b = q->bfirst;
+ if(b == nil)
+ break;
+ QDEBUG checkb(b, "qdiscard");
+ n = BLEN(b);
+ if(n <= len - sofar){
+ q->bfirst = b->next;
+ b->next = 0;
+ q->len -= BALLOC(b);
+ q->dlen -= BLEN(b);
+ freeb(b);
+ } else {
+ n = len - sofar;
+ b->rp += n;
+ q->dlen -= n;
+ }
+ }
+
+ /*
+ * if writer flow controlled, restart
+ *
+ * This used to be
+ * q->len < q->limit/2
+ * but it slows down tcp too much for certain write sizes.
+ * I really don't understand it completely. It may be
+ * due to the queue draining so fast that the transmission
+ * stalls waiting for the app to produce more data. - presotto
+ */
+ if((q->state & Qflow) && q->len < q->limit){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ } else
+ dowakeup = 0;
+
+ iunlock(q);
+
+ if(dowakeup)
+ wakeup(&q->wr);
+
+ return sofar;
+}
+
+/*
+ * Interrupt level copy out of a queue, return # bytes copied.
+ */
+int
+qconsume(Queue *q, void *vp, int len)
+{
+ Block *b;
+ int n, dowakeup;
+ uchar *p = vp;
+ Block *tofree = nil;
+
+ /* sync with qwrite */
+ ilock(q);
+
+ for(;;) {
+ b = q->bfirst;
+ if(b == 0){
+ q->state |= Qstarve;
+ iunlock(q);
+ return -1;
+ }
+ QDEBUG checkb(b, "qconsume 1");
+
+ n = BLEN(b);
+ if(n > 0)
+ break;
+ q->bfirst = b->next;
+ q->len -= BALLOC(b);
+
+ /* remember to free this */
+ b->next = tofree;
+ tofree = b;
+ };
+
+ if(n < len)
+ len = n;
+ memmove(p, b->rp, len);
+ consumecnt += n;
+ b->rp += len;
+ q->dlen -= len;
+
+ /* discard the block if we're done with it */
+ if((q->state & Qmsg) || len == n){
+ q->bfirst = b->next;
+ b->next = 0;
+ q->len -= BALLOC(b);
+ q->dlen -= BLEN(b);
+
+ /* remember to free this */
+ b->next = tofree;
+ tofree = b;
+ }
+
+ /* if writer flow controlled, restart */
+ if((q->state & Qflow) && q->len < q->limit/2){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ } else
+ dowakeup = 0;
+
+ iunlock(q);
+
+ if(dowakeup)
+ wakeup(&q->wr);
+
+ if(tofree != nil)
+ freeblist(tofree);
+
+ return len;
+}
+
+int
+qpass(Queue *q, Block *b)
+{
+ int dlen, len, dowakeup;
+
+ /* sync with qread */
+ dowakeup = 0;
+ ilock(q);
+ if(q->len >= q->limit){
+ freeblist(b);
+ iunlock(q);
+ return -1;
+ }
+ if(q->state & Qclosed){
+ len = blocklen(b);
+ freeblist(b);
+ iunlock(q);
+ return len;
+ }
+
+ /* add buffer to queue */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ len = BALLOC(b);
+ dlen = BLEN(b);
+ QDEBUG checkb(b, "qpass");
+ while(b->next){
+ b = b->next;
+ QDEBUG checkb(b, "qpass");
+ len += BALLOC(b);
+ dlen += BLEN(b);
+ }
+ q->blast = b;
+ q->len += len;
+ q->dlen += dlen;
+
+ if(q->len >= q->limit/2)
+ q->state |= Qflow;
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+ iunlock(q);
+
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ return len;
+}
+
+int
+qpassnolim(Queue *q, Block *b)
+{
+ int dlen, len, dowakeup;
+
+ /* sync with qread */
+ dowakeup = 0;
+ ilock(q);
+
+ if(q->state & Qclosed){
+ freeblist(b);
+ iunlock(q);
+ return BALLOC(b);
+ }
+
+ /* add buffer to queue */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ len = BALLOC(b);
+ dlen = BLEN(b);
+ QDEBUG checkb(b, "qpass");
+ while(b->next){
+ b = b->next;
+ QDEBUG checkb(b, "qpass");
+ len += BALLOC(b);
+ dlen += BLEN(b);
+ }
+ q->blast = b;
+ q->len += len;
+ q->dlen += dlen;
+
+ if(q->len >= q->limit/2)
+ q->state |= Qflow;
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+ iunlock(q);
+
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ return len;
+}
+
+/*
+ * if the allocated space is way out of line with the used
+ * space, reallocate to a smaller block
+ */
+Block*
+packblock(Block *bp)
+{
+ Block **l, *nbp;
+ int n;
+
+ for(l = &bp; *l; l = &(*l)->next){
+ nbp = *l;
+ n = BLEN(nbp);
+ if((n<<2) < BALLOC(nbp)){
+ *l = allocb(n);
+ memmove((*l)->wp, nbp->rp, n);
+ (*l)->wp += n;
+ (*l)->next = nbp->next;
+ freeb(nbp);
+ }
+ }
+
+ return bp;
+}
+
+int
+qproduce(Queue *q, void *vp, int len)
+{
+ Block *b;
+ int dowakeup;
+ uchar *p = vp;
+
+ /* sync with qread */
+ dowakeup = 0;
+ ilock(q);
+
+ /* no waiting receivers, room in buffer? */
+ if(q->len >= q->limit){
+ q->state |= Qflow;
+ iunlock(q);
+ return -1;
+ }
+
+ /* save in buffer */
+ /* use Qcoalesce here to save storage */
+ b = q->blast;
+ if((q->state & Qcoalesce)==0 || q->bfirst==nil || b->lim-b->wp < len){
+ /* need a new block */
+ b = iallocb(len);
+ if(b == 0){
+ iunlock(q);
+ return 0;
+ }
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->blast = b;
+ /* b->next = 0; done by iallocb() */
+ q->len += BALLOC(b);
+ }
+ memmove(b->wp, p, len);
+ producecnt += len;
+ b->wp += len;
+ q->dlen += len;
+ QDEBUG checkb(b, "qproduce");
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+
+ if(q->len >= q->limit)
+ q->state |= Qflow;
+ iunlock(q);
+
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ return len;
+}
+
+/*
+ * copy from offset in the queue
+ */
+Block*
+qcopy(Queue *q, int len, ulong offset)
+{
+ int sofar;
+ int n;
+ Block *b, *nb;
+ uchar *p;
+
+ nb = allocb(len);
+
+ ilock(q);
+
+ /* go to offset */
+ b = q->bfirst;
+ for(sofar = 0; ; sofar += n){
+ if(b == nil){
+ iunlock(q);
+ return nb;
+ }
+ n = BLEN(b);
+ if(sofar + n > offset){
+ p = b->rp + offset - sofar;
+ n -= offset - sofar;
+ break;
+ }
+ QDEBUG checkb(b, "qcopy");
+ b = b->next;
+ }
+
+ /* copy bytes from there */
+ for(sofar = 0; sofar < len;){
+ if(n > len - sofar)
+ n = len - sofar;
+ memmove(nb->wp, p, n);
+ qcopycnt += n;
+ sofar += n;
+ nb->wp += n;
+ b = b->next;
+ if(b == nil)
+ break;
+ n = BLEN(b);
+ p = b->rp;
+ }
+ iunlock(q);
+
+ return nb;
+}
+
+/*
+ * called by non-interrupt code
+ */
+Queue*
+qopen(int limit, int msg, void (*kick)(void*), void *arg)
+{
+ Queue *q;
+
+ q = malloc(sizeof(Queue));
+ if(q == 0)
+ return 0;
+
+ q->limit = q->inilim = limit;
+ q->kick = kick;
+ q->arg = arg;
+ q->state = msg;
+ q->state |= Qstarve;
+ q->eof = 0;
+ q->noblock = 0;
+
+ return q;
+}
+
+/* open a queue to be bypassed */
+Queue*
+qbypass(void (*bypass)(void*, Block*), void *arg)
+{
+ Queue *q;
+
+ q = malloc(sizeof(Queue));
+ if(q == 0)
+ return 0;
+
+ q->limit = 0;
+ q->arg = arg;
+ q->bypass = bypass;
+ q->state = 0;
+
+ return q;
+}
+
+static int
+notempty(void *a)
+{
+ Queue *q = a;
+
+ return (q->state & Qclosed) || q->bfirst != 0;
+}
+
+/*
+ * wait for the queue to be non-empty or closed.
+ * called with q ilocked.
+ */
+static int
+qwait(Queue *q)
+{
+ /* wait for data */
+ for(;;){
+ if(q->bfirst != nil)
+ break;
+
+ if(q->state & Qclosed){
+ if(++q->eof > 3)
+ return -1;
+ if(*q->err && strcmp(q->err, Ehungup) != 0)
+ return -1;
+ return 0;
+ }
+
+ q->state |= Qstarve; /* flag requesting producer to wake me */
+ iunlock(q);
+ sleep(&q->rr, notempty, q);
+ ilock(q);
+ }
+ return 1;
+}
+
+/*
+ * add a block list to a queue
+ */
+void
+qaddlist(Queue *q, Block *b)
+{
+ /* queue the block */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->len += blockalloclen(b);
+ q->dlen += blocklen(b);
+ while(b->next)
+ b = b->next;
+ q->blast = b;
+}
+
+/*
+ * called with q ilocked
+ */
+Block*
+qremove(Queue *q)
+{
+ Block *b;
+
+ b = q->bfirst;
+ if(b == nil)
+ return nil;
+ q->bfirst = b->next;
+ b->next = nil;
+ q->dlen -= BLEN(b);
+ q->len -= BALLOC(b);
+ QDEBUG checkb(b, "qremove");
+ return b;
+}
+
+/*
+ * copy the contents of a string of blocks into
+ * memory. emptied blocks are freed. return
+ * pointer to first unconsumed block.
+ */
+Block*
+bl2mem(uchar *p, Block *b, int n)
+{
+ int i;
+ Block *next;
+
+ for(; b != nil; b = next){
+ i = BLEN(b);
+ if(i > n){
+ memmove(p, b->rp, n);
+ b->rp += n;
+ return b;
+ }
+ memmove(p, b->rp, i);
+ n -= i;
+ p += i;
+ b->rp += i;
+ next = b->next;
+ freeb(b);
+ }
+ return nil;
+}
+
+/*
+ * copy the contents of memory into a string of blocks.
+ * return nil on error.
+ */
+Block*
+mem2bl(uchar *p, int len)
+{
+ int n;
+ Block *b, *first, **l;
+
+ first = nil;
+ l = &first;
+ if(waserror()){
+ freeblist(first);
+ nexterror();
+ }
+ do {
+ n = len;
+ if(n > Maxatomic)
+ n = Maxatomic;
+
+ *l = b = allocb(n);
+ setmalloctag(b, getcallerpc(&p));
+ memmove(b->wp, p, n);
+ b->wp += n;
+ p += n;
+ len -= n;
+ l = &b->next;
+ } while(len > 0);
+ poperror();
+
+ return first;
+}
+
+/*
+ * put a block back to the front of the queue
+ * called with q ilocked
+ */
+void
+qputback(Queue *q, Block *b)
+{
+ b->next = q->bfirst;
+ if(q->bfirst == nil)
+ q->blast = b;
+ q->bfirst = b;
+ q->len += BALLOC(b);
+ q->dlen += BLEN(b);
+}
+
+/*
+ * flow control, get producer going again
+ * called with q ilocked
+ */
+static void
+qwakeup_iunlock(Queue *q)
+{
+ int dowakeup = 0;
+
+ /* if writer flow controlled, restart */
+ if((q->state & Qflow) && q->len < q->limit/2){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ }
+
+ iunlock(q);
+
+ /* wakeup flow controlled writers */
+ if(dowakeup){
+ if(q->kick)
+ q->kick(q->arg);
+ wakeup(&q->wr);
+ }
+}
+
+/*
+ * get next block from a queue (up to a limit)
+ */
+Block*
+qbread(Queue *q, int len)
+{
+ Block *b, *nb;
+ int n;
+
+ qlock(&q->rlock);
+ if(waserror()){
+ qunlock(&q->rlock);
+ nexterror();
+ }
+
+ ilock(q);
+ switch(qwait(q)){
+ case 0:
+ /* queue closed */
+ iunlock(q);
+ qunlock(&q->rlock);
+ poperror();
+ return nil;
+ case -1:
+ /* multiple reads on a closed queue */
+ iunlock(q);
+ error(q->err);
+ }
+
+ /* if we get here, there's at least one block in the queue */
+ b = qremove(q);
+ n = BLEN(b);
+
+ /* split block if it's too big and this is not a message queue */
+ nb = b;
+ if(n > len){
+ if((q->state&Qmsg) == 0){
+ n -= len;
+ b = allocb(n);
+ memmove(b->wp, nb->rp+len, n);
+ b->wp += n;
+ qputback(q, b);
+ }
+ nb->wp = nb->rp + len;
+ }
+
+ /* restart producer */
+ qwakeup_iunlock(q);
+
+ poperror();
+ qunlock(&q->rlock);
+ return nb;
+}
+
+/*
+ * read a queue. if no data is queued, post a Block
+ * and wait on its Rendez.
+ */
+long
+qread(Queue *q, void *vp, int len)
+{
+ Block *b, *first, **l;
+ int m, n;
+
+ qlock(&q->rlock);
+ if(waserror()){
+ qunlock(&q->rlock);
+ nexterror();
+ }
+
+ ilock(q);
+again:
+ switch(qwait(q)){
+ case 0:
+ /* queue closed */
+ iunlock(q);
+ qunlock(&q->rlock);
+ poperror();
+ return 0;
+ case -1:
+ /* multiple reads on a closed queue */
+ iunlock(q);
+ error(q->err);
+ }
+
+ /* if we get here, there's at least one block in the queue */
+ if(q->state & Qcoalesce){
+ /* when coalescing, 0 length blocks just go away */
+ b = q->bfirst;
+ if(BLEN(b) <= 0){
+ freeb(qremove(q));
+ goto again;
+ }
+
+ /* grab the first block plus as many
+ * following blocks as will completely
+ * fit in the read.
+ */
+ n = 0;
+ l = &first;
+ m = BLEN(b);
+ for(;;) {
+ *l = qremove(q);
+ l = &b->next;
+ n += m;
+
+ b = q->bfirst;
+ if(b == nil)
+ break;
+ m = BLEN(b);
+ if(n+m > len)
+ break;
+ }
+ } else {
+ first = qremove(q);
+ n = BLEN(first);
+ }
+
+ /* copy to user space outside of the ilock */
+ iunlock(q);
+ b = bl2mem(vp, first, len);
+ ilock(q);
+
+ /* take care of any left over partial block */
+ if(b != nil){
+ n -= BLEN(b);
+ if(q->state & Qmsg)
+ freeb(b);
+ else
+ qputback(q, b);
+ }
+
+ /* restart producer */
+ qwakeup_iunlock(q);
+
+ poperror();
+ qunlock(&q->rlock);
+ return n;
+}
+
+static int
+qnotfull(void *a)
+{
+ Queue *q = a;
+
+ return q->len < q->limit || (q->state & Qclosed);
+}
+
+ulong noblockcnt;
+
+/*
+ * add a block to a queue obeying flow control
+ */
+long
+qbwrite(Queue *q, Block *b)
+{
+ int n, dowakeup;
+
+ n = BLEN(b);
+
+ if(q->bypass){
+ (*q->bypass)(q->arg, b);
+ return n;
+ }
+
+ dowakeup = 0;
+ qlock(&q->wlock);
+ if(waserror()){
+ if(b != nil)
+ freeb(b);
+ qunlock(&q->wlock);
+ nexterror();
+ }
+
+ ilock(q);
+
+ /* give up if the queue is closed */
+ if(q->state & Qclosed){
+ iunlock(q);
+ error(q->err);
+ }
+
+ /* if nonblocking, don't queue over the limit */
+ if(q->len >= q->limit){
+ if(q->noblock){
+ iunlock(q);
+ freeb(b);
+ noblockcnt += n;
+ qunlock(&q->wlock);
+ poperror();
+ return n;
+ }
+ }
+
+ /* queue the block */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->blast = b;
+ b->next = 0;
+ q->len += BALLOC(b);
+ q->dlen += n;
+ QDEBUG checkb(b, "qbwrite");
+ b = nil;
+
+ /* make sure other end gets awakened */
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+ iunlock(q);
+
+ /* get output going again */
+ if(q->kick && (dowakeup || (q->state&Qkick)))
+ q->kick(q->arg);
+
+ /* wakeup anyone consuming at the other end */
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ /*
+ * flow control, wait for queue to get below the limit
+ * before allowing the process to continue and queue
+ * more. We do this here so that postnote can only
+ * interrupt us after the data has been queued. This
+ * means that things like 9p flushes and ssl messages
+ * will not be disrupted by software interrupts.
+ *
+ * Note - this is moderately dangerous since a process
+ * that keeps getting interrupted and rewriting will
+ * queue infinite crud.
+ */
+ for(;;){
+ if(q->noblock || qnotfull(q))
+ break;
+
+ ilock(q);
+ q->state |= Qflow;
+ iunlock(q);
+ sleep(&q->wr, qnotfull, q);
+ }
+ USED(b);
+
+ qunlock(&q->wlock);
+ poperror();
+ return n;
+}
+
+/*
+ * write to a queue. only Maxatomic bytes at a time is atomic.
+ */
+int
+qwrite(Queue *q, void *vp, int len)
+{
+ int n, sofar;
+ Block *b;
+ uchar *p = vp;
+
+ QDEBUG if(!islo())
+ print("qwrite hi %lux\n", getcallerpc(&q));
+
+ sofar = 0;
+ do {
+ n = len-sofar;
+ if(n > Maxatomic)
+ n = Maxatomic;
+
+ b = allocb(n);
+ setmalloctag(b, getcallerpc(&q));
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ memmove(b->wp, p+sofar, n);
+ poperror();
+ b->wp += n;
+
+ qbwrite(q, b);
+
+ sofar += n;
+ } while(sofar < len && (q->state & Qmsg) == 0);
+
+ return len;
+}
+
+/*
+ * used by print() to write to a queue. Since we may be splhi or not in
+ * a process, don't qlock.
+ */
+int
+qiwrite(Queue *q, void *vp, int len)
+{
+ int n, sofar, dowakeup;
+ Block *b;
+ uchar *p = vp;
+
+ dowakeup = 0;
+
+ sofar = 0;
+ do {
+ n = len-sofar;
+ if(n > Maxatomic)
+ n = Maxatomic;
+
+ b = iallocb(n);
+ if(b == nil)
+ break;
+ memmove(b->wp, p+sofar, n);
+ b->wp += n;
+
+ ilock(q);
+
+ QDEBUG checkb(b, "qiwrite");
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->blast = b;
+ q->len += BALLOC(b);
+ q->dlen += n;
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+
+ iunlock(q);
+
+ if(dowakeup){
+ if(q->kick)
+ q->kick(q->arg);
+ wakeup(&q->rr);
+ }
+
+ sofar += n;
+ } while(sofar < len && (q->state & Qmsg) == 0);
+
+ return sofar;
+}
+
+/*
+ * be extremely careful when calling this,
+ * as there is no reference accounting
+ */
+void
+qfree(Queue *q)
+{
+ qclose(q);
+ free(q);
+}
+
+/*
+ * Mark a queue as closed. No further IO is permitted.
+ * All blocks are released.
+ */
+void
+qclose(Queue *q)
+{
+ Block *bfirst;
+
+ if(q == nil)
+ return;
+
+ /* mark it */
+ ilock(q);
+ q->state |= Qclosed;
+ q->state &= ~(Qflow|Qstarve);
+ strcpy(q->err, Ehungup);
+ bfirst = q->bfirst;
+ q->bfirst = 0;
+ q->len = 0;
+ q->dlen = 0;
+ q->noblock = 0;
+ iunlock(q);
+
+ /* free queued blocks */
+ freeblist(bfirst);
+
+ /* wake up readers/writers */
+ wakeup(&q->rr);
+ wakeup(&q->wr);
+}
+
+/*
+ * Mark a queue as closed. Wakeup any readers. Don't remove queued
+ * blocks.
+ */
+void
+qhangup(Queue *q, char *msg)
+{
+ /* mark it */
+ ilock(q);
+ q->state |= Qclosed;
+ if(msg == 0 || *msg == 0)
+ strcpy(q->err, Ehungup);
+ else
+ strncpy(q->err, msg, ERRMAX-1);
+ iunlock(q);
+
+ /* wake up readers/writers */
+ wakeup(&q->rr);
+ wakeup(&q->wr);
+}
+
+/*
+ * return non-zero if the q is hungup
+ */
+int
+qisclosed(Queue *q)
+{
+ return q->state & Qclosed;
+}
+
+/*
+ * mark a queue as no longer hung up
+ */
+void
+qreopen(Queue *q)
+{
+ ilock(q);
+ q->state &= ~Qclosed;
+ q->state |= Qstarve;
+ q->eof = 0;
+ q->limit = q->inilim;
+ iunlock(q);
+}
+
+/*
+ * return bytes queued
+ */
+int
+qlen(Queue *q)
+{
+ return q->dlen;
+}
+
+/*
+ * return space remaining before flow control
+ */
+int
+qwindow(Queue *q)
+{
+ int l;
+
+ l = q->limit - q->len;
+ if(l < 0)
+ l = 0;
+ return l;
+}
+
+/*
+ * return true if we can read without blocking
+ */
+int
+qcanread(Queue *q)
+{
+ return q->bfirst!=0;
+}
+
+/*
+ * change queue limit
+ */
+void
+qsetlimit(Queue *q, int limit)
+{
+ q->limit = limit;
+}
+
+/*
+ * set blocking/nonblocking
+ */
+void
+qnoblock(Queue *q, int onoff)
+{
+ q->noblock = onoff;
+}
+
+/*
+ * flush the output queue
+ */
+void
+qflush(Queue *q)
+{
+ Block *bfirst;
+
+ /* mark it */
+ ilock(q);
+ bfirst = q->bfirst;
+ q->bfirst = 0;
+ q->len = 0;
+ q->dlen = 0;
+ iunlock(q);
+
+ /* free queued blocks */
+ freeblist(bfirst);
+
+ /* wake up readers/writers */
+ wakeup(&q->wr);
+}
+
+int
+qfull(Queue *q)
+{
+ return q->state & Qflow;
+}
+
+int
+qstate(Queue *q)
+{
+ return q->state;
+}
+
+void
+qdump(Queue *q)
+{
+ if(q)
+ kprint("q=%p bfirst=%p blast=%p len=%d dlen=%d limit=%d state=#%x\n",
+ q, q->bfirst, q->blast, q->len, q->dlen, q->limit, q->state);
+}
diff --git a/os/port/qlock.c b/os/port/qlock.c
new file mode 100644
index 00000000..63d7c329
--- /dev/null
+++ b/os/port/qlock.c
@@ -0,0 +1,111 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+void
+qlock(QLock *q)
+{
+ Proc *p, *mp;
+
+ lock(&q->use);
+ if(!q->locked) {
+ q->locked = 1;
+ unlock(&q->use);
+ return;
+ }
+ p = q->tail;
+ mp = up;
+ if(p == 0)
+ q->head = mp;
+ else
+ p->qnext = mp;
+ q->tail = mp;
+ mp->qnext = 0;
+ mp->state = Queueing;
+ up->qpc = getcallerpc(&q);
+ unlock(&q->use);
+ sched();
+}
+
+int
+canqlock(QLock *q)
+{
+ if(!canlock(&q->use))
+ return 0;
+ if(q->locked){
+ unlock(&q->use);
+ return 0;
+ }
+ q->locked = 1;
+ unlock(&q->use);
+ return 1;
+}
+
+void
+qunlock(QLock *q)
+{
+ Proc *p;
+
+ lock(&q->use);
+ p = q->head;
+ if(p) {
+ q->head = p->qnext;
+ if(q->head == 0)
+ q->tail = 0;
+ unlock(&q->use);
+ ready(p);
+ return;
+ }
+ q->locked = 0;
+ unlock(&q->use);
+}
+
+void
+rlock(RWlock *l)
+{
+ qlock(&l->x); /* wait here for writers and exclusion */
+ lock(l);
+ l->readers++;
+ canqlock(&l->k); /* block writers if we are the first reader */
+ unlock(l);
+ qunlock(&l->x);
+}
+
+/* same as rlock but punts if there are any writers waiting */
+int
+canrlock(RWlock *l)
+{
+ if (!canqlock(&l->x))
+ return 0;
+ lock(l);
+ l->readers++;
+ canqlock(&l->k); /* block writers if we are the first reader */
+ unlock(l);
+ qunlock(&l->x);
+ return 1;
+}
+
+void
+runlock(RWlock *l)
+{
+ lock(l);
+ if(--l->readers == 0) /* last reader out allows writers */
+ qunlock(&l->k);
+ unlock(l);
+}
+
+void
+wlock(RWlock *l)
+{
+ qlock(&l->x); /* wait here for writers and exclusion */
+ qlock(&l->k); /* wait here for last reader */
+}
+
+void
+wunlock(RWlock *l)
+{
+ qunlock(&l->k);
+ qunlock(&l->x);
+}
diff --git a/os/port/random.c b/os/port/random.c
new file mode 100644
index 00000000..720541c0
--- /dev/null
+++ b/os/port/random.c
@@ -0,0 +1,156 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+static struct
+{
+ QLock;
+ Rendez producer;
+ Rendez consumer;
+ ulong randomcount;
+ uchar buf[1024];
+ uchar *ep;
+ uchar *rp;
+ uchar *wp;
+ uchar next;
+ uchar bits;
+ uchar wakeme;
+ uchar filled;
+ int target;
+ int kprocstarted;
+ ulong randn;
+} rb;
+
+static int
+rbnotfull(void*)
+{
+ int i;
+
+ i = rb.wp - rb.rp;
+ if(i < 0)
+ i += sizeof(rb.buf);
+ return i < rb.target;
+}
+
+static int
+rbnotempty(void*)
+{
+ return rb.wp != rb.rp;
+}
+
+static void
+genrandom(void*)
+{
+ setpri(PriBackground);
+
+ for(;;) {
+ for(;;)
+ if(++rb.randomcount > 100000)
+ break;
+ if(anyhigher())
+ sched();
+ if(!rbnotfull(0))
+ sleep(&rb.producer, rbnotfull, 0);
+ }
+}
+
+/*
+ * produce random bits in a circular buffer
+ */
+static void
+randomclock(void)
+{
+ uchar *p;
+
+ if(rb.randomcount == 0)
+ return;
+
+ if(!rbnotfull(0)) {
+ rb.filled = 1;
+ return;
+ }
+
+ rb.bits = (rb.bits<<2) ^ (rb.randomcount&3);
+ rb.randomcount = 0;
+
+ rb.next += 2;
+ if(rb.next != 8)
+ return;
+
+ rb.next = 0;
+ *rb.wp ^= rb.bits ^ *rb.rp;
+ p = rb.wp+1;
+ if(p == rb.ep)
+ p = rb.buf;
+ rb.wp = p;
+
+ if(rb.wakeme)
+ wakeup(&rb.consumer);
+}
+
+void
+randominit(void)
+{
+ /* Frequency close but not equal to HZ */
+ addclock0link(randomclock, 13);
+ rb.target = 16;
+ rb.ep = rb.buf + sizeof(rb.buf);
+ rb.rp = rb.wp = rb.buf;
+}
+
+/*
+ * consume random bytes from a circular buffer
+ */
+ulong
+randomread(void *xp, ulong n)
+{
+ int i, sofar;
+ uchar *e, *p;
+
+ p = xp;
+
+ qlock(&rb);
+ if(waserror()){
+ qunlock(&rb);
+ nexterror();
+ }
+ if(!rb.kprocstarted){
+ rb.kprocstarted = 1;
+ kproc("genrand", genrandom, nil, 0);
+ }
+
+ for(sofar = 0; sofar < n; sofar += i){
+ i = rb.wp - rb.rp;
+ if(i == 0){
+ rb.wakeme = 1;
+ wakeup(&rb.producer);
+ sleep(&rb.consumer, rbnotempty, 0);
+ rb.wakeme = 0;
+ continue;
+ }
+ if(i < 0)
+ i = rb.ep - rb.rp;
+ if((i+sofar) > n)
+ i = n - sofar;
+ memmove(p + sofar, rb.rp, i);
+ e = rb.rp + i;
+ if(e == rb.ep)
+ e = rb.buf;
+ rb.rp = e;
+ }
+ if(rb.filled && rb.wp == rb.rp){
+ i = 2*rb.target;
+ if(i > sizeof(rb.buf) - 1)
+ i = sizeof(rb.buf) - 1;
+ rb.target = i;
+ rb.filled = 0;
+ }
+ poperror();
+ qunlock(&rb);
+
+ wakeup(&rb.producer);
+
+ return n;
+}
diff --git a/os/port/rdb.c b/os/port/rdb.c
new file mode 100644
index 00000000..7386bcc6
--- /dev/null
+++ b/os/port/rdb.c
@@ -0,0 +1,112 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+
+/*
+ * alternative debug protocol for plan 9's rdbfs(4) for plan 9's own acid
+ */
+#define DBG if(0)scrprint
+#pragma varargck argpos scrprint 1
+static Ureg ureg;
+extern Queue *klogq;
+
+static void
+scrprint(char *fmt, ...)
+{
+ char buf[128];
+ va_list va;
+ int n;
+
+ va_start(va, fmt);
+ n = vseprint(buf, buf+sizeof buf, fmt, va)-buf;
+ va_end(va);
+ putstrn(buf, n);
+}
+
+static char*
+getline(void)
+{
+ static char buf[128];
+ int i, c;
+
+ for(;;){
+ for(i=0; i<nelem(buf) && (c=uartgetc()) != '\n'; i++){
+ DBG("%c...", c);
+ buf[i] = c;
+ }
+
+ if(i < nelem(buf)){
+ buf[i] = 0;
+ return buf;
+ }
+ }
+}
+
+static void*
+addr(char *s, Ureg *ureg, char **p)
+{
+ ulong a;
+
+ a = strtoul(s, p, 16);
+ if(a < sizeof(Ureg))
+ return ((uchar*)ureg)+a;
+ return (void*)a;
+}
+
+static void
+talkrdb(Ureg *ureg)
+{
+ uchar *a;
+ char *p;
+ char *req;
+
+ printq = nil; // turn off serial console
+ klogq = nil; // turn off /dev/kprint if active
+ iprint("Edebugger reset\n");
+ for(;;){
+ req = getline();
+ switch(*req){
+ case 'r':
+ a = addr(req+1, ureg, nil);
+ DBG("read %p\n", a);
+ iprint("R%.8lux %.2ux %.2ux %.2ux %.2ux\n", strtoul(req+1, 0, 16), a[0], a[1], a[2], a[3]);
+ break;
+
+ case 'w':
+ a = addr(req+1, ureg, &p);
+ *(ulong*)a = strtoul(p, nil, 16);
+ iprint("W\n");
+ break;
+/*
+ * case Tmput:
+ n = min[4];
+ if(n > 4){
+ mesg(Rerr, Ecount);
+ break;
+ }
+ a = addr(min+0);
+ scrprint("mput %.8lux\n", a);
+ memmove(a, min+5, n);
+ mesg(Rmput, mout);
+ break;
+ *
+ */
+ default:
+ DBG("unknown %c\n", *req);
+ iprint("Eunknown message\n");
+ break;
+ }
+ }
+}
+
+void
+rdb(void)
+{
+ splhi();
+ iprint("rdb...");
+ callwithureg(talkrdb);
+}
diff --git a/os/port/sd.h b/os/port/sd.h
new file mode 100644
index 00000000..a469cff6
--- /dev/null
+++ b/os/port/sd.h
@@ -0,0 +1,132 @@
+/*
+ * Storage Device.
+ */
+typedef struct SDev SDev;
+typedef struct SDifc SDifc;
+typedef struct SDpart SDpart;
+typedef struct SDperm SDperm;
+typedef struct SDreq SDreq;
+typedef struct SDunit SDunit;
+
+struct SDperm {
+ char* name;
+ char* user;
+ ulong perm;
+};
+
+struct SDpart {
+ ulong start;
+ ulong end;
+ SDperm;
+ int valid;
+ ulong vers;
+};
+
+struct SDunit {
+ SDev* dev;
+ int subno;
+ uchar inquiry[256]; /* format follows SCSI spec */
+ SDperm;
+
+ QLock ctl;
+ ulong sectors;
+ ulong secsize;
+ SDpart* part; /* nil or array of size npart */
+ int npart;
+ ulong vers;
+ SDperm ctlperm;
+
+ QLock raw; /* raw read or write in progress */
+ ulong rawinuse; /* really just a test-and-set */
+ int state;
+ SDreq* req;
+ SDperm rawperm;
+};
+
+/*
+ * Each controller is represented by a SDev.
+ * Each controller is responsible for allocating its unit structures.
+ * Each controller has at least one unit.
+ */
+struct SDev {
+ Ref r; /* Number of callers using device */
+ SDifc* ifc; /* pnp/legacy */
+ void* ctlr;
+ int idno;
+ char* name;
+ SDev* next;
+
+ QLock; /* enable/disable */
+ int enabled;
+ int nunit; /* Number of units */
+ QLock unitlock; /* `Loading' of units */
+ int* unitflg; /* Unit flags */
+ SDunit**unit;
+};
+
+struct SDifc {
+ char* name;
+
+ SDev* (*pnp)(void);
+ SDev* (*legacy)(int, int);
+ SDev* (*id)(SDev*);
+ int (*enable)(SDev*);
+ int (*disable)(SDev*);
+
+ int (*verify)(SDunit*);
+ int (*online)(SDunit*);
+ int (*rio)(SDreq*);
+ int (*rctl)(SDunit*, char*, int);
+ int (*wctl)(SDunit*, Cmdbuf*);
+
+ long (*bio)(SDunit*, int, int, void*, long, long);
+ SDev* (*probe)(DevConf*);
+ void (*clear)(SDev*);
+ char* (*stat)(SDev*, char*, char*);
+};
+
+struct SDreq {
+ SDunit* unit;
+ int lun;
+ int write;
+ uchar cmd[16];
+ int clen;
+ void* data;
+ int dlen;
+
+ int flags;
+
+ int status;
+ long rlen;
+ uchar sense[256];
+};
+
+enum {
+ SDnosense = 0x00000001,
+ SDvalidsense = 0x00010000,
+};
+
+enum {
+ SDretry = -5, /* internal to controllers */
+ SDmalloc = -4,
+ SDeio = -3,
+ SDtimeout = -2,
+ SDnostatus = -1,
+
+ SDok = 0,
+
+ SDcheck = 0x02, /* check condition */
+ SDbusy = 0x08, /* busy */
+
+ SDmaxio = 2048*1024,
+ SDnpart = 16,
+};
+
+#define sdmalloc(n) malloc(n)
+#define sdfree(p) free(p)
+
+/* sdscsi.c */
+extern int scsiverify(SDunit*);
+extern int scsionline(SDunit*);
+extern long scsibio(SDunit*, int, int, void*, long, long);
+extern SDev* scsiid(SDev*, SDifc*);
diff --git a/os/port/swcursor.c b/os/port/swcursor.c
new file mode 100644
index 00000000..b4e2628c
--- /dev/null
+++ b/os/port/swcursor.c
@@ -0,0 +1,358 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+
+#include <draw.h>
+#include <memdraw.h>
+#include <cursor.h>
+
+#include "screen.h"
+
+typedef struct SWcursor SWcursor;
+
+/*
+ * Software cursor code: done by hand, might be better to use memdraw
+ */
+
+struct SWcursor {
+ ulong *fb; /* screen frame buffer */
+ Rectangle r;
+ int d; /* ldepth of screen */
+ int width; /* width of screen in ulongs */
+ int x;
+ int y;
+ int hotx;
+ int hoty;
+ uchar cbwid; /* cursor byte width */
+ uchar f; /* flags */
+ uchar cwid;
+ uchar chgt;
+ int hidecount;
+ uchar data[CURSWID*CURSHGT];
+ uchar mask[CURSWID*CURSHGT];
+ uchar save[CURSWID*CURSHGT];
+};
+
+enum {
+ CUR_ENA = 0x01, /* cursor is enabled */
+ CUR_DRW = 0x02, /* cursor is currently drawn */
+ CUR_SWP = 0x10, /* bit swap */
+};
+
+static Rectangle cursoroffrect;
+static int cursorisoff;
+
+static void swcursorflush(int, int);
+static void swcurs_draw_or_undraw(SWcursor *);
+
+static void
+cursorupdate0(void)
+{
+ int inrect, x, y;
+ Point m;
+
+ m = mousexy();
+ x = m.x - swc->hotx;
+ y = m.y - swc->hoty;
+ inrect = (x >= cursoroffrect.min.x && x < cursoroffrect.max.x
+ && y >= cursoroffrect.min.y && y < cursoroffrect.max.y);
+ if (cursorisoff == inrect)
+ return;
+ cursorisoff = inrect;
+ if (inrect)
+ swcurs_hide(swc);
+ else {
+ swc->hidecount = 0;
+ swcurs_draw_or_undraw(swc);
+ }
+ swcursorflush(m.x, m.y);
+}
+
+void
+cursorupdate(Rectangle r)
+{
+ lock(vd);
+ r.min.x -= 16;
+ r.min.y -= 16;
+ cursoroffrect = r;
+ if (swc != nil)
+ cursorupdate0();
+ unlock(vd);
+}
+
+void
+cursorenable(void)
+{
+ Point m;
+
+ lock(vd);
+ if(swc != nil) {
+ swcurs_enable(swc);
+ m = mousexy();
+ swcursorflush(m.x, m.y);
+ }
+ unlock(vd);
+}
+
+void
+cursordisable(void)
+{
+ Point m;
+
+ lock(vd);
+ if(swc != nil) {
+ swcurs_disable(swc);
+ m = mousexy();
+ swcursorflush(m.x, m.y);
+ }
+ unlock(vd);
+}
+
+void
+swcursupdate(int oldx, int oldy, int x, int y)
+{
+
+ if(!canlock(vd))
+ return; /* if can't lock, don't wake up stuff */
+
+ if(x < gscreen->r.min.x)
+ x = gscreen->r.min.x;
+ if(x >= gscreen->r.max.x)
+ x = gscreen->r.max.x;
+ if(y < gscreen->r.min.y)
+ y = gscreen->r.min.y;
+ if(y >= gscreen->r.max.y)
+ y = gscreen->r.max.y;
+ if(swc != nil) {
+ swcurs_hide(swc);
+ swc->x = x;
+ swc->y = y;
+ cursorupdate0();
+ swcurs_unhide(swc);
+ swcursorflush(oldx, oldy);
+ swcursorflush(x, y);
+ }
+
+ unlock(vd);
+}
+
+void
+drawcursor(Drawcursor* c)
+{
+ Point p, m;
+ Cursor curs, *cp;
+ int j, i, h, bpl;
+ uchar *bc, *bs, *cclr, *cset;
+
+ if(swc == nil)
+ return;
+
+ /* Set the default system cursor */
+ if(c == nil || c->data == nil){
+ swcurs_disable(swc);
+ return;
+ }
+ else {
+ cp = &curs;
+ p.x = c->hotx;
+ p.y = c->hoty;
+ cp->offset = p;
+ bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1);
+
+ h = (c->maxy-c->miny)/2;
+ if(h > 16)
+ h = 16;
+
+ bc = c->data;
+ bs = c->data + h*bpl;
+
+ cclr = cp->clr;
+ cset = cp->set;
+ for(i = 0; i < h; i++) {
+ for(j = 0; j < 2; j++) {
+ cclr[j] = bc[j];
+ cset[j] = bs[j];
+ }
+ bc += bpl;
+ bs += bpl;
+ cclr += 2;
+ cset += 2;
+ }
+ }
+ swcurs_load(swc, cp);
+ m = mousexy();
+ swcursorflush(m.x, m.y);
+ swcurs_enable(swc);
+}
+
+SWcursor*
+swcurs_create(ulong *fb, int width, int ldepth, Rectangle r, int bitswap)
+{
+ SWcursor *swc;
+
+ swc = (SWcursor*)malloc(sizeof(SWcursor));
+ swc->fb = fb;
+ swc->r = r;
+ swc->d = ldepth;
+ swc->width = width;
+ swc->f = bitswap ? CUR_SWP : 0;
+ swc->x = swc->y = 0;
+ swc->hotx = swc->hoty = 0;
+ swc->hidecount = 0;
+ return swc;
+}
+
+void
+swcurs_destroy(SWcursor *swc)
+{
+ swcurs_disable(swc);
+ free(swc);
+}
+
+static void
+swcursorflush(int x, int y)
+{
+ Rectangle r;
+
+ /* XXX a little too paranoid here */
+ r.min.x = x-16;
+ r.min.y = y-16;
+ r.max.x = x+17;
+ r.max.y = y+17;
+ flushmemscreen(r);
+}
+
+static void
+swcurs_draw_or_undraw(SWcursor *swc)
+{
+ uchar *p;
+ uchar *cs;
+ int w, vw;
+ int x1 = swc->r.min.x;
+ int y1 = swc->r.min.y;
+ int x2 = swc->r.max.x;
+ int y2 = swc->r.max.y;
+ int xp = swc->x - swc->hotx;
+ int yp = swc->y - swc->hoty;
+ int ofs;
+
+ if(((swc->f & CUR_ENA) && (swc->hidecount <= 0))
+ == ((swc->f & CUR_DRW) != 0))
+ return;
+ w = swc->cbwid*BI2BY/(1 << swc->d);
+ x1 = xp < x1 ? x1 : xp;
+ y1 = yp < y1 ? y1 : yp;
+ x2 = xp+w >= x2 ? x2 : xp+w;
+ y2 = yp+swc->chgt >= y2 ? y2 : yp+swc->chgt;
+ if(x2 <= x1 || y2 <= y1)
+ return;
+ p = (uchar*)(swc->fb + swc->width*y1)
+ + x1*(1 << swc->d)/BI2BY;
+ y2 -= y1;
+ x2 = (x2-x1)*(1 << swc->d)/BI2BY;
+ vw = swc->width*BY2WD - x2;
+ w = swc->cbwid - x2;
+ ofs = swc->cbwid*(y1-yp)+(x1-xp);
+ cs = swc->save + ofs;
+ if((swc->f ^= CUR_DRW) & CUR_DRW) {
+ uchar *cm = swc->mask + ofs;
+ uchar *cd = swc->data + ofs;
+ while(y2--) {
+ x1 = x2;
+ while(x1--) {
+ *p = ((*cs++ = *p) & *cm++) ^ *cd++;
+ p++;
+ }
+ cs += w;
+ cm += w;
+ cd += w;
+ p += vw;
+ }
+ } else {
+ while(y2--) {
+ x1 = x2;
+ while(x1--)
+ *p++ = *cs++;
+ cs += w;
+ p += vw;
+ }
+ }
+}
+
+void
+swcurs_hide(SWcursor *swc)
+{
+ ++swc->hidecount;
+ swcurs_draw_or_undraw(swc);
+}
+
+void
+swcurs_unhide(SWcursor *swc)
+{
+ if (--swc->hidecount < 0)
+ swc->hidecount = 0;
+ swcurs_draw_or_undraw(swc);
+}
+
+void
+swcurs_enable(SWcursor *swc)
+{
+ swc->f |= CUR_ENA;
+ swcurs_draw_or_undraw(swc);
+}
+
+void
+swcurs_disable(SWcursor *swc)
+{
+ swc->f &= ~CUR_ENA;
+ swcurs_draw_or_undraw(swc);
+}
+
+void
+swcurs_load(SWcursor *swc, Cursor *c)
+{
+ int i, k;
+ uchar *bc, *bs, *cd, *cm;
+ static uchar bdv[4] = {0,Backgnd,Foregnd,0xff};
+ static uchar bmv[4] = {0xff,0,0,0xff};
+ int bits = 1<<swc->d;
+ uchar mask = (1<<bits)-1;
+ int bswp = (swc->f&CUR_SWP) ? 8-bits : 0;
+
+ bc = c->clr;
+ bs = c->set;
+
+ swcurs_hide(swc);
+ cd = swc->data;
+ cm = swc->mask;
+ swc->hotx = c->offset.x;
+ swc->hoty = c->offset.y;
+ swc->chgt = CURSHGT;
+ swc->cwid = CURSWID;
+ swc->cbwid = CURSWID*(1<<swc->d)/BI2BY;
+ for(i = 0; i < CURSWID/BI2BY*CURSHGT; i++) {
+ uchar bcb = *bc++;
+ uchar bsb = *bs++;
+ for(k=0; k<BI2BY;) {
+ uchar cdv = 0;
+ uchar cmv = 0;
+ int z;
+ for(z=0; z<BI2BY; z += bits) {
+ int n = ((bsb&(0x80))|((bcb&(0x80))<<1))>>7;
+ int s = z^bswp;
+ cdv |= (bdv[n]&mask) << s;
+ cmv |= (bmv[n]&mask) << s;
+ bcb <<= 1;
+ bsb <<= 1;
+ k++;
+ }
+ *cd++ = cdv;
+ *cm++ = cmv;
+ }
+ }
+ swcurs_unhide(swc);
+}
diff --git a/os/port/sysfile.c b/os/port/sysfile.c
new file mode 100644
index 00000000..1ff0fe5f
--- /dev/null
+++ b/os/port/sysfile.c
@@ -0,0 +1,1125 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+static int
+growfd(Fgrp *f, int fd)
+{
+ int n;
+ Chan **nfd, **ofd;
+
+ if(fd < f->nfd)
+ return 0;
+ n = f->nfd+DELTAFD;
+ if(n > MAXNFD)
+ n = MAXNFD;
+ if(fd >= n)
+ return -1;
+ nfd = malloc(n*sizeof(Chan*));
+ if(nfd == nil)
+ return -1;
+ ofd = f->fd;
+ memmove(nfd, ofd, f->nfd*sizeof(Chan *));
+ f->fd = nfd;
+ f->nfd = n;
+ free(ofd);
+ return 0;
+}
+
+int
+newfd(Chan *c)
+{
+ int i;
+ Fgrp *f = up->env->fgrp;
+
+ lock(f);
+ for(i=f->minfd; i<f->nfd; i++)
+ if(f->fd[i] == 0)
+ break;
+ if(i >= f->nfd && growfd(f, i) < 0){
+ unlock(f);
+ exhausted("file descriptors");
+ return -1;
+ }
+ f->minfd = i + 1;
+ if(i > f->maxfd)
+ f->maxfd = i;
+ f->fd[i] = c;
+ unlock(f);
+ return i;
+}
+
+Chan*
+fdtochan(Fgrp *f, int fd, int mode, int chkmnt, int iref)
+{
+ Chan *c;
+
+ c = 0;
+
+ lock(f);
+ if(fd<0 || f->maxfd<fd || (c = f->fd[fd])==0) {
+ unlock(f);
+ error(Ebadfd);
+ }
+ if(iref)
+ incref(c);
+ unlock(f);
+
+ if(chkmnt && (c->flag&CMSG)) {
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ }
+
+ if(mode<0 || c->mode==ORDWR)
+ return c;
+
+ if((mode&OTRUNC) && c->mode==OREAD) {
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ }
+
+ if((mode&~OTRUNC) != c->mode) {
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ }
+
+ return c;
+}
+
+long
+kchanio(void *vc, void *buf, int n, int mode)
+{
+ int r;
+ Chan *c;
+
+ c = vc;
+ if(waserror())
+ return -1;
+
+ if(mode == OREAD)
+ r = devtab[c->type]->read(c, buf, n, c->offset);
+ else
+ r = devtab[c->type]->write(c, buf, n, c->offset);
+
+ lock(c);
+ c->offset += r;
+ unlock(c);
+ poperror();
+ return r;
+}
+
+int
+openmode(ulong o)
+{
+ if(o >= (OTRUNC|OCEXEC|ORCLOSE|OEXEC))
+ error(Ebadarg);
+ o &= ~(OTRUNC|OCEXEC|ORCLOSE);
+ if(o > OEXEC)
+ error(Ebadarg);
+ if(o == OEXEC)
+ return OREAD;
+ return o;
+}
+
+void
+fdclose(Fgrp *f, int fd)
+{
+ int i;
+ Chan *c;
+
+ lock(f);
+ c = f->fd[fd];
+ if(c == 0){
+ /* can happen for users with shared fd tables */
+ unlock(f);
+ return;
+ }
+ f->fd[fd] = 0;
+ if(fd == f->maxfd)
+ for(i=fd; --i>=0 && f->fd[i]==0; )
+ f->maxfd = i;
+ if(fd < f->minfd)
+ f->minfd = fd;
+ unlock(f);
+ cclose(c);
+}
+
+int
+kchdir(char *path)
+{
+ Chan *c;
+ Pgrp *pg;
+
+ if(waserror())
+ return -1;
+
+ c = namec(path, Atodir, 0, 0);
+ pg = up->env->pgrp;
+ cclose(pg->dot);
+ pg->dot = c;
+ poperror();
+ return 0;
+}
+
+int
+kfgrpclose(Fgrp *f, int fd)
+{
+ if(waserror())
+ return -1;
+
+ /*
+ * Take no reference on the chan because we don't really need the
+ * data structure, and are calling fdtochan only for error checks.
+ * fdclose takes care of processes racing through here.
+ */
+ fdtochan(f, fd, -1, 0, 0);
+ fdclose(f, fd);
+ poperror();
+ return 0;
+}
+
+int
+kclose(int fd)
+{
+ return kfgrpclose(up->env->fgrp, fd);
+}
+
+int
+kcreate(char *path, int mode, ulong perm)
+{
+ int fd;
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ openmode(mode&~OEXCL); /* error check only; OEXCL okay here */
+ c = namec(path, Acreate, mode, perm);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ fd = newfd(c);
+ if(fd < 0)
+ error(Enofd);
+ poperror();
+
+ poperror();
+ return fd;
+}
+
+int
+kdup(int old, int new)
+{
+ int fd;
+ Chan *c, *oc;
+ Fgrp *f = up->env->fgrp;
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(up->env->fgrp, old, -1, 0, 1);
+ if(c->qid.type & QTAUTH)
+ error(Eperm);
+ fd = new;
+ if(fd != -1){
+ lock(f);
+ if(fd<0 || growfd(f, fd) < 0) {
+ unlock(f);
+ cclose(c);
+ error(Ebadfd);
+ }
+ if(fd > f->maxfd)
+ f->maxfd = fd;
+ oc = f->fd[fd];
+ f->fd[fd] = c;
+ unlock(f);
+ if(oc)
+ cclose(oc);
+ }else{
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ fd = newfd(c);
+ if(fd < 0)
+ error(Enofd);
+ poperror();
+ }
+ poperror();
+ return fd;
+}
+
+int
+kfstat(int fd, uchar *buf, int n)
+{
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(up->env->fgrp, fd, -1, 0, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ devtab[c->type]->stat(c, buf, n);
+
+ poperror();
+ cclose(c);
+
+ poperror();
+ return n;
+}
+
+char*
+kfd2path(int fd)
+{
+ Chan *c;
+ char *s;
+
+ if(waserror())
+ return nil;
+ c = fdtochan(up->env->fgrp, fd, -1, 0, 1);
+ s = nil;
+ if(c->name != nil){
+ s = malloc(c->name->len+1);
+ if(s == nil){
+ cclose(c);
+ error(Enomem);
+ }
+ memmove(s, c->name->s, c->name->len+1);
+ cclose(c);
+ }
+ poperror();
+ return s;
+}
+
+int
+kfauth(int fd, char *aname)
+{
+ Chan *c, *ac;
+
+ if(waserror())
+ return -1;
+
+ validname(aname, 0);
+ c = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+
+ ac = mntauth(c, aname);
+
+ /* at this point ac is responsible for keeping c alive */
+ poperror(); /* c */
+ cclose(c);
+
+ if(waserror()){
+ cclose(ac);
+ nexterror();
+ }
+
+ fd = newfd(ac);
+ if(fd < 0)
+ error(Enofd);
+ poperror(); /* ac */
+
+ poperror();
+
+ return fd;
+}
+
+int
+kfversion(int fd, uint msize, char *vers, uint arglen)
+{
+ int m;
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ /* check there's a NUL in the version string */
+ if(arglen==0 || memchr(vers, 0, arglen)==0)
+ error(Ebadarg);
+
+ c = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+
+ m = mntversion(c, vers, msize, arglen);
+
+ poperror();
+ cclose(c);
+
+ poperror();
+ return m;
+}
+
+int
+kpipe(int fd[2])
+{
+ Dev *d;
+ Fgrp *f;
+ Chan *c[2];
+ static char *names[] = {"data", "data1"};
+
+ f = up->env->fgrp;
+
+ d = devtab[devno('|', 0)];
+ c[0] = namec("#|", Atodir, 0, 0);
+ c[1] = 0;
+ fd[0] = -1;
+ fd[1] = -1;
+ if(waserror()) {
+ if(c[0] != 0)
+ cclose(c[0]);
+ if(c[1] != 0)
+ cclose(c[1]);
+ if(fd[0] >= 0)
+ f->fd[fd[0]]=0;
+ if(fd[1] >= 0)
+ f->fd[fd[1]]=0;
+ return -1;
+ }
+ c[1] = cclone(c[0]);
+ if(walk(&c[0], &names[0], 1, 1, nil) < 0)
+ error(Egreg);
+ if(walk(&c[1], &names[1], 1, 1, nil) < 0)
+ error(Egreg);
+ c[0] = d->open(c[0], ORDWR);
+ c[1] = d->open(c[1], ORDWR);
+ fd[0] = newfd(c[0]);
+ if(fd[0] < 0)
+ error(Enofd);
+ fd[1] = newfd(c[1]);
+ if(fd[1] < 0)
+ error(Enofd);
+ poperror();
+ return 0;
+}
+
+int
+kfwstat(int fd, uchar *buf, int n)
+{
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ validstat(buf, n);
+ c = fdtochan(up->env->fgrp, fd, -1, 1, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ n = devtab[c->type]->wstat(c, buf, n);
+ poperror();
+ cclose(c);
+
+ poperror();
+ return n;
+}
+
+long
+bindmount(Chan *c, char *old, int flag, char *spec)
+{
+ int ret;
+ Chan *c1;
+
+ if(flag>MMASK || (flag&MORDER) == (MBEFORE|MAFTER))
+ error(Ebadarg);
+
+ c1 = namec(old, Amount, 0, 0);
+ if(waserror()){
+ cclose(c1);
+ nexterror();
+ }
+ ret = cmount(c, c1, flag, spec);
+
+ poperror();
+ cclose(c1);
+ return ret;
+}
+
+int
+kbind(char *new, char *old, int flags)
+{
+ long r;
+ Chan *c0;
+
+ if(waserror())
+ return -1;
+
+ c0 = namec(new, Abind, 0, 0);
+ if(waserror()) {
+ cclose(c0);
+ nexterror();
+ }
+ r = bindmount(c0, old, flags, "");
+ poperror();
+ cclose(c0);
+
+ poperror();
+ return r;
+}
+
+int
+kmount(int fd, int afd, char *old, int flags, char *spec)
+{
+ long r;
+ volatile struct { Chan *c; } c0;
+ volatile struct { Chan *c; } bc;
+ volatile struct { Chan *c; } ac;
+ Mntparam mntparam;
+
+ ac.c = nil;
+ bc.c = nil;
+ c0.c = nil;
+ if(waserror()) {
+ cclose(ac.c);
+ cclose(bc.c);
+ cclose(c0.c);
+ return -1;
+ }
+ bc.c = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
+ if(afd >= 0)
+ ac.c = fdtochan(up->env->fgrp, afd, ORDWR, 0, 1);
+ mntparam.chan = bc.c;
+ mntparam.authchan = ac.c;
+ mntparam.spec = spec;
+ mntparam.flags = flags;
+ c0.c = devtab[devno('M', 0)]->attach((char*)&mntparam);
+
+ r = bindmount(c0.c, old, flags, spec);
+ poperror();
+ cclose(ac.c);
+ cclose(bc.c);
+ cclose(c0.c);
+
+ return r;
+}
+
+int
+kunmount(char *old, char *new)
+{
+ volatile struct { Chan *c; } cmount;
+ volatile struct { Chan *c; } cmounted;
+
+ cmount.c = nil;
+ cmounted.c = nil;
+ if(waserror()) {
+ cclose(cmount.c);
+ cclose(cmounted.c);
+ return -1;
+ }
+
+ cmount.c = namec(new, Amount, 0, 0);
+ if(old != nil && old[0] != '\0') {
+ /*
+ * This has to be namec(..., Aopen, ...) because
+ * if arg[0] is something like /srv/cs or /fd/0,
+ * opening it is the only way to get at the real
+ * Chan underneath.
+ */
+ cmounted.c = namec(old, Aopen, OREAD, 0);
+ }
+
+ cunmount(cmount.c, cmounted.c);
+ poperror();
+ cclose(cmount.c);
+ cclose(cmounted.c);
+ return 0;
+}
+
+int
+kopen(char *path, int mode)
+{
+ int fd;
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ openmode(mode); /* error check only */
+ c = namec(path, Aopen, mode, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ fd = newfd(c);
+ if(fd < 0)
+ error(Enofd);
+ poperror();
+
+ poperror();
+ return fd;
+}
+
+long
+unionread(Chan *c, void *va, long n)
+{
+ int i;
+ long nr;
+ Mhead *m;
+ Mount *mount;
+
+ qlock(&c->umqlock);
+ m = c->umh;
+ rlock(&m->lock);
+ mount = m->mount;
+ /* bring mount in sync with c->uri and c->umc */
+ for(i = 0; mount != nil && i < c->uri; i++)
+ mount = mount->next;
+
+ nr = 0;
+ while(mount != nil) {
+ /* Error causes component of union to be skipped */
+ if(mount->to && !waserror()) {
+ if(c->umc == nil){
+ c->umc = cclone(mount->to);
+ c->umc = devtab[c->umc->type]->open(c->umc, OREAD);
+ }
+
+ nr = devtab[c->umc->type]->read(c->umc, va, n, c->umc->offset);
+ if(nr < 0)
+ nr = 0; /* dev.c can return -1 */
+ c->umc->offset += nr;
+ poperror();
+ }
+ if(nr > 0)
+ break;
+
+ /* Advance to next element */
+ c->uri++;
+ if(c->umc) {
+ cclose(c->umc);
+ c->umc = nil;
+ }
+ mount = mount->next;
+ }
+ runlock(&m->lock);
+ qunlock(&c->umqlock);
+ return nr;
+}
+
+static void
+unionrewind(Chan *c)
+{
+ qlock(&c->umqlock);
+ c->uri = 0;
+ if(c->umc){
+ cclose(c->umc);
+ c->umc = nil;
+ }
+ qunlock(&c->umqlock);
+}
+
+static long
+rread(int fd, void *va, long n, vlong *offp)
+{
+ int dir;
+ Chan *c;
+ vlong off;
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(up->env->fgrp, fd, OREAD, 1, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+
+ if(n < 0)
+ error(Etoosmall);
+
+ dir = c->qid.type & QTDIR;
+ if(dir && c->umh)
+ n = unionread(c, va, n);
+ else{
+ if(offp == nil){
+ lock(c); /* lock for vlong assignment */
+ off = c->offset;
+ unlock(c);
+ }else
+ off = *offp;
+ if(off < 0)
+ error(Enegoff);
+ if(off == 0){
+ if(offp == nil){
+ lock(c);
+ c->offset = 0;
+ c->dri = 0;
+ unlock(c);
+ }
+ unionrewind(c);
+ }
+ n = devtab[c->type]->read(c, va, n, off);
+ lock(c);
+ c->offset += n;
+ unlock(c);
+ }
+
+ poperror();
+ cclose(c);
+
+ poperror();
+ return n;
+}
+
+long
+kread(int fd, void *va, long n)
+{
+ return rread(fd, va, n, nil);
+}
+
+long
+kpread(int fd, void *va, long n, vlong off)
+{
+ return rread(fd, va, n, &off);
+}
+
+int
+kremove(char *path)
+{
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ c = namec(path, Aremove, 0, 0);
+ if(waserror()) {
+ c->type = 0; /* see below */
+ cclose(c);
+ nexterror();
+ }
+ devtab[c->type]->remove(c);
+ /*
+ * Remove clunks the fid, but we need to recover the Chan
+ * so fake it up. rootclose() is known to be a nop.
+ */
+ c->type = 0;
+ poperror();
+ cclose(c);
+
+ poperror();
+ return 0;
+}
+
+vlong
+kseek(int fd, vlong off, int whence)
+{
+ Dir *dir;
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(up->env->fgrp, fd, -1, 1, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+
+ if(devtab[c->type]->dc == '|')
+ error(Eisstream);
+
+ switch(whence) {
+ case 0:
+ if(c->qid.type & QTDIR){
+ if(off != 0)
+ error(Eisdir);
+ unionrewind(c);
+ }else if(off < 0)
+ error(Enegoff);
+ lock(c); /* lock for vlong assignment */
+ c->offset = off;
+ unlock(c);
+ break;
+
+ case 1:
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ lock(c); /* lock for read/write update */
+ off += c->offset;
+ if(off < 0){
+ unlock(c);
+ error(Enegoff);
+ }
+ c->offset = off;
+ unlock(c);
+ break;
+
+ case 2:
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ dir = chandirstat(c);
+ if(dir == nil)
+ error("internal error: stat error in seek");
+ off += dir->length;
+ free(dir);
+ if(off < 0)
+ error(Enegoff);
+ lock(c); /* lock for read/write update */
+ c->offset = off;
+ unlock(c);
+ break;
+
+ default:
+ error(Ebadarg);
+ break;
+ }
+ poperror();
+ c->dri = 0;
+ cclose(c);
+ poperror();
+ return off;
+}
+
+void
+validstat(uchar *s, int n)
+{
+ int m;
+ char buf[64];
+
+ if(statcheck(s, n) < 0)
+ error(Ebadstat);
+ /* verify that name entry is acceptable */
+ s += STATFIXLEN - 4*BIT16SZ; /* location of first string */
+ /*
+ * s now points at count for first string.
+ * if it's too long, let the server decide; this is
+ * only for his protection anyway. otherwise
+ * we'd have to allocate and waserror.
+ */
+ m = GBIT16(s);
+ s += BIT16SZ;
+ if(m+1 > sizeof buf)
+ return;
+ memmove(buf, s, m);
+ buf[m] = '\0';
+ /* name could be '/' */
+ if(strcmp(buf, "/") != 0)
+ validname(buf, 0);
+}
+
+int
+kstat(char *path, uchar *buf, int n)
+{
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ c = namec(path, Aaccess, 0, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ devtab[c->type]->stat(c, buf, n);
+ poperror();
+ cclose(c);
+
+ poperror();
+ return 0;
+}
+
+static long
+rwrite(int fd, void *va, long n, vlong *offp)
+{
+ Chan *c;
+ vlong off;
+ long m;
+
+ if(waserror())
+ return -1;
+ c = fdtochan(up->env->fgrp, fd, OWRITE, 1, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+
+ if(n < 0)
+ error(Etoosmall);
+
+ if(offp == nil){
+ lock(c);
+ off = c->offset;
+ c->offset += n;
+ unlock(c);
+ }else
+ off = *offp;
+
+ if(waserror()){
+ if(offp == nil){
+ lock(c);
+ c->offset -= n;
+ unlock(c);
+ }
+ nexterror();
+ }
+ if(off < 0)
+ error(Enegoff);
+ m = devtab[c->type]->write(c, va, n, off);
+ poperror();
+
+ if(offp == nil && m < n){
+ lock(c);
+ c->offset -= n - m;
+ unlock(c);
+ }
+
+ poperror();
+ cclose(c);
+
+ poperror();
+ return n;
+}
+
+long
+kwrite(int fd, void *va, long n)
+{
+ return rwrite(fd, va, n, nil);
+}
+
+long
+kpwrite(int fd, void *va, long n, vlong off)
+{
+ return rwrite(fd, va, n, &off);
+}
+
+int
+kwstat(char *path, uchar *buf, int n)
+{
+ Chan *c;
+
+ if(waserror())
+ return -1;
+
+ validstat(buf, n);
+ c = namec(path, Aaccess, 0, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ n = devtab[c->type]->wstat(c, buf, n);
+ poperror();
+ cclose(c);
+
+ poperror();
+ return n;
+}
+
+enum
+{
+ DIRSIZE = STATFIXLEN + 32 * 4,
+ DIRREADLIM = 2048, /* should handle the largest reasonable directory entry */
+};
+
+Dir*
+chandirstat(Chan *c)
+{
+ Dir *d;
+ uchar *buf;
+ int n, nd, i;
+
+ nd = DIRSIZE;
+ for(i=0; i<2; i++){ /* should work by the second try */
+ d = smalloc(sizeof(Dir) + nd);
+ buf = (uchar*)&d[1];
+ if(waserror()){
+ free(d);
+ return nil;
+ }
+ n = devtab[c->type]->stat(c, buf, nd);
+ poperror();
+ if(n < BIT16SZ){
+ free(d);
+ return nil;
+ }
+ nd = GBIT16((uchar*)buf) + BIT16SZ; /* size needed to store whole stat buffer including count */
+ if(nd <= n){
+ convM2D(buf, n, d, (char*)&d[1]);
+ return d;
+ }
+ /* else sizeof(Dir)+nd is plenty */
+ free(d);
+ }
+ return nil;
+
+}
+
+Dir*
+kdirstat(char *name)
+{
+ Chan *c;
+ Dir *d;
+
+ if(waserror())
+ return nil;
+
+ c = namec(name, Aaccess, 0, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ d = chandirstat(c);
+ poperror();
+ cclose(c);
+
+ poperror();
+ return d;
+}
+
+Dir*
+kdirfstat(int fd)
+{
+ Chan *c;
+ Dir *d;
+
+ if(waserror())
+ return nil;
+
+ c = fdtochan(up->env->fgrp, fd, -1, 0, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ d = chandirstat(c);
+ poperror();
+ cclose(c);
+
+ poperror();
+ return d;
+}
+
+int
+kdirwstat(char *name, Dir *dir)
+{
+ uchar *buf;
+ int r;
+
+ r = sizeD2M(dir);
+ buf = smalloc(r);
+ convD2M(dir, buf, r);
+ r = kwstat(name, buf, r);
+ free(buf);
+ return r < 0? r: 0;
+}
+
+int
+kdirfwstat(int fd, Dir *dir)
+{
+ uchar *buf;
+ int r;
+
+ r = sizeD2M(dir);
+ buf = smalloc(r);
+ convD2M(dir, buf, r);
+ r = kfwstat(fd, buf, r);
+ free(buf);
+ return r < 0? r: 0;
+}
+
+static long
+dirpackage(uchar *buf, long ts, Dir **d)
+{
+ char *s;
+ long ss, i, n, nn, m;
+
+ *d = nil;
+ if(ts <= 0)
+ return ts;
+
+ /*
+ * first find number of all stats, check they look like stats, & size all associated strings
+ */
+ ss = 0;
+ n = 0;
+ for(i = 0; i < ts; i += m){
+ m = BIT16SZ + GBIT16(&buf[i]);
+ if(statcheck(&buf[i], m) < 0)
+ break;
+ ss += m;
+ n++;
+ }
+
+ if(i != ts)
+ error("bad directory format");
+
+ *d = malloc(n * sizeof(Dir) + ss);
+ if(*d == nil)
+ error(Enomem);
+
+ /*
+ * then convert all buffers
+ */
+ s = (char*)*d + n * sizeof(Dir);
+ nn = 0;
+ for(i = 0; i < ts; i += m){
+ m = BIT16SZ + GBIT16((uchar*)&buf[i]);
+ if(nn >= n || convM2D(&buf[i], m, *d + nn, s) != m){
+ free(*d);
+ *d = nil;
+ error("bad directory entry");
+ }
+ nn++;
+ s += m;
+ }
+
+ return nn;
+}
+
+long
+kdirread(int fd, Dir **d)
+{
+ uchar *buf;
+ long ts;
+
+ *d = nil;
+ if(waserror())
+ return -1;
+ buf = malloc(DIRREADLIM);
+ if(buf == nil)
+ error(Enomem);
+ if(waserror()){
+ free(buf);
+ nexterror();
+ }
+ ts = kread(fd, buf, DIRREADLIM);
+ if(ts >= 0)
+ ts = dirpackage(buf, ts, d);
+ poperror();
+ free(buf);
+ poperror();
+ return ts;
+}
+
+int
+kiounit(int fd)
+{
+ Chan *c;
+ int n;
+
+ c = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
+ if(waserror()){
+ cclose(c);
+ return 0; /* n.b. */
+ }
+ n = c->iounit;
+ poperror();
+ cclose(c);
+ return n;
+}
diff --git a/os/port/taslock.c b/os/port/taslock.c
new file mode 100644
index 00000000..7914b80a
--- /dev/null
+++ b/os/port/taslock.c
@@ -0,0 +1,126 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+static void
+lockloop(Lock *l, ulong pc)
+{
+ setpanic();
+ print("lock loop 0x%lux key 0x%lux pc 0x%lux held by pc 0x%lux\n", l, l->key, pc, l->pc);
+ panic("lockloop");
+}
+
+void
+lock(Lock *l)
+{
+ int i;
+ ulong pc;
+
+ pc = getcallerpc(&l);
+ if(up == 0) {
+ if (_tas(&l->key) != 0) {
+ for(i=0; ; i++) {
+ if(_tas(&l->key) == 0)
+ break;
+ if (i >= 1000000) {
+ lockloop(l, pc);
+ break;
+ }
+ }
+ }
+ l->pc = pc;
+ return;
+ }
+
+ for(i=0; ; i++) {
+ if(_tas(&l->key) == 0)
+ break;
+ if (i >= 1000) {
+ lockloop(l, pc);
+ break;
+ }
+ if(conf.nmach == 1 && up->state == Running && islo()) {
+ up->pc = pc;
+ sched();
+ }
+ }
+ l->pri = up->pri;
+ up->pri = PriLock;
+ l->pc = pc;
+}
+
+void
+ilock(Lock *l)
+{
+ ulong x, pc;
+ int i;
+
+ pc = getcallerpc(&l);
+ x = splhi();
+ for(;;) {
+ if(_tas(&l->key) == 0) {
+ l->sr = x;
+ l->pc = pc;
+ return;
+ }
+ if(conf.nmach < 2)
+ panic("ilock: no way out: pc 0x%lux: lock 0x%lux held by pc 0x%lux", pc, l, l->pc);
+ for(i=0; ; i++) {
+ if(l->key == 0)
+ break;
+ clockcheck();
+ if (i > 100000) {
+ lockloop(l, pc);
+ break;
+ }
+ }
+ }
+}
+
+int
+canlock(Lock *l)
+{
+ if(_tas(&l->key))
+ return 0;
+ if(up){
+ l->pri = up->pri;
+ up->pri = PriLock;
+ }
+ l->pc = getcallerpc(&l);
+ return 1;
+}
+
+void
+unlock(Lock *l)
+{
+ int p;
+
+ if(l->key == 0)
+ print("unlock: not locked: pc %lux\n", getcallerpc(&l));
+ p = l->pri;
+ l->pc = 0;
+ l->key = 0;
+ coherence();
+ if(up){
+ up->pri = p;
+ if(up->state == Running && anyhigher())
+ sched();
+ }
+}
+
+void
+iunlock(Lock *l)
+{
+ ulong sr;
+
+ if(l->key == 0)
+ print("iunlock: not locked: pc %lux\n", getcallerpc(&l));
+ sr = l->sr;
+ l->pc = 0;
+ l->key = 0;
+ coherence();
+ splxpc(sr);
+}
diff --git a/os/port/tod.c b/os/port/tod.c
new file mode 100644
index 00000000..55486453
--- /dev/null
+++ b/os/port/tod.c
@@ -0,0 +1,283 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+/* compute nanosecond epoch time from the fastest ticking clock
+ * on the system. converting the time to nanoseconds requires
+ * the following formula
+ *
+ * t = (((1000000000<<31)/f)*ticks)>>31
+ *
+ * where
+ *
+ * 'f' is the clock frequency
+ * 'ticks' are clock ticks
+ *
+ * to avoid too much calculation in todget(), we calculate
+ *
+ * mult = (1000000000<<32)/f
+ *
+ * each time f is set. f is normally set by a user level
+ * program writing to /dev/fastclock. mul64fract will then
+ * take that fractional multiplier and a 64 bit integer and
+ * return the resulting integer product.
+ *
+ * We assume that the cpu's of a multiprocessor are synchronized.
+ * This assumption needs to be questioned with each new architecture.
+ */
+
+/* frequency of the tod clock */
+#define TODFREQ 1000000000ULL
+
+struct {
+ int init; // true if initialized
+ ulong cnt;
+ Lock;
+ uvlong multiplier; // t = off + (multiplier*ticks)>>31
+ uvlong divider; // ticks = (divider*(ticks-off))>>31
+ vlong hz; // frequency of fast clock
+ vlong last; // last reading of fast clock
+ vlong off; // offset from epoch to last
+ vlong lasttime; // last return value from todget
+ vlong delta; // add 'delta' each slow clock tick from sstart to send
+ ulong sstart; // ...
+ ulong send; // ...
+} tod;
+
+void
+todinit(void)
+{
+ if(tod.init)
+ return;
+ ilock(&tod);
+ tod.last = fastticks((uvlong*)&tod.hz);
+ iunlock(&tod);
+ todsetfreq(tod.hz);
+ tod.init = 1;
+ addclock0link(todfix, 100);
+}
+
+/*
+ * calculate multiplier
+ */
+void
+todsetfreq(vlong f)
+{
+ ilock(&tod);
+ tod.hz = f;
+
+ /* calculate multiplier for time conversion */
+ tod.multiplier = mk64fract(TODFREQ, f);
+ tod.divider = mk64fract(f, TODFREQ);
+
+ iunlock(&tod);
+}
+
+/*
+ * Set the time of day struct
+ */
+void
+todset(vlong t, vlong delta, int n)
+{
+ if(!tod.init)
+ todinit();
+
+ ilock(&tod);
+ if(t >= 0){
+ tod.off = t;
+ tod.last = fastticks(nil);
+ tod.lasttime = 0;
+ tod.delta = 0;
+ tod.sstart = tod.send;
+ } else {
+ if(n <= 0)
+ n = 1;
+ n *= HZ;
+ if(delta < 0 && n > -delta)
+ n = -delta;
+ if(delta > 0 && n > delta)
+ n = delta;
+ delta = delta/n;
+ tod.sstart = MACHP(0)->ticks;
+ tod.send = tod.sstart + n;
+ tod.delta = delta;
+ }
+ iunlock(&tod);
+}
+
+/*
+ * get time of day
+ */
+vlong
+todget(vlong *ticksp)
+{
+ uvlong x;
+ vlong ticks, diff;
+ ulong t;
+
+ if(!tod.init)
+ todinit();
+
+ // we don't want time to pass twixt the measuring of fastticks
+ // and grabbing tod.last. Also none of the vlongs are atomic so
+ // we have to look at them inside the lock.
+ ilock(&tod);
+ tod.cnt++;
+ ticks = fastticks(nil);
+
+ // add in correction
+ if(tod.sstart != tod.send){
+ t = MACHP(0)->ticks;
+ if(t >= tod.send)
+ t = tod.send;
+ tod.off = tod.off + tod.delta*(t - tod.sstart);
+ tod.sstart = t;
+ }
+
+ // convert to epoch
+ diff = ticks - tod.last;
+ if(diff < 0)
+ diff = 0;
+ mul64fract(&x, diff, tod.multiplier);
+ x += tod.off;
+
+ // time can't go backwards
+ if(x < tod.lasttime)
+ x = tod.lasttime;
+ else
+ tod.lasttime = x;
+
+ iunlock(&tod);
+
+ if(ticksp != nil)
+ *ticksp = ticks;
+
+ return x;
+}
+
+/*
+ * convert time of day to ticks
+ */
+uvlong
+tod2fastticks(vlong ns)
+{
+ uvlong x;
+
+ ilock(&tod);
+ mul64fract(&x, ns-tod.off, tod.divider);
+ x += tod.last;
+ iunlock(&tod);
+ return x;
+}
+
+/*
+ * called regularly to avoid calculation overflows
+ */
+void
+todfix(void)
+{
+ vlong ticks, diff;
+ uvlong x;
+
+ ticks = fastticks(nil);
+
+ diff = ticks - tod.last;
+ if(diff > tod.hz){
+ ilock(&tod);
+
+ // convert to epoch
+ mul64fract(&x, diff, tod.multiplier);
+if(x > 30000000000ULL) print("todfix %llud\n", x);
+ x += tod.off;
+
+ // protect against overflows
+ tod.last = ticks;
+ tod.off = x;
+
+ iunlock(&tod);
+ }
+}
+
+long
+tseconds(void)
+{
+ vlong x;
+ int i;
+
+ x = todget(nil);
+ x = x/TODFREQ;
+ i = x;
+ return i;
+}
+
+// convert milliseconds to fast ticks
+//
+uvlong
+ms2fastticks(ulong ms)
+{
+ if(!tod.init)
+ todinit();
+ return (tod.hz*ms)/1000ULL;
+}
+
+/*
+ * convert nanoseconds to fast ticks
+ */
+uvlong
+ns2fastticks(uvlong ns)
+{
+ uvlong res;
+
+ if(!tod.init)
+ todinit();
+ mul64fract(&res, ns, tod.divider);
+ return res;
+}
+
+/*
+ * convert fast ticks to ns
+ */
+uvlong
+fastticks2ns(uvlong ticks)
+{
+ uvlong res;
+
+ if(!tod.init)
+ todinit();
+ mul64fract(&res, ticks, tod.multiplier);
+ return res;
+}
+
+/*
+ * Make a 64 bit fixed point number that has a decimal point
+ * to the left of the low order 32 bits. This is used with
+ * mul64fract for converting twixt nanoseconds and fastticks.
+ *
+ * multiplier = (to<<32)/from
+ */
+uvlong
+mk64fract(uvlong to, uvlong from)
+{
+/*
+ int shift;
+
+ if(to == 0ULL)
+ return 0ULL;
+
+ shift = 0;
+ while(shift < 32 && to < (1ULL<<(32+24))){
+ to <<= 8;
+ shift += 8;
+ }
+ while(shift < 32 && to < (1ULL<<(32+31))){
+ to <<= 1;
+ shift += 1;
+ }
+
+ return (to/from)<<(32-shift);
+*/
+ return (to<<32)/from;
+}
diff --git a/os/port/uart.h b/os/port/uart.h
new file mode 100644
index 00000000..370ee09a
--- /dev/null
+++ b/os/port/uart.h
@@ -0,0 +1,104 @@
+typedef struct PhysUart PhysUart;
+typedef struct Uart Uart;
+
+/*
+ * routines to access UART hardware
+ */
+struct PhysUart
+{
+ char* name;
+ Uart* (*pnp)(void);
+ void (*enable)(Uart*, int);
+ void (*disable)(Uart*);
+ void (*kick)(Uart*);
+ void (*dobreak)(Uart*, int);
+ int (*baud)(Uart*, int);
+ int (*bits)(Uart*, int);
+ int (*stop)(Uart*, int);
+ int (*parity)(Uart*, int);
+ void (*modemctl)(Uart*, int);
+ void (*rts)(Uart*, int);
+ void (*dtr)(Uart*, int);
+ long (*status)(Uart*, void*, long, long);
+ void (*fifo)(Uart*, int);
+ void (*power)(Uart*, int);
+ int (*getc)(Uart*); /* polling versions, for iprint, rdb */
+ void (*putc)(Uart*, int);
+};
+
+enum {
+ Stagesize= 1024
+};
+
+/*
+ * software UART
+ */
+struct Uart
+{
+ void* regs; /* hardware stuff */
+ void* saveregs; /* place to put registers on power down */
+ char* name; /* internal name */
+ ulong freq; /* clock frequency */
+ int bits; /* bits per character */
+ int stop; /* stop bits */
+ int parity; /* even, odd or no parity */
+ int baud; /* baud rate */
+ PhysUart*phys;
+ int console; /* used as a serial console */
+ int special; /* internal kernel device */
+ Uart* next; /* list of allocated uarts */
+
+ QLock;
+ int type; /* ?? */
+ int dev;
+ int opens;
+
+ int enabled;
+ Uart *elist; /* next enabled interface */
+
+ int perr; /* parity errors */
+ int ferr; /* framing errors */
+ int oerr; /* rcvr overruns */
+ int berr; /* no input buffers */
+ int serr; /* input queue overflow */
+
+ /* buffers */
+ int (*putc)(Queue*, int);
+ Queue *iq;
+ Queue *oq;
+
+ Lock rlock;
+ uchar istage[Stagesize];
+ uchar *iw;
+ uchar *ir;
+ uchar *ie;
+
+ Lock tlock; /* transmit */
+ uchar ostage[Stagesize];
+ uchar *op;
+ uchar *oe;
+ int drain;
+
+ int modem; /* hardware flow control on */
+ int xonoff; /* software flow control on */
+ int blocked;
+ int cts, dsr, dcd, dcdts; /* keep track of modem status */
+ int ctsbackoff;
+ int hup_dsr, hup_dcd; /* send hangup upstream? */
+ int dohup;
+
+ Rendez r;
+};
+
+extern Uart* consuart;
+
+extern int uartctl(Uart*, char*);
+extern int uartgetc(void);
+extern void uartkick(void*);
+extern void uartmouse(Uart*, int (*)(Queue*, int), int);
+extern void uartsetmouseputc(Uart*, int (*)(Queue*, int));
+extern void uartputc(int);
+extern void uartputs(char*, int);
+extern void uartrecv(Uart*, char);
+extern Uart* uartsetup(Uart*);
+extern int uartstageoutput(Uart*);
diff --git a/os/port/xalloc.c b/os/port/xalloc.c
new file mode 100644
index 00000000..73310089
--- /dev/null
+++ b/os/port/xalloc.c
@@ -0,0 +1,246 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+#define datoff ((ulong)((Xhdr*)0)->data)
+
+enum
+{
+ Chunk = 64*1024,
+ Nhole = 128,
+ Magichole = 0xDeadBabe,
+};
+
+typedef struct Hole Hole;
+typedef struct Xalloc Xalloc;
+typedef struct Xhdr Xhdr;
+
+struct Hole
+{
+ ulong addr;
+ ulong size;
+ ulong top;
+ Hole* link;
+};
+
+struct Xhdr
+{
+ ulong size;
+ ulong magix;
+ char data[1];
+};
+
+struct Xalloc
+{
+ Lock;
+ Hole hole[Nhole];
+ Hole* flist;
+ Hole* table;
+};
+
+static Xalloc xlists;
+
+static void
+ixprt()
+{
+ xsummary();
+ ixsummary();
+}
+
+void
+xinit(void)
+{
+ Hole *h, *eh;
+
+ eh = &xlists.hole[Nhole-1];
+ for(h = xlists.hole; h < eh; h++)
+ h->link = h+1;
+
+ xlists.flist = xlists.hole;
+
+ if(conf.npage1)
+ xhole(conf.base1, conf.npage1*BY2PG);
+ conf.npage1 = conf.base1+(conf.npage1*BY2PG);
+
+ if(conf.npage0)
+ xhole(conf.base0, conf.npage0*BY2PG);
+ conf.npage0 = conf.base0+(conf.npage0*BY2PG);
+
+ /* Save the bounds of kernel alloc memory for kernel mmu mapping */
+ conf.base0 = (ulong)KADDR(conf.base0);
+ conf.base1 = (ulong)KADDR(conf.base1);
+ conf.npage0 = (ulong)KADDR(conf.npage0);
+ conf.npage1 = (ulong)KADDR(conf.npage1);
+
+ debugkey('x', "xalloc/ialloc", ixprt, 0);
+}
+
+void*
+xspanalloc(ulong size, int align, ulong span)
+{
+ ulong a, v, t;
+
+ a = (ulong)xalloc(size+align+span);
+ if(a == 0)
+ panic("xspanalloc: %lud %d %lux\n", size, align, span);
+
+ if(span > 2) {
+ v = (a + span) & ~(span-1);
+ t = v - a;
+ if(t > 0)
+ xhole(PADDR(a), t);
+ t = a + span - v;
+ if(t > 0)
+ xhole(PADDR(v+size+align), t);
+ }
+ else
+ v = a;
+
+ if(align > 1)
+ v = (v + align) & ~(align-1);
+
+ return (void*)v;
+}
+
+void*
+xallocz(ulong size, int zero)
+{
+ Xhdr *p;
+ Hole *h, **l;
+
+ size += BY2V + sizeof(Xhdr);
+ size &= ~(BY2V-1);
+
+ ilock(&xlists);
+ l = &xlists.table;
+ for(h = *l; h; h = h->link) {
+ if(h->size >= size) {
+ p = (Xhdr*)h->addr;
+ h->addr += size;
+ h->size -= size;
+ if(h->size == 0) {
+ *l = h->link;
+ h->link = xlists.flist;
+ xlists.flist = h;
+ }
+ iunlock(&xlists);
+ p = KADDR(p);
+ p->magix = Magichole;
+ p->size = size;
+ if(zero)
+ memset(p->data, 0, size);
+ return p->data;
+ }
+ l = &h->link;
+ }
+ iunlock(&xlists);
+ return nil;
+}
+
+void*
+xalloc(ulong size)
+{
+ return xallocz(size, 1);
+}
+
+void
+xfree(void *p)
+{
+ Xhdr *x;
+
+ x = (Xhdr*)((ulong)p - datoff);
+ if(x->magix != Magichole) {
+ xsummary();
+ panic("xfree(0x%lux) 0x%lux!=0x%lux", p, (ulong)Magichole, x->magix);
+ }
+ xhole(PADDR(x), x->size);
+}
+
+int
+xmerge(void *vp, void *vq)
+{
+ Xhdr *p, *q;
+
+ p = vp;
+ if((uchar*)vp+p->size == (uchar*)vq) {
+ q = vq;
+ p->size += q->size;
+ return 1;
+ }
+ return 0;
+}
+
+void
+xhole(ulong addr, ulong size)
+{
+ ulong top;
+ Hole *h, *c, **l;
+
+ if(size == 0)
+ return;
+
+ top = addr + size;
+ ilock(&xlists);
+ l = &xlists.table;
+ for(h = *l; h; h = h->link) {
+ if(h->top == addr) {
+ h->size += size;
+ h->top = h->addr+h->size;
+ c = h->link;
+ if(c && h->top == c->addr) {
+ h->top += c->size;
+ h->size += c->size;
+ h->link = c->link;
+ c->link = xlists.flist;
+ xlists.flist = c;
+ }
+ iunlock(&xlists);
+ return;
+ }
+ if(h->addr > addr)
+ break;
+ l = &h->link;
+ }
+ if(h && top == h->addr) {
+ h->addr -= size;
+ h->size += size;
+ iunlock(&xlists);
+ return;
+ }
+
+ if(xlists.flist == nil) {
+ iunlock(&xlists);
+ print("xfree: no free holes, leaked %lud bytes\n", size);
+ return;
+ }
+
+ h = xlists.flist;
+ xlists.flist = h->link;
+ h->addr = addr;
+ h->top = top;
+ h->size = size;
+ h->link = *l;
+ *l = h;
+ iunlock(&xlists);
+}
+
+void
+xsummary(void)
+{
+ int i;
+ Hole *h;
+
+ i = 0;
+ for(h = xlists.flist; h; h = h->link)
+ i++;
+
+ print("%d holes free\n", i);
+ i = 0;
+ for(h = xlists.table; h; h = h->link) {
+ print("%.8lux %.8lux %lud\n", h->addr, h->top, h->size);
+ i += h->size;
+ }
+ print("%d bytes free\n", i);
+}