/*
* Coraid Ethernet Console driver
* kick (CEC) driver
* Also requires hooks into devcons.c
*/
#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/netif.h"
enum {
Ncbuf = 8192,
Ncmask = Ncbuf-1,
Namelen = 128,
};
enum{
Tinita = 0,
Tinitb,
Tinitc,
Tdata,
Tack,
Tdiscover,
Toffer,
Treset,
};
enum{
Cunused = 0,
Cinitb, // sent initb
Clogin, // login state
Copen, // up
};
typedef struct{
Chan *dc;
Chan *cc;
Dev *d;
uchar ea[6];
char path[32];
}If;
typedef struct{
uchar dst[6];
uchar src[6];
uchar etype[2];
uchar type;
uchar conn;
uchar seq;
uchar len;
uchar data[1500];
}Pkt;
typedef struct{
QLock;
Lock;
uchar ea[6]; // along with cno, the key to the connection
uchar cno; // connection number on remote host
uchar stalled; // cectimer needs to kick it -- cecputs while !islo()
int state; // connection state
int idle; // idle ticks
int to; // ticks to timeout
int retries; // remaining retries
Block *bp; // unacked message
If *ifc; // interface for this connection
uchar sndseq; // sequence number of last sent message
uchar rcvseq; // sequence number of last rcv'd message
char cbuf[Ncbuf]; // curcular buffer
int r, w; // indexes into cbuf
int pwi; // index into passwd;
char passwd[32]; // password typed by connection
}Conn;
/*
* Since this code is in the output chain of procedures for console
* output, we can't use the general printf functions. See the ones
* at the bottom of this file. It assumes the serial port.
*/
static int cecprint(char *, ...);
extern int parseether(uchar *, char *);
extern Chan * chandial(char *, char *, char *, Chan **);
enum {
Qdir = 0,
Qstat,
Qctl,
Qdbg,
Qcfg,
CMsetshelf,
CMsetname,
CMtraceon,
CMtraceoff,
CMsetpasswd,
CMcecon,
CMcecoff,
Nconns = 20,
};
static If ifs[4];
static char name[Namelen];
static int shelf = -1;
static Conn conn[Nconns];
static int tflag; // trace flag
static char passwd[Namelen];
static int xmit;
static int rsnd;
static Rendez trendez;
static uchar broadcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
Dirtab cecdir[] = {
".", { Qdir, 0, QTDIR }, 0, DMDIR | 0555,
"cecstat", { Qstat}, 1, 0444,
"cecctl", { Qctl}, 1, 0222,
"cecdbg", { Qdbg }, 1, 0444,
"ceccfg", { Qcfg }, 1, 0444,
};
static Cmdtab ceccmd[] = {
CMsetname, "name", 2,
CMtraceon, "traceon", 1,
CMtraceoff, "traceoff", 1,
CMsetpasswd, "password", 2,
CMcecon, "cecon", 2,
CMcecoff, "cecoff", 2,
CMsetshelf, "shelf", 2,
};
static void
getaddr(char *path, uchar *ea)
{
char buf[6*2+1];
Chan *c;
int n;
snprint(up->genbuf, sizeof up->genbuf, "%s/addr", path);
c = namec(up->genbuf, Aopen, OREAD, 0);
if(waserror()) {
cclose(c);
nexterror();
}
n = devtab[c->type]->read(c, buf, sizeof buf-1, 0);
if(n != 6*2)
error("getaddr");
buf[n] = 0;
if(parseether(ea, buf) < 0)
error("parseether failure");
poperror();
cclose(c);
}
static char *types[] = {
"Tinita", "Tinitb", "Tinitc", "Tdata", "Tack",
"Tdiscover", "Toffer", "Treset", "*GOK*",
};
static int
cbget(Conn *cp)
{
int c;
if(cp->r == cp->w)
return -1;
c = cp->cbuf[cp->r];
cp->r = (cp->r+1)&Ncmask;
return c;
}
static void
cbput(Conn *cp, int c)
{
if(cp->r == (cp->w+1)&Ncmask) // full
return;
cp->cbuf[cp->w] = c;
cp->w = (cp->w+1)&Ncmask;
}
static void
trace(Block *bp)
{
Pkt *p;
int type;
if(tflag == 0)
return;
p = (Pkt *)bp->rp;
type = p->type;
if(type > Treset)
type = Treset+1;
cecprint("%E > %E) seq %d, type %s, len %d, conn %d\n",
p->src, p->dst, p->seq, types[type], p->len, p->conn);
}
static Block *
sethdr(If *ifc, uchar *ea, Pkt **npp, int len) // set header for response
{
Block *bp;
Pkt *np;
len += 18;
if(len < 60)
len = 60;
bp = allocb(len);
bp->wp = bp->rp+len;
np = (Pkt *)bp->rp;
memmove(np->dst, ea, 6);
memmove(np->src, ifc->ea, 6);
np->etype[0] = 0xbc;
np->etype[1] = 0xbc;
np->seq = 0;
*npp = np;
return bp;
}
static void
send(Conn *cp, Block *bp) // put on output queue
{
Block *nbp;
if(cp->bp != nil)
panic("cecsend: cp->bp not nil\n");
nbp = allocb(BLEN(bp));
memmove(nbp->wp, bp->rp, BLEN(bp));
nbp->wp += BLEN(bp);
cp->bp = nbp;
trace(bp);
cp->ifc->d->bwrite(cp->ifc->dc, bp, 0);
xmit++;
cp->to = 4;
cp->retries = 3;
xmit++;
}
static void
senddata(Conn *cp, void *data, int len)
{
Block *bp;
Pkt *p;
bp = sethdr(cp->ifc, cp->ea, &p, len);
memmove(p->data, data, len);
p->len = len;
p->seq = ++cp->sndseq;
p->conn = cp->cno;
p->type = Tdata;
send(cp, bp);
}
static void
resend(Conn *cp)
{
Block *nbp;
rsnd++;
nbp = allocb(BLEN(cp->bp));
memmove(nbp->wp, cp->bp->rp, BLEN(cp->bp));
nbp->wp += BLEN(cp->bp);
trace(nbp);
cp->ifc->d->bwrite(cp->ifc->dc, nbp, 0);
cp->to = 4;
}
static void
reset(If *ifc, uchar conn)
{
Block *bp;
Pkt *p;
bp = sethdr(ifc, ifc->ea, &p, 0);
p->type = Treset;
p->conn = conn;
trace(bp);
ifc->d->bwrite(ifc->dc, bp, 0);
}
static void
ack(Conn *cp)
{
if(cp->bp)
freeb(cp->bp);
cp->bp = nil;
cp->to = 0;
cp->retries = 0;
}
static void
start(Conn *cp)
{
char buf[250];
int n, c;
if(cp->bp != nil)
return;
n = 0;
ilock(cp);
while(n < sizeof buf){
c = cbget(cp);
if(c == -1)
break;
buf[n] = c;
n++;
}
iunlock(cp);
if(n == 0)
return;
senddata(cp, buf, n);
}
void
cecputs(char *str, int n)
{
int i, c, ien;
Conn *cp;
extern int panicking;
if(panicking || active.exiting)
return;
ien = islo();
for(cp = conn; cp < &conn[Nconns]; cp++){
ilock(cp);
if(cp->state == Copen){
for (i = 0; i < n; i++){
c = str[i];
if(c == '\n')
cbput(cp, '\r');
cbput(cp, c);
}
}
iunlock(cp);
if(ien){
qlock(cp);
start(cp);
qunlock(cp);
}else{
cp->stalled = 1;
wakeup(&trendez);
}
}
}
static void
conputs(Conn *c, char *s)
{
for(; *s; s++)
cbput(c, *s);
}
static void
cectimer(void *)
{
Conn *cp;
for(;;){
tsleep(&trendez, return0, 0, 500);
for(cp = conn; cp < &conn[Nconns]; cp++){
qlock(cp);
if(cp->bp != nil){
if(--cp->to <= 0){
if(--cp->retries <= 0){
freeb(cp->bp);
cp->bp = nil;
cp->state = Cunused;
}else
resend(cp);
}
}else if(cp->stalled){
cp->stalled = 0;
start(cp);
}
qunlock(cp);
}
}
}
static void
discover(If *ifc, Pkt *p) // tell about us
{
Block *bp;
Pkt *np;
uchar *addr;
if(p)
addr = p->src;
else
addr = broadcast;
bp = sethdr(ifc, addr, &np, 0);
np->type = Toffer;
np->len = snprint((char *)np->data, sizeof np->data, "%d %s", shelf, name);
trace(bp);
ifc->d->bwrite(ifc->dc, bp, 0);
}
static Conn *
findconn(uchar *ea, uchar cno) // return locked connection object
{
Conn *cp, *ncp = nil;
for(cp = conn; cp < &conn[Nconns]; cp++){
if(ncp == nil && cp->state == Cunused)
ncp = cp;
if(memcmp(ea, cp->ea, 6) == 0 && cno == cp->cno){
qlock(cp);
return cp;
}
}
if(ncp != nil)
qlock(ncp);
return ncp;
}
static void
checkpw(Conn *cp, char *str, int len)
{
int i, c;
if(passwd[0] == 0)
return;
for(i = 0; i < len; i++){
c = str[i];
if(c != '\n' && c != '\r'){
if(cp->pwi < (sizeof cp->passwd)-1)
cp->passwd[cp->pwi++] = c;
cbput(cp, '#');
cecprint("%c", c);
continue;
}
// is newline; check password
cp->passwd[cp->pwi] = 0;
if(strcmp(cp->passwd, passwd) == 0){
cp->state = Copen;
cp->pwi = 0;
print("\r\n%E logged in\r\n", cp->ea);
}else{
conputs(cp, "\r\nBad password\r\npassword: ");
cp->pwi = 0;
}
}
start(cp);
}
static void
incoming(Conn *cp, If *ifc, Pkt *p)
{
Pkt *np;
int i;
Block *bp;
// ack it no matter what its sequence number
bp = sethdr(ifc, p->src, &np, 0);
np->type = Tack;
np->seq = p->seq;
np->conn = cp->cno;
np->len = 0;
trace(bp);
ifc->d->bwrite(ifc->dc, bp, 0);
if(p->seq == cp->rcvseq)
return;
// process message
cp->rcvseq = p->seq;
if(cp->state == Copen){
for (i = 0; i < p->len; i++)
kbdcr2nl(nil, (char)p->data[i]);
}else if(cp->state == Clogin)
checkpw(cp, (char *)p->data, p->len);
}
static void
inita(Conn *ncp, If *ifc, Pkt *p) // connection request
{
Pkt *np;
Block *bp;
ncp->ifc = ifc;
ncp->state = Cinitb;
memmove(ncp->ea, p->src, 6);
ncp->cno = p->conn;
bp = sethdr(ifc, p->src, &np, 0);
np->type = Tinitb;
np->conn = ncp->cno;
np->len = 0;
send(ncp, bp);
}
static void
cecrdr(void *vp) // reader of incoming frames
{
Block *bp;
If *ifc;
Pkt *p;
Conn *cp;
ifc = vp;
if(waserror())
goto kexit;
discover(ifc, 0);
for(;;){
bp = ifc->d->bread(ifc->dc, ETHERMAXTU, 0);
if(bp == nil)
nexterror();
p = (Pkt *)bp->rp;
if(p->etype[0] != 0xbc || p->etype[1] != 0xbc){
freeb(bp);
continue;
}
trace(bp);
cp = findconn(p->src, p->conn);
if(cp == nil){
cecprint("cec: out of connection structures\n");
freeb(bp);
continue;
}
if (waserror()){
freeb(bp);
qunlock(cp);
continue;
}
switch(p->type){
case Tinita:
// connection request
if(cp->bp){
cecprint("cec: reset with bp!? ask quanstro\n");
freeb(cp->bp);
cp->bp = 0;
}
inita(cp, ifc, p);
break;
case Tinitb:
cecprint("cec: unexpected initb\n");
break;
case Tinitc:
if(cp->state == Cinitb){
ack(cp);
if(cp->passwd[0]){
cp->state = Clogin;
conputs(cp, "password: ");
start(cp);
}else
cp->state = Copen;
}
break;
case Tdata:
// data packet arrived
incoming(cp, ifc, p);
break;
case Tack:
// ack for one I sent arrived
if(cp->state == Clogin || cp->state == Copen){
ack(cp);
start(cp);
}
break;
case Tdiscover:
// someone wanting to know about us
discover(ifc, p);
break;
case Toffer:
// cecprint("cec: unexpected offer\n"); from ourselves.
break;
case Treset:
if(cp->bp)
freeb(cp->bp);
cp->bp = 0;
cp->state = Cunused;
break;
default:
cecprint("bad cec type: %d\n", p->type);
break;
}
nexterror();
}
kexit:
for(cp = conn; cp < conn + nelem(conn); cp++)
if(cp->ifc == ifc){
if(cp->bp)
freeb(cp->bp);
memset(cp, 0, sizeof *cp);
break;
}
memset(ifc, 0, sizeof *ifc);
pexit("cec exiting", 1);
}
static Chan *
cecattach(char *spec)
{
Chan *c;
static QLock q;
static int inited;
qlock(&q);
if(inited == 0){
kproc("cectimer", cectimer, nil);
inited++;
}
qunlock(&q);
c = devattach(L'©', spec);
c->qid.path = Qdir;
return c;
}
static Walkqid*
cecwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, cecdir, nelem(cecdir), devgen);
}
static int
cecstat(Chan *c, uchar *dp, int n)
{
return devstat(c, dp, n, cecdir, nelem(cecdir), devgen);
}
static Chan *
cecopen(Chan *c, int omode)
{
return devopen(c, omode, cecdir, nelem(cecdir), devgen);
}
static void
cecclose(Chan *)
{
}
static char * cstate[] = { "unused", "initb", "login", "open" };
enum { SIZE = READSTR*2 };
static long
cecread(Chan *c, void *a, long n, vlong offset)
{
char *p;
Conn *cp;
int j;
If *ifc;
switch((int)c->qid.path){
case Qdir:
return devdirread(c, a, n, cecdir, nelem(cecdir), devgen);
case Qstat:
p = malloc(SIZE);
j = 0;
for(cp = conn; cp < conn+Nconns; cp++)
if(cp->state != Cunused)
j += snprint(p+j, SIZE-j,
"%E %3d %-6s %12d %d %d %08ulx\n",
cp->ea, cp->cno, cstate[cp->state], cp->idle,
cp->to, cp->retries, cp->bp);
n = readstr(offset, a, n, p);
free(p);
return n;
case Qdbg:
cecprint("xmit %d, rsnd %d\n", xmit, rsnd);
return 0;
case Qcfg:
p = mallocz(SIZE, 1);
j = 0;
for(ifc=ifs; ifc < ifs + nelem(ifs); ifc++)
if(ifc->d)
j += snprint(p+j, SIZE-j, "%s\n", ifc->path);
n = readstr(offset, a, n, p);
free(p);
return n;
}
error(Egreg);
return 0;
}
static void
cecon(char *path)
{
Chan *dc, *cc;
uchar ea[6];
char buf[64];
If *ifc, *nifc = nil;
for(ifc=ifs; ifc < ifs + nelem(ifs); ifc++)
if(ifc->d == nil)
nifc = ifc;
else if(strcmp(ifc->path, path) == 0)
return;
ifc = nifc;
if(ifc == nil)
error("out of interface structures");
getaddr(path, ea);
snprint(buf, sizeof buf, "%s!0xbcbc", path);
dc = chandial(buf, nil, nil, &cc);
if(dc == nil || cc == nil){
if (cc)
cclose(cc);
if (dc)
cclose(dc);
snprint(up->genbuf, nelem(up->genbuf), "can't dial %s", buf);
error(up->genbuf);
}
ifc->d = devtab[cc->type];
ifc->cc = cc;
ifc->dc = dc;
strncpy(ifc->path, path, nelem(ifc->path));
memmove(ifc->ea, ea, 6);
snprint(up->genbuf, nelem(up->genbuf), "cec@%s\n", path);
kproc(up->genbuf, cecrdr, ifc);
}
static void
cecoff(char *path)
{
If *ifc, *e;
ifc = ifs;
e = ifc+nelem(ifs);
for(; ifc < e; ifc++)
if(ifc->d && strcmp(path, ifc->path) == 0)
break;
if(ifc == e)
error("cec not found");
cclose(ifc->cc);
cclose(ifc->dc);
}
static long
cecwrite(Chan *c, void *a, long n, vlong )
{
Cmdbuf *cb;
Cmdtab *cp;
if(c->qid.path == Qctl){
cb = parsecmd(a, n);
if(waserror()){
free(cb);
nexterror();
}
cp = lookupcmd(cb, ceccmd, nelem(ceccmd));
switch(cp->index){
case CMsetname:
strecpy(name, name+(sizeof name - 1), cb->f[1]);
break;
case CMtraceon:
tflag = 1;
break;
case CMtraceoff:
tflag = 0;
break;
case CMsetpasswd:
strcpy(passwd, cb->f[1]);
break;
case CMcecon:
cecon(cb->f[1]);
break;
case CMcecoff:
cecoff(cb->f[1]);
break;
case CMsetshelf:
shelf = atoi(cb->f[1]);
break;
default:
cmderror(cb, "bad control message");
break;
}
free(cb);
poperror();
return n;
}
error(Egreg);
return 0;
}
Dev cecdevtab = {
L'©',
"cethcon",
devreset,
devinit,
devshutdown,
cecattach,
cecwalk,
cecstat,
cecopen,
devcreate,
cecclose,
cecread,
devbread,
cecwrite,
devbwrite,
devremove,
devwstat,
devpower,
devconfig,
};
static int
cecprint(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);
uartputs(buf, n);
return n;
}
|