Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH] One more XFRM audit fix
From: Paul Moore @ 2007-12-20 12:25 UTC (permalink / raw)
  To: David Miller; +Cc: netdev, linux-audit
In-Reply-To: <20071220.000009.173158004.davem@davemloft.net>

On Thursday 20 December 2007 3:00:09 am David Miller wrote:
> From: Paul Moore <paul.moore@hp.com>
> Date: Wed, 19 Dec 2007 14:29:31 -0500
>
> > The following patch is backed against David's net-2.6 tree and is pretty
> > trivial.  I know we're late in the 2.6.24 cycle but I think this is worth
> > merging, if you guys don't feel that way let me know and I'll resubmit it
> > for 2.6.25.
>
> Where is that patch?  Or do you mean the fix you emailed
> seperately today (which I will apply, thanks)?

Yes, it was the patch you applied, "XFRM: Audit function arguments 
misordered".  I was using stacked-git to post the patch and it apparently 
doesn't annotate the cover email's subject line with "0/1" when you only send 
one patch.

Sorry about that.

> > As a side note, I'm unable to actually test the patch because I can't get
> > the kernel to compile (M=net/xfrm works just fine).  The problem I keep
> > seeing is below:
> >
> > make[3]: *** No rule to make target \
> >  `/blah/kernels/net-2.6_xfrm-auid-secid-fix/include/linux/ticable.h', \
> >   needed by \
> >  `/blah/kernels/net-2.6_xfrm-auid-secid-fix/usr/include/linux/ticable.h'.
> > \ Stop.
>
> Remove ticable.h from include/linux/Kbuild
>
> This is already cured in Linus's tree.

Noted, thanks.

-- 
paul moore
linux security @ hp

^ permalink raw reply

* Re: [SNMP]: Fix SNMP counters with PREEMPT
From: David Miller @ 2007-12-20 12:13 UTC (permalink / raw)
  To: herbert
  Cc: dada1, kuznet, wangchen, gerrit, andi, netdev, linux-kernel,
	clameter, mingo
In-Reply-To: <20071216023025.GA959@gondor.apana.org.au>

From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Sun, 16 Dec 2007 10:30:25 +0800

> On Sat, Dec 15, 2007 at 06:03:19PM +0100, Eric Dumazet wrote:
> >
> > How come you change SNMP_INC_STATS_USER() but not SNMP_INC_STATS() ?
> 
> Heh, my brain must have blocked me from seeing it because it's
> too hard :)
> 
> Let's fix it the stupid way first and I'll do a local_t conversion
> later.
> 
> [SNMP]: Fix SNMP counters with PREEMPT
> 
> The SNMP macros use raw_smp_processor_id() in process context
> which is illegal because the process may be preempted and then
> migrated to another CPU.
> 
> This patch makes it use get_cpu/put_cpu to disable preemption.
> 
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

I just noticed this and replaced the other SNMP fix patch
with this one.

^ permalink raw reply

* Re: [PATCH 3/3] [UDP6]: Counter increment on BH mode
From: David Miller @ 2007-12-20 12:12 UTC (permalink / raw)
  To: herbert
  Cc: kuznet, wangchen, gerrit, andi, netdev, linux-kernel, clameter,
	mingo
In-Reply-To: <20071215135851.GA29063@gondor.apana.org.au>

From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Sat, 15 Dec 2007 21:58:52 +0800

> [SNMP]: Fix SNMP counters with PREEMPT
> 
> The SNMP macros use raw_smp_processor_id() in process context
> which is illegal because the process may be preempted and then
> migrated to another CPU.
> 
> This patch makes it use get_cpu/put_cpu to disable preemption.
> 
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Applied to net-2.6.25, thanks.

^ permalink raw reply

* Re: [PATCH 05/11] drivers/net/niu.c: Use print_mac
From: David Miller @ 2007-12-20 12:07 UTC (permalink / raw)
  To: joe; +Cc: linux-kernel, jgarzik, netdev
In-Reply-To: <1197671652-32492-5-git-send-email-joe@perches.com>

From: Joe Perches <joe@perches.com>
Date: Fri, 14 Dec 2007 14:34:06 -0800

> Signed-off-by: Joe Perches <joe@perches.com>

Applied to net-2.6.25

^ permalink raw reply

* Re: [PATCH 09/11] drivers/net/tg3.c: Use print_mac
From: David Miller @ 2007-12-20 12:07 UTC (permalink / raw)
  To: joe; +Cc: linux-kernel, jgarzik, mchan, netdev
In-Reply-To: <1197671652-32492-9-git-send-email-joe@perches.com>

From: Joe Perches <joe@perches.com>
Date: Fri, 14 Dec 2007 14:34:10 -0800

> Signed-off-by: Joe Perches <joe@perches.com>

Applied to net-2.6.25

^ permalink raw reply

* Re: [PATCH 08/11] drivers/net/sunvnet.c: Use print_mac
From: David Miller @ 2007-12-20 12:06 UTC (permalink / raw)
  To: joe; +Cc: linux-kernel, jgarzik, netdev
In-Reply-To: <1197671652-32492-8-git-send-email-joe@perches.com>

From: Joe Perches <joe@perches.com>
Date: Fri, 14 Dec 2007 14:34:09 -0800

> Signed-off-by: Joe Perches <joe@perches.com>

Applied to net-2.6.25

^ permalink raw reply

* Re: TSO trimming question
From: David Miller @ 2007-12-20 12:00 UTC (permalink / raw)
  To: ilpo.jarvinen; +Cc: netdev, herbert
In-Reply-To: <Pine.LNX.4.64.0712201253400.31652@kivilampi-30.cs.helsinki.fi>

From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
Date: Thu, 20 Dec 2007 13:40:51 +0200 (EET)

> That's not the only case, IMHO if there's odd boundary due to 
> snd_una+snd_wnd - skb->seq limit (done in tcp_window_allows()), we don't 
> consider it as odd but break the skb at arbitary point resulting
> two small segments to the network, and what's worse, when the later skb 
> resulting from the first split is matching skb->len < limit check as well 
> causing an unnecessary small skb to be created for nagle purpose too, 
> solving it fully requires some thought in case the mss_now != mss_cache 
> even if non-odd boundaries are honored in the middle of skb.

In the most ideal sense, tcp_window_allows() should probably
be changed to only return MSS multiples.

Unfortunately this would add an expensive modulo operation,
however I think it would elimiate this problem case.

^ permalink raw reply

* Re: TSO trimming question
From: David Miller @ 2007-12-20 11:56 UTC (permalink / raw)
  To: ilpo.jarvinen; +Cc: netdev, herbert, jheffner
In-Reply-To: <Pine.LNX.4.64.0712201253400.31652@kivilampi-30.cs.helsinki.fi>

From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
Date: Thu, 20 Dec 2007 13:40:51 +0200 (EET)

> [PATCH] [TCP]: Fix TSO deferring
> 
> I'd say that most of what tcp_tso_should_defer had in between
> there was dead code because of this.
> 
> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>

Yikes!!!!!

John, we've been living a lie for more than a year. :-/

On the bright side this explains a lot of small TSO frames I've been
seeing in traces over the past year but never got a chance to
investigate.

> diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
> index 8dafda9..693b9f6 100644
> --- a/net/ipv4/tcp_output.c
> +++ b/net/ipv4/tcp_output.c
> @@ -1217,7 +1217,8 @@ static int tcp_tso_should_defer(struct sock *sk, struct sk_buff *skb)
>  		goto send_now;
>  
>  	/* Defer for less than two clock ticks. */
> -	if (!tp->tso_deferred && ((jiffies<<1)>>1) - (tp->tso_deferred>>1) > 1)
> +	if (tp->tso_deferred &&
> +	    ((jiffies << 1) >> 1) - (tp->tso_deferred >> 1) > 1)
>  		goto send_now;
>  
>  	in_flight = tcp_packets_in_flight(tp);

^ permalink raw reply

* Re: [PATCH 1/2] add driver for enc28j60 ethernet chip
From: Claudio Lanconelli @ 2007-12-20 11:47 UTC (permalink / raw)
  To: Jeff Garzik; +Cc: netdev, Stephen Hemminger
In-Reply-To: <47670AFA.1000608@pobox.com>

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

Here a second version of the patch with applied all changes Jeff and 
Stephen suggest me.
All but NAPI, see comments below at this regard.
Furthermore I fixed also a bug that caused a crash after ifconfig down 
and up cycle and changed the mutex_lock policy.

Jeff Garzik wrote:
>
> comments:
>
> * overall:  a clean driver that looks mostly acceptable, good work
Thanks
> * Why do interrupt work in a kernel thread?  Your comment says you 
> cannot, but does not explain.
The enc28j60 is a SPI to Ethernet adapter, so we cannot access register 
with simple in() out() instructions, but we need to use the SPI 
subsystem. The spi_sync() basic operation to read/write a register is a 
blocking operation, so can't be done in interrupt context.
Since every basic operation like read interrupt flag register call 
spi_sync() we need the work queue for almost everything.

>
> * should use NAPI
>
For the reason I just explained I don't think NAPI is a viable way for 
enc28j60.
Furthermore enc28j60 is a 10Mb only device and probably don't suffer to 
interrupt overload.
In my test there are a lot more interrupts from SPI than from enc28j60 
and the enc28j60 interrupts are less than RX+TX packets. See the dump below:

[root@videotron /tmp]# ifconfig
eth0      Link encap:Ethernet  HWaddr 7E:C4:3B:C7:EA:F3
          inet addr:192.168.143.197  Bcast:192.168.143.255  
Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:4972 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2832 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:2009875 (1.9 MiB)  TX bytes:131922 (128.8 KiB)
          Interrupt:18

[root@videotron /tmp]# cat /proc/interrupts
           CPU0
 18:       4316    s3c-ext0  enc28j60
 30:   11591615         s3c  S3C2410 Timer Tick
 32:          0         s3c  s162-lcd
 33:          0         s3c  s3c-mci
 37:         21         s3c  s3c-mci
 38:    2707866         s3c  s3c2410-spi
 42:          0         s3c  ohci_hcd:usb1
 43:         28         s3c  s3c2440-i2c
 45:          1         s3c  s3c2410-spi
 62:          1     s3c-ext  s3c-mci
 73:        342   s3c-uart1  s3c2440-uart
 74:       1225   s3c-uart1  s3c2440-uart
 79:          0     s3c-adc  s3c24xx_action
 80:          0     s3c-adc  s3c24xx_action
Err:          0

I think also USB-ethernet adapter have the same problem, and neither use 
NAPI

Cheers,
Claudio Lanconelli


