162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * net/sched/sch_red.c Random Early Detection queue. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Changes: 862306a36Sopenharmony_ci * J Hadi Salim 980914: computation fixes 962306a36Sopenharmony_ci * Alexey Makarenko <makar@phoenix.kharkov.ua> 990814: qave on idle link was calculated incorrectly. 1062306a36Sopenharmony_ci * J Hadi Salim 980816: ECN support 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/types.h> 1562306a36Sopenharmony_ci#include <linux/kernel.h> 1662306a36Sopenharmony_ci#include <linux/skbuff.h> 1762306a36Sopenharmony_ci#include <net/pkt_sched.h> 1862306a36Sopenharmony_ci#include <net/pkt_cls.h> 1962306a36Sopenharmony_ci#include <net/inet_ecn.h> 2062306a36Sopenharmony_ci#include <net/red.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* Parameters, settable by user: 2462306a36Sopenharmony_ci ----------------------------- 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci limit - bytes (must be > qth_max + burst) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci Hard limit on queue length, should be chosen >qth_max 2962306a36Sopenharmony_ci to allow packet bursts. This parameter does not 3062306a36Sopenharmony_ci affect the algorithms behaviour and can be chosen 3162306a36Sopenharmony_ci arbitrarily high (well, less than ram size) 3262306a36Sopenharmony_ci Really, this limit will never be reached 3362306a36Sopenharmony_ci if RED works correctly. 3462306a36Sopenharmony_ci */ 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct red_sched_data { 3762306a36Sopenharmony_ci u32 limit; /* HARD maximal queue length */ 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci unsigned char flags; 4062306a36Sopenharmony_ci /* Non-flags in tc_red_qopt.flags. */ 4162306a36Sopenharmony_ci unsigned char userbits; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci struct timer_list adapt_timer; 4462306a36Sopenharmony_ci struct Qdisc *sch; 4562306a36Sopenharmony_ci struct red_parms parms; 4662306a36Sopenharmony_ci struct red_vars vars; 4762306a36Sopenharmony_ci struct red_stats stats; 4862306a36Sopenharmony_ci struct Qdisc *qdisc; 4962306a36Sopenharmony_ci struct tcf_qevent qe_early_drop; 5062306a36Sopenharmony_ci struct tcf_qevent qe_mark; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#define TC_RED_SUPPORTED_FLAGS (TC_RED_HISTORIC_FLAGS | TC_RED_NODROP) 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic inline int red_use_ecn(struct red_sched_data *q) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci return q->flags & TC_RED_ECN; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic inline int red_use_harddrop(struct red_sched_data *q) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci return q->flags & TC_RED_HARDDROP; 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int red_use_nodrop(struct red_sched_data *q) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci return q->flags & TC_RED_NODROP; 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic int red_enqueue(struct sk_buff *skb, struct Qdisc *sch, 7162306a36Sopenharmony_ci struct sk_buff **to_free) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 7462306a36Sopenharmony_ci struct Qdisc *child = q->qdisc; 7562306a36Sopenharmony_ci unsigned int len; 7662306a36Sopenharmony_ci int ret; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci q->vars.qavg = red_calc_qavg(&q->parms, 7962306a36Sopenharmony_ci &q->vars, 8062306a36Sopenharmony_ci child->qstats.backlog); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (red_is_idling(&q->vars)) 8362306a36Sopenharmony_ci red_end_of_idle_period(&q->vars); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci switch (red_action(&q->parms, &q->vars, q->vars.qavg)) { 8662306a36Sopenharmony_ci case RED_DONT_MARK: 8762306a36Sopenharmony_ci break; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci case RED_PROB_MARK: 9062306a36Sopenharmony_ci qdisc_qstats_overlimit(sch); 9162306a36Sopenharmony_ci if (!red_use_ecn(q)) { 9262306a36Sopenharmony_ci q->stats.prob_drop++; 9362306a36Sopenharmony_ci goto congestion_drop; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (INET_ECN_set_ce(skb)) { 9762306a36Sopenharmony_ci q->stats.prob_mark++; 9862306a36Sopenharmony_ci skb = tcf_qevent_handle(&q->qe_mark, sch, skb, to_free, &ret); 9962306a36Sopenharmony_ci if (!skb) 10062306a36Sopenharmony_ci return NET_XMIT_CN | ret; 10162306a36Sopenharmony_ci } else if (!red_use_nodrop(q)) { 10262306a36Sopenharmony_ci q->stats.prob_drop++; 10362306a36Sopenharmony_ci goto congestion_drop; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* Non-ECT packet in ECN nodrop mode: queue it. */ 10762306a36Sopenharmony_ci break; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci case RED_HARD_MARK: 11062306a36Sopenharmony_ci qdisc_qstats_overlimit(sch); 11162306a36Sopenharmony_ci if (red_use_harddrop(q) || !red_use_ecn(q)) { 11262306a36Sopenharmony_ci q->stats.forced_drop++; 11362306a36Sopenharmony_ci goto congestion_drop; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (INET_ECN_set_ce(skb)) { 11762306a36Sopenharmony_ci q->stats.forced_mark++; 11862306a36Sopenharmony_ci skb = tcf_qevent_handle(&q->qe_mark, sch, skb, to_free, &ret); 11962306a36Sopenharmony_ci if (!skb) 12062306a36Sopenharmony_ci return NET_XMIT_CN | ret; 12162306a36Sopenharmony_ci } else if (!red_use_nodrop(q)) { 12262306a36Sopenharmony_ci q->stats.forced_drop++; 12362306a36Sopenharmony_ci goto congestion_drop; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Non-ECT packet in ECN nodrop mode: queue it. */ 12762306a36Sopenharmony_ci break; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci len = qdisc_pkt_len(skb); 13162306a36Sopenharmony_ci ret = qdisc_enqueue(skb, child, to_free); 13262306a36Sopenharmony_ci if (likely(ret == NET_XMIT_SUCCESS)) { 13362306a36Sopenharmony_ci sch->qstats.backlog += len; 13462306a36Sopenharmony_ci sch->q.qlen++; 13562306a36Sopenharmony_ci } else if (net_xmit_drop_count(ret)) { 13662306a36Sopenharmony_ci q->stats.pdrop++; 13762306a36Sopenharmony_ci qdisc_qstats_drop(sch); 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci return ret; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cicongestion_drop: 14262306a36Sopenharmony_ci skb = tcf_qevent_handle(&q->qe_early_drop, sch, skb, to_free, &ret); 14362306a36Sopenharmony_ci if (!skb) 14462306a36Sopenharmony_ci return NET_XMIT_CN | ret; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci qdisc_drop(skb, sch, to_free); 14762306a36Sopenharmony_ci return NET_XMIT_CN; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic struct sk_buff *red_dequeue(struct Qdisc *sch) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct sk_buff *skb; 15362306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 15462306a36Sopenharmony_ci struct Qdisc *child = q->qdisc; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci skb = child->dequeue(child); 15762306a36Sopenharmony_ci if (skb) { 15862306a36Sopenharmony_ci qdisc_bstats_update(sch, skb); 15962306a36Sopenharmony_ci qdisc_qstats_backlog_dec(sch, skb); 16062306a36Sopenharmony_ci sch->q.qlen--; 16162306a36Sopenharmony_ci } else { 16262306a36Sopenharmony_ci if (!red_is_idling(&q->vars)) 16362306a36Sopenharmony_ci red_start_of_idle_period(&q->vars); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci return skb; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic struct sk_buff *red_peek(struct Qdisc *sch) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 17162306a36Sopenharmony_ci struct Qdisc *child = q->qdisc; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return child->ops->peek(child); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic void red_reset(struct Qdisc *sch) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci qdisc_reset(q->qdisc); 18162306a36Sopenharmony_ci red_restart(&q->vars); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int red_offload(struct Qdisc *sch, bool enable) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 18762306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 18862306a36Sopenharmony_ci struct tc_red_qopt_offload opt = { 18962306a36Sopenharmony_ci .handle = sch->handle, 19062306a36Sopenharmony_ci .parent = sch->parent, 19162306a36Sopenharmony_ci }; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) 19462306a36Sopenharmony_ci return -EOPNOTSUPP; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (enable) { 19762306a36Sopenharmony_ci opt.command = TC_RED_REPLACE; 19862306a36Sopenharmony_ci opt.set.min = q->parms.qth_min >> q->parms.Wlog; 19962306a36Sopenharmony_ci opt.set.max = q->parms.qth_max >> q->parms.Wlog; 20062306a36Sopenharmony_ci opt.set.probability = q->parms.max_P; 20162306a36Sopenharmony_ci opt.set.limit = q->limit; 20262306a36Sopenharmony_ci opt.set.is_ecn = red_use_ecn(q); 20362306a36Sopenharmony_ci opt.set.is_harddrop = red_use_harddrop(q); 20462306a36Sopenharmony_ci opt.set.is_nodrop = red_use_nodrop(q); 20562306a36Sopenharmony_ci opt.set.qstats = &sch->qstats; 20662306a36Sopenharmony_ci } else { 20762306a36Sopenharmony_ci opt.command = TC_RED_DESTROY; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci return dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, &opt); 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic void red_destroy(struct Qdisc *sch) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci tcf_qevent_destroy(&q->qe_mark, sch); 21862306a36Sopenharmony_ci tcf_qevent_destroy(&q->qe_early_drop, sch); 21962306a36Sopenharmony_ci del_timer_sync(&q->adapt_timer); 22062306a36Sopenharmony_ci red_offload(sch, false); 22162306a36Sopenharmony_ci qdisc_put(q->qdisc); 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic const struct nla_policy red_policy[TCA_RED_MAX + 1] = { 22562306a36Sopenharmony_ci [TCA_RED_UNSPEC] = { .strict_start_type = TCA_RED_FLAGS }, 22662306a36Sopenharmony_ci [TCA_RED_PARMS] = { .len = sizeof(struct tc_red_qopt) }, 22762306a36Sopenharmony_ci [TCA_RED_STAB] = { .len = RED_STAB_SIZE }, 22862306a36Sopenharmony_ci [TCA_RED_MAX_P] = { .type = NLA_U32 }, 22962306a36Sopenharmony_ci [TCA_RED_FLAGS] = NLA_POLICY_BITFIELD32(TC_RED_SUPPORTED_FLAGS), 23062306a36Sopenharmony_ci [TCA_RED_EARLY_DROP_BLOCK] = { .type = NLA_U32 }, 23162306a36Sopenharmony_ci [TCA_RED_MARK_BLOCK] = { .type = NLA_U32 }, 23262306a36Sopenharmony_ci}; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic int __red_change(struct Qdisc *sch, struct nlattr **tb, 23562306a36Sopenharmony_ci struct netlink_ext_ack *extack) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct Qdisc *old_child = NULL, *child = NULL; 23862306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 23962306a36Sopenharmony_ci struct nla_bitfield32 flags_bf; 24062306a36Sopenharmony_ci struct tc_red_qopt *ctl; 24162306a36Sopenharmony_ci unsigned char userbits; 24262306a36Sopenharmony_ci unsigned char flags; 24362306a36Sopenharmony_ci int err; 24462306a36Sopenharmony_ci u32 max_P; 24562306a36Sopenharmony_ci u8 *stab; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (tb[TCA_RED_PARMS] == NULL || 24862306a36Sopenharmony_ci tb[TCA_RED_STAB] == NULL) 24962306a36Sopenharmony_ci return -EINVAL; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci max_P = tb[TCA_RED_MAX_P] ? nla_get_u32(tb[TCA_RED_MAX_P]) : 0; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci ctl = nla_data(tb[TCA_RED_PARMS]); 25462306a36Sopenharmony_ci stab = nla_data(tb[TCA_RED_STAB]); 25562306a36Sopenharmony_ci if (!red_check_params(ctl->qth_min, ctl->qth_max, ctl->Wlog, 25662306a36Sopenharmony_ci ctl->Scell_log, stab)) 25762306a36Sopenharmony_ci return -EINVAL; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci err = red_get_flags(ctl->flags, TC_RED_HISTORIC_FLAGS, 26062306a36Sopenharmony_ci tb[TCA_RED_FLAGS], TC_RED_SUPPORTED_FLAGS, 26162306a36Sopenharmony_ci &flags_bf, &userbits, extack); 26262306a36Sopenharmony_ci if (err) 26362306a36Sopenharmony_ci return err; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci if (ctl->limit > 0) { 26662306a36Sopenharmony_ci child = fifo_create_dflt(sch, &bfifo_qdisc_ops, ctl->limit, 26762306a36Sopenharmony_ci extack); 26862306a36Sopenharmony_ci if (IS_ERR(child)) 26962306a36Sopenharmony_ci return PTR_ERR(child); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* child is fifo, no need to check for noop_qdisc */ 27262306a36Sopenharmony_ci qdisc_hash_add(child, true); 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci sch_tree_lock(sch); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci flags = (q->flags & ~flags_bf.selector) | flags_bf.value; 27862306a36Sopenharmony_ci err = red_validate_flags(flags, extack); 27962306a36Sopenharmony_ci if (err) 28062306a36Sopenharmony_ci goto unlock_out; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci q->flags = flags; 28362306a36Sopenharmony_ci q->userbits = userbits; 28462306a36Sopenharmony_ci q->limit = ctl->limit; 28562306a36Sopenharmony_ci if (child) { 28662306a36Sopenharmony_ci qdisc_tree_flush_backlog(q->qdisc); 28762306a36Sopenharmony_ci old_child = q->qdisc; 28862306a36Sopenharmony_ci q->qdisc = child; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci red_set_parms(&q->parms, 29262306a36Sopenharmony_ci ctl->qth_min, ctl->qth_max, ctl->Wlog, 29362306a36Sopenharmony_ci ctl->Plog, ctl->Scell_log, 29462306a36Sopenharmony_ci stab, 29562306a36Sopenharmony_ci max_P); 29662306a36Sopenharmony_ci red_set_vars(&q->vars); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci del_timer(&q->adapt_timer); 29962306a36Sopenharmony_ci if (ctl->flags & TC_RED_ADAPTATIVE) 30062306a36Sopenharmony_ci mod_timer(&q->adapt_timer, jiffies + HZ/2); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci if (!q->qdisc->q.qlen) 30362306a36Sopenharmony_ci red_start_of_idle_period(&q->vars); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci sch_tree_unlock(sch); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci red_offload(sch, true); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (old_child) 31062306a36Sopenharmony_ci qdisc_put(old_child); 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ciunlock_out: 31462306a36Sopenharmony_ci sch_tree_unlock(sch); 31562306a36Sopenharmony_ci if (child) 31662306a36Sopenharmony_ci qdisc_put(child); 31762306a36Sopenharmony_ci return err; 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cistatic inline void red_adaptative_timer(struct timer_list *t) 32162306a36Sopenharmony_ci{ 32262306a36Sopenharmony_ci struct red_sched_data *q = from_timer(q, t, adapt_timer); 32362306a36Sopenharmony_ci struct Qdisc *sch = q->sch; 32462306a36Sopenharmony_ci spinlock_t *root_lock; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci rcu_read_lock(); 32762306a36Sopenharmony_ci root_lock = qdisc_lock(qdisc_root_sleeping(sch)); 32862306a36Sopenharmony_ci spin_lock(root_lock); 32962306a36Sopenharmony_ci red_adaptative_algo(&q->parms, &q->vars); 33062306a36Sopenharmony_ci mod_timer(&q->adapt_timer, jiffies + HZ/2); 33162306a36Sopenharmony_ci spin_unlock(root_lock); 33262306a36Sopenharmony_ci rcu_read_unlock(); 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic int red_init(struct Qdisc *sch, struct nlattr *opt, 33662306a36Sopenharmony_ci struct netlink_ext_ack *extack) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 33962306a36Sopenharmony_ci struct nlattr *tb[TCA_RED_MAX + 1]; 34062306a36Sopenharmony_ci int err; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci q->qdisc = &noop_qdisc; 34362306a36Sopenharmony_ci q->sch = sch; 34462306a36Sopenharmony_ci timer_setup(&q->adapt_timer, red_adaptative_timer, 0); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci if (!opt) 34762306a36Sopenharmony_ci return -EINVAL; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_RED_MAX, opt, red_policy, 35062306a36Sopenharmony_ci extack); 35162306a36Sopenharmony_ci if (err < 0) 35262306a36Sopenharmony_ci return err; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci err = __red_change(sch, tb, extack); 35562306a36Sopenharmony_ci if (err) 35662306a36Sopenharmony_ci return err; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci err = tcf_qevent_init(&q->qe_early_drop, sch, 35962306a36Sopenharmony_ci FLOW_BLOCK_BINDER_TYPE_RED_EARLY_DROP, 36062306a36Sopenharmony_ci tb[TCA_RED_EARLY_DROP_BLOCK], extack); 36162306a36Sopenharmony_ci if (err) 36262306a36Sopenharmony_ci return err; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci return tcf_qevent_init(&q->qe_mark, sch, 36562306a36Sopenharmony_ci FLOW_BLOCK_BINDER_TYPE_RED_MARK, 36662306a36Sopenharmony_ci tb[TCA_RED_MARK_BLOCK], extack); 36762306a36Sopenharmony_ci} 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_cistatic int red_change(struct Qdisc *sch, struct nlattr *opt, 37062306a36Sopenharmony_ci struct netlink_ext_ack *extack) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 37362306a36Sopenharmony_ci struct nlattr *tb[TCA_RED_MAX + 1]; 37462306a36Sopenharmony_ci int err; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_RED_MAX, opt, red_policy, 37762306a36Sopenharmony_ci extack); 37862306a36Sopenharmony_ci if (err < 0) 37962306a36Sopenharmony_ci return err; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci err = tcf_qevent_validate_change(&q->qe_early_drop, 38262306a36Sopenharmony_ci tb[TCA_RED_EARLY_DROP_BLOCK], extack); 38362306a36Sopenharmony_ci if (err) 38462306a36Sopenharmony_ci return err; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci err = tcf_qevent_validate_change(&q->qe_mark, 38762306a36Sopenharmony_ci tb[TCA_RED_MARK_BLOCK], extack); 38862306a36Sopenharmony_ci if (err) 38962306a36Sopenharmony_ci return err; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci return __red_change(sch, tb, extack); 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic int red_dump_offload_stats(struct Qdisc *sch) 39562306a36Sopenharmony_ci{ 39662306a36Sopenharmony_ci struct tc_red_qopt_offload hw_stats = { 39762306a36Sopenharmony_ci .command = TC_RED_STATS, 39862306a36Sopenharmony_ci .handle = sch->handle, 39962306a36Sopenharmony_ci .parent = sch->parent, 40062306a36Sopenharmony_ci { 40162306a36Sopenharmony_ci .stats.bstats = &sch->bstats, 40262306a36Sopenharmony_ci .stats.qstats = &sch->qstats, 40362306a36Sopenharmony_ci }, 40462306a36Sopenharmony_ci }; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci return qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_RED, &hw_stats); 40762306a36Sopenharmony_ci} 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_cistatic int red_dump(struct Qdisc *sch, struct sk_buff *skb) 41062306a36Sopenharmony_ci{ 41162306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 41262306a36Sopenharmony_ci struct nlattr *opts = NULL; 41362306a36Sopenharmony_ci struct tc_red_qopt opt = { 41462306a36Sopenharmony_ci .limit = q->limit, 41562306a36Sopenharmony_ci .flags = (q->flags & TC_RED_HISTORIC_FLAGS) | 41662306a36Sopenharmony_ci q->userbits, 41762306a36Sopenharmony_ci .qth_min = q->parms.qth_min >> q->parms.Wlog, 41862306a36Sopenharmony_ci .qth_max = q->parms.qth_max >> q->parms.Wlog, 41962306a36Sopenharmony_ci .Wlog = q->parms.Wlog, 42062306a36Sopenharmony_ci .Plog = q->parms.Plog, 42162306a36Sopenharmony_ci .Scell_log = q->parms.Scell_log, 42262306a36Sopenharmony_ci }; 42362306a36Sopenharmony_ci int err; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci err = red_dump_offload_stats(sch); 42662306a36Sopenharmony_ci if (err) 42762306a36Sopenharmony_ci goto nla_put_failure; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci opts = nla_nest_start_noflag(skb, TCA_OPTIONS); 43062306a36Sopenharmony_ci if (opts == NULL) 43162306a36Sopenharmony_ci goto nla_put_failure; 43262306a36Sopenharmony_ci if (nla_put(skb, TCA_RED_PARMS, sizeof(opt), &opt) || 43362306a36Sopenharmony_ci nla_put_u32(skb, TCA_RED_MAX_P, q->parms.max_P) || 43462306a36Sopenharmony_ci nla_put_bitfield32(skb, TCA_RED_FLAGS, 43562306a36Sopenharmony_ci q->flags, TC_RED_SUPPORTED_FLAGS) || 43662306a36Sopenharmony_ci tcf_qevent_dump(skb, TCA_RED_MARK_BLOCK, &q->qe_mark) || 43762306a36Sopenharmony_ci tcf_qevent_dump(skb, TCA_RED_EARLY_DROP_BLOCK, &q->qe_early_drop)) 43862306a36Sopenharmony_ci goto nla_put_failure; 43962306a36Sopenharmony_ci return nla_nest_end(skb, opts); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cinla_put_failure: 44262306a36Sopenharmony_ci nla_nest_cancel(skb, opts); 44362306a36Sopenharmony_ci return -EMSGSIZE; 44462306a36Sopenharmony_ci} 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_cistatic int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d) 44762306a36Sopenharmony_ci{ 44862306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 44962306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 45062306a36Sopenharmony_ci struct tc_red_xstats st = {0}; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci if (sch->flags & TCQ_F_OFFLOADED) { 45362306a36Sopenharmony_ci struct tc_red_qopt_offload hw_stats_request = { 45462306a36Sopenharmony_ci .command = TC_RED_XSTATS, 45562306a36Sopenharmony_ci .handle = sch->handle, 45662306a36Sopenharmony_ci .parent = sch->parent, 45762306a36Sopenharmony_ci { 45862306a36Sopenharmony_ci .xstats = &q->stats, 45962306a36Sopenharmony_ci }, 46062306a36Sopenharmony_ci }; 46162306a36Sopenharmony_ci dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, 46262306a36Sopenharmony_ci &hw_stats_request); 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci st.early = q->stats.prob_drop + q->stats.forced_drop; 46562306a36Sopenharmony_ci st.pdrop = q->stats.pdrop; 46662306a36Sopenharmony_ci st.marked = q->stats.prob_mark + q->stats.forced_mark; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci return gnet_stats_copy_app(d, &st, sizeof(st)); 46962306a36Sopenharmony_ci} 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_cistatic int red_dump_class(struct Qdisc *sch, unsigned long cl, 47262306a36Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci tcm->tcm_handle |= TC_H_MIN(1); 47762306a36Sopenharmony_ci tcm->tcm_info = q->qdisc->handle; 47862306a36Sopenharmony_ci return 0; 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cistatic void red_graft_offload(struct Qdisc *sch, 48262306a36Sopenharmony_ci struct Qdisc *new, struct Qdisc *old, 48362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci struct tc_red_qopt_offload graft_offload = { 48662306a36Sopenharmony_ci .handle = sch->handle, 48762306a36Sopenharmony_ci .parent = sch->parent, 48862306a36Sopenharmony_ci .child_handle = new->handle, 48962306a36Sopenharmony_ci .command = TC_RED_GRAFT, 49062306a36Sopenharmony_ci }; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci qdisc_offload_graft_helper(qdisc_dev(sch), sch, new, old, 49362306a36Sopenharmony_ci TC_SETUP_QDISC_RED, &graft_offload, extack); 49462306a36Sopenharmony_ci} 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_cistatic int red_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, 49762306a36Sopenharmony_ci struct Qdisc **old, struct netlink_ext_ack *extack) 49862306a36Sopenharmony_ci{ 49962306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (new == NULL) 50262306a36Sopenharmony_ci new = &noop_qdisc; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci *old = qdisc_replace(sch, new, &q->qdisc); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci red_graft_offload(sch, new, *old, extack); 50762306a36Sopenharmony_ci return 0; 50862306a36Sopenharmony_ci} 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_cistatic struct Qdisc *red_leaf(struct Qdisc *sch, unsigned long arg) 51162306a36Sopenharmony_ci{ 51262306a36Sopenharmony_ci struct red_sched_data *q = qdisc_priv(sch); 51362306a36Sopenharmony_ci return q->qdisc; 51462306a36Sopenharmony_ci} 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_cistatic unsigned long red_find(struct Qdisc *sch, u32 classid) 51762306a36Sopenharmony_ci{ 51862306a36Sopenharmony_ci return 1; 51962306a36Sopenharmony_ci} 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_cistatic void red_walk(struct Qdisc *sch, struct qdisc_walker *walker) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci if (!walker->stop) { 52462306a36Sopenharmony_ci tc_qdisc_stats_dump(sch, 1, walker); 52562306a36Sopenharmony_ci } 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic const struct Qdisc_class_ops red_class_ops = { 52962306a36Sopenharmony_ci .graft = red_graft, 53062306a36Sopenharmony_ci .leaf = red_leaf, 53162306a36Sopenharmony_ci .find = red_find, 53262306a36Sopenharmony_ci .walk = red_walk, 53362306a36Sopenharmony_ci .dump = red_dump_class, 53462306a36Sopenharmony_ci}; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cistatic struct Qdisc_ops red_qdisc_ops __read_mostly = { 53762306a36Sopenharmony_ci .id = "red", 53862306a36Sopenharmony_ci .priv_size = sizeof(struct red_sched_data), 53962306a36Sopenharmony_ci .cl_ops = &red_class_ops, 54062306a36Sopenharmony_ci .enqueue = red_enqueue, 54162306a36Sopenharmony_ci .dequeue = red_dequeue, 54262306a36Sopenharmony_ci .peek = red_peek, 54362306a36Sopenharmony_ci .init = red_init, 54462306a36Sopenharmony_ci .reset = red_reset, 54562306a36Sopenharmony_ci .destroy = red_destroy, 54662306a36Sopenharmony_ci .change = red_change, 54762306a36Sopenharmony_ci .dump = red_dump, 54862306a36Sopenharmony_ci .dump_stats = red_dump_stats, 54962306a36Sopenharmony_ci .owner = THIS_MODULE, 55062306a36Sopenharmony_ci}; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_cistatic int __init red_module_init(void) 55362306a36Sopenharmony_ci{ 55462306a36Sopenharmony_ci return register_qdisc(&red_qdisc_ops); 55562306a36Sopenharmony_ci} 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_cistatic void __exit red_module_exit(void) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci unregister_qdisc(&red_qdisc_ops); 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_cimodule_init(red_module_init) 56362306a36Sopenharmony_cimodule_exit(red_module_exit) 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 566