patch-2.4.20 linux-2.4.20/drivers/net/8139cp.c

Next file: linux-2.4.20/drivers/net/8139too.c
Previous file: linux-2.4.20/drivers/net/7990.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/drivers/net/8139cp.c linux-2.4.20/drivers/net/8139cp.c
@@ -18,21 +18,21 @@
 
 	See the file COPYING in this distribution for more information.
 
+	Contributors:
+	
+		Wake-on-LAN support - Felipe Damasio <felipewd@terra.com.br>
+		PCI suspend/resume  - Felipe Damasio <felipewd@terra.com.br>
+		LinkChg interrupt   - Felipe Damasio <felipewd@terra.com.br>
+			
 	TODO, in rough priority order:
+	* Test Tx checksumming thoroughly
 	* dev->tx_timeout
-	* LinkChg interrupt
-	* Support forcing media type with a module parameter,
-	  like dl2k.c/sundance.c
-	* Implement PCI suspend/resume
 	* Constants (module parms?) for Rx work limit
-	* support 64-bit PCI DMA
 	* Complete reset on PciErr
 	* Consider Rx interrupt mitigation using TimerIntr
 	* Implement 8139C+ statistics dump; maybe not...
 	  h/w stats can be reset only by software reset
-	* Tx checksumming
 	* Handle netif_rx return value
-	* ETHTOOL_GREGS, ETHTOOL_[GS]WOL,
 	* Investigate using skb->priority with h/w VLAN priority
 	* Investigate using High Priority Tx Queue with skb->priority
 	* Adjust Rx FIFO threshold and Max Rx DMA burst on Rx FIFO error
@@ -41,12 +41,14 @@
 	  Tx descriptor bit
 	* The real minimum of CP_MIN_MTU is 4 bytes.  However,
 	  for this to be supported, one must(?) turn on packet padding.
+	* Support 8169 GMII
+	* Support external MII transceivers
 
  */
 
 #define DRV_NAME		"8139cp"
-#define DRV_VERSION		"0.0.7"
-#define DRV_RELDATE		"Feb 27, 2002"
+#define DRV_VERSION		"0.3.0"
+#define DRV_RELDATE		"Sep 29, 2002"
 
 
 #include <linux/config.h>
@@ -62,9 +64,17 @@
 #include <linux/mii.h>
 #include <linux/if_vlan.h>
 #include <linux/crc32.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
 #include <asm/io.h>
 #include <asm/uaccess.h>
 
+/* experimental TX checksumming feature enable/disable */
+#undef CP_TX_CHECKSUM
+
+/* VLAN tagging feature enable/disable */
 #if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
 #define CP_VLAN_TAG_USED 1
 #define CP_VLAN_TX_TAG(tx_desc,vlan_tag_value) \
@@ -77,7 +87,7 @@
 
 /* These identify the driver base version and may not be removed. */
 static char version[] __devinitdata =
-KERN_INFO DRV_NAME " 10/100 PCI Ethernet driver v" DRV_VERSION " (" DRV_RELDATE ")\n";
+KERN_INFO DRV_NAME ": 10/100 PCI Ethernet driver v" DRV_VERSION " (" DRV_RELDATE ")\n";
 
 MODULE_AUTHOR("Jeff Garzik <jgarzik@mandrakesoft.com>");
 MODULE_DESCRIPTION("RealTek RTL-8139C+ series 10/100 PCI Ethernet driver");
@@ -85,25 +95,34 @@
 
 static int debug = -1;
 MODULE_PARM (debug, "i");
-MODULE_PARM_DESC (debug, "8139cp bitmapped message enable number");
+MODULE_PARM_DESC (debug, "8139cp: bitmapped message enable number");
 
 /* Maximum number of multicast addresses to filter (vs. Rx-all-multicast).
    The RTL chips use a 64 element hash table based on the Ethernet CRC.  */
 static int multicast_filter_limit = 32;
 MODULE_PARM (multicast_filter_limit, "i");
-MODULE_PARM_DESC (multicast_filter_limit, "8139cp maximum number of filtered multicast addresses");
+MODULE_PARM_DESC (multicast_filter_limit, "8139cp: maximum number of filtered multicast addresses");
 
 #define PFX			DRV_NAME ": "
 
+#ifndef TRUE
+#define FALSE 0
+#define TRUE (!FALSE)
+#endif
+
 #define CP_DEF_MSG_ENABLE	(NETIF_MSG_DRV		| \
 				 NETIF_MSG_PROBE 	| \
 				 NETIF_MSG_LINK)
+#define CP_NUM_STATS		14	/* struct cp_dma_stats, plus one */
+#define CP_STATS_SIZE		64	/* size in bytes of DMA stats block */
 #define CP_REGS_SIZE		(0xff + 1)
+#define CP_REGS_VER		1		/* version 1 */
 #define CP_RX_RING_SIZE		64
 #define CP_TX_RING_SIZE		64
 #define CP_RING_BYTES		\
 		((sizeof(struct cp_desc) * CP_RX_RING_SIZE) +	\
-		(sizeof(struct cp_desc) * CP_TX_RING_SIZE))
+		 (sizeof(struct cp_desc) * CP_TX_RING_SIZE) +	\
+		 CP_STATS_SIZE)
 #define NEXT_TX(N)		(((N) + 1) & (CP_TX_RING_SIZE - 1))
 #define NEXT_RX(N)		(((N) + 1) & (CP_RX_RING_SIZE - 1))
 #define TX_BUFFS_AVAIL(CP)					\
@@ -132,6 +151,7 @@
 	/* NIC register offsets */
 	MAC0		= 0x00,	/* Ethernet hardware address. */
 	MAR0		= 0x08,	/* Multicast filter. */
