232 lines
4.3 KiB
C
232 lines
4.3 KiB
C
#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/etherif.h"
|
|
|
|
#include "msaturn.h"
|
|
|
|
enum{
|
|
Etcr = Saturn + 0x0c00,
|
|
Etsr = Saturn + 0x0c02,
|
|
Ercr = Saturn + 0x0c04,
|
|
Ersr = Saturn + 0x0c06,
|
|
Eisr = Saturn + 0x0d04,
|
|
Eimr = Saturn + 0x0d06,
|
|
Emacaddr0 = Saturn + 0x0e02,
|
|
Miicr = Saturn + 0x0f02,
|
|
Miiwdr = Saturn + 0x0f04,
|
|
Miirdr = Saturn + 0x0f06,
|
|
|
|
Ethermem = 0xf2c00000,
|
|
Etherfsize = 0x2000,
|
|
Nrx = 14,
|
|
Ntx = 2, // Nrx + Ntx must be 16
|
|
|
|
Ersr_rxfpmask = 0xf,
|
|
Ersr_rxevent = RBIT(0, ushort),
|
|
Etcr_txfpmask = 0xf,
|
|
Ercr_rxenab = RBIT(0, ushort),
|
|
Ercr_auienab = RBIT(2, ushort),
|
|
Etcr_txstart = RBIT(1, ushort),
|
|
Etcr_retries = 0xf<<8,
|
|
Ei_txecall = RBIT(0, ushort),
|
|
Ei_txretry = RBIT(2, ushort),
|
|
Ei_txdefer = RBIT(3, ushort),
|
|
Ei_txcrs = RBIT(4, ushort),
|
|
Ei_txdone = RBIT(5, ushort),
|
|
Ei_rxcrcerr = RBIT(8, ushort),
|
|
Ei_rxdrib = RBIT(9, ushort),
|
|
Ei_rxdone = RBIT(10, ushort),
|
|
Ei_rxshort = RBIT(11, ushort),
|
|
Ei_rxlong = RBIT(12, ushort),
|
|
|
|
Miicr_regshift = 6,
|
|
Miicr_read = RBIT(10, ushort),
|
|
Miicr_preambledis = RBIT(12, ushort),
|
|
Miicr_accack = RBIT(14, ushort),
|
|
Miicr_accbsy = RBIT(15, ushort),
|
|
};
|
|
|
|
typedef struct {
|
|
Lock;
|
|
int txbusy;
|
|
int txempty;
|
|
int txfull;
|
|
int ntx; /* number of entries in transmit ring */
|
|
int rxlast;
|
|
|
|
int active;
|
|
ulong interrupts; /* statistics */
|
|
ulong overflows;
|
|
} Ctlr;
|
|
|
|
static ushort*etcr=(ushort*)Etcr;
|
|
static ushort*etsr=(ushort*)Etsr;
|
|
static ushort*ercr=(ushort*)Ercr;
|
|
static ushort*ersr=(ushort*)Ersr;
|
|
static ushort*eimr=(ushort*)Eimr;
|
|
static ushort*eisr=(ushort*)Eisr;
|
|
static ushort*miicr=(ushort*)Miicr;
|
|
static ushort*miirdr=(ushort*)Miirdr;
|
|
|
|
static void
|
|
txfill(Ether*ether, Ctlr*ctlr)
|
|
{
|
|
int len;
|
|
Block *b;
|
|
ushort*dst;
|
|
|
|
while(ctlr->ntx<Ntx){
|
|
if((b=qget(ether->oq)) == nil)
|
|
break;
|
|
|
|
len = BLEN(b);
|
|
dst = (ushort*)(Ethermem+(ctlr->txempty+Nrx)*Etherfsize);
|
|
*dst = len;
|
|
memmove(&dst[1], b->rp, len);
|
|
ctlr->ntx++;
|
|
ctlr->txempty++;
|
|
if(ctlr->txempty==Ntx)
|
|
ctlr->txempty = 0;
|
|
freeb(b);
|
|
}
|
|
}
|
|
|
|
static void
|
|
txrestart(Ctlr*ctlr)
|
|
{
|
|
if(ctlr->ntx==0 || ctlr->txbusy)
|
|
return;
|
|
ctlr->txbusy = 1;
|
|
*etcr = Etcr_txstart|Etcr_retries|(ctlr->txfull+Nrx);
|
|
}
|
|
|
|
static void interrupt(Ureg*, void*);
|
|
|
|
static void
|
|
transmit(Ether*ether)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = ether->ctlr;
|
|
ilock(ctlr);
|
|
txfill(ether, ctlr);
|
|
txrestart(ctlr);
|
|
iunlock(ctlr);
|
|
|
|
}
|
|
|
|
static void
|
|
interrupt(Ureg*, void*arg)
|
|
{
|
|
Ctlr*ctlr;
|
|
Ether*ether = arg;
|
|
Etherpkt*pkt;
|
|
ushort ie;
|
|
int rx, len;
|
|
Block *b;
|
|
|
|
ctlr = ether->ctlr;
|
|
if(!ctlr->active)
|
|
return; /* not ours */
|
|
ctlr->interrupts++;
|
|
|
|
ilock(ctlr);
|
|
ie = *eisr;
|
|
*eisr = ie;
|
|
intack();
|
|
|
|
if(ie==0)
|
|
iprint("interrupt: no interrupt source?\n");
|
|
|
|
if(ie&Ei_txdone){
|
|
if((*etcr&Etcr_txstart)==0){
|
|
if(ctlr->txbusy){
|
|
ctlr->txbusy = 0;
|
|
ctlr->ntx--;
|
|
ctlr->txfull++;
|
|
if(ctlr->txfull==Ntx)
|
|
ctlr->txfull = 0;
|
|
}
|
|
txrestart(ctlr);
|
|
txfill(ether, ctlr);
|
|
txrestart(ctlr);
|
|
}
|
|
else
|
|
iprint("interrupt: bogus tx interrupt\n");
|
|
ie &= ~Ei_txdone;
|
|
}
|
|
|
|
if(ie&Ei_rxdone){
|
|
rx=*ersr&Ersr_rxfpmask;
|
|
while(ctlr->rxlast!=rx){
|
|
|
|
ctlr->rxlast++;
|
|
if(ctlr->rxlast >= Nrx)
|
|
ctlr->rxlast = 0;
|
|
|
|
pkt = (Etherpkt*)(Ethermem+ctlr->rxlast*Etherfsize);
|
|
len = *(ushort*)pkt;
|
|
if((b = iallocb(len+sizeof(ushort))) != nil){
|
|
memmove(b->wp, pkt, len+sizeof(ushort));
|
|
b->rp += sizeof(ushort);
|
|
b->wp = b->rp + len;
|
|
etheriq(ether, b);
|
|
}else
|
|
ether->soverflows++;
|
|
rx=*ersr&Ersr_rxfpmask;
|
|
}
|
|
ie &= ~Ei_rxdone;
|
|
}
|
|
|
|
if(ie&Ei_txretry){
|
|
iprint("ethersaturn: txretry!\n");
|
|
ie &= ~Ei_txretry;
|
|
ctlr->txbusy = 0;
|
|
txrestart(ctlr);
|
|
}
|
|
|
|
ie &= ~Ei_txcrs;
|
|
if(ie)
|
|
iprint("interrupt: unhandled interrupts %.4uX\n", ie);
|
|
iunlock(ctlr);
|
|
}
|
|
|
|
static int
|
|
reset(Ether* ether)
|
|
{
|
|
Ctlr*ctlr;
|
|
|
|
*ercr = 0;
|
|
ctlr = malloc(sizeof(*ctlr));
|
|
memset(ctlr, 0, sizeof(*ctlr));
|
|
ctlr->active = 1;
|
|
|
|
ether->ctlr = ctlr;
|
|
ether->transmit = transmit;
|
|
ether->irq = Vecether;
|
|
ether->arg = ether;
|
|
memmove(ether->ea, (ushort*)Emacaddr0, Eaddrlen);
|
|
|
|
*ercr = Ercr_rxenab|Ercr_auienab|(Nrx-1);
|
|
*eimr = Ei_rxdone|Ei_txretry|Ei_txdone;
|
|
|
|
iprint("reset: ercr %.4uX\n", *ercr);
|
|
|
|
intrenable(ether->irq, interrupt, ether, ether->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ethersaturnlink(void)
|
|
{
|
|
addethercard("saturn", reset);
|
|
}
|
|
|