1/*
2 **************************************************************************
3 * Copyright (c) 2014, 2015 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
19#define NSS_WRED_SUPPORT_TRAFFIC_CLASS 6
20#define NSS_WRED_MAX_TRAFFIC_CLASS NSS_WRED_SUPPORT_TRAFFIC_CLASS+1
21
22/*
23 * nsswred traffic class structure
24 */
25struct nss_wred_traffic_class {
26	u32 limit;			/* Queue length */
27	u32 weight_mode_value;		/* Weight mode value */
28	struct tc_red_alg_parameter rap;/* Parameters for RED alg */
29};
30
31/*
32 * nsswred private qdisc structure
33 */
34struct nss_wred_sched_data {
35	struct nss_qdisc nq;			/* Common base class for all nss qdiscs */
36	u32 traffic_classes;			/* # of traffic classs in this wred*/
37	u32 def_traffic_class;			/* Default traffic class if no match */
38	tc_nsswred_weight_mode_t weight_mode;	/* Weight mode */
39	struct nss_wred_traffic_class nwtc[NSS_WRED_MAX_TRAFFIC_CLASS];
40						/* Parameters for each traffic class */
41	u8 ecn;					/* Mark ECN or drop pkt */
42	u8 weighted;				/* This is a wred or red */
43};
44
45/*
46 * nss_wred_enqueue()
47 *	Enqueue API for nsswred qdisc
48 */
49static int nss_wred_enqueue(struct sk_buff *skb, struct Qdisc *sch)
50{
51	return nss_qdisc_enqueue(skb, sch);
52}
53
54/*
55 * nss_wred_dequeue()
56 *	Dequeue API for nsswred qdisc
57 */
58static struct sk_buff *nss_wred_dequeue(struct Qdisc *sch)
59{
60	return nss_qdisc_dequeue(sch);
61}
62
63/*
64 * nss_wred_drop()
65 *	Drops a packet from HLOS queue.
66 */
67static unsigned int nss_wred_drop(struct Qdisc *sch)
68{
69	nss_qdisc_info("nsswred dropping");
70	return nss_qdisc_drop(sch);
71}
72
73/*
74 * nss_wred_reset()
75 *	Reset the nsswred qdisc
76 */
77static void nss_wred_reset(struct Qdisc *sch)
78{
79	nss_qdisc_info("nsswred resetting!");
80	nss_qdisc_reset(sch);
81}
82
83/*
84 * nss_wred_destroy()
85 *	Destroy the nsswred qdisc
86 */
87static void nss_wred_destroy(struct Qdisc *sch)
88{
89	struct nss_qdisc *nq = (struct nss_qdisc *)qdisc_priv(sch);
90
91	/*
92	 * Stop the polling of basic stats
93	 */
94	nss_qdisc_stop_basic_stats_polling(nq);
95
96	nss_qdisc_destroy(nq);
97	nss_qdisc_info("nsswred destroyed");
98}
99
100/*
101 * nsswred policy structure
102 */
103static const struct nla_policy nss_wred_policy[TCA_NSSWRED_MAX + 1] = {
104	[TCA_NSSWRED_PARMS] = { .len = sizeof(struct tc_nsswred_qopt) },
105};
106
107/*
108 * nss_wred_change()
109 *	Function call to configure the nsswred parameters
110 */
111static int nss_wred_change(struct Qdisc *sch, struct nlattr *opt)
112{
113	struct nss_wred_sched_data *q;
114	struct nlattr *na[TCA_NSSWRED_MAX + 1];
115	struct tc_nsswred_qopt *qopt;
116	int err;
117	struct nss_if_msg nim;
118
119	q = qdisc_priv(sch);
120
121	if (opt == NULL) {
122		return -EINVAL;
123	}
124	err = nla_parse_nested(na, TCA_NSSWRED_MAX, opt, nss_wred_policy);
125	if (err < 0) {
126		return err;
127	}
128	if (na[TCA_NSSWRED_PARMS] == NULL) {
129		return -EINVAL;
130	}
131	qopt = nla_data(na[TCA_NSSWRED_PARMS]);
132
133	nss_qdisc_info("%s: nsswred %x traffic_classes:%d def_traffic_class: %d Weight_Mode:%d ECN:%d\n",
134			__func__, sch->handle, qopt->traffic_classes, qopt->def_traffic_class, qopt->weight_mode, qopt->ecn);
135
136	if (qopt->traffic_classes) {
137		/*
138		 * This is a wred setup command, do checks again because parameters might not come from tc utility
139		 */
140		if (qopt->traffic_classes > NSS_WRED_SUPPORT_TRAFFIC_CLASS) {
141			nss_qdisc_error("%s: nsswred %x traffic classes should not exceeds %d\n", __func__, sch->handle, NSS_WRED_SUPPORT_TRAFFIC_CLASS);
142			return -EINVAL;
143		}
144		if (qopt->def_traffic_class < 1 || qopt->def_traffic_class > qopt->traffic_classes) {
145			nss_qdisc_error("%s: nsswred %x invalid default traffic\n", __func__, sch->handle);
146			return -EINVAL;
147		}
148		if (qopt->weight_mode >= TC_NSSWRED_WEIGHT_MODES) {
149			nss_qdisc_error("%s: nsswred %x invalid weight_mode\n", __func__, sch->handle);
150			return -EINVAL;
151		}
152		q->traffic_classes = qopt->traffic_classes ;
153		q->def_traffic_class = qopt->def_traffic_class;
154		q->weight_mode = qopt->weight_mode;
155		q->ecn = qopt->ecn;
156		q->weighted = 1;
157	} else {
158		if (qopt->traffic_id) {
159			/*
160			 * This is a wred traffic class command
161			 */
162			if (!q->traffic_classes) {
163				nss_qdisc_error("%s: nsswred %x not setup yet, can't accept traffic class configuration\n", __func__, sch->handle);
164				return -EINVAL;
165			}
166			if (!qopt->limit || !qopt->rap.min || !qopt->rap.max || !qopt->weight_mode_value || !qopt->rap.exp_weight_factor) {
167				nss_qdisc_error("%s: nsswred %x Requires RED algorithm parameters and weight_mode_value\n", __func__, sch->handle);
168				return -EINVAL;
169			}
170		} else {
171			/*
172			 * This is a red setup command
173			 */
174			if (!qopt->limit || !qopt->rap.exp_weight_factor) {
175				nss_qdisc_error("%s: nsswred %x Requires RED algorithm parameters\n", __func__, sch->handle);
176				return -EINVAL;
177			}
178			/*
179			 * If min/max does not specify, calculated it
180			 */
181			if (!qopt->rap.max) {
182				qopt->rap.max = qopt->rap.min ? qopt->rap.min * 3 : qopt->limit / 4;
183			}
184			if (!qopt->rap.min) {
185				qopt->rap.min = qopt->rap.max / 3;
186			}
187			q->ecn = qopt->ecn;
188		}
189		q->nwtc[qopt->traffic_id].limit = qopt->limit;
190		q->nwtc[qopt->traffic_id].weight_mode_value = qopt->weight_mode_value;
191		q->nwtc[qopt->traffic_id].rap.min = qopt->rap.min;
192		q->nwtc[qopt->traffic_id].rap.max = qopt->rap.max;
193		q->nwtc[qopt->traffic_id].rap.probability = qopt->rap.probability;
194		q->nwtc[qopt->traffic_id].rap.exp_weight_factor = qopt->rap.exp_weight_factor;
195	}
196
197	nim.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = q->nq.qos_tag;
198	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.limit = qopt->limit;
199	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.weight_mode = qopt->weight_mode;
200	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.weight_mode_value = qopt->weight_mode_value;
201	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.rap.min = qopt->rap.min;
202	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.rap.max = qopt->rap.max;
203	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.rap.probability = qopt->rap.probability;
204	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.rap.exp_weight_factor = qopt->rap.exp_weight_factor;
205	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.traffic_classes = qopt->traffic_classes;
206	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.ecn = qopt->ecn;
207	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.def_traffic_class = qopt->def_traffic_class;
208	nim.msg.shaper_configure.config.msg.shaper_node_config.snc.wred_param.traffic_id = qopt->traffic_id;
209
210	if (nss_qdisc_configure(&q->nq, &nim, NSS_SHAPER_CONFIG_TYPE_SHAPER_NODE_CHANGE_PARAM) < 0) {
211		nss_qdisc_error("%s: nsswred %x configuration failed\n", __func__, sch->handle);
212		return -EINVAL;
213	}
214
215	if (qopt->set_default == 0)
216		return 0;
217
218	/*
219	 * Set this qdisc to be the default qdisc for enqueuing packets.
220	*/
221	if (nss_qdisc_set_default(&q->nq) < 0) {
222		nss_qdisc_error("%s: nsswred %x set_default failed\n", __func__, sch->handle);
223		return -EINVAL;
224	}
225
226	nss_qdisc_info("%s: nsswred queue (qos_tag:%u) set as default\n", __func__, q->nq.qos_tag);
227
228	return 0;
229}
230
231/*
232 * nss_wred_init()
233 *	Init the nsswred qdisc
234 */
235static int nss_wred_init(struct Qdisc *sch, struct nlattr *opt)
236{
237	struct nss_qdisc *nq = qdisc_priv(sch);
238
239	if (opt == NULL)
240		return -EINVAL;
241
242	nss_qdisc_info("Initializing Wred - type %d\n", NSS_SHAPER_NODE_TYPE_WRED);
243	nss_wred_reset(sch);
244
245	if (nss_qdisc_init(sch, nq, NSS_SHAPER_NODE_TYPE_WRED, 0) < 0)
246		return -EINVAL;
247
248	nss_qdisc_info("NSS wred initialized - handle %x parent %x\n", sch->handle, sch->parent);
249	if (nss_wred_change(sch, opt) < 0) {
250		nss_qdisc_destroy(nq);
251		return -EINVAL;
252	}
253
254	/*
255	 * Start the stats polling timer
256	 */
257	nss_qdisc_start_basic_stats_polling(nq);
258
259	return 0;
260}
261
262/*
263 * nss_wred_dump()
264 *	Dump the parameters of nsswred to tc
265 */
266static int nss_wred_dump(struct Qdisc *sch, struct sk_buff *skb)
267{
268	struct nss_wred_sched_data *q;
269	struct nlattr *opts = NULL;
270	struct tc_nsswred_qopt opt;
271	int i;
272	nss_qdisc_info("Nsswred Dumping!");
273
274	q = qdisc_priv(sch);
275	if (q == NULL) {
276		return -1;
277	}
278
279	if (q->weighted) {
280		opt.traffic_classes = q->traffic_classes;
281		opt.def_traffic_class = q->def_traffic_class;
282		opt.ecn = q->ecn;
283		opt.weight_mode = q->weight_mode;
284		for (i = 0 ; i < q->traffic_classes; i++) {
285			opt.tntc[i].limit = q->nwtc[i+1].limit;
286			opt.tntc[i].weight_mode_value = q->nwtc[i+1].weight_mode_value;
287			opt.tntc[i].rap.exp_weight_factor = q->nwtc[i+1].rap.exp_weight_factor;
288			opt.tntc[i].rap.min = q->nwtc[i+1].rap.min;
289			opt.tntc[i].rap.max = q->nwtc[i+1].rap.max;
290			opt.tntc[i].rap.probability = q->nwtc[i+1].rap.probability;
291		}
292	} else {
293		opt.ecn = q->ecn;
294		opt.limit = q->nwtc[0].limit;
295		opt.rap.min = q->nwtc[0].rap.min;
296		opt.rap.max = q->nwtc[0].rap.max;
297		opt.rap.exp_weight_factor = q->nwtc[0].rap.exp_weight_factor;
298		opt.rap.probability = q->nwtc[0].rap.probability;
299	}
300
301	opts = nla_nest_start(skb, TCA_OPTIONS);
302	if (opts == NULL || nla_put(skb, TCA_NSSWRED_PARMS, sizeof(opt), &opt)) {
303		goto nla_put_failure;
304	}
305
306	return nla_nest_end(skb, opts);
307
308nla_put_failure:
309	nla_nest_cancel(skb, opts);
310	return -EMSGSIZE;
311}
312
313/*
314 * nss_wred_peek()
315 *	Peeks the first packet in queue for this qdisc
316 */
317static struct sk_buff *nss_wred_peek(struct Qdisc *sch)
318{
319	nss_qdisc_info("Nsswred Peeking");
320	return nss_qdisc_peek(sch);
321}
322
323/*
324 * Registration structure for nss_red qdisc
325 */
326struct Qdisc_ops nss_red_qdisc_ops __read_mostly = {
327	.id		=	"nssred",
328	.priv_size	=	sizeof(struct nss_wred_sched_data),
329	.enqueue	=	nss_wred_enqueue,
330	.dequeue	=	nss_wred_dequeue,
331	.peek		=	nss_wred_peek,
332	.drop		=	nss_wred_drop,
333	.init		=	nss_wred_init,
334	.reset		=	nss_wred_reset,
335	.destroy	=	nss_wred_destroy,
336	.change		=	nss_wred_change,
337	.dump		=	nss_wred_dump,
338	.owner		=	THIS_MODULE,
339};
340
341/*
342 * Registration structure for nss_wred qdisc
343 */
344struct Qdisc_ops nss_wred_qdisc_ops __read_mostly = {
345	.id		=	"nsswred",
346	.priv_size	=	sizeof(struct nss_wred_sched_data),
347	.enqueue	=	nss_wred_enqueue,
348	.dequeue	=	nss_wred_dequeue,
349	.peek		=	nss_wred_peek,
350	.drop		=	nss_wred_drop,
351	.init		=	nss_wred_init,
352	.reset		=	nss_wred_reset,
353	.destroy	=	nss_wred_destroy,
354	.change		=	nss_wred_change,
355	.dump		=	nss_wred_dump,
356	.owner		=	THIS_MODULE,
357};
358