summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Ahern <dsahern@kernel.org>2023-04-24 19:44:47 -0600
committerDavid Ahern <dsahern@kernel.org>2023-04-24 19:44:47 -0600
commit11f2630a1133e2bd2475c99ed774d5358a9f93b8 (patch)
treec4a25cdf98e6b70924b7e5c407e9d6dcc70bac41
parentf57ac749b0359c9239b09a6c80573d5a94245b5b (diff)
parent5fbca3b469ec3cec84ee092165a51b31150a35e3 (diff)
downloadiproute2-11f2630a1133e2bd2475c99ed774d5358a9f93b8.tar.gz
Merge branch 'preemptible-traffic-classes' into next
Vladimir Oltean says: ==================== This is the iproute2 support for the tc program to make use of the kernel features added in commit f7d29571ab0a ("Merge branch 'add-kernel-tc-mqprio-and-tc-taprio-support-for-preemptible-traffic-classes'"). ==================== Signed-off-by: David Ahern <dsahern@kernel.org>
-rw-r--r--include/utils.h8
-rw-r--r--man/man8/tc-mqprio.836
-rw-r--r--man/man8/tc-taprio.811
-rw-r--r--tc/q_mqprio.c99
-rw-r--r--tc/q_taprio.c100
5 files changed, 227 insertions, 27 deletions
diff --git a/include/utils.h b/include/utils.h
index 2eb80b3e..0f1b3bef 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -284,6 +284,14 @@ unsigned int print_name_and_link(const char *fmt,
_min1 < _min2 ? _min1 : _min2; })
#endif
+#ifndef max
+# define max(x, y) ({ \
+ typeof(x) _max1 = (x); \
+ typeof(y) _max2 = (y); \
+ (void) (&_max1 == &_max2); \
+ _max1 < _max2 ? _max2 : _max1; })
+#endif
+
#ifndef __check_format_string
# define __check_format_string(pos_str, pos_args) \
__attribute__ ((format (printf, (pos_str), (pos_args))))
diff --git a/man/man8/tc-mqprio.8 b/man/man8/tc-mqprio.8
index 3441cb68..724ef906 100644
--- a/man/man8/tc-mqprio.8
+++ b/man/man8/tc-mqprio.8
@@ -30,9 +30,11 @@ dcb|bw_rlimit ]
.B min_rate
min_rate1 min_rate2 ... ] [
.B max_rate
-max_rate1 max_rate2 ...
-.B ]
-
+max_rate1 max_rate2 ... ]
+.ti +8
+[
+.B fp
+FP0 FP1 FP2 ... ]
.SH DESCRIPTION
The MQPRIO qdisc is a simple queuing discipline that allows mapping
@@ -162,6 +164,34 @@ the
argument is set to
.B 'bw_rlimit'.
+.TP
+fp
+Selects whether traffic classes are express (deliver packets via the eMAC) or
+preemptible (deliver packets via the pMAC), according to IEEE 802.1Q-2018
+clause 6.7.2 Frame preemption. Takes the form of an array (one element per
+traffic class) with values being
+.B 'E'
+(for express) or
+.B 'P'
+(for preemptible).
+
+Multiple priorities which map to the same traffic class, as well as multiple
+TXQs which map to the same traffic class, must have the same FP attributes.
+To interpret the FP as an attribute per priority, the
+.B 'map'
+argument can be used for translation. To interpret FP as an attribute per TXQ,
+the
+.B 'queues'
+argument can be used for translation.
+
+Traffic classes are express by default. The argument is supported only with
+.B 'hw'
+set to 1. Preemptible traffic classes are accepted only if the device has a MAC
+Merge layer configurable through
+.BR ethtool(8).
+
+.SH SEE ALSO
+.BR ethtool(8)
.SH EXAMPLE
diff --git a/man/man8/tc-taprio.8 b/man/man8/tc-taprio.8
index c3ccefea..bf489b03 100644
--- a/man/man8/tc-taprio.8
+++ b/man/man8/tc-taprio.8
@@ -36,6 +36,10 @@ clockid
[
.B max-sdu
<queueMaxSDU[TC 0]> <queueMaxSDU[TC 1]> <queueMaxSDU[TC N]> ]
+.ti +8
+[
+.B fp
+<adminStatus[TC 0]> <adminStatus[TC 1]> <adminStatus[TC N]> ]
.SH DESCRIPTION
The TAPRIO qdisc implements a simplified version of the scheduling
@@ -163,6 +167,13 @@ 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.
+.TP
+fp
+.br
+Selects whether traffic classes are express or preemptible. See
+.BR tc-mqprio(8)
+for details.
+
.SH EXAMPLES
The following example shows how an traffic schedule with three traffic
diff --git a/tc/q_mqprio.c b/tc/q_mqprio.c
index 99c43491..7a4417f5 100644
--- a/tc/q_mqprio.c
+++ b/tc/q_mqprio.c
@@ -23,12 +23,29 @@ static void explain(void)
"Usage: ... mqprio [num_tc NUMBER] [map P0 P1 ...]\n"
" [queues count1@offset1 count2@offset2 ...] "
"[hw 1|0]\n"
+ " [fp FP0 FP1 FP2 ...]\n"
" [mode dcb|channel]\n"
" [shaper bw_rlimit SHAPER_PARAMS]\n"
"Where: SHAPER_PARAMS := { min_rate MIN_RATE1 MIN_RATE2 ...|\n"
" max_rate MAX_RATE1 MAX_RATE2 ... }\n");
}
+static void add_tc_entries(struct nlmsghdr *n, __u32 fp[TC_QOPT_MAX_QUEUE],
+ int num_fp_entries)
+{
+ struct rtattr *l;
+ __u32 tc;
+
+ for (tc = 0; tc < num_fp_entries; tc++) {
+ l = addattr_nest(n, 1024, TCA_MQPRIO_TC_ENTRY | NLA_F_NESTED);
+
+ addattr32(n, 1024, TCA_MQPRIO_TC_ENTRY_INDEX, tc);
+ addattr32(n, 1024, TCA_MQPRIO_TC_ENTRY_FP, fp[tc]);
+
+ addattr_nest_end(n, l);
+ }
+}
+
static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
char **argv, struct nlmsghdr *n, const char *dev)
{
@@ -43,7 +60,10 @@ static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
__u64 min_rate64[TC_QOPT_MAX_QUEUE] = {0};
__u64 max_rate64[TC_QOPT_MAX_QUEUE] = {0};
__u16 shaper = TC_MQPRIO_SHAPER_DCB;
+ __u32 fp[TC_QOPT_MAX_QUEUE] = { };
__u16 mode = TC_MQPRIO_MODE_DCB;
+ bool have_tc_entries = false;
+ int num_fp_entries = 0;
int cnt_off_pairs = 0;
struct rtattr *tail;
__u32 flags = 0;
@@ -93,6 +113,21 @@ static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
idx++;
cnt_off_pairs++;
}
+ } else if (strcmp(*argv, "fp") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (strcmp(*argv, "E") == 0) {
+ fp[idx] = TC_FP_EXPRESS;
+ } else if (strcmp(*argv, "P") == 0) {
+ fp[idx] = TC_FP_PREEMPTIBLE;
+ } else {
+ PREV_ARG();
+ break;
+ }
+ num_fp_entries++;
+ idx++;
+ }
+ have_tc_entries = true;
} else if (strcmp(*argv, "hw") == 0) {
NEXT_ARG();
if (get_u8(&opt.hw, *argv, 10)) {
@@ -187,6 +222,9 @@ static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
addattr_l(n, 1024, TCA_MQPRIO_SHAPER,
&shaper, sizeof(shaper));
+ if (have_tc_entries)
+ add_tc_entries(n, fp, num_fp_entries);
+
if (flags & TC_MQPRIO_F_MIN_RATE) {
struct rtattr *start;
@@ -218,6 +256,64 @@ static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
return 0;
}
+static void dump_tc_entry(struct rtattr *rta, __u32 fp[TC_QOPT_MAX_QUEUE],
+ int *max_tc_fp)
+{
+ struct rtattr *tb[TCA_MQPRIO_TC_ENTRY_MAX + 1];
+ __u32 tc, val = 0;
+
+ parse_rtattr_nested(tb, TCA_MQPRIO_TC_ENTRY_MAX, rta);
+
+ if (!tb[TCA_MQPRIO_TC_ENTRY_INDEX]) {
+ fprintf(stderr, "Missing tc entry index\n");
+ return;
+ }
+
+ tc = rta_getattr_u32(tb[TCA_MQPRIO_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 (tb[TCA_MQPRIO_TC_ENTRY_FP]) {
+ val = rta_getattr_u32(tb[TCA_MQPRIO_TC_ENTRY_FP]);
+ fp[tc] = val;
+
+ if (*max_tc_fp < (int)tc)
+ *max_tc_fp = tc;
+ }
+}
+
+static void dump_tc_entries(FILE *f, struct rtattr *opt, int len)
+{
+ __u32 fp[TC_QOPT_MAX_QUEUE] = {};
+ int max_tc_fp = -1;
+ struct rtattr *rta;
+ int tc;
+
+ for (rta = opt; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ if (rta->rta_type != (TCA_MQPRIO_TC_ENTRY | NLA_F_NESTED))
+ continue;
+
+ dump_tc_entry(rta, fp, &max_tc_fp);
+ }
+
+ if (max_tc_fp >= 0) {
+ open_json_array(PRINT_ANY,
+ is_json_context() ? "fp" : "\n fp:");
+ for (tc = 0; tc <= max_tc_fp; tc++) {
+ print_string(PRINT_ANY, NULL, " %s",
+ fp[tc] == TC_FP_PREEMPTIBLE ? "P" :
+ fp[tc] == TC_FP_EXPRESS ? "E" :
+ "?");
+ }
+ close_json_array(PRINT_ANY, "");
+
+ print_nl();
+ }
+}
+
static int mqprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
{
int i;
@@ -309,7 +405,10 @@ static int mqprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
tc_print_rate(PRINT_ANY, NULL, "%s ", max_rate64[i]);
close_json_array(PRINT_ANY, "");
}
+
+ dump_tc_entries(f, RTA_DATA(opt) + RTA_ALIGN(sizeof(*qopt)), len);
}
+
return 0;
}
diff --git a/tc/q_taprio.c b/tc/q_taprio.c
index c0da65fe..bc29710c 100644
--- a/tc/q_taprio.c
+++ b/tc/q_taprio.c
@@ -49,6 +49,7 @@ static void explain(void)
" [queues COUNT@OFFSET COUNT@OFFSET COUNT@OFFSET ...]\n"
" [ [sched-entry index cmd gate-mask interval] ... ]\n"
" [base-time time] [txtime-delay delay]\n"
+ " [fp FP0 FP1 FP2 ...]\n"
"\n"
"CLOCKID must be a valid SYS-V id (i.e. CLOCK_TAI)\n");
}
@@ -148,17 +149,29 @@ static struct sched_entry *create_entry(uint32_t gatemask, uint32_t interval, ui
}
static void add_tc_entries(struct nlmsghdr *n, __u32 max_sdu[TC_QOPT_MAX_QUEUE],
- int num_max_sdu_entries)
+ int num_max_sdu_entries, __u32 fp[TC_QOPT_MAX_QUEUE],
+ int num_fp_entries)
{
struct rtattr *l;
+ int num_tc;
__u32 tc;
- for (tc = 0; tc < num_max_sdu_entries; tc++) {
+ num_tc = max(num_max_sdu_entries, num_fp_entries);
+
+ for (tc = 0; tc < num_tc; 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]));
+
+ if (tc < num_max_sdu_entries) {
+ addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_MAX_SDU,
+ &max_sdu[tc], sizeof(max_sdu[tc]));
+ }
+
+ if (tc < num_fp_entries) {
+ addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_FP, &fp[tc],
+ sizeof(fp[tc]));
+ }
addattr_nest_end(n, l);
}
@@ -168,6 +181,7 @@ static int taprio_parse_opt(struct qdisc_util *qu, int argc,
char **argv, struct nlmsghdr *n, const char *dev)
{
__u32 max_sdu[TC_QOPT_MAX_QUEUE] = { };
+ __u32 fp[TC_QOPT_MAX_QUEUE] = { };
__s32 clockid = CLOCKID_INVALID;
struct tc_mqprio_qopt opt = { };
__s64 cycle_time_extension = 0;
@@ -175,6 +189,7 @@ static int taprio_parse_opt(struct qdisc_util *qu, int argc,
bool have_tc_entries = false;
int num_max_sdu_entries = 0;
struct rtattr *tail, *l;
+ int num_fp_entries = 0;
__u32 taprio_flags = 0;
__u32 txtime_delay = 0;
__s64 cycle_time = 0;
@@ -227,6 +242,23 @@ static int taprio_parse_opt(struct qdisc_util *qu, int argc,
free(tmp);
idx++;
}
+ } else if (strcmp(*argv, "fp") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (strcmp(*argv, "E") == 0) {
+ fp[idx] = TC_FP_EXPRESS;
+ } else if (strcmp(*argv, "P") == 0) {
+ fp[idx] = TC_FP_PREEMPTIBLE;
+ } else {
+ fprintf(stderr,
+ "Illegal \"fp\" value \"%s\", expected \"E\" or \"P\"\n",
+ *argv);
+ return -1;
+ }
+ num_fp_entries++;
+ idx++;
+ }
+ have_tc_entries = true;
} else if (strcmp(*argv, "max-sdu") == 0) {
while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
NEXT_ARG();
@@ -369,7 +401,7 @@ static int taprio_parse_opt(struct qdisc_util *qu, int argc,
&cycle_time_extension, sizeof(cycle_time_extension));
if (have_tc_entries)
- add_tc_entries(n, max_sdu, num_max_sdu_entries);
+ add_tc_entries(n, max_sdu, num_max_sdu_entries, fp, num_fp_entries);
l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST | NLA_F_NESTED);
@@ -460,9 +492,10 @@ static int print_schedule(FILE *f, struct rtattr **tb)
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)
+static void dump_tc_entry(struct rtattr *item,
+ __u32 max_sdu[TC_QOPT_MAX_QUEUE],
+ __u32 fp[TC_QOPT_MAX_QUEUE],
+ int *max_tc_max_sdu, int *max_tc_fp)
{
struct rtattr *tb[TCA_TAPRIO_TC_ENTRY_MAX + 1];
__u32 tc, val = 0;
@@ -481,23 +514,30 @@ static void dump_tc_entry(__u32 max_sdu[TC_QOPT_MAX_QUEUE],
return;
}
- if (*max_tc_index < tc)
- *max_tc_index = tc;
-
- if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU])
+ if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]) {
val = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]);
+ max_sdu[tc] = val;
+ if (*max_tc_max_sdu < (int)tc)
+ *max_tc_max_sdu = tc;
+ }
- max_sdu[tc] = val;
+ if (tb[TCA_TAPRIO_TC_ENTRY_FP]) {
+ val = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_FP]);
+ fp[tc] = val;
- *have_tc_entries = true;
+ if (*max_tc_fp < (int)tc)
+ *max_tc_fp = tc;
+ }
}
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;
+ __u32 fp[TC_QOPT_MAX_QUEUE] = {};
+ int max_tc_max_sdu = -1;
+ int max_tc_fp = -1;
struct rtattr *i;
+ int tc, rem;
rem = RTA_PAYLOAD(opt);
@@ -505,18 +545,30 @@ static void dump_tc_entries(FILE *f, struct rtattr *opt)
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);
+ dump_tc_entry(i, max_sdu, fp, &max_tc_max_sdu, &max_tc_fp);
}
- if (!have_tc_entries)
- return;
+ if (max_tc_max_sdu >= 0) {
+ open_json_array(PRINT_ANY, "max-sdu");
+ for (tc = 0; tc <= max_tc_max_sdu; tc++)
+ print_uint(PRINT_ANY, NULL, " %u", max_sdu[tc]);
+ close_json_array(PRINT_ANY, "");
- 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();
+ }
- print_nl();
+ if (max_tc_fp >= 0) {
+ open_json_array(PRINT_ANY, "fp");
+ for (tc = 0; tc <= max_tc_fp; tc++) {
+ print_string(PRINT_ANY, NULL, " %s",
+ fp[tc] == TC_FP_PREEMPTIBLE ? "P" :
+ fp[tc] == TC_FP_EXPRESS ? "E" :
+ "?");
+ }
+ close_json_array(PRINT_ANY, "");
+
+ print_nl();
+ }
}
static int taprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)