Skip to content

Commit

Permalink
[NET]: Add netif_tx_lock
Browse files Browse the repository at this point in the history
Various drivers use xmit_lock internally to synchronise with their
transmission routines.  They do so without setting xmit_lock_owner.
This is fine as long as netpoll is not in use.

With netpoll it is possible for deadlocks to occur if xmit_lock_owner
isn't set.  This is because if a printk occurs while xmit_lock is held
and xmit_lock_owner is not set can cause netpoll to attempt to take
xmit_lock recursively.

While it is possible to resolve this by getting netpoll to use
trylock, it is suboptimal because netpoll's sole objective is to
maximise the chance of getting the printk out on the wire.  So
delaying or dropping the message is to be avoided as much as possible.

So the only alternative is to always set xmit_lock_owner.  The
following patch does this by introducing the netif_tx_lock family of
functions that take care of setting/unsetting xmit_lock_owner.

I renamed xmit_lock to _xmit_lock to indicate that it should not be
used directly.  I didn't provide irq versions of the netif_tx_lock
functions since xmit_lock is meant to be a BH-disabling lock.

This is pretty much a straight text substitution except for a small
bug fix in winbond.  It currently uses
netif_stop_queue/spin_unlock_wait to stop transmission.  This is
unsafe as an IRQ can potentially wake up the queue.  So it is safer to
use netif_tx_disable.

The hamradio bits used spin_lock_irq but it is unnecessary as
xmit_lock must never be taken in an IRQ handler.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
herbertx authored and David S. Miller committed Jun 18, 2006
1 parent bf0857e commit 932ff27
Show file tree
Hide file tree
Showing 21 changed files with 121 additions and 98 deletions.
8 changes: 4 additions & 4 deletions Documentation/networking/netdevices.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ dev->get_stats:
Context: nominally process, but don't sleep inside an rwlock

dev->hard_start_xmit:
Synchronization: dev->xmit_lock spinlock.
Synchronization: netif_tx_lock spinlock.
When the driver sets NETIF_F_LLTX in dev->features this will be
called without holding xmit_lock. In this case the driver
called without holding netif_tx_lock. In this case the driver
has to lock by itself when needed. It is recommended to use a try lock
for this and return -1 when the spin lock fails.
The locking there should also properly protect against
Expand All @@ -62,12 +62,12 @@ dev->hard_start_xmit:
Only valid when NETIF_F_LLTX is set.

dev->tx_timeout:
Synchronization: dev->xmit_lock spinlock.
Synchronization: netif_tx_lock spinlock.
Context: BHs disabled
Notes: netif_queue_stopped() is guaranteed true

dev->set_multicast_list:
Synchronization: dev->xmit_lock spinlock.
Synchronization: netif_tx_lock spinlock.
Context: BHs disabled

dev->poll:
Expand Down
6 changes: 4 additions & 2 deletions drivers/infiniband/ulp/ipoib/ipoib_multicast.c
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ void ipoib_mcast_restart_task(void *dev_ptr)

ipoib_mcast_stop_thread(dev, 0);

spin_lock_irqsave(&dev->xmit_lock, flags);
local_irq_save(flags);
netif_tx_lock(dev);
spin_lock(&priv->lock);

/*
Expand Down Expand Up @@ -896,7 +897,8 @@ void ipoib_mcast_restart_task(void *dev_ptr)
}

spin_unlock(&priv->lock);
spin_unlock_irqrestore(&dev->xmit_lock, flags);
netif_tx_unlock(dev);
local_irq_restore(flags);

/* We have to cancel outside of the spinlock */
list_for_each_entry_safe(mcast, tmcast, &remove_list, list) {
Expand Down
4 changes: 2 additions & 2 deletions drivers/media/dvb/dvb-core/dvb_net.c
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ static void wq_set_multicast_list (void *data)

dvb_net_feed_stop(dev);
priv->rx_mode = RX_MODE_UNI;
spin_lock_bh(&dev->xmit_lock);
netif_tx_lock_bh(dev);

if (dev->flags & IFF_PROMISC) {
dprintk("%s: promiscuous mode\n", dev->name);
Expand All @@ -1077,7 +1077,7 @@ static void wq_set_multicast_list (void *data)
}
}

spin_unlock_bh(&dev->xmit_lock);
netif_tx_unlock_bh(dev);
dvb_net_feed_start(dev);
}

Expand Down
4 changes: 2 additions & 2 deletions drivers/net/bnx2.c
Original file line number Diff line number Diff line change
Expand Up @@ -2009,7 +2009,7 @@ bnx2_poll(struct net_device *dev, int *budget)
return 1;
}

