summaryrefslogtreecommitdiffstats
path: root/dcb
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dcb/.gitignore1
-rw-r--r--dcb/Makefile25
-rw-r--r--dcb/dcb.c616
-rw-r--r--dcb/dcb.h81
-rw-r--r--dcb/dcb_app.c797
-rw-r--r--dcb/dcb_buffer.c235
-rw-r--r--dcb/dcb_dcbx.c192
-rw-r--r--dcb/dcb_ets.c435
-rw-r--r--dcb/dcb_maxrate.c182
-rw-r--r--dcb/dcb_pfc.c286
10 files changed, 2850 insertions, 0 deletions
diff --git a/dcb/.gitignore b/dcb/.gitignore
new file mode 100644
index 0000000..3f26856
--- /dev/null
+++ b/dcb/.gitignore
@@ -0,0 +1 @@
+dcb
diff --git a/dcb/Makefile b/dcb/Makefile
new file mode 100644
index 0000000..ca65d46
--- /dev/null
+++ b/dcb/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+DCBOBJ = dcb.o \
+ dcb_app.o \
+ dcb_buffer.o \
+ dcb_dcbx.o \
+ dcb_ets.o \
+ dcb_maxrate.o \
+ dcb_pfc.o
+TARGETS += dcb
+LDLIBS += -lm
+
+all: $(TARGETS) $(LIBS)
+
+dcb: $(DCBOBJ) $(LIBNETLINK)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ for i in $(TARGETS); \
+ do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \
+ done
+
+clean:
+ rm -f $(DCBOBJ) $(TARGETS)
diff --git a/dcb/dcb.c b/dcb/dcb.c
new file mode 100644
index 0000000..391fd95
--- /dev/null
+++ b/dcb/dcb.c
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+#include <libmnl/libmnl.h>
+#include <getopt.h>
+
+#include "dcb.h"
+#include "mnl_utils.h"
+#include "namespace.h"
+#include "utils.h"
+#include "version.h"
+
+static int dcb_init(struct dcb *dcb)
+{
+ dcb->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
+ if (dcb->buf == NULL) {
+ perror("Netlink buffer allocation");
+ return -1;
+ }
+
+ dcb->nl = mnlu_socket_open(NETLINK_ROUTE);
+ if (dcb->nl == NULL) {
+ perror("Open netlink socket");
+ goto err_socket_open;
+ }
+
+ new_json_obj_plain(dcb->json_output);
+ return 0;
+
+err_socket_open:
+ free(dcb->buf);
+ return -1;
+}
+
+static void dcb_fini(struct dcb *dcb)
+{
+ delete_json_obj_plain();
+ mnl_socket_close(dcb->nl);
+ free(dcb->buf);
+}
+
+static struct dcb *dcb_alloc(void)
+{
+ struct dcb *dcb;
+
+ dcb = calloc(1, sizeof(*dcb));
+ if (!dcb)
+ return NULL;
+ return dcb;
+}
+
+static void dcb_free(struct dcb *dcb)
+{
+ free(dcb);
+}
+
+struct dcb_get_attribute {
+ struct dcb *dcb;
+ int attr;
+ void *payload;
+ __u16 payload_len;
+};
+
+static int dcb_get_attribute_attr_ieee_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_get_attribute *ga = data;
+
+ if (mnl_attr_get_type(attr) != ga->attr)
+ return MNL_CB_OK;
+
+ ga->payload = mnl_attr_get_payload(attr);
+ ga->payload_len = mnl_attr_get_payload_len(attr);
+ return MNL_CB_STOP;
+}
+
+static int dcb_get_attribute_attr_cb(const struct nlattr *attr, void *data)
+{
+ if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE)
+ return MNL_CB_OK;
+
+ return mnl_attr_parse_nested(attr, dcb_get_attribute_attr_ieee_cb, data);
+}
+
+static int dcb_get_attribute_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_get_attribute_attr_cb, data);
+}
+
+static int dcb_get_attribute_bare_cb(const struct nlmsghdr *nlh, void *data)
+{
+ /* Bare attributes (e.g. DCB_ATTR_DCBX) are not wrapped inside an IEEE
+ * container, so this does not have to go through unpacking in
+ * dcb_get_attribute_attr_cb().
+ */
+ return mnl_attr_parse(nlh, sizeof(struct dcbmsg),
+ dcb_get_attribute_attr_ieee_cb, data);
+}
+
+struct dcb_set_attribute_response {
+ int response_attr;
+};
+
+static int dcb_set_attribute_attr_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_set_attribute_response *resp = data;
+ uint16_t len;
+ int8_t err;
+
+ if (mnl_attr_get_type(attr) != resp->response_attr)
+ return MNL_CB_OK;
+
+ len = mnl_attr_get_payload_len(attr);
+ if (len != 1) {
+ fprintf(stderr, "Response attribute expected to have size 1, not %d\n", len);
+ return MNL_CB_ERROR;
+ }
+
+ /* The attribute is formally u8, but actually an i8 containing a
+ * negative errno value.
+ */
+ err = mnl_attr_get_u8(attr);
+ if (err) {
+ errno = -err;
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_STOP;
+}
+
+static int dcb_set_attribute_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_set_attribute_attr_cb, data);
+}
+
+static int dcb_talk(struct dcb *dcb, struct nlmsghdr *nlh, mnl_cb_t cb, void *data)
+{
+ int ret;
+
+ ret = mnl_socket_sendto(dcb->nl, nlh, nlh->nlmsg_len);
+ if (ret < 0) {
+ perror("mnl_socket_sendto");
+ return -1;
+ }
+
+ return mnlu_socket_recv_run(dcb->nl, nlh->nlmsg_seq, dcb->buf, MNL_SOCKET_BUFFER_SIZE,
+ cb, data);
+}
+
+static struct nlmsghdr *dcb_prepare(struct dcb *dcb, const char *dev,
+ uint32_t nlmsg_type, uint8_t dcb_cmd)
+{
+ struct dcbmsg dcbm = {
+ .cmd = dcb_cmd,
+ };
+ struct nlmsghdr *nlh;
+
+ nlh = mnlu_msg_prepare(dcb->buf, nlmsg_type, NLM_F_REQUEST | NLM_F_ACK,
+ &dcbm, sizeof(dcbm));
+ mnl_attr_put_strz(nlh, DCB_ATTR_IFNAME, dev);
+ return nlh;
+}
+
+static int __dcb_get_attribute(struct dcb *dcb, int command,
+ const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p,
+ int (*get_attribute_cb)(const struct nlmsghdr *nlh,
+ void *data))
+{
+ struct dcb_get_attribute ga;
+ struct nlmsghdr *nlh;
+ int ret;
+
+ nlh = dcb_prepare(dcb, dev, RTM_GETDCB, command);
+
+ ga = (struct dcb_get_attribute) {
+ .dcb = dcb,
+ .attr = attr,
+ .payload = NULL,
+ };
+ ret = dcb_talk(dcb, nlh, get_attribute_cb, &ga);
+ if (ret) {
+ perror("Attribute read");
+ return ret;
+ }
+ if (ga.payload == NULL) {
+ perror("Attribute not found");
+ return -ENOENT;
+ }
+
+ *payload_p = ga.payload;
+ *payload_len_p = ga.payload_len;
+ return 0;
+}
+
+int dcb_get_attribute_va(struct dcb *dcb, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p)
+{
+ return __dcb_get_attribute(dcb, DCB_CMD_IEEE_GET, dev, attr,
+ payload_p, payload_len_p,
+ dcb_get_attribute_cb);
+}
+
+int dcb_get_attribute_bare(struct dcb *dcb, int cmd, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p)
+{
+ return __dcb_get_attribute(dcb, cmd, dev, attr,
+ payload_p, payload_len_p,
+ dcb_get_attribute_bare_cb);
+}
+
+int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr, void *data, size_t data_len)
+{
+ __u16 payload_len;
+ void *payload;
+ int ret;
+
+ ret = dcb_get_attribute_va(dcb, dev, attr, &payload, &payload_len);
+ if (ret)
+ return ret;
+
+ if (payload_len != data_len) {
+ fprintf(stderr, "Wrong len %d, expected %zd\n", payload_len, data_len);
+ return -EINVAL;
+ }
+
+ memcpy(data, payload, data_len);
+ return 0;
+}
+
+static int __dcb_set_attribute(struct dcb *dcb, int command, const char *dev,
+ int (*cb)(struct dcb *, struct nlmsghdr *, void *),
+ void *data, int response_attr)
+{
+ struct dcb_set_attribute_response resp = {
+ .response_attr = response_attr,
+ };
+ struct nlmsghdr *nlh;
+ int ret;
+
+ nlh = dcb_prepare(dcb, dev, RTM_SETDCB, command);
+
+ ret = cb(dcb, nlh, data);
+ if (ret)
+ return ret;
+
+ errno = 0;
+ ret = dcb_talk(dcb, nlh, dcb_set_attribute_cb, &resp);
+ if (ret) {
+ if (errno)
+ perror("Attribute write");
+ return ret;
+ }
+ return 0;
+}
+
+struct dcb_set_attribute_ieee_cb {
+ int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data);
+ void *data;
+};
+
+static int dcb_set_attribute_ieee_cb(struct dcb *dcb, struct nlmsghdr *nlh, void *data)
+{
+ struct dcb_set_attribute_ieee_cb *ieee_data = data;
+ struct nlattr *nest;
+ int ret;
+
+ nest = mnl_attr_nest_start(nlh, DCB_ATTR_IEEE);
+ ret = ieee_data->cb(dcb, nlh, ieee_data->data);
+ if (ret)
+ return ret;
+ mnl_attr_nest_end(nlh, nest);
+
+ return 0;
+}
+
+int dcb_set_attribute_va(struct dcb *dcb, int command, const char *dev,
+ int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data),
+ void *data)
+{
+ struct dcb_set_attribute_ieee_cb ieee_data = {
+ .cb = cb,
+ .data = data,
+ };
+
+ return __dcb_set_attribute(dcb, command, dev,
+ &dcb_set_attribute_ieee_cb, &ieee_data,
+ DCB_ATTR_IEEE);
+}
+
+struct dcb_set_attribute {
+ int attr;
+ const void *data;
+ size_t data_len;
+};
+
+static int dcb_set_attribute_put(struct dcb *dcb, struct nlmsghdr *nlh, void *data)
+{
+ struct dcb_set_attribute *dsa = data;
+
+ mnl_attr_put(nlh, dsa->attr, dsa->data_len, dsa->data);
+ return 0;
+}
+
+int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr, const void *data, size_t data_len)
+{
+ struct dcb_set_attribute dsa = {
+ .attr = attr,
+ .data = data,
+ .data_len = data_len,
+ };
+
+ return dcb_set_attribute_va(dcb, DCB_CMD_IEEE_SET, dev,
+ &dcb_set_attribute_put, &dsa);
+}
+
+int dcb_set_attribute_bare(struct dcb *dcb, int command, const char *dev,
+ int attr, const void *data, size_t data_len,
+ int response_attr)
+{
+ struct dcb_set_attribute dsa = {
+ .attr = attr,
+ .data = data,
+ .data_len = data_len,
+ };
+
+ return __dcb_set_attribute(dcb, command, dev,
+ &dcb_set_attribute_put, &dsa, response_attr);
+}
+
+void dcb_print_array_u8(const __u8 *array, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ print_uint(PRINT_JSON, NULL, NULL, array[i]);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_uint(PRINT_FP, NULL, "%d ", array[i]);
+ }
+}
+
+void dcb_print_array_u64(const __u64 *array, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ print_u64(PRINT_JSON, NULL, NULL, array[i]);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_u64(PRINT_FP, NULL, "%" PRIu64 " ", array[i]);
+ }
+}
+
+void dcb_print_array_on_off(const __u8 *array, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ print_on_off(PRINT_JSON, NULL, NULL, array[i]);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_on_off(PRINT_FP, NULL, "%s ", array[i]);
+ }
+}
+
+void dcb_print_array_kw(const __u8 *array, size_t array_size,
+ const char *const kw[], size_t kw_size)
+{
+ size_t i;
+
+ for (i = 0; i < array_size; i++) {
+ const char *str = "???";
+ __u8 emt = array[i];
+
+ if (emt < kw_size && kw[emt])
+ str = kw[emt];
+ print_string(PRINT_JSON, NULL, NULL, str);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_string(PRINT_FP, NULL, "%s ", str);
+ }
+}
+
+void dcb_print_named_array(const char *json_name, const char *fp_name,
+ const __u8 *array, size_t size,
+ void (*print_array)(const __u8 *, size_t))
+{
+ open_json_array(PRINT_JSON, json_name);
+ print_string(PRINT_FP, NULL, "%s ", fp_name);
+ print_array(array, size);
+ close_json_array(PRINT_JSON, json_name);
+}
+
+int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key,
+ const char *what_value, __u64 value, __u64 max_value,
+ void (*set_array)(__u32 index, __u64 value, void *data),
+ void *set_array_data)
+{
+ bool is_all = key == (__u32) -1;
+
+ if (!is_all && key > max_key) {
+ fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%d\n",
+ what_key, what_value, what_key, max_key);
+ return -EINVAL;
+ }
+
+ if (value > max_value) {
+ fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%llu\n",
+ what_key, what_value, what_value, max_value);
+ return -EINVAL;
+ }
+
+ if (is_all) {
+ for (key = 0; key <= max_key; key++)
+ set_array(key, value, set_array_data);
+ } else {
+ set_array(key, value, set_array_data);
+ }
+
+ return 0;
+}
+
+void dcb_set_u8(__u32 key, __u64 value, void *data)
+{
+ __u8 *array = data;
+
+ array[key] = value;
+}
+
+void dcb_set_u32(__u32 key, __u64 value, void *data)
+{
+ __u32 *array = data;
+
+ array[key] = value;
+}
+
+void dcb_set_u64(__u32 key, __u64 value, void *data)
+{
+ __u64 *array = data;
+
+ array[key] = value;
+}
+
+int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv,
+ int (*and_then)(struct dcb *dcb, const char *dev,
+ int argc, char **argv),
+ void (*help)(void))
+{
+ const char *dev;
+
+ if (!argc || matches(*argv, "help") == 0) {
+ help();
+ return 0;
+ } else if (matches(*argv, "dev") == 0) {
+ NEXT_ARG();
+ dev = *argv;
+ if (check_ifname(dev)) {
+ invarg("not a valid ifname", *argv);
+ return -EINVAL;
+ }
+ NEXT_ARG_FWD();
+ return and_then(dcb, dev, argc, argv);
+ } else {
+ fprintf(stderr, "Expected `dev DEV', not `%s'", *argv);
+ help();
+ return -EINVAL;
+ }
+}
+
+static void dcb_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb [ OPTIONS ] OBJECT { COMMAND | help }\n"
+ " dcb [ -f | --force ] { -b | --batch } filename [ -n | --netns ] netnsname\n"
+ "where OBJECT := { app | buffer | dcbx | ets | maxrate | pfc }\n"
+ " OPTIONS := [ -V | --Version | -i | --iec | -j | --json\n"
+ " | -N | --Numeric | -p | --pretty\n"
+ " | -s | --statistics | -v | --verbose]\n");
+}
+
+static int dcb_cmd(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_help();
+ return 0;
+ } else if (matches(*argv, "app") == 0) {
+ return dcb_cmd_app(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "buffer") == 0) {
+ return dcb_cmd_buffer(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "dcbx") == 0) {
+ return dcb_cmd_dcbx(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "ets") == 0) {
+ return dcb_cmd_ets(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "maxrate") == 0) {
+ return dcb_cmd_maxrate(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "pfc") == 0) {
+ return dcb_cmd_pfc(dcb, argc - 1, argv + 1);
+ }
+
+ fprintf(stderr, "Object \"%s\" is unknown\n", *argv);
+ return -ENOENT;
+}
+
+static int dcb_batch_cmd(int argc, char *argv[], void *data)
+{
+ struct dcb *dcb = data;
+
+ return dcb_cmd(dcb, argc, argv);
+}
+
+static int dcb_batch(struct dcb *dcb, const char *name, bool force)
+{
+ return do_batch(name, force, dcb_batch_cmd, dcb);
+}
+
+int main(int argc, char **argv)
+{
+ static const struct option long_options[] = {
+ { "Version", no_argument, NULL, 'V' },
+ { "force", no_argument, NULL, 'f' },
+ { "batch", required_argument, NULL, 'b' },
+ { "iec", no_argument, NULL, 'i' },
+ { "json", no_argument, NULL, 'j' },
+ { "Numeric", no_argument, NULL, 'N' },
+ { "pretty", no_argument, NULL, 'p' },
+ { "statistics", no_argument, NULL, 's' },
+ { "netns", required_argument, NULL, 'n' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+ const char *batch_file = NULL;
+ bool force = false;
+ struct dcb *dcb;
+ int opt;
+ int err;
+ int ret;
+
+ dcb = dcb_alloc();
+ if (!dcb) {
+ fprintf(stderr, "Failed to allocate memory for dcb\n");
+ return EXIT_FAILURE;
+ }
+
+ while ((opt = getopt_long(argc, argv, "b:fhijn:psvNV",
+ long_options, NULL)) >= 0) {
+
+ switch (opt) {
+ case 'V':
+ printf("dcb utility, iproute2-%s\n", version);
+ ret = EXIT_SUCCESS;
+ goto dcb_free;
+ case 'f':
+ force = true;
+ break;
+ case 'b':
+ batch_file = optarg;
+ break;
+ case 'j':
+ dcb->json_output = true;
+ break;
+ case 'N':
+ dcb->numeric = true;
+ break;
+ case 'p':
+ pretty = true;
+ break;
+ case 's':
+ dcb->stats = true;
+ break;
+ case 'n':
+ if (netns_switch(optarg)) {
+ ret = EXIT_FAILURE;
+ goto dcb_free;
+ }
+ break;
+ case 'i':
+ dcb->use_iec = true;
+ break;
+ case 'h':
+ dcb_help();
+ ret = EXIT_SUCCESS;
+ goto dcb_free;
+ default:
+ fprintf(stderr, "Unknown option.\n");
+ dcb_help();
+ ret = EXIT_FAILURE;
+ goto dcb_free;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ err = dcb_init(dcb);
+ if (err) {
+ ret = EXIT_FAILURE;
+ goto dcb_free;
+ }
+
+ if (batch_file)
+ err = dcb_batch(dcb, batch_file, force);
+ else
+ err = dcb_cmd(dcb, argc, argv);
+
+ if (err) {
+ ret = EXIT_FAILURE;
+ goto dcb_fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+dcb_fini:
+ dcb_fini(dcb);
+dcb_free:
+ dcb_free(dcb);
+
+ return ret;
+}
diff --git a/dcb/dcb.h b/dcb/dcb.h
new file mode 100644
index 0000000..244c3d3
--- /dev/null
+++ b/dcb/dcb.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DCB_H__
+#define __DCB_H__ 1
+
+#include <libmnl/libmnl.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* dcb.c */
+
+struct dcb {
+ char *buf;
+ struct mnl_socket *nl;
+ bool json_output;
+ bool stats;
+ bool use_iec;
+ bool numeric;
+};
+
+int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key,
+ const char *what_value, __u64 value, __u64 max_value,
+ void (*set_array)(__u32 index, __u64 value, void *data),
+ void *set_array_data);
+int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv,
+ int (*and_then)(struct dcb *dcb, const char *dev,
+ int argc, char **argv),
+ void (*help)(void));
+
+void dcb_set_u8(__u32 key, __u64 value, void *data);
+void dcb_set_u32(__u32 key, __u64 value, void *data);
+void dcb_set_u64(__u32 key, __u64 value, void *data);
+
+int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr,
+ void *data, size_t data_len);
+int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr,
+ const void *data, size_t data_len);
+int dcb_get_attribute_va(struct dcb *dcb, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p);
+int dcb_set_attribute_va(struct dcb *dcb, int command, const char *dev,
+ int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data),
+ void *data);
+int dcb_get_attribute_bare(struct dcb *dcb, int cmd, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p);
+int dcb_set_attribute_bare(struct dcb *dcb, int command, const char *dev,
+ int attr, const void *data, size_t data_len,
+ int response_attr);
+
+void dcb_print_named_array(const char *json_name, const char *fp_name,
+ const __u8 *array, size_t size,
+ void (*print_array)(const __u8 *, size_t));
+void dcb_print_array_u8(const __u8 *array, size_t size);
+void dcb_print_array_u64(const __u64 *array, size_t size);
+void dcb_print_array_on_off(const __u8 *array, size_t size);
+void dcb_print_array_kw(const __u8 *array, size_t array_size,
+ const char *const kw[], size_t kw_size);
+
+/* dcb_app.c */
+
+int dcb_cmd_app(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_buffer.c */
+
+int dcb_cmd_buffer(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_dcbx.c */
+
+int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_ets.c */
+
+int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_maxrate.c */
+
+int dcb_cmd_maxrate(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_pfc.c */
+
+int dcb_cmd_pfc(struct dcb *dcb, int argc, char **argv);
+
+#endif /* __DCB_H__ */
diff --git a/dcb/dcb_app.c b/dcb/dcb_app.c
new file mode 100644
index 0000000..dad3455
--- /dev/null
+++ b/dcb/dcb_app.c
@@ -0,0 +1,797 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <libmnl/libmnl.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+#include "rt_names.h"
+
+static void dcb_app_help_add(void)
+{
+ fprintf(stderr,
+ "Usage: dcb app { add | del | replace } dev STRING\n"
+ " [ default-prio PRIO ]\n"
+ " [ ethtype-prio ET:PRIO ]\n"
+ " [ stream-port-prio PORT:PRIO ]\n"
+ " [ dgram-port-prio PORT:PRIO ]\n"
+ " [ port-prio PORT:PRIO ]\n"
+ " [ dscp-prio INTEGER:PRIO ]\n"
+ "\n"
+ " where PRIO := { 0 .. 7 }\n"
+ " ET := { 0x600 .. 0xffff }\n"
+ " PORT := { 1 .. 65535 }\n"
+ " DSCP := { 0 .. 63 }\n"
+ "\n"
+ );
+}
+
+static void dcb_app_help_show_flush(void)
+{
+ fprintf(stderr,
+ "Usage: dcb app { show | flush } dev STRING\n"
+ " [ default-prio ]\n"
+ " [ ethtype-prio ]\n"
+ " [ stream-port-prio ]\n"
+ " [ dgram-port-prio ]\n"
+ " [ port-prio ]\n"
+ " [ dscp-prio ]\n"
+ "\n"
+ );
+}
+
+static void dcb_app_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb app help\n"
+ "\n"
+ );
+ dcb_app_help_show_flush();
+ dcb_app_help_add();
+}
+
+struct dcb_app_table {
+ struct dcb_app *apps;
+ size_t n_apps;
+};
+
+static void dcb_app_table_fini(struct dcb_app_table *tab)
+{
+ free(tab->apps);
+}
+
+static int dcb_app_table_push(struct dcb_app_table *tab, struct dcb_app *app)
+{
+ struct dcb_app *apps = realloc(tab->apps, (tab->n_apps + 1) * sizeof(*tab->apps));
+
+ if (apps == NULL) {
+ perror("Cannot allocate APP table");
+ return -ENOMEM;
+ }
+
+ tab->apps = apps;
+ tab->apps[tab->n_apps++] = *app;
+ return 0;
+}
+
+static void dcb_app_table_remove_existing(struct dcb_app_table *a,
+ const struct dcb_app_table *b)
+{
+ size_t ia, ja;
+ size_t ib;
+
+ for (ia = 0, ja = 0; ia < a->n_apps; ia++) {
+ struct dcb_app *aa = &a->apps[ia];
+ bool found = false;
+
+ for (ib = 0; ib < b->n_apps; ib++) {
+ const struct dcb_app *ab = &b->apps[ib];
+
+ if (aa->selector == ab->selector &&
+ aa->protocol == ab->protocol &&
+ aa->priority == ab->priority) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ a->apps[ja++] = *aa;
+ }
+
+ a->n_apps = ja;
+}
+
+static void dcb_app_table_remove_replaced(struct dcb_app_table *a,
+ const struct dcb_app_table *b)
+{
+ size_t ia, ja;
+ size_t ib;
+
+ for (ia = 0, ja = 0; ia < a->n_apps; ia++) {
+ struct dcb_app *aa = &a->apps[ia];
+ bool present = false;
+ bool found = false;
+
+ for (ib = 0; ib < b->n_apps; ib++) {
+ const struct dcb_app *ab = &b->apps[ib];
+
+ if (aa->selector == ab->selector &&
+ aa->protocol == ab->protocol)
+ present = true;
+ else
+ continue;
+
+ if (aa->priority == ab->priority) {
+ found = true;
+ break;
+ }
+ }
+
+ /* Entries that remain in A will be removed, so keep in the
+ * table only APP entries whose sel/pid is mentioned in B,
+ * but that do not have the full sel/pid/prio match.
+ */
+ if (present && !found)
+ a->apps[ja++] = *aa;
+ }
+
+ a->n_apps = ja;
+}
+
+static int dcb_app_table_copy(struct dcb_app_table *a,
+ const struct dcb_app_table *b)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < b->n_apps; i++) {
+ ret = dcb_app_table_push(a, &b->apps[i]);
+ if (ret != 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int dcb_app_cmp(const struct dcb_app *a, const struct dcb_app *b)
+{
+ if (a->protocol < b->protocol)
+ return -1;
+ if (a->protocol > b->protocol)
+ return 1;
+ return a->priority - b->priority;
+}
+
+static int dcb_app_cmp_cb(const void *a, const void *b)
+{
+ return dcb_app_cmp(a, b);
+}
+
+static void dcb_app_table_sort(struct dcb_app_table *tab)
+{
+ qsort(tab->apps, tab->n_apps, sizeof(*tab->apps), dcb_app_cmp_cb);
+}
+
+struct dcb_app_parse_mapping {
+ __u8 selector;
+ struct dcb_app_table *tab;
+ int err;
+};
+
+static void dcb_app_parse_mapping_cb(__u32 key, __u64 value, void *data)
+{
+ struct dcb_app_parse_mapping *pm = data;
+ struct dcb_app app = {
+ .selector = pm->selector,
+ .priority = value,
+ .protocol = key,
+ };
+
+ if (pm->err)
+ return;
+
+ pm->err = dcb_app_table_push(pm->tab, &app);
+}
+
+static int dcb_app_parse_mapping_ethtype_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (key < 0x600) {
+ fprintf(stderr, "Protocol IDs < 0x600 are reserved for EtherType\n");
+ return -EINVAL;
+ }
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("ETHTYPE", key, 0xffff,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+static int dcb_app_parse_dscp(__u32 *key, const char *arg)
+{
+ if (parse_mapping_num_all(key, arg) == 0)
+ return 0;
+
+ if (rtnl_dsfield_a2n(key, arg) != 0)
+ return -1;
+
+ if (*key & 0x03) {
+ fprintf(stderr, "The values `%s' uses non-DSCP bits.\n", arg);
+ return -1;
+ }
+
+ /* Unshift the value to convert it from dsfield to DSCP. */
+ *key >>= 2;
+ return 0;
+}
+
+static int dcb_app_parse_mapping_dscp_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("DSCP", key, 63,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+static int dcb_app_parse_mapping_port_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (key == 0) {
+ fprintf(stderr, "Port ID of 0 is invalid\n");
+ return -EINVAL;
+ }
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PORT", key, 0xffff,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+static int dcb_app_parse_default_prio(int *argcp, char ***argvp, struct dcb_app_table *tab)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret = 0;
+
+ while (argc > 0) {
+ struct dcb_app app;
+ __u8 prio;
+
+ if (get_u8(&prio, *argv, 0)) {
+ ret = 1;
+ break;
+ }
+
+ app = (struct dcb_app){
+ .selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE,
+ .protocol = 0,
+ .priority = prio,
+ };
+ ret = dcb_app_table_push(tab, &app);
+ if (ret != 0)
+ break;
+
+ argc--, argv++;
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+ return ret;
+}
+
+static bool dcb_app_is_ethtype(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
+ app->protocol != 0;
+}
+
+static bool dcb_app_is_default(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
+ app->protocol == 0;
+}
+
+static bool dcb_app_is_dscp(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_DSCP;
+}
+
+static bool dcb_app_is_stream_port(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_STREAM;
+}
+
+static bool dcb_app_is_dgram_port(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_DGRAM;
+}
+
+static bool dcb_app_is_port(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_ANY;
+}
+
+static int dcb_app_print_key_dec(__u16 protocol)
+{
+ return print_uint(PRINT_ANY, NULL, "%d:", protocol);
+}
+
+static int dcb_app_print_key_hex(__u16 protocol)
+{
+ return print_uint(PRINT_ANY, NULL, "%x:", protocol);
+}
+
+static int dcb_app_print_key_dscp(__u16 protocol)
+{
+ const char *name = rtnl_dsfield_get_name(protocol << 2);
+
+
+ if (!is_json_context() && name != NULL)
+ return print_string(PRINT_FP, NULL, "%s:", name);
+ return print_uint(PRINT_ANY, NULL, "%d:", protocol);
+}
+
+static void dcb_app_print_filtered(const struct dcb_app_table *tab,
+ bool (*filter)(const struct dcb_app *),
+ int (*print_key)(__u16 protocol),
+ const char *json_name,
+ const char *fp_name)
+{
+ bool first = true;
+ size_t i;
+
+ for (i = 0; i < tab->n_apps; i++) {
+ struct dcb_app *app = &tab->apps[i];
+
+ if (!filter(app))
+ continue;
+ if (first) {
+ open_json_array(PRINT_JSON, json_name);
+ print_string(PRINT_FP, NULL, "%s ", fp_name);
+ first = false;
+ }
+
+ open_json_array(PRINT_JSON, NULL);
+ print_key(app->protocol);
+ print_uint(PRINT_ANY, NULL, "%d ", app->priority);
+ close_json_array(PRINT_JSON, NULL);
+ }
+
+ if (!first) {
+ close_json_array(PRINT_JSON, json_name);
+ print_nl();
+ }
+}
+
+static void dcb_app_print_ethtype_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_ethtype, dcb_app_print_key_hex,
+ "ethtype_prio", "ethtype-prio");
+}
+
+static void dcb_app_print_dscp_prio(const struct dcb *dcb,
+ const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_dscp,
+ dcb->numeric ? dcb_app_print_key_dec
+ : dcb_app_print_key_dscp,
+ "dscp_prio", "dscp-prio");
+}
+
+static void dcb_app_print_stream_port_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_stream_port, dcb_app_print_key_dec,
+ "stream_port_prio", "stream-port-prio");
+}
+
+static void dcb_app_print_dgram_port_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_dgram_port, dcb_app_print_key_dec,
+ "dgram_port_prio", "dgram-port-prio");
+}
+
+static void dcb_app_print_port_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_port, dcb_app_print_key_dec,
+ "port_prio", "port-prio");
+}
+
+static void dcb_app_print_default_prio(const struct dcb_app_table *tab)
+{
+ bool first = true;
+ size_t i;
+
+ for (i = 0; i < tab->n_apps; i++) {
+ if (!dcb_app_is_default(&tab->apps[i]))
+ continue;
+ if (first) {
+ open_json_array(PRINT_JSON, "default_prio");
+ print_string(PRINT_FP, NULL, "default-prio ", NULL);
+ first = false;
+ }
+ print_uint(PRINT_ANY, NULL, "%d ", tab->apps[i].priority);
+ }
+
+ if (!first) {
+ close_json_array(PRINT_JSON, "default_prio");
+ print_nl();
+ }
+}
+
+static void dcb_app_print(const struct dcb *dcb, const struct dcb_app_table *tab)
+{
+ dcb_app_print_ethtype_prio(tab);
+ dcb_app_print_default_prio(tab);
+ dcb_app_print_dscp_prio(dcb, tab);
+ dcb_app_print_stream_port_prio(tab);
+ dcb_app_print_dgram_port_prio(tab);
+ dcb_app_print_port_prio(tab);
+}
+
+static int dcb_app_get_table_attr_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_app_table *tab = data;
+ struct dcb_app *app;
+ int ret;
+
+ if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE_APP) {
+ fprintf(stderr, "Unknown attribute in DCB_ATTR_IEEE_APP_TABLE: %d\n",
+ mnl_attr_get_type(attr));
+ return MNL_CB_OK;
+ }
+ if (mnl_attr_get_payload_len(attr) < sizeof(struct dcb_app)) {
+ fprintf(stderr, "DCB_ATTR_IEEE_APP payload expected to have size %zd, not %d\n",
+ sizeof(struct dcb_app), mnl_attr_get_payload_len(attr));
+ return MNL_CB_OK;
+ }
+
+ app = mnl_attr_get_payload(attr);
+ ret = dcb_app_table_push(tab, app);
+ if (ret != 0)
+ return MNL_CB_ERROR;
+
+ return MNL_CB_OK;
+}
+
+static int dcb_app_get(struct dcb *dcb, const char *dev, struct dcb_app_table *tab)
+{
+ uint16_t payload_len;
+ void *payload;
+ int ret;
+
+ ret = dcb_get_attribute_va(dcb, dev, DCB_ATTR_IEEE_APP_TABLE, &payload, &payload_len);
+ if (ret != 0)
+ return ret;
+
+ ret = mnl_attr_parse_payload(payload, payload_len, dcb_app_get_table_attr_cb, tab);
+ if (ret != MNL_CB_OK)
+ return -EINVAL;
+
+ return 0;
+}
+
+struct dcb_app_add_del {
+ const struct dcb_app_table *tab;
+ bool (*filter)(const struct dcb_app *app);
+};
+
+static int dcb_app_add_del_cb(struct dcb *dcb, struct nlmsghdr *nlh, void *data)
+{
+ struct dcb_app_add_del *add_del = data;
+ struct nlattr *nest;
+ size_t i;
+
+ nest = mnl_attr_nest_start(nlh, DCB_ATTR_IEEE_APP_TABLE);
+
+ for (i = 0; i < add_del->tab->n_apps; i++) {
+ const struct dcb_app *app = &add_del->tab->apps[i];
+
+ if (add_del->filter == NULL || add_del->filter(app))
+ mnl_attr_put(nlh, DCB_ATTR_IEEE_APP, sizeof(*app), app);
+ }
+
+ mnl_attr_nest_end(nlh, nest);
+ return 0;
+}
+
+static int dcb_app_add_del(struct dcb *dcb, const char *dev, int command,
+ const struct dcb_app_table *tab,
+ bool (*filter)(const struct dcb_app *))
+{
+ struct dcb_app_add_del add_del = {
+ .tab = tab,
+ .filter = filter,
+ };
+
+ if (tab->n_apps == 0)
+ return 0;
+
+ return dcb_set_attribute_va(dcb, command, dev, dcb_app_add_del_cb, &add_del);
+}
+
+static int dcb_cmd_app_parse_add_del(struct dcb *dcb, const char *dev,
+ int argc, char **argv, struct dcb_app_table *tab)
+{
+ struct dcb_app_parse_mapping pm = {
+ .tab = tab,
+ };
+ int ret;
+
+ if (!argc) {
+ dcb_app_help_add();
+ return 0;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_app_help_add();
+ return 0;
+ } else if (matches(*argv, "ethtype-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_ethtype_prio,
+ &pm);
+ } else if (matches(*argv, "default-prio") == 0) {
+ NEXT_ARG();
+ ret = dcb_app_parse_default_prio(&argc, &argv, pm.tab);
+ if (ret != 0) {
+ fprintf(stderr, "Invalid default priority %s\n", *argv);
+ return ret;
+ }
+ } else if (matches(*argv, "dscp-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_DSCP;
+ ret = parse_mapping_gen(&argc, &argv,
+ &dcb_app_parse_dscp,
+ &dcb_app_parse_mapping_dscp_prio,
+ &pm);
+ } else if (matches(*argv, "stream-port-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_STREAM;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_port_prio,
+ &pm);
+ } else if (matches(*argv, "dgram-port-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_DGRAM;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_port_prio,
+ &pm);
+ } else if (matches(*argv, "port-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_ANY;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_port_prio,
+ &pm);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help_add();
+ return -EINVAL;
+ }
+
+ if (ret != 0) {
+ fprintf(stderr, "Invalid mapping %s\n", *argv);
+ return ret;
+ }
+ if (pm.err)
+ return pm.err;
+ } while (argc > 0);
+
+ return 0;
+}
+
+static int dcb_cmd_app_add(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &tab, NULL);
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_del(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL);
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &tab);
+ if (ret != 0)
+ return ret;
+
+ dcb_app_table_sort(&tab);
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_app_print(dcb, &tab);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_app_help_show_flush();
+ goto out;
+ } else if (matches(*argv, "ethtype-prio") == 0) {
+ dcb_app_print_ethtype_prio(&tab);
+ } else if (matches(*argv, "dscp-prio") == 0) {
+ dcb_app_print_dscp_prio(dcb, &tab);
+ } else if (matches(*argv, "stream-port-prio") == 0) {
+ dcb_app_print_stream_port_prio(&tab);
+ } else if (matches(*argv, "dgram-port-prio") == 0) {
+ dcb_app_print_dgram_port_prio(&tab);
+ } else if (matches(*argv, "port-prio") == 0) {
+ dcb_app_print_port_prio(&tab);
+ } else if (matches(*argv, "default-prio") == 0) {
+ dcb_app_print_default_prio(&tab);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help_show_flush();
+ ret = -EINVAL;
+ goto out;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_flush(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &tab);
+ if (ret != 0)
+ return ret;
+
+ if (!argc) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_app_help_show_flush();
+ goto out;
+ } else if (matches(*argv, "ethtype-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_ethtype);
+ if (ret != 0)
+ goto out;
+ } else if (matches(*argv, "default-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_default);
+ if (ret != 0)
+ goto out;
+ } else if (matches(*argv, "dscp-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_dscp);
+ if (ret != 0)
+ goto out;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help_show_flush();
+ ret = -EINVAL;
+ goto out;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_replace(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table orig = {};
+ struct dcb_app_table tab = {};
+ struct dcb_app_table new = {};
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &orig);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab);
+ if (ret != 0)
+ goto out;
+
+ /* Attempts to add an existing entry would be rejected, so drop
+ * these entries from tab.
+ */
+ ret = dcb_app_table_copy(&new, &tab);
+ if (ret != 0)
+ goto out;
+ dcb_app_table_remove_existing(&new, &orig);
+
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &new, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "Could not add new APP entries\n");
+ goto out;
+ }
+
+ /* Remove the obsolete entries. */
+ dcb_app_table_remove_replaced(&orig, &tab);
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &orig, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "Could not remove replaced APP entries\n");
+ goto out;
+ }
+
+out:
+ dcb_app_table_fini(&new);
+ dcb_app_table_fini(&tab);
+ dcb_app_table_fini(&orig);
+ return 0;
+}
+
+int dcb_cmd_app(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_app_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_show, dcb_app_help_show_flush);
+ } else if (matches(*argv, "flush") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_flush, dcb_app_help_show_flush);
+ } else if (matches(*argv, "add") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_add, dcb_app_help_add);
+ } else if (matches(*argv, "del") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_del, dcb_app_help_add);
+ } else if (matches(*argv, "replace") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_replace, dcb_app_help_add);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_buffer.c b/dcb/dcb_buffer.c
new file mode 100644
index 0000000..e6a88a0
--- /dev/null
+++ b/dcb/dcb_buffer.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_buffer_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb buffer set dev STRING\n"
+ " [ prio-buffer PRIO-MAP ]\n"
+ " [ buffer-size SIZE-MAP ]\n"
+ "\n"
+ " where PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n"
+ " PRIO-MAPPING := { all | PRIO }:BUFFER\n"
+ " SIZE-MAP := [ SIZE-MAP ] SIZE-MAPPING\n"
+ " SIZE-MAPPING := { all | BUFFER }:INTEGER\n"
+ " PRIO := { 0 .. 7 }\n"
+ " BUFFER := { 0 .. 7 }\n"
+ "\n"
+ );
+}
+
+static void dcb_buffer_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb buffer show dev STRING\n"
+ " [ prio-buffer ] [ buffer-size ] [ total-size ]\n"
+ "\n"
+ );
+}
+
+static void dcb_buffer_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb buffer help\n"
+ "\n"
+ );
+ dcb_buffer_help_show();
+ dcb_buffer_help_set();
+}
+
+static int dcb_buffer_parse_mapping_prio_buffer(__u32 key, char *value, void *data)
+{
+ struct dcbnl_buffer *buffer = data;
+ __u8 buf;
+
+ if (get_u8(&buf, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PRIO", key, IEEE_8021Q_MAX_PRIORITIES - 1,
+ "BUFFER", buf, DCBX_MAX_BUFFERS - 1,
+ dcb_set_u8, buffer->prio2buffer);
+}
+
+static int dcb_buffer_parse_mapping_buffer_size(__u32 key, char *value, void *data)
+{
+ struct dcbnl_buffer *buffer = data;
+ unsigned int size;
+
+ if (get_size(&size, value)) {
+ fprintf(stderr, "%d:%s: Illegal value for buffer size\n", key, value);
+ return -EINVAL;
+ }
+
+ return dcb_parse_mapping("BUFFER", key, DCBX_MAX_BUFFERS - 1,
+ "INTEGER", size, -1,
+ dcb_set_u32, buffer->buffer_size);
+}
+
+static void dcb_buffer_print_total_size(const struct dcbnl_buffer *buffer)
+{
+ print_size(PRINT_ANY, "total_size", "total-size %s ", buffer->total_size);
+}
+
+static void dcb_buffer_print_prio_buffer(const struct dcbnl_buffer *buffer)
+{
+ dcb_print_named_array("prio_buffer", "prio-buffer",
+ buffer->prio2buffer, ARRAY_SIZE(buffer->prio2buffer),
+ dcb_print_array_u8);
+}
+
+static void dcb_buffer_print_buffer_size(const struct dcbnl_buffer *buffer)
+{
+ size_t size = ARRAY_SIZE(buffer->buffer_size);
+ SPRINT_BUF(b);
+ size_t i;
+
+ open_json_array(PRINT_JSON, "buffer_size");
+ print_string(PRINT_FP, NULL, "buffer-size ", NULL);
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%s ", i);
+ print_size(PRINT_ANY, NULL, b, buffer->buffer_size[i]);
+ }
+
+ close_json_array(PRINT_JSON, "buffer_size");
+}
+
+static void dcb_buffer_print(const struct dcbnl_buffer *buffer)
+{
+ dcb_buffer_print_prio_buffer(buffer);
+ print_nl();
+
+ dcb_buffer_print_buffer_size(buffer);
+ print_nl();
+
+ dcb_buffer_print_total_size(buffer);
+ print_nl();
+}
+
+static int dcb_buffer_get(struct dcb *dcb, const char *dev, struct dcbnl_buffer *buffer)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_DCB_BUFFER, buffer, sizeof(*buffer));
+}
+
+static int dcb_buffer_set(struct dcb *dcb, const char *dev, const struct dcbnl_buffer *buffer)
+{
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_DCB_BUFFER, buffer, sizeof(*buffer));
+}
+
+static int dcb_cmd_buffer_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcbnl_buffer buffer;
+ int ret;
+
+ if (!argc) {
+ dcb_buffer_help_set();
+ return 0;
+ }
+
+ ret = dcb_buffer_get(dcb, dev, &buffer);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_buffer_help_set();
+ return 0;
+ } else if (matches(*argv, "prio-buffer") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_buffer_parse_mapping_prio_buffer, &buffer);
+ if (ret) {
+ fprintf(stderr, "Invalid priority mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "buffer-size") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_buffer_parse_mapping_buffer_size, &buffer);
+ if (ret) {
+ fprintf(stderr, "Invalid buffer size mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_buffer_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_buffer_set(dcb, dev, &buffer);
+}
+
+static int dcb_cmd_buffer_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcbnl_buffer buffer;
+ int ret;
+
+ ret = dcb_buffer_get(dcb, dev, &buffer);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_buffer_print(&buffer);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_buffer_help_show();
+ return 0;
+ } else if (matches(*argv, "prio-buffer") == 0) {
+ dcb_buffer_print_prio_buffer(&buffer);
+ print_nl();
+ } else if (matches(*argv, "buffer-size") == 0) {
+ dcb_buffer_print_buffer_size(&buffer);
+ print_nl();
+ } else if (matches(*argv, "total-size") == 0) {
+ dcb_buffer_print_total_size(&buffer);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_buffer_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_buffer(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_buffer_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_buffer_show, dcb_buffer_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_buffer_set, dcb_buffer_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_buffer_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_dcbx.c b/dcb/dcb_dcbx.c
new file mode 100644
index 0000000..244b671
--- /dev/null
+++ b/dcb/dcb_dcbx.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_dcbx_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb dcbx set dev STRING\n"
+ " [ host | lld-managed ]\n"
+ " [ cee | ieee ] [ static ]\n"
+ "\n"
+ );
+}
+
+static void dcb_dcbx_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb dcbx show dev STRING\n"
+ "\n"
+ );
+}
+
+static void dcb_dcbx_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb dcbx help\n"
+ "\n"
+ );
+ dcb_dcbx_help_show();
+ dcb_dcbx_help_set();
+}
+
+struct dcb_dcbx_flag {
+ __u8 value;
+ const char *key_fp;
+ const char *key_json;
+};
+
+static struct dcb_dcbx_flag dcb_dcbx_flags[] = {
+ {DCB_CAP_DCBX_HOST, "host"},
+ {DCB_CAP_DCBX_LLD_MANAGED, "lld-managed", "lld_managed"},
+ {DCB_CAP_DCBX_VER_CEE, "cee"},
+ {DCB_CAP_DCBX_VER_IEEE, "ieee"},
+ {DCB_CAP_DCBX_STATIC, "static"},
+};
+
+static void dcb_dcbx_print(__u8 dcbx)
+{
+ int bit;
+ int i;
+
+ while ((bit = ffs(dcbx))) {
+ bool found = false;
+
+ bit--;
+ for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) {
+ struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i];
+
+ if (flag->value == 1 << bit) {
+ print_bool(PRINT_JSON, flag->key_json ?: flag->key_fp,
+ NULL, true);
+ print_string(PRINT_FP, NULL, "%s ", flag->key_fp);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ fprintf(stderr, "Unknown DCBX bit %#x.\n", 1 << bit);
+
+ dcbx &= ~(1 << bit);
+ }
+
+ print_nl();
+}
+
+static int dcb_dcbx_get(struct dcb *dcb, const char *dev, __u8 *dcbx)
+{
+ __u16 payload_len;
+ void *payload;
+ int err;
+
+ err = dcb_get_attribute_bare(dcb, DCB_CMD_IEEE_GET, dev, DCB_ATTR_DCBX,
+ &payload, &payload_len);
+ if (err != 0)
+ return err;
+
+ if (payload_len != 1) {
+ fprintf(stderr, "DCB_ATTR_DCBX payload has size %d, expected 1.\n",
+ payload_len);
+ return -EINVAL;
+ }
+ *dcbx = *(__u8 *) payload;
+ return 0;
+}
+
+static int dcb_dcbx_set(struct dcb *dcb, const char *dev, __u8 dcbx)
+{
+ return dcb_set_attribute_bare(dcb, DCB_CMD_SDCBX, dev, DCB_ATTR_DCBX,
+ &dcbx, 1, DCB_ATTR_DCBX);
+}
+
+static int dcb_cmd_dcbx_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ __u8 dcbx = 0;
+ __u8 i;
+
+ if (!argc) {
+ dcb_dcbx_help_set();
+ return 0;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_dcbx_help_set();
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) {
+ struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i];
+
+ if (matches(*argv, flag->key_fp) == 0) {
+ dcbx |= flag->value;
+ NEXT_ARG_FWD();
+ goto next;
+ }
+ }
+
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_dcbx_help_set();
+ return -EINVAL;
+
+next:
+ ;
+ } while (argc > 0);
+
+ return dcb_dcbx_set(dcb, dev, dcbx);
+}
+
+static int dcb_cmd_dcbx_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ __u8 dcbx;
+ int ret;
+
+ ret = dcb_dcbx_get(dcb, dev, &dcbx);
+ if (ret != 0)
+ return ret;
+
+ while (argc > 0) {
+ if (matches(*argv, "help") == 0) {
+ dcb_dcbx_help_show();
+ return 0;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_dcbx_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ open_json_object(NULL);
+ dcb_dcbx_print(dcbx);
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_dcbx_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_dcbx_show, dcb_dcbx_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_dcbx_set, dcb_dcbx_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_dcbx_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_ets.c b/dcb/dcb_ets.c
new file mode 100644
index 0000000..c208810
--- /dev/null
+++ b/dcb/dcb_ets.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_ets_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb ets set dev STRING\n"
+ " [ willing { on | off } ]\n"
+ " [ { tc-tsa | reco-tc-tsa } TSA-MAP ]\n"
+ " [ { pg-bw | tc-bw | reco-tc-bw } BW-MAP ]\n"
+ " [ { prio-tc | reco-prio-tc } PRIO-MAP ]\n"
+ "\n"
+ " where TSA-MAP := [ TSA-MAP ] TSA-MAPPING\n"
+ " TSA-MAPPING := { all | TC }:{ strict | cbs | ets | vendor }\n"
+ " BW-MAP := [ BW-MAP ] BW-MAPPING\n"
+ " BW-MAPPING := { all | TC }:INTEGER\n"
+ " PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n"
+ " PRIO-MAPPING := { all | PRIO }:TC\n"
+ " TC := { 0 .. 7 }\n"
+ " PRIO := { 0 .. 7 }\n"
+ "\n"
+ );
+}
+
+static void dcb_ets_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb ets show dev STRING\n"
+ " [ willing ] [ ets-cap ] [ cbs ] [ tc-tsa ]\n"
+ " [ reco-tc-tsa ] [ pg-bw ] [ tc-bw ] [ reco-tc-bw ]\n"
+ " [ prio-tc ] [ reco-prio-tc ]\n"
+ "\n"
+ );
+}
+
+static void dcb_ets_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb ets help\n"
+ "\n"
+ );
+ dcb_ets_help_show();
+ dcb_ets_help_set();
+}
+
+static const char *const tsa_names[] = {
+ [IEEE_8021QAZ_TSA_STRICT] = "strict",
+ [IEEE_8021QAZ_TSA_CB_SHAPER] = "cbs",
+ [IEEE_8021QAZ_TSA_ETS] = "ets",
+ [IEEE_8021QAZ_TSA_VENDOR] = "vendor",
+};
+
+static int dcb_ets_parse_mapping_tc_tsa(__u32 key, char *value, void *data)
+{
+ __u8 tsa;
+ int ret;
+
+ tsa = parse_one_of("TSA", value, tsa_names, ARRAY_SIZE(tsa_names), &ret);
+ if (ret)
+ return ret;
+
+ return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "TSA", tsa, -1U,
+ dcb_set_u8, data);
+}
+
+static int dcb_ets_parse_mapping_tc_bw(__u32 key, char *value, void *data)
+{
+ __u8 bw;
+
+ if (get_u8(&bw, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "BW", bw, 100,
+ dcb_set_u8, data);
+}
+
+static int dcb_ets_parse_mapping_prio_tc(unsigned int key, char *value, void *data)
+{
+ __u8 tc;
+
+ if (get_u8(&tc, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "TC", tc, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_set_u8, data);
+}
+
+static void dcb_print_array_tsa(const __u8 *array, size_t size)
+{
+ dcb_print_array_kw(array, size, tsa_names, ARRAY_SIZE(tsa_names));
+}
+
+static void dcb_ets_print_willing(const struct ieee_ets *ets)
+{
+ print_on_off(PRINT_ANY, "willing", "willing %s ", ets->willing);
+}
+
+static void dcb_ets_print_ets_cap(const struct ieee_ets *ets)
+{
+ print_uint(PRINT_ANY, "ets_cap", "ets-cap %d ", ets->ets_cap);
+}
+
+static void dcb_ets_print_cbs(const struct ieee_ets *ets)
+{
+ print_on_off(PRINT_ANY, "cbs", "cbs %s ", ets->cbs);
+}
+
+static void dcb_ets_print_tc_bw(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("tc_bw", "tc-bw",
+ ets->tc_tx_bw, ARRAY_SIZE(ets->tc_tx_bw),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_pg_bw(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("pg_bw", "pg-bw",
+ ets->tc_rx_bw, ARRAY_SIZE(ets->tc_rx_bw),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_tc_tsa(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("tc_tsa", "tc-tsa",
+ ets->tc_tsa, ARRAY_SIZE(ets->tc_tsa),
+ dcb_print_array_tsa);
+}
+
+static void dcb_ets_print_prio_tc(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("prio_tc", "prio-tc",
+ ets->prio_tc, ARRAY_SIZE(ets->prio_tc),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_reco_tc_bw(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("reco_tc_bw", "reco-tc-bw",
+ ets->tc_reco_bw, ARRAY_SIZE(ets->tc_reco_bw),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_reco_tc_tsa(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("reco_tc_tsa", "reco-tc-tsa",
+ ets->tc_reco_tsa, ARRAY_SIZE(ets->tc_reco_tsa),
+ dcb_print_array_tsa);
+}
+
+static void dcb_ets_print_reco_prio_tc(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("reco_prio_tc", "reco-prio-tc",
+ ets->reco_prio_tc, ARRAY_SIZE(ets->reco_prio_tc),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print(const struct ieee_ets *ets)
+{
+ dcb_ets_print_willing(ets);
+ dcb_ets_print_ets_cap(ets);
+ dcb_ets_print_cbs(ets);
+ print_nl();
+
+ dcb_ets_print_tc_bw(ets);
+ print_nl();
+
+ dcb_ets_print_pg_bw(ets);
+ print_nl();
+
+ dcb_ets_print_tc_tsa(ets);
+ print_nl();
+
+ dcb_ets_print_prio_tc(ets);
+ print_nl();
+
+ dcb_ets_print_reco_tc_bw(ets);
+ print_nl();
+
+ dcb_ets_print_reco_tc_tsa(ets);
+ print_nl();
+
+ dcb_ets_print_reco_prio_tc(ets);
+ print_nl();
+}
+
+static int dcb_ets_get(struct dcb *dcb, const char *dev, struct ieee_ets *ets)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets));
+}
+
+static int dcb_ets_validate_bw(const __u8 bw[], const __u8 tsa[], const char *what)
+{
+ bool has_ets = false;
+ unsigned int total = 0;
+ unsigned int tc;
+
+ for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) {
+ if (tsa[tc] == IEEE_8021QAZ_TSA_ETS) {
+ has_ets = true;
+ break;
+ }
+ }
+
+ /* TC bandwidth is only intended for ETS, but 802.1Q-2018 only requires
+ * that the sum be 100, and individual entries 0..100. It explicitly
+ * notes that non-ETS TCs can have non-0 TC bandwidth during
+ * reconfiguration.
+ */
+ for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) {
+ if (bw[tc] > 100) {
+ fprintf(stderr, "%d%% for TC %d of %s is not a valid bandwidth percentage, expected 0..100%%\n",
+ bw[tc], tc, what);
+ return -EINVAL;
+ }
+ total += bw[tc];
+ }
+
+ /* This is what 802.1Q-2018 requires. */
+ if (total == 100)
+ return 0;
+
+ /* But this requirement does not make sense for all-strict
+ * configurations. Anything else than 0 does not make sense: either BW
+ * has not been reconfigured for the all-strict allocation yet, at which
+ * point we expect sum of 100. Or it has already been reconfigured, at
+ * which point accept 0.
+ */
+ if (!has_ets && total == 0)
+ return 0;
+
+ fprintf(stderr, "Bandwidth percentages in %s sum to %d%%, expected %d%%\n",
+ what, total, has_ets ? 100 : 0);
+ return -EINVAL;
+}
+
+static int dcb_ets_set(struct dcb *dcb, const char *dev, const struct ieee_ets *ets)
+{
+ /* Do not validate pg-bw, which is not standard and has unclear
+ * meaning.
+ */
+ if (dcb_ets_validate_bw(ets->tc_tx_bw, ets->tc_tsa, "tc-bw") ||
+ dcb_ets_validate_bw(ets->tc_reco_bw, ets->tc_reco_tsa, "reco-tc-bw"))
+ return -EINVAL;
+
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets));
+}
+
+static int dcb_cmd_ets_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_ets ets;
+ int ret;
+
+ if (!argc) {
+ dcb_ets_help_set();
+ return 1;
+ }
+
+ ret = dcb_ets_get(dcb, dev, &ets);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_ets_help_set();
+ return 0;
+ } else if (matches(*argv, "willing") == 0) {
+ NEXT_ARG();
+ ets.willing = parse_on_off("willing", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (matches(*argv, "tc-tsa") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa,
+ ets.tc_tsa);
+ if (ret) {
+ fprintf(stderr, "Invalid tc-tsa mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "reco-tc-tsa") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa,
+ ets.tc_reco_tsa);
+ if (ret) {
+ fprintf(stderr, "Invalid reco-tc-tsa mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "tc-bw") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw,
+ ets.tc_tx_bw);
+ if (ret) {
+ fprintf(stderr, "Invalid tc-bw mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "pg-bw") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw,
+ ets.tc_rx_bw);
+ if (ret) {
+ fprintf(stderr, "Invalid pg-bw mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "reco-tc-bw") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw,
+ ets.tc_reco_bw);
+ if (ret) {
+ fprintf(stderr, "Invalid reco-tc-bw mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "prio-tc") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc,
+ ets.prio_tc);
+ if (ret) {
+ fprintf(stderr, "Invalid prio-tc mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "reco-prio-tc") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc,
+ ets.reco_prio_tc);
+ if (ret) {
+ fprintf(stderr, "Invalid reco-prio-tc mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_ets_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_ets_set(dcb, dev, &ets);
+}
+
+static int dcb_cmd_ets_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_ets ets;
+ int ret;
+
+ ret = dcb_ets_get(dcb, dev, &ets);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_ets_print(&ets);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_ets_help_show();
+ return 0;
+ } else if (matches(*argv, "willing") == 0) {
+ dcb_ets_print_willing(&ets);
+ print_nl();
+ } else if (matches(*argv, "ets-cap") == 0) {
+ dcb_ets_print_ets_cap(&ets);
+ print_nl();
+ } else if (matches(*argv, "cbs") == 0) {
+ dcb_ets_print_cbs(&ets);
+ print_nl();
+ } else if (matches(*argv, "tc-tsa") == 0) {
+ dcb_ets_print_tc_tsa(&ets);
+ print_nl();
+ } else if (matches(*argv, "reco-tc-tsa") == 0) {
+ dcb_ets_print_reco_tc_tsa(&ets);
+ print_nl();
+ } else if (matches(*argv, "tc-bw") == 0) {
+ dcb_ets_print_tc_bw(&ets);
+ print_nl();
+ } else if (matches(*argv, "pg-bw") == 0) {
+ dcb_ets_print_pg_bw(&ets);
+ print_nl();
+ } else if (matches(*argv, "reco-tc-bw") == 0) {
+ dcb_ets_print_reco_tc_bw(&ets);
+ print_nl();
+ } else if (matches(*argv, "prio-tc") == 0) {
+ dcb_ets_print_prio_tc(&ets);
+ print_nl();
+ } else if (matches(*argv, "reco-prio-tc") == 0) {
+ dcb_ets_print_reco_prio_tc(&ets);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_ets_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_ets_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_show, dcb_ets_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_set, dcb_ets_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_ets_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_maxrate.c b/dcb/dcb_maxrate.c
new file mode 100644
index 0000000..1538c6d
--- /dev/null
+++ b/dcb/dcb_maxrate.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_maxrate_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb maxrate set dev STRING\n"
+ " [ tc-maxrate RATE-MAP ]\n"
+ "\n"
+ " where RATE-MAP := [ RATE-MAP ] RATE-MAPPING\n"
+ " RATE-MAPPING := { all | TC }:RATE\n"
+ " TC := { 0 .. 7 }\n"
+ "\n"
+ );
+}
+
+static void dcb_maxrate_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb [ -i ] maxrate show dev STRING\n"
+ " [ tc-maxrate ]\n"
+ "\n"
+ );
+}
+
+static void dcb_maxrate_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb maxrate help\n"
+ "\n"
+ );
+ dcb_maxrate_help_show();
+ dcb_maxrate_help_set();
+}
+
+static int dcb_maxrate_parse_mapping_tc_maxrate(__u32 key, char *value, void *data)
+{
+ __u64 rate;
+
+ if (get_rate64(&rate, value))
+ return -EINVAL;
+
+ return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "RATE", rate, -1,
+ dcb_set_u64, data);
+}
+
+static void dcb_maxrate_print_tc_maxrate(struct dcb *dcb, const struct ieee_maxrate *maxrate)
+{
+ size_t size = ARRAY_SIZE(maxrate->tc_maxrate);
+ SPRINT_BUF(b);
+ size_t i;
+
+ open_json_array(PRINT_JSON, "tc_maxrate");
+ print_string(PRINT_FP, NULL, "tc-maxrate ", NULL);
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%s ", i);
+ print_rate(dcb->use_iec, PRINT_ANY, NULL, b, maxrate->tc_maxrate[i]);
+ }
+
+ close_json_array(PRINT_JSON, "tc_maxrate");
+}
+
+static void dcb_maxrate_print(struct dcb *dcb, const struct ieee_maxrate *maxrate)
+{
+ dcb_maxrate_print_tc_maxrate(dcb, maxrate);
+ print_nl();
+}
+
+static int dcb_maxrate_get(struct dcb *dcb, const char *dev, struct ieee_maxrate *maxrate)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_MAXRATE, maxrate, sizeof(*maxrate));
+}
+
+static int dcb_maxrate_set(struct dcb *dcb, const char *dev, const struct ieee_maxrate *maxrate)
+{
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_MAXRATE, maxrate, sizeof(*maxrate));
+}
+
+static int dcb_cmd_maxrate_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_maxrate maxrate;
+ int ret;
+
+ if (!argc) {
+ dcb_maxrate_help_set();
+ return 0;
+ }
+
+ ret = dcb_maxrate_get(dcb, dev, &maxrate);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_maxrate_help_set();
+ return 0;
+ } else if (matches(*argv, "tc-maxrate") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_maxrate_parse_mapping_tc_maxrate, &maxrate);
+ if (ret) {
+ fprintf(stderr, "Invalid mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_maxrate_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_maxrate_set(dcb, dev, &maxrate);
+}
+
+static int dcb_cmd_maxrate_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_maxrate maxrate;
+ int ret;
+
+ ret = dcb_maxrate_get(dcb, dev, &maxrate);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_maxrate_print(dcb, &maxrate);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_maxrate_help_show();
+ return 0;
+ } else if (matches(*argv, "tc-maxrate") == 0) {
+ dcb_maxrate_print_tc_maxrate(dcb, &maxrate);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_maxrate_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_maxrate(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_maxrate_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_maxrate_show, dcb_maxrate_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_maxrate_set, dcb_maxrate_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_maxrate_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_pfc.c b/dcb/dcb_pfc.c
new file mode 100644
index 0000000..aaa0902
--- /dev/null
+++ b/dcb/dcb_pfc.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_pfc_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb pfc set dev STRING\n"
+ " [ prio-pfc PFC-MAP ]\n"
+ " [ macsec-bypass { on | off } ]\n"
+ " [ delay INTEGER ]\n"
+ "\n"
+ " where PFC-MAP := [ PFC-MAP ] PFC-MAPPING\n"
+ " PFC-MAPPING := { all | TC }:PFC\n"
+ " TC := { 0 .. 7 }\n"
+ " PFC := { on | off }\n"
+ "\n"
+ );
+}
+
+static void dcb_pfc_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb [ -s ] pfc show dev STRING\n"
+ " [ pfc-cap ] [ prio-pfc ] [ macsec-bypass ]\n"
+ " [ delay ] [ requests ] [ indications ]\n"
+ "\n"
+ );
+}
+
+static void dcb_pfc_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb pfc help\n"
+ "\n"
+ );
+ dcb_pfc_help_show();
+ dcb_pfc_help_set();
+}
+
+static void dcb_pfc_to_array(__u8 array[IEEE_8021QAZ_MAX_TCS], __u8 pfc_en)
+{
+ int i;
+
+ for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++)
+ array[i] = !!(pfc_en & (1 << i));
+}
+
+static void dcb_pfc_from_array(__u8 array[IEEE_8021QAZ_MAX_TCS], __u8 *pfc_en_p)
+{
+ __u8 pfc_en = 0;
+ int i;
+
+ for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+ if (array[i])
+ pfc_en |= 1 << i;
+ }
+
+ *pfc_en_p = pfc_en;
+}
+
+static int dcb_pfc_parse_mapping_prio_pfc(__u32 key, char *value, void *data)
+{
+ struct ieee_pfc *pfc = data;
+ __u8 pfc_en[IEEE_8021QAZ_MAX_TCS];
+ bool enabled;
+ int ret;
+
+ dcb_pfc_to_array(pfc_en, pfc->pfc_en);
+
+ enabled = parse_on_off("PFC", value, &ret);
+ if (ret)
+ return ret;
+
+ ret = dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "PFC", enabled, -1,
+ dcb_set_u8, pfc_en);
+ if (ret)
+ return ret;
+
+ dcb_pfc_from_array(pfc_en, &pfc->pfc_en);
+ return 0;
+}
+
+static void dcb_pfc_print_pfc_cap(const struct ieee_pfc *pfc)
+{
+ print_uint(PRINT_ANY, "pfc_cap", "pfc-cap %d ", pfc->pfc_cap);
+}
+
+static void dcb_pfc_print_macsec_bypass(const struct ieee_pfc *pfc)
+{
+ print_on_off(PRINT_ANY, "macsec_bypass", "macsec-bypass %s ", pfc->mbc);
+}
+
+static void dcb_pfc_print_delay(const struct ieee_pfc *pfc)
+{
+ print_uint(PRINT_ANY, "delay", "delay %d ", pfc->delay);
+}
+
+static void dcb_pfc_print_prio_pfc(const struct ieee_pfc *pfc)
+{
+ __u8 pfc_en[IEEE_8021QAZ_MAX_TCS];
+
+ dcb_pfc_to_array(pfc_en, pfc->pfc_en);
+ dcb_print_named_array("prio_pfc", "prio-pfc",
+ pfc_en, ARRAY_SIZE(pfc_en), &dcb_print_array_on_off);
+}
+
+static void dcb_pfc_print_requests(const struct ieee_pfc *pfc)
+{
+ open_json_array(PRINT_JSON, "requests");
+ print_string(PRINT_FP, NULL, "requests ", NULL);
+ dcb_print_array_u64(pfc->requests, ARRAY_SIZE(pfc->requests));
+ close_json_array(PRINT_JSON, "requests");
+}
+
+static void dcb_pfc_print_indications(const struct ieee_pfc *pfc)
+{
+ open_json_array(PRINT_JSON, "indications");
+ print_string(PRINT_FP, NULL, "indications ", NULL);
+ dcb_print_array_u64(pfc->indications, ARRAY_SIZE(pfc->indications));
+ close_json_array(PRINT_JSON, "indications");
+}
+
+static void dcb_pfc_print(const struct dcb *dcb, const struct ieee_pfc *pfc)
+{
+ dcb_pfc_print_pfc_cap(pfc);
+ dcb_pfc_print_macsec_bypass(pfc);
+ dcb_pfc_print_delay(pfc);
+ print_nl();
+
+ dcb_pfc_print_prio_pfc(pfc);
+ print_nl();
+
+ if (dcb->stats) {
+ dcb_pfc_print_requests(pfc);
+ print_nl();
+
+ dcb_pfc_print_indications(pfc);
+ print_nl();
+ }
+}
+
+static int dcb_pfc_get(struct dcb *dcb, const char *dev, struct ieee_pfc *pfc)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_PFC, pfc, sizeof(*pfc));
+}
+
+static int dcb_pfc_set(struct dcb *dcb, const char *dev, const struct ieee_pfc *pfc)
+{
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_PFC, pfc, sizeof(*pfc));
+}
+
+static int dcb_cmd_pfc_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_pfc pfc;
+ int ret;
+
+ if (!argc) {
+ dcb_pfc_help_set();
+ return 0;
+ }
+
+ ret = dcb_pfc_get(dcb, dev, &pfc);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_pfc_help_set();
+ return 0;
+ } else if (matches(*argv, "prio-pfc") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_pfc_parse_mapping_prio_pfc, &pfc);
+ if (ret) {
+ fprintf(stderr, "Invalid pfc mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "macsec-bypass") == 0) {
+ NEXT_ARG();
+ pfc.mbc = parse_on_off("macsec-bypass", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (matches(*argv, "delay") == 0) {
+ NEXT_ARG();
+ /* Do not support the size notations for delay.
+ * Delay is specified in "bit times", not bits, so
+ * it is not applicable. At the same time it would
+ * be confusing that 10Kbit does not mean 10240,
+ * but 1280.
+ */
+ if (get_u16(&pfc.delay, *argv, 0)) {
+ fprintf(stderr, "Invalid delay `%s', expected an integer 0..65535\n",
+ *argv);
+ return -EINVAL;
+ }
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_pfc_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_pfc_set(dcb, dev, &pfc);
+}
+
+static int dcb_cmd_pfc_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_pfc pfc;
+ int ret;
+
+ ret = dcb_pfc_get(dcb, dev, &pfc);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_pfc_print(dcb, &pfc);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_pfc_help_show();
+ return 0;
+ } else if (matches(*argv, "prio-pfc") == 0) {
+ dcb_pfc_print_prio_pfc(&pfc);
+ print_nl();
+ } else if (matches(*argv, "pfc-cap") == 0) {
+ dcb_pfc_print_pfc_cap(&pfc);
+ print_nl();
+ } else if (matches(*argv, "macsec-bypass") == 0) {
+ dcb_pfc_print_macsec_bypass(&pfc);
+ print_nl();
+ } else if (matches(*argv, "delay") == 0) {
+ dcb_pfc_print_delay(&pfc);
+ print_nl();
+ } else if (matches(*argv, "requests") == 0) {
+ dcb_pfc_print_requests(&pfc);
+ print_nl();
+ } else if (matches(*argv, "indications") == 0) {
+ dcb_pfc_print_indications(&pfc);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_pfc_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_pfc(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_pfc_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_pfc_show, dcb_pfc_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_pfc_set, dcb_pfc_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_pfc_help();
+ return -EINVAL;
+ }
+}