/*
    Copyright 2000 (c) by Oliver Pfister,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
	
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/IO/ia32/Drivers/IOPciAm79c97x.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
*/
#include "IOPciAm79c97x.h"
#include "Threads.h"
#include "Tools.h"
#include "Support.h"
#include "SupportAsm.h"
#include "cpu.h" // remember to uncomment K0SEG_TO_PHYS(x) and PHYS_TO_K0SEG(x) in this file !!!
#include "Syscall.h"
#include "Messages.h"
#include "TMIPC.h"
#include "Lock.h"

IODeviceDesc am79c97xDesc = {
                              NULL, NULL,
                              "am79c97x",
                              (Address)0, /* Screen IO: No Direct Memory Access allowed */
                              NULL,       /* buffer must be allocated by driver */
                              -1,
                              (InterruptHandler)ioPciMainInterruptHandler,
                              0, /* thread id filled in after start */
                              am79c97x_read,
                              am79c97x_write,
                              am79c97x_close,
                              am79c97x_init,
                              am79c97x_handleMsg, /* private msg handler */
                              FALSE,
                              &(ioPciDeviceTable[IO_AM79C97X]) /* data extension */
                            };

// The unaligned Main Network Structure Pointer,
// initialized in init and freed in close.
static unsigned int netUnalignedPtr;

// The allocated Buffer Addresses for the RX and the TX Ring:
static Address rxRing[AM79C97X_RX_RING_SIZE];
static Address txRing[AM79C97X_TX_RING_SIZE];

// Lock variable for the rxQueueTail, both am79c97x_RxISR and am79c97x_read
// can write to this variable -> we need a Lock:
static LockDesc rxQueueTailLockDesc;
static Lock rxQueueTailLock = &rxQueueTailLockDesc;

// Global strings
static char strAttachFailed[] = "NET : Can't attach am79c97x\n";
static char strAttachOk[] = "NET : am79c97x attached\n";
static char strDetachFailed[] = "NET : Can't detach am79c97x\n";
static char strInitFailed[] = "NET : am79c97x failed initialization\n";
static char strNotInSTOPState[] = "NET : Am79c97x not in STOP state after reset\n";
static char strSW3Ok[] = "NET : Ok switched to software style 3 = 32 bit structures\n";
static char strSW3Failed[] = "NET : Failed switching to software style 3\n";
static char strBurstModeOk[] = "NET : Ok switched to Burst Mode\n";
static char strBurstModeFailed[] = "NET : Failed switching to Burst Mode\n";
static char strMainStructAllocFailed[] = "NET : Can't allocate main network structure\n";
static char strMainStructFreeFailed[] = "NET : Can't free main network structure\n";

static char strRXBufferAllocFailed[] = "NET : Can't allocate rx buffer\n";
static char strTXBufferAllocFailed[] = "NET : Can't allocate tx buffer\n";
static char strRXBufferFreeFailed[] = "NET : Can't free rx buffer\n";
static char strTXBufferFreeFailed[] = "NET : Can't free tx buffer\n";


static char strMustRestart[] = "NET : Must restart am79c97x...\n";

