Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[deliverable/linux.git] / net / sched / sch_netem.c
index eb3b9a86c6ed93d502a4629f241a0d2496b01385..a92c1b3dab832f6e4c2cdb384349ea76aa2ede62 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/skbuff.h>
 #include <linux/vmalloc.h>
 #include <linux/rtnetlink.h>
+#include <linux/reciprocal_div.h>
 
 #include <net/netlink.h>
 #include <net/pkt_sched.h>
@@ -79,6 +80,11 @@ struct netem_sched_data {
        u32 duplicate;
        u32 reorder;
        u32 corrupt;
+       u32 rate;
+       s32 packet_overhead;
+       u32 cell_size;
+       u32 cell_size_reciprocal;
+       s32 cell_overhead;
 
        struct crndstate {
                u32 last;
@@ -298,6 +304,26 @@ static psched_tdiff_t tabledist(psched_tdiff_t mu, psched_tdiff_t sigma,
        return  x / NETEM_DIST_SCALE + (sigma / NETEM_DIST_SCALE) * t + mu;
 }
 
+static psched_time_t packet_len_2_sched_time(unsigned int len, struct netem_sched_data *q)
+{
+       u64 ticks;
+
+       len += q->packet_overhead;
+
+       if (q->cell_size) {
+               u32 cells = reciprocal_divide(len, q->cell_size_reciprocal);
+
+               if (len > cells * q->cell_size) /* extra cell needed for remainder */
+                       cells++;
+               len = cells * (q->cell_size + q->cell_overhead);
+       }
+
+       ticks = (u64)len * NSEC_PER_SEC;
+
+       do_div(ticks, q->rate);
+       return PSCHED_NS2TICKS(ticks);
+}
+
 /*
  * Insert one skb into qdisc.
  * Note: parent depends on return value to account for queue length.
@@ -371,6 +397,24 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
                                  &q->delay_cor, q->delay_dist);
 
                now = psched_get_time();
+
+               if (q->rate) {
+                       struct sk_buff_head *list = &q->qdisc->q;
+
+                       delay += packet_len_2_sched_time(skb->len, q);
+
+                       if (!skb_queue_empty(list)) {
+                               /*
+                                * Last packet in queue is reference point (now).
+                                * First packet in queue is already in flight,
+                                * calculate this time bonus and substract
+                                * from delay.
+                                */
+                               delay -= now - netem_skb_cb(skb_peek(list))->time_to_send;
+                               now = netem_skb_cb(skb_peek_tail(list))->time_to_send;
+                       }
+               }
+
                cb->time_to_send = now + delay;
                ++q->counter;
                ret = qdisc_enqueue(skb, q->qdisc);
@@ -488,7 +532,7 @@ static int get_dist_table(struct Qdisc *sch, const struct nlattr *attr)
                return -EINVAL;
 
        s = sizeof(struct disttable) + n * sizeof(s16);
-       d = kmalloc(s, GFP_KERNEL);
+       d = kmalloc(s, GFP_KERNEL | __GFP_NOWARN);
        if (!d)
                d = vmalloc(s);
        if (!d)
@@ -501,9 +545,10 @@ static int get_dist_table(struct Qdisc *sch, const struct nlattr *attr)
        root_lock = qdisc_root_sleeping_lock(sch);
 
        spin_lock_bh(root_lock);
-       dist_free(q->delay_dist);
-       q->delay_dist = d;
+       swap(q->delay_dist, d);
        spin_unlock_bh(root_lock);
+
+       dist_free(d);
        return 0;
 }
 
@@ -535,6 +580,19 @@ static void get_corrupt(struct Qdisc *sch, const struct nlattr *attr)
        init_crandom(&q->corrupt_cor, r->correlation);
 }
 
+static void get_rate(struct Qdisc *sch, const struct nlattr *attr)
+{
+       struct netem_sched_data *q = qdisc_priv(sch);
+       const struct tc_netem_rate *r = nla_data(attr);
+
+       q->rate = r->rate;
+       q->packet_overhead = r->packet_overhead;
+       q->cell_size = r->cell_size;
+       if (q->cell_size)
+               q->cell_size_reciprocal = reciprocal_value(q->cell_size);
+       q->cell_overhead = r->cell_overhead;
+}
+
 static int get_loss_clg(struct Qdisc *sch, const struct nlattr *attr)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
@@ -548,7 +606,7 @@ static int get_loss_clg(struct Qdisc *sch, const struct nlattr *attr)
                case NETEM_LOSS_GI: {
                        const struct tc_netem_gimodel *gi = nla_data(la);
 
-                       if (nla_len(la) != sizeof(struct tc_netem_gimodel)) {
+                       if (nla_len(la) < sizeof(struct tc_netem_gimodel)) {
                                pr_info("netem: incorrect gi model size\n");
                                return -EINVAL;
                        }
@@ -567,8 +625,8 @@ static int get_loss_clg(struct Qdisc *sch, const struct nlattr *attr)
                case NETEM_LOSS_GE: {
                        const struct tc_netem_gemodel *ge = nla_data(la);
 
-                       if (nla_len(la) != sizeof(struct tc_netem_gemodel)) {
-                               pr_info("netem: incorrect gi model size\n");
+                       if (nla_len(la) < sizeof(struct tc_netem_gemodel)) {
+                               pr_info("netem: incorrect ge model size\n");
                                return -EINVAL;
                        }
 
@@ -594,6 +652,7 @@ static const struct nla_policy netem_policy[TCA_NETEM_MAX + 1] = {
        [TCA_NETEM_CORR]        = { .len = sizeof(struct tc_netem_corr) },
        [TCA_NETEM_REORDER]     = { .len = sizeof(struct tc_netem_reorder) },
        [TCA_NETEM_CORRUPT]     = { .len = sizeof(struct tc_netem_corrupt) },
+       [TCA_NETEM_RATE]        = { .len = sizeof(struct tc_netem_rate) },
        [TCA_NETEM_LOSS]        = { .type = NLA_NESTED },
 };
 
@@ -666,6 +725,9 @@ static int netem_change(struct Qdisc *sch, struct nlattr *opt)
        if (tb[TCA_NETEM_CORRUPT])
                get_corrupt(sch, tb[TCA_NETEM_CORRUPT]);
 
+       if (tb[TCA_NETEM_RATE])
+               get_rate(sch, tb[TCA_NETEM_RATE]);
+
        q->loss_model = CLG_RANDOM;
        if (tb[TCA_NETEM_LOSS])
                ret = get_loss_clg(sch, tb[TCA_NETEM_LOSS]);
@@ -846,6 +908,7 @@ static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
        struct tc_netem_corr cor;
        struct tc_netem_reorder reorder;
        struct tc_netem_corrupt corrupt;
+       struct tc_netem_rate rate;
 
        qopt.latency = q->latency;
        qopt.jitter = q->jitter;
@@ -868,6 +931,12 @@ static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
        corrupt.correlation = q->corrupt_cor.rho;
        NLA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt);
 
+       rate.rate = q->rate;
+       rate.packet_overhead = q->packet_overhead;
+       rate.cell_size = q->cell_size;
+       rate.cell_overhead = q->cell_overhead;
+       NLA_PUT(skb, TCA_NETEM_RATE, sizeof(rate), &rate);
+
        if (dump_loss_model(q, skb) != 0)
                goto nla_put_failure;
 
This page took 0.026058 seconds and 5 git commands to generate.