/* 
 * net-2-driver for the NI5210 card (i82586 Ethernet chip)
 *
 * This is an extension to the Linux operating system, and is covered by the
 * same Gnu Public License that covers that work. (wow!)
 * 
 * Alphacode 0.12
 * ported for Linux and copyrights (c) by M. Hipp (mhipp@student.uni-tuebingen.de)
 *
 * sources: 
 *   the code is based on the wollman-ie-driver for BSD
 *   (though i have rewritten most, a lot of comments are original.) 
 *   the 'i82586.h' file is a complete copy (with some changes) 
 *   other sources:
 *       crnwyr-driver and (of course) the original driver by Donald Becker
 */
 
/*
 * Intel 82586 Ethernet chip 
 * Register, bit, and structure definitions.
 *
 * Written by GAW with reference to the Clarkson Packet Driver code for this
 * chip written by Russ Nelson and others.
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/string.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/interrupt.h>

#include "dev.h"
#include "eth.h"
#include "timer.h"
#include "ip.h"
#include "protocol.h"
#include "tcp.h"
#include "skbuff.h"
#include "sock.h"
#include "arp.h"

#include "i82586.h"

/* undef the NO_TEST line if you wanna test(!) xmit with more than 1 buffer.
   (if you have no problems and you can see an increase of the performance  
   please let me know.) 
 */
#define NO_TEST

#define PORT dev->base_addr
#define MEM  p->realbase

/* ni5210 specific */
#define IE_BUS_SIZE   IE_8BIT /* ni5210 has an 8Bit-bus */

#define ni_attn586()  {outb(0,PORT+IENI_ATTN);}
#define ni_reset586() {outb(0,PORT+IENI_RESET);}
#define ni_etheradd() {int i;for(i=0;i<ETH_ALEN;i++)dev->dev_addr[i]=inb(PORT+i);}
/*   */

#define MK_24(base, ptr) ((caddr_t)((caddr_t)ptr - base))
#define MK_16(base, ptr) ( (u_short) (u_long) MK_24(base, ptr))

/******************* how to calc the buffers *****************************

sizeof(scp)+2+sizeof(iscp)+sizeof(scb) = 36 = INIT
sizeof(rfd) = 24; sizeof(rbd) = 12; 
sizeof(tbd) = 8; sizeof(xmit_cmd) = 18; 

examples:
-> config 1: NFRAMES=16, NBUFFS=48, IE_RBUF_SIZE=256, XMITBUFFS=2 ,IE_BUF_LEN=1514

NFRAMES * sizeof(rfd) = 384;
NBUFFS * ( sizeof(rbd) + IE_RBUF_SIZE) = 12864
XMITBUFFS * ( sizeof(tbd) + sizeof(xmit_cmd) + IE_BUF_LEN) = 3080
INIT = 36
--------------------
16364   (20 bytes left!)

-> config 2: NFRAMES=10, NBUFFS=18, IE_RBUF_SIZE=256, XMITBUFFS=2 ,IE_BUF_LEN=1514

NFRAMES * sizeof(rfd) = 240
NBUFFS * ( sizeof(rbd) + IE_RBUF_SIZE) = 4824
XMITBUFFS * ( sizeof(tbd) + sizeof(xmit_cmd) + IE_BUF_LEN) = 3080
INIT = 36
------------------
8180    (12 bytes left!)

***************************************************************************/

/* config for 16Kram card */
#define NFRAMES 16		/* number of frames to allow for receive */
#define NBUFFS 48		/* number of buffers to allocate */
#define IE_RBUF_SIZE 256	/* size of each buffer, SHOULD BE POWER OF TWO & EVEN*/
#define IE_BUF_LEN 1514		/* length of transmit buffer (EVEN) */
#define XMITBUFFS 2             /* unused for now */
/*   */

#define DELAY(x) {int i=jiffies;while(i+(x)>jiffies);}

extern struct device *irq2dev_map[16];