/* Called with rtnl_lock from vlan functions and also dev->xmit_lock
/* Called with rtnl_lock from vlan functions and also netif_tx_lock
* from set_multicast.
*/
static void
Expand Down Expand Up @@ -4252,7 +4252,7 @@ bnx2_vlan_rx_kill_vid(struct net_device *dev, uint16_t vid)
}
#endif

/* Called with dev->xmit_lock.
/* Called with netif_tx_lock.
* hard_start_xmit is pseudo-lockless - a lock is only required when
* the tx queue is full. This way, we get the benefit of lockless
* operations most of the time without the complexities to handle
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/bonding/bond_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4191,7 +4191,7 @@ static int bond_init(struct net_device *bond_dev, struct bond_params *params)
*/
bond_dev->features |= NETIF_F_VLAN_CHALLENGED;

/* don't acquire bond device's xmit_lock when
/* don't acquire bond device's netif_tx_lock when
* transmitting */
bond_dev->features |= NETIF_F_LLTX;

Expand Down
18 changes: 9 additions & 9 deletions drivers/net/forcedeth.c
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,9 @@ typedef union _ring_type {
* critical parts:
* - rx is (pseudo-) lockless: it relies on the single-threading provided
* by the arch code for interrupts.
* - tx setup is lockless: it relies on dev->xmit_lock. Actual submission
* - tx setup is lockless: it relies on netif_tx_lock. Actual submission
* needs dev->priv->lock :-(
* - set_multicast_list: preparation lockless, relies on dev->xmit_lock.
* - set_multicast_list: preparation lockless, relies on netif_tx_lock.
*/

/* in dev: base, irq */
Expand Down Expand Up @@ -1213,7 +1213,7 @@ static void drain_ring(struct net_device *dev)

/*
* nv_start_xmit: dev->hard_start_xmit function
* Called with dev->xmit_lock held.
* Called with netif_tx_lock held.
*/
static int nv_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
Expand Down Expand Up @@ -1407,7 +1407,7 @@ static void nv_tx_done(struct net_device *dev)

/*
* nv_tx_timeout: dev->tx_timeout function
* Called with dev->xmit_lock held.
* Called with netif_tx_lock held.
*/
static void nv_tx_timeout(struct net_device *dev)
{
Expand Down Expand Up @@ -1737,7 +1737,7 @@ static int nv_change_mtu(struct net_device *dev, int new_mtu)
* Changing the MTU is a rare event, it shouldn't matter.
*/
nv_disable_irq(dev);
spin_lock_bh(&dev->xmit_lock);
netif_tx_lock_bh(dev);
spin_lock(&np->lock);
/* stop engines */
nv_stop_rx(dev);
Expand Down Expand Up @@ -1768,7 +1768,7 @@ static int nv_change_mtu(struct net_device *dev, int new_mtu)
nv_start_rx(dev);
nv_start_tx(dev);
spin_unlock(&np->lock);
spin_unlock_bh(&dev->xmit_lock);
netif_tx_unlock_bh(dev);
nv_enable_irq(dev);
}
return 0;
Expand Down Expand Up @@ -1803,7 +1803,7 @@ static int nv_set_mac_address(struct net_device *dev, void *addr)
memcpy(dev->dev_addr, macaddr->sa_data, ETH_ALEN);

if (netif_running(dev)) {
spin_lock_bh(&dev->xmit_lock);
netif_tx_lock_bh(dev);
spin_lock_irq(&np->lock);

/* stop rx engine */
Expand All @@ -1815,7 +1815,7 @@ static int nv_set_mac_address(struct net_device *dev, void *addr)
/* restart rx engine */
nv_start_rx(dev);
spin_unlock_irq(&np->lock);
spin_unlock_bh(&dev->xmit_lock);
netif_tx_unlock_bh(dev);
} else {
nv_copy_mac_to_hw(dev);
}
Expand All @@ -1824,7 +1824,7 @@ static int nv_set_mac_address(struct net_device *dev, void *addr)

/*
* nv_set_multicast: dev->set_multicast function
* Called with dev->xmit_lock held.
* Called with netif_tx_lock held.
*/
static void nv_set_multicast(struct net_device *dev)
{
Expand Down
8 changes: 4 additions & 4 deletions drivers/net/hamradio/6pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ static int sp_set_mac_address(struct net_device *dev, void *addr)
{
struct sockaddr_ax25 *sa = addr;

spin_lock_irq(&dev->xmit_lock);
netif_tx_lock_bh(dev);
memcpy(dev->dev_addr, &sa->sax25_call, AX25_ADDR_LEN);
spin_unlock_irq(&dev->xmit_lock);
netif_tx_unlock_bh(dev);

return 0;
}
Expand Down Expand Up @@ -767,9 +767,9 @@ static int sixpack_ioctl(struct tty_struct *tty, struct file *file,
break;
}

spin_lock_irq(&dev->xmit_lock);
netif_tx_lock_bh(dev);
memcpy(dev->dev_addr, &addr, AX25_ADDR_LEN);
spin_unlock_irq(&dev->xmit_lock);
netif_tx_unlock_bh(dev);

err = 0;
break;
Expand Down
8 changes: 4 additions & 4 deletions drivers/net/hamradio/mkiss.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,9 @@ static int ax_set_mac_address(struct net_device *dev, void *addr)
{
struct sockaddr_ax25 *sa = addr;

spin_lock_irq(&dev->xmit_lock);
netif_tx_lock_bh(dev);
memcpy(dev->dev_addr, &sa->sax25_call, AX25_ADDR_LEN);
spin_unlock_irq(&dev->xmit_lock);
netif_tx_unlock_bh(dev);

return 0;
}
Expand Down Expand Up @@ -886,9 +886,9 @@ static int mkiss_ioctl(struct tty_struct *tty, struct file *file,
break;
}

spin_lock_irq(&dev->xmit_lock);
netif_tx_lock_bh(dev);
memcpy(dev->dev_addr, addr, AX25_ADDR_LEN);
spin_unlock_irq(&dev->xmit_lock);
netif_tx_unlock_bh(dev);

err = 0;
break;
Expand Down
10 changes: 5 additions & 5 deletions drivers/net/ifb.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ static void ri_tasklet(unsigned long dev)
dp->st_task_enter++;
if ((skb = skb_peek(&dp->tq)) == NULL) {
dp->st_txq_refl_try++;
if (spin_trylock(&_dev->xmit_lock)) {
if (netif_tx_trylock(_dev)) {
dp->st_rxq_enter++;
while ((skb = skb_dequeue(&dp->rq)) != NULL) {
skb_queue_tail(&dp->tq, skb);
dp->st_rx2tx_tran++;
}
spin_unlock(&_dev->xmit_lock);
netif_tx_unlock(_dev);
} else {
/* reschedule */
dp->st_rxq_notenter++;
Expand Down Expand Up @@ -110,18 +110,18 @@ static void ri_tasklet(unsigned long dev)
}
}

if (spin_trylock(&_dev->xmit_lock)) {
if (netif_tx_trylock(_dev)) {
dp->st_rxq_check++;
if ((skb = skb_peek(&dp->rq)) == NULL) {
dp->tasklet_pending = 0;
if (netif_queue_stopped(_dev))
netif_wake_queue(_dev);
} else {
dp->st_rxq_rsch++;
spin_unlock(&_dev->xmit_lock);
netif_tx_unlock(_dev);
goto resched;
}
spin_unlock(&_dev->xmit_lock);
netif_tx_unlock(_dev);
} else {
resched:
dp->tasklet_pending = 1;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/irda/vlsi_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ static int vlsi_hard_start_xmit(struct sk_buff *skb, struct net_device *ndev)
|| (now.tv_sec==ready.tv_sec && now.tv_usec>=ready.tv_usec))
break;
udelay(100);
/* must not sleep here - we are called under xmit_lock! */
/* must not sleep here - called under netif_tx_lock! */
}
}

Expand Down
4 changes: 2 additions & 2 deletions drivers/net/natsemi.c
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,12 @@ performance critical codepaths:
The rx process only runs in the interrupt handler. Access from outside
the interrupt handler is only permitted after disable_irq().
The rx process usually runs under the dev->xmit_lock. If np->intr_tx_reap
The rx process usually runs under the netif_tx_lock. If np->intr_tx_reap
is set, then access is permitted under spin_lock_irq(&np->lock).
Thus configuration functions that want to access everything must call
disable_irq(dev->irq);
spin_lock_bh(dev->xmit_lock);
netif_tx_lock_bh(dev);
spin_lock_irq(&np->lock);
IV. Notes
Expand Down
9 changes: 4 additions & 5 deletions drivers/net/tulip/winbond-840.c
Original file line number Diff line number Diff line change
Expand Up @@ -1605,11 +1605,11 @@ static void __devexit w840_remove1 (struct pci_dev *pdev)
* - get_stats:
* spin_lock_irq(np->lock), doesn't touch hw if not present
* - hard_start_xmit:
* netif_stop_queue + spin_unlock_wait(&dev->xmit_lock);
* synchronize_irq + netif_tx_disable;
* - tx_timeout:
* netif_device_detach + spin_unlock_wait(&dev->xmit_lock);
* netif_device_detach + netif_tx_disable;
* - set_multicast_list
* netif_device_detach + spin_unlock_wait(&dev->xmit_lock);
* netif_device_detach + netif_tx_disable;
* - interrupt handler
* doesn't touch hw if not present, synchronize_irq waits for
* running instances of the interrupt handler.
Expand All @@ -1635,11 +1635,10 @@ static int w840_suspend (struct pci_dev *pdev, pm_message_t state)
netif_device_detach(dev);
update_csr6(dev, 0);
iowrite32(0, ioaddr + IntrEnable);
netif_stop_queue(dev);
spin_unlock_irq(&np->lock);

spin_unlock_wait(&dev->xmit_lock);
synchronize_irq(dev->irq);
netif_tx_disable(dev);

np->stats.rx_missed_errors += ioread32(ioaddr + RxMissed) & 0xffff;

Expand Down
4 changes: 3 additions & 1 deletion drivers/net/wireless/orinoco.c
Original file line number Diff line number Diff line change
Expand Up @@ -1833,7 +1833,9 @@ static int __orinoco_program_rids(struct net_device *dev)
/* Set promiscuity / multicast*/
priv->promiscuous = 0;
priv->mc_count = 0;
__orinoco_set_multicast_list(dev); /* FIXME: what about the xmit_lock */

/* FIXME: what about netif_tx_lock */
__orinoco_set_multicast_list(dev);

return 0;
}
Expand Down
38 changes: 35 additions & 3 deletions include/linux/netdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ struct net_device
* One part is mostly used on xmit path (device)
*/
/* hard_start_xmit synchronizer */
spinlock_t xmit_lock ____cacheline_aligned_in_smp;
spinlock_t _xmit_lock ____cacheline_aligned_in_smp;
/* cpu id of processor entered to hard_start_xmit or -1,
if nobody entered there.
*/
Expand Down Expand Up @@ -893,11 +893,43 @@ static inline void __netif_rx_complete(struct net_device *dev)
clear_bit(__LINK_STATE_RX_SCHED, &dev->state);
}

static inline void netif_tx_lock(struct net_device *dev)
{
spin_lock(&dev->_xmit_lock);
dev->xmit_lock_owner = smp_processor_id();
}

static inline void netif_tx_lock_bh(struct net_device *dev)
{
spin_lock_bh(&dev->_xmit_lock);
dev->xmit_lock_owner = smp_processor_id();
}

static inline int netif_tx_trylock(struct net_device *dev)
{
int err = spin_trylock(&dev->_xmit_lock);
if (!err)
dev->xmit_lock_owner = smp_processor_id();
return err;
}

static inline void netif_tx_unlock(struct net_device *dev)
{
dev->xmit_lock_owner = -1;
spin_unlock(&dev->_xmit_lock);
}

static inline void netif_tx_unlock_bh(struct net_device *dev)
{
dev->xmit_lock_owner = -1;
spin_unlock_bh(&dev->_xmit_lock);
}

static inline void netif_tx_disable(struct net_device *dev)
{
spin_lock_bh(&dev->xmit_lock);
netif_tx_lock_bh(dev);
netif_stop_queue(dev);
spin_unlock_bh(&dev->xmit_lock);
netif_tx_unlock_bh(dev);
}

/* These functions live elsewhere (drivers/net/net_init.c, but related) */
Expand Down
4 changes: 2 additions & 2 deletions net/atm/clip.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ static void unlink_clip_vcc(struct clip_vcc *clip_vcc)
printk(KERN_CRIT "!clip_vcc->entry (clip_vcc %p)\n", clip_vcc);
return;
}
spin_lock_bh(&entry->neigh->dev->xmit_lock); /* block clip_start_xmit() */
netif_tx_lock_bh(entry->neigh->dev); /* block clip_start_xmit() */
entry->neigh->used = jiffies;
for (walk = &entry->vccs; *walk; walk = &(*walk)->next)
if (*walk == clip_vcc) {
Expand All @@ -122,7 +122,7 @@ static void unlink_clip_vcc(struct clip_vcc *clip_vcc)
printk(KERN_CRIT "ATMARP: unlink_clip_vcc failed (entry %p, vcc "
"0x%p)\n", entry, clip_vcc);
out:
spin_unlock_bh(&entry->neigh->dev->xmit_lock);
netif_tx_unlock_bh(entry->neigh->dev);
}

/* The neighbour entry n->lock is held. */
Expand Down
Loading

0 comments on commit 932ff27

Please sign in to comment.