Skip to content

Commit 3ff4dc3

Browse files
committed
confd: add a per-port egress rate limit
Nothing in the model could hold a port below its link speed, which a virtual port has none of, so there was no way to put the traffic classes under load there and see the scheduler work. The design has the node for it, one token bucket on the whole port. Add /interfaces/interface/qos/egress/rate-limit with a rate in bits per second and an optional burst, rendered as a 'tc tbf' root with the ets scheduler beneath it: tc qdisc add dev e1 root handle 1: tbf rate 10000000bit burst 12500 latency 100ms tc qdisc add dev e1 parent 1:1 handle 2: ets bands 8 strict 8 priomap ... An unset burst covers ten milliseconds at rate, never less than one frame. mqprio can only be the root, so a rate limited port schedules with ets. The operational offload list gains 'rate-limit', reported when the driver marks the tbf offloaded. NET_SCH_TBF is enabled on all boards. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent fec9d7b commit 3ff4dc3

11 files changed

Lines changed: 139 additions & 13 deletions

File tree

‎board/aarch64/linux_defconfig‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ CONFIG_VLAN_8021Q_MVRP=y
246246
CONFIG_NET_SCHED=y
247247
CONFIG_NET_SCH_MQPRIO=y
248248
CONFIG_NET_SCH_ETS=y
249+
CONFIG_NET_SCH_TBF=y
249250
CONFIG_NET_SCH_INGRESS=y
250251
CONFIG_NET_CLS_BASIC=y
251252
CONFIG_NET_CLS_BPF=y

‎board/arm/linux_defconfig‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ CONFIG_VLAN_8021Q_MVRP=y
230230
CONFIG_NET_SCHED=y
231231
CONFIG_NET_SCH_MQPRIO=y
232232
CONFIG_NET_SCH_ETS=y
233+
CONFIG_NET_SCH_TBF=y
233234
CONFIG_NET_SCH_INGRESS=y
234235
CONFIG_NET_CLS_BASIC=y
235236
CONFIG_NET_CLS_BPF=y

‎board/riscv64/linux_defconfig‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ CONFIG_VLAN_8021Q_MVRP=y
194194
CONFIG_NET_SCHED=y
195195
CONFIG_NET_SCH_MQPRIO=y
196196
CONFIG_NET_SCH_ETS=y
197+
CONFIG_NET_SCH_TBF=y
197198
CONFIG_NET_SCH_INGRESS=y
198199
CONFIG_NET_CLS_BASIC=y
199200
CONFIG_NET_CLS_BPF=y

‎board/x86_64/linux_defconfig‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ CONFIG_VLAN_8021Q_MVRP=y
221221
CONFIG_NET_SCHED=y
222222
CONFIG_NET_SCH_MQPRIO=y
223223
CONFIG_NET_SCH_ETS=y
224+
CONFIG_NET_SCH_TBF=y
224225
CONFIG_NET_SCH_INGRESS=y
225226
CONFIG_NET_CLS_BASIC=y
226227
CONFIG_NET_CLS_BPF=y

‎doc/ChangeLog.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ All notable changes to the project are documented in this file.
2424
of the box on every port: trust PCP, one traffic class per
2525
transmit queue mapped per IEEE 802.1Q-2022 Table 8-5, replacing the
2626
fixed boot-time queue setup, see [QoS](qos.md)
27+
- Add a per-port egress rate limit under
28+
`/interfaces/interface/qos/egress/rate-limit`, one token bucket above
29+
the traffic classes, see [QoS](qos.md)
2730
- Transmission selection is rendered with the `ets` scheduler on every
2831
port, so drivers that offload it take the class algorithms and weights
2932
into the switch fabric, not only the priority to class map

‎doc/qos.md‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,30 @@ admin@example:/config/interface/e1/qos/egress/> <b>leave</b>
246246
</code></pre>
247247

248248

249+
### Rate Limit
250+
251+
A port can be held below its link speed with one token bucket on the
252+
whole port, configured under `qos egress rate-limit`. `rate` is the
253+
sustained rate in bits per second and `burst` how many bytes may leave
254+
at once from a full bucket; left unset, the burst covers ten
255+
milliseconds at the configured rate, never less than one maximum-size
256+
frame. The bucket sits above the traffic classes: it bounds what the
257+
port sends in total, and transmission selection decides which class
258+
gets the bounded rate.
259+
260+
<pre class="cli"><code>admin@example:/config/> <b>edit interface e1 qos egress</b>
261+
admin@example:/config/interface/e1/qos/egress/> <b>set rate-limit rate 10000000</b>
262+
admin@example:/config/interface/e1/qos/egress/> <b>leave</b>
263+
</code></pre>
264+
265+
The limit is rendered as a `tc tbf` root qdisc with the scheduler below
266+
it, so on a port without offload it applies to the frames the CPU
267+
sends, and on a port whose driver takes the bucket to frames the fabric
268+
forwards as well; `offload` under `capabilities` then lists
269+
`rate-limit`. A rate limited port always schedules with `tc ets`,
270+
since `tc mqprio` can only be the root qdisc.
271+
272+
249273
### Egress Remarking
250274

