You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1597 lines
43 KiB
1597 lines
43 KiB
From 120f9c488ba7d291f899f1ec2f77e0ae33efcd88 Mon Sep 17 00:00:00 2001 |
|
From: Kamal Heib <kheib@redhat.com> |
|
Date: Thu, 9 Nov 2017 04:44:32 -0500 |
|
Subject: [PATCH] devlink: Add support for pipeline debug (dpipe) |
|
|
|
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1456539 |
|
|
|
commit 153c1a9b21e5b7b78e066de2b93a4edb8c3dc498 |
|
Author: Arkadi Sharshevsky <arkadis@mellanox.com> |
|
Date: Wed May 3 13:25:23 2017 +0200 |
|
|
|
devlink: Add support for pipeline debug (dpipe) |
|
|
|
Add support for pipeline debug (dpipe). The headers are used both the |
|
gain visibillity into the headers supported by the hardware, and to |
|
build the headers/field database which is used by other commands. |
|
|
|
Examples: |
|
|
|
First we can see the headers supported by the hardware: |
|
|
|
$devlink dpipe header show pci/0000:03:00.0 |
|
|
|
pci/0000:03:00.0: |
|
name mlxsw_meta |
|
field: |
|
name erif_port bitwidth 32 mapping_type ifindex |
|
name l3_forward bitwidth 1 |
|
name l3_drop bitwidth 1 |
|
|
|
Note that mapping_type is presented only if relevant. Also the header/ |
|
field id's are reported by the kernel they are not shown by default. |
|
They can be observed by using the -v option. Also the headers scope |
|
(global/local) is specified. |
|
|
|
$devlink -v dpipe header show pci/0000:03:00.0 |
|
|
|
pci/0000:03:00.0: |
|
name mlxsw_meta id 0 global false |
|
field: |
|
name erif_port id 0 bitwidth 32 mapping_type ifindex |
|
name l3_forward id 1 bitwidth 1 |
|
name l3_drop id 2 bitwidth 1 |
|
|
|
Second we can examine the tables supported by the hardware. In order |
|
to dump all the tables no table name should be provided: |
|
$devlink dpipe table show pci/0000:03:00.0 |
|
|
|
In order to examine specific table its name have to be specified: |
|
$devlink dpipe table show pci/0000:03:00.0 name erif |
|
|
|
pci/0000:03:00.0: |
|
name mlxsw_erif size 800 counters_enabled true |
|
match: |
|
type field_exact header mlxsw_meta field erif_port mapping ifindex |
|
action: |
|
type field_modify header mlxsw_meta field l3_forward |
|
type field_modify header mlxsw_meta field l3_drop |
|
|
|
To enable/disable counters on the table: |
|
$devlink dpipe table set pci/0000:03:00.0 name erif counters enable |
|
$devlink dpipe table set pci/0000:03:00.0 name erif counters disable |
|
|
|
In order to see the current entries in the hardware for specific table: |
|
$devlink dpipe table dump pci/0000:03:00.0 name erif |
|
|
|
pci/0000:03:00.0: |
|
index 0 counter 0 |
|
match_value: |
|
type field_exact header mlxsw_meta field erif_port mapping ifindex mapping_value 383 value 0 |
|
action_value: |
|
type field_modify header mlxsw_meta field l3_forward value 1 |
|
|
|
index 1 counter 0 |
|
match_value: |
|
type field_exact header mlxsw_meta field erif_port mapping ifindex mapping_value 381 value 1 |
|
action_value: |
|
type field_modify header mlxsw_meta field l3_forward value 1 |
|
|
|
In the above example the table contains two entries which does match |
|
on erif port and forwards the packet or drop it (currently only the |
|
forward count is implemented). The counter values are provided for |
|
example. In case the counting is not enabled on the table the counters |
|
will not be available. |
|
|
|
Signed-off-by: Arkadi Sharshevsky <arkadis@mellanox.com> |
|
Signed-off-by: Jiri Pirko <jiri@mellanox.com> |
|
|
|
Signed-off-by: Kamal Heib <kheib@redhat.com> |
|
--- |
|
devlink/devlink.c | 1353 +++++++++++++++++++++++++++++++++++++++++++++++++---- |
|
1 file changed, 1254 insertions(+), 99 deletions(-) |
|
|
|
diff --git a/devlink/devlink.c b/devlink/devlink.c |
|
index 35220d8..e22ee0a 100644 |
|
--- a/devlink/devlink.c |
|
+++ b/devlink/devlink.c |
|
@@ -34,7 +34,15 @@ |
|
#define ESWITCH_INLINE_MODE_TRANSPORT "transport" |
|
|
|
#define pr_err(args...) fprintf(stderr, ##args) |
|
-#define pr_out(args...) fprintf(stdout, ##args) |
|
+#define pr_out(args...) \ |
|
+ do { \ |
|
+ if (g_indent_newline) { \ |
|
+ fprintf(stdout, "%s", g_indent_str); \ |
|
+ g_indent_newline = false; \ |
|
+ } \ |
|
+ fprintf(stdout, ##args); \ |
|
+ } while (0) |
|
+ |
|
#define pr_out_sp(num, args...) \ |
|
do { \ |
|
int ret = fprintf(stdout, ##args); \ |
|
@@ -42,6 +50,35 @@ |
|
fprintf(stdout, "%*s", num - ret, ""); \ |
|
} while (0) |
|
|
|
+static int g_indent_level; |
|
+static bool g_indent_newline; |
|
+#define INDENT_STR_STEP 2 |
|
+#define INDENT_STR_MAXLEN 32 |
|
+static char g_indent_str[INDENT_STR_MAXLEN + 1] = ""; |
|
+ |
|
+static void __pr_out_indent_inc(void) |
|
+{ |
|
+ if (g_indent_level + INDENT_STR_STEP > INDENT_STR_MAXLEN) |
|
+ return; |
|
+ g_indent_level += INDENT_STR_STEP; |
|
+ memset(g_indent_str, ' ', sizeof(g_indent_str)); |
|
+ g_indent_str[g_indent_level] = '\0'; |
|
+} |
|
+ |
|
+static void __pr_out_indent_dec(void) |
|
+{ |
|
+ if (g_indent_level - INDENT_STR_STEP < 0) |
|
+ return; |
|
+ g_indent_level -= INDENT_STR_STEP; |
|
+ g_indent_str[g_indent_level] = '\0'; |
|
+} |
|
+ |
|
+static void __pr_out_newline(void) |
|
+{ |
|
+ pr_out("\n"); |
|
+ g_indent_newline = true; |
|
+} |
|
+ |
|
static int _mnlg_socket_recv_run(struct mnlg_socket *nlg, |
|
mnl_cb_t data_cb, void *data) |
|
{ |
|
@@ -137,6 +174,8 @@ static void ifname_map_free(struct ifname_map *ifname_map) |
|
#define DL_OPT_SB_TC BIT(10) |
|
#define DL_OPT_ESWITCH_MODE BIT(11) |
|
#define DL_OPT_ESWITCH_INLINE_MODE BIT(12) |
|
+#define DL_OPT_DPIPE_TABLE_NAME BIT(13) |
|
+#define DL_OPT_DPIPE_TABLE_COUNTERS BIT(14) |
|
|
|
struct dl_opts { |
|
uint32_t present; /* flags of present items */ |
|
@@ -154,6 +193,8 @@ struct dl_opts { |
|
uint16_t sb_tc_index; |
|
enum devlink_eswitch_mode eswitch_mode; |
|
enum devlink_eswitch_inline_mode eswitch_inline_mode; |
|
+ const char *dpipe_table_name; |
|
+ bool dpipe_counters_enable; |
|
}; |
|
|
|
struct dl { |
|
@@ -166,6 +207,7 @@ struct dl { |
|
json_writer_t *jw; |
|
bool json_output; |
|
bool pretty_output; |
|
+ bool verbose; |
|
struct { |
|
bool present; |
|
char *bus_name; |
|
@@ -257,6 +299,38 @@ static const enum mnl_attr_data_type devlink_policy[DEVLINK_ATTR_MAX + 1] = { |
|
[DEVLINK_ATTR_SB_OCC_MAX] = MNL_TYPE_U32, |
|
[DEVLINK_ATTR_ESWITCH_MODE] = MNL_TYPE_U16, |
|
[DEVLINK_ATTR_ESWITCH_INLINE_MODE] = MNL_TYPE_U8, |
|
+ [DEVLINK_ATTR_DPIPE_TABLES] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_TABLE] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_TABLE_NAME] = MNL_TYPE_STRING, |
|
+ [DEVLINK_ATTR_DPIPE_TABLE_SIZE] = MNL_TYPE_U64, |
|
+ [DEVLINK_ATTR_DPIPE_TABLE_MATCHES] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_TABLE_ACTIONS] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED] = MNL_TYPE_U8, |
|
+ [DEVLINK_ATTR_DPIPE_ENTRIES] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_ENTRY] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_ENTRY_INDEX] = MNL_TYPE_U64, |
|
+ [DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_ENTRY_COUNTER] = MNL_TYPE_U64, |
|
+ [DEVLINK_ATTR_DPIPE_MATCH] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_MATCH_VALUE] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_MATCH_TYPE] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_ACTION] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_ACTION_VALUE] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_ACTION_TYPE] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_VALUE_MAPPING] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_HEADERS] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_HEADER] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_HEADER_NAME] = MNL_TYPE_STRING, |
|
+ [DEVLINK_ATTR_DPIPE_HEADER_ID] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_HEADER_FIELDS] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_HEADER_GLOBAL] = MNL_TYPE_U8, |
|
+ [DEVLINK_ATTR_DPIPE_HEADER_INDEX] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_FIELD] = MNL_TYPE_NESTED, |
|
+ [DEVLINK_ATTR_DPIPE_FIELD_NAME] = MNL_TYPE_STRING, |
|
+ [DEVLINK_ATTR_DPIPE_FIELD_ID] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH] = MNL_TYPE_U32, |
|
+ [DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE] = MNL_TYPE_U32, |
|
}; |
|
|
|
static int attr_cb(const struct nlattr *attr, void *data) |
|
@@ -666,6 +740,20 @@ static int eswitch_inline_mode_get(const char *typestr, |
|
return 0; |
|
} |
|
|
|
+static int dpipe_counters_enable_get(const char *typestr, |
|
+ bool *counters_enable) |
|
+{ |
|
+ if (strcmp(typestr, "enable") == 0) { |
|
+ *counters_enable = 1; |
|
+ } else if (strcmp(typestr, "disable") == 0) { |
|
+ *counters_enable = 0; |
|
+ } else { |
|
+ pr_err("Unknown counter_state \"%s\"\n", typestr); |
|
+ return -EINVAL; |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
static int dl_argv_parse(struct dl *dl, uint32_t o_required, |
|
uint32_t o_optional) |
|
{ |
|
@@ -800,6 +888,27 @@ static int dl_argv_parse(struct dl *dl, uint32_t o_required, |
|
if (err) |
|
return err; |
|
o_found |= DL_OPT_ESWITCH_INLINE_MODE; |
|
+ } else if (dl_argv_match(dl, "name") && |
|
+ (o_all & DL_OPT_DPIPE_TABLE_NAME)) { |
|
+ dl_arg_inc(dl); |
|
+ err = dl_argv_str(dl, &opts->dpipe_table_name); |
|
+ if (err) |
|
+ return err; |
|
+ o_found |= DL_OPT_DPIPE_TABLE_NAME; |
|
+ } else if (dl_argv_match(dl, "counters") && |
|
+ (o_all & DL_OPT_DPIPE_TABLE_COUNTERS)) { |
|
+ const char *typestr; |
|
+ |
|
+ dl_arg_inc(dl); |
|
+ err = dl_argv_str(dl, &typestr); |
|
+ if (err) |
|
+ return err; |
|
+ err = dpipe_counters_enable_get(typestr, |
|
+ &opts->dpipe_counters_enable); |
|
+ if (err) |
|
+ return err; |
|
+ o_found |= DL_OPT_DPIPE_TABLE_COUNTERS; |
|
+ |
|
} else { |
|
pr_err("Unknown option \"%s\"\n", dl_argv(dl)); |
|
return -EINVAL; |
|
@@ -866,6 +975,17 @@ static int dl_argv_parse(struct dl *dl, uint32_t o_required, |
|
return -EINVAL; |
|
} |
|
|
|
+ if ((o_required & DL_OPT_DPIPE_TABLE_NAME) && |
|
+ !(o_found & DL_OPT_DPIPE_TABLE_NAME)) { |
|
+ pr_err("Dpipe table name expected\n"); |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ if ((o_required & DL_OPT_DPIPE_TABLE_COUNTERS) && |
|
+ !(o_found & DL_OPT_DPIPE_TABLE_COUNTERS)) { |
|
+ pr_err("Dpipe table counter state expected\n"); |
|
+ return -EINVAL; |
|
+ } |
|
return 0; |
|
} |
|
|
|
@@ -915,6 +1035,12 @@ static void dl_opts_put(struct nlmsghdr *nlh, struct dl *dl) |
|
if (opts->present & DL_OPT_ESWITCH_INLINE_MODE) |
|
mnl_attr_put_u8(nlh, DEVLINK_ATTR_ESWITCH_INLINE_MODE, |
|
opts->eswitch_inline_mode); |
|
+ if (opts->present & DL_OPT_DPIPE_TABLE_NAME) |
|
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_DPIPE_TABLE_NAME, |
|
+ opts->dpipe_table_name); |
|
+ if (opts->present & DL_OPT_DPIPE_TABLE_COUNTERS) |
|
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED, |
|
+ opts->dpipe_counters_enable); |
|
} |
|
|
|
static int dl_argv_parse_put(struct nlmsghdr *nlh, struct dl *dl, |
|
@@ -1033,7 +1159,19 @@ static void __pr_out_handle_start(struct dl *dl, struct nlattr **tb, |
|
jsonw_start_object(dl->jw); |
|
} |
|
} else { |
|
- pr_out("%s%s", buf, content ? ":" : ""); |
|
+ if (array) { |
|
+ if (should_arr_last_handle_end(dl, bus_name, dev_name)) |
|
+ __pr_out_indent_dec(); |
|
+ if (should_arr_last_handle_start(dl, bus_name, |
|
+ dev_name)) { |
|
+ pr_out("%s%s", buf, content ? ":" : ""); |
|
+ __pr_out_newline(); |
|
+ __pr_out_indent_inc(); |
|
+ arr_last_handle_set(dl, bus_name, dev_name); |
|
+ } |
|
+ } else { |
|
+ pr_out("%s%s", buf, content ? ":" : ""); |
|
+ } |
|
} |
|
} |
|
|
|
@@ -1047,7 +1185,7 @@ static void pr_out_handle_end(struct dl *dl) |
|
if (dl->json_output) |
|
jsonw_end_object(dl->jw); |
|
else |
|
- pr_out("\n"); |
|
+ __pr_out_newline(); |
|
} |
|
|
|
static void pr_out_handle(struct dl *dl, struct nlattr **tb) |
|
@@ -1163,18 +1301,26 @@ static void pr_out_port_handle_end(struct dl *dl) |
|
|
|
static void pr_out_str(struct dl *dl, const char *name, const char *val) |
|
{ |
|
- if (dl->json_output) |
|
+ if (dl->json_output) { |
|
jsonw_string_field(dl->jw, name, val); |
|
- else |
|
- pr_out(" %s %s", name, val); |
|
+ } else { |
|
+ if (g_indent_newline) |
|
+ pr_out("%s %s", name, val); |
|
+ else |
|
+ pr_out(" %s %s", name, val); |
|
+ } |
|
} |
|
|
|
static void pr_out_uint(struct dl *dl, const char *name, unsigned int val) |
|
{ |
|
- if (dl->json_output) |
|
+ if (dl->json_output) { |
|
jsonw_uint_field(dl->jw, name, val); |
|
- else |
|
- pr_out(" %s %u", name, val); |
|
+ } else { |
|
+ if (g_indent_newline) |
|
+ pr_out("%s %u", name, val); |
|
+ else |
|
+ pr_out(" %s %u", name, val); |
|
+ } |
|
} |
|
|
|
static void pr_out_dev(struct dl *dl, struct nlattr **tb) |
|
@@ -1201,6 +1347,42 @@ static void pr_out_section_end(struct dl *dl) |
|
} |
|
} |
|
|
|
+static void pr_out_array_start(struct dl *dl, const char *name) |
|
+{ |
|
+ if (dl->json_output) { |
|
+ jsonw_name(dl->jw, name); |
|
+ jsonw_start_array(dl->jw); |
|
+ } else { |
|
+ if (!g_indent_newline) |
|
+ __pr_out_newline(); |
|
+ pr_out("%s:", name); |
|
+ __pr_out_newline(); |
|
+ __pr_out_indent_inc(); |
|
+ } |
|
+} |
|
+ |
|
+static void pr_out_array_end(struct dl *dl) |
|
+{ |
|
+ if (dl->json_output) |
|
+ jsonw_end_array(dl->jw); |
|
+ else |
|
+ __pr_out_indent_dec(); |
|
+} |
|
+ |
|
+static void pr_out_entry_start(struct dl *dl) |
|
+{ |
|
+ if (dl->json_output) |
|
+ jsonw_start_object(dl->jw); |
|
+} |
|
+ |
|
+static void pr_out_entry_end(struct dl *dl) |
|
+{ |
|
+ if (dl->json_output) |
|
+ jsonw_end_object(dl->jw); |
|
+ else |
|
+ __pr_out_newline(); |
|
+} |
|
+ |
|
static const char *eswitch_mode_name(uint32_t mode) |
|
{ |
|
switch (mode) { |
|
@@ -2423,129 +2605,1102 @@ static int cmd_mon(struct dl *dl) |
|
return -ENOENT; |
|
} |
|
|
|
-static void help(void) |
|
+struct dpipe_field { |
|
+ char *name; |
|
+ unsigned int id; |
|
+ unsigned int bitwidth; |
|
+ enum devlink_dpipe_field_mapping_type mapping_type; |
|
+}; |
|
+ |
|
+struct dpipe_header { |
|
+ struct list_head list; |
|
+ char *name; |
|
+ unsigned int id; |
|
+ struct dpipe_field *fields; |
|
+ unsigned int fields_count; |
|
+}; |
|
+ |
|
+struct dpipe_ctx { |
|
+ struct dl *dl; |
|
+ int err; |
|
+ struct list_head global_headers; |
|
+ struct list_head local_headers; |
|
+ bool print_headers; |
|
+}; |
|
+ |
|
+static struct dpipe_header *dpipe_header_alloc(unsigned int fields_count) |
|
{ |
|
- pr_err("Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n" |
|
- "where OBJECT := { dev | port | sb | monitor }\n" |
|
- " OPTIONS := { -V[ersion] | -n[no-nice-names] | -j[json] | -p[pretty] }\n"); |
|
+ struct dpipe_header *header; |
|
+ |
|
+ header = calloc(1, sizeof(struct dpipe_header)); |
|
+ if (!header) |
|
+ return NULL; |
|
+ header->fields = calloc(fields_count, sizeof(struct dpipe_field)); |
|
+ if (!header->fields) |
|
+ goto err_fields_alloc; |
|
+ header->fields_count = fields_count; |
|
+ return header; |
|
+ |
|
+err_fields_alloc: |
|
+ free(header); |
|
+ return NULL; |
|
} |
|
|
|
-static int dl_cmd(struct dl *dl) |
|
+static void dpipe_header_free(struct dpipe_header *header) |
|
{ |
|
- if (dl_argv_match(dl, "help") || dl_no_arg(dl)) { |
|
- help(); |
|
- return 0; |
|
- } else if (dl_argv_match(dl, "dev")) { |
|
- dl_arg_inc(dl); |
|
- return cmd_dev(dl); |
|
- } else if (dl_argv_match(dl, "port")) { |
|
- dl_arg_inc(dl); |
|
- return cmd_port(dl); |
|
- } else if (dl_argv_match(dl, "sb")) { |
|
- dl_arg_inc(dl); |
|
- return cmd_sb(dl); |
|
- } else if (dl_argv_match(dl, "monitor")) { |
|
- dl_arg_inc(dl); |
|
- return cmd_mon(dl); |
|
+ free(header->fields); |
|
+ free(header); |
|
+} |
|
+ |
|
+static void dpipe_header_clear(struct dpipe_header *header) |
|
+{ |
|
+ struct dpipe_field *field; |
|
+ int i; |
|
+ |
|
+ for (i = 0; i < header->fields_count; i++) { |
|
+ field = &header->fields[i]; |
|
+ free(field->name); |
|
} |
|
- pr_err("Object \"%s\" not found\n", dl_argv(dl)); |
|
- return -ENOENT; |
|
+ free(header->name); |
|
} |
|
|
|
-static int dl_init(struct dl *dl, int argc, char **argv) |
|
+static void dpipe_header_add(struct dpipe_ctx *ctx, |
|
+ struct dpipe_header *header, bool global) |
|
{ |
|
- int err; |
|
+ if (global) |
|
+ list_add(&header->list, &ctx->global_headers); |
|
+ else |
|
+ list_add(&header->list, &ctx->local_headers); |
|
+} |
|
|
|
- dl->argc = argc; |
|
- dl->argv = argv; |
|
+static void dpipe_header_del(struct dpipe_header *header) |
|
+{ |
|
+ list_del(&header->list); |
|
+} |
|
|
|
- dl->nlg = mnlg_socket_open(DEVLINK_GENL_NAME, DEVLINK_GENL_VERSION); |
|
- if (!dl->nlg) { |
|
- pr_err("Failed to connect to devlink Netlink\n"); |
|
- return -errno; |
|
+static struct dpipe_ctx *dpipe_ctx_alloc(struct dl *dl) |
|
+{ |
|
+ struct dpipe_ctx *ctx; |
|
+ |
|
+ ctx = calloc(1, sizeof(struct dpipe_ctx)); |
|
+ if (!ctx) |
|
+ return NULL; |
|
+ ctx->dl = dl; |
|
+ INIT_LIST_HEAD(&ctx->global_headers); |
|
+ INIT_LIST_HEAD(&ctx->local_headers); |
|
+ return ctx; |
|
+} |
|
+ |
|
+static void dpipe_ctx_free(struct dpipe_ctx *ctx) |
|
+{ |
|
+ free(ctx); |
|
+} |
|
+ |
|
+static void dpipe_ctx_clear(struct dpipe_ctx *ctx) |
|
+{ |
|
+ struct dpipe_header *header, *tmp; |
|
+ |
|
+ list_for_each_entry_safe(header, tmp, &ctx->global_headers, |
|
+ list) { |
|
+ dpipe_header_del(header); |
|
+ dpipe_header_clear(header); |
|
+ dpipe_header_free(header); |
|
+ } |
|
+ list_for_each_entry_safe(header, tmp, &ctx->local_headers, |
|
+ list) { |
|
+ dpipe_header_del(header); |
|
+ dpipe_header_clear(header); |
|
+ dpipe_header_free(header); |
|
} |
|
+} |
|
|
|
- err = ifname_map_init(dl); |
|
- if (err) { |
|
- pr_err("Failed to create index map\n"); |
|
- goto err_ifname_map_create; |
|
+static const char *dpipe_header_id2s(struct dpipe_ctx *ctx, |
|
+ uint32_t header_id, bool global) |
|
+{ |
|
+ struct list_head *header_list; |
|
+ struct dpipe_header *header; |
|
+ |
|
+ if (global) |
|
+ header_list = &ctx->global_headers; |
|
+ else |
|
+ header_list = &ctx->local_headers; |
|
+ list_for_each_entry(header, header_list, list) { |
|
+ if (header->id != header_id) |
|
+ continue; |
|
+ return header->name; |
|
} |
|
- if (dl->json_output) { |
|
- dl->jw = jsonw_new(stdout); |
|
- if (!dl->jw) { |
|
- pr_err("Failed to create JSON writer\n"); |
|
- goto err_json_new; |
|
- } |
|
- jsonw_pretty(dl->jw, dl->pretty_output); |
|
+ return NULL; |
|
+} |
|
+ |
|
+static const char *dpipe_field_id2s(struct dpipe_ctx *ctx, |
|
+ uint32_t header_id, |
|
+ uint32_t field_id, bool global) |
|
+{ |
|
+ struct list_head *header_list; |
|
+ struct dpipe_header *header; |
|
+ |
|
+ if (global) |
|
+ header_list = &ctx->global_headers; |
|
+ else |
|
+ header_list = &ctx->local_headers; |
|
+ list_for_each_entry(header, header_list, list) { |
|
+ if (header->id != header_id) |
|
+ continue; |
|
+ return header->fields[field_id].name; |
|
} |
|
- return 0; |
|
+ return NULL; |
|
+} |
|
|
|
-err_json_new: |
|
- ifname_map_fini(dl); |
|
-err_ifname_map_create: |
|
- mnlg_socket_close(dl->nlg); |
|
- return err; |
|
+static const char * |
|
+dpipe_field_mapping_e2s(enum devlink_dpipe_field_mapping_type mapping_type) |
|
+{ |
|
+ switch (mapping_type) { |
|
+ case DEVLINK_DPIPE_FIELD_MAPPING_TYPE_NONE: |
|
+ return NULL; |
|
+ case DEVLINK_DPIPE_FIELD_MAPPING_TYPE_IFINDEX: |
|
+ return "ifindex"; |
|
+ default: |
|
+ return "<unknown>"; |
|
+ } |
|
} |
|
|
|
-static void dl_fini(struct dl *dl) |
|
+static const char * |
|
+dpipe_mapping_get(struct dpipe_ctx *ctx, uint32_t header_id, |
|
+ uint32_t field_id, bool global) |
|
{ |
|
- if (dl->json_output) |
|
- jsonw_destroy(&dl->jw); |
|
- ifname_map_fini(dl); |
|
- mnlg_socket_close(dl->nlg); |
|
+ enum devlink_dpipe_field_mapping_type mapping_type; |
|
+ struct list_head *header_list; |
|
+ struct dpipe_header *header; |
|
+ |
|
+ if (global) |
|
+ header_list = &ctx->global_headers; |
|
+ else |
|
+ header_list = &ctx->local_headers; |
|
+ list_for_each_entry(header, header_list, list) { |
|
+ if (header->id != header_id) |
|
+ continue; |
|
+ mapping_type = header->fields[field_id].mapping_type; |
|
+ return dpipe_field_mapping_e2s(mapping_type); |
|
+ } |
|
+ return NULL; |
|
} |
|
|
|
-static struct dl *dl_alloc(void) |
|
+static void pr_out_dpipe_fields(struct dpipe_ctx *ctx, |
|
+ struct dpipe_field *fields, |
|
+ unsigned int field_count) |
|
{ |
|
- struct dl *dl; |
|
+ struct dpipe_field *field; |
|
+ int i; |
|
|
|
- dl = calloc(1, sizeof(*dl)); |
|
- if (!dl) |
|
- return NULL; |
|
- return dl; |
|
+ for (i = 0; i < field_count; i++) { |
|
+ field = &fields[i]; |
|
+ pr_out_entry_start(ctx->dl); |
|
+ pr_out_str(ctx->dl, "name", field->name); |
|
+ if (ctx->dl->verbose) |
|
+ pr_out_uint(ctx->dl, "id", field->id); |
|
+ pr_out_uint(ctx->dl, "bitwidth", field->bitwidth); |
|
+ if (field->mapping_type) |
|
+ pr_out_str(ctx->dl, "mapping_type", |
|
+ dpipe_field_mapping_e2s(field->mapping_type)); |
|
+ pr_out_entry_end(ctx->dl); |
|
+ } |
|
} |
|
|
|
-static void dl_free(struct dl *dl) |
|
+static void |
|
+pr_out_dpipe_header(struct dpipe_ctx *ctx, struct nlattr **tb, |
|
+ struct dpipe_header *header, bool global) |
|
{ |
|
- free(dl); |
|
+ pr_out_handle_start_arr(ctx->dl, tb); |
|
+ pr_out_str(ctx->dl, "name", header->name); |
|
+ if (ctx->dl->verbose) { |
|
+ pr_out_uint(ctx->dl, "id", header->id); |
|
+ pr_out_str(ctx->dl, "global", |
|
+ global ? "true" : "false"); |
|
+ } |
|
+ pr_out_array_start(ctx->dl, "field"); |
|
+ pr_out_dpipe_fields(ctx, header->fields, |
|
+ header->fields_count); |
|
+ pr_out_array_end(ctx->dl); |
|
+ pr_out_handle_end(ctx->dl); |
|
} |
|
|
|
-int main(int argc, char **argv) |
|
+static void pr_out_dpipe_headers(struct dpipe_ctx *ctx, |
|
+ struct nlattr **tb) |
|
{ |
|
- static const struct option long_options[] = { |
|
- { "Version", no_argument, NULL, 'V' }, |
|
- { "no-nice-names", no_argument, NULL, 'n' }, |
|
- { "json", no_argument, NULL, 'j' }, |
|
- { "pretty", no_argument, NULL, 'p' }, |
|
- { NULL, 0, NULL, 0 } |
|
- }; |
|
- struct dl *dl; |
|
- int opt; |
|
+ struct dpipe_header *header; |
|
+ |
|
+ list_for_each_entry(header, &ctx->local_headers, list) |
|
+ pr_out_dpipe_header(ctx, tb, header, false); |
|
+ |
|
+ list_for_each_entry(header, &ctx->global_headers, list) |
|
+ pr_out_dpipe_header(ctx, tb, header, true); |
|
+} |
|
+ |
|
+static int dpipe_header_field_get(struct nlattr *nl, struct dpipe_field *field) |
|
+{ |
|
+ struct nlattr *nla_field[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ const char *name; |
|
int err; |
|
- int ret; |
|
|
|
- dl = dl_alloc(); |
|
- if (!dl) { |
|
- pr_err("Failed to allocate memory for devlink\n"); |
|
- return EXIT_FAILURE; |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_field); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ if (!nla_field[DEVLINK_ATTR_DPIPE_FIELD_ID] || |
|
+ !nla_field[DEVLINK_ATTR_DPIPE_FIELD_NAME] || |
|
+ !nla_field[DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH] || |
|
+ !nla_field[DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE]) |
|
+ return -EINVAL; |
|
+ |
|
+ name = mnl_attr_get_str(nla_field[DEVLINK_ATTR_DPIPE_FIELD_NAME]); |
|
+ field->id = mnl_attr_get_u32(nla_field[DEVLINK_ATTR_DPIPE_FIELD_ID]); |
|
+ field->bitwidth = mnl_attr_get_u32(nla_field[DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH]); |
|
+ field->name = strdup(name); |
|
+ if (!field->name) |
|
+ return -ENOMEM; |
|
+ field->mapping_type = mnl_attr_get_u32(nla_field[DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE]); |
|
+ return 0; |
|
+} |
|
+ |
|
+static int dpipe_header_fields_get(struct nlattr *nla_fields, |
|
+ struct dpipe_field *fields) |
|
+{ |
|
+ struct nlattr *nla_field; |
|
+ int count = 0; |
|
+ int err; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_field, nla_fields) { |
|
+ err = dpipe_header_field_get(nla_field, &fields[count]); |
|
+ if (err) |
|
+ return err; |
|
+ count++; |
|
} |
|
+ return 0; |
|
+} |
|
|
|
- while ((opt = getopt_long(argc, argv, "Vnjp", |
|
- long_options, NULL)) >= 0) { |
|
+static unsigned int dpipe_header_field_count_get(struct nlattr *nla_fields) |
|
+{ |
|
+ struct nlattr *nla_field; |
|
+ unsigned int count = 0; |
|
|
|
- switch (opt) { |
|
- case 'V': |
|
- printf("devlink utility, iproute2-ss%s\n", SNAPSHOT); |
|
- ret = EXIT_SUCCESS; |
|
- goto dl_free; |
|
- case 'n': |
|
- dl->no_nice_names = true; |
|
- break; |
|
- case 'j': |
|
- dl->json_output = true; |
|
- break; |
|
- case 'p': |
|
- dl->pretty_output = true; |
|
+ mnl_attr_for_each_nested(nla_field, nla_fields) |
|
+ count++; |
|
+ return count; |
|
+} |
|
+ |
|
+static int dpipe_header_get(struct dpipe_ctx *ctx, struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_header[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ struct dpipe_header *header; |
|
+ unsigned int fields_count; |
|
+ const char *header_name; |
|
+ bool global; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_header); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_header[DEVLINK_ATTR_DPIPE_HEADER_NAME] || |
|
+ !nla_header[DEVLINK_ATTR_DPIPE_HEADER_ID] || |
|
+ !nla_header[DEVLINK_ATTR_DPIPE_HEADER_FIELDS]) |
|
+ return -EINVAL; |
|
+ |
|
+ fields_count = dpipe_header_field_count_get(nla_header[DEVLINK_ATTR_DPIPE_HEADER_FIELDS]); |
|
+ header = dpipe_header_alloc(fields_count); |
|
+ if (!header) |
|
+ return -ENOMEM; |
|
+ |
|
+ header_name = mnl_attr_get_str(nla_header[DEVLINK_ATTR_DPIPE_HEADER_NAME]); |
|
+ header->name = strdup(header_name); |
|
+ header->id = mnl_attr_get_u32(nla_header[DEVLINK_ATTR_DPIPE_HEADER_ID]); |
|
+ header->fields_count = fields_count; |
|
+ global = !!mnl_attr_get_u8(nla_header[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL]); |
|
+ |
|
+ err = dpipe_header_fields_get(nla_header[DEVLINK_ATTR_DPIPE_HEADER_FIELDS], |
|
+ header->fields); |
|
+ if (err) |
|
+ goto err_field_get; |
|
+ dpipe_header_add(ctx, header, global); |
|
+ return 0; |
|
+ |
|
+err_field_get: |
|
+ dpipe_header_free(header); |
|
+ return err; |
|
+} |
|
+ |
|
+static int dpipe_headers_get(struct dpipe_ctx *ctx, struct nlattr **tb) |
|
+{ |
|
+ struct nlattr *nla_headers = tb[DEVLINK_ATTR_DPIPE_HEADERS]; |
|
+ struct nlattr *nla_header; |
|
+ int err; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_header, nla_headers) { |
|
+ err = dpipe_header_get(ctx, nla_header); |
|
+ if (err) |
|
+ return err; |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
+static int cmd_dpipe_header_cb(const struct nlmsghdr *nlh, void *data) |
|
+{ |
|
+ struct dpipe_ctx *ctx = data; |
|
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); |
|
+ int err; |
|
+ |
|
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb); |
|
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] || |
|
+ !tb[DEVLINK_ATTR_DPIPE_HEADERS]) |
|
+ return MNL_CB_ERROR; |
|
+ err = dpipe_headers_get(ctx, tb); |
|
+ if (err) { |
|
+ ctx->err = err; |
|
+ return MNL_CB_ERROR; |
|
+ } |
|
+ |
|
+ if (ctx->print_headers) |
|
+ pr_out_dpipe_headers(ctx, tb); |
|
+ return MNL_CB_OK; |
|
+} |
|
+ |
|
+static int cmd_dpipe_headers_show(struct dl *dl) |
|
+{ |
|
+ struct nlmsghdr *nlh; |
|
+ struct dpipe_ctx *ctx; |
|
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK; |
|
+ int err; |
|
+ |
|
+ nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_HEADERS_GET, flags); |
|
+ |
|
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0); |
|
+ if (err) |
|
+ return err; |
|
+ |
|
+ ctx = dpipe_ctx_alloc(dl); |
|
+ if (!ctx) |
|
+ return -ENOMEM; |
|
+ |
|
+ ctx->print_headers = true; |
|
+ |
|
+ pr_out_section_start(dl, "header"); |
|
+ err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_header_cb, ctx); |
|
+ if (err) |
|
+ pr_err("error get headers %s\n", strerror(ctx->err)); |
|
+ pr_out_section_end(dl); |
|
+ |
|
+ dpipe_ctx_clear(ctx); |
|
+ dpipe_ctx_free(ctx); |
|
+ return err; |
|
+} |
|
+ |
|
+static void cmd_dpipe_header_help(void) |
|
+{ |
|
+ pr_err("Usage: devlink dpipe headers show DEV\n"); |
|
+} |
|
+ |
|
+static int cmd_dpipe_header(struct dl *dl) |
|
+{ |
|
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) { |
|
+ cmd_dpipe_header_help(); |
|
+ return 0; |
|
+ } else if (dl_argv_match(dl, "show")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe_headers_show(dl); |
|
+ } |
|
+ pr_err("Command \"%s\" not found\n", dl_argv(dl)); |
|
+ return -ENOENT; |
|
+} |
|
+ |
|
+static const char |
|
+*dpipe_action_type_e2s(enum devlink_dpipe_action_type action_type) |
|
+{ |
|
+ switch (action_type) { |
|
+ case DEVLINK_DPIPE_ACTION_TYPE_FIELD_MODIFY: |
|
+ return "field_modify"; |
|
+ default: |
|
+ return "<unknown>"; |
|
+ } |
|
+} |
|
+ |
|
+static void pr_out_dpipe_action(struct dpipe_ctx *ctx, |
|
+ uint32_t header_id, uint32_t field_id, |
|
+ uint32_t action_type, bool global) |
|
+{ |
|
+ const char *mapping; |
|
+ |
|
+ pr_out_str(ctx->dl, "type", dpipe_action_type_e2s(action_type)); |
|
+ pr_out_str(ctx->dl, "header", dpipe_header_id2s(ctx, header_id, |
|
+ global)); |
|
+ pr_out_str(ctx->dl, "field", dpipe_field_id2s(ctx, header_id, field_id, |
|
+ global)); |
|
+ mapping = dpipe_mapping_get(ctx, header_id, field_id, global); |
|
+ if (mapping) |
|
+ pr_out_str(ctx->dl, "mapping", mapping); |
|
+} |
|
+ |
|
+static int dpipe_action_show(struct dpipe_ctx *ctx, struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_action[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ uint32_t header_id, field_id, action_type; |
|
+ bool global; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_action); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_action[DEVLINK_ATTR_DPIPE_ACTION_TYPE] || |
|
+ !nla_action[DEVLINK_ATTR_DPIPE_HEADER_INDEX] || |
|
+ !nla_action[DEVLINK_ATTR_DPIPE_HEADER_ID] || |
|
+ !nla_action[DEVLINK_ATTR_DPIPE_FIELD_ID]) { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ header_id = mnl_attr_get_u32(nla_action[DEVLINK_ATTR_DPIPE_HEADER_ID]); |
|
+ field_id = mnl_attr_get_u32(nla_action[DEVLINK_ATTR_DPIPE_FIELD_ID]); |
|
+ action_type = mnl_attr_get_u32(nla_action[DEVLINK_ATTR_DPIPE_ACTION_TYPE]); |
|
+ global = !!mnl_attr_get_u8(nla_action[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL]); |
|
+ |
|
+ pr_out_dpipe_action(ctx, header_id, field_id, action_type, global); |
|
+ return 0; |
|
+} |
|
+ |
|
+static int dpipe_table_actions_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr *nla_actions) |
|
+{ |
|
+ struct nlattr *nla_action; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_action, nla_actions) { |
|
+ pr_out_entry_start(ctx->dl); |
|
+ if (dpipe_action_show(ctx, nla_action)) |
|
+ goto err_action_show; |
|
+ pr_out_entry_end(ctx->dl); |
|
+ } |
|
+ return 0; |
|
+ |
|
+err_action_show: |
|
+ pr_out_entry_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static const char * |
|
+dpipe_match_type_e2s(enum devlink_dpipe_match_type match_type) |
|
+{ |
|
+ switch (match_type) { |
|
+ case DEVLINK_DPIPE_MATCH_TYPE_FIELD_EXACT: |
|
+ return "field_exact"; |
|
+ default: |
|
+ return "<unknown>"; |
|
+ } |
|
+} |
|
+ |
|
+static void pr_out_dpipe_match(struct dpipe_ctx *ctx, |
|
+ uint32_t header_id, uint32_t field_id, |
|
+ uint32_t match_type, bool global) |
|
+{ |
|
+ const char *mapping; |
|
+ |
|
+ pr_out_str(ctx->dl, "type", dpipe_match_type_e2s(match_type)); |
|
+ pr_out_str(ctx->dl, "header", dpipe_header_id2s(ctx, header_id, |
|
+ global)); |
|
+ pr_out_str(ctx->dl, "field", dpipe_field_id2s(ctx, header_id, field_id, |
|
+ global)); |
|
+ mapping = dpipe_mapping_get(ctx, header_id, field_id, global); |
|
+ if (mapping) |
|
+ pr_out_str(ctx->dl, "mapping", mapping); |
|
+ |
|
+} |
|
+ |
|
+static int dpipe_match_show(struct dpipe_ctx *ctx, struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_match[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ uint32_t header_id, field_id, match_type; |
|
+ bool global; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_match); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_match[DEVLINK_ATTR_DPIPE_MATCH_TYPE] || |
|
+ !nla_match[DEVLINK_ATTR_DPIPE_HEADER_INDEX] || |
|
+ !nla_match[DEVLINK_ATTR_DPIPE_HEADER_ID] || |
|
+ !nla_match[DEVLINK_ATTR_DPIPE_FIELD_ID]) { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ match_type = mnl_attr_get_u32(nla_match[DEVLINK_ATTR_DPIPE_MATCH_TYPE]); |
|
+ header_id = mnl_attr_get_u32(nla_match[DEVLINK_ATTR_DPIPE_HEADER_ID]); |
|
+ field_id = mnl_attr_get_u32(nla_match[DEVLINK_ATTR_DPIPE_FIELD_ID]); |
|
+ global = !!mnl_attr_get_u8(nla_match[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL]); |
|
+ |
|
+ pr_out_dpipe_match(ctx, header_id, field_id, match_type, global); |
|
+ return 0; |
|
+} |
|
+ |
|
+static int dpipe_table_matches_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr *nla_matches) |
|
+{ |
|
+ struct nlattr *nla_match; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_match, nla_matches) { |
|
+ pr_out_entry_start(ctx->dl); |
|
+ if (dpipe_match_show(ctx, nla_match)) |
|
+ goto err_match_show; |
|
+ pr_out_entry_end(ctx->dl); |
|
+ } |
|
+ return 0; |
|
+ |
|
+err_match_show: |
|
+ pr_out_entry_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int dpipe_table_show(struct dpipe_ctx *ctx, struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_table[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ bool counters_enabled; |
|
+ const char *name; |
|
+ uint32_t size; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_table); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_table[DEVLINK_ATTR_DPIPE_TABLE_NAME] || |
|
+ !nla_table[DEVLINK_ATTR_DPIPE_TABLE_SIZE] || |
|
+ !nla_table[DEVLINK_ATTR_DPIPE_TABLE_ACTIONS] || |
|
+ !nla_table[DEVLINK_ATTR_DPIPE_TABLE_MATCHES] || |
|
+ !nla_table[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED]) { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ name = mnl_attr_get_str(nla_table[DEVLINK_ATTR_DPIPE_TABLE_NAME]); |
|
+ size = mnl_attr_get_u32(nla_table[DEVLINK_ATTR_DPIPE_TABLE_SIZE]); |
|
+ counters_enabled = !!mnl_attr_get_u8(nla_table[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED]); |
|
+ |
|
+ pr_out_str(ctx->dl, "name", name); |
|
+ pr_out_uint(ctx->dl, "size", size); |
|
+ pr_out_str(ctx->dl, "counters_enabled", |
|
+ counters_enabled ? "true" : "false"); |
|
+ |
|
+ pr_out_array_start(ctx->dl, "match"); |
|
+ if (dpipe_table_matches_show(ctx, nla_table[DEVLINK_ATTR_DPIPE_TABLE_MATCHES])) |
|
+ goto err_matches_show; |
|
+ pr_out_array_end(ctx->dl); |
|
+ |
|
+ pr_out_array_start(ctx->dl, "action"); |
|
+ if (dpipe_table_actions_show(ctx, nla_table[DEVLINK_ATTR_DPIPE_TABLE_ACTIONS])) |
|
+ goto err_actions_show; |
|
+ pr_out_array_end(ctx->dl); |
|
+ |
|
+ return 0; |
|
+ |
|
+err_actions_show: |
|
+err_matches_show: |
|
+ pr_out_array_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int dpipe_tables_show(struct dpipe_ctx *ctx, struct nlattr **tb) |
|
+{ |
|
+ struct nlattr *nla_tables = tb[DEVLINK_ATTR_DPIPE_TABLES]; |
|
+ struct nlattr *nla_table; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_table, nla_tables) { |
|
+ pr_out_handle_start_arr(ctx->dl, tb); |
|
+ if (dpipe_table_show(ctx, nla_table)) |
|
+ goto err_table_show; |
|
+ pr_out_handle_end(ctx->dl); |
|
+ } |
|
+ return 0; |
|
+ |
|
+err_table_show: |
|
+ pr_out_handle_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int cmd_dpipe_table_show_cb(const struct nlmsghdr *nlh, void *data) |
|
+{ |
|
+ struct dpipe_ctx *ctx = data; |
|
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); |
|
+ |
|
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb); |
|
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] || |
|
+ !tb[DEVLINK_ATTR_DPIPE_TABLES]) |
|
+ return MNL_CB_ERROR; |
|
+ |
|
+ if (dpipe_tables_show(ctx, tb)) |
|
+ return MNL_CB_ERROR; |
|
+ return MNL_CB_OK; |
|
+} |
|
+ |
|
+static int cmd_dpipe_table_show(struct dl *dl) |
|
+{ |
|
+ struct nlmsghdr *nlh; |
|
+ struct dpipe_ctx *ctx; |
|
+ uint16_t flags = NLM_F_REQUEST; |
|
+ int err; |
|
+ |
|
+ ctx = dpipe_ctx_alloc(dl); |
|
+ if (!ctx) |
|
+ return -ENOMEM; |
|
+ |
|
+ err = dl_argv_parse(dl, DL_OPT_HANDLE, DL_OPT_DPIPE_TABLE_NAME); |
|
+ if (err) |
|
+ goto out; |
|
+ |
|
+ nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_HEADERS_GET, flags); |
|
+ dl_opts_put(nlh, dl); |
|
+ err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_header_cb, ctx); |
|
+ if (err) { |
|
+ pr_err("error get headers %s\n", strerror(ctx->err)); |
|
+ goto out; |
|
+ } |
|
+ |
|
+ flags = NLM_F_REQUEST | NLM_F_ACK; |
|
+ nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_TABLE_GET, flags); |
|
+ dl_opts_put(nlh, dl); |
|
+ |
|
+ pr_out_section_start(dl, "table"); |
|
+ _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_table_show_cb, ctx); |
|
+ pr_out_section_end(dl); |
|
+out: |
|
+ dpipe_ctx_clear(ctx); |
|
+ dpipe_ctx_free(ctx); |
|
+ return err; |
|
+} |
|
+ |
|
+static int cmd_dpipe_table_set(struct dl *dl) |
|
+{ |
|
+ struct nlmsghdr *nlh; |
|
+ int err; |
|
+ |
|
+ nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_TABLE_COUNTERS_SET, |
|
+ NLM_F_REQUEST | NLM_F_ACK); |
|
+ |
|
+ err = dl_argv_parse_put(nlh, dl, |
|
+ DL_OPT_HANDLE | DL_OPT_DPIPE_TABLE_NAME | |
|
+ DL_OPT_DPIPE_TABLE_COUNTERS, 0); |
|
+ if (err) |
|
+ return err; |
|
+ |
|
+ return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL); |
|
+} |
|
+ |
|
+static int dpipe_entry_value_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr **nla_match_value) |
|
+{ |
|
+ uint16_t value_len; |
|
+ bool mask, mapping; |
|
+ |
|
+ mask = !!nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MASK]; |
|
+ mapping = !!nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MAPPING]; |
|
+ |
|
+ value_len = mnl_attr_get_payload_len(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]); |
|
+ if (value_len == sizeof(uint32_t)) { |
|
+ uint32_t value, value_mask, value_mapping; |
|
+ |
|
+ if (mapping) { |
|
+ value_mapping = mnl_attr_get_u32(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MAPPING]); |
|
+ pr_out_uint(ctx->dl, "mapping_value", value_mapping); |
|
+ } |
|
+ |
|
+ if (mask) { |
|
+ value_mask = mnl_attr_get_u32(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MASK]); |
|
+ pr_out_uint(ctx->dl, "mask_value", value_mask); |
|
+ } |
|
+ |
|
+ value = mnl_attr_get_u32(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]); |
|
+ pr_out_uint(ctx->dl, "value", value); |
|
+ |
|
+ } else { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ return 0; |
|
+} |
|
+ |
|
+static int dpipe_entry_match_value_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_match_value[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_match_value); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_match_value[DEVLINK_ATTR_DPIPE_MATCH] || |
|
+ !nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]) { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ pr_out_entry_start(ctx->dl); |
|
+ if (dpipe_match_show(ctx, nla_match_value[DEVLINK_ATTR_DPIPE_MATCH])) |
|
+ goto err_match_show; |
|
+ if (dpipe_entry_value_show(ctx, nla_match_value)) |
|
+ goto err_value_show; |
|
+ pr_out_entry_end(ctx->dl); |
|
+ |
|
+ return 0; |
|
+ |
|
+err_match_show: |
|
+err_value_show: |
|
+ pr_out_entry_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int dpipe_entry_action_value_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_action_value[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_action_value); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_action_value[DEVLINK_ATTR_DPIPE_ACTION] || |
|
+ !nla_action_value[DEVLINK_ATTR_DPIPE_VALUE]) { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ pr_out_entry_start(ctx->dl); |
|
+ if (dpipe_action_show(ctx, nla_action_value[DEVLINK_ATTR_DPIPE_ACTION])) |
|
+ goto err_action_show; |
|
+ if (dpipe_entry_value_show(ctx, nla_action_value)) |
|
+ goto err_value_show; |
|
+ pr_out_entry_end(ctx->dl); |
|
+ |
|
+ return 0; |
|
+ |
|
+err_action_show: |
|
+err_value_show: |
|
+ pr_out_entry_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int |
|
+dpipe_tables_action_values_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr *nla_action_values) |
|
+{ |
|
+ struct nlattr *nla_action_value; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_action_value, nla_action_values) { |
|
+ if (dpipe_entry_action_value_show(ctx, nla_action_value)) |
|
+ return -EINVAL; |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
+static int |
|
+dpipe_tables_match_values_show(struct dpipe_ctx *ctx, |
|
+ struct nlattr *nla_match_values) |
|
+{ |
|
+ struct nlattr *nla_match_value; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_match_value, nla_match_values) { |
|
+ if (dpipe_entry_match_value_show(ctx, nla_match_value)) |
|
+ return -EINVAL; |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
+static int dpipe_entry_show(struct dpipe_ctx *ctx, struct nlattr *nl) |
|
+{ |
|
+ struct nlattr *nla_entry[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ uint32_t entry_index; |
|
+ uint64_t counter; |
|
+ int err; |
|
+ |
|
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_entry); |
|
+ if (err != MNL_CB_OK) |
|
+ return -EINVAL; |
|
+ |
|
+ if (!nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_INDEX] || |
|
+ !nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES] || |
|
+ !nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES]) { |
|
+ return -EINVAL; |
|
+ } |
|
+ |
|
+ entry_index = mnl_attr_get_u32(nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_INDEX]); |
|
+ pr_out_uint(ctx->dl, "index", entry_index); |
|
+ |
|
+ if (nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_COUNTER]) { |
|
+ counter = mnl_attr_get_u64(nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_COUNTER]); |
|
+ pr_out_uint(ctx->dl, "counter", counter); |
|
+ } |
|
+ |
|
+ pr_out_array_start(ctx->dl, "match_value"); |
|
+ if (dpipe_tables_match_values_show(ctx, |
|
+ nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES])) |
|
+ goto err_match_values_show; |
|
+ pr_out_array_end(ctx->dl); |
|
+ |
|
+ pr_out_array_start(ctx->dl, "action_value"); |
|
+ if (dpipe_tables_action_values_show(ctx, |
|
+ nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES])) |
|
+ goto err_action_values_show; |
|
+ pr_out_array_end(ctx->dl); |
|
+ return 0; |
|
+ |
|
+err_action_values_show: |
|
+err_match_values_show: |
|
+ pr_out_array_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int dpipe_table_entries_show(struct dpipe_ctx *ctx, struct nlattr **tb) |
|
+{ |
|
+ struct nlattr *nla_entries = tb[DEVLINK_ATTR_DPIPE_ENTRIES]; |
|
+ struct nlattr *nla_entry; |
|
+ |
|
+ mnl_attr_for_each_nested(nla_entry, nla_entries) { |
|
+ pr_out_handle_start_arr(ctx->dl, tb); |
|
+ if (dpipe_entry_show(ctx, nla_entry)) |
|
+ goto err_entry_show; |
|
+ pr_out_handle_end(ctx->dl); |
|
+ } |
|
+ return 0; |
|
+ |
|
+err_entry_show: |
|
+ pr_out_handle_end(ctx->dl); |
|
+ return -EINVAL; |
|
+} |
|
+ |
|
+static int cmd_dpipe_table_entry_dump_cb(const struct nlmsghdr *nlh, void *data) |
|
+{ |
|
+ struct dpipe_ctx *ctx = data; |
|
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {}; |
|
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); |
|
+ |
|
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb); |
|
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] || |
|
+ !tb[DEVLINK_ATTR_DPIPE_ENTRIES]) |
|
+ return MNL_CB_ERROR; |
|
+ |
|
+ if (dpipe_table_entries_show(ctx, tb)) |
|
+ return MNL_CB_ERROR; |
|
+ return MNL_CB_OK; |
|
+} |
|
+ |
|
+static int cmd_dpipe_table_dump(struct dl *dl) |
|
+{ |
|
+ struct nlmsghdr *nlh; |
|
+ struct dpipe_ctx *ctx; |
|
+ uint16_t flags = NLM_F_REQUEST; |
|
+ int err; |
|
+ |
|
+ ctx = dpipe_ctx_alloc(dl); |
|
+ if (!ctx) |
|
+ return -ENOMEM; |
|
+ |
|
+ err = dl_argv_parse(dl, DL_OPT_HANDLE | DL_OPT_DPIPE_TABLE_NAME, 0); |
|
+ if (err) |
|
+ goto out; |
|
+ |
|
+ nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_HEADERS_GET, flags); |
|
+ dl_opts_put(nlh, dl); |
|
+ err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_header_cb, ctx); |
|
+ if (err) { |
|
+ pr_err("error get headers %s\n", strerror(ctx->err)); |
|
+ goto out; |
|
+ } |
|
+ |
|
+ flags = NLM_F_REQUEST | NLM_F_ACK; |
|
+ nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_ENTRIES_GET, flags); |
|
+ dl_opts_put(nlh, dl); |
|
+ |
|
+ pr_out_section_start(dl, "table_entry"); |
|
+ _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_table_entry_dump_cb, ctx); |
|
+ pr_out_section_end(dl); |
|
+out: |
|
+ dpipe_ctx_clear(ctx); |
|
+ dpipe_ctx_free(ctx); |
|
+ return err; |
|
+} |
|
+ |
|
+static void cmd_dpipe_table_help(void) |
|
+{ |
|
+ pr_err("Usage: devlink dpipe table [ OBJECT-LIST ]\n" |
|
+ "where OBJECT-LIST := { show | set | dump }\n"); |
|
+} |
|
+ |
|
+static int cmd_dpipe_table(struct dl *dl) |
|
+{ |
|
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) { |
|
+ cmd_dpipe_table_help(); |
|
+ return 0; |
|
+ } else if (dl_argv_match(dl, "show")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe_table_show(dl); |
|
+ } else if (dl_argv_match(dl, "set")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe_table_set(dl); |
|
+ } else if (dl_argv_match(dl, "dump")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe_table_dump(dl); |
|
+ } |
|
+ pr_err("Command \"%s\" not found\n", dl_argv(dl)); |
|
+ return -ENOENT; |
|
+} |
|
+ |
|
+static void cmd_dpipe_help(void) |
|
+{ |
|
+ pr_err("Usage: devlink dpipe [ OBJECT-LIST ]\n" |
|
+ "where OBJECT-LIST := { header | table }\n"); |
|
+} |
|
+ |
|
+static int cmd_dpipe(struct dl *dl) |
|
+{ |
|
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) { |
|
+ cmd_dpipe_help(); |
|
+ return 0; |
|
+ } else if (dl_argv_match(dl, "header")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe_header(dl); |
|
+ } else if (dl_argv_match(dl, "table")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe_table(dl); |
|
+ } |
|
+ pr_err("Command \"%s\" not found\n", dl_argv(dl)); |
|
+ return -ENOENT; |
|
+} |
|
+ |
|
+static void help(void) |
|
+{ |
|
+ pr_err("Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n" |
|
+ "where OBJECT := { dev | port | sb | monitor | dpipe }\n" |
|
+ " OPTIONS := { -V[ersion] | -n[no-nice-names] | -j[json] | -p[pretty] | -v[verbose] }\n"); |
|
+} |
|
+ |
|
+static int dl_cmd(struct dl *dl) |
|
+{ |
|
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) { |
|
+ help(); |
|
+ return 0; |
|
+ } else if (dl_argv_match(dl, "dev")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dev(dl); |
|
+ } else if (dl_argv_match(dl, "port")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_port(dl); |
|
+ } else if (dl_argv_match(dl, "sb")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_sb(dl); |
|
+ } else if (dl_argv_match(dl, "monitor")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_mon(dl); |
|
+ } else if (dl_argv_match(dl, "dpipe")) { |
|
+ dl_arg_inc(dl); |
|
+ return cmd_dpipe(dl); |
|
+ } |
|
+ pr_err("Object \"%s\" not found\n", dl_argv(dl)); |
|
+ return -ENOENT; |
|
+} |
|
+ |
|
+static int dl_init(struct dl *dl, int argc, char **argv) |
|
+{ |
|
+ int err; |
|
+ |
|
+ dl->argc = argc; |
|
+ dl->argv = argv; |
|
+ |
|
+ dl->nlg = mnlg_socket_open(DEVLINK_GENL_NAME, DEVLINK_GENL_VERSION); |
|
+ if (!dl->nlg) { |
|
+ pr_err("Failed to connect to devlink Netlink\n"); |
|
+ return -errno; |
|
+ } |
|
+ |
|
+ err = ifname_map_init(dl); |
|
+ if (err) { |
|
+ pr_err("Failed to create index map\n"); |
|
+ goto err_ifname_map_create; |
|
+ } |
|
+ if (dl->json_output) { |
|
+ dl->jw = jsonw_new(stdout); |
|
+ if (!dl->jw) { |
|
+ pr_err("Failed to create JSON writer\n"); |
|
+ goto err_json_new; |
|
+ } |
|
+ jsonw_pretty(dl->jw, dl->pretty_output); |
|
+ } |
|
+ return 0; |
|
+ |
|
+err_json_new: |
|
+ ifname_map_fini(dl); |
|
+err_ifname_map_create: |
|
+ mnlg_socket_close(dl->nlg); |
|
+ return err; |
|
+} |
|
+ |
|
+static void dl_fini(struct dl *dl) |
|
+{ |
|
+ if (dl->json_output) |
|
+ jsonw_destroy(&dl->jw); |
|
+ ifname_map_fini(dl); |
|
+ mnlg_socket_close(dl->nlg); |
|
+} |
|
+ |
|
+static struct dl *dl_alloc(void) |
|
+{ |
|
+ struct dl *dl; |
|
+ |
|
+ dl = calloc(1, sizeof(*dl)); |
|
+ if (!dl) |
|
+ return NULL; |
|
+ return dl; |
|
+} |
|
+ |
|
+static void dl_free(struct dl *dl) |
|
+{ |
|
+ free(dl); |
|
+} |
|
+ |
|
+int main(int argc, char **argv) |
|
+{ |
|
+ static const struct option long_options[] = { |
|
+ { "Version", no_argument, NULL, 'V' }, |
|
+ { "no-nice-names", no_argument, NULL, 'n' }, |
|
+ { "json", no_argument, NULL, 'j' }, |
|
+ { "pretty", no_argument, NULL, 'p' }, |
|
+ { "verbose", no_argument, NULL, 'v' }, |
|
+ { NULL, 0, NULL, 0 } |
|
+ }; |
|
+ struct dl *dl; |
|
+ int opt; |
|
+ int err; |
|
+ int ret; |
|
+ |
|
+ dl = dl_alloc(); |
|
+ if (!dl) { |
|
+ pr_err("Failed to allocate memory for devlink\n"); |
|
+ return EXIT_FAILURE; |
|
+ } |
|
+ |
|
+ while ((opt = getopt_long(argc, argv, "Vnjpv", |
|
+ long_options, NULL)) >= 0) { |
|
+ |
|
+ switch (opt) { |
|
+ case 'V': |
|
+ printf("devlink utility, iproute2-ss%s\n", SNAPSHOT); |
|
+ ret = EXIT_SUCCESS; |
|
+ goto dl_free; |
|
+ case 'n': |
|
+ dl->no_nice_names = true; |
|
+ break; |
|
+ case 'j': |
|
+ dl->json_output = true; |
|
+ break; |
|
+ case 'p': |
|
+ dl->pretty_output = true; |
|
+ break; |
|
+ case 'v': |
|
+ dl->verbose = true; |
|
break; |
|
default: |
|
pr_err("Unknown option.\n"); |
|
-- |
|
1.8.3.1 |
|
|
|
|