Netdev List
 help / color / mirror / Atom feed
* Re: Deleting child qdisc doesn't reset parent to default qdisc?
From: Jiri Kosina @ 2016-04-14 15:34 UTC (permalink / raw)
  To: Phil Sutter; +Cc: Eric Dumazet, Jamal Hadi Salim, netdev, linux-kernel
In-Reply-To: <20160414151813.GE3715@orbyte.nwl.cc>

On Thu, 14 Apr 2016, Phil Sutter wrote:

> OTOH some qdiscs (CBQ, DRR, DSMARK, HFSC, HTB, QFQ) assign the default
> one upon deletion instead of noop_qdisc, hence I would describe
> the situation using the words 'inconsistent' and 'accident' rather than
> 'expected'. :)

Exactly. I'd again like to stress the fact that this configuration works:

	jikos:~ # tc qdisc show
	qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 

and this (after performing add/delete operation) doesn't:

	jikos:~ # tc qdisc show
	qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 

It's hard to spot a difference (hint: there is none).

Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: Deleting child qdisc doesn't reset parent to default qdisc?
From: Phil Sutter @ 2016-04-14 15:18 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: Jiri Kosina, Jamal Hadi Salim, netdev, linux-kernel
In-Reply-To: <1460646099.10638.44.camel@edumazet-glaptop3.roam.corp.google.com>

On Thu, Apr 14, 2016 at 08:01:39AM -0700, Eric Dumazet wrote:
> On Thu, 2016-04-14 at 16:44 +0200, Jiri Kosina wrote:
> > Hi,
> > 
> > I've came across the behavior where adding a child qdisc and then deleting 
> > it again makes the networking dysfunctional (I guess that's because all of 
> > a sudden there is absolutely no working qdisc on the device, although 
> > there originally was a default one in the parent).
> > 
> > In a nutshell, is this expected behavior or bug?
> 
> This is the expected behavior.

OTOH some qdiscs (CBQ, DRR, DSMARK, HFSC, HTB, QFQ) assign the default
one upon deletion instead of noop_qdisc, hence I would describe
the situation using the words 'inconsistent' and 'accident' rather than
'expected'. :)

Anyhow, the problem with skilled admins is they accept quirks too easily
and just build their scripts around them - the same scripts we have to
keep compatible to then.

Cheers, Phil

^ permalink raw reply

* [RFC] VLAN aux info for AF_PACKET available only with ETH_P_ALL
From: Peter Palúch @ 2016-04-14 15:17 UTC (permalink / raw)
  To: netdev

Greetings,

When using AF_PACKET sockets with PACKET_AUXDATA socket option to access 
the VLAN TCI information of received frames, I have noticed that the 
VLAN information in struct tpacket_auxdata, namely,

- tp_vlan_tci
- tp_vlan_tpid
- TP_STATUS_VLAN_VALID and TP_STATUS_VLAN_TPID_VALID flags

is filled in only when the socket is bound to htons (ETH_P_ALL). If the 
socket is bound to any specific protocol, the VLAN information fields in 
struct tpacket_auxdata are set to 0 even if the datagram of the specific 
protocol was received in an 802.1Q-tagged frame.

As the VLAN tag is being stripped off the frame soon in the receive 
path, using PACKET_AUXDATA is the only way for an application over an 
AF_PACKET socket to know what VLAN did a particular frame arrive in; 
yet, the current behavior forces the application to listen to all 
received traffic to get the actual VLAN info.

Is this behavior intentional, or is this lack of VLAN info a bug? I am 
running vanilla Linux kernel v4.4.6.

Thanks!

Best regards,
Peter

^ permalink raw reply

* [PATCH v3 5/5] net: w5100: support W5200
From: Akinobu Mita @ 2016-04-14 15:11 UTC (permalink / raw)
  To: netdev; +Cc: Akinobu Mita, Mike Sinkovsky, David S. Miller
In-Reply-To: <1460646693-25179-1-git-send-email-akinobu.mita@gmail.com>

This adds support for W5200 chip.

W5100 and W5200 have similar memory map although some of their offsets
are different.  The register access sequences between them are different
but w5100 driver has abstraction layer for difference bus interface
modes so it is easy to add W5200 support to w5100 and w5100-spi drivers.

Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Cc: Mike Sinkovsky <msink@permonline.ru>
Cc: David S. Miller <davem@davemloft.net>
---
* v3
- Allocate w5200 ops specific data structure to put DMA-safe buffer
- Add missing chip_id assignment for w5100_*_ops

 drivers/net/ethernet/wiznet/Kconfig     |   2 +-
 drivers/net/ethernet/wiznet/w5100-spi.c | 174 +++++++++++++++++++++++++++++++-
 drivers/net/ethernet/wiznet/w5100.c     | 155 ++++++++++++++++++++--------
 drivers/net/ethernet/wiznet/w5100.h     |   6 ++
 4 files changed, 289 insertions(+), 48 deletions(-)

diff --git a/drivers/net/ethernet/wiznet/Kconfig b/drivers/net/ethernet/wiznet/Kconfig
index d1ab353..1f15376 100644
--- a/drivers/net/ethernet/wiznet/Kconfig
+++ b/drivers/net/ethernet/wiznet/Kconfig
@@ -70,7 +70,7 @@ config WIZNET_BUS_ANY
 endchoice
 
 config WIZNET_W5100_SPI
-	tristate "WIZnet W5100 Ethernet support for SPI mode"
+	tristate "WIZnet W5100/W5200 Ethernet support for SPI mode"
 	depends on WIZNET_BUS_ANY
 	depends on SPI
 	---help---
diff --git a/drivers/net/ethernet/wiznet/w5100-spi.c b/drivers/net/ethernet/wiznet/w5100-spi.c
index 32f406c..598a7b0 100644
--- a/drivers/net/ethernet/wiznet/w5100-spi.c
+++ b/drivers/net/ethernet/wiznet/w5100-spi.c
@@ -1,9 +1,13 @@
 /*
- * Ethernet driver for the WIZnet W5100 chip.
+ * Ethernet driver for the WIZnet W5100/W5200 chip.
  *
  * Copyright (C) 2016 Akinobu Mita <akinobu.mita@gmail.com>
  *
  * Licensed under the GPL-2 or later.
+ *
+ * Datasheet:
+ * http://www.wiznet.co.kr/wp-content/uploads/wiznethome/Chip/W5100/Document/W5100_Datasheet_v1.2.6.pdf
+ * http://wiznethome.cafe24.com/wp-content/uploads/wiznethome/Chip/W5200/Documents/W5200_DS_V140E.pdf
  */
 
 #include <linux/kernel.h>
@@ -95,6 +99,7 @@ static int w5100_spi_writebulk(struct net_device *ndev, u16 addr, const u8 *buf,
 
 static const struct w5100_ops w5100_spi_ops = {
 	.may_sleep = true,
+	.chip_id = W5100,
 	.read = w5100_spi_read,
 	.write = w5100_spi_write,
 	.read16 = w5100_spi_read16,
@@ -103,10 +108,168 @@ static const struct w5100_ops w5100_spi_ops = {
 	.writebulk = w5100_spi_writebulk,
 };
 
+#define W5200_SPI_WRITE_OPCODE 0x80
+
+struct w5200_spi_priv {
+	/* Serialize access to cmd_buf */
+	struct mutex cmd_lock;
+
+	/* DMA (thus cache coherency maintenance) requires the
+	 * transfer buffers to live in their own cache lines.
+	 */
+	u8 cmd_buf[4] ____cacheline_aligned;
+};
+
+static struct w5200_spi_priv *w5200_spi_priv(struct net_device *ndev)
+{
+	return w5100_ops_priv(ndev);
+}
+
+static int w5200_spi_init(struct net_device *ndev)
+{
+	struct w5200_spi_priv *spi_priv = w5200_spi_priv(ndev);
+
+	mutex_init(&spi_priv->cmd_lock);
+
+	return 0;
+}
+
+static int w5200_spi_read(struct net_device *ndev, u16 addr)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	u8 cmd[4] = { addr >> 8, addr & 0xff, 0, 1 };
+	u8 data;
+	int ret;
+
+	ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1);
+
+	return ret ? ret : data;
+}
+
+static int w5200_spi_write(struct net_device *ndev, u16 addr, u8 data)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	u8 cmd[5] = { addr >> 8, addr & 0xff, W5200_SPI_WRITE_OPCODE, 1, data };
+
+	return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0);
+}
+
+static int w5200_spi_read16(struct net_device *ndev, u16 addr)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	u8 cmd[4] = { addr >> 8, addr & 0xff, 0, 2 };
+	__be16 data;
+	int ret;
+
+	ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, sizeof(data));
+
+	return ret ? ret : be16_to_cpu(data);
+}
+
+static int w5200_spi_write16(struct net_device *ndev, u16 addr, u16 data)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	u8 cmd[6] = {
+		addr >> 8, addr & 0xff,
+		W5200_SPI_WRITE_OPCODE, 2,
+		data >> 8, data & 0xff
+	};
+
+	return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0);
+}
+
+static int w5200_spi_readbulk(struct net_device *ndev, u16 addr, u8 *buf,
+			      int len)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	struct w5200_spi_priv *spi_priv = w5200_spi_priv(ndev);
+	struct spi_transfer xfer[] = {
+		{
+			.tx_buf = spi_priv->cmd_buf,
+			.len = sizeof(spi_priv->cmd_buf),
+		},
+		{
+			.rx_buf = buf,
+			.len = len,
+		},
+	};
+	int ret;
+
+	mutex_lock(&spi_priv->cmd_lock);
+
+	spi_priv->cmd_buf[0] = addr >> 8;
+	spi_priv->cmd_buf[1] = addr;
+	spi_priv->cmd_buf[2] = len >> 8;
+	spi_priv->cmd_buf[3] = len;
+	ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
+
+	mutex_unlock(&spi_priv->cmd_lock);
+
+	return ret;
+}
+
+static int w5200_spi_writebulk(struct net_device *ndev, u16 addr, const u8 *buf,
+			       int len)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	struct w5200_spi_priv *spi_priv = w5200_spi_priv(ndev);
+	struct spi_transfer xfer[] = {
+		{
+			.tx_buf = spi_priv->cmd_buf,
+			.len = sizeof(spi_priv->cmd_buf),
+		},
+		{
+			.tx_buf = buf,
+			.len = len,
+		},
+	};
+	int ret;
+
+	mutex_lock(&spi_priv->cmd_lock);
+
+	spi_priv->cmd_buf[0] = addr >> 8;
+	spi_priv->cmd_buf[1] = addr;
+	spi_priv->cmd_buf[2] = W5200_SPI_WRITE_OPCODE | (len >> 8);
+	spi_priv->cmd_buf[3] = len;
+	ret = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
+
+	mutex_unlock(&spi_priv->cmd_lock);
+
+	return ret;
+}
+
+static const struct w5100_ops w5200_ops = {
+	.may_sleep = true,
+	.chip_id = W5200,
+	.read = w5200_spi_read,
+	.write = w5200_spi_write,
+	.read16 = w5200_spi_read16,
+	.write16 = w5200_spi_write16,
+	.readbulk = w5200_spi_readbulk,
+	.writebulk = w5200_spi_writebulk,
+	.init = w5200_spi_init,
+};
+
 static int w5100_spi_probe(struct spi_device *spi)
 {
-	return w5100_probe(&spi->dev, &w5100_spi_ops, 0, NULL, spi->irq,
-			   -EINVAL);
+	const struct spi_device_id *id = spi_get_device_id(spi);
+	const struct w5100_ops *ops;
+	int priv_size;
+
+	switch (id->driver_data) {
+	case W5100:
+		ops = &w5100_spi_ops;
+		priv_size = 0;
+		break;
+	case W5200:
+		ops = &w5200_ops;
+		priv_size = sizeof(struct w5200_spi_priv);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return w5100_probe(&spi->dev, ops, priv_size, NULL, spi->irq, -EINVAL);
 }
 
 static int w5100_spi_remove(struct spi_device *spi)
@@ -115,7 +278,8 @@ static int w5100_spi_remove(struct spi_device *spi)
 }
 
 static const struct spi_device_id w5100_spi_ids[] = {
-	{ "w5100", 0 },
+	{ "w5100", W5100 },
+	{ "w5200", W5200 },
 	{}
 };
 MODULE_DEVICE_TABLE(spi, w5100_spi_ids);
@@ -131,6 +295,6 @@ static struct spi_driver w5100_spi_driver = {
 };
 module_spi_driver(w5100_spi_driver);
 
-MODULE_DESCRIPTION("WIZnet W5100 Ethernet driver for SPI mode");
+MODULE_DESCRIPTION("WIZnet W5100/W5200 Ethernet driver for SPI mode");
 MODULE_AUTHOR("Akinobu Mita <akinobu.mita@gmail.com>");
 MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c
index 42a9de4..09149c9 100644
--- a/drivers/net/ethernet/wiznet/w5100.c
+++ b/drivers/net/ethernet/wiznet/w5100.c
@@ -38,7 +38,7 @@ MODULE_ALIAS("platform:"DRV_NAME);
 MODULE_LICENSE("GPL");
 
 /*
- * Registers
+ * W5100 and W5100 common registers
  */
 #define W5100_COMMON_REGS	0x0000
 #define W5100_MR		0x0000 /* Mode Register */
@@ -52,37 +52,69 @@ MODULE_LICENSE("GPL");
 #define   IR_S0			  0x01 /* S0 interrupt */
 #define W5100_RTR		0x0017 /* Retry Time-value Register */
 #define   RTR_DEFAULT		  2000 /* =0x07d0 (2000) */
-#define W5100_RMSR		0x001a /* Receive Memory Size */
-#define W5100_TMSR		0x001b /* Transmit Memory Size */
 #define W5100_COMMON_REGS_LEN	0x0040
 
-#define W5100_S0_REGS		0x0400
-#define W5100_S0_MR		0x0400 /* S0 Mode Register */
+#define W5100_Sn_MR		0x0000 /* Sn Mode Register */
+#define W5100_Sn_CR		0x0001 /* Sn Command Register */
+#define W5100_Sn_IR		0x0002 /* Sn Interrupt Register */
+#define W5100_Sn_SR		0x0003 /* Sn Status Register */
+#define W5100_Sn_TX_FSR		0x0020 /* Sn Transmit free memory size */
+#define W5100_Sn_TX_RD		0x0022 /* Sn Transmit memory read pointer */
+#define W5100_Sn_TX_WR		0x0024 /* Sn Transmit memory write pointer */
+#define W5100_Sn_RX_RSR		0x0026 /* Sn Receive free memory size */
+#define W5100_Sn_RX_RD		0x0028 /* Sn Receive memory read pointer */
+
+#define S0_REGS(priv)		(is_w5200(priv) ? W5200_S0_REGS : W5100_S0_REGS)
+
+#define W5100_S0_MR(priv)	(S0_REGS(priv) + W5100_Sn_MR)
 #define   S0_MR_MACRAW		  0x04 /* MAC RAW mode (promiscuous) */
 #define   S0_MR_MACRAW_MF	  0x44 /* MAC RAW mode (filtered) */
-#define W5100_S0_CR		0x0401 /* S0 Command Register */
+#define W5100_S0_CR(priv)	(S0_REGS(priv) + W5100_Sn_CR)
 #define   S0_CR_OPEN		  0x01 /* OPEN command */
 #define   S0_CR_CLOSE		  0x10 /* CLOSE command */
 #define   S0_CR_SEND		  0x20 /* SEND command */
 #define   S0_CR_RECV		  0x40 /* RECV command */
-#define W5100_S0_IR		0x0402 /* S0 Interrupt Register */
+#define W5100_S0_IR(priv)	(S0_REGS(priv) + W5100_Sn_IR)
 #define   S0_IR_SENDOK		  0x10 /* complete sending */
 #define   S0_IR_RECV		  0x04 /* receiving data */
-#define W5100_S0_SR		0x0403 /* S0 Status Register */
+#define W5100_S0_SR(priv)	(S0_REGS(priv) + W5100_Sn_SR)
 #define   S0_SR_MACRAW		  0x42 /* mac raw mode */
-#define W5100_S0_TX_FSR		0x0420 /* S0 Transmit free memory size */
-#define W5100_S0_TX_RD		0x0422 /* S0 Transmit memory read pointer */
-#define W5100_S0_TX_WR		0x0424 /* S0 Transmit memory write pointer */
-#define W5100_S0_RX_RSR		0x0426 /* S0 Receive free memory size */
-#define W5100_S0_RX_RD		0x0428 /* S0 Receive memory read pointer */
+#define W5100_S0_TX_FSR(priv)	(S0_REGS(priv) + W5100_Sn_TX_FSR)
+#define W5100_S0_TX_RD(priv)	(S0_REGS(priv) + W5100_Sn_TX_RD)
+#define W5100_S0_TX_WR(priv)	(S0_REGS(priv) + W5100_Sn_TX_WR)
+#define W5100_S0_RX_RSR(priv)	(S0_REGS(priv) + W5100_Sn_RX_RSR)
+#define W5100_S0_RX_RD(priv)	(S0_REGS(priv) + W5100_Sn_RX_RD)
+
 #define W5100_S0_REGS_LEN	0x0040
 
+/*
+ * W5100 specific registers
+ */
+#define W5100_RMSR		0x001a /* Receive Memory Size */
+#define W5100_TMSR		0x001b /* Transmit Memory Size */
+
+#define W5100_S0_REGS		0x0400
+
 #define W5100_TX_MEM_START	0x4000
 #define W5100_TX_MEM_SIZE	0x2000
 #define W5100_RX_MEM_START	0x6000
 #define W5100_RX_MEM_SIZE	0x2000
 
 /*
+ * W5200 specific registers
+ */
+#define W5200_S0_REGS		0x4000
+
+#define W5200_Sn_RXMEM_SIZE(n)	(0x401e + (n) * 0x0100) /* Sn RX Memory Size */
+#define W5200_Sn_TXMEM_SIZE(n)	(0x401f + (n) * 0x0100) /* Sn TX Memory Size */
+#define W5200_S0_IMR		0x402c /* S0 Interrupt Mask Register */
+
+#define W5200_TX_MEM_START	0x8000
+#define W5200_TX_MEM_SIZE	0x4000
+#define W5200_RX_MEM_START	0xc000
+#define W5200_RX_MEM_SIZE	0x4000
+
+/*
  * Device driver private data structure
  */
 
@@ -105,6 +137,11 @@ struct w5100_priv {
 	struct work_struct restart_work;
 };
 
+static inline bool is_w5200(struct w5100_priv *priv)
+{
+	return priv->ops->chip_id == W5200;
+}
+
 /************************************************************************
  *
  *  Lowlevel I/O functions
@@ -217,6 +254,7 @@ static int w5100_mmio_init(struct net_device *ndev)
 }
 
 static const struct w5100_ops w5100_mmio_direct_ops = {
+	.chip_id = W5100,
 	.read = w5100_read_direct,
 	.write = w5100_write_direct,
 	.read16 = w5100_read16_direct,
@@ -341,6 +379,7 @@ static int w5100_reset_indirect(struct net_device *ndev)
 }
 
 static const struct w5100_ops w5100_mmio_indirect_ops = {
+	.chip_id = W5100,
 	.read = w5100_read_indirect,
 	.write = w5100_write_indirect,
 	.read16 = w5100_read16_indirect,
@@ -457,20 +496,24 @@ static int w5100_readbuf(struct w5100_priv *priv, u16 offset, u8 *buf, int len)
 	u16 addr;
 	int remain = 0;
 	int ret;
+	const u16 mem_start =
+		is_w5200(priv) ? W5200_RX_MEM_START : W5100_RX_MEM_START;
+	const u16 mem_size =
+		is_w5200(priv) ? W5200_RX_MEM_SIZE : W5100_RX_MEM_SIZE;
 
-	offset %= W5100_RX_MEM_SIZE;
-	addr = W5100_RX_MEM_START + offset;
+	offset %= mem_size;
+	addr = mem_start + offset;
 
-	if (offset + len > W5100_RX_MEM_SIZE) {
-		remain = (offset + len) % W5100_RX_MEM_SIZE;
-		len = W5100_RX_MEM_SIZE - offset;
+	if (offset + len > mem_size) {
+		remain = (offset + len) % mem_size;
+		len = mem_size - offset;
 	}
 
 	ret = w5100_readbulk(priv, addr, buf, len);
 	if (ret || !remain)
 		return ret;
 
-	return w5100_readbulk(priv, W5100_RX_MEM_START, buf + len, remain);
+	return w5100_readbulk(priv, mem_start, buf + len, remain);
 }
 
 static int w5100_writebuf(struct w5100_priv *priv, u16 offset, const u8 *buf,
@@ -479,20 +522,24 @@ static int w5100_writebuf(struct w5100_priv *priv, u16 offset, const u8 *buf,
 	u16 addr;
 	int ret;
 	int remain = 0;
+	const u16 mem_start =
+		is_w5200(priv) ? W5200_TX_MEM_START : W5100_TX_MEM_START;
+	const u16 mem_size =
+		is_w5200(priv) ? W5200_TX_MEM_SIZE : W5100_TX_MEM_SIZE;
 
-	offset %= W5100_TX_MEM_SIZE;
-	addr = W5100_TX_MEM_START + offset;
+	offset %= mem_size;
+	addr = mem_start + offset;
 
-	if (offset + len > W5100_TX_MEM_SIZE) {
-		remain = (offset + len) % W5100_TX_MEM_SIZE;
-		len = W5100_TX_MEM_SIZE - offset;
+	if (offset + len > mem_size) {
+		remain = (offset + len) % mem_size;
+		len = mem_size - offset;
 	}
 
 	ret = w5100_writebulk(priv, addr, buf, len);
 	if (ret || !remain)
 		return ret;
 
-	return w5100_writebulk(priv, W5100_TX_MEM_START, buf + len, remain);
+	return w5100_writebulk(priv, mem_start, buf + len, remain);
 }
 
 static int w5100_reset(struct w5100_priv *priv)
@@ -511,11 +558,11 @@ static int w5100_command(struct w5100_priv *priv, u16 cmd)
 {
 	unsigned long timeout;
 
-	w5100_write(priv, W5100_S0_CR, cmd);
+	w5100_write(priv, W5100_S0_CR(priv), cmd);
 
 	timeout = jiffies + msecs_to_jiffies(100);
 
-	while (w5100_read(priv, W5100_S0_CR) != 0) {
+	while (w5100_read(priv, W5100_S0_CR(priv)) != 0) {
 		if (time_after(jiffies, timeout))
 			return -EIO;
 		cpu_relax();
@@ -531,6 +578,31 @@ static void w5100_write_macaddr(struct w5100_priv *priv)
 	w5100_writebulk(priv, W5100_SHAR, ndev->dev_addr, ETH_ALEN);
 }
 
+static void w5100_memory_configure(struct w5100_priv *priv)
+{
+	/* Configure 16K of internal memory
+	 * as 8K RX buffer and 8K TX buffer
+	 */
+	w5100_write(priv, W5100_RMSR, 0x03);
+	w5100_write(priv, W5100_TMSR, 0x03);
+}
+
+static void w5200_memory_configure(struct w5100_priv *priv)
+{
+	int i;
+
+	/* Configure internal RX memory as 16K RX buffer and
+	 * internal TX memory as 16K TX buffer
+	 */
+	w5100_write(priv, W5200_Sn_RXMEM_SIZE(0), 0x10);
+	w5100_write(priv, W5200_Sn_TXMEM_SIZE(0), 0x10);
+
+	for (i = 1; i < 8; i++) {
+		w5100_write(priv, W5200_Sn_RXMEM_SIZE(i), 0);
+		w5100_write(priv, W5200_Sn_TXMEM_SIZE(i), 0);
+	}
+}
+
 static void w5100_hw_reset(struct w5100_priv *priv)
 {
 	w5100_reset(priv);
@@ -538,16 +610,15 @@ static void w5100_hw_reset(struct w5100_priv *priv)
 	w5100_write(priv, W5100_IMR, 0);
 	w5100_write_macaddr(priv);
 
-	/* Configure 16K of internal memory
-	 * as 8K RX buffer and 8K TX buffer
-	 */
-	w5100_write(priv, W5100_RMSR, 0x03);
-	w5100_write(priv, W5100_TMSR, 0x03);
+	if (is_w5200(priv))
+		w5200_memory_configure(priv);
+	else
+		w5100_memory_configure(priv);
 }
 
 static void w5100_hw_start(struct w5100_priv *priv)
 {
-	w5100_write(priv, W5100_S0_MR, priv->promisc ?
+	w5100_write(priv, W5100_S0_MR(priv), priv->promisc ?
 			  S0_MR_MACRAW : S0_MR_MACRAW_MF);
 	w5100_command(priv, S0_CR_OPEN);
 	w5100_write(priv, W5100_IMR, IR_S0);
@@ -611,7 +682,7 @@ static void w5100_get_regs(struct net_device *ndev,
 	regs->version = 1;
 	w5100_readbulk(priv, W5100_COMMON_REGS, buf, W5100_COMMON_REGS_LEN);
 	buf += W5100_COMMON_REGS_LEN;
-	w5100_readbulk(priv, W5100_S0_REGS, buf, W5100_S0_REGS_LEN);
+	w5100_readbulk(priv, S0_REGS(priv), buf, W5100_S0_REGS_LEN);
 }
 
 static void w5100_restart(struct net_device *ndev)
@@ -649,9 +720,9 @@ static void w5100_tx_skb(struct net_device *ndev, struct sk_buff *skb)
 	struct w5100_priv *priv = netdev_priv(ndev);
 	u16 offset;
 
-	offset = w5100_read16(priv, W5100_S0_TX_WR);
+	offset = w5100_read16(priv, W5100_S0_TX_WR(priv));
 	w5100_writebuf(priv, offset, skb->data, skb->len);
-	w5100_write16(priv, W5100_S0_TX_WR, offset + skb->len);
+	w5100_write16(priv, W5100_S0_TX_WR(priv), offset + skb->len);
 	ndev->stats.tx_bytes += skb->len;
 	ndev->stats.tx_packets++;
 	dev_kfree_skb(skb);
@@ -696,18 +767,18 @@ static struct sk_buff *w5100_rx_skb(struct net_device *ndev)
 	u16 rx_len;
 	u16 offset;
 	u8 header[2];
-	u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR);
+	u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR(priv));
 
 	if (rx_buf_len == 0)
 		return NULL;
 