static int     init586(struct device *dev);
static int     probe586(struct device *dev);
static int     check586(struct device *dev,caddr_t where,unsigned size);
static int     alloc586(struct device *dev);
static void    *setup_rfa(struct device *dev,void *ptr);
static void    run_tdr(struct device *,volatile struct ie_tdr_cmd *cmd);
static void    ierint(struct device *dev);
static void    iernr(struct device *dev);
static void    ietint(struct device *dev);
static void    intr586(int reg_ptr);
static int     open586(struct device *dev);
static int     close586(struct device *dev);
static void    startrecv586(struct device *dev);
static int     xmit586(struct sk_buff *,struct device *);
/* ni5210 specific */
static int     probe_ni5210(struct device *dev);

struct sigaction sig586 = { intr586,0,0,NULL };

struct priv
{
  unsigned long   realbase;
  unsigned int    memsize;
  volatile int    rfhead,rftail;
  volatile int    rbhead,rbtail;
  volatile struct ie_sys_conf_ptr     *scp;  /* volatile very important */
  volatile struct ie_int_sys_conf_ptr *iscp; /* without, gcc 'optimizes' wait-loops */
  volatile struct ie_sys_ctl_block    *scb;
  volatile struct ie_recv_frame_desc  *rframes[NFRAMES];
  volatile struct ie_recv_buf_desc    *rbuffs[NBUFFS];
  volatile char  *cbuffs[NBUFFS];
  volatile struct ie_xmit_buf *xmit_buffs[XMITBUFFS];
  volatile struct ie_xmit_cmd *xmit_cmds[XMITBUFFS];
  volatile char  *xmit_cbuffs[XMITBUFFS];
  volatile int    xmit_count,xmit_last;
  volatile int    xmit_off;
};


/***************************************************
 *   card- & deviceinit (for Space.c)
 */ 

int ni5210_init(struct device *dev)
{
  int i;

  dev->priv = (void *) kmalloc(sizeof(struct priv),GFP_KERNEL);
  memset((char *) dev->priv,0,sizeof(struct priv));

  if(!probe_ni5210(dev)) return 1;
  if(!probe586(dev)) return 1;

  ni_etheradd();

  printk("%s: io: %04x address: ",dev->name,(int) dev->base_addr);
  for(i=0;i<ETH_ALEN;i++)
  {
    printk("%02x",dev->dev_addr[i]);
    dev->broadcast[i] = 0xff;
    if(i != (ETH_ALEN-1)) printk(":");
  }
  printk("\n");

  init586(dev);

  dev->queue_xmit     = dev_queue_xmit;
  dev->open           = &open586;
  dev->stop           = &close586;
  dev->hard_start_xmit= &xmit586;

  dev->type           = ARPHRD_ETHER;
  dev->mtu            = 1500; /* eth_mtu */
  dev->hard_header    = eth_header;
  dev->add_arp        = eth_add_arp;
  dev->rebuild_header = eth_rebuild_header;
  dev->type_trans     = eth_type_trans;
  dev->hard_header_len= ETH_HLEN;
  dev->addr_len       = ETH_ALEN;

  dev->flags          = 0;
  dev->family         = AF_INET;
  dev->pa_addr        = 0;
  dev->pa_brdaddr     = 0;
  dev->pa_mask        = 0;
  dev->pa_alen        = sizeof(unsigned long);

  dev->tbusy = 0;
  dev->interrupt = 0;
  dev->start = 0;

  irq2dev_map[dev->irq] = dev;

  if (irqaction (dev->irq, &sig586))
  {
     printk ("%s: err: Unable to get IRQ%d\n",dev->name,dev->irq);
     return 1;
  }
  return 0;
}

/**********************************************
 * close device 
 */

static int close586(struct device *dev)
{
  ni_reset586(); /* the hard way to stop the receiver */
  dev->start = 0;
  dev->tbusy = 0;
  dev->start = 0;
  return 0;
}

/**********************************************
 * open device 
 */

