| /* |
| * q_taprio.c Time Aware Priority Scheduler |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2 of the License, or (at your option) any later version. |
| * |
| * 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; |
| }; |
| |
| #define CLOCKID_INVALID (-1) |
| static const struct static_clockid { |
| const char *name; |
| clockid_t clockid; |
| } clockids_sysv[] = { |
| { "REALTIME", CLOCK_REALTIME }, |
| { "TAI", CLOCK_TAI }, |
| { "BOOTTIME", CLOCK_BOOTTIME }, |
| { "MONOTONIC", CLOCK_MONOTONIC }, |
| { NULL } |
| }; |
| |
| static void explain(void) |
| { |
| fprintf(stderr, |
| "Usage: ... taprio clockid CLOCKID\n" |
| " [num_tc NUMBER] [map P0 P1 ...] " |
| " [queues COUNT@OFFSET COUNT@OFFSET COUNT@OFFSET ...] " |
| " [ [sched-entry index cmd gate-mask interval] ... ] " |
| " [base-time time] [txtime-delay delay]" |
| "\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 int get_clockid(__s32 *val, const char *arg) |
| { |
| const struct static_clockid *c; |
| |
| /* Drop the CLOCK_ prefix if that is being used. */ |
| if (strcasestr(arg, "CLOCK_") != NULL) |
| arg += sizeof("CLOCK_") - 1; |
| |
| for (c = clockids_sysv; c->name; c++) { |
| if (strcasecmp(c->name, arg) == 0) { |
| *val = c->clockid; |
| |
| return 0; |
| } |
| } |
| |
| return -1; |
| } |
| |
| static const char* get_clock_name(clockid_t clockid) |
| { |
| const struct static_clockid *c; |
| |
| for (c = clockids_sysv; c->name; c++) { |
| if (clockid == c->clockid) |
| return c->name; |
| } |
| |
| return "invalid"; |
| } |
| |
| 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 int taprio_parse_opt(struct qdisc_util *qu, int argc, |
| char **argv, struct nlmsghdr *n, const char *dev) |
| { |
| __s32 clockid = CLOCKID_INVALID; |
| struct tc_mqprio_qopt opt = { }; |
| __s64 cycle_time_extension = 0; |
| struct list_head sched_entries; |
| struct rtattr *tail, *l; |
| __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, "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)); |
| |
| 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 int print_sched_list(FILE *f, struct rtattr *list) |
| { |
| struct rtattr *item; |
| int rem; |
| |
| if (list == NULL) |
| return 0; |
| |
| rem = RTA_PAYLOAD(list); |
| |
| open_json_array(PRINT_JSON, "schedule"); |
| |
| print_string(PRINT_FP, NULL, "%s", _SL_); |
| |
| for (item = RTA_DATA(list); RTA_OK(item, rem); item = RTA_NEXT(item, rem)) { |
| struct rtattr *tb[TCA_TAPRIO_SCHED_ENTRY_MAX + 1]; |
| __u32 index = 0, gatemask = 0, interval = 0; |
| __u8 command = 0; |
| |
| parse_rtattr_nested(tb, TCA_TAPRIO_SCHED_ENTRY_MAX, item); |
| |
| if (tb[TCA_TAPRIO_SCHED_ENTRY_INDEX]) |
| index = rta_getattr_u32(tb[TCA_TAPRIO_SCHED_ENTRY_INDEX]); |
| |
| if (tb[TCA_TAPRIO_SCHED_ENTRY_CMD]) |
| command = rta_getattr_u8(tb[TCA_TAPRIO_SCHED_ENTRY_CMD]); |
| |
| if (tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]) |
| gatemask = rta_getattr_u32(tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]); |
| |
| if (tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]) |
| interval = rta_getattr_u32(tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]); |
| |
| open_json_object(NULL); |
| print_uint(PRINT_ANY, "index", "\tindex %u", index); |
| print_string(PRINT_ANY, "cmd", " cmd %s", entry_cmd_to_str(command)); |
| print_0xhex(PRINT_ANY, "gatemask", " gatemask %#llx", gatemask); |
| print_uint(PRINT_ANY, "interval", " interval %u", interval); |
| close_json_object(); |
| |
| print_string(PRINT_FP, NULL, "%s", _SL_); |
| } |
| |
| close_json_array(PRINT_ANY, ""); |
| |
| return 0; |
| } |
| |
| static int print_schedule(FILE *f, struct rtattr **tb) |
| { |
| int64_t base_time = 0, cycle_time = 0, cycle_time_extension = 0; |
| |
| if (tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]) |
| base_time = rta_getattr_s64(tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]); |
| |
| if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]) |
| cycle_time = rta_getattr_s64(tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]); |
| |
| if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]) |
| cycle_time_extension = rta_getattr_s64( |
| tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]); |
| |
| print_lluint(PRINT_ANY, "base_time", "\tbase-time %lld", base_time); |
| |
| print_lluint(PRINT_ANY, "cycle_time", " cycle-time %lld", cycle_time); |
| |
| print_lluint(PRINT_ANY, "cycle_time_extension", |
| " cycle-time-extension %lld", cycle_time_extension); |
| |
| print_sched_list(f, tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST]); |
| |
| return 0; |
| } |
| |
| 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; |
| __s32 clockid = CLOCKID_INVALID; |
| 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_string(PRINT_FP, NULL, "%s", _SL_); |
| |
| 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_string(PRINT_FP, NULL, "%s", _SL_); |
| |
| if (tb[TCA_TAPRIO_ATTR_SCHED_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(NULL); |
| |
| print_schedule(f, t); |
| |
| close_json_object(); |
| } |
| |
| return 0; |
| } |
| |
| struct qdisc_util taprio_qdisc_util = { |
| .id = "taprio", |
| .parse_qopt = taprio_parse_opt, |
| .print_qopt = taprio_print_opt, |
| }; |