mpc5200_psc_spi: typo fix in header block
[safe/jmp/linux-2.6] / net / sched / sch_netem.c
index bb9bf8d..c9c649b 100644 (file)
@@ -4,27 +4,27 @@
  *             This program is free software; you can redistribute it and/or
  *             modify it under the terms of the GNU General Public License
  *             as published by the Free Software Foundation; either version
- *             2 of the License, or (at your option) any later version.
+ *             2 of the License.
  *
  *             Many of the algorithms and ideas for this came from
- *             NIST Net which is not copyrighted. 
+ *             NIST Net which is not copyrighted.
  *
  * Authors:    Stephen Hemminger <shemminger@osdl.org>
  *             Catalin(ux aka Dino) BOIE <catab at umbrella dot ro>
  */
 
-#include <linux/config.h>
 #include <linux/module.h>
-#include <linux/bitops.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
 #include <linux/errno.h>
-#include <linux/netdevice.h>
 #include <linux/skbuff.h>
 #include <linux/rtnetlink.h>
 
+#include <net/netlink.h>
 #include <net/pkt_sched.h>
 
+#define VERSION "1.2"
+
 /*     Network Emulation Queuing algorithm.
        ====================================
 
 
 struct netem_sched_data {
        struct Qdisc    *qdisc;
-       struct timer_list timer;
+       struct qdisc_watchdog watchdog;
+
+       psched_tdiff_t latency;
+       psched_tdiff_t jitter;
 
-       u32 latency;
        u32 loss;
        u32 limit;
        u32 counter;
        u32 gap;
-       u32 jitter;
        u32 duplicate;
        u32 reorder;
+       u32 corrupt;
 
        struct crndstate {
-               unsigned long last;
-               unsigned long rho;
-       } delay_cor, loss_cor, dup_cor, reorder_cor;
+               u32 last;
+               u32 rho;
+       } delay_cor, loss_cor, dup_cor, reorder_cor, corrupt_cor;
 
        struct disttable {
                u32  size;
@@ -93,12 +95,12 @@ static void init_crandom(struct crndstate *state, unsigned long rho)
  * Next number depends on last value.
  * rho is scaled to avoid floating point.
  */