-	offset = w5100_read16(priv, W5100_S0_RX_RD);
+	offset = w5100_read16(priv, W5100_S0_RX_RD(priv));
 	w5100_readbuf(priv, offset, header, 2);
 	rx_len = get_unaligned_be16(header) - 2;
 
 	skb = netdev_alloc_skb_ip_align(ndev, rx_len);
 	if (unlikely(!skb)) {
-		w5100_write16(priv, W5100_S0_RX_RD, offset + rx_buf_len);
+		w5100_write16(priv, W5100_S0_RX_RD(priv), offset + rx_buf_len);
 		w5100_command(priv, S0_CR_RECV);
 		ndev->stats.rx_dropped++;
 		return NULL;
@@ -715,7 +786,7 @@ static struct sk_buff *w5100_rx_skb(struct net_device *ndev)
 
 	skb_put(skb, rx_len);
 	w5100_readbuf(priv, offset + 2, skb->data, rx_len);
-	w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len);
+	w5100_write16(priv, W5100_S0_RX_RD(priv), offset + 2 + rx_len);
 	w5100_command(priv, S0_CR_RECV);
 	skb->protocol = eth_type_trans(skb, ndev);
 
@@ -764,10 +835,10 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
 	struct net_device *ndev = ndev_instance;
 	struct w5100_priv *priv = netdev_priv(ndev);
 
-	int ir = w5100_read(priv, W5100_S0_IR);
+	int ir = w5100_read(priv, W5100_S0_IR(priv));
 	if (!ir)
 		return IRQ_NONE;
-	w5100_write(priv, W5100_S0_IR, ir);
+	w5100_write(priv, W5100_S0_IR(priv), ir);
 
 	if (ir & S0_IR_SENDOK) {
 		netif_dbg(priv, tx_done, ndev, "tx done\n");
diff --git a/drivers/net/ethernet/wiznet/w5100.h b/drivers/net/ethernet/wiznet/w5100.h
index 69045f0..9b1fa23 100644
--- a/drivers/net/ethernet/wiznet/w5100.h
+++ b/drivers/net/ethernet/wiznet/w5100.h
@@ -7,8 +7,14 @@
  * Licensed under the GPL-2 or later.
  */
 
+enum {
+	W5100,
+	W5200,
+};
+
 struct w5100_ops {
 	bool may_sleep;
+	int chip_id;
 	int (*read)(struct net_device *ndev, u16 addr);
 	int (*write)(struct net_device *ndev, u16 addr, u8 data);
 	int (*read16)(struct net_device *ndev, u16 addr);
-- 
2.5.0

^ permalink raw reply related

* [PATCH v3 4/5] net: w5100: support SPI interface mode
From: Akinobu Mita @ 2016-04-14 15:11 UTC (permalink / raw)
  To: netdev; +Cc: Akinobu Mita, Mike Sinkovsky, David S. Miller
In-Reply-To: <1460646693-25179-1-git-send-email-akinobu.mita@gmail.com>

This adds new w5100-spi driver which shares the bus interface
independent code with existing w5100 driver.

Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Cc: Mike Sinkovsky <msink@permonline.ru>
Cc: David S. Miller <davem@davemloft.net>
---
* No changes from v2

 drivers/net/ethernet/wiznet/Kconfig     |  14 ++++
 drivers/net/ethernet/wiznet/Makefile    |   1 +
 drivers/net/ethernet/wiznet/w5100-spi.c | 136 ++++++++++++++++++++++++++++++++
 3 files changed, 151 insertions(+)
 create mode 100644 drivers/net/ethernet/wiznet/w5100-spi.c

diff --git a/drivers/net/ethernet/wiznet/Kconfig b/drivers/net/ethernet/wiznet/Kconfig
index f98b91d..d1ab353 100644
--- a/drivers/net/ethernet/wiznet/Kconfig
+++ b/drivers/net/ethernet/wiznet/Kconfig
@@ -69,4 +69,18 @@ config WIZNET_BUS_ANY
 	  Performance may decrease compared to explicitly selected bus mode.
 endchoice
 
+config WIZNET_W5100_SPI
+	tristate "WIZnet W5100 Ethernet support for SPI mode"
+	depends on WIZNET_BUS_ANY
+	depends on SPI
+	---help---
+	  In SPI mode host system accesses registers using SPI protocol
+	  (mode 0) on the SPI bus.
+
+	  Performance decreases compared to other bus interface mode.
+	  In W5100 SPI mode, burst READ/WRITE processing are not provided.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called w5100-spi.
+
 endif # NET_VENDOR_WIZNET
diff --git a/drivers/net/ethernet/wiznet/Makefile b/drivers/net/ethernet/wiznet/Makefile
index c614535..1e05e1a 100644
--- a/drivers/net/ethernet/wiznet/Makefile
+++ b/drivers/net/ethernet/wiznet/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_WIZNET_W5100) += w5100.o
+obj-$(CONFIG_WIZNET_W5100_SPI) += w5100-spi.o
 obj-$(CONFIG_WIZNET_W5300) += w5300.o
diff --git a/drivers/net/ethernet/wiznet/w5100-spi.c b/drivers/net/ethernet/wiznet/w5100-spi.c
new file mode 100644
index 0000000..32f406c
--- /dev/null
+++ b/drivers/net/ethernet/wiznet/w5100-spi.c
@@ -0,0 +1,136 @@
+/*
+ * Ethernet driver for the WIZnet W5100 chip.
+ *
+ * Copyright (C) 2016 Akinobu Mita <akinobu.mita@gmail.com>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/spi/spi.h>
+
+#include "w5100.h"
+
+#define W5100_SPI_WRITE_OPCODE 0xf0
+#define W5100_SPI_READ_OPCODE 0x0f
+
+static int w5100_spi_read(struct net_device *ndev, u16 addr)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	u8 cmd[3] = { W5100_SPI_READ_OPCODE, addr >> 8, addr & 0xff };
+	u8 data;
+	int ret;
+
+	ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1);
+
+	return ret ? ret : data;
+}
+
+static int w5100_spi_write(struct net_device *ndev, u16 addr, u8 data)
+{
+	struct spi_device *spi = to_spi_device(ndev->dev.parent);
+	u8 cmd[4] = { W5100_SPI_WRITE_OPCODE, addr >> 8, addr & 0xff, data};
+
+	return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0);
+}
+
+static int w5100_spi_read16(struct net_device *ndev, u16 addr)
+{
+	u16 data;
+	int ret;
+
+	ret = w5100_spi_read(ndev, addr);
+	if (ret < 0)
+		return ret;
+	data = ret << 8;
+	ret = w5100_spi_read(ndev, addr + 1);
+
+	return ret < 0 ? ret : data | ret;
+}
+
+static int w5100_spi_write16(struct net_device *ndev, u16 addr, u16 data)
+{
+	int ret;
+
+	ret = w5100_spi_write(ndev, addr, data >> 8);
+	if (ret)
+		return ret;
+
+	return w5100_spi_write(ndev, addr + 1, data & 0xff);
+}
+
+static int w5100_spi_readbulk(struct net_device *ndev, u16 addr, u8 *buf,
+			      int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		int ret = w5100_spi_read(ndev, addr + i);
+
+		if (ret < 0)
+			return ret;
+		buf[i] = ret;
+	}
+
+	return 0;
+}
+
+static int w5100_spi_writebulk(struct net_device *ndev, u16 addr, const u8 *buf,
+			       int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		int ret = w5100_spi_write(ndev, addr + i, buf[i]);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct w5100_ops w5100_spi_ops = {
+	.may_sleep = true,
+	.read = w5100_spi_read,
+	.write = w5100_spi_write,
+	.read16 = w5100_spi_read16,
+	.write16 = w5100_spi_write16,
+	.readbulk = w5100_spi_readbulk,
+	.writebulk = w5100_spi_writebulk,
+};
+
+static int w5100_spi_probe(struct spi_device *spi)
+{
+	return w5100_probe(&spi->dev, &w5100_spi_ops, 0, NULL, spi->irq,
+			   -EINVAL);
+}
+
+static int w5100_spi_remove(struct spi_device *spi)
+{
+	return w5100_remove(&spi->dev);
+}
+
+static const struct spi_device_id w5100_spi_ids[] = {
+	{ "w5100", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, w5100_spi_ids);
+
+static struct spi_driver w5100_spi_driver = {
+	.driver		= {
+		.name	= "w5100",
+		.pm	= &w5100_pm_ops,
+	},
+	.probe		= w5100_spi_probe,
+	.remove		= w5100_spi_remove,
+	.id_table	= w5100_spi_ids,
+};
+module_spi_driver(w5100_spi_driver);
+
+MODULE_DESCRIPTION("WIZnet W5100 Ethernet driver for SPI mode");
+MODULE_AUTHOR("Akinobu Mita <akinobu.mita@gmail.com>");
+MODULE_LICENSE("GPL");
-- 
2.5.0

^ permalink raw reply related

* [PATCH v3 3/5] net: w5100: enable to support sleepable register access interface
From: Akinobu Mita @ 2016-04-14 15:11 UTC (permalink / raw)
  To: netdev; +Cc: Akinobu Mita, Mike Sinkovsky, David S. Miller
In-Reply-To: <1460646693-25179-1-git-send-email-akinobu.mita@gmail.com>

SPI transfer routines are callable only from contexts that can sleep.

This adds ability to tell the core driver that the interface mode
cannot access w5100 register on atomic contexts.  In this case,
workqueue and threaded irq are required.

This also corrects timeout period waiting for command register to be
automatically cleared because the latency of the register access with
SPI transfer can be interfered by other contexts.

Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Cc: Mike Sinkovsky <msink@permonline.ru>
Cc: David S. Miller <davem@davemloft.net>
---
* No changes from v2

 drivers/net/ethernet/wiznet/w5100.c | 190 ++++++++++++++++++++++++++++--------
 drivers/net/ethernet/wiznet/w5100.h |   1 +
 2 files changed, 153 insertions(+), 38 deletions(-)

diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c
index 89cba67..42a9de4 100644
--- a/drivers/net/ethernet/wiznet/w5100.c
+++ b/drivers/net/ethernet/wiznet/w5100.c
@@ -96,6 +96,13 @@ struct w5100_priv {
 	struct net_device *ndev;
 	bool promisc;
 	u32 msg_enable;
+
+	struct workqueue_struct *xfer_wq;
+	struct work_struct rx_work;
+	struct sk_buff *tx_skb;
+	struct work_struct tx_work;
+	struct work_struct setrx_work;
+	struct work_struct restart_work;
 };
 
 /************************************************************************
@@ -502,10 +509,12 @@ static int w5100_reset(struct w5100_priv *priv)
 
 static int w5100_command(struct w5100_priv *priv, u16 cmd)
 {
-	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+	unsigned long timeout;
 
 	w5100_write(priv, W5100_S0_CR, cmd);
 
+	timeout = jiffies + msecs_to_jiffies(100);
+
 	while (w5100_read(priv, W5100_S0_CR) != 0) {
 		if (time_after(jiffies, timeout))
 			return -EIO;
@@ -605,7 +614,7 @@ static void w5100_get_regs(struct net_device *ndev,
 	w5100_readbulk(priv, W5100_S0_REGS, buf, W5100_S0_REGS_LEN);
 }
 
-static void w5100_tx_timeout(struct net_device *ndev)
+static void w5100_restart(struct net_device *ndev)
 {
 	struct w5100_priv *priv = netdev_priv(ndev);
 
@@ -617,12 +626,28 @@ static void w5100_tx_timeout(struct net_device *ndev)
 	netif_wake_queue(ndev);
 }
 
-static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
+static void w5100_restart_work(struct work_struct *work)
+{
+	struct w5100_priv *priv = container_of(work, struct w5100_priv,
+					       restart_work);
+
+	w5100_restart(priv->ndev);
+}
+
+static void w5100_tx_timeout(struct net_device *ndev)
 {
 	struct w5100_priv *priv = netdev_priv(ndev);
-	u16 offset;
 
-	netif_stop_queue(ndev);
+	if (priv->ops->may_sleep)
+		schedule_work(&priv->restart_work);
+	else
+		w5100_restart(ndev);
+}
+
+static void w5100_tx_skb(struct net_device *ndev, struct sk_buff *skb)
+{
+	struct w5100_priv *priv = netdev_priv(ndev);
+	u16 offset;
 
 	offset = w5100_read16(priv, W5100_S0_TX_WR);
 	w5100_writebuf(priv, offset, skb->data, skb->len);
@@ -632,47 +657,98 @@ static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
 	dev_kfree_skb(skb);
 
 	w5100_command(priv, S0_CR_SEND);
+}
+
+static void w5100_tx_work(struct work_struct *work)
+{
+	struct w5100_priv *priv = container_of(work, struct w5100_priv,
+					       tx_work);
+	struct sk_buff *skb = priv->tx_skb;
+
+	priv->tx_skb = NULL;
+
+	if (WARN_ON(!skb))
+		return;
+	w5100_tx_skb(priv->ndev, skb);
+}
+
+static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct w5100_priv *priv = netdev_priv(ndev);
+
+	netif_stop_queue(ndev);
+
+	if (priv->ops->may_sleep) {
+		WARN_ON(priv->tx_skb);
+		priv->tx_skb = skb;
+		queue_work(priv->xfer_wq, &priv->tx_work);
+	} else {
+		w5100_tx_skb(ndev, skb);
+	}
 
 	return NETDEV_TX_OK;
 }
 
-static int w5100_napi_poll(struct napi_struct *napi, int budget)
+static struct sk_buff *w5100_rx_skb(struct net_device *ndev)
 {
-	struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi);
-	struct net_device *ndev = priv->ndev;
+	struct w5100_priv *priv = netdev_priv(ndev);
 	struct sk_buff *skb;
-	int rx_count;
 	u16 rx_len;
 	u16 offset;
 	u8 header[2];
+	u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR);
 
-	for (rx_count = 0; rx_count < budget; rx_count++) {
-		u16 rx_buf_len = w5100_read16(priv, W5100_S0_RX_RSR);
-		if (rx_buf_len == 0)
-			break;
+	if (rx_buf_len == 0)
+		return NULL;
 
-		offset = w5100_read16(priv, W5100_S0_RX_RD);
-		w5100_readbuf(priv, offset, header, 2);
-		rx_len = get_unaligned_be16(header) - 2;
-
-		skb = netdev_alloc_skb_ip_align(ndev, rx_len);
-		if (unlikely(!skb)) {
-			w5100_write16(priv, W5100_S0_RX_RD,
-					    offset + rx_buf_len);
-			w5100_command(priv, S0_CR_RECV);
-			ndev->stats.rx_dropped++;
-			return -ENOMEM;
-		}
+	offset = w5100_read16(priv, W5100_S0_RX_RD);
+	w5100_readbuf(priv, offset, header, 2);
+	rx_len = get_unaligned_be16(header) - 2;
 
-		skb_put(skb, rx_len);
-		w5100_readbuf(priv, offset + 2, skb->data, rx_len);
-		w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len);
+	skb = netdev_alloc_skb_ip_align(ndev, rx_len);
+	if (unlikely(!skb)) {
+		w5100_write16(priv, W5100_S0_RX_RD, offset + rx_buf_len);
 		w5100_command(priv, S0_CR_RECV);
-		skb->protocol = eth_type_trans(skb, ndev);
+		ndev->stats.rx_dropped++;
+		return NULL;
+	}
+
+	skb_put(skb, rx_len);
+	w5100_readbuf(priv, offset + 2, skb->data, rx_len);
+	w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len);
+	w5100_command(priv, S0_CR_RECV);
+	skb->protocol = eth_type_trans(skb, ndev);
+
+	ndev->stats.rx_packets++;
+	ndev->stats.rx_bytes += rx_len;
+
+	return skb;
+}
+
+static void w5100_rx_work(struct work_struct *work)
+{
+	struct w5100_priv *priv = container_of(work, struct w5100_priv,
+					       rx_work);
+	struct sk_buff *skb;
+
+	while ((skb = w5100_rx_skb(priv->ndev)))
+		netif_rx_ni(skb);
+
+	w5100_write(priv, W5100_IMR, IR_S0);
+}
+
+static int w5100_napi_poll(struct napi_struct *napi, int budget)
+{
+	struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi);
+	int rx_count;
+
+	for (rx_count = 0; rx_count < budget; rx_count++) {
+		struct sk_buff *skb = w5100_rx_skb(priv->ndev);
 
-		netif_receive_skb(skb);
-		ndev->stats.rx_packets++;
-		ndev->stats.rx_bytes += rx_len;
+		if (skb)
+			netif_receive_skb(skb);
+		else
+			break;
 	}
 
 	if (rx_count < budget) {
@@ -699,10 +775,12 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
 	}
 
 	if (ir & S0_IR_RECV) {
-		if (napi_schedule_prep(&priv->napi)) {
-			w5100_write(priv, W5100_IMR, 0);
+		w5100_write(priv, W5100_IMR, 0);
+
+		if (priv->ops->may_sleep)
+			queue_work(priv->xfer_wq, &priv->rx_work);
+		else if (napi_schedule_prep(&priv->napi))
 			__napi_schedule(&priv->napi);
-		}
 	}
 
 	return IRQ_HANDLED;
@@ -726,6 +804,14 @@ static irqreturn_t w5100_detect_link(int irq, void *ndev_instance)
 	return IRQ_HANDLED;
 }
 
+static void w5100_setrx_work(struct work_struct *work)
+{
+	struct w5100_priv *priv = container_of(work, struct w5100_priv,
+					       setrx_work);
+
+	w5100_hw_start(priv);
+}
+
 static void w5100_set_rx_mode(struct net_device *ndev)
 {
 	struct w5100_priv *priv = netdev_priv(ndev);
@@ -733,7 +819,11 @@ static void w5100_set_rx_mode(struct net_device *ndev)
 
 	if (priv->promisc != set_promisc) {
 		priv->promisc = set_promisc;
-		w5100_hw_start(priv);
+
+		if (priv->ops->may_sleep)
+			schedule_work(&priv->setrx_work);
+		else
+			w5100_hw_start(priv);
 	}
 }
 
@@ -872,6 +962,17 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
 	if (err < 0)
 		goto err_register;
 
+	priv->xfer_wq = create_workqueue(netdev_name(ndev));
+	if (!priv->xfer_wq) {
+		err = -ENOMEM;
+		goto err_wq;
+	}
+
+	INIT_WORK(&priv->rx_work, w5100_rx_work);
+	INIT_WORK(&priv->tx_work, w5100_tx_work);
+	INIT_WORK(&priv->setrx_work, w5100_setrx_work);
+	INIT_WORK(&priv->restart_work, w5100_restart_work);
+
 	if (mac_addr)
 		memcpy(ndev->dev_addr, mac_addr, ETH_ALEN);
 	else
@@ -889,8 +990,14 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
 		goto err_hw;
 	}
 
-	err = request_irq(priv->irq, w5100_interrupt, IRQF_TRIGGER_LOW,
-			  netdev_name(ndev), ndev);
+	if (ops->may_sleep) {
+		err = request_threaded_irq(priv->irq, NULL, w5100_interrupt,
+					   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					   netdev_name(ndev), ndev);
+	} else {
+		err = request_irq(priv->irq, w5100_interrupt,
+				  IRQF_TRIGGER_LOW, netdev_name(ndev), ndev);
+	}
 	if (err)
 		goto err_hw;
 
@@ -915,6 +1022,8 @@ int w5100_probe(struct device *dev, const struct w5100_ops *ops,
 err_gpio:
 	free_irq(priv->irq, ndev);
 err_hw:
+	destroy_workqueue(priv->xfer_wq);
+err_wq:
 	unregister_netdev(ndev);
 err_register:
 	free_netdev(ndev);
@@ -932,6 +1041,11 @@ int w5100_remove(struct device *dev)
 	if (gpio_is_valid(priv->link_gpio))
 		free_irq(priv->link_irq, ndev);
 
+	flush_work(&priv->setrx_work);
+	flush_work(&priv->restart_work);
+	flush_workqueue(priv->xfer_wq);
+	destroy_workqueue(priv->xfer_wq);
+
 	unregister_netdev(ndev);
 	free_netdev(ndev);
 	return 0;
diff --git a/drivers/net/ethernet/wiznet/w5100.h b/drivers/net/ethernet/wiznet/w5100.h
index 39d452d8..69045f0 100644
--- a/drivers/net/ethernet/wiznet/w5100.h
+++ b/drivers/net/ethernet/wiznet/w5100.h
@@ -8,6 +8,7 @@
  */
 
 struct w5100_ops {
+	bool may_sleep;
 	int (*read)(struct net_device *ndev, u16 addr);
 	int (*write)(struct net_device *ndev, u16 addr, u8 data);
 	int (*read16)(struct net_device *ndev, u16 addr);
-- 
2.5.0

^ permalink raw reply related

* [PATCH v3 2/5] net: w5100: add ability to support other bus interface
From: Akinobu Mita @ 2016-04-14 15:11 UTC (permalink / raw)
  To: netdev; +Cc: Akinobu Mita, Mike Sinkovsky, David S. Miller
In-Reply-To: <1460646693-25179-1-git-send-email-akinobu.mita@gmail.com>

The w5100 driver currently only supports direct and indirect bus
interface mode which use MMIO space for accessing w5100 registers.

In order to support SPI interface mode which is supported by W5100 chip,
this makes the bus interface abstraction layer more generic so that
separated w5100-spi driver can use w5100 driver as core module.

Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Cc: Mike Sinkovsky <msink@permonline.ru>
Cc: David S. Miller <davem@davemloft.net>
---
* v3
- Add comment for reg_lock
- Add ability to allocate ops specific data structure

 drivers/net/ethernet/wiznet/w5100.c | 604 ++++++++++++++++++++++++------------
 drivers/net/ethernet/wiznet/w5100.h |  28 ++
 2 files changed, 431 insertions(+), 201 deletions(-)
 create mode 100644 drivers/net/ethernet/wiznet/w5100.h

diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c
index f4b7200..89cba67 100644
--- a/drivers/net/ethernet/wiznet/w5100.c
+++ b/drivers/net/ethernet/wiznet/w5100.c
@@ -27,6 +27,8 @@
 #include <linux/irq.h>
 #include <linux/gpio.h>
 
+#include "w5100.h"
+
 #define DRV_NAME	"w5100"
 #define DRV_VERSION	"2012-04-04"
 
@@ -76,25 +78,16 @@ MODULE_LICENSE("GPL");
 #define W5100_S0_REGS_LEN	0x0040
 
 #define W5100_TX_MEM_START	0x4000
-#define W5100_TX_MEM_END	0x5fff
-#define W5100_TX_MEM_MASK	0x1fff
+#define W5100_TX_MEM_SIZE	0x2000
 #define W5100_RX_MEM_START	0x6000
-#define W5100_RX_MEM_END	0x7fff
-#define W5100_RX_MEM_MASK	0x1fff
+#define W5100_RX_MEM_SIZE	0x2000
 
 /*
  * Device driver private data structure
  */
+
 struct w5100_priv {
-	void __iomem *base;
-	spinlock_t reg_lock;
-	bool indirect;
-	u8   (*read)(struct w5100_priv *priv, u16 addr);
-	void (*write)(struct w5100_priv *priv, u16 addr, u8 data);
-	u16  (*read16)(struct w5100_priv *priv, u16 addr);
-	void (*write16)(struct w5100_priv *priv, u16 addr, u16 data);
-	void (*readbuf)(struct w5100_priv *priv, u16 addr, u8 *buf, int len);
-	void (*writebuf)(struct w5100_priv *priv, u16 addr, u8 *buf, int len);
+	const struct w5100_ops *ops;
 	int irq;
 	int link_irq;
 	int link_gpio;
@@ -111,72 +104,121 @@ struct w5100_priv {
  *
  ***********************************************************************/
 
+struct w5100_mmio_priv {
+	void __iomem *base;
+	/* Serialize access in indirect address mode */
+	spinlock_t reg_lock;
+};
+
+static inline struct w5100_mmio_priv *w5100_mmio_priv(struct net_device *dev)
+{
+	return w5100_ops_priv(dev);
+}
+
+static inline void __iomem *w5100_mmio(struct net_device *ndev)
+{
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
+
+	return mmio_priv->base;
+}
+
 /*
  * In direct address mode host system can directly access W5100 registers
  * after mapping to Memory-Mapped I/O space.
  *
  * 0x8000 bytes are required for memory space.
  */
-static inline u8 w5100_read_direct(struct w5100_priv *priv, u16 addr)
+static inline int w5100_read_direct(struct net_device *ndev, u16 addr)
 {
-	return ioread8(priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
+	return ioread8(w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT));
 }
 
-static inline void __w5100_write_direct(struct w5100_priv *priv, u16 addr,
-					u8 data)
+static inline int __w5100_write_direct(struct net_device *ndev, u16 addr,
+				       u8 data)
 {
-	iowrite8(data, priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
+	iowrite8(data, w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT));
+
+	return 0;
 }
 
-static inline void w5100_write_direct(struct w5100_priv *priv,
-				      u16 addr, u8 data)
+static inline int w5100_write_direct(struct net_device *ndev, u16 addr, u8 data)
 {
-	__w5100_write_direct(priv, addr, data);
+	__w5100_write_direct(ndev, addr, data);
 	mmiowb();
+
+	return 0;
 }
 
-static u16 w5100_read16_direct(struct w5100_priv *priv, u16 addr)
+static int w5100_read16_direct(struct net_device *ndev, u16 addr)
 {
 	u16 data;
-	data  = w5100_read_direct(priv, addr) << 8;
-	data |= w5100_read_direct(priv, addr + 1);
+	data  = w5100_read_direct(ndev, addr) << 8;
+	data |= w5100_read_direct(ndev, addr + 1);
 	return data;
 }
 
-static void w5100_write16_direct(struct w5100_priv *priv, u16 addr, u16 data)
+static int w5100_write16_direct(struct net_device *ndev, u16 addr, u16 data)
 {
-	__w5100_write_direct(priv, addr, data >> 8);
-	__w5100_write_direct(priv, addr + 1, data);
+	__w5100_write_direct(ndev, addr, data >> 8);
+	__w5100_write_direct(ndev, addr + 1, data);
 	mmiowb();
+
+	return 0;
 }
 
-static void w5100_readbuf_direct(struct w5100_priv *priv,
-				 u16 offset, u8 *buf, int len)
+static int w5100_readbulk_direct(struct net_device *ndev, u16 addr, u8 *buf,
+				 int len)
 {
-	u16 addr = W5100_RX_MEM_START + (offset & W5100_RX_MEM_MASK);
 	int i;
 
-	for (i = 0; i < len; i++, addr++) {
-		if (unlikely(addr > W5100_RX_MEM_END))
-			addr = W5100_RX_MEM_START;
-		*buf++ = w5100_read_direct(priv, addr);
-	}
+	for (i = 0; i < len; i++, addr++)
+		*buf++ = w5100_read_direct(ndev, addr);
+
+	return 0;
 }
 
-static void w5100_writebuf_direct(struct w5100_priv *priv,
-				  u16 offset, u8 *buf, int len)
+static int w5100_writebulk_direct(struct net_device *ndev, u16 addr,
+				  const u8 *buf, int len)
 {
-	u16 addr = W5100_TX_MEM_START + (offset & W5100_TX_MEM_MASK);
 	int i;
 
-	for (i = 0; i < len; i++, addr++) {
-		if (unlikely(addr > W5100_TX_MEM_END))
-			addr = W5100_TX_MEM_START;
-		__w5100_write_direct(priv, addr, *buf++);
-	}
+	for (i = 0; i < len; i++, addr++)
+		__w5100_write_direct(ndev, addr, *buf++);
+
 	mmiowb();
+
+	return 0;
+}
+
+static int w5100_mmio_init(struct net_device *ndev)
+{
+	struct platform_device *pdev = to_platform_device(ndev->dev.parent);
+	struct w5100_priv *priv = netdev_priv(ndev);
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
+	struct resource *mem;
+
+	spin_lock_init(&mmio_priv->reg_lock);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mmio_priv->base = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(mmio_priv->base))
+		return PTR_ERR(mmio_priv->base);
+
+	netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, priv->irq);
+
+	return 0;
 }
 
+static const struct w5100_ops w5100_mmio_direct_ops = {
+	.read = w5100_read_direct,
+	.write = w5100_write_direct,
+	.read16 = w5100_read16_direct,
+	.write16 = w5100_write16_direct,
+	.readbulk = w5100_readbulk_direct,
+	.writebulk = w5100_writebulk_direct,
+	.init = w5100_mmio_init,
+};
+
 /*
  * In indirect address mode host system indirectly accesses registers by
  * using Indirect Mode Address Register (IDM_AR) and Indirect Mode Data
@@ -188,121 +230,276 @@ static void w5100_writebuf_direct(struct w5100_priv *priv,
 #define W5100_IDM_AR		0x01   /* Indirect Mode Address Register */
 #define W5100_IDM_DR		0x03   /* Indirect Mode Data Register */
 
-static u8 w5100_read_indirect(struct w5100_priv *priv, u16 addr)
+static int w5100_read_indirect(struct net_device *ndev, u16 addr)
 {
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
 	unsigned long flags;
 	u8 data;
 
-	spin_lock_irqsave(&priv->reg_lock, flags);
-	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	data = w5100_read_direct(priv, W5100_IDM_DR);
-	spin_unlock_irqrestore(&priv->reg_lock, flags);
+	spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+	w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+	data = w5100_read_direct(ndev, W5100_IDM_DR);
+	spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
 
 	return data;
 }
 
-static void w5100_write_indirect(struct w5100_priv *priv, u16 addr, u8 data)
+static int w5100_write_indirect(struct net_device *ndev, u16 addr, u8 data)
 {
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
 	unsigned long flags;
 
-	spin_lock_irqsave(&priv->reg_lock, flags);
-	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	w5100_write_direct(priv, W5100_IDM_DR, data);
-	spin_unlock_irqrestore(&priv->reg_lock, flags);
+	spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+	w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+	w5100_write_direct(ndev, W5100_IDM_DR, data);
+	spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+	return 0;
 }
 
-static u16 w5100_read16_indirect(struct w5100_priv *priv, u16 addr)
+static int w5100_read16_indirect(struct net_device *ndev, u16 addr)
 {
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
 	unsigned long flags;
 	u16 data;
 
-	spin_lock_irqsave(&priv->reg_lock, flags);
-	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	data  = w5100_read_direct(priv, W5100_IDM_DR) << 8;
-	data |= w5100_read_direct(priv, W5100_IDM_DR);
-	spin_unlock_irqrestore(&priv->reg_lock, flags);
+	spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+	w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+	data  = w5100_read_direct(ndev, W5100_IDM_DR) << 8;
+	data |= w5100_read_direct(ndev, W5100_IDM_DR);
+	spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
 
 	return data;
 }
 
-static void w5100_write16_indirect(struct w5100_priv *priv, u16 addr, u16 data)
+static int w5100_write16_indirect(struct net_device *ndev, u16 addr, u16 data)
 {
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
 	unsigned long flags;
 
-	spin_lock_irqsave(&priv->reg_lock, flags);
-	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	__w5100_write_direct(priv, W5100_IDM_DR, data >> 8);
-	w5100_write_direct(priv, W5100_IDM_DR, data);
-	spin_unlock_irqrestore(&priv->reg_lock, flags);
+	spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+	w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+	__w5100_write_direct(ndev, W5100_IDM_DR, data >> 8);
+	w5100_write_direct(ndev, W5100_IDM_DR, data);
+	spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+	return 0;
 }
 
-static void w5100_readbuf_indirect(struct w5100_priv *priv,
-				   u16 offset, u8 *buf, int len)
+static int w5100_readbulk_indirect(struct net_device *ndev, u16 addr, u8 *buf,
+				   int len)
 {
-	u16 addr = W5100_RX_MEM_START + (offset & W5100_RX_MEM_MASK);
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
 	unsigned long flags;
 	int i;
 
-	spin_lock_irqsave(&priv->reg_lock, flags);
-	w5100_write16_direct(priv, W5100_IDM_AR, addr);
+	spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+	w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+
+	for (i = 0; i < len; i++)
+		*buf++ = w5100_read_direct(ndev, W5100_IDM_DR);
 
-	for (i = 0; i < len; i++, addr++) {
-		if (unlikely(addr > W5100_RX_MEM_END)) {
-			addr = W5100_RX_MEM_START;
-			w5100_write16_direct(priv, W5100_IDM_AR, addr);
-		}
-		*buf++ = w5100_read_direct(priv, W5100_IDM_DR);
-	}
 	mmiowb();
-	spin_unlock_irqrestore(&priv->reg_lock, flags);
+	spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+	return 0;
 }
 
-static void w5100_writebuf_indirect(struct w5100_priv *priv,
-				    u16 offset, u8 *buf, int len)
+static int w5100_writebulk_indirect(struct net_device *ndev, u16 addr,
+				    const u8 *buf, int len)
 {
-	u16 addr = W5100_TX_MEM_START + (offset & W5100_TX_MEM_MASK);
+	struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev);
 	unsigned long flags;
 	int i;
 
-	spin_lock_irqsave(&priv->reg_lock, flags);
-	w5100_write16_direct(priv, W5100_IDM_AR, addr);
+	spin_lock_irqsave(&mmio_priv->reg_lock, flags);
+	w5100_write16_direct(ndev, W5100_IDM_AR, addr);
+
+	for (i = 0; i < len; i++)
+		__w5100_write_direct(ndev, W5100_IDM_DR, *buf++);
 
-	for (i = 0; i < len; i++, addr++) {
-		if (unlikely(addr > W5100_TX_MEM_END)) {
-			addr = W5100_TX_MEM_START;
-			w5100_write16_direct(priv, W5100_IDM_AR, addr);
-		}
-		__w5100_write_direct(priv, W5100_IDM_DR, *buf++);
-	}
 	mmiowb();
-	spin_unlock_irqrestore(&priv->reg_lock, flags);
+	spin_unlock_irqrestore(&mmio_priv->reg_lock, flags);
+
+	return 0;
+}
+
+static int w5100_reset_indirect(struct net_device *ndev)
+{
+	w5100_write_direct(ndev, W5100_MR, MR_RST);
+	mdelay(5);
+	w5100_write_direct(ndev, W5100_MR, MR_PB | MR_AI | MR_IND);
+
+	return 0;
 }
 
+static const struct w5100_ops w5100_mmio_indirect_ops = {
+	.read = w5100_read_indirect,
+	.write = w5100_write_indirect,
+	.read16 = w5100_read16_indirect,
+	.write16 = w5100_write16_indirect,
+	.readbulk = w5100_readbulk_indirect,
+	.writebulk = w5100_writebulk_indirect,
+	.init = w5100_mmio_init,
+	.reset = w5100_reset_indirect,
+};
+
 #if defined(CONFIG_WIZNET_BUS_DIRECT)
-#define w5100_read	w5100_read_direct
-#define w5100_write	w5100_write_direct
-#define w5100_read16	w5100_read16_direct
-#define w5100_write16	w5100_write16_direct
-#define w5100_readbuf	w5100_readbuf_direct
-#define w5100_writebuf	w5100_writebuf_direct
+
+static int w5100_read(struct w5100_priv *priv, u16 addr)
+{
+	return w5100_read_direct(priv->ndev, addr);
+}
+
+static int w5100_write(struct w5100_priv *priv, u16 addr, u8 data)
+{
+	return w5100_write_direct(priv->ndev, addr, data);
+}
+
+static int w5100_read16(struct w5100_priv *priv, u16 addr)
+{
+	return w5100_read16_direct(priv->ndev, addr);
+}
+
+static int w5100_write16(struct w5100_priv *priv, u16 addr, u16 data)
+{
+	return w5100_write16_direct(priv->ndev, addr, data);
+}
+
+static int w5100_readbulk(struct w5100_priv *priv, u16 addr, u8 *buf, int len)
+{
+	return w5100_readbulk_direct(priv->ndev, addr, buf, len);
+}
+
+static int w5100_writebulk(struct w5100_priv *priv, u16 addr, const u8 *buf,
+			   int len)
+{
+	return w5100_writebulk_direct(priv->ndev, addr, buf, len);
+}
 
 #elif defined(CONFIG_WIZNET_BUS_INDIRECT)
-#define w5100_read	w5100_read_indirect
-#define w5100_write	w5100_write_indirect
-#define w5100_read16	w5100_read16_indirect
-#define w5100_write16	w5100_write16_indirect
-#define w5100_readbuf	w5100_readbuf_indirect
-#define w5100_writebuf	w5100_writebuf_indirect
+
+static int w5100_read(struct w5100_priv *priv, u16 addr)
+{
+	return w5100_read_indirect(priv->ndev, addr);
+}
+
+static int w5100_write(struct w5100_priv *priv, u16 addr, u8 data)
+{
+	return w5100_write_indirect(priv->ndev, addr, data);
+}
+
+static int w5100_read16(struct w5100_priv *priv, u16 addr)
+{
+	return w5100_read16_indirect(priv->ndev, addr);
+}
+
+static int w5100_write16(struct w5100_priv *priv, u16 addr, u16 data)
+{
+	return w5100_write16_indirect(priv->ndev, addr, data);
+}
+
+static int w5100_readbulk(struct w5100_priv *priv, u16 addr, u8 *buf, int len)
+{
+	return w5100_readbulk_indirect(priv->ndev, addr, buf, len);
+}
+
+static int w5100_writebulk(struct w5100_priv *priv, u16 addr, const u8 *buf,
+			   int len)
+{
+	return w5100_writebulk_indirect(priv->ndev, addr, buf, len);
+}
 
 #else /* CONFIG_WIZNET_BUS_ANY */
-#define w5100_read	priv->read
-#define w5100_write	priv->write
-#define w5100_read16	priv->read16
-#define w5100_write16	priv->write16
-#define w5100_readbuf	priv->readbuf
-#define w5100_writebuf	priv->writebuf
+
+static int w5100_read(struct w5100_priv *priv, u16 addr)
+{
+	return priv->ops->read(priv->ndev, addr);
+}
+
+static int w5100_write(struct w5100_priv *priv, u16 addr, u8 data)
+{
+	return priv->ops->write(priv->ndev, addr, data);
+}
+
+static int w5100_read16(struct w5100_priv *priv, u16 addr)
+{
+	return priv->ops->read16(priv->ndev, addr);
+}
+
+static int w5100_write16(struct w5100_priv *priv, u16 addr, u16 data)
+{
+	return priv->ops->write16(priv->ndev, addr, data);
+}
+
+static int w5100_readbulk(struct w5100_priv *priv, u16 addr, u8 *buf, int len)
+{
+	return priv->ops->readbulk(priv->ndev, addr, buf, len);
+}
+
+static int w5100_writebulk(struct w5100_priv *priv, u16 addr, const u8 *buf,
+			   int len)
+{
+	return priv->ops->writebulk(priv->ndev, addr, buf, len);
+}
+
 #endif
 
+static int w5100_readbuf(struct w5100_priv *priv, u16 offset, u8 *buf, int len)
+{
+	u16 addr;
+	int remain = 0;
+	int ret;
+
+	offset %= W5100_RX_MEM_SIZE;
+	addr = W5100_RX_MEM_START + offset;
+
+	if (offset + len > W5100_RX_MEM_SIZE) {
+		remain = (offset + len) % W5100_RX_MEM_SIZE;
+		len = W5100_RX_MEM_SIZE - offset;
+	}
+
+	ret = w5100_readbulk(priv, addr, buf, len);
+	if (ret || !remain)
+		return ret;
+
+	return w5100_readbulk(priv, W5100_RX_MEM_START, buf + len, remain);
+}
+
+static int w5100_writebuf(struct w5100_priv *priv, u16 offset, const u8 *buf,
+			  int len)
+{
+	u16 addr;
+	int ret;
+	int remain = 0;
+
+	offset %= W5100_TX_MEM_SIZE;
+	addr = W5100_TX_MEM_START + offset;
+
+	if (offset + len > W5100_TX_MEM_SIZE) {
+		remain = (offset + len) % W5100_TX_MEM_SIZE;
+		len = W5100_TX_MEM_SIZE - offset;
+	}
+
+	ret = w5100_writebulk(priv, addr, buf, len);
+	if (ret || !remain)
+		return ret;
+
+	return w5100_writebulk(priv, W5100_TX_MEM_START, buf + len, remain);
+}
+
+static int w5100_reset(struct w5100_priv *priv)
+{
+	if (priv->ops->reset)
+		return priv->ops->reset(priv->ndev);
+
+	w5100_write(priv, W5100_MR, MR_RST);
+	mdelay(5);
+	w5100_write(priv, W5100_MR, MR_PB);
+
+	return 0;
+}
+
 static int w5100_command(struct w5100_priv *priv, u16 cmd)
 {
 	unsigned long timeout = jiffies + msecs_to_jiffies(100);
@@ -321,19 +518,14 @@ static int w5100_command(struct w5100_priv *priv, u16 cmd)
 static void w5100_write_macaddr(struct w5100_priv *priv)
 {
 	struct net_device *ndev = priv->ndev;
-	int i;
 
-	for (i = 0; i < ETH_ALEN; i++)
-		w5100_write(priv, W5100_SHAR + i, ndev->dev_addr[i]);
+	w5100_writebulk(priv, W5100_SHAR, ndev->dev_addr, ETH_ALEN);
 }
 
 static void w5100_hw_reset(struct w5100_priv *priv)
 {
-	w5100_write_direct(priv, W5100_MR, MR_RST);
-	mdelay(5);
-	w5100_write_direct(priv, W5100_MR, priv->indirect ?
-				  MR_PB | MR_AI | MR_IND :
-				  MR_PB);
+	w5100_reset(priv);
+
 	w5100_write(priv, W5100_IMR, 0);
 	w5100_write_macaddr(priv);
 
@@ -403,17 +595,14 @@ static int w5100_get_regs_len(struct net_device *ndev)
 }
 
 static void w5100_get_regs(struct net_device *ndev,
-			   struct ethtool_regs *regs, void *_buf)
+			   struct ethtool_regs *regs, void *buf)
 {
 	struct w5100_priv *priv = netdev_priv(ndev);
-	u8 *buf = _buf;
-	u16 i;
 
 	regs->version = 1;
-	for (i = 0; i < W5100_COMMON_REGS_LEN; i++)
-		*buf++ = w5100_read(priv, W5100_COMMON_REGS + i);
-	for (i = 0; i < W5100_S0_REGS_LEN; i++)
-		*buf++ = w5100_read(priv, W5100_S0_REGS + i);
+	w5100_readbulk(priv, W5100_COMMON_REGS, buf, W5100_COMMON_REGS_LEN);
+	buf += W5100_COMMON_REGS_LEN;
+	w5100_readbulk(priv, W5100_S0_REGS, buf, W5100_S0_REGS_LEN);
 }
 
 static void w5100_tx_timeout(struct net_device *ndev)
@@ -606,91 +795,68 @@ static const struct net_device_ops w5100_netdev_ops = {
 	.ndo_change_mtu		= eth_change_mtu,
 };
 
-static int w5100_hw_probe(struct platform_device *pdev)
+static int w5100_mmio_probe(struct platform_device *pdev)
 {
 	struct wiznet_platform_data *data = dev_get_platdata(&pdev->dev);
-	struct net_device *ndev = platform_get_drvdata(pdev);
-	struct w5100_priv *priv = netdev_priv(ndev);
-	const char *name = netdev_name(ndev);
+	u8 *mac_addr = NULL;
 	struct resource *mem;
-	int mem_size;
+	const struct w5100_ops *ops;
 	int irq;
-	int ret;
 
-	if (data && is_valid_ether_addr(data->mac_addr)) {
-		memcpy(ndev->dev_addr, data->mac_addr, ETH_ALEN);
-	} else {
-		eth_hw_addr_random(ndev);
-	}
+	if (data && is_valid_ether_addr(data->mac_addr))
+		mac_addr = data->mac_addr;
 
 	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	priv->base = devm_ioremap_resource(&pdev->dev, mem);
-	if (IS_ERR(priv->base))
-		return PTR_ERR(priv->base);
-
-	mem_size = resource_size(mem);
-
-	spin_lock_init(&priv->reg_lock);
-	priv->indirect = mem_size < W5100_BUS_DIRECT_SIZE;
-	if (priv->indirect) {
-		priv->read     = w5100_read_indirect;
-		priv->write    = w5100_write_indirect;
-		priv->read16   = w5100_read16_indirect;
-		priv->write16  = w5100_write16_indirect;
-		priv->readbuf  = w5100_readbuf_indirect;
-		priv->writebuf = w5100_writebuf_indirect;
-	} else {
-		priv->read     = w5100_read_direct;
-		priv->write    = w5100_write_direct;
-		priv->read16   = w5100_read16_direct;
-		priv->write16  = w5100_write16_direct;
-		priv->readbuf  = w5100_readbuf_direct;
-		priv->writebuf = w5100_writebuf_direct;
-	}
-
-	w5100_hw_reset(priv);
-	if (w5100_read16(priv, W5100_RTR) != RTR_DEFAULT)
-		return -ENODEV;
+	if (resource_size(mem) < W5100_BUS_DIRECT_SIZE)
+		ops = &w5100_mmio_indirect_ops;
+	else
+		ops = &w5100_mmio_direct_ops;
 
 	irq = platform_get_irq(pdev, 0);
 	if (irq < 0)
 		return irq;
-	ret = request_irq(irq, w5100_interrupt,
-			  IRQ_TYPE_LEVEL_LOW, name, ndev);
-	if (ret < 0)
-		return ret;
-	priv->irq = irq;
 
-	priv->link_gpio = data ? data->link_gpio : -EINVAL;
-	if (gpio_is_valid(priv->link_gpio)) {
-		char *link_name = devm_kzalloc(&pdev->dev, 16, GFP_KERNEL);
-		if (!link_name)
-			return -ENOMEM;
-		snprintf(link_name, 16, "%s-link", name);
-		priv->link_irq = gpio_to_irq(priv->link_gpio);
-		if (request_any_context_irq(priv->link_irq, w5100_detect_link,
-				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
-				link_name, priv->ndev) < 0)
-			priv->link_gpio = -EINVAL;
-	}
+	return w5100_probe(&pdev->dev, ops, sizeof(struct w5100_mmio_priv),
+			   mac_addr, irq, data ? data->link_gpio : -EINVAL);
+}
 
-	netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, irq);
-	return 0;
+static int w5100_mmio_remove(struct platform_device *pdev)
+{
+	return w5100_remove(&pdev->dev);
 }
 
