Netdev List
 help / color / mirror / Atom feed
* [PATCH 2/2] add driver for enc28j60 ethernet chip
From: Claudio Lanconelli @ 2007-12-11 14:58 UTC (permalink / raw)
  To: netdev; +Cc: jgarzik

[-- Attachment #1: Type: text/plain, Size: 138 bytes --]

Second part contains changes on Kconfig and Makefile on dir drivers/net

Signed-off-by: Claudio Lanconelli <lanconelli.claudio@eptar.com>

[-- Attachment #2: enc28j60_p2.diff --]
[-- Type: text/x-patch, Size: 1605 bytes --]

 drivers/net/Kconfig  |   26 ++++++++++++++++++++++++++
 drivers/net/Makefile |    1 +
 2 files changed, 27 insertions(+), 0 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index d9107e5..e643e0f 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -922,6 +922,32 @@ config DM9000
 	  To compile this driver as a module, choose M here.  The module
 	  will be called dm9000.
 
+config ENC28J60
+	tristate "ENC28J60 support"
+	depends on EXPERIMENTAL && SPI && NET_ETHERNET
+	select CRC32
+	select MII
+	---help---
+	  Support for the Microchip EN28J60 ethernet chip.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/networking/net-modules.txt>.  The module will be
+	  called enc28j60.
+
+config ENC28J60_DBGLEVEL
+	int "Debugging verbosity (0 = quiet, 3 = noisy)"
+	depends on ENC28J60
+	default "0"
+	---help---
+	  Determines the verbosity level of the enc28j60 driver debugging messages.
+
+config ENC28J60_WRITEVERIFY
+	bool "Enable write verify"
+	depends on ENC28J60
+	---help---
+	  Enable the verify after the buffer write useful for debugging purpose.
+	  If unsure, say N.
+
 config SMC911X
 	tristate "SMSC LAN911[5678] support"
 	select CRC32
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 0e5fde4..e91e3a3 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -216,6 +216,7 @@ obj-$(CONFIG_DM9000) += dm9000.o
 obj-$(CONFIG_FEC_8XX) += fec_8xx/
 obj-$(CONFIG_PASEMI_MAC) += pasemi_mac.o
 obj-$(CONFIG_MLX4_CORE) += mlx4/
+obj-$(CONFIG_ENC28J60) += enc28j60.o
 
 obj-$(CONFIG_MACB) += macb.o
 



^ permalink raw reply related

* [PATCH 1/2] add driver for enc28j60 ethernet chip
From: Claudio Lanconelli @ 2007-12-11 14:57 UTC (permalink / raw)
  To: netdev; +Cc: jgarzik

[-- Attachment #1: Type: text/plain, Size: 237 bytes --]

These patches add support for Microchip enc28j60 ethernet chip 
controlled via SPI.
I tested it on my custom board (S162) with ARM9 s3c2442 SoC.
Any comments are welcome.

Signed-off-by: Claudio Lanconelli <lanconelli.claudio@eptar.com>

[-- Attachment #2: enc28j60_p1.diff --]
[-- Type: text/x-patch, Size: 51496 bytes --]

 drivers/net/enc28j60.c    | 1400 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/enc28j60_hw.h |  303 ++++++++++
 2 files changed, 1703 insertions(+), 0 deletions(-)

diff --git a/drivers/net/enc28j60.c b/drivers/net/enc28j60.c
new file mode 100644
index 0000000..6182473
--- /dev/null
+++ b/drivers/net/enc28j60.c
@@ -0,0 +1,1400 @@
+/*
+ * Microchip ENC28J60 ethernet driver (MAC + PHY)
+ *
+ * Copyright (C) 2007 Eurek srl
+ * Author: Claudio Lanconelli <lanconelli.claudio@eptar.com>
+ * based on enc28j60.c written by David Anders for 2.4 kernel version
+ *
+ * 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.
+ *
+ * $Id: enc28j60.c,v 1.10 2007/12/10 16:59:37 claudio Exp $
+ */
+
+#include <linux/autoconf.h>
+
+#if CONFIG_ENC28J60_DBGLEVEL > 1
+# define VERBOSE_DEBUG
+#endif
+#if CONFIG_ENC28J60_DBGLEVEL > 0
+# define DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <asm/semaphore.h>
+
+#include "enc28j60_hw.h"
+
+/* Buffer size required for the largest SPI transfer (i.e., reading a
+ * frame). */
+#define SPI_TRANSFER_BUF_LEN	(4 + MAX_FRAMELEN)
+
+#define MY_TX_TIMEOUT  ((500*HZ)/1000)
+
+/* Max TX retries in case of collision as suggested by errata datasheet */
+#define MAX_TX_RETRYCOUNT	16
+
+/* Driver local data */
+struct enc28j60_net_local {
+	struct net_device_stats stats;
+
+	struct net_device *netdev;
+	struct spi_device *spi;
+	struct semaphore semlock;	/* protect spi_transfer_buf */
+	uint8_t *spi_transfer_buf;
+	struct sk_buff *tx_skb;
+	struct work_struct tx_work;
+	struct work_struct irq_work;
+	int bank;			/* current register bank selected */
+	uint16_t next_pk_ptr;		/* next packet pointer within FIFO */
+	int max_pk_counter;		/* statistics: max packet counter */
+	int tx_retry_count;
+	int hw_enable;
+};
+
+/* Selects Full duplex vs. Half duplex mode */
+static int full_duplex = 0;
+
+static int enc28j60_send_packet(struct sk_buff *skb, struct net_device *dev);
+static int enc28j60_net_close(struct net_device *dev);
+static struct net_device_stats *enc28j60_net_get_stats(struct net_device *dev);
+static void enc28j60_set_multicast_list(struct net_device *dev);
+static void enc28j60_net_tx_timeout(struct net_device *ndev);
+
+static int enc28j60_chipset_init(struct net_device *dev);
+static void enc28j60_hw_disable(struct enc28j60_net_local *priv);
+static void enc28j60_hw_enable(struct enc28j60_net_local *priv);
+static void enc28j60_hw_rx(struct enc28j60_net_local *priv);
+static void enc28j60_hw_tx(struct enc28j60_net_local *priv);
+
+/* Basic SPI operations */
+static int spi_read_buf(struct enc28j60_net_local *priv, int len,
+			uint8_t *data);
+static int spi_write_buf(struct enc28j60_net_local *priv, int len,
+			const uint8_t * data);
+static uint8_t spi_read_op(struct enc28j60_net_local *priv, uint8_t op,
+			uint8_t addr);
+static int spi_write_op(struct enc28j60_net_local *priv, uint8_t op,
+			uint8_t addr, uint8_t val);
+
+/* Low level routines */
+
+/* utility function for register access routines */
+static void enc28j60_set_bank(struct enc28j60_net_local *priv, uint8_t address);
+
+static inline int enc28j60_regb_read(struct enc28j60_net_local *priv,
+					uint8_t address);
+static inline int enc28j60_regw_read(struct enc28j60_net_local *priv,
+					uint8_t address);
+static inline void enc28j60_regb_write(struct enc28j60_net_local *priv,
+					uint8_t address, uint8_t data);
+static inline void enc28j60_regw_write(struct enc28j60_net_local *priv,
+					uint8_t address, uint16_t data);
+
+static inline void enc28j60_reg_bfset(struct enc28j60_net_local *priv,
+					uint8_t reg, uint8_t mask);
+static inline void enc28j60_reg_bfclr(struct enc28j60_net_local *priv,
+					uint8_t reg, uint8_t mask);
+
+static void enc28j60_soft_reset(struct enc28j60_net_local *priv);
+
+/* debug routines */
+static void dump_packet(struct enc28j60_net_local *priv, const char *msg,
+			int len, const char *data);
+static void enc28j60_dump_tsv(struct enc28j60_net_local *priv, const char *msg,
+			uint8_t tsv[TSV_SIZE]);
+static void enc28j60_dump_rsv(struct enc28j60_net_local *priv, const char *msg,
+			uint16_t pk_ptr, int len, uint16_t sts);
+static void enc28j60_dump_regs(struct enc28j60_net_local *priv,
+			const char *msg);
+
+/*
+ * SPI read buffer
+ * wait for the SPI transfer and copy received data to destination
+ */
+static int
+spi_read_buf(struct enc28j60_net_local *priv, int len, uint8_t *data)
+{
+	uint8_t *rx_buf;
+	uint8_t *tx_buf;
+	struct spi_transfer t;
+	struct spi_message msg;
+	int ret, slen;
+
+	slen = 1;
+	memset(&t, 0, sizeof(t));
+	t.tx_buf = tx_buf = priv->spi_transfer_buf;
+	t.rx_buf = rx_buf = priv->spi_transfer_buf + 4;
+	t.len = slen + len;
+
+	down(&priv->semlock);
+	tx_buf[0] = ENC28J60_READ_BUF_MEM;
+	tx_buf[1] = tx_buf[2] = tx_buf[3] = 0;	/* don't care */
+
+	spi_message_init(&msg);
+	spi_message_add_tail(&t, &msg);
+	ret = spi_sync(priv->spi, &msg);
+	if (ret == 0) {
+		memcpy(data, &rx_buf[slen], len);
+		ret = msg.status;
+	}
+	up(&priv->semlock);
+	if (ret != 0)
+		dev_dbg(&priv->netdev->dev, "%s: failed: ret = %d\n",
+			__FUNCTION__, ret);
+
+	return ret;
+}
+
+/*
+ * SPI write buffer
+ */
+static int spi_write_buf(struct enc28j60_net_local *priv, int len,
+			 const uint8_t * data)
+{
+	int ret;
+
+	if (len > SPI_TRANSFER_BUF_LEN - 1 || len <= 0)
+		ret = -EINVAL;
+	else {
+		down(&priv->semlock);
+		priv->spi_transfer_buf[0] = ENC28J60_WRITE_BUF_MEM;
+		memcpy(&priv->spi_transfer_buf[1], data, len);
+		ret = spi_write(priv->spi, priv->spi_transfer_buf, len + 1);
+		up(&priv->semlock);
+		if (ret != 0)
+			dev_dbg(&priv->netdev->dev, "%s: failed: ret = %d\n",
+				__FUNCTION__, ret);
+	}
+	return ret;
+}
+
+/*
+ * basic SPI read operation
+ */
+static uint8_t spi_read_op(struct enc28j60_net_local *priv, uint8_t op,
+			   uint8_t addr)
+{
+	uint8_t tx_buf[2];
+	uint8_t rx_buf[4];
+	uint8_t val = 0;
+	int ret;
+	int slen;
+
+	slen = 1;
+	/* do dummy read if needed */
+	if (addr & SPRD_MASK)
+		slen++;
+
+	tx_buf[0] = op | (addr & ADDR_MASK);
+	ret = spi_write_then_read(priv->spi, tx_buf, 1, rx_buf, slen);
+	if (ret != 0)
+		dev_err(&priv->netdev->dev, "%s: failed: ret = %d\n",
+			__FUNCTION__, ret);
+	else
+		val = rx_buf[slen - 1];
+
+	return val;
+}
+
+/*
+ * basic SPI write operation
+ */
+static int spi_write_op(struct enc28j60_net_local *priv, uint8_t op,
+			uint8_t addr, uint8_t val)
+{
+	int ret;
+
+	down(&priv->semlock);
+	priv->spi_transfer_buf[0] = op | (addr & ADDR_MASK);
+	priv->spi_transfer_buf[1] = val;
+	ret = spi_write(priv->spi, priv->spi_transfer_buf, 2);
+	up(&priv->semlock);
+	if (ret != 0)
+		dev_dbg(&priv->netdev->dev, "%s: failed: ret = %d\n",
+			__FUNCTION__, ret);
+	return ret;
+}
+
+/*
+ * Issue a soft reset command
+ */
+static void enc28j60_soft_reset(struct enc28j60_net_local *priv)
+{
+	dev_vdbg(&priv->netdev->dev, "%s\n", __FUNCTION__);
+
+	spi_write_op(priv, ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
+	/* Errata workaround #1, CLKRDY check is unreliable,
+	 * delay 1 mS instead */
+	udelay(1000);
+}
+
+/*
+ * select the current register bank if necessary
+ */
+static void enc28j60_set_bank(struct enc28j60_net_local *priv, uint8_t addr)
+{
+	if ((addr & BANK_MASK) != priv->bank) {
+		uint8_t b = (addr & BANK_MASK) >> 5;
+
+		if (b != (ECON1_BSEL1 | ECON1_BSEL0))
+			spi_write_op(priv, ENC28J60_BIT_FIELD_CLR, ECON1,
+				     ECON1_BSEL1 | ECON1_BSEL0);
+		if (b != 0)
+			spi_write_op(priv, ENC28J60_BIT_FIELD_SET, ECON1, b);
+		priv->bank = (addr & BANK_MASK);
+	}
+}
+
+/*
+ * Register bit field Set
+ */
+static inline void enc28j60_reg_bfset(struct enc28j60_net_local *priv,
+				      uint8_t addr, uint8_t mask)
+{
+	enc28j60_set_bank(priv, addr);
+	spi_write_op(priv, ENC28J60_BIT_FIELD_SET, addr, mask);
+}
+
+/*
+ * Register bit field Clear
+ */
+static inline void enc28j60_reg_bfclr(struct enc28j60_net_local *priv,
+				      uint8_t addr, uint8_t mask)
+{
+	enc28j60_set_bank(priv, addr);
+	spi_write_op(priv, ENC28J60_BIT_FIELD_CLR, addr, mask);
+}
+
+/*
+ * Register byte read
+ */
+static inline int enc28j60_regb_read(struct enc28j60_net_local *priv,
+				     uint8_t address)
+{
+	enc28j60_set_bank(priv, address);
+	return spi_read_op(priv, ENC28J60_READ_CTRL_REG, address);
+}
+
+/*
+ * Register word read
+ */
+static inline int enc28j60_regw_read(struct enc28j60_net_local *priv,
+				     uint8_t address)
+{
+	int rl, rh;
+
+	enc28j60_set_bank(priv, address);
+	rl = spi_read_op(priv, ENC28J60_READ_CTRL_REG, address);
+	rh = spi_read_op(priv, ENC28J60_READ_CTRL_REG, address + 1);
+
+	return (rh << 8) | rl;
+}
+
+/*
+ * Register byte write
+ */
+static inline void enc28j60_regb_write(struct enc28j60_net_local *priv,
+				       uint8_t address, uint8_t data)
+{
+	enc28j60_set_bank(priv, address);
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, address, data);
+}
+
+/*
+ * Register word write
+ */
+static inline void enc28j60_regw_write(struct enc28j60_net_local *priv,
+				       uint8_t address, uint16_t data)
+{
+	enc28j60_set_bank(priv, address);
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, address, (uint8_t) data);
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, address + 1,
+		     (uint8_t) (data >> 8));
+}
+
+/*
+ * Buffer memory read
+ * Select the starting address and execute a SPI buffer read
+ */
+static inline void enc28j60_mem_read(struct enc28j60_net_local *priv,
+				     uint16_t addr, int len, uint8_t * data)
+{
+	enc28j60_regw_write(priv, ERDPTL, addr);
+
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	{
+		uint16_t reg;
+		reg = enc28j60_regw_read(priv, ERDPTL);
+		if (reg != addr) {
+			dev_dbg(&priv->netdev->dev,
+				"%s() error writing ERDPT (0x%04x - 0x%04x)\n",
+				__FUNCTION__, reg, addr);
+		}
+	}
+#endif
+	spi_read_buf(priv, len, data);
+}
+
+/*
+ * Wait until the PHY operation is complete.
+ */
+static inline int wait_phy_ready(struct enc28j60_net_local *priv)
+{
+	unsigned long timeout = jiffies + 20 * HZ / 1000;
+	int ret = 1;
+
+	/* 20msec timeout read */
+	while ((enc28j60_regb_read(priv, MISTAT) & MISTAT_BUSY) != 0) {
+		if (time_after(jiffies, timeout)) {
+			dev_dbg(&priv->netdev->dev,
+				"enc28j60: PHY ready timeout!\n");
+			ret = 0;
+			break;
+		}
+		cpu_relax();
+	}
+	return ret;
+}
+
+/*
+ * PHY register read
+ * PHY registers are not accessed directly
+ */
+static uint16_t enc28j60_phy_read(struct enc28j60_net_local *priv,
+				  uint8_t address)
+{
+	/* set the PHY register address */
+	enc28j60_regb_write(priv, MIREGADR, address);
+	/* start the register read operation */
+	enc28j60_regb_write(priv, MICMD, MICMD_MIIRD);
+	udelay(11);
+	/* wait until the PHY read completes */
+	wait_phy_ready(priv);
+	/* quit reading */
+	enc28j60_regb_write(priv, MICMD, 0x00);
+	/* return the data */
+	return enc28j60_regw_read(priv, MIRDL);
+}
+
+static int enc28j60_phy_write(struct enc28j60_net_local *priv, uint8_t address,
+			      uint16_t data)
+{
+	/* set the PHY register address */
+	enc28j60_regb_write(priv, MIREGADR, address);
+	/* write the PHY data */
+	enc28j60_regw_write(priv, MIWRL, data);
+	udelay(11);
+	/* wait until the PHY write completes and return */
+	return wait_phy_ready(priv);
+}
+
+/*
+ * read MAC address registers
+ */
+static void enc28j60_get_hw_macaddr(struct enc28j60_net_local *priv)
+{
+	struct net_device *ndev = priv->netdev;
+
+	/* NOTE: MAC address in ENC28J60 is byte-backward */
+	ndev->dev_addr[0] = enc28j60_regb_read(priv, MAADR5);
+	ndev->dev_addr[1] = enc28j60_regb_read(priv, MAADR4);
+	ndev->dev_addr[2] = enc28j60_regb_read(priv, MAADR3);
+	ndev->dev_addr[3] = enc28j60_regb_read(priv, MAADR2);
+	ndev->dev_addr[4] = enc28j60_regb_read(priv, MAADR1);
+	ndev->dev_addr[5] = enc28j60_regb_read(priv, MAADR0);
+
+	dev_dbg(&priv->spi->dev,
+		"%s() Get MAC address %02x:%02x:%02x:%02x:%02x:%02x\n",
+		__FUNCTION__, ndev->dev_addr[0], ndev->dev_addr[1],
+		ndev->dev_addr[2], ndev->dev_addr[3], ndev->dev_addr[4],
+		ndev->dev_addr[5]);
+}
+
+/*
+ * Program the hardware MAC address from dev->dev_addr.
+ */
+static void enc28j60_set_hw_macaddr(struct enc28j60_net_local *priv)
+{
+	struct net_device *ndev = priv->netdev;
+
+	if (!priv->hw_enable) {
+		/* NOTE: MAC address in ENC28J60 is byte-backward */
+		enc28j60_regb_write(priv, MAADR5, ndev->dev_addr[0]);
+		enc28j60_regb_write(priv, MAADR4, ndev->dev_addr[1]);
+		enc28j60_regb_write(priv, MAADR3, ndev->dev_addr[2]);
+		enc28j60_regb_write(priv, MAADR2, ndev->dev_addr[3]);
+		enc28j60_regb_write(priv, MAADR1, ndev->dev_addr[4]);
+		enc28j60_regb_write(priv, MAADR0, ndev->dev_addr[5]);
+
+		dev_dbg(&ndev->dev,
+			"%s() [%s] Setting MAC address to "
+			"%02x:%02x:%02x:%02x:%02x:%02x\n",
+			__FUNCTION__, ndev->name, ndev->dev_addr[0],
+			ndev->dev_addr[1], ndev->dev_addr[2], ndev->dev_addr[3],
+			ndev->dev_addr[4], ndev->dev_addr[5]);
+	} else
+		dev_dbg(&ndev->dev,
+			"%s() Warning: hw must be disabled to set hw "
+			"Mac address\n", __FUNCTION__);
+}
+
+/*
+ * Store the new hardware address in dev->dev_addr, and update the MAC.
+ */
+static int enc28j60_set_mac_address(struct net_device *dev, void *addr)
+{
+	struct sockaddr *address = addr;
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	if (!is_valid_ether_addr(address->sa_data))
+		return -EADDRNOTAVAIL;
+
+	memcpy(dev->dev_addr, address->sa_data, dev->addr_len);
+	enc28j60_set_hw_macaddr(priv);
+
+	return 0;
+}
+
+static void enc28j60_dump_regs(struct enc28j60_net_local *priv, const char *msg)
+{
+	dev_dbg(&priv->spi->dev, "%s - HwRevID: 0x%02x\n", msg,
+		enc28j60_regb_read(priv, EREVID));
+
+#if CONFIG_ENC28J60_DBGLEVEL > 2
+	printk("Cntrl: ECON1 ECON2 ESTAT  EIR  EIE\n");
+	printk("       0x%02x  ", enc28j60_regb_read(priv, ECON1));
+	printk("0x%02x  ", enc28j60_regb_read(priv, ECON2));
+	printk("0x%02x  ", enc28j60_regb_read(priv, ESTAT));
+	printk("0x%02x  ", enc28j60_regb_read(priv, EIR));
+	printk("0x%02x\n", enc28j60_regb_read(priv, EIE));
+
+	printk("MAC  : MACON1 MACON3 MACON4 MAC-Address\n");
+	printk("       0x%02x   ", enc28j60_regb_read(priv, MACON1));
+	printk("0x%02x   ", enc28j60_regb_read(priv, MACON3));
+	printk("0x%02x   ", enc28j60_regb_read(priv, MACON4));
+	printk("%02x:", enc28j60_regb_read(priv, MAADR5));
+	printk("%02x:", enc28j60_regb_read(priv, MAADR4));
+	printk("%02x:", enc28j60_regb_read(priv, MAADR3));
+	printk("%02x:", enc28j60_regb_read(priv, MAADR2));
+	printk("%02x:", enc28j60_regb_read(priv, MAADR1));
+	printk("%02x\n", enc28j60_regb_read(priv, MAADR0));
+
+	printk("Rx   : ERXST  ERXND  ERXWRPT ERXRDPT ERXFCON EPKTCNT MAMXFL\n");
+	printk("       0x%04x ", enc28j60_regw_read(priv, ERXSTL));
+	printk("0x%04x ", enc28j60_regw_read(priv, ERXNDL));
+	printk("0x%04x  ", enc28j60_regw_read(priv, ERXWRPTL));
+	printk("0x%04x  ", enc28j60_regw_read(priv, ERXRDPTL));
+	printk("0x%02x    ", enc28j60_regb_read(priv, ERXFCON));
+	printk("0x%02x    ", enc28j60_regb_read(priv, EPKTCNT));
+	printk("0x%04x\n", enc28j60_regw_read(priv, MAMXFLL));
+
+	printk("Tx   : ETXST  ETXND  MACLCON1 MACLCON2 MAPHSUP\n");
+	printk("       0x%04x ", enc28j60_regw_read(priv, ETXSTL));
+	printk("0x%04x ", enc28j60_regw_read(priv, ETXNDL));
+	printk("0x%02x     ", enc28j60_regb_read(priv, MACLCON1));
+	printk("0x%02x     ", enc28j60_regb_read(priv, MACLCON2));
+	printk("0x%02x\n", enc28j60_regb_read(priv, MAPHSUP));
+#endif
+}
+
+/*
+ * ERXRDPT need to be set always at odd addresses, refer to errata datasheet
+ */
+static inline uint16_t erxrdpt_workaround(uint16_t next_packet_ptr,
+					  uint16_t start, uint16_t end)
+{
+	uint16_t erxrdpt;
+
+	if ((next_packet_ptr - 1 < start) || (next_packet_ptr - 1 > end)) {
+		erxrdpt = end;
+	} else
+		erxrdpt = next_packet_ptr - 1;
+
+	return erxrdpt;
+}
+
+static void enc28j60_rxfifo_init(struct enc28j60_net_local *priv,
+				 uint16_t start, uint16_t end)
+{
+	uint16_t erxrdpt;
+
+	if (start > 0x1FFF || end > 0x1FFF || start > end)
+		dev_err(&priv->netdev->dev,
+			"%s(%d, %d) RXFIFO bad parameters, fatal error!!\n",
+			__FUNCTION__, start, end);
+
+	priv->next_pk_ptr = start;
+	/* set receive buffer start */
+	enc28j60_regw_write(priv, ERXSTL, start);
+	/* set receive pointer address */
+	erxrdpt = erxrdpt_workaround(priv->next_pk_ptr, start, end);
+	enc28j60_regw_write(priv, ERXRDPTL, erxrdpt);
+	/* set receive buffer end */
+	enc28j60_regw_write(priv, ERXNDL, end);
+}
+
+static void enc28j60_txfifo_init(struct enc28j60_net_local *priv,
+				 uint16_t start, uint16_t end)
+{
+	if (start > 0x1FFF || end > 0x1FFF || start > end)
+		dev_err(&priv->netdev->dev,
+			"%s(%d, %d) TXFIFO bad parameters, fatal error!!\n",
+			__FUNCTION__, start, end);
+
+	/* set transmit buffer start */
+	enc28j60_regw_write(priv, ETXSTL, start);
+	/* set transmit buffer end */
+	enc28j60_regw_write(priv, ETXNDL, end);
+}
+
+static int enc28j60_hw_init(struct enc28j60_net_local *priv)
+{
+	uint8_t reg;
+
+	dev_dbg(&priv->spi->dev, "%s() - %s\n",
+		__FUNCTION__, full_duplex ? "FullDuplex" : "HalfDuplex");
+	/* first soft reset the chip */
+	enc28j60_soft_reset(priv);
+
+	dev_vdbg(&priv->spi->dev, "%s() bank0\n", __FUNCTION__);
+
+	/* Clear ECON1 */
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, ECON1, 0x00);
+	priv->bank = 0;
+	priv->hw_enable = 0;
+	priv->tx_retry_count = 0;
+
+	enc28j60_regb_write(priv, ECON2, ECON2_AUTOINC);
+	enc28j60_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT);
+	enc28j60_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
+
+	/*
+	 * Check the RevID.
+	 * If it's 0x00 or 0xFF probably the enc28j60 is not mounted or
+	 * damaged
+	 */
+	reg = enc28j60_regb_read(priv, EREVID);
+	if (reg == 0x00 || reg == 0xff)
+		return 0;
+
+	dev_vdbg(&priv->spi->dev, "%s() bank1\n", __FUNCTION__);
+
+	/* default filter mode: (unicast OR broadcast) AND crc valid */
+	enc28j60_regb_write(priv, ERXFCON,
+			    ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN);
+
+	dev_vdbg(&priv->spi->dev, "%s() bank2\n", __FUNCTION__);
+	/* enable MAC receive */
+	enc28j60_regb_write(priv, MACON1,
+			    MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS);
+	/* enable automatic padding and CRC operations */
+	if (full_duplex) {
+		enc28j60_regb_write(priv, MACON3,
+				    MACON3_PADCFG0 | MACON3_TXCRCEN |
+				    MACON3_FRMLNEN | MACON3_FULDPX);
+		/* set inter-frame gap (non-back-to-back) */
+		enc28j60_regb_write(priv, MAIPGL, 0x12);
+		/* set inter-frame gap (back-to-back) */
+		enc28j60_regb_write(priv, MABBIPG, 0x15);
+	} else {
+		enc28j60_regb_write(priv, MACON3,
+				    MACON3_PADCFG0 | MACON3_TXCRCEN |
+				    MACON3_FRMLNEN);
+		enc28j60_regb_write(priv, MACON4, 1 << 6);	/* DEFER bit */
+		/* set inter-frame gap (non-back-to-back) */
+		enc28j60_regw_write(priv, MAIPGL, 0x0C12);
+		/* set inter-frame gap (back-to-back) */
+		enc28j60_regb_write(priv, MABBIPG, 0x12);
+	}
+	/*
+	 * MACLCON1 (default)
+	 * MACLCON2 (default)
+	 * Set the maximum packet size which the controller will accept
+	 */
+	enc28j60_regw_write(priv, MAMXFLL, MAX_FRAMELEN);
+
+	dev_vdbg(&priv->spi->dev, "%s() bank3\n", __FUNCTION__);
+	/* NOTE: MAC address in ENC28J60 is byte-backward */
+	enc28j60_regb_write(priv, MAADR5, ENC28J60_MAC0);
+	enc28j60_regb_write(priv, MAADR4, ENC28J60_MAC1);
+	enc28j60_regb_write(priv, MAADR3, ENC28J60_MAC2);
+	enc28j60_regb_write(priv, MAADR2, ENC28J60_MAC3);
+	enc28j60_regb_write(priv, MAADR1, ENC28J60_MAC4);
+	enc28j60_regb_write(priv, MAADR0, ENC28J60_MAC5);
+
+	/* no loopback of transmitted frames */
+	dev_vdbg(&priv->spi->dev, "%s() PHY\n", __FUNCTION__);
+
+	/* Configure LEDs */
+	if (!enc28j60_phy_write(priv, PHLCON, ENC28J60_LAMPS_MODE))
+		return 0;
+
+	if (full_duplex) {
+		if (!enc28j60_phy_write(priv, PHCON1, PHCON1_PDPXMD))
+			return 0;
+		if (!enc28j60_phy_write(priv, PHCON2, 0x00))
+			return 0;
+	} else {
+		if (!enc28j60_phy_write(priv, PHCON1, 0x00))
+			return 0;
+		if (!enc28j60_phy_write(priv, PHCON2, PHCON2_HDLDIS))
+			return 0;
+	}
+	enc28j60_dump_regs(priv, "enc28j60 initialized");
+
+	return 1;
+}
+
+static void enc28j60_hw_enable(struct enc28j60_net_local *priv)
+{
+	/* enable interrutps */
+	dev_dbg(&priv->netdev->dev, "%s() enabling interrupts!\n",
+		__FUNCTION__);
+
+	enc28j60_reg_bfclr(priv, EIR, EIR_DMAIF | EIR_LINKIF |
+			   EIR_TXIF | EIR_TXERIF | EIR_RXERIF | EIR_PKTIF);
+	enc28j60_regb_write(priv, EIE, EIE_INTIE | EIE_PKTIE |
+			    EIE_TXIE | EIE_TXERIE | EIE_RXERIE);
+
+	/* enable receive logic */
+	enc28j60_reg_bfset(priv, ECON1, ECON1_RXEN);
+	priv->hw_enable = 1;
+}
+
+static void enc28j60_hw_disable(struct enc28j60_net_local *priv)
+{
+	/* disable interrutps */
+	enc28j60_regb_write(priv, EIE, 0x00);
+	/* disable packet reception */
+	enc28j60_reg_bfclr(priv, ECON1, ECON1_RXEN);
+	priv->hw_enable = 0;
+}
+
+/*
+ * Read the Transmit Status Vector
+ */
+static inline void enc28j60_read_tsv(struct enc28j60_net_local *priv,
+				     uint8_t tsv[TSV_SIZE])
+{
+	int endptr;
+
+	endptr = enc28j60_regw_read(priv, ETXNDL);
+	dev_vdbg(&priv->netdev->dev, "reading TSV at addr:0x%04x\n",
+		 endptr + 1);
+	enc28j60_mem_read(priv, endptr + 1, sizeof(tsv), tsv);
+}
+
+static void enc28j60_dump_tsv(struct enc28j60_net_local *priv, const char *msg,
+			      uint8_t tsv[TSV_SIZE])
+{
+	struct net_device *ndev = priv->netdev;
+	uint16_t tmp1, tmp2;
+
+	dev_vdbg(&ndev->dev, "%s - TSV:\n", msg);
+	tmp1 = tsv[1];
+	tmp1 <<= 8;
+	tmp1 |= tsv[0];
+
+	tmp2 = tsv[5];
+	tmp2 <<= 8;
+	tmp2 |= tsv[4];
+
+	dev_vdbg(&ndev->dev,
+		 "ByteCount: %d, CollisionCount: %d, TotByteOnWire: %d\n", tmp1,
+		 tsv[2] & 0x0f, tmp2);
+	dev_vdbg(&ndev->dev,
+		 "TxDone: %d, CRCErr:%d, LenChkErr: %d, LenOutOfRange: %d\n",
+		 TSV_GETBIT(tsv, TSV_TXDONE), TSV_GETBIT(tsv, TSV_TXCRCERROR),
+		 TSV_GETBIT(tsv, TSV_TXLENCHKERROR),
+		 TSV_GETBIT(tsv, TSV_TXLENOUTOFRANGE));
+	dev_vdbg(&ndev->dev,
+		 "Multicast: %d, Broadcast: %d, PacketDefer: %d, ExDefer: %d\n",
+		 TSV_GETBIT(tsv, TSV_TXMULTICAST),
+		 TSV_GETBIT(tsv, TSV_TXBROADCAST),
+		 TSV_GETBIT(tsv, TSV_TXPACKETDEFER),
+		 TSV_GETBIT(tsv, TSV_TXEXDEFER));
+	dev_vdbg(&ndev->dev,
+		 "ExCollision: %d, LateCollision: %d, Giant: %d, Underrun: %d\n",
+		 TSV_GETBIT(tsv, TSV_TXEXCOLLISION),
+		 TSV_GETBIT(tsv, TSV_TXLATECOLLISION),
+		 TSV_GETBIT(tsv, TSV_TXGIANT), TSV_GETBIT(tsv, TSV_TXUNDERRUN));
+	dev_vdbg(&ndev->dev,
+		 "ControlFrame: %d, PauseFrame: %d, "
+		 "BackPressApp: %d, VLanTagFrame: %d\n",
+		 TSV_GETBIT(tsv, TSV_TXCONTROLFRAME),
+		 TSV_GETBIT(tsv, TSV_TXPAUSEFRAME),
+		 TSV_GETBIT(tsv, TSV_BACKPRESSUREAPP),
+		 TSV_GETBIT(tsv, TSV_TXVLANTAGFRAME));
+}
+
+/*
+ * Calculate free space in RxFIFO
+ */
+static inline int enc28j60_get_free_rxfifo(struct enc28j60_net_local *priv)
+{
+	int epkcnt, erxst, erxnd, erxwr, erxrd;
+	int free_space;
+
+	epkcnt = enc28j60_regb_read(priv, EPKTCNT);
+	if (epkcnt >= 255)
+		free_space = -1;
+	else {
+		erxst = enc28j60_regw_read(priv, ERXSTL);
+		erxnd = enc28j60_regw_read(priv, ERXNDL);
+		erxwr = enc28j60_regw_read(priv, ERXWRPTL);
+		erxrd = enc28j60_regw_read(priv, ERXRDPTL);
+
+		if (erxwr > erxrd)
+			free_space = (erxnd - erxst) - (erxwr - erxrd);
+		else if (erxwr == erxrd)
+			free_space = (erxnd - erxst);
+		else
+			free_space = erxrd - erxwr - 1;
+	}
+	dev_vdbg(&priv->netdev->dev, "%s() free_space = %d\n", __FUNCTION__,
+		 free_space);
+
+	return free_space;
+}
+
+static void enc28j60_irq_work_handler(struct work_struct *work)
+{
+	struct enc28j60_net_local *priv =
+	    container_of(work, struct enc28j60_net_local, irq_work);
+	int intflags, loop, pk_counter;
+
+	dev_vdbg(&priv->netdev->dev, "%s(priv=%p)\n", __FUNCTION__, priv);
+
+	/* disable further interrupts */
+	enc28j60_reg_bfclr(priv, EIE, EIE_INTIE);
+
+	do {
+		loop = 0;
+		intflags = enc28j60_regb_read(priv, EIR);
+		/* DMA interrupt handler */
+		if ((intflags & EIR_DMAIF) != 0) {
+			loop++;
+			dev_vdbg(&priv->netdev->dev, "intDMA(%d)\n", loop);
+			enc28j60_reg_bfclr(priv, EIR, EIR_DMAIF);
+		}
+		/* LINK changed handler */
+		if ((intflags & EIR_LINKIF) != 0) {
+			loop++;
+			dev_vdbg(&priv->netdev->dev, "intLINK(%d)\n", loop);
+			/* read PHIR to clear the flag */
+			enc28j60_phy_read(priv, PHIR);
+		}
+		/* TX complete handler */
+		if ((intflags & EIR_TXIF) != 0) {
+			loop++;
+			dev_vdbg(&priv->netdev->dev, "intTX(%d,skb:%p)\n", loop,
+				 priv->tx_skb);
+
+			priv->tx_retry_count = 0;
+			if (enc28j60_regb_read(priv, ESTAT) & ESTAT_TXABRT) {
+				dev_err(&priv->netdev->dev,
+					"enc28j60 tx error (aborted)\n");
+				priv->stats.tx_errors++;
+			} else
+				priv->stats.tx_packets++;
+			if (priv->tx_skb) {
+				/* update statistics and free skb */
+				priv->stats.tx_bytes += priv->tx_skb->len;
+				dev_kfree_skb(priv->tx_skb);
+				priv->tx_skb = NULL;
+			}
+			netif_wake_queue(priv->netdev);
+			enc28j60_reg_bfclr(priv, ECON1, ECON1_TXRTS);
+			enc28j60_reg_bfclr(priv, EIR, EIR_TXIF);
+		}
+		/* TX Error handler */
+		if ((intflags & EIR_TXERIF) != 0) {
+			uint8_t tsv[TSV_SIZE];
+
+			loop++;
+			dev_vdbg(&priv->netdev->dev, "intTXErr(%d)\n", loop);
+
+			enc28j60_reg_bfclr(priv, ECON1, ECON1_TXRTS);
+			enc28j60_read_tsv(priv, tsv);
+			enc28j60_dump_tsv(priv, __FUNCTION__, tsv);
+			/* Reset TX logic */
+			enc28j60_reg_bfset(priv, ECON1, ECON1_TXRST);
+			enc28j60_reg_bfclr(priv, ECON1, ECON1_TXRST);
+			/* Transmit Late collision check for retransmit */
+			if (TSV_GETBIT(tsv, TSV_TXLATECOLLISION)) {
+				if (priv->tx_retry_count++ < MAX_TX_RETRYCOUNT)
+					enc28j60_reg_bfset(priv, ECON1,
+							   ECON1_TXRTS);
+				else
+					priv->stats.tx_errors++;
+			} else
+				priv->stats.tx_errors++;
+			/* Clear IF flag */
+			enc28j60_reg_bfclr(priv, EIR, EIR_TXERIF);
+		}
+		/* RX Error handler */
+		if ((intflags & EIR_RXERIF) != 0) {
+			loop++;
+			dev_vdbg(&priv->netdev->dev, "intRXErr(%d)\n", loop);
+
+			/* Check free FIFO space to flag RX overrun */
+			if (enc28j60_get_free_rxfifo(priv) <= 0) {
+				dev_err(&priv->netdev->dev,
+					"enc28j60 RX overrun\n");
+				priv->stats.rx_dropped++;
+			}
+			enc28j60_reg_bfclr(priv, EIR, EIR_RXERIF);
+		}
+		/*
+		 * RX handler
+		 * PKTIF don't work reliably!!! (look at the errata datasheet)
+		 * check EPKTCNT is the suggested workaround
+		 * process any packet pending (hw_rx MUST decrement PKCNT for
+		 * any packet processed)
+		 */
+		while ((pk_counter = enc28j60_regb_read(priv, EPKTCNT)) > 0) {
+			loop++;
+			dev_vdbg(&priv->netdev->dev, "intRX(%d), pk_cnt: %d\n",
+				 loop, pk_counter);
+			/* update statistics */
+			if (pk_counter > priv->max_pk_counter) {
+				priv->max_pk_counter = pk_counter;
+				if (priv->max_pk_counter > 4)
+					dev_dbg(&priv->netdev->dev,
+						"enc28j60 RX, max_pk_cnt: %d\n",
+						priv->max_pk_counter);
+				else if (priv->max_pk_counter > 1)
+					dev_vdbg(&priv->netdev->dev,
+						 "enc28j60 RX, max_pk_cnt: %d\n",
+						 priv->max_pk_counter);
+			}
+			/*
+			 * don't need to clear interrupt flag, automatically done
+			 * when enc28j60_hw_rx() decrements the packet counter
+			 */
+			enc28j60_hw_rx(priv);
+		}
+	} while (loop);
+
+	/* re-enable interrupts */
+	enc28j60_reg_bfset(priv, EIE, EIE_INTIE);
+	dev_vdbg(&priv->netdev->dev, "%s() exit\n", __FUNCTION__);
+}
+
+static void dump_packet(struct enc28j60_net_local *priv, const char *msg,
+			int len, const char *data)
+{
+#if CONFIG_ENC28J60_DBGLEVEL > 2
+	int k;
+	struct net_device *ndev = priv->netdev;
+
+	dev_vdbg(&ndev->dev, "%s(), packet len:%d", msg, len);
+	for (k = 0; len--; k++) {
+		if (!(k % 16))
+			printk("\n%04x: ", k);
+		printk("%02x ", data[k]);
+	}
+	printk("\n");
+#endif
+}
+
+/*
+ * Hardware transmit function.
+ * Fill the buffer memory and send the contents of the transmit buffer
+ * onto the network
+ */
+static void enc28j60_hw_tx(struct enc28j60_net_local *priv)
+{
+	dev_vdbg(&priv->netdev->dev, "%s(), packet len:%d\n",
+		 __FUNCTION__, priv->tx_skb->len);
+
+	/* Set the write pointer to start of transmit buffer area */
+	enc28j60_regw_write(priv, EWRPTL, TXSTART_INIT);
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	{
+		uint16_t reg;
+		reg = enc28j60_regw_read(priv, EWRPTL);
+		if (reg != TXSTART_INIT)
+			dev_dbg(&priv->netdev->dev,
+				"%s() ERWPT:0x%04x != 0x%04x\n", __FUNCTION__,
+				reg, TXSTART_INIT);
+	}
+#endif
+	/* Set the TXND pointer to correspond to the packet size given */
+	enc28j60_regw_write(priv, ETXNDL, TXSTART_INIT + priv->tx_skb->len);
+	/* write per-packet control byte */
+	spi_write_op(priv, ENC28J60_WRITE_BUF_MEM, 0, 0x00);
+	dev_vdbg(&priv->netdev->dev, "%s() after control byte ERWPT:0x%04x\n",
+		 __FUNCTION__, enc28j60_regw_read(priv, EWRPTL));
+
+	dump_packet(priv, __FUNCTION__, priv->tx_skb->len, priv->tx_skb->data);
+
+	/* copy the packet into the transmit buffer */
+	spi_write_buf(priv, priv->tx_skb->len, priv->tx_skb->data);
+	dev_vdbg(&priv->netdev->dev,
+		 "%s() after write packet ERWPT:0x%04x, len=%d\n", __FUNCTION__,
+		 enc28j60_regw_read(priv, EWRPTL), priv->tx_skb->len);
+
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	{	/* readback and verify written data */
+		int test_len, k;
+		uint8_t test_buf[256];
+		int okflag = 1;
+
+		test_len = priv->tx_skb->len;
+		if (test_len > sizeof(test_buf))
+			test_len = sizeof(test_buf);
+
+		/* + 1 to skip control byte */
+		enc28j60_mem_read(priv, TXSTART_INIT + 1, test_len, test_buf);
+		for (k = 0; k < test_len; k++) {
+			if (priv->tx_skb->data[k] != test_buf[k]) {
+				dev_vdbg(&priv->netdev->dev,
+					 "%s(),Err! differ [%d] "
+					 "0x%02x - 0x%02x\n",
+					 __FUNCTION__, k, priv->tx_skb->data[k],
+					 test_buf[k]);
+				okflag = 0;
+			}
+		}
+		if (!okflag)
+			dev_dbg(&priv->netdev->dev,
+				"%s(), Write buffer verify error!\n",
+				__FUNCTION__);
+		else
+			dev_vdbg(&priv->netdev->dev,
+				 "%s(), Write buffer verify OK\n",
+				 __FUNCTION__);
+	}
+#endif
+	/* set TX request flag */
+	enc28j60_reg_bfset(priv, ECON1, ECON1_TXRTS);
+}
+
+/*
+ * Read the Receive Status Vector
+ */
+static void enc28j60_dump_rsv(struct enc28j60_net_local *priv, const char *msg,
+			      uint16_t pk_ptr, int len, uint16_t sts)
+{
+	struct net_device *ndev = priv->netdev;
+
+	dev_vdbg(&ndev->dev, "%s - NexPk: 0x%04x - RSV:\n", msg, pk_ptr);
+	dev_vdbg(&ndev->dev, "ByteCount: %d, DribbleNibble: %d\n", len,
+		 RSV_GETBIT(sts, RSV_DRIBBLENIBBLE));
+	dev_vdbg(&ndev->dev,
+		 "RxOK: %d, CRCErr:%d, LenChkErr: %d, LenOutOfRange: %d\n",
+		 RSV_GETBIT(sts, RSV_RXOK), RSV_GETBIT(sts, RSV_CRCERROR),
+		 RSV_GETBIT(sts, RSV_LENCHECKERR),
+		 RSV_GETBIT(sts, RSV_LENOUTOFRANGE));
+	dev_vdbg(&ndev->dev,
+		 "Multicast: %d, Broadcast: %d, "
+		 "LongDropEvent: %d, CarrierEvent: %d\n",
+		 RSV_GETBIT(sts, RSV_RXMULTICAST),
+		 RSV_GETBIT(sts, RSV_RXBROADCAST),
+		 RSV_GETBIT(sts, RSV_RXLONGEVDROPEV),
+		 RSV_GETBIT(sts, RSV_CARRIEREV));
+	dev_vdbg(&ndev->dev,
+		 "ControlFrame: %d, PauseFrame: %d, UnknownOp: %d, "
+		 "VLanTagFrame: %d\n",
+		 RSV_GETBIT(sts, RSV_RXCONTROLFRAME),
+		 RSV_GETBIT(sts, RSV_RXPAUSEFRAME),
+		 RSV_GETBIT(sts, RSV_RXUNKNOWNOPCODE),
+		 RSV_GETBIT(sts, RSV_RXTYPEVLAN));
+}
+
+/*
+ * Hardware receive function.
+ * Read the buffer memory, update the FIFO pointer,
+ * check the status vector and decrement the packet counter
+ */
+static void enc28j60_hw_rx(struct enc28j60_net_local *priv)
+{
+	struct sk_buff *skb;
+	uint16_t erxrdpt, next_packet, rxstat;
+	uint8_t tmpv[6];
+	int len;
+
+	dev_vdbg(&priv->netdev->dev, "%s() pk_addr:0x%04x\n", __FUNCTION__,
+		 priv->next_pk_ptr);
+
+	if (priv->next_pk_ptr > RXEND_INIT) {
+		dev_err(&priv->netdev->dev,
+			"%s() Invalid packet address!! 0x%04x\n", __FUNCTION__,
+			priv->next_pk_ptr);
+		return;
+	}
+	/* Read next packet pointer and rx status vector */
+	enc28j60_mem_read(priv, priv->next_pk_ptr, sizeof(tmpv), tmpv);
+
+	next_packet = tmpv[1];
+	next_packet <<= 8;
+	next_packet |= tmpv[0];
+
+	len = tmpv[3];
+	len <<= 8;
+	len |= tmpv[2];
+
+	rxstat = tmpv[5];
+	rxstat <<= 8;
+	rxstat |= tmpv[4];
+
+	enc28j60_dump_rsv(priv, __FUNCTION__, next_packet, len, rxstat);
+
+	if (!RSV_GETBIT(rxstat, RSV_RXOK)) {
+		dev_dbg(&priv->netdev->dev, "enc28j60: Rx Error (%04x)\n",
+			rxstat);
+
+		priv->stats.rx_errors++;
+		if (RSV_GETBIT(rxstat, RSV_CRCERROR))
+			priv->stats.rx_crc_errors++;
+		if (RSV_GETBIT(rxstat, RSV_LENCHECKERR))
+			priv->stats.rx_frame_errors++;
+	} else {
+		skb = dev_alloc_skb(len);
+		if (!skb) {
+			dev_err(&priv->netdev->dev,
+				"enc28j60: out of memory for Rx'd frame\n");
+			priv->stats.rx_dropped++;
+			return;
+		}
+		skb->dev = priv->netdev;
+
+		/* copy the packet from the receive buffer */
+		enc28j60_mem_read(priv, priv->next_pk_ptr + sizeof(tmpv), len,
+				  skb_put(skb, len));
+
+		priv->next_pk_ptr = next_packet;
+
+		/*
+		 * Move the RX read pointer to the start of the next
+		 * received packet.
+		 * This frees the memory we just read out
+		 */
+		erxrdpt =
+		    erxrdpt_workaround(priv->next_pk_ptr, RXSTART_INIT,
+				       RXEND_INIT);
+		enc28j60_regw_write(priv, ERXRDPTL, erxrdpt);
+
+		dev_vdbg(&priv->netdev->dev,
+			 "%s() RxSize:%d, RxStat:0x%04x ERXRDPT:0x%04x\n",
+			 __FUNCTION__, len, rxstat, erxrdpt);
+		dump_packet(priv, __FUNCTION__, skb->len, skb->data);
+
+		/* we are done with this packet, decrement the packet counter */
+		enc28j60_reg_bfset(priv, ECON2, ECON2_PKTDEC);
+
+		skb->protocol = eth_type_trans(skb, priv->netdev);
+
+		/* update statistics */
+		priv->stats.rx_packets++;
+		priv->stats.rx_bytes += len;
+		priv->netdev->last_rx = jiffies;
+		netif_rx(skb);
+	}
+}
+
+static int enc28j60_send_packet(struct sk_buff *skb, struct net_device *dev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	dev_vdbg(&dev->dev, "%s()\n", __FUNCTION__);
+
+	/* If some error occurs while trying to transmit this
+	 * packet, you should return '1' from this function.
+	 * In such a case you _may not_ do anything to the
+	 * SKB, it is still owned by the network queueing
+	 * layer when an error is returned.  This means you
+	 * may not modify any SKB fields, you may not free
+	 * the SKB, etc.
+	 */
+	netif_stop_queue(dev);
+
+	/* save the timestamp */
+	priv->netdev->trans_start = jiffies;
+	/* Remember the skb for deferred processing */
+	priv->tx_skb = skb;
+	schedule_work(&priv->tx_work);
+
+	return 0;
+}
+
+static void enc28j60_tx_work_handler(struct work_struct *work)
+{
+	struct enc28j60_net_local *priv =
+	    container_of(work, struct enc28j60_net_local, tx_work);
+	dev_vdbg(&priv->netdev->dev, "%s()\n", __FUNCTION__);
+
+	/* actual delivery of data */
+	enc28j60_hw_tx(priv);
+}
+
+static irqreturn_t enc28j60_irq(int irq, void *dev_id)
+{
+	struct enc28j60_net_local *priv = dev_id;
+
+	dev_vdbg(&priv->netdev->dev, "%s(priv=%p)\n", __FUNCTION__, priv);
+
+	/*
+	 * Can't do anything in interrupt context so fire of the interrupt
+	 * handling workqueue.
+	 */
+	schedule_work(&priv->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+static void enc28j60_net_tx_timeout(struct net_device *ndev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(ndev);
+
+	dev_dbg(&ndev->dev, "enc28j60 transmit timed out\n");
+
+	/* Reset the TX logic */
+	enc28j60_reg_bfset(priv, ECON1, ECON1_TXRST);
+	enc28j60_reg_bfclr(priv, ECON1, ECON1_TXRST);
+	enc28j60_reg_bfclr(priv, EIR, EIR_TXERIF | EIR_TXIF);
+	priv->stats.tx_errors++;
+	netif_wake_queue(ndev);
+}
+
+/*
+ * Open/initialize the board. This is called (in the current kernel)
+ * sometime after booting when the 'ifconfig' program is run.
+ *
+ * This routine should set everything up anew at each open, even
+ * registers that "should" only need to be set once at boot, so that
+ * there is non-reboot way to recover if something goes wrong.
+ */
+static int enc28j60_net_open(struct net_device *dev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	dev_dbg(&dev->dev, "%s() enter [priv:%p]\n", __FUNCTION__, priv);
+
+	if (!is_valid_ether_addr(dev->dev_addr)) {
+		dev_err(&dev->dev, "%s() invalid MAC address\n", __FUNCTION__);
+		return -EADDRNOTAVAIL;
+	}
+
+	/* Reset the hardware here */
+	enc28j60_hw_disable(priv);
+	enc28j60_reg_bfset(priv, ECON1, ECON1_TXRST | ECON1_RXRST);
+	enc28j60_reg_bfclr(priv, ECON1, ECON1_TXRST | ECON1_RXRST);
+
+	/* Update the MAC address (in case user has changed it) */
+	enc28j60_set_hw_macaddr(priv);
+
+	/* Enable interrupts */
+	enc28j60_hw_enable(priv);
+
+	/* We are now ready to accept transmit requests from
+	 * the queueing layer of the networking.
+	 */
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+/* The inverse routine to net_open(). */
+static int enc28j60_net_close(struct net_device *dev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	dev_dbg(&dev->dev, "%s()\n", __FUNCTION__);
+
+	enc28j60_hw_disable(priv);
+	netif_stop_queue(dev);
+
+	return 0;
+}
+
+/*
+ * Get the current statistics.
+ * This may be called with the card open or closed.
+ */
+static struct net_device_stats *enc28j60_net_get_stats(struct net_device *dev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	return &priv->stats;
+}
+
+/*
+ * Set or clear the multicast filter for this adaptor.
+ * num_addrs == -1	Promiscuous mode, receive all packets
+ * num_addrs == 0	Normal mode, clear multicast list
+ * num_addrs > 0	Multicast mode, receive normal and MC packets,
+ *			and do best-effort filtering.
+ */
+static void enc28j60_set_multicast_list(struct net_device *dev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	if (!priv->hw_enable) {
+		if (dev->flags & IFF_PROMISC) {
+			dev_dbg(&dev->dev, "%s() promiscuous mode\n",
+				__FUNCTION__);
+			enc28j60_regb_write(priv, ERXFCON, 0x00);
+		} else if (dev->flags & IFF_ALLMULTI) {
+			dev_dbg(&dev->dev, "%s() multicast mode\n",
+				__FUNCTION__);
+			enc28j60_regb_write(priv, ERXFCON,
+					    ERXFCON_UCEN | ERXFCON_CRCEN |
+					    ERXFCON_BCEN | ERXFCON_MCEN);
+		} else {
+			dev_dbg(&dev->dev, "%s() normal mode\n", __FUNCTION__);
+			enc28j60_regb_write(priv, ERXFCON,
+					    ERXFCON_UCEN | ERXFCON_CRCEN |
+					    ERXFCON_BCEN);
+		}
+	} else
+		dev_dbg(&dev->dev,
+			"%s() Warning: hw must be disabled to set rx filter\n",
+			__FUNCTION__);
+}
+
+static int enc28j60_chipset_init(struct net_device *dev)
+{
+	struct enc28j60_net_local *priv = netdev_priv(dev);
+
+	return enc28j60_hw_init(priv);
+}
+
+static int __devinit enc28j60_probe(struct spi_device *spi)
+{
+	struct net_device *dev;
+	struct enc28j60_net_local *priv;
+	int ret = 0;
+
+	dev_dbg(&spi->dev, "%s() start\n", __FUNCTION__);
+
+	dev = alloc_etherdev(sizeof(struct enc28j60_net_local));
+	if (!dev) {
+		ret = -ENOMEM;
+		goto error_alloc;
+	}
+	priv = netdev_priv(dev);
+
+	priv->netdev = dev;	/* priv to netdev reference */
+	priv->spi = spi;	/* priv to spi reference */
+	priv->spi_transfer_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL);
+	if (!priv->spi_transfer_buf) {
+		ret = -ENOMEM;
+		goto error_buf;
+	}
+	init_MUTEX(&priv->semlock);
+
+	INIT_WORK(&priv->tx_work, enc28j60_tx_work_handler);
+	INIT_WORK(&priv->irq_work, enc28j60_irq_work_handler);
+	dev_set_drvdata(&spi->dev, priv);	/* spi to priv reference */
+	SET_NETDEV_DEV(dev, &spi->dev);
+
+	if (!enc28j60_chipset_init(dev)) {
+		dev_dbg(&spi->dev, "enc28j60 not found\n");
+		ret = -EIO;
+		goto error_irq;
+	}
+	enc28j60_get_hw_macaddr(priv);
+
+	ret = request_irq(spi->irq, enc28j60_irq, IRQF_TRIGGER_FALLING,
+			  "enc28j60", priv);
+	if (ret < 0) {
+		dev_err(&spi->dev, "request irq %d failed (ret = %d)\n",
+			spi->irq, ret);
+		goto error_irq;
+	}
+
+	dev->irq = spi->irq;
+
+	dev->open = enc28j60_net_open;
+	dev->stop = enc28j60_net_close;
+	dev->hard_start_xmit = enc28j60_send_packet;
+	dev->get_stats = enc28j60_net_get_stats;
+	dev->set_multicast_list = &enc28j60_set_multicast_list;
+	dev->set_mac_address = enc28j60_set_mac_address;
+	dev->tx_timeout = &enc28j60_net_tx_timeout;
+	dev->watchdog_timeo = MY_TX_TIMEOUT;
+
+	ret = register_netdev(dev);
+	if (ret) {
+		dev_err(&spi->dev,
+			"register netdev enc28j60 failed (ret = %d)\n", ret);
+		goto error_register;
+	}
+	dev_info(&spi->dev, "%s: enc28j60 driver registered (interrupt %d)\n",
+		 dev->name, dev->irq);
+
+	return 0;
+
+      error_register:
+	free_irq(spi->irq, priv);
+      error_irq:
+	kfree(priv->spi_transfer_buf);
+      error_buf:
+	free_netdev(dev);
+      error_alloc:
+	return ret;
+}
+
+static int enc28j60_remove(struct spi_device *spi)
+{
+	struct enc28j60_net_local *priv = dev_get_drvdata(&spi->dev);
+
+	dev_dbg(&spi->dev, "%s: stop\n", __FUNCTION__);
+
+	unregister_netdev(priv->netdev);
+	free_irq(spi->irq, priv);
+	kfree(priv->spi_transfer_buf);
+	free_netdev(priv->netdev);
+
+	return 0;
+}
+
+static struct spi_driver enc28j60_driver = {
+	.driver = {
+		   .name = "enc28j60",
+		   .bus = &spi_bus_type,
+		   .owner = THIS_MODULE,
+		   },
+	.probe = enc28j60_probe,
+	.remove = __devexit_p(enc28j60_remove),
+};
+
+static int __init enc28j60_init(void)
+{
+	return spi_register_driver(&enc28j60_driver);
+}
+
+module_init(enc28j60_init);
+
+static void __exit enc28j60_exit(void)
+{
+	spi_unregister_driver(&enc28j60_driver);
+}
+
+module_exit(enc28j60_exit);
+
+MODULE_DESCRIPTION("ENC28J60 ethernet driver");
+MODULE_AUTHOR("Claudio Lanconelli <lanconelli.claudio@eptar.com>");
+MODULE_LICENSE("GPL");
+module_param(full_duplex, int, 0);
+MODULE_PARM_DESC(full_duplex, "Enable full duplex mode");
diff --git a/drivers/net/enc28j60_hw.h b/drivers/net/enc28j60_hw.h
new file mode 100644
index 0000000..78f415a
--- /dev/null
+++ b/drivers/net/enc28j60_hw.h
@@ -0,0 +1,303 @@
+/*
+ * enc28j60_hw.h: EDTP FrameThrower style enc28j60 registers
+ *
+ * $Id: enc28j60_hw.h,v 1.5 2007/12/11 10:35:40 claudio Exp $
+ */
+
+#ifndef _ENC28J60_HW_H
+#define _ENC28J60_HW_H
+
+/*
+ * ENC28J60 Control Registers
+ * Control register definitions are a combination of address,
+ * bank number, and Ethernet/MAC/PHY indicator bits.
+ * - Register address   (bits 0-4)
+ * - Bank number        (bits 5-6)
+ * - MAC/MII indicator  (bit 7)
+ */
+#define ADDR_MASK   0x1F
+#define BANK_MASK   0x60
+#define SPRD_MASK   0x80
+/* All-bank registers */
+#define EIE         0x1B
+#define EIR         0x1C
+#define ESTAT       0x1D
+#define ECON2       0x1E
+#define ECON1       0x1F
+/* Bank 0 registers */
+#define ERDPTL      (0x00|0x00)
+#define ERDPTH      (0x01|0x00)
+#define EWRPTL      (0x02|0x00)
+#define EWRPTH      (0x03|0x00)
+#define ETXSTL      (0x04|0x00)
+#define ETXSTH      (0x05|0x00)
+#define ETXNDL      (0x06|0x00)
+#define ETXNDH      (0x07|0x00)
+#define ERXSTL      (0x08|0x00)
+#define ERXSTH      (0x09|0x00)
+#define ERXNDL      (0x0A|0x00)
+#define ERXNDH      (0x0B|0x00)
+#define ERXRDPTL    (0x0C|0x00)
+#define ERXRDPTH    (0x0D|0x00)
+#define ERXWRPTL    (0x0E|0x00)
+#define ERXWRPTH    (0x0F|0x00)
+#define EDMASTL     (0x10|0x00)
+#define EDMASTH     (0x11|0x00)
+#define EDMANDL     (0x12|0x00)
+#define EDMANDH     (0x13|0x00)
+#define EDMADSTL    (0x14|0x00)
+#define EDMADSTH    (0x15|0x00)
+#define EDMACSL     (0x16|0x00)
+#define EDMACSH     (0x17|0x00)
+/* Bank 1 registers */
+#define EHT0        (0x00|0x20)
+#define EHT1        (0x01|0x20)
+#define EHT2        (0x02|0x20)
+#define EHT3        (0x03|0x20)
+#define EHT4        (0x04|0x20)
+#define EHT5        (0x05|0x20)
+#define EHT6        (0x06|0x20)
+#define EHT7        (0x07|0x20)
+#define EPMM0       (0x08|0x20)
+#define EPMM1       (0x09|0x20)
+#define EPMM2       (0x0A|0x20)
+#define EPMM3       (0x0B|0x20)
+#define EPMM4       (0x0C|0x20)
+#define EPMM5       (0x0D|0x20)
+#define EPMM6       (0x0E|0x20)
+#define EPMM7       (0x0F|0x20)
+#define EPMCSL      (0x10|0x20)
+#define EPMCSH      (0x11|0x20)
+#define EPMOL       (0x14|0x20)
+#define EPMOH       (0x15|0x20)
+#define EWOLIE      (0x16|0x20)
+#define EWOLIR      (0x17|0x20)
+#define ERXFCON     (0x18|0x20)
+#define EPKTCNT     (0x19|0x20)
+/* Bank 2 registers */
+#define MACON1      (0x00|0x40|SPRD_MASK)
+/* #define MACON2      (0x01|0x40|SPRD_MASK) */
+#define MACON3      (0x02|0x40|SPRD_MASK)
+#define MACON4      (0x03|0x40|SPRD_MASK)
+#define MABBIPG     (0x04|0x40|SPRD_MASK)
+#define MAIPGL      (0x06|0x40|SPRD_MASK)
+#define MAIPGH      (0x07|0x40|SPRD_MASK)
+#define MACLCON1    (0x08|0x40|SPRD_MASK)
+#define MACLCON2    (0x09|0x40|SPRD_MASK)
+#define MAMXFLL     (0x0A|0x40|SPRD_MASK)
+#define MAMXFLH     (0x0B|0x40|SPRD_MASK)
+#define MAPHSUP     (0x0D|0x40|SPRD_MASK)
+#define MICON       (0x11|0x40|SPRD_MASK)
+#define MICMD       (0x12|0x40|SPRD_MASK)
+#define MIREGADR    (0x14|0x40|SPRD_MASK)
+#define MIWRL       (0x16|0x40|SPRD_MASK)
+#define MIWRH       (0x17|0x40|SPRD_MASK)
+#define MIRDL       (0x18|0x40|SPRD_MASK)
+#define MIRDH       (0x19|0x40|SPRD_MASK)
+/* Bank 3 registers */
+#define MAADR1      (0x00|0x60|SPRD_MASK)
+#define MAADR0      (0x01|0x60|SPRD_MASK)
+#define MAADR3      (0x02|0x60|SPRD_MASK)
+#define MAADR2      (0x03|0x60|SPRD_MASK)
+#define MAADR5      (0x04|0x60|SPRD_MASK)
+#define MAADR4      (0x05|0x60|SPRD_MASK)
+#define EBSTSD      (0x06|0x60)
+#define EBSTCON     (0x07|0x60)
+#define EBSTCSL     (0x08|0x60)
+#define EBSTCSH     (0x09|0x60)
+#define MISTAT      (0x0A|0x60|SPRD_MASK)
+#define EREVID      (0x12|0x60)
+#define ECOCON      (0x15|0x60)
+#define EFLOCON     (0x17|0x60)
+#define EPAUSL      (0x18|0x60)
+#define EPAUSH      (0x19|0x60)
+/* PHY registers */
+#define PHCON1      0x00
+#define PHSTAT1     0x01
+#define PHHID1      0x02
+#define PHHID2      0x03
+#define PHCON2      0x10
+#define PHSTAT2     0x11
+#define PHIE        0x12
+#define PHIR        0x13
+#define PHLCON      0x14
+
+/* ENC28J60 EIE Register Bit Definitions */
+#define EIE_INTIE       0x80
+#define EIE_PKTIE       0x40
+#define EIE_DMAIE       0x20
+#define EIE_LINKIE      0x10
+#define EIE_TXIE        0x08
+#define EIE_WOLIE       0x04
+#define EIE_TXERIE      0x02
+#define EIE_RXERIE      0x01
+/* ENC28J60 EIR Register Bit Definitions */
+#define EIR_PKTIF       0x40
+#define EIR_DMAIF       0x20
+#define EIR_LINKIF      0x10
+#define EIR_TXIF        0x08
+#define EIR_WOLIF       0x04
+#define EIR_TXERIF      0x02
+#define EIR_RXERIF      0x01
+/* ENC28J60 ESTAT Register Bit Definitions */
+#define ESTAT_INT       0x80
+#define ESTAT_LATECOL   0x10
+#define ESTAT_RXBUSY    0x04
+#define ESTAT_TXABRT    0x02
+#define ESTAT_CLKRDY    0x01
+/* ENC28J60 ECON2 Register Bit Definitions */
+#define ECON2_AUTOINC   0x80
+#define ECON2_PKTDEC    0x40
+#define ECON2_PWRSV     0x20
+#define ECON2_VRPS      0x08
+/* ENC28J60 ECON1 Register Bit Definitions */
+#define ECON1_TXRST     0x80
+#define ECON1_RXRST     0x40
+#define ECON1_DMAST     0x20
+#define ECON1_CSUMEN    0x10
+#define ECON1_TXRTS     0x08
+#define ECON1_RXEN      0x04
+#define ECON1_BSEL1     0x02
+#define ECON1_BSEL0     0x01
+/* ENC28J60 MACON1 Register Bit Definitions */
+#define MACON1_LOOPBK   0x10
+#define MACON1_TXPAUS   0x08
+#define MACON1_RXPAUS   0x04
+#define MACON1_PASSALL  0x02
+#define MACON1_MARXEN   0x01
+/* ENC28J60 MACON2 Register Bit Definitions */
+#define MACON2_MARST    0x80
+#define MACON2_RNDRST   0x40
+#define MACON2_MARXRST  0x08
+#define MACON2_RFUNRST  0x04
+#define MACON2_MATXRST  0x02
+#define MACON2_TFUNRST  0x01
+/* ENC28J60 MACON3 Register Bit Definitions */
+#define MACON3_PADCFG2  0x80
+#define MACON3_PADCFG1  0x40
+#define MACON3_PADCFG0  0x20
+#define MACON3_TXCRCEN  0x10
+#define MACON3_PHDRLEN  0x08
+#define MACON3_HFRMLEN  0x04
+#define MACON3_FRMLNEN  0x02
+#define MACON3_FULDPX   0x01
+/* ENC28J60 MICMD Register Bit Definitions */
+#define MICMD_MIISCAN   0x02
+#define MICMD_MIIRD     0x01
+/* ENC28J60 MISTAT Register Bit Definitions */
+#define MISTAT_NVALID   0x04
+#define MISTAT_SCAN     0x02
+#define MISTAT_BUSY     0x01
+/* ENC28J60 ERXFCON Register Bit Definitions */
+#define ERXFCON_UCEN	0x80
+#define ERXFCON_ANDOR	0x40
+#define ERXFCON_CRCEN	0x20
+#define ERXFCON_PMEN	0x10
+#define ERXFCON_MPEN	0x08
+#define ERXFCON_HTEN	0x04
+#define ERXFCON_MCEN	0x02
+#define ERXFCON_BCEN	0x01
+
+/* ENC28J60 PHY PHCON1 Register Bit Definitions */
+#define PHCON1_PRST     0x8000
+#define PHCON1_PLOOPBK  0x4000
+#define PHCON1_PPWRSV   0x0800
+#define PHCON1_PDPXMD   0x0100
+/* ENC28J60 PHY PHSTAT1 Register Bit Definitions */
+#define PHSTAT1_PFDPX   0x1000
+#define PHSTAT1_PHDPX   0x0800
+#define PHSTAT1_LLSTAT  0x0004
+#define PHSTAT1_JBSTAT  0x0002
+/* ENC28J60 PHY PHCON2 Register Bit Definitions */
+#define PHCON2_FRCLINK  0x4000
+#define PHCON2_TXDIS    0x2000
+#define PHCON2_JABBER   0x0400
+#define PHCON2_HDLDIS   0x0100
+
+/* ENC28J60 Packet Control Byte Bit Definitions */
+#define PKTCTRL_PHUGEEN     0x08
+#define PKTCTRL_PPADEN      0x04
+#define PKTCTRL_PCRCEN      0x02
+#define PKTCTRL_POVERRIDE   0x01
+
+/* ENC28J60 Transmit Status Vector */
+#define TSV_TXBYTECNT		0
+#define TSV_TXCOLLISIONCNT	16
+#define TSV_TXCRCERROR		20
+#define TSV_TXLENCHKERROR	21
+#define TSV_TXLENOUTOFRANGE	22
+#define TSV_TXDONE		23
+#define TSV_TXMULTICAST		24
+#define TSV_TXBROADCAST		25
+#define TSV_TXPACKETDEFER	26
+#define TSV_TXEXDEFER		27
+#define TSV_TXEXCOLLISION	28
+#define TSV_TXLATECOLLISION	29
+#define TSV_TXGIANT		30
+#define TSV_TXUNDERRUN		31
+#define TSV_TOTBYTETXONWIRE	32
+#define TSV_TXCONTROLFRAME	48
+#define TSV_TXPAUSEFRAME	49
+#define TSV_BACKPRESSUREAPP	50
+#define TSV_TXVLANTAGFRAME	51
+
+#define TSV_SIZE 7
+#define TSV_BYTEOF(x)		((x) / 8)
+#define TSV_BITMASK(x)		(1 << ((x) % 8))
+#define TSV_GETBIT(x, y)	(((x)[TSV_BYTEOF(y)] & TSV_BITMASK(y)) ? 1 : 0)
+
+/* ENC28J60 Receive Status Vector */
+#define RSV_RXLONGEVDROPEV	16
+#define RSV_CARRIEREV		18
+#define RSV_CRCERROR		20
+#define RSV_LENCHECKERR		21
+#define RSV_LENOUTOFRANGE	22
+#define RSV_RXOK		23
+#define RSV_RXMULTICAST		24
+#define RSV_RXBROADCAST		25
+#define RSV_DRIBBLENIBBLE	26
+#define RSV_RXCONTROLFRAME	27
+#define RSV_RXPAUSEFRAME	28
+#define RSV_RXUNKNOWNOPCODE	29
+#define RSV_RXTYPEVLAN		30
+
+#define RSV_BITMASK(x)		(1 << ((x) - 16))
+#define RSV_GETBIT(x, y)	(((x) & RSV_BITMASK(y)) ? 1 : 0)
+
+
+/* SPI operation codes */
+#define ENC28J60_READ_CTRL_REG  0x00
+#define ENC28J60_READ_BUF_MEM   0x3A
+#define ENC28J60_WRITE_CTRL_REG 0x40
+#define ENC28J60_WRITE_BUF_MEM  0x7A
+#define ENC28J60_BIT_FIELD_SET  0x80
+#define ENC28J60_BIT_FIELD_CLR  0xA0
+#define ENC28J60_SOFT_RESET     0xFF
+
+
+/* buffer boundaries applied to internal 8K ram
+ * entire available packet buffer space is allocated.
+ * Give TX buffer space for one full ethernet frame (~1500 bytes)
+ * receive buffer gets the rest */
+#define TXSTART_INIT	0x1A00
+#define TXEND_INIT	0x1FFF
+
+/* Put RX buffer at 0 as suggested by the Errata datasheet */
+#define RXSTART_INIT	0x0000
+#define RXEND_INIT	0x19FF
+
+/* maximum ethernet frame length */
+#define MAX_FRAMELEN    1518
+
+/* Prefered half duplex: LEDA: Link status LEDB: Rx/Tx activity */
+#define ENC28J60_LAMPS_MODE	0x3476
+
+/* Default MAC address for this interface */
+#define ENC28J60_MAC0 0x00
+#define ENC28J60_MAC1 0x00
+#define ENC28J60_MAC2 'F'
+#define ENC28J60_MAC3 'I'
+#define ENC28J60_MAC4 'C'
+#define ENC28J60_MAC5 'E'
+
+#endif

^ permalink raw reply related

* libnl - netlink library: Memory leak in address cache?
From: Joerg Pommnitz @ 2007-12-11 14:52 UTC (permalink / raw)
  To: Thomas Graf, netdev

Hello Thomas and all,
sorry for bothering you if this is the wrong place. The following tiny program leaks memory:

#define _GNU_SOURCE
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>

#include <netlink-local.h>
#include <netlink/route/addr.h>

static void
nl_addr_cb (struct nl_object *obj, void *userdata)
{
  struct rtnl_addr *addr = (struct rtnl_addr *)obj;
  char buf[100];
  char buf2[100];
  extern char *if_indextoname(unsigned ifindex, char *ifname);

  printf ("interface %s addr: %s\n",
          if_indextoname(rtnl_addr_get_ifindex (addr), buf2),
          inet_ntop(rtnl_addr_get_family (addr), rtnl_addr_get_local (addr)->a_addr, buf, sizeof (buf)));
}

int main(int argc, char *argv[])
{
  struct nl_handle *nlh;
  struct nl_cache *addr_cache;
  struct rtnl_addr *addr;
  int err = 1;

  extern unsigned if_nametoindex(const char *ifname);

  nlh = nl_handle_alloc();
  if (!nlh)
    return -1;

  addr = rtnl_addr_alloc();
  if (!addr)
    goto errout;

  if (nl_connect(nlh, NETLINK_ROUTE) < 0)
    goto errout_free;

  addr_cache = rtnl_addr_alloc_cache(nlh);
  if (!addr_cache)
    goto errout_close;

  rtnl_addr_set_ifindex(addr, if_nametoindex("eth0"));

  nl_cache_foreach_filter(addr_cache, (struct nl_object *)addr, nl_addr_cb, NULL);

  err = 0;

  nl_cache_free(addr_cache);
 errout_close:
  nl_close(nlh);
 errout_free:
  rtnl_addr_put(addr);
 errout:
  return err;
}

The valgrind output:
==29411== 528 (96 direct, 432 indirect) bytes in 1 blocks are definitely lost in loss record 6 of 8
==29411==    at 0x402095F: calloc (vg_replace_malloc.c:279)
==29411==    by 0x403D938: nl_object_alloc (object.c:49)
==29411==    by 0x404233F: rtnl_addr_alloc (addr.c:627)
==29411==    by 0x404236B: addr_msg_parser (addr.c:194)
==29411==    by 0x4037B7D: nl_cache_parse (cache.c:615)
==29411==    by 0x4037CB6: update_msg_parser (cache.c:438)
==29411==    by 0x403CD00: nl_recvmsgs (netlink-local.h:335)
==29411==    by 0x4038238: __cache_pickup (cache.c:461)
==29411==    by 0x40382F6: nl_cache_pickup (cache.c:494)
==29411==    by 0x40386AF: nl_cache_refill (cache.c:671)
==29411==    by 0x40422D0: rtnl_addr_alloc_cache (addr.c:650)
==29411==    by 0x80489DA: main (in /home_crypt/pommnitz/himonn/HiMoNN-1.3-IPv6/xx/nltest/main)

I think the leak comes from addr_msg_parser. The newly created address object gets added to the cache with nl_cache_add wich takes a reference, so the reference in addr_msg_parser should be dropped, e.g. the following patch might be correct:
--- ../../COMMON/libnl/lib/route/addr.c (revision 1380)
+++ ../../COMMON/libnl/lib/route/addr.c (working copy)
@@ -288,7 +288,7 @@
        if (err < 0)
                goto errout_free;

-       return P_ACCEPT;
+       // return P_ACCEPT;

 errout_free:
        rtnl_addr_put(addr);

 
--  
Kind regards
 
       Joerg
 






      Heute schon einen Blick in die Zukunft von E-Mails wagen? www.yahoo.de/mail

^ permalink raw reply

* Re: 2.6.24-rc4-mm1
From: Reuben Farrelly @ 2007-12-11 14:12 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-kernel, netdev
In-Reply-To: <20071210131135.855118f5.akpm@linux-foundation.org>



On 11/12/2007 8:11 AM, Andrew Morton wrote:
> On Tue, 11 Dec 2007 01:48:39 +1100
> Reuben Farrelly <reuben-linuxkernel@reub.net> wrote:
> 
>>
>> On 5/12/2007 4:17 PM, Andrew Morton wrote:
>>> Temporarily at
>>>
>>>   http://userweb.kernel.org/~akpm/2.6.24-rc4-mm1/
>>>
>>> Will appear later at
>>>
>>>   ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.24-rc4/2.6.24-rc4-mm1/
>>>
>>>
>>> - Lots of device IDs have been removed from the e1000 driver and moved over
>>>   to e1000e.  So if your e1000 stops working, you forgot to set CONFIG_E1000E.
>>>
>>> - The s390 build is still broken.
>> I'm seeing this most incredibly unhelpful (to debug) but fortunately
>> reproduceable problem (so far 4/4 times) on this -mm kernel.  I thought this 
>> problem may have been related to another bug which I have reported (A TCP oops) 
>> but even after applying a likely fix for that I am still seeing this problem.
>>
>> The machine boots up perfectly fine and runs good until I load it up.
>> In this case I can reliably cause this to occur by pulling a 3G ISO across the
>> GigE network from my Linux box to my PC.  After maybe 50M or so, the console 
>> just displays this (ignore initial boot banner):
>>
>> ----------
>>
>>   * Starting local ...                                                     [ ok ]
>>
>>
>> This is tornado.reub.net (Linux x86_64 2.6.24-rc4-mm1) 00:24:01
>>
>> tornado login: *** buffer overf
>>
>> -------
>>
>> Yes - after displaying the 'f' in what I can only guess is the word 'overflow',
>> the box spontaneously reboots.  There is no further console output until it 
>> starts to come back up again.
>>
>> The problem does not exist in 2.6.23-gentoo kernels nor in a vanilla 
>> 2.6.24-rc4-git6 (phew!), so this looks to be an -mm only problem at this stage.
>>
>> I enabled a number of kernel debugging options but then I got no output at all 
>> when the machine crashed.
>>
>> I'm at a bit of a loss as to which subsystem this might be coming from, so I'm 
>> not sure who to CC.
>>
>> Box information is (still) up at http://www.reub.net/files/kernel/2.6.24-rc4-mm1/
>>
> 
> hm.  grepping around for "buffer overflow" doesn't turn up anything except in
> drivers which you won't be using on that machine.
> 
> I'd be suspecting networking, obviously.  If you're feeling keen could you please
> grep a 2.6.24-rc4 tree and apply 2.6.24-rc4-mm1's origin.patch and git-net.patch
> and see if the bug is still present?

No - seems to be fine with just origin.patch and git-net.patch.

Just for good measure I then reverted git-net.patch and applied 
git-netdev-all.patch instead, and still wasn't able to trigger the reboot or 
console message, no matter how hard I tried.

I guess for now I'll sit on it, and if it appears in the next -mm it'll probably 
annoy me enough and inspire me to dig deeper (or, "guess" deeper, given the lack 
of direction as to where to even begin).

Reuben

^ permalink raw reply

* Re: [PATCH] XFRM: assorted IPsec fixups
From: Paul Moore @ 2007-12-11 14:05 UTC (permalink / raw)
  To: David Miller; +Cc: eparis, netdev, linux-audit, selinux
In-Reply-To: <20071211.022202.208850272.davem@davemloft.net>

On Tuesday 11 December 2007 5:22:02 am David Miller wrote:
> From: Eric Paris <eparis@redhat.com>
> Date: Fri, 07 Dec 2007 15:36:08 -0500
>
> > On Fri, 2007-12-07 at 12:11 -0500, Paul Moore wrote:
> > > This patch fixes a number of small but potentially troublesome things
> > > in the XFRM/IPsec code:
>
> ...
>
> > > Signed-off-by: Paul Moore <paul.moore@hp.com>
> >
> > Acked-by: Eric Paris <eparis@redhat.com>
> >
> > although it does make me wonder why audit_log_start doesn't just check
> > audit_enabled itself....   Anyway, this patch looks good.
>
> I want to apply this but it doesn't apply cleanly to
> net-2.6.25 right now, could you respin this Paul?

Sure, sorry about that.  I wasn't certain who was going to pick this up so I 
just backed again Linus' tree.  I'll get a respun version out later today.

Thanks.

-- 
paul moore
linux security @ hp

^ permalink raw reply

* Re: [kvm-devel] [PATCH resent] virtio_net: Fix stalled inbound trafficon early packets
From: Christian Borntraeger @ 2007-12-11 13:19 UTC (permalink / raw)
  To: dor.laor; +Cc: Rusty Russell, kvm-devel, netdev, virtualization
In-Reply-To: <475E892D.9060400@qumranet.com>

2nd try. I somehow enable html on the last post

Dor Laor wrote:
> Christian Borntraeger wrote:
>>
>> Hello Rusty,
>>
>> while implementing and testing virtio on s390 I found a problem in
>> virtio_net: The current virtio_net driver has a startup race, which
>> prevents any incoming traffic:
>>
>> If try_fill_recv submits buffers to the host system data might be
>> filled in and an interrupt is sent, before napi_enable finishes.
>> In that case the interrupt will kick skb_recv_done which will then
>> call netif_rx_schedule. netif_rx_schedule checks, if NAPI_STATE_SCHED
>> is set - which is not as we did not run napi_enable. No poll routine
>> is scheduled. Furthermore, skb_recv_done returns false, we disables
>> interrupts for this device.
>>
>> One solution is the enable napi before inbound buffer are available.
>>
> But then you might get recv interrupt without a buffer.

If I look at the current implementation (lguest) no interrupt is sent if
there is not buffer available, no?
On the other hand, if the host does send an interrupt when no buffer is
available, this is also no problem. Looking at virtnet_poll, it seems it
can cope with an empty ring. 

> The way other physical NICs doing it is by dis/en/abling interrupt 
> using registers (look at e1000).
> I suggest we can export add_status and use the original code but
> before enabling napi add a call to add_status(dev, 
> VIRTIO_CONFIG_DEV_OPEN).
> The host won't trigger an irq until it sees the above.

That would also work. We already have VRING_AVAIL_F_NO_INTERRUPT in
virtio_ring.c - maybe we can use that. Its hidden in callback and
restart handling, what about adding an explicit startup?
>
> BTW: Rusty is on vacation and that's probably the reason he didn't 
> respond.
> Regards,
> Dor.

Ok, didnt know that. At the moment I can live with my private patch
while we work on a final solution. Meanwhile I will try to debug virtio
on SMP guests - I still see some strange races on our test system. (But
block and net is now working on s390 and can cope with medium load. )

Christian



^ permalink raw reply

* Re: [PATCH resent] virtio_net: Fix stalled inbound trafficon early packets
From: Christian Borntraeger @ 2007-12-11 13:16 UTC (permalink / raw)
  To: dor.laor-atKUWr5tajBWk0Htik3J/w
  Cc: kvm-devel, netdev-u79uwXL29TY76Z2rM5mHXA,
	virtualization-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA
In-Reply-To: <475E892D.9060400-atKUWr5tajBWk0Htik3J/w@public.gmane.org>


[-- Attachment #1.1: Type: text/plain, Size: 2021 bytes --]

Dor Laor wrote:
> Christian Borntraeger wrote:
>>
>> Hello Rusty,
>>
>> while implementing and testing virtio on s390 I found a problem in
>> virtio_net: The current virtio_net driver has a startup race, which
>> prevents any incoming traffic:
>>
>> If try_fill_recv submits buffers to the host system data might be
>> filled in and an interrupt is sent, before napi_enable finishes.
>> In that case the interrupt will kick skb_recv_done which will then
>> call netif_rx_schedule. netif_rx_schedule checks, if NAPI_STATE_SCHED
>> is set - which is not as we did not run napi_enable. No poll routine
>> is scheduled. Furthermore, skb_recv_done returns false, we disables
>> interrupts for this device.
>>
>> One solution is the enable napi before inbound buffer are available.
>>
> But then you might get recv interrupt without a buffer.

If I look at the current implementation (lguest) no interrupt is sent if
there is not buffer available, no?
On the other hand, if the host does send an interrupt when no buffer is
available, this is also no problem. Looking at virtnet_poll, it seems it
can cope with an empty ring. 

> The way other physical NICs doing it is by dis/en/abling interrupt 
> using registers (look at e1000).
> I suggest we can export add_status and use the original code but
> before enabling napi add a call to add_status(dev, 
> VIRTIO_CONFIG_DEV_OPEN).
> The host won't trigger an irq until it sees the above.

That would also work. We already have VRING_AVAIL_F_NO_INTERRUPT in
virtio_ring.c - maybe we can use that. Its hidden in callback and
restart handling, what about adding an explicit startup?
>
> BTW: Rusty is on vacation and that's probably the reason he didn't 
> respond.
> Regards,
> Dor.

Ok, didnt know that. At the moment I can live with my private patch
while we work on a final solution. Meanwhile I will try to debug virtio
on SMP guests - I still see some strange races on our test system. (But
block and net is now working on s390 and can cope with medium load. )

Christian



[-- Attachment #1.2: Type: text/html, Size: 2651 bytes --]

[-- Attachment #2: Type: text/plain, Size: 277 bytes --]

-------------------------------------------------------------------------
SF.Net email is sponsored by:
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php

[-- Attachment #3: Type: text/plain, Size: 186 bytes --]

_______________________________________________
kvm-devel mailing list
kvm-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/kvm-devel

^ permalink raw reply

* Re: [IPSEC]: Fix potential dst leak in xfrm_lookup
From: Herbert Xu @ 2007-12-11 12:44 UTC (permalink / raw)
  To: David Miller; +Cc: netdev
In-Reply-To: <20071211.044016.218495218.davem@davemloft.net>

On Tue, Dec 11, 2007 at 04:40:16AM -0800, David Miller wrote:
> 
> I bet the __xfrm_lookup() callers could stand a major audit, with the
> special -EREMOTE logic I bet there are non-EREMOTE code paths there
> that don't handle the dst ref semantics properly.
> 
> This is a very error prone interface, both at the implementation
> and in the callers.

Heh, didn't we already do an audit a couple of years ago when
we made the decision that the dst is always going to be freed
in case of an error?

But yeah another audit for this and EREMOTE wouldn't hurt.

Cheers,
-- 
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply

* Re: [PATCH resent] virtio_net: Fix stalled inbound trafficon early packets
From: Dor Laor @ 2007-12-11 12:57 UTC (permalink / raw)
  To: dor.laor-atKUWr5tajBWk0Htik3J/w
  Cc: kvm-devel, Christian Borntraeger,
	virtualization-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	netdev-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <475E8716.2010500-atKUWr5tajBWk0Htik3J/w@public.gmane.org>

This time I send in text so netdev list won't reject it; sorry.
Dor Laor wrote:
> Christian Borntraeger wrote:
>>
>> Hello Rusty,
>>
>> while implementing and testing virtio on s390 I found a problem in
>> virtio_net: The current virtio_net driver has a startup race, which
>> prevents any incoming traffic:
>>
>> If try_fill_recv submits buffers to the host system data might be
>> filled in and an interrupt is sent, before napi_enable finishes.
>> In that case the interrupt will kick skb_recv_done which will then
>> call netif_rx_schedule. netif_rx_schedule checks, if NAPI_STATE_SCHED
>> is set - which is not as we did not run napi_enable. No poll routine
>> is scheduled. Furthermore, skb_recv_done returns false, we disables
>> interrupts for this device.
>>
>> One solution is the enable napi before inbound buffer are available.
>>
> But then you might get recv interrupt without a buffer.
> The way other physical NICs doing it is by dis/en/abling interrupt 
> using registers (look at e1000).
> I suggest we can export add_status and use the original code but
> before enabling napi add a call to add_status(dev, 
> VIRTIO_CONFIG_DEV_OPEN).
> The host won't trigger an irq until it sees the above.
>
> BTW: Rusty is on vacation and that's probably the reason he didn't 
> respond.
> Regards,
> Dor.
>>
>> Signed-off-by: Christian Borntraeger <borntraeger-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
>> ---
>>  drivers/net/virtio_net.c |    6 ++++--
>>  1 file changed, 4 insertions(+), 2 deletions(-)
>>
>> Index: kvm/drivers/net/virtio_net.c
>> ===================================================================
>> --- kvm.orig/drivers/net/virtio_net.c
>> +++ kvm/drivers/net/virtio_net.c
>> @@ -285,13 +285,15 @@ static int virtnet_open(struct net_devic
>>  {
>>         struct virtnet_info *vi = netdev_priv(dev);
>>
>> +       napi_enable(&vi->napi);
>>         try_fill_recv(vi);
>>
>>         /* If we didn't even get one input buffer, we're useless. */
>> -       if (vi->num == 0)
>> +       if (vi->num == 0) {
>> +               napi_disable(&vi->napi);
>>                 return -ENOMEM;
>> +       }
>>
>> -       napi_enable(&vi->napi);
>>         return 0;
>>  }
>>
>>
>> -------------------------------------------------------------------------
>> SF.Net email is sponsored by:
>> Check out the new SourceForge.net Marketplace.
>> It's the best place to buy or sell services for
>> just about anything Open Source.
>> http://sourceforge.net/services/buy/index.php
>> _______________________________________________
>> kvm-devel mailing list
>> kvm-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
>> https://lists.sourceforge.net/lists/listinfo/kvm-devel
>>
>


-------------------------------------------------------------------------
SF.Net email is sponsored by:
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php

^ permalink raw reply

* Re: [PATCH] [NET]: Fix Ooops of napi net_rx_action.
From: Joonwoo Park @ 2007-12-11 12:57 UTC (permalink / raw)
  To: David Miller; +Cc: netdev, linux-kernel, herbert
In-Reply-To: <20071211.023218.31965443.davem@davemloft.net>

2007/12/11, David Miller <davem@davemloft.net>:
> From: "Joonwoo Park" <joonwpark81@gmail.com>
> Date: Tue, 11 Dec 2007 18:13:34 +0900
>
> Joonwoo-ssi annyoung haseyo,

Wow Great! :-)

> How can the NAPI_STATE_SCHED bit be cleared externally yet we take
> this list_move_tail() code path?
>
> If NAPI_STATE_SCHED is cleared, work will be zero which will never be
> equal to 'weight', and this we'll never attempt the list_move_tail().
>
> If something clears NAPI_STATE_SCHED meanwhile, we have a serious race
> and your patch is an incomplete bandaid.  For example, if it can
> happen, then a case like:
>
>                if (test_bit(NAPI_STATE_SCHED, &n->state))
>        ... something clears NAPI_STATE_SCHED right now ...
>                        work = n->poll(n, weight);
>
> can crash too.
>

David,
With your suggestions, I'm feeling a doubt at e1000.
It's seems to me e1000_clean does call netif_rx_complete and returns
non-zero work_done when !netif_running()
So net_rx_action has (work == weight) as true with NAPI_STATE_SCHED cleared.

Here is the e1000_clean.

static int
e1000_clean(struct napi_struct *napi, int budget)
{
    /* Keep link state information with original netdev */
    if (!netif_carrier_ok(poll_dev))
        goto quit_polling;

...

    adapter->clean_rx(adapter, &adapter->rx_ring[0],
                      &work_done, budget);

    /* If no Tx and not enough Rx work done, exit the polling mode */
    if ((!tx_cleaned && (work_done == 0)) ||
       !netif_running(poll_dev)) {
quit_polling:
        if (likely(adapter->itr_setting & 3))
            e1000_set_itr(adapter);
        netif_rx_complete(poll_dev, napi);
        e1000_irq_enable(adapter);
    }

    return work_done;
}

Thanks.
Joonwoo

^ permalink raw reply

* Re: [PATCH resent] virtio_net: Fix stalled inbound trafficon early packets
From: Dor Laor @ 2007-12-11 12:48 UTC (permalink / raw)
  To: Christian Borntraeger
  Cc: kvm-devel, netdev-u79uwXL29TY76Z2rM5mHXA,
	virtualization-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA
In-Reply-To: <200712111242.28843.borntraeger-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>


[-- Attachment #1.1: Type: text/plain, Size: 2528 bytes --]

Christian Borntraeger wrote:
>
> Hello Rusty,
>
> while implementing and testing virtio on s390 I found a problem in
> virtio_net: The current virtio_net driver has a startup race, which
> prevents any incoming traffic:
>
> If try_fill_recv submits buffers to the host system data might be
> filled in and an interrupt is sent, before napi_enable finishes.
> In that case the interrupt will kick skb_recv_done which will then
> call netif_rx_schedule. netif_rx_schedule checks, if NAPI_STATE_SCHED
> is set - which is not as we did not run napi_enable. No poll routine
> is scheduled. Furthermore, skb_recv_done returns false, we disables
> interrupts for this device.
>
> One solution is the enable napi before inbound buffer are available.
>
But then you might get recv interrupt without a buffer.
The way other physical NICs doing it is by dis/en/abling interrupt using 
registers (look at e1000).
I suggest we can export add_status and use the original code but
before enabling napi add a call to add_status(dev, VIRTIO_CONFIG_DEV_OPEN).
The host won't trigger an irq until it sees the above.

BTW: Rusty is on vacation and that's probably the reason he didn't respond.
Regards,
Dor.
>
> Signed-off-by: Christian Borntraeger <borntraeger-tA70FqPdS9bQT0dZR+AlfA@public.gmane.org>
> ---
>  drivers/net/virtio_net.c |    6 ++++--
>  1 file changed, 4 insertions(+), 2 deletions(-)
>
> Index: kvm/drivers/net/virtio_net.c
> ===================================================================
> --- kvm.orig/drivers/net/virtio_net.c
> +++ kvm/drivers/net/virtio_net.c
> @@ -285,13 +285,15 @@ static int virtnet_open(struct net_devic
>  {
>         struct virtnet_info *vi = netdev_priv(dev);
>
> +       napi_enable(&vi->napi);
>         try_fill_recv(vi);
>
>         /* If we didn't even get one input buffer, we're useless. */
> -       if (vi->num == 0)
> +       if (vi->num == 0) {
> +               napi_disable(&vi->napi);
>                 return -ENOMEM;
> +       }
>
> -       napi_enable(&vi->napi);
>         return 0;
>  }
>
>
> -------------------------------------------------------------------------
> SF.Net email is sponsored by:
> Check out the new SourceForge.net Marketplace.
> It's the best place to buy or sell services for
> just about anything Open Source.
> http://sourceforge.net/services/buy/index.php
> _______________________________________________
> kvm-devel mailing list
> kvm-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
> https://lists.sourceforge.net/lists/listinfo/kvm-devel
>


[-- Attachment #1.2: Type: text/html, Size: 4420 bytes --]

[-- Attachment #2: Type: text/plain, Size: 277 bytes --]

-------------------------------------------------------------------------
SF.Net email is sponsored by:
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php

[-- Attachment #3: Type: text/plain, Size: 186 bytes --]

_______________________________________________
kvm-devel mailing list
kvm-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/kvm-devel

^ permalink raw reply

* Re: [PATCH] [NET]: Fix Ooops of napi net_rx_action.
From: David Miller @ 2007-12-11 12:41 UTC (permalink / raw)
  To: herbert; +Cc: joonwpark81, netdev, linux-kernel
In-Reply-To: <E1J24LV-0003ig-00@gondolin.me.apana.org.au>

From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Tue, 11 Dec 2007 20:36:21 +0800

> David Miller <davem@davemloft.net> wrote:
> >
> > How can the NAPI_STATE_SCHED bit be cleared externally yet we take
> > this list_move_tail() code path?
> 
> His driver is probably buggy.  When we had two drivers beginning
> with e100 we often forgot to apply fixes to the both of them.  Now
> that we have three it's even more confusing.
> 
> I just checked and indeed e1000e seems to be missing the NAPI fix
> that was applied to e1000.  Of course it doesn't rule out the
> possibility of another NAPI bug in e1000.

Thanks for checking.

Indeed I stuck the huge comment there in net_rx_action() above the
list move to try and explain things to people, so that if you saw a
crash in the list manipulation, you're go check the driver first.

^ permalink raw reply

* Re: [IPSEC]: Fix potential dst leak in xfrm_lookup
From: David Miller @ 2007-12-11 12:40 UTC (permalink / raw)
  To: herbert; +Cc: netdev
In-Reply-To: <20071211120729.GA14088@gondor.apana.org.au>

From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Tue, 11 Dec 2007 20:07:29 +0800

> [IPSEC]: Fix potential dst leak in xfrm_lookup
> 
> If we get an error during the actual policy lookup we don't free the
> original dst while the caller expects us to always free the original
> dst in case of error.
> 
> This patch fixes that.
> 
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Sigh, thanks for fixing this.  Applied to net-2.6 and I'll toss it
over to -stable too.

I bet the __xfrm_lookup() callers could stand a major audit, with the
special -EREMOTE logic I bet there are non-EREMOTE code paths there
that don't handle the dst ref semantics properly.

This is a very error prone interface, both at the implementation
and in the callers.

^ permalink raw reply

* Re: [PATCH] [NET]: Fix Ooops of napi net_rx_action.
From: Herbert Xu @ 2007-12-11 12:36 UTC (permalink / raw)
  To: David Miller; +Cc: joonwpark81, netdev, linux-kernel
In-Reply-To: <20071211.023218.31965443.davem@davemloft.net>

David Miller <davem@davemloft.net> wrote:
>
> How can the NAPI_STATE_SCHED bit be cleared externally yet we take
> this list_move_tail() code path?

His driver is probably buggy.  When we had two drivers beginning
with e100 we often forgot to apply fixes to the both of them.  Now
that we have three it's even more confusing.

I just checked and indeed e1000e seems to be missing the NAPI fix
that was applied to e1000.  Of course it doesn't rule out the
possibility of another NAPI bug in e1000.

Cheers,
-- 
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply

* Re: [RFC PATCH net-2.6.25 uncompilable] [TCP]: Avoid breaking GSOed skbs when SACKed one-by-one
From: David Miller @ 2007-12-11 12:32 UTC (permalink / raw)
  To: ilpo.jarvinen; +Cc: lachlan.andrew, netdev, quetchen
In-Reply-To: <Pine.LNX.4.64.0712081523470.17809@kivilampi-30.cs.helsinki.fi>

From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
Date: Tue, 11 Dec 2007 13:59:16 +0200 (EET)

> How about this...
> 
> ...I've left couple of FIXMEs there still, should be quite simple & 
> straightforward to handle them if this seems viable solution at all.
> 
> Beware, this doesn't even compile yet because not all parameters are 
> transferred currently (first_sack_index was killed earlier, I need to 
> summon it back for this). Also, I'd need to do the dirty work of kill 
> recv_sack_cache first to make this to not produce, well, interesting 
> effects due to missed SACK blocks... :-)

Interesting approach, but I think there is limited value to this
(arguably) complex form.

The core issue is that the data and the SACK state are maintained in
the same datastructure.  The complexity in all the state management
and fixups in your patch is purely because of this.

If we maintain SACK scoreboard information seperately, outside of
the SKB, then there are only two changes to make:

1) Every access to TCP_SKB_CB() SACK scoreboard is adjusted to
   new data structure.

2) Retransmit is adjusted so that it can retransmit an SKB
   constructed as a portion of an existing SKB.  Since TSO
   implies SG, this can be handled with simple offset and
   length arguments and suitable creation of a clone referencing
   the pages in the SG vector that contain the desired data.

I would envision this SACK state thing to reference into the
retransmit queue SKB's somehow.  Each SACK block could perhaps
look something like:

	struct sack_ref {
		struct sk_buff *start_skb;
		struct sk_buff *end_skb;
		unsigned int start_off;
		unsigned int len;
	};

Traditionally we've prioritized the design of the SKB and other
infrastructure to suit TCP optimally and I still think we should
operate that way.

Therefore, long term, it is time to make a formal data blob to assist
with all of the repetitive work we do in cases like this and horribly
inefficient places like clean_rtx_queue().

So I'm basically advocating a two-pronged approach to this, the
seperate SACK scoreboard datastructure and the data blob.  I
think we can work on the former right now, and take our time with
the data blob because it requires lots of thinking and we should
get it right as it might have network driver interface implications.

^ permalink raw reply

* RE: [PATCH] Increase virtual FIFOs in ucc_geth.
From: Joakim Tjernlund @ 2007-12-11 12:24 UTC (permalink / raw)
  To: Li Yang; +Cc: netdev
In-Reply-To: <989B956029373F45A0B8AF029708189001B4D908@zch01exm26.fsl.freescale.net>


On Tue, 2007-12-11 at 19:51 +0800, Li Yang wrote:
> > -----Original Message-----
> > From: Joakim Tjernlund [mailto:joakim.tjernlund@transmode.se] 
> > Sent: Tuesday, December 11, 2007 6:58 PM
> > To: Li Yang
> > Cc: netdev@vger.kernel.org
> > Subject: RE: [PATCH] Increase virtual FIFOs in ucc_geth.
> > 
> > 
> > On Tue, 2007-12-11 at 11:11 +0100, Joakim Tjernlund wrote:
> > > On Tue, 2007-12-11 at 17:49 +0800, Li Yang wrote:
> > > > > -----Original Message-----
> > > > > From: Joakim Tjernlund [mailto:Joakim.Tjernlund@transmode.se] 
> > > > > Sent: Tuesday, December 11, 2007 2:46 AM
> > > > > To: Li Yang-r58472 <LeoLi@freescale.com> Netdev
> > > > > Cc: Joakim Tjernlund
> > > > > Subject: [PATCH] Increase virtual FIFOs in ucc_geth.
> > > > > 
> > > > > Increase UCC_GETH_URFS_INIT to 1152 and
> > > > > UCC_GETH_UTFS_INIT to 896 to avoid HW Overrun/Underrun.
> > > > 
> > > > Please be noted that these values are only used for 
> > 10/100Mbps speed.
> > > > Did you get Overrun in 10/100M mode?
> > > 
> > > I get both TX Underrun and RX overrun in 100Mbps, FD, just
> > > by running a tftp transfer. It feels like the URFET and/or URSFET
> > > isn't working. Why I don't know. CPU is MPC832x
> > > 
> > >   Jocke
> > 
> > I am a bit confused how the RBMR and TBMR is supposed to work. In
> > ucc_get there is:
> >  out_be32(&ugeth->p_tx_glbl_pram->tstate, ((u32) 
> > function_code) << 24);
> >  ugeth->p_rx_glbl_pram->rstate = function_code;
> > First, should not the rx part look the same as tx?
> 
> To be consist with the chip RM, type for tstate is u32 and type for
> rstate is u8.  Personally I don't think that it will be different to
> access tstate as u8, but it will be more readable to be the same as
> manual.  Well, rstate probably should be changed to use IO accessor too.

Ah, missed that rstate is u8.

> 
> > Does programing rstate/tstate replace RBMR and TBMR?
> 
> RBMR and TBMR?  These are for other protocols.

So it seems, but they are listed under UCC fast protocols so I figured 
they were valid for all such protocols.

 Jocke

> 
> - Leo
> 
> 

^ permalink raw reply

* Re: [PATCH 2/2 2.6.25] netns: separate af_packet netns data
From: David Miller @ 2007-12-11 12:20 UTC (permalink / raw)
  To: den; +Cc: containers, devel, netdev, herbert
In-Reply-To: <20071211115552.GA9961@iris.sw.ru>

From: "Denis V. Lunev" <den@openvz.org>
Date: Tue, 11 Dec 2007 14:55:52 +0300

> netns: move af_packet data to the separate header
> 
> Signed-off-by: Denis V. Lunev <den@openvz.org>

Also applied, thanks a lot.

^ permalink raw reply

* Re: [PATCH 1/2 2.6.25] netns: struct net content re-work (v3)
From: David Miller @ 2007-12-11 12:19 UTC (permalink / raw)
  To: den; +Cc: containers, devel, netdev, herbert
In-Reply-To: <20071211115507.GA9947@iris.sw.ru>

From: "Denis V. Lunev" <den@openvz.org>
Date: Tue, 11 Dec 2007 14:55:07 +0300

> Recently David Miller and Herbert Xu pointed out that struct net becomes
> overbloated and un-maintainable. There are two solutions:
> - provide a pointer to a network subsystem definition from struct net.
>   This costs an additional dereferrence
> - place sub-system definition into the structure itself. This will speedup
>   run-time access at the cost of recompilation time
> 
> The second approach looks better for us. Other sub-systems will follow.
> 
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> Acked-by: Daniel Lezcano <dlezcano@fr.ibm.com>

Applied.

^ permalink raw reply

* Re: [PATCH 2.6.25] UNIX: remove unused declaration of sysctl_unix_max_dgram_qlen (resend, wrong patch was sent)
From: David Miller @ 2007-12-11 12:18 UTC (permalink / raw)
  To: den; +Cc: containers, devel, netdev
In-Reply-To: <20071211115323.GA9922@iris.sw.ru>

From: "Denis V. Lunev" <den@openvz.org>
Date: Tue, 11 Dec 2007 14:53:23 +0300

> UNIX: remove unused declaration of sysctl_unix_max_dgram_qlen
> 
> Signed-off-by: Denis V. Lunev <den@openvz.org>

Applied, thanks.

^ permalink raw reply

* Re: [PATCH 2/2] [TCP]: Include __tcp_reset_fack_counts to non-__ version
From: David Miller @ 2007-12-11 12:17 UTC (permalink / raw)
  To: ilpo.jarvinen; +Cc: netdev
In-Reply-To: <11973738392955-git-send-email-ilpo.jarvinen@helsinki.fi>

From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
Date: Tue, 11 Dec 2007 13:50:39 +0200

> This makes flow more obvious in case of short-circuit and
> removes need for prev double pointer & fc recount per queue
> switch.
> 
> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>

Looks good.  References to pointers are always ugly and result
in hard to understand code and subtle bugs.

Applied.

^ permalink raw reply

* Re: [PATCH 1/2] [TCP]: Push fack_count calculation deeper into functions
From: David Miller @ 2007-12-11 12:16 UTC (permalink / raw)
  To: ilpo.jarvinen; +Cc: netdev
In-Reply-To: <11973738393633-git-send-email-ilpo.jarvinen@helsinki.fi>

From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
Date: Tue, 11 Dec 2007 13:50:38 +0200

> This shouldn't have a significant impact because the call to
> tcp_sacktag_one is typically made just once per ACK (this
> doesn't hold if there were some ACK losses in between or the
> receiver didn't generate all ACKs that it should have, but
> those are not very likely events to occur).
> 
> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>

Applied.

Note that the LRO code will generate stretch ACKs for
in-order normal ACKs, it is something to keep in mind
about this but seems irrelevant for the code you are
touching.

^ permalink raw reply

* Re: [TCP]: fack_counts more fixes (the previous ones were incomplete)
From: David Miller @ 2007-12-11 12:15 UTC (permalink / raw)
  To: ilpo.jarvinen; +Cc: netdev, reuben-linuxkernel, akpm
In-Reply-To: <Pine.LNX.4.64.0712111339340.22872@kivilampi-30.cs.helsinki.fi>

From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
Date: Tue, 11 Dec 2007 13:43:24 +0200 (EET)

> [PATCH] [TCP]: fack_counts more fixes (the previous ones were incomplete)
> 
> 1) Prev NULL check should also dereference
> 
> 2) The loop reorganization did make things only slightly better,
>    just changing the same not-updated problem to occur in
>    another scenario. And I also noticed that in worst case it
>    would be an infinite loop (could do that also before the
>    change), but it required losses in the same window when TCP
>    runs out new data. Now check really against the list heads
>    rather than a valid skb which should still be processed.
> 
> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>

Applied, thanks Ilpo.

^ permalink raw reply

* [IPSEC]: Fix potential dst leak in xfrm_lookup
From: Herbert Xu @ 2007-12-11 12:07 UTC (permalink / raw)
  To: David S. Miller, netdev

Hi Dave:

This patch fixes a possible dst leak that has existed for years.

[IPSEC]: Fix potential dst leak in xfrm_lookup

If we get an error during the actual policy lookup we don't free the
original dst while the caller expects us to always free the original
dst in case of error.

This patch fixes that.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 0cb3e8c..265c679 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -1464,8 +1464,9 @@ restart:
 
 	if (sk && sk->sk_policy[XFRM_POLICY_OUT]) {
 		policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
+		err = PTR_ERR(policy);
 		if (IS_ERR(policy))
-			return PTR_ERR(policy);
+			goto dropdst;
 	}
 
 	if (!policy) {
@@ -1476,8 +1477,9 @@ restart:
 
 		policy = flow_cache_lookup(fl, dst_orig->ops->family,
 					   dir, xfrm_policy_lookup);
+		err = PTR_ERR(policy);
 		if (IS_ERR(policy))
-			return PTR_ERR(policy);
+			goto dropdst;
 	}
 
 	if (!policy)
@@ -1642,8 +1644,9 @@ restart:
 	return 0;
 
 error:
-	dst_release(dst_orig);
 	xfrm_pols_put(pols, npols);
+dropdst:
+	dst_release(dst_orig);
 	*dst_p = NULL;
 	return err;
 }

Cheers,
-- 
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related

* [RFC PATCH net-2.6.25 uncompilable] [TCP]: Avoid breaking GSOed skbs when SACKed one-by-one (Was: Re: [RFC] TCP illinois max rtt aging)
From: Ilpo Järvinen @ 2007-12-11 11:59 UTC (permalink / raw)
  To: David Miller; +Cc: lachlan.andrew, Netdev, quetchen
In-Reply-To: <20071207.173201.95379610.davem@davemloft.net>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 12766 bytes --]

On Fri, 7 Dec 2007, David Miller wrote:

> From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
> Date: Fri, 7 Dec 2007 15:05:59 +0200 (EET)
> 
> > On Fri, 7 Dec 2007, David Miller wrote:
> > 
> > > From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
> > > Date: Fri, 7 Dec 2007 13:05:46 +0200 (EET)
> > > 
> > > > I guess if you get a large cumulative ACK, the amount of processing is 
> > > > still overwhelming (added DaveM if he has some idea how to combat it).
> > > > 
> > > > Even a simple scenario (this isn't anything fancy at all, will occur all 
> > > > the time): Just one loss => rest skbs grow one by one into a single 
> > > > very large SACK block (and we do that efficiently for sure) => then the 
> > > > fast retransmit gets delivered and a cumulative ACK for whole orig_window 
> > > > arrives => clean_rtx_queue has to do a lot of processing. In this case we 
> > > > could optimize RB-tree cleanup away (by just blanking it all) but still 
> > > > getting rid of all those skbs is going to take a larger moment than I'd 
> > > > like to see.
> > > 
> > > Yes, it's the classic problem.  But it ought to be at least
> > > partially masked when TSO is in use, because we'll only process
> > > a handful of SKBs.  The more effectively TSO batches, the
> > > less work clean_rtx_queue() will do.
> > 
> > No, that's not what is going to happen, TSO won't help at all
> > because one-by-one SACKs will fragment every single one of them
> > (see tcp_match_skb_to_sack) :-(. ...So we're back in non-TSO
> > case, or am I missing something?
> 
> You're of course right, and it's ironic that I wrote the SACK
> splitting code so I should have known this :-)
> 
> A possible approach just occurred to me wherein we maintain
> the SACK state external to the SKBs so that we don't need to
> mess with them at all.
> 
> That would allow us to eliminate the TSO splitting but it would
> not remove the general problem of clean_rtx_queue()'s overhead.
> 
> I'll try to give some thought to this over the weekend.

How about this...

...I've left couple of FIXMEs there still, should be quite simple & 
straightforward to handle them if this seems viable solution at all.


Beware, this doesn't even compile yet because not all parameters are 
transferred currently (first_sack_index was killed earlier, I need to 
summon it back for this). Also, I'd need to do the dirty work of kill 
recv_sack_cache first to make this to not produce, well, interesting 
effects due to missed SACK blocks... :-)

Applies cleanly only after this:
  [TCP]: Push fack_count calculation deeper into functions


--
 i.

--
[RFC PATCH net-2.6.25 uncompilable] [TCP]: Avoid breaking GSOed skbs when 
SACKed one-by-one

Because receiver reports out-of-order segment one-by-one using
SACK, the tcp_fragment may do a lot of unnecessary splits that
would be avoided if the sender could see the upcoming future.
Not only SACK processing suffers but clean_rtx_queue as well
is considerable hit when the corresponding cumulative ACK
arrives.

Thus implement a local cache for a single skb to avoid enormous
splitting efforts while the latest SACK block is still growing.
Messy enough, other parts must be made aware of this change as
well because the skb state is a bit fuzzy while have not yet
marked it in tcp_sacktag_one.

Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>
---
 include/linux/tcp.h   |    2 +
 include/net/tcp.h     |   19 ++++++++
 net/ipv4/tcp_input.c  |  110 ++++++++++++++++++++++++++++++++++++++++++++++--
 net/ipv4/tcp_output.c |    7 +++
 4 files changed, 133 insertions(+), 5 deletions(-)

diff --git a/include/linux/tcp.h b/include/linux/tcp.h
index 56342c3..4fbfa46 100644
--- a/include/linux/tcp.h
+++ b/include/linux/tcp.h
@@ -360,6 +360,8 @@ struct tcp_sock {
 	u32	fackets_out;	/* FACK'd packets			*/
 	u32	high_seq;	/* snd_nxt at onset of congestion	*/
 
+	u32	sack_pending;	/* End seqno of postponed SACK tagging	*/
+
 	u32	retrans_stamp;	/* Timestamp of the last retransmit,
 				 * also used in SYN-SENT to remember stamp of
 				 * the first SYN. */
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 5e6c433..e2b88e3 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -462,6 +462,21 @@ extern void tcp_send_delayed_ack(struct sock *sk);
 
 /* tcp_input.c */
 extern void tcp_cwnd_application_limited(struct sock *sk);
+extern void __tcp_process_postponed_sack(struct sock *sk, struct sk_buff *skb);
+extern struct sk_buff *tcp_find_postponed_skb(struct sock *sk);
+
+static inline void tcp_process_postponed_sack(struct sock *sk)
+{
+	if (tcp_sk(sk)->sack_pending)
+		__tcp_process_postponed_sack(sk, tcp_find_postponed_skb(sk));
+}
+
+static inline void tcp_process_postponed_sack_overlapping(struct sock *sk,
+							  struct sk_buff *skb)
+{
+	if (tcp_sk(sk)->sack_pending && (skb == tcp_find_postponed_skb(sk)))
+		__tcp_process_postponed_sack(sk, skb);
+}
 
 /* tcp_timer.c */
 extern void tcp_init_xmit_timers(struct sock *);
@@ -1625,6 +1640,10 @@ static inline u32 tcp_highest_sack_seq(struct tcp_sock *tp)
 	if (tp->highest_sack == NULL)
 		return tp->snd_nxt;
 
+	if (tp->sack_pending &&
+	    before(TCP_SKB_CB(tp->highest_sack)->seq, tp->sack_pending))
+		return tp->sack_pending;
+
 	return TCP_SKB_CB(tp->highest_sack)->seq;
 }
 
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 977c68c..5bed358 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -1181,6 +1181,74 @@ static void tcp_mark_lost_retrans(struct sock *sk)
 		tp->lost_retrans_low = new_low_seq;
 }
 
+struct sk_buff *tcp_find_postponed_skb(struct sock *sk)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+
+	if (before(TCP_SKB_CB(tcp_highest_sack(sk))->seq, tp->sack_pending))
+		return tcp_highest_sack(sk);
+	else
+		return tcp_write_queue_find(sk, tp->sack_pending, 0);
+}
+
+static void tcp_clear_postponed_tweaks(struct sock *sk, struct sk_buff *skb)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+	unsigned int pcount;
+
+	pcount = (tp->sack_pending - TCP_SKB_CB(skb)->seq) / skb_shinfo(skb)->gso_size;
+
+	tp->sacked_out -= pcount;
+	if (TCP_SKB_CB(skb)->sacked & TCPCB_LOST)
+		tp->lost_out += pcount;
+	/* FIXME: This might be unnecessary */
+	if (skb == tcp_highest_sack(sk))
+		tp->fackets_out -= pcount;
+}
+
+static int tcp_postpone_sack_handling(struct sock *sk, struct sk_buff *skb, u32 end_seq)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+	unsigned int pcount;
+
+	if (tp->sack_pending) {
+		struct sk_buff *oskb = tcp_find_postponed_skb(sk);
+
+		/* Already considered this area? E.g., due to reordering */
+		if (unlikely((skb == oskb) && !after(end_seq, tp->sack_pending)))
+			return 1;
+
+		if (skb != oskb)
+			__tcp_process_postponed_sack(sk, oskb);
+		else
+			tcp_clear_postponed_tweaks(sk, oskb);
+	}
+
+	/* To be discarded very soon, this case might be improved somehow */
+	if (unlikely(!after(TCP_SKB_CB(skb)->seq, tp->snd_una)))
+		return 0;
+
+	/* 1 out of 2^32 hits this */
+	if (unlikely(!end_seq))
+		return 0;
+
+	pcount = (end_seq - TCP_SKB_CB(skb)->seq) / skb_shinfo(skb)->gso_size;
+
+	if (TCP_SKB_CB(skb)->sacked & TCPCB_LOST)
+		tp->lost_out -= pcount;
+	tp->sacked_out += pcount;
+	if (!before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp))) {
+		unsigned int fack_count_base;
+
+		fack_count_base = TCP_SKB_CB(tcp_write_queue_head(sk))->fack_count;
+		tp->fackets_out = TCP_SKB_CB(skb)->fack_count -
+					fack_count_base + pcount;
+		tp->highest_sack = skb;
+	}
+	tp->sack_pending = end_seq;
+
+}
+
 /* Check if skb is fully within the SACK block. In presence of GSO skbs,
  * the incoming SACK may not exactly match but we can find smaller MSS
  * aligned portion of it that matches. Therefore we might need to fragment
@@ -1188,7 +1256,7 @@ static void tcp_mark_lost_retrans(struct sock *sk)
  * returns).
  */
 static int tcp_match_skb_to_sack(struct sock *sk, struct sk_buff *skb,
-				 u32 start_seq, u32 end_seq)
+				 u32 start_seq, u32 end_seq, int likely_grows)
 {
 	int in_sack, err;
 	unsigned int pkt_len;
@@ -1201,10 +1269,14 @@ static int tcp_match_skb_to_sack(struct sock *sk, struct sk_buff *skb,
 
 		in_sack = !after(start_seq, TCP_SKB_CB(skb)->seq);
 
-		if (!in_sack)
+		if (!in_sack) {
 			pkt_len = start_seq - TCP_SKB_CB(skb)->seq;
-		else
+		} else {
 			pkt_len = end_seq - TCP_SKB_CB(skb)->seq;
+
+			if (likely_grows && tcp_postpone_sack_handling(sk, skb, end_seq))
+				return -EAGAIN;
+		}
 		err = tcp_fragment(sk, skb, pkt_len, skb_shinfo(skb)->gso_size);
 		if (err < 0)
 			return err;
@@ -1318,6 +1390,7 @@ static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
 					int dup_sack, int *reord, int *flag,
 					int queue)
 {
+	struct tcp_sock *tp = tcp_sk(sk);
 	struct sk_buff *next;
 
 	tcp_for_write_queue_from_safe(skb, next, sk, queue) {
@@ -1330,16 +1403,32 @@ static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
 		if (!before(TCP_SKB_CB(skb)->seq, end_seq))
 			break;
 
-		in_sack = tcp_match_skb_to_sack(sk, skb, start_seq, end_seq);
+		in_sack = tcp_match_skb_to_sack(sk, skb, start_seq, end_seq,
+						!dup_sack && first_block_idx);
 		if (unlikely(in_sack < 0))
 			break;
 
-		if (in_sack)
+		if (in_sack) {
+			if (tp->sack_pending && skb == tcp_find_postponed_skb(sk))
+				tcp_clear_postponed_tweaks(sk, skb);
 			*flag |= tcp_sacktag_one(skb, sk, reord, dup_sack);
+		}
 	}
 	return skb;
 }
 
+void __tcp_process_postponed_sack(struct sock *sk, struct sk_buff *skb)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+	int reord = tp->packets_out;
+
+	tcp_clear_postponed_tweaks(sk, skb);
+	tp->sack_pending = 0;
+	tcp_sacktag_one(skb, sk, &reord, 0);
+
+	/* FIXME: reord handling here */
+}
+
 /* Avoid all extra work that is being done by sacktag while walking in
  * a normal way
  */
