Goto sanos source index

//
// pcnet32.c
//
// PCnet32 network driver
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os/krnl.h>

#define WMWARE

// PCI IDs

#define PCI_DEVICE_PCNET32      0x2000
#define PCI_VENDOR_AMD          0x1022

// Offsets from base I/O address

#define PCNET32_WIO_RDP         0x10 // Register data port
#define PCNET32_WIO_RAP         0x12 // Register address port
#define PCNET32_WIO_RESET       0x14
#define PCNET32_WIO_BDP         0x16

#define PCNET32_DWIO_RDP        0x10
#define PCNET32_DWIO_RAP        0x14
#define PCNET32_DWIO_RESET      0x18
#define PCNET32_DWIO_BDP        0x1C

// Controller Status Registers

#define CSR                     0
#define INIT_BLOCK_ADDRESS_LOW  1
#define INIT_BLOCK_ADDRESS_HIGH 2
#define INTERRUPT_MASK          3
#define FEATURE_CONTROL         4
#define CHIP_ID_LOWER           88
#define CHIP_ID_UPPER           89

// Controller Status Register (CSR0) Bits

#define CSR_ERR                 0x8000
#define CSR_BABL                0x4000
#define CSR_CERR                0x2000
#define CSR_MISS                0x1000
#define CSR_MERR                0x0800
#define CSR_RINT                0x0400
#define CSR_TINT                0x0200
#define CSR_IDON                0x0100
#define CSR_INTR                0x0080
#define CSR_IENA                0x0040
#define CSR_RXON                0x0020
#define CSR_TXON                0x0010
#define CSR_TDMD                0x0008
#define CSR_STOP                0x0004
#define CSR_STRT                0x0002
#define CSR_INIT                0x0001

// Miscellaneous Configuration (BCR2)

#define MISCCFG                 2
#define MISCCFG_TMAULOOP        0x4000
#define MISCCFG_APROMWE         0x0100
#define MISCCFG_INTLEVEL        0x0080
#define MISCCFG_DXCVRCTL        0x0020
#define MISCCFG_DXCVRPOL        0x0010
#define MISCCFG_EADISEL         0x0008
#define MISCCFG_AWAKE           0x0004
#define MISCCFG_ASEL            0x0002
#define MISCCFG_XMAUSEL         0x0001
#define MISCCFG_                0x0000
#define MISCCFG_                0x0000

// Transmit Descriptor (TMD) Status

#define TMD_OWN                 0x8000
#define TMD_ERR                 0x4000
#define TMD_ADD_FCS             0x2000  // ADD_FCS and NO_FCS is controlled through the same bit
#define TMD_NO_FCS              0x2000
#define TMD_MORE                0x1000  // MORE and LTINT is controlled through the same bit
#define TMD_LTINT               0x1000
#define TMD_ONE                 0x0800
#define TMD_DEF                 0x0400
#define TMD_STP                 0x0200
#define TMD_ENP                 0x0100
#define TMD_BPE                 0x0080
#define TMD_RES                 0x007F

// Receive Descriptor (RMD) Status

#define RMD_OWN                 0x8000
#define RMD_ERR                 0x4000
#define RMD_FRAM                0x2000
#define RMD_OFLO                0x1000
#define RMD_CRC                 0x0800
#define RMD_BUFF                0x0400
#define RMD_STP                 0x0200
#define RMD_ENP                 0x0100
#define RMD_BPE                 0x0080
#define RMD_PAM                 0x0040
#define RMD_LAFM                0x0020
#define RMD_BAM                 0x0010

// Size of Tx and Rx rings

#ifndef PCNET32_LOG_TX_BUFFERS
#define PCNET32_LOG_TX_BUFFERS  4
#define PCNET32_LOG_RX_BUFFERS  5
#endif

#define TX_RING_SIZE            (1 << (PCNET32_LOG_TX_BUFFERS))
#define TX_RING_MOD_MASK        (TX_RING_SIZE - 1)
#define TX_RING_LEN_BITS        ((PCNET32_LOG_TX_BUFFERS) << 12)

#define RX_RING_SIZE            (1 << (PCNET32_LOG_RX_BUFFERS))
#define RX_RING_MOD_MASK        (RX_RING_SIZE - 1)
#define RX_RING_LEN_BITS        ((PCNET32_LOG_RX_BUFFERS) << 4)

#define ETHER_FRAME_LEN         1544
#define TX_TIMEOUT              5000

#pragma pack(push, 1)

// Rx and Tx ring descriptors

struct pcnet32_rx_head {
  unsigned long buffer;
  short length;
  unsigned short status;
  unsigned long msg_length;
  unsigned long reserved;
};
        
struct pcnet32_tx_head {
  unsigned long buffer;
  short length;
  unsigned short status;
  unsigned long misc;
  unsigned long reserved;
};

// Initialization block

struct pcnet32_init_block {
  unsigned short mode;
  unsigned short tlen_rlen;
  unsigned char phys_addr[6];
  unsigned short reserved;
  unsigned long filter[2];
  // Receive and transmit ring base, along with extra bits
  unsigned long rx_ring;
  unsigned long tx_ring;
};

#pragma pack(pop)

// PCnet32 access functions

struct pcnet32_access {
  unsigned short (*read_csr)(unsigned short, int);
  void (*write_csr)(unsigned short, int, unsigned short);
  unsigned short (*read_bcr)(unsigned short, int);
  void (*write_bcr)(unsigned short, int, unsigned short);
  unsigned short (*read_rap)(unsigned short);
  void (*write_rap)(unsigned short, unsigned short);
  void (*reset)(unsigned short);
};

struct pcnet32 {
  struct pcnet32_rx_head rx_ring[RX_RING_SIZE];
  struct pcnet32_tx_head tx_ring[TX_RING_SIZE];
  struct pcnet32_init_block init_block;
  struct pbuf *rx_buffer[RX_RING_SIZE];
  struct pbuf *tx_buffer[TX_RING_SIZE];

  unsigned long phys_addr;              // Physical address of this structure
  dev_t devno;                          // Device number

  unsigned short iobase;                // Configured I/O base
  unsigned short irq;                   // Configured IRQ
  unsigned short membase;               // Configured memory base

  unsigned long next_rx;                // Next entry to receive

  struct sem sem_tx;
  unsigned long size_tx;                // Number of outstanding transmit ring entries
  unsigned long curr_tx;                // First outstanding transmit ring entry
  unsigned long next_tx;                // Next transmit ring entry

  struct interrupt intr;                // Interrupt object for driver
  struct dpc dpc;                       // DPC for driver

  struct event rdc;                     // Remote DMA completed event
  struct event ptx;                     // Packet transmitted event

  struct eth_addr hwaddr;               // MAC address for NIC

  // Access functions
  unsigned short (*read_csr)(unsigned short, int);
  void (*write_csr)(unsigned short, int, unsigned short);
  unsigned short (*read_bcr)(unsigned short, int);
  void (*write_bcr)(unsigned short, int, unsigned short);
  unsigned short (*read_rap)(unsigned short);
  void (*write_rap)(unsigned short, unsigned short);
  void (*reset)(unsigned short);
};

struct netstats *netstats;

static void dump_csr(unsigned short csr) {
  kprintf("CRS: ");
  if (csr & CSR_ERR) kprintf(" ERR");
  if (csr & CSR_BABL) kprintf(" BABL");
  if (csr & CSR_CERR) kprintf(" CERR");
  if (csr & CSR_MISS) kprintf(" MISS");
  if (csr & CSR_MERR) kprintf(" MERR");
  if (csr & CSR_RINT) kprintf(" RINT");
  if (csr & CSR_TINT) kprintf(" TINT");
  if (csr & CSR_IDON) kprintf(" IDON");
  if (csr & CSR_INTR) kprintf(" INTR");
  if (csr & CSR_IENA) kprintf(" IENA");
  if (csr & CSR_RXON) kprintf(" RXON");
  if (csr & CSR_TXON) kprintf(" TXON");
  if (csr & CSR_TDMD) kprintf(" TDMD");
  if (csr & CSR_STOP) kprintf(" STOP");
  if (csr & CSR_STRT) kprintf(" STRT");
  if (csr & CSR_INIT) kprintf(" INIT");
  kprintf("\n");
}

