diff mbox

[01/13] KS8851: Fix ks8851 snl transmit problem

Message ID 20100429231739.509103394@fluff.org.uk
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Ben Dooks April 29, 2010, 11:16 p.m. UTC
From: Tristram Ha <Tristram.Ha@micrel.com>

This fixes a transmit problem of the ks8851 snl network driver.

Under heavy TCP traffic the device will stop transmitting. Turning off
the transmit interrupt avoids this issue.  A new workqueue was
implemented to replace the functionality of the transmit interrupt processing.

Signed-off-by: Tristram Ha <Tristram.Ha@micrel.com>

---
---
 drivers/net/ks8851.c |   38 ++++++++++++++++++++------------------
 1 file changed, 20 insertions(+), 18 deletions(-)


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

Comments

David Miller May 3, 2010, 5:38 a.m. UTC | #1
From: Ben Dooks <ben@simtec.co.uk>
Date: Fri, 30 Apr 2010 00:16:22 +0100

> From: Tristram Ha <Tristram.Ha@micrel.com>
> 
> This fixes a transmit problem of the ks8851 snl network driver.
> 
> Under heavy TCP traffic the device will stop transmitting. Turning off
> the transmit interrupt avoids this issue.  A new workqueue was
> implemented to replace the functionality of the transmit interrupt processing.
> 
> Signed-off-by: Tristram Ha <Tristram.Ha@micrel.com>

Please, try to fix this properly.  Unless you have a known chip errata
with the TX interrupt that cannot be worked around reasonably, which
would need to be detailed and explained completely in the commit log
message, you should try to figure out what the real problem is.

Otherwise just tossing everything to a workqueue looks like a hack, at
best.

I suspect you have some kind of race between IRQ processing and the
->ndo_start_xmit() handler, so you end up missing a queue wakeup.
Either that or you end up misprogramming the hardware due to the race.

There is no way I'm applying this as-is.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ha, Tristram May 3, 2010, 7:06 p.m. UTC | #2
David Miller wrote:
> From: Ben Dooks <ben@simtec.co.uk>
> Date: Fri, 30 Apr 2010 00:16:22 +0100
> 
>> From: Tristram Ha <Tristram.Ha@micrel.com>
>> 
>> This fixes a transmit problem of the ks8851 snl network driver.
>> 
>> Under heavy TCP traffic the device will stop transmitting. Turning
off
>> the transmit interrupt avoids this issue.  A new workqueue was
>> implemented to replace the functionality of the transmit interrupt
processing.
>> 
>> Signed-off-by: Tristram Ha <Tristram.Ha@micrel.com>
> 
> Please, try to fix this properly.  Unless you have a known chip errata
with the TX interrupt
> that cannot be worked around reasonably, which would need to be
detailed and explained
> completely in the commit log message, you should try to figure out
what the real problem is.  
> 
> Otherwise just tossing everything to a workqueue looks like a hack, at
best.
> 
> I suspect you have some kind of race between IRQ processing and the
> ->ndo_start_xmit() handler, so you end up missing a queue wakeup.
> Either that or you end up misprogramming the hardware due to the race.
> 
> There is no way I'm applying this as-is.

As I explained a long time ago (last year), this patch is no longer
considered as a fix but for performance.

The transmit done interrupt in the KSZ8851 chips is not required for
normal operation.  Turning it off actually will improve transmit
performance because the system will not be interrupted every time a
packet is sent.

This driver runs on SPI bus.  Just reading register requires a workqueue
because it cannot be done under interrupt context.  Processing the
transmit interrupt requires scheduling a workqueue anyway.

I tested the driver under the Beagle Zippy2 board with a 24 MHz SPI bus
clock, which limits the throughput to 10 Mbps.  On other systems the
transmit performance improvement may be greater, but I do not have that
data to back me up.

If you feel strongly that this workqueue implementation is not
appropriate, then please disregard the patch.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller May 3, 2010, 8:03 p.m. UTC | #3
From: "Ha, Tristram" <Tristram.Ha@Micrel.Com>
Date: Mon, 3 May 2010 12:06:21 -0700

> The transmit done interrupt in the KSZ8851 chips is not required for
> normal operation.  Turning it off actually will improve transmit
> performance because the system will not be interrupted every time a
> packet is sent.

But you only trigger this workqueue when you notice in ->ndo_start_xmit()
that you're out of space.

