Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next v1 6/6] tg3: use netif_tx_start_queue instead of wake_queue when no reschedule needed
From: David Decotigny @ 2011-12-16 18:19 UTC (permalink / raw)
  To: Matt Carlson, Michael Chan, netdev, linux-kernel
  Cc: Javier Martinez Canillas, Robin Getz, Matt Mackall, Ying Cai,
	David Decotigny
In-Reply-To: <cover.1324059527.git.decot@googlers.com>

From: Ying Cai <ycai@google.com>

This commit replaces netif_tx_wake_queue() with netif_tx_start_queue()
when __netif_schedule() is not needed. It also adds code to deal with
race condition between netif_tx_start_queue() and netif_tx_stop_queue().



Signed-off-by: David Decotigny <decot@googlers.com>
---
 drivers/net/ethernet/broadcom/tg3.c |   23 ++++++++++++-----------
 1 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c
index e3f221d..311e073 100644
--- a/drivers/net/ethernet/broadcom/tg3.c
+++ b/drivers/net/ethernet/broadcom/tg3.c
@@ -6860,9 +6860,10 @@ static int tg3_tso_bug(struct tg3 *tp, struct sk_buff *skb)
 {
 	struct sk_buff *segs, *nskb;
 	u32 frag_cnt_est = skb_shinfo(skb)->gso_segs * 3;
+	struct tg3_napi *tnapi = &tp->napi[0];
 
 	/* Estimate the number of fragments in the worst case */
-	if (unlikely(tg3_tx_avail(&tp->napi[0]) <= frag_cnt_est)) {
+	if (unlikely(tg3_tx_avail(tnapi) <= frag_cnt_est)) {
 		netif_stop_queue(tp->dev);
 
 		/* netif_tx_stop_queue() must be done before checking
@@ -6871,10 +6872,10 @@ static int tg3_tso_bug(struct tg3 *tp, struct sk_buff *skb)
 		 * netif_tx_queue_stopped().
 		 */
 		smp_mb();
-		if (tg3_tx_avail(&tp->napi[0]) <= frag_cnt_est)
+		if (tg3_tx_avail(tnapi) <= TG3_TX_WAKEUP_THRESH(tnapi))
 			return NETDEV_TX_BUSY;
 
-		netif_wake_queue(tp->dev);
+		netif_start_queue(tp->dev);
 	}
 
 	segs = skb_gso_segment(skb, tp->dev->features & ~NETIF_F_TSO);
@@ -6926,14 +6927,14 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
 	 * interrupt context.
 	 */
 	if (unlikely(budget <= (skb_shinfo(skb)->nr_frags + 1))) {
-		if (!netif_tx_queue_stopped(txq)) {
-			netif_tx_stop_queue(txq);
+		/* This is a hard error, log it. */
+		netdev_err(dev, "BUG! Tx Ring full when queue awake!\n");
 
-			/* This is a hard error, log it. */
-			netdev_err(dev,
-				   "BUG! Tx Ring full when queue awake!\n");
-		}
-		return NETDEV_TX_BUSY;
+		netif_tx_stop_queue(txq);
+		smp_mb();
+		if (tg3_tx_avail(tnapi) <= TG3_TX_WAKEUP_THRESH(tnapi))
+			return NETDEV_TX_BUSY;
+		netif_tx_start_queue(txq);
 	}
 
 	entry = tnapi->tx_prod;
@@ -7100,7 +7101,7 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
 		 */
 		smp_mb();
 		if (tg3_tx_avail(tnapi) > TG3_TX_WAKEUP_THRESH(tnapi))
-			netif_tx_wake_queue(txq);
+			netif_tx_start_queue(txq);
 	}
 
 	mmiowb();
-- 
1.7.3.1

^ permalink raw reply related

* [PATCH net-next v1 5/6] tg3: implementation of a non-NAPI mode
From: David Decotigny @ 2011-12-16 18:19 UTC (permalink / raw)
  To: Matt Carlson, Michael Chan, netdev, linux-kernel
  Cc: Javier Martinez Canillas, Robin Getz, Matt Mackall, Tom Herbert,
	David Decotigny
In-Reply-To: <cover.1324059527.git.decot@googlers.com>

From: Tom Herbert <therbert@google.com>

The tg3 NIC has a hard limit of 511 descriptors for the receive ring.
Under heavy load of small packets, this device receive queue may not
be serviced fast enough to prevent packets drops.  This could be due
to a variety of reasons such as lengthy processing delays of packets
in the stack, softirqs being disabled too long, etc. If the driver is
run in non-NAPI mode, the RX queue is serviced in the device
interrupt, which is much less likely to be deferred for a substantial
period of time.

There are some effects in not using NAPI that need to be considered.
It does increase the chance of live-lock in interrupt handler,
although since the tg3 does interrupt coalescing this is very unlikely
to occur.  Also, more code is being run with interrupts disabled
potentially deferring other hardware interrupts.  The amount of time
spent in the interrupt handler should be minimized by dequeuing
packets of the device queue and queuing them to a host queue as
quickly as possible.

The default mode of operation remains NAPI and its performances are
kept unchanged (code unchanged). Non-NAPI mode is enabled by
commenting-out CONFIG_TIGON3_NAPI Kconfig parameter.



Signed-off-by: David Decotigny <decot@googlers.com>
---
 drivers/net/ethernet/broadcom/Kconfig |    8 ++
 drivers/net/ethernet/broadcom/tg3.c   |  151 +++++++++++++++++++++++++++++++--
 drivers/net/ethernet/broadcom/tg3.h   |    5 +
 3 files changed, 157 insertions(+), 7 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig
index f15e72e..e808b3d 100644
--- a/drivers/net/ethernet/broadcom/Kconfig
+++ b/drivers/net/ethernet/broadcom/Kconfig
@@ -107,6 +107,14 @@ config TIGON3
 	  To compile this driver as a module, choose M here: the module
 	  will be called tg3.  This is recommended.
 
+if TIGON3
+config TIGON3_NAPI
+	bool "Use Rx Polling (NAPI)"
+	default y
+	---help---
+	  Use NAPI for Tigon3 driver. If unsure, say Y.
+endif # TIGON3
+
 config BNX2X
 	tristate "Broadcom NetXtremeII 10Gb support"
 	depends on PCI
diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c
index ecd6ea5..e3f221d 100644
--- a/drivers/net/ethernet/broadcom/tg3.c
+++ b/drivers/net/ethernet/broadcom/tg3.c
@@ -5349,7 +5349,11 @@ static void tg3_tx(struct tg3_napi *tnapi)
 		pkts_compl++;
 		bytes_compl += skb->len;
 
+#ifdef CONFIG_TIGON3_NAPI
 		dev_kfree_skb(skb);
+#else
+		dev_kfree_skb_any(skb);
+#endif
 
 		if (unlikely(tx_bug)) {
 			tg3_tx_recover(tp);
@@ -5370,11 +5374,15 @@ static void tg3_tx(struct tg3_napi *tnapi)
 
 	if (unlikely(netif_tx_queue_stopped(txq) &&
 		     (tg3_tx_avail(tnapi) > TG3_TX_WAKEUP_THRESH(tnapi)))) {
+#ifdef CONFIG_TIGON3_NAPI
 		__netif_tx_lock(txq, smp_processor_id());
 		if (netif_tx_queue_stopped(txq) &&
 		    (tg3_tx_avail(tnapi) > TG3_TX_WAKEUP_THRESH(tnapi)))
 			netif_tx_wake_queue(txq);
 		__netif_tx_unlock(txq);
+#else
+		netif_tx_wake_queue(txq);
+#endif
 	}
 }
 
@@ -5694,7 +5702,11 @@ static inline int tg3_coal_adaptive_rx(struct tg3 *tp, int received)
  * If both the host and chip were to write into the same ring, cache line
  * eviction could occur since both entities want it in an exclusive state.
  */
+#ifdef CONFIG_TIGON3_NAPI
 static int tg3_rx(struct tg3_napi *tnapi, int budget)
+#else
+static int tg3_rx(struct tg3_napi *tnapi)
+#endif
 {
 	struct tg3 *tp = tnapi->tp;
 	u32 work_mask, rx_std_posted = 0;
@@ -5714,7 +5726,11 @@ static int tg3_rx(struct tg3_napi *tnapi, int budget)
 	received = 0;
 	std_prod_idx = tpr->rx_std_prod_idx;
 	jmb_prod_idx = tpr->rx_jmb_prod_idx;
-	while (sw_idx != hw_idx && budget > 0) {
+	while (sw_idx != hw_idx
+#ifdef CONFIG_TIGON3_NAPI
+	       && budget > 0
+#endif
+		) {
 		struct ring_info *ri;
 		struct tg3_rx_buffer_desc *desc = &tnapi->rx_rcb[sw_idx];
 		unsigned int len;
@@ -5819,10 +5835,16 @@ static int tg3_rx(struct tg3_napi *tnapi, int budget)
 			__vlan_hwaccel_put_tag(skb,
 					       desc->err_vlan & RXD_VLAN_MASK);
 
+#ifdef CONFIG_TIGON3_NAPI
 		napi_gro_receive(&tnapi->napi, skb);
+#else
+		netif_rx(skb);
+#endif
 
 		received++;
+#ifdef CONFIG_TIGON3_NAPI
 		budget--;
+#endif
 
 next_pkt:
 		(*post_ptr)++;
@@ -5868,7 +5890,10 @@ next_pkt_nopost:
 				     tpr->rx_jmb_prod_idx);
 		}
 		mmiowb();
-	} else if (work_mask) {
+	}
+#ifdef CONFIG_TIGON3_NAPI
+	/* TG3_FLG3_ENABLE_RSS is only set in NAPI mode. */
+	else if (work_mask) {
 		/* rx_std_buffers[] and rx_jmb_buffers[] entries must be
 		 * updated before the producer indices can be updated.
 		 */
@@ -5880,6 +5905,7 @@ next_pkt_nopost:
 		if (tnapi != &tp->napi[1])
 			napi_schedule(&tp->napi[1].napi);
 	}
+#endif
 
 	return received;
 }
@@ -5893,7 +5919,12 @@ static void tg3_poll_link(struct tg3 *tp)
 		if (sblk->status & SD_STATUS_LINK_CHG) {
 			sblk->status = SD_STATUS_UPDATED |
 				       (sblk->status & ~SD_STATUS_LINK_CHG);
+
+#ifdef CONFIG_TIGON3_NAPI
 			spin_lock(&tp->lock);
+#else
+			spin_lock_bh(&tp->lock);
+#endif
 			if (tg3_flag(tp, USE_PHYLIB)) {
 				tw32_f(MAC_STATUS,
 				     (MAC_STATUS_SYNC_CHANGED |
@@ -5903,11 +5934,16 @@ static void tg3_poll_link(struct tg3 *tp)
 				udelay(40);
 			} else
 				tg3_setup_phy(tp, 0);
+#ifdef CONFIG_TIGON3_NAPI
 			spin_unlock(&tp->lock);
+#else
+			spin_unlock_bh(&tp->lock);
+#endif
 		}
 	}
 }
 
+#ifdef CONFIG_TIGON3_NAPI
 static int tg3_rx_prodring_xfer(struct tg3 *tp,
 				struct tg3_rx_prodring_set *dpr,
 				struct tg3_rx_prodring_set *spr)
@@ -6207,6 +6243,58 @@ tx_recovery:
 	return work_done;
 }
 
+#else /* !CONFIG_TIGON3_NAPI */
+
+static void tg3_poll_link_task(struct work_struct *work)
+{
+	struct tg3 *tp = container_of(work, struct tg3, poll_link_task);
+	tg3_poll_link(tp);
+}
+
+static void tg3_int_work(struct tg3_napi *tnapi)
+{
+	struct tg3 *tp = tnapi->tp;
+	struct tg3_hw_status *sblk = tnapi->hw_status;
+
+	if (tg3_flag(tp, TAGGED_STATUS)) {
+		tnapi->last_irq_tag = sblk->status_tag;
+		tnapi->last_tag = tnapi->last_irq_tag;
+	}
+
+	if (!(tg3_flag(tp, USE_LINKCHG_REG) || tg3_flag(tp, POLL_SERDES))) {
+		if (sblk->status & SD_STATUS_LINK_CHG)
+			schedule_work(&tp->poll_link_task);
+	}
+
+	/* run TX completion thread */
+	if (tnapi->hw_status->idx[0].tx_consumer != tnapi->tx_cons) {
+		tg3_tx(tnapi);
+		if (unlikely(tg3_flag(tp, TX_RECOVERY_PENDING)))
+			goto tx_recovery;
+	}
+
+	/* run RX thread */
+	if (*(tnapi->rx_rcb_prod_idx) != tnapi->rx_rcb_ptr)
+		tg3_rx(tnapi);
+
+	if (unlikely(tg3_flag(tp, TX_RECOVERY_PENDING)))
+		goto tx_recovery;
+
+	if (!(tg3_flag(tp, TAGGED_STATUS)))
+		sblk->status &= ~SD_STATUS_UPDATED;
+
+	/* Reenable interrupts */
+	tg3_int_reenable(tnapi);
+
+	return;
+
+tx_recovery:
+	schedule_work(&tp->reset_task);
+}
+
+#endif /* CONFIG_TIGON3_NAPI */
+
+#ifdef CONFIG_TIGON3_NAPI
 static void tg3_napi_disable(struct tg3 *tp)
 {
 	int i;
@@ -6239,11 +6327,14 @@ static void tg3_napi_fini(struct tg3 *tp)
 	for (i = 0; i < tp->irq_cnt; i++)
 		netif_napi_del(&tp->napi[i].napi);
 }
+#endif
 
 static inline void tg3_netif_stop(struct tg3 *tp)
 {
 	tp->dev->trans_start = jiffies;	/* prevent tx timeout */
+#ifdef CONFIG_TIGON3_NAPI
 	tg3_napi_disable(tp);
+#endif
 	netif_tx_disable(tp->dev);
 }
 
@@ -6255,7 +6346,9 @@ static inline void tg3_netif_start(struct tg3 *tp)
 	 */
 	netif_tx_wake_all_queues(tp->dev);
 
+#ifdef CONFIG_TIGON3_NAPI
 	tg3_napi_enable(tp);
+#endif
 	tp->napi[0].hw_status->status |= SD_STATUS_UPDATED;
 	tg3_enable_ints(tp);
 }
@@ -6303,7 +6396,11 @@ static irqreturn_t tg3_msi_1shot(int irq, void *dev_id)
 		prefetch(&tnapi->rx_rcb[tnapi->rx_rcb_ptr]);
 
 	if (likely(!tg3_irq_sync(tp)))
+#ifdef CONFIG_TIGON3_NAPI
 		napi_schedule(&tnapi->napi);
+#else
+		tg3_int_work(tnapi);
+#endif
 
 	return IRQ_HANDLED;
 }
@@ -6329,7 +6426,11 @@ static irqreturn_t tg3_msi(int irq, void *dev_id)
 	 */
 	tw32_mailbox(tnapi->int_mbox, 0x00000001);
 	if (likely(!tg3_irq_sync(tp)))
+#ifdef CONFIG_TIGON3_NAPI
 		napi_schedule(&tnapi->napi);
+#else
+		tg3_int_work(tnapi);
+#endif
 
 	return IRQ_RETVAL(1);
 }
