162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2008, Intel Corporation.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Alexander Duyck <alexander.h.duyck@intel.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/types.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/string.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/skbuff.h>
1562306a36Sopenharmony_ci#include <net/netlink.h>
1662306a36Sopenharmony_ci#include <net/pkt_sched.h>
1762306a36Sopenharmony_ci#include <net/pkt_cls.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct multiq_sched_data {
2062306a36Sopenharmony_ci	u16 bands;
2162306a36Sopenharmony_ci	u16 max_bands;
2262306a36Sopenharmony_ci	u16 curband;
2362306a36Sopenharmony_ci	struct tcf_proto __rcu *filter_list;
2462306a36Sopenharmony_ci	struct tcf_block *block;
2562306a36Sopenharmony_ci	struct Qdisc **queues;
2662306a36Sopenharmony_ci};
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic struct Qdisc *
3062306a36Sopenharmony_cimultiq_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
3362306a36Sopenharmony_ci	u32 band;
3462306a36Sopenharmony_ci	struct tcf_result res;
3562306a36Sopenharmony_ci	struct tcf_proto *fl = rcu_dereference_bh(q->filter_list);
3662306a36Sopenharmony_ci	int err;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
3962306a36Sopenharmony_ci	err = tcf_classify(skb, NULL, fl, &res, false);
4062306a36Sopenharmony_ci#ifdef CONFIG_NET_CLS_ACT
4162306a36Sopenharmony_ci	switch (err) {
4262306a36Sopenharmony_ci	case TC_ACT_STOLEN:
4362306a36Sopenharmony_ci	case TC_ACT_QUEUED:
4462306a36Sopenharmony_ci	case TC_ACT_TRAP:
4562306a36Sopenharmony_ci		*qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN;
4662306a36Sopenharmony_ci		fallthrough;
4762306a36Sopenharmony_ci	case TC_ACT_SHOT:
4862306a36Sopenharmony_ci		return NULL;
4962306a36Sopenharmony_ci	}
5062306a36Sopenharmony_ci#endif
5162306a36Sopenharmony_ci	band = skb_get_queue_mapping(skb);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (band >= q->bands)
5462306a36Sopenharmony_ci		return q->queues[0];
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	return q->queues[band];
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int
6062306a36Sopenharmony_cimultiq_enqueue(struct sk_buff *skb, struct Qdisc *sch,
6162306a36Sopenharmony_ci	       struct sk_buff **to_free)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct Qdisc *qdisc;
6462306a36Sopenharmony_ci	int ret;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	qdisc = multiq_classify(skb, sch, &ret);
6762306a36Sopenharmony_ci#ifdef CONFIG_NET_CLS_ACT
6862306a36Sopenharmony_ci	if (qdisc == NULL) {
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		if (ret & __NET_XMIT_BYPASS)
7162306a36Sopenharmony_ci			qdisc_qstats_drop(sch);
7262306a36Sopenharmony_ci		__qdisc_drop(skb, to_free);
7362306a36Sopenharmony_ci		return ret;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci#endif
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	ret = qdisc_enqueue(skb, qdisc, to_free);
7862306a36Sopenharmony_ci	if (ret == NET_XMIT_SUCCESS) {
7962306a36Sopenharmony_ci		sch->q.qlen++;
8062306a36Sopenharmony_ci		return NET_XMIT_SUCCESS;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci	if (net_xmit_drop_count(ret))
8362306a36Sopenharmony_ci		qdisc_qstats_drop(sch);
8462306a36Sopenharmony_ci	return ret;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic struct sk_buff *multiq_dequeue(struct Qdisc *sch)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
9062306a36Sopenharmony_ci	struct Qdisc *qdisc;
9162306a36Sopenharmony_ci	struct sk_buff *skb;
9262306a36Sopenharmony_ci	int band;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	for (band = 0; band < q->bands; band++) {
9562306a36Sopenharmony_ci		/* cycle through bands to ensure fairness */
9662306a36Sopenharmony_ci		q->curband++;
9762306a36Sopenharmony_ci		if (q->curband >= q->bands)
9862306a36Sopenharmony_ci			q->curband = 0;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		/* Check that target subqueue is available before
10162306a36Sopenharmony_ci		 * pulling an skb to avoid head-of-line blocking.
10262306a36Sopenharmony_ci		 */
10362306a36Sopenharmony_ci		if (!netif_xmit_stopped(
10462306a36Sopenharmony_ci		    netdev_get_tx_queue(qdisc_dev(sch), q->curband))) {
10562306a36Sopenharmony_ci			qdisc = q->queues[q->curband];
10662306a36Sopenharmony_ci			skb = qdisc->dequeue(qdisc);
10762306a36Sopenharmony_ci			if (skb) {
10862306a36Sopenharmony_ci				qdisc_bstats_update(sch, skb);
10962306a36Sopenharmony_ci				sch->q.qlen--;
11062306a36Sopenharmony_ci				return skb;
11162306a36Sopenharmony_ci			}
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci	return NULL;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic struct sk_buff *multiq_peek(struct Qdisc *sch)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
12162306a36Sopenharmony_ci	unsigned int curband = q->curband;
12262306a36Sopenharmony_ci	struct Qdisc *qdisc;
12362306a36Sopenharmony_ci	struct sk_buff *skb;
12462306a36Sopenharmony_ci	int band;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	for (band = 0; band < q->bands; band++) {
12762306a36Sopenharmony_ci		/* cycle through bands to ensure fairness */
12862306a36Sopenharmony_ci		curband++;
12962306a36Sopenharmony_ci		if (curband >= q->bands)
13062306a36Sopenharmony_ci			curband = 0;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		/* Check that target subqueue is available before
13362306a36Sopenharmony_ci		 * pulling an skb to avoid head-of-line blocking.
13462306a36Sopenharmony_ci		 */
13562306a36Sopenharmony_ci		if (!netif_xmit_stopped(
13662306a36Sopenharmony_ci		    netdev_get_tx_queue(qdisc_dev(sch), curband))) {
13762306a36Sopenharmony_ci			qdisc = q->queues[curband];
13862306a36Sopenharmony_ci			skb = qdisc->ops->peek(qdisc);
13962306a36Sopenharmony_ci			if (skb)
14062306a36Sopenharmony_ci				return skb;
14162306a36Sopenharmony_ci		}
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci	return NULL;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic void
14862306a36Sopenharmony_cimultiq_reset(struct Qdisc *sch)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	u16 band;
15162306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	for (band = 0; band < q->bands; band++)
15462306a36Sopenharmony_ci		qdisc_reset(q->queues[band]);
15562306a36Sopenharmony_ci	q->curband = 0;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic void
15962306a36Sopenharmony_cimultiq_destroy(struct Qdisc *sch)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	int band;
16262306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	tcf_block_put(q->block);
16562306a36Sopenharmony_ci	for (band = 0; band < q->bands; band++)
16662306a36Sopenharmony_ci		qdisc_put(q->queues[band]);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	kfree(q->queues);
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic int multiq_tune(struct Qdisc *sch, struct nlattr *opt,
17262306a36Sopenharmony_ci		       struct netlink_ext_ack *extack)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
17562306a36Sopenharmony_ci	struct tc_multiq_qopt *qopt;
17662306a36Sopenharmony_ci	struct Qdisc **removed;
17762306a36Sopenharmony_ci	int i, n_removed = 0;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!netif_is_multiqueue(qdisc_dev(sch)))
18062306a36Sopenharmony_ci		return -EOPNOTSUPP;
18162306a36Sopenharmony_ci	if (nla_len(opt) < sizeof(*qopt))
18262306a36Sopenharmony_ci		return -EINVAL;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	qopt = nla_data(opt);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	qopt->bands = qdisc_dev(sch)->real_num_tx_queues;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	removed = kmalloc(sizeof(*removed) * (q->max_bands - q->bands),
18962306a36Sopenharmony_ci			  GFP_KERNEL);
19062306a36Sopenharmony_ci	if (!removed)
19162306a36Sopenharmony_ci		return -ENOMEM;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	sch_tree_lock(sch);
19462306a36Sopenharmony_ci	q->bands = qopt->bands;
19562306a36Sopenharmony_ci	for (i = q->bands; i < q->max_bands; i++) {
19662306a36Sopenharmony_ci		if (q->queues[i] != &noop_qdisc) {
19762306a36Sopenharmony_ci			struct Qdisc *child = q->queues[i];
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci			q->queues[i] = &noop_qdisc;
20062306a36Sopenharmony_ci			qdisc_purge_queue(child);
20162306a36Sopenharmony_ci			removed[n_removed++] = child;
20262306a36Sopenharmony_ci		}
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	sch_tree_unlock(sch);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	for (i = 0; i < n_removed; i++)
20862306a36Sopenharmony_ci		qdisc_put(removed[i]);
20962306a36Sopenharmony_ci	kfree(removed);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	for (i = 0; i < q->bands; i++) {
21262306a36Sopenharmony_ci		if (q->queues[i] == &noop_qdisc) {
21362306a36Sopenharmony_ci			struct Qdisc *child, *old;
21462306a36Sopenharmony_ci			child = qdisc_create_dflt(sch->dev_queue,
21562306a36Sopenharmony_ci						  &pfifo_qdisc_ops,
21662306a36Sopenharmony_ci						  TC_H_MAKE(sch->handle,
21762306a36Sopenharmony_ci							    i + 1), extack);
21862306a36Sopenharmony_ci			if (child) {
21962306a36Sopenharmony_ci				sch_tree_lock(sch);
22062306a36Sopenharmony_ci				old = q->queues[i];
22162306a36Sopenharmony_ci				q->queues[i] = child;
22262306a36Sopenharmony_ci				if (child != &noop_qdisc)
22362306a36Sopenharmony_ci					qdisc_hash_add(child, true);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci				if (old != &noop_qdisc)
22662306a36Sopenharmony_ci					qdisc_purge_queue(old);
22762306a36Sopenharmony_ci				sch_tree_unlock(sch);
22862306a36Sopenharmony_ci				qdisc_put(old);
22962306a36Sopenharmony_ci			}
23062306a36Sopenharmony_ci		}
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci	return 0;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic int multiq_init(struct Qdisc *sch, struct nlattr *opt,
23662306a36Sopenharmony_ci		       struct netlink_ext_ack *extack)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
23962306a36Sopenharmony_ci	int i, err;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	q->queues = NULL;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (!opt)
24462306a36Sopenharmony_ci		return -EINVAL;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	err = tcf_block_get(&q->block, &q->filter_list, sch, extack);
24762306a36Sopenharmony_ci	if (err)
24862306a36Sopenharmony_ci		return err;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	q->max_bands = qdisc_dev(sch)->num_tx_queues;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	q->queues = kcalloc(q->max_bands, sizeof(struct Qdisc *), GFP_KERNEL);
25362306a36Sopenharmony_ci	if (!q->queues)
25462306a36Sopenharmony_ci		return -ENOBUFS;
25562306a36Sopenharmony_ci	for (i = 0; i < q->max_bands; i++)
25662306a36Sopenharmony_ci		q->queues[i] = &noop_qdisc;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return multiq_tune(sch, opt, extack);
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic int multiq_dump(struct Qdisc *sch, struct sk_buff *skb)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
26462306a36Sopenharmony_ci	unsigned char *b = skb_tail_pointer(skb);
26562306a36Sopenharmony_ci	struct tc_multiq_qopt opt;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	opt.bands = q->bands;
26862306a36Sopenharmony_ci	opt.max_bands = q->max_bands;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (nla_put(skb, TCA_OPTIONS, sizeof(opt), &opt))
27162306a36Sopenharmony_ci		goto nla_put_failure;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	return skb->len;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cinla_put_failure:
27662306a36Sopenharmony_ci	nlmsg_trim(skb, b);
27762306a36Sopenharmony_ci	return -1;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int multiq_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
28162306a36Sopenharmony_ci			struct Qdisc **old, struct netlink_ext_ack *extack)
28262306a36Sopenharmony_ci{
28362306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
28462306a36Sopenharmony_ci	unsigned long band = arg - 1;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (new == NULL)
28762306a36Sopenharmony_ci		new = &noop_qdisc;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	*old = qdisc_replace(sch, new, &q->queues[band]);
29062306a36Sopenharmony_ci	return 0;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic struct Qdisc *
29462306a36Sopenharmony_cimultiq_leaf(struct Qdisc *sch, unsigned long arg)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
29762306a36Sopenharmony_ci	unsigned long band = arg - 1;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	return q->queues[band];
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic unsigned long multiq_find(struct Qdisc *sch, u32 classid)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
30562306a36Sopenharmony_ci	unsigned long band = TC_H_MIN(classid);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (band - 1 >= q->bands)
30862306a36Sopenharmony_ci		return 0;
30962306a36Sopenharmony_ci	return band;
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_cistatic unsigned long multiq_bind(struct Qdisc *sch, unsigned long parent,
31362306a36Sopenharmony_ci				 u32 classid)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	return multiq_find(sch, classid);
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic void multiq_unbind(struct Qdisc *q, unsigned long cl)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_cistatic int multiq_dump_class(struct Qdisc *sch, unsigned long cl,
32462306a36Sopenharmony_ci			     struct sk_buff *skb, struct tcmsg *tcm)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	tcm->tcm_handle |= TC_H_MIN(cl);
32962306a36Sopenharmony_ci	tcm->tcm_info = q->queues[cl - 1]->handle;
33062306a36Sopenharmony_ci	return 0;
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistatic int multiq_dump_class_stats(struct Qdisc *sch, unsigned long cl,
33462306a36Sopenharmony_ci				 struct gnet_dump *d)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
33762306a36Sopenharmony_ci	struct Qdisc *cl_q;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	cl_q = q->queues[cl - 1];
34062306a36Sopenharmony_ci	if (gnet_stats_copy_basic(d, cl_q->cpu_bstats, &cl_q->bstats, true) < 0 ||
34162306a36Sopenharmony_ci	    qdisc_qstats_copy(d, cl_q) < 0)
34262306a36Sopenharmony_ci		return -1;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	return 0;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic void multiq_walk(struct Qdisc *sch, struct qdisc_walker *arg)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
35062306a36Sopenharmony_ci	int band;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	if (arg->stop)
35362306a36Sopenharmony_ci		return;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	for (band = 0; band < q->bands; band++) {
35662306a36Sopenharmony_ci		if (!tc_qdisc_stats_dump(sch, band + 1, arg))
35762306a36Sopenharmony_ci			break;
35862306a36Sopenharmony_ci	}
35962306a36Sopenharmony_ci}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic struct tcf_block *multiq_tcf_block(struct Qdisc *sch, unsigned long cl,
36262306a36Sopenharmony_ci					  struct netlink_ext_ack *extack)
36362306a36Sopenharmony_ci{
36462306a36Sopenharmony_ci	struct multiq_sched_data *q = qdisc_priv(sch);
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	if (cl)
36762306a36Sopenharmony_ci		return NULL;
36862306a36Sopenharmony_ci	return q->block;
36962306a36Sopenharmony_ci}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_cistatic const struct Qdisc_class_ops multiq_class_ops = {
37262306a36Sopenharmony_ci	.graft		=	multiq_graft,
37362306a36Sopenharmony_ci	.leaf		=	multiq_leaf,
37462306a36Sopenharmony_ci	.find		=	multiq_find,
37562306a36Sopenharmony_ci	.walk		=	multiq_walk,
37662306a36Sopenharmony_ci	.tcf_block	=	multiq_tcf_block,
37762306a36Sopenharmony_ci	.bind_tcf	=	multiq_bind,
37862306a36Sopenharmony_ci	.unbind_tcf	=	multiq_unbind,
37962306a36Sopenharmony_ci	.dump		=	multiq_dump_class,
38062306a36Sopenharmony_ci	.dump_stats	=	multiq_dump_class_stats,
38162306a36Sopenharmony_ci};
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_cistatic struct Qdisc_ops multiq_qdisc_ops __read_mostly = {
38462306a36Sopenharmony_ci	.next		=	NULL,
38562306a36Sopenharmony_ci	.cl_ops		=	&multiq_class_ops,
38662306a36Sopenharmony_ci	.id		=	"multiq",
38762306a36Sopenharmony_ci	.priv_size	=	sizeof(struct multiq_sched_data),
38862306a36Sopenharmony_ci	.enqueue	=	multiq_enqueue,
38962306a36Sopenharmony_ci	.dequeue	=	multiq_dequeue,
39062306a36Sopenharmony_ci	.peek		=	multiq_peek,
39162306a36Sopenharmony_ci	.init		=	multiq_init,
39262306a36Sopenharmony_ci	.reset		=	multiq_reset,
39362306a36Sopenharmony_ci	.destroy	=	multiq_destroy,
39462306a36Sopenharmony_ci	.change		=	multiq_tune,
39562306a36Sopenharmony_ci	.dump		=	multiq_dump,
39662306a36Sopenharmony_ci	.owner		=	THIS_MODULE,
39762306a36Sopenharmony_ci};
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_cistatic int __init multiq_module_init(void)
40062306a36Sopenharmony_ci{
40162306a36Sopenharmony_ci	return register_qdisc(&multiq_qdisc_ops);
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic void __exit multiq_module_exit(void)
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci	unregister_qdisc(&multiq_qdisc_ops);
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cimodule_init(multiq_module_init)
41062306a36Sopenharmony_cimodule_exit(multiq_module_exit)
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
413