Add support for Microchip (ex SMSC) LAN78xx usb ethernet, as used in LAN7515 chip on Raspberry Pi 3B+. Also tweak performance a bit for SMSC ethernet as used in earlier Raspberry Pi models. Note: this patch incorporates all changes from usbether-rpi and usb-ether-cdc and therefore supersedes those two patches. It may be necessary to revert them before applying this patch. Reference: /n/sources/patch/usb-ether-lan78xx Date: Thu Apr 5 21:37:20 GMT 2018 Signed-off-by: miller@hamnavoe.com --- /sys/src/cmd/usb/ether/ether.h Thu Apr 5 09:52:59 2018 +++ /sys/src/cmd/usb/ether/ether.h Thu Apr 5 09:52:57 2018 @@ -14,6 +14,7 @@ A88179, A88772, S95xx, /* SMSC */ + S78xx, Eaddrlen = 6, Epktlen = 1514, @@ -114,6 +115,7 @@ int ethermain(Dev *dev, int argc, char **argv); int asixreset(Ether*); int smscreset(Ether*); +int lan78xxreset(Ether*); int cdcreset(Ether*); int parseaddr(uchar *m, char *s); void dumpframe(char *tag, void *p, int n); --- /sys/src/cmd/usb/ether/ether.c Thu Apr 5 09:53:05 2018 +++ /sys/src/cmd/usb/ether/ether.c Thu Apr 5 09:53:01 2018 @@ -82,6 +82,9 @@ {0x0424, 0x9505, S95xx}, {0x0424, 0x9E00, S95xx}, {0x0424, 0x9E01, S95xx}, + /* LAN78xx family - gigabit ethernet + */ + {0x0424, 0x7800, S78xx}, /* raspberry pi 3 B+ */ {0, 0, 0}, }; @@ -121,6 +124,7 @@ { asixreset, smscreset, + lan78xxreset, cdcreset, /* keep last */ }; --- /sys/src/cmd/usb/ether/mkfile Thu Apr 5 09:53:08 2018 +++ /sys/src/cmd/usb/ether/mkfile Thu Apr 5 09:53:06 2018 @@ -8,6 +8,7 @@ ether.$O\ asix.$O\ smsc.$O\ + lan78xx.$O\ cdc.$O\ HFILES=\ --- /sys/src/cmd/usb/ether/lan78xx.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/usb/ether/lan78xx.c Thu Apr 5 09:53:10 2018 @@ -0,0 +1,452 @@ +/* + * Microchip (ex SMSC) LAN78XX + * Also used as ethernet core in LAN7515 usb hub + ethernet + */ + +#include +#include +#include +#include +#include "usb.h" +#include "usbfs.h" +#include "ether.h" + +enum { + Doburst = 1, + Resettime = 1000, + E2pbusytime = 1000, + Hsburst = 32, + Defbulkdly = 1000, + Rxfifosize = (12*1024), + Txfifosize = (12*1024), + + MACoffset = 1, + PHYinternal = 1, + Rxerror = 0x00400000, + Txfcs = 1<<22, + + /* USB vendor requests */ + Writereg = 0xA0, + Readreg = 0xA1, + + /* device registers */ + Idrev = 0x00, + Intsts = 0x0C, + Hwcfg = 0x10, + Led0en = 1<<20, + Led1en = 1<<21, + Mef = 1<<4, + Lrst = 1<<1, + Pmctrl = 0x14, + Ready = 1<<7, + Phyrst = 1<<4, + Gpiocfg0 = 0x18, + Gpiocfg1 = 0x1C, + E2pcmd = 0x40, + Busy = 1<<31, + Timeout = 1<<10, + Loaded = 1<<9, + Read = 0, + E2pdata = 0x44, + Burstcap = 0x90, + Intepctl = 0x98, + Phyint = 1<<17, + Bulkdelay = 0x94, + Rfectl = 0xB0, + Rxcoe = 0xF<<11, + Ab = 1<<10, + Am = 1<<9, + Au = 1<<8, + Dpf = 1<<1, + Usbcfg0 = 0x80, + Bir = 1<<6, + Bce = 1<<5, + Usbcfg1 = 0x84, + Rxfifoctl = 0xC0, + Rxen = 1<<31, + Txfifoctl = 0xC4, + Txen = 1<<31, + Rxfifo = 0xC8, + Txfifo = 0xCc, + Fctflow = 0xD0, + Maccr = 0x100, + Add = 1<<12, + Asd = 1<<11, + Macrx = 0x104, + Macfcs = 1<<4, + Macrxen = 1<<0, + Mactx = 0x108, + Mactxen = 1<<0, + Addrh = 0x118, + Addrl = 0x11C, + MIIaddr = 0x120, + MIIwrite= 1<<1, + MIIread = 0<<1, + MIIbusy = 1<<0, + MIIdata = 0x124, + Flow = 0x10C, + Addrfilth = 0x400, + Afvalid = 1<<31, + Addrfiltl = 0x404, + + /* MII registers */ + Bmcr = 0, + Bmcrreset= 1<<15, + Speed100= 1<<13, + Anenable= 1<<12, + Anrestart= 1<<9, + Fulldpx = 1<<8, + Speed1000= 1<<6, + Bmsr = 1, + Advertise = 4, + Adcsma = 0x0001, + Ad10h = 0x0020, + Ad10f = 0x0040, + Ad100h = 0x0080, + Ad100f = 0x0100, + Adpause = 0x0400, + Adpauseasym= 0x0800, + Adall = Ad10h|Ad10f|Ad100h|Ad100f, + Lpa = 5, + Ctrl1000 = 9, + Ad1000h = 0x0400, + Ad1000f = 0x0200, + Ledmodes = 29, + Led0shift = 0, + Led1shift = 4, + Linkact = 0x0, + Link1000 = 0x1, + Phyintmask = 25, + Anegcomp= 1<<10, + Linkchg = 1<<13, +}; + +static int burstcap = Hsburst, bulkdelay = Defbulkdly; + +static int +wr(Dev *d, int reg, int val) +{ + int ret; + + ret = usbcmd(d, Rh2d|Rvendor|Rdev, Writereg, 0, reg, + (uchar*)&val, sizeof(val)); + if(ret < 0) + deprint(2, "%s: wr(%x, %x): %r", argv0, reg, val); + return ret; +} + +static int +rr(Dev *d, int reg) +{ + int ret, rval; + + ret = usbcmd(d, Rd2h|Rvendor|Rdev, Readreg, 0, reg, + (uchar*)&rval, sizeof(rval)); + if(ret < 0){ + fprint(2, "%s: rr(%x): %r", argv0, reg); + return 0; + } + return rval; +} + +static int +miird(Dev *d, int idx) +{ + while(rr(d, MIIaddr) & MIIbusy) + ; + wr(d, MIIaddr, PHYinternal<<11 | idx<<6 | MIIread | MIIbusy); + while(rr(d, MIIaddr) & MIIbusy) + ; + return rr(d, MIIdata); +} + +static void +miiwr(Dev *d, int idx, int val) +{ + while(rr(d, MIIaddr) & MIIbusy) + ; + wr(d, MIIdata, val); + wr(d, MIIaddr, PHYinternal<<11 | idx<<6 | MIIwrite | MIIbusy); + while(rr(d, MIIaddr) & MIIbusy) + ; +} + +static int +eepromr(Dev *d, int off, uchar *buf, int len) +{ + int i, v; + + for(i = 0; i < E2pbusytime; i++) + if((rr(d, E2pcmd) & Busy) == 0) + break; + if(i == E2pbusytime) + return -1; + for(i = 0; i < len; i++){ + wr(d, E2pcmd, Busy|Read|(i+off)); + while((v = rr(d, E2pcmd) & (Busy|Timeout)) == Busy) + ; + if(v & Timeout) + return -1; + buf[i] = rr(d, E2pdata); + } + return 0; +} + +static void +phyinit(Dev *d) +{ + int i; + + miiwr(d, Bmcr, Bmcrreset|Anenable); + for(i = 0; i < Resettime/10; i++){ + if((miird(d, Bmcr) & Bmcrreset) == 0) + break; + sleep(10); + } + miiwr(d, Advertise, Adcsma|Adall|Adpause|Adpauseasym); + miiwr(d, Ctrl1000, Ad1000f); + miiwr(d, Phyintmask, 0); + miiwr(d, Ledmodes, (Linkact<cid != S78xx) + return -1; + d = ether->dev; + deprint(2, "%s: setting up LAN78XX\n", argv0); + deprint(2, "chip id/rev = %8.8ux\n", rr(d, Idrev)); + if(!doreset(d, Hwcfg, Lrst) || !doreset(d, Pmctrl, Phyrst)) + return -1; + for(i = 0; i < Resettime/10; i++){ + if(rr(d, Pmctrl) & Ready) + break; + sleep(10); + } + if((rr(d, Pmctrl) & Ready) == 0){ + deprint(2, "%s: device not ready after reset\n", argv0); + return -1; + } + if(getmac(d, ether->addr) < 0) + deprint(2, "%s: can't read etheraddr from EEPROM\n", argv0); + a = GET4(ether->addr); + wr(d, Addrl, a); + wr(d, Addrfiltl, a); + a = GET2(ether->addr+4); + wr(d, Addrh, a); + wr(d, Addrfilth, a|Afvalid); + deprint(2, "Address filter %8.8ux %8.8ux\n", rr(d, Addrfilth), rr(d, Addrfiltl)); + + wr(d, Usbcfg0, rr(d, Usbcfg0) | Bir); + if(Doburst){ + wr(d, Hwcfg, rr(d, Hwcfg)|Mef); + wr(d, Usbcfg0, rr(d, Usbcfg0)|Bce); + wr(d, Burstcap, burstcap); + wr(d, Bulkdelay, bulkdelay); + }else{ + wr(d, Hwcfg, rr(d, Hwcfg)&~Mef); + wr(d, Usbcfg0, rr(d, Usbcfg0)&~Bce); + wr(d, Burstcap, 0); + wr(d, Bulkdelay, 0); + } + wr(d, Rxfifo, (Rxfifosize-512)/512); + wr(d, Txfifo, (Txfifosize-512)/512); + wr(d, Intsts, ~0); + wr(d, Hwcfg, rr(d, Hwcfg) | Led0en|Led1en); + wr(d, Flow, 0); + wr(d, Fctflow, 0); + wr(d, Rfectl, (rr(d, Rfectl) & ~Rxcoe) | Ab|Dpf); /* TODO could offload checksums? */ + + phyinit(d); + + wr(d, Maccr, rr(d,Maccr)|Add|Asd); + + wr(d, Intepctl, rr(d, Intepctl)|Phyint); + wr(d, Mactx, Mactxen); + wr(d, Macrx, rr(d, Macrx) | Macfcs|Macrxen); + wr(d, Txfifoctl, Txen); + wr(d, Rxfifoctl, Rxen); + + return 0; +} + +static long +lan78xxbread(Ether *e, Buf *bp) +{ + uint hd; + int n, m; + Buf *rbp; + + rbp = e->aux; + if(rbp->ndata < 10){ + rbp->rp = rbp->data; + rbp->ndata = read(e->epin->dfd, rbp->rp, Doburst? burstcap*512: + Maxpkt); + if(rbp->ndata < 0) + return -1; + } + if(rbp->ndata < 10){ + werrstr("short frame"); + fprint(2, "lan78xx short frame %d bytes\n", rbp->ndata); + sleep(1000); + return 0; + } + hd = GET4(rbp->rp); + n = hd & 0x3FFF; + rbp->rp += 10; + rbp->ndata -= 10; + if(n < 6 || n > rbp->ndata){ + werrstr("frame length"); + fprint(2, "lan78xx length error packet %d buf %d\n", n, rbp->ndata); + rbp->ndata = 0; + return 0; + } + if(hd & Rxerror){ + fprint(2, "lan78xx rx error %8.8ux\n", hd); + n = 0; + }else{ + bp->rp = bp->data + Hdrsize; + memmove(bp->rp, rbp->rp, n); + } + bp->ndata = n; + rbp->rp += n; + rbp->ndata -= n; + if(rbp->ndata > 0){ + m = rbp->rp - rbp->data; + if(m&3){ + m = 4 - (m&3); + rbp->rp += m; + rbp->ndata -= m; + } + } + return n; +} + +static long +lan78xxbwrite(Ether *e, Buf *bp) +{ + int n; + + n = bp->ndata & 0xFFFFF; + bp->rp -= 8; + bp->ndata += 8; + PUT4(bp->rp, n | Txfcs); + PUT4(bp->rp+4, 0); + n = write(e->epout->dfd, bp->rp, bp->ndata); + if(n != bp->ndata) + deprint(2, "bwrite %d: %r\n", n); + return n; +} + +static int +lan78xxpromiscuous(Ether *e, int on) +{ + Dev *d; + int rxctl; + + d = e->dev; + rxctl = rr(d, Rfectl); + if(on) + rxctl |= Am|Au; + else + rxctl &= ~(Am|Au); + return wr(d, Rfectl, rxctl); +} + +static int +lan78xxmulticast(Ether *e, uchar *addr, int on) +{ + int rxctl; + Dev *d; + + USED(addr, on); + /* BUG: should write multicast filter */ + d = e->dev; + rxctl = rr(d, Rfectl); + if(e->nmcasts != 0) + rxctl |= Am; + else + rxctl &= ~Am; + deprint(2, "%s: lan78xxmulticast %d\n", argv0, e->nmcasts); + return wr(d, Rfectl, rxctl); +} + +static void +lan78xxfree(Ether *ether) +{ + free(ether->aux); + ether->aux = nil; +} + +int +lan78xxreset(Ether *ether) +{ + Cinfo *ip; + Dev *dev; + + dev = ether->dev; + for(ip = cinfo; ip->vid != 0; ip++) + if(ip->vid == dev->usb->vid && ip->did == dev->usb->did){ + ether->cid = ip->cid; + if(lan78xxinit(ether) < 0){ + deprint(2, "%s: lan78xx init failed: %r\n", argv0); + return -1; + } + deprint(2, "%s: lan78xx reset done\n", argv0); + ether->name = "lan78xx"; + if(Doburst){ + ether->bufsize = burstcap*512; + ether->aux = emallocz(sizeof(Buf) + + ether->bufsize - Maxpkt, 1); + }else{ + ether->bufsize = Maxpkt; + ether->aux = emallocz(sizeof(Buf), 1); + } + ether->free = lan78xxfree; + ether->bread = lan78xxbread; + ether->bwrite = lan78xxbwrite; + ether->promiscuous = lan78xxpromiscuous; + ether->multicast = lan78xxmulticast; + ether->mbps = 100; /* BUG */ + return 0; + } + return -1; +} --- /sys/src/cmd/usb/ether/smsc.c Thu Apr 5 09:53:13 2018 +++ /sys/src/cmd/usb/ether/smsc.c Thu Apr 5 09:53:11 2018 @@ -15,10 +15,9 @@ Resettime = 1000, E2pbusytime = 1000, Afcdefault = 0xF830A1, -// Hsburst = 37, /* from original linux driver */ - Hsburst = 8, + Hsburst = 24, Fsburst = 129, - Defbulkdly = 0x2000, + Defbulkdly = 1000, Ethp8021q = 0x8100, MACoffset = 1, @@ -102,6 +101,8 @@ Linkdown= 1<<4, }; +static int burstcap = Hsburst, bulkdelay = Defbulkdly; + static int wr(Dev *d, int reg, int val) { @@ -238,12 +239,13 @@ wr(d, Addrh, GET2(ether->addr+4)); if(Doburst){ wr(d, Hwcfg, (rr(d,Hwcfg)&~Rxdoff)|Bir|Mef|Bce); - wr(d, Burstcap, Hsburst); + wr(d, Burstcap, burstcap); + wr(d, Bulkdelay, bulkdelay); }else{ wr(d, Hwcfg, (rr(d,Hwcfg)&~(Rxdoff|Mef|Bce))|Bir); wr(d, Burstcap, 0); + wr(d, Bulkdelay, 0); } - wr(d, Bulkdelay, Defbulkdly); wr(d, Intsts, ~0); wr(d, Ledgpio, Ledspd|Ledlnk|Ledfdx); wr(d, Flow, 0); @@ -274,7 +276,7 @@ rbp = e->aux; if(rbp->ndata < 4){ rbp->rp = rbp->data; - rbp->ndata = read(e->epin->dfd, rbp->rp, Doburst? Hsburst*512: + rbp->ndata = read(e->epin->dfd, rbp->rp, Doburst? burstcap*512: Maxpkt); if(rbp->ndata < 0) return -1; @@ -285,20 +287,24 @@ return 0; } hd = GET4(rbp->rp); + rbp->rp += 4; + rbp->ndata -= 4; n = hd >> 16; - m = (n + 4 + 3) & ~3; - if(n < 6 || m > rbp->ndata){ + if(n < 6 || n > rbp->ndata){ werrstr("frame length"); fprint(2, "smsc length error packet %d buf %d\n", n, rbp->ndata); rbp->ndata = 0; return 0; } + m = n; + if(rbp->ndata - m < 4) + m = rbp->ndata; if(hd & Rxerror){ fprint(2, "smsc rx error %8.8ux\n", hd); n = 0; }else{ bp->rp = bp->data + Hdrsize; - memmove(bp->rp, rbp->rp+4, n); + memmove(bp->rp, rbp->rp, n); } bp->ndata = n; rbp->rp += m; @@ -381,7 +383,7 @@ deprint(2, "%s: smsc reset done\n", argv0); ether->name = "smsc"; if(Doburst){ - ether->bufsize = Hsburst*512; + ether->bufsize = burstcap*512; ether->aux = emallocz(sizeof(Buf) + ether->bufsize - Maxpkt, 1); }else{ --- /sys/src/cmd/usb/usbd/usbdb Thu Apr 5 09:53:16 2018 +++ /sys/src/cmd/usb/usbd/usbdb Thu Apr 5 09:53:15 2018 @@ -5,6 +5,7 @@ disk class=storage args= ether class=255 csp=0x00ffff vid=0x0b95 args= ether class=255 csp=0xff00ff vid=0x0424 did=0xec00 args= + ether class=255 csp=0xff00ff vid=0x0424 did=0x7800 args= ether class=255 csp=0x000602 vid=0x0bda did=0x8152 args= serial class=255 csp=0xffffff vid=0x9e88 did=0x9e8f args= serial class=255 csp=0xffffff vid=0x0403 args=