/* * FCCn ethernet */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "imm.h" #include "../port/error.h" #include "../port/netif.h" #include "etherif.h" #include "../ppc/ethermii.h" #define DBG 1 enum { Nrdre = 128, /* receive descriptor ring entries */ Ntdre = 128, /* transmit descriptor ring entries */ Rbsize = ETHERMAXTU+4, /* ring buffer size (+4 for CRC) */ Bufsize = Rbsize+CACHELINESZ, /* extra room for alignment */ }; enum { /* ether-specific Rx BD bits */ RxMiss= SBIT(7), RxeLG= SBIT(10), RxeNO= SBIT(11), RxeSH= SBIT(12), RxeCR= SBIT(13), RxeOV= SBIT(14), RxeCL= SBIT(15), RxError= (RxeLG|RxeNO|RxeSH|RxeCR|RxeOV|RxeCL), /* various error flags */ /* ether-specific Tx BD bits */ TxPad= SBIT(1), /* pad short frames */ TxTC= SBIT(5), /* transmit CRC */ TxeDEF= SBIT(6), TxeHB= SBIT(7), TxeLC= SBIT(8), TxeRL= SBIT(9), TxeUN= SBIT(14), TxeCSL= SBIT(15), /* psmr */ CRCE= BIT(24), /* Ethernet CRC */ FCE= BIT(10), /* flow control */ PRO= BIT(9), /* promiscuous mode */ FDE= BIT(5), /* full duplex ethernet */ LPB= BIT(3), /* local protect bit */ /* gfmr */ ENET= 0xc, /* ethernet mode */ ENT= BIT(27), ENR= BIT(26), TCI= BIT(2), /* FCC function code register */ GBL= 0x20, BO= 0x18, EB= 0x10, /* Motorola byte order */ TC2= 0x04, DTB= 0x02, BDB= 0x01, /* FCC Event/Mask bits */ GRA= SBIT(8), RXC= SBIT(9), TXC= SBIT(10), TXE= SBIT(11), RXF= SBIT(12), BSY= SBIT(13), TXB= SBIT(14), RXB= SBIT(15), }; enum { /* Mcr */ MDIread = 0x60020000, /* read opcode */ MDIwrite = 0x50020000, /* write opcode */ }; typedef struct Etherparam Etherparam; struct Etherparam { /*0x00*/ FCCparam; /*0x3c*/ ulong stat_buf; /*0x40*/ ulong cam_ptr; /*0x44*/ ulong cmask; /*0x48*/ ulong cpres; /*0x4c*/ ulong crcec; /*0x50*/ ulong alec; /*0x54*/ ulong disfc; /*0x58*/ ushort retlim; /*0x5a*/ ushort retcnt; /*0x5c*/ ushort p_per; /*0x5e*/ ushort boff_cnt; /*0x60*/ ulong gaddr[2]; /*0x68*/ ushort tfcstat; /*0x6a*/ ushort tfclen; /*0x6c*/ ulong tfcptr; /*0x70*/ ushort mflr; /*0x72*/ ushort paddr[3]; /*0x78*/ ushort ibd_cnt; /*0x7a*/ ushort ibd_start; /*0x7c*/ ushort ibd_end; /*0x7e*/ ushort tx_len; /*0x80*/ uchar ibd_base[32]; /*0xa0*/ ulong iaddr[2]; /*0xa8*/ ushort minflr; /*0xaa*/ ushort taddr[3]; /*0xb0*/ ushort padptr; /*0xb2*/ ushort Rsvdb2; /*0xb4*/ ushort cf_range; /*0xb6*/ ushort max_b; /*0xb8*/ ushort maxd1; /*0xba*/ ushort maxd2; /*0xbc*/ ushort maxd; /*0xbe*/ ushort dma_cnt; /*0xc0*/ ulong octc; /*0xc4*/ ulong colc; /*0xc8*/ ulong broc; /*0xcc*/ ulong mulc; /*0xd0*/ ulong uspc; /*0xd4*/ ulong frgc; /*0xd8*/ ulong ospc; /*0xdc*/ ulong jbrc; /*0xe0*/ ulong p64c; /*0xe4*/ ulong p65c; /*0xe8*/ ulong p128c; /*0xec*/ ulong p256c; /*0xf0*/ ulong p512c; /*0xf4*/ ulong p1024c; /*0xf8*/ ulong cam_buf; /*0xfc*/ ulong Rsvdfc; /*0x100*/ }; typedef struct Ctlr Ctlr; struct Ctlr { Lock; int fccid; int port; ulong pmdio; ulong pmdck; int init; int active; int duplex; /* 1 == full */ FCC* fcc; Ring; Block* rcvbufs[Nrdre]; Mii* mii; Timer; ulong interrupts; /* statistics */ ulong deferred; ulong heartbeat; ulong latecoll; ulong retrylim; ulong underrun; ulong overrun; ulong carrierlost; ulong retrycount; }; static int fccirq[] = {0x20, 0x21, 0x22}; static int fccid[] = {FCC1ID, FCC2ID, FCC3ID}; #ifdef DBG ulong fccrhisto[16]; ulong fccthisto[16]; ulong fccrthisto[16]; ulong fcctrhisto[16]; ulong ehisto[0x80]; #endif static int fccmiimir(Mii*, int, int); static int fccmiimiw(Mii*, int, int, int); static void fccltimer(Ureg*, Timer*); static void attach(Ether *ether) { Ctlr *ctlr; ctlr = ether->ctlr; ilock(ctlr); ctlr->active = 1; ctlr->fcc->gfmr |= ENR|ENT; iunlock(ctlr); ctlr->tmode = Tperiodic; ctlr->tf = fccltimer; ctlr->ta = ether; ctlr->tns = 5000000000LL; /* 5 seconds */ timeradd(ctlr); } static void closed(Ether *ether) { Ctlr *ctlr; ctlr = ether->ctlr; ilock(ctlr); ctlr->active = 0; ctlr->fcc->gfmr &= ~(ENR|ENT); iunlock(ctlr); print("Ether closed\n"); } static void promiscuous(void* arg, int on) { Ether *ether; Ctlr *ctlr; ether = (Ether*)arg; ctlr = ether->ctlr; ilock(ctlr); if(on || ether->nmaddr) ctlr->fcc->fpsmr |= PRO; else ctlr->fcc->fpsmr &= ~PRO; iunlock(ctlr); } static void multicast(void* arg, uchar *addr, int on) { Ether *ether; Ctlr *ctlr; USED(addr, on); /* if on, could SetGroupAddress; if !on, it's hard */ ether = (Ether*)arg; ctlr = ether->ctlr; ilock(ctlr); if(ether->prom || ether->nmaddr) ctlr->fcc->fpsmr |= PRO; else ctlr->fcc->fpsmr &= ~PRO; iunlock(ctlr); } static void txstart(Ether *ether) { int len; Ctlr *ctlr; Block *b; BD *dre; ctlr = ether->ctlr; if(ctlr->init) return; while(ctlr->ntq < Ntdre-1){ b = qget(ether->oq); if(b == 0) break; dre = &ctlr->tdr[ctlr->tdrh]; dczap(dre, sizeof(BD)); if(dre->status & BDReady) panic("ether: txstart"); /* * Give ownership of the descriptor to the chip, increment the * software ring descriptor pointer and tell the chip to poll. */ len = BLEN(b); if(ctlr->txb[ctlr->tdrh] != nil) panic("fcc/ether: txstart"); ctlr->txb[ctlr->tdrh] = b; if((ulong)b->rp&1) panic("fcc/ether: txstart align"); /* TO DO: ensure alignment */ dre->addr = PADDR(b->rp); dre->length = len; dcflush(b->rp, len); dcflush(dre, sizeof(BD)); dre->status = (dre->status & BDWrap) | BDReady|TxPad|BDInt|BDLast|TxTC; dcflush(dre, sizeof(BD)); /* ctlr->fcc->ftodr = 1<<15; /* transmit now; Don't do this according to errata */ ctlr->ntq++; ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre); } } static void transmit(Ether* ether) { Ctlr *ctlr; ctlr = ether->ctlr; ilock(ctlr); txstart(ether); iunlock(ctlr); } static void interrupt(Ureg*, void *arg) { int len, status, rcvd, xmtd, restart; ushort events; Ctlr *ctlr; BD *dre; Block *b, *nb; Ether *ether = arg; ctlr = ether->ctlr; if(!ctlr->active) return; /* not ours */ /* * Acknowledge all interrupts and whine about those that shouldn't * happen. */ events = ctlr->fcc->fcce; ctlr->fcc->fcce = events; /* clear events */ #ifdef DBG ehisto[events & 0x7f]++; #endif ctlr->interrupts++; if(events & BSY) ctlr->overrun++; if(events & TXE) ether->oerrs++; #ifdef DBG rcvd = xmtd = 0; #endif /* * Receiver interrupt: run round the descriptor ring logging * errors and passing valid receive data up to the higher levels * until we encounter a descriptor still owned by the chip. */ if(events & RXF){ dre = &ctlr->rdr[ctlr->rdrx]; dczap(dre, sizeof(BD)); while(((status = dre->status) & BDEmpty) == 0){ rcvd++; if(status & RxError || (status & (BDFirst|BDLast)) != (BDFirst|BDLast)){ if(status & (RxeLG|RxeSH)) ether->buffs++; if(status & RxeNO) ether->frames++; if(status & RxeCR) ether->crcs++; if(status & RxeOV) ether->overflows++; print("eth rx: %ux\n", status); }else{ /* * We have a packet. Read it in. */ len = dre->length-4; b = ctlr->rcvbufs[ctlr->rdrx]; assert(dre->addr == PADDR(b->rp)); dczap(b->rp, len); if(nb = iallocb(Bufsize)){ b->wp += len; etheriq(ether, b, 1); b = nb; b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1)); b->wp = b->rp; ctlr->rcvbufs[ctlr->rdrx] = b; ctlr->rdr[ctlr->rdrx].addr = PADDR(b->wp); }else ether->soverflows++; } /* * Finished with this descriptor, reinitialise it, * give it back to the chip, then on to the next... */ dre->length = 0; dre->status = (status & BDWrap) | BDEmpty | BDInt; dcflush(dre, sizeof(BD)); ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre); dre = &ctlr->rdr[ctlr->rdrx]; dczap(dre, sizeof(BD)); } } /* * Transmitter interrupt: handle anything queued for a free descriptor. */ if(events & (TXB|TXE)){ ilock(ctlr); restart = 0; while(ctlr->ntq){ dre = &ctlr->tdr[ctlr->tdri]; dczap(dre, sizeof(BD)); status = dre->status; if(status & BDReady) break; if(status & TxeDEF) ctlr->deferred++; if(status & TxeHB) ctlr->heartbeat++; if(status & TxeLC) ctlr->latecoll++; if(status & TxeRL) ctlr->retrylim++; if(status & TxeUN) ctlr->underrun++; if(status & TxeCSL) ctlr->carrierlost++; if(status & (TxeLC|TxeRL|TxeUN)) restart = 1; ctlr->retrycount += (status>>2)&0xF; b = ctlr->txb[ctlr->tdri]; if(b == nil) panic("fcce/interrupt: bufp"); ctlr->txb[ctlr->tdri] = nil; freeb(b); ctlr->ntq--; ctlr->tdri = NEXT(ctlr->tdri, Ntdre); xmtd++; } if(restart){ ctlr->fcc->gfmr &= ~ENT; delay(10); ctlr->fcc->gfmr |= ENT; cpmop(RestartTx, ctlr->fccid, 0xc); } txstart(ether); iunlock(ctlr); } #ifdef DBG if(rcvd >= nelem(fccrhisto)) rcvd = nelem(fccrhisto) - 1; if(xmtd >= nelem(fccthisto)) xmtd = nelem(fccthisto) - 1; if(rcvd) fcctrhisto[xmtd]++; else fccthisto[xmtd]++; if(xmtd) fccrthisto[rcvd]++; else fccrhisto[rcvd]++; #endif } static long ifstat(Ether* ether, void* a, long n, ulong offset) { char *p; int len, i, r; Ctlr *ctlr; MiiPhy *phy; if(n == 0) return 0; ctlr = ether->ctlr; p = malloc(READSTR); len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts); len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost); len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat); len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim); len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount); len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll); len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun); len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun); len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred); miistatus(ctlr->mii); phy = ctlr->mii->curphy; len += snprint(p+len, READSTR-len, "phy: link=%d, tfc=%d, rfc=%d, speed=%d, fd=%d\n", phy->link, phy->tfc, phy->rfc, phy->speed, phy->fd); #ifdef DBG if(ctlr->mii != nil && ctlr->mii->curphy != nil){ len += snprint(p+len, READSTR, "phy: "); for(i = 0; i < NMiiPhyr; i++){ if(i && ((i & 0x07) == 0)) len += snprint(p+len, READSTR-len, "\n "); r = miimir(ctlr->mii, i); len += snprint(p+len, READSTR-len, " %4.4uX", r); } snprint(p+len, READSTR-len, "\n"); } #endif snprint(p+len, READSTR-len, "\n"); n = readstr(offset, a, n, p); free(p); return n; } IMM* imm; /* * This follows the MPC8260 user guide: section28.9's initialisation sequence. */ static int fccsetup(Ctlr *ctlr, FCC *fcc, uchar *ea) { int i; Etherparam *p; MiiPhy *phy; /* Turn Ethernet off */ fcc->gfmr &= ~(ENR | ENT); ioplock(); switch(ctlr->port) { default: iopunlock(); return -1; case 0: /* Step 1 (Section 28.9), write the parallel ports */ ctlr->pmdio = 0x01000000; ctlr->pmdck = 0x08000000; imm->port[0].pdir &= ~A1dir0; imm->port[0].pdir |= A1dir1; imm->port[0].psor &= ~A1psor0; imm->port[0].psor |= A1psor1; imm->port[0].ppar |= (A1dir0 | A1dir1); /* Step 2, Port C clocks */ imm->port[2].psor &= ~0x00000c00; imm->port[2].pdir &= ~0x00000c00; imm->port[2].ppar |= 0x00000c00; imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck); imm->port[3].podr |= ctlr->pmdio; imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck); imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck); eieio(); /* Step 3, Serial Interface clock routing */ imm->cmxfcr &= ~0xff000000; /* Clock mask */ imm->cmxfcr |= 0x37000000; /* Clock route */ break; case 1: /* Step 1 (Section 28.9), write the parallel ports */ ctlr->pmdio = 0x00400000; ctlr->pmdck = 0x00200000; imm->port[1].pdir &= ~B2dir0; imm->port[1].pdir |= B2dir1; imm->port[1].psor &= ~B2psor0; imm->port[1].psor |= B2psor1; imm->port[1].ppar |= (B2dir0 | B2dir1); /* Step 2, Port C clocks */ imm->port[2].psor &= ~0x00003000; imm->port[2].pdir &= ~0x00003000; imm->port[2].ppar |= 0x00003000; imm->port[2].pdat |= (ctlr->pmdio | ctlr->pmdck); imm->port[2].podr |= ctlr->pmdio; imm->port[2].pdir |= (ctlr->pmdio | ctlr->pmdck); imm->port[2].ppar &= ~(ctlr->pmdio | ctlr->pmdck); eieio(); /* Step 3, Serial Interface clock routing */ imm->cmxfcr &= ~0x00ff0000; imm->cmxfcr |= 0x00250000; break; case 2: /* Step 1 (Section 28.9), write the parallel ports */ imm->port[1].pdir &= ~B3dir0; imm->port[1].pdir |= B3dir1; imm->port[1].psor &= ~B3psor0; imm->port[1].psor |= B3psor1; imm->port[1].ppar |= (B3dir0 | B3dir1); /* Step 2, Port C clocks */ imm->port[2].psor &= ~0x0000c000; imm->port[2].pdir &= ~0x0000c000; imm->port[2].ppar |= 0x0000c000; imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck); imm->port[3].podr |= ctlr->pmdio; imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck); imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck); eieio(); /* Step 3, Serial Interface clock routing */ imm->cmxfcr &= ~0x0000ff00; imm->cmxfcr |= 0x00003700; break; } iopunlock(); p = (Etherparam*)(m->immr->prmfcc + ctlr->port); memset(p, 0, sizeof(Etherparam)); /* Step 4 */ fcc->gfmr |= ENET; /* Step 5 */ fcc->fpsmr = CRCE | FDE | LPB; /* full duplex operation */ ctlr->duplex = ~0; /* Step 6 */ fcc->fdsr = 0xd555; /* Step 7, initialize parameter ram */ p->rbase = PADDR(ctlr->rdr); p->tbase = PADDR(ctlr->tdr); p->rstate = (GBL | EB) << 24; p->tstate = (GBL | EB) << 24; p->cmask = 0xdebb20e3; p->cpres = 0xffffffff; p->retlim = 15; /* retry limit */ p->mrblr = (Rbsize+0x1f)&~0x1f; /* multiple of 32 */ p->mflr = Rbsize; p->minflr = ETHERMINTU; p->maxd1 = (Rbsize+7) & ~7; p->maxd2 = (Rbsize+7) & ~7; for(i=0; ipaddr[2-i/2] = (ea[i+1]<<8)|ea[i]; /* Step 7, initialize parameter ram, configuration-dependent values */ p->riptr = m->immr->fccextra[ctlr->port].ri - (uchar*)IMMR; p->tiptr = m->immr->fccextra[ctlr->port].ti - (uchar*)IMMR; p->padptr = m->immr->fccextra[ctlr->port].pad - (uchar*)IMMR; memset(m->immr->fccextra[ctlr->port].pad, 0x88, 0x20); /* Step 8, clear out events */ fcc->fcce = ~0; /* Step 9, Interrupt enable */ fcc->fccm = TXE | RXF | TXB; /* Step 10, Configure interrupt priority (not done here) */ /* Step 11, Clear out current events */ /* Step 12, Enable interrupts to the CP interrupt controller */ /* Step 13, Issue the Init Tx and Rx command, specifying 0xc for ethernet*/ cpmop(InitRxTx, fccid[ctlr->port], 0xc); /* Step 14, Link management */ if((ctlr->mii = malloc(sizeof(Mii))) == nil) return -1; ctlr->mii->mir = fccmiimir; ctlr->mii->miw = fccmiimiw; ctlr->mii->ctlr = ctlr; if(mii(ctlr->mii, ~0) == 0 || (phy = ctlr->mii->curphy) == nil){ free(ctlr->mii); ctlr->mii = nil; return -1; } miiane(ctlr->mii, ~0, ~0, ~0); #ifdef DBG print("oui=%X, phyno=%d, ", phy->oui, phy->phyno); print("anar=%ux, ", phy->anar); print("fc=%ux, ", phy->fc); print("mscr=%ux, ", phy->mscr); print("link=%ux, ", phy->link); print("speed=%ux, ", phy->speed); print("fd=%ux, ", phy->fd); print("rfc=%ux, ", phy->rfc); print("tfc=%ux\n", phy->tfc); #endif /* Step 15, Enable ethernet: done at attach time */ return 0; } static int reset(Ether* ether) { uchar ea[Eaddrlen]; Ctlr *ctlr; FCC *fcc; Block *b; int i; if(m->cpuhz < 24000000){ print("%s ether: system speed must be >= 24MHz for ether use\n", ether->type); return -1; } if(ether->port > 3){ print("%s ether: no FCC port %ld\n", ether->type, ether->port); return -1; } ether->irq = fccirq[ether->port]; ether->tbdf = BusPPC; fcc = imm->fcc + ether->port; ctlr = malloc(sizeof(*ctlr)); ether->ctlr = ctlr; memset(ctlr, 0, sizeof(*ctlr)); ctlr->fcc = fcc; ctlr->port = ether->port; ctlr->fccid = fccid[ether->port]; /* Ioringinit will allocate the buffer descriptors in normal memory * and NOT in Dual-Ported Ram, as prescribed by the MPC8260 * PowerQUICC II manual (Section 28.6). When they are allocated * in DPram and the Dcache is enabled, the processor will hang */ if(ioringinit(ctlr, Nrdre, Ntdre, 0) < 0) panic("etherfcc init"); for(i = 0; i < Nrdre; i++){ b = iallocb(Bufsize); b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1)); b->wp = b->rp; ctlr->rcvbufs[i] = b; ctlr->rdr[i].addr = PADDR(b->wp); } fccsetup(ctlr, fcc, ether->ea); ether->mbps = 100; /* TO DO: could be 10mbps */ ether->attach = attach; ether->transmit = transmit; ether->interrupt = interrupt; ether->ifstat = ifstat; ether->arg = ether; ether->promiscuous = promiscuous; ether->multicast = multicast; /* * Until we know where to find it, insist that the plan9.ini * entry holds the Ethernet address. */ memset(ea, 0, Eaddrlen); if(memcmp(ea, ether->ea, Eaddrlen) == 0){ print("no ether address"); return -1; } return 0; } void etherfcclink(void) { addethercard("fcc", reset); } static void nanodelay(void) { static int count; int i; for(i = 0; i < 500; i++) count++; return; } static void miiwriteloop(Ctlr *ctlr, Port *port, int cnt, ulong cmd) { int i; for(i = 0; i < cnt; i++){ port->pdat &= ~ctlr->pmdck; if(cmd & BIT(i)) port->pdat |= ctlr->pmdio; else port->pdat &= ~ctlr->pmdio; nanodelay(); port->pdat |= ctlr->pmdck; nanodelay(); } } static int fccmiimiw(Mii *mii, int pa, int ra, int data) { int x; Port *port; ulong cmd; Ctlr *ctlr; /* * MII Management Interface Write. */ ctlr = mii->ctlr; port = imm->port + 3; cmd = MDIwrite | (pa<<(5+2+16))| (ra<<(2+16)) | (data & 0xffff); x = splhi(); port->pdir |= (ctlr->pmdio|ctlr->pmdck); nanodelay(); miiwriteloop(ctlr, port, 32, ~0); miiwriteloop(ctlr, port, 32, cmd); port->pdir |= (ctlr->pmdio|ctlr->pmdck); nanodelay(); miiwriteloop(ctlr, port, 32, ~0); splx(x); return 1; } static int fccmiimir(Mii *mii, int pa, int ra) { int data, i, x; Port *port; ulong cmd; Ctlr *ctlr; ctlr = mii->ctlr; port = imm->port + 3; cmd = MDIread | pa<<(5+2+16) | ra<<(2+16); x = splhi(); port->pdir |= (ctlr->pmdio|ctlr->pmdck); nanodelay(); miiwriteloop(ctlr, port, 32, ~0); /* Clock out the first 14 MS bits of the command */ miiwriteloop(ctlr, port, 14, cmd); /* Turn-around */ port->pdat &= ~ctlr->pmdck; port->pdir &= ~ctlr->pmdio; nanodelay(); /* For read, clock in 18 bits, use 16 */ data = 0; for(i=0; i<18; i++){ data <<= 1; if(port->pdat & ctlr->pmdio) data |= 1; port->pdat |= ctlr->pmdck; nanodelay(); port->pdat &= ~ctlr->pmdck; nanodelay(); } port->pdir |= (ctlr->pmdio|ctlr->pmdck); nanodelay(); miiwriteloop(ctlr, port, 32, ~0); splx(x); return data & 0xffff; } static void fccltimer(Ureg*, Timer *t) { Ether *ether; Ctlr *ctlr; MiiPhy *phy; ulong gfmr; ether = t->ta; ctlr = ether->ctlr; if(ctlr->mii == nil || ctlr->mii->curphy == nil) return; phy = ctlr->mii->curphy; if(miistatus(ctlr->mii) < 0){ print("miistatus failed\n"); return; } if(phy->link == 0){ print("link lost\n"); return; } ether->mbps = phy->speed; if(phy->fd != ctlr->duplex) print("set duplex\n"); ilock(ctlr); gfmr = ctlr->fcc->gfmr; if(phy->fd != ctlr->duplex){ ctlr->fcc->gfmr &= ~(ENR|ENT); if(phy->fd) ctlr->fcc->fpsmr |= FDE | LPB; /* full duplex operation */ else ctlr->fcc->fpsmr &= ~(FDE | LPB); /* half duplex operation */ ctlr->duplex = phy->fd; } ctlr->fcc->gfmr = gfmr; iunlock(ctlr); }