162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Fair Queue CoDel discipline 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2012,2015 Eric Dumazet <edumazet@google.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/types.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/jiffies.h> 1262306a36Sopenharmony_ci#include <linux/string.h> 1362306a36Sopenharmony_ci#include <linux/in.h> 1462306a36Sopenharmony_ci#include <linux/errno.h> 1562306a36Sopenharmony_ci#include <linux/init.h> 1662306a36Sopenharmony_ci#include <linux/skbuff.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/vmalloc.h> 1962306a36Sopenharmony_ci#include <net/netlink.h> 2062306a36Sopenharmony_ci#include <net/pkt_sched.h> 2162306a36Sopenharmony_ci#include <net/pkt_cls.h> 2262306a36Sopenharmony_ci#include <net/codel.h> 2362306a36Sopenharmony_ci#include <net/codel_impl.h> 2462306a36Sopenharmony_ci#include <net/codel_qdisc.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* Fair Queue CoDel. 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * Principles : 2962306a36Sopenharmony_ci * Packets are classified (internal classifier or external) on flows. 3062306a36Sopenharmony_ci * This is a Stochastic model (as we use a hash, several flows 3162306a36Sopenharmony_ci * might be hashed on same slot) 3262306a36Sopenharmony_ci * Each flow has a CoDel managed queue. 3362306a36Sopenharmony_ci * Flows are linked onto two (Round Robin) lists, 3462306a36Sopenharmony_ci * so that new flows have priority on old ones. 3562306a36Sopenharmony_ci * 3662306a36Sopenharmony_ci * For a given flow, packets are not reordered (CoDel uses a FIFO) 3762306a36Sopenharmony_ci * head drops only. 3862306a36Sopenharmony_ci * ECN capability is on by default. 3962306a36Sopenharmony_ci * Low memory footprint (64 bytes per flow) 4062306a36Sopenharmony_ci */ 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistruct fq_codel_flow { 4362306a36Sopenharmony_ci struct sk_buff *head; 4462306a36Sopenharmony_ci struct sk_buff *tail; 4562306a36Sopenharmony_ci struct list_head flowchain; 4662306a36Sopenharmony_ci int deficit; 4762306a36Sopenharmony_ci struct codel_vars cvars; 4862306a36Sopenharmony_ci}; /* please try to keep this structure <= 64 bytes */ 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistruct fq_codel_sched_data { 5162306a36Sopenharmony_ci struct tcf_proto __rcu *filter_list; /* optional external classifier */ 5262306a36Sopenharmony_ci struct tcf_block *block; 5362306a36Sopenharmony_ci struct fq_codel_flow *flows; /* Flows table [flows_cnt] */ 5462306a36Sopenharmony_ci u32 *backlogs; /* backlog table [flows_cnt] */ 5562306a36Sopenharmony_ci u32 flows_cnt; /* number of flows */ 5662306a36Sopenharmony_ci u32 quantum; /* psched_mtu(qdisc_dev(sch)); */ 5762306a36Sopenharmony_ci u32 drop_batch_size; 5862306a36Sopenharmony_ci u32 memory_limit; 5962306a36Sopenharmony_ci struct codel_params cparams; 6062306a36Sopenharmony_ci struct codel_stats cstats; 6162306a36Sopenharmony_ci u32 memory_usage; 6262306a36Sopenharmony_ci u32 drop_overmemory; 6362306a36Sopenharmony_ci u32 drop_overlimit; 6462306a36Sopenharmony_ci u32 new_flow_count; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci struct list_head new_flows; /* list of new flows */ 6762306a36Sopenharmony_ci struct list_head old_flows; /* list of old flows */ 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic unsigned int fq_codel_hash(const struct fq_codel_sched_data *q, 7162306a36Sopenharmony_ci struct sk_buff *skb) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci return reciprocal_scale(skb_get_hash(skb), q->flows_cnt); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic unsigned int fq_codel_classify(struct sk_buff *skb, struct Qdisc *sch, 7762306a36Sopenharmony_ci int *qerr) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 8062306a36Sopenharmony_ci struct tcf_proto *filter; 8162306a36Sopenharmony_ci struct tcf_result res; 8262306a36Sopenharmony_ci int result; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (TC_H_MAJ(skb->priority) == sch->handle && 8562306a36Sopenharmony_ci TC_H_MIN(skb->priority) > 0 && 8662306a36Sopenharmony_ci TC_H_MIN(skb->priority) <= q->flows_cnt) 8762306a36Sopenharmony_ci return TC_H_MIN(skb->priority); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci filter = rcu_dereference_bh(q->filter_list); 9062306a36Sopenharmony_ci if (!filter) 9162306a36Sopenharmony_ci return fq_codel_hash(q, skb) + 1; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; 9462306a36Sopenharmony_ci result = tcf_classify(skb, NULL, filter, &res, false); 9562306a36Sopenharmony_ci if (result >= 0) { 9662306a36Sopenharmony_ci#ifdef CONFIG_NET_CLS_ACT 9762306a36Sopenharmony_ci switch (result) { 9862306a36Sopenharmony_ci case TC_ACT_STOLEN: 9962306a36Sopenharmony_ci case TC_ACT_QUEUED: 10062306a36Sopenharmony_ci case TC_ACT_TRAP: 10162306a36Sopenharmony_ci *qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN; 10262306a36Sopenharmony_ci fallthrough; 10362306a36Sopenharmony_ci case TC_ACT_SHOT: 10462306a36Sopenharmony_ci return 0; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci#endif 10762306a36Sopenharmony_ci if (TC_H_MIN(res.classid) <= q->flows_cnt) 10862306a36Sopenharmony_ci return TC_H_MIN(res.classid); 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci return 0; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* helper functions : might be changed when/if skb use a standard list_head */ 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci/* remove one skb from head of slot queue */ 11662306a36Sopenharmony_cistatic inline struct sk_buff *dequeue_head(struct fq_codel_flow *flow) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci struct sk_buff *skb = flow->head; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci flow->head = skb->next; 12162306a36Sopenharmony_ci skb_mark_not_on_list(skb); 12262306a36Sopenharmony_ci return skb; 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci/* add skb to flow queue (tail add) */ 12662306a36Sopenharmony_cistatic inline void flow_queue_add(struct fq_codel_flow *flow, 12762306a36Sopenharmony_ci struct sk_buff *skb) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci if (flow->head == NULL) 13062306a36Sopenharmony_ci flow->head = skb; 13162306a36Sopenharmony_ci else 13262306a36Sopenharmony_ci flow->tail->next = skb; 13362306a36Sopenharmony_ci flow->tail = skb; 13462306a36Sopenharmony_ci skb->next = NULL; 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic unsigned int fq_codel_drop(struct Qdisc *sch, unsigned int max_packets, 13862306a36Sopenharmony_ci struct sk_buff **to_free) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 14162306a36Sopenharmony_ci struct sk_buff *skb; 14262306a36Sopenharmony_ci unsigned int maxbacklog = 0, idx = 0, i, len; 14362306a36Sopenharmony_ci struct fq_codel_flow *flow; 14462306a36Sopenharmony_ci unsigned int threshold; 14562306a36Sopenharmony_ci unsigned int mem = 0; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci /* Queue is full! Find the fat flow and drop packet(s) from it. 14862306a36Sopenharmony_ci * This might sound expensive, but with 1024 flows, we scan 14962306a36Sopenharmony_ci * 4KB of memory, and we dont need to handle a complex tree 15062306a36Sopenharmony_ci * in fast path (packet queue/enqueue) with many cache misses. 15162306a36Sopenharmony_ci * In stress mode, we'll try to drop 64 packets from the flow, 15262306a36Sopenharmony_ci * amortizing this linear lookup to one cache line per drop. 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_ci for (i = 0; i < q->flows_cnt; i++) { 15562306a36Sopenharmony_ci if (q->backlogs[i] > maxbacklog) { 15662306a36Sopenharmony_ci maxbacklog = q->backlogs[i]; 15762306a36Sopenharmony_ci idx = i; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* Our goal is to drop half of this fat flow backlog */ 16262306a36Sopenharmony_ci threshold = maxbacklog >> 1; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci flow = &q->flows[idx]; 16562306a36Sopenharmony_ci len = 0; 16662306a36Sopenharmony_ci i = 0; 16762306a36Sopenharmony_ci do { 16862306a36Sopenharmony_ci skb = dequeue_head(flow); 16962306a36Sopenharmony_ci len += qdisc_pkt_len(skb); 17062306a36Sopenharmony_ci mem += get_codel_cb(skb)->mem_usage; 17162306a36Sopenharmony_ci __qdisc_drop(skb, to_free); 17262306a36Sopenharmony_ci } while (++i < max_packets && len < threshold); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* Tell codel to increase its signal strength also */ 17562306a36Sopenharmony_ci flow->cvars.count += i; 17662306a36Sopenharmony_ci q->backlogs[idx] -= len; 17762306a36Sopenharmony_ci q->memory_usage -= mem; 17862306a36Sopenharmony_ci sch->qstats.drops += i; 17962306a36Sopenharmony_ci sch->qstats.backlog -= len; 18062306a36Sopenharmony_ci sch->q.qlen -= i; 18162306a36Sopenharmony_ci return idx; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int fq_codel_enqueue(struct sk_buff *skb, struct Qdisc *sch, 18562306a36Sopenharmony_ci struct sk_buff **to_free) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 18862306a36Sopenharmony_ci unsigned int idx, prev_backlog, prev_qlen; 18962306a36Sopenharmony_ci struct fq_codel_flow *flow; 19062306a36Sopenharmony_ci int ret; 19162306a36Sopenharmony_ci unsigned int pkt_len; 19262306a36Sopenharmony_ci bool memory_limited; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci idx = fq_codel_classify(skb, sch, &ret); 19562306a36Sopenharmony_ci if (idx == 0) { 19662306a36Sopenharmony_ci if (ret & __NET_XMIT_BYPASS) 19762306a36Sopenharmony_ci qdisc_qstats_drop(sch); 19862306a36Sopenharmony_ci __qdisc_drop(skb, to_free); 19962306a36Sopenharmony_ci return ret; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci idx--; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci codel_set_enqueue_time(skb); 20462306a36Sopenharmony_ci flow = &q->flows[idx]; 20562306a36Sopenharmony_ci flow_queue_add(flow, skb); 20662306a36Sopenharmony_ci q->backlogs[idx] += qdisc_pkt_len(skb); 20762306a36Sopenharmony_ci qdisc_qstats_backlog_inc(sch, skb); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (list_empty(&flow->flowchain)) { 21062306a36Sopenharmony_ci list_add_tail(&flow->flowchain, &q->new_flows); 21162306a36Sopenharmony_ci q->new_flow_count++; 21262306a36Sopenharmony_ci flow->deficit = q->quantum; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci get_codel_cb(skb)->mem_usage = skb->truesize; 21562306a36Sopenharmony_ci q->memory_usage += get_codel_cb(skb)->mem_usage; 21662306a36Sopenharmony_ci memory_limited = q->memory_usage > q->memory_limit; 21762306a36Sopenharmony_ci if (++sch->q.qlen <= sch->limit && !memory_limited) 21862306a36Sopenharmony_ci return NET_XMIT_SUCCESS; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci prev_backlog = sch->qstats.backlog; 22162306a36Sopenharmony_ci prev_qlen = sch->q.qlen; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* save this packet length as it might be dropped by fq_codel_drop() */ 22462306a36Sopenharmony_ci pkt_len = qdisc_pkt_len(skb); 22562306a36Sopenharmony_ci /* fq_codel_drop() is quite expensive, as it performs a linear search 22662306a36Sopenharmony_ci * in q->backlogs[] to find a fat flow. 22762306a36Sopenharmony_ci * So instead of dropping a single packet, drop half of its backlog 22862306a36Sopenharmony_ci * with a 64 packets limit to not add a too big cpu spike here. 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ci ret = fq_codel_drop(sch, q->drop_batch_size, to_free); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci prev_qlen -= sch->q.qlen; 23362306a36Sopenharmony_ci prev_backlog -= sch->qstats.backlog; 23462306a36Sopenharmony_ci q->drop_overlimit += prev_qlen; 23562306a36Sopenharmony_ci if (memory_limited) 23662306a36Sopenharmony_ci q->drop_overmemory += prev_qlen; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* As we dropped packet(s), better let upper stack know this. 23962306a36Sopenharmony_ci * If we dropped a packet for this flow, return NET_XMIT_CN, 24062306a36Sopenharmony_ci * but in this case, our parents wont increase their backlogs. 24162306a36Sopenharmony_ci */ 24262306a36Sopenharmony_ci if (ret == idx) { 24362306a36Sopenharmony_ci qdisc_tree_reduce_backlog(sch, prev_qlen - 1, 24462306a36Sopenharmony_ci prev_backlog - pkt_len); 24562306a36Sopenharmony_ci return NET_XMIT_CN; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci qdisc_tree_reduce_backlog(sch, prev_qlen, prev_backlog); 24862306a36Sopenharmony_ci return NET_XMIT_SUCCESS; 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci/* This is the specific function called from codel_dequeue() 25262306a36Sopenharmony_ci * to dequeue a packet from queue. Note: backlog is handled in 25362306a36Sopenharmony_ci * codel, we dont need to reduce it here. 25462306a36Sopenharmony_ci */ 25562306a36Sopenharmony_cistatic struct sk_buff *dequeue_func(struct codel_vars *vars, void *ctx) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci struct Qdisc *sch = ctx; 25862306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 25962306a36Sopenharmony_ci struct fq_codel_flow *flow; 26062306a36Sopenharmony_ci struct sk_buff *skb = NULL; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci flow = container_of(vars, struct fq_codel_flow, cvars); 26362306a36Sopenharmony_ci if (flow->head) { 26462306a36Sopenharmony_ci skb = dequeue_head(flow); 26562306a36Sopenharmony_ci q->backlogs[flow - q->flows] -= qdisc_pkt_len(skb); 26662306a36Sopenharmony_ci q->memory_usage -= get_codel_cb(skb)->mem_usage; 26762306a36Sopenharmony_ci sch->q.qlen--; 26862306a36Sopenharmony_ci sch->qstats.backlog -= qdisc_pkt_len(skb); 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci return skb; 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cistatic void drop_func(struct sk_buff *skb, void *ctx) 27462306a36Sopenharmony_ci{ 27562306a36Sopenharmony_ci struct Qdisc *sch = ctx; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci kfree_skb(skb); 27862306a36Sopenharmony_ci qdisc_qstats_drop(sch); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic struct sk_buff *fq_codel_dequeue(struct Qdisc *sch) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 28462306a36Sopenharmony_ci struct sk_buff *skb; 28562306a36Sopenharmony_ci struct fq_codel_flow *flow; 28662306a36Sopenharmony_ci struct list_head *head; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cibegin: 28962306a36Sopenharmony_ci head = &q->new_flows; 29062306a36Sopenharmony_ci if (list_empty(head)) { 29162306a36Sopenharmony_ci head = &q->old_flows; 29262306a36Sopenharmony_ci if (list_empty(head)) 29362306a36Sopenharmony_ci return NULL; 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci flow = list_first_entry(head, struct fq_codel_flow, flowchain); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if (flow->deficit <= 0) { 29862306a36Sopenharmony_ci flow->deficit += q->quantum; 29962306a36Sopenharmony_ci list_move_tail(&flow->flowchain, &q->old_flows); 30062306a36Sopenharmony_ci goto begin; 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams, 30462306a36Sopenharmony_ci &flow->cvars, &q->cstats, qdisc_pkt_len, 30562306a36Sopenharmony_ci codel_get_enqueue_time, drop_func, dequeue_func); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci if (!skb) { 30862306a36Sopenharmony_ci /* force a pass through old_flows to prevent starvation */ 30962306a36Sopenharmony_ci if ((head == &q->new_flows) && !list_empty(&q->old_flows)) 31062306a36Sopenharmony_ci list_move_tail(&flow->flowchain, &q->old_flows); 31162306a36Sopenharmony_ci else 31262306a36Sopenharmony_ci list_del_init(&flow->flowchain); 31362306a36Sopenharmony_ci goto begin; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci qdisc_bstats_update(sch, skb); 31662306a36Sopenharmony_ci flow->deficit -= qdisc_pkt_len(skb); 31762306a36Sopenharmony_ci /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0, 31862306a36Sopenharmony_ci * or HTB crashes. Defer it for next round. 31962306a36Sopenharmony_ci */ 32062306a36Sopenharmony_ci if (q->cstats.drop_count && sch->q.qlen) { 32162306a36Sopenharmony_ci qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, 32262306a36Sopenharmony_ci q->cstats.drop_len); 32362306a36Sopenharmony_ci q->cstats.drop_count = 0; 32462306a36Sopenharmony_ci q->cstats.drop_len = 0; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci return skb; 32762306a36Sopenharmony_ci} 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_cistatic void fq_codel_flow_purge(struct fq_codel_flow *flow) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci rtnl_kfree_skbs(flow->head, flow->tail); 33262306a36Sopenharmony_ci flow->head = NULL; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic void fq_codel_reset(struct Qdisc *sch) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 33862306a36Sopenharmony_ci int i; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci INIT_LIST_HEAD(&q->new_flows); 34162306a36Sopenharmony_ci INIT_LIST_HEAD(&q->old_flows); 34262306a36Sopenharmony_ci for (i = 0; i < q->flows_cnt; i++) { 34362306a36Sopenharmony_ci struct fq_codel_flow *flow = q->flows + i; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci fq_codel_flow_purge(flow); 34662306a36Sopenharmony_ci INIT_LIST_HEAD(&flow->flowchain); 34762306a36Sopenharmony_ci codel_vars_init(&flow->cvars); 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci memset(q->backlogs, 0, q->flows_cnt * sizeof(u32)); 35062306a36Sopenharmony_ci q->memory_usage = 0; 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic const struct nla_policy fq_codel_policy[TCA_FQ_CODEL_MAX + 1] = { 35462306a36Sopenharmony_ci [TCA_FQ_CODEL_TARGET] = { .type = NLA_U32 }, 35562306a36Sopenharmony_ci [TCA_FQ_CODEL_LIMIT] = { .type = NLA_U32 }, 35662306a36Sopenharmony_ci [TCA_FQ_CODEL_INTERVAL] = { .type = NLA_U32 }, 35762306a36Sopenharmony_ci [TCA_FQ_CODEL_ECN] = { .type = NLA_U32 }, 35862306a36Sopenharmony_ci [TCA_FQ_CODEL_FLOWS] = { .type = NLA_U32 }, 35962306a36Sopenharmony_ci [TCA_FQ_CODEL_QUANTUM] = { .type = NLA_U32 }, 36062306a36Sopenharmony_ci [TCA_FQ_CODEL_CE_THRESHOLD] = { .type = NLA_U32 }, 36162306a36Sopenharmony_ci [TCA_FQ_CODEL_DROP_BATCH_SIZE] = { .type = NLA_U32 }, 36262306a36Sopenharmony_ci [TCA_FQ_CODEL_MEMORY_LIMIT] = { .type = NLA_U32 }, 36362306a36Sopenharmony_ci [TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR] = { .type = NLA_U8 }, 36462306a36Sopenharmony_ci [TCA_FQ_CODEL_CE_THRESHOLD_MASK] = { .type = NLA_U8 }, 36562306a36Sopenharmony_ci}; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic int fq_codel_change(struct Qdisc *sch, struct nlattr *opt, 36862306a36Sopenharmony_ci struct netlink_ext_ack *extack) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 37162306a36Sopenharmony_ci struct nlattr *tb[TCA_FQ_CODEL_MAX + 1]; 37262306a36Sopenharmony_ci u32 quantum = 0; 37362306a36Sopenharmony_ci int err; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_FQ_CODEL_MAX, opt, 37662306a36Sopenharmony_ci fq_codel_policy, NULL); 37762306a36Sopenharmony_ci if (err < 0) 37862306a36Sopenharmony_ci return err; 37962306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_FLOWS]) { 38062306a36Sopenharmony_ci if (q->flows) 38162306a36Sopenharmony_ci return -EINVAL; 38262306a36Sopenharmony_ci q->flows_cnt = nla_get_u32(tb[TCA_FQ_CODEL_FLOWS]); 38362306a36Sopenharmony_ci if (!q->flows_cnt || 38462306a36Sopenharmony_ci q->flows_cnt > 65536) 38562306a36Sopenharmony_ci return -EINVAL; 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_QUANTUM]) { 38862306a36Sopenharmony_ci quantum = max(256U, nla_get_u32(tb[TCA_FQ_CODEL_QUANTUM])); 38962306a36Sopenharmony_ci if (quantum > FQ_CODEL_QUANTUM_MAX) { 39062306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid quantum"); 39162306a36Sopenharmony_ci return -EINVAL; 39262306a36Sopenharmony_ci } 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci sch_tree_lock(sch); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_TARGET]) { 39762306a36Sopenharmony_ci u64 target = nla_get_u32(tb[TCA_FQ_CODEL_TARGET]); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci q->cparams.target = (target * NSEC_PER_USEC) >> CODEL_SHIFT; 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_CE_THRESHOLD]) { 40362306a36Sopenharmony_ci u64 val = nla_get_u32(tb[TCA_FQ_CODEL_CE_THRESHOLD]); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci q->cparams.ce_threshold = (val * NSEC_PER_USEC) >> CODEL_SHIFT; 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR]) 40962306a36Sopenharmony_ci q->cparams.ce_threshold_selector = nla_get_u8(tb[TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR]); 41062306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_CE_THRESHOLD_MASK]) 41162306a36Sopenharmony_ci q->cparams.ce_threshold_mask = nla_get_u8(tb[TCA_FQ_CODEL_CE_THRESHOLD_MASK]); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_INTERVAL]) { 41462306a36Sopenharmony_ci u64 interval = nla_get_u32(tb[TCA_FQ_CODEL_INTERVAL]); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci q->cparams.interval = (interval * NSEC_PER_USEC) >> CODEL_SHIFT; 41762306a36Sopenharmony_ci } 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_LIMIT]) 42062306a36Sopenharmony_ci sch->limit = nla_get_u32(tb[TCA_FQ_CODEL_LIMIT]); 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_ECN]) 42362306a36Sopenharmony_ci q->cparams.ecn = !!nla_get_u32(tb[TCA_FQ_CODEL_ECN]); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci if (quantum) 42662306a36Sopenharmony_ci q->quantum = quantum; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_DROP_BATCH_SIZE]) 42962306a36Sopenharmony_ci q->drop_batch_size = max(1U, nla_get_u32(tb[TCA_FQ_CODEL_DROP_BATCH_SIZE])); 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci if (tb[TCA_FQ_CODEL_MEMORY_LIMIT]) 43262306a36Sopenharmony_ci q->memory_limit = min(1U << 31, nla_get_u32(tb[TCA_FQ_CODEL_MEMORY_LIMIT])); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci while (sch->q.qlen > sch->limit || 43562306a36Sopenharmony_ci q->memory_usage > q->memory_limit) { 43662306a36Sopenharmony_ci struct sk_buff *skb = fq_codel_dequeue(sch); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci q->cstats.drop_len += qdisc_pkt_len(skb); 43962306a36Sopenharmony_ci rtnl_kfree_skbs(skb, skb); 44062306a36Sopenharmony_ci q->cstats.drop_count++; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, q->cstats.drop_len); 44362306a36Sopenharmony_ci q->cstats.drop_count = 0; 44462306a36Sopenharmony_ci q->cstats.drop_len = 0; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci sch_tree_unlock(sch); 44762306a36Sopenharmony_ci return 0; 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_cistatic void fq_codel_destroy(struct Qdisc *sch) 45162306a36Sopenharmony_ci{ 45262306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci tcf_block_put(q->block); 45562306a36Sopenharmony_ci kvfree(q->backlogs); 45662306a36Sopenharmony_ci kvfree(q->flows); 45762306a36Sopenharmony_ci} 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_cistatic int fq_codel_init(struct Qdisc *sch, struct nlattr *opt, 46062306a36Sopenharmony_ci struct netlink_ext_ack *extack) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 46362306a36Sopenharmony_ci int i; 46462306a36Sopenharmony_ci int err; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci sch->limit = 10*1024; 46762306a36Sopenharmony_ci q->flows_cnt = 1024; 46862306a36Sopenharmony_ci q->memory_limit = 32 << 20; /* 32 MBytes */ 46962306a36Sopenharmony_ci q->drop_batch_size = 64; 47062306a36Sopenharmony_ci q->quantum = psched_mtu(qdisc_dev(sch)); 47162306a36Sopenharmony_ci INIT_LIST_HEAD(&q->new_flows); 47262306a36Sopenharmony_ci INIT_LIST_HEAD(&q->old_flows); 47362306a36Sopenharmony_ci codel_params_init(&q->cparams); 47462306a36Sopenharmony_ci codel_stats_init(&q->cstats); 47562306a36Sopenharmony_ci q->cparams.ecn = true; 47662306a36Sopenharmony_ci q->cparams.mtu = psched_mtu(qdisc_dev(sch)); 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci if (opt) { 47962306a36Sopenharmony_ci err = fq_codel_change(sch, opt, extack); 48062306a36Sopenharmony_ci if (err) 48162306a36Sopenharmony_ci goto init_failure; 48262306a36Sopenharmony_ci } 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci err = tcf_block_get(&q->block, &q->filter_list, sch, extack); 48562306a36Sopenharmony_ci if (err) 48662306a36Sopenharmony_ci goto init_failure; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci if (!q->flows) { 48962306a36Sopenharmony_ci q->flows = kvcalloc(q->flows_cnt, 49062306a36Sopenharmony_ci sizeof(struct fq_codel_flow), 49162306a36Sopenharmony_ci GFP_KERNEL); 49262306a36Sopenharmony_ci if (!q->flows) { 49362306a36Sopenharmony_ci err = -ENOMEM; 49462306a36Sopenharmony_ci goto init_failure; 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci q->backlogs = kvcalloc(q->flows_cnt, sizeof(u32), GFP_KERNEL); 49762306a36Sopenharmony_ci if (!q->backlogs) { 49862306a36Sopenharmony_ci err = -ENOMEM; 49962306a36Sopenharmony_ci goto alloc_failure; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci for (i = 0; i < q->flows_cnt; i++) { 50262306a36Sopenharmony_ci struct fq_codel_flow *flow = q->flows + i; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci INIT_LIST_HEAD(&flow->flowchain); 50562306a36Sopenharmony_ci codel_vars_init(&flow->cvars); 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci if (sch->limit >= 1) 50962306a36Sopenharmony_ci sch->flags |= TCQ_F_CAN_BYPASS; 51062306a36Sopenharmony_ci else 51162306a36Sopenharmony_ci sch->flags &= ~TCQ_F_CAN_BYPASS; 51262306a36Sopenharmony_ci return 0; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_cialloc_failure: 51562306a36Sopenharmony_ci kvfree(q->flows); 51662306a36Sopenharmony_ci q->flows = NULL; 51762306a36Sopenharmony_ciinit_failure: 51862306a36Sopenharmony_ci q->flows_cnt = 0; 51962306a36Sopenharmony_ci return err; 52062306a36Sopenharmony_ci} 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_cistatic int fq_codel_dump(struct Qdisc *sch, struct sk_buff *skb) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 52562306a36Sopenharmony_ci struct nlattr *opts; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci opts = nla_nest_start_noflag(skb, TCA_OPTIONS); 52862306a36Sopenharmony_ci if (opts == NULL) 52962306a36Sopenharmony_ci goto nla_put_failure; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci if (nla_put_u32(skb, TCA_FQ_CODEL_TARGET, 53262306a36Sopenharmony_ci codel_time_to_us(q->cparams.target)) || 53362306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_LIMIT, 53462306a36Sopenharmony_ci sch->limit) || 53562306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_INTERVAL, 53662306a36Sopenharmony_ci codel_time_to_us(q->cparams.interval)) || 53762306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_ECN, 53862306a36Sopenharmony_ci q->cparams.ecn) || 53962306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_QUANTUM, 54062306a36Sopenharmony_ci q->quantum) || 54162306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_DROP_BATCH_SIZE, 54262306a36Sopenharmony_ci q->drop_batch_size) || 54362306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_MEMORY_LIMIT, 54462306a36Sopenharmony_ci q->memory_limit) || 54562306a36Sopenharmony_ci nla_put_u32(skb, TCA_FQ_CODEL_FLOWS, 54662306a36Sopenharmony_ci q->flows_cnt)) 54762306a36Sopenharmony_ci goto nla_put_failure; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci if (q->cparams.ce_threshold != CODEL_DISABLED_THRESHOLD) { 55062306a36Sopenharmony_ci if (nla_put_u32(skb, TCA_FQ_CODEL_CE_THRESHOLD, 55162306a36Sopenharmony_ci codel_time_to_us(q->cparams.ce_threshold))) 55262306a36Sopenharmony_ci goto nla_put_failure; 55362306a36Sopenharmony_ci if (nla_put_u8(skb, TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR, q->cparams.ce_threshold_selector)) 55462306a36Sopenharmony_ci goto nla_put_failure; 55562306a36Sopenharmony_ci if (nla_put_u8(skb, TCA_FQ_CODEL_CE_THRESHOLD_MASK, q->cparams.ce_threshold_mask)) 55662306a36Sopenharmony_ci goto nla_put_failure; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci return nla_nest_end(skb, opts); 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_cinla_put_failure: 56262306a36Sopenharmony_ci return -1; 56362306a36Sopenharmony_ci} 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_cistatic int fq_codel_dump_stats(struct Qdisc *sch, struct gnet_dump *d) 56662306a36Sopenharmony_ci{ 56762306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 56862306a36Sopenharmony_ci struct tc_fq_codel_xstats st = { 56962306a36Sopenharmony_ci .type = TCA_FQ_CODEL_XSTATS_QDISC, 57062306a36Sopenharmony_ci }; 57162306a36Sopenharmony_ci struct list_head *pos; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci st.qdisc_stats.maxpacket = q->cstats.maxpacket; 57462306a36Sopenharmony_ci st.qdisc_stats.drop_overlimit = q->drop_overlimit; 57562306a36Sopenharmony_ci st.qdisc_stats.ecn_mark = q->cstats.ecn_mark; 57662306a36Sopenharmony_ci st.qdisc_stats.new_flow_count = q->new_flow_count; 57762306a36Sopenharmony_ci st.qdisc_stats.ce_mark = q->cstats.ce_mark; 57862306a36Sopenharmony_ci st.qdisc_stats.memory_usage = q->memory_usage; 57962306a36Sopenharmony_ci st.qdisc_stats.drop_overmemory = q->drop_overmemory; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci sch_tree_lock(sch); 58262306a36Sopenharmony_ci list_for_each(pos, &q->new_flows) 58362306a36Sopenharmony_ci st.qdisc_stats.new_flows_len++; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci list_for_each(pos, &q->old_flows) 58662306a36Sopenharmony_ci st.qdisc_stats.old_flows_len++; 58762306a36Sopenharmony_ci sch_tree_unlock(sch); 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci return gnet_stats_copy_app(d, &st, sizeof(st)); 59062306a36Sopenharmony_ci} 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_cistatic struct Qdisc *fq_codel_leaf(struct Qdisc *sch, unsigned long arg) 59362306a36Sopenharmony_ci{ 59462306a36Sopenharmony_ci return NULL; 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cistatic unsigned long fq_codel_find(struct Qdisc *sch, u32 classid) 59862306a36Sopenharmony_ci{ 59962306a36Sopenharmony_ci return 0; 60062306a36Sopenharmony_ci} 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_cistatic unsigned long fq_codel_bind(struct Qdisc *sch, unsigned long parent, 60362306a36Sopenharmony_ci u32 classid) 60462306a36Sopenharmony_ci{ 60562306a36Sopenharmony_ci return 0; 60662306a36Sopenharmony_ci} 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_cistatic void fq_codel_unbind(struct Qdisc *q, unsigned long cl) 60962306a36Sopenharmony_ci{ 61062306a36Sopenharmony_ci} 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_cistatic struct tcf_block *fq_codel_tcf_block(struct Qdisc *sch, unsigned long cl, 61362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 61462306a36Sopenharmony_ci{ 61562306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci if (cl) 61862306a36Sopenharmony_ci return NULL; 61962306a36Sopenharmony_ci return q->block; 62062306a36Sopenharmony_ci} 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_cistatic int fq_codel_dump_class(struct Qdisc *sch, unsigned long cl, 62362306a36Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 62462306a36Sopenharmony_ci{ 62562306a36Sopenharmony_ci tcm->tcm_handle |= TC_H_MIN(cl); 62662306a36Sopenharmony_ci return 0; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_cistatic int fq_codel_dump_class_stats(struct Qdisc *sch, unsigned long cl, 63062306a36Sopenharmony_ci struct gnet_dump *d) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 63362306a36Sopenharmony_ci u32 idx = cl - 1; 63462306a36Sopenharmony_ci struct gnet_stats_queue qs = { 0 }; 63562306a36Sopenharmony_ci struct tc_fq_codel_xstats xstats; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci if (idx < q->flows_cnt) { 63862306a36Sopenharmony_ci const struct fq_codel_flow *flow = &q->flows[idx]; 63962306a36Sopenharmony_ci const struct sk_buff *skb; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci memset(&xstats, 0, sizeof(xstats)); 64262306a36Sopenharmony_ci xstats.type = TCA_FQ_CODEL_XSTATS_CLASS; 64362306a36Sopenharmony_ci xstats.class_stats.deficit = flow->deficit; 64462306a36Sopenharmony_ci xstats.class_stats.ldelay = 64562306a36Sopenharmony_ci codel_time_to_us(flow->cvars.ldelay); 64662306a36Sopenharmony_ci xstats.class_stats.count = flow->cvars.count; 64762306a36Sopenharmony_ci xstats.class_stats.lastcount = flow->cvars.lastcount; 64862306a36Sopenharmony_ci xstats.class_stats.dropping = flow->cvars.dropping; 64962306a36Sopenharmony_ci if (flow->cvars.dropping) { 65062306a36Sopenharmony_ci codel_tdiff_t delta = flow->cvars.drop_next - 65162306a36Sopenharmony_ci codel_get_time(); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci xstats.class_stats.drop_next = (delta >= 0) ? 65462306a36Sopenharmony_ci codel_time_to_us(delta) : 65562306a36Sopenharmony_ci -codel_time_to_us(-delta); 65662306a36Sopenharmony_ci } 65762306a36Sopenharmony_ci if (flow->head) { 65862306a36Sopenharmony_ci sch_tree_lock(sch); 65962306a36Sopenharmony_ci skb = flow->head; 66062306a36Sopenharmony_ci while (skb) { 66162306a36Sopenharmony_ci qs.qlen++; 66262306a36Sopenharmony_ci skb = skb->next; 66362306a36Sopenharmony_ci } 66462306a36Sopenharmony_ci sch_tree_unlock(sch); 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci qs.backlog = q->backlogs[idx]; 66762306a36Sopenharmony_ci qs.drops = 0; 66862306a36Sopenharmony_ci } 66962306a36Sopenharmony_ci if (gnet_stats_copy_queue(d, NULL, &qs, qs.qlen) < 0) 67062306a36Sopenharmony_ci return -1; 67162306a36Sopenharmony_ci if (idx < q->flows_cnt) 67262306a36Sopenharmony_ci return gnet_stats_copy_app(d, &xstats, sizeof(xstats)); 67362306a36Sopenharmony_ci return 0; 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic void fq_codel_walk(struct Qdisc *sch, struct qdisc_walker *arg) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci struct fq_codel_sched_data *q = qdisc_priv(sch); 67962306a36Sopenharmony_ci unsigned int i; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci if (arg->stop) 68262306a36Sopenharmony_ci return; 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci for (i = 0; i < q->flows_cnt; i++) { 68562306a36Sopenharmony_ci if (list_empty(&q->flows[i].flowchain)) { 68662306a36Sopenharmony_ci arg->count++; 68762306a36Sopenharmony_ci continue; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci if (!tc_qdisc_stats_dump(sch, i + 1, arg)) 69062306a36Sopenharmony_ci break; 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci} 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_cistatic const struct Qdisc_class_ops fq_codel_class_ops = { 69562306a36Sopenharmony_ci .leaf = fq_codel_leaf, 69662306a36Sopenharmony_ci .find = fq_codel_find, 69762306a36Sopenharmony_ci .tcf_block = fq_codel_tcf_block, 69862306a36Sopenharmony_ci .bind_tcf = fq_codel_bind, 69962306a36Sopenharmony_ci .unbind_tcf = fq_codel_unbind, 70062306a36Sopenharmony_ci .dump = fq_codel_dump_class, 70162306a36Sopenharmony_ci .dump_stats = fq_codel_dump_class_stats, 70262306a36Sopenharmony_ci .walk = fq_codel_walk, 70362306a36Sopenharmony_ci}; 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_cistatic struct Qdisc_ops fq_codel_qdisc_ops __read_mostly = { 70662306a36Sopenharmony_ci .cl_ops = &fq_codel_class_ops, 70762306a36Sopenharmony_ci .id = "fq_codel", 70862306a36Sopenharmony_ci .priv_size = sizeof(struct fq_codel_sched_data), 70962306a36Sopenharmony_ci .enqueue = fq_codel_enqueue, 71062306a36Sopenharmony_ci .dequeue = fq_codel_dequeue, 71162306a36Sopenharmony_ci .peek = qdisc_peek_dequeued, 71262306a36Sopenharmony_ci .init = fq_codel_init, 71362306a36Sopenharmony_ci .reset = fq_codel_reset, 71462306a36Sopenharmony_ci .destroy = fq_codel_destroy, 71562306a36Sopenharmony_ci .change = fq_codel_change, 71662306a36Sopenharmony_ci .dump = fq_codel_dump, 71762306a36Sopenharmony_ci .dump_stats = fq_codel_dump_stats, 71862306a36Sopenharmony_ci .owner = THIS_MODULE, 71962306a36Sopenharmony_ci}; 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic int __init fq_codel_module_init(void) 72262306a36Sopenharmony_ci{ 72362306a36Sopenharmony_ci return register_qdisc(&fq_codel_qdisc_ops); 72462306a36Sopenharmony_ci} 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_cistatic void __exit fq_codel_module_exit(void) 72762306a36Sopenharmony_ci{ 72862306a36Sopenharmony_ci unregister_qdisc(&fq_codel_qdisc_ops); 72962306a36Sopenharmony_ci} 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_cimodule_init(fq_codel_module_init) 73262306a36Sopenharmony_cimodule_exit(fq_codel_module_exit) 73362306a36Sopenharmony_ciMODULE_AUTHOR("Eric Dumazet"); 73462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 73562306a36Sopenharmony_ciMODULE_DESCRIPTION("Fair Queue CoDel discipline"); 736