@@ -1705,6 +1794,8 @@ void tcp_enter_frto(struct sock *sk)
 	struct tcp_sock *tp = tcp_sk(sk);
 	struct sk_buff *skb;
 
+	tcp_process_postponed_sack(sk);
+
 	if ((!tp->frto_counter && icsk->icsk_ca_state <= TCP_CA_Disorder) ||
 	    tp->snd_una == tp->high_seq ||
 	    ((icsk->icsk_ca_state == TCP_CA_Loss || tp->frto_counter) &&
@@ -1777,6 +1868,8 @@ static void tcp_enter_frto_loss(struct sock *sk, int allowed_segments, int flag)
 	struct tcp_sock *tp = tcp_sk(sk);
 	struct sk_buff *skb;
 
+	tcp_process_postponed_sack(sk);
+
 	tp->lost_out = 0;
 	tp->retrans_out = 0;
 	if (tcp_is_reno(tp))
@@ -1875,10 +1968,12 @@ void tcp_enter_loss(struct sock *sk, int how)
 		/* Push undo marker, if it was plain RTO and nothing
 		 * was retransmitted. */
 		tp->undo_marker = tp->snd_una;
+		tcp_process_postponed_sack(sk);
 		tcp_clear_retrans_hints_partial(tp);
 	} else {
 		tp->sacked_out = 0;
 		tp->fackets_out = 0;
+		tp->sack_pending = 0;
 		tcp_clear_all_retrans_hints(tp);
 
 		tcp_for_write_queue(skb, sk, TCP_WQ_SACKED) {
@@ -2171,6 +2266,7 @@ static void tcp_mark_head_lost(struct sock *sk, int packets, int fast_rexmit)
 		if (tcp_is_fack(tp)) {
 			cnt = fc - fack_count_base + tcp_skb_pcount(skb);
 		} else {
+			/* FIXME: SACK postponing adaption */
 			if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)
 				cnt += tcp_skb_pcount(skb);
 			/* Add SACK blocks between this and skb->prev */
@@ -2224,6 +2320,9 @@ static void tcp_update_scoreboard(struct sock *sk, int fast_rexmit)
 		skb = tp->scoreboard_skb_hint ? tp->scoreboard_skb_hint
 			: tcp_write_queue_head(sk);
 
+		/* FIXME: Performance penalty of this likely significant */
+		tcp_process_postponed_sack(sk);
+
 		tcp_for_write_queue_from(skb, sk, 0) {
 			if (skb == tcp_send_head(sk))
 				break;
@@ -2422,6 +2521,7 @@ static int tcp_try_undo_loss(struct sock *sk)
 
 	if (tcp_may_undo(tp)) {
 		struct sk_buff *skb;
+
 		tcp_for_write_queue(skb, sk, 0) {
 			if (skb == tcp_send_head(sk))
 				break;
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 1d83c65..e7cb39b 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -696,6 +696,8 @@ int tcp_fragment(struct sock *sk, struct sk_buff *skb, u32 len, unsigned int mss
 	    pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
 		return -ENOMEM;
 
+	tcp_process_postponed_sack_overlapping(sk, skb);
+
 	/* Get a new skb... force flag on. */
 	buff = sk_stream_alloc_skb(sk, nsize, GFP_ATOMIC);
 	if (buff == NULL)
@@ -1761,6 +1763,8 @@ void tcp_simple_retransmit(struct sock *sk)
 	unsigned int mss = tcp_current_mss(sk, 0);
 	int lost = 0;
 
+	tcp_process_postponed_sack(sk);
+
 	tcp_for_write_queue(skb, sk, 0) {
 		if (skb == tcp_send_head(sk))
 			break;
@@ -1928,6 +1932,9 @@ void tcp_xmit_retransmit_queue(struct sock *sk)
 	struct sk_buff *skb;
 	int packet_cnt;
 
+	/* FIXME: There are better was to do this in here */
+	tcp_process_postponed_sack(sk);
+
 	if (tp->retransmit_skb_hint) {
 		skb = tp->retransmit_skb_hint;
 		packet_cnt = tp->retransmit_cnt_hint;
-- 
1.5.0.6

^ permalink raw reply related

* [PATCH 2/2 2.6.25] netns: separate af_packet netns data
From: Denis V. Lunev @ 2007-12-11 11:55 UTC (permalink / raw)
  To: davem; +Cc: containers, devel, netdev, herbert

netns: move af_packet data to the separate header

Signed-off-by: Denis V. Lunev <den@openvz.org>
---
 include/net/net_namespace.h |    6 ++----
 include/net/netns/packet.h  |   15 +++++++++++++++
 net/packet/af_packet.c      |   28 ++++++++++++++--------------
 3 files changed, 31 insertions(+), 18 deletions(-)
 create mode 100644 include/net/netns/packet.h

diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index d943fd4..18da0af 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -9,6 +9,7 @@
 #include <linux/list.h>
 
 #include <net/netns/unix.h>
+#include <net/netns/packet.h>
 
 struct proc_dir_entry;
 struct net_device;
@@ -43,10 +44,7 @@ struct net {
 	struct ctl_table_header	*sysctl_core_hdr;
 	int			sysctl_somaxconn;
 
-	/* List of all packet sockets. */
-	rwlock_t		packet_sklist_lock;
-	struct hlist_head	packet_sklist;
-
+	struct netns_packet	packet;
 	struct netns_unix	unx;
 };
 
diff --git a/include/net/netns/packet.h b/include/net/netns/packet.h
new file mode 100644
index 0000000..637daf6
--- /dev/null
+++ b/include/net/netns/packet.h
@@ -0,0 +1,15 @@
+/*
+ * Packet network namespace
+ */
+#ifndef __NETNS_PACKET_H__
+#define __NETNS_PACKET_H__
+
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+struct netns_packet {
+	rwlock_t		sklist_lock;
+	struct hlist_head	sklist;
+};
+
+#endif /* __NETNS_PACKET_H__ */
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index ace29f1..485af56 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -803,9 +803,9 @@ static int packet_release(struct socket *sock)
 	net = sk->sk_net;
 	po = pkt_sk(sk);
 
-	write_lock_bh(&net->packet_sklist_lock);
+	write_lock_bh(&net->packet.sklist_lock);
 	sk_del_node_init(sk);
-	write_unlock_bh(&net->packet_sklist_lock);
+	write_unlock_bh(&net->packet.sklist_lock);
 
 	/*
 	 *	Unhook packet receive handler.
@@ -1015,9 +1015,9 @@ static int packet_create(struct net *net, struct socket *sock, int protocol)
 		po->running = 1;
 	}
 
-	write_lock_bh(&net->packet_sklist_lock);
-	sk_add_node(sk, &net->packet_sklist);
-	write_unlock_bh(&net->packet_sklist_lock);
+	write_lock_bh(&net->packet.sklist_lock);
+	sk_add_node(sk, &net->packet.sklist);
+	write_unlock_bh(&net->packet.sklist_lock);
 	return(0);
 out:
 	return err;
@@ -1452,8 +1452,8 @@ static int packet_notifier(struct notifier_block *this, unsigned long msg, void
 	struct net_device *dev = data;
 	struct net *net = dev->nd_net;
 
-	read_lock(&net->packet_sklist_lock);
-	sk_for_each(sk, node, &net->packet_sklist) {
+	read_lock(&net->packet.sklist_lock);
+	sk_for_each(sk, node, &net->packet.sklist) {
 		struct packet_sock *po = pkt_sk(sk);
 
 		switch (msg) {
@@ -1492,7 +1492,7 @@ static int packet_notifier(struct notifier_block *this, unsigned long msg, void
 			break;
 		}
 	}
-	read_unlock(&net->packet_sklist_lock);
+	read_unlock(&net->packet.sklist_lock);
 	return NOTIFY_DONE;
 }
 
@@ -1862,7 +1862,7 @@ static inline struct sock *packet_seq_idx(struct net *net, loff_t off)
 	struct sock *s;
 	struct hlist_node *node;
 
-	sk_for_each(s, node, &net->packet_sklist) {
+	sk_for_each(s, node, &net->packet.sklist) {
 		if (!off--)
 			return s;
 	}
@@ -1872,7 +1872,7 @@ static inline struct sock *packet_seq_idx(struct net *net, loff_t off)
 static void *packet_seq_start(struct seq_file *seq, loff_t *pos)
 {
 	struct net *net = seq_file_net(seq);
-	read_lock(&net->packet_sklist_lock);
+	read_lock(&net->packet.sklist_lock);
 	return *pos ? packet_seq_idx(net, *pos - 1) : SEQ_START_TOKEN;
 }
 
@@ -1881,14 +1881,14 @@ static void *packet_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 	struct net *net = seq->private;
 	++*pos;
 	return  (v == SEQ_START_TOKEN)
-		? sk_head(&net->packet_sklist)
+		? sk_head(&net->packet.sklist)
 		: sk_next((struct sock*)v) ;
 }
 
 static void packet_seq_stop(struct seq_file *seq, void *v)
 {
 	struct net *net = seq->private;
-	read_unlock(&net->packet_sklist_lock);
+	read_unlock(&net->packet.sklist_lock);
 }
 
 static int packet_seq_show(struct seq_file *seq, void *v)
@@ -1940,8 +1940,8 @@ static const struct file_operations packet_seq_fops = {
 
 static int packet_net_init(struct net *net)
 {
-	rwlock_init(&net->packet_sklist_lock);
-	INIT_HLIST_HEAD(&net->packet_sklist);
+	rwlock_init(&net->packet.sklist_lock);
+	INIT_HLIST_HEAD(&net->packet.sklist);
 
 	if (!proc_net_fops_create(net, "packet", 0, &packet_seq_fops))
 		return -ENOMEM;
-- 
1.5.3.rc5


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox