/*
* RTEMS driver for WD800x
*
* Based on the 68360 Network Driver by:
* W. Eric Norum
* Saskatchewan Accelerator Laboratory
* University of Saskatchewan
* Saskatoon, Saskatchewan, CANADA
* eric@skatter.usask.ca
*
* $Id$
*/
#include <bsp.h>
#include <wd80x3.h>
#include <stdio.h>
#include <stdarg.h>
#include <rtems/error.h>
#include <rtems/rtems_bsdnet.h>
#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <irq.h>
#define ET_MINLEN 60 /* minimum message length */
/*
* Number of WDs supported by this driver
*/
#define NWDDRIVER 1
/*
* Default number of buffer descriptors set aside for this driver.
* The number of transmit buffer descriptors has to be quite large
* since a single frame often uses four or more buffer descriptors.
*/
#define RX_BUF_COUNT 15
#define TX_BUF_COUNT 4
#define TX_BD_PER_BUF 4
/*
* RTEMS event used by interrupt handler to signal driver tasks.
* This must not be any of the events used by the network task synchronization.
*/
#define INTERRUPT_EVENT RTEMS_EVENT_1
/*
* RTEMS event used to start transmit daemon.
* This must not be the same as INTERRUPT_EVENT.
*/
#define START_TRANSMIT_EVENT RTEMS_EVENT_2
/*
* Receive buffer size -- Allow for a full ethernet packet including CRC
*/
#define RBUF_SIZE 1520
#if (MCLBYTES < RBUF_SIZE)
# error "Driver must have MCLBYTES > RBUF_SIZE"
#endif
/*
* Per-device data
*/
struct wd_softc {
struct arpcom arpcom;
rtems_irq_connect_data irqInfo;
struct mbuf **rxMbuf;
struct mbuf **txMbuf;
int acceptBroadcast;
int rxBdCount;
int txBdCount;
int txBdHead;
int txBdTail;
int txBdActiveCount;
rtems_id rxDaemonTid;
rtems_id txDaemonTid;
unsigned int port;
unsigned char *base;
unsigned long bpar;
/*
* Statistics
*/
unsigned long rxInterrupts;
unsigned long rxNotFirst;
unsigned long rxNotLast;
unsigned long rxGiant;
unsigned long rxNonOctet;
unsigned long rxRunt;
unsigned long rxBadCRC;
unsigned long rxOverrun;
unsigned long rxCollision;
unsigned long txInterrupts;
unsigned long txDeferred;
unsigned long txHeartbeat;
unsigned long txLateCollision;
unsigned long txRetryLimit;
unsigned long txUnderrun;
unsigned long txLostCarrier;
unsigned long txRawWait;
};
#define RO 0x10
#define SHATOT (8*1024) /* size of shared memory */
#define SHAPAGE 256 /* shared memory information */
#define MAXSIZ 1536 /*(MAXBUF - MESSH_SZ)*/
#define OUTPAGE ((SHATOT-(MAXSIZ+SHAPAGE-1))/SHAPAGE)
static volatile unsigned long overrun;
static volatile unsigned long resend;
static struct wd_softc wd_softc[NWDDRIVER];
/*
* WD interrupt handler
*/
static void
wd8003Enet_interrupt_handler (void)
{
unsigned int tport;
unsigned char status, status2;
tport = wd_softc[0].port ;
/*
* Read status
*/
inport_byte(tport+ISR, status);
outport_byte(tport+IMR, 0x00);
/*
* Ring overwrite
*/
if (status & MSK_OVW){
outport_byte(tport+CMDR, MSK_STP + MSK_RD2); /* stop 8390 */
Wait_X_ms(2);
outport_byte(tport+RBCR0, 0); /* clear byte count */
outport_byte(tport+RBCR1, 0);
inport_byte(tport+ISR, status2);
status |= (status2 & (MSK_PTX+MSK_TXE)) ; /* TX status */
outport_byte(tport+TCR, MSK_LOOP); /* loopback mode */
outport_byte(tport+CMDR, MSK_STA + MSK_RD2); /* start */
overrun = 1 ;
if ((status & (MSK_PTX+MSK_TXE)) == 0)
resend = 1;
}
/*
* Frame received?
*/
if (status & (MSK_PRX+MSK_RXE)) {
outport_byte(tport+ISR, status & (MSK_PRX+MSK_RXE));
wd_softc[0].rxInterrupts++;
rtems_event_send (wd_softc[0].rxDaemonTid, INTERRUPT_EVENT);
}
}
static void nopOn(const rtems_irq_connect_data* notUsed)
{
/*
* code should be moved from wd8003Enet_initialize_hardware
* to this location
*/
}
static int wdIsOn(const rtems_irq_connect_data* irq)
{
return BSP_irq_enabled_at_i8259s (irq->name);
}
/*
* Initialize the ethernet hardware
*/
static void
wd8003Enet_initialize_hardware (struct wd_softc *sc)
{
int i1, ultra;
char cc1, cc2;
unsigned char temp;
rtems_status_code st;
unsigned int tport;
unsigned char *hwaddr;
tport = sc->port;
/* address from board ROM */
inport_byte(tport+0x04, temp);
outport_byte(tport+0x04, temp & 0x7f);
hwaddr = sc->arpcom.ac_enaddr;
for (i1=cc2=0; i1<8; i1++) {
inport_byte(tport + ADDROM + i1, cc1);
cc2 += cc1;
if (i1 < 6)
hwaddr[i1] = cc1;
}
inport_byte(tport+0x04, temp);
outport_byte(tport+0x04, temp | 0x80); /* alternate registers */
outport_byte(tport+W83CREG, MSK_RESET); /* reset board, set buffer */
outport_byte(tport+W83CREG, 0);
outport_byte(tport+W83CREG, MSK_ENASH + (int)((sc->bpar>>13)&0x3f));
outport_byte(tport+CMDR, MSK_PG0 + MSK_RD2);
cc1 = MSK_BMS + MSK_FT10; /* configure 8 or 16 bits */
inport_byte(tport+0x07, temp) ;
ultra = ((temp & 0xf0) == 0x20 || (temp & 0xf0) == 0x40);
if (ultra)
cc1 = MSK_WTS + MSK_BMS + MSK_FT10;
outport_byte(tport+DCR, cc1);
outport_byte(tport+RBCR0, 0);
outport_byte(tport+RBCR1, 0);
outport_byte(tport+RCR, MSK_MON); /* disable the rxer */
outport_byte(tport+TCR, 0); /* normal operation */
outport_byte(tport+PSTOP, OUTPAGE); /* init PSTOP */
outport_byte(tport+PSTART, 0); /* init PSTART */
outport_byte(tport+BNRY, -1); /* init BNRY */
outport_byte(tport+ISR, -1); /* clear IR's */
outport_byte(tport+IMR, 0x15); /* enable interrupt */
outport_byte(tport+CMDR, MSK_PG1 + MSK_RD2);
for (i1=0; i1<6; i1++) /* initial physical addr */
outport_byte(tport+PAR+i1, hwaddr[i1]);
for (i1=0; i1<MARsize; i1++) /* clear multicast */
outport_byte(tport+MAR+i1, 0);
outport_byte(tport+CURR, 0); /* init current packet */
outport_byte(tport+CMDR, MSK_PG0 + MSK_RD2);
outport_byte(tport+CMDR, MSK_STA + MSK_RD2); /* put 8390 on line */
outport_byte(tport+RCR, MSK_AB); /* MSK_AB accept broadcast */
if (ultra) {
inport_byte(tport+0x0c, temp);
outport_byte(tport+0x0c, temp | 0x80);
outport_byte(tport+0x05, 0x80);
outport_byte(tport+0x06, 0x01);
}
/*
* Set up interrupts
*/
sc->irqInfo.hdl = wd8003Enet_interrupt_handler;
sc->irqInfo.on = nopOn;
sc->irqInfo.off = nopOn;
sc->irqInfo.isOn = wdIsOn;
st = BSP_install_rtems_irq_handler (&sc->irqInfo);
if (!st)
rtems_panic ("Can't attach WD interrupt handler for irq %d\n",
sc->irqInfo.name);
}
static void
wd_rxDaemon (void *arg)
{
unsigned int tport;
struct ether_header *eh;
struct wd_softc *dp = (struct wd_softc *)&wd_softc[0];
struct ifnet *ifp = &dp->arpcom.ac_if;
struct mbuf *m;
unsigned int i2;
unsigned int len;
volatile unsigned char start, next, current;
char *shp, *temp;
rtems_event_set events;
tport = wd_softc[0].port ;
for (;;){
rtems_bsdnet_event_receive (INTERRUPT_EVENT,
RTEMS_WAIT|RTEMS_EVENT_ANY,
RTEMS_NO_TIMEOUT,
&events);
for (;;){
inport_byte(tport+BNRY, start);
outport_byte(tport+CMDR, MSK_PG1 + MSK_RD2);
inport_byte(tport+CURR, current);
outport_byte(tport+CMDR, MSK_PG0 + MSK_RD2);
start += 1;
if (start >= OUTPAGE){
start = 0;
}
if (current == start)
break;
shp = dp->base + 1 + (SHAPAGE * start);
next = *shp++;
len = *((short *)shp)++ - 4;
if (next >= OUTPAGE){
next = 0;
}
MGETHDR (m, M_WAIT, MT_DATA);
MCLGET (m, M_WAIT);
m->m_pkthdr.rcvif = ifp;
temp = m->m_data;
m->m_len = m->m_pkthdr.len = len - sizeof(struct ether_header);
if ((i2 = (OUTPAGE - start) * SHAPAGE - 4) < len){
memcpy(temp, shp, i2);
len -= i2;
temp += i2;
shp = dp->base;
}
memcpy(temp, shp, len);
eh = mtod (m, struct ether_header *);
m->m_data += sizeof(struct ether_header);
ether_input (ifp, eh, m);
outport_byte(tport+BNRY, next-1);
}
/*
* Ring overwrite
*/
if (overrun){
outport_byte(tport+ISR, MSK_OVW); /* reset IR */
outport_byte(tport+TCR, 0); /* out of loopback */
if (resend == 1)
outport_byte(tport+CMDR, MSK_TXP + MSK_RD2); /* resend */
resend = 0;
overrun = 0;
}
outport_byte(tport+IMR, 0x15); /* re-enable IT rx */
}
}
static void
sendpacket (struct ifnet *ifp, struct mbuf *m)
{
struct wd_softc *dp = ifp->if_softc;
struct mbuf *n;
unsigned int len, tport;
char *shp, txReady;
tport = dp->port;
/*
* Waiting for Transmitter ready
*/
inport_byte(tport+CMDR, txReady);
while(txReady & MSK_TXP)
inport_byte(tport+CMDR, txReady);
len = 0;
shp = dp->base + (SHAPAGE * OUTPAGE);
n = m;
for (;;){
len += m->m_len;
memcpy(shp, (char *)m->m_data, m->m_len);
shp += m->m_len ;
if ((m = m->m_next) == NULL)
break;
}
m_freem(n);
if (len < ET_MINLEN) len = ET_MINLEN;
outport_byte(tport+TBCR0, len);
outport_byte(tport+TBCR1, (len >> 8) );
outport_byte(tport+TPSR, OUTPAGE);
outport_byte(tport+CMDR, MSK_TXP + MSK_RD2);
}
/*
* Driver transmit daemon
*/
void
wd_txDaemon (void *arg)
{
struct wd_softc *sc = (struct wd_softc *)arg;
struct ifnet *ifp = &sc->arpcom.ac_if;
struct mbuf *m;
rtems_event_set events;
for (;;) {
/*
* Wait for packet
*/
rtems_bsdnet_event_receive (START_TRANSMIT_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events);
/*
* Send packets till queue is empty
*/
for (;;) {
/*
* Get the next mbuf chain to transmit.
*/
IF_DEQUEUE(&ifp->if_snd, m);
if (!m)
break;
sendpacket (ifp, m);
}
ifp->if_flags &= ~IFF_OACTIVE;
}
}
/*
* Send packet (caller provides header).
*/
static void
wd_start (struct ifnet *ifp)
{
struct wd_softc *sc = ifp->if_softc;
rtems_event_send (sc->txDaemonTid, START_TRANSMIT_EVENT);
ifp->if_flags |= IFF_OACTIVE;
}
/*
* Initialize and start the device
*/
static void
wd_init (void *arg)
{
struct wd_softc *sc = arg;
struct ifnet *ifp = &sc->arpcom.ac_if;
if (sc->txDaemonTid == 0) {
/*
* Set up WD hardware
*/
wd8003Enet_initialize_hardware (sc);
/*
* Start driver tasks
*/
sc->txDaemonTid = rtems_bsdnet_newproc ("SCtx", 4096, wd_txDaemon, sc);
sc->rxDaemonTid = rtems_bsdnet_newproc ("SCrx", 4096, wd_rxDaemon, sc);
}
/*
* Tell the world that we're running.
*/
ifp->if_flags |= IFF_RUNNING;
}
/*
* Stop the device
*/
static void
wd_stop (struct wd_softc *sc)
{
unsigned int tport;
unsigned char temp;
struct ifnet *ifp = &sc->arpcom.ac_if;
ifp->if_flags &= ~IFF_RUNNING;
/*
* Stop the transmitter
*/
tport=wd_softc[0].port ;
inport_byte(tport+0x04,temp);
outport_byte(tport+0x04, temp & 0x7f);
outport_byte(tport + CMDR, MSK_STP + MSK_RD2);
}
/*
* Show interface statistics
*/
static void
wd_stats (struct wd_softc *sc)
{
printf (" Rx Interrupts:%-8lu", sc->rxInterrupts);
printf (" Not First:%-8lu", sc->rxNotFirst);
printf (" Not Last:%-8lu\n", sc->rxNotLast);
printf (" Giant:%-8lu", sc->rxGiant);
printf (" Runt:%-8lu", sc->rxRunt);
printf (" Non-octet:%-8lu\n", sc->rxNonOctet);
printf (" Bad CRC:%-8lu", sc->rxBadCRC);
printf (" Overrun:%-8lu", sc->rxOverrun);
printf (" Collision:%-8lu\n", sc->rxCollision);
printf (" Tx Interrupts:%-8lu", sc->txInterrupts);
printf (" Deferred:%-8lu", sc->txDeferred);
printf (" Missed Hearbeat:%-8lu\n", sc->txHeartbeat);
printf (" No Carrier:%-8lu", sc->txLostCarrier);
printf ("Retransmit Limit:%-8lu", sc->txRetryLimit);
printf (" Late Collision:%-8lu\n", sc->txLateCollision);
printf (" Underrun:%-8lu", sc->txUnderrun);
printf (" Raw output wait:%-8lu\n", sc->txRawWait);
}
/*
* Driver ioctl handler
*/
static int
wd_ioctl (struct ifnet *ifp, int command, caddr_t data)
{
struct wd_softc *sc = ifp->if_softc;
int error = 0;
switch (command) {
case SIOCGIFADDR:
case SIOCSIFADDR:
ether_ioctl (ifp, command, data);
break;
case SIOCSIFFLAGS:
switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) {
case IFF_RUNNING:
wd_stop (sc);
break;
case IFF_UP:
wd_init (sc);
break;
case IFF_UP | IFF_RUNNING:
wd_stop (sc);
wd_init (sc);
break;
default:
break;
}
break;
case SIO_RTEMS_SHOW_STATS:
wd_stats (sc);
break;
/*
* FIXME: All sorts of multicast commands need to be added here!
*/
default:
error = EINVAL;
break;
}
return error;
}
/*
* Attach an WD driver to the system
*/
int
rtems_wd_driver_attach (struct rtems_bsdnet_ifconfig *config)
{
struct wd_softc *sc;
struct ifnet *ifp;
int mtu;
int i;
/*
* Find a free driver
*/
for (i = 0 ; i < NWDDRIVER ; i++) {
sc = &wd_softc[i];
ifp = &sc->arpcom.ac_if;
if (ifp->if_softc == NULL)
break;
}
if (i >= NWDDRIVER) {
printf ("Too many WD drivers.\n");
return 0;
}
/*
* Process options
*/
if (config->hardware_address) {
memcpy (sc->arpcom.ac_enaddr, config->hardware_address,
ETHER_ADDR_LEN);
}
else {
memset (sc->arpcom.ac_enaddr, 0x08,ETHER_ADDR_LEN);
}
if (config->mtu)
mtu = config->mtu;
else
mtu = ETHERMTU;
if (config->irno)
sc->irqInfo.name = config->irno;
else
sc->irqInfo.name = 5;
if (config->port)
sc->port = config->port;
else
sc->port = 0x240;
if (config->bpar) {
sc->bpar = config->bpar;
sc->base = (unsigned char*) config->bpar;
}
else {
sc->bpar = 0xD0000;
sc->base = (unsigned char*) 0xD0000;
}
sc->acceptBroadcast = !config->ignore_broadcast;
/*
* Set up network interface values
*/
ifp->if_softc = sc;
ifp->if_unit = i + 1;
ifp->if_name = "wd";
ifp->if_mtu = mtu;
ifp->if_init = wd_init;
ifp->if_ioctl = wd_ioctl;
ifp->if_start = wd_start;
ifp->if_output = ether_output;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
if (ifp->if_snd.ifq_maxlen == 0)
ifp->if_snd.ifq_maxlen = ifqmaxlen;
/*
* init some variables
*/
overrun = 0;
resend = 0;
/*
* Attach the interface
*/
if_attach (ifp);
ether_ifattach (ifp);
return 1;
};