+	StatsAddr	= 0x10,	/* 64-bit start addr of 64-byte DMA stats blk */
 	TxRingAddr	= 0x20, /* 64-bit start addr of Tx ring */
 	HiTxRingAddr	= 0x28, /* 64-bit start addr of high priority Tx ring */
 	Cmd		= 0x37, /* Command register */
@@ -152,7 +172,9 @@
 	NWayExpansion	= 0x6A, /* MII Expansion */
 	Config5		= 0xD8,	/* Config5 */
 	TxPoll		= 0xD9,	/* Tell chip to check Tx descriptors for work */
+	RxMaxSize	= 0xDA, /* Max size of an Rx packet (8169 only) */
 	CpCmd		= 0xE0, /* C+ Command register (C+ mode only) */
+	IntrMitigate	= 0xE2,	/* rx/tx interrupt mitigation control */
 	RxRingAddr	= 0xE4, /* 64-bit start addr of Rx ring */
 	TxThresh	= 0xEC, /* Early Tx threshold */
 	OldRxBufAddr	= 0x30, /* DMA address of Rx ring buffer (C mode) */
@@ -176,8 +198,8 @@
 	NormalTxPoll	= (1 << 6),  /* One or more normal Tx packets to send */
 	PID1		= (1 << 17), /* 2 protocol id bits:  0==non-IP, */
 	PID0		= (1 << 16), /* 1==UDP/IP, 2==TCP/IP, 3==IP */
-	RxProtoTCP	= 2,
-	RxProtoUDP	= 1,
+	RxProtoTCP	= 1,
+	RxProtoUDP	= 2,
 	RxProtoIP	= 3,
 	TxFIFOUnder	= (1 << 25), /* Tx FIFO underrun */
 	TxOWC		= (1 << 22), /* Tx Out-of-window collision */
@@ -192,6 +214,9 @@
 	RxErrLong	= (1 << 21), /* Rx error, packet > 4096 bytes */
 	RxErrFIFO	= (1 << 22), /* Rx error, FIFO overflowed, pkt bad */
 
+	/* StatsAddr register */
+	DumpStats	= (1 << 3),  /* Begin stats dump */
+
 	/* RxConfig register */
 	RxCfgFIFOShift	= 13,	     /* Shift, to get Rx FIFO thresh value */
 	RxCfgDMAShift	= 8,	     /* Shift, to get Rx Max DMA value */
@@ -230,6 +255,7 @@
 	/* C+ mode command register */
 	RxVlanOn	= (1 << 6),  /* Rx VLAN de-tagging enable */
 	RxChkSum	= (1 << 5),  /* Rx checksum offload enable */
+	PCIDAC		= (1 << 4),  /* PCI Dual Address Cycle (64-bit PCI) */
 	PCIMulRW	= (1 << 3),  /* Enable PCI read/write multiple */
 	CpRxOn		= (1 << 1),  /* Rx mode enable */
 	CpTxOn		= (1 << 0),  /* Tx mode enable */
@@ -248,12 +274,23 @@
 
 	/* Config1 register */
 	DriverLoaded	= (1 << 5),  /* Software marker, driver is loaded */
+	LWACT           = (1 << 4),  /* LWAKE active mode */
 	PMEnable	= (1 << 0),  /* Enable various PM features of chip */
 
 	/* Config3 register */
 	PARMEnable	= (1 << 6),  /* Enable auto-loading of PHY parms */
+	MagicPacket     = (1 << 5),  /* Wake up when receives a Magic Packet */
+	LinkUp          = (1 << 4),  /* Wake up when the cable connection is re-established */
+
+	/* Config4 register */
+	LWPTN           = (1 << 1),  /* LWAKE Pattern */
+	LWPME           = (1 << 4),  /* LANWAKE vs PMEB */
 
 	/* Config5 register */
+	BWF             = (1 << 6),  /* Accept Broadcast wakeup frame */
+	MWF             = (1 << 5),  /* Accept Multicast wakeup frame */
+	UWF             = (1 << 4),  /* Accept Unicast wakeup frame */
+	LANWake         = (1 << 1),  /* Enable LANWake signal */
 	PMEStatus	= (1 << 0),  /* PME status can be reset by PCI RST# */
 };
 
@@ -269,8 +306,7 @@
 struct cp_desc {
 	u32		opts1;
 	u32		opts2;
-	u32		addr_lo;
-	u32		addr_hi;
+	u64		addr;
 };
 
 struct ring_info {
@@ -279,6 +315,22 @@
 	unsigned		frag;
 };
 
+struct cp_dma_stats {
+	u64			tx_ok;
+	u64			rx_ok;
+	u64			tx_err;
+	u32			rx_err;
+	u16			rx_fifo;
+	u16			frame_align;
+	u32			tx_ok_1col;
+	u32			tx_ok_mcol;
+	u64			rx_ok_phys;
+	u64			rx_ok_bcast;
+	u32			rx_ok_mcast;
+	u16			tx_abort;
+	u16			tx_underrun;
+} __attribute__((packed));
+
 struct cp_extra_stats {
 	unsigned long		rx_frags;
 };
@@ -307,12 +359,19 @@
 
 	struct net_device_stats net_stats;
 	struct cp_extra_stats	cp_stats;
+	struct cp_dma_stats	*nic_stats;
+	dma_addr_t		nic_stats_dma;
 
 	struct pci_dev		*pdev;
 	u32			rx_config;
 
 	struct sk_buff		*frag_skb;
 	unsigned		dropping_frag : 1;