-static int w5100_probe(struct platform_device *pdev)
+void *w5100_ops_priv(const struct net_device *ndev)
+{
+	return netdev_priv(ndev) +
+	       ALIGN(sizeof(struct w5100_priv), NETDEV_ALIGN);
+}
+EXPORT_SYMBOL_GPL(w5100_ops_priv);
+
+int w5100_probe(struct device *dev, const struct w5100_ops *ops,
+		int sizeof_ops_priv, u8 *mac_addr, int irq, int link_gpio)
 {
 	struct w5100_priv *priv;
 	struct net_device *ndev;
 	int err;
+	size_t alloc_size;
+
+	alloc_size = sizeof(*priv);
+	if (sizeof_ops_priv) {
+		alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
+		alloc_size += sizeof_ops_priv;
+	}
+	alloc_size += NETDEV_ALIGN - 1;
 
-	ndev = alloc_etherdev(sizeof(*priv));
+	ndev = alloc_etherdev(alloc_size);
 	if (!ndev)
 		return -ENOMEM;
-	SET_NETDEV_DEV(ndev, &pdev->dev);
-	platform_set_drvdata(pdev, ndev);
+	SET_NETDEV_DEV(ndev, dev);
+	dev_set_drvdata(dev, ndev);
 	priv = netdev_priv(ndev);
 	priv->ndev = ndev;
+	priv->ops = ops;
+	priv->irq = irq;
+	priv->link_gpio = link_gpio;
 
 	ndev->netdev_ops = &w5100_netdev_ops;
 	ndev->ethtool_ops = &w5100_ethtool_ops;
@@ -706,22 +872,59 @@ static int w5100_probe(struct platform_device *pdev)
 	if (err < 0)
 		goto err_register;
 
-	err = w5100_hw_probe(pdev);
-	if (err < 0)
-		goto err_hw_probe;
+	if (mac_addr)
+		memcpy(ndev->dev_addr, mac_addr, ETH_ALEN);
+	else
+		eth_hw_addr_random(ndev);
+
+	if (priv->ops->init) {
+		err = priv->ops->init(priv->ndev);
+		if (err)
+			goto err_hw;
+	}
+
+	w5100_hw_reset(priv);
+	if (w5100_read16(priv, W5100_RTR) != RTR_DEFAULT) {
+		err = -ENODEV;
+		goto err_hw;
+	}
+
+	err = request_irq(priv->irq, w5100_interrupt, IRQF_TRIGGER_LOW,
+			  netdev_name(ndev), ndev);
+	if (err)
+		goto err_hw;
+
+	if (gpio_is_valid(priv->link_gpio)) {
+		char *link_name = devm_kzalloc(dev, 16, GFP_KERNEL);
+
+		if (!link_name) {
+			err = -ENOMEM;
+			goto err_gpio;
+		}
+		snprintf(link_name, 16, "%s-link", netdev_name(ndev));
+		priv->link_irq = gpio_to_irq(priv->link_gpio);
+		if (request_any_context_irq(priv->link_irq, w5100_detect_link,
+					    IRQF_TRIGGER_RISING |
+					    IRQF_TRIGGER_FALLING,
+					    link_name, priv->ndev) < 0)
+			priv->link_gpio = -EINVAL;
+	}
 
 	return 0;
 
-err_hw_probe:
+err_gpio:
+	free_irq(priv->irq, ndev);
+err_hw:
 	unregister_netdev(ndev);
 err_register:
 	free_netdev(ndev);
 	return err;
 }
+EXPORT_SYMBOL_GPL(w5100_probe);
 
