/*
* Kernel proxy for usb ethernet 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/netif.h"
#include "etherif.h"
#include "../ip/ip.h"
#define GET4(p) ((p)[3]<<24 | (p)[2]<<16 | (p)[1]<<8 | (p)[0])
#define PUT4(p, v) ((p)[0] = (v), (p)[1] = (v)>>8, \
(p)[2] = (v)>>16, (p)[3] = (v)>>24)
#define dprint if(debug) print
#define ddump if(0) dump
static int debug = 0;
enum {
Bind = 0,
Unbind,
SmscRxerror = 0x8000,
SmscTxfirst = 0x2000,
SmscTxlast = 0x1000,
Lan78Rxerror = 0x00400000,
Lan78Txfcs = 1<<22,
};
typedef struct Ctlr Ctlr;
typedef struct Udev Udev;
typedef int (Unpackfn)(Ether*, Block*);
typedef void (Transmitfn)(Ctlr*, Block*);
struct Ctlr {
Ether* edev;
Udev* udev;
Chan* inchan;
Chan* outchan;
char* buf;
int bufsize;
int maxpkt;
uint rxbuf;
uint rxpkt;
uint txbuf;
uint txpkt;
QLock;
};
struct Udev {
char *name;
Unpackfn *unpack;
Transmitfn *transmit;
};
static Cmdtab cmds[] = {
{ Bind, "bind", 7, },
{ Unbind, "unbind", 0, },
};
static Unpackfn unpackcdc, unpackasix, unpacksmsc, unpacklan78;
static Transmitfn transmitcdc, transmitasix, transmitsmsc, transmitlan78;
static Udev udevtab[] = {
{ "cdc", unpackcdc, transmitcdc, },
{ "asix", unpackasix, transmitasix, },
{ "smsc", unpacksmsc, transmitsmsc, },
{ "lan78xx", unpacklan78, transmitlan78, },
{ nil },
};
static char nullea[Eaddrlen];
static void
dump(int c, Block *b)
{
int s, i;
s = splhi();
print("%c%ld:", c, BLEN(b));
for(i = 0; i < 32; i++)
print(" %2.2ux", b->rp[i]);
print("\n");
splx(s);
}
static int
unpack(Ether *edev, Block *b, int m)
{
Block *nb;
Ctlr *ctlr;
ctlr = edev->ctlr;
ddump('?', b);
if(m == BLEN(b)){
etheriq(edev, b, 1);
ctlr->rxpkt++;
return 1;
}
nb = iallocb(m);
if(nb != nil){
memmove(nb->wp, b->rp, m);
nb->wp += m;
etheriq(edev, nb, 1);
ctlr->rxpkt++;
}else
edev->soverflows++;
b->rp += m;
return 0;
}
static int
unpackcdc(Ether *edev, Block *b)
{
int m;
m = BLEN(b);
if(m < 6)
return -1;
return unpack(edev, b, m);
}
static int
unpackasix(Ether *edev, Block *b)
{
ulong hd;
int m;
uchar *wp;
if(BLEN(b) < 4)
return -1;
hd = GET4(b->rp);
b->rp += 4;
m = hd & 0xFFFF;
hd >>= 16;
if(m != (~hd & 0xFFFF))
return -1;
m = ROUND(m, 2);
if(m < 6 || m > BLEN(b))
return -1;
if((wp = b->rp + m) != b->wp && b->wp - wp < 4)
b->wp = wp;
return unpack(edev, b, m);
}
static int
unpacksmsc(Ether *edev, Block *b)
{
ulong hd;
int m;
ddump('@', b);
if(BLEN(b) < 4)
return -1;
hd = GET4(b->rp);
b->rp += 4;
m = hd >> 16;
if(m < 6 || m > BLEN(b))
return -1;
if(BLEN(b) - m < 4)
b->wp = b->rp + m;
if(hd & SmscRxerror){
edev->frames++;
b->rp += m;
if(BLEN(b) == 0){
freeb(b);
return 1;
}
}else if(unpack(edev, b, m) == 1)
return 1;
if((m &= 3) != 0)
b->rp += 4 - m;
return 0;
}
static int
unpacklan78(Ether *edev, Block *b)
{
ulong hd;
int m;
if(BLEN(b) < 10)
return -1;
hd = GET4(b->rp);
b->rp += 10;
m = hd & 0x3FFF;
if(m < 6 || m > BLEN(b))
return -1;
if(hd & Lan78Rxerror){
edev->frames++;
b->rp += m;
if(BLEN(b) == 0){
freeb(b);
return 1;
}
}else if(unpack(edev, b, m) == 1)
return 1;
if(BLEN(b) > 0)
b->rp = (uchar*)((((uintptr)b->rp)+3)&~3);
return 0;
}
static void
transmit(Ctlr *ctlr, Block *b)
{
Chan *c;
ddump('!', b);
c = ctlr->outchan;
devtab[c->type]->bwrite(c, b, 0);
}
static void
transmitcdc(Ctlr *ctlr, Block *b)
{
transmit(ctlr, b);
}
static void
transmitasix(Ctlr *ctlr, Block *b)
{
int n;
n = BLEN(b) & 0xFFFF;
n |= ~n << 16;
b = padblock(b, 4);
PUT4(b->rp, n);
if(BLEN(b) % ctlr->maxpkt == 0){
b = padblock(b, -4);
PUT4(b->wp, 0xFFFF0000);
b->wp += 4;
}
transmit(ctlr, b);
}
static void
transmitsmsc(Ctlr *ctlr, Block *b)
{
int n;
n = BLEN(b) & 0x7FF;
b = padblock(b, 8);
PUT4(b->rp, n | SmscTxfirst | SmscTxlast);
PUT4(b->rp+4, n);
transmit(ctlr, b);
}
static void
transmitlan78(Ctlr *ctlr, Block *b)
{
int n;
n = BLEN(b) & 0xFFFFF;
b = padblock(b, 8);
PUT4(b->rp, n | Lan78Txfcs);
PUT4(b->rp+4, n);
transmit(ctlr, b);
}
static void
etherusbproc(void *a)
{
Ether *edev;
Ctlr *ctlr;
Chan *c;
Block *b;
edev = a;
ctlr = edev->ctlr;
c = ctlr->inchan;
b = nil;
if(waserror()){
print("etherusbproc: error exit %s\n", up->errstr);
pexit(up->errstr, 1);
return;
}
for(;;){
if(b == nil){
b = devtab[c->type]->bread(c, ctlr->bufsize, 0);
ctlr->rxbuf++;
}
switch(ctlr->udev->unpack(edev, b)){
case -1:
edev->buffs++;
freeb(b);
/* fall through */
case 1:
b = nil;
break;
}
}
}
/*
* bind type indev outdev mac bufsize maxpkt
*/
static void
bind(Ctlr *ctlr, Udev *udev, Cmdbuf *cb)
{
Chan *inchan, *outchan;
char *buf;
uint bufsize, maxpkt;
uchar ea[Eaddrlen];
qlock(ctlr);
inchan = outchan = nil;
buf = nil;
if(waserror()){
free(buf);
if(inchan)
cclose(inchan);
if(outchan)
cclose(outchan);
qunlock(ctlr);
nexterror();
}
if(ctlr->buf != nil)
cmderror(cb, "already bound to a device");
maxpkt = strtol(cb->f[6], 0, 0);
if(maxpkt < 8)
cmderror(cb, "bad maxpkt");
bufsize = strtol(cb->f[5], 0, 0);
if(bufsize < maxpkt || bufsize > 32*1024)
cmderror(cb, "bad bufsize");
buf = smalloc(bufsize);
inchan = namec(cb->f[2], Aopen, OREAD, 0);
outchan = namec(cb->f[3], Aopen, OWRITE, 0);
assert(inchan != nil && outchan != nil);
if(parsemac(ea, cb->f[4], Eaddrlen) != Eaddrlen)
cmderror(cb, "bad etheraddr");
if(memcmp(ctlr->edev->ea, nullea, Eaddrlen) == 0)
memmove(ctlr->edev->ea, ea, Eaddrlen);
else if(memcmp(ctlr->edev->ea, ea, Eaddrlen) != 0)
cmderror(cb, "wrong ether address");
ctlr->buf = buf;
ctlr->inchan = inchan;
ctlr->outchan = outchan;
ctlr->bufsize = bufsize;
ctlr->maxpkt = maxpkt;
ctlr->udev = udev;
kproc("etherusb", etherusbproc, ctlr->edev);
memmove(ctlr->edev->addr, ea, Eaddrlen);
print("\netherusb %s: %E\n", udev->name, ctlr->edev->addr);
poperror();
qunlock(ctlr);
}
static void
unbind(Ctlr *ctlr)
{
qlock(ctlr);
if(ctlr->buf != nil){
free(ctlr->buf);
ctlr->buf = nil;
if(ctlr->inchan)
cclose(ctlr->inchan);
if(ctlr->outchan)
cclose(ctlr->outchan);
ctlr->inchan = ctlr->outchan = nil;
}
qunlock(ctlr);
}
static long
etherusbifstat(Ether* edev, void* a, long n, ulong offset)
{
Ctlr *ctlr;
char *p;
int l;
ctlr = edev->ctlr;
p = malloc(READSTR);
l = 0;
l += snprint(p+l, READSTR-l, "rxbuf: %ud\n", ctlr->rxbuf);
l += snprint(p+l, READSTR-l, "rxpkt: %ud\n", ctlr->rxpkt);
l += snprint(p+l, READSTR-l, "txbuf: %ud\n", ctlr->txbuf);
l += snprint(p+l, READSTR-l, "txpkt: %ud\n", ctlr->txpkt);
USED(l);
n = readstr(offset, a, n, p);
free(p);
return n;
}
static void
etherusbtransmit(Ether *edev)
{
Ctlr *ctlr;
Block *b;
ctlr = edev->ctlr;
while((b = qget(edev->oq)) != nil){
ctlr->txpkt++;
if(ctlr->buf == nil)
freeb(b);
else{
ctlr->udev->transmit(ctlr, b);
ctlr->txbuf++;
}
}
}
static long
etherusbctl(Ether* edev, void* buf, long n)
{
Ctlr *ctlr;
Cmdbuf *cb;
Cmdtab *ct;
Udev *udev;
if((ctlr = edev->ctlr) == nil)
error(Enonexist);
cb = parsecmd(buf, n);
if(waserror()){
free(cb);
nexterror();
}
ct = lookupcmd(cb, cmds, nelem(cmds));
switch(ct->index){
case Bind:
for(udev = udevtab; udev->name; udev++)
if(strcmp(cb->f[1], udev->name) == 0)
break;
if(udev->name == nil)
cmderror(cb, "unknown etherusb type");
bind(ctlr, udev, cb);
break;
case Unbind:
unbind(ctlr);
break;
default:
cmderror(cb, "unknown etherusb control message");
}
poperror();
free(cb);
return n;
}
static void
etherusbmulticast(void*, uchar*, int)
{
/* nothing to do, we allow all multicast packets in */
}
static void
etherusbshutdown(Ether*)
{
}
static void
etherusbattach(Ether* edev)
{
Ctlr *ctlr;
ctlr = edev->ctlr;
if(ctlr->edev == 0){
/*
* Don't let boot process access etherusb until
* usbether driver has assigned an address.
*/
if(up->pid == 1 && strcmp(up->text, "boot") == 0)
while(memcmp(edev->ea, nullea, Eaddrlen) == 0)
tsleep(&up->sleep, return0, 0, 100);
ctlr->edev = edev;
}
}
static int
etherusbpnp(Ether* edev)
{
Ctlr *ctlr;
ctlr = malloc(sizeof(Ctlr));
edev->ctlr = ctlr;
edev->irq = -1;
edev->mbps = 100; /* TODO: get this from usbether */
/*
* Linkage to the generic ethernet driver.
*/
edev->attach = etherusbattach;
edev->transmit = etherusbtransmit;
edev->interrupt = nil;
edev->ifstat = etherusbifstat;
edev->ctl = etherusbctl;
edev->arg = edev;
/* TODO: promiscuous, multicast (for ipv6), shutdown (for reboot) */
// edev->promiscuous = etherusbpromiscuous;
edev->shutdown = etherusbshutdown;
edev->multicast = etherusbmulticast;
return 0;
}
void
etherusblink(void)
{
addethercard("usb", etherusbpnp);
}
|