Error am79c97x_init(IODevice devPtr)
{
  int i;
  u16 ioSelector;
  u32 bcr20;
  u32 bcr18;
  u32 csr0;
  u32 physAddr;
  u16 physAddrLow;
  u16 physAddrHigh;
  IOPciDeviceDesc *devPciPtr;
  IOAm79c97x *netPtr; // initialized later


  // Init the Lock:
  lockInit(rxQueueTailLock);

  if (devPtr->isInitialised)
    return IO_INITOK;

  // If we can't find a matching device for this driver -> Fail and exit !
  if (!ioPciDeviceAttach(devPtr))
  {
    PCIprintf(strAttachFailed);
    return IO_INITFAILED;
  }
  PCIprintf(strAttachOk);

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;

  // 1. Read Base Memory Mapped IO Address from PCI structure
  if (devPciPtr->memMapped32Count == 1)
    ioSelector = devPciPtr->memMapped32[0].selector;
  else
    goto initFailed;

  // 2. First init PCI
  if (!ioPciEnableMemDecoder(devPciPtr)) // always needed to decode Memory Mapped IO accesses
    goto initFailed;
  if (!ioPciEnableBusMaster(devPciPtr)) // set device as bus master capable
    goto initFailed;

  // 3. Reset the am79c97x
  __readw(ioSelector, AM79C97X_RESET_W); // S_RESET if in Word I/O mode
  __readl(ioSelector, AM79C97X_RESET); // S_RESET if in DWord I/O mode

  // 4. Switch to DWord I/O mode
  __writel(ioSelector, AM79C97X_RDP, 0);

  // 5. Check if we are in the STOP state
  __writel(ioSelector, AM79C97X_RAP, 0); // Switch to CSR0
  csr0 = __readl(ioSelector, AM79C97X_RDP);
  if ((csr0 & 0x00000004) != 0x00000004)
  {
    ERROR(strNotInSTOPState);
    goto initFailed;
  }

  // 6. Chance Software style to PCnet-PCI II 32 bit style (SWSTYLE=3)
  // (This style is only useful with Burst Mode enabled)
  __writel(ioSelector, AM79C97X_RAP, 20); // Switch to BCR20
  bcr20 = __readl(ioSelector, AM79C97X_BDP);
  __writel(ioSelector, AM79C97X_BDP, bcr20 | 0x00000003);
  bcr20 = __readl(ioSelector, AM79C97X_BDP);
  if ((bcr20 & 0x000000ff) == 0x00000003)
    PCIprintf(strSW3Ok);
  else
  {
    ERROR(strSW3Failed);
    goto initFailed;
  }
  // Switch to Burst mode (only with SWSTYLE 3 available)
  __writel(ioSelector, AM79C97X_RAP, 18); // Switch to BCR18
  bcr18 = __readl(ioSelector, AM79C97X_BDP);
  __writel(ioSelector, AM79C97X_BDP, bcr18 | 0x00000060); // Set BREADE and BWRITE
  bcr18 = __readw(ioSelector, AM79C97X_BDP);
  if ((bcr18 & 0x00000060) == 0x00000060)
    PCIprintf(strBurstModeOk);
  else
  {
    ERROR(strBurstModeFailed);
    goto initFailed;
  }

  // 7. Allocate the main network structure
  if (vmAlloc((Address*)&netUnalignedPtr, sizeof(IOAm79c97x)+15) == VM_ALLOCFAILED)
  {
    ERROR(strMainStructAllocFailed);
    goto initFailed;
  }

  // Aligne on 16 byte boundary
  netPtr = (IOAm79c97x*)((netUnalignedPtr+15) & ~15);

  devPciPtr->extension = (void*)netPtr;

  // 8a. There is a 16 byte EEPROM at the base address 0.
  // The first six bytes are the MAC address
  for (i = 0; i < 6; i++)
    netPtr->macAddr[i] = __readb(ioSelector, i);
  PCIprintf("NET : MAC Address = %x:%x:%x:%x:%x:%x\n", // Big endian order !
            netPtr->macAddr[0],
            netPtr->macAddr[1],
            netPtr->macAddr[2],
            netPtr->macAddr[3],
            netPtr->macAddr[4],
            netPtr->macAddr[5]);

  // 8b. No upper layer subscriber at initialization
  netPtr->subscribed = 0;

  // 8c. Initialaze the Statistics structure to all zero
  zeroOut(&(netPtr->stats), sizeof(netDeviceStats));

  // 8d. Initialize the Rx and Tx rings and fill-in the init structure
  if (!am79c97xInitRing(devPtr))
    goto initFailed;

  // 9. Pass the init structure pointer to the device
  physAddr = (u32)(K0SEG_TO_PHYS(&netPtr->initBlock));
  physAddrLow = (u16)(physAddr);
  physAddrHigh = (u16)(physAddr >> 16);
  __writel(ioSelector, AM79C97X_RAP, 1); // Switch to CSR1
  __writel(ioSelector, AM79C97X_RDP, physAddrLow);
  __writel(ioSelector, AM79C97X_RAP, 2); // Switch to CSR2
  __writel(ioSelector, AM79C97X_RDP, physAddrHigh);

  // 10. Turn on auto-select of media (10baseT or BNC) so that the user
  // can watch the LEDs even if the board isn't opened
  __writel(ioSelector, AM79C97X_RAP, 2); // Switch to BCR2
  __writel(ioSelector, AM79C97X_BDP, __readl(ioSelector, AM79C97X_BDP) | 0x00000002);

  // 11. Trigger the initialization
  __writel(ioSelector, AM79C97X_RAP, 0);
  __writel(ioSelector, AM79C97X_RDP, 0x00000001);
  while (1)
    if (__readl(ioSelector, AM79C97X_RDP) & 0x00000100) // Poll until IDON is set
      break;

  // 12. Interrupt Mask
  __writel(ioSelector, AM79C97X_RAP, 3);
  __writel(ioSelector, AM79C97X_RDP, 0x00000000);

  // 13. Set IENA(interrupt enable) and START bits
  __writel(ioSelector, AM79C97X_RAP, 0);
  __writel(ioSelector, AM79C97X_RDP, 0x00000042);

  // 14. Check if initialization was successfull
  // if TXON=1 and RXON=1 and IENA=1 -> success
  if ((__readl(ioSelector, AM79C97X_RDP) & 0x00000070) == 0x00000070)
  {
    devPtr->isInitialised = TRUE;
    return IO_INITOK;
  }

initFailed:
  devPtr->isInitialised = FALSE;
  PCIprintf(strInitFailed);
  if (!ioPciDeviceDetach(devPtr))
    ERROR(strDetachFailed);
  return IO_INITFAILED;
}