static int open586(struct device *dev)
{
  alloc586(dev);
  init586(dev);  
  startrecv586(dev);
  dev->interrupt = 0;
  dev->tbusy = 0;
  dev->start = 1;
  return 0; /* most done by init */
}

/**********************************************
 * Check to see if there's an 82586 out there. 
 */

static int check586(struct device *dev,caddr_t where,unsigned size)
{
  struct priv *p = (struct priv *) dev->priv;

  p->realbase = (unsigned long) where + size - 0x01000000;
  p->memsize  = size;

  p->scp = (struct ie_sys_conf_ptr *)(p->realbase + IE_SCP_ADDR);
  memset((char *)p->scp,0, sizeof(struct ie_sys_conf_ptr));
  
  /*
   * First we put the ISCP at the bottom of memory; this tests to make
   * sure that our idea of the size of memory is the same as the controller's.
   * This is NOT where the ISCP will be in normal operation.
   */
  p->iscp = (struct ie_int_sys_conf_ptr *)where;
  memset((char *)p->iscp,0, sizeof(struct ie_int_sys_conf_ptr));

  p->scb = (struct ie_sys_ctl_block *)where;
  memset((char *)p->scb,0, sizeof(struct ie_sys_ctl_block));

  p->scp->ie_bus_use = IE_BUS_SIZE;
  p->scp->ie_iscp_ptr = (caddr_t)((caddr_t)(p->iscp) - (caddr_t)p->realbase);

  p->iscp->ie_busy = 1;

  ni_reset586();
  ni_attn586();

  DELAY(2);			/* wait a while... */

  if(p->iscp->ie_busy) 
    return 0;

  return alloc586(dev);
}

/***************************************************+
 * set iscp at the right place , called by check586
 * and open586
 */

int alloc586(struct device *dev)
{
  struct priv *p =  (struct priv *) dev->priv; 

  /*
   * relocate the ISCP to its real home, and reset the controller
   */

  p->iscp = (struct ie_int_sys_conf_ptr *) ((caddr_t) p->scp - sizeof(struct ie_int_sys_conf_ptr));
  memset((char *)p->iscp,0, sizeof(struct ie_int_sys_conf_ptr));

  memset((char *) p->scp,0,sizeof(struct ie_sys_conf_ptr));
  p->scp->ie_iscp_ptr = MK_24(MEM,p->iscp);
  p->scp->ie_bus_use = IE_BUS_SIZE;

  p->iscp->ie_busy = 1;

  ni_reset586();
  ni_attn586();

  DELAY(2); 

  if(p->iscp->ie_busy)
    return 0;

  p->iscp->ie_scb_offset = MK_16(MEM,p->scb);
  memset((char *)p->scb,0,sizeof(struct ie_sys_ctl_block));

  return 1;
}

/**********************************************
 * probe the ni5210-card
 */

static int probe_ni5210(struct device *dev)
{
  int i;

  if(dev->base_addr != 0)
  {
  /* NI5210 specific (probe) */
    if(!(inb(PORT+IENI_REVISION) == NI_REVISION) || 
       !(inb(PORT+IENI_ATTRIB) == NI_ATTRIB))
    {
      printk("%s: can't find card at 0x%x.\n",dev->name,(int) PORT);
      return 0;
    }
  }
  else
  {
    for(i=0x200;i<0x400;i+=8)
    {
      dev->base_addr = i;
      if((inb(PORT+IENI_REVISION) == NI_REVISION) && 
         (inb(PORT+IENI_ATTRIB) == NI_ATTRIB))
            break;
    }
    if(i == 0x400)
    {
      printk("%s: can't find card between 0x200-0x400\n",dev->name);
      return 0;
    }
  }
  return 1;
}

/************************************************
 * probe the 586 chip & shmem 
 */