-static int w5100_remove(struct platform_device *pdev)
+int w5100_remove(struct device *dev)
 {
-	struct net_device *ndev = platform_get_drvdata(pdev);
+	struct net_device *ndev = dev_get_drvdata(dev);
 	struct w5100_priv *priv = netdev_priv(ndev);
 
 	w5100_hw_reset(priv);
@@ -733,12 +936,12 @@ static int w5100_remove(struct platform_device *pdev)
 	free_netdev(ndev);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(w5100_remove);
 
 #ifdef CONFIG_PM_SLEEP
 static int w5100_suspend(struct device *dev)
 {
-	struct platform_device *pdev = to_platform_device(dev);
-	struct net_device *ndev = platform_get_drvdata(pdev);
+	struct net_device *ndev = dev_get_drvdata(dev);
 	struct w5100_priv *priv = netdev_priv(ndev);
 
 	if (netif_running(ndev)) {
@@ -752,8 +955,7 @@ static int w5100_suspend(struct device *dev)
 
 static int w5100_resume(struct device *dev)
 {
-	struct platform_device *pdev = to_platform_device(dev);
-	struct net_device *ndev = platform_get_drvdata(pdev);
+	struct net_device *ndev = dev_get_drvdata(dev);
 	struct w5100_priv *priv = netdev_priv(ndev);
 
 	if (netif_running(ndev)) {
@@ -769,15 +971,15 @@ static int w5100_resume(struct device *dev)
 }
 #endif /* CONFIG_PM_SLEEP */
 
-static SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume);
+SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume);
+EXPORT_SYMBOL_GPL(w5100_pm_ops);
 
-static struct platform_driver w5100_driver = {
+static struct platform_driver w5100_mmio_driver = {
 	.driver		= {
 		.name	= DRV_NAME,
 		.pm	= &w5100_pm_ops,
 	},
-	.probe		= w5100_probe,
-	.remove		= w5100_remove,
+	.probe		= w5100_mmio_probe,
+	.remove		= w5100_mmio_remove,
 };
-
-module_platform_driver(w5100_driver);
+module_platform_driver(w5100_mmio_driver);
diff --git a/drivers/net/ethernet/wiznet/w5100.h b/drivers/net/ethernet/wiznet/w5100.h
new file mode 100644
index 0000000..39d452d8
--- /dev/null
+++ b/drivers/net/ethernet/wiznet/w5100.h
@@ -0,0 +1,28 @@
+/*
+ * Ethernet driver for the WIZnet W5100 chip.
+ *
+ * Copyright (C) 2006-2008 WIZnet Co.,Ltd.
+ * Copyright (C) 2012 Mike Sinkovsky <msink@permonline.ru>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+struct w5100_ops {
+	int (*read)(struct net_device *ndev, u16 addr);
+	int (*write)(struct net_device *ndev, u16 addr, u8 data);
+	int (*read16)(struct net_device *ndev, u16 addr);
+	int (*write16)(struct net_device *ndev, u16 addr, u16 data);
+	int (*readbulk)(struct net_device *ndev, u16 addr, u8 *buf, int len);
+	int (*writebulk)(struct net_device *ndev, u16 addr, const u8 *buf,
+			 int len);
+	int (*reset)(struct net_device *ndev);
+	int (*init)(struct net_device *ndev);
+};
+
+void *w5100_ops_priv(const struct net_device *ndev);
+
+int w5100_probe(struct device *dev, const struct w5100_ops *ops,
+		int sizeof_ops_priv, u8 *mac_addr, int irq, int link_gpio);
+int w5100_remove(struct device *dev);
+
+extern const struct dev_pm_ops w5100_pm_ops;
-- 
2.5.0

^ permalink raw reply related

* [PATCH v3 1/5] net: w5100: move mmiowb into register access callbacks
From: Akinobu Mita @ 2016-04-14 15:11 UTC (permalink / raw)
  To: netdev; +Cc: Akinobu Mita, Mike Sinkovsky, David S. Miller
In-Reply-To: <1460646693-25179-1-git-send-email-akinobu.mita@gmail.com>

Instead of sprinkle mmiowb over the driver code, move it into primary
register write callbacks. (w5100_write, w5100_write16, w5100_writebuf)

This is a preparation for supporting SPI interface which doesn't use
MMIO for accessing w5100 registers.

Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Cc: Mike Sinkovsky <msink@permonline.ru>
Cc: David S. Miller <davem@davemloft.net>
---
* No changes from v2

 drivers/net/ethernet/wiznet/w5100.c | 44 +++++++++++++------------------------
 1 file changed, 15 insertions(+), 29 deletions(-)

diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c
index 8b282d0..f4b7200 100644
--- a/drivers/net/ethernet/wiznet/w5100.c
+++ b/drivers/net/ethernet/wiznet/w5100.c
@@ -122,10 +122,17 @@ static inline u8 w5100_read_direct(struct w5100_priv *priv, u16 addr)
 	return ioread8(priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
 }
 
+static inline void __w5100_write_direct(struct w5100_priv *priv, u16 addr,
+					u8 data)
+{
+	iowrite8(data, priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
+}
+
 static inline void w5100_write_direct(struct w5100_priv *priv,
 				      u16 addr, u8 data)
 {
-	iowrite8(data, priv->base + (addr << CONFIG_WIZNET_BUS_SHIFT));
+	__w5100_write_direct(priv, addr, data);
+	mmiowb();
 }
 
 static u16 w5100_read16_direct(struct w5100_priv *priv, u16 addr)
@@ -138,8 +145,9 @@ static u16 w5100_read16_direct(struct w5100_priv *priv, u16 addr)
 
 static void w5100_write16_direct(struct w5100_priv *priv, u16 addr, u16 data)
 {
-	w5100_write_direct(priv, addr, data >> 8);
-	w5100_write_direct(priv, addr + 1, data);
+	__w5100_write_direct(priv, addr, data >> 8);
+	__w5100_write_direct(priv, addr + 1, data);
+	mmiowb();
 }
 
 static void w5100_readbuf_direct(struct w5100_priv *priv,
@@ -164,8 +172,9 @@ static void w5100_writebuf_direct(struct w5100_priv *priv,
 	for (i = 0; i < len; i++, addr++) {
 		if (unlikely(addr > W5100_TX_MEM_END))
 			addr = W5100_TX_MEM_START;
-		w5100_write_direct(priv, addr, *buf++);
+		__w5100_write_direct(priv, addr, *buf++);
 	}
+	mmiowb();
 }
 
 /*
@@ -186,7 +195,6 @@ static u8 w5100_read_indirect(struct w5100_priv *priv, u16 addr)
 
 	spin_lock_irqsave(&priv->reg_lock, flags);
 	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	mmiowb();
 	data = w5100_read_direct(priv, W5100_IDM_DR);
 	spin_unlock_irqrestore(&priv->reg_lock, flags);
 
@@ -199,9 +207,7 @@ static void w5100_write_indirect(struct w5100_priv *priv, u16 addr, u8 data)
 
 	spin_lock_irqsave(&priv->reg_lock, flags);
 	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	mmiowb();
 	w5100_write_direct(priv, W5100_IDM_DR, data);
-	mmiowb();
 	spin_unlock_irqrestore(&priv->reg_lock, flags);
 }
 
@@ -212,7 +218,6 @@ static u16 w5100_read16_indirect(struct w5100_priv *priv, u16 addr)
 
 	spin_lock_irqsave(&priv->reg_lock, flags);
 	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	mmiowb();
 	data  = w5100_read_direct(priv, W5100_IDM_DR) << 8;
 	data |= w5100_read_direct(priv, W5100_IDM_DR);
 	spin_unlock_irqrestore(&priv->reg_lock, flags);
@@ -226,10 +231,8 @@ static void w5100_write16_indirect(struct w5100_priv *priv, u16 addr, u16 data)
 
 	spin_lock_irqsave(&priv->reg_lock, flags);
 	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	mmiowb();
-	w5100_write_direct(priv, W5100_IDM_DR, data >> 8);
+	__w5100_write_direct(priv, W5100_IDM_DR, data >> 8);
 	w5100_write_direct(priv, W5100_IDM_DR, data);
-	mmiowb();
 	spin_unlock_irqrestore(&priv->reg_lock, flags);
 }
 
@@ -242,13 +245,11 @@ static void w5100_readbuf_indirect(struct w5100_priv *priv,
 
 	spin_lock_irqsave(&priv->reg_lock, flags);
 	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	mmiowb();
 
 	for (i = 0; i < len; i++, addr++) {
 		if (unlikely(addr > W5100_RX_MEM_END)) {
 			addr = W5100_RX_MEM_START;
 			w5100_write16_direct(priv, W5100_IDM_AR, addr);
-			mmiowb();
 		}
 		*buf++ = w5100_read_direct(priv, W5100_IDM_DR);
 	}
@@ -265,15 +266,13 @@ static void w5100_writebuf_indirect(struct w5100_priv *priv,
 
 	spin_lock_irqsave(&priv->reg_lock, flags);
 	w5100_write16_direct(priv, W5100_IDM_AR, addr);
-	mmiowb();
 
 	for (i = 0; i < len; i++, addr++) {
 		if (unlikely(addr > W5100_TX_MEM_END)) {
 			addr = W5100_TX_MEM_START;
 			w5100_write16_direct(priv, W5100_IDM_AR, addr);
-			mmiowb();
 		}
-		w5100_write_direct(priv, W5100_IDM_DR, *buf++);
+		__w5100_write_direct(priv, W5100_IDM_DR, *buf++);
 	}
 	mmiowb();
 	spin_unlock_irqrestore(&priv->reg_lock, flags);
@@ -309,7 +308,6 @@ static int w5100_command(struct w5100_priv *priv, u16 cmd)
 	unsigned long timeout = jiffies + msecs_to_jiffies(100);
 
 	w5100_write(priv, W5100_S0_CR, cmd);
-	mmiowb();
 
 	while (w5100_read(priv, W5100_S0_CR) != 0) {
 		if (time_after(jiffies, timeout))
@@ -327,18 +325,15 @@ static void w5100_write_macaddr(struct w5100_priv *priv)
 
 	for (i = 0; i < ETH_ALEN; i++)
 		w5100_write(priv, W5100_SHAR + i, ndev->dev_addr[i]);
-	mmiowb();
 }
 
 static void w5100_hw_reset(struct w5100_priv *priv)
 {
 	w5100_write_direct(priv, W5100_MR, MR_RST);
-	mmiowb();
 	mdelay(5);
 	w5100_write_direct(priv, W5100_MR, priv->indirect ?
 				  MR_PB | MR_AI | MR_IND :
 				  MR_PB);
-	mmiowb();
 	w5100_write(priv, W5100_IMR, 0);
 	w5100_write_macaddr(priv);
 
@@ -347,23 +342,19 @@ static void w5100_hw_reset(struct w5100_priv *priv)
 	 */
 	w5100_write(priv, W5100_RMSR, 0x03);
 	w5100_write(priv, W5100_TMSR, 0x03);
-	mmiowb();
 }
 
 static void w5100_hw_start(struct w5100_priv *priv)
 {
 	w5100_write(priv, W5100_S0_MR, priv->promisc ?
 			  S0_MR_MACRAW : S0_MR_MACRAW_MF);
-	mmiowb();
 	w5100_command(priv, S0_CR_OPEN);
 	w5100_write(priv, W5100_IMR, IR_S0);
-	mmiowb();
 }
 
 static void w5100_hw_close(struct w5100_priv *priv)
 {
 	w5100_write(priv, W5100_IMR, 0);
-	mmiowb();
 	w5100_command(priv, S0_CR_CLOSE);
 }
 
@@ -447,7 +438,6 @@ static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
 	offset = w5100_read16(priv, W5100_S0_TX_WR);
 	w5100_writebuf(priv, offset, skb->data, skb->len);
 	w5100_write16(priv, W5100_S0_TX_WR, offset + skb->len);
-	mmiowb();
 	ndev->stats.tx_bytes += skb->len;
 	ndev->stats.tx_packets++;
 	dev_kfree_skb(skb);
@@ -488,7 +478,6 @@ static int w5100_napi_poll(struct napi_struct *napi, int budget)
 		skb_put(skb, rx_len);
 		w5100_readbuf(priv, offset + 2, skb->data, rx_len);
 		w5100_write16(priv, W5100_S0_RX_RD, offset + 2 + rx_len);
-		mmiowb();
 		w5100_command(priv, S0_CR_RECV);
 		skb->protocol = eth_type_trans(skb, ndev);
 
@@ -500,7 +489,6 @@ static int w5100_napi_poll(struct napi_struct *napi, int budget)
 	if (rx_count < budget) {
 		napi_complete(napi);
 		w5100_write(priv, W5100_IMR, IR_S0);
-		mmiowb();
 	}
 
 	return rx_count;
@@ -515,7 +503,6 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
 	if (!ir)
 		return IRQ_NONE;
 	w5100_write(priv, W5100_S0_IR, ir);
-	mmiowb();
 
 	if (ir & S0_IR_SENDOK) {
 		netif_dbg(priv, tx_done, ndev, "tx done\n");
@@ -525,7 +512,6 @@ static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
 	if (ir & S0_IR_RECV) {
 		if (napi_schedule_prep(&priv->napi)) {
 			w5100_write(priv, W5100_IMR, 0);
-			mmiowb();
 			__napi_schedule(&priv->napi);
 		}
 	}
-- 
2.5.0

^ permalink raw reply related

* [PATCH v3 0/5] net: w5100: add support W5100/W5200 for SPI interface
From: Akinobu Mita @ 2016-04-14 15:11 UTC (permalink / raw)
  To: netdev; +Cc: Akinobu Mita, Mike Sinkovsky, David S. Miller

This series add support for Wiznet W5100 and W5200 for SPI interface.

We can easily find the ethernet modules and shield for Arduino with
these chips for purchase.  I've tested them with BeagleBone.

Wiznet W5100 for mmio access has already supported by w5100 driver.

In order to share the code between mmio mode and SPI mode, this series
firstly adds ability to support another register access interface to
the existing w5100 driver.  This ground work also requires to introduce
workqueue and threaded irq because SPI transfers are callable only from
contexts that can sleep unlike mmio access.

The latter part of this series adds w5100-spi driver which actually
support W5100 and W5200 for SPI interface.  Supporting W5100 is
straight forward because it only required to add a register access
interface by the SPI transfer.  W5100 and W5200 have similar memory
map which justifies adding W5200 support to w5100 driver.

* Changes from v2 to v3
- Add comment for reg_lock
- Add ability to allocate ops specific data structure
- Allocate w5200 ops specific data structure to put DMA-safe buffer
- Add missing chip_id assignment for w5100_*_ops

* Changes from v1 to v2
- Use a plain single pointer instead of SKB queue, spotted by David S. Miller
- Correct timeout period in w5100_command
- Use spi_write_then_read instead of spi_write which needs DMA-safe buffer
- Support W5200


Akinobu Mita (5):
  net: w5100: move mmiowb into register access callbacks
  net: w5100: add ability to support other bus interface
  net: w5100: enable to support sleepable register access interface
  net: w5100: support SPI interface mode
  net: w5100: support W5200

 drivers/net/ethernet/wiznet/Kconfig     |  14 +
 drivers/net/ethernet/wiznet/Makefile    |   1 +
 drivers/net/ethernet/wiznet/w5100-spi.c | 300 ++++++++++
 drivers/net/ethernet/wiznet/w5100.c     | 935 ++++++++++++++++++++++----------
 drivers/net/ethernet/wiznet/w5100.h     |  35 ++
 5 files changed, 1004 insertions(+), 281 deletions(-)
 create mode 100644 drivers/net/ethernet/wiznet/w5100-spi.c
 create mode 100644 drivers/net/ethernet/wiznet/w5100.h

Cc: Mike Sinkovsky <msink@permonline.ru>
Cc: David S. Miller <davem@davemloft.net>
-- 
2.5.0

^ permalink raw reply

* Re: Deleting child qdisc doesn't reset parent to default qdisc?
From: Eric Dumazet @ 2016-04-14 15:01 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: Jamal Hadi Salim, netdev, linux-kernel
In-Reply-To: <alpine.LNX.2.00.1604141638230.27368@cbobk.fhfr.pm>

On Thu, 2016-04-14 at 16:44 +0200, Jiri Kosina wrote:
> Hi,
> 
> I've came across the behavior where adding a child qdisc and then deleting 
> it again makes the networking dysfunctional (I guess that's because all of 
> a sudden there is absolutely no working qdisc on the device, although 
> there originally was a default one in the parent).
> 
> In a nutshell, is this expected behavior or bug?

This is the expected behavior.

If the kernel was suddenly doing a 'replace' when you ask a delete,
then the scripts doing a delete , than a add would break.

tc users are skilled admins ;)

^ permalink raw reply

* Re: [PATCH iproute2 0/4] add MACsec support
From: Phil Sutter @ 2016-04-14 14:59 UTC (permalink / raw)
  To: Sabrina Dubroca; +Cc: netdev, Stephen Hemminger
In-Reply-To: <cover.1460622809.git.sd@queasysnail.net>

On Thu, Apr 14, 2016 at 03:01:06PM +0200, Sabrina Dubroca wrote:
> This series introduces support for MACsec devices, with a new device
> type for `ip link`, and a new `ip macsec` subcommand.
> 
> The first three patches introduce some necessary helper functions.
> 
> Sabrina Dubroca (4):
>   utils: make hexstring_a2n provide the number of hex digits parsed
>   utils: add get_be{16,32,64}, use them where possible
>   utils: provide get_hex to read an hex digit from a char
>   ip: add MACsec support

Apart from the unrelated coding-style fix in patch 1 and the somewhat
intricate man page markup:

Acked-by: Phil Sutter <phil@nwl.cc>

^ permalink raw reply

* Re: [PATCH net-next 5/8] dsa: mv88e6xxx: Kill the REG_READ and REG_WRITE macros
From: Vivien Didelot @ 2016-04-14 14:54 UTC (permalink / raw)
  To: Andrew Lunn, David Miller; +Cc: Florian Fainelli, netdev, Andrew Lunn
In-Reply-To: <1460591998-20598-6-git-send-email-andrew@lunn.ch>

Hi Andrew,

Andrew Lunn <andrew@lunn.ch> writes:

<snip>

>  int mv88e6xxx_set_addr_indirect(struct dsa_switch *ds, u8 *addr)
>  {
> +	int ret, err;
>  	int i;
> -	int ret;
>  
>  	for (i = 0; i < 6; i++) {
>  		int j;
>  
>  		/* Write the MAC address byte. */
> -		REG_WRITE(REG_GLOBAL2, GLOBAL2_SWITCH_MAC,
> -			  GLOBAL2_SWITCH_MAC_BUSY | (i << 8) | addr[i]);
> +		err = mv88e6xxx_reg_write(ds, REG_GLOBAL2, GLOBAL2_SWITCH_MAC,
> +					  GLOBAL2_SWITCH_MAC_BUSY |
> +					  (i << 8) | addr[i]);
> +		if (err)
> +			return err;

Just use the ret variable here.

>  
>  		/* Wait for the write to complete. */
>  		for (j = 0; j < 16; j++) {
> -			ret = REG_READ(REG_GLOBAL2, GLOBAL2_SWITCH_MAC);
> +			ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2,
> +						 GLOBAL2_SWITCH_MAC);
> +			if (ret < 0)
> +				return ret;
> +
> +			if (ret < 0)
> +				return ret;

duplicated condition.

>  			if ((ret & GLOBAL2_SWITCH_MAC_BUSY) == 0)
>  				break;
>  		}

<snip>

> @@ -2697,7 +2732,8 @@ int mv88e6xxx_setup_common(struct dsa_switch *ds)
>  	ps->ds = ds;
>  	mutex_init(&ps->smi_mutex);
>  
> -	ps->id = REG_READ(REG_PORT(0), PORT_SWITCH_ID) & 0xfff0;
> +	ps->id = __mv88e6xxx_reg_read(ps->bus, ps->sw_addr, REG_PORT(0),
> +				      PORT_SWITCH_ID) & 0xfff0;

Here you dropped the error checking, and the (maybe unnecessary?) mutex
locking.

>  
>  	INIT_WORK(&ps->bridge_work, mv88e6xxx_bridge_work);
>  
> @@ -2708,42 +2744,66 @@ EXPORT_SYMBOL_GPL(mv88e6xxx_setup_common);
>  int mv88e6xxx_setup_global(struct dsa_switch *ds)
>  {
>  	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
> -	int ret;
> +	int err;
>  	int i;
>  
>  	/* Set the default address aging time to 5 minutes, and
>  	 * enable address learn messages to be sent to all message
>  	 * ports.
>  	 */
> -	REG_WRITE(REG_GLOBAL, GLOBAL_ATU_CONTROL,
> -		  0x0140 | GLOBAL_ATU_CONTROL_LEARN2ALL);
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_ATU_CONTROL,
> +				  0x0140 | GLOBAL_ATU_CONTROL_LEARN2ALL);
> +	if (err)
> +		return err;
>  
>  	/* Configure the IP ToS mapping registers. */
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_0, 0x0000);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_1, 0x0000);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_2, 0x5555);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_3, 0x5555);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_4, 0xaaaa);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_5, 0xaaaa);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_6, 0xffff);
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IP_PRI_7, 0xffff);
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_0, 0x0000);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_1, 0x0000);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_2, 0x5555);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_3, 0x5555);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_4, 0xaaaa);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_5, 0xaaaa);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_6, 0xffff);
> +	if (err)
> +		return err;
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IP_PRI_7, 0xffff);
> +	if (err)
> +		return err;
>  
>  	/* Configure the IEEE 802.1p priority mapping register. */
> -	REG_WRITE(REG_GLOBAL, GLOBAL_IEEE_PRI, 0xfa41);
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_IEEE_PRI, 0xfa41);
> +	if (err)
> +		return err;
>  
>  	/* Send all frames with destination addresses matching
>  	 * 01:80:c2:00:00:0x to the CPU port.
>  	 */
> -	REG_WRITE(REG_GLOBAL2, GLOBAL2_MGMT_EN_0X, 0xffff);
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL2, GLOBAL2_MGMT_EN_0X, 0xffff);
> +	if (err)
> +		return err;
>  
>  	/* Ignore removed tag data on doubly tagged packets, disable
>  	 * flow control messages, force flow control priority to the
>  	 * highest, and send all special multicast frames to the CPU
>  	 * port at the highest priority.
>  	 */
> -	REG_WRITE(REG_GLOBAL2, GLOBAL2_SWITCH_MGMT,
> -		  0x7 | GLOBAL2_SWITCH_MGMT_RSVD2CPU | 0x70 |
> -		  GLOBAL2_SWITCH_MGMT_FORCE_FLOW_CTRL_PRI);
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL2, GLOBAL2_SWITCH_MGMT,
> +				  0x7 | GLOBAL2_SWITCH_MGMT_RSVD2CPU | 0x70 |
> +				  GLOBAL2_SWITCH_MGMT_FORCE_FLOW_CTRL_PRI);
> +	if (err)
> +		return err;
>  
>  	/* Program the DSA routing table. */
>  	for (i = 0; i < 32; i++) {
> @@ -2753,23 +2813,35 @@ int mv88e6xxx_setup_global(struct dsa_switch *ds)
>  		    i != ds->index && i < ds->dst->pd->nr_chips)
>  			nexthop = ds->pd->rtable[i] & 0x1f;
>  
> -		REG_WRITE(REG_GLOBAL2, GLOBAL2_DEVICE_MAPPING,
> -			  GLOBAL2_DEVICE_MAPPING_UPDATE |
> -			  (i << GLOBAL2_DEVICE_MAPPING_TARGET_SHIFT) |
> -			  nexthop);
> +		err = mv88e6xxx_reg_write(
> +			ds, REG_GLOBAL2,
> +			GLOBAL2_DEVICE_MAPPING,
> +			GLOBAL2_DEVICE_MAPPING_UPDATE |
> +			(i << GLOBAL2_DEVICE_MAPPING_TARGET_SHIFT) | nexthop);
> +	if (err)
> +		return err;

Wrong indentation here.

