summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libbsp/i386/pc386/wd8003/wd8003.c
blob: a23a16531f8ac8e9eabf511e1ef6e868cddff49e (plain) (tree)
1
2
3
4
5
6
7
8
9
  
                          
  





                                         


        
 

                   


                   
                        











                               
                



                                                            
                                         
   
                         





                                                                   




                           

                                                                               



                                     






                                                                        
   




                                               

  
                  
   

                                       


                                         
                                                




                                                





                                            



               








                             
  








                                







                                                               


                                           

  
                       
   

                                   
 
                     

                                
                            




                                 
                                






                                                                       
                 





                                                                              


                                          


    
                    
     



                                                                    

   











                                                             
                                               





                                   
                                                    



                      
                       
                     
                        
 
                   




                                        
                                



                                          
                       





                                                                             
                                                                      

















                                                                      
                                                                      



                                                                           
                                           


















                                                                             



                                                 
  
                                                    
          
                                                                  
                                    

 

                       
 









                                                        
 






















































                                                                     
    
                   
     


















                                                                     
                           


                         





                                      
 


                                       
        

           



                                             


            

             




                                              




































































                                                                                                                           

    
                                       
     
                               
 
 

  
                  
   

                             


                     



                                        


                         
                          


                                                
 

 
 



                            
                              
 

















                                                                 


  
                       
   

                                                       
 









































                                                                        
 



                                    
                                                                         







































































                                                                 












                               
/*
 * 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, int attach)
{
	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;
};