static int probe586(struct device *dev)
{
  unsigned int size;

  /* i82586 specific (mem-probe and iscp-init) */
  for(size = 0x10000; size >= 0x2000; size -= 0x2000) 
    if(check586(dev,(caddr_t) dev->mem_start, size)) 
      return 1;

  printk("%s: can't find i82586-iomem at %lx\n",dev->name,dev->mem_start);
  return 0;
}

/********************************************** 
 * init the chip
 */

static int init586(struct device *dev)
{
  void *ptr;
  unsigned long s;
  int i;
  struct priv *p = (struct priv *) dev->priv;
  volatile struct ie_config_cmd  *cfg_cmd;
  volatile struct ie_iasetup_cmd *ias_cmd;

  ptr = (void *) ((char *)p->scb + sizeof(struct ie_sys_ctl_block));

  /* disable interrupt */
  outb(0,PORT+IENI_INTDIS);  /* strange..if intr586 acks our interrupts we sometimes lose another */

  cfg_cmd = (struct ie_config_cmd *)ptr; /* configure-command */
 
  cfg_cmd->ie_config_count   = 0x0c;
  cfg_cmd->ie_fifo           = 8;
  cfg_cmd->ie_save_bad       = 0x40;
  cfg_cmd->ie_addr_len       = 0x2e;
  cfg_cmd->ie_priority       = 0;
  cfg_cmd->ie_ifs            = 0x60;
  cfg_cmd->ie_slot_low       = 0;
  cfg_cmd->ie_slot_high      = 0xf2;
  cfg_cmd->ie_promisc        = 0; 
  cfg_cmd->ie_crs_cdt        = 0;
  cfg_cmd->ie_min_len        = 60; /* or 64 (60+fcd)*/
  cfg_cmd->ie_junk           = 0xff;
  cfg_cmd->com.ie_cmd_status = 0;
  cfg_cmd->com.ie_cmd_cmd    = IE_CMD_CONFIG | IE_CMD_LAST;
  cfg_cmd->com.ie_cmd_link   = 0xffff;

  p->scb->ie_command_list = MK_16(MEM, cfg_cmd);

  p->scb->ie_command = IE_CU_START; /* cmd.-unit start */
  ni_attn586();  /* here: cfg_cmd */
 
  s = jiffies; 
  while(!(cfg_cmd->com.ie_cmd_status & IE_STAT_COMPL)) if(jiffies-s > 30) break;

  if((cfg_cmd->com.ie_cmd_status & (IE_STAT_OK|IE_STAT_COMPL)) != (IE_STAT_COMPL|IE_STAT_OK))
  {
    printk("%s: configure command failed: %x\n",dev->name,cfg_cmd->com.ie_cmd_status);
    return 1; 
  }

    /* individual address setup */
  ias_cmd = (struct ie_iasetup_cmd *)ptr;

  ias_cmd->com.ie_cmd_status = 0;
  ias_cmd->com.ie_cmd_cmd = IE_CMD_IASETUP | IE_CMD_LAST;
  ias_cmd->com.ie_cmd_link = 0xffff;

  memcpy((char *)&ias_cmd->ie_address,(char *) dev->dev_addr,ETH_ALEN);

  p->scb->ie_command_list = MK_16(MEM, ias_cmd);

  p->scb->ie_command = IE_CU_START; /* cmd.-unit start */
  ni_attn586(); /* here: initialaddress-setup-cmd */

  s = jiffies;
  while(!(ias_cmd->com.ie_cmd_status & IE_STAT_COMPL)) if(jiffies-s > 30) break;

  if((ias_cmd->com.ie_cmd_status & (IE_STAT_OK|IE_STAT_COMPL)) != (IE_STAT_OK|IE_STAT_COMPL)) 
  {
    printk("%s: individual address setup command failed: %04x\n",dev->name,ias_cmd->com.ie_cmd_status);
    return 1; 
  }

  run_tdr(dev,(struct ie_tdr_cmd *)ptr);  /* wire check .. e.g. no resistor e.t.c */

  p->scb->ie_command = p->scb->ie_status & IE_ST_WHENCE; /* ack the interrupts */
  ni_attn586();

  ptr = setup_rfa(dev,(void *)ptr); /* alloc recv-buffs and attach them in a chain */ 

  for(i=0;i<XMITBUFFS;i++)
  {
    p->xmit_cmds[i] = (struct ie_xmit_cmd *)ptr; /* transmit cmd/buff 0 */
    ptr += sizeof(struct ie_xmit_cmd);
    p->xmit_buffs[i] = (struct ie_xmit_buf *)ptr;
    ptr += sizeof(struct ie_xmit_buf);
    p->xmit_cbuffs[i] = (char *)ptr; /* char-buffs */
    ptr += IE_BUF_LEN;
    if((void *)ptr > (void *)p->iscp) 
    {
      printk("%s: not enough memory for your configuration!\n",dev->name);
      return 1;
    }   
    memset((caddr_t)(p->xmit_cmds[i]) ,0, sizeof(struct ie_xmit_cmd));
    memset((caddr_t)(p->xmit_buffs[i]),0, sizeof(struct ie_xmit_buf));
    p->xmit_cmds[i]->ie_xmit_status = IE_STAT_COMPL;
    p->xmit_cmds[i]->ie_xmit_desc = MK_16(MEM,(p->xmit_buffs[i]));
    p->xmit_buffs[i]->ie_xmit_next = 0xffff;
    p->xmit_buffs[i]->ie_xmit_buf = MK_24(MEM,(p->xmit_cbuffs[i]));
  }
  for(i=0;i<XMITBUFFS;i++) /* link them */
    p->xmit_cmds[i]->com.ie_cmd_link = MK_16(MEM,p->xmit_cmds[(i+1)%XMITBUFFS]);

  p->xmit_count = 0; 
  p->xmit_last = 0;
  p->xmit_off = 1;

  /* enable interrupt */ 
  outb(0,PORT+IENI_INTENA); 

  return 0;
}