+	unsigned		pci_using_dac : 1;
+	unsigned int		board_type;
+
+	unsigned int		wol_enabled : 1; /* Is Wake-on-LAN enabled? */
+	u32			power_state[16];
 
 	struct mii_if_info	mii_if;
 };
@@ -341,14 +400,52 @@
 static void cp_tx (struct cp_private *cp);
 static void cp_clean_rings (struct cp_private *cp);
 
+enum board_type {
+	RTL8139Cp,
+	RTL8169,
+};
+
+static struct cp_board_info {
+	const char *name;
+} cp_board_tbl[] __devinitdata = {
+	/* RTL8139Cp */
+	{ "RTL-8139C+" },
+
+	/* RTL8169 */
+	{ "RTL-8169" },
+};
 
 static struct pci_device_id cp_pci_tbl[] __devinitdata = {
 	{ PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_REALTEK_8139,
-	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139Cp },
+#if 0
+	{ PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_REALTEK_8169,
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8169 },
+#endif
 	{ },
 };
 MODULE_DEVICE_TABLE(pci, cp_pci_tbl);
 
+static struct {
+	const char str[ETH_GSTRING_LEN];
+} ethtool_stats_keys[] = {
+	{ "tx_ok" },
+	{ "rx_ok" },
+	{ "tx_err" },
+	{ "rx_err" },
+	{ "rx_fifo" },
+	{ "frame_align" },
+	{ "tx_ok_1col" },
+	{ "tx_ok_mcol" },
+	{ "rx_ok_phys" },
+	{ "rx_ok_bcast" },
+	{ "rx_ok_mcast" },
+	{ "tx_abort" },
+	{ "tx_underrun" },
+	{ "rx_frags" },
+};
+
+
 static inline void cp_set_rxbufsize (struct cp_private *cp)
 {
 	unsigned int mtu = cp->dev->mtu;
@@ -545,13 +642,13 @@
 		cp_rx_skb(cp, skb, desc);
 
 rx_next:
+		cp->rx_ring[rx_tail].opts2 = 0;
+		cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);
 		if (rx_tail == (CP_RX_RING_SIZE - 1))
 			desc->opts1 = cpu_to_le32(DescOwn | RingEnd |
 						  cp->rx_buf_sz);
 		else
 			desc->opts1 = cpu_to_le32(DescOwn | cp->rx_buf_sz);
-		cp->rx_ring[rx_tail].opts2 = 0;
-		cp->rx_ring[rx_tail].addr_lo = cpu_to_le32(mapping);
 		rx_tail = NEXT_RX(rx_tail);
 	}
 
@@ -575,14 +672,16 @@
 		printk(KERN_DEBUG "%s: intr, status %04x cmd %02x cpcmd %04x\n",
 		        dev->name, status, cpr8(Cmd), cpr16(CpCmd));
 
+	cpw16_f(IntrStatus, status);
+
 	spin_lock(&cp->lock);
 
 	if (status & (RxOK | RxErr | RxEmpty | RxFIFOOvr))
 		cp_rx(cp);
 	if (status & (TxOK | TxErr | TxEmpty | SWInt))
 		cp_tx(cp);
