1/*
2 **************************************************************************
3 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
4 * Permission to use, copy, modify, and/or distribute this software for
5 * any purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all copies.
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
13 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 **************************************************************************
15 */
16
17#include "nss_qdisc.h"
18
19struct nss_codel_stats {
20	u32 peak_queue_delay;		/* Peak delay experienced by a dequeued packet */
21	u32 peak_drop_delay;		/* Peak delay experienced by a packet that is dropped */
22};
23
24struct nss_codel_sched_data {
25	struct nss_qdisc nq;	/* Common base class for all nss qdiscs */
26	u32 target;			/* Acceptable value of queue delay */
27	u32 limit;			/* Length of queue */
28	u32 interval;			/* Monitoring interval */
29	u8 set_default;			/* Flag to set qdisc as default qdisc for enqueue */
30	struct nss_codel_stats stats;	/* Contains nss_codel related stats */
31};
32
33static int nss_codel_enqueue(struct sk_buff *skb, struct Qdisc *sch)
34{
35	return nss_qdisc_enqueue(skb, sch);
36}
37
38static struct sk_buff *nss_codel_dequeue(struct Qdisc *sch)
39{
40	return nss_qdisc_dequeue(sch);
41}
42
43static unsigned int nss_codel_drop(struct Qdisc *sch)
44{
45	return nss_qdisc_drop(sch);
46}
47
48static void nss_codel_reset(struct Qdisc *sch)
49{
50	nss_qdisc_info("nss_codel resetting!");
51	nss_qdisc_reset(sch);
52}
53
54static void nss_codel_destroy(struct Qdisc *sch)
55{
56	struct nss_qdisc *nq = qdisc_priv(sch);
57	/*
58	 * Stop the polling of basic stats
59	 */
60	nss_qdisc_stop_basic_stats_polling(nq);
61	nss_qdisc_destroy(nq);
62	nss_qdisc_info("nss_codel destroyed");
63}
64
65static const struct nla_policy nss_codel_policy[TCA_NSSCODEL_MAX + 1] = {
66	[TCA_NSSCODEL_PARMS] = { .len = sizeof(struct tc_nsscodel_qopt) },
67};
68
69static int nss_codel_change(struct Qdisc *sch, struct nlattr *opt)
70{
71	struct nss_codel_sched_data *q;
72	struct nlattr *na[TCA_NSSCODEL_MAX + 1];
73	struct tc_nsscodel_qopt *qopt;
74	struct nss_if_msg nim;
75	int err;
76	struct net_device *dev = qdisc_dev(sch);
77
78	q = qdisc_priv(sch);
79
80	if (opt == NULL)
81		return -EINVAL;
82
83	err = nla_parse_nested(na, TCA_NSSCODEL_MAX, opt, nss_codel_policy);
84	if (err < 0)
85		return err;
86
87	if (na[TCA_NSSCODEL_PARMS] == NULL)
88		return -EINVAL;
89
90	qopt = nla_data(na[TCA_NSSCODEL_PARMS]);
91
92	if (!qopt->target || !qopt->interval) {
93		nss_qdisc_error("nss_codel requires a non-zero value for target "
94				"and interval\n");
95		return -EINVAL;
96	}
97
98	if (!qopt->limit)
99		qopt->limit = dev->tx_queue_len ? : 1;
100
101	q->target = qopt->target;
102	q->limit = qopt->limit;
103	q->interval = qopt->interval;
104	q->set_default = qopt->set_default;
105
106	/*
107	 * Required for basic stats display
108	 */
109	sch->limit = qopt->limit;
110
111	nss_qdisc_info("Target:%u Limit:%u Interval:%u set_default = %u\n",
112		q->target, q->limit, q->interval, qopt->set_default);
113
114
115	nim.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = q->nq.qos_tag;
116	/*
117	 * Target and interval time needs to be provided in milliseconds
118	 * (tc provides us the time in mircoseconds and therefore we divide by 1000)
119	 */
120	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.codel_param.qlen_max = q->limit;
121	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.codel_param.cap.interval = q->interval/1000;
122	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.codel_param.cap.target = q->target/1000;
123	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.codel_param.cap.mtu = psched_mtu(dev);
124	nss_qdisc_info("%s: MTU size of interface %s is %u bytes\n", __func__, dev->name,
125			nim.msg.shaper_configure.config.msg.shaper_node_config.snc.codel_param.cap.mtu);
126
127	if (nss_qdisc_configure(&q->nq, &nim,
128				NSS_SHAPER_CONFIG_TYPE_CODEL_CHANGE_PARAM) < 0) {
129		return -EINVAL;
130	}
131
132	/*
133	 * There is nothing we need to do if the qdisc is not
134	 * set as default qdisc.
135	 */
136	if (!q->set_default)
137		return 0;
138
139	/*
140	 * Set this qdisc to be the default qdisc for enqueuing packets.
141	 */
142	if (nss_qdisc_set_default(&q->nq) < 0)
143		return -EINVAL;
144
145	return 0;
146}
147
148static int nss_codel_init(struct Qdisc *sch, struct nlattr *opt)
149{
150	struct nss_qdisc *nq = qdisc_priv(sch);
151
152	if (opt == NULL)
153		return -EINVAL;
154
155	nss_codel_reset(sch);
156	if (nss_qdisc_init(sch, nq, NSS_SHAPER_NODE_TYPE_CODEL, 0) < 0)
157		return -EINVAL;
158
159	if (nss_codel_change(sch, opt) < 0) {
160		nss_qdisc_destroy(nq);
161		return -EINVAL;
162	}
163
164	/*
165	 * Start the stats polling timer
166	 */
167	nss_qdisc_start_basic_stats_polling(nq);
168
169	return 0;
170}
171
172static int nss_codel_dump(struct Qdisc *sch, struct sk_buff *skb)
173{
174	struct nss_codel_sched_data *q;
175	struct nlattr *opts = NULL;
176	struct tc_nsscodel_qopt opt;
177
178	nss_qdisc_info("NssCodel Dumping!");
179
180	q = qdisc_priv(sch);
181	if (q == NULL) {
182		return -1;
183	}
184
185	opt.target = q->target;
186	opt.limit = q->limit;
187	opt.interval = q->interval;
188	opt.set_default = q->set_default;
189	opts = nla_nest_start(skb, TCA_OPTIONS);
190	if (opts == NULL) {
191		goto nla_put_failure;
192	}
193	if (nla_put(skb, TCA_NSSCODEL_PARMS, sizeof(opt), &opt))
194		goto nla_put_failure;
195
196	return nla_nest_end(skb, opts);
197
198nla_put_failure:
199	nla_nest_cancel(skb, opts);
200	return -EMSGSIZE;
201}
202
203static int nss_codel_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
204{
205	struct nss_codel_sched_data *q = qdisc_priv(sch);
206	struct tc_nsscodel_xstats st = {
207		.peak_queue_delay = q->nq.basic_stats_latest.packet_latency_peak_msec_dequeued,
208		.peak_drop_delay = q->nq.basic_stats_latest.packet_latency_peak_msec_dropped,
209	};
210
211	return gnet_stats_copy_app(d, &st, sizeof(st));
212}
213
214static struct sk_buff *nss_codel_peek(struct Qdisc *sch)
215{
216	nss_qdisc_info("Nsscodel Peeking");
217	return nss_qdisc_peek(sch);
218}
219
220
221struct Qdisc_ops nss_codel_qdisc_ops __read_mostly = {
222	.id		=	"nsscodel",
223	.priv_size	=	sizeof(struct nss_codel_sched_data),
224	.enqueue	=	nss_codel_enqueue,
225	.dequeue	=	nss_codel_dequeue,
226	.peek		=	nss_codel_peek,
227	.drop		=	nss_codel_drop,
228	.init		=	nss_codel_init,
229	.reset		=	nss_codel_reset,
230	.destroy	=	nss_codel_destroy,
231	.change		=	nss_codel_change,
232	.dump		=	nss_codel_dump,
233	.dump_stats	=	nss_codel_dump_stats,
234	.owner		=	THIS_MODULE,
235};
236