>  	}
>  
>  	/* Clear all trunk masks. */
> -	for (i = 0; i < 8; i++)
> -		REG_WRITE(REG_GLOBAL2, GLOBAL2_TRUNK_MASK,
> -			  0x8000 | (i << GLOBAL2_TRUNK_MASK_NUM_SHIFT) |
> -			  ((1 << ps->num_ports) - 1));
> +	for (i = 0; i < 8; i++) {
> +		err = mv88e6xxx_reg_write(ds, REG_GLOBAL2, GLOBAL2_TRUNK_MASK,
> +					  0x8000 |
> +					  (i << GLOBAL2_TRUNK_MASK_NUM_SHIFT) |
> +					  ((1 << ps->num_ports) - 1));
> +		if (err)
> +			return err;
> +	}
>  
>  	/* Clear all trunk mappings. */
> -	for (i = 0; i < 16; i++)
> -		REG_WRITE(REG_GLOBAL2, GLOBAL2_TRUNK_MAPPING,
> -			  GLOBAL2_TRUNK_MAPPING_UPDATE |
> -			  (i << GLOBAL2_TRUNK_MAPPING_ID_SHIFT));
> +	for (i = 0; i < 16; i++) {
> +		err = mv88e6xxx_reg_write(
> +			ds, REG_GLOBAL2,
> +			GLOBAL2_TRUNK_MAPPING,
> +			GLOBAL2_TRUNK_MAPPING_UPDATE |
> +			(i << GLOBAL2_TRUNK_MAPPING_ID_SHIFT));
> +		if (err)
> +			return err;
> +	}
>  
>  	if (mv88e6xxx_6352_family(ds) || mv88e6xxx_6351_family(ds) ||
>  	    mv88e6xxx_6165_family(ds) || mv88e6xxx_6097_family(ds) ||
> @@ -2777,17 +2849,27 @@ int mv88e6xxx_setup_global(struct dsa_switch *ds)
>  		/* Send all frames with destination addresses matching
>  		 * 01:80:c2:00:00:2x to the CPU port.
>  		 */
> -		REG_WRITE(REG_GLOBAL2, GLOBAL2_MGMT_EN_2X, 0xffff);
> +		err = mv88e6xxx_reg_write(ds, REG_GLOBAL2,
> +					  GLOBAL2_MGMT_EN_2X, 0xffff);
> +		if (err)
> +			return err;
>  
>  		/* Initialise cross-chip port VLAN table to reset
>  		 * defaults.
>  		 */
> -		REG_WRITE(REG_GLOBAL2, GLOBAL2_PVT_ADDR, 0x9000);
> +		err = mv88e6xxx_reg_write(ds, REG_GLOBAL2,
> +					  GLOBAL2_PVT_ADDR, 0x9000);
> +		if (err)
> +			return err;
>  
>  		/* Clear the priority override table. */
> -		for (i = 0; i < 16; i++)
> -			REG_WRITE(REG_GLOBAL2, GLOBAL2_PRIO_OVERRIDE,
> -				  0x8000 | (i << 8));
> +		for (i = 0; i < 16; i++) {
> +			err = mv88e6xxx_reg_write(ds, REG_GLOBAL2,
> +						  GLOBAL2_PRIO_OVERRIDE,
> +						  0x8000 | (i << 8));
> +			if (err)
> +				return err;
> +		}
>  	}
>  
>  	if (mv88e6xxx_6352_family(ds) || mv88e6xxx_6351_family(ds) ||
> @@ -2798,31 +2880,38 @@ int mv88e6xxx_setup_global(struct dsa_switch *ds)
>  		 * ingress rate limit registers to their initial
>  		 * state.
>  		 */
> -		for (i = 0; i < ps->num_ports; i++)
> -			REG_WRITE(REG_GLOBAL2, GLOBAL2_INGRESS_OP,
> -				  0x9000 | (i << 8));
> +		for (i = 0; i < ps->num_ports; i++) {
> +			err = mv88e6xxx_reg_write(ds, REG_GLOBAL2,
> +						  GLOBAL2_INGRESS_OP,
> +						  0x9000 | (i << 8));
> +			if (err)
> +				return err;
> +		}
>  	}
>  
>  	/* Clear the statistics counters for all ports */
> -	REG_WRITE(REG_GLOBAL, GLOBAL_STATS_OP, GLOBAL_STATS_OP_FLUSH_ALL);
> +	err = mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_STATS_OP,
> +				  GLOBAL_STATS_OP_FLUSH_ALL);
> +	if (err)
> +		return err;
>  
>  	/* Wait for the flush to complete. */
>  	mutex_lock(&ps->smi_mutex);

This function is ugly. As you are rewriting all read/write access in it,
Please put that mutex_lock at its beginning and use the _mv88e6xxx_reg_*
variant with goto unlock statement instead, so we lock/unlock just once.

> -	ret = _mv88e6xxx_stats_wait(ds);
> -	if (ret < 0)
> +	err = _mv88e6xxx_stats_wait(ds);
> +	if (err < 0)
>  		goto unlock;
>  
>  	/* Clear all ATU entries */
> -	ret = _mv88e6xxx_atu_flush(ds, 0, true);
> -	if (ret < 0)
> +	err = _mv88e6xxx_atu_flush(ds, 0, true);
> +	if (err < 0)
>  		goto unlock;
>  
>  	/* Clear all the VTU and STU entries */
> -	ret = _mv88e6xxx_vtu_stu_flush(ds);
> +	err = _mv88e6xxx_vtu_stu_flush(ds);
>  unlock:
>  	mutex_unlock(&ps->smi_mutex);
>  
> -	return ret;
> +	return err;
>  }
>  EXPORT_SYMBOL_GPL(mv88e6xxx_setup_global);
>  
> @@ -2832,13 +2921,19 @@ int mv88e6xxx_switch_reset(struct dsa_switch *ds, bool ppu_active)
>  	u16 is_reset = (ppu_active ? 0x8800 : 0xc800);
>  	struct gpio_desc *gpiod = ps->reset;
>  	unsigned long timeout;
> -	int ret;
> +	int ret, err;
>  	int i;
>  
>  	/* Set all ports to the disabled state. */
>  	for (i = 0; i < ps->num_ports; i++) {
> -		ret = REG_READ(REG_PORT(i), PORT_CONTROL);
> -		REG_WRITE(REG_PORT(i), PORT_CONTROL, ret & 0xfffc);
> +		ret = mv88e6xxx_reg_read(ds, REG_PORT(i), PORT_CONTROL);
> +		if (ret < 0)
> +			return ret;
> +
> +		err = mv88e6xxx_reg_write(ds, REG_PORT(i), PORT_CONTROL,
> +					  ret & 0xfffc);
> +		if (err)
> +			return err;
>  	}
>  
>  	/* Wait for transmit queues to drain. */
> @@ -2857,14 +2952,19 @@ int mv88e6xxx_switch_reset(struct dsa_switch *ds, bool ppu_active)
>  	 * through global registers 0x18 and 0x19.
>  	 */
>  	if (ppu_active)
> -		REG_WRITE(REG_GLOBAL, 0x04, 0xc000);
> +		err = mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x04, 0xc000);
>  	else
> -		REG_WRITE(REG_GLOBAL, 0x04, 0xc400);
> +		err = mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x04, 0xc400);
> +	if (err)
> +		return err;

The ret variable is already there, I'd stick with it instead of adding a
new err variable to the function.