-
-	cpw16_f(IntrStatus, status);
+	if (status & LinkChg)
+		mii_check_media(&cp->mii_if, netif_msg_link(cp), FALSE);
 
 	if (status & PciErr) {
 		u16 pci_status;
@@ -682,22 +781,32 @@
 	eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0;
 	if (skb_shinfo(skb)->nr_frags == 0) {
 		struct cp_desc *txd = &cp->tx_ring[entry];
-		u32 mapping, len;
+		u32 len;
+		dma_addr_t mapping;
 
 		len = skb->len;
 		mapping = pci_map_single(cp->pdev, skb->data, len, PCI_DMA_TODEVICE);
-		eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0;
 		CP_VLAN_TX_TAG(txd, vlan_tag);
-		txd->addr_lo = cpu_to_le32(mapping);
+		txd->addr = cpu_to_le64(mapping);
 		wmb();
 
 #ifdef CP_TX_CHECKSUM
-		txd->opts1 = cpu_to_le32(eor | len | DescOwn | FirstFrag |
-			LastFrag | IPCS | UDPCS | TCPCS);
-#else
-		txd->opts1 = cpu_to_le32(eor | len | DescOwn | FirstFrag |
-			LastFrag);
+		if (skb->ip_summed == CHECKSUM_HW) {
+			const struct iphdr *ip = skb->nh.iph;
+			if (ip->protocol == IPPROTO_TCP)
+				txd->opts1 = cpu_to_le32(eor | len | DescOwn |
+							 FirstFrag | LastFrag |
+							 IPCS | TCPCS);
+			else if (ip->protocol == IPPROTO_UDP)
+				txd->opts1 = cpu_to_le32(eor | len | DescOwn |
+							 FirstFrag | LastFrag |
+							 IPCS | UDPCS);
+			else
+				BUG();
+		} else
 #endif
+			txd->opts1 = cpu_to_le32(eor | len | DescOwn |
+						 FirstFrag | LastFrag);
 		wmb();
 
 		cp->tx_skb[entry].skb = skb;
@@ -706,12 +815,17 @@
 		entry = NEXT_TX(entry);
 	} else {
 		struct cp_desc *txd;
-		u32 first_len, first_mapping;
+		u32 first_len, first_eor;
+		dma_addr_t first_mapping;
 		int frag, first_entry = entry;
+#ifdef CP_TX_CHECKSUM
+		const struct iphdr *ip = skb->nh.iph;
+#endif
 
 		/* We must give this initial chunk to the device last.
 		 * Otherwise we could race with the device.
 		 */
+		first_eor = eor;
 		first_len = skb->len - skb->data_len;
 		first_mapping = pci_map_single(cp->pdev, skb->data,
 					       first_len, PCI_DMA_TODEVICE);
@@ -722,8 +836,9 @@
 
 		for (frag = 0; frag < skb_shinfo(skb)->nr_frags; frag++) {
 			skb_frag_t *this_frag = &skb_shinfo(skb)->frags[frag];
-			u32 len, mapping;
+			u32 len;
 			u32 ctrl;
+			dma_addr_t mapping;
 
 			len = this_frag->size;
 			mapping = pci_map_single(cp->pdev,
@@ -732,16 +847,24 @@
 						 len, PCI_DMA_TODEVICE);
 			eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0;
 #ifdef CP_TX_CHECKSUM
-			ctrl = eor | len | DescOwn | IPCS | UDPCS | TCPCS;
-#else
-			ctrl = eor | len | DescOwn;
+			if (skb->ip_summed == CHECKSUM_HW) {
+				ctrl = eor | len | DescOwn | IPCS;
+				if (ip->protocol == IPPROTO_TCP)
+					ctrl |= TCPCS;
+				else if (ip->protocol == IPPROTO_UDP)
+					ctrl |= UDPCS;
+				else
+					BUG();
+			} else
 #endif
+				ctrl = eor | len | DescOwn;
+
 			if (frag == skb_shinfo(skb)->nr_frags - 1)
 				ctrl |= LastFrag;
 
 			txd = &cp->tx_ring[entry];
 			CP_VLAN_TX_TAG(txd, vlan_tag);
-			txd->addr_lo = cpu_to_le32(mapping);
+			txd->addr = cpu_to_le64(mapping);
 			wmb();
 
 			txd->opts1 = cpu_to_le32(ctrl);
@@ -755,14 +878,25 @@
 
 		txd = &cp->tx_ring[first_entry];
 		CP_VLAN_TX_TAG(txd, vlan_tag);
-		txd->addr_lo = cpu_to_le32(first_mapping);
+		txd->addr = cpu_to_le64(first_mapping);
 		wmb();
 
 #ifdef CP_TX_CHECKSUM
-		txd->opts1 = cpu_to_le32(first_len | FirstFrag | DescOwn | IPCS | UDPCS | TCPCS);
-#else
-		txd->opts1 = cpu_to_le32(first_len | FirstFrag | DescOwn);
+		if (skb->ip_summed == CHECKSUM_HW) {
+			if (ip->protocol == IPPROTO_TCP)
+				txd->opts1 = cpu_to_le32(first_eor | first_len |
+							 FirstFrag | DescOwn |
+							 IPCS | TCPCS);
+			else if (ip->protocol == IPPROTO_UDP)
+				txd->opts1 = cpu_to_le32(first_eor | first_len |
+							 FirstFrag | DescOwn |
+							 IPCS | UDPCS);
+			else
+				BUG();
+		} else
 #endif
+			txd->opts1 = cpu_to_le32(first_eor | first_len |
+						 FirstFrag | DescOwn);
 		wmb();
 	}
 	cp->tx_head = entry;
@@ -889,8 +1023,12 @@
 
 static inline void cp_start_hw (struct cp_private *cp)
 {
+	u16 pci_dac = cp->pci_using_dac ? PCIDAC : 0;
+	if (cp->board_type == RTL8169)
+		cpw16(CpCmd, pci_dac | PCIMulRW | RxChkSum);
+	else
+		cpw16(CpCmd, pci_dac | PCIMulRW | RxChkSum | CpRxOn | CpTxOn);
 	cpw8(Cmd, RxOn | TxOn);
-	cpw16(CpCmd, PCIMulRW | RxChkSum | CpRxOn | CpTxOn);
 }
 
 static void cp_init_hw (struct cp_private *cp)
@@ -912,27 +1050,28 @@
 	cpw32_f (TxConfig, IFG | (TX_DMA_BURST << TxDMAShift));
 
 	cpw8(Config1, cpr8(Config1) | DriverLoaded | PMEnable);
-	cpw8(Config3, PARMEnable); /* disables magic packet and WOL */
-	cpw8(Config5, cpr8(Config5) & PMEStatus); /* disables more WOL stuff */
+	/* Disable Wake-on-LAN. Can be turned on with ETHTOOL_SWOL */
+	if (cp->board_type == RTL8139Cp) {
+		cpw8(Config3, PARMEnable);
+		cp->wol_enabled = 0;
+	}
+	cpw8(Config5, cpr8(Config5) & PMEStatus); 
+	if (cp->board_type == RTL8169)
+		cpw16(RxMaxSize, cp->rx_buf_sz);
 
 	cpw32_f(HiTxRingAddr, 0);
 	cpw32_f(HiTxRingAddr + 4, 0);
-	cpw32_f(OldRxBufAddr, 0);
-	cpw32_f(OldTSD0, 0);
-	cpw32_f(OldTSD0 + 4, 0);
-	cpw32_f(OldTSD0 + 8, 0);
-	cpw32_f(OldTSD0 + 12, 0);
 
 	cpw32_f(RxRingAddr, cp->ring_dma);
-	cpw32_f(RxRingAddr + 4, 0);
+	cpw32_f(RxRingAddr + 4, 0);		/* FIXME: 64-bit PCI */
 	cpw32_f(TxRingAddr, cp->ring_dma + (sizeof(struct cp_desc) * CP_RX_RING_SIZE));
-	cpw32_f(TxRingAddr + 4, 0);
+	cpw32_f(TxRingAddr + 4, 0);		/* FIXME: 64-bit PCI */
 
 	cpw16(MultiIntr, 0);
 
-	cpw16(IntrMask, cp_intr_mask);
+	cpw16_f(IntrMask, cp_intr_mask);
 
-	cpw8_f (Cfg9346, Cfg9346_Lock);
+	cpw8_f(Cfg9346, Cfg9346_Lock);
 }
 
 static int cp_refill_rx (struct cp_private *cp)
@@ -954,15 +1093,14 @@
 		cp->rx_skb[i].skb = skb;
 		cp->rx_skb[i].frag = 0;
 
+		cp->rx_ring[i].opts2 = 0;
+		cp->rx_ring[i].addr = cpu_to_le64(cp->rx_skb[i].mapping);
 		if (i == (CP_RX_RING_SIZE - 1))
 			cp->rx_ring[i].opts1 =
 				cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);
 		else
 			cp->rx_ring[i].opts1 =
 				cpu_to_le32(DescOwn | cp->rx_buf_sz);