/******************************************************
 * Here is a helper routine for iernr() and init586(). 
 * This sets up the RFA.
 */

static void *setup_rfa(struct device *dev,void *ptr) 
{
  volatile struct ie_recv_frame_desc *rfd = (struct ie_recv_frame_desc *)ptr;
  volatile struct ie_recv_buf_desc *rbd;
  int i;
  struct priv *p = (struct priv *) dev->priv;

  /* first get frame descriptors */ 

  memset((char *) rfd,0,sizeof(struct ie_recv_frame_desc)*NFRAMES);
  for(i = 0; i < NFRAMES; i++)
  {
    p->rframes[i] = (rfd + i);
    p->rframes[i]->ie_fd_next = MK_16(MEM,(rfd + (i+1) % NFRAMES));
  }
  p->rframes[NFRAMES - 1]->ie_fd_last |= IE_FD_LAST; /* set EOL */
  ptr = (caddr_t) (rfd + NFRAMES);

  /* now get recv-descriptors & recv-buffers */

  rbd = (struct ie_recv_buf_desc *) ptr;
  ptr += sizeof(struct ie_recv_buf_desc)*NBUFFS;

  memset((char *) rbd,0,sizeof(struct ie_recv_buf_desc)*NBUFFS); /* clr descriptors */
  for(i=0;i<NBUFFS;i++)
  {
    p->rbuffs[i] = rbd + i;
    p->rbuffs[i]->ie_rbd_next = MK_16(MEM,(rbd + (i+1) % NBUFFS));
    p->rbuffs[i]->ie_rbd_length = IE_RBUF_SIZE;
    p->rbuffs[i]->ie_rbd_buffer = MK_24(MEM,ptr);
    p->cbuffs[i] = (char *)ptr;
    ptr += IE_RBUF_SIZE;
  }
  p->rbuffs[NBUFFS - 1]->ie_rbd_length |= IE_RBD_LAST; /* set eol */

  /* We use the head and tail pointers on receive to keep track of
   * the order in which RFDs and RBDs are used. */
  p->rfhead = 0;
  p->rftail = NFRAMES - 1;
  p->rbhead = 0;
  p->rbtail = NBUFFS - 1;

  p->scb->ie_recv_list = MK_16(MEM,(p->rframes[0]));
  p->rframes[0]->ie_fd_buf_desc = MK_16(MEM, p->rbuffs[0]);

  return ptr;
}

