diff options
Diffstat (limited to 'net/sched/sch_netem.c')
-rw-r--r-- | net/sched/sch_netem.c | 108 |
1 files changed, 53 insertions, 55 deletions
diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index 1ccbfb55b0b8..5d9d8bc9cc3a 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <linux/skbuff.h> | 22 | #include <linux/skbuff.h> |
23 | #include <linux/rtnetlink.h> | 23 | #include <linux/rtnetlink.h> |
24 | 24 | ||
25 | #include <net/netlink.h> | ||
25 | #include <net/pkt_sched.h> | 26 | #include <net/pkt_sched.h> |
26 | 27 | ||
27 | #define VERSION "1.2" | 28 | #define VERSION "1.2" |
@@ -54,21 +55,22 @@ | |||
54 | 55 | ||
55 | struct netem_sched_data { | 56 | struct netem_sched_data { |
56 | struct Qdisc *qdisc; | 57 | struct Qdisc *qdisc; |
57 | struct timer_list timer; | 58 | struct qdisc_watchdog watchdog; |
59 | |||
60 | psched_tdiff_t latency; | ||
61 | psched_tdiff_t jitter; | ||
58 | 62 | ||
59 | u32 latency; | ||
60 | u32 loss; | 63 | u32 loss; |
61 | u32 limit; | 64 | u32 limit; |
62 | u32 counter; | 65 | u32 counter; |
63 | u32 gap; | 66 | u32 gap; |
64 | u32 jitter; | ||
65 | u32 duplicate; | 67 | u32 duplicate; |
66 | u32 reorder; | 68 | u32 reorder; |
67 | u32 corrupt; | 69 | u32 corrupt; |
68 | 70 | ||
69 | struct crndstate { | 71 | struct crndstate { |
70 | unsigned long last; | 72 | u32 last; |
71 | unsigned long rho; | 73 | u32 rho; |
72 | } delay_cor, loss_cor, dup_cor, reorder_cor, corrupt_cor; | 74 | } delay_cor, loss_cor, dup_cor, reorder_cor, corrupt_cor; |
73 | 75 | ||
74 | struct disttable { | 76 | struct disttable { |
@@ -95,12 +97,12 @@ static void init_crandom(struct crndstate *state, unsigned long rho) | |||
95 | * Next number depends on last value. | 97 | * Next number depends on last value. |
96 | * rho is scaled to avoid floating point. | 98 | * rho is scaled to avoid floating point. |
97 | */ | 99 | */ |
98 | static unsigned long get_crandom(struct crndstate *state) | 100 | static u32 get_crandom(struct crndstate *state) |
99 | { | 101 | { |
100 | u64 value, rho; | 102 | u64 value, rho; |
101 | unsigned long answer; | 103 | unsigned long answer; |
102 | 104 | ||
103 | if (state->rho == 0) /* no correllation */ | 105 | if (state->rho == 0) /* no correlation */ |
104 | return net_random(); | 106 | return net_random(); |
105 | 107 | ||
106 | value = net_random(); | 108 | value = net_random(); |
@@ -114,11 +116,13 @@ static unsigned long get_crandom(struct crndstate *state) | |||
114 | * std deviation sigma. Uses table lookup to approximate the desired | 116 | * std deviation sigma. Uses table lookup to approximate the desired |
115 | * distribution, and a uniformly-distributed pseudo-random source. | 117 | * distribution, and a uniformly-distributed pseudo-random source. |
116 | */ | 118 | */ |
117 | static long tabledist(unsigned long mu, long sigma, | 119 | static psched_tdiff_t tabledist(psched_tdiff_t mu, psched_tdiff_t sigma, |
118 | struct crndstate *state, const struct disttable *dist) | 120 | struct crndstate *state, |
121 | const struct disttable *dist) | ||
119 | { | 122 | { |
120 | long t, x; | 123 | psched_tdiff_t x; |
121 | unsigned long rnd; | 124 | long t; |
125 | u32 rnd; | ||
122 | 126 | ||
123 | if (sigma == 0) | 127 | if (sigma == 0) |
124 | return mu; | 128 | return mu; |
@@ -213,8 +217,8 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch) | |||
213 | delay = tabledist(q->latency, q->jitter, | 217 | delay = tabledist(q->latency, q->jitter, |
214 | &q->delay_cor, q->delay_dist); | 218 | &q->delay_cor, q->delay_dist); |
215 | 219 | ||
216 | PSCHED_GET_TIME(now); | 220 | now = psched_get_time(); |
217 | PSCHED_TADD2(now, delay, cb->time_to_send); | 221 | cb->time_to_send = now + delay; |
218 | ++q->counter; | 222 | ++q->counter; |
219 | ret = q->qdisc->enqueue(skb, q->qdisc); | 223 | ret = q->qdisc->enqueue(skb, q->qdisc); |
220 | } else { | 224 | } else { |
@@ -222,7 +226,7 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch) | |||
222 | * Do re-ordering by putting one out of N packets at the front | 226 | * Do re-ordering by putting one out of N packets at the front |
223 | * of the queue. | 227 | * of the queue. |
224 | */ | 228 | */ |
225 | PSCHED_GET_TIME(cb->time_to_send); | 229 | cb->time_to_send = psched_get_time(); |
226 | q->counter = 0; | 230 | q->counter = 0; |
227 | ret = q->qdisc->ops->requeue(skb, q->qdisc); | 231 | ret = q->qdisc->ops->requeue(skb, q->qdisc); |
228 | } | 232 | } |
@@ -269,55 +273,43 @@ static struct sk_buff *netem_dequeue(struct Qdisc *sch) | |||
269 | struct netem_sched_data *q = qdisc_priv(sch); | 273 | struct netem_sched_data *q = qdisc_priv(sch); |
270 | struct sk_buff *skb; | 274 | struct sk_buff *skb; |
271 | 275 | ||
276 | smp_mb(); | ||
277 | if (sch->flags & TCQ_F_THROTTLED) | ||
278 | return NULL; | ||
279 | |||
272 | skb = q->qdisc->dequeue(q->qdisc); | 280 | skb = q->qdisc->dequeue(q->qdisc); |
273 | if (skb) { | 281 | if (skb) { |
274 | const struct netem_skb_cb *cb | 282 | const struct netem_skb_cb *cb |
275 | = (const struct netem_skb_cb *)skb->cb; | 283 | = (const struct netem_skb_cb *)skb->cb; |
276 | psched_time_t now; | 284 | psched_time_t now = psched_get_time(); |
277 | 285 | ||
278 | /* if more time remaining? */ | 286 | /* if more time remaining? */ |
279 | PSCHED_GET_TIME(now); | 287 | if (cb->time_to_send <= now) { |
280 | |||
281 | if (PSCHED_TLESS(cb->time_to_send, now)) { | ||
282 | pr_debug("netem_dequeue: return skb=%p\n", skb); | 288 | pr_debug("netem_dequeue: return skb=%p\n", skb); |
283 | sch->q.qlen--; | 289 | sch->q.qlen--; |
284 | sch->flags &= ~TCQ_F_THROTTLED; | ||
285 | return skb; | 290 | return skb; |
286 | } else { | 291 | } |
287 | psched_tdiff_t delay = PSCHED_TDIFF(cb->time_to_send, now); | ||
288 | |||
289 | if (q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS) { | ||
290 | qdisc_tree_decrease_qlen(q->qdisc, 1); | ||
291 | sch->qstats.drops++; | ||
292 | printk(KERN_ERR "netem: queue discpline %s could not requeue\n", | ||
293 | q->qdisc->ops->id); | ||
294 | } | ||
295 | 292 | ||
296 | mod_timer(&q->timer, jiffies + PSCHED_US2JIFFIE(delay)); | 293 | if (unlikely(q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS)) { |
297 | sch->flags |= TCQ_F_THROTTLED; | 294 | qdisc_tree_decrease_qlen(q->qdisc, 1); |
295 | sch->qstats.drops++; | ||
296 | printk(KERN_ERR "netem: %s could not requeue\n", | ||
297 | q->qdisc->ops->id); | ||
298 | } | 298 | } |
299 | |||
300 | qdisc_watchdog_schedule(&q->watchdog, cb->time_to_send); | ||
299 | } | 301 | } |
300 | 302 | ||
301 | return NULL; | 303 | return NULL; |
302 | } | 304 | } |
303 | 305 | ||
304 | static void netem_watchdog(unsigned long arg) | ||
305 | { | ||
306 | struct Qdisc *sch = (struct Qdisc *)arg; | ||
307 | |||
308 | pr_debug("netem_watchdog qlen=%d\n", sch->q.qlen); | ||
309 | sch->flags &= ~TCQ_F_THROTTLED; | ||
310 | netif_schedule(sch->dev); | ||
311 | } | ||
312 | |||
313 | static void netem_reset(struct Qdisc *sch) | 306 | static void netem_reset(struct Qdisc *sch) |
314 | { | 307 | { |
315 | struct netem_sched_data *q = qdisc_priv(sch); | 308 | struct netem_sched_data *q = qdisc_priv(sch); |
316 | 309 | ||
317 | qdisc_reset(q->qdisc); | 310 | qdisc_reset(q->qdisc); |
318 | sch->q.qlen = 0; | 311 | sch->q.qlen = 0; |
319 | sch->flags &= ~TCQ_F_THROTTLED; | 312 | qdisc_watchdog_cancel(&q->watchdog); |
320 | del_timer_sync(&q->timer); | ||
321 | } | 313 | } |
322 | 314 | ||
323 | /* Pass size change message down to embedded FIFO */ | 315 | /* Pass size change message down to embedded FIFO */ |
@@ -438,10 +430,11 @@ static int netem_change(struct Qdisc *sch, struct rtattr *opt) | |||
438 | q->loss = qopt->loss; | 430 | q->loss = qopt->loss; |
439 | q->duplicate = qopt->duplicate; | 431 | q->duplicate = qopt->duplicate; |
440 | 432 | ||
441 | /* for compatiablity with earlier versions. | 433 | /* for compatibility with earlier versions. |
442 | * if gap is set, need to assume 100% probablity | 434 | * if gap is set, need to assume 100% probability |
443 | */ | 435 | */ |
444 | q->reorder = ~0; | 436 | if (q->gap) |
437 | q->reorder = ~0; | ||
445 | 438 | ||
446 | /* Handle nested options after initial queue options. | 439 | /* Handle nested options after initial queue options. |
447 | * Should have put all options in nested format but too late now. | 440 | * Should have put all options in nested format but too late now. |
@@ -487,22 +480,28 @@ static int netem_change(struct Qdisc *sch, struct rtattr *opt) | |||
487 | */ | 480 | */ |
488 | struct fifo_sched_data { | 481 | struct fifo_sched_data { |
489 | u32 limit; | 482 | u32 limit; |
483 | psched_time_t oldest; | ||
490 | }; | 484 | }; |
491 | 485 | ||
492 | static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch) | 486 | static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch) |
493 | { | 487 | { |
494 | struct fifo_sched_data *q = qdisc_priv(sch); | 488 | struct fifo_sched_data *q = qdisc_priv(sch); |
495 | struct sk_buff_head *list = &sch->q; | 489 | struct sk_buff_head *list = &sch->q; |
496 | const struct netem_skb_cb *ncb | 490 | psched_time_t tnext = ((struct netem_skb_cb *)nskb->cb)->time_to_send; |
497 | = (const struct netem_skb_cb *)nskb->cb; | ||
498 | struct sk_buff *skb; | 491 | struct sk_buff *skb; |
499 | 492 | ||
500 | if (likely(skb_queue_len(list) < q->limit)) { | 493 | if (likely(skb_queue_len(list) < q->limit)) { |
494 | /* Optimize for add at tail */ | ||
495 | if (likely(skb_queue_empty(list) || tnext >= q->oldest)) { | ||
496 | q->oldest = tnext; | ||
497 | return qdisc_enqueue_tail(nskb, sch); | ||
498 | } | ||
499 | |||
501 | skb_queue_reverse_walk(list, skb) { | 500 | skb_queue_reverse_walk(list, skb) { |
502 | const struct netem_skb_cb *cb | 501 | const struct netem_skb_cb *cb |
503 | = (const struct netem_skb_cb *)skb->cb; | 502 | = (const struct netem_skb_cb *)skb->cb; |
504 | 503 | ||
505 | if (!PSCHED_TLESS(ncb->time_to_send, cb->time_to_send)) | 504 | if (tnext >= cb->time_to_send) |
506 | break; | 505 | break; |
507 | } | 506 | } |
508 | 507 | ||
@@ -515,7 +514,7 @@ static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch) | |||
515 | return NET_XMIT_SUCCESS; | 514 | return NET_XMIT_SUCCESS; |
516 | } | 515 | } |
517 | 516 | ||
518 | return qdisc_drop(nskb, sch); | 517 | return qdisc_reshape_fail(nskb, sch); |
519 | } | 518 | } |
520 | 519 | ||
521 | static int tfifo_init(struct Qdisc *sch, struct rtattr *opt) | 520 | static int tfifo_init(struct Qdisc *sch, struct rtattr *opt) |
@@ -531,6 +530,7 @@ static int tfifo_init(struct Qdisc *sch, struct rtattr *opt) | |||
531 | } else | 530 | } else |
532 | q->limit = max_t(u32, sch->dev->tx_queue_len, 1); | 531 | q->limit = max_t(u32, sch->dev->tx_queue_len, 1); |
533 | 532 | ||
533 | q->oldest = PSCHED_PASTPERFECT; | ||
534 | return 0; | 534 | return 0; |
535 | } | 535 | } |
536 | 536 | ||
@@ -567,9 +567,7 @@ static int netem_init(struct Qdisc *sch, struct rtattr *opt) | |||
567 | if (!opt) | 567 | if (!opt) |
568 | return -EINVAL; | 568 | return -EINVAL; |
569 | 569 | ||
570 | init_timer(&q->timer); | 570 | qdisc_watchdog_init(&q->watchdog, sch); |
571 | q->timer.function = netem_watchdog; | ||
572 | q->timer.data = (unsigned long) sch; | ||
573 | 571 | ||
574 | q->qdisc = qdisc_create_dflt(sch->dev, &tfifo_qdisc_ops, | 572 | q->qdisc = qdisc_create_dflt(sch->dev, &tfifo_qdisc_ops, |
575 | TC_H_MAKE(sch->handle, 1)); | 573 | TC_H_MAKE(sch->handle, 1)); |
@@ -590,7 +588,7 @@ static void netem_destroy(struct Qdisc *sch) | |||
590 | { | 588 | { |
591 | struct netem_sched_data *q = qdisc_priv(sch); | 589 | struct netem_sched_data *q = qdisc_priv(sch); |
592 | 590 | ||
593 | del_timer_sync(&q->timer); | 591 | qdisc_watchdog_cancel(&q->watchdog); |
594 | qdisc_destroy(q->qdisc); | 592 | qdisc_destroy(q->qdisc); |
595 | kfree(q->delay_dist); | 593 | kfree(q->delay_dist); |
596 | } | 594 | } |
@@ -598,7 +596,7 @@ static void netem_destroy(struct Qdisc *sch) | |||
598 | static int netem_dump(struct Qdisc *sch, struct sk_buff *skb) | 596 | static int netem_dump(struct Qdisc *sch, struct sk_buff *skb) |
599 | { | 597 | { |
600 | const struct netem_sched_data *q = qdisc_priv(sch); | 598 | const struct netem_sched_data *q = qdisc_priv(sch); |
601 | unsigned char *b = skb->tail; | 599 | unsigned char *b = skb_tail_pointer(skb); |
602 | struct rtattr *rta = (struct rtattr *) b; | 600 | struct rtattr *rta = (struct rtattr *) b; |
603 | struct tc_netem_qopt qopt; | 601 | struct tc_netem_qopt qopt; |
604 | struct tc_netem_corr cor; | 602 | struct tc_netem_corr cor; |
@@ -626,12 +624,12 @@ static int netem_dump(struct Qdisc *sch, struct sk_buff *skb) | |||
626 | corrupt.correlation = q->corrupt_cor.rho; | 624 | corrupt.correlation = q->corrupt_cor.rho; |
627 | RTA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt); | 625 | RTA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt); |
628 | 626 | ||
629 | rta->rta_len = skb->tail - b; | 627 | rta->rta_len = skb_tail_pointer(skb) - b; |
630 | 628 | ||
631 | return skb->len; | 629 | return skb->len; |
632 | 630 | ||
633 | rtattr_failure: | 631 | rtattr_failure: |
634 | skb_trim(skb, b - skb->data); | 632 | nlmsg_trim(skb, b); |
635 | return -1; | 633 | return -1; |
636 | } | 634 | } |
637 | 635 | ||