taprio: support dumping and setting per-tc max SDU

The 802.1Q queueMaxSDU table is technically implemented in Linux as
the TCA_TAPRIO_TC_ENTRY_MAX_SDU attribute of the TCA_TAPRIO_ATTR_TC_ENTRY
nest. Multiple TCA_TAPRIO_ATTR_TC_ENTRY nests may appear in the netlink
message, one per traffic class. Other configuration items that are per
traffic class are also supposed to go there.

This is done for future extensibility of the netlink interface (I have
the feeling that the struct tc_mqprio_qopt passed through
TCA_TAPRIO_ATTR_PRIOMAP is not exactly extensible, which kind of defeats
the purpose of using netlink). But otherwise, the max-sdu is parsed from
the user, and printed, just like any other fixed-size 16 element array.

I've modified the example for a fully offloaded configuration (flags 2)
to also show a max-sdu use case. The gate intervals were 0x80 (for TC 7),
0xa0 (for TCs 7 and 5) and 0xdf (for TCs 7, 6, 4, 3, 2, 1, 0).
I modified the last gate to exclude TC 7 (df -> 5f), so that TC 7 now
only interferes with TC 5.

Output after running the full offload command from the man page example
(the new attribute is "max-sdu"):

$ tc qdisc show dev swp0 root
qdisc taprio 8002: root tc 8 map 0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 0
queues offset 0 count 1 offset 1 count 1 offset 2 count 1 offset 3 count 1 offset 4 count 1 offset 5 count 1 offset 6 count 1 offset 7 count 1
 flags 0x2      base-time 200 cycle-time 100000 cycle-time-extension 0
        index 0 cmd S gatemask 0x80 interval 20000
        index 1 cmd S gatemask 0xa0 interval 20000
        index 2 cmd S gatemask 0x5f interval 60000
max-sdu 0 0 0 0 0 200 0 0 0 0 0 0 0 0 0 0

$ tc -j -p qdisc show dev eno0 root
[ {
        "kind": "taprio",
        "handle": "8002:",
        "root": true,
        "options": {
            "tc": 8,
            "map": [ 0,1,2,3,4,5,6,7,0,0,0,0,0,0,0,0 ],
            "queues": [ {
                    "offset": 0,
                    "count": 1
                },{
                    "offset": 1,
                    "count": 1
                },{
                    "offset": 2,
                    "count": 1
                },{
                    "offset": 3,
                    "count": 1
                },{
                    "offset": 4,
                    "count": 1
                },{
                    "offset": 5,
                    "count": 1
                },{
                    "offset": 6,
                    "count": 1
                },{
                    "offset": 7,
                    "count": 1
                } ],
            "flags": "0x2",
            "base_time": 200,
            "cycle_time": 100000,
            "cycle_time_extension": 0,
            "schedule": [ {
                    "index": 0,
                    "cmd": "S",
                    "gatemask": "0x80",
                    "interval": 20000
                },{
                    "index": 1,
                    "cmd": "S",
                    "gatemask": "0xa0",
                    "interval": 20000
                },{
                    "index": 2,
                    "cmd": "S",
                    "gatemask": "0x5f",
                    "interval": 60000
                } ],
            "max-sdu": [ 0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0 ]
        }
    } ]

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David Ahern <dsahern@kernel.org>
This commit is contained in:
Vladimir Oltean 2022-10-28 14:50:53 +03:00 committed by David Ahern
parent 9313ba541f
commit b10a6509c1
2 changed files with 111 additions and 2 deletions

View File

@ -150,6 +150,15 @@ value should always be greater than the delta specified in the
.BR etf(8) .BR etf(8)
qdisc. qdisc.
.TP
max-sdu
.br
Specifies an array containing at most 16 elements, one per traffic class, which
corresponds to the queueMaxSDU table from IEEE 802.1Q-2018. Each array element
represents the maximum L2 payload size that can egress that traffic class.
Elements that are not filled in default to 0. The value 0 means that the
traffic class can send packets up to the port's maximum MTU in size.
.SH EXAMPLES .SH EXAMPLES
The following example shows how an traffic schedule with three traffic The following example shows how an traffic schedule with three traffic
@ -205,17 +214,22 @@ is implicitly calculated as the sum of all
durations (i.e. 20 us + 20 us + 60 us = 100 us). Although the base-time is in durations (i.e. 20 us + 20 us + 60 us = 100 us). Although the base-time is in
the past, the hardware will start executing the schedule at a PTP time equal to the past, the hardware will start executing the schedule at a PTP time equal to
the smallest integer multiple of 100 us, plus 200 ns, that is larger than the the smallest integer multiple of 100 us, plus 200 ns, that is larger than the
NIC's current PTP time. NIC's current PTP time. In addition, the MTU for traffic class 5 is limited to
200 octets, so that the interference this creates upon traffic class 7 during
the time window when their gates are both open is bounded. The interference is
determined by the transmit time of the max SDU, plus the L2 header length, plus
the L1 overhead.
.EX .EX
# tc qdisc add dev eth0 parent root taprio \\ # tc qdisc add dev eth0 parent root taprio \\
num_tc 8 \\ num_tc 8 \\
map 0 1 2 3 4 5 6 7 \\ map 0 1 2 3 4 5 6 7 \\
queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \\ queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \\
max-sdu 0 0 0 0 0 200 0 0 \\
base-time 200 \\ base-time 200 \\
sched-entry S 80 20000 \\ sched-entry S 80 20000 \\
sched-entry S a0 20000 \\ sched-entry S a0 20000 \\
sched-entry S df 60000 \\ sched-entry S 5f 60000 \\
flags 0x2 flags 0x2
.EE .EE