-		cp->rx_ring[i].opts2 = 0;
-		cp->rx_ring[i].addr_lo = cpu_to_le32(cp->rx_skb[i].mapping);
-		cp->rx_ring[i].addr_hi = 0;
 	}
 
 	return 0;
@@ -985,10 +1123,19 @@
 
 static int cp_alloc_rings (struct cp_private *cp)
 {
-	cp->rx_ring = pci_alloc_consistent(cp->pdev, CP_RING_BYTES, &cp->ring_dma);
-	if (!cp->rx_ring)
+	void *mem;
+
+	mem = pci_alloc_consistent(cp->pdev, CP_RING_BYTES, &cp->ring_dma);
+	if (!mem)
 		return -ENOMEM;
+
+	cp->rx_ring = mem;
 	cp->tx_ring = &cp->rx_ring[CP_RX_RING_SIZE];
+
+	mem += (CP_RING_BYTES - CP_STATS_SIZE);
+	cp->nic_stats = mem;
+	cp->nic_stats_dma = cp->ring_dma + (CP_RING_BYTES - CP_STATS_SIZE);
+
 	return cp_init_rings(cp);
 }
 
@@ -1027,6 +1174,7 @@
 	pci_free_consistent(cp->pdev, CP_RING_BYTES, cp->rx_ring, cp->ring_dma);
 	cp->rx_ring = NULL;
 	cp->tx_ring = NULL;
+	cp->nic_stats = NULL;
 }
 
 static int cp_open (struct net_device *dev)
@@ -1047,6 +1195,8 @@
 	if (rc)
 		goto err_out_hw;
 
+	netif_carrier_off(dev);
+	mii_check_media(&cp->mii_if, netif_msg_link(cp), TRUE);
 	netif_start_queue(dev);
 
 	return 0;
@@ -1065,12 +1215,18 @@
 		printk(KERN_DEBUG "%s: disabling interface\n", dev->name);
 
 	netif_stop_queue(dev);
+	netif_carrier_off(dev);
+
+	spin_lock_irq(&cp->lock);
 	cp_stop_hw(cp);
+	spin_unlock_irq(&cp->lock);
+
 	free_irq(dev->irq, dev);
 	cp_free_rings(cp);
 	return 0;
 }
 
+#ifdef BROKEN
 static int cp_change_mtu(struct net_device *dev, int new_mtu)
 {
 	struct cp_private *cp = dev->priv;
@@ -1094,6 +1250,8 @@
 
 	dev->mtu = new_mtu;
 	cp_set_rxbufsize(cp);		/* set new rx buf size */
+	if (cp->board_type == RTL8169)
+		cpw16(RxMaxSize, cp->rx_buf_sz);
 
 	rc = cp_init_rings(cp);		/* realloc and restart h/w */
 	cp_start_hw(cp);
@@ -1102,6 +1260,7 @@
 
 	return rc;
 }
+#endif /* BROKEN */
 
 static char mii_2_8139_map[8] = {
 	BasicModeCtrl,
@@ -1136,6 +1295,60 @@
 		cpw16(mii_2_8139_map[location], value);
 }
 
