[gPXE-devel] [PATCH v2][pcnet32] Replace pcnet32 driver

Andrei Faur da3drus at gmail.com
Sat Jun 19 09:27:05 EDT 2010


This patch replaces the old pcnet32 driver with a new one that
uses gPXE's API.

Signed-off-by: Andrei Faur <da3drus at gmail.com>
---
v2:
 * fixed transmit interrupt enable bug
 * removed unused variables (mostly regarding PHY)
 * init_block is now only set once, before the nic is started. Since there
 is no guarantee that the block is not accesed after nic initialization, it
 is left as part of the pcnet32_private structure.
 * various small cleanups mentioned in the previous thread
 
 src/drivers/net/pcnet32.c | 2185 ++++++++++++++++++++++++---------------------
 src/drivers/net/pcnet32.h |  181 ++++
 2 files changed, 1345 insertions(+), 1021 deletions(-)
 rewrite src/drivers/net/pcnet32.c (93%)
 create mode 100644 src/drivers/net/pcnet32.h

diff --git a/src/drivers/net/pcnet32.c b/src/drivers/net/pcnet32.c
dissimilarity index 93%
index 223bc1e..3da7747 100644
--- a/src/drivers/net/pcnet32.c
+++ b/src/drivers/net/pcnet32.c
@@ -1,1021 +1,1164 @@
-/**************************************************************************
-*
-*    pcnet32.c -- Etherboot device driver for the AMD PCnet32
-*    Written 2003-2003 by Timothy Legge <tlegge at rogers.com>
-*
-*    This program is free software; you can redistribute it and/or modify
-*    it under the terms of the GNU General Public License as published by
-*    the Free Software Foundation; either version 2 of the License, or
-*    (at your option) any later version.
-*
-*    This program is distributed in the hope that it will be useful,
-*    but WITHOUT ANY WARRANTY; without even the implied warranty of
-*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-*    GNU General Public License for more details.
-*
-*    You should have received a copy of the GNU General Public License
-*    along with this program; if not, write to the Free Software
-*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*
-*    Portions of this code based on:
-*		pcnet32.c: An AMD PCnet32 ethernet driver for linux:
-*
-*	(C) 1996-1999 Thomas Bogendoerfer
-*		See Linux Driver for full information
-*	
-*	The transmit and poll functions were written with reference to:
-*	lance.c - LANCE NIC driver for Etherboot written by Ken Yap 
-*	
-*	Linux Driver Version 1.27a, 10.02.2002
-* 
-* 
-*    REVISION HISTORY:
-*    ================
-*    v1.0	08-06-2003	timlegge	Initial port of Linux driver
-*    v1.1	08-23-2003	timlegge	Add multicast support
-*    v1.2	01-17-2004	timlegge	Initial driver output cleanup
-*    v1.3	03-29-2004	timlegge	More driver cleanup
-*    
-*    Indent Options: indent -kr -i8
-***************************************************************************/
-
-FILE_LICENCE ( GPL2_OR_LATER );
-
-#include "etherboot.h"
-#include "nic.h"
-#include <gpxe/pci.h>
-#include <gpxe/ethernet.h>
-#include "mii.h"
-
-/* void hex_dump(const char *data, const unsigned int len); */
-
-/* Etherboot Specific definations */
-#define drv_version "v1.3"
-#define drv_date "03-29-2004"
-
-static u32 ioaddr;		/* Globally used for the card's io address */
-static struct nic_operations pcnet32_operations;
-
-#ifdef EDEBUG
-#define dprintf(x) printf x
-#else
-#define dprintf(x)
-#endif
-
-/* Condensed operations for readability. */
-#define virt_to_le32desc(addr)  cpu_to_le32(virt_to_bus(addr))
-#define le32desc_to_virt(addr)  bus_to_virt(le32_to_cpu(addr))
-
-/* End Etherboot Specific */
-
-static int cards_found = 0 /* __initdata */ ;
-
-#ifdef REMOVE
-/* FIXME: Remove these they are probably pointless */
-
-/* 
- * VLB I/O addresses 
- */
-static unsigned int pcnet32_portlist[] /*__initdata */  =
-{ 0x300, 0x320, 0x340, 0x360, 0 };
-
-static int pcnet32_debug = 1;
-static int tx_start = 1;	/* Mapping -- 0:20, 1:64, 2:128, 3:~220 (depends on chip vers) */
-static int pcnet32vlb;		/* check for VLB cards ? */
-
-static struct net_device *pcnet32_dev;
-
-static int max_interrupt_work = 80;
-static int rx_copybreak = 200;
-#endif
-#define PCNET32_PORT_AUI      0x00
-#define PCNET32_PORT_10BT     0x01
-#define PCNET32_PORT_GPSI     0x02
-#define PCNET32_PORT_MII      0x03
-
-#define PCNET32_PORT_PORTSEL  0x03
-#define PCNET32_PORT_ASEL     0x04
-#define PCNET32_PORT_100      0x40
-#define PCNET32_PORT_FD	      0x80
-
-#define PCNET32_DMA_MASK 0xffffffff
-
-/*
- * table to translate option values from tulip
- * to internal options
- */
-static unsigned char options_mapping[] = {
-	PCNET32_PORT_ASEL,	/*  0 Auto-select      */
-	PCNET32_PORT_AUI,	/*  1 BNC/AUI          */
-	PCNET32_PORT_AUI,	/*  2 AUI/BNC          */
-	PCNET32_PORT_ASEL,	/*  3 not supported    */
-	PCNET32_PORT_10BT | PCNET32_PORT_FD,	/*  4 10baseT-FD       */
-	PCNET32_PORT_ASEL,	/*  5 not supported    */
-	PCNET32_PORT_ASEL,	/*  6 not supported    */
-	PCNET32_PORT_ASEL,	/*  7 not supported    */
-	PCNET32_PORT_ASEL,	/*  8 not supported    */
-	PCNET32_PORT_MII,	/*  9 MII 10baseT      */
-	PCNET32_PORT_MII | PCNET32_PORT_FD,	/* 10 MII 10baseT-FD   */
-	PCNET32_PORT_MII,	/* 11 MII (autosel)    */
-	PCNET32_PORT_10BT,	/* 12 10BaseT          */
-	PCNET32_PORT_MII | PCNET32_PORT_100,	/* 13 MII 100BaseTx    */
-	PCNET32_PORT_MII | PCNET32_PORT_100 | PCNET32_PORT_FD,	/* 14 MII 100BaseTx-FD */
-	PCNET32_PORT_ASEL	/* 15 not supported    */
-};
-
-#define MAX_UNITS 8		/* More are supported, limit only on options */
-static int options[MAX_UNITS];
-static int full_duplex[MAX_UNITS];
-
-/*
- *				Theory of Operation
- * 
- * This driver uses the same software structure as the normal lance
- * driver. So look for a verbose description in lance.c. The differences
- * to the normal lance driver is the use of the 32bit mode of PCnet32
- * and PCnetPCI chips. Because these chips are 32bit chips, there is no
- * 16MB limitation and we don't need bounce buffers.
- */
-
-
-
-/*
- * Set the number of Tx and Rx buffers, using Log_2(# buffers).
- * Reasonable default values are 4 Tx buffers, and 16 Rx buffers.
- * That translates to 2 (4 == 2^^2) and 4 (16 == 2^^4).
- */
-#ifndef PCNET32_LOG_TX_BUFFERS
-#define PCNET32_LOG_TX_BUFFERS 1
-#define PCNET32_LOG_RX_BUFFERS 2
-#endif
-
-#define TX_RING_SIZE		(1 << (PCNET32_LOG_TX_BUFFERS))
-#define TX_RING_MOD_MASK	(TX_RING_SIZE - 1)
-/* FIXME: Fix this to allow multiple tx_ring descriptors */
-#define TX_RING_LEN_BITS	0x0000	/*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 PKT_BUF_SZ		1544
-
-/* Offsets from base I/O address. */
-#define PCNET32_WIO_RDP		0x10
-#define PCNET32_WIO_RAP		0x12
-#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
-
-#define PCNET32_TOTAL_SIZE	0x20
-
-/* The PCNET32 Rx and Tx ring descriptors. */
-struct pcnet32_rx_head {
-	u32 base;
-	s16 buf_length;
-	s16 status;
-	u32 msg_length;
-	u32 reserved;
-};
-
-struct pcnet32_tx_head {
-	u32 base;
-	s16 length;
-	s16 status;
-	u32 misc;
-	u32 reserved;
-};
-
-/* The PCNET32 32-Bit initialization block, described in databook. */
-struct pcnet32_init_block {
-	u16 mode;
-	u16 tlen_rlen;
-	u8 phys_addr[6];
-	u16 reserved;
-	u32 filter[2];
-	/* Receive and transmit ring base, along with extra bits. */
-	u32 rx_ring;
-	u32 tx_ring;
-};
-/* PCnet32 access functions */
-struct pcnet32_access {
-	u16(*read_csr) (unsigned long, int);
-	void (*write_csr) (unsigned long, int, u16);
-	 u16(*read_bcr) (unsigned long, int);
-	void (*write_bcr) (unsigned long, int, u16);
-	 u16(*read_rap) (unsigned long);
-	void (*write_rap) (unsigned long, u16);
-	void (*reset) (unsigned long);
-};
-
-/* Define the TX and RX Descriptors and Rings */
-struct {
-	struct pcnet32_tx_head tx_ring[TX_RING_SIZE]
-	__attribute__ ((aligned(16)));
-	struct pcnet32_rx_head rx_ring[RX_RING_SIZE]
-	__attribute__ ((aligned(16)));
-	unsigned char txb[TX_RING_SIZE][PKT_BUF_SZ];
-	unsigned char rxb[RX_RING_SIZE][PKT_BUF_SZ];
-} pcnet32_bufs __shared;
-
-
-/*
- * The first three fields of pcnet32_private are read by the ethernet device 
- * so we allocate the structure should be allocated by pci_alloc_consistent().
- */
-#define MII_CNT 4
-struct pcnet32_private {
-	struct pcnet32_init_block init_block;
-	struct pci_dev *pci_dev;	/* Pointer to the associated pci device structure */
-	const char *name;
-	/* The saved address of a sent-in-place packet/buffer, for skfree(). */
-	struct sk_buff *tx_skbuff[TX_RING_SIZE];
-	struct sk_buff *rx_skbuff[RX_RING_SIZE];
-	struct pcnet32_access a;
-	unsigned int cur_rx, cur_tx;	/* The next free ring entry */
-	char tx_full;
-	int options;
-	int shared_irq:1,	/* shared irq possible */
-	 ltint:1,		/* enable TxDone-intr inhibitor */
-	 dxsuflo:1,		/* disable transmit stop on uflo */
-	 mii:1;			/* mii port available */
-	struct mii_if_info mii_if;
-	unsigned char phys[MII_CNT];
-	struct net_device *next;
-	int full_duplex:1;
-} lpx;
-
-static struct pcnet32_private *lp;
-
-static int mdio_read(struct nic *nic __unused, int phy_id, int reg_num);
-#if 0
-static void mdio_write(struct nic *nic __unused, int phy_id, int reg_num,
-		       int val);
-#endif
-enum pci_flags_bit {
-	PCI_USES_IO = 1, PCI_USES_MEM = 2, PCI_USES_MASTER = 4,
-	PCI_ADDR0 = 0x10 << 0, PCI_ADDR1 = 0x10 << 1, PCI_ADDR2 =
-	    0x10 << 2, PCI_ADDR3 = 0x10 << 3,
-};
-
-
-static u16 pcnet32_wio_read_csr(unsigned long addr, int index)
-{
-	outw(index, addr + PCNET32_WIO_RAP);
-	return inw(addr + PCNET32_WIO_RDP);
-}
-
-static void pcnet32_wio_write_csr(unsigned long addr, int index, u16 val)
-{
-	outw(index, addr + PCNET32_WIO_RAP);
-	outw(val, addr + PCNET32_WIO_RDP);
-}
-
-static u16 pcnet32_wio_read_bcr(unsigned long addr, int index)
-{
-	outw(index, addr + PCNET32_WIO_RAP);
-	return inw(addr + PCNET32_WIO_BDP);
-}
-
-static void pcnet32_wio_write_bcr(unsigned long addr, int index, u16 val)
-{
-	outw(index, addr + PCNET32_WIO_RAP);
-	outw(val, addr + PCNET32_WIO_BDP);
-}
-
-static u16 pcnet32_wio_read_rap(unsigned long addr)
-{
-	return inw(addr + PCNET32_WIO_RAP);
-}
-
-static void pcnet32_wio_write_rap(unsigned long addr, u16 val)
-{
-	outw(val, addr + PCNET32_WIO_RAP);
-}
-
-static void pcnet32_wio_reset(unsigned long addr)
-{
-	inw(addr + PCNET32_WIO_RESET);
-}
-
-static int pcnet32_wio_check(unsigned long addr)
-{
-	outw(88, addr + PCNET32_WIO_RAP);
-	return (inw(addr + PCNET32_WIO_RAP) == 88);
-}
-
-static struct pcnet32_access pcnet32_wio = {
-      read_csr:pcnet32_wio_read_csr,
-      write_csr:pcnet32_wio_write_csr,
-      read_bcr:pcnet32_wio_read_bcr,
-      write_bcr:pcnet32_wio_write_bcr,
-      read_rap:pcnet32_wio_read_rap,
-      write_rap:pcnet32_wio_write_rap,
-      reset:pcnet32_wio_reset
-};
-
-static u16 pcnet32_dwio_read_csr(unsigned long addr, int index)
-{
-	outl(index, addr + PCNET32_DWIO_RAP);
-	return (inl(addr + PCNET32_DWIO_RDP) & 0xffff);
-}
-
-static void pcnet32_dwio_write_csr(unsigned long addr, int index, u16 val)
-{
-	outl(index, addr + PCNET32_DWIO_RAP);
-	outl(val, addr + PCNET32_DWIO_RDP);
-}
-
-static u16 pcnet32_dwio_read_bcr(unsigned long addr, int index)
-{
-	outl(index, addr + PCNET32_DWIO_RAP);
-	return (inl(addr + PCNET32_DWIO_BDP) & 0xffff);
-}
-
-static void pcnet32_dwio_write_bcr(unsigned long addr, int index, u16 val)
-{
-	outl(index, addr + PCNET32_DWIO_RAP);
-	outl(val, addr + PCNET32_DWIO_BDP);
-}
-
-static u16 pcnet32_dwio_read_rap(unsigned long addr)
-{
-	return (inl(addr + PCNET32_DWIO_RAP) & 0xffff);
-}
-
-static void pcnet32_dwio_write_rap(unsigned long addr, u16 val)
-{
-	outl(val, addr + PCNET32_DWIO_RAP);
-}
-
-static void pcnet32_dwio_reset(unsigned long addr)
-{
-	inl(addr + PCNET32_DWIO_RESET);
-}
-
-static int pcnet32_dwio_check(unsigned long addr)
-{
-	outl(88, addr + PCNET32_DWIO_RAP);
-	return ((inl(addr + PCNET32_DWIO_RAP) & 0xffff) == 88);
-}
-
-static struct pcnet32_access pcnet32_dwio = {
-      read_csr:pcnet32_dwio_read_csr,
-      write_csr:pcnet32_dwio_write_csr,
-      read_bcr:pcnet32_dwio_read_bcr,
-      write_bcr:pcnet32_dwio_write_bcr,
-      read_rap:pcnet32_dwio_read_rap,
-      write_rap:pcnet32_dwio_write_rap,
-      reset:pcnet32_dwio_reset
-};
-
-
-/* Initialize the PCNET32 Rx and Tx rings. */
-static int pcnet32_init_ring(struct nic *nic)
-{
-	int i;
-
-	lp->tx_full = 0;
-	lp->cur_rx = lp->cur_tx = 0;
-
-	for (i = 0; i < RX_RING_SIZE; i++) {
-		pcnet32_bufs.rx_ring[i].base =
-			virt_to_le32desc(&pcnet32_bufs.rxb[i]);
-		pcnet32_bufs.rx_ring[i].buf_length = le16_to_cpu(-PKT_BUF_SZ);
-		pcnet32_bufs.rx_ring[i].status = le16_to_cpu(0x8000);
-	}
-
-	/* The Tx buffer address is filled in as needed, but we do need to clear
-	   the upper ownership bit. */
-	for (i = 0; i < TX_RING_SIZE; i++) {
-		pcnet32_bufs.tx_ring[i].base = 0;
-		pcnet32_bufs.tx_ring[i].status = 0;
-	}
-
-
-	lp->init_block.tlen_rlen =
-	    le16_to_cpu(TX_RING_LEN_BITS | RX_RING_LEN_BITS);
-	for (i = 0; i < 6; i++)
-		lp->init_block.phys_addr[i] = nic->node_addr[i];
-	lp->init_block.rx_ring = virt_to_le32desc(&pcnet32_bufs.rx_ring[0]);
-	lp->init_block.tx_ring = virt_to_le32desc(&pcnet32_bufs.tx_ring[0]);
-	return 0;
-}
-
-/**************************************************************************
-RESET - Reset adapter
-***************************************************************************/
-static void pcnet32_reset(struct nic *nic)
-{
-	/* put the card in its initial state */
-	u16 val;
-	int i;
-
-	/* Reset the PCNET32 */
-	lp->a.reset(ioaddr);
-
-	/* switch pcnet32 to 32bit mode */
-	lp->a.write_bcr(ioaddr, 20, 2);
-
-	/* set/reset autoselect bit */
-	val = lp->a.read_bcr(ioaddr, 2) & ~2;
-	if (lp->options & PCNET32_PORT_ASEL)
-		val |= 2;
-	lp->a.write_bcr(ioaddr, 2, val);
-
-	/* handle full duplex setting */
-	if (lp->full_duplex) {
-		val = lp->a.read_bcr(ioaddr, 9) & ~3;
-		if (lp->options & PCNET32_PORT_FD) {
-			val |= 1;
-			if (lp->options ==
-			    (PCNET32_PORT_FD | PCNET32_PORT_AUI))
-				val |= 2;
-		} else if (lp->options & PCNET32_PORT_ASEL) {
-			/* workaround of xSeries250, turn on for 79C975 only */
-			i = ((lp->a.
-			      read_csr(ioaddr,
-				       88) | (lp->a.read_csr(ioaddr,
-							     89) << 16)) >>
-			     12) & 0xffff;
-			if (i == 0x2627)
-				val |= 3;
-		}
-		lp->a.write_bcr(ioaddr, 9, val);
-	}
-
-	/* set/reset GPSI bit in test register */
-	val = lp->a.read_csr(ioaddr, 124) & ~0x10;
-	if ((lp->options & PCNET32_PORT_PORTSEL) == PCNET32_PORT_GPSI)
-		val |= 0x10;
-	lp->a.write_csr(ioaddr, 124, val);
-
-	if (lp->mii && !(lp->options & PCNET32_PORT_ASEL)) {
-		val = lp->a.read_bcr(ioaddr, 32) & ~0x38;	/* disable Auto Negotiation, set 10Mpbs, HD */
-		if (lp->options & PCNET32_PORT_FD)
-			val |= 0x10;
-		if (lp->options & PCNET32_PORT_100)
-			val |= 0x08;
-		lp->a.write_bcr(ioaddr, 32, val);
-	} else {
-		if (lp->options & PCNET32_PORT_ASEL) {	/* enable auto negotiate, setup, disable fd */
-			val = lp->a.read_bcr(ioaddr, 32) & ~0x98;
-			val |= 0x20;
-			lp->a.write_bcr(ioaddr, 32, val);
-		}
-	}
-
-#ifdef DO_DXSUFLO
-	if (lp->dxsuflo) {	/* Disable transmit stop on underflow */
-		val = lp->a.read_csr(ioaddr, 3);
-		val |= 0x40;
-		lp->a.write_csr(ioaddr, 3, val);
-	}
-#endif
-	if (1)
-	{
-		//disable interrupts
-		val = lp->a.read_csr(ioaddr, 3);
-		val = val
-			| (1 << 14) //BABLM intr disabled
-			| (1 << 12) //MISSM missed frame mask intr disabled
-			| (1 << 10) //RINTM receive intr disabled
-			| (1 << 9) //TINTM transmit intr disabled
-			| (1 << 8) //IDONM init done intr disabled
-			;
-		lp->a.write_csr(ioaddr, 3, val);
-	}
-
-	if (lp->ltint) {	/* Enable TxDone-intr inhibitor */
-		val = lp->a.read_csr(ioaddr, 5);
-		val |= (1 << 14);
-		lp->a.write_csr(ioaddr, 5, val);
-	}
-	lp->init_block.mode =
-	    le16_to_cpu((lp->options & PCNET32_PORT_PORTSEL) << 7);
-	lp->init_block.filter[0] = 0xffffffff;
-	lp->init_block.filter[1] = 0xffffffff;
-
-	pcnet32_init_ring(nic);
-
-
-	/* Re-initialize the PCNET32, and start it when done. */
-	lp->a.write_csr(ioaddr, 1,
-			(virt_to_bus(&lp->init_block)) & 0xffff);
-	lp->a.write_csr(ioaddr, 2, (virt_to_bus(&lp->init_block)) >> 16);
-	lp->a.write_csr(ioaddr, 4, 0x0915);
-	lp->a.write_csr(ioaddr, 0, 0x0001);
-
-
-	i = 0;
-	while (i++ < 100)
-		if (lp->a.read_csr(ioaddr, 0) & 0x0100)
-			break;
-	/* 
-	 * We used to clear the InitDone bit, 0x0100, here but Mark Stockton
-	 * reports that doing so triggers a bug in the '974.
-	 */
-	lp->a.write_csr(ioaddr, 0, 0x0042);
-
-	dprintf(("pcnet32 open, csr0 %hX.\n", lp->a.read_csr(ioaddr, 0)));
-
-}
-
-/**************************************************************************
-POLL - Wait for a frame
-***************************************************************************/
-static int pcnet32_poll(struct nic *nic __unused, int retrieve)
-{
-	/* return true if there's an ethernet packet ready to read */
-	/* nic->packet should contain data on return */
-	/* nic->packetlen should contain length of data */
-
-	signed char status;
-	int entry;
-
-	entry = lp->cur_rx & RX_RING_MOD_MASK;
-	status = (le16_to_cpu(pcnet32_bufs.rx_ring[entry].status) >> 8);
-
-	if (status < 0)
-		return 0;
-
-	if ( ! retrieve ) return 1;
-
-	if (status == 0x03) {
-		nic->packetlen =
-			(le32_to_cpu(pcnet32_bufs.rx_ring[entry].msg_length)
-			 & 0xfff) - 4;
-		memcpy(nic->packet, &pcnet32_bufs.rxb[entry], nic->packetlen);
-
-		/* Andrew Boyd of QNX reports that some revs of the 79C765
-		 * clear the buffer length */
-		pcnet32_bufs.rx_ring[entry].buf_length
-			= le16_to_cpu(-PKT_BUF_SZ);
-		/* prime for next receive */
-		pcnet32_bufs.rx_ring[entry].status |= le16_to_cpu(0x8000);
-		/* Switch to the next Rx ring buffer */
-		lp->cur_rx++;
-
-	} else {
-		return 0;
-	}
-
-	return 1;
-}
-
-/**************************************************************************
-TRANSMIT - Transmit a frame
-***************************************************************************/
-static void pcnet32_transmit(struct nic *nic __unused, const char *d,	/* Destination */
-			     unsigned int t,	/* Type */
-			     unsigned int s,	/* size */
-			     const char *p)
-{				/* Packet */
-	/* send the packet to destination */
-	unsigned long time;
-	u8 *ptxb;
-	u16 nstype;
-	u16 status;
-	int entry = 0;		/*lp->cur_tx & TX_RING_MOD_MASK; */
-
-	status = 0x8300;
-	/* point to the current txb incase multiple tx_rings are used */
-	ptxb = pcnet32_bufs.txb[lp->cur_tx];
-
-	/* copy the packet to ring buffer */
-	memcpy(ptxb, d, ETH_ALEN);	/* dst */
-	memcpy(ptxb + ETH_ALEN, nic->node_addr, ETH_ALEN);	/* src */
-	nstype = htons((u16) t);	/* type */
-	memcpy(ptxb + 2 * ETH_ALEN, (u8 *) & nstype, 2);	/* type */
-	memcpy(ptxb + ETH_HLEN, p, s);
-
-	s += ETH_HLEN;
-	while (s < ETH_ZLEN)	/* pad to min length */
-		ptxb[s++] = '\0';
-
-	pcnet32_bufs.tx_ring[entry].length = le16_to_cpu(-s);
-	pcnet32_bufs.tx_ring[entry].misc = 0x00000000;
-	pcnet32_bufs.tx_ring[entry].base = (u32) virt_to_le32desc(ptxb);
-
-	/* we set the top byte as the very last thing */
-	pcnet32_bufs.tx_ring[entry].status = le16_to_cpu(status);
-
-
-	/* Trigger an immediate send poll */
-	lp->a.write_csr(ioaddr, 0, 0x0048);
-
-	/* wait for transmit complete */
-	lp->cur_tx = 0;		/* (lp->cur_tx + 1); */
-	time = currticks() + TICKS_PER_SEC;	/* wait one second */
-	while (currticks() < time &&
-	       ((short) le16_to_cpu(pcnet32_bufs.tx_ring[entry].status) < 0));
-
-	if ((short) le16_to_cpu(pcnet32_bufs.tx_ring[entry].status) < 0)
-		printf("PCNET32 timed out on transmit\n");
-
-	/* Stop pointing at the current txb
-	 * otherwise the card continues to send the packet */
-	pcnet32_bufs.tx_ring[entry].base = 0;
-
-}
-
-/**************************************************************************
-DISABLE - Turn off ethernet interface
-***************************************************************************/
-static void pcnet32_disable ( struct nic *nic __unused ) {
-	/* Stop the PCNET32 here -- it ocassionally polls memory if we don't */
-	lp->a.write_csr(ioaddr, 0, 0x0004);
-
-	/*
-	 * Switch back to 16-bit mode to avoid problems with dumb 
-	 * DOS packet driver after a warm reboot
-	 */
-	lp->a.write_bcr(ioaddr, 20, 0);
-}
-
-/**************************************************************************
-IRQ - Enable, Disable, or Force interrupts
-***************************************************************************/
-static void pcnet32_irq(struct nic *nic __unused, irq_action_t action __unused)
-{
-  switch ( action ) {
-  case DISABLE :
-    break;
-  case ENABLE :
-    break;
-  case FORCE :
-    break;
-  }
-}
-
-
-/**************************************************************************
-PROBE - Look for an adapter, this routine's visible to the outside
-You should omit the last argument struct pci_device * for a non-PCI NIC
-***************************************************************************/
-static int pcnet32_probe ( struct nic *nic, struct pci_device *pci ) {
-
-	int i, media;
-	int fdx, mii, fset, dxsuflo, ltint;
-	int chip_version;
-	struct pcnet32_access *a = NULL;
-	char *chipname;
-	u8 promaddr[6];
-	int shared = 1;
-
-	if (pci->ioaddr == 0)
-		return 0;
-
-	/* BASE is used throughout to address the card */
-	ioaddr = pci->ioaddr;
-	printf("pcnet32.c: Found %s, Vendor=0x%hX Device=0x%hX\n",
-	       pci->driver_name, pci->vendor, pci->device);
-
-	nic->irqno  = 0;
-	nic->ioaddr = pci->ioaddr & ~3;
-
-	/* reset the chip */
-	pcnet32_wio_reset(ioaddr);
-
-	/* NOTE: 16-bit check is first, otherwise some older PCnet chips fail */
-	if (pcnet32_wio_read_csr(ioaddr, 0) == 4
-	    && pcnet32_wio_check(ioaddr)) {
-		a = &pcnet32_wio;
-	} else {
-		pcnet32_dwio_reset(ioaddr);
-		if (pcnet32_dwio_read_csr(ioaddr, 0) == 4
-		    && pcnet32_dwio_check(ioaddr)) {
-			a = &pcnet32_dwio;
-		} else
-			return 0;
-	}
-
-	chip_version =
-	    a->read_csr(ioaddr, 88) | (a->read_csr(ioaddr, 89) << 16);
-
-	dprintf(("PCnet chip version is 0x%X\n", chip_version));
-	if ((chip_version & 0xfff) != 0x003)
-		return 0;
-
-	/* initialize variables */
-	fdx = mii = fset = dxsuflo = ltint = 0;
-	chip_version = (chip_version >> 12) & 0xffff;
-
-	switch (chip_version) {
-	case 0x2420:
-		chipname = "PCnet/PCI 79C970";	/* PCI */
-		break;
-	case 0x2430:
-		if (shared)
-			chipname = "PCnet/PCI 79C970";	/* 970 gives the wrong chip id back */
-		else
-			chipname = "PCnet/32 79C965";	/* 486/VL bus */
-		break;
-	case 0x2621:
-		chipname = "PCnet/PCI II 79C970A";	/* PCI */
-		fdx = 1;
-		break;
-	case 0x2623:
-		chipname = "PCnet/FAST 79C971";	/* PCI */
-		fdx = 1;
-		mii = 1;
-		fset = 1;
-		ltint = 1;
-		break;
-	case 0x2624:
-		chipname = "PCnet/FAST+ 79C972";	/* PCI */
-		fdx = 1;
-		mii = 1;
-		fset = 1;
-		break;
-	case 0x2625:
-		chipname = "PCnet/FAST III 79C973";	/* PCI */
-		fdx = 1;
-		mii = 1;
-		break;
-	case 0x2626:
-		chipname = "PCnet/Home 79C978";	/* PCI */
-		fdx = 1;
-		/* 
-		 * This is based on specs published at www.amd.com.  This section
-		 * assumes that a card with a 79C978 wants to go into 1Mb HomePNA
-		 * mode.  The 79C978 can also go into standard ethernet, and there
-		 * probably should be some sort of module option to select the
-		 * mode by which the card should operate
-		 */
-		/* switch to home wiring mode */
-		media = a->read_bcr(ioaddr, 49);
-
-		printf("media reset to %#x.\n", media);
-		a->write_bcr(ioaddr, 49, media);
-		break;
-	case 0x2627:
-		chipname = "PCnet/FAST III 79C975";	/* PCI */
-		fdx = 1;
-		mii = 1;
-		break;
-	default:
-		chipname = "UNKNOWN";
-		printf("PCnet version %#x, no PCnet32 chip.\n",
-		       chip_version);
-		return 0;
-	}
-
-	/*
-	 *  On selected chips turn on the BCR18:NOUFLO bit. This stops transmit
-	 *  starting until the packet is loaded. Strike one for reliability, lose
-	 *  one for latency - although on PCI this isnt a big loss. Older chips 
-	 *  have FIFO's smaller than a packet, so you can't do this.
-	 */
-
-	if (fset) {
-		a->write_bcr(ioaddr, 18,
-			     (a->read_bcr(ioaddr, 18) | 0x0800));
-		a->write_csr(ioaddr, 80,
-			     (a->read_csr(ioaddr, 80) & 0x0C00) | 0x0c00);
-		dxsuflo = 1;
-		ltint = 1;
-	}
-
-	DBG ( "%s at %hX,", chipname, (unsigned int) ioaddr );
-
-	/* read PROM address */
-	for (i = 0; i < 6; i++)
-		promaddr[i] = inb(ioaddr + i);
-
-	/* Update the nic structure with the MAC Address */
-	for (i = 0; i < ETH_ALEN; i++) {
-		nic->node_addr[i] = promaddr[i];
-	}
-
-	/* Print out some hardware info */
-	DBG ( "%s: IO Addr 0x%hX, MAC Addr %s\n ", chipname, (unsigned int) ioaddr,
-	      eth_ntoa ( nic->node_addr ) );
-
-	/* Set to pci bus master */
-	adjust_pci_device(pci);
-
-	/* point to private storage */
-	lp = &lpx;
-
-#if EBDEBUG
-	if (((chip_version + 1) & 0xfffe) == 0x2624) {	/* Version 0x2623 or 0x2624 */
-		i = a->read_csr(ioaddr, 80) & 0x0C00;	/* Check tx_start_pt */
-		dprintf(("    tx_start_pt(0x%hX):", i));
-		switch (i >> 10) {
-		case 0:
-			dprintf(("  20 bytes,"));
-			break;
-		case 1:
-			dprintf(("  64 bytes,"));
-			break;
-		case 2:
-			dprintf((" 128 bytes,"));
-			break;
-		case 3:
-			dprintf(("~220 bytes,"));
-			break;
-		}
-		i = a->read_bcr(ioaddr, 18);	/* Check Burst/Bus control */
-		dprintf((" BCR18(%hX):", i & 0xffff));
-		if (i & (1 << 5))
-			dprintf(("BurstWrEn "));
-		if (i & (1 << 6))
-			dprintf(("BurstRdEn "));
-		if (i & (1 << 7))
-			dprintf(("DWordIO "));
-		if (i & (1 << 11))
-			dprintf(("NoUFlow "));
-		i = a->read_bcr(ioaddr, 25);
-		dprintf(("    SRAMSIZE=0x%hX,", i << 8));
-		i = a->read_bcr(ioaddr, 26);
-		dprintf((" SRAM_BND=0x%hX,", i << 8));
-		i = a->read_bcr(ioaddr, 27);
-		if (i & (1 << 14))
-			dprintf(("LowLatRx"));
-	}
-#endif
-	lp->name = chipname;
-	lp->shared_irq = shared;
-	lp->full_duplex = fdx;
-	lp->dxsuflo = dxsuflo;
-	lp->ltint = ltint;
-	lp->mii = mii;
-	/* FIXME: Fix Options for only one card */
-	if ((cards_found >= MAX_UNITS)
-	    || ((unsigned int) options[cards_found] > sizeof(options_mapping)))
-		lp->options = PCNET32_PORT_ASEL;
-	else
-		lp->options = options_mapping[options[cards_found]];
-
-	if (fdx && !(lp->options & PCNET32_PORT_ASEL) &&
-	    ((cards_found >= MAX_UNITS) || full_duplex[cards_found]))
-		lp->options |= PCNET32_PORT_FD;
-
-	if (!a) {
-		printf("No access methods\n");
-		return 0;
-	}
-
-	//  lp->a = *a;
-	//  Causes a loader:
-	//     bin/blib.a(pcnet32.o)(.text+0x6b6): In function `pcnet32_probe':
-	//     drivers/net/pcnet32.c:871: undefined reference to `memcpy'
-	//     make: *** [bin/pcnet32.dsk.tmp] Error 1
-	//  So we do:
-	memcpy ( &lp->a, a, sizeof ( lp->a ) );
-	//   To explicity call memcpy.
-
-	/* detect special T1/E1 WAN card by checking for MAC address */
-	if (nic->node_addr[0] == 0x00 && nic->node_addr[1] == 0xe0
-	    && nic->node_addr[2] == 0x75)
-		lp->options = PCNET32_PORT_FD | PCNET32_PORT_GPSI;
-
-	lp->init_block.mode = le16_to_cpu(0x0003);	/* Disable Rx and Tx. */
-	lp->init_block.tlen_rlen =
-	    le16_to_cpu(TX_RING_LEN_BITS | RX_RING_LEN_BITS);
-	for (i = 0; i < 6; i++)
-		lp->init_block.phys_addr[i] = nic->node_addr[i];
-	lp->init_block.filter[0] = 0xffffffff;
-	lp->init_block.filter[1] = 0xffffffff;
-	lp->init_block.rx_ring = virt_to_bus(&pcnet32_bufs.rx_ring);
-	lp->init_block.tx_ring = virt_to_bus(&pcnet32_bufs.tx_ring);
-
-	/* switch pcnet32 to 32bit mode */
-	a->write_bcr(ioaddr, 20, 2);
-
-	a->write_csr(ioaddr, 1, (virt_to_bus(&lp->init_block)) & 0xffff);
-	a->write_csr(ioaddr, 2, (virt_to_bus(&lp->init_block)) >> 16);
-
-	/* 
-	 * To auto-IRQ we enable the initialization-done and DMA error
-	 * interrupts. For ISA boards we get a DMA error, but VLB and PCI
-	 * boards will work.
-	 */
-	/* Trigger an initialization just for the interrupt. */
-
-	
-//	a->write_csr(ioaddr, 0, 0x41); 
-//	mdelay(1);
-
-	cards_found++;
-
-	/* point to NIC specific routines */
-	pcnet32_reset(nic);
-	if (mii) {
-		int tmp;
-		int phy, phy_idx = 0;
-		u16 mii_lpa;
-		lp->phys[0] = 1;	/* Default Setting */
-		for (phy = 1; phy < 32 && phy_idx < MII_CNT; phy++) {
-			int mii_status = mdio_read(nic, phy, MII_BMSR);
-			if (mii_status != 0xffff && mii_status != 0x0000) {
-				lp->phys[phy_idx++] = phy;
-				lp->mii_if.advertising =
-				    mdio_read(nic, phy, MII_ADVERTISE);
-				if ((mii_status & 0x0040) == 0) {
-				  tmp = phy;
-				  dprintf (("MII PHY found at address %d, status " 
-					    "%hX advertising %hX\n", phy, mii_status, 
-					    lp->mii_if.advertising));
-				}
-			}
-		}
-		if (phy_idx == 0)
-			printf("No MII transceiver found!\n");
-		lp->mii_if.phy_id = lp->phys[0];
-
-		lp->mii_if.advertising =
-		    mdio_read(nic, lp->phys[0], MII_ADVERTISE);
-
-		mii_lpa = mdio_read(nic, lp->phys[0], MII_LPA);
-		lp->mii_if.advertising &= mii_lpa;
-		if (lp->mii_if.advertising & ADVERTISE_100FULL)
-			printf("100Mbps Full-Duplex\n");
-		else if (lp->mii_if.advertising & ADVERTISE_100HALF)
-			printf("100Mbps Half-Duplex\n");
-		else if (lp->mii_if.advertising & ADVERTISE_10FULL)
-			printf("10Mbps Full-Duplex\n");
-		else if (lp->mii_if.advertising & ADVERTISE_10HALF)
-			printf("10Mbps Half-Duplex\n");
-		else
-			printf("\n");
-	} else {
-		/* The older chips are fixed 10Mbps, and some support full duplex,
-		 * although not via autonegotiation, but only via configuration.  */
-		if (fdx)
-			printf("10Mbps Full-Duplex\n");
-		else
-			printf("10Mbps Half-Duplex\n");
-	}
-
-	nic->nic_op	= &pcnet32_operations;
-
-	return 1;
-}
-static int mdio_read(struct nic *nic __unused, int phy_id, int reg_num)
-{
-	u16 val_out;
-	int phyaddr;
-
-	if (!lp->mii)
-		return 0;
-
-	phyaddr = lp->a.read_bcr(ioaddr, 33);
-
-	lp->a.write_bcr(ioaddr, 33,
-			((phy_id & 0x1f) << 5) | (reg_num & 0x1f));
-	val_out = lp->a.read_bcr(ioaddr, 34);
-	lp->a.write_bcr(ioaddr, 33, phyaddr);
-
-	return val_out;
-}
-
-#if 0
-static void mdio_write(struct nic *nic __unused, int phy_id, int reg_num,
-		       int val)
-{
-	int phyaddr;
-
-	if (!lp->mii)
-		return;
-
-	phyaddr = lp->a.read_bcr(ioaddr, 33);
-
-	lp->a.write_bcr(ioaddr, 33,
-			((phy_id & 0x1f) << 5) | (reg_num & 0x1f));
-	lp->a.write_bcr(ioaddr, 34, val);
-	lp->a.write_bcr(ioaddr, 33, phyaddr);
-}
-#endif
-
-static struct nic_operations pcnet32_operations = {
-	.connect	= dummy_connect,
-	.poll		= pcnet32_poll,
-	.transmit	= pcnet32_transmit,
-	.irq		= pcnet32_irq,
-
-};
-
-static struct pci_device_id pcnet32_nics[] = {
-	PCI_ROM(0x1022, 0x2000, "pcnet32", "AMD PCnet/PCI", 0),
-	PCI_ROM(0x1022, 0x2625, "pcnetfastiii", "AMD PCNet FAST III", 0),
-	PCI_ROM(0x1022, 0x2001, "amdhomepna", "AMD PCnet/HomePNA", 0),
-};
-
-PCI_DRIVER ( pcnet32_driver, pcnet32_nics, PCI_NO_CLASS );
-
-DRIVER ( "PCNET32/PCI", nic_driver, pci_driver, pcnet32_driver,
-	 pcnet32_probe, pcnet32_disable );
-
-/*
- * Local variables:
- *  c-basic-offset: 8
- *  c-indent-level: 8
- *  tab-width: 8
- * End:
- */
+/*
+ * Copyright (c) 2010 Andrei Faur <da3drus at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <errno.h>
+#include <gpxe/ethernet.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/io.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/malloc.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/pci.h>
+#include <gpxe/timer.h>
+#include <mii.h>
+#include "pcnet32.h"
+
+static u16 pcnet32_wio_read_csr ( unsigned long addr, int index )
+{
+	outw ( index, addr + PCNET32_WIO_RAP );
+	return inw ( addr + PCNET32_WIO_RDP );
+}
+
+static void pcnet32_wio_write_csr ( unsigned long addr, int index, u16 val )
+{
+	outw ( index, addr + PCNET32_WIO_RAP );
+	outw ( val, addr + PCNET32_WIO_RDP );
+}
+
+static u16 pcnet32_wio_read_bcr ( unsigned long addr, int index )
+{
+	outw ( index, addr + PCNET32_WIO_RAP );
+	return inw ( addr + PCNET32_WIO_BDP );
+}
+
+static void pcnet32_wio_write_bcr ( unsigned long addr, int index, u16 val )
+{
+	outw ( index, addr + PCNET32_WIO_RAP );
+	outw ( val, addr + PCNET32_WIO_BDP );
+}
+
+static u16 pcnet32_wio_read_rap ( unsigned long addr )
+{
+	return inw ( addr + PCNET32_WIO_RAP );
+}
+
+static void pcnet32_wio_write_rap ( unsigned long addr , u16 val )
+{
+	outw ( val, addr + PCNET32_WIO_RAP );
+}
+
+static void pcnet32_wio_reset ( unsigned long addr )
+{
+	inw ( addr + PCNET32_DWIO_RESET );
+}
+
+static int pcnet32_wio_check ( unsigned long addr )
+{
+	outw ( 88, addr + PCNET32_WIO_RAP );
+	return ( inw ( addr + PCNET32_WIO_RAP ) == 88 );
+}
+
+static struct pcnet32_access pcnet32_wio = {
+	.read_csr	= pcnet32_wio_read_csr,
+	.write_csr	= pcnet32_wio_write_csr,
+	.read_bcr	= pcnet32_wio_read_bcr,
+	.write_bcr	= pcnet32_wio_write_bcr,
+	.read_rap	= pcnet32_wio_read_rap,
+	.write_rap	= pcnet32_wio_write_rap,
+	.reset		= pcnet32_wio_reset,
+};
+
+static u16 pcnet32_dwio_read_csr ( unsigned long addr, int index )
+{
+	outl ( index, addr + PCNET32_DWIO_RAP );
+	return ( inl ( addr + PCNET32_DWIO_RDP ) & 0xffff );
+}
+
+static void pcnet32_dwio_write_csr ( unsigned long addr, int index, u16 val )
+{
+	outl ( index, addr + PCNET32_DWIO_RAP );
+	outl ( val, addr + PCNET32_DWIO_RDP );
+}
+
+static u16 pcnet32_dwio_read_bcr ( unsigned long addr, int index )
+{
+	outl ( index, addr + PCNET32_DWIO_RAP );
+	return ( inl ( addr + PCNET32_DWIO_BDP ) & 0xffff );
+}
+
+static void pcnet32_dwio_write_bcr ( unsigned long addr, int index, u16 val )
+{
+	outl ( index, addr + PCNET32_DWIO_RAP );
+	outl ( val, addr + PCNET32_DWIO_BDP );
+}
+
+static u16 pcnet32_dwio_read_rap ( unsigned long addr )
+{
+	return ( inl ( addr + PCNET32_DWIO_RAP ) & 0xffff );
+}
+
+static void pcnet32_dwio_write_rap ( unsigned long addr , u16 val )
+{
+	outl ( val, addr + PCNET32_DWIO_RAP );
+}
+
+static void pcnet32_dwio_reset ( unsigned long addr )
+{
+	inl ( addr + PCNET32_DWIO_RESET );
+}
+
+static int pcnet32_dwio_check ( unsigned long addr )
+{
+	outl ( 88, addr + PCNET32_DWIO_RAP );
+	return ( ( inl ( addr + PCNET32_DWIO_RAP ) & 0xffff ) == 88 );
+}
+
+
+static struct pcnet32_access pcnet32_dwio = {
+	.read_csr	= pcnet32_dwio_read_csr,
+	.write_csr	= pcnet32_dwio_write_csr,
+	.read_bcr	= pcnet32_dwio_read_bcr,
+	.write_bcr	= pcnet32_dwio_write_bcr,
+	.read_rap	= pcnet32_dwio_read_rap,
+	.write_rap	= pcnet32_dwio_write_rap,
+	.reset		= pcnet32_dwio_reset,
+};
+
+static int
+pcnet32_mdio_read ( struct net_device *netdev, int phy, int reg )
+{
+	struct pcnet32_private *priv = netdev->priv;
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	u16 val_out;
+
+	if ( ! priv->mii )
+		return 0;
+
+	/* First, select PHY chip and the register we want to read */
+	priv->a->write_bcr ( ioaddr, 33,
+		( ( phy & 0x1f ) << 5 ) | ( reg & 0x1f ) );
+
+	/* Read the selected register's value */
+	val_out = priv->a->read_bcr ( ioaddr, 34 );
+
+	return val_out;
+}
+
+static void
+__unused pcnet32_mdio_write ( struct net_device *netdev, int phy, int reg, int val )
+{
+	struct pcnet32_private *priv = netdev->priv;
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+
+	if ( ! priv->mii )
+		return;
+
+	/* First, select PHY chip and the register we want to write to */
+	priv->a->write_bcr ( ioaddr, 33,
+		( ( phy & 0x1f ) << 5 ) | ( reg & 0x1f ) );
+
+	/* Write val to the selected register */
+	priv->a->write_bcr ( ioaddr, 34, val );
+}
+
+
+/**
+ * pcnet32_refill_rx_ring - Allocates iobufs for every Rx descriptor
+ * that doesn't have one and isn't in use by the hardware
+ *
+ * @v priv	Driver private structure
+ */
+static void
+pcnet32_refill_rx_ring ( struct pcnet32_private *priv )
+{
+	struct pcnet32_rx_desc *rx_curr_desc;
+	u16 status;
+	int i;
+
+	DBGP ( "pcnet32_refill_rx_ring\n" );
+
+	for ( i = 0; i < RX_RING_SIZE; i++ ) {
+		rx_curr_desc = priv->rx_base + i;
+
+		status = le16_to_cpu ( rx_curr_desc->status );
+
+		/* Don't touch descriptors owned by the hardware */
+		if ( status & DescOwn )
+			continue;
+
+		/* Descriptors with iobufs still need to be processed */
+		if ( priv->rx_iobuf[i] != NULL )
+			continue;
+
+		/* If alloc_iob fails, try again later (next poll) */
+		if ( ! ( priv->rx_iobuf[i] = alloc_iob ( PKT_BUF_SIZE ) ) ) {
+			DBG ( "Refill rx ring failed\n" );
+			break;
+		}
+
+		rx_curr_desc->base =
+			cpu_to_le32 ( virt_to_bus ( priv->rx_iobuf[i]->data ) );
+		rx_curr_desc->buf_length = cpu_to_le16 ( -PKT_BUF_SIZE );
+		rx_curr_desc->msg_length = rx_curr_desc->reserved = 0;
+
+		/* Owner changes after the other status fields are set */
+		wmb();
+		rx_curr_desc->status = cpu_to_le16 ( DescOwn );
+	}
+
+}
+
+/**
+ * pcnet32_setup_rx_resources - allocate Rx resources (Descriptors)
+ *
+ * @v priv	Driver private structure
+ *
+ * @ret rc	Returns 0 on success, negative on failure
+ */
+static int
+pcnet32_setup_rx_resources ( struct pcnet32_private *priv )
+{
+	DBGP ( "pcnet32_setup_rx_resources\n" );
+
+	priv->rx_base = malloc_dma ( RX_RING_BYTES, RX_RING_ALIGN );
+
+	DBG ( "priv->rx_base = %#08lx\n", virt_to_bus ( priv->rx_base ) );
+
+	if ( ! priv->rx_base ) {
+		return -ENOMEM;
+	}
+
+	memset ( priv->rx_base, 0, RX_RING_BYTES );
+
+	pcnet32_refill_rx_ring ( priv );
+
+	priv->rx_curr = 0;
+
+	return 0;
+}
+
+static void
+pcnet32_free_rx_resources ( struct pcnet32_private *priv )
+{
+	int i;
+
+	DBGP ( "pcnet32_free_rx_resources\n" );
+
+	free_dma ( priv->rx_base, RX_RING_BYTES );
+
+	for ( i = 0; i < RX_RING_SIZE; i++ ) {
+		free_iob ( priv->rx_iobuf[i] );
+		priv->rx_iobuf[i] = NULL;
+	}
+}
+
+/**
+ * pcnet32_setup_tx_resources - allocate Tx resources (Descriptors)
+ *
+ * @v priv	Driver private structure
+ *
+ * @ret rc	Returns 0 on success, negative on failure
+ */
+static int
+pcnet32_setup_tx_resources ( struct pcnet32_private *priv )
+{
+	DBGP ( "pcnet32_setup_tx_resources\n" );
+
+	priv->tx_base = malloc_dma ( TX_RING_BYTES, TX_RING_ALIGN );
+
+	if ( ! priv->tx_base ) {
+		return -ENOMEM;
+	}
+
+	memset ( priv->tx_base, 0, TX_RING_BYTES );
+
+	DBG ( "priv->tx_base = %#08lx\n", virt_to_bus ( priv->tx_base ) );
+
+	priv->tx_curr = 0;
+	priv->tx_fill_ctr = 0;
+	priv->tx_tail = 0;
+
+	return 0;
+}
+
+static void
+pcnet32_free_tx_resources ( struct pcnet32_private *priv )
+{
+	DBGP ( "pcnet32_free_tx_resources\n" );
+
+	free_dma ( priv->tx_base, TX_RING_BYTES );
+}
+
+static int
+pcnet32_chip_detect ( struct pcnet32_private *priv )
+{
+	int fdx, mii, fset;
+	int media;
+	int rc;
+	unsigned long ioaddr;
+	struct pcnet32_access *a;
+	int chip_version;
+	char *chipname;
+
+	ioaddr = priv->pci_dev->ioaddr;
+	a = priv->a;
+
+	chip_version = a->read_csr ( ioaddr, 88 )
+		| ( a->read_csr ( ioaddr, 89 ) << 16 );
+
+	rc = -ENODEV;
+
+	DBG ( "PCnet chip version is 0x%X\n", chip_version );
+	if ( ( chip_version & 0xfff ) != 0x003 )
+		goto err_unsupported;
+
+	fdx = mii = fset = 0;
+	chip_version = ( chip_version >> 12 ) & 0xffff;
+
+	switch (chip_version) {
+	case 0x2420:
+		chipname = "PCnet/PCI 79C970";
+		break;
+	case 0x2430:
+		/* 970 gives the wrong chip id back */
+		chipname = "PCnet/PCI 79C970";
+		break;
+	case 0x2621:
+		chipname = "PCnet/PCI II 79C970A";
+		fdx = 1;
+		break;
+	case 0x2623:
+		chipname = "PCnet/FAST 79C971";
+		fdx = 1;
+		mii = 1;
+		fset = 1;
+		break;
+	case 0x2624:
+		chipname = "PCnet/FAST+ 79C972";
+		fdx = 1;
+		mii = 1;
+		fset = 1;
+		break;
+	case 0x2625:
+		chipname = "PCnet/FAST III 79C973";
+		fdx = 1;
+		mii = 1;
+		break;
+	case 0x2626:
+		chipname = "PCnet/Home 79C978";
+		fdx = 1;
+		/*
+		 * This is based on specs published at www.amd.com. This section
+		 * assumes that a NIC with a 79C978 wants to go into 1Mb HomePNA
+		 * mode. The 79C978 can also go into standard ethernet, and
+		 * there probably should be some sort of module option to select
+		 * the mode by which the card should operate
+		 */
+		/* switch to home wiring mode */
+		media = a->read_bcr(ioaddr, 49);
+
+		printf("media reset to %#x.\n", media);
+		a->write_bcr(ioaddr, 49, media);
+		break;
+	case 0x2627:
+		chipname = "PCnet/FAST III 79C975";
+		fdx = 1;
+		mii = 1;
+		break;
+	case 0x2628:
+		chipname = "PCnet/PRO 79C976";
+		fdx = 1;
+		mii = 1;
+		break;
+	default:
+		chipname = "UNKNOWN";
+		DBG ( "PCnet version %#x, no PCnet32 chip.\n", chip_version );
+		goto err_unsupported;
+	}
+
+	printf ( "PCnet version %#x, chipname %s\n", chip_version, chipname );
+
+	/*
+	 * On selected chips turn on the BCR18:NOUFLO bit. This stops transmit
+	 * starting until the packet is loaded. Strike one for reliability, lose
+	 * one for latency - although on PCI this isnt a big loss. Older chips
+	 * have FIFO's smaller than a packet, so you can't do this.
+	 * Turn on BCR18:BurstRdEn and BCR18:BurstWrEn.
+	 */
+	if (fset) {
+		a->write_bcr ( ioaddr, 18,
+			( a->read_bcr ( ioaddr, 18 ) | 0x0860 ) );
+		a->write_csr ( ioaddr, 80,
+			( a->read_csr ( ioaddr, 80 ) & 0x0C00) | 0x0C00 );
+	}
+
+	priv->full_duplex = fdx;
+	priv->mii = mii;
+
+	return 0;
+
+err_unsupported:
+	return rc;
+}
+
+/**
+ * pcnet32_set_ops - Determines the ops used to access the registers
+ *
+ * @v priv	Driver private structure
+ *
+ * @ret rc	Returns 0 on success, negative on failure
+ */
+static int
+pcnet32_set_ops ( struct pcnet32_private *priv )
+{
+	int rc;
+	unsigned long ioaddr;
+
+	ioaddr = priv->pci_dev->ioaddr;
+
+	/* Check if CSR0 has its default value and perform a write / read
+	   in the RAP register to see if it works. Based on these results
+	   determine what mode the NIC is in (WIO / DWIO)
+	 */
+	rc = -ENODEV;
+
+	if ( pcnet32_wio_read_csr ( ioaddr, 0 ) == 4 &&
+	     pcnet32_wio_check ( ioaddr ) ) {
+		priv->a = &pcnet32_wio;
+	} else {
+		pcnet32_dwio_reset ( ioaddr );
+		if ( pcnet32_dwio_read_csr ( ioaddr, 0 ) == 4 &&
+		     pcnet32_dwio_check ( ioaddr ) ) {
+			priv->a = &pcnet32_dwio;
+		} else {
+			goto err_unsupported;
+		}
+	}
+
+	return 0;
+
+err_unsupported:
+	return rc;
+}
+
+/**
+ * pcnet32_setup_init_block - setup the NICs initialization block
+ *
+ * @v priv	Driver private structure
+ *
+ * @ret rc	Returns 0 on success, negative on failure
+ */
+static void
+pcnet32_setup_init_block ( struct pcnet32_private *priv )
+{
+	int i;
+
+	/* Configure the network port based on what we've established so far */
+	priv->init_block.mode =
+		cpu_to_le16 ( ( priv->options & PCNET32_PORT_PORTSEL ) << 7 );
+
+	/* Setup RLEN and TLEN fields */
+	priv->init_block.tlen_rlen =
+		cpu_to_le16 ( ( PCNET32_LOG_RX_BUFFERS << 4 ) |
+			      ( PCNET32_LOG_TX_BUFFERS << 12 ) );
+
+	/* Fill in physical address */
+	for ( i = 0; i < ETH_ALEN; i++)
+		priv->init_block.phys_addr[i] = priv->netdev->hw_addr[i];
+
+	/* No multicasting scheme, accept everything */
+	priv->init_block.filter[0] = 0xffffffff;
+	priv->init_block.filter[1] = 0xffffffff;
+
+	priv->init_block.rx_ring =
+		cpu_to_le32 ( virt_to_bus ( priv->rx_base ) );
+	priv->init_block.tx_ring =
+		cpu_to_le32 ( virt_to_bus ( priv->tx_base ) );
+
+	/* Make sure all changes are visible */
+	wmb();
+}
+
+/**
+ * pcnet32_setup_probe_phy - go through all PHYs and see which one is present
+ *
+ * @v priv	Driver private structure
+ */
+static void
+pcnet32_setup_probe_phy ( struct pcnet32_private *priv )
+{
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	char phycount;
+	int phy_id;
+	int i;
+
+	if ( priv->mii ) {
+		phy_id = ( ( priv->a->read_bcr ( ioaddr, 33 ) ) >> 5 ) & 0x1f;
+		for ( i = 0; i < PCNET32_MAX_PHYS; i++ ) {
+			unsigned short id1, id2;
+			id1 = pcnet32_mdio_read ( priv->netdev, i, MII_PHYSID1 );
+			if ( id1 == 0xffff )
+				continue;
+			id2 = pcnet32_mdio_read ( priv->netdev, i, MII_PHYSID2 );
+			if ( id2 == 0xffff )
+				continue;
+			if ( i == 31 && ( ( priv->chip_version + 1 ) & 0xfffe ) == 0x2624 )
+				continue;
+
+			phycount++;
+			phy_id = i;
+		}
+		priv->a->write_bcr ( ioaddr, 33, phy_id << 5 );
+		if ( phycount > 1 )
+			priv->options |= PCNET32_PORT_MII;
+	}
+}
+
+/**
+ * pcnet32_setup_mac_addr - check for inconsistency between CSR12-14
+ * and PROM addresses
+ *
+ * @v priv	Driver private structure
+ */
+static int
+pcnet32_setup_mac_addr ( struct pcnet32_private *priv )
+{
+	int i;
+	u8 promaddr[ETH_ALEN];
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+
+	/* In most chips, after a chip reset, the ethernet address is read from
+	 * the station address PROM at the base address and programmed into the
+	 * "Physical Address Registers" CSR12-14.
+	 * As a precautionary measure, we read the PROM values and complain if
+	 * they disagree with the CSRs.  If they miscompare, and the PROM addr
+	 * is valid, then the PROM addr is used.
+	 */
+	for ( i = 0; i < 3; i++ ) {
+		unsigned int val;
+		val = priv->a->read_csr ( ioaddr, i + 12 ) & 0x0ffff;
+		/* There may be endianness issues here. */
+		priv->netdev->hw_addr[2 * i] = val & 0x0ff;
+		priv->netdev->hw_addr[2 * i + 1] = ( val >> 8 ) & 0x0ff;
+	}
+
+	for ( i = 0; i < ETH_ALEN; i++ )
+		promaddr[i] = inb ( ioaddr + i );
+
+	if ( memcmp ( promaddr, priv->netdev->hw_addr, ETH_ALEN ) ||
+	     ! is_valid_ether_addr ( priv->netdev->hw_addr ) ) {
+		if ( is_valid_ether_addr ( promaddr ) ) {
+			DBG ( "CSR address is invalid, using PROM addr\n" );
+			memcpy ( priv->netdev->hw_addr, promaddr, ETH_ALEN );
+		}
+	}
+
+	/* If ethernet address is not valid, return error */
+	if ( ! is_valid_ether_addr ( priv->netdev->hw_addr ) )
+		return -EADDRNOTAVAIL;
+
+	return 0;
+}
+
+/**
+ * pcnet32_setup_if_duplex - Sets the NICs used interface and duplex mode
+ *
+ * @v priv	Driver private structure
+ */
+static void
+pcnet32_setup_if_duplex ( struct pcnet32_private *priv )
+{
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	u16 val;
+
+	/* Set/Reset autoselect bit */
+	val = priv->a->read_bcr ( ioaddr, 2 ) & ~2;
+	if ( priv->options & PCNET32_PORT_ASEL )
+		val |= 2;
+	priv->a->write_bcr ( ioaddr, 2, val );
+
+	/* Handle full duplex setting */
+	if ( priv->full_duplex ) {
+		val = priv->a->read_bcr ( ioaddr, 9 ) & ~3;
+		if ( priv->options & PCNET32_PORT_FD ) {
+			val |= 1;
+			if ( priv->options == ( PCNET32_PORT_FD | PCNET32_PORT_AUI ) )
+				val |= 2;
+		} else if ( priv->options & PCNET32_PORT_ASEL ) {
+			/* Workaround of xSeries 250, on for 79C975 only */
+			if ( priv->chip_version == 0x2627 )
+				val |= 3;
+		}
+		priv->a->write_bcr ( ioaddr, 9, val );
+	}
+
+	/* Set/Reset GPSI bit in test register */
+	val = priv->a->read_csr ( ioaddr, 124 ) & ~0x10;
+	if ( ( priv->options & PCNET32_PORT_PORTSEL ) == PCNET32_PORT_GPSI )
+		val |= 0x10;
+	priv->a->write_bcr ( ioaddr, 124, val );
+
+	/* Allied Telesyn AT are 100Mbit only and do not negotiate */
+	u16 subsys_vend_id, subsys_dev_id;
+	pci_read_config_word ( priv->pci_dev,
+			       PCI_SUBSYSTEM_VENDOR_ID,
+			       &subsys_vend_id );
+	pci_read_config_word ( priv->pci_dev,
+			       PCI_SUBSYSTEM_ID,
+			       &subsys_dev_id );
+	if ( subsys_vend_id == PCI_VENDOR_ID_AT &&
+	     ( ( subsys_dev_id == PCI_SUBDEVICE_ID_AT_2700FX ) ||
+	       ( subsys_dev_id == PCI_SUBDEVICE_ID_AT_2701FX ) ) ) {
+		priv->options = PCNET32_PORT_FD | PCNET32_PORT_100;
+	}
+
+	if ( priv->mii && ! ( priv->options & PCNET32_PORT_ASEL ) ) {
+		/* Disable Auto Negotiation, set 10Mbps, HD */
+		val = priv->a->read_bcr ( ioaddr, 32 ) & ~0x38;
+		if ( priv->options & PCNET32_PORT_FD )
+			val |= 0x10;
+		if ( priv->options & PCNET32_PORT_100 )
+			val |= 0x08;
+		priv->a->write_bcr ( ioaddr, 32, val );
+	} else if ( priv->options & PCNET32_PORT_ASEL ) {
+		/* 79C970 chips do not have the BCR32 register */
+		if ( ( priv->chip_version != 0x2420 ) &&
+		     ( priv->chip_version != 0x2621 ) ) {
+			/* Enable Auto Negotiation, setup, disable FD */
+			val = priv->a->read_bcr ( ioaddr, 32 ) & ~0x98;
+			val |= 0x20;
+			priv->a->write_bcr ( ioaddr, 32, val );
+		}
+	}
+}
+
+/**
+ * pcnet32_hw_start - Starts up the NIC
+ *
+ * @v priv	Driver private structure
+ */
+static void
+pcnet32_hw_start ( struct pcnet32_private *priv )
+{
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	int i;
+
+	/* Begin initialization procedure */
+	priv->a->write_csr ( ioaddr, 0, Init );
+
+	/* Wait for the initialization to be done */
+	i = 0;
+	while ( i++ < 100 )
+		if ( priv->a->read_csr ( ioaddr, 0 ) & InitDone )
+			break;
+
+	/* Start the chip */
+	priv->a->write_csr ( ioaddr, 0, Strt );
+}
+
+/**
+ * open - Called when a network interface is made active
+ *
+ * @v netdev	Network device
+ * @ret rc	Return status code, 0 on success, negative value on failure
+ **/
+static int
+pcnet32_open ( struct net_device *netdev )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	int rc;
+	u16 val;
+
+	/* Setup TX and RX descriptors */
+	if ( ( rc = pcnet32_setup_tx_resources ( priv ) ) != 0 ) {
+		DBG ( "Error setting up TX resources\n" );
+		goto err_setup_tx;
+	}
+
+	if ( ( rc = pcnet32_setup_rx_resources ( priv ) ) != 0 ) {
+		DBG ( "Error setting up RX resources\n" );
+		goto err_setup_rx;
+	}
+
+	/* Reset the chip */
+	priv->a->reset ( ioaddr );
+
+	/* Switch pcnet32 to 32bit mode */
+	priv->a->write_bcr ( ioaddr, 20, PCNET32_SWSTYLE_PCNET32 );
+
+	/* Setup the interface and duplex mode */
+	pcnet32_setup_if_duplex ( priv );
+
+	/* Disable interrupts */
+	val = priv->a->read_csr ( ioaddr, 3 );
+	val |= BablMask | MissFrameMask | RxIntMask | TxIntMask | InitDoneMask;
+	priv->a->write_csr ( ioaddr, 3, val );
+
+	/* Setup initialization block */
+	pcnet32_setup_init_block ( priv );
+
+	/* Fill in the address of the initialization block */
+	priv->a->write_csr ( ioaddr, 1,
+		( virt_to_bus ( &priv->init_block ) ) & 0xffff );
+	priv->a->write_csr ( ioaddr, 2,
+		( virt_to_bus ( &priv->init_block ) ) >> 16 );
+
+	/* Enable Auto-Pad, disable interrupts */
+	priv->a->write_csr ( ioaddr, 4, 0x0915 );
+
+	pcnet32_hw_start ( priv );
+
+	return 0;
+
+err_setup_rx:
+	pcnet32_free_tx_resources ( priv );
+err_setup_tx:
+	priv->a->reset( priv->pci_dev->ioaddr );
+	return rc;
+}
+
+/**
+ * transmit - Transmit a packet
+ *
+ * @v netdev	Network device
+ * @v iobuf	I/O buffer
+ *
+ * @ret rc	Returns 0 on success, negative on failure
+ */
+static int
+pcnet32_transmit ( struct net_device *netdev, struct io_buffer *iobuf )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	uint32_t tx_len = iob_len ( iobuf );
+	struct pcnet32_tx_desc *tx_curr_desc;
+
+	DBGP ( "pcnet32_transmit\n" );
+
+	if ( priv->tx_fill_ctr == RX_RING_SIZE ) {
+		DBG ( "Tx overflow\n" );
+		return -ENOTSUP;
+	}
+
+	priv->tx_iobuf[priv->tx_curr] = iobuf;
+
+	tx_curr_desc = priv->tx_base + priv->tx_curr;
+
+	/* Configure current descriptor to transmit packet */
+	tx_curr_desc->length = cpu_to_le16 ( -tx_len );
+	tx_curr_desc->misc = 0x00000000;
+	tx_curr_desc->base = cpu_to_le32 ( virt_to_bus ( iobuf->data ) );
+
+	/* Owner changes after the other status fields are set */
+	wmb();
+	tx_curr_desc->status =
+		cpu_to_le16 ( DescOwn | StartOfPacket | EndOfPacket );
+
+	/* Trigger an immediate send poll */
+	priv->a->write_csr ( ioaddr, 0,
+		( priv->irq_enabled ? IntEnable : 0 ) | TxDemand );
+
+	/* Point to the next free descriptor */
+	priv->tx_curr = ( priv->tx_curr + 1 ) % TX_RING_SIZE;
+
+	/* Increment number of tx descriptors in use */
+	priv->tx_fill_ctr++;
+
+	return 0;
+}
+
+/**
+ * pcnet32_process_tx_packets - Checks for successfully sent packets,
+ * reports them to gPXE with netdev_tx_complete()
+ *
+ * @v netdev	Network device
+ */
+static void
+pcnet32_process_tx_packets ( struct net_device *netdev )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+	struct pcnet32_tx_desc *tx_curr_desc;
+
+	DBGP ( "pcnet32_process_tx_packets\n" );
+
+	while ( priv->tx_tail != priv->tx_curr ) {
+		tx_curr_desc = priv->tx_base + priv->tx_tail;
+
+		u16 status = le16_to_cpu ( tx_curr_desc->status );
+
+		DBG ( "Before OWN bit check, status: %#08x\n", status );
+
+		/* Skip this descriptor if hardware still owns it */
+		if ( status & DescOwn )
+			break;
+
+		DBG ( "Transmitted packet.\n" );
+		DBG ( "priv->tx_fill_ctr= %d\n", priv->tx_fill_ctr );
+		DBG ( "priv->tx_tail	= %d\n", priv->tx_tail );
+		DBG ( "priv->tx_curr	= %d\n", priv->tx_curr );
+		DBG ( "tx_curr_desc	= %#08lx\n", virt_to_bus ( tx_curr_desc ) );
+
+		/* This packet is ready for completion */
+		netdev_tx_complete ( netdev, priv->tx_iobuf[priv->tx_tail]);
+
+		/* Clear the descriptor */
+		memset ( tx_curr_desc, 0, sizeof(*tx_curr_desc) );
+
+		/* Reduce the number of tx descriptors in use */
+		priv->tx_fill_ctr--;
+
+		/* Go to next available descriptor */
+		priv->tx_tail = ( priv->tx_tail + 1 ) % TX_RING_SIZE;
+	}
+}
+
+/**
+ * pcnet32_process_rx_packets - Checks for received packets, reports them
+ * to gPXE with netdev_rx() or netdev_rx_err() if there was an error receiving
+ * the packet
+ *
+ * @v netdev	Network device
+ */
+static void
+pcnet32_process_rx_packets ( struct net_device *netdev )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+	struct pcnet32_rx_desc *rx_curr_desc;
+	u16 status;
+	u32 len;
+	int i;
+
+	DBGP ( "pcnet32_process_rx_packets\n" );
+
+	for ( i = 0; i < RX_RING_SIZE; i++ ) {
+		rx_curr_desc = priv->rx_base + priv->rx_curr;
+
+		status = le16_to_cpu ( rx_curr_desc->status );
+		rmb();
+
+		DBG ( "Before OWN bit check, status: %#08x\n", status );
+
+		/* Skip this descriptor if hardware still owns it */
+		if ( status & DescOwn )
+			break;
+
+		/* We own the descriptor, but it has not been refilled yet */
+		if ( priv->rx_iobuf[priv->rx_curr] == NULL )
+			break;
+
+		DBG ( "Received packet.\n" );
+		DBG ( "priv->rx_curr	= %d\n", priv->rx_curr );
+		DBG ( "rx_len		= %d\n",
+		      ( le32_to_cpu ( rx_curr_desc->msg_length ) & 0xfff ) - 4 );
+		DBG ( "rx_curr_desc	= %#08lx\n",
+		      virt_to_bus ( rx_curr_desc ) );
+
+		/* Check ERR bit */
+		if ( status & 0x4000 ) {
+			netdev_rx_err ( netdev, priv->rx_iobuf[priv->rx_curr],
+					-EINVAL );
+			DBG ( "Corrupted packet received!\n");
+		} else {
+			/* Adjust size of the iobuf to reflect received data */
+			len = ( le32_to_cpu ( rx_curr_desc->msg_length ) & 0xfff ) - 4;
+			iob_put ( priv->rx_iobuf[priv->rx_curr], len );
+
+			/* Add this packet to the receive queue */
+			netdev_rx ( netdev, priv->rx_iobuf[priv->rx_curr] );
+		}
+
+		/* Invalidate iobuf and descriptor */
+		priv->rx_iobuf[priv->rx_curr] = NULL;
+		memset ( rx_curr_desc, 0, sizeof(*rx_curr_desc) );
+
+		/* Point to the next free descriptor */
+		priv->rx_curr = ( priv->rx_curr + 1 ) % RX_RING_SIZE;
+	}
+
+	/* Allocate new iobufs where needed */
+	pcnet32_refill_rx_ring ( priv );
+}
+
+/**
+ * poll - Poll for received packets
+ *
+ * @v netdev	Network device
+ */
+static void
+pcnet32_poll ( struct net_device *netdev )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	u16 status;
+
+	DBGP ( "pcnet32_poll\n" );
+
+	status = priv->a->read_csr ( ioaddr, 0 );
+
+	/* Clear interrupts */
+	priv->a->write_csr ( ioaddr, 0, status );
+
+	DBG ( "pcnet32_poll: mask = %#04x, status = %#04x\n",
+		priv->a->read_csr ( ioaddr, 3 ), status );
+
+	/* Return when RINT or TINT are not set */
+	if ( ( status & 0x0500 ) == 0x0000 )
+		return;
+
+	/* Process transmitted packets */
+	pcnet32_process_tx_packets ( netdev );
+
+	/* Process received packets */
+	pcnet32_process_rx_packets ( netdev );
+}
+
+/**
+ * close - Disable network interface
+ *
+ * @v netdev	network interface device structure
+ **/
+static void
+pcnet32_close ( struct net_device *netdev )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+
+	DBGP ( "pcnet32_close\n" );
+
+	/* Reset the chip */
+	pcnet32_wio_reset ( ioaddr );
+
+	/* Stop the PCNET32 - it occasionally polls memory if we don't */
+	priv->a->write_csr ( ioaddr, 0, Stop );
+
+	/* Switch back to 16bit mode to avoid problems with dumb
+	 * DOS packet driver after a warm reboot */
+	priv->a->write_bcr ( ioaddr, 20, PCNET32_SWSTYLE_LANCE );
+
+	pcnet32_free_rx_resources ( priv );
+	pcnet32_free_tx_resources ( priv );
+}
+
+static void pcnet32_irq_enable ( struct pcnet32_private *priv )
+{
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	u16 val;
+
+	DBGP ( "pcnet32_irq_enable\n" );
+
+	/* Enable TINT and RINT masks */
+	val = priv->a->read_csr ( ioaddr, 3 );
+	val &= ~( RxIntMask | TxIntMask );
+	priv->a->write_csr ( ioaddr, 3, val );
+
+	/* Enable interrupts */
+	val = priv->a->read_csr ( ioaddr, 0 );
+	priv->a->write_csr ( ioaddr, 0, val | IntEnable );
+
+	priv->irq_enabled = 1;
+}
+
+static void pcnet32_irq_disable ( struct pcnet32_private *priv )
+{
+	unsigned long ioaddr = priv->pci_dev->ioaddr;
+	u16 val;
+
+	DBGP ( "pcnet32_irq_disable\n" );
+
+	val = priv->a->read_csr ( ioaddr, 0 );
+	priv->a->write_csr ( ioaddr, 0, val & ~IntEnable );
+
+	priv->irq_enabled = 0;
+}
+
+/**
+ * irq - enable or disable interrupts
+ *
+ * @v netdev    network adapter
+ * @v action    requested interrupt action
+ **/
+static void
+pcnet32_irq ( struct net_device *netdev, int action )
+{
+	struct pcnet32_private *priv = netdev_priv ( netdev );
+
+	DBGP ( "pcnet32_irq\n" );
+
+	switch ( action ) {
+	case 0:
+		pcnet32_irq_disable ( priv );
+		break;
+	default:
+		pcnet32_irq_enable ( priv );
+		break;
+	}
+}
+
+static struct net_device_operations pcnet32_operations = {
+	.open		= pcnet32_open,
+	.transmit	= pcnet32_transmit,
+	.poll		= pcnet32_poll,
+	.close		= pcnet32_close,
+	.irq		= pcnet32_irq,
+};
+
+/**
+ * probe - Initial configuration of NIC
+ *
+ * @v pdev	PCI device
+ * @v ent	PCI IDs
+ *
+ * @ret rc	Return status code
+ **/
+static int
+pcnet32_probe ( struct pci_device *pdev, const struct pci_device_id *ent )
+{
+	struct net_device *netdev;
+	struct pcnet32_private *priv;
+	unsigned long ioaddr;
+	int rc;
+
+	DBGP ( "pcnet32_probe\n" );
+
+	DBG ( "Found %s, vendor = %#04x, device = %#04x\n",
+		pdev->driver_name, ent->vendor, ent->device );
+
+	/* Allocate our private data */
+	netdev = alloc_etherdev ( sizeof ( *priv ) );
+	if ( ! netdev ) {
+		rc = -ENOMEM;
+		goto err_alloc_etherdev;
+	}
+
+	/* Link our operations to the netdev struct */
+	netdev_init ( netdev, &pcnet32_operations );
+
+	/* Link the PCI device to the netdev struct */
+	pci_set_drvdata ( pdev, netdev );
+	netdev->dev = &pdev->dev;
+
+	/* Get a reference to our private data */
+	priv = netdev_priv ( netdev );
+
+	/* We'll need these set up for the rest of the routines */
+	priv->pci_dev = pdev;
+	priv->netdev = netdev;
+
+	ioaddr = pdev->ioaddr;
+
+	/* Only use irqs under UNDI */
+	priv->irq_enabled = 0;
+
+	/* Reset the chip */
+	pcnet32_wio_reset ( ioaddr );
+
+	if ( ( rc = pcnet32_set_ops ( priv ) ) != 0 ) {
+		DBG ( "Setting driver operations failed\n");
+		goto err_set_ops;
+	}
+
+	if ( ( rc = pcnet32_chip_detect ( priv ) ) != 0 ) {
+		DBG ( "pcnet32_chip_detect failed\n" );
+		goto err_chip_detect;
+	}
+
+	/* Enter bus mastering mode */
+	adjust_pci_device ( pdev );
+
+	/* Verify and get MAC address */
+	if ( ( rc = pcnet32_setup_mac_addr ( priv ) ) != 0 ) {
+		DBG ( "Setting MAC address failed\n" );
+		goto err_mac_addr;
+	}
+
+	DBG ( "IO Addr 0x%lX, MAC Addr %s\n", ioaddr,
+		eth_ntoa ( netdev->hw_addr ) );
+
+	priv->options = PCNET32_PORT_ASEL;
+
+	/* Detect special T1/E1 WAN card by checking for MAC address */
+	if ( netdev->hw_addr[0] == 0x00 &&
+	     netdev->hw_addr[1] == 0xE0 &&
+	     netdev->hw_addr[2] == 0x75 )
+		priv->options = PCNET32_PORT_FD | PCNET32_PORT_GPSI;
+
+	/* Probe the PHY so we can check link state and speed */
+	pcnet32_setup_probe_phy ( priv );
+
+	if ( ( rc = register_netdev ( netdev ) ) != 0 ) {
+		DBG ( "Error registering netdev\n" );
+		goto err_register;
+	}
+
+	netdev_link_up ( netdev );
+
+	return 0;
+
+err_register:
+	netdev_put ( netdev );
+err_chip_detect:
+err_set_ops:
+err_alloc_etherdev:
+err_mac_addr:
+	return rc;
+}
+
+/**
+ * remove - Device Removal Routine
+ *
+ * @v pdev PCI device information struct
+ **/
+static void
+pcnet32_remove ( struct pci_device *pdev )
+{
+	struct net_device *netdev = pci_get_drvdata ( pdev );
+	unsigned long ioaddr = pdev->ioaddr;
+
+	DBGP ( "pcnet32_remove\n" );
+
+	/* Reset the chip */
+	pcnet32_wio_reset ( ioaddr );
+
+	unregister_netdev ( netdev );
+	netdev_nullify ( netdev );
+	netdev_put ( netdev );
+}
+
+static struct pci_device_id pcnet32_nics[] = {
+	PCI_ROM(0x1022, 0x2000, "pcnet32", "AMD PCnet/PCI", 0),
+	PCI_ROM(0x1022, 0x2625, "pcnetfastiii", "AMD PCNet FAST III", 0),
+	PCI_ROM(0x1022, 0x2001, "amdhomepna", "AMD PCnet/HomePNA", 0),
+};
+
+struct pci_driver pcnet32_driver __pci_driver = {
+	.ids		= pcnet32_nics,
+	.id_count	= ARRAY_SIZE ( pcnet32_nics ),
+	.probe		= pcnet32_probe,
+	.remove		= pcnet32_remove,
+};
diff --git a/src/drivers/net/pcnet32.h b/src/drivers/net/pcnet32.h
new file mode 100644
index 0000000..bd03cbc
--- /dev/null
+++ b/src/drivers/net/pcnet32.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2010 Andrei Faur <da3drus at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#ifndef _PCNET32_H_
+#define _PCNET32_H_
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/*
+ * Set the number of Tx and Rx buffers, using Log_2(# buffers).
+ * Set default values to 16 Tx buffers and 32 Rx buffers.
+ */
+#define PCNET32_LOG_TX_BUFFERS		4
+#define PCNET32_LOG_RX_BUFFERS		5
+
+/* Maximum number of descriptor rings is 512 */
+#define PCNET32_LOG_MAX_TX_BUFFERS	9
+#define PCNET32_LOG_MAX_RX_BUFFERS	9
+
+#define TX_RING_SIZE		( 1 << ( PCNET32_LOG_TX_BUFFERS ) )
+#define TX_MAX_RING_SIZE	( 1 << ( PCNET32_LOG_MAX_TX_BUFFERS ) )
+
+#define RX_RING_SIZE		( 1 << ( PCNET32_LOG_RX_BUFFERS ) )
+#define RX_MAX_RING_SIZE	( 1 << ( PCNET32_LOG_MAX_RX_BUFFERS ) )
+
+#define RX_RING_BYTES		( RX_RING_SIZE * sizeof(struct pcnet32_rx_desc ) )
+#define TX_RING_BYTES		( TX_RING_SIZE * sizeof(struct pcnet32_tx_desc ) )
+
+#define PKT_BUF_SIZE	1536
+
+#define RX_RING_ALIGN		16
+#define TX_RING_ALIGN		16
+
+#define INIT_BLOCK_ALIGN	32
+
+#define PCNET32_WIO_RDP		0x10
+#define PCNET32_WIO_RAP		0x12
+#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
+
+#define PCNET32_PORT_AUI	0x00
+#define PCNET32_PORT_10BT	0x01
+#define PCNET32_PORT_GPSI	0x02
+#define PCNET32_PORT_MII	0x03
+
+#define PCNET32_PORT_PORTSEL	0x03
+#define PCNET32_PORT_ASEL	0x04
+#define PCNET32_PORT_100	0x40
+#define PCNET32_PORT_FD		0x80
+
+#define PCNET32_SWSTYLE_LANCE	0x00
+#define PCNET32_SWSTYLE_ILACC	0x01
+#define PCNET32_SWSTYLE_PCNET32	0x02
+
+#define PCNET32_MAX_PHYS	32
+
+#ifndef PCI_VENDOR_ID_AT
+#define PCI_VENDOR_ID_AT	0x1259
+#endif
+
+#ifndef PCI_SUBDEVICE_ID_AT_2700FX
+#define PCI_SUBDEVICE_ID_AT_2700FX	0x2701
+#endif
+
+#ifndef PCI_SUBDEVICE_ID_AT_2701FX
+#define PCI_SUBDEVICE_ID_AT_2701FX	0x2703
+#endif
+
+struct pcnet32_rx_desc {
+	u32 base;
+	s16 buf_length;
+	s16 status;
+	u32 msg_length;
+	u32 reserved;
+};
+
+struct pcnet32_tx_desc {
+	u32 base;
+	s16 length;
+	s16 status;
+	u32 misc;
+	u32 reserved;
+};
+
+struct pcnet32_init_block {
+	u16 mode;
+	u16 tlen_rlen;
+	u8 phys_addr[6];
+	u16 reserved;
+	u32 filter[2];
+	u32 rx_ring;
+	u32 tx_ring;
+};
+
+struct pcnet32_access {
+	u16 ( *read_csr ) ( unsigned long, int );
+	void ( *write_csr ) ( unsigned long, int, u16 );
+	u16 ( *read_bcr ) ( unsigned long, int );
+	void ( *write_bcr ) ( unsigned long, int, u16 );
+	u16 ( *read_rap ) ( unsigned long );
+	void ( *write_rap ) ( unsigned long, u16 );
+	void ( *reset ) ( unsigned long );
+};
+
+struct pcnet32_private {
+	struct pcnet32_init_block init_block __attribute__((aligned(32)));
+	struct pci_device *pci_dev;
+	struct net_device *netdev;
+
+	struct io_buffer *rx_iobuf[RX_RING_SIZE];
+	struct io_buffer *tx_iobuf[TX_RING_SIZE];
+
+	struct pcnet32_rx_desc *rx_base;
+	struct pcnet32_tx_desc *tx_base;
+	uint32_t rx_curr;
+	uint32_t tx_curr;
+	uint32_t tx_tail;
+	uint32_t tx_fill_ctr;
+
+	struct pcnet32_access *a;
+	int options;
+	unsigned int	mii:1,
+			full_duplex:1;
+
+	unsigned short chip_version;
+
+	char irq_enabled;
+};
+
+enum pcnet32_desc_status_bit {
+	DescOwn		= (1 << 15),
+	StartOfPacket	= (1 << 9),
+	EndOfPacket	= (1 << 8)
+};
+
+enum pcnet32_register_content {
+	/* CSR0 bits - Controller status register */
+	RxInt		= (1 << 10),
+	TxInt		= (1 << 9),
+	InitDone	= (1 << 8),
+	IntFlag		= (1 << 7),
+	IntEnable	= (1 << 6),
+	TxDemand	= (1 << 3),
+	Stop		= (1 << 2),
+	Strt		= (1 << 1),
+	Init		= (1 << 0),
+
+	/* CSR3 bits - Controller status register */
+	BablMask	= (1 << 14),
+	MissFrameMask	= (1 << 12),
+	MemErrMask	= (1 << 11),
+	RxIntMask	= (1 << 10),
+	TxIntMask	= (1 << 9),
+	InitDoneMask	= (1 << 8)
+
+};
+
+#endif /* _PCNET32_H_ */
-- 
1.6.3.3



More information about the gPXE-devel mailing list