patch-2_6_7-vs1_9_1_12
[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         rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt));
169         ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
170         ret = q->ops->change(q, rta);
171         kfree(rta);
172
173         return ret;
174 }
175
176 /* Setup underlying FIFO discipline */
177 static int dly_change(struct Qdisc *sch, struct rtattr *opt)
178 {
179         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
180         struct tc_dly_qopt *qopt = RTA_DATA(opt);
181         int err;
182
183         if (q->qdisc == &noop_qdisc) {
184                 struct Qdisc *child
185                         = qdisc_create_dflt(sch->dev, &bfifo_qdisc_ops);
186                 if (!child)
187                         return -EINVAL;
188                 q->qdisc = child;
189         }
190
191         err = change_limit(q->qdisc, qopt->limit);
192         if (err) {
193                 qdisc_destroy(q->qdisc);
194                 q->qdisc = &noop_qdisc;
195         } else {
196                 q->latency = qopt->latency;
197                 q->limit = qopt->limit;
198         }
199         return err;
200 }
201
202 static int dly_init(struct Qdisc *sch, struct rtattr *opt)
203 {
204         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
205
206         if (!opt)
207                 return -EINVAL;
208
209         init_timer(&q->timer);
210         q->timer.function = dly_timer;
211         q->timer.data = (unsigned long) sch;
212         q->qdisc = &noop_qdisc;
213
214         return dly_change(sch, opt);
215 }
216
217 static void dly_destroy(struct Qdisc *sch)
218 {
219         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
220
221         del_timer(&q->timer);
222         qdisc_destroy(q->qdisc);
223         q->qdisc = &noop_qdisc;
224 }
225
226 static int dly_dump(struct Qdisc *sch, struct sk_buff *skb)
227 {
228         struct dly_sched_data *q = (struct dly_sched_data *)sch->data;
229         unsigned char    *b = skb->tail;
230         struct tc_dly_qopt qopt;
231
232         qopt.latency = q->latency;
233         qopt.limit = q->limit;
234
235         RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
236
237         return skb->len;
238
239 rtattr_failure:
240         skb_trim(skb, b - skb->data);
241         return -1;
242 }
243
244 static struct Qdisc_ops dly_qdisc_ops = {
245         .id             =       "delay",
246         .priv_size      =       sizeof(struct dly_sched_data),
247         .enqueue        =       dly_enqueue,
248         .dequeue        =       dly_dequeue,
249         .requeue        =       dly_requeue,
250         .drop           =       dly_drop,
251         .init           =       dly_init,
252         .reset          =       dly_reset,
253         .destroy        =       dly_destroy,
254         .change         =       dly_change,
255         .dump           =       dly_dump,
256         .owner          =       THIS_MODULE,
257 };
258
259
260 static int __init dly_module_init(void)
261 {
262         return register_qdisc(&dly_qdisc_ops);
263 }
264 static void __exit dly_module_exit(void)
265 {
266         unregister_qdisc(&dly_qdisc_ops);
267 }
268 module_init(dly_module_init)
269 module_exit(dly_module_exit)
270 MODULE_LICENSE("GPL");