This makes the chip sit idle with no packets to send until the workqueue
executes asynchronously to the initial transmit path which noticed the
queue was full.

That doesn't make any sense to me.  If anything you should at least try
to purge the TX queue and make space directly in the ->ndo_start_xmit()
handler.  And if that fails trigger an hrtimer to poll the TX state.

Without some kind of timer based polling mechanism, if the workqueue
finds the TX queue is still full, what's going to do more checks
later?  You will no longer get ->ndo_start_xmit() calls because the
queue has been marked full, so nothing will trigger the workqueue to
run any more.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller May 3, 2010, 8:04 p.m. UTC | #4
And btw, your commit message should have explained all of the things
you're telling me here.  Rather than just mention some vague "problem".

Your commit messages should explain everything about why you're
making the change, not just say what changes are being made.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ha, Tristram May 3, 2010, 9:11 p.m. UTC | #5
David Miller wrote:
> From: "Ha, Tristram" <Tristram.Ha@Micrel.Com>
> Date: Mon, 3 May 2010 12:06:21 -0700
> 
>> The transmit done interrupt in the KSZ8851 chips is not required for
>> normal operation.  Turning it off actually will improve transmit
>> performance because the system will not be interrupted every time a
>> packet is sent.
> 
> But you only trigger this workqueue when you notice in
->ndo_start_xmit() that you're out of
> space. 
> 
> This makes the chip sit idle with no packets to send until the
workqueue executes asynchronously
> to the initial transmit path which noticed the queue was full. 
> 
> That doesn't make any sense to me.  If anything you should at least
try to purge the TX queue
> and make space directly in the ->ndo_start_xmit() handler.  And if
that fails trigger an hrtimer
> to poll the TX state.  
> 
> Without some kind of timer based polling mechanism, if the workqueue
finds the TX queue is still
> full, what's going to do more checks later?  You will no longer get
->ndo_start_xmit() calls
> because the queue has been marked full, so nothing will trigger the
workqueue to run any more.  

I thought the transmit check workqueue reschedules itself when the
buffer available is still not enough, and this is the part you objected.

The buffer has about 8K.  Suppose 5 1514-byte packets are sent and the
buffer is not enough to hold the next packet and so the transmit queue
is stopped.  A workqueue is scheduled to read the buffer available
register.  As previous packets should have been sent the buffer size
read is big enough and so the transmit queue is restarted.  This happens
very often as the network bandwidth is only 10 Mbps but the CPU speed is
very fast.

I should also read the buffer available register during receive
interrupt processing so that this situation does not trigger in slow
traffic.

This roundabout way of using workqueue is because the register cannot be
read directly inside ndo_start_xmit().

Actually in other driver with similar transmit buffer available
operation I just return NETDEV_TX_BUSY and let the kernel stack calls
ndo_start_xmit again and again.  Supposedly it is bad for the whole
system but the overall transmit throughput is much better than when the
transmit done interrupt is enabled.

There should be a way to trigger the transmit interrupt after certain
number of packets are sent.  That will improve the performance and allay
your concern.  As I am not the engineer who worked on the Micrel KSZ8851
chip during its development, I am not quite aware of its functions and
limitations.  I will try the new code and submit a better patch if
possible.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller May 3, 2010, 9:13 p.m. UTC | #6
From: "Ha, Tristram" <Tristram.Ha@Micrel.Com>
Date: Mon, 3 May 2010 14:11:41 -0700

> I thought the transmit check workqueue reschedules itself when the
> buffer available is still not enough, and this is the part you objected.

If it reschedules itself, it runs immediately.  That will just hog a cpu
endlessly until the TX packets start to be transmitted by the chip.  That's
just as bad a polling in a loop.

You need to use an hrtimer or similar.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