View File

@ -151,13 +151,33 @@ static struct sched_entry *create_entry(uint32_t gatemask, uint32_t interval, ui
return e; return e;
} }
static void add_tc_entries(struct nlmsghdr *n, __u32 max_sdu[TC_QOPT_MAX_QUEUE],
int num_max_sdu_entries)
{
struct rtattr *l;
__u32 tc;
for (tc = 0; tc <= num_max_sdu_entries; tc++) {
l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_TC_ENTRY | NLA_F_NESTED);
addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_INDEX, &tc, sizeof(tc));
addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_MAX_SDU,
&max_sdu[tc], sizeof(max_sdu[tc]));
addattr_nest_end(n, l);
}
}
static int taprio_parse_opt(struct qdisc_util *qu, int argc, static int taprio_parse_opt(struct qdisc_util *qu, int argc,
char **argv, struct nlmsghdr *n, const char *dev) char **argv, struct nlmsghdr *n, const char *dev)
{ {
__u32 max_sdu[TC_QOPT_MAX_QUEUE] = { };
__s32 clockid = CLOCKID_INVALID; __s32 clockid = CLOCKID_INVALID;
struct tc_mqprio_qopt opt = { }; struct tc_mqprio_qopt opt = { };
__s64 cycle_time_extension = 0; __s64 cycle_time_extension = 0;
struct list_head sched_entries; struct list_head sched_entries;
bool have_tc_entries = false;
int num_max_sdu_entries = 0;
struct rtattr *tail, *l; struct rtattr *tail, *l;
__u32 taprio_flags = 0; __u32 taprio_flags = 0;
__u32 txtime_delay = 0; __u32 txtime_delay = 0;
@ -211,6 +231,17 @@ static int taprio_parse_opt(struct qdisc_util *qu, int argc,
free(tmp); free(tmp);
idx++; idx++;
} }
} else if (strcmp(*argv, "max-sdu") == 0) {
while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
NEXT_ARG();
if (get_u32(&max_sdu[idx], *argv, 10)) {
PREV_ARG();
break;
}
num_max_sdu_entries++;
idx++;
}
have_tc_entries = true;
} else if (strcmp(*argv, "sched-entry") == 0) { } else if (strcmp(*argv, "sched-entry") == 0) {
uint32_t mask, interval; uint32_t mask, interval;
struct sched_entry *e; struct sched_entry *e;
@ -341,6 +372,9 @@ static int taprio_parse_opt(struct qdisc_util *qu, int argc,
addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION, addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION,
&cycle_time_extension, sizeof(cycle_time_extension)); &cycle_time_extension, sizeof(cycle_time_extension));
if (have_tc_entries)
add_tc_entries(n, max_sdu, num_max_sdu_entries);
l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST | NLA_F_NESTED); l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST | NLA_F_NESTED);
err = add_sched_list(&sched_entries, n); err = add_sched_list(&sched_entries, n);
@ -430,6 +464,65 @@ static int print_schedule(FILE *f, struct rtattr **tb)
return 0; return 0;
} }
static void dump_tc_entry(__u32 max_sdu[TC_QOPT_MAX_QUEUE],
struct rtattr *item, bool *have_tc_entries,
int *max_tc_index)
{
struct rtattr *tb[TCA_TAPRIO_TC_ENTRY_MAX + 1];
__u32 tc, val = 0;
parse_rtattr_nested(tb, TCA_TAPRIO_TC_ENTRY_MAX, item);
if (!tb[TCA_TAPRIO_TC_ENTRY_INDEX]) {
fprintf(stderr, "Missing tc entry index\n");
return;
}
tc = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_INDEX]);
/* Prevent array out of bounds access */
if (tc >= TC_QOPT_MAX_QUEUE) {
fprintf(stderr, "Unexpected tc entry index %d\n", tc);
return;
}
if (*max_tc_index < tc)
*max_tc_index = tc;
if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU])
val = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]);
max_sdu[tc] = val;
*have_tc_entries = true;
}
static void dump_tc_entries(FILE *f, struct rtattr *opt)
{
__u32 max_sdu[TC_QOPT_MAX_QUEUE] = {};
int tc, rem, max_tc_index = 0;
bool have_tc_entries = false;
struct rtattr *i;
rem = RTA_PAYLOAD(opt);
for (i = RTA_DATA(opt); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
if (i->rta_type != (TCA_TAPRIO_ATTR_TC_ENTRY | NLA_F_NESTED))
continue;
dump_tc_entry(max_sdu, i, &have_tc_entries, &max_tc_index);
}
if (!have_tc_entries)
return;
open_json_array(PRINT_ANY, "max-sdu");
for (tc = 0; tc <= max_tc_index; tc++)
print_uint(PRINT_ANY, NULL, " %u", max_sdu[tc]);
close_json_array(PRINT_ANY, "");
print_nl();
}
static int taprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) static int taprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
{ {
struct rtattr *tb[TCA_TAPRIO_ATTR_MAX + 1]; struct rtattr *tb[TCA_TAPRIO_ATTR_MAX + 1];
@ -503,6 +596,8 @@ static int taprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
close_json_object(); close_json_object();
} }
dump_tc_entries(f, opt);
return 0; return 0;
} }