1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Fair Queue CoDel discipline
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (C) 2012,2015 Eric Dumazet <edumazet@google.com>
6*4882a593Smuzhiyun */
7*4882a593Smuzhiyun
8*4882a593Smuzhiyun #include <linux/module.h>
9*4882a593Smuzhiyun #include <linux/types.h>
10*4882a593Smuzhiyun #include <linux/kernel.h>
11*4882a593Smuzhiyun #include <linux/jiffies.h>
12*4882a593Smuzhiyun #include <linux/string.h>
13*4882a593Smuzhiyun #include <linux/in.h>
14*4882a593Smuzhiyun #include <linux/errno.h>
15*4882a593Smuzhiyun #include <linux/init.h>
16*4882a593Smuzhiyun #include <linux/skbuff.h>
17*4882a593Smuzhiyun #include <linux/slab.h>
18*4882a593Smuzhiyun #include <linux/vmalloc.h>
19*4882a593Smuzhiyun #include <net/netlink.h>
20*4882a593Smuzhiyun #include <net/pkt_sched.h>
21*4882a593Smuzhiyun #include <net/pkt_cls.h>
22*4882a593Smuzhiyun #include <net/codel.h>
23*4882a593Smuzhiyun #include <net/codel_impl.h>
24*4882a593Smuzhiyun #include <net/codel_qdisc.h>
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun /* Fair Queue CoDel.
27*4882a593Smuzhiyun *
28*4882a593Smuzhiyun * Principles :
29*4882a593Smuzhiyun * Packets are classified (internal classifier or external) on flows.
30*4882a593Smuzhiyun * This is a Stochastic model (as we use a hash, several flows
31*4882a593Smuzhiyun * might be hashed on same slot)
32*4882a593Smuzhiyun * Each flow has a CoDel managed queue.
33*4882a593Smuzhiyun * Flows are linked onto two (Round Robin) lists,
34*4882a593Smuzhiyun * so that new flows have priority on old ones.
35*4882a593Smuzhiyun *
36*4882a593Smuzhiyun * For a given flow, packets are not reordered (CoDel uses a FIFO)
37*4882a593Smuzhiyun * head drops only.
38*4882a593Smuzhiyun * ECN capability is on by default.
39*4882a593Smuzhiyun * Low memory footprint (64 bytes per flow)
40*4882a593Smuzhiyun */
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun struct fq_codel_flow {
43*4882a593Smuzhiyun struct sk_buff *head;
44*4882a593Smuzhiyun struct sk_buff *tail;
45*4882a593Smuzhiyun struct list_head flowchain;
46*4882a593Smuzhiyun int deficit;
47*4882a593Smuzhiyun struct codel_vars cvars;
48*4882a593Smuzhiyun }; /* please try to keep this structure <= 64 bytes */
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun struct fq_codel_sched_data {
51*4882a593Smuzhiyun struct tcf_proto __rcu *filter_list; /* optional external classifier */
52*4882a593Smuzhiyun struct tcf_block *block;
53*4882a593Smuzhiyun struct fq_codel_flow *flows; /* Flows table [flows_cnt] */
54*4882a593Smuzhiyun u32 *backlogs; /* backlog table [flows_cnt] */
55*4882a593Smuzhiyun u32 flows_cnt; /* number of flows */
56*4882a593Smuzhiyun u32 quantum; /* psched_mtu(qdisc_dev(sch)); */
57*4882a593Smuzhiyun u32 drop_batch_size;
58*4882a593Smuzhiyun u32 memory_limit;
59*4882a593Smuzhiyun struct codel_params cparams;
60*4882a593Smuzhiyun struct codel_stats cstats;
61*4882a593Smuzhiyun u32 memory_usage;
62*4882a593Smuzhiyun u32 drop_overmemory;
63*4882a593Smuzhiyun u32 drop_overlimit;
64*4882a593Smuzhiyun u32 new_flow_count;
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun struct list_head new_flows; /* list of new flows */
67*4882a593Smuzhiyun struct list_head old_flows; /* list of old flows */
68*4882a593Smuzhiyun };
69*4882a593Smuzhiyun
fq_codel_hash(const struct fq_codel_sched_data * q,struct sk_buff * skb)70*4882a593Smuzhiyun static unsigned int fq_codel_hash(const struct fq_codel_sched_data *q,
71*4882a593Smuzhiyun struct sk_buff *skb)
72*4882a593Smuzhiyun {
73*4882a593Smuzhiyun return reciprocal_scale(skb_get_hash(skb), q->flows_cnt);
74*4882a593Smuzhiyun }
75*4882a593Smuzhiyun
fq_codel_classify(struct sk_buff * skb,struct Qdisc * sch,int * qerr)76*4882a593Smuzhiyun static unsigned int fq_codel_classify(struct sk_buff *skb, struct Qdisc *sch,
77*4882a593Smuzhiyun int *qerr)
78*4882a593Smuzhiyun {
79*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
80*4882a593Smuzhiyun struct tcf_proto *filter;
81*4882a593Smuzhiyun struct tcf_result res;
82*4882a593Smuzhiyun int result;
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun if (TC_H_MAJ(skb->priority) == sch->handle &&
85*4882a593Smuzhiyun TC_H_MIN(skb->priority) > 0 &&
86*4882a593Smuzhiyun TC_H_MIN(skb->priority) <= q->flows_cnt)
87*4882a593Smuzhiyun return TC_H_MIN(skb->priority);
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun filter = rcu_dereference_bh(q->filter_list);
90*4882a593Smuzhiyun if (!filter)
91*4882a593Smuzhiyun return fq_codel_hash(q, skb) + 1;
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
94*4882a593Smuzhiyun result = tcf_classify(skb, filter, &res, false);
95*4882a593Smuzhiyun if (result >= 0) {
96*4882a593Smuzhiyun #ifdef CONFIG_NET_CLS_ACT
97*4882a593Smuzhiyun switch (result) {
98*4882a593Smuzhiyun case TC_ACT_STOLEN:
99*4882a593Smuzhiyun case TC_ACT_QUEUED:
100*4882a593Smuzhiyun case TC_ACT_TRAP:
101*4882a593Smuzhiyun *qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN;
102*4882a593Smuzhiyun fallthrough;
103*4882a593Smuzhiyun case TC_ACT_SHOT:
104*4882a593Smuzhiyun return 0;
105*4882a593Smuzhiyun }
106*4882a593Smuzhiyun #endif
107*4882a593Smuzhiyun if (TC_H_MIN(res.classid) <= q->flows_cnt)
108*4882a593Smuzhiyun return TC_H_MIN(res.classid);
109*4882a593Smuzhiyun }
110*4882a593Smuzhiyun return 0;
111*4882a593Smuzhiyun }
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun /* helper functions : might be changed when/if skb use a standard list_head */
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun /* remove one skb from head of slot queue */
dequeue_head(struct fq_codel_flow * flow)116*4882a593Smuzhiyun static inline struct sk_buff *dequeue_head(struct fq_codel_flow *flow)
117*4882a593Smuzhiyun {
118*4882a593Smuzhiyun struct sk_buff *skb = flow->head;
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun flow->head = skb->next;
121*4882a593Smuzhiyun skb_mark_not_on_list(skb);
122*4882a593Smuzhiyun return skb;
123*4882a593Smuzhiyun }
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun /* add skb to flow queue (tail add) */
flow_queue_add(struct fq_codel_flow * flow,struct sk_buff * skb)126*4882a593Smuzhiyun static inline void flow_queue_add(struct fq_codel_flow *flow,
127*4882a593Smuzhiyun struct sk_buff *skb)
128*4882a593Smuzhiyun {
129*4882a593Smuzhiyun if (flow->head == NULL)
130*4882a593Smuzhiyun flow->head = skb;
131*4882a593Smuzhiyun else
132*4882a593Smuzhiyun flow->tail->next = skb;
133*4882a593Smuzhiyun flow->tail = skb;
134*4882a593Smuzhiyun skb->next = NULL;
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun
fq_codel_drop(struct Qdisc * sch,unsigned int max_packets,struct sk_buff ** to_free)137*4882a593Smuzhiyun static unsigned int fq_codel_drop(struct Qdisc *sch, unsigned int max_packets,
138*4882a593Smuzhiyun struct sk_buff **to_free)
139*4882a593Smuzhiyun {
140*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
141*4882a593Smuzhiyun struct sk_buff *skb;
142*4882a593Smuzhiyun unsigned int maxbacklog = 0, idx = 0, i, len;
143*4882a593Smuzhiyun struct fq_codel_flow *flow;
144*4882a593Smuzhiyun unsigned int threshold;
145*4882a593Smuzhiyun unsigned int mem = 0;
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun /* Queue is full! Find the fat flow and drop packet(s) from it.
148*4882a593Smuzhiyun * This might sound expensive, but with 1024 flows, we scan
149*4882a593Smuzhiyun * 4KB of memory, and we dont need to handle a complex tree
150*4882a593Smuzhiyun * in fast path (packet queue/enqueue) with many cache misses.
151*4882a593Smuzhiyun * In stress mode, we'll try to drop 64 packets from the flow,
152*4882a593Smuzhiyun * amortizing this linear lookup to one cache line per drop.
153*4882a593Smuzhiyun */
154*4882a593Smuzhiyun for (i = 0; i < q->flows_cnt; i++) {
155*4882a593Smuzhiyun if (q->backlogs[i] > maxbacklog) {
156*4882a593Smuzhiyun maxbacklog = q->backlogs[i];
157*4882a593Smuzhiyun idx = i;
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun }
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun /* Our goal is to drop half of this fat flow backlog */
162*4882a593Smuzhiyun threshold = maxbacklog >> 1;
163*4882a593Smuzhiyun
164*4882a593Smuzhiyun flow = &q->flows[idx];
165*4882a593Smuzhiyun len = 0;
166*4882a593Smuzhiyun i = 0;
167*4882a593Smuzhiyun do {
168*4882a593Smuzhiyun skb = dequeue_head(flow);
169*4882a593Smuzhiyun len += qdisc_pkt_len(skb);
170*4882a593Smuzhiyun mem += get_codel_cb(skb)->mem_usage;
171*4882a593Smuzhiyun __qdisc_drop(skb, to_free);
172*4882a593Smuzhiyun } while (++i < max_packets && len < threshold);
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun /* Tell codel to increase its signal strength also */
175*4882a593Smuzhiyun flow->cvars.count += i;
176*4882a593Smuzhiyun q->backlogs[idx] -= len;
177*4882a593Smuzhiyun q->memory_usage -= mem;
178*4882a593Smuzhiyun sch->qstats.drops += i;
179*4882a593Smuzhiyun sch->qstats.backlog -= len;
180*4882a593Smuzhiyun sch->q.qlen -= i;
181*4882a593Smuzhiyun return idx;
182*4882a593Smuzhiyun }
183*4882a593Smuzhiyun
fq_codel_enqueue(struct sk_buff * skb,struct Qdisc * sch,struct sk_buff ** to_free)184*4882a593Smuzhiyun static int fq_codel_enqueue(struct sk_buff *skb, struct Qdisc *sch,
185*4882a593Smuzhiyun struct sk_buff **to_free)
186*4882a593Smuzhiyun {
187*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
188*4882a593Smuzhiyun unsigned int idx, prev_backlog, prev_qlen;
189*4882a593Smuzhiyun struct fq_codel_flow *flow;
190*4882a593Smuzhiyun int ret;
191*4882a593Smuzhiyun unsigned int pkt_len;
192*4882a593Smuzhiyun bool memory_limited;
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun idx = fq_codel_classify(skb, sch, &ret);
195*4882a593Smuzhiyun if (idx == 0) {
196*4882a593Smuzhiyun if (ret & __NET_XMIT_BYPASS)
197*4882a593Smuzhiyun qdisc_qstats_drop(sch);
198*4882a593Smuzhiyun __qdisc_drop(skb, to_free);
199*4882a593Smuzhiyun return ret;
200*4882a593Smuzhiyun }
201*4882a593Smuzhiyun idx--;
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun codel_set_enqueue_time(skb);
204*4882a593Smuzhiyun flow = &q->flows[idx];
205*4882a593Smuzhiyun flow_queue_add(flow, skb);
206*4882a593Smuzhiyun q->backlogs[idx] += qdisc_pkt_len(skb);
207*4882a593Smuzhiyun qdisc_qstats_backlog_inc(sch, skb);
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun if (list_empty(&flow->flowchain)) {
210*4882a593Smuzhiyun list_add_tail(&flow->flowchain, &q->new_flows);
211*4882a593Smuzhiyun q->new_flow_count++;
212*4882a593Smuzhiyun flow->deficit = q->quantum;
213*4882a593Smuzhiyun }
214*4882a593Smuzhiyun get_codel_cb(skb)->mem_usage = skb->truesize;
215*4882a593Smuzhiyun q->memory_usage += get_codel_cb(skb)->mem_usage;
216*4882a593Smuzhiyun memory_limited = q->memory_usage > q->memory_limit;
217*4882a593Smuzhiyun if (++sch->q.qlen <= sch->limit && !memory_limited)
218*4882a593Smuzhiyun return NET_XMIT_SUCCESS;
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun prev_backlog = sch->qstats.backlog;
221*4882a593Smuzhiyun prev_qlen = sch->q.qlen;
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun /* save this packet length as it might be dropped by fq_codel_drop() */
224*4882a593Smuzhiyun pkt_len = qdisc_pkt_len(skb);
225*4882a593Smuzhiyun /* fq_codel_drop() is quite expensive, as it performs a linear search
226*4882a593Smuzhiyun * in q->backlogs[] to find a fat flow.
227*4882a593Smuzhiyun * So instead of dropping a single packet, drop half of its backlog
228*4882a593Smuzhiyun * with a 64 packets limit to not add a too big cpu spike here.
229*4882a593Smuzhiyun */
230*4882a593Smuzhiyun ret = fq_codel_drop(sch, q->drop_batch_size, to_free);
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun prev_qlen -= sch->q.qlen;
233*4882a593Smuzhiyun prev_backlog -= sch->qstats.backlog;
234*4882a593Smuzhiyun q->drop_overlimit += prev_qlen;
235*4882a593Smuzhiyun if (memory_limited)
236*4882a593Smuzhiyun q->drop_overmemory += prev_qlen;
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun /* As we dropped packet(s), better let upper stack know this.
239*4882a593Smuzhiyun * If we dropped a packet for this flow, return NET_XMIT_CN,
240*4882a593Smuzhiyun * but in this case, our parents wont increase their backlogs.
241*4882a593Smuzhiyun */
242*4882a593Smuzhiyun if (ret == idx) {
243*4882a593Smuzhiyun qdisc_tree_reduce_backlog(sch, prev_qlen - 1,
244*4882a593Smuzhiyun prev_backlog - pkt_len);
245*4882a593Smuzhiyun return NET_XMIT_CN;
246*4882a593Smuzhiyun }
247*4882a593Smuzhiyun qdisc_tree_reduce_backlog(sch, prev_qlen, prev_backlog);
248*4882a593Smuzhiyun return NET_XMIT_SUCCESS;
249*4882a593Smuzhiyun }
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun /* This is the specific function called from codel_dequeue()
252*4882a593Smuzhiyun * to dequeue a packet from queue. Note: backlog is handled in
253*4882a593Smuzhiyun * codel, we dont need to reduce it here.
254*4882a593Smuzhiyun */
dequeue_func(struct codel_vars * vars,void * ctx)255*4882a593Smuzhiyun static struct sk_buff *dequeue_func(struct codel_vars *vars, void *ctx)
256*4882a593Smuzhiyun {
257*4882a593Smuzhiyun struct Qdisc *sch = ctx;
258*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
259*4882a593Smuzhiyun struct fq_codel_flow *flow;
260*4882a593Smuzhiyun struct sk_buff *skb = NULL;
261*4882a593Smuzhiyun
262*4882a593Smuzhiyun flow = container_of(vars, struct fq_codel_flow, cvars);
263*4882a593Smuzhiyun if (flow->head) {
264*4882a593Smuzhiyun skb = dequeue_head(flow);
265*4882a593Smuzhiyun q->backlogs[flow - q->flows] -= qdisc_pkt_len(skb);
266*4882a593Smuzhiyun q->memory_usage -= get_codel_cb(skb)->mem_usage;
267*4882a593Smuzhiyun sch->q.qlen--;
268*4882a593Smuzhiyun sch->qstats.backlog -= qdisc_pkt_len(skb);
269*4882a593Smuzhiyun }
270*4882a593Smuzhiyun return skb;
271*4882a593Smuzhiyun }
272*4882a593Smuzhiyun
drop_func(struct sk_buff * skb,void * ctx)273*4882a593Smuzhiyun static void drop_func(struct sk_buff *skb, void *ctx)
274*4882a593Smuzhiyun {
275*4882a593Smuzhiyun struct Qdisc *sch = ctx;
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun kfree_skb(skb);
278*4882a593Smuzhiyun qdisc_qstats_drop(sch);
279*4882a593Smuzhiyun }
280*4882a593Smuzhiyun
fq_codel_dequeue(struct Qdisc * sch)281*4882a593Smuzhiyun static struct sk_buff *fq_codel_dequeue(struct Qdisc *sch)
282*4882a593Smuzhiyun {
283*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
284*4882a593Smuzhiyun struct sk_buff *skb;
285*4882a593Smuzhiyun struct fq_codel_flow *flow;
286*4882a593Smuzhiyun struct list_head *head;
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun begin:
289*4882a593Smuzhiyun head = &q->new_flows;
290*4882a593Smuzhiyun if (list_empty(head)) {
291*4882a593Smuzhiyun head = &q->old_flows;
292*4882a593Smuzhiyun if (list_empty(head))
293*4882a593Smuzhiyun return NULL;
294*4882a593Smuzhiyun }
295*4882a593Smuzhiyun flow = list_first_entry(head, struct fq_codel_flow, flowchain);
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun if (flow->deficit <= 0) {
298*4882a593Smuzhiyun flow->deficit += q->quantum;
299*4882a593Smuzhiyun list_move_tail(&flow->flowchain, &q->old_flows);
300*4882a593Smuzhiyun goto begin;
301*4882a593Smuzhiyun }
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams,
304*4882a593Smuzhiyun &flow->cvars, &q->cstats, qdisc_pkt_len,
305*4882a593Smuzhiyun codel_get_enqueue_time, drop_func, dequeue_func);
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun if (!skb) {
308*4882a593Smuzhiyun /* force a pass through old_flows to prevent starvation */
309*4882a593Smuzhiyun if ((head == &q->new_flows) && !list_empty(&q->old_flows))
310*4882a593Smuzhiyun list_move_tail(&flow->flowchain, &q->old_flows);
311*4882a593Smuzhiyun else
312*4882a593Smuzhiyun list_del_init(&flow->flowchain);
313*4882a593Smuzhiyun goto begin;
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun qdisc_bstats_update(sch, skb);
316*4882a593Smuzhiyun flow->deficit -= qdisc_pkt_len(skb);
317*4882a593Smuzhiyun /* We cant call qdisc_tree_reduce_backlog() if our qlen is 0,
318*4882a593Smuzhiyun * or HTB crashes. Defer it for next round.
319*4882a593Smuzhiyun */
320*4882a593Smuzhiyun if (q->cstats.drop_count && sch->q.qlen) {
321*4882a593Smuzhiyun qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
322*4882a593Smuzhiyun q->cstats.drop_len);
323*4882a593Smuzhiyun q->cstats.drop_count = 0;
324*4882a593Smuzhiyun q->cstats.drop_len = 0;
325*4882a593Smuzhiyun }
326*4882a593Smuzhiyun return skb;
327*4882a593Smuzhiyun }
328*4882a593Smuzhiyun
fq_codel_flow_purge(struct fq_codel_flow * flow)329*4882a593Smuzhiyun static void fq_codel_flow_purge(struct fq_codel_flow *flow)
330*4882a593Smuzhiyun {
331*4882a593Smuzhiyun rtnl_kfree_skbs(flow->head, flow->tail);
332*4882a593Smuzhiyun flow->head = NULL;
333*4882a593Smuzhiyun }
334*4882a593Smuzhiyun
fq_codel_reset(struct Qdisc * sch)335*4882a593Smuzhiyun static void fq_codel_reset(struct Qdisc *sch)
336*4882a593Smuzhiyun {
337*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
338*4882a593Smuzhiyun int i;
339*4882a593Smuzhiyun
340*4882a593Smuzhiyun INIT_LIST_HEAD(&q->new_flows);
341*4882a593Smuzhiyun INIT_LIST_HEAD(&q->old_flows);
342*4882a593Smuzhiyun for (i = 0; i < q->flows_cnt; i++) {
343*4882a593Smuzhiyun struct fq_codel_flow *flow = q->flows + i;
344*4882a593Smuzhiyun
345*4882a593Smuzhiyun fq_codel_flow_purge(flow);
346*4882a593Smuzhiyun INIT_LIST_HEAD(&flow->flowchain);
347*4882a593Smuzhiyun codel_vars_init(&flow->cvars);
348*4882a593Smuzhiyun }
349*4882a593Smuzhiyun memset(q->backlogs, 0, q->flows_cnt * sizeof(u32));
350*4882a593Smuzhiyun q->memory_usage = 0;
351*4882a593Smuzhiyun }
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun static const struct nla_policy fq_codel_policy[TCA_FQ_CODEL_MAX + 1] = {
354*4882a593Smuzhiyun [TCA_FQ_CODEL_TARGET] = { .type = NLA_U32 },
355*4882a593Smuzhiyun [TCA_FQ_CODEL_LIMIT] = { .type = NLA_U32 },
356*4882a593Smuzhiyun [TCA_FQ_CODEL_INTERVAL] = { .type = NLA_U32 },
357*4882a593Smuzhiyun [TCA_FQ_CODEL_ECN] = { .type = NLA_U32 },
358*4882a593Smuzhiyun [TCA_FQ_CODEL_FLOWS] = { .type = NLA_U32 },
359*4882a593Smuzhiyun [TCA_FQ_CODEL_QUANTUM] = { .type = NLA_U32 },
360*4882a593Smuzhiyun [TCA_FQ_CODEL_CE_THRESHOLD] = { .type = NLA_U32 },
361*4882a593Smuzhiyun [TCA_FQ_CODEL_DROP_BATCH_SIZE] = { .type = NLA_U32 },
362*4882a593Smuzhiyun [TCA_FQ_CODEL_MEMORY_LIMIT] = { .type = NLA_U32 },
363*4882a593Smuzhiyun };
364*4882a593Smuzhiyun
fq_codel_change(struct Qdisc * sch,struct nlattr * opt,struct netlink_ext_ack * extack)365*4882a593Smuzhiyun static int fq_codel_change(struct Qdisc *sch, struct nlattr *opt,
366*4882a593Smuzhiyun struct netlink_ext_ack *extack)
367*4882a593Smuzhiyun {
368*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
369*4882a593Smuzhiyun struct nlattr *tb[TCA_FQ_CODEL_MAX + 1];
370*4882a593Smuzhiyun u32 quantum = 0;
371*4882a593Smuzhiyun int err;
372*4882a593Smuzhiyun
373*4882a593Smuzhiyun if (!opt)
374*4882a593Smuzhiyun return -EINVAL;
375*4882a593Smuzhiyun
376*4882a593Smuzhiyun err = nla_parse_nested_deprecated(tb, TCA_FQ_CODEL_MAX, opt,
377*4882a593Smuzhiyun fq_codel_policy, NULL);
378*4882a593Smuzhiyun if (err < 0)
379*4882a593Smuzhiyun return err;
380*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_FLOWS]) {
381*4882a593Smuzhiyun if (q->flows)
382*4882a593Smuzhiyun return -EINVAL;
383*4882a593Smuzhiyun q->flows_cnt = nla_get_u32(tb[TCA_FQ_CODEL_FLOWS]);
384*4882a593Smuzhiyun if (!q->flows_cnt ||
385*4882a593Smuzhiyun q->flows_cnt > 65536)
386*4882a593Smuzhiyun return -EINVAL;
387*4882a593Smuzhiyun }
388*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_QUANTUM]) {
389*4882a593Smuzhiyun quantum = max(256U, nla_get_u32(tb[TCA_FQ_CODEL_QUANTUM]));
390*4882a593Smuzhiyun if (quantum > FQ_CODEL_QUANTUM_MAX) {
391*4882a593Smuzhiyun NL_SET_ERR_MSG(extack, "Invalid quantum");
392*4882a593Smuzhiyun return -EINVAL;
393*4882a593Smuzhiyun }
394*4882a593Smuzhiyun }
395*4882a593Smuzhiyun sch_tree_lock(sch);
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_TARGET]) {
398*4882a593Smuzhiyun u64 target = nla_get_u32(tb[TCA_FQ_CODEL_TARGET]);
399*4882a593Smuzhiyun
400*4882a593Smuzhiyun q->cparams.target = (target * NSEC_PER_USEC) >> CODEL_SHIFT;
401*4882a593Smuzhiyun }
402*4882a593Smuzhiyun
403*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_CE_THRESHOLD]) {
404*4882a593Smuzhiyun u64 val = nla_get_u32(tb[TCA_FQ_CODEL_CE_THRESHOLD]);
405*4882a593Smuzhiyun
406*4882a593Smuzhiyun q->cparams.ce_threshold = (val * NSEC_PER_USEC) >> CODEL_SHIFT;
407*4882a593Smuzhiyun }
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_INTERVAL]) {
410*4882a593Smuzhiyun u64 interval = nla_get_u32(tb[TCA_FQ_CODEL_INTERVAL]);
411*4882a593Smuzhiyun
412*4882a593Smuzhiyun q->cparams.interval = (interval * NSEC_PER_USEC) >> CODEL_SHIFT;
413*4882a593Smuzhiyun }
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_LIMIT])
416*4882a593Smuzhiyun sch->limit = nla_get_u32(tb[TCA_FQ_CODEL_LIMIT]);
417*4882a593Smuzhiyun
418*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_ECN])
419*4882a593Smuzhiyun q->cparams.ecn = !!nla_get_u32(tb[TCA_FQ_CODEL_ECN]);
420*4882a593Smuzhiyun
421*4882a593Smuzhiyun if (quantum)
422*4882a593Smuzhiyun q->quantum = quantum;
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_DROP_BATCH_SIZE])
425*4882a593Smuzhiyun q->drop_batch_size = max(1U, nla_get_u32(tb[TCA_FQ_CODEL_DROP_BATCH_SIZE]));
426*4882a593Smuzhiyun
427*4882a593Smuzhiyun if (tb[TCA_FQ_CODEL_MEMORY_LIMIT])
428*4882a593Smuzhiyun q->memory_limit = min(1U << 31, nla_get_u32(tb[TCA_FQ_CODEL_MEMORY_LIMIT]));
429*4882a593Smuzhiyun
430*4882a593Smuzhiyun while (sch->q.qlen > sch->limit ||
431*4882a593Smuzhiyun q->memory_usage > q->memory_limit) {
432*4882a593Smuzhiyun struct sk_buff *skb = fq_codel_dequeue(sch);
433*4882a593Smuzhiyun
434*4882a593Smuzhiyun q->cstats.drop_len += qdisc_pkt_len(skb);
435*4882a593Smuzhiyun rtnl_kfree_skbs(skb, skb);
436*4882a593Smuzhiyun q->cstats.drop_count++;
437*4882a593Smuzhiyun }
438*4882a593Smuzhiyun qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, q->cstats.drop_len);
439*4882a593Smuzhiyun q->cstats.drop_count = 0;
440*4882a593Smuzhiyun q->cstats.drop_len = 0;
441*4882a593Smuzhiyun
442*4882a593Smuzhiyun sch_tree_unlock(sch);
443*4882a593Smuzhiyun return 0;
444*4882a593Smuzhiyun }
445*4882a593Smuzhiyun
fq_codel_destroy(struct Qdisc * sch)446*4882a593Smuzhiyun static void fq_codel_destroy(struct Qdisc *sch)
447*4882a593Smuzhiyun {
448*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun tcf_block_put(q->block);
451*4882a593Smuzhiyun kvfree(q->backlogs);
452*4882a593Smuzhiyun kvfree(q->flows);
453*4882a593Smuzhiyun }
454*4882a593Smuzhiyun
fq_codel_init(struct Qdisc * sch,struct nlattr * opt,struct netlink_ext_ack * extack)455*4882a593Smuzhiyun static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt,
456*4882a593Smuzhiyun struct netlink_ext_ack *extack)
457*4882a593Smuzhiyun {
458*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
459*4882a593Smuzhiyun int i;
460*4882a593Smuzhiyun int err;
461*4882a593Smuzhiyun
462*4882a593Smuzhiyun sch->limit = 10*1024;
463*4882a593Smuzhiyun q->flows_cnt = 1024;
464*4882a593Smuzhiyun q->memory_limit = 32 << 20; /* 32 MBytes */
465*4882a593Smuzhiyun q->drop_batch_size = 64;
466*4882a593Smuzhiyun q->quantum = psched_mtu(qdisc_dev(sch));
467*4882a593Smuzhiyun INIT_LIST_HEAD(&q->new_flows);
468*4882a593Smuzhiyun INIT_LIST_HEAD(&q->old_flows);
469*4882a593Smuzhiyun codel_params_init(&q->cparams);
470*4882a593Smuzhiyun codel_stats_init(&q->cstats);
471*4882a593Smuzhiyun q->cparams.ecn = true;
472*4882a593Smuzhiyun q->cparams.mtu = psched_mtu(qdisc_dev(sch));
473*4882a593Smuzhiyun
474*4882a593Smuzhiyun if (opt) {
475*4882a593Smuzhiyun err = fq_codel_change(sch, opt, extack);
476*4882a593Smuzhiyun if (err)
477*4882a593Smuzhiyun goto init_failure;
478*4882a593Smuzhiyun }
479*4882a593Smuzhiyun
480*4882a593Smuzhiyun err = tcf_block_get(&q->block, &q->filter_list, sch, extack);
481*4882a593Smuzhiyun if (err)
482*4882a593Smuzhiyun goto init_failure;
483*4882a593Smuzhiyun
484*4882a593Smuzhiyun if (!q->flows) {
485*4882a593Smuzhiyun q->flows = kvcalloc(q->flows_cnt,
486*4882a593Smuzhiyun sizeof(struct fq_codel_flow),
487*4882a593Smuzhiyun GFP_KERNEL);
488*4882a593Smuzhiyun if (!q->flows) {
489*4882a593Smuzhiyun err = -ENOMEM;
490*4882a593Smuzhiyun goto init_failure;
491*4882a593Smuzhiyun }
492*4882a593Smuzhiyun q->backlogs = kvcalloc(q->flows_cnt, sizeof(u32), GFP_KERNEL);
493*4882a593Smuzhiyun if (!q->backlogs) {
494*4882a593Smuzhiyun err = -ENOMEM;
495*4882a593Smuzhiyun goto alloc_failure;
496*4882a593Smuzhiyun }
497*4882a593Smuzhiyun for (i = 0; i < q->flows_cnt; i++) {
498*4882a593Smuzhiyun struct fq_codel_flow *flow = q->flows + i;
499*4882a593Smuzhiyun
500*4882a593Smuzhiyun INIT_LIST_HEAD(&flow->flowchain);
501*4882a593Smuzhiyun codel_vars_init(&flow->cvars);
502*4882a593Smuzhiyun }
503*4882a593Smuzhiyun }
504*4882a593Smuzhiyun if (sch->limit >= 1)
505*4882a593Smuzhiyun sch->flags |= TCQ_F_CAN_BYPASS;
506*4882a593Smuzhiyun else
507*4882a593Smuzhiyun sch->flags &= ~TCQ_F_CAN_BYPASS;
508*4882a593Smuzhiyun return 0;
509*4882a593Smuzhiyun
510*4882a593Smuzhiyun alloc_failure:
511*4882a593Smuzhiyun kvfree(q->flows);
512*4882a593Smuzhiyun q->flows = NULL;
513*4882a593Smuzhiyun init_failure:
514*4882a593Smuzhiyun q->flows_cnt = 0;
515*4882a593Smuzhiyun return err;
516*4882a593Smuzhiyun }
517*4882a593Smuzhiyun
fq_codel_dump(struct Qdisc * sch,struct sk_buff * skb)518*4882a593Smuzhiyun static int fq_codel_dump(struct Qdisc *sch, struct sk_buff *skb)
519*4882a593Smuzhiyun {
520*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
521*4882a593Smuzhiyun struct nlattr *opts;
522*4882a593Smuzhiyun
523*4882a593Smuzhiyun opts = nla_nest_start_noflag(skb, TCA_OPTIONS);
524*4882a593Smuzhiyun if (opts == NULL)
525*4882a593Smuzhiyun goto nla_put_failure;
526*4882a593Smuzhiyun
527*4882a593Smuzhiyun if (nla_put_u32(skb, TCA_FQ_CODEL_TARGET,
528*4882a593Smuzhiyun codel_time_to_us(q->cparams.target)) ||
529*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_LIMIT,
530*4882a593Smuzhiyun sch->limit) ||
531*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_INTERVAL,
532*4882a593Smuzhiyun codel_time_to_us(q->cparams.interval)) ||
533*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_ECN,
534*4882a593Smuzhiyun q->cparams.ecn) ||
535*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_QUANTUM,
536*4882a593Smuzhiyun q->quantum) ||
537*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_DROP_BATCH_SIZE,
538*4882a593Smuzhiyun q->drop_batch_size) ||
539*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_MEMORY_LIMIT,
540*4882a593Smuzhiyun q->memory_limit) ||
541*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_FLOWS,
542*4882a593Smuzhiyun q->flows_cnt))
543*4882a593Smuzhiyun goto nla_put_failure;
544*4882a593Smuzhiyun
545*4882a593Smuzhiyun if (q->cparams.ce_threshold != CODEL_DISABLED_THRESHOLD &&
546*4882a593Smuzhiyun nla_put_u32(skb, TCA_FQ_CODEL_CE_THRESHOLD,
547*4882a593Smuzhiyun codel_time_to_us(q->cparams.ce_threshold)))
548*4882a593Smuzhiyun goto nla_put_failure;
549*4882a593Smuzhiyun
550*4882a593Smuzhiyun return nla_nest_end(skb, opts);
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun nla_put_failure:
553*4882a593Smuzhiyun return -1;
554*4882a593Smuzhiyun }
555*4882a593Smuzhiyun
fq_codel_dump_stats(struct Qdisc * sch,struct gnet_dump * d)556*4882a593Smuzhiyun static int fq_codel_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
557*4882a593Smuzhiyun {
558*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
559*4882a593Smuzhiyun struct tc_fq_codel_xstats st = {
560*4882a593Smuzhiyun .type = TCA_FQ_CODEL_XSTATS_QDISC,
561*4882a593Smuzhiyun };
562*4882a593Smuzhiyun struct list_head *pos;
563*4882a593Smuzhiyun
564*4882a593Smuzhiyun st.qdisc_stats.maxpacket = q->cstats.maxpacket;
565*4882a593Smuzhiyun st.qdisc_stats.drop_overlimit = q->drop_overlimit;
566*4882a593Smuzhiyun st.qdisc_stats.ecn_mark = q->cstats.ecn_mark;
567*4882a593Smuzhiyun st.qdisc_stats.new_flow_count = q->new_flow_count;
568*4882a593Smuzhiyun st.qdisc_stats.ce_mark = q->cstats.ce_mark;
569*4882a593Smuzhiyun st.qdisc_stats.memory_usage = q->memory_usage;
570*4882a593Smuzhiyun st.qdisc_stats.drop_overmemory = q->drop_overmemory;
571*4882a593Smuzhiyun
572*4882a593Smuzhiyun sch_tree_lock(sch);
573*4882a593Smuzhiyun list_for_each(pos, &q->new_flows)
574*4882a593Smuzhiyun st.qdisc_stats.new_flows_len++;
575*4882a593Smuzhiyun
576*4882a593Smuzhiyun list_for_each(pos, &q->old_flows)
577*4882a593Smuzhiyun st.qdisc_stats.old_flows_len++;
578*4882a593Smuzhiyun sch_tree_unlock(sch);
579*4882a593Smuzhiyun
580*4882a593Smuzhiyun return gnet_stats_copy_app(d, &st, sizeof(st));
581*4882a593Smuzhiyun }
582*4882a593Smuzhiyun
fq_codel_leaf(struct Qdisc * sch,unsigned long arg)583*4882a593Smuzhiyun static struct Qdisc *fq_codel_leaf(struct Qdisc *sch, unsigned long arg)
584*4882a593Smuzhiyun {
585*4882a593Smuzhiyun return NULL;
586*4882a593Smuzhiyun }
587*4882a593Smuzhiyun
fq_codel_find(struct Qdisc * sch,u32 classid)588*4882a593Smuzhiyun static unsigned long fq_codel_find(struct Qdisc *sch, u32 classid)
589*4882a593Smuzhiyun {
590*4882a593Smuzhiyun return 0;
591*4882a593Smuzhiyun }
592*4882a593Smuzhiyun
fq_codel_bind(struct Qdisc * sch,unsigned long parent,u32 classid)593*4882a593Smuzhiyun static unsigned long fq_codel_bind(struct Qdisc *sch, unsigned long parent,
594*4882a593Smuzhiyun u32 classid)
595*4882a593Smuzhiyun {
596*4882a593Smuzhiyun return 0;
597*4882a593Smuzhiyun }
598*4882a593Smuzhiyun
fq_codel_unbind(struct Qdisc * q,unsigned long cl)599*4882a593Smuzhiyun static void fq_codel_unbind(struct Qdisc *q, unsigned long cl)
600*4882a593Smuzhiyun {
601*4882a593Smuzhiyun }
602*4882a593Smuzhiyun
fq_codel_tcf_block(struct Qdisc * sch,unsigned long cl,struct netlink_ext_ack * extack)603*4882a593Smuzhiyun static struct tcf_block *fq_codel_tcf_block(struct Qdisc *sch, unsigned long cl,
604*4882a593Smuzhiyun struct netlink_ext_ack *extack)
605*4882a593Smuzhiyun {
606*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
607*4882a593Smuzhiyun
608*4882a593Smuzhiyun if (cl)
609*4882a593Smuzhiyun return NULL;
610*4882a593Smuzhiyun return q->block;
611*4882a593Smuzhiyun }
612*4882a593Smuzhiyun
fq_codel_dump_class(struct Qdisc * sch,unsigned long cl,struct sk_buff * skb,struct tcmsg * tcm)613*4882a593Smuzhiyun static int fq_codel_dump_class(struct Qdisc *sch, unsigned long cl,
614*4882a593Smuzhiyun struct sk_buff *skb, struct tcmsg *tcm)
615*4882a593Smuzhiyun {
616*4882a593Smuzhiyun tcm->tcm_handle |= TC_H_MIN(cl);
617*4882a593Smuzhiyun return 0;
618*4882a593Smuzhiyun }
619*4882a593Smuzhiyun
fq_codel_dump_class_stats(struct Qdisc * sch,unsigned long cl,struct gnet_dump * d)620*4882a593Smuzhiyun static int fq_codel_dump_class_stats(struct Qdisc *sch, unsigned long cl,
621*4882a593Smuzhiyun struct gnet_dump *d)
622*4882a593Smuzhiyun {
623*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
624*4882a593Smuzhiyun u32 idx = cl - 1;
625*4882a593Smuzhiyun struct gnet_stats_queue qs = { 0 };
626*4882a593Smuzhiyun struct tc_fq_codel_xstats xstats;
627*4882a593Smuzhiyun
628*4882a593Smuzhiyun if (idx < q->flows_cnt) {
629*4882a593Smuzhiyun const struct fq_codel_flow *flow = &q->flows[idx];
630*4882a593Smuzhiyun const struct sk_buff *skb;
631*4882a593Smuzhiyun
632*4882a593Smuzhiyun memset(&xstats, 0, sizeof(xstats));
633*4882a593Smuzhiyun xstats.type = TCA_FQ_CODEL_XSTATS_CLASS;
634*4882a593Smuzhiyun xstats.class_stats.deficit = flow->deficit;
635*4882a593Smuzhiyun xstats.class_stats.ldelay =
636*4882a593Smuzhiyun codel_time_to_us(flow->cvars.ldelay);
637*4882a593Smuzhiyun xstats.class_stats.count = flow->cvars.count;
638*4882a593Smuzhiyun xstats.class_stats.lastcount = flow->cvars.lastcount;
639*4882a593Smuzhiyun xstats.class_stats.dropping = flow->cvars.dropping;
640*4882a593Smuzhiyun if (flow->cvars.dropping) {
641*4882a593Smuzhiyun codel_tdiff_t delta = flow->cvars.drop_next -
642*4882a593Smuzhiyun codel_get_time();
643*4882a593Smuzhiyun
644*4882a593Smuzhiyun xstats.class_stats.drop_next = (delta >= 0) ?
645*4882a593Smuzhiyun codel_time_to_us(delta) :
646*4882a593Smuzhiyun -codel_time_to_us(-delta);
647*4882a593Smuzhiyun }
648*4882a593Smuzhiyun if (flow->head) {
649*4882a593Smuzhiyun sch_tree_lock(sch);
650*4882a593Smuzhiyun skb = flow->head;
651*4882a593Smuzhiyun while (skb) {
652*4882a593Smuzhiyun qs.qlen++;
653*4882a593Smuzhiyun skb = skb->next;
654*4882a593Smuzhiyun }
655*4882a593Smuzhiyun sch_tree_unlock(sch);
656*4882a593Smuzhiyun }
657*4882a593Smuzhiyun qs.backlog = q->backlogs[idx];
658*4882a593Smuzhiyun qs.drops = 0;
659*4882a593Smuzhiyun }
660*4882a593Smuzhiyun if (gnet_stats_copy_queue(d, NULL, &qs, qs.qlen) < 0)
661*4882a593Smuzhiyun return -1;
662*4882a593Smuzhiyun if (idx < q->flows_cnt)
663*4882a593Smuzhiyun return gnet_stats_copy_app(d, &xstats, sizeof(xstats));
664*4882a593Smuzhiyun return 0;
665*4882a593Smuzhiyun }
666*4882a593Smuzhiyun
fq_codel_walk(struct Qdisc * sch,struct qdisc_walker * arg)667*4882a593Smuzhiyun static void fq_codel_walk(struct Qdisc *sch, struct qdisc_walker *arg)
668*4882a593Smuzhiyun {
669*4882a593Smuzhiyun struct fq_codel_sched_data *q = qdisc_priv(sch);
670*4882a593Smuzhiyun unsigned int i;
671*4882a593Smuzhiyun
672*4882a593Smuzhiyun if (arg->stop)
673*4882a593Smuzhiyun return;
674*4882a593Smuzhiyun
675*4882a593Smuzhiyun for (i = 0; i < q->flows_cnt; i++) {
676*4882a593Smuzhiyun if (list_empty(&q->flows[i].flowchain) ||
677*4882a593Smuzhiyun arg->count < arg->skip) {
678*4882a593Smuzhiyun arg->count++;
679*4882a593Smuzhiyun continue;
680*4882a593Smuzhiyun }
681*4882a593Smuzhiyun if (arg->fn(sch, i + 1, arg) < 0) {
682*4882a593Smuzhiyun arg->stop = 1;
683*4882a593Smuzhiyun break;
684*4882a593Smuzhiyun }
685*4882a593Smuzhiyun arg->count++;
686*4882a593Smuzhiyun }
687*4882a593Smuzhiyun }
688*4882a593Smuzhiyun
689*4882a593Smuzhiyun static const struct Qdisc_class_ops fq_codel_class_ops = {
690*4882a593Smuzhiyun .leaf = fq_codel_leaf,
691*4882a593Smuzhiyun .find = fq_codel_find,
692*4882a593Smuzhiyun .tcf_block = fq_codel_tcf_block,
693*4882a593Smuzhiyun .bind_tcf = fq_codel_bind,
694*4882a593Smuzhiyun .unbind_tcf = fq_codel_unbind,
695*4882a593Smuzhiyun .dump = fq_codel_dump_class,
696*4882a593Smuzhiyun .dump_stats = fq_codel_dump_class_stats,
697*4882a593Smuzhiyun .walk = fq_codel_walk,
698*4882a593Smuzhiyun };
699*4882a593Smuzhiyun
700*4882a593Smuzhiyun static struct Qdisc_ops fq_codel_qdisc_ops __read_mostly = {
701*4882a593Smuzhiyun .cl_ops = &fq_codel_class_ops,
702*4882a593Smuzhiyun .id = "fq_codel",
703*4882a593Smuzhiyun .priv_size = sizeof(struct fq_codel_sched_data),
704*4882a593Smuzhiyun .enqueue = fq_codel_enqueue,
705*4882a593Smuzhiyun .dequeue = fq_codel_dequeue,
706*4882a593Smuzhiyun .peek = qdisc_peek_dequeued,
707*4882a593Smuzhiyun .init = fq_codel_init,
708*4882a593Smuzhiyun .reset = fq_codel_reset,
709*4882a593Smuzhiyun .destroy = fq_codel_destroy,
710*4882a593Smuzhiyun .change = fq_codel_change,
711*4882a593Smuzhiyun .dump = fq_codel_dump,
712*4882a593Smuzhiyun .dump_stats = fq_codel_dump_stats,
713*4882a593Smuzhiyun .owner = THIS_MODULE,
714*4882a593Smuzhiyun };
715*4882a593Smuzhiyun
fq_codel_module_init(void)716*4882a593Smuzhiyun static int __init fq_codel_module_init(void)
717*4882a593Smuzhiyun {
718*4882a593Smuzhiyun return register_qdisc(&fq_codel_qdisc_ops);
719*4882a593Smuzhiyun }
720*4882a593Smuzhiyun
fq_codel_module_exit(void)721*4882a593Smuzhiyun static void __exit fq_codel_module_exit(void)
722*4882a593Smuzhiyun {
723*4882a593Smuzhiyun unregister_qdisc(&fq_codel_qdisc_ops);
724*4882a593Smuzhiyun }
725*4882a593Smuzhiyun
726*4882a593Smuzhiyun module_init(fq_codel_module_init)
727*4882a593Smuzhiyun module_exit(fq_codel_module_exit)
728*4882a593Smuzhiyun MODULE_AUTHOR("Eric Dumazet");
729*4882a593Smuzhiyun MODULE_LICENSE("GPL");
730*4882a593Smuzhiyun MODULE_DESCRIPTION("Fair Queue CoDel discipline");
731