[PATCH] natsemi: NAPI and a bugfix
This patch converts the natsemi driver to use NAPI. It was originally based on one written by Harald Welte, though it has since been modified quite a bit, most extensively in order to remove the ability to disable NAPI since none of the other drivers seem to provide that functionality any more. Signed-off-by: Mark Brown <broonie@sirena.org.uk> Signed-off-by: Jeff Garzik <jeff@garzik.org>
This commit is contained in:
parent
fb15b9dd8b
commit
b27a16b7c4
@ -3,6 +3,7 @@
|
||||
Written/copyright 1999-2001 by Donald Becker.
|
||||
Portions copyright (c) 2001,2002 Sun Microsystems (thockin@sun.com)
|
||||
Portions copyright 2001,2002 Manfred Spraul (manfred@colorfullife.com)
|
||||
Portions copyright 2004 Harald Welte <laforge@gnumonks.org>
|
||||
|
||||
This software may be used and distributed according to the terms of
|
||||
the GNU General Public License (GPL), incorporated herein by reference.
|
||||
@ -135,8 +136,6 @@
|
||||
|
||||
TODO:
|
||||
* big endian support with CFG:BEM instead of cpu_to_le32
|
||||
* support for an external PHY
|
||||
* NAPI
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
@ -160,6 +159,7 @@
|
||||
#include <linux/mii.h>
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/prefetch.h>
|
||||
#include <asm/processor.h> /* Processor type for cache alignment. */
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
@ -183,8 +183,6 @@
|
||||
NETIF_MSG_TX_ERR)
|
||||
static int debug = -1;
|
||||
|
||||
/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
|
||||
static int max_interrupt_work = 20;
|
||||
static int mtu;
|
||||
|
||||
/* Maximum number of multicast addresses to filter (vs. rx-all-multicast).
|
||||
@ -251,14 +249,11 @@ MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
|
||||
MODULE_DESCRIPTION("National Semiconductor DP8381x series PCI Ethernet driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_param(max_interrupt_work, int, 0);
|
||||
module_param(mtu, int, 0);
|
||||
module_param(debug, int, 0);
|
||||
module_param(rx_copybreak, int, 0);
|
||||
module_param_array(options, int, NULL, 0);
|
||||
module_param_array(full_duplex, int, NULL, 0);
|
||||
MODULE_PARM_DESC(max_interrupt_work,
|
||||
"DP8381x maximum events handled per interrupt");
|
||||
MODULE_PARM_DESC(mtu, "DP8381x MTU (all boards)");
|
||||
MODULE_PARM_DESC(debug, "DP8381x default debug level");
|
||||
MODULE_PARM_DESC(rx_copybreak,
|
||||
@ -691,6 +686,8 @@ struct netdev_private {
|
||||
/* Based on MTU+slack. */
|
||||
unsigned int rx_buf_sz;
|
||||
int oom;
|
||||
/* Interrupt status */
|
||||
u32 intr_status;
|
||||
/* Do not touch the nic registers */
|
||||
int hands_off;
|
||||
/* external phy that is used: only valid if dev->if_port != PORT_TP */
|
||||
@ -748,7 +745,8 @@ static void init_registers(struct net_device *dev);
|
||||
static int start_tx(struct sk_buff *skb, struct net_device *dev);
|
||||
static irqreturn_t intr_handler(int irq, void *dev_instance, struct pt_regs *regs);
|
||||
static void netdev_error(struct net_device *dev, int intr_status);
|
||||
static void netdev_rx(struct net_device *dev);
|
||||
static int natsemi_poll(struct net_device *dev, int *budget);
|
||||
static void netdev_rx(struct net_device *dev, int *work_done, int work_to_do);
|
||||
static void netdev_tx_done(struct net_device *dev);
|
||||
static int natsemi_change_mtu(struct net_device *dev, int new_mtu);
|
||||
#ifdef CONFIG_NET_POLL_CONTROLLER
|
||||
@ -776,6 +774,18 @@ static inline void __iomem *ns_ioaddr(struct net_device *dev)
|
||||
return (void __iomem *) dev->base_addr;
|
||||
}
|
||||
|
||||
static inline void natsemi_irq_enable(struct net_device *dev)
|
||||
{
|
||||
writel(1, ns_ioaddr(dev) + IntrEnable);
|
||||
readl(ns_ioaddr(dev) + IntrEnable);
|
||||
}
|
||||
|
||||
static inline void natsemi_irq_disable(struct net_device *dev)
|
||||
{
|
||||
writel(0, ns_ioaddr(dev) + IntrEnable);
|
||||
readl(ns_ioaddr(dev) + IntrEnable);
|
||||
}
|
||||
|
||||
static void move_int_phy(struct net_device *dev, int addr)
|
||||
{
|
||||
struct netdev_private *np = netdev_priv(dev);
|
||||
@ -879,6 +889,7 @@ static int __devinit natsemi_probe1 (struct pci_dev *pdev,
|
||||
spin_lock_init(&np->lock);
|
||||
np->msg_enable = (debug >= 0) ? (1<<debug)-1 : NATSEMI_DEF_MSG;
|
||||
np->hands_off = 0;
|
||||
np->intr_status = 0;
|
||||
|
||||
/* Initial port:
|
||||
* - If the nic was configured to use an external phy and if find_mii
|
||||
@ -932,6 +943,9 @@ static int __devinit natsemi_probe1 (struct pci_dev *pdev,
|
||||
dev->do_ioctl = &netdev_ioctl;
|
||||
dev->tx_timeout = &tx_timeout;
|
||||
dev->watchdog_timeo = TX_TIMEOUT;
|
||||
dev->poll = natsemi_poll;
|
||||
dev->weight = 64;
|
||||
|
||||
#ifdef CONFIG_NET_POLL_CONTROLLER
|
||||
dev->poll_controller = &natsemi_poll_controller;
|
||||
#endif
|
||||
@ -2158,68 +2172,92 @@ static void netdev_tx_done(struct net_device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
/* The interrupt handler does all of the Rx thread work and cleans up
|
||||
after the Tx thread. */
|
||||
/* The interrupt handler doesn't actually handle interrupts itself, it
|
||||
* schedules a NAPI poll if there is anything to do. */
|
||||
static irqreturn_t intr_handler(int irq, void *dev_instance, struct pt_regs *rgs)
|
||||
{
|
||||
struct net_device *dev = dev_instance;
|
||||
struct netdev_private *np = netdev_priv(dev);
|
||||
void __iomem * ioaddr = ns_ioaddr(dev);
|
||||
int boguscnt = max_interrupt_work;
|
||||
unsigned int handled = 0;
|
||||
|
||||
if (np->hands_off)
|
||||
return IRQ_NONE;
|
||||
|
||||
/* Reading automatically acknowledges. */
|
||||
np->intr_status = readl(ioaddr + IntrStatus);
|
||||
|
||||
if (netif_msg_intr(np))
|
||||
printk(KERN_DEBUG
|
||||
"%s: Interrupt, status %#08x, mask %#08x.\n",
|
||||
dev->name, np->intr_status,
|
||||
readl(ioaddr + IntrMask));
|
||||
|
||||
if (!np->intr_status)
|
||||
return IRQ_NONE;
|
||||
|
||||
prefetch(&np->rx_skbuff[np->cur_rx % RX_RING_SIZE]);
|
||||
|
||||
if (netif_rx_schedule_prep(dev)) {
|
||||
/* Disable interrupts and register for poll */
|
||||
natsemi_irq_disable(dev);
|
||||
__netif_rx_schedule(dev);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* This is the NAPI poll routine. As well as the standard RX handling
|
||||
* it also handles all other interrupts that the chip might raise.
|
||||
*/
|
||||
static int natsemi_poll(struct net_device *dev, int *budget)
|
||||
{
|
||||
struct netdev_private *np = netdev_priv(dev);
|
||||
void __iomem * ioaddr = ns_ioaddr(dev);
|
||||
|
||||
int work_to_do = min(*budget, dev->quota);
|
||||
int work_done = 0;
|
||||
|
||||
do {
|
||||
/* Reading automatically acknowledges all int sources. */
|
||||
u32 intr_status = readl(ioaddr + IntrStatus);
|
||||
|
||||
if (netif_msg_intr(np))
|
||||
printk(KERN_DEBUG
|
||||
"%s: Interrupt, status %#08x, mask %#08x.\n",
|
||||
dev->name, intr_status,
|
||||
readl(ioaddr + IntrMask));
|
||||
|
||||
if (intr_status == 0)
|
||||
break;
|
||||
handled = 1;
|
||||
|
||||
if (intr_status &
|
||||
(IntrRxDone | IntrRxIntr | RxStatusFIFOOver |
|
||||
IntrRxErr | IntrRxOverrun)) {
|
||||
netdev_rx(dev);
|
||||
}
|
||||
|
||||
if (intr_status &
|
||||
(IntrTxDone | IntrTxIntr | IntrTxIdle | IntrTxErr)) {
|
||||
if (np->intr_status &
|
||||
(IntrTxDone | IntrTxIntr | IntrTxIdle | IntrTxErr)) {
|
||||
spin_lock(&np->lock);
|
||||
netdev_tx_done(dev);
|
||||
spin_unlock(&np->lock);
|
||||
}
|
||||
|
||||
/* Abnormal error summary/uncommon events handlers. */
|
||||
if (intr_status & IntrAbnormalSummary)
|
||||
netdev_error(dev, intr_status);
|
||||
|
||||
if (--boguscnt < 0) {
|
||||
if (netif_msg_intr(np))
|
||||
printk(KERN_WARNING
|
||||
"%s: Too much work at interrupt, "
|
||||
"status=%#08x.\n",
|
||||
dev->name, intr_status);
|
||||
break;
|
||||
if (np->intr_status & IntrAbnormalSummary)
|
||||
netdev_error(dev, np->intr_status);
|
||||
|
||||
if (np->intr_status &
|
||||
(IntrRxDone | IntrRxIntr | RxStatusFIFOOver |
|
||||
IntrRxErr | IntrRxOverrun)) {
|
||||
netdev_rx(dev, &work_done, work_to_do);
|
||||
}
|
||||
} while (1);
|
||||
|
||||
*budget -= work_done;
|
||||
dev->quota -= work_done;
|
||||
|
||||
if (netif_msg_intr(np))
|
||||
printk(KERN_DEBUG "%s: exiting interrupt.\n", dev->name);
|
||||
if (work_done >= work_to_do)
|
||||
return 1;
|
||||
|
||||
return IRQ_RETVAL(handled);
|
||||
np->intr_status = readl(ioaddr + IntrStatus);
|
||||
} while (np->intr_status);
|
||||
|
||||
netif_rx_complete(dev);
|
||||
|
||||
/* Reenable interrupts providing nothing is trying to shut
|
||||
* the chip down. */
|
||||
spin_lock(&np->lock);
|
||||
if (!np->hands_off && netif_running(dev))
|
||||
natsemi_irq_enable(dev);
|
||||
spin_unlock(&np->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This routine is logically part of the interrupt handler, but separated
|
||||
for clarity and better register allocation. */
|
||||
static void netdev_rx(struct net_device *dev)
|
||||
static void netdev_rx(struct net_device *dev, int *work_done, int work_to_do)
|
||||
{
|
||||
struct netdev_private *np = netdev_priv(dev);
|
||||
int entry = np->cur_rx % RX_RING_SIZE;
|
||||
@ -2237,6 +2275,12 @@ static void netdev_rx(struct net_device *dev)
|
||||
entry, desc_status);
|
||||
if (--boguscnt < 0)
|
||||
break;
|
||||
|
||||
if (*work_done >= work_to_do)
|
||||
break;
|
||||
|
||||
(*work_done)++;
|
||||
|
||||
pkt_len = (desc_status & DescSizeMask) - 4;
|
||||
if ((desc_status&(DescMore|DescPktOK|DescRxLong)) != DescPktOK){
|
||||
if (desc_status & DescMore) {
|
||||
@ -2293,7 +2337,7 @@ static void netdev_rx(struct net_device *dev)
|
||||
np->rx_skbuff[entry] = NULL;
|
||||
}
|
||||
skb->protocol = eth_type_trans(skb, dev);
|
||||
netif_rx(skb);
|
||||
netif_receive_skb(skb);
|
||||
dev->last_rx = jiffies;
|
||||
np->stats.rx_packets++;
|
||||
np->stats.rx_bytes += pkt_len;
|
||||
@ -3074,9 +3118,7 @@ static int netdev_close(struct net_device *dev)
|
||||
del_timer_sync(&np->timer);
|
||||
disable_irq(dev->irq);
|
||||
spin_lock_irq(&np->lock);
|
||||
/* Disable interrupts, and flush posted writes */
|
||||
writel(0, ioaddr + IntrEnable);
|
||||
readl(ioaddr + IntrEnable);
|
||||
natsemi_irq_disable(dev);
|
||||
np->hands_off = 1;
|
||||
spin_unlock_irq(&np->lock);
|
||||
enable_irq(dev->irq);
|
||||
@ -3158,6 +3200,9 @@ static void __devexit natsemi_remove1 (struct pci_dev *pdev)
|
||||
* * netdev_timer: timer stopped by natsemi_suspend.
|
||||
* * intr_handler: doesn't acquire the spinlock. suspend calls
|
||||
* disable_irq() to enforce synchronization.
|
||||
* * natsemi_poll: checks before reenabling interrupts. suspend
|
||||
* sets hands_off, disables interrupts and then waits with
|
||||
* netif_poll_disable().
|
||||
*
|
||||
* Interrupts must be disabled, otherwise hands_off can cause irq storms.
|
||||
*/
|
||||
@ -3183,6 +3228,8 @@ static int natsemi_suspend (struct pci_dev *pdev, pm_message_t state)
|
||||
spin_unlock_irq(&np->lock);
|
||||
enable_irq(dev->irq);
|
||||
|
||||
netif_poll_disable(dev);
|
||||
|
||||
/* Update the error counts. */
|
||||
__get_stats(dev);
|
||||
|
||||
@ -3235,6 +3282,7 @@ static int natsemi_resume (struct pci_dev *pdev)
|
||||
mod_timer(&np->timer, jiffies + 1*HZ);
|
||||
}
|
||||
netif_device_attach(dev);
|
||||
netif_poll_enable(dev);
|
||||
out:
|
||||
rtnl_unlock();
|
||||
return 0;
|
||||
|
Loading…
Reference in New Issue
Block a user