Index: b/drivers/net/ks8851.c
===================================================================
--- a/drivers/net/ks8851.c	2010-04-20 18:13:58.000000000 +0100
+++ b/drivers/net/ks8851.c	2010-04-20 18:38:18.000000000 +0100
@@ -111,11 +111,13 @@  struct ks8851_net {
 	struct mii_if_info	mii;
 	struct ks8851_rxctrl	rxctrl;
 
+	struct work_struct	tx_check;
 	struct work_struct	tx_work;
 	struct work_struct	irq_work;
 	struct work_struct	rxctrl_work;
 
 	struct sk_buff_head	txq;
+	int			tx_len;
 
 	struct spi_message	spi_msg1;
 	struct spi_message	spi_msg2;
@@ -573,19 +575,6 @@  static void ks8851_irq_work(struct work_
 	if (status & IRQ_RXPSI)
 		handled |= IRQ_RXPSI;
 
-	if (status & IRQ_TXI) {
-		handled |= IRQ_TXI;
-
-		/* no lock here, tx queue should have been stopped */
-
-		/* update our idea of how much tx space is available to the
-		 * system */
-		ks->tx_space = ks8851_rdreg16(ks, KS_TXMIR);
-
-		if (netif_msg_intr(ks))
-			ks_dbg(ks, "%s: txspace %d\n", __func__, ks->tx_space);
-	}
-
 	if (status & IRQ_RXI)
 		handled |= IRQ_RXI;
 
@@ -623,9 +612,6 @@  static void ks8851_irq_work(struct work_
 
 	mutex_unlock(&ks->lock);
 
-	if (status & IRQ_TXI)
-		netif_wake_queue(ks->netdev);
-
 	enable_irq(ks->netdev->irq);
 }
 
@@ -703,6 +689,17 @@  static void ks8851_done_tx(struct ks8851
 	dev_kfree_skb(txb);
 }
 
+static void ks8851_tx_check(struct work_struct *work)
+{
+	struct ks8851_net *ks = container_of(work, struct ks8851_net, tx_check);
+
+	ks->tx_space = ks8851_rdreg16(ks, KS_TXMIR);
+	if (ks->tx_space > ks->tx_len)
+		netif_wake_queue(ks->netdev);
+	else
+		schedule_work(&ks->tx_check);
+}
+
 /**
  * ks8851_tx_work - process tx packet(s)
  * @work: The work strucutre what was scheduled.
@@ -814,7 +811,6 @@  static int ks8851_net_open(struct net_de
 	/* clear then enable interrupts */
 
 #define STD_IRQ (IRQ_LCI |	/* Link Change */	\
-		 IRQ_TXI |	/* TX done */		\
 		 IRQ_RXI |	/* RX done */		\
 		 IRQ_SPIBEI |	/* SPI bus error */	\
 		 IRQ_TXPSI |	/* TX process stop */	\
@@ -830,6 +826,7 @@  static int ks8851_net_open(struct net_de
 		ks_dbg(ks, "network device %s up\n", dev->name);
 
 	mutex_unlock(&ks->lock);
+	ks8851_write_mac_addr(dev);
 	return 0;
 }
 
@@ -854,6 +851,7 @@  static int ks8851_net_stop(struct net_de
 
 	/* stop any outstanding work */
 	flush_work(&ks->irq_work);
+	flush_work(&ks->tx_check);
 	flush_work(&ks->tx_work);
 	flush_work(&ks->rxctrl_work);
 
@@ -912,14 +910,16 @@  static netdev_tx_t ks8851_start_xmit(str
 
 	if (needed > ks->tx_space) {
 		netif_stop_queue(dev);
+		ks->tx_len = needed;
+		schedule_work(&ks->tx_check);
 		ret = NETDEV_TX_BUSY;
 	} else {
 		ks->tx_space -= needed;
 		skb_queue_tail(&ks->txq, skb);
+		schedule_work(&ks->tx_work);
 	}
 
 	spin_unlock(&ks->statelock);
-	schedule_work(&ks->tx_work);
 
 	return ret;
 }
@@ -1227,6 +1227,7 @@  static int __devinit ks8851_probe(struct
 	mutex_init(&ks->lock);
 	spin_lock_init(&ks->statelock);
 
+	INIT_WORK(&ks->tx_check, ks8851_tx_check);
 	INIT_WORK(&ks->tx_work, ks8851_tx_work);
 	INIT_WORK(&ks->irq_work, ks8851_irq_work);
 	INIT_WORK(&ks->rxctrl_work, ks8851_rxctrl_work);
@@ -1279,6 +1280,7 @@  static int __devinit ks8851_probe(struct
 
 	ks8851_read_selftest(ks);
 	ks8851_init_mac(ks);
+	ks->tx_space = ks8851_rdreg16(ks, KS_TXMIR);
 
 	ret = request_irq(spi->irq, ks8851_irq, IRQF_TRIGGER_LOW,
 			  ndev->name, ks);