summaryrefslogtreecommitdiffstats
path: root/tc/q_taprio.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tc/q_taprio.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/tc/q_taprio.c b/tc/q_taprio.c
new file mode 100644
index 0000000..c47fe24
--- /dev/null
+++ b/tc/q_taprio.c
@@ -0,0 +1,654 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * q_taprio.c Time Aware Priority Scheduler
+ *
+ * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com>
+ * Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "list.h"
+
+struct sched_entry {
+ struct list_head list;
+ uint32_t index;
+ uint32_t interval;
+ uint32_t gatemask;
+ uint8_t cmd;
+};
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... taprio clockid CLOCKID\n"
+ " [num_tc NUMBER] [map P0 P1 ...]\n"
+ " [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");
+}
+
+static void explain_clockid(const char *val)
+{
+ fprintf(stderr, "taprio: illegal value for \"clockid\": \"%s\".\n", val);
+ fprintf(stderr, "It must be a valid SYS-V id (i.e. CLOCK_TAI)\n");
+}
+
+static const char *entry_cmd_to_str(__u8 cmd)
+{
+ switch (cmd) {
+ case TC_TAPRIO_CMD_SET_GATES:
+ return "S";
+ default:
+ return "Invalid";
+ }
+}
+
+static int str_to_entry_cmd(const char *str)
+{
+ if (strcmp(str, "S") == 0)
+ return TC_TAPRIO_CMD_SET_GATES;
+
+ return -1;
+}
+
+static int add_sched_list(struct list_head *sched_entries, struct nlmsghdr *n)
+{
+ struct sched_entry *e;
+
+ list_for_each_entry(e, sched_entries, list) {
+ struct rtattr *a;
+
+ a = addattr_nest(n, 1024, TCA_TAPRIO_SCHED_ENTRY);
+
+ addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_CMD, &e->cmd, sizeof(e->cmd));
+ addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_GATE_MASK, &e->gatemask, sizeof(e->gatemask));
+ addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_INTERVAL, &e->interval, sizeof(e->interval));
+
+ addattr_nest_end(n, a);
+ }
+
+ return 0;
+}
+
+static void explain_sched_entry(void)
+{
+ fprintf(stderr, "Usage: ... taprio ... sched-entry <cmd> <gate mask> <interval>\n");
+}
+
+static struct sched_entry *create_entry(uint32_t gatemask, uint32_t interval, uint8_t cmd)
+{
+ struct sched_entry *e;
+
+ e = calloc(1, sizeof(*e));
+ if (!e)
+ return NULL;
+
+ e->gatemask = gatemask;
+ e->interval = interval;
+ e->cmd = cmd;
+
+ return e;
+}
+
+static void add_tc_entries(struct nlmsghdr *n, __u32 max_sdu[TC_QOPT_MAX_QUEUE],
+ int num_max_sdu_entries, __u32 fp[TC_QOPT_MAX_QUEUE],
+ int num_fp_entries)
+{
+ struct rtattr *l;
+ int num_tc;
+ __u32 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));
+
+ 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);
+ }
+}
+
+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;
+ struct list_head sched_entries;
+ 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;
+ __s64 base_time = 0;
+ int err, idx;
+
+ INIT_LIST_HEAD(&sched_entries);
+
+ while (argc > 0) {
+ idx = 0;
+ if (strcmp(*argv, "num_tc") == 0) {
+ NEXT_ARG();
+ if (get_u8(&opt.num_tc, *argv, 10)) {
+ fprintf(stderr, "Illegal \"num_tc\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "map") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (get_u8(&opt.prio_tc_map[idx], *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ idx++;
+ }
+ for ( ; idx < TC_QOPT_MAX_QUEUE; idx++)
+ opt.prio_tc_map[idx] = 0;
+ } else if (strcmp(*argv, "queues") == 0) {
+ char *tmp, *tok;
+
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+
+ tmp = strdup(*argv);
+ if (!tmp)
+ break;
+
+ tok = strtok(tmp, "@");
+ if (get_u16(&opt.count[idx], tok, 10)) {
+ free(tmp);
+ PREV_ARG();
+ break;
+ }
+ tok = strtok(NULL, "@");
+ if (get_u16(&opt.offset[idx], tok, 10)) {
+ free(tmp);
+ PREV_ARG();
+ break;
+ }
+ 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 {
+ PREV_ARG();
+ break;
+ }
+ 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();
+ 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) {
+ uint32_t mask, interval;
+ struct sched_entry *e;
+ uint8_t cmd;
+
+ NEXT_ARG();
+ err = str_to_entry_cmd(*argv);
+ if (err < 0) {
+ explain_sched_entry();
+ return -1;
+ }
+ cmd = err;
+
+ NEXT_ARG();
+ if (get_u32(&mask, *argv, 16)) {
+ explain_sched_entry();
+ return -1;
+ }
+
+ NEXT_ARG();
+ if (get_u32(&interval, *argv, 0)) {
+ explain_sched_entry();
+ return -1;
+ }
+
+ e = create_entry(mask, interval, cmd);
+ if (!e) {
+ fprintf(stderr, "taprio: not enough memory for new schedule entry\n");
+ return -1;
+ }
+
+ list_add_tail(&e->list, &sched_entries);
+
+ } else if (strcmp(*argv, "base-time") == 0) {
+ NEXT_ARG();
+ if (get_s64(&base_time, *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ } else if (strcmp(*argv, "cycle-time") == 0) {
+ NEXT_ARG();
+ if (cycle_time) {
+ fprintf(stderr, "taprio: duplicate \"cycle-time\" specification\n");
+ return -1;
+ }
+
+ if (get_s64(&cycle_time, *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+
+ } else if (strcmp(*argv, "cycle-time-extension") == 0) {
+ NEXT_ARG();
+ if (cycle_time_extension) {
+ fprintf(stderr, "taprio: duplicate \"cycle-time-extension\" specification\n");
+ return -1;
+ }
+
+ if (get_s64(&cycle_time_extension, *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ } else if (strcmp(*argv, "clockid") == 0) {
+ NEXT_ARG();
+ if (clockid != CLOCKID_INVALID) {
+ fprintf(stderr, "taprio: duplicate \"clockid\" specification\n");
+ return -1;
+ }
+ if (get_clockid(&clockid, *argv)) {
+ explain_clockid(*argv);
+ return -1;
+ }
+ } else if (strcmp(*argv, "flags") == 0) {
+ NEXT_ARG();
+ if (taprio_flags) {
+ fprintf(stderr, "taprio: duplicate \"flags\" specification\n");
+ return -1;
+ }
+ if (get_u32(&taprio_flags, *argv, 0)) {
+ PREV_ARG();
+ return -1;
+ }
+
+ } else if (strcmp(*argv, "txtime-delay") == 0) {
+ NEXT_ARG();
+ if (txtime_delay != 0) {
+ fprintf(stderr, "taprio: duplicate \"txtime-delay\" specification\n");
+ return -1;
+ }
+ if (get_u32(&txtime_delay, *argv, 0)) {
+ PREV_ARG();
+ return -1;
+ }
+
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "Unknown argument\n");
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+
+ if (clockid != CLOCKID_INVALID)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CLOCKID, &clockid, sizeof(clockid));
+
+ if (taprio_flags)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_FLAGS, &taprio_flags, sizeof(taprio_flags));
+
+ if (opt.num_tc > 0)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_PRIOMAP, &opt, sizeof(opt));
+
+ if (txtime_delay)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_TXTIME_DELAY, &txtime_delay, sizeof(txtime_delay));
+
+ if (base_time)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_BASE_TIME, &base_time, sizeof(base_time));
+
+ if (cycle_time)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME,
+ &cycle_time, sizeof(cycle_time));
+
+ if (cycle_time_extension)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION,
+ &cycle_time_extension, sizeof(cycle_time_extension));
+
+ if (have_tc_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);
+
+ err = add_sched_list(&sched_entries, n);
+ if (err < 0) {
+ fprintf(stderr, "Could not add schedule to netlink message\n");
+ return -1;
+ }
+
+ addattr_nest_end(n, l);
+
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+ return 0;
+}
+
+static void print_sched_list(FILE *f, struct rtattr *list)
+{
+ struct rtattr *item, *nla;
+ int rem;
+
+ rem = RTA_PAYLOAD(list);
+
+ open_json_array(PRINT_JSON, "schedule");
+
+ print_nl();
+
+ for (item = RTA_DATA(list); RTA_OK(item, rem); item = RTA_NEXT(item, rem)) {
+ struct rtattr *tb[TCA_TAPRIO_SCHED_ENTRY_MAX + 1];
+
+ parse_rtattr_nested(tb, TCA_TAPRIO_SCHED_ENTRY_MAX, item);
+
+ open_json_object(NULL);
+
+ nla = tb[TCA_TAPRIO_SCHED_ENTRY_INDEX];
+ if (nla) {
+ __u32 index = rta_getattr_u32(nla);
+
+ print_uint(PRINT_ANY, "index", "\tindex %u", index);
+ }
+
+ nla = tb[TCA_TAPRIO_SCHED_ENTRY_CMD];
+ if (nla) {
+ __u8 command = rta_getattr_u8(nla);
+
+ print_string(PRINT_ANY, "cmd", " cmd %s",
+ entry_cmd_to_str(command));
+ }
+
+ nla = tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK];
+ if (nla) {
+ __u32 gatemask = rta_getattr_u32(nla);
+
+ print_0xhex(PRINT_ANY, "gatemask", " gatemask %#llx",
+ gatemask);
+ }
+
+ nla = tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL];
+ if (nla) {
+ __u32 interval = rta_getattr_u32(nla);
+
+ print_uint(PRINT_ANY, "interval", " interval %u",
+ interval);
+ }
+
+ close_json_object();
+
+ print_nl();
+ }
+
+ close_json_array(PRINT_ANY, "");
+}
+
+static int print_schedule(FILE *f, struct rtattr **tb)
+{
+ struct rtattr *nla;
+
+ nla = tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME];
+ if (nla) {
+ int64_t base_time = rta_getattr_s64(nla);
+
+ print_lluint(PRINT_ANY, "base_time", "\tbase-time %lld",
+ base_time);
+ }
+
+ nla = tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME];
+ if (nla) {
+ int64_t cycle_time = rta_getattr_s64(nla);
+
+ print_lluint(PRINT_ANY, "cycle_time", " cycle-time %lld",
+ cycle_time);
+ }
+
+ nla = tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION];
+ if (nla) {
+ int64_t cycle_time_extension = rta_getattr_s64(nla);
+
+ print_lluint(PRINT_ANY, "cycle_time_extension",
+ " cycle-time-extension %lld",
+ cycle_time_extension);
+ }
+
+ nla = tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST];
+ if (nla)
+ print_sched_list(f, nla);
+
+ return 0;
+}
+
+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;
+
+ 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 (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;
+ }
+
+ if (tb[TCA_TAPRIO_TC_ENTRY_FP]) {
+ val = rta_getattr_u32(tb[TCA_TAPRIO_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)
+{
+ __u32 max_sdu[TC_QOPT_MAX_QUEUE] = {};
+ __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);
+
+ 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(i, max_sdu, fp, &max_tc_max_sdu, &max_tc_fp);
+ }
+
+ 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, "");
+
+ 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)
+{
+ struct rtattr *tb[TCA_TAPRIO_ATTR_MAX + 1];
+ struct tc_mqprio_qopt *qopt = 0;
+ int i;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_TAPRIO_ATTR_MAX, opt);
+
+ if (tb[TCA_TAPRIO_ATTR_PRIOMAP] == NULL)
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_TAPRIO_ATTR_PRIOMAP]);
+
+ print_uint(PRINT_ANY, "tc", "tc %u ", qopt->num_tc);
+
+ open_json_array(PRINT_ANY, "map");
+ for (i = 0; i <= TC_PRIO_MAX; i++)
+ print_uint(PRINT_ANY, NULL, " %u", qopt->prio_tc_map[i]);
+ close_json_array(PRINT_ANY, "");
+
+ print_nl();
+
+ open_json_array(PRINT_ANY, "queues");
+ for (i = 0; i < qopt->num_tc; i++) {
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "offset", " offset %u", qopt->offset[i]);
+ print_uint(PRINT_ANY, "count", " count %u", qopt->count[i]);
+ close_json_object();
+ }
+ close_json_array(PRINT_ANY, "");
+
+ print_nl();
+
+ if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]) {
+ __s32 clockid;
+
+ clockid = rta_getattr_s32(tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]);
+ print_string(PRINT_ANY, "clockid", "clockid %s",
+ get_clock_name(clockid));
+ }
+
+ if (tb[TCA_TAPRIO_ATTR_FLAGS]) {
+ __u32 flags;
+
+ flags = rta_getattr_u32(tb[TCA_TAPRIO_ATTR_FLAGS]);
+ print_0xhex(PRINT_ANY, "flags", " flags %#x", flags);
+ }
+
+ if (tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]) {
+ __u32 txtime_delay;
+
+ txtime_delay = rta_getattr_s32(tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]);
+ print_uint(PRINT_ANY, "txtime_delay", " txtime delay %d", txtime_delay);
+ }
+
+ print_schedule(f, tb);
+
+ if (tb[TCA_TAPRIO_ATTR_ADMIN_SCHED]) {
+ struct rtattr *t[TCA_TAPRIO_ATTR_MAX + 1];
+
+ parse_rtattr_nested(t, TCA_TAPRIO_ATTR_MAX,
+ tb[TCA_TAPRIO_ATTR_ADMIN_SCHED]);
+
+ open_json_object("admin");
+
+ print_schedule(f, t);
+
+ close_json_object();
+ }
+
+ dump_tc_entries(f, opt);
+
+ return 0;
+}
+
+static int taprio_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct rtattr *tb[TCA_TAPRIO_OFFLOAD_STATS_MAX + 1], *nla;
+
+ if (!xstats)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_TAPRIO_OFFLOAD_STATS_MAX, xstats);
+
+ nla = tb[TCA_TAPRIO_OFFLOAD_STATS_WINDOW_DROPS];
+ if (nla)
+ print_lluint(PRINT_ANY, "window_drops", " window_drops %llu",
+ rta_getattr_u64(nla));
+
+ nla = tb[TCA_TAPRIO_OFFLOAD_STATS_TX_OVERRUNS];
+ if (nla)
+ print_lluint(PRINT_ANY, "tx_overruns", " tx_overruns %llu",
+ rta_getattr_u64(nla));
+
+ return 0;
+}
+
+struct qdisc_util taprio_qdisc_util = {
+ .id = "taprio",
+ .parse_qopt = taprio_parse_opt,
+ .print_qopt = taprio_print_opt,
+ .print_xstats = taprio_print_xstats,
+};