@@ -6371,7 +6472,11 @@ static irqreturn_t tg3_interrupt(int irq, void *dev_id)
 	sblk->status &= ~SD_STATUS_UPDATED;
 	if (likely(tg3_has_work(tnapi))) {
 		prefetch(&tnapi->rx_rcb[tnapi->rx_rcb_ptr]);
+#ifdef CONFIG_TIGON3_NAPI
 		napi_schedule(&tnapi->napi);
+#else
+		tg3_int_work(tnapi);
+#endif
 	} else {
 		/* No work, shared interrupt perhaps?  re-enable
 		 * interrupts, and flush that PCI write
@@ -6429,7 +6534,11 @@ static irqreturn_t tg3_interrupt_tagged(int irq, void *dev_id)
 
 	prefetch(&tnapi->rx_rcb[tnapi->rx_rcb_ptr]);
 
+#ifdef CONFIG_TIGON3_NAPI
 	napi_schedule(&tnapi->napi);
+#else
+	tg3_int_work(tnapi);
+#endif
 
 out:
 	return IRQ_RETVAL(handled);
@@ -6470,7 +6579,9 @@ static int tg3_restart_hw(struct tg3 *tp, int reset_phy)
 		tg3_full_unlock(tp);
 		del_timer_sync(&tp->timer);
 		tp->irq_sync = 0;
+#ifdef CONFIG_TIGON3_NAPI
 		tg3_napi_enable(tp);
+#endif
 		dev_close(tp->dev);
 		tg3_full_lock(tp, 0);
 	}
@@ -6804,10 +6915,15 @@ static netdev_tx_t tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
 
 	budget = tg3_tx_avail(tnapi);
 
-	/* We are running in BH disabled context with netif_tx_lock
-	 * and TX reclaim runs via tp->napi.poll inside of a software
-	 * interrupt.  Furthermore, IRQ processing runs lockless so we have
-	 * no IRQ context deadlocks to worry about either.  Rejoice!
+	/* When in NAPI mode, we are running in BH disabled context
+	 * with netif_tx_lock and TX reclaim runs via tp->napi.poll
+	 * inside of a software interrupt.  Furthermore, IRQ
+	 * processing runs lockless so we have no IRQ context
+	 * deadlocks to worry about either.  Rejoice!
+	 *
+	 * When in non-NAPI mode, we are running in BH disabled
+	 * context with netif_tx_lock and TX reclaim runs lockless in
+	 * interrupt context.
 	 */
 	if (unlikely(budget <= (skb_shinfo(skb)->nr_frags + 1))) {
 		if (!netif_tx_queue_stopped(txq)) {
@@ -9847,9 +9963,10 @@ static int tg3_open(struct net_device *dev)
 	if (err)
 		goto err_out1;
 
+#ifdef CONFIG_TIGON3_NAPI
 	tg3_napi_init(tp);
-
 	tg3_napi_enable(tp);
+#endif
 
 	for (i = 0; i < tp->irq_cnt; i++) {
 		struct tg3_napi *tnapi = &tp->napi[i];
@@ -9942,8 +10059,10 @@ err_out3:
 	}
 
 err_out2:
+#ifdef CONFIG_TIGON3_NAPI
 	tg3_napi_disable(tp);
 	tg3_napi_fini(tp);
+#endif
 	tg3_free_consistent(tp);
 
 err_out1:
@@ -9958,7 +10077,9 @@ static int tg3_close(struct net_device *dev)
 	int i;
 	struct tg3 *tp = netdev_priv(dev);
 
+#ifdef CONFIG_TIGON3_NAPI
 	tg3_napi_disable(tp);
+#endif
 	tg3_reset_task_cancel(tp);
 
 	netif_tx_stop_all_queues(dev);
@@ -9988,7 +10109,9 @@ static int tg3_close(struct net_device *dev)
 	memset(&tp->net_stats_prev, 0, sizeof(tp->net_stats_prev));
 	memset(&tp->estats_prev, 0, sizeof(tp->estats_prev));
 
+#ifdef CONFIG_TIGON3_NAPI
 	tg3_napi_fini(tp);
+#endif
 
 	tg3_free_consistent(tp);
 
@@ -14223,10 +14346,13 @@ static int __devinit tg3_get_invariants(struct tg3 *tp)
 			tg3_flag_set(tp, 1SHOT_MSI);
 		}
 
+#ifdef CONFIG_TIGON3_NAPI
+		/* Do not enable MSIX in non-NAPI mode. */
 		if (tg3_flag(tp, 57765_PLUS)) {
 			tg3_flag_set(tp, SUPPORT_MSIX);
 			tp->irq_max = TG3_IRQ_MAX_VECS;
 		}
+#endif
 	}
 
 	if (tg3_flag(tp, 5755_PLUS))
@@ -15601,6 +15727,9 @@ static int __devinit tg3_init_one(struct pci_dev *pdev,
 	spin_lock_init(&tp->lock);
 	spin_lock_init(&tp->indirect_lock);
 	INIT_WORK(&tp->reset_task, tg3_reset_task);
+#ifndef CONFIG_TIGON3_NAPI
+	INIT_WORK(&tp->poll_link_task, tg3_poll_link_task);
+#endif
 
 	tp->regs = pci_ioremap_bar(pdev, BAR_0);
 	if (!tp->regs) {
@@ -15821,6 +15950,14 @@ static int __devinit tg3_init_one(struct pci_dev *pdev,
 		goto err_out_apeunmap;
 	}
 
+#ifdef CONFIG_TIGON3_NAPI
+	netdev_info(dev, "Tigon3 driver %s loaded in NAPI mode.\n",
+		    DRV_MODULE_VERSION);
+#else
+	netdev_info(dev, "Tigon3 driver %s loaded in non-NAPI mode.\n",
+		    DRV_MODULE_VERSION);
+#endif
+
 	netdev_info(dev, "Tigon3 [partno(%s) rev %04x] (%s) MAC address %pM\n",
 		    tp->board_part_number,
 		    tp->pci_chip_rev_id,
diff --git a/drivers/net/ethernet/broadcom/tg3.h b/drivers/net/ethernet/broadcom/tg3.h
index 695cf14..b023e96 100644
--- a/drivers/net/ethernet/broadcom/tg3.h
+++ b/drivers/net/ethernet/broadcom/tg3.h
@@ -2832,7 +2832,9 @@ struct tg3_rx_prodring_set {
 #define TG3_IRQ_MAX_VECS		TG3_IRQ_MAX_VECS_RSS
 
 struct tg3_napi {
+#ifdef CONFIG_TIGON3_NAPI
 	struct napi_struct		napi	____cacheline_aligned;
+#endif
 	struct tg3			*tp;
 	struct tg3_hw_status		*hw_status;
 
@@ -3167,6 +3169,9 @@ struct tg3 {
 	struct tg3_hw_stats		*hw_stats;
 	dma_addr_t			stats_mapping;
 	struct work_struct		reset_task;
+#ifndef CONFIG_TIGON3_NAPI
+	struct work_struct              poll_link_task;
+#endif
 
 	int				nvram_lock_cnt;
 	u32				nvram_size;
-- 
1.7.3.1

^ permalink raw reply related

* [PATCH net-next v1 2/6] tg3: Remove IRQF_SAMPLE_RANDOM flag from internal tests
From: David Decotigny @ 2011-12-16 18:19 UTC (permalink / raw)
  To: Matt Carlson, Michael Chan, netdev, linux-kernel
  Cc: Javier Martinez Canillas, Robin Getz, Matt Mackall,
	Maciej Żenczykowski, David Decotigny
In-Reply-To: <cover.1324059527.git.decot@googlers.com>

From: Maciej Żenczykowski <maze@google.com>

This complements commit ab392d2d6d4 ("Remove IRQF_SAMPLE_RANDOM flag
from network drivers"), removing IRQF_SAMPLE_RANDOM flag from internal
and self tests.

Tested: no visible regression on 1 actual host + ethtool -t passes.


Signed-off-by: David Decotigny <decot@googlers.com>
---
 drivers/net/ethernet/broadcom/tg3.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c
index e04c4f9..a65b419 100644
--- a/drivers/net/ethernet/broadcom/tg3.c
+++ b/drivers/net/ethernet/broadcom/tg3.c
@@ -9387,7 +9387,7 @@ static int tg3_test_interrupt(struct tg3 *tp)
 	}
 
 	err = request_irq(tnapi->irq_vec, tg3_test_isr,
-			  IRQF_SHARED | IRQF_SAMPLE_RANDOM, dev->name, tnapi);
+			  IRQF_SHARED, dev->name, tnapi);
 	if (err)
 		return err;
 
-- 
1.7.3.1

^ permalink raw reply related

* [PATCH net-next v1 3/6] tg3: Implement adaptive interrupt coalescing
From: David Decotigny @ 2011-12-16 18:19 UTC (permalink / raw)
  To: Matt Carlson, Michael Chan, netdev, linux-kernel
  Cc: Javier Martinez Canillas, Robin Getz, Matt Mackall, Ying Cai,
	David Decotigny
In-Reply-To: <cover.1324059527.git.decot@googlers.com>

From: Ying Cai <ycai@google.com>

Implement adaptive coalescing in the tg3 driver. On an opteron-based
test system, interrupt rate can be reduced from 40K intrs/sec to less
than 10K intrs/sec on netperf tests, with netperf performances gaining
2 to 14% (depending on the tests).

Example with 200 netperf streams in parallel for 100s, irq/s measured
at the netperf client host, netperf figure is cumulative on all 200
streams:

Without this patch:
TCP_RR       netperf=284208               eth0 irq/s=55141.9
TCP_CRR      netperf=32204.5              eth0 irq/s=15727.7
TCP_MAERTS   netperf=944.68               eth0 irq/s=16255.5
pktgen loopback (pkt_size 60) 484718 pps

With patch:
TCP_RR       netperf=317511  (111.72%)    eth0 irq/s=8307.77 (15.07%)
TCP_CRR      netperf=35890.1 (111.44%)    eth0 irq/s=4390.89 (27.92%)
TCP_MAERTS   netperf=971.64  (102.85%)    eth0 irq/s=8135.58 (50.05%)
pktgen loopback (pkt_size 60) 552185 pps (113.92%)



Signed-off-by: David Decotigny <decot@googlers.com>
---
 drivers/net/ethernet/broadcom/tg3.c |  174 +++++++++++++++++++++++++++++++++++
 drivers/net/ethernet/broadcom/tg3.h |   37 ++++++++
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c
index a65b419..9deb6a6 100644
--- a/drivers/net/ethernet/broadcom/tg3.c
+++ b/drivers/net/ethernet/broadcom/tg3.c
@@ -52,6 +52,7 @@
 #include <linux/io.h>
 #include <asm/byteorder.h>
 #include <linux/uaccess.h>
+#include <linux/jiffies.h>
 
 #ifdef CONFIG_SPARC
 #include <asm/idprom.h>
@@ -413,6 +414,9 @@ static const struct {
 #define TG3_NUM_TEST	ARRAY_SIZE(ethtool_test_keys)
 
 
+static inline void tg3_full_lock(struct tg3 *tp, int irq_sync);
+static inline void tg3_full_unlock(struct tg3 *tp);
+
 static void tg3_write32(struct tg3 *tp, u32 off, u32 val)
 {
 	writel(val, tp->regs + off);
@@ -5503,6 +5507,168 @@ static void tg3_recycle_rx(struct tg3_napi *tnapi,
 	src_map->data = NULL;
 }
 
+static inline int tg3_coal_adaptive_init(struct tg3 *tp)
+{
+	tp->ad.rx_jiffies         = jiffies;
+	tp->ad.rx_interval        = 0;
+	tp->ad.rx_frames          = 0;
+	tp->ad.rx_average_frames  = 0;
+	tp->ad.rx_reg_frames      = TG3_COAL_RX_FRAMES;
+	tp->ad.rx_adaptive_mode   = TG3_COAL_ADAPTIVE_MODE;
+	tp->ad.rx_frames_high     = TG3_COAL_ADAPTIVE_MAX_FRAMES;
+	tp->ad.rx_usecs_high      = TG3_COAL_ADAPTIVE_MAX_USECS;
+	tp->ad.rx_sample_interval = msecs_to_jiffies(TG3_COAL_ADAPTIVE_SAMPLE);
+
+	/* over-write tg3 stored coalescing values. Using 2.6.11 tg3
+	 * adaptive coalescing values
+	 */
+	tp->coal.rx_coalesce_usecs = TG3_COAL_RX_TICKS;
+	tp->coal.tx_coalesce_usecs = TG3_COAL_TX_TICKS;
+	tp->coal.rx_max_coalesced_frames = TG3_COAL_RX_FRAMES;
+	tp->coal.tx_max_coalesced_frames = TG3_COAL_TX_FRAMES;
+	tp->coal.rx_max_coalesced_frames_irq = TG3_COAL_RX_FRAMES;
+
+	return 0;
+}
+
+static int tg3_coal_adaptive_set(struct net_device *dev,
+				 struct ethtool_coalesce *cmd)
+{
+	struct tg3 *tp = netdev_priv(dev);
+	int i = 0;
+
+	tg3_full_lock(tp, 0);
+	/* changing adaptive coalescing resets the rx frames coalescing to
+	 * the default value. turning off adaptive coalescing, means use the
+	 * default behavior, while turning it on means starts computing now....
+	 * note that this is done before setting any hardcoded values, thus
+	 * allowing a single call to turn off adaptive coalescing and setting
+	 * a new value for hard coded (static) coalescing
+	 */
+
+	/* reset host coalescing engine. */
+	tw32(HOSTCC_MODE, 0);
+	for (i = 0; i < 2000; i++) {
+		if (!(tr32(HOSTCC_MODE) & HOSTCC_MODE_ENABLE))
+			break;
+		udelay(10);
+	}
+
+	if (tp->ad.rx_adaptive_mode != cmd->use_adaptive_rx_coalesce) {
+		tp->ad.rx_jiffies  = jiffies;
+		tp->ad.rx_frames   = 0;
+		tp->ad.rx_interval = 0;
+		tp->ad.rx_average_frames = 0;
+	}
+
+	tp->ad.rx_adaptive_mode   = cmd->use_adaptive_rx_coalesce;
+	tp->ad.rx_usecs_high      = cmd->rx_coalesce_usecs_high;
+	tp->ad.rx_frames_high     = cmd->rx_max_coalesced_frames_high;
+	tp->ad.rx_sample_interval = msecs_to_jiffies(cmd->rate_sample_interval);
+
+	tw32(HOSTCC_MODE, HOSTCC_MODE_ENABLE | tp->coalesce_mode);
+
+	tg3_full_unlock(tp);
+
+	return 0;
+}
+
+static int tg3_coal_adaptive_get(struct net_device *dev,
+				 struct ethtool_coalesce *cmd)
+{
+	struct tg3 *tp = netdev_priv(dev);
+
+	if (tp->ad.rx_adaptive_mode) {
+		tg3_full_lock(tp, 0);
+
+		cmd->rx_coalesce_usecs       = tr32(HOSTCC_RXCOL_TICKS);
+		cmd->rx_max_coalesced_frames = tr32(HOSTCC_RXMAX_FRAMES);
+
+		cmd->tx_coalesce_usecs       = tr32(HOSTCC_TXCOL_TICKS);
+		cmd->tx_max_coalesced_frames = tr32(HOSTCC_TXMAX_FRAMES);
+
+		cmd->rx_max_coalesced_frames_irq = tr32(HOSTCC_RXCOAL_MAXF_INT);
+		cmd->tx_max_coalesced_frames_irq = tr32(HOSTCC_TXCOAL_MAXF_INT);
+
+		cmd->use_adaptive_rx_coalesce     = tp->ad.rx_adaptive_mode;
+		cmd->rx_coalesce_usecs_high       = tp->ad.rx_usecs_high;
+		cmd->rx_max_coalesced_frames_high = tp->ad.rx_frames_high;
+		cmd->rate_sample_interval         = jiffies_to_msecs(
+		    tp->ad.rx_sample_interval);
+
+		tg3_full_unlock(tp);
+	}
+	return 0;
+}
+
+static inline int tg3_coal_adaptive_rx(struct tg3 *tp, int received)
+{
+	unsigned long cur_jiffies = jiffies;
+
+	unsigned long diff = cur_jiffies - tp->ad.rx_jiffies;
+
+	tp->ad.rx_interval += diff;
+
+	tp->ad.rx_jiffies  = cur_jiffies;
+	tp->ad.rx_frames  += received;
+
+	if ((tp->ad.rx_interval >= tp->ad.rx_sample_interval) &&
+	    (0 != tp->ad.rx_interval)) {
+		unsigned long rx_rate;
+
+		/* average packet per ms   */
+		tp->ad.rx_frames /= jiffies_to_msecs(tp->ad.rx_interval);
+		/* apply coalescing factor */
+		tp->ad.rx_frames >>= TG3_COAL_FACTOR_EXP;
+
+		/* compute a running average of the packet rate
+		 * the goal of the running average is to provide a faster
+		 * response to lowering rate compare to slowly increasing rate.
+		 *
+		 * if the new sample interval rate as decreased from the running
+		 * average, set the register coalescing to new sample interval
+		 * rate. else, average the new rate with the running average
+		 */
+		tp->ad.rx_average_frames += tp->ad.rx_frames;
+		tp->ad.rx_average_frames >>= 1;
+
+		if (tp->ad.rx_frames <= tp->ad.rx_average_frames)
+			rx_rate = tp->ad.rx_frames;
+		else
+			rx_rate = tp->ad.rx_average_frames;
+
+		/* adjust based on max values. Also do not set a '0' value in
+		 * the average frames, always set the average to at least one
+		 * frame. From the BCM documentation it is recommended to set
+		 * the register value to get an interrupt for every rx packet.
+		 * we could use 0, which would disable coalescing and should
+		 * have the same result.
+		 */
+		if (rx_rate > tp->ad.rx_frames_high)
+			rx_rate = tp->ad.rx_frames_high;
+		else if (0 == rx_rate)
+			rx_rate = 1;
+
+		if (rx_rate != tp->ad.rx_reg_frames) {
+			unsigned long rx_usecs;
+			rx_usecs = rx_rate *  TG3_COAL_TICK_PER_FRAME;
+			if (rx_usecs > tp->ad.rx_usecs_high)
+				rx_usecs = tp->ad.rx_usecs_high;
+
+			tw32(HOSTCC_RXCOL_TICKS, rx_usecs);
+			tw32(HOSTCC_RXMAX_FRAMES, rx_rate);
+			tw32(HOSTCC_RXCOAL_MAXF_INT, rx_rate);
+			tp->ad.rx_reg_frames = rx_rate;
+		}
+
+		tp->ad.rx_frames   = 0;
+		tp->ad.rx_interval = 0;
+
+	}
+
+	return 0;
+}
+
 /* The RX ring scheme is composed of multiple rings which post fresh
  * buffers to the chip, and one special ring the chip uses to report
  * status back to the host.
@@ -5679,6 +5845,9 @@ next_pkt_nopost:
 		}
 	}
 
+	if (TG3_COAL_ADAPTIVE_ON == tp->ad.rx_adaptive_mode)
+		tg3_coal_adaptive_rx(tp, received);
+
 	/* ACK the status ring. */
 	tnapi->rx_rcb_ptr = sw_idx;
 	tw32_rx_mbox(tnapi->consmbox, sw_idx);
@@ -11866,6 +12035,7 @@ static int tg3_get_coalesce(struct net_device *dev, struct ethtool_coalesce *ec)
 	struct tg3 *tp = netdev_priv(dev);
 
 	memcpy(ec, &tp->coal, sizeof(*ec));
+	tg3_coal_adaptive_get(dev, ec);
 	return 0;
 }
 
@@ -11915,6 +12085,8 @@ static int tg3_set_coalesce(struct net_device *dev, struct ethtool_coalesce *ec)
 	tp->coal.tx_max_coalesced_frames_irq = ec->tx_max_coalesced_frames_irq;
 	tp->coal.stats_block_coalesce_usecs = ec->stats_block_coalesce_usecs;
 
+	tg3_coal_adaptive_set(dev, ec);
+
 	if (netif_running(dev)) {
 		tg3_full_lock(tp, 0);
 		__tg3_set_coalesce(tp, &tp->coal);
@@ -15319,6 +15491,8 @@ static void __devinit tg3_init_coal(struct tg3 *tp)
 		ec->tx_coalesce_usecs_irq = 0;
 		ec->stats_block_coalesce_usecs = 0;
 	}
+
+	tg3_coal_adaptive_init(tp);
 }
 
 static const struct net_device_ops tg3_netdev_ops = {
diff --git a/drivers/net/ethernet/broadcom/tg3.h b/drivers/net/ethernet/broadcom/tg3.h
index aea8f72..695cf14 100644
--- a/drivers/net/ethernet/broadcom/tg3.h
+++ b/drivers/net/ethernet/broadcom/tg3.h
@@ -3211,6 +3211,43 @@ struct tg3 {
 
 	struct ethtool_coalesce		coal;
 
+	struct {
+		unsigned long  rx_jiffies;	  /* last read jiffies */
+		unsigned long  rx_interval;	  /* rcv interval in jiffies */
+		unsigned long  rx_reg_frames;	  /* current register value */
+		unsigned long  rx_frames;	  /* received frame in interval*/
+		unsigned long  rx_average_frames; /* computed received average */
+		unsigned int   rx_adaptive_mode;  /* adaptive mode on/off */
+		unsigned long  rx_frames_high;	  /* max coalescing frame in */
+						  /* adaptive mode */
+		unsigned long  rx_usecs_high;	  /* max usecs in adaptive  */
+						  /* mode */
+		unsigned long  rx_sample_interval;/* adaptive sample rate */
+						  /* in jiffies (msecs) */
+	} ad; /* adaptive coalescing */
+
+#define TG3_COAL_ADAPTIVE_ON	1
+#define TG3_COAL_ADAPTIVE_OFF	0
+#define TG3_COAL_ADAPTIVE_MODE	TG3_COAL_ADAPTIVE_ON
+
+/* Ticks are in TG3 register units and not in system units */
+#define TG3_COAL_FACTOR_EXP	3 /* coalescence factor, num irq per ms max. */
+				  /* this is the exponent of a power of two.  */
+
+#define TG3_COAL_TICK_PER_FRAME	10 /* tick per frame, num of us per tick */
+				   /* per frame. Used tg3 ms tick unit */
+
+#define TG3_COAL_ADAPTIVE_MAX_FRAMES	32
+#define TG3_COAL_ADAPTIVE_MAX_USECS	(TG3_COAL_ADAPTIVE_MAX_FRAMES \
+					 << TG3_COAL_TICK_PER_FRAME)
+
+#define TG3_COAL_ADAPTIVE_SAMPLE	10 /* ms samples */
+
+#define TG3_COAL_TX_FRAMES	32  /* must be <= to 1/2 TG3_TX_RING_SIZE */
+#define TG3_COAL_TX_TICKS	(TG3_COAL_TX_FRAMES * TG3_COAL_TICK_PER_FRAME)
+#define TG3_COAL_RX_FRAMES	1
+#define TG3_COAL_RX_TICKS	(TG3_COAL_RX_FRAMES * TG3_COAL_TICK_PER_FRAME)
+
 	/* firmware info */
 	const char			*fw_needed;
 	const struct firmware		*fw;
-- 
1.7.3.1

^ permalink raw reply related

* Re: nonlocal_bind and IPv6
From: David Miller @ 2011-12-16 18:20 UTC (permalink / raw)
  To: romieu; +Cc: bernat, zenczykowski, netdev, yoshfuji
In-Reply-To: <20111216111027.GA2315@electric-eye.fr.zoreil.com>

From: Francois Romieu <romieu@fr.zoreil.com>
Date: Fri, 16 Dec 2011 12:10:27 +0100

> Vincent Bernat <bernat@luffy.cx> :
>> On Fri, 16 Dec 2011 02:06:00 -0500 (EST), David Miller wrote:
>> >>04:58, Maciej Żenczykowski <zenczykowski@gmail.com> disait :
> [...]
>> >>>why not simply use the IP_TRANSPARENT or IP_FREEBIND socket
>> >>>options?
>> >>
>> >>Because  this requires  modifying each  affected software.  This
>> >>can be difficult if you don't have the source code available.
>> >
>> >But it means that it would work on every single kernel verion out
>> >there.
> [...]
>> Moreover, I am just adding the IPv6 version of this setting. The
>> IPv4 version already exists.
> 
> For IPv6 this is adding a system-scope function which will have to be
> maintained and available for ages. It will compete with the existing,
> per-application answer. The "fix you application / design" argument
> is thus stronger than with IPv4.

Another excellent point.

^ permalink raw reply

* Re: nonlocal_bind and IPv6
From: David Miller @ 2011-12-16 18:18 UTC (permalink / raw)
  To: bernat; +Cc: zenczykowski, netdev, yoshfuji
In-Reply-To: <e21a2d9682b5d83fefe3f09a07316033@luffy.cx>

From: Vincent Bernat <bernat@luffy.cx>
Date: Fri, 16 Dec 2011 10:53:48 +0100

> Moreover, I am just adding the IPv6 version of this setting. The IPv4
> version already exists.

I don't think the ipv4 feature was a wise thing to add, so just because
ipv4 has something doesn't automatically make it appropriate to support
it in ipv6 too.  So please don't use arguments like that.

^ permalink raw reply

* [PATCH net-next v1 1/6] tg3: fix possible infinite loop
From: David Decotigny @ 2011-12-16 18:19 UTC (permalink / raw)
  To: Matt Carlson, Michael Chan, netdev, linux-kernel
  Cc: Javier Martinez Canillas, Robin Getz, Matt Mackall,
	David Decotigny
In-Reply-To: <cover.1324059527.git.decot@googlers.com>

Found by browsing the code.

Tested:
  Not tested along the affected path. No regression observed.



Signed-off-by: David Decotigny <decot@googlers.com>
---
 drivers/net/ethernet/broadcom/tg3.c |    4 +++-
 1 files changed, 3 insertions(+), 1 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c
index 8bf11ca..e04c4f9 100644
--- a/drivers/net/ethernet/broadcom/tg3.c
+++ b/drivers/net/ethernet/broadcom/tg3.c
@@ -13877,8 +13877,10 @@ static int __devinit tg3_get_invariants(struct tg3 *tp)
 				continue;
 			}
 			if (pci_id->rev != PCI_ANY_ID) {
-				if (bridge->revision > pci_id->rev)
+				if (bridge->revision > pci_id->rev) {
+					pci_id++;
 					continue;
+				}
 			}
 			if (bridge->subordinate &&
 			    (bridge->subordinate->number ==
-- 
1.7.3.1

^ permalink raw reply related

* [PATCH net-next v1 0/6] tg3: adaptive interrupt coalescing, non-napi mode
From: David Decotigny @ 2011-12-16 18:19 UTC (permalink / raw)
  To: Matt Carlson, Michael Chan, netdev, linux-kernel
  Cc: Javier Martinez Canillas, Robin Getz, Matt Mackall,
	David Decotigny

This series implements adaptive interrupt coalescing for tg3 NIC,
improving performance substancially. It also implements non-NAPI mode
for specific system loads.

############################################
# Patch Set Summary:

David Decotigny (2):
  tg3: fix possible infinite loop
  tg3: move functions related to reset_task together

Maciej Żenczykowski (1):
  tg3: Remove IRQF_SAMPLE_RANDOM flag from internal tests

Tom Herbert (1):
  tg3: implementation of a non-NAPI mode

Ying Cai (2):
  tg3: Implement adaptive interrupt coalescing
  tg3: use netif_tx_start_queue instead of wake_queue when no
    reschedule needed

 drivers/net/ethernet/broadcom/Kconfig |    8 +
 drivers/net/ethernet/broadcom/tg3.c   |  379 ++++++++++++++++++++++++++++++---
 drivers/net/ethernet/broadcom/tg3.h   |   42 ++++
 3 files changed, 397 insertions(+), 32 deletions(-)

-- 
1.7.3.1

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Eric Dumazet @ 2011-12-16 18:08 UTC (permalink / raw)
  To: Christoph Lameter; +Cc: igorm, netdev, davem
In-Reply-To: <alpine.DEB.2.00.1112161128060.26765@router.home>

Le vendredi 16 décembre 2011 à 11:29 -0600, Christoph Lameter a écrit :

> I have some latency critical processes here and I wish I could get
> networking in general off the processor where the latency sensitive stuff
> is running should that process decide to do some calls that cause network
> I/O.
> 
> Traditional networking is a slow process these days.


Most of the slow process is in the process scheduler actually and cache
line misses in large TCP structures, but also in many layers (Qdisc...),
not counting icache footprint.

We slowly improve things, but its always a tradeoff (did I said code
bloat ?)

If you have dedicated network thread(s) in your application, bound to
the right cpu(s), then all network stack can be run on the cpus you
decide [ Also needs using correct irq affinities for the NIC interrupts]

This means your latency critical threads should delegate their network
IO (like disk IO) to other threads, _and_ avoid being blocked in
scheduler land.

Given the nature of socket api, I am not sure adding a layer to
transparently delegate network IO to a pool of dedicated cpus would be a
win.

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Christoph Lameter @ 2011-12-16 17:29 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: igorm, netdev, davem
In-Reply-To: <1324055686.25554.66.camel@edumazet-HP-Compaq-6005-Pro-SFF-PC>

On Fri, 16 Dec 2011, Eric Dumazet wrote:

> >  Reduction of network processing to a set of processors also will
> > have other beneficial effects in addition to cache hotness. It would
> > removing OS jitter etc etc.
> >
>
> You already can do that right now, with or without hardware help.
>
> See numerous improvements in this area (RPS/RFS/RSS/XPS ... in
> Documentation/networking/scaling.txt)

I have some latency critical processes here and I wish I could get
networking in general off the processor where the latency sensitive stuff
is running should that process decide to do some calls that cause network
I/O.

Traditional networking is a slow process these days.

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Stephen Hemminger @ 2011-12-16 17:19 UTC (permalink / raw)
  To: Christoph Lameter; +Cc: Eric Dumazet, igorm, netdev, davem
In-Reply-To: <alpine.DEB.2.00.1112161053030.26765@router.home>

On Fri, 16 Dec 2011 10:55:18 -0600 (CST)
Christoph Lameter <cl@linux.com> wrote:

> On Fri, 16 Dec 2011, Eric Dumazet wrote:
> 
> > Or we could use a hierarchical split : Say 16 (or 32 or 64) cpus share
> > same counters (must be atomic if NR_CPUS > 16/32/64)
> 
> Then you'd need to have locking or full atomic operations for the
> counters. Reduction of network processing to a set of processors also will
> have other beneficial effects in addition to cache hotness. It would
> removing OS jitter etc etc.
> 
> > percpu_alloc() -> percpugroup_alloc()
> 
> Sounds like a per cpuset/cgroup allocation?

The problem is not per cpu usage for the traffic counters at the ipv4 level. The problem is
the multiplicative growth with 10,000 interfaces and 1024 cpus! Also, IPV6
was ridiculous with keeping for per-cpu counters for things that don't matter
the ICMP counters (thanks to Eric for addressing that one. Only a few
values in the ipstats mib are really in the hot path, others seem to be handled
that way only because the code is cleaner doing it that way.

^ permalink raw reply

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
From: Dan Williams @ 2011-12-16 17:17 UTC (permalink / raw)
  To: Bjørn Mork
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <874nx0bj1d.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>

On Fri, 2011-12-16 at 17:03 +0100, Bjørn Mork wrote:
> Dan Williams <dcbw-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:
> > On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
> >
> > But I agree that eventually the full QMI protocol should be made
> >> available to userspace for other uses.  That should be fairly easy to do
> >> if you just proxy the commands.  But I'm worring about the interface.
> >> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
> >
> > It would have to be /dev/qmiX (in case you have more than one
> > QMI-capable card in the system) and it would also have to have the right
> > sysfs entries so that we could match the qmiX entry up with it's parent
> > USB interface.  Not entirely sure how to do that.
> 
> The idea of creating multiple independent devices and then stitch them
> together again using a sysfs API seems a little backwards to me.  How
> about just use ioctls and forward both request and reply?  That won't
> support unsolicited notifications, but otherwise it should be enough to
> be useful.

Typically adding more ioctls isn't well received.  Plus I don't think
there's a huge reason to do so, because it's really just a stream
protocol that's easily handled from a file descriptor.  ioctls add
unecessary structure.  Netlink might be a better approach if we want
something there, but any time you think of adding an ioctl you need to
start questioning that thought :)  I just don't think ioctls are really
required in this instance.

And we don't really have to stitch devices together; the QMI device is
really just a file descriptor with read/write operations.  It's not any
different than a serial device like ttyUSB0.  And that fits just fine
into the sysfs hierarchy just like the USB interfaces from the modem do,
and the ethernet device from the modem does.  In sysfs they all have a
common ancestor: the USB device they are provided by. That allows
connection  managers to find out that ttyUSB0 and ttyUSB1 and usb0 are
all owned by the same device, and that say usb0 is the network device
that must be set up using ttyUSB0.  This is what allows hardware
autodetection, otherwise users have to sit around editing files in /etc
to tell the machine things it should already know.

> The attached patch implements straight QMI forwarding.  Note that it is
> only intended as a demo, and *not* a submission.  I have not yet decided
> whether this is a good idea or not, and I assume that the API should
> receive some more thought and blessings from others than me before
> anything like this can be submitted.
> 
> It should maybe even be extended to something a little less
> driver/device specific.  I believe a common WWAN API for settings things
> like PIN code and APN would be very useful, in the same way the wireless
> API has made WLAN usage possible without driver specific tools.

Unfortunately there's really too much variation in WWAN modems to make
this happen in the kernel.  It's really best left to userspace.  Plus,
there are a *ton* of quirks for different devices.  On some devices you
can send a command on any AT port, on others it has to be a specific AT
port.  AT parsing isn't something we should be doing in the kernel, and
most modems use AT at this point.  Others use QMI, some QCDM, some CnS,
some WMC, etc.  A common kernel-side WWAN API is just not going to
happen.  But in userspace we have various projects like ModemManager
that try to provide that generic API and abstract the differences
between modems.  That's my primary interest in this.

> 
> But anyway, the demo does work.  Using it to send this reqest for
> firmware revision from userspace:
> 
> 0000  01 0c 00 00 02 00 00 01  00 23 00 00 00
> 
> partly decoded as:
> 
> .tf = 0x01
> .len = 0x000c
> .ctrl = 0x00 control point
> .service = 0x02
> .qmicid = 0x00
> .flags = 0x00 request
> .tid = 0x0001
> .msgid = 0x0023
> .len = 000000
> 
> 
> I get the expected reply:
> 
> 0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
> 0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
> 0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
> 0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
> 0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d
> 
> partly decoded as:
> 
> .tf = 0x01
> .len = 0x004c
> .ctrl = 0x80 service
> .service = 0x02
> .qmicid = 0x00
> .flags = 0x02 response
> .tid = 0x0001
> .msgid = 0x0023
> .len = 0x0040
> [0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
> [0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]
> 
> 
> This is while having an open connection and sending traffic over it (not
> that that should matter, but anyway..)
> 
> > Huawei writes custom firmware for their dongles. 
> 
> Somehow I find that hard to believe.  I can believe that they *build* a
> custom firmware, enabling a vendor specific set of options, setting
> their own USB descriptors etc.  And maybe even write some vendor
> specific feature.  But I would be surprised if most of their firmware
> code didn't come directly from the chipset vendor example code.

Yeah, I wasn't specific enough.  Everyone who uses a Qualcomm chipset
and licenses the firmware sources can modify it.  Some vendors just use
the straight Qualcomm firmware (mostly no-name Asian manufacturers).
But many vendors add their own AT commands, write custom protocols (CnS,
WMC, etc), some add custom network transports (Sierra), etc.  Huawei has
apparnetly decided to change the USB descriptors too.

> And the same goes for every other dongle maker.
> 
> > Gobi devices and other
> > devices that talk QMI don't  necessarily have such a full quite of AT
> > commands, yet they all talk the same QMI protocol.  It makes sense to
> > have a generic driver for this if we can.  That probably means a QMI
> > core (like you've got with the qmi_wwan stuff) and device-specific
> > drives.  The Huawei device would use the ECM-like stuff while the Gobi
> > bits would implement what gobi_net does.  They might even be almost the
> > same, I haven't looked in a while.  But they are similar enough that
> > they should be sharing most of the code.
> 
> Yes, I absolutely agree. That's why I tried to keep the qmi specific
> code as driver agnostic as possible.  Well, I could probably have done
> better, but it's a start..

So the reason gobi_net wasn't accepted was that with Gobi 1000 and 2000
chipsets, firmware needs to be loaded into the chip.  And that firmware
isn't free; you have to get it from the Windows partition of your
machine.  So davem rejected it because there was no way distros could
really test it easily since the firmware isn't freely available.

But now the Gobi 3000 stores firmware onboard, so it's worth another try
to get gobi_net upstreamed.

Dan


> 
> 
> Bjørn
> 
> differences between files attachment
> (0001-qmi_wwan-allow-QMI-proxying-via-netdev-ioctl.patch)
> From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn-yOkvZcmFvRU@public.gmane.org>
> Date: Fri, 16 Dec 2011 16:31:43 +0100
> Subject: [PATCH] qmi_wwan: allow QMI proxying via netdev ioctl
> MIME-Version: 1.0
> Content-Type: text/plain; charset=UTF-8
> Content-Transfer-Encoding: 8bit
> 
> Proxy QMI requests received via ioctl to embedded CDC commands, and
> return the embedded CDC responses.  Will not allow QMI_CTL commands.
> Shared subsystem client IDs are allocated on-demand by the driver.
> 
> Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> ---
>  drivers/net/usb/qmi.c           |  118 +++++++++++++++++++++++++++++++++++++++
>  drivers/net/usb/qmi.h           |    6 ++
>  drivers/net/usb/qmi_wwan_core.c |   60 ++++++++++++++++++++
>  3 files changed, 184 insertions(+), 0 deletions(-)
> 
> diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c
> index 78269a3..b3c022b 100644
> --- a/drivers/net/usb/qmi.c
> +++ b/drivers/net/usb/qmi.c
> @@ -459,6 +459,10 @@ static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system)
>  	u8 tlvdata[1] = { system };
>  	char buf[32];
>  
> +	/* fail if we have no slot for the cid */
> +	if (system >= sizeof(qmi->cid))
> +		return -1;
> +
>  	/* return immediately if a CID is already allocated */
>  	if (qmi->cid[system])
>  		return qmi->cid[system];
> @@ -579,6 +583,120 @@ static int qmi_dms_verify_pin(struct qmi_state *qmi)
>  
>  /* ----- exported API ----- */
>  
> +/* proxy QMI requests, using our own client and transaction IDs */
> +int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen)
> +{
> +	char *buf;
> +	struct qmux *h;
> +	struct qmi_msg *m;
> +	size_t len;
> +	u8 ucid;
> +	__le16 utid, tid;
> +	unsigned long flags = qmi->flags;
> +	int ret = -EFAULT;
> +	int timeout = 30;
> +
> +	/* we don't allow QMI_CTL, so the minimum QMI message size is: */
> +	if (buflen < sizeof(*buf) + sizeof(*m))
> +                return -EINVAL;
> +
> +
> +	buf = kmalloc(buflen, GFP_ATOMIC);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	if (copy_from_user(buf, arg, buflen))
> +		goto error;
> +
> +	/* verify that what we got is a valid QMI message and that we have a CID for the system */
> +	h = (struct qmux *)buf;
> +	len = h->len + 1; /* total QMUX length including the tf byte */
> +
> +
> +
> +	/* sanity.  Note that we disallow the QMI_CTL subsystem */
> +	if (h->tf != 0x01 || len > buflen || h->service == QMI_CTL) {
> +		ret = -EINVAL;
> +		goto error;
> +	}
> +
> +	m = (struct qmi_msg *)h->msg;
> +
> +	/* require an exact length match */
> +	if (sizeof(*h) + sizeof(*m) + m->len != len) {
> +		ret = -EINVAL;
> +		goto error;
> +	}
> +
> +	/* attempt to get a client ID for the requested subsystem */
> +	if (qmi_ctl_request_cid(qmi, h->service) < 0)
> +		goto error;
> +
> +	/* use our private cid and tid, saving the user's so that we can restore them on return */
> +	ucid = h->qmicid;
> +	utid = h->tid.w;
> +	tid = new_tid();
> +	h->qmicid = qmi->cid[h->service];
> +	h->tid.w = tid;
> +
> +	/* turn off async reads and send the request */
> +	qmi->flags &= ~QMI_FLAG_RECV;
> +	if (usb_control_msg(qmi->dev, usb_sndctrlpipe(qmi->dev, 0),
> +				USB_CDC_SEND_ENCAPSULATED_COMMAND,
> +				USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
> +				0, qmi->intfnr, buf, len, 1000) != len)
> +		goto error;
> +
> +	/* read the reply into the same buffer */
> +
> +	/* wait a while for the (correct) reply */
> +	do {
> +		ret = usb_control_msg(qmi->dev, usb_rcvctrlpipe(qmi->dev, 0),
> +			       USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
> +			       0, qmi->intfnr, buf, buflen, 1000);
> +		if (ret < 0)
> +			goto error;
> +		
> +		m = qmi_qmux_verify(buf, ret);
> +		if (m && h->tid.w == tid) /* got it */
> +			break;
> +
> +		/* handle other packets returned while waiting for the correct one */
> +		if (ret && m) {
> +			switch (h->service) {
> +			case QMI_CTL:
> +				qmi_ctl_parse(qmi, m);
> +				break;
> +			case QMI_WDS:
> +				qmi_wds_parse(qmi, m);
> +				break;
> +			case QMI_DMS:
> +				qmi_dms_parse(qmi, m);
> +			}
> +			if (qmi_debug)
> +				qmi_dump_qmux(qmi, buf, h->len + 1);
> +		}
> +		msleep(100);
> +	} while (timeout-- > 0);
> +	if (timeout == 0)
> +		goto error;
> +
> +	/* restore the users input to hide our internal id's */
> +	h->qmicid = ucid;
> +	h->tid.w = utid;
> +
> +	/* return the reply to the user */
> +	if (copy_to_user(arg, buf, h->len + 1))
> +		goto error;
> +
> +	ret = 0;
> +
> +error:
> +	qmi->flags = flags;
> +	kfree(buf);
> +	return ret;
> +}
> +
>  int qmi_reset(struct qmi_state *qmi)
>  {
>  	/* NOTE:  We do NOT clear the allocated CIDs! */
> diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h
> index 53ebafe..bf9c8fd 100644
> --- a/drivers/net/usb/qmi.h
> +++ b/drivers/net/usb/qmi.h
> @@ -82,6 +82,12 @@ struct qmi_tlv_response_data {
>  } __packed;
>  
> 
> +/* <linux/if.h> extension */
> +#define IF_IFACE_WWAN 0x1042
> +
> +/* proxy QMI requests via ioctl */
> +extern int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen);
> +
>  /* reset state to INIT */
>  extern int qmi_reset(struct qmi_state *qmi);
>  
> diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
> index 6d96465..79c3b1a 100644
> --- a/drivers/net/usb/qmi_wwan_core.c
> +++ b/drivers/net/usb/qmi_wwan_core.c
> @@ -8,6 +8,7 @@
>  
>  #include <linux/module.h>
>  #include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
>  #include <linux/ethtool.h>
>  #include <linux/mii.h>
>  #include <linux/usb.h>
> @@ -46,6 +47,57 @@ static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
>  	}
>  }
>  
> +static int qmi_wwan_ioctl(struct net_device *net, struct ifreq *ifr, int cmd)
> +{
> +        struct usbnet *dev = netdev_priv(net);
> +	struct if_settings *settings = &ifr->ifr_settings;
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	switch (cmd) {
> +	case SIOCDEVPRIVATE + 0: /* get something to verify that the private ioctl protocol */
> +		/* FIMXE: implement this! */
> +		return 0;
> +
> +	case SIOCDEVPRIVATE + 1: /* set SIM PIN */
> +		/* FIMXE: implement this! */
> +		return 0;
> +
> +	case SIOCDEVPRIVATE + 2: /* set APN */
> +		if (!capable(CAP_NET_ADMIN))
> +			return -EPERM;
> +
> +		/* cannot change APN if interface is up */
> +		if (net->flags & IFF_UP)
> +			return -EBUSY;
> +
> +		/* FIMXE: implement this! */
> +		return 0;
> +
> +	case SIOCDEVPRIVATE + 3: /* proxy QMI */
> +		if (!capable(CAP_NET_ADMIN))
> +			return -EPERM;
> +		if (settings->type != IF_IFACE_WWAN)
> +			return -EINVAL;
> +		netdev_dbg(net, "proxying userspace QMI request\n");
> +		return qmi_ioctl_proxy(info->qmi, settings->ifs_ifsu.fr, settings->size);
> +		
> +	default:
> +		netdev_dbg(net, "ioctl 0x%08x\n", cmd);
> +	}
> +        return -EINVAL;
> +}
> +
> +/* same as usbnet_netdev_ops but ioctl added */
> +static const struct net_device_ops qmi_wwan_netdev_ops = {
> +        .ndo_open               = usbnet_open,
> +        .ndo_stop               = usbnet_stop,
> +        .ndo_start_xmit         = usbnet_start_xmit,
> +        .ndo_tx_timeout         = usbnet_tx_timeout,
> +        .ndo_set_mac_address    = eth_mac_addr,
> +        .ndo_validate_addr      = eth_validate_addr,
> +        .ndo_do_ioctl           = qmi_wwan_ioctl,
> +};
> +
>  static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf)
>  {
>  	int status;
> @@ -61,6 +113,10 @@ static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *i
>  		usbnet_cdc_unbind(dev, intf);
>  		return -1;
>  	}
> +
> +	/* override default usbnet ops so that we can handle ioctls */
> +        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
> +
>  	return 0;
>  }
>  
> @@ -80,6 +136,10 @@ static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
>  		return -1;
>  
>  	info->control = intf;
> +
> +	/* override default usbnet ops so that we can handle ioctls */
> +        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
> +
>  	return 0;
>  }
>  


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

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Eric Dumazet @ 2011-12-16 17:14 UTC (permalink / raw)
  To: Christoph Lameter; +Cc: igorm, netdev, davem
In-Reply-To: <alpine.DEB.2.00.1112161053030.26765@router.home>

Le vendredi 16 décembre 2011 à 10:55 -0600, Christoph Lameter a écrit :
> On Fri, 16 Dec 2011, Eric Dumazet wrote:
> 
> > Or we could use a hierarchical split : Say 16 (or 32 or 64) cpus share
> > same counters (must be atomic if NR_CPUS > 16/32/64)
> 
> Then you'd need to have locking or full atomic operations for the
> counters.

I mentioned that : "(must be atomic if NR_CPUS > 16/32/64)"


>  Reduction of network processing to a set of processors also will
> have other beneficial effects in addition to cache hotness. It would
> removing OS jitter etc etc.
> 

You already can do that right now, with or without hardware help.

See numerous improvements in this area (RPS/RFS/RSS/XPS ... in
Documentation/networking/scaling.txt)


> > percpu_alloc() -> percpugroup_alloc()
> 
> Sounds like a per cpuset/cgroup allocation?
> 

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Christoph Lameter @ 2011-12-16 16:55 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: igorm, netdev, davem
In-Reply-To: <1324054244.25554.56.camel@edumazet-HP-Compaq-6005-Pro-SFF-PC>

On Fri, 16 Dec 2011, Eric Dumazet wrote:

> Or we could use a hierarchical split : Say 16 (or 32 or 64) cpus share
> same counters (must be atomic if NR_CPUS > 16/32/64)

Then you'd need to have locking or full atomic operations for the
counters. Reduction of network processing to a set of processors also will
have other beneficial effects in addition to cache hotness. It would
removing OS jitter etc etc.

> percpu_alloc() -> percpugroup_alloc()

Sounds like a per cpuset/cgroup allocation?

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Eric Dumazet @ 2011-12-16 16:50 UTC (permalink / raw)
  To: Christoph Lameter; +Cc: igorm, netdev, davem
In-Reply-To: <alpine.DEB.2.00.1112161043080.26765@router.home>

Le vendredi 16 décembre 2011 à 10:44 -0600, Christoph Lameter a écrit :
> On Fri, 16 Dec 2011, Eric Dumazet wrote:
> 
> > > Also I did it because ipv6 has per interface stats.
> >
> > I was considering _removing_ them, or make it optional.
> >
> > Memory costs are huge for percpu data.
> >
> 
> And these costs are going to increase as we add more processors. Intel is
> talking about hundred in the future.
> 
> So maybe we need to change the per cpu subsystem to be able to allocate
> these only for subsets of cpus and then only allow the operation of
> the network subsystems on the same subset? That would also increase cache
> hotness. Pretty radical but I think at some point we will have to consider
> that given that the number of cpus keep growing.
> 

Or we could use a hierarchical split : Say 16 (or 32 or 64) cpus share
same counters (must be atomic if NR_CPUS > 16/32/64)

percpu_alloc() -> percpugroup_alloc()

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Christoph Lameter @ 2011-12-16 16:44 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: igorm, netdev, davem
In-Reply-To: <1324053184.25554.47.camel@edumazet-HP-Compaq-6005-Pro-SFF-PC>

On Fri, 16 Dec 2011, Eric Dumazet wrote:

> > Also I did it because ipv6 has per interface stats.
>
> I was considering _removing_ them, or make it optional.
>
> Memory costs are huge for percpu data.
>

And these costs are going to increase as we add more processors. Intel is
talking about hundred in the future.

So maybe we need to change the per cpu subsystem to be able to allocate
these only for subsets of cpus and then only allow the operation of
the network subsystems on the same subset? That would also increase cache
hotness. Pretty radical but I think at some point we will have to consider
that given that the number of cpus keep growing.

^ permalink raw reply

* Re: [PATCH] asix: new device id
From: Jiri Kosina @ 2011-12-16 16:38 UTC (permalink / raw)
  To: Aurelien Jacobs; +Cc: linux-kernel, netdev
In-Reply-To: <1324050921-9007-1-git-send-email-aurel@gnuage.org>

On Fri, 16 Dec 2011, Aurelien Jacobs wrote:

> Adds the device id needed for the USB Ethernet Adapter delivered by
> ASUS with their Zenbook.
> 
> Signed-off-by: Aurelien Jacobs <aurel@gnuage.org>
> ---
>  drivers/net/usb/asix.c |    4 ++++
>  1 files changed, 4 insertions(+), 0 deletions(-)
> 
> diff --git a/drivers/net/usb/asix.c b/drivers/net/usb/asix.c
> index e6fed4d..e95f0e6 100644
> --- a/drivers/net/usb/asix.c
> +++ b/drivers/net/usb/asix.c
> @@ -1655,6 +1655,10 @@ static const struct usb_device_id	products [] = {
>  	// ASIX 88772a
>  	USB_DEVICE(0x0db0, 0xa877),
>  	.driver_info = (unsigned long) &ax88772_info,
> +}, {
> +	// Asus USB Ethernet Adapter
> +	USB_DEVICE (0x0b95, 0x7e2b),
> +	.driver_info = (unsigned long) &ax88772_info,
>  },
>  	{ },		// END
>  };

Adding netdev to CC so that it doesn't get lost.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Eric Dumazet @ 2011-12-16 16:33 UTC (permalink / raw)
  To: igorm; +Cc: netdev, davem
In-Reply-To: <CAFdo_mWY3030_WsL_awXFMaviMA5MGJP_gMs0cM=6KeGQft6-Q@mail.gmail.com>

Le vendredi 16 décembre 2011 à 16:58 +0100, Igor Maravić a écrit :
> > 1) Why is it needed ? Any RFC requires this bloat ?
> >
> 
> RFC4293 defines ipIfStatsTable.

Only as an option :

   In addition to the ipSystemStatsTable, the MIB includes the
   ipIfStatsTable.  This table counts the same items as the system table
   but does so on a per interface basis.  It is optional and may be
   ignored.  If you decide to implement it, you may wish to arrange to
   collect the data on a per-interface basis and then sum those counters
   in order to provide the aggregate system level statistics.  However,
   if you choose to provide the system level statistics by summing the
   interface level counters, no interface level statistics can be lost -
   if an interface is removed, the statistics associated with it must be
   retained.

If not enough memory is available, we cannot up the interfaces anymore
after your patches. Some people disable ipv6 on their machines because
they setup thousand of interfaces, and added memory cost of
ipIfStatsTable on IPv6 is huge.

> Also I did it because ipv6 has per interface stats.

I was considering _removing_ them, or make it optional.

Memory costs are huge for percpu data.

> 
> > 2) Every patch in a patch serie must be compilable. Think about
> > bisection. This is mandatory.
> >
> 
> OK, I didn't know about that...
> Do I need to resubmit it? If I do, I'l do that in monday.

As I said, this is _mandatory_.

Before spending time on this stuff, please wait for other people
comments.

> Would it be acceptable to send it as one big patch?

I dont think so, its too big.

^ permalink raw reply

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
From: Bjørn Mork @ 2011-12-16 16:03 UTC (permalink / raw)
  To: Dan Williams
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1323971530.23419.13.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>

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

Dan Williams <dcbw-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:
> On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
>
> But I agree that eventually the full QMI protocol should be made
>> available to userspace for other uses.  That should be fairly easy to do
>> if you just proxy the commands.  But I'm worring about the interface.
>> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
>
> It would have to be /dev/qmiX (in case you have more than one
> QMI-capable card in the system) and it would also have to have the right
> sysfs entries so that we could match the qmiX entry up with it's parent
> USB interface.  Not entirely sure how to do that.

The idea of creating multiple independent devices and then stitch them
together again using a sysfs API seems a little backwards to me.  How
about just use ioctls and forward both request and reply?  That won't
support unsolicited notifications, but otherwise it should be enough to
be useful.

The attached patch implements straight QMI forwarding.  Note that it is
only intended as a demo, and *not* a submission.  I have not yet decided
whether this is a good idea or not, and I assume that the API should
receive some more thought and blessings from others than me before
anything like this can be submitted.

It should maybe even be extended to something a little less
driver/device specific.  I believe a common WWAN API for settings things
like PIN code and APN would be very useful, in the same way the wireless
API has made WLAN usage possible without driver specific tools.


But anyway, the demo does work.  Using it to send this reqest for
firmware revision from userspace:

0000  01 0c 00 00 02 00 00 01  00 23 00 00 00

partly decoded as:

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0001
.msgid = 0x0023
.len = 000000


I get the expected reply:

0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d

partly decoded as:

.tf = 0x01
.len = 0x004c
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x0001
.msgid = 0x0023
.len = 0x0040
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]


This is while having an open connection and sending traffic over it (not
that that should matter, but anyway..)

> Huawei writes custom firmware for their dongles. 

Somehow I find that hard to believe.  I can believe that they *build* a
custom firmware, enabling a vendor specific set of options, setting
their own USB descriptors etc.  And maybe even write some vendor
specific feature.  But I would be surprised if most of their firmware
code didn't come directly from the chipset vendor example code.

And the same goes for every other dongle maker.

> Gobi devices and other
> devices that talk QMI don't  necessarily have such a full quite of AT
> commands, yet they all talk the same QMI protocol.  It makes sense to
> have a generic driver for this if we can.  That probably means a QMI
> core (like you've got with the qmi_wwan stuff) and device-specific
> drives.  The Huawei device would use the ECM-like stuff while the Gobi
> bits would implement what gobi_net does.  They might even be almost the
> same, I haven't looked in a while.  But they are similar enough that
> they should be sharing most of the code.

Yes, I absolutely agree. That's why I tried to keep the qmi specific
code as driver agnostic as possible.  Well, I could probably have done
better, but it's a start..



Bjørn


[-- Attachment #2: 0001-qmi_wwan-allow-QMI-proxying-via-netdev-ioctl.patch --]
[-- Type: text/x-diff, Size: 7869 bytes --]

>From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn-yOkvZcmFvRU@public.gmane.org>
Date: Fri, 16 Dec 2011 16:31:43 +0100
Subject: [PATCH] qmi_wwan: allow QMI proxying via netdev ioctl
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Proxy QMI requests received via ioctl to embedded CDC commands, and
return the embedded CDC responses.  Will not allow QMI_CTL commands.
Shared subsystem client IDs are allocated on-demand by the driver.

Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
---
 drivers/net/usb/qmi.c           |  118 +++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/qmi.h           |    6 ++
 drivers/net/usb/qmi_wwan_core.c |   60 ++++++++++++++++++++
 3 files changed, 184 insertions(+), 0 deletions(-)

diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c
index 78269a3..b3c022b 100644
--- a/drivers/net/usb/qmi.c
+++ b/drivers/net/usb/qmi.c
@@ -459,6 +459,10 @@ static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system)
 	u8 tlvdata[1] = { system };
 	char buf[32];
 
+	/* fail if we have no slot for the cid */
+	if (system >= sizeof(qmi->cid))
+		return -1;
+
 	/* return immediately if a CID is already allocated */
 	if (qmi->cid[system])
 		return qmi->cid[system];
@@ -579,6 +583,120 @@ static int qmi_dms_verify_pin(struct qmi_state *qmi)
 
 /* ----- exported API ----- */
 
+/* proxy QMI requests, using our own client and transaction IDs */
+int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen)
+{
+	char *buf;
+	struct qmux *h;
+	struct qmi_msg *m;
+	size_t len;
+	u8 ucid;
+	__le16 utid, tid;
+	unsigned long flags = qmi->flags;
+	int ret = -EFAULT;
+	int timeout = 30;
+
+	/* we don't allow QMI_CTL, so the minimum QMI message size is: */
+	if (buflen < sizeof(*buf) + sizeof(*m))
+                return -EINVAL;
+
+
+	buf = kmalloc(buflen, GFP_ATOMIC);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, arg, buflen))
+		goto error;
+
+	/* verify that what we got is a valid QMI message and that we have a CID for the system */
+	h = (struct qmux *)buf;
+	len = h->len + 1; /* total QMUX length including the tf byte */
+
+
+
+	/* sanity.  Note that we disallow the QMI_CTL subsystem */
+	if (h->tf != 0x01 || len > buflen || h->service == QMI_CTL) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	m = (struct qmi_msg *)h->msg;
+
+	/* require an exact length match */
+	if (sizeof(*h) + sizeof(*m) + m->len != len) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* attempt to get a client ID for the requested subsystem */
+	if (qmi_ctl_request_cid(qmi, h->service) < 0)
+		goto error;
+
+	/* use our private cid and tid, saving the user's so that we can restore them on return */
+	ucid = h->qmicid;
+	utid = h->tid.w;
+	tid = new_tid();
+	h->qmicid = qmi->cid[h->service];
+	h->tid.w = tid;
+
+	/* turn off async reads and send the request */
+	qmi->flags &= ~QMI_FLAG_RECV;
+	if (usb_control_msg(qmi->dev, usb_sndctrlpipe(qmi->dev, 0),
+				USB_CDC_SEND_ENCAPSULATED_COMMAND,
+				USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+				0, qmi->intfnr, buf, len, 1000) != len)
+		goto error;
+
+	/* read the reply into the same buffer */
+
+	/* wait a while for the (correct) reply */
+	do {
+		ret = usb_control_msg(qmi->dev, usb_rcvctrlpipe(qmi->dev, 0),
+			       USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+			       0, qmi->intfnr, buf, buflen, 1000);
+		if (ret < 0)
+			goto error;
+		
+		m = qmi_qmux_verify(buf, ret);
+		if (m && h->tid.w == tid) /* got it */
+			break;
+
+		/* handle other packets returned while waiting for the correct one */
+		if (ret && m) {
+			switch (h->service) {
+			case QMI_CTL:
+				qmi_ctl_parse(qmi, m);
+				break;
+			case QMI_WDS:
+				qmi_wds_parse(qmi, m);
+				break;
+			case QMI_DMS:
+				qmi_dms_parse(qmi, m);
+			}
+			if (qmi_debug)
+				qmi_dump_qmux(qmi, buf, h->len + 1);
+		}
+		msleep(100);
+	} while (timeout-- > 0);
+	if (timeout == 0)
+		goto error;
+
+	/* restore the users input to hide our internal id's */
+	h->qmicid = ucid;
+	h->tid.w = utid;
+
+	/* return the reply to the user */
+	if (copy_to_user(arg, buf, h->len + 1))
+		goto error;
+
+	ret = 0;
+
+error:
+	qmi->flags = flags;
+	kfree(buf);
+	return ret;
+}
+
 int qmi_reset(struct qmi_state *qmi)
 {
 	/* NOTE:  We do NOT clear the allocated CIDs! */
diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h
index 53ebafe..bf9c8fd 100644
--- a/drivers/net/usb/qmi.h
+++ b/drivers/net/usb/qmi.h
@@ -82,6 +82,12 @@ struct qmi_tlv_response_data {
 } __packed;
 
 
+/* <linux/if.h> extension */
+#define IF_IFACE_WWAN 0x1042
+
+/* proxy QMI requests via ioctl */
+extern int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen);
+
 /* reset state to INIT */
 extern int qmi_reset(struct qmi_state *qmi);
 
diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
index 6d96465..79c3b1a 100644
--- a/drivers/net/usb/qmi_wwan_core.c
+++ b/drivers/net/usb/qmi_wwan_core.c
@@ -8,6 +8,7 @@
 
 #include <linux/module.h>
 #include <linux/netdevice.h>
+#include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/mii.h>
 #include <linux/usb.h>
@@ -46,6 +47,57 @@ static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
 	}
 }
 
+static int qmi_wwan_ioctl(struct net_device *net, struct ifreq *ifr, int cmd)
+{
+        struct usbnet *dev = netdev_priv(net);
+	struct if_settings *settings = &ifr->ifr_settings;
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	switch (cmd) {
+	case SIOCDEVPRIVATE + 0: /* get something to verify that the private ioctl protocol */
+		/* FIMXE: implement this! */
+		return 0;
+
+	case SIOCDEVPRIVATE + 1: /* set SIM PIN */
+		/* FIMXE: implement this! */
+		return 0;
+
+	case SIOCDEVPRIVATE + 2: /* set APN */
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		/* cannot change APN if interface is up */
+		if (net->flags & IFF_UP)
+			return -EBUSY;
+
+		/* FIMXE: implement this! */
+		return 0;
+
+	case SIOCDEVPRIVATE + 3: /* proxy QMI */
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (settings->type != IF_IFACE_WWAN)
+			return -EINVAL;
+		netdev_dbg(net, "proxying userspace QMI request\n");
+		return qmi_ioctl_proxy(info->qmi, settings->ifs_ifsu.fr, settings->size);
+		
+	default:
+		netdev_dbg(net, "ioctl 0x%08x\n", cmd);
+	}
+        return -EINVAL;
+}
+
+/* same as usbnet_netdev_ops but ioctl added */
+static const struct net_device_ops qmi_wwan_netdev_ops = {
+        .ndo_open               = usbnet_open,
+        .ndo_stop               = usbnet_stop,
+        .ndo_start_xmit         = usbnet_start_xmit,
+        .ndo_tx_timeout         = usbnet_tx_timeout,
+        .ndo_set_mac_address    = eth_mac_addr,
+        .ndo_validate_addr      = eth_validate_addr,
+        .ndo_do_ioctl           = qmi_wwan_ioctl,
+};
+
 static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf)
 {
 	int status;
@@ -61,6 +113,10 @@ static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *i
 		usbnet_cdc_unbind(dev, intf);
 		return -1;
 	}
+
+	/* override default usbnet ops so that we can handle ioctls */
+        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
+
 	return 0;
 }
 
@@ -80,6 +136,10 @@ static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
 		return -1;
 
 	info->control = intf;
+
+	/* override default usbnet ops so that we can handle ioctls */
+        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
+
 	return 0;
 }
 
-- 
1.7.7.3


^ permalink raw reply related

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Igor Maravić @ 2011-12-16 15:58 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: netdev, davem
In-Reply-To: <1324050080.25554.34.camel@edumazet-HP-Compaq-6005-Pro-SFF-PC>

> 1) Why is it needed ? Any RFC requires this bloat ?
>

RFC4293 defines ipIfStatsTable.
Also I did it because ipv6 has per interface stats.

> 2) Every patch in a patch serie must be compilable. Think about
> bisection. This is mandatory.
>

OK, I didn't know about that...
Do I need to resubmit it? If I do, I'l do that in monday.
Would it be acceptable to send it as one big patch?
BR
Igor

^ permalink raw reply

* Re: TCP fast retransmit
From: Esztermann, Ansgar @ 2011-12-16 15:53 UTC (permalink / raw)
  To: netdev@vger.kernel.org
In-Reply-To: <1323937469.2631.31.camel@edumazet-laptop>


On Dec 15, 2011, at 9:24 , Eric Dumazet wrote:

> Really, my feeling is this trace is taken on receiver, and maybe LRO/GRO
> is buggy ?

Oh dear. I'm sorry, my mistake: in the beginning of a backup session, the server transmits to the client, so this is indeed taken on the receiver. I should have waited until the client begins to push data to the server, as that is where we've noticed the original problem of frequent RTOs.

I'll get a new trace extending all the way to the interesting part of the session...


Sorry for the confusion,

A.

-- 
Ansgar Esztermann
DV-Systemadministration
Max-Planck-Institut für biophysikalische Chemie, Abteilung 105

^ permalink raw reply

* RE: [PATCH net-next] igb: reset PHY after recovering from PHY power down
From: Wyborny, Carolyn @ 2011-12-16 15:53 UTC (permalink / raw)
  To: Koki Sanagi, netdev@vger.kernel.org, Kirsher, Jeffrey T,
	Brandeburg, Jesse, Allan, Bruce W, Skidmore, Donald C,
	Rose, Gregory V, Waskiewicz Jr, Peter P, Duyck, Alexander H,
	Ronciak, John, e1000-devel@lists.sourceforge.net
  Cc: davem@davemloft.net
In-Reply-To: <4ECDB76F.1090104@jp.fujitsu.com>



>-----Original Message-----
>From: Koki Sanagi [mailto:sanagi.koki@jp.fujitsu.com]
>Sent: Wednesday, November 23, 2011 7:18 PM
>To: netdev@vger.kernel.org; Kirsher, Jeffrey T; Brandeburg, Jesse;
>Allan, Bruce W; Wyborny, Carolyn; Skidmore, Donald C; Rose, Gregory V;
>Waskiewicz Jr, Peter P; Duyck, Alexander H; Ronciak, John; e1000-
>devel@lists.sourceforge.net
>Cc: davem@davemloft.net
>Subject: [PATCH net-next] igb: reset PHY after recovering from PHY power
>down
>
>According to 82576_Datasheet.pdf, PHY setting is lost after PHY power
>down.
>So resetting PHY is needed when recovering from PHY power down to set a
>default
>setting to PHY register.
>
>Owing to this lack, NIC doesn't link up in some rare situation.
>The situation I encountered is following.
>
>
>1.Both ports connect to switch.
>+---------+           +--------+
>|         |-----------|        |
>| 82576NS |           | switch |
>|         |-----------|        |
>+---------+           +--------+
>
>2.Detach both cables from switch.
>+---------+           +--------+
>|         |-------    |        |
>| 82576NS |           | switch |
>|         |-------    |        |
>+---------+           +--------+
>
>3.Detach one cable from one port.
>+---------+           +--------+
>|         |-------    |        |
>| 82576NS |           | switch |
>|         |           |        |
>+---------+           +--------+
>
>4.Attach that cable to the other port.(It means connecting directly each
>port)
>+---------+           +--------+
>|         |-------+   |        |
>| 82576NS |       |   | switch |
>|         |-------+   |        |
>+---------+           +--------+
>
>As a result, NIC doesn't link up sometimes.
>
>Signed-off-by: Koki Sanagi <sanagi.koki@jp.fujitsu.com>
>---
> drivers/net/ethernet/intel/igb/igb_main.c |    1 +
> 1 files changed, 1 insertions(+), 0 deletions(-)
>
>diff --git a/drivers/net/ethernet/intel/igb/igb_main.c
>b/drivers/net/ethernet/intel/igb/igb_main.c
>index bd9b30e..4d4f065 100644
>--- a/drivers/net/ethernet/intel/igb/igb_main.c
>+++ b/drivers/net/ethernet/intel/igb/igb_main.c
>@@ -2496,6 +2496,7 @@ static int igb_open(struct net_device *netdev)
> 		goto err_setup_rx;
>
> 	igb_power_up_link(adapter);
>+	igb_reset_phy(hw);
>
> 	/* before we allocate an interrupt, we must be ready to handle it.
> 	 * Setting DEBUG_SHIRQ in the kernel makes it fire an interrupt

I apologize for the delay in getting back to you.  After a review, I see that the needed change is simpler than I thought at first.  Can you resubmit with the igb_reset_phy call being added to the end of the igb_power_up_link function instead of after it in igb_open.  That way, whenever igb_power_link is called in the code, it will also execute the igb_reset_phy function which is the complete solution.  Thank you for your work on the igb driver.

Carolyn

Carolyn Wyborny
Linux Development
LAN Access Division
Intel Corporation

^ permalink raw reply

* Re: [PATCH 00/10 net-next] Introduce per interface ipv4 statistics
From: Eric Dumazet @ 2011-12-16 15:41 UTC (permalink / raw)
  To: igorm; +Cc: netdev, davem
In-Reply-To: <1324049163-11207-1-git-send-email-igorm@etf.rs>

Le vendredi 16 décembre 2011 à 16:25 +0100, igorm@etf.rs a écrit :
> From: Igor Maravic <igorm@etf.rs>
> 
> Hi all,
> in this patch series I introduced per interface statistics for ipv4.
> 
> I referenced how it was done for ipv6 per interface statistics.
> BR
> Igor

Questions/Comments are :

1) Why is it needed ? Any RFC requires this bloat ?

2) Every patch in a patch serie must be compilable. Think about
bisection. This is mandatory.

  You cannot have a patch that change prototypes, like your 06/10 
-#define ICMP_INC_STATS(net, field)     SNMP_INC_STATS((net)->mib.icmp_statistics, field)
+#define ICMP_INC_STATS(net, dev, field)        \

 Then, in another patch, 'fix' callers, like your 10/10

-               IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
+               IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INTRUNCATEDPKTS);

^ permalink raw reply

* Re: [PATCH] llc_cmsg_rcv was getting called after sk_eat_skb.
From: Daniel Baluta @ 2011-12-16 15:29 UTC (permalink / raw)
  To: Alexandru Juncu
  Cc: acme, davem, netdev, alex.juncu, Kunjan Naik, Eric Dumazet
In-Reply-To: <1324026085-11812-1-git-send-email-ajuncu@ixiacom.com>

Hello Alex,

Please add a llc: prefix to the Subject. Also the title
should suggest what is the patch supposed to do.

e.g:

llc: Don't call llc_cmsg_rcv after sk_eat_skb

On Fri, Dec 16, 2011 at 11:01 AM, Alexandru Juncu <ajuncu@ixiacom.com> wrote:
> Received non stream protocol packets were calling llc_cmsg_rcv that used a
> skb after that skb was released by sk_eat_skb. This caused received STP
> packets to generate kernel panics.
>
> Signed-off-by: Alexandru Juncu <ajuncu@ixiacom.com>
> Signed-off-by: Kunjan Naik <knaik@ixiacom.com>
> ---
>  net/llc/af_llc.c |   14 ++++++++++----
>  1 files changed, 10 insertions(+), 4 deletions(-)
>
> diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c
> index dfd3a64..a18e6c3 100644
> --- a/net/llc/af_llc.c
> +++ b/net/llc/af_llc.c
> @@ -833,15 +833,15 @@ static int llc_ui_recvmsg(struct kiocb *iocb, struct socket *sock,
>                copied += used;
>                len -= used;
>
> +               /* For non stream protcols we get one packet per recvmsg call */
> +               if (sk->sk_type != SOCK_STREAM)
> +                       goto copy_uaddr;
> +
>                if (!(flags & MSG_PEEK)) {
>                        sk_eat_skb(sk, skb, 0);
>                        *seq = 0;
>                }
>
> -               /* For non stream protcols we get one packet per recvmsg call */
> -               if (sk->sk_type != SOCK_STREAM)
> -                       goto copy_uaddr;
> -
>                /* Partial read */
>                if (used + offset < skb->len)
>                        continue;
> @@ -857,6 +857,12 @@ copy_uaddr:
>        }
>        if (llc_sk(sk)->cmsg_flags)
>                llc_cmsg_rcv(msg, skb);
> +
> +       if (!(flags & MSG_PEEK)) {
> +                       sk_eat_skb(sk, skb, 0);
> +                       *seq = 0;
> +       }
> +
>        goto out;
>  }
>
> --
> 1.7.4.1

David, Eric could you please review this?

thanks,
Daniel.

^ permalink raw reply

* [PATCH 10/10 net-next] net: Enable ipv4 per interface statistics
From: igorm @ 2011-12-16 15:26 UTC (permalink / raw)
  To: netdev; +Cc: davem, Igor Maravic
In-Reply-To: <1324049163-11207-1-git-send-email-igorm@etf.rs>

From: Igor Maravic <igorm@etf.rs>

Added argument of type net_device* to macros IP_*_STATS and ICMP_*_STATS
In most places that was trivial - just added net_device* from
which we've read net*.

Signed-off-by: Igor Maravic <igorm@etf.rs>
---
 net/bridge/br_netfilter.c       |    6 ++--
 net/dccp/ipv4.c                 |    9 ++++---
 net/ipv4/datagram.c             |    2 +-
 net/ipv4/icmp.c                 |   29 ++++++++++++++-----------
 net/ipv4/inet_connection_sock.c |    8 +++++-
 net/ipv4/ip_forward.c           |    8 ++++--
 net/ipv4/ip_fragment.c          |   43 +++++++++++++++++++++-----------------
 net/ipv4/ip_input.c             |   33 +++++++++++++++--------------
 net/ipv4/ip_output.c            |   43 +++++++++++++++++++++-----------------
 net/ipv4/ipmr.c                 |    6 +++-
 net/ipv4/ping.c                 |    8 +++---
 net/ipv4/raw.c                  |    4 +-
 net/ipv4/route.c                |    2 +-
 net/ipv4/tcp_ipv4.c             |    9 ++++---
 net/ipv4/udp.c                  |    7 +++--
 net/l2tp/l2tp_ip.c              |    6 ++--
 net/sctp/input.c                |    4 +-
 net/sctp/output.c               |    2 +-
 18 files changed, 127 insertions(+), 102 deletions(-)

diff --git a/net/bridge/br_netfilter.c b/net/bridge/br_netfilter.c
index 834dfab..a821217 100644
--- a/net/bridge/br_netfilter.c
+++ b/net/bridge/br_netfilter.c
@@ -255,13 +255,13 @@ static int br_parse_ip_options(struct sk_buff *skb)
 
 	len = ntohs(iph->tot_len);
 	if (skb->len < len) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INTRUNCATEDPKTS);
 		goto drop;
 	} else if (len < (iph->ihl*4))
 		goto inhdr_error;
 
 	if (pskb_trim_rcsum(skb, len)) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INDISCARDS);
 		goto drop;
 	}
 
@@ -286,7 +286,7 @@ static int br_parse_ip_options(struct sk_buff *skb)
 	return 0;
 
 inhdr_error:
-	IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
+	IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INHDRERRORS);
 drop:
 	return -1;
 }
diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c
index 1c67fe8..ca8a024 100644
--- a/net/dccp/ipv4.c
+++ b/net/dccp/ipv4.c
@@ -219,11 +219,12 @@ static void dccp_v4_err(struct sk_buff *skb, u32 info)
 	struct sock *sk;
 	__u64 seq;
 	int err;
-	struct net *net = dev_net(skb->dev);
+	struct net_device *dev = skb->dev;
+	struct net *net = dev_net(dev);
 
 	if (skb->len < offset + sizeof(*dh) ||
 	    skb->len < offset + __dccp_basic_hdr_len(dh)) {
-		ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 		return;
 	}
 
@@ -231,7 +232,7 @@ static void dccp_v4_err(struct sk_buff *skb, u32 info)
 			iph->daddr, dh->dccph_dport,
 			iph->saddr, dh->dccph_sport, inet_iif(skb));
 	if (sk == NULL) {
-		ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 		return;
 	}
 
@@ -488,7 +489,7 @@ static struct dst_entry* dccp_v4_route_skb(struct net *net, struct sock *sk,
 	security_skb_classify_flow(skb, flowi4_to_flowi(&fl4));
 	rt = ip_route_output_flow(net, &fl4, sk);
 	if (IS_ERR(rt)) {
-		IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
+		IP_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 		return NULL;
 	}
 
diff --git a/net/ipv4/datagram.c b/net/ipv4/datagram.c
index 424fafb..ca1d0ba 100644
--- a/net/ipv4/datagram.c
+++ b/net/ipv4/datagram.c
@@ -57,7 +57,7 @@ int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
 	if (IS_ERR(rt)) {
 		err = PTR_ERR(rt);
 		if (err == -ENETUNREACH)
-			IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
+			IP_INC_STATS_BH(sock_net(sk), NULL, IPSTATS_MIB_OUTNOROUTES);
 		goto out;
 	}
 
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index ab188ae..8a064db 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -264,10 +264,10 @@ out:
 /*
  *	Maintain the counters used in the SNMP statistics for outgoing ICMP
  */
-void icmp_out_count(struct net *net, unsigned char type)
+void icmp_out_count(struct net_device *dev, unsigned char type)
 {
-	ICMPMSGOUT_INC_STATS(net, type);
-	ICMP_INC_STATS(net, ICMP_MIB_OUTMSGS);
+	ICMPMSGOUT_INC_STATS(dev_net(dev), dev, type);
+	ICMP_INC_STATS(dev_net(dev), dev, ICMP_MIB_OUTMSGS);
 }
 
 /*
@@ -296,13 +296,14 @@ static void icmp_push_reply(struct icmp_bxm *icmp_param,
 {
 	struct sock *sk;
 	struct sk_buff *skb;
+	struct net_device *dev = (*rt)->dst.dev;
 
-	sk = icmp_sk(dev_net((*rt)->dst.dev));
+	sk = icmp_sk(dev_net(dev));
 	if (ip_append_data(sk, fl4, icmp_glue_bits, icmp_param,
 			   icmp_param->data_len+icmp_param->head_len,
 			   icmp_param->head_len,
 			   ipc, rt, MSG_DONTWAIT) < 0) {
-		ICMP_INC_STATS_BH(sock_net(sk), ICMP_MIB_OUTERRORS);
+		ICMP_INC_STATS_BH(sock_net(sk), dev, ICMP_MIB_OUTERRORS);
 		ip_flush_pending_frames(sk);
 	} else if ((skb = skb_peek(&sk->sk_write_queue)) != NULL) {
 		struct icmphdr *icmph = icmp_hdr(skb);
@@ -643,8 +644,9 @@ static void icmp_unreach(struct sk_buff *skb)
 	const struct net_protocol *ipprot;
 	u32 info = 0;
 	struct net *net;
+	struct net_device *dev = skb_dst(skb)->dev;
 
-	net = dev_net(skb_dst(skb)->dev);
+	net = dev_net(dev);
 
 	/*
 	 *	Incomplete header ?
@@ -747,7 +749,7 @@ static void icmp_unreach(struct sk_buff *skb)
 out:
 	return;
 out_err:
-	ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+	ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 	goto out;
 }
 
@@ -796,7 +798,7 @@ static void icmp_redirect(struct sk_buff *skb)
 out:
 	return;
 out_err:
-	ICMP_INC_STATS_BH(dev_net(skb->dev), ICMP_MIB_INERRORS);
+	ICMP_INC_STATS_BH(dev_net(skb->dev), skb->dev, ICMP_MIB_INERRORS);
 	goto out;
 }
 
@@ -867,7 +869,7 @@ static void icmp_timestamp(struct sk_buff *skb)
 out:
 	return;
 out_err:
-	ICMP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), ICMP_MIB_INERRORS);
+	ICMP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), skb_dst(skb)->dev, ICMP_MIB_INERRORS);
 	goto out;
 }
 
@@ -963,7 +965,8 @@ int icmp_rcv(struct sk_buff *skb)
 {
 	struct icmphdr *icmph;
 	struct rtable *rt = skb_rtable(skb);
-	struct net *net = dev_net(rt->dst.dev);
+	struct net_device *dev = rt->dst.dev;
+	struct net *net = dev_net(dev);
 
 	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
 		struct sec_path *sp = skb_sec_path(skb);
@@ -985,7 +988,7 @@ int icmp_rcv(struct sk_buff *skb)
 		skb_set_network_header(skb, nh);
 	}
 
-	ICMP_INC_STATS_BH(net, ICMP_MIB_INMSGS);
+	ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INMSGS);
 
 	switch (skb->ip_summed) {
 	case CHECKSUM_COMPLETE:
@@ -1003,7 +1006,7 @@ int icmp_rcv(struct sk_buff *skb)
 
 	icmph = icmp_hdr(skb);
 
-	ICMPMSGIN_INC_STATS_BH(net, icmph->type);
+	ICMPMSGIN_INC_STATS_BH(net, dev, icmph->type);
 	/*
 	 *	18 is the highest 'known' ICMP type. Anything else is a mystery
 	 *
@@ -1044,7 +1047,7 @@ drop:
 	kfree_skb(skb);
 	return 0;
 error:
-	ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+	ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 	goto drop;
 }
 
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 2e4e244..f527d70 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -372,9 +372,11 @@ struct dst_entry *inet_csk_route_req(struct sock *sk,
 	return &rt->dst;
 
 route_err:
+	IP_INC_STATS_BH(net, rt->dst.dev, IPSTATS_MIB_OUTNOROUTES);
 	ip_rt_put(rt);
+	return NULL;
 no_route:
-	IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
+	IP_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(inet_csk_route_req);
@@ -405,9 +407,11 @@ struct dst_entry *inet_csk_route_child_sock(struct sock *sk,
 	return &rt->dst;
 
 route_err:
+	IP_INC_STATS_BH(net, rt->dst.dev, IPSTATS_MIB_OUTNOROUTES);
 	ip_rt_put(rt);
+	return NULL;
 no_route:
-	IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
+	IP_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(inet_csk_route_child_sock);
diff --git a/net/ipv4/ip_forward.c b/net/ipv4/ip_forward.c
index 29a07b6..f8ab57e 100644
--- a/net/ipv4/ip_forward.c
+++ b/net/ipv4/ip_forward.c
@@ -43,7 +43,8 @@ static int ip_forward_finish(struct sk_buff *skb)
 {
 	struct ip_options * opt	= &(IPCB(skb)->opt);
 
-	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);
+	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), skb_dst(skb)->dev,
+		IPSTATS_MIB_OUTFORWDATAGRAMS);
 
 	if (unlikely(opt->optlen))
 		ip_forward_options(skb);
@@ -89,7 +90,7 @@ int ip_forward(struct sk_buff *skb)
 
 	if (unlikely(skb->len > dst_mtu(&rt->dst) && !skb_is_gso(skb) &&
 		     (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {
-		IP_INC_STATS(dev_net(rt->dst.dev), IPSTATS_MIB_FRAGFAILS);
+		IP_INC_STATS(dev_net(rt->dst.dev), rt->dst.dev, IPSTATS_MIB_FRAGFAILS);
 		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
 			  htonl(dst_mtu(&rt->dst)));
 		goto drop;
@@ -124,7 +125,8 @@ sr_failed:
 
 too_many_hops:
 	/* Tell the sender its packet died... */
-	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_INHDRERRORS);
+	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), skb_dst(skb)->dev,
+		IPSTATS_MIB_INHDRERRORS);
 	icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
 drop:
 	kfree_skb(skb);
diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
index fdaabf2..9aa00e1 100644
--- a/net/ipv4/ip_fragment.c
+++ b/net/ipv4/ip_fragment.c
@@ -209,13 +209,14 @@ static void ipq_kill(struct ipq *ipq)
 /* Memory limiting on fragments.  Evictor trashes the oldest
  * fragment queue until we are back under the threshold.
  */
-static void ip_evictor(struct net *net)
+static void ip_evictor(struct net_device *dev)
 {
 	int evicted;
+	struct net *net = dev_net(dev);
 
 	evicted = inet_frag_evictor(&net->ipv4.frags, &ip4_frags);
 	if (evicted)
-		IP_ADD_STATS_BH(net, IPSTATS_MIB_REASMFAILS, evicted);
+		IP_ADD_STATS_BH(net, dev, IPSTATS_MIB_REASMFAILS, evicted);
 }
 
 /*
@@ -225,6 +226,7 @@ static void ip_expire(unsigned long arg)
 {
 	struct ipq *qp;
 	struct net *net;
+	struct net_device *dev;
 
 	qp = container_of((struct inet_frag_queue *) arg, struct ipq, q);
 	net = container_of(qp->q.net, struct net, ipv4.frags);
@@ -235,19 +237,18 @@ static void ip_expire(unsigned long arg)
 		goto out;
 
 	ipq_kill(qp);
+	
+	rcu_read_lock();
+	dev = dev_get_by_index_rcu(net, qp->iif);
+	IP_INC_STATS_BH(net, dev, IPSTATS_MIB_REASMTIMEOUT);
+	IP_INC_STATS_BH(net, dev, IPSTATS_MIB_REASMFAILS);
 
-	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMTIMEOUT);
-	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
-
-	if ((qp->q.last_in & INET_FRAG_FIRST_IN) && qp->q.fragments != NULL) {
+	if ((qp->q.last_in & INET_FRAG_FIRST_IN) && qp->q.fragments && dev) {
 		struct sk_buff *head = qp->q.fragments;
 		const struct iphdr *iph;
 		int err;
 
-		rcu_read_lock();
-		head->dev = dev_get_by_index_rcu(net, qp->iif);
-		if (!head->dev)
-			goto out_rcu_unlock;
+		head->dev = dev;
 
 		/* skb dst is stale, drop it, and perform route lookup again */
 		skb_dst_drop(head);
@@ -269,9 +270,9 @@ static void ip_expire(unsigned long arg)
 
 		/* Send an ICMP "Fragment Reassembly Timeout" message. */
 		icmp_send(head, ICMP_TIME_EXCEEDED, ICMP_EXC_FRAGTIME, 0);
-out_rcu_unlock:
-		rcu_read_unlock();
 	}
+out_rcu_unlock:
+	rcu_read_unlock();
 out:
 	spin_unlock(&qp->q.lock);
 	ipq_put(qp);
@@ -325,7 +326,10 @@ static inline int ip_frag_too_far(struct ipq *qp)
 		struct net *net;
 
 		net = container_of(qp->q.net, struct net, ipv4.frags);
-		IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
+		rcu_read_lock();
+		IP_INC_STATS_BH(net, dev_get_by_index_rcu(net, qp->iif),
+			IPSTATS_MIB_REASMFAILS);
+		rcu_read_unlock();
 	}
 
 	return rc;
@@ -631,7 +635,7 @@ static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev,
 	iph->frag_off = 0;
 	iph->tot_len = htons(len);
 	iph->tos |= ecn;
-	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMOKS);
+	IP_INC_STATS_BH(net, dev, IPSTATS_MIB_REASMOKS);
 	qp->q.fragments = NULL;
 	qp->q.fragments_tail = NULL;
 	return 0;
@@ -646,7 +650,7 @@ out_oversize:
 		printk(KERN_INFO "Oversized IP packet from %pI4.\n",
 			&qp->saddr);
 out_fail:
-	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
+	IP_INC_STATS_BH(net, dev, IPSTATS_MIB_REASMFAILS);
 	return err;
 }
 
@@ -655,13 +659,14 @@ int ip_defrag(struct sk_buff *skb, u32 user)
 {
 	struct ipq *qp;
 	struct net *net;
+	struct net_device *dev = skb->dev ? skb->dev : skb_dst(skb)->dev;
 
-	net = skb->dev ? dev_net(skb->dev) : dev_net(skb_dst(skb)->dev);
-	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMREQDS);
+	net = dev_net(dev);
+	IP_INC_STATS_BH(net, dev, IPSTATS_MIB_REASMREQDS);
 
 	/* Start by cleaning up the memory. */
 	if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
-		ip_evictor(net);
+		ip_evictor(dev);
 
 	/* Lookup (or create) queue header */
 	if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {
@@ -676,7 +681,7 @@ int ip_defrag(struct sk_buff *skb, u32 user)
 		return ret;
 	}
 
-	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
+	IP_INC_STATS_BH(net, dev, IPSTATS_MIB_REASMFAILS);
 	kfree_skb(skb);
 	return -ENOMEM;
 }
diff --git a/net/ipv4/ip_input.c b/net/ipv4/ip_input.c
index 073a9b0..53f5ba0 100644
--- a/net/ipv4/ip_input.c
+++ b/net/ipv4/ip_input.c
@@ -187,8 +187,9 @@ int ip_call_ra_chain(struct sk_buff *skb)
 
 static int ip_local_deliver_finish(struct sk_buff *skb)
 {
-	struct net *net = dev_net(skb->dev);
-
+	struct net_device *dev = skb->dev;
+	struct net *net = dev_net(dev);
+	
 	__skb_pull(skb, ip_hdrlen(skb));
 
 	/* Point into the IP datagram, just past the header. */
@@ -228,16 +229,16 @@ static int ip_local_deliver_finish(struct sk_buff *skb)
 				protocol = -ret;
 				goto resubmit;
 			}
-			IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
+			IP_INC_STATS_BH(net, dev, IPSTATS_MIB_INDELIVERS);
 		} else {
 			if (!raw) {
 				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
-					IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);
+					IP_INC_STATS_BH(net, dev, IPSTATS_MIB_INUNKNOWNPROTOS);
 					icmp_send(skb, ICMP_DEST_UNREACH,
 						  ICMP_PROT_UNREACH, 0);
 				}
 			} else
-				IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
+				IP_INC_STATS_BH(net, dev, IPSTATS_MIB_INDELIVERS);
 			kfree_skb(skb);
 		}
 	}
@@ -279,7 +280,7 @@ static inline int ip_rcv_options(struct sk_buff *skb)
 					      --ANK (980813)
 	*/
 	if (skb_cow(skb, skb_headroom(skb))) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INDISCARDS);
 		goto drop;
 	}
 
@@ -288,7 +289,7 @@ static inline int ip_rcv_options(struct sk_buff *skb)
 	opt->optlen = iph->ihl*4 - sizeof(struct iphdr);
 
 	if (ip_options_compile(dev_net(dev), opt, skb)) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INHDRERRORS);
 		goto drop;
 	}
 