// Initialize the Rx and Tx rings and fill-in the init structure
Boolean am79c97xInitRing(IODevice devPtr)
{
  int i;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;

  netPtr->rxQueueHead = 0;
  netPtr->txQueueHead = 0;
  netPtr->rxQueueTail = 0;
  netPtr->txQueueTail = 0;


  // Set OWN=1 (initial owner is the Device)
  // and allocate buffer memory
  for (i = 0; i < AM79C97X_RX_RING_SIZE; i++)
  {
    if (vmAlloc(&(rxRing[i]), AM79C97X_PKT_BUF_SZ) == VM_ALLOCFAILED)
    {
      ERROR(strRXBufferAllocFailed);
      return FALSE;
    }

    // Translate from Virtual to Physical memory and set OWN=1
    netPtr->rxRing[i].baseAddr = (u32)(K0SEG_TO_PHYS(rxRing[i]));
    netPtr->rxRing[i].buf_length = -AM79C97X_PKT_BUF_SZ;
    netPtr->rxRing[i].flags |= 0x8000; // Set OWN=1
  }

  // Set OWN=0 (initial owner is the Driver)
  // and allocate buffer memory
  for (i = 0; i < AM79C97X_TX_RING_SIZE; i++)
  {
    if (vmAlloc(&(txRing[i]), AM79C97X_PKT_BUF_SZ) == VM_ALLOCFAILED)
    {
      ERROR(strTXBufferAllocFailed);
      return FALSE;
    }

    // Translate from Virtual to Physical memory and set OWN=0
    netPtr->txRing[i].baseAddr = (u32)(K0SEG_TO_PHYS(txRing[i]));
    netPtr->txRing[i].flags &= ~0x8000; // Set OWN=0
  }

  // The initialization block :
  //(jb, 30.5.01) setting in promiscuous mode
  //-------- OLD ---------
  //netPtr->initBlock.mode = (u16)0x0000;
  //-------- NEW ---------
  netPtr->initBlock.mode = (u16)0x8000;
  netPtr->initBlock.lens = (u16)((AM79C97X_TX_RING_LEN_BITS
                                  //-------- END ---------
                                  | AM79C97X_RX_RING_LEN_BITS));
  for (i = 0; i < 6; i++)
    netPtr->initBlock.macAddr[i] = netPtr->macAddr[i];
  //(jb, 30.5.01) receiving all multicast addresses
  //-------- OLD ---------
  //netPtr->initBlock.filter[0] = (u32)0x00000000;
  //netPtr->initBlock.filter[1] = (u32)0x00000000;
  //-------- NEW ---------
  netPtr->initBlock.filter[0] = (u32)0xffffffff;
  netPtr->initBlock.filter[1] = (u32)0xffffffff;
  //-------- END ---------
  netPtr->initBlock.rxRing = (u32)(K0SEG_TO_PHYS(netPtr->rxRing));
  netPtr->initBlock.txRing = (u32)(K0SEG_TO_PHYS(netPtr->txRing));

  return TRUE;
}


Boolean am79c97xPurgeTxRing(IODevice devPtr)
{
  int i;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;

  for (i = 0; i < AM79C97X_TX_RING_SIZE; i++)
  {
    if (vmFree((Address)txRing[i]) == VM_FREEFAILED)
    {
      ERROR(strTXBufferFreeFailed);
      return FALSE;
    }
    netPtr->txRing[i].baseAddr = 0;
    netPtr->txRing[i].flags &= ~0x8000; // Set OWN=0
  }
  return TRUE;
}


Boolean am79c97xPurgeRxRing(IODevice devPtr)
{
  int i;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;

  for (i = 0; i < AM79C97X_RX_RING_SIZE; i++)
  {
    if (vmFree(rxRing[i]) == VM_FREEFAILED)
    {
      ERROR(strRXBufferFreeFailed);
      return FALSE;
    }
    netPtr->rxRing[i].baseAddr = 0;
    netPtr->rxRing[i].flags &= ~0x8000; // Set OWN=0, am79c97xInitRing will set OWN=1
  }
  return TRUE;
}


void am79c97xRestart(IODevice devPtr)
{
  u16 ioSelector;
  IOPciDeviceDesc* devPciPtr;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  ioSelector = devPciPtr->memMapped32[0].selector;

  // Set the STOP bit :
  __writel(ioSelector, AM79C97X_RAP, 0);
  __writel(ioSelector, AM79C97X_RDP, 0x00000004);

  if (am79c97x_close(devPtr) == IO_CLOSEOK)
    ;
  am79c97x_init(devPtr);
}


Error am79c97x_read(IODevice devPtr, ThreadId threadId, char* bufPtr, long int* sizePtr)
{
  unsigned int entry;
  char* rx_buff;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;
  u16 flags;
  u16 pkt_len;

  if (devPtr->isInitialised)
  {
    devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
    netPtr = (IOAm79c97x*)devPciPtr->extension;

    lock(rxQueueTailLock); // Block until exclusive access to rxQueueTail is garanted.
    entry = netPtr->rxQueueTail & AM79C97X_RX_RING_MOD_MASK;
    flags = netPtr->rxRing[entry].flags;

    // Remove all packets with errors, statistics already updated in am79c97xRxISR:
    while ((flags & 0x4000) && // Error
           (netPtr->rxQueueTail < netPtr->rxQueueHead) // Do not reach Queue Head!
           && !(flags & 0x8000) ) // While OWN=0
    {
      netPtr->rxRing[entry].flags &= ~0x7C00; // Clear all error bits
      netPtr->rxRing[entry].flags |= 0x8000; // Set OWN=1
      netPtr->rxQueueTail++;
      entry = netPtr->rxQueueTail & AM79C97X_RX_RING_MOD_MASK;
      flags = netPtr->rxRing[entry].flags;
    }

    // Ok, now read the packet:
    if ( (netPtr->rxQueueTail < netPtr->rxQueueHead) // Do not reach Queue Head!
         && !(flags & 0x8000) ) // if OWN=0
    {
      pkt_len = netPtr->rxRing[entry].msg_length - 4; // pkt_len is without 4 bytes FCS (CRC)
      if (pkt_len > *sizePtr)
      {
        *sizePtr = 0;
        netPtr->rxRing[entry].flags |= 0x8000; // Set OWN=1
        netPtr->rxQueueTail++;
        unlock(rxQueueTailLock);
        return IO_READFAILED;
      }
      *sizePtr = pkt_len;
      // Translate from Physical to Kernel Address
      rx_buff = (char*)(PHYS_TO_K0SEG(netPtr->rxRing[entry].baseAddr));
      byteCopy(bufPtr, rx_buff, *sizePtr);
      netPtr->rxRing[entry].flags |= 0x8000; // Set OWN=1
      netPtr->rxQueueTail++;
      //PCIprintf("NET : read, Next Queue Tail = %i\n", netPtr->rxQueueTail & AM79C97X_RX_RING_MOD_MASK);
      unlock(rxQueueTailLock);
      return IO_READOK;
    }
    else
    {
      unlock(rxQueueTailLock);
      *sizePtr = 0;
      return IO_READFAILED;
    }
  }
  else
  {
    *sizePtr = 0;
    return IO_READFAILED;
  }
}


Error am79c97x_write(IODevice devPtr, ThreadId threadId, char* bufPtr, long int* sizePtr)
{
  u16 ioSelector;
  unsigned int entry;
  char* tx_buff;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  if (devPtr->isInitialised)
  {
    devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
    netPtr = (IOAm79c97x*)devPciPtr->extension;
    ioSelector = devPciPtr->memMapped32[0].selector;

    entry = netPtr->txQueueHead & AM79C97X_TX_RING_MOD_MASK;

    if (netPtr->txRing[entry].flags & 0x8000 || // If OWN=1
        netPtr->txQueueHead >= (netPtr->txQueueTail+AM79C97X_TX_RING_SIZE-1))
    {
      *sizePtr = 0;
      return IO_WRITEFAILED;
    }
    // Translate from Physical to Kernel Address
    tx_buff = (char*)(PHYS_TO_K0SEG(netPtr->txRing[entry].baseAddr));
    byteCopy(tx_buff, bufPtr, *sizePtr);

    netPtr->txRing[entry].msg_length = -(*sizePtr);
    netPtr->txRing[entry].flags2 = 0x00000000;
    netPtr->txRing[entry].flags |= 0xA300; // OWN=1, ADD_FCS=1 and STP=ENP=1

    netPtr->txQueueHead++;
    //PCIprintf("NET : write, Next Queue Head = %i\n", netPtr->txQueueHead & AM79C97X_TX_RING_MOD_MASK);
    netPtr->stats.tx_bytes += *sizePtr;

    // Trigger an immediate send poll :
    __writel(ioSelector, AM79C97X_RAP, 0);
    __writel(ioSelector, AM79C97X_RDP, 0x00000048);

    return IO_WRITEOK;
  }
  else
  {
    *sizePtr = 0;
    return IO_WRITEFAILED;
  }
}


void am79c97x_interrupt(IODevice devPtr)
{
  u16 ioSelector;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;
  u32 csr0;
  int must_restart;
  int boguscnt = 10;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;
  ioSelector = devPciPtr->memMapped32[0].selector;

  __writel(ioSelector, AM79C97X_RAP, 0);
  csr0 = __readl(ioSelector, AM79C97X_RDP);

  /* Check if we are in the STOP state */
  if (csr0 & 0x00000004)
    return;

  /* CSR0 : 0x8600 = 1000 0110 0000 0000 : ERR , RINT, TINT
   * check for error, receive and transmit interrupts
   */
  while ((csr0 & 0x00008600) && (--boguscnt >= 0))
  {
    /* Acknowledge all of the current interrupt sources,
     * and reset IENA (Interrupt Enable) bit.
     * ~0x004f = 1111 1111 1011 0000 
     * (Writing a 1 to a set interrupt flag clears it !)
     */
    __writel(ioSelector, AM79C97X_RDP, csr0 & ~0x0000004f);

    must_restart = 0;

    // Log misc errors
    if (csr0 & 0x00004000) // Babble Error=Transmitter time-out error. If the transmitter
      // sends 1519 or more bytes, BABL will be set.
    {
      //PCIprintf("NET : Tx Babble error.\n");
      netPtr->stats.tx_errors++; // Tx babble
    }
    if (csr0 & 0x00001000)
    {
      //PCIprintf("NET : Missed Rx Frame.\n");
      netPtr->stats.rx_errors++; // Missed a Rx frame
    }
    if (csr0 & 0x00000800)
    {
      PCIprintf("NET : Bus master arbitration failure, CSR0=%x\n", csr0);
      must_restart = 1; // Restart the Chip
    }

    if (csr0 & 0x00000400) // Rx interrupt
    {
      //PCIprintf("NET : Entering RX ISR.\n");
      must_restart = am79c97xRxISR(devPtr);
    }

    if (csr0 & 0x00000200) // Tx-done interrupt
    {
      //PCIprintf("NET : Entering TX-done ISR.\n");
      must_restart = am79c97xTxDoneISR(devPtr);
    }

    if (must_restart)
    {
      must_restart = 0;
      ERROR(strMustRestart);
      am79c97xRestart(devPtr);
      return;
    }
    csr0 = __readl(ioSelector, AM79C97X_RDP);
  }
  /* Clear any other interrupt (except RINT, TINT) and set interrupt enable
   * 0x7940 = 0111 1001 0100 0000 
   */
  __writel(ioSelector, AM79C97X_RAP, 0);
  __writel(ioSelector, AM79C97X_RDP, 0x00007940);
}


