2019-05-27 14:55:01 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* net/sched/sch_red.c Random Early Detection queue.
|
|
|
|
*
|
|
|
|
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
|
|
|
|
*
|
|
|
|
* Changes:
|
2005-11-06 04:14:08 +08:00
|
|
|
* J Hadi Salim 980914: computation fixes
|
2005-04-17 06:20:36 +08:00
|
|
|
* Alexey Makarenko <makar@phoenix.kharkov.ua> 990814: qave on idle link was calculated incorrectly.
|
2005-11-06 04:14:08 +08:00
|
|
|
* J Hadi Salim 980816: ECN support
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <net/pkt_sched.h>
|
2017-11-06 14:23:41 +08:00
|
|
|
#include <net/pkt_cls.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <net/inet_ecn.h>
|
2005-11-06 04:14:05 +08:00
|
|
|
#include <net/red.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
|
2005-11-06 04:14:05 +08:00
|
|
|
/* Parameters, settable by user:
|
2005-04-17 06:20:36 +08:00
|
|
|
-----------------------------
|
|
|
|
|
|
|
|
limit - bytes (must be > qth_max + burst)
|
|
|
|
|
|
|
|
Hard limit on queue length, should be chosen >qth_max
|
|
|
|
to allow packet bursts. This parameter does not
|
|
|
|
affect the algorithms behaviour and can be chosen
|
|
|
|
arbitrarily high (well, less than ram size)
|
|
|
|
Really, this limit will never be reached
|
|
|
|
if RED works correctly.
|
|
|
|
*/
|
|
|
|
|
2011-01-20 03:26:56 +08:00
|
|
|
struct red_sched_data {
|
2005-11-06 04:14:05 +08:00
|
|
|
u32 limit; /* HARD maximal queue length */
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
|
2005-11-06 04:14:05 +08:00
|
|
|
unsigned char flags;
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
/* Non-flags in tc_red_qopt.flags. */
|
|
|
|
unsigned char userbits;
|
|
|
|
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
struct timer_list adapt_timer;
|
2017-10-17 08:29:17 +08:00
|
|
|
struct Qdisc *sch;
|
2005-11-06 04:14:05 +08:00
|
|
|
struct red_parms parms;
|
2012-01-05 10:25:16 +08:00
|
|
|
struct red_vars vars;
|
2005-11-06 04:14:05 +08:00
|
|
|
struct red_stats stats;
|
2006-03-21 11:20:44 +08:00
|
|
|
struct Qdisc *qdisc;
|
2020-06-27 06:45:28 +08:00
|
|
|
struct tcf_qevent qe_early_drop;
|
|
|
|
struct tcf_qevent qe_mark;
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2020-05-01 04:13:05 +08:00
|
|
|
#define TC_RED_SUPPORTED_FLAGS (TC_RED_HISTORIC_FLAGS | TC_RED_NODROP)
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
|
2005-11-06 04:14:05 +08:00
|
|
|
static inline int red_use_ecn(struct red_sched_data *q)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-06 04:14:05 +08:00
|
|
|
return q->flags & TC_RED_ECN;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-11-06 04:14:28 +08:00
|
|
|
static inline int red_use_harddrop(struct red_sched_data *q)
|
|
|
|
{
|
|
|
|
return q->flags & TC_RED_HARDDROP;
|
|
|
|
}
|
|
|
|
|
2020-03-13 07:10:57 +08:00
|
|
|
static int red_use_nodrop(struct red_sched_data *q)
|
|
|
|
{
|
|
|
|
return q->flags & TC_RED_NODROP;
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:03:08 +08:00
|
|
|
static int red_enqueue(struct sk_buff *skb, struct Qdisc *sch,
|
2016-06-22 14:16:49 +08:00
|
|
|
struct sk_buff **to_free)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
2006-03-21 11:20:44 +08:00
|
|
|
struct Qdisc *child = q->qdisc;
|
|
|
|
int ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-01-05 10:25:16 +08:00
|
|
|
q->vars.qavg = red_calc_qavg(&q->parms,
|
|
|
|
&q->vars,
|
|
|
|
child->qstats.backlog);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-01-05 10:25:16 +08:00
|
|
|
if (red_is_idling(&q->vars))
|
|
|
|
red_end_of_idle_period(&q->vars);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-01-05 10:25:16 +08:00
|
|
|
switch (red_action(&q->parms, &q->vars, q->vars.qavg)) {
|
2011-01-20 03:26:56 +08:00
|
|
|
case RED_DONT_MARK:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RED_PROB_MARK:
|
2014-09-29 02:53:29 +08:00
|
|
|
qdisc_qstats_overlimit(sch);
|
2020-03-13 07:10:57 +08:00
|
|
|
if (!red_use_ecn(q)) {
|
2011-01-20 03:26:56 +08:00
|
|
|
q->stats.prob_drop++;
|
|
|
|
goto congestion_drop;
|
|
|
|
}
|
|
|
|
|
2020-03-13 07:10:57 +08:00
|
|
|
if (INET_ECN_set_ce(skb)) {
|
|
|
|
q->stats.prob_mark++;
|
2020-07-15 01:03:07 +08:00
|
|
|
skb = tcf_qevent_handle(&q->qe_mark, sch, skb, to_free, &ret);
|
2020-06-27 06:45:28 +08:00
|
|
|
if (!skb)
|
|
|
|
return NET_XMIT_CN | ret;
|
2020-03-13 07:10:57 +08:00
|
|
|
} else if (!red_use_nodrop(q)) {
|
|
|
|
q->stats.prob_drop++;
|
|
|
|
goto congestion_drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Non-ECT packet in ECN nodrop mode: queue it. */
|
2011-01-20 03:26:56 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RED_HARD_MARK:
|
2014-09-29 02:53:29 +08:00
|
|
|
qdisc_qstats_overlimit(sch);
|
2020-03-13 07:10:57 +08:00
|
|
|
if (red_use_harddrop(q) || !red_use_ecn(q)) {
|
|
|
|
q->stats.forced_drop++;
|
|
|
|
goto congestion_drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (INET_ECN_set_ce(skb)) {
|
|
|
|
q->stats.forced_mark++;
|
2020-07-15 01:03:07 +08:00
|
|
|
skb = tcf_qevent_handle(&q->qe_mark, sch, skb, to_free, &ret);
|
2020-06-27 06:45:28 +08:00
|
|
|
if (!skb)
|
|
|
|
return NET_XMIT_CN | ret;
|
2020-03-13 07:10:57 +08:00
|
|
|
} else if (!red_use_nodrop(q)) {
|
2011-01-20 03:26:56 +08:00
|
|
|
q->stats.forced_drop++;
|
|
|
|
goto congestion_drop;
|
|
|
|
}
|
|
|
|
|
2020-03-13 07:10:57 +08:00
|
|
|
/* Non-ECT packet in ECN nodrop mode: queue it. */
|
2011-01-20 03:26:56 +08:00
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:03:08 +08:00
|
|
|
ret = qdisc_enqueue(skb, child, to_free);
|
2006-03-21 11:20:44 +08:00
|
|
|
if (likely(ret == NET_XMIT_SUCCESS)) {
|
2016-06-02 07:15:18 +08:00
|
|
|
qdisc_qstats_backlog_inc(sch, skb);
|
2006-03-21 11:20:44 +08:00
|
|
|
sch->q.qlen++;
|
2008-08-05 13:31:03 +08:00
|
|
|
} else if (net_xmit_drop_count(ret)) {
|
2006-03-21 11:20:44 +08:00
|
|
|
q->stats.pdrop++;
|
2014-09-29 02:53:29 +08:00
|
|
|
qdisc_qstats_drop(sch);
|
2006-03-21 11:20:44 +08:00
|
|
|
}
|
|
|
|
return ret;
|
2005-11-06 04:14:05 +08:00
|
|
|
|
|
|
|
congestion_drop:
|
2020-07-15 01:03:07 +08:00
|
|
|
skb = tcf_qevent_handle(&q->qe_early_drop, sch, skb, to_free, &ret);
|
2020-06-27 06:45:28 +08:00
|
|
|
if (!skb)
|
|
|
|
return NET_XMIT_CN | ret;
|
|
|
|
|
2016-06-22 14:16:49 +08:00
|
|
|
qdisc_drop(skb, sch, to_free);
|
2005-04-17 06:20:36 +08:00
|
|
|
return NET_XMIT_CN;
|
|
|
|
}
|
|
|
|
|
2011-01-20 03:26:56 +08:00
|
|
|
static struct sk_buff *red_dequeue(struct Qdisc *sch)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
2006-03-21 11:20:44 +08:00
|
|
|
struct Qdisc *child = q->qdisc;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 11:20:44 +08:00
|
|
|
skb = child->dequeue(child);
|
2011-01-21 15:31:33 +08:00
|
|
|
if (skb) {
|
|
|
|
qdisc_bstats_update(sch, skb);
|
2016-06-02 07:15:18 +08:00
|
|
|
qdisc_qstats_backlog_dec(sch, skb);
|
2006-03-21 11:20:44 +08:00
|
|
|
sch->q.qlen--;
|
2011-01-21 15:31:33 +08:00
|
|
|
} else {
|
2012-01-05 10:25:16 +08:00
|
|
|
if (!red_is_idling(&q->vars))
|
|
|
|
red_start_of_idle_period(&q->vars);
|
2011-01-21 15:31:33 +08:00
|
|
|
}
|
2005-11-06 04:14:06 +08:00
|
|
|
return skb;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2011-01-20 03:26:56 +08:00
|
|
|
static struct sk_buff *red_peek(struct Qdisc *sch)
|
2008-10-31 15:45:55 +08:00
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
|
|
|
struct Qdisc *child = q->qdisc;
|
|
|
|
|
|
|
|
return child->ops->peek(child);
|
|
|
|
}
|
|
|
|
|
2011-01-20 03:26:56 +08:00
|
|
|
static void red_reset(struct Qdisc *sch)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
|
|
|
|
2006-03-21 11:20:44 +08:00
|
|
|
qdisc_reset(q->qdisc);
|
2016-06-02 07:15:18 +08:00
|
|
|
sch->qstats.backlog = 0;
|
2006-03-21 11:20:44 +08:00
|
|
|
sch->q.qlen = 0;
|
2012-01-05 10:25:16 +08:00
|
|
|
red_restart(&q->vars);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-11-06 14:23:41 +08:00
|
|
|
static int red_offload(struct Qdisc *sch, bool enable)
|
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
|
|
|
struct net_device *dev = qdisc_dev(sch);
|
|
|
|
struct tc_red_qopt_offload opt = {
|
|
|
|
.handle = sch->handle,
|
|
|
|
.parent = sch->parent,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (enable) {
|
|
|
|
opt.command = TC_RED_REPLACE;
|
|
|
|
opt.set.min = q->parms.qth_min >> q->parms.Wlog;
|
|
|
|
opt.set.max = q->parms.qth_max >> q->parms.Wlog;
|
|
|
|
opt.set.probability = q->parms.max_P;
|
2018-11-13 06:58:16 +08:00
|
|
|
opt.set.limit = q->limit;
|
2017-11-06 14:23:41 +08:00
|
|
|
opt.set.is_ecn = red_use_ecn(q);
|
2018-11-09 11:50:38 +08:00
|
|
|
opt.set.is_harddrop = red_use_harddrop(q);
|
2020-03-13 07:10:57 +08:00
|
|
|
opt.set.is_nodrop = red_use_nodrop(q);
|
2018-01-15 12:01:26 +08:00
|
|
|
opt.set.qstats = &sch->qstats;
|
2017-11-06 14:23:41 +08:00
|
|
|
} else {
|
|
|
|
opt.command = TC_RED_DESTROY;
|
|
|
|
}
|
|
|
|
|
2017-12-25 16:51:41 +08:00
|
|
|
return dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, &opt);
|
2017-11-06 14:23:41 +08:00
|
|
|
}
|
|
|
|
|
2006-03-21 11:20:44 +08:00
|
|
|
static void red_destroy(struct Qdisc *sch)
|
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
|
2020-06-27 06:45:28 +08:00
|
|
|
tcf_qevent_destroy(&q->qe_mark, sch);
|
|
|
|
tcf_qevent_destroy(&q->qe_early_drop, sch);
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
del_timer_sync(&q->adapt_timer);
|
2017-11-06 14:23:41 +08:00
|
|
|
red_offload(sch, false);
|
2018-09-25 00:22:50 +08:00
|
|
|
qdisc_put(q->qdisc);
|
2006-03-21 11:20:44 +08:00
|
|
|
}
|
|
|
|
|
2008-01-24 12:35:39 +08:00
|
|
|
static const struct nla_policy red_policy[TCA_RED_MAX + 1] = {
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
[TCA_RED_UNSPEC] = { .strict_start_type = TCA_RED_FLAGS },
|
2008-01-24 12:35:39 +08:00
|
|
|
[TCA_RED_PARMS] = { .len = sizeof(struct tc_red_qopt) },
|
|
|
|
[TCA_RED_STAB] = { .len = RED_STAB_SIZE },
|
2011-12-09 10:46:45 +08:00
|
|
|
[TCA_RED_MAX_P] = { .type = NLA_U32 },
|
2020-05-01 04:13:05 +08:00
|
|
|
[TCA_RED_FLAGS] = NLA_POLICY_BITFIELD32(TC_RED_SUPPORTED_FLAGS),
|
2020-06-27 06:45:28 +08:00
|
|
|
[TCA_RED_EARLY_DROP_BLOCK] = { .type = NLA_U32 },
|
|
|
|
[TCA_RED_MARK_BLOCK] = { .type = NLA_U32 },
|
2008-01-24 12:35:39 +08:00
|
|
|
};
|
|
|
|
|
2020-06-27 06:45:27 +08:00
|
|
|
static int __red_change(struct Qdisc *sch, struct nlattr **tb,
|
|
|
|
struct netlink_ext_ack *extack)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-11-08 09:33:39 +08:00
|
|
|
struct Qdisc *old_child = NULL, *child = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
struct nla_bitfield32 flags_bf;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct tc_red_qopt *ctl;
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
unsigned char userbits;
|
|
|
|
unsigned char flags;
|
2008-01-24 12:33:32 +08:00
|
|
|
int err;
|
2011-12-09 10:46:45 +08:00
|
|
|
u32 max_P;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-23 14:11:17 +08:00
|
|
|
if (tb[TCA_RED_PARMS] == NULL ||
|
2008-01-24 12:35:39 +08:00
|
|
|
tb[TCA_RED_STAB] == NULL)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2011-12-09 10:46:45 +08:00
|
|
|
max_P = tb[TCA_RED_MAX_P] ? nla_get_u32(tb[TCA_RED_MAX_P]) : 0;
|
|
|
|
|
2008-01-23 14:11:17 +08:00
|
|
|
ctl = nla_data(tb[TCA_RED_PARMS]);
|
2020-12-25 14:23:44 +08:00
|
|
|
if (!red_check_params(ctl->qth_min, ctl->qth_max, ctl->Wlog, ctl->Scell_log))
|
2017-12-04 19:31:11 +08:00
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
err = red_get_flags(ctl->flags, TC_RED_HISTORIC_FLAGS,
|
2020-05-01 04:13:05 +08:00
|
|
|
tb[TCA_RED_FLAGS], TC_RED_SUPPORTED_FLAGS,
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
&flags_bf, &userbits, extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2006-03-21 11:20:44 +08:00
|
|
|
if (ctl->limit > 0) {
|
2017-12-21 01:35:21 +08:00
|
|
|
child = fifo_create_dflt(sch, &bfifo_qdisc_ops, ctl->limit,
|
|
|
|
extack);
|
2008-07-06 14:40:21 +08:00
|
|
|
if (IS_ERR(child))
|
|
|
|
return PTR_ERR(child);
|
2006-03-21 11:20:44 +08:00
|
|
|
|
2018-05-18 20:51:44 +08:00
|
|
|
/* child is fifo, no need to check for noop_qdisc */
|
2017-03-08 23:03:32 +08:00
|
|
|
qdisc_hash_add(child, true);
|
2018-05-18 20:51:44 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
sch_tree_lock(sch);
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
|
|
|
|
flags = (q->flags & ~flags_bf.selector) | flags_bf.value;
|
|
|
|
err = red_validate_flags(flags, extack);
|
|
|
|
if (err)
|
|
|
|
goto unlock_out;
|
|
|
|
|
|
|
|
q->flags = flags;
|
|
|
|
q->userbits = userbits;
|
2005-04-17 06:20:36 +08:00
|
|
|
q->limit = ctl->limit;
|
2006-11-30 09:36:20 +08:00
|
|
|
if (child) {
|
2019-03-28 23:53:13 +08:00
|
|
|
qdisc_tree_flush_backlog(q->qdisc);
|
2018-11-08 09:33:39 +08:00
|
|
|
old_child = q->qdisc;
|
2008-11-20 20:11:36 +08:00
|
|
|
q->qdisc = child;
|
2006-11-30 09:36:20 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-01-05 10:25:16 +08:00
|
|
|
red_set_parms(&q->parms,
|
|
|
|
ctl->qth_min, ctl->qth_max, ctl->Wlog,
|
2011-12-09 10:46:45 +08:00
|
|
|
ctl->Plog, ctl->Scell_log,
|
|
|
|
nla_data(tb[TCA_RED_STAB]),
|
|
|
|
max_P);
|
2012-01-05 10:25:16 +08:00
|
|
|
red_set_vars(&q->vars);
|
2005-11-06 04:14:05 +08:00
|
|
|
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
del_timer(&q->adapt_timer);
|
|
|
|
if (ctl->flags & TC_RED_ADAPTATIVE)
|
|
|
|
mod_timer(&q->adapt_timer, jiffies + HZ/2);
|
|
|
|
|
sch_red: fix red_change
Le mercredi 30 novembre 2011 à 14:36 -0800, Stephen Hemminger a écrit :
> (Almost) nobody uses RED because they can't figure it out.
> According to Wikipedia, VJ says that:
> "there are not one, but two bugs in classic RED."
RED is useful for high throughput routers, I doubt many linux machines
act as such devices.
I was considering adding Adaptative RED (Sally Floyd, Ramakrishna
Gummadi, Scott Shender), August 2001
In this version, maxp is dynamic (from 1% to 50%), and user only have to
setup min_th (target average queue size)
(max_th and wq (burst in linux RED) are automatically setup)
By the way it seems we have a small bug in red_change()
if (skb_queue_empty(&sch->q))
red_end_of_idle_period(&q->parms);
First, if queue is empty, we should call
red_start_of_idle_period(&q->parms);
Second, since we dont use anymore sch->q, but q->qdisc, the test is
meaningless.
Oh well...
[PATCH] sch_red: fix red_change()
Now RED is classful, we must check q->qdisc->q.qlen, and if queue is empty,
we start an idle period, not end it.
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-01 19:06:34 +08:00
|
|
|
if (!q->qdisc->q.qlen)
|
2012-01-05 10:25:16 +08:00
|
|
|
red_start_of_idle_period(&q->vars);
|
2005-11-06 04:14:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
sch_tree_unlock(sch);
|
2018-11-08 09:33:39 +08:00
|
|
|
|
2017-11-06 14:23:41 +08:00
|
|
|
red_offload(sch, true);
|
2018-11-08 09:33:39 +08:00
|
|
|
|
|
|
|
if (old_child)
|
|
|
|
qdisc_put(old_child);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
|
|
|
|
unlock_out:
|
|
|
|
sch_tree_unlock(sch);
|
|
|
|
if (child)
|
|
|
|
qdisc_put(child);
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-10-17 08:29:17 +08:00
|
|
|
static inline void red_adaptative_timer(struct timer_list *t)
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
{
|
2017-10-17 08:29:17 +08:00
|
|
|
struct red_sched_data *q = from_timer(q, t, adapt_timer);
|
|
|
|
struct Qdisc *sch = q->sch;
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
spinlock_t *root_lock = qdisc_lock(qdisc_root_sleeping(sch));
|
|
|
|
|
|
|
|
spin_lock(root_lock);
|
2012-01-05 10:25:16 +08:00
|
|
|
red_adaptative_algo(&q->parms, &q->vars);
|
sch_red: Adaptative RED AQM
Adaptative RED AQM for linux, based on paper from Sally FLoyd,
Ramakrishna Gummadi, and Scott Shenker, August 2001 :
http://icir.org/floyd/papers/adaptiveRed.pdf
Goal of Adaptative RED is to make max_p a dynamic value between 1% and
50% to reach the target average queue : (max_th - min_th) / 2
Every 500 ms:
if (avg > target and max_p <= 0.5)
increase max_p : max_p += alpha;
else if (avg < target and max_p >= 0.01)
decrease max_p : max_p *= beta;
target :[min_th + 0.4*(min_th - max_th),
min_th + 0.6*(min_th - max_th)].
alpha : min(0.01, max_p / 4)
beta : 0.9
max_P is a Q0.32 fixed point number (unsigned, with 32 bits mantissa)
Changes against our RED implementation are :
max_p is no longer a negative power of two (1/(2^Plog)), but a Q0.32
fixed point number, to allow full range described in Adatative paper.
To deliver a random number, we now use a reciprocal divide (thats really
a multiply), but this operation is done once per marked/droped packet
when in RED_BETWEEN_TRESH window, so added cost (compared to previous
AND operation) is near zero.
dump operation gives current max_p value in a new TCA_RED_MAX_P
attribute.
Example on a 10Mbit link :
tc qdisc add dev $DEV parent 1:1 handle 10: est 1sec 8sec red \
limit 400000 min 30000 max 90000 avpkt 1000 \
burst 55 ecn adaptative bandwidth 10Mbit
# tc -s -d qdisc show dev eth3
...
qdisc red 10: parent 1:1 limit 400000b min 30000b max 90000b ecn
adaptative ewma 5 max_p=0.113335 Scell_log 15
Sent 50414282 bytes 34504 pkt (dropped 35, overlimits 1392 requeues 0)
rate 9749Kbit 831pps backlog 72056b 16p requeues 0
marked 1357 early 35 pdrop 0 other 0
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-12-08 14:06:03 +08:00
|
|
|
mod_timer(&q->adapt_timer, jiffies + HZ/2);
|
|
|
|
spin_unlock(root_lock);
|
|
|
|
}
|
|
|
|
|
2017-12-21 01:35:13 +08:00
|
|
|
static int red_init(struct Qdisc *sch, struct nlattr *opt,
|
|
|
|
struct netlink_ext_ack *extack)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-03-21 11:20:44 +08:00
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
2020-06-27 06:45:27 +08:00
|
|
|
struct nlattr *tb[TCA_RED_MAX + 1];
|
|
|
|
int err;
|
|
|
|
|
2020-07-26 04:17:07 +08:00
|
|
|
q->qdisc = &noop_qdisc;
|
|
|
|
q->sch = sch;
|
|
|
|
timer_setup(&q->adapt_timer, red_adaptative_timer, 0);
|
|
|
|
|
2020-06-27 06:45:27 +08:00
|
|
|
if (!opt)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
err = nla_parse_nested_deprecated(tb, TCA_RED_MAX, opt, red_policy,
|
|
|
|
extack);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2006-03-21 11:20:44 +08:00
|
|
|
|
2020-06-27 06:45:28 +08:00
|
|
|
err = __red_change(sch, tb, extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = tcf_qevent_init(&q->qe_early_drop, sch,
|
|
|
|
FLOW_BLOCK_BINDER_TYPE_RED_EARLY_DROP,
|
|
|
|
tb[TCA_RED_EARLY_DROP_BLOCK], extack);
|
|
|
|
if (err)
|
2020-08-28 01:40:41 +08:00
|
|
|
return err;
|
2020-06-27 06:45:28 +08:00
|
|
|
|
2020-08-28 01:40:41 +08:00
|
|
|
return tcf_qevent_init(&q->qe_mark, sch,
|
|
|
|
FLOW_BLOCK_BINDER_TYPE_RED_MARK,
|
|
|
|
tb[TCA_RED_MARK_BLOCK], extack);
|
2020-06-27 06:45:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int red_change(struct Qdisc *sch, struct nlattr *opt,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
2020-06-27 06:45:28 +08:00
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
2020-06-27 06:45:27 +08:00
|
|
|
struct nlattr *tb[TCA_RED_MAX + 1];
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!opt)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
err = nla_parse_nested_deprecated(tb, TCA_RED_MAX, opt, red_policy,
|
|
|
|
extack);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2020-06-27 06:45:28 +08:00
|
|
|
err = tcf_qevent_validate_change(&q->qe_early_drop,
|
|
|
|
tb[TCA_RED_EARLY_DROP_BLOCK], extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = tcf_qevent_validate_change(&q->qe_mark,
|
|
|
|
tb[TCA_RED_MARK_BLOCK], extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2020-06-27 06:45:27 +08:00
|
|
|
return __red_change(sch, tb, extack);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2018-11-08 09:33:35 +08:00
|
|
|
static int red_dump_offload_stats(struct Qdisc *sch)
|
2017-11-06 14:23:41 +08:00
|
|
|
{
|
|
|
|
struct tc_red_qopt_offload hw_stats = {
|
2017-11-11 07:09:53 +08:00
|
|
|
.command = TC_RED_STATS,
|
2017-11-06 14:23:41 +08:00
|
|
|
.handle = sch->handle,
|
|
|
|
.parent = sch->parent,
|
2017-11-11 07:09:53 +08:00
|
|
|
{
|
|
|
|
.stats.bstats = &sch->bstats,
|
|
|
|
.stats.qstats = &sch->qstats,
|
|
|
|
},
|
2017-11-06 14:23:41 +08:00
|
|
|
};
|
2017-12-25 16:51:41 +08:00
|
|
|
|
2018-11-08 09:33:34 +08:00
|
|
|
return qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_RED, &hw_stats);
|
2017-11-06 14:23:41 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int red_dump(struct Qdisc *sch, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
2008-01-23 14:11:17 +08:00
|
|
|
struct nlattr *opts = NULL;
|
2005-11-06 04:14:05 +08:00
|
|
|
struct tc_red_qopt opt = {
|
|
|
|
.limit = q->limit,
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
.flags = (q->flags & TC_RED_HISTORIC_FLAGS) |
|
|
|
|
q->userbits,
|
2005-11-06 04:14:05 +08:00
|
|
|
.qth_min = q->parms.qth_min >> q->parms.Wlog,
|
|
|
|
.qth_max = q->parms.qth_max >> q->parms.Wlog,
|
|
|
|
.Wlog = q->parms.Wlog,
|
|
|
|
.Plog = q->parms.Plog,
|
|
|
|
.Scell_log = q->parms.Scell_log,
|
|
|
|
};
|
2017-11-06 14:23:41 +08:00
|
|
|
int err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-11-08 09:33:35 +08:00
|
|
|
err = red_dump_offload_stats(sch);
|
2017-11-06 14:23:41 +08:00
|
|
|
if (err)
|
|
|
|
goto nla_put_failure;
|
|
|
|
|
2019-04-26 17:13:06 +08:00
|
|
|
opts = nla_nest_start_noflag(skb, TCA_OPTIONS);
|
2008-01-23 14:11:17 +08:00
|
|
|
if (opts == NULL)
|
|
|
|
goto nla_put_failure;
|
2012-03-29 17:11:39 +08:00
|
|
|
if (nla_put(skb, TCA_RED_PARMS, sizeof(opt), &opt) ||
|
net: sched: Allow extending set of supported RED flags
The qdiscs RED, GRED, SFQ and CHOKE use different subsets of the same pool
of global RED flags. These are passed in tc_red_qopt.flags. However none of
these qdiscs validate the flag field, and just copy it over wholesale to
internal structures, and later dump it back. (An exception is GRED, which
does validate for VQs -- however not for the main setup.)
A broken userspace can therefore configure a qdisc with arbitrary
unsupported flags, and later expect to see the flags on qdisc dump. The
current ABI therefore allows storage of several bits of custom data to
qdisc instances of the types mentioned above. How many bits, depends on
which flags are meaningful for the qdisc in question. E.g. SFQ recognizes
flags ECN and HARDDROP, and the rest is not interpreted.
If SFQ ever needs to support ADAPTATIVE, it needs another way of doing it,
and at the same time it needs to retain the possibility to store 6 bits of
uninterpreted data. Likewise RED, which adds a new flag later in this
patchset.
To that end, this patch adds a new function, red_get_flags(), to split the
passed flags of RED-like qdiscs to flags and user bits, and
red_validate_flags() to validate the resulting configuration. It further
adds a new attribute, TCA_RED_FLAGS, to pass arbitrary flags.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-03-13 07:10:56 +08:00
|
|
|
nla_put_u32(skb, TCA_RED_MAX_P, q->parms.max_P) ||
|
2020-03-28 23:37:42 +08:00
|
|
|
nla_put_bitfield32(skb, TCA_RED_FLAGS,
|
2020-06-27 06:45:28 +08:00
|
|
|
q->flags, TC_RED_SUPPORTED_FLAGS) ||
|
|
|
|
tcf_qevent_dump(skb, TCA_RED_MARK_BLOCK, &q->qe_mark) ||
|
|
|
|
tcf_qevent_dump(skb, TCA_RED_EARLY_DROP_BLOCK, &q->qe_early_drop))
|
2012-03-29 17:11:39 +08:00
|
|
|
goto nla_put_failure;
|
2008-01-23 14:11:17 +08:00
|
|
|
return nla_nest_end(skb, opts);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-23 14:11:17 +08:00
|
|
|
nla_put_failure:
|
2008-06-04 07:36:54 +08:00
|
|
|
nla_nest_cancel(skb, opts);
|
|
|
|
return -EMSGSIZE;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
|
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
2017-11-06 14:23:41 +08:00
|
|
|
struct net_device *dev = qdisc_dev(sch);
|
2018-01-10 21:59:59 +08:00
|
|
|
struct tc_red_xstats st = {0};
|
2005-11-06 04:14:05 +08:00
|
|
|
|
2017-12-14 21:54:30 +08:00
|
|
|
if (sch->flags & TCQ_F_OFFLOADED) {
|
2017-11-06 14:23:41 +08:00
|
|
|
struct tc_red_qopt_offload hw_stats_request = {
|
2017-11-11 07:09:53 +08:00
|
|
|
.command = TC_RED_XSTATS,
|
2017-11-06 14:23:41 +08:00
|
|
|
.handle = sch->handle,
|
|
|
|
.parent = sch->parent,
|
2017-11-11 07:09:53 +08:00
|
|
|
{
|
2018-01-10 21:59:59 +08:00
|
|
|
.xstats = &q->stats,
|
2017-11-11 07:09:53 +08:00
|
|
|
},
|
2017-11-06 14:23:41 +08:00
|
|
|
};
|
2018-01-10 21:59:59 +08:00
|
|
|
dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED,
|
|
|
|
&hw_stats_request);
|
2017-11-06 14:23:41 +08:00
|
|
|
}
|
2018-01-10 21:59:59 +08:00
|
|
|
st.early = q->stats.prob_drop + q->stats.forced_drop;
|
|
|
|
st.pdrop = q->stats.pdrop;
|
|
|
|
st.other = q->stats.other;
|
|
|
|
st.marked = q->stats.prob_mark + q->stats.forced_mark;
|
2017-11-06 14:23:41 +08:00
|
|
|
|
2005-11-06 04:14:05 +08:00
|
|
|
return gnet_stats_copy_app(d, &st, sizeof(st));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-03-21 11:20:44 +08:00
|
|
|
static int red_dump_class(struct Qdisc *sch, unsigned long cl,
|
|
|
|
struct sk_buff *skb, struct tcmsg *tcm)
|
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
|
|
|
|
|
|
|
tcm->tcm_handle |= TC_H_MIN(1);
|
|
|
|
tcm->tcm_info = q->qdisc->handle;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-13 06:58:13 +08:00
|
|
|
static void red_graft_offload(struct Qdisc *sch,
|
|
|
|
struct Qdisc *new, struct Qdisc *old,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct tc_red_qopt_offload graft_offload = {
|
|
|
|
.handle = sch->handle,
|
|
|
|
.parent = sch->parent,
|
|
|
|
.child_handle = new->handle,
|
|
|
|
.command = TC_RED_GRAFT,
|
|
|
|
};
|
|
|
|
|
|
|
|
qdisc_offload_graft_helper(qdisc_dev(sch), sch, new, old,
|
|
|
|
TC_SETUP_QDISC_RED, &graft_offload, extack);
|
|
|
|
}
|
|
|
|
|
2006-03-21 11:20:44 +08:00
|
|
|
static int red_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
|
2017-12-21 01:35:17 +08:00
|
|
|
struct Qdisc **old, struct netlink_ext_ack *extack)
|
2006-03-21 11:20:44 +08:00
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
|
|
|
|
|
|
|
if (new == NULL)
|
|
|
|
new = &noop_qdisc;
|
|
|
|
|
2016-02-26 06:55:00 +08:00
|
|
|
*old = qdisc_replace(sch, new, &q->qdisc);
|
2018-11-13 06:58:13 +08:00
|
|
|
|
|
|
|
red_graft_offload(sch, new, *old, extack);
|
2006-03-21 11:20:44 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct Qdisc *red_leaf(struct Qdisc *sch, unsigned long arg)
|
|
|
|
{
|
|
|
|
struct red_sched_data *q = qdisc_priv(sch);
|
|
|
|
return q->qdisc;
|
|
|
|
}
|
|
|
|
|
net_sched: remove tc class reference counting
For TC classes, their ->get() and ->put() are always paired, and the
reference counting is completely useless, because:
1) For class modification and dumping paths, we already hold RTNL lock,
so all of these ->get(),->change(),->put() are atomic.
2) For filter bindiing/unbinding, we use other reference counter than
this one, and they should have RTNL lock too.
3) For ->qlen_notify(), it is special because it is called on ->enqueue()
path, but we already hold qdisc tree lock there, and we hold this
tree lock when graft or delete the class too, so it should not be gone
or changed until we release the tree lock.
Therefore, this patch removes ->get() and ->put(), but:
1) Adds a new ->find() to find the pointer to a class by classid, no
refcnt.
2) Move the original class destroy upon the last refcnt into ->delete(),
right after releasing tree lock. This is fine because the class is
already removed from hash when holding the lock.
For those who also use ->put() as ->unbind(), just rename them to reflect
this change.
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-08-25 07:51:29 +08:00
|
|
|
static unsigned long red_find(struct Qdisc *sch, u32 classid)
|
2006-03-21 11:20:44 +08:00
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void red_walk(struct Qdisc *sch, struct qdisc_walker *walker)
|
|
|
|
{
|
|
|
|
if (!walker->stop) {
|
|
|
|
if (walker->count >= walker->skip)
|
|
|
|
if (walker->fn(sch, 1, walker) < 0) {
|
|
|
|
walker->stop = 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
walker->count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-11-14 17:44:41 +08:00
|
|
|
static const struct Qdisc_class_ops red_class_ops = {
|
2006-03-21 11:20:44 +08:00
|
|
|
.graft = red_graft,
|
|
|
|
.leaf = red_leaf,
|
net_sched: remove tc class reference counting
For TC classes, their ->get() and ->put() are always paired, and the
reference counting is completely useless, because:
1) For class modification and dumping paths, we already hold RTNL lock,
so all of these ->get(),->change(),->put() are atomic.
2) For filter bindiing/unbinding, we use other reference counter than
this one, and they should have RTNL lock too.
3) For ->qlen_notify(), it is special because it is called on ->enqueue()
path, but we already hold qdisc tree lock there, and we hold this
tree lock when graft or delete the class too, so it should not be gone
or changed until we release the tree lock.
Therefore, this patch removes ->get() and ->put(), but:
1) Adds a new ->find() to find the pointer to a class by classid, no
refcnt.
2) Move the original class destroy upon the last refcnt into ->delete(),
right after releasing tree lock. This is fine because the class is
already removed from hash when holding the lock.
For those who also use ->put() as ->unbind(), just rename them to reflect
this change.
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-08-25 07:51:29 +08:00
|
|
|
.find = red_find,
|
2006-03-21 11:20:44 +08:00
|
|
|
.walk = red_walk,
|
|
|
|
.dump = red_dump_class,
|
|
|
|
};
|
|
|
|
|
2007-11-14 17:44:41 +08:00
|
|
|
static struct Qdisc_ops red_qdisc_ops __read_mostly = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.id = "red",
|
|
|
|
.priv_size = sizeof(struct red_sched_data),
|
2006-03-21 11:20:44 +08:00
|
|
|
.cl_ops = &red_class_ops,
|
2005-04-17 06:20:36 +08:00
|
|
|
.enqueue = red_enqueue,
|
|
|
|
.dequeue = red_dequeue,
|
2008-10-31 15:45:55 +08:00
|
|
|
.peek = red_peek,
|
2005-04-17 06:20:36 +08:00
|
|
|
.init = red_init,
|
|
|
|
.reset = red_reset,
|
2006-03-21 11:20:44 +08:00
|
|
|
.destroy = red_destroy,
|
2005-04-17 06:20:36 +08:00
|
|
|
.change = red_change,
|
|
|
|
.dump = red_dump,
|
|
|
|
.dump_stats = red_dump_stats,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init red_module_init(void)
|
|
|
|
{
|
|
|
|
return register_qdisc(&red_qdisc_ops);
|
|
|
|
}
|
2005-11-06 04:14:08 +08:00
|
|
|
|
|
|
|
static void __exit red_module_exit(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unregister_qdisc(&red_qdisc_ops);
|
|
|
|
}
|
2005-11-06 04:14:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
module_init(red_module_init)
|
|
|
|
module_exit(red_module_exit)
|
2005-11-06 04:14:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
MODULE_LICENSE("GPL");
|