>  
>  	/* Wait up to one second for reset to complete. */
>  	timeout = jiffies + 1 * HZ;
>  	while (time_before(jiffies, timeout)) {
> -		ret = REG_READ(REG_GLOBAL, 0x00);
> +		ret = mv88e6xxx_reg_read(ds, REG_GLOBAL, 0x00);
> +		if (ret < 0)
> +			return ret;
> +
>  		if ((ret & is_reset) == is_reset)
>  			break;
>  		usleep_range(1000, 2000);

This comment is up to you, but as you're adding error conditions to the
function, I'd go with a single mutex_lock/mutex_unlock, use the
_mv88e6xxx_reg_* variants and goto unlock statements here as well.

> diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h
> index 739d3ff1bddf..6d1b6207144d 100644
> --- a/drivers/net/dsa/mv88e6xxx.h
> +++ b/drivers/net/dsa/mv88e6xxx.h
> @@ -554,25 +554,6 @@ extern struct dsa_switch_driver mv88e6123_switch_driver;
>  extern struct dsa_switch_driver mv88e6352_switch_driver;
>  extern struct dsa_switch_driver mv88e6171_switch_driver;
>  
> -#define REG_READ(addr, reg)						\
> -	({								\
> -		int __ret;						\
> -									\
> -		__ret = mv88e6xxx_reg_read(ds, addr, reg);		\
> -		if (__ret < 0)						\
> -			return __ret;					\
> -		__ret;							\
> -	})
> -
> -#define REG_WRITE(addr, reg, val)					\
> -	({								\
> -		int __ret;						\
> -									\
> -		__ret = mv88e6xxx_reg_write(ds, addr, reg, val);	\
> -		if (__ret < 0)						\
> -			return __ret;					\
> -	})
> -
>  /**
>   * mv88e6xxx_module_driver() - Helper macro for registering mv88e6xxx drivers
>   *

I'm 200% for this patch. However it is not related to the rest of the
patchset, which I'm not really OK with yet (still reviewing). As this
needs a respin anyway, can you extract and send it as a single patch? It
applies cleanly to net-next once you remove the mv88e6xxx_module_driver
macro. I'll ack it right away.

Thanks,
Vivien

^ permalink raw reply

* Re: Deleting child qdisc doesn't reset parent to default qdisc?
From: Jiri Kosina @ 2016-04-14 14:51 UTC (permalink / raw)
  To: Jamal Hadi Salim; +Cc: netdev, linux-kernel
In-Reply-To: <alpine.LNX.2.00.1604141638230.27368@cbobk.fhfr.pm>

On Thu, 14 Apr 2016, Jiri Kosina wrote:

> In a nutshell, is this expected behavior or bug?

Just to clarify what seems to suggest to me that this is rather a bug that 
needs to be fixed (but apparently one that has been there for quite a long 
time) can be demonstrated by this:

> 
> =====
> jikos:~ # tc qdisc show
> qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 

The above configuration works.

> jikos:~ # ping -c 1 nix.cz | head -2
> PING nix.cz (195.47.235.3) 56(84) bytes of data.
> 64 bytes from info.nix.cz (195.47.235.3): icmp_seq=1 ttl=89 time=1.59 ms
> 
> jikos:~ # tc qdisc add dev eth0 parent 10:1 sfq
> jikos:~ # tc qdisc show
> qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 
> qdisc sfq 8008: dev eth0 parent 10:1 limit 127p quantum 1514b depth 127 divisor 1024 
> 
> jikos:~ # ping -c 1 nix.cz | head -2
> PING nix.cz (195.47.235.3) 56(84) bytes of data.
> 64 bytes from info.nix.cz (195.47.235.3): icmp_seq=1 ttl=89 time=1.67 ms
> 
> jikos:~ # tc qdisc del dev eth0 parent 10:1 sfq
> jikos:~ # tc qdisc show
> qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 

The above configuration doesn't although it's identical to the working one 
at the beginning.

> jikos:~ # ping -c 1 nix.cz | head -2
> PING nix.cz (195.47.235.3) 56(84) bytes of data.
> 	[ ... nothing happens ... ]
> ^C

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Deleting child qdisc doesn't reset parent to default qdisc?
From: Jiri Kosina @ 2016-04-14 14:44 UTC (permalink / raw)
  To: Jamal Hadi Salim; +Cc: netdev, linux-kernel

Hi,

I've came across the behavior where adding a child qdisc and then deleting 
it again makes the networking dysfunctional (I guess that's because all of 
a sudden there is absolutely no working qdisc on the device, although 
there originally was a default one in the parent).

In a nutshell, is this expected behavior or bug?

=====
jikos:~ # tc qdisc show
qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 
jikos:~ # ping -c 1 nix.cz | head -2
PING nix.cz (195.47.235.3) 56(84) bytes of data.
64 bytes from info.nix.cz (195.47.235.3): icmp_seq=1 ttl=89 time=1.59 ms

jikos:~ # tc qdisc add dev eth0 parent 10:1 sfq
jikos:~ # tc qdisc show
qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 
qdisc sfq 8008: dev eth0 parent 10:1 limit 127p quantum 1514b depth 127 divisor 1024 

jikos:~ # ping -c 1 nix.cz | head -2
PING nix.cz (195.47.235.3) 56(84) bytes of data.
64 bytes from info.nix.cz (195.47.235.3): icmp_seq=1 ttl=89 time=1.67 ms

jikos:~ # tc qdisc del dev eth0 parent 10:1 sfq
jikos:~ # tc qdisc show
qdisc tbf 10: dev eth0 root refcnt 2 rate 800Mbit burst 131000b lat 1.0ms 
jikos:~ # ping -c 1 nix.cz | head -2
PING nix.cz (195.47.235.3) 56(84) bytes of data.
	[ ... nothing happens ... ]
^C
jikos:~ # tc qdisc add dev eth0 parent 10:1 sfq
jikos:~ # ping -c 1 nix.cz | head -2
PING nix.cz (195.47.235.3) 56(84) bytes of data.
64 bytes from info.nix.cz (195.47.235.3): icmp_seq=1 ttl=89 time=1.66 ms
=====

Thanks,

-- 
Jiri Kosina

^ permalink raw reply

* Re: [PATCH v3 0/2] sctp: delay calls to sk_data_ready() as much as possible
From: Neil Horman @ 2016-04-14 13:03 UTC (permalink / raw)
  To: David Miller
  Cc: marcelo.leitner, netdev, vyasevich, linux-sctp, David.Laight,
	jkbs
In-Reply-To: <20160413.230532.676746231426161126.davem@davemloft.net>

On Wed, Apr 13, 2016 at 11:05:32PM -0400, David Miller wrote:
> From: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
> Date: Fri,  8 Apr 2016 16:41:26 -0300
> 
> > 1st patch is a preparation for the 2nd. The idea is to not call
> > ->sk_data_ready() for every data chunk processed while processing
> > packets but only once before releasing the socket.
> > 
> > v2: patchset re-checked, small changelog fixes
> > v3: on patch 2, make use of local vars to make it more readable
> 
> Applied to net-next, but isn't this reduced overhead coming at the
> expense of latency?  What if that lower latency is important to the
> application and/or consumer?
Thats a fair point, but I'd make the counter argument that, as it currently
stands, any latency introduced (or removed), is an artifact of our
implementation rather than a designed feature of it.  That is to say, we make no
guarantees at the application level regarding how long it takes to signal data
readines from the time we get data off the wire, so I would rather see our
throughput raised if we can, as thats been sctp's more pressing achilles heel.


Thats not to say I'd like to enable lower latency, but I'd rather have this now,
and start pondering how to design that in.  Perhaps we can convert the pending
flag to a counter to count the number of events we enqueue, and call
sk_data_ready every  time we reach a sysctl defined threshold.

Neil

> --
> To unsubscribe from this list: send the line "unsubscribe linux-sctp" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply

* [PATCH iproute2 4/4] ip: add MACsec support
From: Sabrina Dubroca @ 2016-04-14 13:01 UTC (permalink / raw)
  To: netdev; +Cc: Stephen Hemminger, Sabrina Dubroca
In-Reply-To: <cover.1460622809.git.sd@queasysnail.net>

Extend ip-link to create MACsec devices

  ip link add link <master> <macsec> type macsec [options]

Add an `ip macsec` command to configure receive-side secure channels
and secure associations within a macsec netdevice.

Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
---
Changes since v1:
 - use new utils
 - adapt to new netlink API
 - clean up stats printing
 - make commands and output symmetrical

Stephen, this depends on if_macsec.h being picked up from kernel uapi,
but it wasn't part of the recent headers updates.  Did I screw up
something in the kernel's Makefiles?
---
 ip/Makefile           |    2 +-
 ip/ip.c               |    3 +-
 ip/ip_common.h        |    1 +
 ip/ipmacsec.c         | 1231 +++++++++++++++++++++++++++++++++++++++++++++++++
 man/man8/Makefile     |    2 +-
 man/man8/ip-link.8.in |  134 ++++++
 man/man8/ip-macsec.8  |  110 +++++
 7 files changed, 1480 insertions(+), 3 deletions(-)
 create mode 100644 ip/ipmacsec.c
 create mode 100644 man/man8/ip-macsec.8

diff --git a/ip/Makefile b/ip/Makefile
index f3d298739cac..fe59ea8bdc76 100644
--- a/ip/Makefile
+++ b/ip/Makefile
@@ -7,7 +7,7 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
     iplink_vxlan.o tcp_metrics.o iplink_ipoib.o ipnetconf.o link_ip6tnl.o \
     link_iptnl.o link_gre6.o iplink_bond.o iplink_bond_slave.o iplink_hsr.o \
     iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o \
-    iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o
+    iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o
 
 RTMONOBJ=rtmon.o
 
diff --git a/ip/ip.c b/ip/ip.c
index 123f18133a2e..166ef17499d4 100644
--- a/ip/ip.c
+++ b/ip/ip.c
@@ -51,7 +51,7 @@ static void usage(void)
 "       ip [ -force ] -batch filename\n"
 "where  OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |\n"
 "                   tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |\n"
-"                   netns | l2tp | fou | tcp_metrics | token | netconf }\n"
+"                   netns | l2tp | fou | macsec | tcp_metrics | token | netconf }\n"
 "       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
 "                    -h[uman-readable] | -iec |\n"
 "                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n"
@@ -84,6 +84,7 @@ static const struct cmd {
 	{ "link",	do_iplink },
 	{ "l2tp",	do_ipl2tp },
 	{ "fou",	do_ipfou },
+	{ "macsec",	do_ipmacsec },
 	{ "tunnel",	do_iptunnel },
 	{ "tunl",	do_iptunnel },
 	{ "tuntap",	do_iptuntap },
diff --git a/ip/ip_common.h b/ip/ip_common.h
index b7361a8fc65d..3a7a2a9d0e00 100644
--- a/ip/ip_common.h
+++ b/ip/ip_common.h
@@ -43,6 +43,7 @@ int do_iptunnel(int argc, char **argv);
 int do_ip6tunnel(int argc, char **argv);
 int do_iptuntap(int argc, char **argv);
 int do_iplink(int argc, char **argv);
+int do_ipmacsec(int argc, char **argv);
 int do_ipmonitor(int argc, char **argv);
 int do_multiaddr(int argc, char **argv);
 int do_multiroute(int argc, char **argv);
diff --git a/ip/ipmacsec.c b/ip/ipmacsec.c
new file mode 100644
index 000000000000..70408222fcba
--- /dev/null
+++ b/ip/ipmacsec.c
@@ -0,0 +1,1231 @@
+/*
+ * ipmacsec.c		"ip macsec".
+ *
+ *		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.
+ *
+ * Authors:	Sabrina Dubroca <sd@queasysnail.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_ether.h>
+#include <linux/if_macsec.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ll_map.h"
+#include "libgenl.h"
+
+static const char *values_on_off[] = { "on", "off" };
+
+static const char *VALIDATE_STR[] = {
+	[MACSEC_VALIDATE_DISABLED] = "disabled",
+	[MACSEC_VALIDATE_CHECK] = "check",
+	[MACSEC_VALIDATE_STRICT] = "strict",
+};
+
+struct sci {
+	__u64 sci;
+	__u16 port;
+	char abuf[6];
+};
+
+struct sa_desc {
+	__u8 an;
+	__u32 pn;
+	__u32 key_id;
+	__u32 key_len;
+	__u8 key[MACSEC_MAX_KEY_LEN];
+	__u8 active;
+};
+
+struct cipher_args {
+	__u64 id;
+	__u8 icv_len;
+};
+
+struct txsc_desc {
+	int ifindex;
+	__u64 sci;
+	__be16 port;
+	struct cipher_args cipher;
+	__u32 flags;
+	__u32 flags_set;
+	__u32 window;
+	enum macsec_validation_type validate;
+	bool validate_set;
+	__u8 encoding_sa;
+};
+
+struct rxsc_desc {
+	int ifindex;
+	__u64 sci;
+	__u8 active;
+};
+
+#define MACSEC_BUFLEN 1024
+
+
+/* netlink socket */
+static struct rtnl_handle genl_rth;
+static int genl_family = -1;
+
+#define MACSEC_GENL_REQ(_req, _bufsiz, _cmd, _flags) \
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0, MACSEC_GENL_VERSION, _cmd, _flags)
+
+
+static void init_genl(void)
+{
+	if (genl_family >= 0)
+		return;
+
+	if (rtnl_open_byproto(&genl_rth, 0, NETLINK_GENERIC) < 0) {
+		fprintf(stderr, "Cannot open generic netlink socket\n");
+		exit(1);
+	}
+
+	genl_family = genl_resolve_family(&genl_rth, MACSEC_GENL_NAME);
+	if (genl_family < 0)
+		exit(1);
+}
+
+static void ipmacsec_usage(void)
+{
+	fprintf(stderr, "Usage: ip macsec add DEV tx sa { 0..3 } [ OPTS ] key ID KEY\n");
+	fprintf(stderr, "       ip macsec set DEV tx sa { 0..3 } [ OPTS ]\n");
+	fprintf(stderr, "       ip macsec del DEV tx sa { 0..3 }\n");
+	fprintf(stderr, "       ip macsec add DEV rx SCI [ on | off ]\n");
+	fprintf(stderr, "       ip macsec set DEV rx SCI [ on | off ]\n");
+	fprintf(stderr, "       ip macsec del DEV rx SCI\n");
+	fprintf(stderr, "       ip macsec add DEV rx SCI sa { 0..3 } [ OPTS ] key ID KEY\n");
+	fprintf(stderr, "       ip macsec set DEV rx SCI sa { 0..3 } [ OPTS ]\n");
+	fprintf(stderr, "       ip macsec del DEV rx SCI sa { 0..3 }\n");
+	fprintf(stderr, "       ip macsec show\n");
+	fprintf(stderr, "       ip macsec show DEV\n");
+	fprintf(stderr, "where  OPTS := [ pn <u32> ] [ on | off ]\n");
+	fprintf(stderr, "       SCI  := { sci <u64> | port <u16> address <lladdr> }\n");
+
+	exit(-1);
+}
+
+static int one_of(const char *msg, const char *realval, const char **list,
+		  size_t len, int *index)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (matches(realval, list[i]) == 0) {
+			*index = i;
+			return 0;
+		}
+	}
+
+	fprintf(stderr, "Error: argument of \"%s\" must be one of ", msg);
+	for (i = 0; i < len; i++)
+		fprintf(stderr, "\"%s\", ", list[i]);
+	fprintf(stderr, "not \"%s\"\n", realval);
+	return -1;
+}
+
+static int get_an(__u8 *val, const char *arg)
+{
+	int ret = get_u8(val, arg, 0);
+
+	if (ret)
+		return ret;
+
+	if (*val > 3)
+		return -1;
+
+	return 0;
+}
+
+static int get_sci(__u64 *sci, const char *arg)
+{
+	return get_u64(sci, arg, 16);
+}
+
+static int get_port(__be16 *port, const char *arg)
+{
+	return get_be16(port, arg, 10);
+}
+
+#define _STR(a) #a
+#define STR(a) _STR(a)
+
+static void get_icvlen(__u8 *icvlen, char *arg)
+{
+	int ret = get_u8(icvlen, arg, 10);
+
+	if (ret)
+		invarg("expected ICV length", arg);
+
+	if (*icvlen < MACSEC_MIN_ICV_LEN || *icvlen > MACSEC_MAX_ICV_LEN)
+		invarg("ICV length must be in the range {"
+		       STR(MACSEC_MIN_ICV_LEN) ".." STR(MACSEC_MAX_ICV_LEN)
+		       "}", arg);
+}
+
+static bool get_sa(int *argcp, char ***argvp, __u8 *an)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+
+	if (argc <= 0 || strcmp(*argv, "sa") != 0)
+		return false;
+
+	NEXT_ARG();
+	ret = get_an(an, *argv);
+	if (ret)
+		invarg("expected an { 0..3 }", *argv);
+	argc--; argv++;
+
+	*argvp = argv;
+	*argcp = argc;
+	return true;
+}
+
+static int parse_sa_args(int *argcp, char ***argvp, struct sa_desc *sa)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+	bool active_set = false;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "pn") == 0) {
+			if (sa->pn != 0)
+				duparg2("pn", "pn");
+			NEXT_ARG();
+			ret = get_u32(&sa->pn, *argv, 0);
+			if (ret)
+				invarg("expected pn", *argv);
+			if (sa->pn == 0)
+				invarg("expected pn != 0", *argv);
+		} else if (strcmp(*argv, "key") == 0) {
+			NEXT_ARG();
+			ret = get_u32(&sa->key_id, *argv, 0);
+			if (ret)
+				invarg("expected key id", *argv);
+			NEXT_ARG();
+			if (!hexstring_a2n(*argv, sa->key, MACSEC_MAX_KEY_LEN, &sa->key_len))
+				invarg("expected key", *argv);
+		} else if (strcmp(*argv, "on") == 0) {
+			if (active_set)
+				duparg2("on/off", "on");
+			sa->active = true;
+			active_set = true;
+		} else if (strcmp(*argv, "off") == 0) {
+			if (active_set)
+				duparg2("on/off", "off");
+			sa->active = false;
+			active_set = true;
+		} else {
+			fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+				*argv);
+			ipmacsec_usage();
+		}
+
+		argv++; argc--;
+	}
+
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+static __u64 make_sci(char *addr, __be16 port)
+{
+	__u64 sci;
+
+	memcpy(&sci, addr, ETH_ALEN);
+	memcpy(((char *)&sci) + ETH_ALEN, &port, sizeof(port));
+
+	return sci;
+}
+
+static bool sci_complete(bool sci, bool port, bool addr, bool port_only)
+{
+	return sci || (port && (addr || port_only));
+}
+
+static int get_sci_portaddr(struct sci *sci, int *argcp, char ***argvp,
+			    bool port_only, bool optional)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+	bool p = false, a = false, s = false;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "sci") == 0) {
+			if (p)
+				invarg("expected address", *argv);
+			if (a)
+				invarg("expected port", *argv);
+			NEXT_ARG();
+			ret = get_sci(&sci->sci, *argv);
+			if (ret)
+				invarg("expected sci", *argv);
+			s = true;
+		} else if (strcmp(*argv, "port") == 0) {
+			NEXT_ARG();
+			ret = get_port(&sci->port, *argv);
+			if (ret)
+				invarg("expected port", *argv);
+			if (sci->port == 0)
+				invarg("expected port != 0", *argv);
+			p = true;
+		} else if (strcmp(*argv, "address") == 0) {
+			NEXT_ARG();
+			ret = ll_addr_a2n(sci->abuf, sizeof(sci->abuf), *argv);
+			if (ret < 0)
+				invarg("expected lladdr", *argv);
+			a = true;
+		} else if (optional) {
+			break;
+		} else {
+			invarg("expected sci, port, or address", *argv);
+		}
+
+		argv++; argc--;
+
+		if (sci_complete(s, p, a, port_only))
+			break;
+	}
+
+	if (!optional && !sci_complete(s, p, a, port_only))
+		return -1;
+
+	if (p && a)
+		sci->sci = make_sci(sci->abuf, sci->port);
+
+	*argvp = argv;
+	*argcp = argc;
+
+	return p || a || s;
+}
+
+static bool parse_rxsci(int *argcp, char ***argvp, struct rxsc_desc *rxsc,
+			struct sa_desc *rxsa)
+{
+	struct sci sci = { 0 };
+
+	if (*argcp == 0 ||
+	    get_sci_portaddr(&sci, argcp, argvp, false, false) < 0) {
+		fprintf(stderr, "expected sci\n");
+		ipmacsec_usage();
+	}
+
+	rxsc->sci = sci.sci;
+
+	return get_sa(argcp, argvp, &rxsa->an);
+}
+
+static int parse_rxsci_args(int *argcp, char ***argvp, struct rxsc_desc *rxsc)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	bool active_set = false;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "on") == 0) {
+			if (active_set)
+				duparg2("on/off", "on");
+			rxsc->active = true;
+			active_set = true;
+		} else if (strcmp(*argv, "off") == 0) {
+			if (active_set)
+				duparg2("on/off", "off");
+			rxsc->active = false;
+			active_set = true;
+		} else {
+			fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+				*argv);
+			ipmacsec_usage();
+		}
+
+		argv++; argc--;
+	}
+
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+enum cmd {
+	CMD_ADD,
+	CMD_DEL,
+	CMD_UPD,
+	__CMD_MAX
+};
+
+static const enum macsec_nl_commands macsec_commands[__CMD_MAX][2][2] = {
+	[CMD_ADD] = {
+		[0] = {-1, MACSEC_CMD_ADD_RXSC},
+		[1] = {MACSEC_CMD_ADD_TXSA, MACSEC_CMD_ADD_RXSA},
+	},
+	[CMD_UPD] = {
+		[0] = {-1, MACSEC_CMD_UPD_RXSC},
+		[1] = {MACSEC_CMD_UPD_TXSA, MACSEC_CMD_UPD_RXSA},
+	},
+	[CMD_DEL] = {
+		[0] = {-1, MACSEC_CMD_DEL_RXSC},
+		[1] = {MACSEC_CMD_DEL_TXSA, MACSEC_CMD_DEL_RXSA},
+	},
+};
+
+static int do_modify_nl(enum cmd c, enum macsec_nl_commands cmd, int ifindex,
+			struct rxsc_desc *rxsc, struct sa_desc *sa)
+{
+	struct rtattr *attr_sa;
+
+	MACSEC_GENL_REQ(req, MACSEC_BUFLEN, cmd, NLM_F_REQUEST);
+
+	addattr32(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_IFINDEX, ifindex);
+	if (rxsc) {
+		struct rtattr *attr_rxsc = addattr_nest(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_RXSC_CONFIG);
+
+		addattr64(&req.n, MACSEC_BUFLEN, MACSEC_RXSC_ATTR_SCI, rxsc->sci);
+		if (c != CMD_DEL && rxsc->active != 0xff)
+			addattr8(&req.n, MACSEC_BUFLEN, MACSEC_RXSC_ATTR_ACTIVE, rxsc->active);
+
+		addattr_nest_end(&req.n, attr_rxsc);
+	}
+
+	if (sa->an == 0xff)
+		goto talk;
+
+	attr_sa = addattr_nest(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_SA_CONFIG);
+
+	addattr8(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_AN, sa->an);
+
+	if (c != CMD_DEL) {
+		if (sa->pn)
+			addattr32(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_PN,
+				  sa->pn);
+
+		if (sa->key_len) {
+			addattr64(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_KEYID,
+				  sa->key_id);
+			addattr_l(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_KEY,
+				  sa->key, sa->key_len);
+		}
+
+		if (sa->active != 0xff)
+			addattr8(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_ACTIVE, sa->active);
+	}
+
+	addattr_nest_end(&req.n, attr_sa);
+
+talk:
+	if (rtnl_talk(&genl_rth, &req.n, NULL, 0) < 0)
+		return -2;
+
+	return 0;
+}
+
+static bool check_sa_args(enum cmd c, struct sa_desc *sa)
+{
+	if (c == CMD_ADD) {
+		if (!sa->key_len) {
+			fprintf(stderr, "cannot create SA without key\n");
+			return -1;
+		}
+
+		if (sa->pn == 0) {
+			fprintf(stderr, "must specify a packet number != 0\n");
+			return -1;
+		}
+	} else if (c == CMD_UPD) {
+		if (sa->key_len) {
+			fprintf(stderr, "cannot change key on SA\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int do_modify_txsa(enum cmd c, int argc, char **argv, int ifindex)
+{
+	struct sa_desc txsa = {0};
+	enum macsec_nl_commands cmd;
+
+	txsa.an = 0xff;
+	txsa.active = 0xff;
+
+	if (argc == 0 || !get_sa(&argc, &argv, &txsa.an))
+		ipmacsec_usage();
+
+	if (c == CMD_DEL)
+		goto modify;
+
+	if (parse_sa_args(&argc, &argv, &txsa))
+		return -1;
+
+	if (check_sa_args(c, &txsa))
+		return -1;
+
+modify:
+	cmd = macsec_commands[c][1][0];
+	return do_modify_nl(c, cmd, ifindex, NULL, &txsa);
+}
+
+static int do_modify_rxsci(enum cmd c, int argc, char **argv, int ifindex)
+{
+	struct rxsc_desc rxsc = {0};
+	struct sa_desc rxsa = {0};
+	bool sa_set;
+	enum macsec_nl_commands cmd;
+
+	rxsc.ifindex = ifindex;
+	rxsc.active = 0xff;
+	rxsa.an = 0xff;
+	rxsa.active = 0xff;
+
+	sa_set = parse_rxsci(&argc, &argv, &rxsc, &rxsa);
+
+	if (c == CMD_DEL)
+		goto modify;
+
+	if (sa_set && (parse_sa_args(&argc, &argv, &rxsa) ||
+		       check_sa_args(c, &rxsa)))
+		return -1;
+	if (!sa_set && parse_rxsci_args(&argc, &argv, &rxsc))
+		return -1;
+
+modify:
+	cmd = macsec_commands[c][sa_set][1];
+	return do_modify_nl(c, cmd, rxsc.ifindex, &rxsc, &rxsa);
+}
+
+static int do_modify(enum cmd c, int argc, char **argv)
+{
+	int ifindex;
+
+	if (argc == 0)
+		ipmacsec_usage();
+
+	ifindex = ll_name_to_index(*argv);
+	if (!ifindex) {
+		fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+		return -1;
+	}
+	argc--; argv++;
+
+	if (argc == 0)
+		ipmacsec_usage();
+
+	if (strcmp(*argv, "tx") == 0)
+		return do_modify_txsa(c, argc-1, argv+1, ifindex);
+	if (strcmp(*argv, "rx") == 0)
+		return do_modify_rxsci(c, argc-1, argv+1, ifindex);
+
+	ipmacsec_usage();
+	return -1;
+}
+
+/* dump/show */
+static struct {
+	int ifindex;
+	__u64 sci;
+} filter;
+
+static int validate_dump(struct rtattr **attrs)
+{
+	return attrs[MACSEC_ATTR_IFINDEX] && attrs[MACSEC_ATTR_SECY] &&
+	       attrs[MACSEC_ATTR_TXSA_LIST] && attrs[MACSEC_ATTR_RXSC_LIST] &&
+	       attrs[MACSEC_ATTR_TXSC_STATS] && attrs[MACSEC_ATTR_SECY_STATS];
+
+}
+
+static int validate_secy_dump(struct rtattr **attrs)
+{
+	return attrs[MACSEC_SECY_ATTR_SCI] &&
+	       attrs[MACSEC_SECY_ATTR_ENCODING_SA] &&
+	       attrs[MACSEC_SECY_ATTR_CIPHER_SUITE] &&
+	       attrs[MACSEC_SECY_ATTR_ICV_LEN] &&
+	       attrs[MACSEC_SECY_ATTR_PROTECT] &&
+	       attrs[MACSEC_SECY_ATTR_REPLAY] &&
+	       attrs[MACSEC_SECY_ATTR_OPER] &&
+	       attrs[MACSEC_SECY_ATTR_VALIDATE] &&
+	       attrs[MACSEC_SECY_ATTR_ENCRYPT] &&
+	       attrs[MACSEC_SECY_ATTR_INC_SCI] &&
+	       attrs[MACSEC_SECY_ATTR_ES] &&
+	       attrs[MACSEC_SECY_ATTR_SCB];
+}
+
+static void print_flag(FILE *f, struct rtattr *attrs[], const char *desc,
+		       int field)
+{
+	if (attrs[field])
+		fprintf(f, "%s %s ", desc,
+			rta_getattr_u8(attrs[field]) ? "on" : "off");
+}
+
+#define DEFAULT_CIPHER_NAME "GCM-AES-128"
+
+static const char *cs_id_to_name(__u64 cid)
+{
+	switch (cid) {
+	case DEFAULT_CIPHER_ID:
+	case DEFAULT_CIPHER_ALT:
+		return DEFAULT_CIPHER_NAME;
+	default:
+		return "(unknown)";
+	}
+}
+
+static void print_cipher_suite(const char *prefix, __u64 cid, __u8 icv_len)
+{
+	printf("%scipher suite: %s, using ICV length %d\n", prefix,
+	       cs_id_to_name(cid), icv_len);
+}
+
+static void print_attrs(const char *prefix, struct rtattr *attrs[])
+{
+	print_flag(stdout, attrs, "protect", MACSEC_SECY_ATTR_PROTECT);
+
+	if (attrs[MACSEC_SECY_ATTR_VALIDATE]) {
+		printf("validate %s ",
+		       VALIDATE_STR[rta_getattr_u8(attrs[MACSEC_SECY_ATTR_VALIDATE])]);
+	}
+
+	print_flag(stdout, attrs, "sc", MACSEC_RXSC_ATTR_ACTIVE);
+	print_flag(stdout, attrs, "sa", MACSEC_SA_ATTR_ACTIVE);
+	print_flag(stdout, attrs, "encrypt", MACSEC_SECY_ATTR_ENCRYPT);
+	print_flag(stdout, attrs, "send_sci", MACSEC_SECY_ATTR_INC_SCI);
+	print_flag(stdout, attrs, "end_station", MACSEC_SECY_ATTR_ES);
+	print_flag(stdout, attrs, "scb", MACSEC_SECY_ATTR_SCB);
+
+	print_flag(stdout, attrs, "replay", MACSEC_SECY_ATTR_REPLAY);
+	if (attrs[MACSEC_SECY_ATTR_WINDOW]) {
+		printf("window %d ",
+		       rta_getattr_u32(attrs[MACSEC_SECY_ATTR_WINDOW]));
+	}
+
+	if (attrs[MACSEC_SECY_ATTR_CIPHER_SUITE] && attrs[MACSEC_SECY_ATTR_ICV_LEN]) {
+		printf("\n");
+		print_cipher_suite(prefix,
+			rta_getattr_u64(attrs[MACSEC_SECY_ATTR_CIPHER_SUITE]),
+			rta_getattr_u8(attrs[MACSEC_SECY_ATTR_ICV_LEN]));
+	}
+
+}
+
+static void print_one_stat(const char **names, struct rtattr **attr, int idx, bool long_stat)
+{
+	int pad = strlen(names[idx]) + 1;
+
+	if (attr[idx]) {
+		if (long_stat)
+			printf("%*llu", pad, rta_getattr_u64(attr[idx]));
+		else
+			printf("%*u", pad, rta_getattr_u32(attr[idx]));
+	} else {
+		printf("%*c", pad, '-');
+	}
+}
+
+static const char *txsc_stats_names[] = {
+	[MACSEC_TXSC_STATS_ATTR_OUT_PKTS_PROTECTED] = "OutOctetsProtected",
+	[MACSEC_TXSC_STATS_ATTR_OUT_PKTS_ENCRYPTED] = "OutOctetsEncrypted",
+	[MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_PROTECTED] = "OutPktsProtected",
+	[MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_ENCRYPTED] = "OutPktsEncrypted",
+};
+
+static void print_txsc_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_TXSC_STATS_ATTR_MAX + 1];
+	int i;
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_TXSC_STATS_ATTR_MAX + 1, attr);
+	printf("%sstats:", prefix);
+
+	for (i = 1; i < NUM_MACSEC_TXSC_STATS_ATTR; i++)
+		printf(" %s", txsc_stats_names[i]);
+
+	printf("\n%s      ", prefix);
+
+	for (i = 1; i < NUM_MACSEC_TXSC_STATS_ATTR; i++)
+		print_one_stat(txsc_stats_names, stats, i, true);
+
+	printf("\n");
+}
+
+static const char *secy_stats_names[] = {
+	[MACSEC_SECY_STATS_ATTR_OUT_PKTS_UNTAGGED] = "OutPktsUntagged",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_UNTAGGED] = "InPktsUntagged",
+	[MACSEC_SECY_STATS_ATTR_OUT_PKTS_TOO_LONG] = "OutPktsTooLong",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_TAG] = "InPktsNoTag",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_BAD_TAG] = "InPktsBadTag",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_UNKNOWN_SCI] = "InPktsUnknownSCI",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_SCI] = "InPktsNoSCI",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_OVERRUN] = "InPktsOverrun",
+};
+
+static void print_secy_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_SECY_STATS_ATTR_MAX + 1];
+	int i;
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_SECY_STATS_ATTR_MAX + 1, attr);
+	printf("%sstats:", prefix);
+
+	for (i = 1; i < NUM_MACSEC_SECY_STATS_ATTR; i++)
+		printf(" %s", secy_stats_names[i]);
+
+	printf("\n%s      ", prefix);
+
+	for (i = 1; i < NUM_MACSEC_SECY_STATS_ATTR; i++)
+		print_one_stat(secy_stats_names, stats, i, true);
+
+	printf("\n");
+}
+
+static const char *rxsa_stats_names[] = {
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_OK] = "InPktsOK",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_INVALID] = "InPktsInvalid",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_VALID] = "InPktsNotValid",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_USING_SA] = "InPktsNotUsingSA",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA] = "InPktsUnusedSA",
+};
+
+static void print_rxsa_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_SA_STATS_ATTR_MAX + 1];
+	int i;
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_SA_STATS_ATTR_MAX + 1, attr);
+	printf("%s%s  ", prefix, prefix);
+
+	for (i = 1; i <= MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA; i++)
+		printf(" %s", rxsa_stats_names[i]);
+
+	printf("\n%s%s  ", prefix, prefix);
+
+	for (i = 1; i <= MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA; i++)
+		print_one_stat(rxsa_stats_names, stats, i, false);
+
+	printf("\n");
+}
+
+static const char *txsa_stats_names[] = {
+	[MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED] = "OutPktsProtected",
+	[MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED] = "OutPktsEncrypted",
+};
+
+static void print_txsa_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_SA_STATS_ATTR_MAX + 1];
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_SA_STATS_ATTR_MAX + 1, attr);
+	printf("%s%s   %s %s\n", prefix, prefix,
+	       txsa_stats_names[MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED],
+	       txsa_stats_names[MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED]);
+	printf("%s%s  ", prefix, prefix);
+
+	print_one_stat(txsa_stats_names, stats, MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED, false);
+	print_one_stat(txsa_stats_names, stats, MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED, false);
+	printf("\n");
+}
+
+static void print_tx_sc(const char *prefix, __u64 sci, __u8 encoding_sa,
+			struct rtattr *txsc_stats, struct rtattr *secy_stats,
+			struct rtattr *sa)
+{
+	struct rtattr *sa_attr[MACSEC_SA_ATTR_MAX + 1];
+	struct rtattr *a;
+	int rem;
+
+	printf("%sTXSC: %016llx on SA %d\n", prefix, sci, encoding_sa);
+	print_secy_stats(prefix, secy_stats);
+	print_txsc_stats(prefix, txsc_stats);
+
+	rem = RTA_PAYLOAD(sa);
+	for (a = RTA_DATA(sa); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+		parse_rtattr_nested(sa_attr, MACSEC_SA_ATTR_MAX + 1, a);
+		printf("%s%s%d: PN %u, state %s, key %llu\n", prefix, prefix,
+			rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_AN]),
+			rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_PN]),
+			rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_ACTIVE]) ? "on" :
+									 "off",
+			rta_getattr_u64(sa_attr[MACSEC_SA_ATTR_KEYID]));
+		print_txsa_stats(prefix, sa_attr[MACSEC_SA_ATTR_STATS]);
+	}
+}
+
+static const char *rxsc_stats_names[] = {
+	[MACSEC_RXSC_STATS_ATTR_IN_OCTETS_VALIDATED] = "InOctetsValidated",
+	[MACSEC_RXSC_STATS_ATTR_IN_OCTETS_DECRYPTED] = "InOctetsDecrypted",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNCHECKED] = "InPktsUnchecked",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_DELAYED] = "InPktsDelayed",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_OK] = "InPktsOK",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_INVALID] = "InPktsInvalid",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_LATE] = "InPktsLate",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_VALID] = "InPktsNotValid",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_USING_SA] = "InPktsNotUsingSA",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNUSED_SA] = "InPktsUnusedSA",
+};
+
+static void print_rxsc_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_RXSC_STATS_ATTR_MAX + 1];
+	int i;
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_RXSC_STATS_ATTR_MAX + 1, attr);
+	printf("%sstats:", prefix);
+	for (i = 1; i < NUM_MACSEC_RXSC_STATS_ATTR; i++)
+		printf(" %s", rxsc_stats_names[i]);
+
+	printf("\n%s      ", prefix);
+
+	for (i = 1; i < NUM_MACSEC_RXSC_STATS_ATTR; i++)
+		print_one_stat(rxsc_stats_names, stats, i, true);
+
+	printf("\n");
+}
+
+static void print_rx_sc(const char *prefix, __u64 sci, __u8 active, struct rtattr *rxsc_stats, struct rtattr *sa)
+{
+	struct rtattr *sa_attr[MACSEC_SA_ATTR_MAX + 1];
+	struct rtattr *a;
+	int rem;
+
+	printf("%sRXSC: %016llx, state %s\n", prefix, sci, active ? "on" :
+								    "off");
+	print_rxsc_stats(prefix, rxsc_stats);
+
+	rem = RTA_PAYLOAD(sa);
+	for (a = RTA_DATA(sa); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+		parse_rtattr_nested(sa_attr, MACSEC_SA_ATTR_MAX + 1, a);
+		printf("%s%s%d: PN %u, state %s, key %llu\n", prefix, prefix,
+			rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_AN]),
+			rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_PN]),
+			rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_ACTIVE]) ? "on" :
+									 "off",
+			rta_getattr_u64(sa_attr[MACSEC_SA_ATTR_KEYID]));
+		print_rxsa_stats(prefix, sa_attr[MACSEC_SA_ATTR_STATS]);
+	}
+}
+
+static int process(const struct sockaddr_nl *who, struct nlmsghdr *n,
+		   void *arg)
+{
+	struct genlmsghdr *ghdr;
+	struct rtattr *attrs[MACSEC_ATTR_MAX + 1], *sc, *c;
+	struct rtattr *attrs_secy[MACSEC_SECY_ATTR_MAX + 1];
+	int len = n->nlmsg_len;
+	int ifindex;
+	__u64 sci;
+	__u8 encoding_sa;
+	int rem;
+
+	if (n->nlmsg_type != genl_family)
+		return -1;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+	if (ghdr->cmd != MACSEC_CMD_GET_TXSC)
+		return 0;
+
+	parse_rtattr(attrs, MACSEC_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+	if (!validate_dump(attrs)) {
+		printf("incomplete dump message\n");
+		return -1;
+	}
+
+	ifindex = rta_getattr_u32(attrs[MACSEC_ATTR_IFINDEX]);
+	parse_rtattr_nested(attrs_secy, MACSEC_SECY_ATTR_MAX + 1, attrs[MACSEC_ATTR_SECY]);
+
+	if (!validate_secy_dump(attrs_secy)) {
+		printf("incomplete dump message\n");
+		return -1;
+	}
+
+	sci = rta_getattr_u64(attrs_secy[MACSEC_SECY_ATTR_SCI]);
+	encoding_sa = rta_getattr_u8(attrs_secy[MACSEC_SECY_ATTR_ENCODING_SA]);
+
+	if (filter.ifindex && ifindex != filter.ifindex)
+		return 0;
+
+	if (filter.sci && sci != filter.sci)
+		return 0;
+
+	printf("%d: %s: ", ifindex, ll_index_to_name(ifindex));
+	print_attrs("    ", attrs_secy);
+
+	print_tx_sc("    ", sci, encoding_sa, attrs[MACSEC_ATTR_TXSC_STATS],
+		    attrs[MACSEC_ATTR_SECY_STATS], attrs[MACSEC_ATTR_TXSA_LIST]);
+
+	if (!attrs[MACSEC_ATTR_RXSC_LIST])
+		return 0;
+
+	sc = attrs[MACSEC_ATTR_RXSC_LIST];
+	rem = RTA_PAYLOAD(sc);
+	for (c = RTA_DATA(sc); RTA_OK(c, rem); c = RTA_NEXT(c, rem)) {
+		struct rtattr *sc_attr[MACSEC_RXSC_ATTR_MAX + 1];
+
+		parse_rtattr_nested(sc_attr, MACSEC_RXSC_ATTR_MAX + 1, c);
+		print_rx_sc("    ",
+			    rta_getattr_u64(sc_attr[MACSEC_RXSC_ATTR_SCI]),
+			    rta_getattr_u32(sc_attr[MACSEC_RXSC_ATTR_ACTIVE]),
+			    sc_attr[MACSEC_RXSC_ATTR_STATS],
+			    sc_attr[MACSEC_RXSC_ATTR_SA_LIST]);
+	}
+
+	return 0;
+}
+
+static int do_dump(int ifindex)
+{
+	MACSEC_GENL_REQ(req, MACSEC_BUFLEN, MACSEC_CMD_GET_TXSC,
+			NLM_F_REQUEST | NLM_F_DUMP);
+
+	memset(&filter, 0, sizeof(filter));
+	filter.ifindex = ifindex;
+
+	req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+	if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) {
+		perror("Failed to send dump request");
+		exit(1);
+	}
+
+	if (rtnl_dump_filter(&genl_rth, process, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+
+	return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+	int ifindex;
+
+	if (argc == 0)
+		return do_dump(0);
+
+	ifindex = ll_name_to_index(*argv);
+	if (ifindex == 0) {
+		fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+		return -1;
+	}
+
+	argc--, argv++;
+	if (argc == 0)
+		return do_dump(ifindex);
+
+	ipmacsec_usage();
+	return -1;
+}
+
+int do_ipmacsec(int argc, char **argv)
+{
+	init_genl();
+
+	if (argc < 1)
+		ipmacsec_usage();
+
+	if (matches(*argv, "help") == 0)
+		ipmacsec_usage();
+
+	if (matches(*argv, "show") == 0)
+		return do_show(argc-1, argv+1);
+
+	if (matches(*argv, "add") == 0)
+		return do_modify(CMD_ADD, argc-1, argv+1);
+	if (matches(*argv, "set") == 0)
+		return do_modify(CMD_UPD, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return do_modify(CMD_DEL, argc-1, argv+1);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip macsec help\".\n",
+		*argv);
+	exit(-1);
+}
+
+/* device creation */
+static void macsec_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_MACSEC_SCI])
+		fprintf(f, "sci %016llx ", rta_getattr_u64(tb[IFLA_MACSEC_SCI]));
+
+	print_flag(f, tb, "protect", IFLA_MACSEC_PROTECT);
+
+	if (tb[IFLA_MACSEC_CIPHER_SUITE])
+		fprintf(f, "cipher %s ", cs_id_to_name(rta_getattr_u64(tb[IFLA_MACSEC_CIPHER_SUITE])));
+
+	if (tb[IFLA_MACSEC_ICV_LEN])
+		fprintf(f, "icvlen %d ", rta_getattr_u8(tb[IFLA_MACSEC_ICV_LEN]));
+
+	if (tb[IFLA_MACSEC_ENCODING_SA])
+		fprintf(f, "encodingsa %d ", rta_getattr_u8(tb[IFLA_MACSEC_ENCODING_SA]));
+
+	if (tb[IFLA_MACSEC_VALIDATION])
+		fprintf(f, "validate %s ", VALIDATE_STR[rta_getattr_u8(tb[IFLA_MACSEC_VALIDATION])]);
+
+	print_flag(f, tb, "encrypt", IFLA_MACSEC_ENCRYPT);
+	print_flag(f, tb, "send_sci", IFLA_MACSEC_INC_SCI);
+	print_flag(f, tb, "end_station", IFLA_MACSEC_ES);
+	print_flag(f, tb, "scb", IFLA_MACSEC_SCB);
+
+	print_flag(f, tb, "replay", IFLA_MACSEC_REPLAY_PROTECT);
+	if (tb[IFLA_MACSEC_WINDOW])
+		fprintf(f, "window %d ", rta_getattr_u32(tb[IFLA_MACSEC_WINDOW]));
+}
+
+
+static int do_cipher_suite(struct cipher_args *cipher, int *argcp,
+			   char ***argvp)
+{
+	char **argv = *argvp;
+	int argc = *argcp;
+
+	if (argc == 0)
+		return -1;
+
+	if (strcmp(*argv, "default") == 0 ||
+	    strcmp(*argv, "gcm-aes-128") == 0 ||
+	    strcmp(*argv, "GCM-AES-128") == 0)
+		cipher->id = DEFAULT_CIPHER_ID;
+	NEXT_ARG();
+
+	if (strcmp(*argv, "icvlen") == 0) {
+		NEXT_ARG();
+		if (cipher->icv_len != 0)
+			duparg2("icvlen", "icvlen");
+		get_icvlen(&cipher->icv_len, *argv);
+	}
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static bool check_txsc_flags(bool es, bool scb, bool sci)
+{
+	if (sci && (es || scb))
+		return false;
+	if (es && scb)
+		return false;
+	return true;
+}
+
+static void usage(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... macsec [ port PORT | sci SCI ]\n"
+		"                  [ cipher CIPHER_SUITE ]\n"
+		"                  [ encrypt { on | off } ]\n"
+		"                  [ send_sci { on | off } ]\n"
+		"                  [ end_station { on | off } ]\n"
+		"                  [ scb { on | off } ]\n"
+		"                  [ protect { on | off } ]\n"
+		"                  [ replay { on | off} window { 0..2^32-1 } ]\n"
+		"                  [ validate { strict | check | disabled } ]\n"
+		"                  [ encodingsa { 0..3 } ]\n"
+		);
+	fprintf(f, "CIPHER_SUITE := [ default = gcm-aes-128 ] icvlen { 8..32 }\n");
+}
+
+static int macsec_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *hdr)
+{
+	int ret;
+	__u8 encoding_sa = 0xff;
+	__u32 window = -1;
+	struct cipher_args cipher = {0};
+	enum macsec_validation_type validate;
+	bool es = false, scb = false, send_sci = false;
+	int replay_protect = -1;
+	struct sci sci = { 0 };
+
+	ret = get_sci_portaddr(&sci, &argc, &argv, true, true);
+	if (ret < 0) {
+		fprintf(stderr, "expected sci\n");
+		return -1;
+	}
+
+	if (ret > 0) {
+		if (sci.sci)
+			addattr_l(hdr, MACSEC_BUFLEN, IFLA_MACSEC_SCI,
+				  &sci.sci, sizeof(sci.sci));
+		else
+			addattr_l(hdr, MACSEC_BUFLEN, IFLA_MACSEC_PORT,
+				  &sci.port, sizeof(sci.port));
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "cipher") == 0) {
+			if (cipher.id)
+				duparg2("cipher", "cipher");
+			NEXT_ARG();
+			if (do_cipher_suite(&cipher, &argc, &argv))
+				return -1;
+		} else if (strcmp(*argv, "encrypt") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("encrypt", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_ENCRYPT, !i);
+		} else if (strcmp(*argv, "send_sci") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("send_sci", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			send_sci = !i;
+			addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_INC_SCI, send_sci);
+		} else if (strcmp(*argv, "end_station") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("end_station", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			es = !i;
+			addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_ES, es);
+		} else if (strcmp(*argv, "scb") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("scb", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			scb = !i;
+			addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_SCB, scb);
+		} else if (strcmp(*argv, "protect") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("protect", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_PROTECT, !i);
+		} else if (strcmp(*argv, "replay") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("replay", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			replay_protect = !i;
+		} else if (strcmp(*argv, "window") == 0) {
+			NEXT_ARG();
+			ret = get_u32(&window, *argv, 0);
+			if (ret)
+				invarg("expected replay window size", *argv);
+		} else if (strcmp(*argv, "validate") == 0) {
+			NEXT_ARG();
+			ret = one_of("validate", *argv,
+				     VALIDATE_STR, ARRAY_SIZE(VALIDATE_STR),
+				     (int *)&validate);
+			if (ret != 0)
+				return ret;
+			addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_VALIDATION,
+				 validate);
+		} else if (strcmp(*argv, "encodingsa") == 0) {
+			if (encoding_sa != 0xff)
+				duparg2("encodingsa", "encodingsa");
+			NEXT_ARG();
+			ret = get_an(&encoding_sa, *argv);
+			if (ret)
+				invarg("expected an { 0..3 }", *argv);
+		} else {
+			fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+				*argv);
+			usage(stderr);
+			return -1;
+		}
+
+		argv++; argc--;
+	}
+
+	if (!check_txsc_flags(es, scb, send_sci)) {
+		fprintf(stderr, "invalid combination of send_sci/end_station/scb\n");
+		return -1;
+	}
+
+	if (window != -1 && replay_protect == -1) {
+		fprintf(stderr, "replay window set, but replay protection not enabled. did you mean 'replay on window %u'?\n", window);
+		return -1;
+	} else if (window == -1 && replay_protect != -1) {
+		fprintf(stderr, "replay protection enabled, but no window set. did you mean 'replay on window VALUE'?\n");
+		return -1;
+	}
+
+	if (cipher.id) {
+		addattr_l(hdr, MACSEC_BUFLEN, IFLA_MACSEC_CIPHER_SUITE,
+			  &cipher.id, sizeof(cipher.id));
+		addattr_l(hdr, MACSEC_BUFLEN, IFLA_MACSEC_ICV_LEN,
+			  &cipher.icv_len, sizeof(cipher.icv_len));
+	}
+
+	if (replay_protect != -1) {
+		addattr32(hdr, MACSEC_BUFLEN, IFLA_MACSEC_WINDOW, window);
+		addattr8(hdr, MACSEC_BUFLEN, IFLA_MACSEC_REPLAY_PROTECT,
+			 replay_protect);
+	}
+
+	if (encoding_sa != 0xff) {
+		addattr_l(hdr, MACSEC_BUFLEN, IFLA_MACSEC_ENCODING_SA,
+			  &encoding_sa, sizeof(encoding_sa));
+	}
+
+	return 0;
+}
+
+static void macsec_print_help(struct link_util *lu, int argc, char **argv,
+			      FILE *f)
+{
+	usage(f);
+}
+
+struct link_util macsec_link_util = {
+	.id = "macsec",
+	.maxattr = IFLA_MACSEC_MAX,
+	.parse_opt = macsec_parse_opt,
+	.print_help = macsec_print_help,
+	.print_opt = macsec_print_opt,
+	.slave = false,
+};
diff --git a/man/man8/Makefile b/man/man8/Makefile
index d3fdf66ab11d..929826ecb56c 100644
--- a/man/man8/Makefile
+++ b/man/man8/Makefile
@@ -7,7 +7,7 @@ MAN8PAGES = $(TARGETS) ip.8 arpd.8 lnstat.8 routel.8 rtacct.8 rtmon.8 rtpr.8 ss.
 	tc-mqprio.8 tc-netem.8 tc-pfifo.8 tc-pfifo_fast.8 tc-prio.8 tc-red.8 \
 	tc-sfb.8 tc-sfq.8 tc-stab.8 tc-tbf.8 \
 	bridge.8 rtstat.8 ctstat.8 nstat.8 routef.8 \