int am79c97xRxISR(IODevice devPtr)
{
  u16 pkt_len;
  unsigned int entry;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;
  Message msg;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;

  entry = netPtr->rxQueueHead & AM79C97X_RX_RING_MOD_MASK;

  // If we own the next entry, it's a new packet :
  while (!(netPtr->rxRing[entry].flags & 0x8000) && // while OWN=0
         netPtr->rxQueueHead < (netPtr->rxQueueTail+AM79C97X_RX_RING_SIZE))
  {
    if (netPtr->rxRing[entry].flags & 0x4000) // There was an error, packet is dropped later in am79c97x_read
    {
      if (netPtr->rxRing[entry].flags & 0x0100)	// Only count a general error at the
        netPtr->stats.rx_errors++;          	// end of a packet
      if (netPtr->rxRing[entry].flags & 0x2000)
        netPtr->stats.rx_frame_errors++; // if FRAM set
      if (netPtr->rxRing[entry].flags & 0x1000)
        netPtr->stats.rx_over_errors++; // if OFLO set
      if (netPtr->rxRing[entry].flags & 0x0800)
        netPtr->stats.rx_crc_errors++; // if CRC set
      if (netPtr->rxRing[entry].flags & 0x0400)
        netPtr->stats.rx_fifo_errors++; // if BUFF set
      //PCIprintf("Error in RxISR, Flags : 0x%04x\n", netPtr->rxRing[entry].flags);
    }
    else
    {
      pkt_len = netPtr->rxRing[entry].msg_length - 4; // pkt_len is without 4 bytes FCS (CRC)

      if(pkt_len < 60)
      {
        //PCIprintf("NET : Runt packet!\n");
        netPtr->stats.rx_errors++;
      }
      else
      {
        if (netPtr->subscribed != 0)
        {
          // Send notify message to subscribed thread.
          msg.from = devPtr->threadId;
          msg.id   = IO_NOTIFY;
          msg.msg.ioNotify.deviceNumber = IO_AM79C97X;

          msg.msg.ioNotify.length = pkt_len;
          if (kSend(netPtr->subscribed, &msg) != TM_OK)
            ERROR("am79c97xRxISR : kSend failed.\n");
        }
        netPtr->stats.rx_bytes += pkt_len;
        netPtr->stats.rx_packets++;
      }
    }

    // Pay attention to don't reach the queue tail(queue is circular)!
    // -> let a margin of three empty (OWN=1) receive descriptors by dropping old (still unread) entries.
    if (lockTry(rxQueueTailLock)) // Try exclusive access to rxQueueTail.
    {
      while (netPtr->rxQueueHead >= (netPtr->rxQueueTail+AM79C97X_RX_RING_SIZE-2))
      {
        netPtr->stats.rx_dropped++;
        netPtr->rxRing[netPtr->rxQueueTail & AM79C97X_RX_RING_MOD_MASK].flags |= 0x8000; // Set OWN=1
        netPtr->rxQueueTail++;
        //PCIprintf("NET : RxISR, RX-Dropped, Next RX Queue Tail = %i\n", netPtr->rxQueueTail & AM79C97X_RX_RING_MOD_MASK);
      }
      unlock(rxQueueTailLock);
    }

    entry = (++netPtr->rxQueueHead) & AM79C97X_RX_RING_MOD_MASK;
    //PCIprintf("NET : RxISR, Next Queue Head = %i\n", entry);
  }
  return 0; // Don't restart Chip
}