-static unsigned long get_crandom(struct crndstate *state)
+static u32 get_crandom(struct crndstate *state)
 {
        u64 value, rho;
        unsigned long answer;
 
-       if (state->rho == 0)    /* no correllation */
+       if (state->rho == 0)    /* no correlation */
                return net_random();
 
        value = net_random();
@@ -112,11 +114,13 @@ static unsigned long get_crandom(struct crndstate *state)
  * std deviation sigma.  Uses table lookup to approximate the desired
  * distribution, and a uniformly-distributed pseudo-random source.
  */
-static long tabledist(unsigned long mu, long sigma, 
-                     struct crndstate *state, const struct disttable *dist)
+static psched_tdiff_t tabledist(psched_tdiff_t mu, psched_tdiff_t sigma,
+                               struct crndstate *state,
+                               const struct disttable *dist)
 {
-       long t, x;
-       unsigned long rnd;
+       psched_tdiff_t x;
+       long t;
+       u32 rnd;
 
        if (sigma == 0)
                return mu;
@@ -124,7 +128,7 @@ static long tabledist(unsigned long mu, long sigma,
        rnd = get_crandom(state);
 
        /* default uniform distribution */
-       if (dist == NULL) 
+       if (dist == NULL)
                return (rnd % (2*sigma)) - sigma + mu;
 
        t = dist->table[rnd % dist->size];
@@ -146,7 +150,8 @@ static long tabledist(unsigned long mu, long sigma,
 static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
-       struct netem_skb_cb *cb = (struct netem_skb_cb *)skb->cb;
+       /* We don't fill cb now as skb_unshare() may invalidate it */
+       struct netem_skb_cb *cb;
        struct sk_buff *skb2;
        int ret;
        int count = 1;
@@ -164,9 +169,11 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
        if (count == 0) {
                sch->qstats.drops++;
                kfree_skb(skb);
-               return NET_XMIT_DROP;
+               return NET_XMIT_BYPASS;
        }
 
+       skb_orphan(skb);
+
        /*
         * If we need to duplicate packet, then re-insert at top of the
         * qdisc tree, since parent queuer expects that only one
@@ -181,22 +188,43 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
                q->duplicate = dupsave;
        }
 
+       /*
+        * Randomized packet corruption.
+        * Make copy if needed since we are modifying
+        * If packet is going to be hardware checksummed, then
+        * do it now in software before we mangle it.
+        */
+       if (q->corrupt && q->corrupt >= get_crandom(&q->corrupt_cor)) {
+               if (!(skb = skb_unshare(skb, GFP_ATOMIC))
+                   || (skb->ip_summed == CHECKSUM_PARTIAL
+                       && skb_checksum_help(skb))) {
+                       sch->qstats.drops++;
+                       return NET_XMIT_DROP;
+               }
+
+               skb->data[net_random() % skb_headlen(skb)] ^= 1<<(net_random() % 8);
+       }
+
+       cb = (struct netem_skb_cb *)skb->cb;
        if (q->gap == 0                 /* not doing reordering */
            || q->counter < q->gap      /* inside last reordering gap */
            || q->reorder < get_crandom(&q->reorder_cor)) {
                psched_time_t now;
-               PSCHED_GET_TIME(now);
-               PSCHED_TADD2(now, tabledist(q->latency, q->jitter, 
-                                           &q->delay_cor, q->delay_dist),
-                            cb->time_to_send);
+               psched_tdiff_t delay;
+
+               delay = tabledist(q->latency, q->jitter,
+                                 &q->delay_cor, q->delay_dist);
+
+               now = psched_get_time();
+               cb->time_to_send = now + delay;
                ++q->counter;
                ret = q->qdisc->enqueue(skb, q->qdisc);
        } else {
-               /* 
+               /*
                 * Do re-ordering by putting one out of N packets at the front
                 * of the queue.
                 */
-               PSCHED_GET_TIME(cb->time_to_send);
+               cb->time_to_send = psched_get_time();
                q->counter = 0;
                ret = q->qdisc->ops->requeue(skb, q->qdisc);
        }
@@ -229,9 +257,9 @@ static int netem_requeue(struct sk_buff *skb, struct Qdisc *sch)
 static unsigned int netem_drop(struct Qdisc* sch)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
-       unsigned int len;
+       unsigned int len = 0;
 
-       if ((len = q->qdisc->ops->drop(q->qdisc)) != 0) {
+       if (q->qdisc->ops->drop && (len = q->qdisc->ops->drop(q->qdisc)) != 0) {
                sch->q.qlen--;
                sch->qstats.drops++;
        }
@@ -243,66 +271,63 @@ static struct sk_buff *netem_dequeue(struct Qdisc *sch)
        struct netem_sched_data *q = qdisc_priv(sch);
        struct sk_buff *skb;
 
+       smp_mb();
+       if (sch->flags & TCQ_F_THROTTLED)
+               return NULL;
+
        skb = q->qdisc->dequeue(q->qdisc);
        if (skb) {
                const struct netem_skb_cb *cb
                        = (const struct netem_skb_cb *)skb->cb;
-               psched_time_t now;
-               long delay;
+               psched_time_t now = psched_get_time();
 
                /* if more time remaining? */
-               PSCHED_GET_TIME(now);
-               delay = PSCHED_US2JIFFIE(PSCHED_TDIFF(cb->time_to_send, now));
-               pr_debug("netem_run: skb=%p delay=%ld\n", skb, delay);
-               if (delay <= 0) {
+               if (cb->time_to_send <= now) {
                        pr_debug("netem_dequeue: return skb=%p\n", skb);
                        sch->q.qlen--;
-                       sch->flags &= ~TCQ_F_THROTTLED;
                        return skb;
                }
 
-               mod_timer(&q->timer, jiffies + delay);
-               sch->flags |= TCQ_F_THROTTLED;
-
-               if (q->qdisc->ops->requeue(skb, q->qdisc) != 0)
+               if (unlikely(q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS)) {
+                       qdisc_tree_decrease_qlen(q->qdisc, 1);
                        sch->qstats.drops++;
+                       printk(KERN_ERR "netem: %s could not requeue\n",
+                              q->qdisc->ops->id);
+               }
+
+               qdisc_watchdog_schedule(&q->watchdog, cb->time_to_send);
        }
 
        return NULL;
 }
 
-static void netem_watchdog(unsigned long arg)
-{
-       struct Qdisc *sch = (struct Qdisc *)arg;
-
-       pr_debug("netem_watchdog qlen=%d\n", sch->q.qlen);
-       sch->flags &= ~TCQ_F_THROTTLED;
-       netif_schedule(sch->dev);
-}
-
 static void netem_reset(struct Qdisc *sch)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
 
        qdisc_reset(q->qdisc);
        sch->q.qlen = 0;
-       sch->flags &= ~TCQ_F_THROTTLED;
-       del_timer_sync(&q->timer);
+       qdisc_watchdog_cancel(&q->watchdog);
 }
 
+/* Pass size change message down to embedded FIFO */
 static int set_fifo_limit(struct Qdisc *q, int limit)
 {
-        struct rtattr *rta;
+       struct nlattr *nla;
        int ret = -ENOMEM;
 
-       rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
-       if (rta) {
-               rta->rta_type = RTM_NEWQDISC;
-               rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt)); 
-               ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
-               
-               ret = q->ops->change(q, rta);
-               kfree(rta);
+       /* Hack to avoid sending change message to non-FIFO */
+       if (strncmp(q->ops->id + 1, "fifo", 4) != 0)
+               return 0;
+
+       nla = kmalloc(nla_attr_size(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
+       if (nla) {
+               nla->nla_type = RTM_NEWQDISC;
+               nla->nla_len = nla_attr_size(sizeof(struct tc_fifo_qopt));
+               ((struct tc_fifo_qopt *)nla_data(nla))->limit = limit;
+
+               ret = q->ops->change(q, nla);
+               kfree(nla);
        }
        return ret;
 }
@@ -311,11 +336,11 @@ static int set_fifo_limit(struct Qdisc *q, int limit)
  * Distribution data is a variable size payload containing
  * signed 16 bit values.
  */
-static int get_dist_table(struct Qdisc *sch, const struct rtattr *attr)
+static int get_dist_table(struct Qdisc *sch, const struct nlattr *attr)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
-       unsigned long n = RTA_PAYLOAD(attr)/sizeof(__s16);
-       const __s16 *data = RTA_DATA(attr);
+       unsigned long n = nla_len(attr)/sizeof(__s16);
+       const __s16 *data = nla_data(attr);
        struct disttable *d;
        int i;
 
@@ -329,7 +354,7 @@ static int get_dist_table(struct Qdisc *sch, const struct rtattr *attr)
        d->size = n;
        for (i = 0; i < n; i++)
                d->table[i] = data[i];
-       
+
        spin_lock_bh(&sch->dev->queue_lock);
        d = xchg(&q->delay_dist, d);
        spin_unlock_bh(&sch->dev->queue_lock);
@@ -338,13 +363,10 @@ static int get_dist_table(struct Qdisc *sch, const struct rtattr *attr)
        return 0;
 }
 
-static int get_correlation(struct Qdisc *sch, const struct rtattr *attr)
+static int get_correlation(struct Qdisc *sch, const struct nlattr *attr)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
-       const struct tc_netem_corr *c = RTA_DATA(attr);
-
-       if (RTA_PAYLOAD(attr) != sizeof(*c))
-               return -EINVAL;
+       const struct tc_netem_corr *c = nla_data(attr);
 
        init_crandom(&q->delay_cor, c->delay_corr);
        init_crandom(&q->loss_cor, c->loss_corr);
@@ -352,35 +374,54 @@ static int get_correlation(struct Qdisc *sch, const struct rtattr *attr)
        return 0;
 }
 
-static int get_reorder(struct Qdisc *sch, const struct rtattr *attr)
+static int get_reorder(struct Qdisc *sch, const struct nlattr *attr)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
-       const struct tc_netem_reorder *r = RTA_DATA(attr);
-
-       if (RTA_PAYLOAD(attr) != sizeof(*r))
-               return -EINVAL;
+       const struct tc_netem_reorder *r = nla_data(attr);
 
        q->reorder = r->probability;
        init_crandom(&q->reorder_cor, r->correlation);
        return 0;
 }
 
-static int netem_change(struct Qdisc *sch, struct rtattr *opt)
+static int get_corrupt(struct Qdisc *sch, const struct nlattr *attr)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
+       const struct tc_netem_corrupt *r = nla_data(attr);
+
+       q->corrupt = r->probability;
+       init_crandom(&q->corrupt_cor, r->correlation);
+       return 0;
+}
+
+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) },
+};
+
+/* Parse netlink message to set options */
+static int netem_change(struct Qdisc *sch, struct nlattr *opt)
+{
+       struct netem_sched_data *q = qdisc_priv(sch);
+       struct nlattr *tb[TCA_NETEM_MAX + 1];
        struct tc_netem_qopt *qopt;
        int ret;
-       
-       if (opt == NULL || RTA_PAYLOAD(opt) < sizeof(*qopt))
+
+       if (opt == NULL)
                return -EINVAL;
 
-       qopt = RTA_DATA(opt);
+       ret = nla_parse_nested_compat(tb, TCA_NETEM_MAX, opt, netem_policy,
+                                     qopt, sizeof(*qopt));
+       if (ret < 0)
+               return ret;
+
        ret = set_fifo_limit(q->qdisc, qopt->limit);
        if (ret) {
                pr_debug("netem: can't set fifo limit\n");
                return ret;
        }
-       
+
        q->latency = qopt->latency;
        q->jitter = qopt->jitter;
        q->limit = qopt->limit;
@@ -389,44 +430,125 @@ static int netem_change(struct Qdisc *sch, struct rtattr *opt)
        q->loss = qopt->loss;
        q->duplicate = qopt->duplicate;
 
-       /* for compatiablity with earlier versions.
-        * if gap is set, need to assume 100% probablity
+       /* for compatibility with earlier versions.
+        * if gap is set, need to assume 100% probability
         */
-       q->reorder = ~0;
-
-       /* Handle nested options after initial queue options.
-        * Should have put all options in nested format but too late now.
-        */ 
-       if (RTA_PAYLOAD(opt) > sizeof(*qopt)) {
-               struct rtattr *tb[TCA_NETEM_MAX];
-               if (rtattr_parse(tb, TCA_NETEM_MAX, 
-                                RTA_DATA(opt) + sizeof(*qopt),
-                                RTA_PAYLOAD(opt) - sizeof(*qopt)))
-                       return -EINVAL;
+       if (q->gap)
+               q->reorder = ~0;
 
-               if (tb[TCA_NETEM_CORR-1]) {
-                       ret = get_correlation(sch, tb[TCA_NETEM_CORR-1]);
-                       if (ret)
-                               return ret;
-               }
+       if (tb[TCA_NETEM_CORR]) {
+               ret = get_correlation(sch, tb[TCA_NETEM_CORR]);
+               if (ret)
+                       return ret;
+       }
+
+       if (tb[TCA_NETEM_DELAY_DIST]) {
+               ret = get_dist_table(sch, tb[TCA_NETEM_DELAY_DIST]);
+               if (ret)
+                       return ret;
+       }
 
-               if (tb[TCA_NETEM_DELAY_DIST-1]) {
-                       ret = get_dist_table(sch, tb[TCA_NETEM_DELAY_DIST-1]);
-                       if (ret)
-                               return ret;
+       if (tb[TCA_NETEM_REORDER]) {
+               ret = get_reorder(sch, tb[TCA_NETEM_REORDER]);
+               if (ret)
+                       return ret;
+       }
+
+       if (tb[TCA_NETEM_CORRUPT]) {
+               ret = get_corrupt(sch, tb[TCA_NETEM_CORRUPT]);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/*
+ * Special case version of FIFO queue for use by netem.
+ * It queues in order based on timestamps in skb's
+ */
+struct fifo_sched_data {
+       u32 limit;
+       psched_time_t oldest;
+};
+
+static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch)
+{
+       struct fifo_sched_data *q = qdisc_priv(sch);
+       struct sk_buff_head *list = &sch->q;
+       psched_time_t tnext = ((struct netem_skb_cb *)nskb->cb)->time_to_send;
+       struct sk_buff *skb;
+
+       if (likely(skb_queue_len(list) < q->limit)) {
+               /* Optimize for add at tail */
+               if (likely(skb_queue_empty(list) || tnext >= q->oldest)) {
+                       q->oldest = tnext;
+                       return qdisc_enqueue_tail(nskb, sch);
                }
-               if (tb[TCA_NETEM_REORDER-1]) {
-                       ret = get_reorder(sch, tb[TCA_NETEM_REORDER-1]);
-                       if (ret)
-                               return ret;
+
+               skb_queue_reverse_walk(list, skb) {
+                       const struct netem_skb_cb *cb
+                               = (const struct netem_skb_cb *)skb->cb;
+
+                       if (tnext >= cb->time_to_send)
+                               break;
                }
+
+               __skb_queue_after(list, skb, nskb);
+
+               sch->qstats.backlog += nskb->len;
+               sch->bstats.bytes += nskb->len;
+               sch->bstats.packets++;
+
+               return NET_XMIT_SUCCESS;
        }
 
+       return qdisc_reshape_fail(nskb, sch);
+}
+
+static int tfifo_init(struct Qdisc *sch, struct nlattr *opt)
+{
+       struct fifo_sched_data *q = qdisc_priv(sch);
 
+       if (opt) {
+               struct tc_fifo_qopt *ctl = nla_data(opt);
+               if (nla_len(opt) < sizeof(*ctl))
+                       return -EINVAL;
+
+               q->limit = ctl->limit;
+       } else
+               q->limit = max_t(u32, sch->dev->tx_queue_len, 1);
+
+       q->oldest = PSCHED_PASTPERFECT;
        return 0;
 }
 
-static int netem_init(struct Qdisc *sch, struct rtattr *opt)
+static int tfifo_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+       struct fifo_sched_data *q = qdisc_priv(sch);
+       struct tc_fifo_qopt opt = { .limit = q->limit };
+
+       NLA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+       return skb->len;
+
+nla_put_failure:
+       return -1;
+}
+
+static struct Qdisc_ops tfifo_qdisc_ops __read_mostly = {
+       .id             =       "tfifo",
+       .priv_size      =       sizeof(struct fifo_sched_data),
+       .enqueue        =       tfifo_enqueue,
+       .dequeue        =       qdisc_dequeue_head,
+       .requeue        =       qdisc_requeue,
+       .drop           =       qdisc_queue_drop,
+       .init           =       tfifo_init,
+       .reset          =       qdisc_reset_queue,
+       .change         =       tfifo_init,
+       .dump           =       tfifo_dump,
+};
+
+static int netem_init(struct Qdisc *sch, struct nlattr *opt)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
        int ret;
@@ -434,11 +556,10 @@ static int netem_init(struct Qdisc *sch, struct rtattr *opt)
        if (!opt)
                return -EINVAL;
 
-       init_timer(&q->timer);
-       q->timer.function = netem_watchdog;
-       q->timer.data = (unsigned long) sch;
+       qdisc_watchdog_init(&q->watchdog, sch);
 
-       q->qdisc = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+       q->qdisc = qdisc_create_dflt(sch->dev, &tfifo_qdisc_ops,
+                                    TC_H_MAKE(sch->handle, 1));
        if (!q->qdisc) {
                pr_debug("netem: qdisc create failed\n");
                return -ENOMEM;
@@ -456,7 +577,7 @@ static void netem_destroy(struct Qdisc *sch)
 {
        struct netem_sched_data *q = qdisc_priv(sch);
 
-       del_timer_sync(&q->timer);
+       qdisc_watchdog_cancel(&q->watchdog);
        qdisc_destroy(q->qdisc);
        kfree(q->delay_dist);
 }
@@ -464,11 +585,12 @@ static void netem_destroy(struct Qdisc *sch)
 static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
 {
        const struct netem_sched_data *q = qdisc_priv(sch);
-       unsigned char    *b = skb->tail;
-       struct rtattr *rta = (struct rtattr *) b;
+       unsigned char *b = skb_tail_pointer(skb);
+       struct nlattr *nla = (struct nlattr *) b;
        struct tc_netem_qopt qopt;
        struct tc_netem_corr cor;
        struct tc_netem_reorder reorder;
+       struct tc_netem_corrupt corrupt;
 
        qopt.latency = q->latency;
        qopt.jitter = q->jitter;
@@ -476,23 +598,27 @@ static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
        qopt.loss = q->loss;
        qopt.gap = q->gap;
        qopt.duplicate = q->duplicate;
-       RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
+       NLA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
 
        cor.delay_corr = q->delay_cor.rho;
        cor.loss_corr = q->loss_cor.rho;
        cor.dup_corr = q->dup_cor.rho;
-       RTA_PUT(skb, TCA_NETEM_CORR, sizeof(cor), &cor);
+       NLA_PUT(skb, TCA_NETEM_CORR, sizeof(cor), &cor);
 
        reorder.probability = q->reorder;
        reorder.correlation = q->reorder_cor.rho;
-       RTA_PUT(skb, TCA_NETEM_REORDER, sizeof(reorder), &reorder);
+       NLA_PUT(skb, TCA_NETEM_REORDER, sizeof(reorder), &reorder);
 
-       rta->rta_len = skb->tail - b;
+       corrupt.probability = q->corrupt;
+       corrupt.correlation = q->corrupt_cor.rho;
+       NLA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt);
+
+       nla->nla_len = skb_tail_pointer(skb) - b;
 
        return skb->len;
 
-rtattr_failure:
-       skb_trim(skb, b - skb->data);
+nla_put_failure:
+       nlmsg_trim(skb, b);
        return -1;
 }
 
@@ -520,8 +646,8 @@ static int netem_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
 
        sch_tree_lock(sch);
        *old = xchg(&q->qdisc, new);
+       qdisc_tree_decrease_qlen(*old, (*old)->q.qlen);
        qdisc_reset(*old);
-       sch->q.qlen = 0;
        sch_tree_unlock(sch);
 
        return 0;
@@ -542,8 +668,8 @@ static void netem_put(struct Qdisc *sch, unsigned long arg)
 {
 }
 
-static int netem_change_class(struct Qdisc *sch, u32 classid, u32 parentid, 
-                           struct rtattr **tca, unsigned long *arg)
+static int netem_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
+                           struct nlattr **tca, unsigned long *arg)
 {
        return -ENOSYS;
 }
@@ -570,7 +696,7 @@ static struct tcf_proto **netem_find_tcf(struct Qdisc *sch, unsigned long cl)
        return NULL;
 }
 
-static struct Qdisc_class_ops netem_class_ops = {
+static const struct Qdisc_class_ops netem_class_ops = {
        .graft          =       netem_graft,
        .leaf           =       netem_leaf,
        .get            =       netem_get,
@@ -582,7 +708,7 @@ static struct Qdisc_class_ops netem_class_ops = {
        .dump           =       netem_dump_class,
 };
 
-static struct Qdisc_ops netem_qdisc_ops = {
+static struct Qdisc_ops netem_qdisc_ops __read_mostly = {
        .id             =       "netem",
        .cl_ops         =       &netem_class_ops,
        .priv_size      =       sizeof(struct netem_sched_data),
@@ -601,6 +727,7 @@ static struct Qdisc_ops netem_qdisc_ops = {
 
 static int __init netem_module_init(void)
 {
+       pr_info("netem: version " VERSION "\n");
        return register_qdisc(&netem_qdisc_ops);
 }
 static void __exit netem_module_exit(void)