/*********************************************************
 * Run the time-domain reflectometer...
 */

static void run_tdr(struct device *dev,volatile struct ie_tdr_cmd *cmd)
{
  int result=0;
  unsigned long s;
  struct priv *p = (struct priv *) dev->priv;

  cmd->com.ie_cmd_status = 0;
  cmd->com.ie_cmd_cmd = IE_CMD_TDR | IE_CMD_LAST;
  cmd->com.ie_cmd_link = 0xffff;
  cmd->ie_tdr_time = 0;

  p->scb->ie_command_list = MK_16(MEM, cmd);

  p->scb->ie_command = IE_CU_START; /* cmd.-unit start */
  ni_attn586();

  s = jiffies;
  while(!(cmd->com.ie_cmd_status & IE_STAT_COMPL)) 
    if(jiffies - s > 30)
      break;

  if(cmd->com.ie_cmd_status & IE_STAT_COMPL)
    result = cmd->ie_tdr_time;
  else
    result = 0x2000 | IE_TDR_TIME;

  p->scb->ie_command = p->scb->ie_status & IE_ST_WHENCE;
  ni_attn586(); /* ack the interrupts */

  if(result & IE_TDR_SUCCESS) return;

  if(result & IE_TDR_XCVR) {
    printk("%s: transceiver problem\n",dev->name);
  } else if(result & IE_TDR_OPEN) {
    printk("%s: TDR detected an open %d clocks away\n",dev->name,result & IE_TDR_TIME);
  } else if(result & IE_TDR_SHORT) {
    printk("%s: TDR detected a short %d clocks away\n",dev->name,result & IE_TDR_TIME);
  } else {
    printk("%s: TDR returned unknown status %x\n",dev->name,result);
  }
}

/*********************************************************
 * What to do upon receipt of an interrupt.
 */

static void intr586(int reg_ptr)
{
  int irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2);
  struct device *dev = irq2dev_map[irq];
  unsigned short stat;
  int pd = 0;
  struct priv *p = (struct priv *) dev->priv;

  for(stat=p->scb->ie_status;stat & IE_ST_WHENCE;stat=p->scb->ie_status)
  {
    if(pd) printk("-%04x",(int) stat); /* debug */

    if(stat & (IE_ST_RECV | IE_ST_RNR)) 
      ierint(dev);

    if(stat & IE_ST_DONE) 
      ietint(dev);

    if(stat & IE_ST_RNR) 
    {
      printk("%s: rnr: %04x ",dev->name,(int) stat);
      iernr(dev); pd = 1; /* debug on */
    }

    p->scb->ie_command = p->scb->ie_status & (IE_ST_WHENCE & stat);
    ni_attn586(); /* ack inter. */
  }
}

/*******************************************************
 * receive-interrupt
 */

