Fedora Core 2 - 1.494
[linux-2.6.git] / net / sched / sch_delay.c
1 /*
2  * net/sched/sch_delay.c        Simple constant delay
3  *
4  *              This program is free software; you can redistribute it and/or
5  *              modify it under the terms of the GNU General Public License
6  *              as published by the Free Software Foundation; either version
7  *              2 of the License, or (at your option) any later version.
8  *
9  * Authors:     Stephen Hemminger <shemminger@osdl.org>
10  */
11
12 #include <linux/config.h>
13 #include <linux/module.h>
14 #include <linux/types.h>
15 #include <linux/kernel.h>
16
17 #include <linux/string.h>
18 #include <linux/mm.h>
19 #include <linux/socket.h>
20 #include <linux/sockios.h>
21 #include <linux/in.h>
22 #include <linux/errno.h>
23 #include <linux/interrupt.h>
24 #include <linux/if_ether.h>
25 #include <linux/inet.h>
26 #include <linux/netdevice.h>
27 #include <linux/etherdevice.h>
28 #include <linux/notifier.h>
29 #include <net/ip.h>
30 #include <net/route.h>
31 #include <linux/skbuff.h>
32 #include <net/sock.h>
33 #include <net/pkt_sched.h>
34
35 /*      Network delay simulator
36         This scheduler adds a fixed delay to all packets.
37         Similar to NISTnet and BSD Dummynet.
38
39         It uses byte fifo underneath similar to TBF */
40 struct dly_sched_data {
41         u32     latency;
42         u32     limit;
43         u32     loss;
44         struct timer_list timer;
45         struct Qdisc *qdisc;
46 };
47
48 /* Time stamp put into socket buffer control block */
49 struct dly_skb_cb {
50         psched_time_t   queuetime;
51 };
52
53 /* Enqueue packets with underlying discipline (fifo)
54  * but mark them with current time first.
55  */
56 static int dly_enqueue(struct sk_buff *skb, struct Qdisc *sch)
57 {
58         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
59         struct dly_skb_cb *cb = (struct dly_skb_cb *)skb->cb;
60         int ret;
61
62         /* Random packet drop 0 => none, ~0 => all */
63         if (q->loss >= net_random()) {
64                 sch->stats.drops++;
65                 return 0;       /* lie about loss so TCP doesn't know */
66         }
67
68         PSCHED_GET_TIME(cb->queuetime);
69
70         /* Queue to underlying scheduler */
71         ret = q->qdisc->enqueue(skb, q->qdisc);
72         if (ret)
73                 sch->stats.drops++;
74         else {
75                 sch->q.qlen++;
76                 sch->stats.bytes += skb->len;
77                 sch->stats.packets++;
78         }
79         return ret;
80 }
81
82 /* Requeue packets but don't change time stamp */
83 static int dly_requeue(struct sk_buff *skb, struct Qdisc *sch)
84 {
85         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
86         int ret;
87
88         ret = q->qdisc->ops->requeue(skb, q->qdisc);
89         if (ret == 0)
90                 sch->q.qlen++;
91         return ret;
92 }
93
94 static unsigned int dly_drop(struct Qdisc *sch)
95 {
96         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
97         unsigned int len;
98
99         len = q->qdisc->ops->drop(q->qdisc);
100         if (len) {
101                 sch->q.qlen--;
102                 sch->stats.drops++;
103         }
104         return len;
105 }
106
107 /* Dequeue packet.
108  * If packet needs to be held up, then stop the
109  * queue and set timer to wakeup later.
110  */
111 static struct sk_buff *dly_dequeue(struct Qdisc *sch)
112 {
113         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
114         struct sk_buff *skb;
115
116  retry:
117         skb = q->qdisc->dequeue(q->qdisc);
118         if (skb) {
119                 struct dly_skb_cb *cb = (struct dly_skb_cb *)skb->cb;
120                 psched_time_t now;
121                 long diff, delay;
122
123                 PSCHED_GET_TIME(now);
124                 diff = q->latency - PSCHED_TDIFF(now, cb->queuetime);
125
126                 if (diff <= 0) {
127                         sch->q.qlen--;
128                         sch->flags &= ~TCQ_F_THROTTLED;
129                         return skb;
130                 }
131
132                 if (q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS) {
133                         sch->q.qlen--;
134                         sch->stats.drops++;
135                         goto retry;
136                 }
137
138                 delay = PSCHED_US2JIFFIE(diff);
139                 if (delay <= 0)
140                   delay = 1;
141                 mod_timer(&q->timer, jiffies+delay);
142
143                 sch->flags |= TCQ_F_THROTTLED;
144         }
145         return NULL;
146 }
147
148 static void dly_reset(struct Qdisc *sch)
149 {
150         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
151
152         qdisc_reset(q->qdisc);
153         sch->q.qlen = 0;
154         sch->flags &= ~TCQ_F_THROTTLED;
155         del_timer(&q->timer);
156 }
157
158 static void dly_timer(unsigned long arg)
159 {
160         struct Qdisc *sch = (struct Qdisc *)arg;
161
162         sch->flags &= ~TCQ_F_THROTTLED;
163         netif_schedule(sch->dev);
164 }
165
166 /* Tell Fifo the new limit. */
167 static int change_limit(struct Qdisc *q, u32 limit)
168 {
169         struct rtattr *rta;
170         int ret;
171
172         rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
173         if (!rta)
174                 return -ENOMEM;
175
176         rta->rta_type = RTM_NEWQDISC;
177         rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt));
178         ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
179         ret = q->ops->change(q, rta);
180         kfree(rta);
181
182         return ret;
183 }
184
185 /* Setup underlying FIFO discipline */
186 static int dly_change(struct Qdisc *sch, struct rtattr *opt)
187 {
188         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
189         struct tc_dly_qopt *qopt = RTA_DATA(opt);
190         int err;
191
192         if (q->qdisc == &noop_qdisc) {
193                 struct Qdisc *child
194                         = qdisc_create_dflt(sch->dev, &bfifo_qdisc_ops);
195                 if (!child)
196                         return -EINVAL;
197                 q->qdisc = child;
198         }
199
200         err = change_limit(q->qdisc, qopt->limit);
201         if (err) {
202                 qdisc_destroy(q->qdisc);
203                 q->qdisc = &noop_qdisc;
204         } else {
205                 q->latency = qopt->latency;
206                 q->limit = qopt->limit;
207                 q->loss = qopt->loss;
208         }
209         return err;
210 }
211
212 static int dly_init(struct Qdisc *sch, struct rtattr *opt)
213 {
214         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
215
216         if (!opt)
217                 return -EINVAL;
218
219         init_timer(&q->timer);
220         q->timer.function = dly_timer;
221         q->timer.data = (unsigned long) sch;
222         q->qdisc = &noop_qdisc;
223
224         return dly_change(sch, opt);
225 }
226
227 static void dly_destroy(struct Qdisc *sch)
228 {
229         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
230
231         del_timer(&q->timer);
232         qdisc_destroy(q->qdisc);
233         q->qdisc = &noop_qdisc;
234 }
235
236 static int dly_dump(struct Qdisc *sch, struct sk_buff *skb)
237 {
238         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
239         unsigned char    *b = skb->tail;
240         struct tc_dly_qopt qopt;
241
242         qopt.latency = q->latency;
243         qopt.limit = q->limit;
244         qopt.loss = q->loss;
245
246         RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
247
248         return skb->len;
249
250 rtattr_failure:
251         skb_trim(skb, b - skb->data);
252         return -1;
253 }
254
255 static struct Qdisc_ops dly_qdisc_ops = {
256         .id             =       "delay",
257         .priv_size      =       sizeof(struct dly_sched_data),
258         .enqueue        =       dly_enqueue,
259         .dequeue        =       dly_dequeue,
260         .requeue        =       dly_requeue,
261         .drop           =       dly_drop,
262         .init           =       dly_init,
263         .reset          =       dly_reset,
264         .destroy        =       dly_destroy,
265         .change         =       dly_change,
266         .dump           =       dly_dump,
267         .owner          =       THIS_MODULE,
268 };
269
270
271 static int __init dly_module_init(void)
272 {
273         return register_qdisc(&dly_qdisc_ops);
274 }
275 static void __exit dly_module_exit(void)
276 {
277         unregister_qdisc(&dly_qdisc_ops);
278 }
279 module_init(dly_module_init)
280 module_exit(dly_module_exit)
281 MODULE_LICENSE("GPL");