-	ip-addrlabel.8 ip-fou.8 ip-gue.8 ip-l2tp.8 \
+	ip-addrlabel.8 ip-fou.8 ip-gue.8 ip-l2tp.8 ip-macsec.8 \
 	ip-maddress.8 ip-monitor.8 ip-mroute.8 ip-neighbour.8 \
 	ip-netns.8 ip-ntable.8 ip-rule.8 ip-tunnel.8 ip-xfrm.8 \
 	ip-tcp_metrics.8 ip-netconf.8 ip-token.8 \
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index 984fb2eb0d63..6b443b27b2af 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -258,6 +258,9 @@ Link types:
 .sp
 .BR geneve
 - GEneric NEtwork Virtualization Encapsulation
+.sp
+.BR macsec
+- Interface for IEEE 802.1AE MAC Security (MACsec)
 .in -8
 
 .TP
@@ -846,6 +849,137 @@ forces the underlying interface into promiscuous mode. Passing the
 using standard tools.
 .in -8
 
+.TP
+MACsec Type Support
+For a link of type
+.I MACsec
+the following additional arguments are supported:
+
+.BI "ip link add link " DEVICE " name " NAME
+.BI type " macsec "
+.R " [ "
+.BI port " PORT "
+.R " | "
+.BI sci " SCI "
+.R " ] [ "
+.BI cipher " CIPHER_SUITE "
+.R " ] ["
+.BI encrypt
+.R " { "
+.BI "on "
+.R " |"
+.BI "off "
+.R " } "
+.R " ] ["
+.BI send_sci
+.R " { "
+.BI "on "
+.R " |"
+.BI "off "
+.R " } "
+.R " ] ["
+.BI es
+.R " { "
+.BI "on "
+.R " |"
+.BI "off "
+.R " } "
+.R " ] ["
+.BI scb
+.R " { "
+.BI "on "
+.R " |"
+.BI "off "
+.R " } "
+.R " ] ["
+.BI protect
+.R " { "
+.BI "on "
+.R " |"
+.BI "off "
+.R " } "
+.R " ] ["
+.BI replay
+.R " { "
+.BI "on "
+.R " |"
+.BI "off "
+.R " } "
+.BI window
+.R " { "
+.BI "0..2^32-1 "
+.R " } "
+.R " ] ["
+.BI validate
+.R " { "
+.BI "strict "
+.R " |"
+.BI "check "
+.R " |"
+.BI "disabled "
+.R " } "
+.R " ] ["
+.BI encoding
+.R " {"
+.BI "0..3 "
+.R " } "
+.R " ]"
+
+.in +8
+.sp
+.BI  port " PORT "
+- sets the port number for this MACsec device.
+
+.sp
+.BI sci " SCI "
+- sets the SCI for this MACsec device.
+
+.sp
+.BI cipher " CIPHER_SUITE "
+- defines the cipher suite to use.
+
+.sp
+.BR "encrypt on " or " encrypt off"
+- switches between authenticated encryption, or authenticity mode only.
+
+.sp
+.BR "send_sci on " or " send_sci off"
+- specifies whether the SCI is included in every packet, or only when it is necessary.
+
+.sp
+.BR "es on " or " es off"
+- sets the End Station bit.
+
+.sp
+.BR "scb on " or " scb off"
+- sets the Single Copy Broadcast bit.
+
+.sp
+.BR "protect on " or " protect off"
+- enables MACsec protection on the device.
+
+.sp
+.BR "replay on " or " replay off"
+- enables replay protection on the device.
+
+.in +8
+
+.sp
+.BI window " SIZE "
+- sets the size of the replay window.
+
+.in -8
+
+.sp
+.BR "validate strict " or " validate check " or " validate disabled"
+- sets the validation mode on the device.
+
+.sp
+.BI encoding " AN "
+- sets the active secure association for transmission.
+
+.in -8
+
 .SS ip link delete - delete virtual link
 
 .TP
diff --git a/man/man8/ip-macsec.8 b/man/man8/ip-macsec.8
new file mode 100644
index 000000000000..bd150d3b16eb
--- /dev/null
+++ b/man/man8/ip-macsec.8
@@ -0,0 +1,110 @@
+.TH IP\-MACSEC 8 "07 Mar 2016" "iproute" "Linux"
+.SH "NAME"
+ip-macsec \- MACsec device configuration
+.SH "SYNOPSIS"
+.BI "ip link add link " DEVICE " name " NAME
+.BR type " macsec "
+.R " [ "
+.R " [ "
+.B cipher " { " default " | " gcm-aes-128 " } "
+.R " ] "
+.B icvlen
+.I ICVLEN
+.R " ] [ "
+.B encrypt " { " on " | " off " } "
+.R " ] [ "
+.B send_sci " { " on " | " off " } "
+.R " ] [ "
+.B end_station " { " on " | " off " } "
+.R " ] [ "
+.B scb " { " on " | " off " } "
+.R " ] [ "
+.B protect " { " on " | " off " } "
+.R " ] [ "
+.B replay " { " on " | " off " } "
+.R " ] [ "
+.B window
+.I WINDOW
+.R " ] [ "
+.B encodingsa
+.I SA
+.R " ]"
+
+.BR "ip macsec add"
+.IR DEV
+.B "tx sa { 0..3 } [ OPTS ] key ID KEY"
+.br
+.BR "ip macsec set"
+.IR DEV
+.B "tx sa { 0..3 } [ OPTS ]"
+.br
+.BR "ip macsec del"
+.IR DEV
+.B "tx sa { 0..3 }"
+
+.BR "ip macsec add"
+.IR DEV
+.B "rx SCI [ on | off ]"
+.br
+.BR "ip macsec set"
+.IR DEV
+.B "rx SCI [ on | off ]"
+.br
+.BR "ip macsec del"
+.IR DEV
+.B "rx SCI"
+
+.BR "ip macsec add"
+.IR DEV
+.B "rx SCI sa { 0..3 } [ OPTS ] key ID KEY"
+.br
+.BR "ip macsec set"
+.IR DEV
+.B "rx SCI sa { 0..3 } [ OPTS ]"
+.br
+.BR "ip macsec del"
+.IR DEV
+.B "rx SCI sa { 0..3 }"
+
+.BR "ip macsec show [ DEV ]"
+
+.IR OPTS " := [ pn  { 1..2^32-1  } ] [ on | off ]"
+.br
+.IR SCI  " := { sci <u64> | port <u16> address <lladdr> }"
+
+
+.SH "DESCRIPTION"
+The
+.B ip macsec
+commands are used to configure transmit secure associations and receive secure channels and their secure associations on a MACsec device created with the
+.B ip link add
+command using the
+.I macsec
+type.
+
+.SH "EXAMPLES"
+.PP
+.SS Create a MACsec device on link eth0
+.nf
+# ip link add device eth0 macsec0 type macsec port 11 encrypt on
+.PP
+.SS Configure a secure association on that device
+.nf
+# ip macsec add macsec0 tx sa 0 pn 1024 on key 1 81818181818181818181818181818181
+.PP
+.SS Configure a receive channel
+.nf
+# ip macsec add macsec0 rx port 1234 address c6:19:52:8f:e6:a0
+.PP
+.SS Configure a receive association
+.nf
+# ip macsec add macsec0 rx port 1234 address c6:19:52:8f:e6:a0 sa 0 pn 1 on key 0 82828282828282828282828282828282
+.PP
+.SS Display MACsec configuration
+.nf
+# ip macsec show
+.SH "SEE ALSO"
+.br
+.BR ip-link (8)
+.SH "AUTHOR"
+Sabrina Dubroca <sd@queasysnail.net>
-- 
2.8.0

^ permalink raw reply related

* [PATCH iproute2 3/4] utils: provide get_hex to read an hex digit from a char
From: Sabrina Dubroca @ 2016-04-14 13:01 UTC (permalink / raw)
  To: netdev; +Cc: Stephen Hemminger, Sabrina Dubroca
In-Reply-To: <cover.1460622809.git.sd@queasysnail.net>

Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
---
 include/utils.h |  1 +
 ip/ipl2tp.c     | 15 ++-------------
 lib/ipx_pton.c  | 18 +++---------------
 lib/utils.c     | 12 ++++++++++++
 4 files changed, 18 insertions(+), 28 deletions(-)

diff --git a/include/utils.h b/include/utils.h
index a9aa89162950..27562a1c949c 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -99,6 +99,7 @@ int get_prefix(inet_prefix *dst, char *arg, int family);
 int mask2bits(__u32 netmask);
 int get_addr_ila(__u64 *val, const char *arg);
 
+int get_hex(char c);
 int get_integer(int *val, const char *arg, int base);
 int get_unsigned(unsigned *val, const char *arg, int base);
 int get_time_rtt(unsigned *val, const char *arg, int *raw);
diff --git a/ip/ipl2tp.c b/ip/ipl2tp.c
index 3c8ee9355439..1f84c6149f39 100644
--- a/ip/ipl2tp.c
+++ b/ip/ipl2tp.c
@@ -425,30 +425,19 @@ static int get_tunnel(struct l2tp_data *p)
  * Command parser
  *****************************************************************************/
 