static unsigned short pcnet32_wio_read_csr(unsigned short addr, int index) {
  outpw((unsigned short) (addr + PCNET32_WIO_RAP), (unsigned short) index);
  return inpw((unsigned short) (addr + PCNET32_WIO_RDP));
}

static void pcnet32_wio_write_csr(unsigned short addr, int index, unsigned short val) {
  outpw((unsigned short) (addr + PCNET32_WIO_RAP), (unsigned short) index);
  outpw((unsigned short) (addr + PCNET32_WIO_RDP), val);
}

static unsigned short pcnet32_wio_read_bcr(unsigned short addr, int index) {
  outpw((unsigned short) (addr + PCNET32_WIO_RAP), (unsigned short) index);
  return inpw((unsigned short) (addr + PCNET32_WIO_BDP));
}

static void pcnet32_wio_write_bcr(unsigned short addr, int index, unsigned short val) {
  outpw((unsigned short) (addr + PCNET32_WIO_RAP), (unsigned short) index);
  outpw((unsigned short) (addr + PCNET32_WIO_BDP), val);
}

static unsigned short pcnet32_wio_read_rap(unsigned short addr) {
  return inpw((unsigned short) (addr + PCNET32_WIO_RAP));
}

static void pcnet32_wio_write_rap(unsigned short addr, unsigned short val) {
  outpw((unsigned short) (addr + PCNET32_WIO_RAP), val);
}

static void pcnet32_wio_reset(unsigned short addr) {
  inpw((unsigned short) (addr + PCNET32_WIO_RESET));
}

static int pcnet32_wio_check(unsigned short addr) {
  outpw((unsigned short) (addr + PCNET32_WIO_RAP), 88);
  return inpw((unsigned short) (addr + PCNET32_WIO_RAP)) == 88;
}

static struct pcnet32_access pcnet32_wio = {
  pcnet32_wio_read_csr,
  pcnet32_wio_write_csr,
  pcnet32_wio_read_bcr,
  pcnet32_wio_write_bcr,
  pcnet32_wio_read_rap,
  pcnet32_wio_write_rap,
  pcnet32_wio_reset
};

static unsigned short pcnet32_dwio_read_csr(unsigned short addr, int index) {
  outpd((unsigned short) (addr + PCNET32_DWIO_RAP), index);
  return (unsigned short) (inpd((unsigned short) (addr + PCNET32_DWIO_RDP)) & 0xffff);
}

static void pcnet32_dwio_write_csr (unsigned short addr, int index, unsigned short val) {
  outpd((unsigned short) (addr + PCNET32_DWIO_RAP), index);
  outpd((unsigned short) (addr + PCNET32_DWIO_RDP), val);
}

static unsigned short pcnet32_dwio_read_bcr(unsigned short addr, int index) {
  outpd((unsigned short) (addr + PCNET32_DWIO_RAP), index);
  return (unsigned short) inpd((unsigned short) (addr + PCNET32_DWIO_BDP)) & 0xffff;
}

static void pcnet32_dwio_write_bcr(unsigned short addr, int index, unsigned short val) {
  outpd((unsigned short) (addr + PCNET32_DWIO_RAP), index);
  outpd((unsigned short) (addr + PCNET32_DWIO_BDP), val);
}

static unsigned short pcnet32_dwio_read_rap(unsigned short addr) {
  return (unsigned short) inpd((unsigned short) (addr + PCNET32_DWIO_RAP)) & 0xffff;
}

static void pcnet32_dwio_write_rap(unsigned short addr, unsigned short val) {
  outpd((unsigned short) (addr + PCNET32_DWIO_RAP), val);
}

static void pcnet32_dwio_reset(unsigned short addr) {
  inpd((unsigned short) (addr + PCNET32_DWIO_RESET));
}

