blob: dccfa44c2d71d3be7d7d766987a7b1d0bdc9977d [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * net/sched/sch_red.c Random Early Detection queue.
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: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10 *
11 * Changes:
Thomas Grafdba051f2005-11-05 21:14:08 +010012 * J Hadi Salim 980914: computation fixes
Linus Torvalds1da177e2005-04-16 15:20:36 -070013 * Alexey Makarenko <makar@phoenix.kharkov.ua> 990814: qave on idle link was calculated incorrectly.
Thomas Grafdba051f2005-11-05 21:14:08 +010014 * J Hadi Salim 980816: ECN support
Linus Torvalds1da177e2005-04-16 15:20:36 -070015 */
16
17#include <linux/config.h>
18#include <linux/module.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070019#include <linux/types.h>
20#include <linux/kernel.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070021#include <linux/netdevice.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070022#include <linux/skbuff.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070023#include <net/pkt_sched.h>
24#include <net/inet_ecn.h>
Thomas Graf6b31b282005-11-05 21:14:05 +010025#include <net/red.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070026
27
Thomas Graf6b31b282005-11-05 21:14:05 +010028/* Parameters, settable by user:
Linus Torvalds1da177e2005-04-16 15:20:36 -070029 -----------------------------
30
31 limit - bytes (must be > qth_max + burst)
32
33 Hard limit on queue length, should be chosen >qth_max
34 to allow packet bursts. This parameter does not
35 affect the algorithms behaviour and can be chosen
36 arbitrarily high (well, less than ram size)
37 Really, this limit will never be reached
38 if RED works correctly.
Linus Torvalds1da177e2005-04-16 15:20:36 -070039 */
40
41struct red_sched_data
42{
Thomas Graf6b31b282005-11-05 21:14:05 +010043 u32 limit; /* HARD maximal queue length */
44 unsigned char flags;
45 struct red_parms parms;
46 struct red_stats stats;
Linus Torvalds1da177e2005-04-16 15:20:36 -070047};
48
Thomas Graf6b31b282005-11-05 21:14:05 +010049static inline int red_use_ecn(struct red_sched_data *q)
Linus Torvalds1da177e2005-04-16 15:20:36 -070050{
Thomas Graf6b31b282005-11-05 21:14:05 +010051 return q->flags & TC_RED_ECN;
Linus Torvalds1da177e2005-04-16 15:20:36 -070052}
53
Thomas Grafbdc450a2005-11-05 21:14:28 +010054static inline int red_use_harddrop(struct red_sched_data *q)
55{
56 return q->flags & TC_RED_HARDDROP;
57}
58
Thomas Grafdba051f2005-11-05 21:14:08 +010059static int red_enqueue(struct sk_buff *skb, struct Qdisc* sch)
Linus Torvalds1da177e2005-04-16 15:20:36 -070060{
61 struct red_sched_data *q = qdisc_priv(sch);
62
Thomas Graf6b31b282005-11-05 21:14:05 +010063 q->parms.qavg = red_calc_qavg(&q->parms, sch->qstats.backlog);
Linus Torvalds1da177e2005-04-16 15:20:36 -070064
Thomas Graf6b31b282005-11-05 21:14:05 +010065 if (red_is_idling(&q->parms))
66 red_end_of_idle_period(&q->parms);
Linus Torvalds1da177e2005-04-16 15:20:36 -070067
Thomas Graf6b31b282005-11-05 21:14:05 +010068 switch (red_action(&q->parms, q->parms.qavg)) {
69 case RED_DONT_MARK:
70 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -070071
Thomas Graf6b31b282005-11-05 21:14:05 +010072 case RED_PROB_MARK:
73 sch->qstats.overlimits++;
74 if (!red_use_ecn(q) || !INET_ECN_set_ce(skb)) {
75 q->stats.prob_drop++;
76 goto congestion_drop;
77 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070078
Thomas Graf6b31b282005-11-05 21:14:05 +010079 q->stats.prob_mark++;
80 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -070081
Thomas Graf6b31b282005-11-05 21:14:05 +010082 case RED_HARD_MARK:
83 sch->qstats.overlimits++;
Thomas Grafbdc450a2005-11-05 21:14:28 +010084 if (red_use_harddrop(q) || !red_use_ecn(q) ||
85 !INET_ECN_set_ce(skb)) {
Thomas Graf6b31b282005-11-05 21:14:05 +010086 q->stats.forced_drop++;
87 goto congestion_drop;
88 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070089
Thomas Graf6b31b282005-11-05 21:14:05 +010090 q->stats.forced_mark++;
91 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -070092 }
93
Thomas Graf9e178ff2005-11-05 21:14:06 +010094 if (sch->qstats.backlog + skb->len <= q->limit)
95 return qdisc_enqueue_tail(skb, sch);
Linus Torvalds1da177e2005-04-16 15:20:36 -070096
Thomas Graf6b31b282005-11-05 21:14:05 +010097 q->stats.pdrop++;
Thomas Graf9e178ff2005-11-05 21:14:06 +010098 return qdisc_drop(skb, sch);
Linus Torvalds1da177e2005-04-16 15:20:36 -070099
Thomas Graf6b31b282005-11-05 21:14:05 +0100100congestion_drop:
Thomas Graf9e178ff2005-11-05 21:14:06 +0100101 qdisc_drop(skb, sch);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700102 return NET_XMIT_CN;
103}
104
Thomas Grafdba051f2005-11-05 21:14:08 +0100105static int red_requeue(struct sk_buff *skb, struct Qdisc* sch)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700106{
107 struct red_sched_data *q = qdisc_priv(sch);
108
Thomas Graf6b31b282005-11-05 21:14:05 +0100109 if (red_is_idling(&q->parms))
110 red_end_of_idle_period(&q->parms);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700111
Thomas Graf9e178ff2005-11-05 21:14:06 +0100112 return qdisc_requeue(skb, sch);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700113}
114
Thomas Grafdba051f2005-11-05 21:14:08 +0100115static struct sk_buff * red_dequeue(struct Qdisc* sch)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116{
117 struct sk_buff *skb;
118 struct red_sched_data *q = qdisc_priv(sch);
119
Thomas Graf9e178ff2005-11-05 21:14:06 +0100120 skb = qdisc_dequeue_head(sch);
Thomas Graf6b31b282005-11-05 21:14:05 +0100121
Thomas Graf6a1b63d2005-11-05 21:14:07 +0100122 if (skb == NULL && !red_is_idling(&q->parms))
Thomas Graf9e178ff2005-11-05 21:14:06 +0100123 red_start_of_idle_period(&q->parms);
124
125 return skb;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126}
127
128static unsigned int red_drop(struct Qdisc* sch)
129{
130 struct sk_buff *skb;
131 struct red_sched_data *q = qdisc_priv(sch);
132
Thomas Graf9e178ff2005-11-05 21:14:06 +0100133 skb = qdisc_dequeue_tail(sch);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134 if (skb) {
135 unsigned int len = skb->len;
Thomas Graf6b31b282005-11-05 21:14:05 +0100136 q->stats.other++;
Thomas Graf9e178ff2005-11-05 21:14:06 +0100137 qdisc_drop(skb, sch);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138 return len;
139 }
Thomas Graf6b31b282005-11-05 21:14:05 +0100140
Thomas Graf6a1b63d2005-11-05 21:14:07 +0100141 if (!red_is_idling(&q->parms))
142 red_start_of_idle_period(&q->parms);
143
Linus Torvalds1da177e2005-04-16 15:20:36 -0700144 return 0;
145}
146
147static void red_reset(struct Qdisc* sch)
148{
149 struct red_sched_data *q = qdisc_priv(sch);
150
Thomas Graf9e178ff2005-11-05 21:14:06 +0100151 qdisc_reset_queue(sch);
Thomas Graf6b31b282005-11-05 21:14:05 +0100152 red_restart(&q->parms);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700153}
154
155static int red_change(struct Qdisc *sch, struct rtattr *opt)
156{
157 struct red_sched_data *q = qdisc_priv(sch);
Thomas Grafdba051f2005-11-05 21:14:08 +0100158 struct rtattr *tb[TCA_RED_MAX];
Linus Torvalds1da177e2005-04-16 15:20:36 -0700159 struct tc_red_qopt *ctl;
160
Thomas Grafdba051f2005-11-05 21:14:08 +0100161 if (opt == NULL || rtattr_parse_nested(tb, TCA_RED_MAX, opt))
162 return -EINVAL;
163
164 if (tb[TCA_RED_PARMS-1] == NULL ||
Linus Torvalds1da177e2005-04-16 15:20:36 -0700165 RTA_PAYLOAD(tb[TCA_RED_PARMS-1]) < sizeof(*ctl) ||
Thomas Grafdba051f2005-11-05 21:14:08 +0100166 tb[TCA_RED_STAB-1] == NULL ||
167 RTA_PAYLOAD(tb[TCA_RED_STAB-1]) < RED_STAB_SIZE)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168 return -EINVAL;
169
170 ctl = RTA_DATA(tb[TCA_RED_PARMS-1]);
171
172 sch_tree_lock(sch);
173 q->flags = ctl->flags;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174 q->limit = ctl->limit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175
Thomas Graf6b31b282005-11-05 21:14:05 +0100176 red_set_parms(&q->parms, ctl->qth_min, ctl->qth_max, ctl->Wlog,
177 ctl->Plog, ctl->Scell_log,
178 RTA_DATA(tb[TCA_RED_STAB-1]));
179
David S. Millerb03efcf2005-07-08 14:57:23 -0700180 if (skb_queue_empty(&sch->q))
Thomas Graf6b31b282005-11-05 21:14:05 +0100181 red_end_of_idle_period(&q->parms);
Thomas Grafdba051f2005-11-05 21:14:08 +0100182
Linus Torvalds1da177e2005-04-16 15:20:36 -0700183 sch_tree_unlock(sch);
184 return 0;
185}
186
187static int red_init(struct Qdisc* sch, struct rtattr *opt)
188{
189 return red_change(sch, opt);
190}
191
192static int red_dump(struct Qdisc *sch, struct sk_buff *skb)
193{
194 struct red_sched_data *q = qdisc_priv(sch);
Thomas Grafdba051f2005-11-05 21:14:08 +0100195 struct rtattr *opts = NULL;
Thomas Graf6b31b282005-11-05 21:14:05 +0100196 struct tc_red_qopt opt = {
197 .limit = q->limit,
198 .flags = q->flags,
199 .qth_min = q->parms.qth_min >> q->parms.Wlog,
200 .qth_max = q->parms.qth_max >> q->parms.Wlog,
201 .Wlog = q->parms.Wlog,
202 .Plog = q->parms.Plog,
203 .Scell_log = q->parms.Scell_log,
204 };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700205
Thomas Grafdba051f2005-11-05 21:14:08 +0100206 opts = RTA_NEST(skb, TCA_OPTIONS);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700207 RTA_PUT(skb, TCA_RED_PARMS, sizeof(opt), &opt);
Thomas Grafdba051f2005-11-05 21:14:08 +0100208 return RTA_NEST_END(skb, opts);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700209
210rtattr_failure:
Thomas Grafdba051f2005-11-05 21:14:08 +0100211 return RTA_NEST_CANCEL(skb, opts);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212}
213
214static int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
215{
216 struct red_sched_data *q = qdisc_priv(sch);
Thomas Graf6b31b282005-11-05 21:14:05 +0100217 struct tc_red_xstats st = {
218 .early = q->stats.prob_drop + q->stats.forced_drop,
219 .pdrop = q->stats.pdrop,
220 .other = q->stats.other,
221 .marked = q->stats.prob_mark + q->stats.forced_mark,
222 };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223
Thomas Graf6b31b282005-11-05 21:14:05 +0100224 return gnet_stats_copy_app(d, &st, sizeof(st));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700225}
226
227static struct Qdisc_ops red_qdisc_ops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228 .id = "red",
229 .priv_size = sizeof(struct red_sched_data),
230 .enqueue = red_enqueue,
231 .dequeue = red_dequeue,
232 .requeue = red_requeue,
233 .drop = red_drop,
234 .init = red_init,
235 .reset = red_reset,
236 .change = red_change,
237 .dump = red_dump,
238 .dump_stats = red_dump_stats,
239 .owner = THIS_MODULE,
240};
241
242static int __init red_module_init(void)
243{
244 return register_qdisc(&red_qdisc_ops);
245}
Thomas Grafdba051f2005-11-05 21:14:08 +0100246
247static void __exit red_module_exit(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700248{
249 unregister_qdisc(&red_qdisc_ops);
250}
Thomas Grafdba051f2005-11-05 21:14:08 +0100251
Linus Torvalds1da177e2005-04-16 15:20:36 -0700252module_init(red_module_init)
253module_exit(red_module_exit)
Thomas Grafdba051f2005-11-05 21:14:08 +0100254
Linus Torvalds1da177e2005-04-16 15:20:36 -0700255MODULE_LICENSE("GPL");