[-- Attachment #2: enc28j60.patch --]
[-- Type: text/x-patch, Size: 56016 bytes --]

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

diff --git a/drivers/net/enc28j60.c b/drivers/net/enc28j60.c
new file mode 100644
index 0000000..0809a6a
--- /dev/null
+++ b/drivers/net/enc28j60.c
@@ -0,0 +1,1600 @@
+/*
+ * 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.22 2007/12/20 10:47:01 claudio Exp $
+ */
+
+#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/errno.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#include "enc28j60_hw.h"
+
+#define DRV_NAME	"enc28j60"
+#define DRV_VERSION	"1.01"
+
+#define SPI_OPLEN	1
+
+#define ENC28J60_MSG_DEFAULT	\
+	(NETIF_MSG_PROBE | NETIF_MSG_IFUP | NETIF_MSG_IFDOWN | NETIF_MSG_LINK)
+
+/* Buffer size required for the largest SPI transfer (i.e., reading a
+ * frame). */
+#define SPI_TRANSFER_BUF_LEN	(4 + MAX_FRAMELEN)
+
+#define TX_TIMEOUT	(4 * HZ)
+
+/* Max TX retries in case of collision as suggested by errata datasheet */
+#define MAX_TX_RETRYCOUNT	16
+
+enum {
+	RXFILTER_NORMAL,
+	RXFILTER_MULTI,
+	RXFILTER_PROMISC
+};
+
+/* Driver local data */
+struct enc28j60_net {
+	struct net_device *netdev;
+	struct spi_device *spi;
+	struct mutex lock;
+	struct sk_buff *tx_skb;
+	struct work_struct tx_work;
+	struct work_struct irq_work;
+	struct work_struct setrx_work;
+	struct work_struct restart_work;
+	u8 bank;		/* current register bank selected */
+	u16 next_pk_ptr;	/* next packet pointer within FIFO */
+	u16 max_pk_counter;	/* statistics: max packet counter */
+	u16 tx_retry_count;
+	bool hw_enable;
+	bool full_duplex;
+	int rxfilter;
+	u32 msg_enable;
+	u8 spi_transfer_buf[SPI_TRANSFER_BUF_LEN];
+};
+
+/* use ethtool to change the level for any given device */
+static struct {
+	u32 msg_enable;
+} debug = { -1 };
+
+/*
+ * SPI read buffer
+ * wait for the SPI transfer and copy received data to destination
+ */
+static int
+spi_read_buf(struct enc28j60_net *priv, int len, u8 *data)
+{
+	u8 *rx_buf = priv->spi_transfer_buf + 4;
+	u8 *tx_buf = priv->spi_transfer_buf;
+	struct spi_transfer t = {
+		.tx_buf = tx_buf,
+		.rx_buf = rx_buf,
+		.len = SPI_OPLEN + len,
+	};
+	struct spi_message msg;
+	int ret;
+
+	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[SPI_OPLEN], len);
+		ret = msg.status;
+	}
+	if (ret && netif_msg_drv(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n",
+			__FUNCTION__, ret);
+
+	return ret;
+}
+
+/*
+ * SPI write buffer
+ */
+static int spi_write_buf(struct enc28j60_net *priv, int len,
+			 const u8 *data)
+{
+	int ret;
+
+	if (len > SPI_TRANSFER_BUF_LEN - 1 || len <= 0)
+		ret = -EINVAL;
+	else {
+		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);
+		if (ret && netif_msg_drv(priv))
+			printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n",
+				__FUNCTION__, ret);
+	}
+	return ret;
+}
+
+/*
+ * basic SPI read operation
+ */
+static u8 spi_read_op(struct enc28j60_net *priv, u8 op,
+			   u8 addr)
+{
+	u8 tx_buf[2];
+	u8 rx_buf[4];
+	u8 val = 0;
+	int ret;
+	int slen = SPI_OPLEN;
+
+	/* 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)
+		printk(KERN_DEBUG DRV_NAME ": %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 *priv, u8 op,
+			u8 addr, u8 val)
+{
+	int ret;
+
+	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);
+	if (ret && netif_msg_drv(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n",
+			__FUNCTION__, ret);
+	return ret;
+}
+
+static void enc28j60_soft_reset(struct enc28j60_net *priv)
+{
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__);
+
+	spi_write_op(priv, ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
+	/* Errata workaround #1, CLKRDY check is unreliable,
+	 * delay at least 1 mS instead */
+	udelay(2000);
+}
+
+/*
+ * select the current register bank if necessary
+ */
+static void enc28j60_set_bank(struct enc28j60_net *priv, u8 addr)
+{
+	if ((addr & BANK_MASK) != priv->bank) {
+		u8 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 access routines through the SPI bus.
+ * Every register access comes in two flavours:
+ * - nolock_xxx: caller needs to invoke mutex_lock, usually to access
+ *   atomically more than one register
+ * - locked_xxx: caller doesn't need to invoke mutex_lock, single access
+ *
+ * Some registers can be accessed through the bit field clear and
+ * bit field set to avoid a read modify write cycle.
+ */
+
+/*
+ * Register bit field Set
+ */
+static void nolock_reg_bfset(struct enc28j60_net *priv,
+				      u8 addr, u8 mask)
+{
+	enc28j60_set_bank(priv, addr);
+	spi_write_op(priv, ENC28J60_BIT_FIELD_SET, addr, mask);
+}
+
+static void locked_reg_bfset(struct enc28j60_net *priv,
+				      u8 addr, u8 mask)
+{
+	mutex_lock(&priv->lock);
+	nolock_reg_bfset(priv, addr, mask);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Register bit field Clear
+ */
+static void nolock_reg_bfclr(struct enc28j60_net *priv,
+				      u8 addr, u8 mask)
+{
+	enc28j60_set_bank(priv, addr);
+	spi_write_op(priv, ENC28J60_BIT_FIELD_CLR, addr, mask);
+}
+
+static void locked_reg_bfclr(struct enc28j60_net *priv,
+				      u8 addr, u8 mask)
+{
+	mutex_lock(&priv->lock);
+	nolock_reg_bfclr(priv, addr, mask);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Register byte read
+ */
+static int nolock_regb_read(struct enc28j60_net *priv,
+				     u8 address)
+{
+	enc28j60_set_bank(priv, address);
+	return spi_read_op(priv, ENC28J60_READ_CTRL_REG, address);
+}
+
+static int locked_regb_read(struct enc28j60_net *priv,
+				     u8 address)
+{
+	int ret;
+
+	mutex_lock(&priv->lock);
+	ret = nolock_regb_read(priv, address);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+/*
+ * Register word read
+ */
+static int nolock_regw_read(struct enc28j60_net *priv,
+				     u8 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;
+}
+
+static int locked_regw_read(struct enc28j60_net *priv,
+				     u8 address)
+{
+	int ret;
+
+	mutex_lock(&priv->lock);
+	ret = nolock_regw_read(priv, address);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+/*
+ * Register byte write
+ */
+static void nolock_regb_write(struct enc28j60_net *priv,
+				       u8 address, u8 data)
+{
+	enc28j60_set_bank(priv, address);
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, address, data);
+}
+
+static void locked_regb_write(struct enc28j60_net *priv,
+				       u8 address, u8 data)
+{
+	mutex_lock(&priv->lock);
+	nolock_regb_write(priv, address, data);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Register word write
+ */
+static void nolock_regw_write(struct enc28j60_net *priv,
+				       u8 address, u16 data)
+{
+	enc28j60_set_bank(priv, address);
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, address, (u8) data);
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, address + 1,
+		     (u8) (data >> 8));
+}
+
+static void locked_regw_write(struct enc28j60_net *priv,
+				       u8 address, u16 data)
+{
+	mutex_lock(&priv->lock);
+	nolock_regw_write(priv, address, data);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Buffer memory read
+ * Select the starting address and execute a SPI buffer read
+ */
+static void enc28j60_mem_read(struct enc28j60_net *priv,
+				     u16 addr, int len, u8 *data)
+{
+	mutex_lock(&priv->lock);
+	nolock_regw_write(priv, ERDPTL, addr);
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	if (netif_msg_drv(priv)) {
+		u16 reg;
+		reg = nolock_regw_read(priv, ERDPTL);
+		if (reg != addr)
+			printk(KERN_DEBUG DRV_NAME ": %s() error writing ERDPT "
+				"(0x%04x - 0x%04x)\n", __FUNCTION__, reg, addr);
+	}
+#endif
+	spi_read_buf(priv, len, data);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Write packet to enc28j60 TX buffer memory
+ */
+static void
+enc28j60_packet_write(struct enc28j60_net *priv, int len, const u8 *data)
+{
+	mutex_lock(&priv->lock);
+	/* Set the write pointer to start of transmit buffer area */
+	nolock_regw_write(priv, EWRPTL, TXSTART_INIT);
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	if (netif_msg_drv(priv)) {
+		u16 reg;
+		reg = nolock_regw_read(priv, EWRPTL);
+		if (reg != TXSTART_INIT)
+			printk(KERN_DEBUG DRV_NAME
+				": %s() ERWPT:0x%04x != 0x%04x\n",
+				__FUNCTION__, reg, TXSTART_INIT);
+	}
+#endif
+	/* Set the TXND pointer to correspond to the packet size given */
+	nolock_regw_write(priv, ETXNDL, TXSTART_INIT + len);
+	/* write per-packet control byte */
+	spi_write_op(priv, ENC28J60_WRITE_BUF_MEM, 0, 0x00);
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME
+			": %s() after control byte ERWPT:0x%04x\n",
+			__FUNCTION__, nolock_regw_read(priv, EWRPTL));
+	/* copy the packet into the transmit buffer */
+	spi_write_buf(priv, len, data);
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME
+			 ": %s() after write packet ERWPT:0x%04x, len=%d\n",
+			 __FUNCTION__, nolock_regw_read(priv, EWRPTL), len);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Wait until the PHY operation is complete.
+ */
+static int wait_phy_ready(struct enc28j60_net *priv)
+{
+	unsigned long timeout = jiffies + 20 * HZ / 1000;
+	int ret = 1;
+
+	/* 20 msec timeout read */
+	while (nolock_regb_read(priv, MISTAT) & MISTAT_BUSY) {
+		if (time_after(jiffies, timeout)) {
+			if (netif_msg_drv(priv))
+				printk(KERN_DEBUG DRV_NAME
+					": PHY ready timeout!\n");
+			ret = 0;
+			break;
+		}
+		cpu_relax();
+	}
+	return ret;
+}
+
+/*
+ * PHY register read
+ * PHY registers are not accessed directly, but through the MII
+ */
+static u16 enc28j60_phy_read(struct enc28j60_net *priv, u8 address)
+{
+	u16 ret;
+
+	mutex_lock(&priv->lock);
+	/* set the PHY register address */
+	nolock_regb_write(priv, MIREGADR, address);
+	/* start the register read operation */
+	nolock_regb_write(priv, MICMD, MICMD_MIIRD);
+	/* wait until the PHY read completes */
+	wait_phy_ready(priv);
+	/* quit reading */
+	nolock_regb_write(priv, MICMD, 0x00);
+	/* return the data */
+	ret  = nolock_regw_read(priv, MIRDL);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int enc28j60_phy_write(struct enc28j60_net *priv, u8 address, u16 data)
+{
+	int ret;
+
+	mutex_lock(&priv->lock);
+	/* set the PHY register address */
+	nolock_regb_write(priv, MIREGADR, address);
+	/* write the PHY data */
+	nolock_regw_write(priv, MIWRL, data);
+	/* wait until the PHY write completes and return */
+	ret = wait_phy_ready(priv);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+/*
+ * Program the hardware MAC address from dev->dev_addr.
+ */
+static int enc28j60_set_hw_macaddr(struct net_device *ndev)
+{
+	int ret;
+	struct enc28j60_net *priv = netdev_priv(ndev);
+
+	mutex_lock(&priv->lock);
+	if (!priv->hw_enable) {
+		if (netif_msg_drv(priv)) {
+			DECLARE_MAC_BUF(mac);
+			printk(KERN_INFO DRV_NAME
+				": %s: Setting MAC address to %s\n",
+				ndev->name, print_mac(mac, ndev->dev_addr));
+		}
+		/* NOTE: MAC address in ENC28J60 is byte-backward */
+		nolock_regb_write(priv, MAADR5, ndev->dev_addr[0]);
+		nolock_regb_write(priv, MAADR4, ndev->dev_addr[1]);
+		nolock_regb_write(priv, MAADR3, ndev->dev_addr[2]);
+		nolock_regb_write(priv, MAADR2, ndev->dev_addr[3]);
+		nolock_regb_write(priv, MAADR1, ndev->dev_addr[4]);
+		nolock_regb_write(priv, MAADR0, ndev->dev_addr[5]);
+		ret = 0;
+	} else {
+		if (netif_msg_drv(priv))
+			printk(KERN_DEBUG DRV_NAME
+				": %s() Hardware must be disabled to set "
+				"Mac address\n", __FUNCTION__);
+		ret = -EBUSY;
+	}
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+/*
+ * 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;
+
+	if (netif_running(dev))
+		return -EBUSY;
+	if (!is_valid_ether_addr(address->sa_data))
+		return -EADDRNOTAVAIL;
+
+	memcpy(dev->dev_addr, address->sa_data, dev->addr_len);
+	return enc28j60_set_hw_macaddr(dev);
+}
+
+/*
+ * Debug routine to dump useful register contents
+ */
+static void enc28j60_dump_regs(struct enc28j60_net *priv, const char *msg)
+{
+	mutex_lock(&priv->lock);
+	printk(KERN_DEBUG DRV_NAME " %s\n"
+		"HwRevID: 0x%02x\n"
+		"Cntrl: ECON1 ECON2 ESTAT  EIR  EIE\n"
+		"       0x%02x  0x%02x  0x%02x  0x%02x  0x%02x\n"
+		"MAC  : MACON1 MACON3 MACON4\n"
+		"       0x%02x   0x%02x   0x%02x\n"
+		"Rx   : ERXST  ERXND  ERXWRPT ERXRDPT ERXFCON EPKTCNT MAMXFL\n"
+		"       0x%04x 0x%04x 0x%04x  0x%04x  "
+		"0x%02x    0x%02x    0x%04x\n"
+		"Tx   : ETXST  ETXND  MACLCON1 MACLCON2 MAPHSUP\n"
+		"       0x%04x 0x%04x 0x%02x     0x%02x     0x%02x\n",
+		msg, nolock_regb_read(priv, EREVID),
+		nolock_regb_read(priv, ECON1), nolock_regb_read(priv, ECON2),
+		nolock_regb_read(priv, ESTAT), nolock_regb_read(priv, EIR),
+		nolock_regb_read(priv, EIE), nolock_regb_read(priv, MACON1),
+		nolock_regb_read(priv, MACON3), nolock_regb_read(priv, MACON4),
+		nolock_regw_read(priv, ERXSTL), nolock_regw_read(priv, ERXNDL),
+		nolock_regw_read(priv, ERXWRPTL),
+		nolock_regw_read(priv, ERXRDPTL),
+		nolock_regb_read(priv, ERXFCON),
+		nolock_regb_read(priv, EPKTCNT),
+		nolock_regw_read(priv, MAMXFLL), nolock_regw_read(priv, ETXSTL),
+		nolock_regw_read(priv, ETXNDL),
+		nolock_regb_read(priv, MACLCON1),
+		nolock_regb_read(priv, MACLCON2),
+		nolock_regb_read(priv, MAPHSUP));
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * ERXRDPT need to be set always at odd addresses, refer to errata datasheet
+ */
+static u16 erxrdpt_workaround(u16 next_packet_ptr, u16 start, u16 end)
+{
+	u16 erxrdpt;
+
+	if ((next_packet_ptr - 1 < start) || (next_packet_ptr - 1 > end))
+		erxrdpt = end;
+	else
+		erxrdpt = next_packet_ptr - 1;
+
+	return erxrdpt;
+}
+
+static void nolock_rxfifo_init(struct enc28j60_net *priv, u16 start, u16 end)
+{
+	u16 erxrdpt;
+
+	if (start > 0x1FFF || end > 0x1FFF || start > end) {
+		if (netif_msg_drv(priv))
+			printk(KERN_ERR DRV_NAME ": %s(%d, %d) RXFIFO "
+				"bad parameters!\n", __FUNCTION__, start, end);
+		return;
+	}
+	/* set receive buffer start + end */
+	priv->next_pk_ptr = start;
+	nolock_regw_write(priv, ERXSTL, start);
+	erxrdpt = erxrdpt_workaround(priv->next_pk_ptr, start, end);
+	nolock_regw_write(priv, ERXRDPTL, erxrdpt);
+	nolock_regw_write(priv, ERXNDL, end);
+}
+
+static void nolock_txfifo_init(struct enc28j60_net *priv, u16 start, u16 end)
+{
+	if (start > 0x1FFF || end > 0x1FFF || start > end) {
+		if (netif_msg_drv(priv))
+			printk(KERN_ERR DRV_NAME ": %s(%d, %d) TXFIFO "
+				"bad parameters!\n", __FUNCTION__, start, end);
+		return;
+	}
+	/* set transmit buffer start + end */
+	nolock_regw_write(priv, ETXSTL, start);
+	nolock_regw_write(priv, ETXNDL, end);
+}
+
+static int enc28j60_hw_init(struct enc28j60_net *priv)
+{
+	u8 reg;
+
+	if (netif_msg_drv(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() - %s\n", __FUNCTION__,
+			priv->full_duplex ? "FullDuplex" : "HalfDuplex");
+
+	mutex_lock(&priv->lock);
+	/* first reset the chip */
+	enc28j60_soft_reset(priv);
+	/* Clear ECON1 */
+	spi_write_op(priv, ENC28J60_WRITE_CTRL_REG, ECON1, 0x00);
+	priv->bank = 0;
+	priv->hw_enable = false;
+	priv->tx_retry_count = 0;
+	priv->max_pk_counter = 0;
+	priv->rxfilter = RXFILTER_NORMAL;
+	/* enable address auto increment */
+	nolock_regb_write(priv, ECON2, ECON2_AUTOINC);
+
+	nolock_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT);
+	nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
+	mutex_unlock(&priv->lock);
+
+	/*
+	 * Check the RevID.
+	 * If it's 0x00 or 0xFF probably the enc28j60 is not mounted or
+	 * damaged
+	 */
+	reg = locked_regb_read(priv, EREVID);
+	if (netif_msg_drv(priv))
+		printk(KERN_INFO DRV_NAME ": chip RevID: 0x%02x\n", reg);
+	if (reg == 0x00 || reg == 0xff) {
+		if (netif_msg_drv(priv))
+			printk(KERN_DEBUG DRV_NAME ": %s() Invalid RevId %d\n",
+				__FUNCTION__, reg);
+		return 0;
+	}
+
+	/* default filter mode: (unicast OR broadcast) AND crc valid */
+	locked_regb_write(priv, ERXFCON,
+			    ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN);
+
+	/* enable MAC receive */
+	locked_regb_write(priv, MACON1,
+			    MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS);
+	/* enable automatic padding and CRC operations */
+	if (priv->full_duplex) {
+		locked_regb_write(priv, MACON3,
+				    MACON3_PADCFG0 | MACON3_TXCRCEN |
+				    MACON3_FRMLNEN | MACON3_FULDPX);
+		/* set inter-frame gap (non-back-to-back) */
+		locked_regb_write(priv, MAIPGL, 0x12);
+		/* set inter-frame gap (back-to-back) */
+		locked_regb_write(priv, MABBIPG, 0x15);
+	} else {
+		locked_regb_write(priv, MACON3,
+				    MACON3_PADCFG0 | MACON3_TXCRCEN |
+				    MACON3_FRMLNEN);
+		locked_regb_write(priv, MACON4, 1 << 6);	/* DEFER bit */
+		/* set inter-frame gap (non-back-to-back) */
+		locked_regw_write(priv, MAIPGL, 0x0C12);
+		/* set inter-frame gap (back-to-back) */
+		locked_regb_write(priv, MABBIPG, 0x12);
+	}
+	/*
+	 * MACLCON1 (default)
+	 * MACLCON2 (default)
+	 * Set the maximum packet size which the controller will accept
+	 */
+	locked_regw_write(priv, MAMXFLL, MAX_FRAMELEN);
+
+	/* Configure LEDs */
+	if (!enc28j60_phy_write(priv, PHLCON, ENC28J60_LAMPS_MODE))
+		return 0;
+
+	if (priv->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;
+	}
+	if (netif_msg_hw(priv))
+		enc28j60_dump_regs(priv, "Hw initialized.");
+
+	return 1;
+}
+
+static void enc28j60_hw_enable(struct enc28j60_net *priv)
+{
+	/* enable interrutps */
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() enabling interrupts.\n",
+			__FUNCTION__);
+
+	enc28j60_phy_write(priv, PHIE, PHIE_PGEIE | PHIE_PLNKIE);
+
+	mutex_lock(&priv->lock);
+	nolock_reg_bfclr(priv, EIR, EIR_DMAIF | EIR_LINKIF |
+			 EIR_TXIF | EIR_TXERIF | EIR_RXERIF | EIR_PKTIF);
+	nolock_regb_write(priv, EIE, EIE_INTIE | EIE_PKTIE | EIE_LINKIE |
+			  EIE_TXIE | EIE_TXERIE | EIE_RXERIE);
+
+	/* enable receive logic */
+	nolock_reg_bfset(priv, ECON1, ECON1_RXEN);
+	priv->hw_enable = true;
+	mutex_unlock(&priv->lock);
+}
+
+static void enc28j60_hw_disable(struct enc28j60_net *priv)
+{
+	mutex_lock(&priv->lock);
+	/* disable interrutps and packet reception */
+	nolock_regb_write(priv, EIE, 0x00);
+	nolock_reg_bfclr(priv, ECON1, ECON1_RXEN);
+	priv->hw_enable = false;
+	mutex_unlock(&priv->lock);
+}
+
+static int
+enc28j60_setlink(struct net_device *ndev, u8 autoneg, u16 speed, u8 duplex)
+{
+	struct enc28j60_net *priv = netdev_priv(ndev);
+	int ret = 0;
+
+	if (!priv->hw_enable) {
+		if (autoneg == AUTONEG_DISABLE && speed == SPEED_10) {
+			priv->full_duplex = (duplex == DUPLEX_FULL);
+			if (!enc28j60_hw_init(priv)) {
+				if (netif_msg_drv(priv))
+					dev_err(&ndev->dev,
+						"hw_reset() failed\n");
+				ret = -EINVAL;
+			}
+		} else {
+			if (netif_msg_link(priv))
+				dev_warn(&ndev->dev,
+					"unsupported link setting\n");
+			ret = -EOPNOTSUPP;
+		}
+	} else {
+		if (netif_msg_link(priv))
+			dev_warn(&ndev->dev, "Warning: hw must be disabled "
+				"to set link mode\n");
+		ret = -EBUSY;
+	}
+	return ret;
+}
+
+/*
+ * Read the Transmit Status Vector
+ */
+static void enc28j60_read_tsv(struct enc28j60_net *priv, u8 tsv[TSV_SIZE])
+{
+	int endptr;
+
+	endptr = locked_regw_read(priv, ETXNDL);
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME ": 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 *priv, const char *msg,
+				u8 tsv[TSV_SIZE])
+{
+	u16 tmp1, tmp2;
+
+	printk(KERN_DEBUG DRV_NAME ": %s - TSV:\n", msg);
+	tmp1 = tsv[1];
+	tmp1 <<= 8;
+	tmp1 |= tsv[0];
+
+	tmp2 = tsv[5];
+	tmp2 <<= 8;
+	tmp2 |= tsv[4];
+
+	printk(KERN_DEBUG DRV_NAME ": ByteCount: %d, CollisionCount: %d,"
+		" TotByteOnWire: %d\n", tmp1, tsv[2] & 0x0f, tmp2);
+	printk(KERN_DEBUG DRV_NAME ": 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));
+	printk(KERN_DEBUG DRV_NAME ": 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));
+	printk(KERN_DEBUG DRV_NAME ": 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));
+	printk(KERN_DEBUG DRV_NAME ": 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));
+}
+
+/*
+ * Receive Status vector
+ */
+static void enc28j60_dump_rsv(struct enc28j60_net *priv, const char *msg,
+			      u16 pk_ptr, int len, u16 sts)
+{
+	printk(KERN_DEBUG DRV_NAME ": %s - NextPk: 0x%04x - RSV:\n",
+		msg, pk_ptr);
+	printk(KERN_DEBUG DRV_NAME ": ByteCount: %d, DribbleNibble: %d\n", len,
+		 RSV_GETBIT(sts, RSV_DRIBBLENIBBLE));
+	printk(KERN_DEBUG DRV_NAME ": 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));
+	printk(KERN_DEBUG DRV_NAME ": 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));
+	printk(KERN_DEBUG DRV_NAME ": 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));
+}
+
+static void dump_packet(const char *msg, int len, const char *data)
+{
+	printk(KERN_DEBUG DRV_NAME ": %s - packet len:%d\n", msg, len);
+	print_hex_dump(KERN_DEBUG, "pk data: ", DUMP_PREFIX_OFFSET, 16, 1,
+			data, len, true);
+}
+
+/*
+ * Hardware receive function.
+ * Read the buffer memory, update the FIFO pointer to free the buffer,
+ * check the status vector and decrement the packet counter.
+ */
+static void enc28j60_hw_rx(struct net_device *ndev)
+{
+	struct enc28j60_net *priv = netdev_priv(ndev);
+	struct sk_buff *skb = NULL;
+	u16 erxrdpt, next_packet, rxstat;
+	u8 rsv[RSV_SIZE];
+	int len;
+
+	if (netif_msg_rx_status(priv))
+		printk(KERN_DEBUG DRV_NAME ": RX pk_addr:0x%04x\n",
+			priv->next_pk_ptr);
+
+	if (unlikely(priv->next_pk_ptr > RXEND_INIT)) {
+		if (netif_msg_rx_err(priv))
+			dev_err(&ndev->dev,
+				"%s() Invalid packet address!! 0x%04x\n",
+				__FUNCTION__, priv->next_pk_ptr);
+		/* packet address corrupted: reset RX logic */
+		mutex_lock(&priv->lock);
+		nolock_reg_bfclr(priv, ECON1, ECON1_RXEN);
+		nolock_reg_bfset(priv, ECON1, ECON1_RXRST);
+		nolock_reg_bfclr(priv, ECON1, ECON1_RXRST);
+		nolock_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT);
+		nolock_reg_bfclr(priv, EIR, EIR_RXERIF);
+		nolock_reg_bfset(priv, ECON1, ECON1_RXEN);
+		mutex_unlock(&priv->lock);
+		ndev->stats.rx_errors++;
+		return;
+	}
+	/* Read next packet pointer and rx status vector */
+	enc28j60_mem_read(priv, priv->next_pk_ptr, sizeof(rsv), rsv);
+
+	next_packet = rsv[1];
+	next_packet <<= 8;
+	next_packet |= rsv[0];
+
+	len = rsv[3];
+	len <<= 8;
+	len |= rsv[2];
+
+	rxstat = rsv[5];
+	rxstat <<= 8;
+	rxstat |= rsv[4];
+
+	if (netif_msg_rx_status(priv))
+		enc28j60_dump_rsv(priv, __FUNCTION__, next_packet, len, rxstat);
+
+	if (!RSV_GETBIT(rxstat, RSV_RXOK)) {
+		if (netif_msg_rx_err(priv))
+			dev_err(&ndev->dev, "Rx Error (%04x)\n", rxstat);
+		ndev->stats.rx_errors++;
+		if (RSV_GETBIT(rxstat, RSV_CRCERROR))
+			ndev->stats.rx_crc_errors++;
+		if (RSV_GETBIT(rxstat, RSV_LENCHECKERR))
+			ndev->stats.rx_frame_errors++;
+	} else {
+		skb = dev_alloc_skb(len);
+		if (!skb) {
+			if (netif_msg_rx_err(priv))
+				dev_err(&ndev->dev,
+					"out of memory for Rx'd frame\n");
+			ndev->stats.rx_dropped++;
+		} else {
+			skb->dev = ndev;
+			/* copy the packet from the receive buffer */
+			enc28j60_mem_read(priv, priv->next_pk_ptr + sizeof(rsv),
+					len, skb_put(skb, len));
+			if (netif_msg_pktdata(priv))
+				dump_packet(__FUNCTION__, skb->len, skb->data);
+			skb->protocol = eth_type_trans(skb, ndev);
+			/* update statistics */
+			ndev->stats.rx_packets++;
+			ndev->stats.rx_bytes += len;
+			ndev->last_rx = jiffies;
+			netif_rx(skb);
+		}
+	}
+	/*
+	 * Move the RX read pointer to the start of the next
+	 * received packet.
+	 * This frees the memory we just read out
+	 */
+	erxrdpt = erxrdpt_workaround(next_packet, RXSTART_INIT, RXEND_INIT);
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() ERXRDPT:0x%04x\n",
+			__FUNCTION__, erxrdpt);
+
+	mutex_lock(&priv->lock);
+	nolock_regw_write(priv, ERXRDPTL, erxrdpt);
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	if (netif_msg_drv(priv)) {
+		u16 reg;
+		reg = nolock_regw_read(priv, ERXRDPTL);
+		if (reg != erxrdpt)
+			printk(KERN_DEBUG DRV_NAME ": %s() ERXRDPT verify "
+				"error (0x%04x - 0x%04x)\n", __FUNCTION__,
+				reg, erxrdpt);
+	}
+#endif
+	priv->next_pk_ptr = next_packet;
+	/* we are done with this packet, decrement the packet counter */
+	nolock_reg_bfset(priv, ECON2, ECON2_PKTDEC);
+	mutex_unlock(&priv->lock);
+}
+
+/*
+ * Calculate free space in RxFIFO
+ */
+static int enc28j60_get_free_rxfifo(struct enc28j60_net *priv)
+{
+	int epkcnt, erxst, erxnd, erxwr, erxrd;
+	int free_space;
+
+	mutex_lock(&priv->lock);
+	epkcnt = nolock_regb_read(priv, EPKTCNT);
+	if (epkcnt >= 255)
+		free_space = -1;
+	else {
+		erxst = nolock_regw_read(priv, ERXSTL);
+		erxnd = nolock_regw_read(priv, ERXNDL);
+		erxwr = nolock_regw_read(priv, ERXWRPTL);
+		erxrd = nolock_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;
+	}
+	mutex_unlock(&priv->lock);
+	if (netif_msg_rx_status(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() free_space = %d\n",
+			__FUNCTION__, free_space);
+	return free_space;
+}
+
+/*
+ * Access the PHY to determine link status
+ */
+static void enc28j60_check_link_status(struct net_device *ndev)
+{
+	struct enc28j60_net *priv = netdev_priv(ndev);
+	u16 reg;
+	int duplex;
+
+	reg = enc28j60_phy_read(priv, PHSTAT2);
+	if (netif_msg_hw(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() PHSTAT1: %04x, "
+			"PHSTAT2: %04x\n", __FUNCTION__,
+			enc28j60_phy_read(priv, PHSTAT1), reg);
+	duplex = reg & PHSTAT2_DPXSTAT;
+
+	if (reg & PHSTAT2_LSTAT) {
+		netif_carrier_on(ndev);
+		if (netif_msg_ifup(priv))
+			dev_info(&ndev->dev, "link up - %s\n",
+				duplex ? "Full duplex" : "Half duplex");
+	} else {
+		if (netif_msg_ifdown(priv))
+			dev_info(&ndev->dev, "link down\n");
+		netif_carrier_off(ndev);
+	}
+}
+
+static void enc28j60_tx_clear(struct net_device *ndev, bool err)
+{
+	struct enc28j60_net *priv = netdev_priv(ndev);
+
+	if (err)
+		ndev->stats.tx_errors++;
+	else
+		ndev->stats.tx_packets++;
+
+	if (priv->tx_skb) {
+		if (!err)
+			ndev->stats.tx_bytes += priv->tx_skb->len;
+		dev_kfree_skb(priv->tx_skb);
+		priv->tx_skb = NULL;
+	}
+	locked_reg_bfclr(priv, ECON1, ECON1_TXRTS);
+	netif_wake_queue(ndev);
+}
+
+/*
+ * RX handler
+ * ignore PKTIF because is unreliable! (look at the errata datasheet)
+ * check EPKTCNT is the suggested workaround.
+ * We don't need to clear interrupt flag, automatically done when
+ * enc28j60_hw_rx() decrements the packet counter.
+ * Returns how many packet processed.
+ */
+static int enc28j60_rx_interrupt(struct net_device *ndev)
+{
+	struct enc28j60_net *priv = netdev_priv(ndev);
+	int pk_counter, ret;
+
+	pk_counter = locked_regb_read(priv, EPKTCNT);
+	if (pk_counter && netif_msg_intr(priv))
+		printk(KERN_DEBUG DRV_NAME ": intRX, pk_cnt: %d\n", pk_counter);
+	if (pk_counter > priv->max_pk_counter) {
+		/* update statistics */
+		priv->max_pk_counter = pk_counter;
+		if (netif_msg_rx_status(priv) && priv->max_pk_counter > 1)
+			printk(KERN_DEBUG DRV_NAME ": RX max_pk_cnt: %d\n",
+				priv->max_pk_counter);
+	}
+	ret = pk_counter;
+	while (pk_counter-- > 0)
+		enc28j60_hw_rx(ndev);
+
+	return ret;
+}
+
+static void enc28j60_irq_work_handler(struct work_struct *work)
+{
+	struct enc28j60_net *priv =
+		container_of(work, struct enc28j60_net, irq_work);
+	struct net_device *ndev = priv->netdev;
+	int intflags, loop;
+
+	if (netif_msg_intr(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__);
+	/* disable further interrupts */
+	locked_reg_bfclr(priv, EIE, EIE_INTIE);
+
+	do {
+		loop = 0;
+		intflags = locked_regb_read(priv, EIR);
+		/* DMA interrupt handler (not currently used) */
+		if ((intflags & EIR_DMAIF) != 0) {
+			loop++;
+			if (netif_msg_intr(priv))
+				printk(KERN_DEBUG DRV_NAME
+					": intDMA(%d)\n", loop);
+			locked_reg_bfclr(priv, EIR, EIR_DMAIF);
+		}
+		/* LINK changed handler */
+		if ((intflags & EIR_LINKIF) != 0) {
+			loop++;
+			if (netif_msg_intr(priv))
+				printk(KERN_DEBUG DRV_NAME
+					": intLINK(%d)\n", loop);
+			enc28j60_check_link_status(ndev);
+			/* read PHIR to clear the flag */
+			enc28j60_phy_read(priv, PHIR);
+		}
+		/* TX complete handler */
+		if ((intflags & EIR_TXIF) != 0) {
+			bool err = false;
+			loop++;
+			if (netif_msg_intr(priv))
+				printk(KERN_DEBUG DRV_NAME
+					": intTX(%d)\n", loop);
+			priv->tx_retry_count = 0;
+			if (locked_regb_read(priv, ESTAT) & ESTAT_TXABRT) {
+				if (netif_msg_tx_err(priv))
+					dev_err(&ndev->dev,
+						"Tx Error (aborted)\n");
+				err = true;
+			}
+			if (netif_msg_tx_done(priv)) {
+				u8 tsv[TSV_SIZE];
+				enc28j60_read_tsv(priv, tsv);
+				enc28j60_dump_tsv(priv, "Tx Done", tsv);
+			}
+			enc28j60_tx_clear(ndev, err);
+			locked_reg_bfclr(priv, EIR, EIR_TXIF);
+		}
+		/* TX Error handler */
+		if ((intflags & EIR_TXERIF) != 0) {
+			u8 tsv[TSV_SIZE];
+
+			loop++;
+			if (netif_msg_intr(priv))
+				printk(KERN_DEBUG DRV_NAME
+					": intTXErr(%d)\n", loop);
+			locked_reg_bfclr(priv, ECON1, ECON1_TXRTS);
+			enc28j60_read_tsv(priv, tsv);
+			if (netif_msg_tx_err(priv))
+				enc28j60_dump_tsv(priv, "Tx Error", tsv);
+			/* Reset TX logic */
+			mutex_lock(&priv->lock);
+			nolock_reg_bfset(priv, ECON1, ECON1_TXRST);
+			nolock_reg_bfclr(priv, ECON1, ECON1_TXRST);
+			nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
+			mutex_unlock(&priv->lock);
+			/* Transmit Late collision check for retransmit */
+			if (TSV_GETBIT(tsv, TSV_TXLATECOLLISION)) {
+				if (netif_msg_tx_err(priv))
+					printk(KERN_DEBUG DRV_NAME
+						": LateCollision TXErr (%d)\n",
+						priv->tx_retry_count);
+				if (priv->tx_retry_count++ < MAX_TX_RETRYCOUNT)
+					locked_reg_bfset(priv, ECON1,
+							   ECON1_TXRTS);
+				else
+					enc28j60_tx_clear(ndev, true);
+			} else
+				enc28j60_tx_clear(ndev, true);
+			locked_reg_bfclr(priv, EIR, EIR_TXERIF);
+		}
+		/* RX Error handler */
+		if ((intflags & EIR_RXERIF) != 0) {
+			loop++;
+			if (netif_msg_intr(priv))
+				printk(KERN_DEBUG DRV_NAME
+					": intRXErr(%d)\n", loop);
+			/* Check free FIFO space to flag RX overrun */
+			if (enc28j60_get_free_rxfifo(priv) <= 0) {
+				if (netif_msg_rx_err(priv))
+					printk(KERN_DEBUG DRV_NAME
+						": RX Overrun\n");
+				ndev->stats.rx_dropped++;
+			}
+			locked_reg_bfclr(priv, EIR, EIR_RXERIF);
+		}
+		/* RX handler */
+		if (enc28j60_rx_interrupt(ndev))
+			loop++;
+	} while (loop);
+
+	/* re-enable interrupts */
+	locked_reg_bfset(priv, EIE, EIE_INTIE);
+	if (netif_msg_intr(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() exit\n", __FUNCTION__);
+}
+
+/*
+ * 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 *priv)
+{
+	if (netif_msg_tx_queued(priv))
+		printk(KERN_DEBUG DRV_NAME
+			": Tx Packet Len:%d\n", priv->tx_skb->len);
+
+	if (netif_msg_pktdata(priv))
+		dump_packet(__FUNCTION__,
+			    priv->tx_skb->len, priv->tx_skb->data);
+	enc28j60_packet_write(priv, priv->tx_skb->len, priv->tx_skb->data);
+
+#ifdef CONFIG_ENC28J60_WRITEVERIFY
+	/* readback and verify written data */
+	if (netif_msg_drv(priv)) {
+		int test_len, k;
+		u8 test_buf[64]; /* limit the test to the first 64 bytes */
+		int okflag;
+
+		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);
+		okflag = 1;
+		for (k = 0; k < test_len; k++) {
+			if (priv->tx_skb->data[k] != test_buf[k]) {
+				printk(KERN_DEBUG DRV_NAME
+					 ": Error, %d location differ: "
+					 "0x%02x-0x%02x\n", k,
+					 priv->tx_skb->data[k], test_buf[k]);
+				okflag = 0;
+			}
+		}
+		if (!okflag)
+			printk(KERN_DEBUG DRV_NAME ": Tx write buffer, "
+				"verify ERROR!\n");
+	}
+#endif
+	/* set TX request flag */
+	locked_reg_bfset(priv, ECON1, ECON1_TXRTS);
+}
+
+static int enc28j60_send_packet(struct sk_buff *skb, struct net_device *dev)
+{
+	struct enc28j60_net *priv = netdev_priv(dev);
+
+	if (netif_msg_tx_queued(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() enter\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 *priv =
+		container_of(work, struct enc28j60_net, tx_work);
+
+	/* actual delivery of data */
+	enc28j60_hw_tx(priv);
+}
+
+static irqreturn_t enc28j60_irq(int irq, void *dev_id)
+{
+	struct enc28j60_net *priv = dev_id;
+
+	/*
+	 * Can't do anything in interrupt context because we need to
+	 * block (spi_sync() is blocking) so fire of the interrupt
+	 * handling workqueue.
+	 * Remember that we access enc28j60 registers through SPI bus
+	 * via spi_sync() call.
+	 */
+	schedule_work(&priv->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+static void enc28j60_tx_timeout(struct net_device *ndev)
+{
+	struct enc28j60_net *priv = netdev_priv(ndev);
+
+	if (netif_msg_timer(priv))
+		dev_err(&ndev->dev, DRV_NAME " tx timeout\n");
+
+	ndev->stats.tx_errors++;
+	/* can't restart safely under softirq */
+	schedule_work(&priv->restart_work);
+}
+
+/*
+ * 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 *priv = netdev_priv(dev);
+
+	if (netif_msg_drv(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__);
+
+	if (!is_valid_ether_addr(dev->dev_addr)) {
+		if (netif_msg_ifup(priv)) {
+			DECLARE_MAC_BUF(mac);
+			dev_err(&dev->dev, "invalid MAC address %s\n",
+				print_mac(mac, dev->dev_addr));
+		}
+		return -EADDRNOTAVAIL;
+	}
+	/* Reset the hardware here */
+	enc28j60_hw_disable(priv);
+	if (!enc28j60_hw_init(priv)) {
+		if (netif_msg_ifup(priv))
+			dev_err(&dev->dev, "hw_reset() failed\n");
+		return -EINVAL;
+	}
+	/* Update the MAC address (in case user has changed it) */
+	enc28j60_set_hw_macaddr(dev);
+	/* Enable interrupts */
+	enc28j60_hw_enable(priv);
+	/* check link status */
+	enc28j60_check_link_status(dev);
+	/* 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 *priv = netdev_priv(dev);
+
+	if (netif_msg_drv(priv))
+		printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__);
+
+	enc28j60_hw_disable(priv);
+	netif_stop_queue(dev);
+
+	return 0;
+}
+
+/*
+ * Set or clear the multicast filter for this adapter
+ * num_addrs == -1	Promiscuous mode, receive all packets
+ * num_addrs == 0	Normal mode, filter out multicast packets
+ * num_addrs > 0	Multicast mode, receive normal and MC packets
+ */
+static void enc28j60_set_multicast_list(struct net_device *dev)
+{
+	struct enc28j60_net *priv = netdev_priv(dev);
+	int oldfilter = priv->rxfilter;
+
+	if (dev->flags & IFF_PROMISC) {
+		if (netif_msg_link(priv))
+			dev_info(&dev->dev, "promiscuous mode\n");
+		priv->rxfilter = RXFILTER_PROMISC;
+	} else if ((dev->flags & IFF_ALLMULTI) || dev->mc_count) {
+		if (netif_msg_link(priv))
+			dev_info(&dev->dev, "%smulticast mode\n",
+				(dev->flags & IFF_ALLMULTI) ? "all-" : "");
+		priv->rxfilter = RXFILTER_MULTI;
+	} else {
+		if (netif_msg_link(priv))
+			dev_info(&dev->dev, "normal mode\n");
+		priv->rxfilter = RXFILTER_NORMAL;
+	}
+
+	if (oldfilter != priv->rxfilter)
+		schedule_work(&priv->setrx_work);
+}
+
+static void enc28j60_setrx_work_handler(struct work_struct *work)
+{
+	struct enc28j60_net *priv =
+		container_of(work, struct enc28j60_net, setrx_work);
+
+	if (priv->rxfilter == RXFILTER_PROMISC) {
+		if (netif_msg_drv(priv))
+			printk(KERN_DEBUG DRV_NAME ": promiscuous mode\n");
+		locked_regb_write(priv, ERXFCON, 0x00);
+	} else if (priv->rxfilter == RXFILTER_MULTI) {
+		if (netif_msg_drv(priv))
+			printk(KERN_DEBUG DRV_NAME ": multicast mode\n");
+		locked_regb_write(priv, ERXFCON,
+					ERXFCON_UCEN | ERXFCON_CRCEN |
+					ERXFCON_BCEN | ERXFCON_MCEN);
+	} else {
+		if (netif_msg_drv(priv))
+			printk(KERN_DEBUG DRV_NAME ": normal mode\n");
+		locked_regb_write(priv, ERXFCON,
+					ERXFCON_UCEN | ERXFCON_CRCEN |
+					ERXFCON_BCEN);
+	}
+}
+
+static void enc28j60_restart_work_handler(struct work_struct *work)
+{
+	struct enc28j60_net *priv =
+			container_of(work, struct enc28j60_net, restart_work);
+	struct net_device *ndev = priv->netdev;
+	int ret;
+
+	rtnl_lock();
+	if (netif_running(ndev)) {
+		enc28j60_net_close(ndev);
+		ret = enc28j60_net_open(ndev);
+		if (unlikely(ret)) {
+			dev_info(&ndev->dev, " could not restart %d\n", ret);
+			dev_close(ndev);
+		}
+	}
+	rtnl_unlock();
+}
+
+/* ......................... ETHTOOL SUPPORT ........................... */
+
+static void
+enc28j60_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
+{
+	strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
+	strlcpy(info->version, DRV_VERSION, sizeof(info->version));
+	strlcpy(info->bus_info,
+		dev->dev.parent->bus_id, sizeof(info->bus_info));
+}
+
+static int
+enc28j60_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct enc28j60_net *priv = netdev_priv(dev);
+
+	cmd->transceiver = XCVR_INTERNAL;
+	cmd->supported	= SUPPORTED_10baseT_Half
+			| SUPPORTED_10baseT_Full
+			| SUPPORTED_TP;
+	cmd->speed	= SPEED_10;
+	cmd->duplex	= priv->full_duplex ? DUPLEX_FULL : DUPLEX_HALF;
+	cmd->port	= PORT_TP;
+	cmd->autoneg	= AUTONEG_DISABLE;
+
+	return 0;
+}
+
+static int
+enc28j60_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	return enc28j60_setlink(dev, cmd->autoneg, cmd->speed, cmd->duplex);
+}
+
+static u32 enc28j60_get_msglevel(struct net_device *dev)
+{
+	struct enc28j60_net *priv = netdev_priv(dev);
+	return priv->msg_enable;
+}
+
+static void enc28j60_set_msglevel(struct net_device *dev, u32 val)
+{
+	struct enc28j60_net *priv = netdev_priv(dev);
+	priv->msg_enable = val;
+}
+
+static const struct ethtool_ops enc28j60_ethtool_ops = {
+	.get_settings	= enc28j60_get_settings,
+	.set_settings	= enc28j60_set_settings,
+	.get_drvinfo	= enc28j60_get_drvinfo,
+	.get_msglevel	= enc28j60_get_msglevel,
+	.set_msglevel	= enc28j60_set_msglevel,
+};
+
+static int enc28j60_chipset_init(struct net_device *dev)
+{
+	struct enc28j60_net *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 *priv;
+	int ret = 0;
+
+	if (netif_msg_drv(&debug))
+		dev_info(&spi->dev, DRV_NAME " Ethernet driver %s loaded\n",
+			DRV_VERSION);
+
+	dev = alloc_etherdev(sizeof(struct enc28j60_net));
+	if (!dev) {
+		if (netif_msg_drv(&debug))
+			dev_err(&spi->dev, DRV_NAME
+				": unable to alloc new ethernet\n");
+		ret = -ENOMEM;
+		goto error_alloc;
+	}
+	priv = netdev_priv(dev);
+
+	priv->netdev = dev;	/* priv to netdev reference */
+	priv->spi = spi;	/* priv to spi reference */
+	priv->msg_enable = netif_msg_init(debug.msg_enable,
+						ENC28J60_MSG_DEFAULT);
+	mutex_init(&priv->lock);
+	INIT_WORK(&priv->tx_work, enc28j60_tx_work_handler);
+	INIT_WORK(&priv->setrx_work, enc28j60_setrx_work_handler);
+	INIT_WORK(&priv->irq_work, enc28j60_irq_work_handler);
+	INIT_WORK(&priv->restart_work, enc28j60_restart_work_handler);
+	dev_set_drvdata(&spi->dev, priv);	/* spi to priv reference */
+	SET_NETDEV_DEV(dev, &spi->dev);
+
+	if (!enc28j60_chipset_init(dev)) {
+		if (netif_msg_probe(priv))
+			dev_info(&spi->dev, DRV_NAME " chip not found\n");
+		ret = -EIO;
+		goto error_irq;
+	}
+	random_ether_addr(dev->dev_addr);
+	enc28j60_set_hw_macaddr(dev);
+
+	ret = request_irq(spi->irq, enc28j60_irq, IRQF_TRIGGER_FALLING,
+			  DRV_NAME, priv);
+	if (ret < 0) {
+		if (netif_msg_probe(priv))
+			dev_err(&spi->dev, DRV_NAME ": request irq %d failed "
+				"(ret = %d)\n", spi->irq, ret);
+		goto error_irq;
+	}
+
+	dev->if_port = IF_PORT_10BASET;
+	dev->irq = spi->irq;
+	dev->open = enc28j60_net_open;
+	dev->stop = enc28j60_net_close;
+	dev->hard_start_xmit = enc28j60_send_packet;
+	dev->set_multicast_list = &enc28j60_set_multicast_list;
+	dev->set_mac_address = enc28j60_set_mac_address;
+	dev->tx_timeout = &enc28j60_tx_timeout;
+	dev->watchdog_timeo = TX_TIMEOUT;
+	SET_ETHTOOL_OPS(dev, &enc28j60_ethtool_ops);
+
+	ret = register_netdev(dev);
+	if (ret) {
+		if (netif_msg_probe(priv))
+			dev_err(&spi->dev, "register netdev " DRV_NAME
+				" failed (ret = %d)\n", ret);
+		goto error_register;
+	}
+	dev_info(&dev->dev, DRV_NAME " driver registered\n");
+
+	return 0;
+
+error_register:
+	free_irq(spi->irq, priv);
+error_irq:
+	free_netdev(dev);
+error_alloc:
+	return ret;
+}
+
+static int enc28j60_remove(struct spi_device *spi)
+{
+	struct enc28j60_net *priv = dev_get_drvdata(&spi->dev);
+
+	if (netif_msg_drv(priv))
+		printk(KERN_DEBUG DRV_NAME ": remove\n");
+
+	unregister_netdev(priv->netdev);
+	free_irq(spi->irq, priv);
+	free_netdev(priv->netdev);
+
+	return 0;
+}
+
+static struct spi_driver enc28j60_driver = {
+	.driver = {
+		   .name = DRV_NAME,
+		   .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(DRV_NAME " ethernet driver");
+MODULE_AUTHOR("Claudio Lanconelli <lanconelli.claudio@eptar.com>");
+MODULE_LICENSE("GPL");
+module_param_named(debug, debug.msg_enable, int, 0);
+MODULE_PARM_DESC(debug, "Debug verbosity level (0=none, ..., ffff=all)");
diff --git a/drivers/net/enc28j60_hw.h b/drivers/net/enc28j60_hw.h
new file mode 100644
index 0000000..1a0b209
--- /dev/null
+++ b/drivers/net/enc28j60_hw.h
@@ -0,0 +1,309 @@
+/*
+ * enc28j60_hw.h: EDTP FrameThrower style enc28j60 registers
+ *
+ * $Id: enc28j60_hw.h,v 1.9 2007/12/14 11:59:16 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 (reserved) */
+#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 (reserved) */
+#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 PHSTAT2 Register Bit Definitions */
+#define PHSTAT2_TXSTAT	(1 << 13)
+#define PHSTAT2_RXSTAT	(1 << 12)
+#define PHSTAT2_COLSTAT	(1 << 11)
+#define PHSTAT2_LSTAT	(1 << 10)
+#define PHSTAT2_DPXSTAT	(1 << 9)
+#define PHSTAT2_PLRITY	(1 << 5)
+/* ENC28J60 PHY PHCON2 Register Bit Definitions */
+#define PHCON2_FRCLINK	0x4000
+#define PHCON2_TXDIS	0x2000
+#define PHCON2_JABBER	0x0400
+#define PHCON2_HDLDIS	0x0100
+/* ENC28J60 PHY PHIE Register Bit Definitions */
+#define PHIE_PLNKIE	(1 << 4)
+#define PHIE_PGEIE	(1 << 1)
+/* ENC28J60 PHY PHIR Register Bit Definitions */
+#define PHIR_PLNKIF	(1 << 4)
+#define PHIR_PGEIF	(1 << 1)
+
+/* 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_SIZE		6
+#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
+
+#endif
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index d9107e5..3e196cf 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -922,6 +922,24 @@ 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
+	---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_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

* Re: [PATCH 4/4] [UDP]: memory accounting in IPv4
From: David Miller @ 2007-12-20 11:44 UTC (permalink / raw)
  To: haoki
  Cc: herbert, netdev, tyasui, mhiramat, satoshi.oshima.fk, billfink,
	andi, johnpol, shemminger, yoshfuji, yumiko.sugita.yf
In-Reply-To: <476732B7.9040502@redhat.com>

From: Hideo AOKI <haoki@redhat.com>
Date: Mon, 17 Dec 2007 21:38:47 -0500

> This patch adds UDP memory usage accounting in IPv4.
> 
> Send buffer accounting is performed by IP layer, because skbuff is
> allocated in the layer.
> 
> Receive buffer is charged, when the buffer successfully received.
> Destructor of the buffer does uncharging and reclaiming, when the
> buffer is freed. To set destructor at proper place, we use
> __udp_queue_rcv_skb() instead of sock_queue_rcv_skb(). To maintain
> consistency of memory accounting, socket lock is used to free receive
> buffer in udp_recvmsg().
> 
> New packet will be add to backlog when the socket is used by user.
> 
> Cc: Satoshi Oshima <satoshi.oshima.fk@hitachi.com>
> signed-off-by: Takahiro Yasui <tyasui@redhat.com>
> signed-off-by: Masami Hiramatsu <mhiramat@redhat.com>
> signed-off-by: Hideo Aoki <haoki@redhat.com>

We can't accept these changes, even once the other issues
are fixed, until IPV6 is supported as well.

It's pointless to support proper UDP memory accounting only
in IPV4 and not in IPV6 as well.

^ permalink raw reply

* Re: dn_neigh_table vs pneigh_lookup/pneigh_delete
From: Steven Whitehouse @ 2007-12-20 11:24 UTC (permalink / raw)
  To: Pavel Emelyanov; +Cc: Linux Netdev List, Patrick Caulfield, linux-decnet-user
In-Reply-To: <47692696.1080905@openvz.org>

Hi,

On Wed, Dec 19, 2007 at 05:11:34PM +0300, Pavel Emelyanov wrote:
> Hi
> 
> The pneigh_lookup/delete silently concerns, that the 
> key_len of the table is more that 4 bytes. Look:
> 
>          u32 hash_val = *(u32 *)(pkey + key_len - 4);
> 
> The hash_val for the proxy neighbor entry is four last bytes
> from the pkey.
> 
> But the dn_neigh_tables' key_len is sizeof(__le16), that is 2,
> so setting (via netlink) the proxy neighbor entry for decnet 
> will cause this entry to reside in arbitrary hash chain.
> 
> Is this too bad for decnet?
> 
> Thanks,
> Pavel
The pneigh code is never used in DECnet, we only use the "normal"
part of the neigh code where the hash function was changed so that
it can be defined for each protocol (and thus doesn't suffer from
this problem)

Steve.

^ permalink raw reply

* Re: [PATCH 2/4] [CORE]: datagram: basic memory accounting functions
From: David Miller @ 2007-12-20 11:43 UTC (permalink / raw)
  To: haoki
  Cc: herbert, netdev, tyasui, mhiramat, satoshi.oshima.fk, billfink,
	andi, johnpol, shemminger, yoshfuji, yumiko.sugita.yf
In-Reply-To: <47673299.3010301@redhat.com>

From: Hideo AOKI <haoki@redhat.com>
Date: Mon, 17 Dec 2007 21:38:17 -0500

Why do we need seperate stream and datagram accounting functions?

Is it just to facilitate things like the following test?

> +static inline int sk_wmem_schedule(struct sock *sk, int size)
> +{
> +	if (sk->sk_type == SOCK_DGRAM)
> +		return sk_datagram_wmem_schedule(sk, size);
> +	else
> +		return 1;
> +}

If so, this can be greatly improved.

All of these other functions are identical copies of the stream
counterparts, they should all be consolidated.

I still see a lot of special casing, instead of large pieces of common
code.

There should be one core set of functions that handle the memory
accounting, regardless of socket type.  Maybe there is one spot where
something like sk->prot->doing_memory_accounting is tested, but that's
it.

I am still very dissatisfied with these changes.  They are
full of special cases, because they mix generic facilities
(the socket memory accounting) with an unrelated issue
(we only support memory accounting for datagram sockets which
are actually UDP).

Also, the memory accounting is done at different parts in
the socket code paths for stream vs. datagram.  This is why
everything is inconsistent, and, a mess.

What's funny is that I absolutely do not care if these changes are
perfect and pass every possible regression test.  Rather, I'm more
concerned that this thing is designed correctly and will allow us to
have one core set of memory accounting functions regardless of socket
type.  As it is coded now, we have two sets of code paths to
fix, two ways of doing the socket accounting, and therefore twice
as much code to maintain and debug.

The whole thing needs to be consistent and without special cases.

The "protocol supports memory accounting" test can be performed, as
you did in this patch, by simply checking if
sk->sk_prot->memory_allocated is non-NULL.

^ permalink raw reply

* Re: TSO trimming question
From: Ilpo Järvinen @ 2007-12-20 11:40 UTC (permalink / raw)
  To: David Miller; +Cc: Netdev, Herbert Xu
In-Reply-To: <20071219.235450.170670042.davem@davemloft.net>

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

On Wed, 19 Dec 2007, David Miller wrote:

> From: "Ilpo_Järvinen" <ilpo.jarvinen@helsinki.fi>
> Date: Wed, 19 Dec 2007 23:46:33 +0200 (EET)
> 
> > I'm not fully sure what's purpose of this code in tcp_write_xmit:
> > 
> >                        if (skb->len < limit) {
> >                                unsigned int trim = skb->len % mss_now;
> > 
> >                                if (trim)
> >                                        limit = skb->len - trim;
> > 			}
> > 
> > Is it used to make sure we send only multiples of mss_now here and leave 
> > the left-over into another skb?

Yeah, I now understand that this part is correct. I somehow got such 
impression while trying to figure this out that it ends up being dead code 
but that wasn't correct thought from my side. However, it caught my 
attention and after some thinking I'd say there's more to handle here 
(covered by the second question).

Also note that patch I sent earlier is not right either but needs some 
refining to do the right thing.

> > Or does it try to make sure that
> > tso_fragment result honors multiple of mss_now boundaries when snd_wnd
> > is the limitting factor? For latter IMHO this would be necessary:
> > 
> > 			if (skb->len > limit)
> > 				limit -= limit % mss_now;
> 
> The purpose of the test is to make sure we process tail sub-mss chunks
> correctly wrt. Nagle, which most closely matches the first purpose
> you've listed.
> 
> So I think the calculation really does belong where it is.
>
> Because of the way that the sendmsg() super-skb formation logic
> works, we always will tack on more data and grow the tail
> SKB before creating a new one.  So any sub-mss chunk at the
> end of a TSO frame really is at the end of the write queue
> and really should get nagle processing.

Yes, I now agree this is fully correct for this task.

> Actually, there is an exception, which is when we run out of
> skb_frag_list slots.  In that case we'll potentially have breaks at
> odd boundaries in the middle of the queue.  But this can only happen
> in exceptional cases (user does tons of 1-byte sendfile()'s over
> random non-consequetive locations of a file) or outright bugs
> (MAX_SKB_FRAGS is defined incorrectly, for example) and thus this
> situation is not worth coding for.

That's not the only case, IMHO if there's odd boundary due to 
snd_una+snd_wnd - skb->seq limit (done in tcp_window_allows()), we don't 
consider it as odd but break the skb at arbitary point resulting
two small segments to the network, and what's worse, when the later skb 
resulting from the first split is matching skb->len < limit check as well 
causing an unnecessary small skb to be created for nagle purpose too, 
solving it fully requires some thought in case the mss_now != mss_cache 
even if non-odd boundaries are honored in the middle of skb.

Though whether we get there is depending on what tcp_tso_should_defer() 
decided. Hmm, there seems to be an unrelated bug in it as well :-/. A 
patch below. Please consider the fact that enabling TSO deferring may 
have some unpleasant effect to TCP dynamics, considering that I don't find 
stable mandatory for this to avoid breaking, besides things have been 
working quite well without it too... Only compile tested.

-- 
 i.


--
[PATCH] [TCP]: Fix TSO deferring

I'd say that most of what tcp_tso_should_defer had in between
there was dead code because of this.

Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@helsinki.fi>
---
 net/ipv4/tcp_output.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 8dafda9..693b9f6 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -1217,7 +1217,8 @@ static int tcp_tso_should_defer(struct sock *sk, struct sk_buff *skb)
 		goto send_now;
 
 	/* Defer for less than two clock ticks. */
-	if (!tp->tso_deferred && ((jiffies<<1)>>1) - (tp->tso_deferred>>1) > 1)
+	if (tp->tso_deferred &&
+	    ((jiffies << 1) >> 1) - (tp->tso_deferred >> 1) > 1)
 		goto send_now;
 
 	in_flight = tcp_packets_in_flight(tp);
-- 
1.5.0.6

^ permalink raw reply related

* Re: [PATCH 1/4] [UDP]: fix send buffer check
From: David Miller @ 2007-12-20 11:31 UTC (permalink / raw)
  To: haoki
  Cc: herbert, netdev, tyasui, mhiramat, satoshi.oshima.fk, billfink,
	andi, johnpol, shemminger, yoshfuji, yumiko.sugita.yf
In-Reply-To: <4767328B.70601@redhat.com>

From: Hideo AOKI <haoki@redhat.com>
Date: Mon, 17 Dec 2007 21:38:03 -0500

> This patch introduces sndbuf size check before memory allocation for
> send buffer.
> 
> signed-off-by: Satoshi Oshima <satoshi.oshima.fk@hitachi.com>
> signed-off-by: Hideo Aoki <haoki@redhat.com>
 ...
> diff -pruN net-2.6/net/ipv4/ip_output.c net-2.6-udp-take11a1-p1/net/ipv4/ip_output.c
> --- net-2.6/net/ipv4/ip_output.c	2007-12-11 10:54:55.000000000 -0500
> +++ net-2.6-udp-take11a1-p1/net/ipv4/ip_output.c	2007-12-17 14:42:31.000000000 -0500
> @@ -1004,6 +1004,11 @@ alloc_new_skb:
>  					frag = &skb_shinfo(skb)->frags[i];
>  				}
>  			} else if (i < MAX_SKB_FRAGS) {
> +				if (atomic_read(&sk->sk_wmem_alloc) + PAGE_SIZE
> +				    > 2 * sk->sk_sndbuf) {
> +					err = -ENOBUFS;
> +					goto error;
> +				}
>  				if (copy > PAGE_SIZE)
>  					copy = PAGE_SIZE;
>  				page = alloc_pages(sk->sk_allocation, 0);

If we are going to do this, we need to add the same check to
skb_append_datato_frags() which is invoked via ip_ufo_append_data().

We also have to be very careful in this area.  One problem we had a
long time ago was that we would socket account when fragmenting an
outgoing frame.  This was bogus because even if the socket had enough
space for one full sized frame, the packet send would fail because it
could not fit the space for both the original frame and the
fragmented copy of it.

This situation was cured by simply not enforcing accounting for the
fragmented copy.  It is valid because after we fragment, we keep
the fragmented copy but free the original.

This doesn't apply directly to this specific patch, but it is
something to keep in mind when doing these changes.

^ permalink raw reply

* Re: A short question about net git tree and patches
From: David Miller @ 2007-12-20 11:22 UTC (permalink / raw)
  To: sam; +Cc: dshwatrz, netdev
In-Reply-To: <20071220095510.GA2081@uranus.ravnborg.org>

From: Sam Ravnborg <sam@ravnborg.org>
Date: Thu, 20 Dec 2007 10:55:10 +0100

> net-2.6.25.git   <= patches for current kernel release (only fixes)
> net-2.6.git      <= patches for next kernel relase and planned to be
>                     applied in next merge window
> 
> So net-2.6.git is the correct choice for bleeding edge.

You reversed them, net-2.6.25.git is for bleeding edge
stuff, net-2.6.git is for bug fixes only.

^ permalink raw reply

* Re: [RFC] net: napi fix
From: David Miller @ 2007-12-20 11:22 UTC (permalink / raw)
  To: Robert.Olsson
  Cc: gallatin, joonwpark81, netdev, linux-kernel, jgarzik, shemminger
In-Reply-To: <18282.15185.592112.550961@robur.slu.se>

From: Robert Olsson <Robert.Olsson@data.slu.se>
Date: Thu, 20 Dec 2007 10:52:17 +0100

>  Yes but the reason was not to wait for all pending polls to
>  complete so a server/router could be rebooted even under high-
>  load and DOS. We've experienced some nasty problems with this.

I know, see the rest of the thread where I agree that
we need to deal with this somehow.

The device is marked down first, and somehow we need to
tip off of that to break out of the NAPI loop.  This
"how" is what hasn't been resolved yet.

^ permalink raw reply

* Re: Badness at net/core/dev.c:2199
From: Meelis Roos @ 2007-12-20 10:57 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: Evgeniy Polyakov, netdev
In-Reply-To: <20071216103340.07479359@deepthought>

> I already sendout a correct patch last week. It should pre-increment.

Any hope getting it upstream?

-- 
Meelis Roos (mroos@ut.ee)      http://www.cs.ut.ee/~mroos/

^ permalink raw reply

* Re: A short question about net git tree and patches
From: Ilpo Järvinen @ 2007-12-20 10:49 UTC (permalink / raw)
  To: Sam Ravnborg, David Shwatrz; +Cc: Netdev
In-Reply-To: <20071220095510.GA2081@uranus.ravnborg.org>

On Thu, 20 Dec 2007, Sam Ravnborg wrote:

> On Thu, Dec 20, 2007 at 11:20:26AM +0200, David Shwatrz wrote:
> > Hello,
> > I have a short question regarding the net git tree and patches:
> > I want to write and send patches against the most recent and
> > bleeding edge kernel networking code.
> > I see in:
> > http://kernel.org/pub/scm/linux/kernel/git/davem/?C=M;O=A
> > that there are 3 git trees which can be candidates for git-clone and
> > making patches against;
> > these are:
> > netdev-2.6.git, net-2.6.25.git and net-2.6.git.
> > 
> > It seems to me that net-2.6.git is the most suitable one to work against;
> > am I right ?
> > what is the difference, in short, between the three repositories?
> 
> IIRC the usage is:
> netdev-2.6.git   <= old stuff, 4 weeks since last update. Not in use
> net-2.6.25.git   <= patches for current kernel release (only fixes)

Nope, we don't even have 2.6.24 yet. :-)

> net-2.6.git      <= patches for next kernel relase and planned to be
>                     applied in next merge window
>
> So net-2.6.git is the correct choice for bleeding edge.

net-2.6 is for fixes only, net-2.6.25 will become net-2.6 once 2.6.24
gets released, eventually net-2.6.26 gets opened (not necessarily at the 
same time as the merge window is closed but a bit later) and the cycle 
repeats with similar transitions when 2.6.25 gets released.

The netdev trees are for network drivers and are usually managed by
Jeff Garzik but there were recently some arrangement between Dave and 
Jeff due to vacations so that also netdev was managed by Dave.

-- 
 i.

^ permalink raw reply

* Re: DM9000_IRQ_FLAGS
From: Remy Bohmer @ 2007-12-20  9:54 UTC (permalink / raw)
  To: Ben Dooks; +Cc: Daniel Mack, netdev
In-Reply-To: <20071220094626.GB9216@fluff.org.uk>

Hello Ben,

> >
> > Actually, the best way to go is to let the platform resources flags
> > decide about that with something like
> >
> >       resource->flags = IORESOURCE_IRQ | IRQT_RISING;
> >
> > but the dm9000 does not care about them at all. Changing that would also
> > imply modifications to all board support code.
>
> I did have a go at trying to get people to pass the information this
> way, but it seem to be ignored last time I sent it. I can dig out the
> code that converts resource->flags to IRQT_ flags.

I thought this issue was already solved by using set_irq_type() in the
BSP, just like all the other boards do...

Kind Regards,

Remy

^ permalink raw reply

* Re: A short question about net git tree and patches
From: Sam Ravnborg @ 2007-12-20  9:55 UTC (permalink / raw)
  To: David Shwatrz; +Cc: netdev
In-Reply-To: <31436f4a0712200120o4e6de008jf999ca1024b47f9d@mail.gmail.com>

On Thu, Dec 20, 2007 at 11:20:26AM +0200, David Shwatrz wrote:
> Hello,
> I have a short question regarding the net git tree and patches:
> I want to write and send patches against the most recent and
> bleeding edge kernel networking code.
> I see in:
> http://kernel.org/pub/scm/linux/kernel/git/davem/?C=M;O=A
> that there are 3 git trees which can be candidates for git-clone and
> making patches against;
> these are:
> netdev-2.6.git, net-2.6.25.git and net-2.6.git.
> 
> It seems to me that net-2.6.git is the most suitable one to work against;
> am I right ?
> what is the difference, in short, between the three repositories?

IIRC the usage is:
netdev-2.6.git   <= old stuff, 4 weeks since last update. Not in use
net-2.6.25.git   <= patches for current kernel release (only fixes)
net-2.6.git      <= patches for next kernel relase and planned to be
                    applied in next merge window

So net-2.6.git is the correct choice for bleeding edge.

	Sam

^ permalink raw reply

* Re: [RFC] net: napi fix
From: Robert Olsson @ 2007-12-20  9:52 UTC (permalink / raw)
  To: David Miller
  Cc: gallatin, joonwpark81, netdev, linux-kernel, jgarzik, shemminger
In-Reply-To: <20071212.093819.205755031.davem@davemloft.net>


David Miller writes:

 > > Is the netif_running() check even required?
 > 
 > No, it is not.
 > 
 > When a device is brought down, one of the first things
 > that happens is that we wait for all pending NAPI polls
 > to complete, then block any new polls from starting.

 Hello!

 Yes but the reason was not to wait for all pending polls to
 complete so a server/router could be rebooted even under high-
 load and DOS. We've experienced some nasty problems with this.

 Cheers.
					--ro

^ permalink raw reply

* [PATCH net-2.6.25][NEIGH] Make neigh_add_timer symmetrical to neigh_del_timer
From: Pavel Emelyanov @ 2007-12-20  9:50 UTC (permalink / raw)
  To: David Miller; +Cc: Linux Netdev List, devel

The neigh_del_timer() looks sane - it removes the timer and
(conditionally) puts the neighbor. I expected, that the
neigh_add_timer() is symmetrical to the del one - i.e. it
holds the neighbor and arms the timer - but it turned out
that it was not so.

I think, that making them look symmetrical makes the code 
more readable.

Signed-off-by: Pavel Emelyanov <xemul@openvz.org>

---


diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 4b6dd1e..9a283fc 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -165,6 +165,16 @@ static int neigh_forced_gc(struct neigh_table *tbl)
 	return shrunk;
 }
 
+static void neigh_add_timer(struct neighbour *n, unsigned long when)
+{
+	neigh_hold(n);
+	if (unlikely(mod_timer(&n->timer, when))) {
+		printk("NEIGH: BUG, double timer add, state is %x\n",
+		       n->nud_state);
+		dump_stack();
+	}
+}
+
 static int neigh_del_timer(struct neighbour *n)
 {
 	if ((n->nud_state & NUD_IN_TIMER) &&
@@ -716,15 +726,6 @@ static __inline__ int neigh_max_probes(struct neighbour *n)
 		p->ucast_probes + p->app_probes + p->mcast_probes);
 }
 
-static inline void neigh_add_timer(struct neighbour *n, unsigned long when)
-{
-	if (unlikely(mod_timer(&n->timer, when))) {
-		printk("NEIGH: BUG, double timer add, state is %x\n",
-		       n->nud_state);
-		dump_stack();
-	}
-}
-
 /* Called when a timer expires for a neighbour entry. */
 
 static void neigh_timer_handler(unsigned long arg)
@@ -856,7 +857,6 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
 			atomic_set(&neigh->probes, neigh->parms->ucast_probes);
 			neigh->nud_state     = NUD_INCOMPLETE;
 			neigh->updated = jiffies;
-			neigh_hold(neigh);
 			neigh_add_timer(neigh, now + 1);
 		} else {
 			neigh->nud_state = NUD_FAILED;
@@ -869,7 +869,6 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
 		}
 	} else if (neigh->nud_state & NUD_STALE) {
 		NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
-		neigh_hold(neigh);
 		neigh->nud_state = NUD_DELAY;
 		neigh->updated = jiffies;
 		neigh_add_timer(neigh,
@@ -1013,13 +1012,11 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
 
 	if (new != old) {
 		neigh_del_timer(neigh);
-		if (new & NUD_IN_TIMER) {
-			neigh_hold(neigh);
+		if (new & NUD_IN_TIMER)
 			neigh_add_timer(neigh, (jiffies +
 						((new & NUD_REACHABLE) ?
 						 neigh->parms->reachable_time :
 						 0)));
-		}
 		neigh->nud_state = new;
 	}
 

^ permalink raw reply related

* [PATCH net-2.6.25 (resend) 1/3] Uninline the __inet_hash function
From: Pavel Emelyanov @ 2007-12-20  9:46 UTC (permalink / raw)
  To: David Miller; +Cc: dada1, netdev, devel
In-Reply-To: <20071220.003027.20529103.davem@davemloft.net>

This one is used in quite many places in the networking code and
seems to big to be inline.

After the patch net/ipv4/build-in.o loses ~650 bytes:
add/remove: 2/0 grow/shrink: 0/5 up/down: 461/-1114 (-653)
function                                     old     new   delta
__inet_hash_nolisten                           -     282    +282
__inet_hash                                    -     179    +179
tcp_sacktag_write_queue                     2255    2254      -1
__inet_lookup_listener                       284     274     -10
tcp_v4_syn_recv_sock                         755     493    -262
tcp_v4_hash                                  389      35    -354
inet_hash_connect                           1086     599    -487

This version addresses the issue pointed by Eric, that
while being inline this function was optimized by gcc
in respect to the 'listen_possible' argument.

(Patches 2 and 3 in this series are still applied after this)

Signed-off-by: Pavel Emelyanov <xemul@openvz.org>

---

diff --git a/include/net/inet_hashtables.h b/include/net/inet_hashtables.h
index fef4442..65ddb25 100644
--- a/include/net/inet_hashtables.h
+++ b/include/net/inet_hashtables.h
@@ -264,37 +264,14 @@ static inline void inet_listen_unlock(struct inet_hashinfo *hashinfo)
 		wake_up(&hashinfo->lhash_wait);
 }
 
-static inline void __inet_hash(struct inet_hashinfo *hashinfo,
-			       struct sock *sk, const int listen_possible)
-{
-	struct hlist_head *list;
-	rwlock_t *lock;
-
-	BUG_TRAP(sk_unhashed(sk));
-	if (listen_possible && sk->sk_state == TCP_LISTEN) {
-		list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
-		lock = &hashinfo->lhash_lock;
-		inet_listen_wlock(hashinfo);
-	} else {
-		struct inet_ehash_bucket *head;
-		sk->sk_hash = inet_sk_ehashfn(sk);
-		head = inet_ehash_bucket(hashinfo, sk->sk_hash);
-		list = &head->chain;
-		lock = inet_ehash_lockp(hashinfo, sk->sk_hash);
-		write_lock(lock);
-	}
-	__sk_add_node(sk, list);
-	sock_prot_inc_use(sk->sk_prot);
-	write_unlock(lock);
-	if (listen_possible && sk->sk_state == TCP_LISTEN)
-		wake_up(&hashinfo->lhash_wait);
-}
+extern void __inet_hash(struct inet_hashinfo *hashinfo, struct sock *sk);
+extern void __inet_hash_nolisten(struct inet_hashinfo *hinfo, struct sock *sk);
 
 static inline void inet_hash(struct inet_hashinfo *hashinfo, struct sock *sk)
 {
 	if (sk->sk_state != TCP_CLOSE) {
 		local_bh_disable();
-		__inet_hash(hashinfo, sk, 1);
+		__inet_hash(hashinfo, sk);
 		local_bh_enable();
 	}
 }
diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c
index 02fc91c..f450df2 100644
--- a/net/dccp/ipv4.c
+++ b/net/dccp/ipv4.c
@@ -408,7 +408,7 @@ struct sock *dccp_v4_request_recv_sock(struct sock *sk, struct sk_buff *skb,
 
 	dccp_sync_mss(newsk, dst_mtu(dst));
 
-	__inet_hash(&dccp_hashinfo, newsk, 0);
+	__inet_hash_nolisten(&dccp_hashinfo, newsk);
 	__inet_inherit_port(&dccp_hashinfo, sk, newsk);
 
 	return newsk;
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index b07e2d3..2e5814a 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -305,6 +305,48 @@ static inline u32 inet_sk_port_offset(const struct sock *sk)
 					  inet->dport);
 }
 
+void __inet_hash_nolisten(struct inet_hashinfo *hashinfo, struct sock *sk)
+{
+	struct hlist_head *list;
+	rwlock_t *lock;
+	struct inet_ehash_bucket *head;
+
+	BUG_TRAP(sk_unhashed(sk));
+
+	sk->sk_hash = inet_sk_ehashfn(sk);
+	head = inet_ehash_bucket(hashinfo, sk->sk_hash);
+	list = &head->chain;
+	lock = inet_ehash_lockp(hashinfo, sk->sk_hash);
+
+	write_lock(lock);
+	__sk_add_node(sk, list);
+	sock_prot_inc_use(sk->sk_prot);
+	write_unlock(lock);
+}
+EXPORT_SYMBOL_GPL(__inet_hash_nolisten);
+
+void __inet_hash(struct inet_hashinfo *hashinfo, struct sock *sk)
+{
+	struct hlist_head *list;
+	rwlock_t *lock;
+
+	if (sk->sk_state != TCP_LISTEN) {
+		__inet_hash_nolisten(hashinfo, sk);
+		return;
+	}
+
+	BUG_TRAP(sk_unhashed(sk));
+	list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
+	lock = &hashinfo->lhash_lock;
+
+	inet_listen_wlock(hashinfo);
+	__sk_add_node(sk, list);
+	sock_prot_inc_use(sk->sk_prot);
+	write_unlock(lock);
+	wake_up(&hashinfo->lhash_wait);
+}
+EXPORT_SYMBOL_GPL(__inet_hash);
+
 /*
  * Bind a port for a connect operation and hash it.
  */
@@ -372,7 +414,7 @@ ok:
 		inet_bind_hash(sk, tb, port);
 		if (sk_unhashed(sk)) {
 			inet_sk(sk)->sport = htons(port);
-			__inet_hash(hinfo, sk, 0);
+			__inet_hash_nolisten(hinfo, sk);
 		}
 		spin_unlock(&head->lock);
 
@@ -389,7 +431,7 @@ ok:
 	tb  = inet_csk(sk)->icsk_bind_hash;
 	spin_lock_bh(&head->lock);
 	if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {
-		__inet_hash(hinfo, sk, 0);
+		__inet_hash_nolisten(hinfo, sk);
 		spin_unlock_bh(&head->lock);
 		return 0;
 	} else {
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 652c323..fc9bdd8 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1478,7 +1478,7 @@ struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
 	}
 #endif
 
-	__inet_hash(&tcp_hashinfo, newsk, 0);
+	__inet_hash_nolisten(&tcp_hashinfo, newsk);
 	__inet_inherit_port(&tcp_hashinfo, sk, newsk);
 
 	return newsk;


^ permalink raw reply related

* Re: DM9000_IRQ_FLAGS
From: Ben Dooks @ 2007-12-20  9:46 UTC (permalink / raw)
  To: Daniel Mack; +Cc: Remy Bohmer, netdev
In-Reply-To: <20071212134153.GA21510@buzzloop.caiaq.de>

On Wed, Dec 12, 2007 at 02:41:53PM +0100, Daniel Mack wrote:
> Hi Remy,
> 
> On Tue, Dec 11, 2007 at 09:31:03PM +0100, Remy Bohmer wrote:
> > This controller is also used on many other boards, like the e.g. Atmel
> > AT91sam9261-ek board. On that board on both the rising _and_ falling
> > edge an interrupt is generated.
> 
> However, request_irq() is called with IRQF_SHARED only, so neither
> IRQT_RISING nor IRQT_FALLING is set and the value defaults to
> IRQT_NOEDGE. How can you get IRQs?
> 
> > I can test tomorrow if this patch leaves this board in tact, but
> > should the board-specific code not add this flag if it is required ?
> > By modifying this driver you will interfere the behavior of other
> > boards, and I do not know if there any level triggered types used.
> 
> Actually, the best way to go is to let the platform resources flags 
> decide about that with something like
> 
> 	resource->flags = IORESOURCE_IRQ | IRQT_RISING;
> 
> but the dm9000 does not care about them at all. Changing that would also
> imply modifications to all board support code.

I did have a go at trying to get people to pass the information this
way, but it seem to be ignored last time I sent it. I can dig out the
code that converts resource->flags to IRQT_ flags.

-- 
Ben (ben@fluff.org, http://www.fluff.org/)

  'a smiley only costs 4 bytes'

^ permalink raw reply

* Re: DM9000_IRQ_FLAGS
From: Ben Dooks @ 2007-12-20  9:45 UTC (permalink / raw)
  To: Daniel Mack; +Cc: netdev
In-Reply-To: <20071211191823.GD15295@buzzloop.caiaq.de>

On Tue, Dec 11, 2007 at 08:18:23PM +0100, Daniel Mack wrote:
> Hi,
> 
> on Toradex' Colibri, a PXA270 based board with a DM9000 ethernet
> controller, this driver won't work due to unsuitable DM9000_IRQ_FLAGS.
> If I understood the code behind request_irq() correctly, it's not
> recommended to register an IRQ without any of the IRQT_* flags set.
> 
> Is there any concerns about applying the patch below?

Yes, that will possibly break all systems using level-triggered
interrupts.

Probably the best solution is to pass the data via the platform
information being fed to the device.

-- 
Ben (ben@fluff.org, http://www.fluff.org/)

  'a smiley only costs 4 bytes'

^ permalink raw reply


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