static int pcnet32_dwio_check(unsigned short addr) {
  outpd((unsigned short) (addr + PCNET32_DWIO_RAP), 88);
  return inpd((unsigned short) (addr + PCNET32_DWIO_RAP)) == 88;
}

static struct pcnet32_access pcnet32_dwio = {
  pcnet32_dwio_read_csr,
  pcnet32_dwio_write_csr,
  pcnet32_dwio_read_bcr,
  pcnet32_dwio_write_bcr,
  pcnet32_dwio_read_rap,
  pcnet32_dwio_write_rap,
  pcnet32_dwio_reset
};

int pcnet32_transmit(struct dev *dev, struct pbuf *p) {
  struct pcnet32 *pcnet32 = dev->privdata;
  int len;
  int left;
  struct pbuf *q;
  int entry = pcnet32->next_tx;
  unsigned short status;

  //kprintf("pcnet32: transmit packet len=%d\n", p->tot_len);

  if (!p || p->len == 0) return -EINVAL;

#ifdef WMWARE
  if (wait_for_object(&pcnet32->sem_tx, TX_TIMEOUT) < 0) {
    kprintf("pcnet32: transmit timeout, drop packet\n");
    netstats->link.drop++;
    return -ETIMEOUT;
  }

  p = pbuf_linearize(PBUF_RAW, p);
#else
  // Wait for free entries in transmit ring
  for (q = p; q != NULL; q = q->next) {
    if (q->len > 0) {
      if (wait_for_object(&pcnet32->sem_tx, TX_TIMEOUT) < 0) {
        kprintf("pcnet32: transmit timeout, drop packet\n");
        netstats->link.drop++;
        return -ETIMEOUT;
      }
    }
  }
#endif

  // Set Start of Packet (STP) in first buffer
  status = TMD_OWN | TMD_STP;

  // Place each part of the packet in the transmit ring
  left = p->tot_len;
  for (q = p; q != NULL; q = q->next) {
    len = q->len;
    left -= len;
    if (len > 0) {
      //kprintf("pcnet32_transmit: sending %d (entry = %d)\n", len, entry);

      // Set End of Packet if this is the last part
      if (left == 0) status |= TMD_ENP;
      pcnet32->tx_buffer[entry] = p == q ? p : NULL;
      pcnet32->tx_ring[entry].buffer = virt2phys(q->payload);
      pcnet32->tx_ring[entry].length = -len;
      pcnet32->tx_ring[entry].misc = 0x00000000;
      pcnet32->tx_ring[entry].status = status;

      // Move to next entry
      entry = (++entry) & TX_RING_MOD_MASK;
      pcnet32->next_tx = entry;
      pcnet32->size_tx++;

      // This is not first buffer so Start of Packet (STP) is not set
      status = TMD_OWN;
    }

    // Trigger an immediate send poll
    pcnet32->write_csr(pcnet32->iobase, CSR, CSR_IENA | CSR_TDMD);
  }

  return 0;
}

void pcnet32_receive(struct pcnet32 *pcnet32) {
  int entry = pcnet32->next_rx;
  struct pbuf *p;

  //kprintf("pcnet32: receive entry %d, 0x%04X\n", entry, pcnet32->rx_ring[entry].status);

  while (!(pcnet32->rx_ring[entry].status & RMD_OWN)) {
    int status = pcnet32->rx_ring[entry].status >> 8;

    //kprintf("receive entry %d, %d\n", entry, status);

    if (status != 0x03) {
      // Error
      kprintf("pcnet32: rx error\n");
      netstats->link.err++;
    } else {
      short len = (short) (pcnet32->rx_ring[entry].msg_length & 0xfff) - 4;
      //kprintf("length %d\n", pkt_len);
      
      // Get packet
      p = pcnet32->rx_buffer[entry];

      // Allocate new packet for receive ring
      pcnet32->rx_buffer[entry] = pbuf_alloc(PBUF_RAW, ETHER_FRAME_LEN, PBUF_RW);
      if (pcnet32->rx_buffer[entry]) {
        // Give ownership back to card
        pcnet32->rx_ring[entry].buffer = virt2phys(pcnet32->rx_buffer[entry]->payload);
        pcnet32->rx_ring[entry].length = -ETHER_FRAME_LEN; // Note 1
        pcnet32->rx_ring[entry].status |= RMD_OWN;
      } else {
        kprintf(KERN_WARNING "pcnet32: unable to allocate packet for receive ring\n");
        netstats->link.memerr++;
      }

      if (p) {
        // Resize packet buffer
        pbuf_realloc(p, len);

        // Send packet to upper layer
        if (dev_receive(pcnet32->devno, p) < 0) pbuf_free(p);
      }

      // Move to next entry
      entry = (++entry) & RX_RING_MOD_MASK;
      pcnet32->next_rx = entry;
    }
  }
}

void pcnet32_transmitted(struct pcnet32 *pcnet32) {
  int entry = pcnet32->curr_tx;
  int freed = 0;

  while (pcnet32->size_tx > 0 && (pcnet32->rx_ring[entry].status & TMD_OWN) != 0) {
    if (pcnet32->tx_buffer[entry]) {
      pbuf_free(pcnet32->tx_buffer[entry]);
      pcnet32->tx_buffer[entry] = NULL;
    }
    freed++;

    // Move to next entry
    entry = (++entry) & TX_RING_MOD_MASK;
    pcnet32->curr_tx = entry;
    pcnet32->size_tx--;
  }

  //kprintf("pcnet32: release %d tx entries\n", freed);

  release_sem(&pcnet32->sem_tx, freed);
}

void pcnet32_dpc(void *arg) {
  struct pcnet32 *pcnet32 = (struct pcnet32 *) arg;
  unsigned short iobase = pcnet32->iobase;
  unsigned short csr;

  while ((csr = pcnet32->read_csr(iobase, CSR)) & (CSR_ERR | CSR_RINT | CSR_TINT)) {
    // Acknowledge all of the current interrupt sources
    pcnet32->write_csr(iobase, CSR, (unsigned short) (csr & ~(CSR_IENA | CSR_TDMD | CSR_STOP | CSR_STRT | CSR_INIT)));
    //dump_csr(csr);

    if (csr & CSR_RINT) pcnet32_receive(pcnet32);
    if (csr & CSR_TINT) pcnet32_transmitted(pcnet32);
  }

  //dump_csr(csr);

  //kprintf("pcnet32: intr (csr0 = %08x)\n", pcnet32->read_csr(pcnet32->iobase, 0));
  //dump_csr(pcnet32->read_csr(pcnet32->iobase, 0));

  
  pcnet32->write_csr(iobase, CSR, CSR_BABL | CSR_CERR | CSR_MISS | CSR_MERR | CSR_IDON | CSR_IENA);

  eoi(pcnet32->irq);
}

int pcnet32_handler(struct context *ctxt, void *arg) {
  struct pcnet32 *pcnet32 = (struct pcnet32 *) arg;

  // Queue DPC to service interrupt
  queue_irq_dpc(&pcnet32->dpc, pcnet32_dpc, pcnet32);
  return 0;
}

int pcnet32_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
  return -ENOSYS;
}

int pcnet32_attach(struct dev *dev, struct eth_addr *hwaddr) {
  struct pcnet32 *pcnet32 = dev->privdata;
  *hwaddr = pcnet32->hwaddr;

  return 0;
}

int pcnet32_detach(struct dev *dev) {
  return 0;
}

struct driver pcnet32_driver = {
  "pcnet32",
  DEV_TYPE_PACKET,
  pcnet32_ioctl,
  NULL,
  NULL,
  pcnet32_attach,
  pcnet32_detach,
  pcnet32_transmit
};

int __declspec(dllexport) install(struct unit *unit) {
  struct pcnet32 *pcnet32;
  int version;
  int i;
  char str[20];
  unsigned short val;
  unsigned long init_block;

  // Check for PCI device
  if (unit->bus->bustype != BUSTYPE_PCI) return -EINVAL;

  // Allocate device structure
  pcnet32 = (struct pcnet32 *) kmalloc(sizeof(struct pcnet32));
  if (!pcnet32) return -ENOMEM;
  memset(pcnet32, 0, sizeof(struct pcnet32));
  pcnet32->phys_addr = virt2phys(pcnet32);

  // Setup NIC configuration
  pcnet32->iobase = (unsigned short) get_unit_iobase(unit);
  pcnet32->irq = (unsigned short) get_unit_irq(unit);
  pcnet32->membase = (unsigned short) (unsigned long) get_unit_membase(unit);

  // Enable bus mastering
  pci_enable_busmastering(unit);
  
  // Reset the chip
  pcnet32_dwio_reset(pcnet32->iobase);
  pcnet32_wio_reset(pcnet32->iobase);

  // Setup access functions
  if (pcnet32_wio_read_csr(pcnet32->iobase, 0) == 4 && pcnet32_wio_check(pcnet32->iobase)) {
    pcnet32->read_csr = pcnet32_wio.read_csr;
    pcnet32->write_csr = pcnet32_wio.write_csr;
    pcnet32->read_bcr = pcnet32_wio.read_bcr;
    pcnet32->write_bcr = pcnet32_wio.write_bcr;
    pcnet32->read_rap = pcnet32_wio.read_rap;
    pcnet32->write_rap = pcnet32_wio.write_rap;
    pcnet32->reset = pcnet32_wio.reset;
  } else {
    if (pcnet32_dwio_read_csr(pcnet32->iobase, 0) == 4 && pcnet32_dwio_check(pcnet32->iobase)) {
      pcnet32->read_csr = pcnet32_dwio.read_csr;
      pcnet32->write_csr = pcnet32_dwio.write_csr;
      pcnet32->read_bcr = pcnet32_dwio.read_bcr;
      pcnet32->write_bcr = pcnet32_dwio.write_bcr;
      pcnet32->read_rap = pcnet32_dwio.read_rap;
      pcnet32->write_rap = pcnet32_dwio.write_rap;
      pcnet32->reset = pcnet32_dwio.reset;
    } else {
      return -EIO;
    }
  }

  version = pcnet32->read_csr(pcnet32->iobase, CHIP_ID_LOWER) | (pcnet32_wio_read_csr(pcnet32->iobase, CHIP_ID_UPPER) << 16);
  //kprintf("PCnet chip version is %#x.\n", version);
  if ((version & 0xfff) != 0x003) return 0;
  version = (version >> 12) & 0xffff;
  switch (version) {
    case 0x2420:
      unit->vendorname = "AMD";
      unit->productname = "AMD PCI 79C970";
      break;

    case 0x2430:
      unit->vendorname = "AMD";
      unit->productname = "AMD PCI 79C970";
      break;

    case 0x2621:
      unit->vendorname = "AMD";
      unit->productname = "AMD PCI II 79C970A";
      //fdx = 1;
      break;

    case 0x2623:
      unit->vendorname = "AMD";
      unit->productname = "AMD FAST 79C971";
      //fdx = 1; mii = 1; fset = 1;
      //ltint = 1;
      break;

    case 0x2624:
      unit->vendorname = "AMD";
      unit->productname = "AMD FAST+ 79C972";
      //fdx = 1; mii = 1; fset = 1;
      break;

    case 0x2625:
      unit->vendorname = "AMD";
      unit->productname = "AMD FAST III 79C973";
      //fdx = 1; mii = 1;
      break;

    case 0x2626:
      unit->vendorname = "AMD";
      unit->productname = "AMD Home 79C978";
      //fdx = 1;
      break;

    case 0x2627:
      unit->vendorname = "AMD";
      unit->productname = "AMD FAST III 79C975";
      //fdx = 1; mii = 1;
      break;

    default:
      kprintf("pcnet32: PCnet version %#x, no PCnet32 chip.\n", version);
      return 0;
  }

  // Install interrupt handler
  init_dpc(&pcnet32->dpc);
  register_interrupt(&pcnet32->intr, IRQ2INTR(pcnet32->irq), pcnet32_handler, pcnet32);
  enable_irq(pcnet32->irq);

  // Read MAC address from PROM
  for (i = 0; i < ETHER_ADDR_LEN; i++) pcnet32->hwaddr.addr[i] = (unsigned char) inp((unsigned short) (pcnet32->iobase + i));

  // Setup the init block
  //pcnet32->init_block.mode = 0x0003;
  //pcnet32->init_block.mode = 0x8000;
  //pcnet32->init_block.mode = 0x0000;
  pcnet32->init_block.mode = 0x0080; // ASEL
  pcnet32->init_block.tlen_rlen = TX_RING_LEN_BITS | RX_RING_LEN_BITS;
  for (i = 0; i < 6; i++) pcnet32->init_block.phys_addr[i] = pcnet32->hwaddr.addr[i];
  pcnet32->init_block.filter[0] = 0x00000000;
  pcnet32->init_block.filter[1] = 0x00000000;
  pcnet32->init_block.rx_ring = pcnet32->phys_addr + offsetof(struct pcnet32, rx_ring);
  pcnet32->init_block.tx_ring = pcnet32->phys_addr + offsetof(struct pcnet32, tx_ring);

  // Allocate receive ring
  for (i = 0; i < RX_RING_SIZE; i++) {
    pcnet32->rx_buffer[i] = pbuf_alloc(PBUF_RAW, ETHER_FRAME_LEN, PBUF_RW);
    pcnet32->rx_ring[i].buffer = virt2phys(pcnet32->rx_buffer[i]->payload);
    pcnet32->rx_ring[i].length = (short) -ETHER_FRAME_LEN;
    pcnet32->rx_ring[i].status = RMD_OWN;
  }
  pcnet32->next_rx = 0;

  // Initialize transmit ring
  for (i = 0; i < TX_RING_SIZE; i++) {
    pcnet32->tx_buffer[i] = NULL;
    pcnet32->tx_ring[i].buffer = 0;
    pcnet32->tx_ring[i].status = 0;
  }
  init_sem(&pcnet32->sem_tx, TX_RING_SIZE);
  pcnet32->size_tx = 0;
  pcnet32->curr_tx = 0;
  pcnet32->next_tx = 0;

  // Reset pcnet32
  pcnet32->reset(pcnet32->iobase);
  
  /// Switch pcnet32 to 32bit mode
  pcnet32->write_bcr(pcnet32->iobase, 20, 2);

  // Set autoselect bit
  //val = pcnet32->read_bcr(pcnet32->iobase, MISCCFG) & ~MISCCFG_ASEL;
  //val |= MISCCFG_ASEL;
  //pcnet32->write_bcr(pcnet32->iobase, MISCCFG, val);

  // Set full duplex
  val = pcnet32->read_bcr(pcnet32->iobase, 9) & ~3;
  val |= 1;
  pcnet32->write_bcr(pcnet32->iobase, 9, val);

  init_block = pcnet32->phys_addr + offsetof(struct pcnet32, init_block);
  pcnet32->write_csr(pcnet32->iobase, 1, (unsigned short) (init_block & 0xffff));
  pcnet32->write_csr(pcnet32->iobase, 2, (unsigned short) (init_block >> 16));

  pcnet32->write_csr(pcnet32->iobase, 4, 0x0915);
  pcnet32->write_csr(pcnet32->iobase, 0, CSR_INIT);

  i = 0;
  while (i++ < 100) {
    if (pcnet32->read_csr(pcnet32->iobase, 0) & CSR_IDON) break;
  }

  pcnet32->write_csr(pcnet32->iobase, 0, CSR_IENA | CSR_STRT); // note 2

  pcnet32->devno = dev_make("eth#", &pcnet32_driver, unit, pcnet32);

  kprintf(KERN_INFO "%s: %s iobase 0x%x irq %d mac %s\n", device(pcnet32->devno)->name, unit->productname, pcnet32->iobase, pcnet32->irq, ether2str(&pcnet32->hwaddr, str));

  return 0;
}

int __stdcall start(hmodule_t hmod, int reason, void *reserved2) {
  netstats = get_netstats();
  return 1;
}