251275
Configured under `qos egress remark`, both leaves default to `none`:
@@ -352,8 +376,8 @@ and each port reports it under `qos capabilities`:
352376

353377
`supported-trust-order` is absent when the driver has no ingress
354378
classification support. `offload` lists `classification`, `remarking`
355-
and `transmission-selection` as the driver takes them; a stage not
356-
listed runs in the kernel.
379+
`transmission-selection` and `rate-limit` as the driver takes them; a
380+
stage not listed runs in the kernel.
357381

358382
Each feature maps to one Linux mechanism, and whether it reaches the
359383
hardware depends on the driver implementing the matching hook:
@@ -364,6 +388,7 @@ hardware depends on the driver implementing the matching hook:
364388
| Trust order | `dcb apptrust` | `dcbnl_setapptrust` | Rule order, software |
365389
| Egress remarking | `dcb rewr` | `dcbnl_setrewr` | DSCP only, software |
366390
| Transmission selection | `tc ets` | `TC_SETUP_QDISC_ETS`| software |
391+
| Port rate limit | `tc tbf` | `TC_SETUP_QDISC_TBF`| software |
367392
/// table-caption
368393
QoS features and their Linux backends.
369394
///

‎src/confd/src/if-qos.c‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* SPDX-License-Identifier: BSD-3-Clause */
22

3+
#include <inttypes.h>
34
#include <stdbool.h>
45
#include <dirent.h>
56

@@ -64,6 +65,8 @@ struct qos_egress {
6465
uint8_t map[NUM_PRIO];
6566
enum tsa algo[MAX_TC];
6667
uint32_t weight[MAX_TC];
68+
uint64_t rate; /* bits/s, 0 = no rate limit */
69+
uint32_t burst; /* bytes */
6770
};
6871

6972
/*
@@ -328,6 +331,18 @@ static int qos_parse_egress(struct lyd_node *egress, const char *ifname, struct
328331
eg->weight[id] = strtoul(val, NULL, 10);
329332
}
330333

334+
table = lydx_get_child(egress, "rate-limit");
335+
if (table) {
336+
val = lydx_get_cattr(table, "rate");
337+
eg->rate = val ? strtoull(val, NULL, 10) : 0;
338+
339+
/* Ten milliseconds at rate, never below one frame */
340+
val = lydx_get_cattr(table, "burst");
341+
eg->burst = val ? strtoul(val, NULL, 10) : eg->rate / 8 / 100;
342+
if (eg->burst < 1518)
343+
eg->burst = 1518;
344+
}
345+
331346
return 0;
332347
}
333348