+/* Set the ethtool Wake-on-LAN settings */
+static void netdev_set_wol (struct cp_private *cp,
+                     const struct ethtool_wolinfo *wol)
+{
+	u8 options;
+
+	options = cpr8 (Config3) & ~(LinkUp | MagicPacket);
+	/* If WOL is being disabled, no need for complexity */
+	if (wol->wolopts) {
+		if (wol->wolopts & WAKE_PHY)	options |= LinkUp;
+		if (wol->wolopts & WAKE_MAGIC)	options |= MagicPacket;
+	}
+
+	cpw8 (Cfg9346, Cfg9346_Unlock);
+	cpw8 (Config3, options);
+	cpw8 (Cfg9346, Cfg9346_Lock);
+
+	options = 0; /* Paranoia setting */
+	options = cpr8 (Config5) & ~(UWF | MWF | BWF);
+	/* If WOL is being disabled, no need for complexity */
+	if (wol->wolopts) {
+		if (wol->wolopts & WAKE_UCAST)  options |= UWF;
+		if (wol->wolopts & WAKE_BCAST)	options |= BWF;
+		if (wol->wolopts & WAKE_MCAST)	options |= MWF;
+	}
+
+	cpw8 (Config5, options);
+
+	cp->wol_enabled = (wol->wolopts) ? 1 : 0;
+}
+
+/* Get the ethtool Wake-on-LAN settings */
+static void netdev_get_wol (struct cp_private *cp,
+	             struct ethtool_wolinfo *wol)
+{
+	u8 options;
+
+	wol->wolopts   = 0; /* Start from scratch */
+	wol->supported = WAKE_PHY   | WAKE_BCAST | WAKE_MAGIC |
+		         WAKE_MCAST | WAKE_UCAST;
+	/* We don't need to go on if WOL is disabled */
+	if (!cp->wol_enabled) return;
+	
+	options        = cpr8 (Config3);
+	if (options & LinkUp)        wol->wolopts |= WAKE_PHY;
+	if (options & MagicPacket)   wol->wolopts |= WAKE_MAGIC;
+
+	options        = 0; /* Paranoia setting */
+	options        = cpr8 (Config5);
+	if (options & UWF)           wol->wolopts |= WAKE_UCAST;
+	if (options & BWF)           wol->wolopts |= WAKE_BCAST;
+	if (options & MWF)           wol->wolopts |= WAKE_MCAST;
+}
+
 static int cp_ethtool_ioctl (struct cp_private *cp, void *useraddr)
 {
 	u32 ethcmd;
@@ -1153,6 +1366,8 @@
 		strcpy (info.driver, DRV_NAME);
 		strcpy (info.version, DRV_VERSION);
 		strcpy (info.bus_info, cp->pdev->slot_name);
+		info.regdump_len = CP_REGS_SIZE;
+		info.n_stats = CP_NUM_STATS;
 		if (copy_to_user (useraddr, &info, sizeof (info)))
 			return -EFAULT;
 		return 0;
@@ -1209,6 +1424,226 @@
 		return 0;
 	}
 