static void ierint(struct device *dev)
{
  int i,status,rfdstat,brk,head,last,cnt;
  unsigned short totlen,sksize,pnt,rbtailold;
  struct sk_buff *skb; 
  struct priv *p = (struct priv *) dev->priv;

  for(i=p->rfhead;;i=(i+1)%NFRAMES)
  {
    status = p->rframes[i]->ie_fd_status;

    if((status & IE_FD_COMPLETE) && (status & IE_FD_OK)) 
    {
      rfdstat = p->rframes[i]->ie_fd_status;
      p->rframes[i]->ie_fd_status = 0;
      p->rframes[i]->ie_fd_last |= IE_FD_LAST;
      p->rframes[p->rftail]->ie_fd_last &= ~IE_FD_LAST;
      p->rftail = p->rfhead;
      p->rfhead = (p->rfhead + 1) % NFRAMES;

      if(rfdstat & IE_FD_OK) 
      {
        totlen = 0; cnt = 0; brk = 0; head=p->rbhead;
        while(!brk) /* calc recv-frame-len */
        {
          brk = p->rbuffs[head]->ie_rbd_actual & IE_RBD_LAST;
          totlen += p->rbuffs[head]->ie_rbd_actual & IE_RBD_MASK;
          p->rbuffs[head]->ie_rbd_actual = 0;
          head++; last = head; head %= NBUFFS; cnt++;
        }

        sksize = sizeof(struct sk_buff) + totlen;
        skb = (struct sk_buff *) kmalloc(sksize, GFP_ATOMIC);

        if (skb != NULL) /* copy header */
        {
          skb->lock = 0;
          skb->mem_len = sksize;
          skb->mem_addr = skb;

          if( (last - cnt) < 0)
          {
            pnt = (cnt - last)*IE_RBUF_SIZE; 
            memcpy( (char *)(skb+1),(char *) p->cbuffs[p->rbhead],pnt);
            memcpy( ((char *)(skb+1)) + pnt,(char *) p->cbuffs[0],totlen - pnt); 
          }
          else
            memcpy( (char *)(skb+1),(char *) p->cbuffs[p->rbhead],totlen);

          p->rbhead = head;
          rbtailold = p->rbtail; p->rbtail += cnt; p->rbtail %= NBUFFS;
          p->rbuffs[p->rbtail]->ie_rbd_length |= IE_RBD_LAST; 
          p->rbuffs[rbtailold]->ie_rbd_length &= ~IE_RBD_LAST;

          if(dev_rint((unsigned char *)skb, totlen, IN_SKBUFF, dev)) 
          {
            printk("%s: receive buffers full.\n",dev->name);
          }
        }
        else
        {
          p->rbhead = head;
          rbtailold = p->rbtail; p->rbtail += cnt; p->rbtail %= NBUFFS;
          p->rbuffs[p->rbtail]->ie_rbd_length |= IE_RBD_LAST; 
          p->rbuffs[rbtailold]->ie_rbd_length &= ~IE_RBD_LAST;
        }
      }
    }
    else
      break;
  }
}

/**********************************************************
 * I never got this error , (which should occure if someone 
 * wants to blast your machine) so I couldn't debug it for now.
 */

static void iernr(struct device *dev)   /* try to fix the receive not ready int. */
{
  struct priv *p = (struct priv *) dev->priv;

  p->scb->ie_command = IE_RU_DISABLE;
  ni_attn586(); 
  while(p->scb->ie_command);    /* wait for accept cmd. */

  setup_rfa(dev,(caddr_t)p->rframes[0]);
  startrecv586(dev); /* restart */
}

/**********************************************************
 * handle xmit - interrupt
 */

static void ietint(struct device *dev)
{
  int status;
  struct priv *p = (struct priv *) dev->priv;

#ifndef NO_TEST
  if(p->xmit_off) return;
#endif

  if( (status=p->xmit_cmds[p->xmit_last]->ie_xmit_status) & IE_XS_ERRMASK)
  {
    if(status & IE_XS_LATECOLL) 
    {
      printk("%s: late collision\n",dev->name);
    } 
    else if(status & IE_XS_NOCARRIER) 
    {
      printk("%s: no carrier\n",dev->name);
    } 
    else if(status & IE_XS_LOSTCTS) 
    {
      printk("%s: lost CTS\n",dev->name);
    } 
    else if(status & IE_XS_UNDERRUN) 
    {
      printk("%s: DMA underrun\n",dev->name);
    } 
    else if(status & IE_XS_EXCMAX) 
    {
      printk("%s: too many collisions\n",dev->name);
    } 
  }
  dev->tbusy = 0;

#ifdef NO_TEST  
    /* dunno if necessary */
  p->xmit_cmds[p->xmit_last]->ie_xmit_status |= IE_STAT_COMPL; 
#else
  p->xmit_cmds[p->xmit_last]->ie_xmit_status |= IE_STAT_COMPL;
  p->xmit_last++; p->xmit_last %= XMITBUFFS;
  if(!(p->xmit_cmds[p->xmit_last]->ie_xmit_status & IE_STAT_COMPL))
  {
    p->scb->ie_command_list = MK_16(MEM,(p->xmit_cmds[p->xmit_last]));
    p->scb->ie_command = IE_CU_START;
    ni_attn586();
    dev->trans_start = jiffies;
    while(p->scb->ie_command);
  }
  else
  {
    p->xmit_off = 1;
  }
#endif

  mark_bh(INET_BH);
}