@@ -691,13 +706,26 @@ static void gen_dcb_log(FILE *fp, const char *ifname, struct lyd_node *ingress,
691706
*/
692707
static void gen_egress(FILE *fp, const char *ifname, struct qos_egress *eg)
693708
{
709+
const char *attach = "root";
694710
int i, nstrict = 0;
695711

696712
fprintf(fp, "tc qdisc del dev %s root 2>/dev/null\n", ifname);
713+
714+
/*
715+
* The rate limit is one bucket on the whole port, so it takes
716+
* the root and the scheduler hangs below it. mqprio can only
717+
* be the root, so a rate limited port always schedules with ets.
718+
*/
719+
if (eg->rate) {
720+
fprintf(fp, "tc qdisc add dev %s root handle 1: tbf rate %" PRIu64 "bit burst %u"
721+
" latency 100ms\n", ifname, eg->rate, eg->burst);
722+
attach = "parent 1:1 handle 2:";
723+
}
724+
697725
if (eg->num_tc < 2)
698726
return;
699727

700-
if (qos_mqprio(ifname) && !iface_has_quirk(ifname, "broken-mqprio")) {
728+
if (!eg->rate && qos_mqprio(ifname) && !iface_has_quirk(ifname, "broken-mqprio")) {
701729
fprintf(fp, "tc qdisc add dev %s root mqprio num_tc %d map", ifname, eg->num_tc);
702730
for (i = 0; i < NUM_PRIO; i++)
703731
fprintf(fp, " %d", eg->map[i]);
@@ -710,7 +738,7 @@ static void gen_egress(FILE *fp, const char *ifname, struct qos_egress *eg)
710738
for (i = eg->num_tc - 1; i >= 0 && eg->algo[i] == TSA_STRICT; i--)
711739
nstrict++;
712740

713-
fprintf(fp, "tc qdisc add dev %s root ets bands %d strict %d", ifname, eg->num_tc, nstrict);
741+
fprintf(fp, "tc qdisc add dev %s %s ets bands %d strict %d", ifname, attach, eg->num_tc, nstrict);
714742
if (nstrict < eg->num_tc) {
715743
fputs(" quanta", fp);
716744
for (i = eg->num_tc - 1 - nstrict; i >= 0; i--)
@@ -831,7 +859,8 @@ int netdag_gen_qos(sr_session_ctx_t *session, struct dagger *net, struct lyd_nod
831859
*/
832860
if (lydx_get_op(dif) == LYDX_OP_CREATE ||
833861
qos_has_change(lydx_get_descendant(lyd_child(dqos), "egress", "traffic-class-table", NULL)) ||
834-
qos_has_change(lydx_get_descendant(lyd_child(dqos), "egress", "traffic-class", NULL)))
862+
qos_has_change(lydx_get_descendant(lyd_child(dqos), "egress", "traffic-class", NULL)) ||
863+
qos_has_change(lydx_get_descendant(lyd_child(dqos), "egress", "rate-limit", NULL)))
835864
gen_egress(fp, ifname, &eg);
836865
fclose(fp);
837866

‎src/confd/yang/confd/infix-if-qos.yang‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ submodule infix-if-qos {
179179
description "Traffic classes and transmission selection
180180
run in the switch fabric.";
181181
}
182+
enum rate-limit {
183+
description "The port rate limit runs in the switch fabric.";
184+
}
182185
}
183186
description "Pipeline stages the driver has taken into
184187
hardware. A stage not listed runs in the kernel, which on
@@ -479,6 +482,34 @@ submodule infix-if-qos {
479482
description "DSCP rewrite on transmit.";
480483
}
481484
}
485+
486+
/* Rendered as a tc tbf root above the scheduler. */
487+
container rate-limit {
488+
presence "Bounds the transmit rate of the port.";
489+
description "One token bucket on the whole port, above the
490+
traffic classes. It bounds what the port sends in total
491+
and knows nothing about classes: transmission selection
492+
decides which class gets the bounded rate.";
493+
494+
leaf rate {
495+
type uint64 {
496+
range "8..max";
497+
}
498+
units "bits/second";
499+
mandatory true;
500+
description "Sustained transmit rate.";
501+
}
502+
503+
leaf burst {
504+
type uint32 {
505+
range "1518..max";
506+
}
507+
units "bytes";
508+
description "Bytes that may leave at once when the bucket is
509+
full. Unset, the daemon sizes it for ten milliseconds
510+
at rate, never below one maximum-size frame.";
511+
}
512+
}
482513
}
483514
}
484515
}

‎src/statd/python/yanger/ietf_interfaces/link.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ def qos_capabilities(iplink, qdiscs):
170170
result["supported-trust-order"] = trust
171171

172172
# Stages the driver runs. DCB tables exist only on drivers with the
173-
# operations, so only those ports are asked. ets reports offloaded
174-
# when the driver took it. mqprio never sets that flag,
173+
# operations, so only those ports are asked. ets and tbf report
174+
# offloaded when the driver took them. mqprio never sets that flag,
175175
# but it is only ever installed with hw 1, which the kernel refuses
176176
# without driver support, so its presence means the same.
177177
offload = []
@@ -187,6 +187,8 @@ def qos_capabilities(iplink, qdiscs):
187187
kind, offloaded = qdisc.get("kind"), qdisc.get("offloaded", False)
188188
if kind == "mqprio" and qdisc.get("root") or kind == "ets" and offloaded:
189189
offload.append("transmission-selection")
190+
if kind == "tbf" and qdisc.get("root") and offloaded:
191+
offload.append("rate-limit")
190192

191193
if offload:
192194
result["offload"] = offload

‎test/case/interfaces/qos_traffic_class/test.adoc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ strict bands first, the weights as quanta, and the priomap inverted as
1313

1414
The port's class count comes from its transmit queues, or eight for a
1515
single-queue port. Switching the table to the ieee-sr preset must put
16-
the SR classes, priorities 2 and 3, on the two highest classes. Removing
17-
the qos container must restore the default table, IEEE 802.1Q-2022
18-
Table 8-5, and the operational datastore must report whether transmission
16+
the SR classes, priorities 2 and 3, on the two highest classes. A rate
17+
limit must put a tbf root qdisc above the scheduler, with the same ets
18+
table beneath it, and go away with the setting. Removing the qos
19+
container must restore the default table, IEEE 802.1Q-2022 Table 8-5,
20+
and the operational datastore must report whether transmission
1921
selection is offloaded throughout.
2022

2123
==== Topology
@@ -29,6 +31,8 @@ image::topology.svg[QoS Traffic Classes and Transmission Selection topology, ali
2931
. Verify the root qdisc carries the custom map and weights
3032
. Verify the offload capability matches the qdisc
3133
. Switch to the ieee-sr preset and verify SR classes on top
34+
. Add a 10 Mbit/s rate limit and verify the tbf root above the ets table
35+
. Remove the rate limit and verify the scheduler is the root again
3236
. Remove qos configuration and verify the default table is back
3337

3438

0 commit comments

Comments
 (0)