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