int am79c97xTxDoneISR(IODevice devPtr)
{
  unsigned int entry;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;

  entry = netPtr->txQueueTail & AM79C97X_TX_RING_MOD_MASK;

  while ( (netPtr->txQueueTail < netPtr->txQueueHead) // Do not reach Queue Head!
          && !(netPtr->txRing[entry].flags & 0x8000) ) // If OWN=0
  {
    if (netPtr->txRing[entry].flags & 0x4000)
    {
      // There was a major error, log it
      netPtr->stats.tx_errors++;
      if (netPtr->txRing[entry].flags2 & 0x04000000)
        netPtr->stats.tx_aborted_errors++;
      if (netPtr->txRing[entry].flags2 & 0x08000000)
        netPtr->stats.tx_carrier_errors++;
      if (netPtr->txRing[entry].flags2 & 0x10000000)
        netPtr->stats.tx_window_errors++;
      if (netPtr->txRing[entry].flags2 & 0x40000000)
      {
        // Ack! On FIFO errors the Tx unit is turned off!
        netPtr->stats.tx_fifo_errors++;
        PCIprintf("NET : Tx FIFO error\n");
        return 1; // Restart the chip
      }
    }
    else
    {
      if (netPtr->txRing[entry].flags & 0x1800)
        netPtr->stats.collisions++;
      netPtr->stats.tx_packets++;
    }

    entry = (++netPtr->txQueueTail) & AM79C97X_TX_RING_MOD_MASK;
    //PCIprintf("NET : TxDoneISR, Next Queue Tail = %i\n", entry);
  }
  return 0; // Don't restart the Chip
}


Error am79c97x_close(IODevice devPtr)
{
  u16 ioSelector;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  if (devPtr->isInitialised)
  {
    devPtr->isInitialised = FALSE;
    devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
    netPtr = (IOAm79c97x*)devPciPtr->extension;
    ioSelector = devPciPtr->memMapped32[0].selector;

    // Set the missed frame count
    __writel(ioSelector, AM79C97X_RAP, 112);
    netPtr->stats.rx_missed_errors = __readl(ioSelector, AM79C97X_RDP);

    // Stop the Controller
    __writel(ioSelector, AM79C97X_RAP, 0);
    __writel(ioSelector, AM79C97X_RDP, 0x00000004);

    // Free all the buffs in the Rx and Tx Descriptor Rings
    if (!am79c97xPurgeRxRing(devPtr))
      return IO_CLOSEFAILED;
    if (!am79c97xPurgeTxRing(devPtr))
      return IO_CLOSEFAILED;

    if (vmFree((Address)netUnalignedPtr) == VM_FREEFAILED)
    {
      ERROR(strMainStructFreeFailed);
      return IO_CLOSEFAILED;
    }

    if (!ioPciDeviceDetach(devPtr))
    {
      ERROR(strDetachFailed);
      return IO_CLOSEFAILED;
    }

    netUnalignedPtr = (unsigned int)NULL;
  }
  return IO_CLOSEOK;
}


void am79c97x_handleMsg(IODevice devPtr, Message *msg)
{
  Message reply;
  IOPciDeviceDesc* devPciPtr;
  IOAm79c97x *netPtr;

  devPciPtr = (IOPciDeviceDesc*)devPtr->extension;
  netPtr = (IOAm79c97x*)devPciPtr->extension;
  reply.from = devPtr->threadId;
  CLEAR_MSG(reply);

  switch (msg->id)
  {
  case IO_SUBSCRIBE:
    reply.id = IO_SUBSCRIBEREPLY;
    if(netPtr->subscribed == 0)
    {
      reply.msg.ioSubscribeReply.errorCode = IO_SUBSCRIBEOK;
      netPtr->subscribed = msg->from;
    }
    else
    {
      reply.msg.ioSubscribeReply.errorCode = IO_SUBSCRIBEFAILED;
    }
    tmMsgSend(msg->from, &reply);
    break;
  case IO_GETADDR:
    reply.id = IO_GETADDRREPLY;
    reply.msg.ioGetAddrReply.addr[0] = netPtr->macAddr[0];
    reply.msg.ioGetAddrReply.addr[1] = netPtr->macAddr[1];
    reply.msg.ioGetAddrReply.addr[2] = netPtr->macAddr[2];
    reply.msg.ioGetAddrReply.addr[3] = netPtr->macAddr[3];
    reply.msg.ioGetAddrReply.addr[4] = netPtr->macAddr[4];
    reply.msg.ioGetAddrReply.addr[5] = netPtr->macAddr[5];
    tmMsgSend(msg->from, &reply);
    break;
  default:
    reply.id = UNKNOWN_SYSCALL;
    tmMsgSend(msg->from, &reply);
    break;
  }
}