/***********************************************************
 * (re)start the receiver
 */ 

static void startrecv586(struct device *dev)
{
  struct priv *p = (struct priv *) dev->priv;

  p->scb->ie_recv_list = MK_16(MEM,(p->rframes[0]));
  p->scb->ie_command = IE_RU_START;
  ni_attn586(); /* start cmd. */
  while(p->scb->ie_command); /* wait for accept cmd. */

  DELAY(2); /* isn't necess. */

  p->scb->ie_command = p->scb->ie_status & IE_ST_WHENCE;
  ni_attn586();
}

/******************************************************
 * send frame 
 */

static int xmit586(struct sk_buff *skb, struct device *dev)
{
  int len;
  struct priv *p = (struct priv *) dev->priv;

  if(dev->tbusy) 
  {
    int tickssofar = jiffies - dev->trans_start;
    if (tickssofar < 15)
      return 1;

    printk("%s: xmitter timed out, try to restart!\n",dev->name);
    ni_reset586();
    dev->tbusy = 1;
    alloc586(dev);
    init586(dev);
    startrecv586(dev);
  }

  if(skb == NULL)
  {
    dev_tint(dev);
    return 0;
  }

  if (!skb->arp)
  {
    if(dev->rebuild_header(skb+1, dev)) 
    {
      skb->dev = dev;
      arp_queue (skb);
      return 0; /* arp has queued */
    }
  }

  dev->tbusy = 1;

  memcpy((char *) p->xmit_cbuffs[p->xmit_count],(char *) (skb+1),skb->len);

  if( (len = skb->len) < 60) len = 60;  /* + fcs(4) = 64 */

  p->xmit_buffs[p->xmit_count]->ie_xmit_flags = IE_XMIT_LAST | len;

  p->xmit_cmds[p->xmit_count]->com.ie_cmd_cmd = IE_CMD_XMIT | IE_CMD_LAST | IE_CMD_INTR;
  p->xmit_cmds[p->xmit_count]->ie_xmit_status = 0;

#ifdef NO_TEST
  p->scb->ie_command_list = MK_16(MEM,(p->xmit_cmds[p->xmit_count]));
  p->scb->ie_command = IE_CU_START;
  dev->trans_start = jiffies;
  ni_attn586();
  while(p->scb->ie_command); /* wait for accept cmd. */
#else
  cli();
  if(p->xmit_off)
  {
    if(p->xmit_last != p->xmit_count) printk("$"); 
    p->scb->ie_command_list = MK_16(MEM,(p->xmit_cmds[p->xmit_count]));
    p->scb->ie_command = IE_CU_START;
    ni_attn586();
    dev->trans_start = jiffies;
    p->xmit_count++; p->xmit_count %= XMITBUFFS; 
    dev->tbusy = 0; 
    p->xmit_off = 0; 
    while(p->scb->ie_command); /* wait for accept cmd. */
  }
  else
  {
    p->xmit_count++; p->xmit_count %= XMITBUFFS;
    if(p->xmit_count != p->xmit_last) dev->tbusy = 0;
  }
  sti(); 
#endif

  if(skb->free) kfree_skb(skb,FREE_WRITE);

  return 0;
}