+	/* NIC register dump */
+	case ETHTOOL_GREGS: {
+                struct ethtool_regs regs;
+                u8 *regbuf = kmalloc(CP_REGS_SIZE, GFP_KERNEL);
+                int rc;
+
+		if (!regbuf)
+			return -ENOMEM;
+		memset(regbuf, 0, CP_REGS_SIZE);
+
+                rc = copy_from_user(&regs, useraddr, sizeof(regs));
+		if (rc) {
+			rc = -EFAULT;
+			goto err_out_gregs;
+		}
+                
+                if (regs.len > CP_REGS_SIZE)
+                        regs.len = CP_REGS_SIZE;
+                if (regs.len < CP_REGS_SIZE) {
+			rc = -EINVAL;
+			goto err_out_gregs;
+		}
+
+                regs.version = CP_REGS_VER;
+                rc = copy_to_user(useraddr, &regs, sizeof(regs));
+		if (rc) {
+			rc = -EFAULT;
+			goto err_out_gregs;
+		}
+
+                useraddr += offsetof(struct ethtool_regs, data);
+
+                spin_lock_irq(&cp->lock);
+                memcpy_fromio(regbuf, cp->regs, CP_REGS_SIZE);
+                spin_unlock_irq(&cp->lock);
+
+                if (copy_to_user(useraddr, regbuf, regs.len))
+                        rc = -EFAULT;
+
+err_out_gregs:
+		kfree(regbuf);
+		return rc;
+	}
+
+	/* get/set RX checksumming */
+	case ETHTOOL_GRXCSUM: {
+		struct ethtool_value edata = { ETHTOOL_GRXCSUM };
+		u16 cmd = cpr16(CpCmd) & RxChkSum;
+
+		edata.data = cmd ? 1 : 0;
+		if (copy_to_user(useraddr, &edata, sizeof(edata)))
+			return -EFAULT;
+		return 0;
+	}
+	case ETHTOOL_SRXCSUM: {
+		struct ethtool_value edata;
+		u16 cmd = cpr16(CpCmd), newcmd;
+
+		newcmd = cmd;
+
+		if (copy_from_user(&edata, useraddr, sizeof(edata)))
+			return -EFAULT;
+
+		if (edata.data)
+			newcmd |= RxChkSum;
+		else
+			newcmd &= ~RxChkSum;
+
+		if (newcmd == cmd)
+			return 0;
+
+		spin_lock_irq(&cp->lock);
+		cpw16_f(CpCmd, newcmd);
+		spin_unlock_irq(&cp->lock);
+	}
+
+	/* get/set TX checksumming */
+	case ETHTOOL_GTXCSUM: {
+		struct ethtool_value edata = { ETHTOOL_GTXCSUM };
+
+		edata.data = (cp->dev->features & NETIF_F_IP_CSUM) != 0;
+		if (copy_to_user(useraddr, &edata, sizeof(edata)))
+			return -EFAULT;
+		return 0;
+	}
+	case ETHTOOL_STXCSUM: {
+		struct ethtool_value edata;
+
+		if (copy_from_user(&edata, useraddr, sizeof(edata)))
+			return -EFAULT;
+
+		if (edata.data)
+			cp->dev->features |= NETIF_F_IP_CSUM;
+		else
+			cp->dev->features &= ~NETIF_F_IP_CSUM;
+
+		return 0;
+	}
+
+	/* get/set scatter-gather */
+	case ETHTOOL_GSG: {
+		struct ethtool_value edata = { ETHTOOL_GSG };
+
+		edata.data = (cp->dev->features & NETIF_F_SG) != 0;
+		if (copy_to_user(useraddr, &edata, sizeof(edata)))
+			return -EFAULT;
+		return 0;
+	}
+	case ETHTOOL_SSG: {
+		struct ethtool_value edata;
+
+		if (copy_from_user(&edata, useraddr, sizeof(edata)))
+			return -EFAULT;
+
+		if (edata.data)
+			cp->dev->features |= NETIF_F_SG;
+		else
+			cp->dev->features &= ~NETIF_F_SG;
+
+		return 0;
+	}
+
+	/* get string list(s) */
+	case ETHTOOL_GSTRINGS: {
+		struct ethtool_gstrings estr = { ETHTOOL_GSTRINGS };
+
+		if (copy_from_user(&estr, useraddr, sizeof(estr)))
+			return -EFAULT;
+		if (estr.string_set != ETH_SS_STATS)
+			return -EINVAL;
+
+		estr.len = CP_NUM_STATS;
+		if (copy_to_user(useraddr, &estr, sizeof(estr)))
+			return -EFAULT;
+		if (copy_to_user(useraddr + sizeof(estr),
+				 &ethtool_stats_keys,
+				 sizeof(ethtool_stats_keys)))
+			return -EFAULT;
+		return 0;
+	}
+
+	/* get NIC-specific statistics */
+	case ETHTOOL_GSTATS: {
+		struct ethtool_stats estats = { ETHTOOL_GSTATS };
+		u64 *tmp_stats;
+		unsigned int work = 100;
+		const unsigned int sz = sizeof(u64) * CP_NUM_STATS;
+		int i;
+
+		/* begin NIC statistics dump */
+		cpw32(StatsAddr + 4, 0); /* FIXME: 64-bit PCI */
+		cpw32(StatsAddr, cp->nic_stats_dma | DumpStats);
+		cpr32(StatsAddr);
+
+		estats.n_stats = CP_NUM_STATS;
+		if (copy_to_user(useraddr, &estats, sizeof(estats)))
+			return -EFAULT;
+
+		while (work-- > 0) {
+			if ((cpr32(StatsAddr) & DumpStats) == 0)
+				break;
+			cpu_relax();
+		}
+
+		if (cpr32(StatsAddr) & DumpStats)
+			return -EIO;
+
+		tmp_stats = kmalloc(sz, GFP_KERNEL);
+		if (!tmp_stats)
+			return -ENOMEM;
+		memset(tmp_stats, 0, sz);
+
+		i = 0;
+		tmp_stats[i++] = le64_to_cpu(cp->nic_stats->tx_ok);
+		tmp_stats[i++] = le64_to_cpu(cp->nic_stats->rx_ok);
+		tmp_stats[i++] = le64_to_cpu(cp->nic_stats->tx_err);
+		tmp_stats[i++] = le32_to_cpu(cp->nic_stats->rx_err);
+		tmp_stats[i++] = le16_to_cpu(cp->nic_stats->rx_fifo);
+		tmp_stats[i++] = le16_to_cpu(cp->nic_stats->frame_align);
+		tmp_stats[i++] = le32_to_cpu(cp->nic_stats->tx_ok_1col);
+		tmp_stats[i++] = le32_to_cpu(cp->nic_stats->tx_ok_mcol);
+		tmp_stats[i++] = le64_to_cpu(cp->nic_stats->rx_ok_phys);
+		tmp_stats[i++] = le64_to_cpu(cp->nic_stats->rx_ok_bcast);
+		tmp_stats[i++] = le32_to_cpu(cp->nic_stats->rx_ok_mcast);
+		tmp_stats[i++] = le16_to_cpu(cp->nic_stats->tx_abort);
+		tmp_stats[i++] = le16_to_cpu(cp->nic_stats->tx_underrun);
+		tmp_stats[i++] = cp->cp_stats.rx_frags;
+		if (i != CP_NUM_STATS)
+			BUG();
+
+		i = copy_to_user(useraddr + sizeof(estats),
+				 tmp_stats, sz);
+		kfree(tmp_stats);
+
+		if (i)
+			return -EFAULT;
+		return 0;
+	}
+
+	/* get/set Wake-on-LAN settings */
+	case ETHTOOL_GWOL: {
+		struct ethtool_wolinfo wol = { ETHTOOL_GWOL };
+		
+		spin_lock_irq (&cp->lock);
+		netdev_get_wol (cp, &wol);
+		spin_unlock_irq (&cp->lock);
+		return ((copy_to_user (useraddr, &wol, sizeof (wol)))? -EFAULT : 0);
+	}
+	
+	case ETHTOOL_SWOL: {
+		struct ethtool_wolinfo wol;
+
+		if (copy_from_user (&wol, useraddr, sizeof (wol)))
+			return -EFAULT;
+		spin_lock_irq (&cp->lock);
+		netdev_set_wol (cp, &wol);
+		spin_unlock_irq (&cp->lock);
+		return 0;
+	}
+
 	default:
 		break;
 	}
@@ -1220,20 +1655,18 @@
 static int cp_ioctl (struct net_device *dev, struct ifreq *rq, int cmd)
 {
 	struct cp_private *cp = dev->priv;
-	int rc = 0;
+	struct mii_ioctl_data *mii = (struct mii_ioctl_data *) &rq->ifr_data;
+	int rc;
 
 	if (!netif_running(dev))
 		return -EINVAL;
 
-	switch (cmd) {
-	case SIOCETHTOOL:
+	if (cmd == SIOCETHTOOL)
 		return cp_ethtool_ioctl(cp, (void *) rq->ifr_data);
 
-	default:
-		rc = -EOPNOTSUPP;
-		break;
-	}
-
+	spin_lock_irq(&cp->lock);
+	rc = generic_mii_ioctl(&cp->mii_if, mii, cmd, NULL);
+	spin_unlock_irq(&cp->lock);
 	return rc;
 }
 
@@ -1321,6 +1754,13 @@
 	return retval;
 }
 
