1 /* SPDX-License-Identifier: LGPL-2.1-only */
2 /*
3  * Copyright (c) 2018 Volodymyr Bendiuga <volodymyr.bendiuga@westermo.se>
4  */
5 
6 #include <netlink-private/netlink.h>
7 #include <netlink-private/tc.h>
8 #include <netlink/netlink.h>
9 #include <netlink/utils.h>
10 #include <netlink-private/route/tc-api.h>
11 #include <netlink/route/qdisc.h>
12 #include <netlink/route/qdisc/mqprio.h>
13 
14 /** @cond SKIP */
15 #define SCH_MQPRIO_ATTR_NUMTC           (1 << 0)
16 #define SCH_MQPRIO_ATTR_PRIOMAP         (1 << 1)
17 #define SCH_MQPRIO_ATTR_HW              (1 << 2)
18 #define SCH_MQPRIO_ATTR_QUEUE           (1 << 3)
19 #define SCH_MQPRIO_ATTR_MODE            (1 << 4)
20 #define SCH_MQPRIO_ATTR_SHAPER          (1 << 5)
21 #define SCH_MQPRIO_ATTR_MIN_RATE        (1 << 6)
22 #define SCH_MQPRIO_ATTR_MAX_RATE        (1 << 7)
23 /** @endcond */
24 
25 static struct nla_policy mqprio_policy[TCA_MQPRIO_MAX + 1] = {
26 	[TCA_MQPRIO_MODE]       = { .minlen = sizeof(uint16_t) },
27 	[TCA_MQPRIO_SHAPER]     = { .minlen = sizeof(uint16_t) },
28 	[TCA_MQPRIO_MIN_RATE64] = { .type = NLA_NESTED },
29 	[TCA_MQPRIO_MAX_RATE64] = { .type = NLA_NESTED },
30 };
31 
mqprio_msg_parser(struct rtnl_tc * tc,void * data)32 static int mqprio_msg_parser(struct rtnl_tc *tc, void *data)
33 {
34 	struct rtnl_mqprio *mqprio = data;
35 	struct tc_mqprio_qopt *qopt;
36 	struct nlattr *attr;
37 	int len, rem, i, err;
38 
39 	if (tc->tc_opts->d_size < sizeof(*qopt))
40 		return -NLE_INVAL;
41 
42 	qopt = (struct tc_mqprio_qopt *) tc->tc_opts->d_data;
43 	mqprio->qm_num_tc = qopt->num_tc;
44 	mqprio->qm_hw = qopt->hw;
45 	memcpy(mqprio->qm_prio_map, qopt->prio_tc_map,
46 	       TC_QOPT_MAX_QUEUE * sizeof(uint8_t));
47 	memcpy(mqprio->qm_count, qopt->count,
48 	       TC_QOPT_MAX_QUEUE * sizeof(uint16_t));
49 	memcpy(mqprio->qm_offset, qopt->offset,
50 	       TC_QOPT_MAX_QUEUE * sizeof(uint16_t));
51 	mqprio->qm_mask = (SCH_MQPRIO_ATTR_NUMTC | SCH_MQPRIO_ATTR_PRIOMAP |
52 	                   SCH_MQPRIO_ATTR_QUEUE | SCH_MQPRIO_ATTR_HW);
53 
54 	len = tc->tc_opts->d_size - NLA_ALIGN(sizeof(*qopt));
55 
56 	if (len > 0) {
57 		struct nlattr *tb[TCA_MQPRIO_MAX + 1];
58 
59 		err = nla_parse(tb, TCA_MQPRIO_MAX, (struct nlattr *)
60 		                ((char *) tc->tc_opts->d_data + NLA_ALIGN(sizeof(*qopt))),
61 		                len, mqprio_policy);
62 		if (err < 0)
63 			return err;
64 
65 		if (tb[TCA_MQPRIO_MODE]) {
66 			mqprio->qm_mode = nla_get_u16(tb[TCA_MQPRIO_MODE]);
67 			mqprio->qm_mask |= SCH_MQPRIO_ATTR_MODE;
68 		}
69 
70 		if (tb[TCA_MQPRIO_SHAPER]) {
71 			mqprio->qm_shaper = nla_get_u16(tb[TCA_MQPRIO_SHAPER]);
72 			mqprio->qm_mask |= SCH_MQPRIO_ATTR_SHAPER;
73 		}
74 
75 		if (tb[TCA_MQPRIO_MIN_RATE64]) {
76 			i = 0;
77 			nla_for_each_nested(attr, tb[TCA_MQPRIO_MIN_RATE64], rem) {
78 				if (nla_type(attr) != TCA_MQPRIO_MIN_RATE64)
79 					return -EINVAL;
80 
81 				if (i >= mqprio->qm_num_tc)
82 					break;
83 
84 				mqprio->qm_min_rate[i] = nla_get_u64(attr);
85 			}
86 
87 			mqprio->qm_mask |= SCH_MQPRIO_ATTR_MIN_RATE;
88 		}
89 
90 		if (tb[TCA_MQPRIO_MAX_RATE64]) {
91 			i = 0;
92 			nla_for_each_nested(attr, tb[TCA_MQPRIO_MAX_RATE64], rem) {
93 				if (nla_type(attr) != TCA_MQPRIO_MAX_RATE64)
94 					return -EINVAL;
95 
96 				if (i >= mqprio->qm_num_tc)
97 					break;
98 
99 				mqprio->qm_max_rate[i] = nla_get_u64(attr);
100 			}
101 
102 			mqprio->qm_mask |= SCH_MQPRIO_ATTR_MAX_RATE;
103 		}
104 	}
105 
106 	return 0;
107 }
108 
mqprio_msg_fill(struct rtnl_tc * tc,void * data,struct nl_msg * msg)109 static int mqprio_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
110 {
111 	struct rtnl_mqprio *mqprio = data;
112 	struct tc_mqprio_qopt qopt = { 0 };
113 	struct nlattr *nest = NULL;
114 	int i;
115 
116 	if (!mqprio ||
117 	    !(mqprio->qm_mask & SCH_MQPRIO_ATTR_NUMTC) ||
118 	    !(mqprio->qm_mask & SCH_MQPRIO_ATTR_PRIOMAP) ||
119 	    !(mqprio->qm_mask & SCH_MQPRIO_ATTR_QUEUE))
120 		return -NLE_INVAL;
121 
122 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_HW))
123 		qopt.hw = 0;
124 	else
125 		qopt.hw = mqprio->qm_hw;
126 
127 	qopt.num_tc = mqprio->qm_num_tc;
128 	memcpy(qopt.count, mqprio->qm_count, TC_QOPT_MAX_QUEUE * sizeof(uint16_t));
129 	memcpy(qopt.offset, mqprio->qm_offset, TC_QOPT_MAX_QUEUE * sizeof(uint16_t));
130 	memcpy(qopt.prio_tc_map, mqprio->qm_prio_map, TC_QOPT_MAX_QUEUE * sizeof(uint8_t));
131 
132 	nlmsg_append(msg, &qopt, sizeof(qopt), NL_DONTPAD);
133 
134 	if (mqprio->qm_hw) {
135 		if (mqprio->qm_mask & SCH_MQPRIO_ATTR_MODE)
136 			NLA_PUT_U16(msg, TCA_MQPRIO_MODE, mqprio->qm_mode);
137 
138 		if (mqprio->qm_mask & SCH_MQPRIO_ATTR_SHAPER)
139 			NLA_PUT_U16(msg, TCA_MQPRIO_SHAPER, mqprio->qm_shaper);
140 
141 		if (mqprio->qm_mask & SCH_MQPRIO_ATTR_MIN_RATE) {
142 			nest = nla_nest_start(msg, TCA_MQPRIO_MIN_RATE64);
143 			if (!nest)
144 				goto nla_put_failure;
145 
146 			for (i = 0; i < mqprio->qm_num_tc; i++) {
147 				if (nla_put(msg, TCA_MQPRIO_MIN_RATE64,
148 				            sizeof(mqprio->qm_min_rate[i]),
149 				            &mqprio->qm_min_rate[i]) < 0)
150 					goto nla_nest_cancel;
151 			}
152 			nla_nest_end(msg, nest);
153 		}
154 
155 		if (mqprio->qm_mask & SCH_MQPRIO_ATTR_MAX_RATE) {
156 			nest = nla_nest_start(msg, TCA_MQPRIO_MAX_RATE64);
157 			if (!nest)
158 				goto nla_put_failure;
159 
160 			for (i = 0; i < mqprio->qm_num_tc; i++) {
161 				if (nla_put(msg, TCA_MQPRIO_MAX_RATE64,
162 				            sizeof(mqprio->qm_max_rate[i]),
163 				            &mqprio->qm_max_rate[i]) < 0)
164 					goto nla_nest_cancel;
165 			}
166 			nla_nest_end(msg, nest);
167 		}
168 	}
169 
170 	return 0;
171 
172 nla_nest_cancel:
173 	nla_nest_cancel(msg, nest);
174 	return -NLE_MSGSIZE;
175 
176 nla_put_failure:
177 	return -NLE_MSGSIZE;
178 }
179 
mqprio_dump_line(struct rtnl_tc * tc,void * data,struct nl_dump_params * p)180 static void mqprio_dump_line(struct rtnl_tc *tc, void *data,
181                              struct nl_dump_params *p)
182 {
183 	struct rtnl_mqprio *mqprio = data;
184 
185 	if (mqprio)
186 		nl_dump(p, " num_tc %u", mqprio->qm_num_tc);
187 }
188 
mqprio_dump_details(struct rtnl_tc * tc,void * data,struct nl_dump_params * p)189 static void mqprio_dump_details(struct rtnl_tc *tc, void *data,
190                                 struct nl_dump_params *p)
191 {
192 	struct rtnl_mqprio *mqprio = data;
193 	int i;
194 
195 	if (!mqprio)
196 		return;
197 
198 	nl_dump(p, "map [");
199 
200 	for (i = 0; i <= TC_QOPT_BITMASK; i++)
201 		nl_dump(p, "%u%s", mqprio->qm_prio_map[i],
202 			i < TC_QOPT_BITMASK ? " " : "");
203 
204 	nl_dump(p, "]\n");
205 	nl_new_line(p);
206 }
207 
208 /**
209  * @name Attribute Modification
210  * @{
211  */
212 
213 /**
214  * Set number of traffic classes.
215  * @arg qdisc           MQPRIO qdisc to be modified.
216  * @arg num_tc          Number of traffic classes to create.
217  * @return 0 on success or a negative error code.
218  */
rtnl_qdisc_mqprio_set_num_tc(struct rtnl_qdisc * qdisc,int num_tc)219 int rtnl_qdisc_mqprio_set_num_tc(struct rtnl_qdisc *qdisc, int num_tc)
220 {
221 	struct rtnl_mqprio *mqprio;
222 
223 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
224 		return -NLE_NOMEM;
225 
226 	mqprio->qm_num_tc = num_tc;
227 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_NUMTC;
228 	return 0;
229 }
230 
231 /**
232  * Get number of traffic classes of MQPRIO qdisc.
233  * @arg qdisc           MQPRIO qdisc.
234  * @return Number of traffic classes or a negative error code.
235  */
rtnl_qdisc_mqprio_get_num_tc(struct rtnl_qdisc * qdisc)236 int rtnl_qdisc_mqprio_get_num_tc(struct rtnl_qdisc *qdisc)
237 {
238 	struct rtnl_mqprio *mqprio;
239 
240 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
241 		return -NLE_INVAL;
242 
243 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_NUMTC)
244 		return mqprio->qm_num_tc;
245 	else
246 		return -NLE_MISSING_ATTR;
247 }
248 
249 /**
250  * Set priomap of the MQPRIO qdisc.
251  * @arg qdisc           MQPRIO qdisc to be modified.
252  * @arg priomap         New priority mapping.
253  * @arg len             Length of priomap (# of elements).
254  * @return 0 on success or a negative error code.
255  */
rtnl_qdisc_mqprio_set_priomap(struct rtnl_qdisc * qdisc,uint8_t priomap[],int len)256 int rtnl_qdisc_mqprio_set_priomap(struct rtnl_qdisc *qdisc, uint8_t priomap[],
257                                 int len)
258 {
259 	struct rtnl_mqprio *mqprio;
260 	int i;
261 
262 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
263 		return -NLE_NOMEM;
264 
265 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_NUMTC))
266 		return -NLE_MISSING_ATTR;
267 
268 	if (len > TC_QOPT_BITMASK + 1)
269 		return -NLE_RANGE;
270 
271 	for (i = 0; i < len; i++) {
272 		if (priomap[i] > mqprio->qm_num_tc)
273 			return -NLE_RANGE;
274 	}
275 
276 	memset(mqprio->qm_prio_map, 0, sizeof(mqprio->qm_prio_map));
277 	memcpy(mqprio->qm_prio_map, priomap, len * sizeof(uint8_t));
278 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_PRIOMAP;
279 
280 	return 0;
281 }
282 
283 /**
284  * Get priomap of MQPRIO qdisc.
285  * @arg qdisc           MQPRIO qdisc.
286  * @return Priority mapping as array of size TC_QOPT_BANDS+1
287  *         or NULL if an error occured.
288  */
rtnl_qdisc_mqprio_get_priomap(struct rtnl_qdisc * qdisc)289 uint8_t *rtnl_qdisc_mqprio_get_priomap(struct rtnl_qdisc *qdisc)
290 {
291 	struct rtnl_mqprio *mqprio;
292 
293 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
294 		return NULL;
295 
296 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_PRIOMAP)
297 		return mqprio->qm_prio_map;
298 	else
299 		return NULL;
300 }
301 
302 /**
303  * Offload to HW or run in SW (default).
304  * @arg qdisc           MQPRIO qdisc to be modified.
305  * @arg offload         1 - offload to HW, 0 - run in SW only (default).
306  * @return 0 on success or a negative error code.
307  */
rtnl_qdisc_mqprio_hw_offload(struct rtnl_qdisc * qdisc,int offload)308 int rtnl_qdisc_mqprio_hw_offload(struct rtnl_qdisc *qdisc, int offload)
309 {
310 	struct rtnl_mqprio *mqprio;
311 
312 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
313 		return -NLE_NOMEM;
314 
315 	switch (offload) {
316 	case 0:
317 	case 1:
318 		mqprio->qm_hw = offload;
319 		break;
320 	default:
321 		return -NLE_INVAL;
322 	}
323 
324 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_HW;
325 	return 0;
326 }
327 
328 /**
329  * Check whether running in HW or SW.
330  * @arg qdisc           MQPRIO qdisc to be modified.
331  * @return 0 if running in SW, otherwise 1 (HW)
332  */
rtnl_qdisc_mqprio_get_hw_offload(struct rtnl_qdisc * qdisc)333 int rtnl_qdisc_mqprio_get_hw_offload(struct rtnl_qdisc *qdisc)
334 {
335 	struct rtnl_mqprio *mqprio;
336 
337 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
338 		return -NLE_INVAL;
339 
340 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_HW)
341 		return mqprio->qm_hw;
342 
343 	return 0;
344 }
345 
346 /**
347  * Set tc queue of the MQPRIO qdisc.
348  * @arg qdisc           MQPRIO qdisc to be modified.
349  * @arg count           count of queue range for each traffic class
350  * @arg offset          offset of queue range for each traffic class
351  * @return 0 on success or a negative error code.
352  */
rtnl_qdisc_mqprio_set_queue(struct rtnl_qdisc * qdisc,uint16_t count[],uint16_t offset[],int len)353 int rtnl_qdisc_mqprio_set_queue(struct rtnl_qdisc *qdisc, uint16_t count[],
354                                 uint16_t offset[], int len)
355 {
356 	struct rtnl_mqprio *mqprio;
357 
358 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
359 		return -NLE_NOMEM;
360 
361 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_NUMTC))
362 		return -NLE_MISSING_ATTR;
363 
364 	if (len < 0 || len > TC_QOPT_MAX_QUEUE)
365 		return -NLE_RANGE;
366 
367 	memset(mqprio->qm_count, 0, sizeof(mqprio->qm_count));
368 	memset(mqprio->qm_offset, 0, sizeof(mqprio->qm_offset));
369 	memcpy(mqprio->qm_count, count, len * sizeof(uint16_t));
370 	memcpy(mqprio->qm_offset, offset, len * sizeof(uint16_t));
371 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_QUEUE;
372 
373 	return 0;
374 }
375 
376 /**
377  * Get tc queue of the MQPRIO qdisc.
378  * @arg qdisc           MQPRIO qdisc to be modified.
379  * @arg count           count of queue range for each traffic class
380  * @arg offset          offset of queue range for each traffic class
381  * @return 0 on success or a negative error code.
382  */
rtnl_qdisc_mqprio_get_queue(struct rtnl_qdisc * qdisc,uint16_t * count,uint16_t * offset)383 int rtnl_qdisc_mqprio_get_queue(struct rtnl_qdisc *qdisc, uint16_t *count,
384                                 uint16_t *offset)
385 {
386 	struct rtnl_mqprio *mqprio;
387 
388 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
389 		return -NLE_INVAL;
390 
391 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_QUEUE))
392 		return -NLE_MISSING_ATTR;
393 
394 	memcpy(count, mqprio->qm_count, TC_QOPT_MAX_QUEUE * sizeof(uint16_t));
395 	memcpy(offset, mqprio->qm_offset, TC_QOPT_MAX_QUEUE * sizeof(uint16_t));
396 
397 	return 0;
398 }
399 
400 /**
401  * Set mode of mqprio Qdisc
402  * @arg qdisc           MQPRIO qdisc to be modified.
403  * @arg mode            one of: TC_MQPRIO_MODE_DCB, TC_MQPRIO_MODE_CHANNEL
404  * @return 0 on success or a negative error code.
405  */
rtnl_qdisc_mqprio_set_mode(struct rtnl_qdisc * qdisc,uint16_t mode)406 int rtnl_qdisc_mqprio_set_mode(struct rtnl_qdisc *qdisc, uint16_t mode)
407 {
408 	struct rtnl_mqprio *mqprio;
409 
410 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
411 		return -NLE_NOMEM;
412 
413 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_HW))
414 		return -NLE_MISSING_ATTR;
415 
416 	mqprio->qm_mode = mode;
417 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_MODE;
418 
419 	return 0;
420 }
421 
422 /**
423  * Get mode of mqprio Qdisc
424  * @arg qdisc           MQPRIO qdisc.
425  * @return mode on success or negative error code.
426  */
rtnl_qdisc_mqprio_get_mode(struct rtnl_qdisc * qdisc)427 int rtnl_qdisc_mqprio_get_mode(struct rtnl_qdisc *qdisc)
428 {
429 	struct rtnl_mqprio *mqprio;
430 
431 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
432 		return -NLE_INVAL;
433 
434 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_MODE)
435 		return mqprio->qm_mode;
436 	else
437 		return -NLE_MISSING_ATTR;
438 }
439 
440 /**
441  * Set shaper of mqprio Qdisc
442  * @arg qdisc           MQPRIO qdisc to be modified.
443  * @arg shaper          one of: TC_MQPRIO_SHAPER_DCB, TC_MQPRIO_SHAPER_BW_RATE
444  * @return 0 on success or a negative error code.
445  */
rtnl_qdisc_mqprio_set_shaper(struct rtnl_qdisc * qdisc,uint16_t shaper)446 int rtnl_qdisc_mqprio_set_shaper(struct rtnl_qdisc *qdisc, uint16_t shaper)
447 {
448 	struct rtnl_mqprio *mqprio;
449 
450 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
451 		return -NLE_NOMEM;
452 
453 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_HW))
454 		return -NLE_MISSING_ATTR;
455 
456 	mqprio->qm_shaper = shaper;
457 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_SHAPER;
458 
459 	return 0;
460 }
461 
462 /**
463  * Get shaper of mqprio Qdisc
464  * @arg qdisc           MQPRIO qdisc.
465  * @return shaper on success or negative error code.
466  */
rtnl_qdisc_mqprio_get_shaper(struct rtnl_qdisc * qdisc)467 int rtnl_qdisc_mqprio_get_shaper(struct rtnl_qdisc *qdisc)
468 {
469 	struct rtnl_mqprio *mqprio;
470 
471 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
472 		return -NLE_INVAL;
473 
474 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_SHAPER)
475 		return mqprio->qm_shaper;
476 	else
477 		return -NLE_MISSING_ATTR;
478 }
479 
480 /**
481  * Set minimum value of bandwidth rate limit for each traffic class
482  * @arg qdisc           MQPRIO qdisc.
483  * @arg min             minimum rate for each traffic class
484  * @return 0 on success or a negative error code.
485  */
rtnl_qdisc_mqprio_set_min_rate(struct rtnl_qdisc * qdisc,uint64_t min[],int len)486 int rtnl_qdisc_mqprio_set_min_rate(struct rtnl_qdisc *qdisc, uint64_t min[], int len)
487 {
488 	struct rtnl_mqprio *mqprio;
489 
490 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
491 		return -NLE_NOMEM;
492 
493 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_SHAPER))
494 		return -NLE_MISSING_ATTR;
495 
496 	if (mqprio->qm_shaper != TC_MQPRIO_SHAPER_BW_RATE)
497 		return -NLE_INVAL;
498 
499 	if (len < 0 || len > TC_QOPT_MAX_QUEUE)
500 		return -NLE_RANGE;
501 
502 	memset(mqprio->qm_min_rate, 0, sizeof(mqprio->qm_min_rate));
503 	memcpy(mqprio->qm_min_rate, min, len * sizeof(uint64_t));
504 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_MIN_RATE;
505 
506 	return 0;
507 }
508 
509 /**
510  * Get minimum value of bandwidth rate limit for each traffic class
511  * @arg qdisc           MQPRIO qdisc.
512  * @arg min             minimum rate for each traffic class
513  * @return 0 on success or a negative error code.
514  */
rtnl_qdisc_mqprio_get_min_rate(struct rtnl_qdisc * qdisc,uint64_t * min)515 int rtnl_qdisc_mqprio_get_min_rate(struct rtnl_qdisc *qdisc, uint64_t *min)
516 {
517 	struct rtnl_mqprio *mqprio;
518 
519 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
520 		return -NLE_INVAL;
521 
522 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_MIN_RATE) {
523 		memcpy(min, mqprio->qm_min_rate, TC_QOPT_MAX_QUEUE * sizeof(uint64_t));
524 		return 0;
525 	}
526 
527 	return -NLE_MISSING_ATTR;
528 }
529 
530 /**
531  * Set maximum value of bandwidth rate limit for each traffic class
532  * @arg qdisc           MQPRIO qdisc.
533  * @arg max             maximum rate for each traffic class
534  * @return 0 on success or a negative error code.
535  */
rtnl_qdisc_mqprio_set_max_rate(struct rtnl_qdisc * qdisc,uint64_t max[],int len)536 int rtnl_qdisc_mqprio_set_max_rate(struct rtnl_qdisc *qdisc, uint64_t max[], int len)
537 {
538 	struct rtnl_mqprio *mqprio;
539 
540 	if (!(mqprio = rtnl_tc_data(TC_CAST(qdisc))))
541 		return -NLE_NOMEM;
542 
543 	if (!(mqprio->qm_mask & SCH_MQPRIO_ATTR_SHAPER))
544 		return -NLE_MISSING_ATTR;
545 
546 	if (mqprio->qm_shaper != TC_MQPRIO_SHAPER_BW_RATE)
547 		return -NLE_INVAL;
548 
549 	if (len < 0 || len > TC_QOPT_MAX_QUEUE)
550 		return -NLE_RANGE;
551 
552 	memset(mqprio->qm_max_rate, 0, sizeof(mqprio->qm_max_rate));
553 	memcpy(mqprio->qm_max_rate, max, len * sizeof(uint64_t));
554 	mqprio->qm_mask |= SCH_MQPRIO_ATTR_MAX_RATE;
555 
556 	return 0;
557 }
558 
559 /**
560  * Get maximum value of bandwidth rate limit for each traffic class
561  * @arg qdisc           MQPRIO qdisc.
562  * @arg min             maximum rate for each traffic class
563  * @return 0 on success or a negative error code.
564  */
rtnl_qdisc_mqprio_get_max_rate(struct rtnl_qdisc * qdisc,uint64_t * max)565 int rtnl_qdisc_mqprio_get_max_rate(struct rtnl_qdisc *qdisc, uint64_t *max)
566 {
567 	struct rtnl_mqprio *mqprio;
568 
569 	if (!(mqprio = rtnl_tc_data_peek(TC_CAST(qdisc))))
570 		return -NLE_INVAL;
571 
572 	if (mqprio->qm_mask & SCH_MQPRIO_ATTR_MAX_RATE) {
573 		memcpy(max, mqprio->qm_max_rate, TC_QOPT_MAX_QUEUE * sizeof(uint64_t));
574 		return 0;
575 	}
576 
577 	return -NLE_MISSING_ATTR;
578 }
579 
580 /** @} */
581 
582 static struct rtnl_tc_ops mqprio_ops = {
583 	.to_kind                = "mqprio",
584 	.to_type                = RTNL_TC_TYPE_QDISC,
585 	.to_size                = sizeof(struct rtnl_mqprio),
586 	.to_msg_parser          = mqprio_msg_parser,
587 	.to_dump = {
588 	    [NL_DUMP_LINE]      = mqprio_dump_line,
589 	    [NL_DUMP_DETAILS]   = mqprio_dump_details,
590 	},
591 	.to_msg_fill            = mqprio_msg_fill,
592 };
593 
mqprio_init(void)594 static void __init mqprio_init(void)
595 {
596 	rtnl_tc_register(&mqprio_ops);
597 }
598 
mqprio_exit(void)599 static void __exit mqprio_exit(void)
600 {
601 	rtnl_tc_unregister(&mqprio_ops);
602 }
603 
604 /** @} */
605