diff options
| author | Jarek Poplawski <jarkao2@gmail.com> | 2008-08-22 06:24:05 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2008-08-22 06:31:39 -0400 |
| commit | f6e0b239a2657ea8cb67f0d83d0bfdbfd19a481b (patch) | |
| tree | 9d6e3c97a81869fde3958cdbcf431ee3739f9b2c | |
| parent | 2540e0511ea17e25831be543cdf9381e6209950d (diff) | |
pkt_sched: Fix qdisc list locking
Since some qdiscs call qdisc_tree_decrease_qlen() (so qdisc_lookup())
without rtnl_lock(), adding and deleting from a qdisc list needs
additional locking. This patch adds global spinlock qdisc_list_lock
and wrapper functions for modifying the list. It is considered as a
temporary solution until hfsc_dequeue(), netem_dequeue() and
tbf_dequeue() (or qdisc_tree_decrease_qlen()) are redone.
With feedback from Herbert Xu and David S. Miller.
Signed-off-by: Jarek Poplawski <jarkao2@gmail.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | include/net/pkt_sched.h | 1 | ||||
| -rw-r--r-- | net/sched/sch_api.c | 44 | ||||
| -rw-r--r-- | net/sched/sch_generic.c | 5 |
3 files changed, 42 insertions, 8 deletions
diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h index 853fe83d9f37..b786a5b09253 100644 --- a/include/net/pkt_sched.h +++ b/include/net/pkt_sched.h | |||
| @@ -78,6 +78,7 @@ extern struct Qdisc *fifo_create_dflt(struct Qdisc *sch, struct Qdisc_ops *ops, | |||
| 78 | 78 | ||
| 79 | extern int register_qdisc(struct Qdisc_ops *qops); | 79 | extern int register_qdisc(struct Qdisc_ops *qops); |
| 80 | extern int unregister_qdisc(struct Qdisc_ops *qops); | 80 | extern int unregister_qdisc(struct Qdisc_ops *qops); |
| 81 | extern void qdisc_list_del(struct Qdisc *q); | ||
| 81 | extern struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle); | 82 | extern struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle); |
| 82 | extern struct Qdisc *qdisc_lookup_class(struct net_device *dev, u32 handle); | 83 | extern struct Qdisc *qdisc_lookup_class(struct net_device *dev, u32 handle); |
| 83 | extern struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, | 84 | extern struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, |
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 45f442d7de47..e7fb9e0d21b4 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c | |||
| @@ -199,19 +199,53 @@ struct Qdisc *qdisc_match_from_root(struct Qdisc *root, u32 handle) | |||
| 199 | return NULL; | 199 | return NULL; |
| 200 | } | 200 | } |
| 201 | 201 | ||
| 202 | /* | ||
| 203 | * This lock is needed until some qdiscs stop calling qdisc_tree_decrease_qlen() | ||
| 204 | * without rtnl_lock(); currently hfsc_dequeue(), netem_dequeue(), tbf_dequeue() | ||
| 205 | */ | ||
| 206 | static DEFINE_SPINLOCK(qdisc_list_lock); | ||
| 207 | |||
| 208 | static void qdisc_list_add(struct Qdisc *q) | ||
| 209 | { | ||
| 210 | if ((q->parent != TC_H_ROOT) && !(q->flags & TCQ_F_INGRESS)) { | ||
| 211 | spin_lock_bh(&qdisc_list_lock); | ||
| 212 | list_add_tail(&q->list, &qdisc_root_sleeping(q)->list); | ||
| 213 | spin_unlock_bh(&qdisc_list_lock); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | void qdisc_list_del(struct Qdisc *q) | ||
| 218 | { | ||
| 219 | if ((q->parent != TC_H_ROOT) && !(q->flags & TCQ_F_INGRESS)) { | ||
| 220 | spin_lock_bh(&qdisc_list_lock); | ||
| 221 | list_del(&q->list); | ||
| 222 | spin_unlock_bh(&qdisc_list_lock); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | EXPORT_SYMBOL(qdisc_list_del); | ||
| 226 | |||
| 202 | struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle) | 227 | struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle) |
| 203 | { | 228 | { |
| 204 | unsigned int i; | 229 | unsigned int i; |
| 230 | struct Qdisc *q; | ||
| 231 | |||
| 232 | spin_lock_bh(&qdisc_list_lock); | ||
| 205 | 233 | ||
| 206 | for (i = 0; i < dev->num_tx_queues; i++) { | 234 | for (i = 0; i < dev->num_tx_queues; i++) { |
| 207 | struct netdev_queue *txq = netdev_get_tx_queue(dev, i); | 235 | struct netdev_queue *txq = netdev_get_tx_queue(dev, i); |
| 208 | struct Qdisc *q, *txq_root = txq->qdisc_sleeping; | 236 | struct Qdisc *txq_root = txq->qdisc_sleeping; |
| 209 | 237 | ||
| 210 | q = qdisc_match_from_root(txq_root, handle); | 238 | q = qdisc_match_from_root(txq_root, handle); |
| 211 | if (q) | 239 | if (q) |
| 212 | return q; | 240 | goto unlock; |
| 213 | } | 241 | } |
| 214 | return qdisc_match_from_root(dev->rx_queue.qdisc_sleeping, handle); | 242 | |
| 243 | q = qdisc_match_from_root(dev->rx_queue.qdisc_sleeping, handle); | ||
| 244 | |||
| 245 | unlock: | ||
| 246 | spin_unlock_bh(&qdisc_list_lock); | ||
| 247 | |||
| 248 | return q; | ||
| 215 | } | 249 | } |
| 216 | 250 | ||
| 217 | static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid) | 251 | static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid) |
| @@ -810,8 +844,8 @@ qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue, | |||
| 810 | goto err_out3; | 844 | goto err_out3; |
| 811 | } | 845 | } |
| 812 | } | 846 | } |
| 813 | if ((parent != TC_H_ROOT) && !(sch->flags & TCQ_F_INGRESS)) | 847 | |
| 814 | list_add_tail(&sch->list, &dev_queue->qdisc_sleeping->list); | 848 | qdisc_list_add(sch); |
| 815 | 849 | ||
| 816 | return sch; | 850 | return sch; |
| 817 | } | 851 | } |
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index c3ed4d44fc14..5f0ade7806a7 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c | |||
| @@ -526,10 +526,9 @@ void qdisc_destroy(struct Qdisc *qdisc) | |||
| 526 | !atomic_dec_and_test(&qdisc->refcnt)) | 526 | !atomic_dec_and_test(&qdisc->refcnt)) |
| 527 | return; | 527 | return; |
| 528 | 528 | ||
| 529 | if (qdisc->parent) | ||
| 530 | list_del(&qdisc->list); | ||
| 531 | |||
| 532 | #ifdef CONFIG_NET_SCHED | 529 | #ifdef CONFIG_NET_SCHED |
| 530 | qdisc_list_del(qdisc); | ||
| 531 | |||
| 533 | qdisc_put_stab(qdisc->stab); | 532 | qdisc_put_stab(qdisc->stab); |
| 534 | #endif | 533 | #endif |
| 535 | gen_kill_estimator(&qdisc->bstats, &qdisc->rate_est); | 534 | gen_kill_estimator(&qdisc->bstats, &qdisc->rate_est); |