-static int hex(char ch)
-{
-	if ((ch >= 'a') && (ch <= 'f'))
-		return ch - 'a' + 10;
-	if ((ch >= '0') && (ch <= '9'))
-		return ch - '0';
-	if ((ch >= 'A') && (ch <= 'F'))
-		return ch - 'A' + 10;
-	return -1;
-}
-
 static int hex2mem(const char *buf, uint8_t *mem, int count)
 {
 	int i, j;
 	int c;
 
 	for (i = 0, j = 0; i < count; i++, j += 2) {
-		c = hex(buf[j]);
+		c = get_hex(buf[j]);
 		if (c < 0)
 			goto err;
 
 		mem[i] = c << 4;
 
-		c = hex(buf[j + 1]);
+		c = get_hex(buf[j + 1]);
 		if (c < 0)
 			goto err;
 
diff --git a/lib/ipx_pton.c b/lib/ipx_pton.c
index 3dca2713719a..071a775e7437 100644
--- a/lib/ipx_pton.c
+++ b/lib/ipx_pton.c
@@ -6,18 +6,6 @@
 
 #include "utils.h"
 
-static u_int32_t hexget(char c)
-{
-	if (c >= 'A' && c <= 'F')
-		return c - 'A' + 10;
-	if (c >= 'a' && c <= 'f')
-		return c - 'a' + 10;
-	if (c >= '0' && c <= '9')
-		return c - '0';
-
-	return 0xf0;
-}
-
 static int ipx_getnet(u_int32_t *net, const char *str)
 {
 	int i;
@@ -25,7 +13,7 @@ static int ipx_getnet(u_int32_t *net, const char *str)
 
 	for(i = 0; *str && (i < 8); i++) {
 
-		if ((tmp = hexget(*str)) & 0xf0) {
+		if ((tmp = get_hex(*str)) == -1) {
 			if (*str == '.')
 				return 0;
 			else
@@ -49,11 +37,11 @@ static int ipx_getnode(u_int8_t *node, const char *str)
 	u_int32_t tmp;
 
 	for(i = 0; i < 6; i++) {
-		if ((tmp = hexget(*str++)) & 0xf0)
+		if ((tmp = get_hex(*str++)) == -1)
 			return -1;
 		node[i] = (u_int8_t)tmp;
 		node[i] <<= 4;
-		if ((tmp = hexget(*str++)) & 0xf0)
+		if ((tmp = get_hex(*str++)) == -1)
 			return -1;
 		node[i] |= (u_int8_t)tmp;
 		if (*str == ':')
diff --git a/lib/utils.c b/lib/utils.c
index 50d268066d94..591e70cc3450 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -37,6 +37,18 @@
 
 int timestamp_short = 0;
 
+int get_hex(char c)
+{
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	if (c >= '0' && c <= '9')
+		return c - '0';
+
+	return -1;
+}
+
 int get_integer(int *val, const char *arg, int base)
 {
 	long res;
-- 
2.8.0

^ permalink raw reply related

* [PATCH iproute2 2/4] utils: add get_be{16,32,64}, use them where possible
From: Sabrina Dubroca @ 2016-04-14 13:01 UTC (permalink / raw)
  To: netdev; +Cc: Stephen Hemminger, Sabrina Dubroca
In-Reply-To: <cover.1460622809.git.sd@queasysnail.net>

Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
---
 include/utils.h       |  3 +++
 ip/ipfou.c            |  3 +--
 ip/iplink_vxlan.c     |  8 ++------
 ip/iproute_lwtunnel.c |  8 ++++----
 ip/ipxfrm.c           | 13 +++----------
 ip/xfrm_state.c       | 10 +++-------
 lib/ll_proto.c        |  3 +--
 lib/utils.c           | 36 ++++++++++++++++++++++++++++++++++++
 tc/f_flower.c         |  4 ++--
 tc/f_u32.c            | 10 ++--------
 10 files changed, 57 insertions(+), 41 deletions(-)

diff --git a/include/utils.h b/include/utils.h
index aef28ce732ab..a9aa89162950 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -112,6 +112,9 @@ int get_u16(__u16 *val, const char *arg, int base);
 int get_s16(__s16 *val, const char *arg, int base);
 int get_u8(__u8 *val, const char *arg, int base);
 int get_s8(__s8 *val, const char *arg, int base);
+int get_be64(__be64 *val, const char *arg, int base);
+int get_be32(__be32 *val, const char *arg, int base);
+int get_be16(__be16 *val, const char *arg, int base);
 int get_addr64(__u64 *ap, const char *cp);
 
 char *hexstring_n2a(const __u8 *str, int len, char *buf, int blen);
diff --git a/ip/ipfou.c b/ip/ipfou.c
index 8a86b18fc284..2a6ae1755d3e 100644
--- a/ip/ipfou.c
+++ b/ip/ipfou.c
@@ -55,9 +55,8 @@ static int fou_parse_opt(int argc, char **argv, struct nlmsghdr *n,
 		if (!matches(*argv, "port")) {
 			NEXT_ARG();
 
-			if (get_u16(&port, *argv, 0) || port == 0)
+			if (get_be16(&port, *argv, 0) || port == 0)
 				invarg("invalid port", *argv);
-			port = htons(port);
 			port_set = 1;
 		} else if (!matches(*argv, "ipproto")) {
 			struct protoent *servptr;
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
index 49a40befa5d5..7ba68bc14c78 100644
--- a/ip/iplink_vxlan.c
+++ b/ip/iplink_vxlan.c
@@ -172,16 +172,12 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
 				invarg("max addresses", *argv);
 		} else if (!matches(*argv, "port") ||
 			   !matches(*argv, "srcport")) {
-			__u16 minport, maxport;
-
 			NEXT_ARG();
-			if (get_u16(&minport, *argv, 0))
+			if (get_be16(&range.low, *argv, 0))
 				invarg("min port", *argv);
 			NEXT_ARG();
-			if (get_u16(&maxport, *argv, 0))
+			if (get_be16(&range.high, *argv, 0))
 				invarg("max port", *argv);
-			range.low = htons(minport);
-			range.high = htons(maxport);
 		} else if (!matches(*argv, "dstport")) {
 			NEXT_ARG();
 			if (get_u16(&dstport, *argv, 0))
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 3baac7720816..bdbb15d2b746 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -190,9 +190,9 @@ static int parse_encap_ip(struct rtattr *rta, size_t len, int *argcp, char ***ar
 			NEXT_ARG();
 			if (id_ok++)
 				duparg2("id", *argv);
-			if (get_u64(&id, *argv, 0))
+			if (get_be64(&id, *argv, 0))
 				invarg("\"id\" value is invalid\n", *argv);
-			rta_addattr64(rta, len, LWTUNNEL_IP_ID, htonll(id));
+			rta_addattr64(rta, len, LWTUNNEL_IP_ID, id);
 		} else if (strcmp(*argv, "dst") == 0) {
 			inet_prefix addr;
 
@@ -267,9 +267,9 @@ static int parse_encap_ip6(struct rtattr *rta, size_t len, int *argcp, char ***a
 			NEXT_ARG();
 			if (id_ok++)
 				duparg2("id", *argv);
-			if (get_u64(&id, *argv, 0))
+			if (get_be64(&id, *argv, 0))
 				invarg("\"id\" value is invalid\n", *argv);
-			rta_addattr64(rta, len, LWTUNNEL_IP6_ID, htonll(id));
+			rta_addattr64(rta, len, LWTUNNEL_IP6_ID, id);
 		} else if (strcmp(*argv, "dst") == 0) {
 			inet_prefix addr;
 
diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
index 8741ff3b302a..8d786d1334df 100644
--- a/ip/ipxfrm.c
+++ b/ip/ipxfrm.c
@@ -1109,15 +1109,10 @@ int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
 			filter.id_proto_mask = XFRM_FILTER_MASK_FULL;
 
 		} else if (strcmp(*argv, "spi") == 0) {
-			__u32 spi;
-
 			NEXT_ARG();
-			if (get_u32(&spi, *argv, 0))
+			if (get_be32(&id->spi, *argv, 0))
 				invarg("SPI value is invalid", *argv);
 
-			spi = htonl(spi);
-			id->spi = spi;
-
 			filter.id_spi_mask = XFRM_FILTER_MASK_FULL;
 
 		} else {
@@ -1252,9 +1247,8 @@ static int xfrm_selector_upspec_parse(struct xfrm_selector *sel,
 
 			NEXT_ARG();
 
-			if (get_u16(&sel->sport, *argv, 0))
+			if (get_be16(&sel->sport, *argv, 0))
 				invarg("value after \"sport\" is invalid", *argv);
-			sel->sport = htons(sel->sport);
 			if (sel->sport)
 				sel->sport_mask = ~((__u16)0);
 
@@ -1265,9 +1259,8 @@ static int xfrm_selector_upspec_parse(struct xfrm_selector *sel,
 
 			NEXT_ARG();
 
-			if (get_u16(&sel->dport, *argv, 0))
+			if (get_be16(&sel->dport, *argv, 0))
 				invarg("value after \"dport\" is invalid", *argv);
-			sel->dport = htons(sel->dport);
 			if (sel->dport)
 				sel->dport_mask = ~((__u16)0);
 
diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
index 5e2b641959bf..21ada3647ba4 100644
--- a/ip/xfrm_state.c
+++ b/ip/xfrm_state.c
@@ -175,11 +175,9 @@ static int xfrm_seq_parse(__u32 *seq, int *argcp, char ***argvp)
 	int argc = *argcp;
 	char **argv = *argvp;
 
-	if (get_u32(seq, *argv, 0))
+	if (get_be32(seq, *argv, 0))
 		invarg("SEQ value is invalid", *argv);
 
-	*seq = htonl(*seq);
-
 	*argcp = argc;
 	*argvp = argv;
 
@@ -359,13 +357,11 @@ static int xfrm_state_modify(int cmd, unsigned int flags, int argc, char **argv)
 			NEXT_ARG();
 			xfrm_encap_type_parse(&encap.encap_type, &argc, &argv);
 			NEXT_ARG();
-			if (get_u16(&encap.encap_sport, *argv, 0))
+			if (get_be16(&encap.encap_sport, *argv, 0))
 				invarg("SPORT value after \"encap\" is invalid", *argv);
-			encap.encap_sport = htons(encap.encap_sport);
 			NEXT_ARG();
-			if (get_u16(&encap.encap_dport, *argv, 0))
+			if (get_be16(&encap.encap_dport, *argv, 0))
 				invarg("DPORT value after \"encap\" is invalid", *argv);
-			encap.encap_dport = htons(encap.encap_dport);
 			NEXT_ARG();
 			get_addr(&oa, *argv, AF_UNSPEC);
 			memcpy(&encap.encap_oa, &oa.data, sizeof(encap.encap_oa));
diff --git a/lib/ll_proto.c b/lib/ll_proto.c
index d8df68c110b0..e094d9f81ccc 100644
--- a/lib/ll_proto.c
+++ b/lib/ll_proto.c
@@ -111,8 +111,7 @@ int ll_proto_a2n(unsigned short *id, const char *buf)
 			 return 0;
 		 }
 	}
-	if (get_u16(id, buf, 0))
+	if (get_be16(id, buf, 0))
 		return -1;
-	*id = htons(*id);
 	return 0;
 }
diff --git a/lib/utils.c b/lib/utils.c
index 9337f57a5bde..50d268066d94 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -353,6 +353,42 @@ int get_s8(__s8 *val, const char *arg, int base)
 	return 0;
 }
 
+int get_be64(__be64 *val, const char *arg, int base)
+{
+	__u64 v;
+	int ret = get_u64(&v, arg, base);
+
+	if (ret)
+		return ret;
+
+	*val = htonll(v);
+	return 0;
+}
+
+int get_be32(__be32 *val, const char *arg, int base)
+{
+	__u32 v;
+	int ret = get_u32(&v, arg, base);
+
+	if (ret)
+		return ret;
+
+	*val = htonl(v);
+	return 0;
+}
+
+int get_be16(__be16 *val, const char *arg, int base)
+{
+	__u16 v;
+	int ret = get_u16(&v, arg, base);
+
+	if (ret)
+		return ret;
+
+	*val = htons(v);
+	return 0;
+}
+
 /* This uses a non-standard parsing (ie not inet_aton, or inet_pton)
  * because of legacy choice to parse 10.8 as 10.8.0.0 not 10.0.0.8
  */
diff --git a/tc/f_flower.c b/tc/f_flower.c
index 306f056c1b66..fd2014b374a1 100644
--- a/tc/f_flower.c
+++ b/tc/f_flower.c
@@ -150,11 +150,11 @@ static int flower_parse_port(char *str, __u8 ip_port,
 		return -1;
 	}
 
-	ret = get_u16(&port, str, 10);
+	ret = get_be16(&port, str, 10);
 	if (ret)
 		return -1;
 
-	addattr16(n, MAX_MSG, type, htons(port));
+	addattr16(n, MAX_MSG, type, port);
 
 	return 0;
 }
diff --git a/tc/f_u32.c b/tc/f_u32.c
index 62995153923a..e2d39b3d52fe 100644
--- a/tc/f_u32.c
+++ b/tc/f_u32.c
@@ -766,12 +766,9 @@ static int parse_offset(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
 			}
 			sel->flags |= TC_U32_VAROFFSET;
 		} else if (matches(*argv, "mask") == 0) {
-			__u16 mask;
-
 			NEXT_ARG();
-			if (get_u16(&mask, *argv, 16))
+			if (get_be16(&sel->offmask, *argv, 16))
 				return -1;
-			sel->offmask = htons(mask);
 			sel->flags |= TC_U32_VAROFFSET;
 		} else if (matches(*argv, "shift") == 0) {
 			int shift;
@@ -801,12 +798,9 @@ static int parse_hashkey(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
 
 	while (argc > 0) {
 		if (matches(*argv, "mask") == 0) {
-			__u32 mask;
-
 			NEXT_ARG();
-			if (get_u32(&mask, *argv, 16))
+			if (get_be32(&sel->hmask, *argv, 16))
 				return -1;
-			sel->hmask = htonl(mask);
 		} else if (matches(*argv, "at") == 0) {
 			int num;
 
-- 
2.8.0

^ permalink raw reply related

* [PATCH iproute2 1/4] utils: make hexstring_a2n provide the number of hex digits parsed
From: Sabrina Dubroca @ 2016-04-14 13:01 UTC (permalink / raw)
  To: netdev; +Cc: Stephen Hemminger, Sabrina Dubroca
In-Reply-To: <cover.1460622809.git.sd@queasysnail.net>

Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
---
 include/utils.h | 4 ++--
 lib/utils.c     | 5 +++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/include/utils.h b/include/utils.h
index ef81d00f3d70..aef28ce732ab 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -114,8 +114,8 @@ int get_u8(__u8 *val, const char *arg, int base);
 int get_s8(__s8 *val, const char *arg, int base);
 int get_addr64(__u64 *ap, const char *cp);
 
-char* hexstring_n2a(const __u8 *str, int len, char *buf, int blen);
-__u8* hexstring_a2n(const char *str, __u8 *buf, int blen);
+char *hexstring_n2a(const __u8 *str, int len, char *buf, int blen);
+__u8 *hexstring_a2n(const char *str, __u8 *buf, int blen, unsigned int *len);
 #define ADDR64_BUF_SIZE sizeof("xxxx:xxxx:xxxx:xxxx")
 int addr64_n2a(__u64 addr, char *buff, size_t len);
 
diff --git a/lib/utils.c b/lib/utils.c
index b93b1dc84315..9337f57a5bde 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -866,9 +866,9 @@ char *hexstring_n2a(const __u8 *str, int len, char *buf, int blen)
 	return buf;
 }
 
-__u8* hexstring_a2n(const char *str, __u8 *buf, int blen)
+__u8 *hexstring_a2n(const char *str, __u8 *buf, int blen, unsigned int *len)
 {
-	int cnt = 0;
+	unsigned int cnt = 0;
 	char *endptr;
 
 	if (strlen(str) % 2)
@@ -885,6 +885,7 @@ __u8* hexstring_a2n(const char *str, __u8 *buf, int blen)
 		buf[cnt++] = tmp;
 		str += 2;
 	}
+	*len = cnt;
 	return buf;
 }
 
-- 
2.8.0

^ permalink raw reply related

* [PATCH iproute2 0/4] add MACsec support
From: Sabrina Dubroca @ 2016-04-14 13:01 UTC (permalink / raw)
  To: netdev; +Cc: Stephen Hemminger, Sabrina Dubroca

This series introduces support for MACsec devices, with a new device
type for `ip link`, and a new `ip macsec` subcommand.

The first three patches introduce some necessary helper functions.

Sabrina Dubroca (4):
  utils: make hexstring_a2n provide the number of hex digits parsed
  utils: add get_be{16,32,64}, use them where possible
  utils: provide get_hex to read an hex digit from a char
  ip: add MACsec support

 include/utils.h       |    8 +-
 ip/Makefile           |    2 +-
 ip/ip.c               |    3 +-
 ip/ip_common.h        |    1 +
 ip/ipfou.c            |    3 +-
 ip/ipl2tp.c           |   15 +-
 ip/iplink_vxlan.c     |    8 +-
 ip/ipmacsec.c         | 1231 +++++++++++++++++++++++++++++++++++++++++++++++++
 ip/iproute_lwtunnel.c |    8 +-
 ip/ipxfrm.c           |   13 +-
 ip/xfrm_state.c       |   10 +-
 lib/ipx_pton.c        |   18 +-
 lib/ll_proto.c        |    3 +-
 lib/utils.c           |   53 ++-
 man/man8/Makefile     |    2 +-
 man/man8/ip-link.8.in |  134 ++++++
 man/man8/ip-macsec.8  |  110 +++++
 tc/f_flower.c         |    4 +-
 tc/f_u32.c            |   10 +-
 19 files changed, 1560 insertions(+), 76 deletions(-)
 create mode 100644 ip/ipmacsec.c
 create mode 100644 man/man8/ip-macsec.8

-- 
2.8.0

^ permalink raw reply

* [PATCH] ipv6: allow bypassing cross-intf routing limits
From: Michal Kazior @ 2016-04-14 12:46 UTC (permalink / raw)
  To: netdev; +Cc: davem, Michal Kazior
In-Reply-To: <1459757755-25424-1-git-send-email-michal.kazior@tieto.com>

There are some use-cases to allow link-local
routing for bridging purposes.

One of these is allowing transparent 802.11
bridging. Due to 802.11 framing limitations many
Access Points make it impossible to create bridges
on Client endpoints because they can't maintain
Destination/Source/Transmitter/Receiver address
distinction with only 3 addresses in frame header.

The default behavior, i.e. link-local traffic
being non-routable, remains. The user has to
explicitly enable the bypass when defining a given
route.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---
For more background see:

  http://www.spinics.net/lists/netdev/msg371022.html
  https://lists.openwrt.org/pipermail/openwrt-devel/2016-April/040783.html

 include/uapi/linux/rtnetlink.h |  8 ++++++--
 net/ipv6/ip6_output.c          | 11 +++++++++--
 net/ipv6/route.c               |  4 ++++
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index ca764b5da86d..a577eec0e56e 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -424,9 +424,13 @@ enum {
 #define RTAX_FEATURE_SACK	(1 << 1)
 #define RTAX_FEATURE_TIMESTAMP	(1 << 2)
 #define RTAX_FEATURE_ALLFRAG	(1 << 3)
+#define RTAX_FEATURE_XFACE	(1 << 4)
 
-#define RTAX_FEATURE_MASK	(RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \
-				 RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG)
+#define RTAX_FEATURE_MASK	(RTAX_FEATURE_ECN | \
+				 RTAX_FEATURE_SACK | \
+				 RTAX_FEATURE_TIMESTAMP | \
+				 RTAX_FEATURE_ALLFRAG | \
+				 RTAX_FEATURE_XFACE)
 
 struct rta_session {
 	__u8	proto;
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 9428345d3a07..9abb42acb6ad 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -283,6 +283,7 @@ static int ip6_forward_proxy_check(struct sk_buff *skb)
 	u8 nexthdr = hdr->nexthdr;
 	__be16 frag_off;
 	int offset;
+	int feat = dst_metric_raw(skb_dst(skb), RTAX_FEATURES);
 
 	if (ipv6_ext_hdr(nexthdr)) {
 		offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, &frag_off);
@@ -320,8 +321,11 @@ static int ip6_forward_proxy_check(struct sk_buff *skb)
 	 * The proxying router can't forward traffic sent to a link-local
 	 * address, so signal the sender and discard the packet. This
 	 * behavior is clarified by the MIPv6 specification.
+	 *
+	 * It's useful to allow an override for transparent traffic relay.
 	 */
-	if (ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL) {
+	if ((ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL) &&
+	    !(feat & RTAX_FEATURE_XFACE)) {
 		dst_link_failure(skb);
 		return -1;
 	}
@@ -485,12 +489,15 @@ int ip6_forward(struct sk_buff *skb)
 			inet_putpeer(peer);
 	} else {
 		int addrtype = ipv6_addr_type(&hdr->saddr);
+		int feat = dst_metric_raw(dst, RTAX_FEATURES);
 
 		/* This check is security critical. */
 		if (addrtype == IPV6_ADDR_ANY ||
 		    addrtype & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LOOPBACK))
 			goto error;
-		if (addrtype & IPV6_ADDR_LINKLOCAL) {
+
+		if ((addrtype & IPV6_ADDR_LINKLOCAL) &&
+		    !(feat & RTAX_FEATURE_XFACE)) {
 			icmpv6_send(skb, ICMPV6_DEST_UNREACH,
 				    ICMPV6_NOT_NEIGHBOUR, 0);
 			goto error;
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index ed446639219c..560c99853907 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -629,8 +629,12 @@ static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt)
 static int rt6_score_route(struct rt6_info *rt, int oif,
 			   int strict)
 {
+	int feat = dst_metric_raw(&rt->dst, RTAX_FEATURES);
 	int m;
 
+	if (feat & RTAX_FEATURE_XFACE)
+		strict &= ~RT6_LOOKUP_F_IFACE;
+
 	m = rt6_check_dev(rt, oif);
 	if (!m && (strict & RT6_LOOKUP_F_IFACE))
 		return RT6_NUD_FAIL_HARD;
-- 
2.1.4

^ permalink raw reply related

* Re: [PATCHv3 net-next 4/6] sctp: add the sctp_diag.c file
From: Jakub Sitnicki @ 2016-04-14 12:41 UTC (permalink / raw)
  To: Xin Long
  Cc: network dev, linux-sctp, Marcelo Ricardo Leitner, Vlad Yasevich,
	daniel, davem, eric.dumazet
In-Reply-To: <c48759e983ae5f9cefdd1805025abb7f57299886.1460618169.git.lucien.xin@gmail.com>

Hi Xin,

On Thu, Apr 14, 2016 at 09:35 AM CEST, Xin Long <lucien.xin@gmail.com> wrote:
> This one will implement all the interface of inet_diag, inet_diag_handler.
> which includes sctp_diag_dump, sctp_diag_dump_one and sctp_diag_get_info.
>
> It will work as a module, and register inet_diag_handler when loading.
>
> v2->v3:
> - fix the mistake in inet_assoc_attr_size().
>
> - change inet_diag_msg_laddrs_fill() name to inet_diag_msg_sctpladdrs_fill.
>
> - change inet_diag_msg_paddrs_fill() name to inet_diag_msg_sctpaddrs_fill.
>
> - add inet_diag_msg_sctpinfo_fill() to make asoc/ep fill code clearer.
>
> - add inet_diag_msg_sctpasoc_fill() to make asoc fill code clearer.
>
> - merge inet_asoc_diag_fill() and inet_ep_diag_fill() to
>   inet_sctp_diag_fill().
>
> - call sctp_diag_get_info() directly, instead by handler, cause the caller
>   is in the same file with it.
>
> - call lock_sock in sctp_tsp_dump_one() to make sure we call get sctp info
>   safely.
>
> - after lock_sock(sk), we should check sk != assoc->base.sk.
>
> - change mem[SK_MEMINFO_WMEM_ALLOC] to asoc->sndbuf_used for asoc dump when
>   asoc->ep->sndbuf_policy is set. don't use INET_DIAG_MEMINFO attr any more.
>
> Signed-off-by: Xin Long <lucien.xin@gmail.com>
> ---

[...]

> diff --git a/net/sctp/sctp_diag.c b/net/sctp/sctp_diag.c
> new file mode 100644
> index 0000000..98ecd16
> --- /dev/null
> +++ b/net/sctp/sctp_diag.c
> @@ -0,0 +1,497 @@

[...]

> +#define EXPIRES_IN_MS(tmo)  DIV_ROUND_UP((tmo - jiffies) * 1000, HZ)
> +	r->idiag_expires =
> +		EXPIRES_IN_MS(asoc->timeouts[SCTP_EVENT_TIMEOUT_T3_RTX]);
> +#undef EXPIRES_IN_MS

How about using jiffies_to_msecs() here? It's already being used
elsewhere in net/sctp.

AFAICT EXPIRES_IN_MS macro comes from net/ipv4/inet_diag.c and dates
back to before jiffies_to_msecs() has been introduced. Perhaps that's
why it's still used in inet_diag.c. But this is new code.

Thanks,
Jakub

^ permalink raw reply

* Re: [PATCH nf] netfilter: ipv6: Orphan skbs in nf_ct_frag6_gather()
From: Pablo Neira Ayuso @ 2016-04-14 10:35 UTC (permalink / raw)
  To: Florian Westphal
  Cc: David Laight, 'Joe Stringer',
	netfilter-devel@vger.kernel.org, diproiettod@vmware.com,
	netdev@vger.kernel.org
In-Reply-To: <20160414084015.GA3192@breakpoint.cc>

On Thu, Apr 14, 2016 at 10:40:15AM +0200, Florian Westphal wrote:
> David Laight <David.Laight@ACULAB.COM> wrote:
> > From: Joe Stringer
> > > Sent: 13 April 2016 19:10
> > > This is the IPv6 equivalent of commit 8282f27449bf ("inet: frag: Always
> > > orphan skbs inside ip_defrag()").
> > > 
> > > Prior to commit 029f7f3b8701 ("netfilter: ipv6: nf_defrag: avoid/free
> > > clone operations"), ipv6 fragments sent to nf_ct_frag6_gather() would be
> > > cloned (implicitly orphaning) prior to queueing for reassembly. As such,
> > > when the IPv6 message is eventually reassembled, the skb->sk for all
> > > fragments would be NULL. After that commit was introduced, rather than
> > > cloning, the original skbs were queued directly without orphaning. The
> > > end result is that all frags except for the first and last may have a
> > > socket attached.
> > 
> > I'd have thought that the queued fragments would still want to be
> > resource-counted against the socket (I think that is what skb->sk is for).
> 
> No, ipv4/ipv6 reasm has its own accouting.
> 
> > Although I can't imagine why IPv6 reassembly is happening on skb
> > associated with a socket.
> 
> Right, thats a much more interesting question -- both ipv4 and
> ipv6 orphan skbs before NF_HOOK prerouting trip.
> 
> (That being said, I don't mind the patch, I'm just be curious how this
>  can happen).

If this change is specific to get this working in ovs and its
conntrack support, then I don't think this belong to core
infrastructure. This should be fixed in ovs instead.

^ permalink raw reply

* Re: [PATCH RFC 1/2] tun: don't require serialization lock on tx
From: Hannes Frederic Sowa @ 2016-04-14 10:27 UTC (permalink / raw)
  To: Paolo Abeni, netdev
  Cc: David S. Miller, Michael S. Tsirkin, Eric W. Biederman, Greg Kurz,
	Jason Wang
In-Reply-To: <425e8f69ffd8fe315ef9d3cc678519c7060fb2e0.1460393493.git.pabeni@redhat.com>

On 13.04.2016 11:04, Paolo Abeni wrote:
> The current tun_net_xmit() implementation don't need any external
> lock since it relay on rcu protection for the tun data structure
> and on socket queue lock for skb queuing.
>
> This patch set the NETIF_F_LLTX feature bit in the tun device, so
> that on xmit, in absence of qdisc, no serialization lock is acquired
> by the caller.
>
> Signed-off-by: Paolo Abeni <pabeni@redhat.com>

Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>

^ permalink raw reply

* Re: [PATCH net-next V3 00/16] net: fec: cleanup and fixes
From: Holger Schurig @ 2016-04-14 10:13 UTC (permalink / raw)
  To: Troy Kisky
  Cc: netdev, davem, fugang.duan, lznuaa, andrew, stillcompiling, arnd,
	sergei.shtylyov, gerg, fabio.estevam, johannes, l.stach,
	linux-arm-kernel, tremyfr
In-Reply-To: <1459909562-22865-1-git-send-email-troy.kisky@boundarydevices.com>

Do you guys that work with the FEC driver ever run with
CONFIG_DMA_API_DEBUG enabled?

I ask this Because I get this error when it's turned on when I do some
"rsync" transfer to my device:

[   58.420980] ------------[ cut here ]------------
[   58.425667] WARNING: CPU: 0 PID: 377 at /home/schurig/d/mkarm/linux-4.5/lib/dma-debug.c:1096 check_unmap+0x9d0/0xab8()
[   58.436405] fec 2188000.ethernet: DMA-API: device driver tries to free DMA memory it has not allocated [device address=0x0000000000000000] [size=66 bytes]
[   58.450248] Modules linked in: bnep usbhid imx_sdma flexcan btusb btrtl btbcm btintel smsc95xx usbnet mii bluetooth
[   58.460882] CPU: 0 PID: 377 Comm: sshd Tainted: G        W       4.5.1 #3
[   58.467671] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[   58.474199] Backtrace: 
[   58.476675] [<c0012a24>] (dump_backtrace) from [<c0012c20>] (show_stack+0x18/0x1c)
[   58.484244]  r6:60000113 r5:c05a96c0 r4:00000000 r3:00000000
[   58.489964] [<c0012c08>] (show_stack) from [<c01dbc4c>] (dump_stack+0x9c/0xb0)
[   58.497197] [<c01dbbb0>] (dump_stack) from [<c001f558>] (warn_slowpath_common+0x8c/0xbc)
[   58.505286]  r6:c01f9c74 r5:00000009 r4:ee9f17f8 r3:c0596da4
[   58.511002] [<c001f4cc>] (warn_slowpath_common) from [<c001f5c0>] (warn_slowpath_fmt+0x38/0x40)
[   58.519698]  r8:00000042 r7:00000001 r6:00000000 r5:00000000 r4:c050c020
[   58.526470] [<c001f58c>] (warn_slowpath_fmt) from [<c01f9c74>] (check_unmap+0x9d0/0xab8)
[   58.534559]  r3:c0520e6c r2:c050c020
[   58.538159]  r4:00000000
[   58.540710] [<c01f92a4>] (check_unmap) from [<c01f9de0>] (debug_dma_unmap_page+0x84/0x8c)
[   58.548886]  r10:ef2ec000 r9:f09e5fa0 r8:ef0ef810 r7:00000001 r6:00000000 r5:00000042
[   58.556780]  r4:00000001
[   58.559336] [<c01f9d5c>] (debug_dma_unmap_page) from [<c02cdf00>] (fec_txq+0x140/0x31c)
[   58.567338]  r8:ef0ef810 r7:00000000 r6:00000000 r5:00000000 r4:ef2c6000
[   58.574108] [<c02cddc0>] (fec_txq) from [<c02ce2f4>] (fec_enet_napi_q1+0x98/0xe8)
[   58.581589]  r10:08000000 r9:ef2ec580 r8:00000000 r7:00000040 r6:00000000 r5:ef2ec000
[   58.589483]  r4:0c008000
[   58.592042] [<c02ce25c>] (fec_enet_napi_q1) from [<c038b3d8>] (net_rx_action+0x1fc/0x2f0)
[   58.600218]  r10:ee9f19c0 r9:00000040 r8:c059e100 r7:0000012c r6:ffffa1a3 r5:c02ce25c
[   58.608112]  r4:ef2ec580 r3:ee9f19c0
[   58.611720] [<c038b1dc>] (net_rx_action) from [<c00224c4>] (__do_softirq+0x134/0x254)
[   58.619549]  r10:c059e080 r9:40000003 r8:00000100 r7:ee9f0000 r6:c059e08c r5:00000003
[   58.627445]  r4:00000000
[   58.629995] [<c0022390>] (__do_softirq) from [<c00228a8>] (irq_exit+0xb8/0x120)
[   58.637303]  r10:ee9f1e38 r9:f4001100 r8:ef008000 r7:00000001 r6:00000000 r5:00000000
[   58.645197]  r4:c05970b8
[   58.647754] [<c00227f0>] (irq_exit) from [<c0061340>] (__handle_domain_irq+0x68/0xbc)
[   58.655583]  r4:c05970b8 r3:c0064e24
[   58.659190] [<c00612d8>] (__handle_domain_irq) from [<c00093f8>] (gic_handle_irq+0x50/0x90)
[   58.667539]  r8:f4000100 r7:ee9f1ac8 r6:f400010c r5:c059e7a0 r4:c05a9788 r3:ee9f1ac8
[   58.675350] [<c00093a8>] (gic_handle_irq) from [<c0013740>] (__irq_svc+0x40/0x54)
[   58.682833] Exception stack(0xee9f1ac8 to 0xee9f1b10)
[   58.687887] 1ac0:                   00000000 ee9c0d4c 0000000c 00000000 00000000 00000000
[   58.696067] 1ae0: ee9f1e38 ee9f1e3c ee9f1e40 edc6ac00 ee9f1e38 ee9f1e1c 00000000 ee9f1b18
[   58.704245] 1b00: ee9c0d4c c00df648 60000013 ffffffff
[   58.709295]  r9:edc6ac00 r8:ee9f1e40 r7:ee9f1afc r6:ffffffff r5:60000013 r4:c00df648
[   58.717112] [<c00df544>] (do_select) from [<c00dfcb8>] (core_sys_select+0x144/0x320)
[   58.724854]  r10:ee9f1e38 r9:ee9f1e38 r8:0000000c r7:805af838 r6:00000000 r5:805af848
[   58.732749]  r4:00000004
[   58.735300] [<c00dfb74>] (core_sys_select) from [<c00dff68>] (SyS_select+0xd4/0x120)
[   58.743042]  r10:00000000 r9:0000000c r8:805af848 r7:805af838 r6:00000000 r5:ee9f1f70
[   58.750936]  r4:00000000
[   58.753488] [<c00dfe94>] (SyS_select) from [<c000f820>] (ret_fast_syscall+0x0/0x34)
[   58.761143]  r9:ee9f0000 r8:c000f9c4 r7:0000008e r6:00000000 r5:7f5f77c0 r4:00000000
[   58.768984] ---[ end trace cb88537fdc8fa202 ]---

The amount of data transferred isn't even huge:

sent 382,979 bytes  received 28,086 bytes  32,885.20 bytes/sec
total size is 147,758,955  speedup is 359.45



This happens with:

* Kernel 4.5
* Kernel 4.5.1
* Kernel 4.5.1 with the fec-related patches from 4.6-rc3
* Kernel 4.5.1 with the fec-related patches from 4.6-rc3 and Troy's
  patch series from this thread


Should I post an extra e-mail with "BUG" in the subject?

^ 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