+/* Put the board into D3cold state and wait for WakeUp signal */
+static void cp_set_d3_state (struct cp_private *cp)
+{
+	pci_enable_wake (cp->pdev, 0, 1); /* Enable PME# generation */
+	pci_set_power_state (cp->pdev, 3);
+}
+
 static int __devinit cp_init_one (struct pci_dev *pdev,
 				  const struct pci_device_id *ent)
 {
@@ -1329,9 +1769,10 @@
 	int rc;
 	void *regs;
 	long pciaddr;
-	unsigned addr_len, i;
+	unsigned int addr_len, i;
 	u8 pci_rev, cache_size;
 	u16 pci_command;
+	unsigned int board_type = (unsigned int) ent->driver_data;
 
 #ifndef MODULE
 	static int version_printed;
@@ -1355,6 +1796,7 @@
 	SET_MODULE_OWNER(dev);
 	cp = dev->priv;
 	cp->pdev = pdev;
+	cp->board_type = board_type;
 	cp->dev = dev;
 	cp->msg_enable = (debug < 0 ? CP_DEF_MSG_ENABLE : debug);
 	spin_lock_init (&cp->lock);
@@ -1362,6 +1804,8 @@
 	cp->mii_if.mdio_read = mdio_read;
 	cp->mii_if.mdio_write = mdio_write;
 	cp->mii_if.phy_id = CP_INTERNAL_PHY;
+	cp->mii_if.phy_id_mask = 0x1f;
+	cp->mii_if.reg_num_mask = 0x1f;
 	cp_set_rxbufsize(cp);
 
 	rc = pci_enable_device(pdev);
@@ -1392,6 +1836,19 @@
 		goto err_out_res;
 	}
 
+	/* Configure DMA attributes. */
+	if (!pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff)) {
+		cp->pci_using_dac = 1;
+	} else {
+		rc = pci_set_dma_mask(pdev, (u64) 0xffffffff);
+		if (rc) {
+			printk(KERN_ERR PFX "No usable DMA configuration, "
+			       "aborting.\n");
+			goto err_out_res;
+		}
+		cp->pci_using_dac = 0;
+	}
+
 	regs = ioremap_nocache(pciaddr, CP_REGS_SIZE);
 	if (!regs) {
 		rc = -EIO;
@@ -1416,7 +1873,9 @@
 	dev->hard_start_xmit = cp_start_xmit;
 	dev->get_stats = cp_get_stats;
 	dev->do_ioctl = cp_ioctl;
+#ifdef BROKEN
 	dev->change_mtu = cp_change_mtu;
+#endif
 #if 0
 	dev->tx_timeout = cp_tx_timeout;
 	dev->watchdog_timeo = TX_TIMEOUT;
@@ -1440,7 +1899,7 @@
 		"%02x:%02x:%02x:%02x:%02x:%02x, "
 		"IRQ %d\n",
 		dev->name,
-		"RTL-8139C+",
+		cp_board_tbl[board_type].name,
 		dev->base_addr,
 		dev->dev_addr[0], dev->dev_addr[1],
 		dev->dev_addr[2], dev->dev_addr[3],
@@ -1477,6 +1936,8 @@
 	}
 	pci_set_master(pdev);
 
+	if (cp->wol_enabled) cp_set_d3_state (cp);
+
 	return 0;
 
 err_out_iomap:
@@ -1499,17 +1960,75 @@
 		BUG();
 	unregister_netdev(dev);
 	iounmap(cp->regs);
+	if (cp->wol_enabled) pci_set_power_state (pdev, 0);
 	pci_release_regions(pdev);
 	pci_disable_device(pdev);
 	pci_set_drvdata(pdev, NULL);
 	kfree(dev);
 }
 
+#ifdef CONFIG_PM
+static int cp_suspend (struct pci_dev *pdev, u32 state)
+{
+	struct net_device *dev;
+	struct cp_private *cp;
+	unsigned long flags;
+
+	dev = pci_get_drvdata (pdev);
+	cp  = dev->priv;
+
+	if (!dev || !netif_running (dev)) return 0;
+
+	netif_device_detach (dev);
+	netif_stop_queue (dev);
+
+	spin_lock_irqsave (&cp->lock, flags);
+
+	/* Disable Rx and Tx */
+	cpw16 (IntrMask, 0);
+	cpw8  (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn));
+
+	spin_unlock_irqrestore (&cp->lock, flags);
+
+	if (cp->pdev && cp->wol_enabled) {
+		pci_save_state (cp->pdev, cp->power_state);
+		cp_set_d3_state (cp);
+	}
+
+	return 0;
+}
+
+static int cp_resume (struct pci_dev *pdev)
+{
+	struct net_device *dev;
+	struct cp_private *cp;
+
+	dev = pci_get_drvdata (pdev);
+	cp  = dev->priv;
+
+	netif_device_attach (dev);
+	
+	if (cp->pdev && cp->wol_enabled) {
+		pci_set_power_state (cp->pdev, 0);
+		pci_restore_state (cp->pdev, cp->power_state);
+	}
+	
+	cp_init_hw (cp);
+	netif_start_queue (dev);
+	
+	return 0;
+}
+#endif /* CONFIG_PM */
+
 static struct pci_driver cp_driver = {
-	name:		DRV_NAME,
-	id_table:	cp_pci_tbl,
-	probe:		cp_init_one,
-	remove:		__devexit_p(cp_remove_one),
+	.name         = DRV_NAME,
+	.id_table     = cp_pci_tbl,
+	.probe        =	cp_init_one,
+	.remove       = __devexit_p(cp_remove_one),
+#ifdef CONFIG_PM
+	.resume       = cp_resume,
+	.suspend      = cp_suspend,
+#endif
 };
 
 static int __init cp_init (void)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)