@@ -328,10 +329,10 @@ static int ip_rcv_finish(struct sk_buff *skb)
 					       iph->tos, skb->dev);
 		if (unlikely(err)) {
 			if (err == -EHOSTUNREACH)
-				IP_INC_STATS_BH(dev_net(skb->dev),
+				IP_INC_STATS_BH(dev_net(skb->dev), skb->dev,
 						IPSTATS_MIB_INADDRERRORS);
 			else if (err == -ENETUNREACH)
-				IP_INC_STATS_BH(dev_net(skb->dev),
+				IP_INC_STATS_BH(dev_net(skb->dev), skb->dev,
 						IPSTATS_MIB_INNOROUTES);
 			else if (err == -EXDEV)
 				NET_INC_STATS_BH(dev_net(skb->dev),
@@ -356,10 +357,10 @@ static int ip_rcv_finish(struct sk_buff *skb)
 
 	rt = skb_rtable(skb);
 	if (rt->rt_type == RTN_MULTICAST) {
-		IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,
+		IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), rt->dst.dev, IPSTATS_MIB_INMCAST,
 				skb->len);
 	} else if (rt->rt_type == RTN_BROADCAST)
-		IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,
+		IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), rt->dst.dev, IPSTATS_MIB_INBCAST,
 				skb->len);
 
 	return dst_input(skb);
@@ -384,10 +385,10 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
 		goto drop;
 
 
-	IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
+	IP_UPD_PO_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_IN, skb->len);
 
 	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INDISCARDS);
 		goto out;
 	}
 
@@ -420,7 +421,7 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
 
 	len = ntohs(iph->tot_len);
 	if (skb->len < len) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INTRUNCATEDPKTS);
 		goto drop;
 	} else if (len < (iph->ihl*4))
 		goto inhdr_error;
@@ -430,7 +431,7 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
 	 * Note this now means skb->len holds ntohs(iph->tot_len).
 	 */
 	if (pskb_trim_rcsum(skb, len)) {
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
+		IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INDISCARDS);
 		goto drop;
 	}
 
@@ -444,7 +445,7 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
 		       ip_rcv_finish);
 
 inhdr_error:
-	IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
+	IP_INC_STATS_BH(dev_net(dev), dev, IPSTATS_MIB_INHDRERRORS);
 drop:
 	kfree_skb(skb);
 out:
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index ff302bd..994bbb5 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -186,9 +186,9 @@ static inline int ip_finish_output2(struct sk_buff *skb)
 	struct neighbour *neigh;
 
 	if (rt->rt_type == RTN_MULTICAST) {
-		IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTMCAST, skb->len);
+		IP_UPD_PO_STATS(dev_net(dev), dev, IPSTATS_MIB_OUTMCAST, skb->len);
 	} else if (rt->rt_type == RTN_BROADCAST)
-		IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTBCAST, skb->len);
+		IP_UPD_PO_STATS(dev_net(dev), dev, IPSTATS_MIB_OUTBCAST, skb->len);
 
 	/* Be paranoid, rather than too clever. */
 	if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
@@ -253,7 +253,7 @@ int ip_mc_output(struct sk_buff *skb)
 	/*
 	 *	If the indicated interface is up and running, send the packet.
 	 */
-	IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);
+	IP_UPD_PO_STATS(dev_net(dev), dev, IPSTATS_MIB_OUT, skb->len);
 
 	skb->dev = dev;
 	skb->protocol = htons(ETH_P_IP);
@@ -309,7 +309,7 @@ int ip_output(struct sk_buff *skb)
 {
 	struct net_device *dev = skb_dst(skb)->dev;
 
-	IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);
+	IP_UPD_PO_STATS(dev_net(dev), dev, IPSTATS_MIB_OUT, skb->len);
 
 	skb->dev = dev;
 	skb->protocol = htons(ETH_P_IP);
@@ -375,7 +375,7 @@ int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
 					   RT_CONN_FLAGS(sk),
 					   sk->sk_bound_dev_if);
 		if (IS_ERR(rt))
-			goto no_route;
+			goto no_route_no_dev;
 		sk_setup_caps(sk, &rt->dst);
 	}
 	skb_dst_set_noref(skb, &rt->dst);
@@ -414,11 +414,16 @@ packet_routed:
 	rcu_read_unlock();
 	return res;
 
-no_route:
+out_err:
 	rcu_read_unlock();
-	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
 	kfree_skb(skb);
 	return -EHOSTUNREACH;
+no_route_no_dev:
+	IP_INC_STATS(sock_net(sk), NULL, IPSTATS_MIB_OUTNOROUTES);
+	goto out_err;
+no_route:
+	IP_INC_STATS(sock_net(sk), rt->dst.dev, IPSTATS_MIB_OUTNOROUTES);
+	goto out_err;
 }
 EXPORT_SYMBOL(ip_queue_xmit);
 
@@ -478,7 +483,7 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 	iph = ip_hdr(skb);
 
 	if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {
-		IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
+		IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGFAILS);
 		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
 			  htonl(ip_skb_dst_mtu(skb)));
 		kfree_skb(skb);
@@ -570,7 +575,7 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 			err = output(skb);
 
 			if (!err)
-				IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);
+				IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGCREATES);
 			if (err || !frag)
 				break;
 
@@ -580,7 +585,7 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 		}
 
 		if (err == 0) {
-			IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);
+			IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGOKS);
 			return 0;
 		}
 
@@ -589,7 +594,7 @@ int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 			kfree_skb(frag);
 			frag = skb;
 		}
-		IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
+		IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGFAILS);
 		return err;
 
 slow_path_clean:
@@ -708,15 +713,15 @@ slow_path:
 		if (err)
 			goto fail;
 
-		IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);
+		IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGCREATES);
 	}
 	kfree_skb(skb);
-	IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);
+	IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGOKS);
 	return err;
 
 fail:
 	kfree_skb(skb);
-	IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
+	IP_INC_STATS(dev_net(dev), dev, IPSTATS_MIB_FRAGFAILS);
 	return err;
 }
 EXPORT_SYMBOL(ip_fragment);
@@ -1049,7 +1054,7 @@ alloc_new_skb:
 
 error:
 	cork->length -= length;
-	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
+	IP_INC_STATS(sock_net(sk), rt->dst.dev, IPSTATS_MIB_OUTDISCARDS);
 	return err;
 }
 
@@ -1270,7 +1275,7 @@ ssize_t	ip_append_page(struct sock *sk, struct flowi4 *fl4, struct page *page,
 
 error:
 	cork->length -= size;
-	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
+	IP_INC_STATS(sock_net(sk), rt->dst.dev, IPSTATS_MIB_OUTDISCARDS);
 	return err;
 }
 
@@ -1295,7 +1300,6 @@ struct sk_buff *__ip_make_skb(struct sock *sk,
 	struct sk_buff *skb, *tmp_skb;
 	struct sk_buff **tail_skb;
 	struct inet_sock *inet = inet_sk(sk);
-	struct net *net = sock_net(sk);
 	struct ip_options *opt = NULL;
 	struct rtable *rt = (struct rtable *)cork->dst;
 	struct iphdr *iph;
@@ -1368,7 +1372,7 @@ struct sk_buff *__ip_make_skb(struct sock *sk,
 	skb_dst_set(skb, &rt->dst);
 
 	if (iph->protocol == IPPROTO_ICMP)
-		icmp_out_count(net, ((struct icmphdr *)
+		icmp_out_count(rt->dst.dev, ((struct icmphdr *)
 			skb_transport_header(skb))->type);
 
 	ip_cork_release(cork);
@@ -1379,6 +1383,7 @@ out:
 int ip_send_skb(struct sk_buff *skb)
 {
 	struct net *net = sock_net(skb->sk);
+	struct net_device *dev = skb_dst(skb)->dev;
 	int err;
 
 	err = ip_local_out(skb);
@@ -1386,7 +1391,7 @@ int ip_send_skb(struct sk_buff *skb)
 		if (err > 0)
 			err = net_xmit_errno(err);
 		if (err)
-			IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
+			IP_INC_STATS(net, dev, IPSTATS_MIB_OUTDISCARDS);
 	}
 
 	return err;
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index 8e54490..0aaa704 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1575,7 +1575,8 @@ static inline int ipmr_forward_finish(struct sk_buff *skb)
 {
 	struct ip_options *opt = &(IPCB(skb)->opt);
 
-	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);
+	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev),
+		skb_dst(skb)->dev, IPSTATS_MIB_OUTFORWDATAGRAMS);
 
 	if (unlikely(opt->optlen))
 		ip_forward_options(skb);
@@ -1637,7 +1638,8 @@ static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt,
 		 * to blackhole.
 		 */
 
-		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
+		IP_INC_STATS_BH(dev_net(dev), dev,
+			IPSTATS_MIB_FRAGFAILS);
 		ip_rt_put(rt);
 		goto out_free;
 	}
diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c
index 43d4c3b..10504e8 100644
--- a/net/ipv4/ping.c
+++ b/net/ipv4/ping.c
@@ -567,7 +567,7 @@ static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
 		err = PTR_ERR(rt);
 		rt = NULL;
 		if (err == -ENETUNREACH)
-			IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
+			IP_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 		goto out;
 	}
 
@@ -602,13 +602,13 @@ back_from_confirm:
 	release_sock(sk);
 
 out:
-	ip_rt_put(rt);
 	if (free)
 		kfree(ipc.opt);
 	if (!err) {
-		icmp_out_count(sock_net(sk), user_icmph.type);
-		return len;
+		icmp_out_count(rt->dst.dev, user_icmph.type);
+		err = len;
 	}
+	ip_rt_put(rt);
 	return err;
 
 do_confirm:
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index 3ccda5a..7c579c3 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -387,7 +387,7 @@ static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
 		iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
 	}
 	if (iph->protocol == IPPROTO_ICMP)
-		icmp_out_count(net, ((struct icmphdr *)
+		icmp_out_count(rt->dst.dev, ((struct icmphdr *)
 			skb_transport_header(skb))->type);
 
 	err = NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
@@ -402,7 +402,7 @@ out:
 error_free:
 	kfree_skb(skb);
 error:
-	IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
+	IP_INC_STATS(net, rt->dst.dev, IPSTATS_MIB_OUTDISCARDS);
 	if (err == -ENOBUFS && !inet->recverr)
 		err = 0;
 	return err;
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index f30112f..e9b1124 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1545,7 +1545,7 @@ static int ip_error(struct sk_buff *skb)
 	case ENETUNREACH:
 		code = ICMP_NET_UNREACH;
 		IP_INC_STATS_BH(dev_net(rt->dst.dev),
-				IPSTATS_MIB_INNOROUTES);
+				rt->dst.dev, IPSTATS_MIB_INNOROUTES);
 		break;
 	case EACCES:
 		code = ICMP_PKT_FILTERED;
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 1eb4ad5..8b99f21 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -183,7 +183,7 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
 	if (IS_ERR(rt)) {
 		err = PTR_ERR(rt);
 		if (err == -ENETUNREACH)
-			IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
+			IP_INC_STATS_BH(sock_net(sk), NULL, IPSTATS_MIB_OUTNOROUTES);
 		return err;
 	}
 
@@ -359,17 +359,18 @@ void tcp_v4_err(struct sk_buff *icmp_skb, u32 info)
 	__u32 seq;
 	__u32 remaining;
 	int err;
-	struct net *net = dev_net(icmp_skb->dev);
+	struct net_device *dev = icmp_skb->dev;
+	struct net *net = dev_net(dev);
 
 	if (icmp_skb->len < (iph->ihl << 2) + 8) {
-		ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 		return;
 	}
 
 	sk = inet_lookup(net, &tcp_hashinfo, iph->daddr, th->dest,
 			iph->saddr, th->source, inet_iif(icmp_skb));
 	if (!sk) {
-		ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 		return;
 	}
 	if (sk->sk_state == TCP_TIME_WAIT) {
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 5d075b5..b4af9c0 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -587,12 +587,13 @@ void __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable)
 	struct sock *sk;
 	int harderr;
 	int err;
-	struct net *net = dev_net(skb->dev);
+	struct net_device *dev = skb->dev;
+	struct net *net = dev_net(dev);
 
 	sk = __udp4_lib_lookup(net, iph->daddr, uh->dest,
 			iph->saddr, uh->source, skb->dev->ifindex, udptable);
 	if (sk == NULL) {
-		ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(net, dev, ICMP_MIB_INERRORS);
 		return;	/* No socket for error */
 	}
 
@@ -937,7 +938,7 @@ int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
 			err = PTR_ERR(rt);
 			rt = NULL;
 			if (err == -ENETUNREACH)
-				IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
+				IP_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 			goto out;
 		}
 
diff --git a/net/l2tp/l2tp_ip.c b/net/l2tp/l2tp_ip.c
index d21e7eb..1620860 100644
--- a/net/l2tp/l2tp_ip.c
+++ b/net/l2tp/l2tp_ip.c
@@ -330,7 +330,7 @@ static int l2tp_ip_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len
 	if (IS_ERR(rt)) {
 		rc = PTR_ERR(rt);
 		if (rc == -ENETUNREACH)
-			IP_INC_STATS_BH(&init_net, IPSTATS_MIB_OUTNOROUTES);
+			IP_INC_STATS_BH(&init_net, NULL, IPSTATS_MIB_OUTNOROUTES);
 		goto out;
 	}
 
@@ -406,7 +406,7 @@ static int l2tp_ip_backlog_recv(struct sock *sk, struct sk_buff *skb)
 	return 0;
 
 drop:
-	IP_INC_STATS(&init_net, IPSTATS_MIB_INDISCARDS);
+	IP_INC_STATS(&init_net, skb->dev, IPSTATS_MIB_INDISCARDS);
 	kfree_skb(skb);
 	return -1;
 }
@@ -532,7 +532,7 @@ out:
 
 no_route:
 	rcu_read_unlock();
-	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
+	IP_INC_STATS(sock_net(sk), NULL, IPSTATS_MIB_OUTNOROUTES);
 	kfree_skb(skb);
 	rc = -EHOSTUNREACH;
 	goto out;
diff --git a/net/sctp/input.c b/net/sctp/input.c
index 80f71af..f86f811 100644
--- a/net/sctp/input.c
+++ b/net/sctp/input.c
@@ -576,7 +576,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
 	int err;
 
 	if (skb->len < ihlen + 8) {
-		ICMP_INC_STATS_BH(&init_net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(&init_net, skb->dev, ICMP_MIB_INERRORS);
 		return;
 	}
 
@@ -590,7 +590,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
 	skb->network_header = saveip;
 	skb->transport_header = savesctp;
 	if (!sk) {
-		ICMP_INC_STATS_BH(&init_net, ICMP_MIB_INERRORS);
+		ICMP_INC_STATS_BH(&init_net, skb->dev, ICMP_MIB_INERRORS);
 		return;
 	}
 	/* Warning:  The sock lock is held.  Remember to call
diff --git a/net/sctp/output.c b/net/sctp/output.c
index 08b3cea..7fb40d0 100644
--- a/net/sctp/output.c
+++ b/net/sctp/output.c
@@ -569,7 +569,7 @@ out:
 	return err;
 no_route:
 	kfree_skb(nskb);
-	IP_INC_STATS_BH(&init_net, IPSTATS_MIB_OUTNOROUTES);
+	IP_INC_STATS_BH(&init_net, NULL, IPSTATS_MIB_OUTNOROUTES);
 
 	/* FIXME: Returning the 'err' will effect all the associations
 	 * associated with a socket, although only one of the paths of the
-- 
1.7.5.4

^ permalink raw reply related


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