ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[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         struct timer_list timer;
44         struct Qdisc *qdisc;
45 };
46
47 /* Time stamp put into socket buffer control block */
48 struct dly_skb_cb {
49         psched_time_t   queuetime;
50 };
51
52 /* Enqueue packets with underlying discipline (fifo)
53  * but mark them with current time first.
54  */
55 static int dly_enqueue(struct sk_buff *skb, struct Qdisc *sch)
56 {
57         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
58         struct dly_skb_cb *cb = (struct dly_skb_cb *)skb->cb;
59         int ret;
60
61         PSCHED_GET_TIME(cb->queuetime);
62
63         /* Queue to underlying scheduler */
64         ret = q->qdisc->enqueue(skb, q->qdisc);
65         if (ret)
66                 sch->stats.drops++;
67         else {
68                 sch->q.qlen++;
69                 sch->stats.bytes += skb->len;
70                 sch->stats.packets++;
71         }
72         return 0;
73 }
74
75 /* Requeue packets but don't change time stamp */
76 static int dly_requeue(struct sk_buff *skb, struct Qdisc *sch)
77 {
78         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
79         int ret;
80
81         ret = q->qdisc->ops->requeue(skb, q->qdisc);
82         if (ret == 0)
83                 sch->q.qlen++;
84         return ret;
85 }
86
87 static unsigned int dly_drop(struct Qdisc *sch)
88 {
89         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
90         unsigned int len;
91
92         len = q->qdisc->ops->drop(q->qdisc);
93         if (len) {
94                 sch->q.qlen--;
95                 sch->stats.drops++;
96         }
97         return len;
98 }
99
100 /* Dequeue packet.
101  * If packet needs to be held up, then stop the
102  * queue and set timer to wakeup later.
103  */
104 static struct sk_buff *dly_dequeue(struct Qdisc *sch)
105 {
106         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
107         struct sk_buff *skb = q->qdisc->dequeue(q->qdisc);
108
109         if (skb) {
110                 struct dly_skb_cb *cb = (struct dly_skb_cb *)skb->cb;
111                 psched_time_t now;
112                 long diff;
113
114                 PSCHED_GET_TIME(now);
115                 diff = q->latency - PSCHED_TDIFF(now, cb->queuetime);
116
117                 if (diff <= 0) {
118                         sch->q.qlen--;
119                         sch->flags &= ~TCQ_F_THROTTLED;
120                         return skb;
121                 }
122
123                 if (!netif_queue_stopped(sch->dev)) {
124                         long delay = PSCHED_US2JIFFIE(diff);
125                         if (delay <= 0)
126                                 delay = 1;
127                         mod_timer(&q->timer, jiffies+delay);
128                 }
129
130                 if (q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS) {
131                         sch->q.qlen--;
132                         sch->stats.drops++;
133                 }
134                 sch->flags |= TCQ_F_THROTTLED;
135         }
136         return NULL;
137 }
138
139 static void dly_reset(struct Qdisc *sch)
140 {
141         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
142
143         qdisc_reset(q->qdisc);
144         sch->q.qlen = 0;
145         sch->flags &= ~TCQ_F_THROTTLED;
146         del_timer(&q->timer);
147 }
148
149 static void dly_timer(unsigned long arg)
150 {
151         struct Qdisc *sch = (struct Qdisc *)arg;
152
153         sch->flags &= ~TCQ_F_THROTTLED;
154         netif_schedule(sch->dev);
155 }
156
157 /* Tell Fifo the new limit. */
158 static int change_limit(struct Qdisc *q, u32 limit)
159 {
160         struct rtattr *rta;
161         int ret;
162
163         rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
164         if (!rta)
165                 return -ENOMEM;
166
167         rta->rta_type = RTM_NEWQDISC;
168         ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
169         ret = q->ops->change(q, rta);
170         kfree(rta);
171
172         return ret;
173 }
174
175 /* Setup underlying FIFO discipline */
176 static int dly_change(struct Qdisc *sch, struct rtattr *opt)
177 {
178         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
179         struct tc_dly_qopt *qopt = RTA_DATA(opt);
180         int err;
181
182         if (q->qdisc == &noop_qdisc) {
183                 struct Qdisc *child
184                         = qdisc_create_dflt(sch->dev, &bfifo_qdisc_ops);
185                 if (!child)
186                         return -EINVAL;
187                 q->qdisc = child;
188         }
189
190         err = change_limit(q->qdisc, qopt->limit);
191         if (err) {
192                 qdisc_destroy(q->qdisc);
193                 q->qdisc = &noop_qdisc;
194         } else {
195                 q->latency = qopt->latency;
196                 q->limit = qopt->limit;
197         }
198         return err;
199 }
200
201 static int dly_init(struct Qdisc *sch, struct rtattr *opt)
202 {
203         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
204
205         if (!opt)
206                 return -EINVAL;
207
208         init_timer(&q->timer);
209         q->timer.function = dly_timer;
210         q->timer.data = (unsigned long) sch;
211         q->qdisc = &noop_qdisc;
212
213         return dly_change(sch, opt);
214 }
215
216 static void dly_destroy(struct Qdisc *sch)
217 {
218         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
219
220         del_timer(&q->timer);
221         qdisc_destroy(q->qdisc);
222         q->qdisc = &noop_qdisc;
223 }
224
225 static int dly_dump(struct Qdisc *sch, struct sk_buff *skb)
226 {
227         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
228         unsigned char    *b = skb->tail;
229         struct tc_dly_qopt qopt;
230
231         qopt.latency = q->latency;
232         qopt.limit = q->limit;
233
234         RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
235
236         return skb->len;
237
238 rtattr_failure:
239         skb_trim(skb, b - skb->data);
240         return -1;
241 }
242
243 static struct Qdisc_ops dly_qdisc_ops = {
244         .id             =       "delay",
245         .priv_size      =       sizeof(struct dly_sched_data),
246         .enqueue        =       dly_enqueue,
247         .dequeue        =       dly_dequeue,
248         .requeue        =       dly_requeue,
249         .drop           =       dly_drop,
250         .init           =       dly_init,
251         .reset          =       dly_reset,
252         .destroy        =       dly_destroy,
253         .change         =       dly_change,
254         .dump           =       dly_dump,
255         .owner          =       THIS_MODULE,
256 };
257
258
259 static int __init dly_module_init(void)
260 {
261         return register_qdisc(&dly_qdisc_ops);
262 }
263 static void __exit dly_module_exit(void)
264 {
265         unregister_qdisc(&dly_qdisc_ops);
266 }
267 module_init(dly_module_init)
268 module_exit(dly_module_exit)
269 MODULE_LICENSE("GPL");