aboutsummaryrefslogtreecommitdiffstats
path: root/net/sched
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2007-10-19 01:37:58 -0400
committerDavid S. Miller <davem@davemloft.net>2007-10-19 01:37:58 -0400
commitce0e32e65f70337e0732c97499b643205fa8ea31 (patch)
tree3f3a4a48210a1e4208a8ed5507f2e3269a7b79da /net/sched
parenta25de534f89c515c82d3553c42d3bb02c2d1a7da (diff)
[NET]: Fix possible dev_deactivate race condition
The function dev_deactivate is supposed to only return when all outstanding transmissions have completed. Unfortunately it is possible for store operations in the driver's transmit function to only become visible after dev_deactivate returns. This patch fixes this by taking the queue lock after we see the end of the queue run. This ensures that all effects of any previous transmit calls are visible. If however we detect that there is another queue run occuring, then we'll warn about it because this should never happen as we have pointed dev->qdisc to noop_qdisc within the same queue lock earlier in the functino. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched')
-rw-r--r--net/sched/sch_generic.c26
1 files changed, 23 insertions, 3 deletions
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index e01d57692c9a..fa1a6f45dc41 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -556,6 +556,7 @@ void dev_deactivate(struct net_device *dev)
556{ 556{
557 struct Qdisc *qdisc; 557 struct Qdisc *qdisc;
558 struct sk_buff *skb; 558 struct sk_buff *skb;
559 int running;
559 560
560 spin_lock_bh(&dev->queue_lock); 561 spin_lock_bh(&dev->queue_lock);
561 qdisc = dev->qdisc; 562 qdisc = dev->qdisc;
@@ -571,12 +572,31 @@ void dev_deactivate(struct net_device *dev)
571 572
572 dev_watchdog_down(dev); 573 dev_watchdog_down(dev);
573 574
574 /* Wait for outstanding dev_queue_xmit calls. */ 575 /* Wait for outstanding qdisc-less dev_queue_xmit calls. */
575 synchronize_rcu(); 576 synchronize_rcu();
576 577
577 /* Wait for outstanding qdisc_run calls. */ 578 /* Wait for outstanding qdisc_run calls. */
578 while (test_bit(__LINK_STATE_QDISC_RUNNING, &dev->state)) 579 do {
579 yield(); 580 while (test_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))
581 yield();
582
583 /*
584 * Double-check inside queue lock to ensure that all effects
585 * of the queue run are visible when we return.
586 */
587 spin_lock_bh(&dev->queue_lock);
588 running = test_bit(__LINK_STATE_QDISC_RUNNING, &dev->state);
589 spin_unlock_bh(&dev->queue_lock);
590
591 /*
592 * The running flag should never be set at this point because
593 * we've already set dev->qdisc to noop_qdisc *inside* the same
594 * pair of spin locks. That is, if any qdisc_run starts after
595 * our initial test it should see the noop_qdisc and then
596 * clear the RUNNING bit before dropping the queue lock. So
597 * if it is set here then we've found a bug.
598 */
599 } while (WARN_ON_ONCE(running));
580} 600}
581 601
582void dev_init_scheduler(struct net_device *dev) 602void dev_init_scheduler(struct net_device *dev)