diff options
-rw-r--r-- | ethtool.8.in | 31 | ||||
-rw-r--r-- | ethtool.c | 448 | ||||
-rw-r--r-- | test-cmdline.c | 7 | ||||
-rw-r--r-- | test-features.c | 345 |
4 files changed, 707 insertions, 124 deletions
diff --git a/ethtool.8.in b/ethtool.8.in index 523b737..70ae31d 100644 --- a/ethtool.8.in +++ b/ethtool.8.in @@ -199,23 +199,13 @@ ethtool \- query or control network driver and hardware settings .BN length .BN value .HP -.B ethtool \-k|\-\-show\-offload +.B ethtool \-k|\-\-show\-features|\-\-show\-offload .I devname .HP -.B ethtool \-K|\-\-offload -.I devname -.B2 rx on off -.B2 tx on off -.B2 sg on off -.B2 tso on off -.B2 ufo on off -.B2 gso on off -.B2 gro on off -.B2 lro on off -.B2 rxvlan on off -.B2 txvlan on off -.B2 ntuple on off -.B2 rxhash on off +.B ethtool \-K|\-\-features|\-\-offload +.I devname feature +.A1 on off +.RB ... .HP .B ethtool \-p|\-\-identify .I devname @@ -428,11 +418,14 @@ parameters allow writing to certain portions of the EEPROM. Because of the persistent nature of writing to the EEPROM, a device-specific magic key must be specified to prevent the accidental writing to the EEPROM. .TP -.B \-k \-\-show\-offload -Queries the specified network device for offload information. +.B \-k \-\-show\-features \-\-show\-offload +Queries the specified network device for the state of protocol +offload and other features. .TP -.B \-K \-\-offload -Changes the offload parameters of the specified network device. +.B \-K \-\-features \-\-offload +Changes the offload parameters and other features of the specified +network device. The following feature names are built-in and others +may be defined by the kernel. .TP .A2 rx on off Specifies whether RX checksumming should be enabled. @@ -128,38 +128,63 @@ static const struct flag_info flags_msglvl[] = { { "wol", NETIF_MSG_WOL }, }; -static const struct { +struct off_flag_def { const char *short_name; const char *long_name; + const char *kernel_name; u32 get_cmd, set_cmd; u32 value; -} off_flag_def[] = { - { "rx", "rx-checksumming", +}; +static const struct off_flag_def off_flag_def[] = { + { "rx", "rx-checksumming", "rx-checksum", ETHTOOL_GRXCSUM, ETHTOOL_SRXCSUM, ETH_FLAG_RXCSUM }, - { "tx", "tx-checksumming", + { "tx", "tx-checksumming", "tx-checksum-*", ETHTOOL_GTXCSUM, ETHTOOL_STXCSUM, ETH_FLAG_TXCSUM }, - { "sg", "scatter-gather", + { "sg", "scatter-gather", "tx-scatter-gather*", ETHTOOL_GSG, ETHTOOL_SSG, ETH_FLAG_SG }, - { "tso", "tcp-segmentation-offload", + { "tso", "tcp-segmentation-offload", "tx-tcp*-segmentation", ETHTOOL_GTSO, ETHTOOL_STSO, ETH_FLAG_TSO }, - { "ufo", "udp-fragmentation-offload", + { "ufo", "udp-fragmentation-offload", "tx-udp-fragmentation", ETHTOOL_GUFO, ETHTOOL_SUFO, ETH_FLAG_UFO }, - { "gso", "generic-segmentation-offload", + { "gso", "generic-segmentation-offload", "tx-generic-segmentation", ETHTOOL_GGSO, ETHTOOL_SGSO, ETH_FLAG_GSO }, - { "gro", "generic-receive-offload", + { "gro", "generic-receive-offload", "rx-gro", ETHTOOL_GGRO, ETHTOOL_SGRO, ETH_FLAG_GRO }, - { "lro", "large-receive-offload", + { "lro", "large-receive-offload", "rx-lro", 0, 0, ETH_FLAG_LRO }, - { "rxvlan", "rx-vlan-offload", + { "rxvlan", "rx-vlan-offload", "rx-vlan-hw-parse", 0, 0, ETH_FLAG_RXVLAN }, - { "txvlan", "tx-vlan-offload", + { "txvlan", "tx-vlan-offload", "tx-vlan-hw-insert", 0, 0, ETH_FLAG_TXVLAN }, - { "ntuple", "ntuple-filters", + { "ntuple", "ntuple-filters", "rx-ntuple-filter", 0, 0, ETH_FLAG_NTUPLE }, - { "rxhash", "receive-hashing", + { "rxhash", "receive-hashing", "rx-hashing", 0, 0, ETH_FLAG_RXHASH }, }; +struct feature_def { + char name[ETH_GSTRING_LEN]; + int off_flag_index; /* index in off_flag_def; negative if none match */ +}; + +struct feature_defs { + size_t n_features; + /* Number of features each offload flag is associated with */ + unsigned int off_flag_matched[ARRAY_SIZE(off_flag_def)]; + /* Name and offload flag index for each feature */ + struct feature_def def[0]; +}; + +#define FEATURE_BITS_TO_BLOCKS(n_bits) DIV_ROUND_UP(n_bits, 32U) +#define FEATURE_WORD(blocks, index, field) ((blocks)[(index) / 32U].field) +#define FEATURE_FIELD_FLAG(index) (1U << (index) % 32U) +#define FEATURE_BIT_SET(blocks, index, field) \ + (FEATURE_WORD(blocks, index, field) |= FEATURE_FIELD_FLAG(index)) +#define FEATURE_BIT_CLEAR(blocks, index, field) \ + (FEATURE_WORD(blocks, index, filed) &= ~FEATURE_FIELD_FLAG(index)) +#define FEATURE_BIT_IS_SET(blocks, index, field) \ + (FEATURE_WORD(blocks, index, field) & FEATURE_FIELD_FLAG(index)) + static long long get_int_range(char *str, int base, long long min, long long max) { @@ -1069,21 +1094,84 @@ static int dump_coalesce(const struct ethtool_coalesce *ecoal) return 0; } -static int dump_offload(u32 active, u32 mask) +struct feature_state { + u32 off_flags; + struct ethtool_gfeatures features; +}; + +static void dump_one_feature(const char *indent, const char *name, + const struct feature_state *state, + const struct feature_state *ref_state, + u32 index) +{ + if (ref_state && + !(FEATURE_BIT_IS_SET(state->features.features, index, active) ^ + FEATURE_BIT_IS_SET(ref_state->features.features, index, active))) + return; + + printf("%s%s: %s%s\n", + indent, name, + FEATURE_BIT_IS_SET(state->features.features, index, active) ? + "on" : "off", + (!FEATURE_BIT_IS_SET(state->features.features, index, available) + || FEATURE_BIT_IS_SET(state->features.features, index, + never_changed)) + ? " [fixed]" + : (FEATURE_BIT_IS_SET(state->features.features, index, requested) + ^ FEATURE_BIT_IS_SET(state->features.features, index, active)) + ? (FEATURE_BIT_IS_SET(state->features.features, index, requested) + ? " [requested on]" : " [requested off]") + : ""); +} + +static void dump_features(const struct feature_defs *defs, + const struct feature_state *state, + const struct feature_state *ref_state) { u32 value; - int i; + int indent; + int i, j; for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) { value = off_flag_def[i].value; - if (!(mask & value)) - continue; - printf("%s: %s\n", - off_flag_def[i].long_name, - (active & value) ? "on" : "off"); + + /* If this offload flag matches exactly one generic + * feature then it's redundant to show the flag and + * feature states separately. Otherwise, show the + * flag state first. + */ + if (defs->off_flag_matched[i] != 1 && + (!ref_state || + (state->off_flags ^ ref_state->off_flags) & value)) { + printf("%s: %s\n", + off_flag_def[i].long_name, + (state->off_flags & value) ? "on" : "off"); + indent = 1; + } else { + indent = 0; + } + + /* Show matching features */ + for (j = 0; j < defs->n_features; j++) { + if (defs->def[j].off_flag_index != i) + continue; + if (defs->off_flag_matched[i] != 1) + /* Show all matching feature states */ + dump_one_feature(indent ? "\t" : "", + defs->def[j].name, + state, ref_state, j); + else + /* Show full state with the old flag name */ + dump_one_feature("", off_flag_def[i].long_name, + state, ref_state, j); + } } - return 0; + /* Show all unmatched features that have non-null names */ + for (j = 0; j < defs->n_features; j++) + if (defs->def[j].off_flag_index < 0 && defs->def[j].name[0]) + dump_one_feature("", defs->def[j].name, + state, ref_state, j); } static int dump_rxfhash(int fhash, u64 val) @@ -1259,6 +1347,72 @@ get_stringset(struct cmd_context *ctx, enum ethtool_stringset set_id, return strings; } +static struct feature_defs *get_feature_defs(struct cmd_context *ctx) +{ + struct ethtool_gstrings *names; + struct feature_defs *defs; + u32 n_features; + int i, j; + + names = get_stringset(ctx, ETH_SS_FEATURES, 0); + if (names) { + n_features = names->len; + } else if (errno == EOPNOTSUPP || errno == EINVAL) { + /* Kernel doesn't support named features; not an error */ + n_features = 0; + } else { + return NULL; + } + + defs = malloc(sizeof(*defs) + sizeof(defs->def[0]) * n_features); + if (!defs) + return NULL; + + defs->n_features = n_features; + memset(defs->off_flag_matched, 0, sizeof(defs->off_flag_matched)); + + /* Copy out feature names and find those associated with legacy flags */ + for (i = 0; i < defs->n_features; i++) { + memcpy(defs->def[i].name, names->data + i * ETH_GSTRING_LEN, + ETH_GSTRING_LEN); + defs->def[i].off_flag_index = -1; + + for (j = 0; + j < ARRAY_SIZE(off_flag_def) && + defs->def[i].off_flag_index < 0; + j++) { + const char *pattern = + off_flag_def[j].kernel_name; + const char *name = defs->def[i].name; + for (;;) { + if (*pattern == '*') { + /* There is only one wildcard; so + * switch to a suffix comparison */ + size_t pattern_len = + strlen(pattern + 1); + size_t name_len = strlen(name); + if (name_len < pattern_len) + break; /* name is too short */ + name += name_len - pattern_len; + ++pattern; + } else if (*pattern != *name) { + break; /* mismatch */ + } else if (*pattern == 0) { + defs->def[i].off_flag_index = j; + defs->off_flag_matched[j]++; + break; + } else { + ++name; + ++pattern; + } + } + } + } + + free(names); + return defs; +} + static int do_gdrv(struct cmd_context *ctx) { int err; @@ -1649,14 +1803,22 @@ static int do_scoalesce(struct cmd_context *ctx) return 0; } -static int get_offload(struct cmd_context *ctx, u32 *flags) +static struct feature_state * +get_features(struct cmd_context *ctx, const struct feature_defs *defs) { + struct feature_state *state; struct ethtool_value eval; int err, allfail = 1; u32 value; int i; - *flags = 0; + state = malloc(sizeof(*state) + + FEATURE_BITS_TO_BLOCKS(defs->n_features) * + sizeof(state->features.features[0])); + if (!state) + return NULL; + + state->off_flags = 0; for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) { value = off_flag_def[i].value; @@ -1670,7 +1832,7 @@ static int get_offload(struct cmd_context *ctx, u32 *flags) off_flag_def[i].long_name); } else { if (eval.data) - *flags |= value; + state->off_flags |= value; allfail = 0; } } @@ -1680,101 +1842,214 @@ static int get_offload(struct cmd_context *ctx, u32 *flags) if (err) { perror("Cannot get device flags"); } else { - *flags |= eval.data & ETH_FLAG_EXT_MASK; + state->off_flags |= eval.data & ETH_FLAG_EXT_MASK; allfail = 0; } - return allfail; + if (defs->n_features) { + state->features.cmd = ETHTOOL_GFEATURES; + state->features.size = FEATURE_BITS_TO_BLOCKS(defs->n_features); + err = send_ioctl(ctx, &state->features); + if (err) + perror("Cannot get device generic features"); + else + allfail = 0; + } + + if (allfail) { + free(state); + return NULL; + } + + return state; } -static int do_goffload(struct cmd_context *ctx) +static int do_gfeatures(struct cmd_context *ctx) { - u32 flags; + struct feature_defs *defs; + struct feature_state *features; if (ctx->argc != 0) exit_bad_args(); - fprintf(stdout, "Offload parameters for %s:\n", ctx->devname); + defs = get_feature_defs(ctx); + if (!defs) + return 1; + + fprintf(stdout, "Features for %s:\n", ctx->devname); - if (get_offload(ctx, &flags)) { - fprintf(stdout, "no offload info available\n"); - return 83; + features = get_features(ctx, defs); + if (!features) { + fprintf(stdout, "no feature info available\n"); + return 1; } - return dump_offload(flags, ~(u32)0); + dump_features(defs, features, NULL); + return 0; } -static int do_soffload(struct cmd_context *ctx) +static int do_sfeatures(struct cmd_context *ctx) { - int goffload_changed = 0; + struct feature_defs *defs; + int any_changed = 0, any_mismatch = 0; u32 off_flags_wanted = 0; u32 off_flags_mask = 0; - struct cmdline_info cmdline_offload[ARRAY_SIZE(off_flag_def)]; - u32 old_flags, new_flags, diff; + struct ethtool_sfeatures *efeatures; + struct cmdline_info *cmdline_features; + struct feature_state *old_state, *new_state; struct ethtool_value eval; int err; - int i; + int i, j; + + defs = get_feature_defs(ctx); + if (!defs) + return 1; + if (defs->n_features) { + efeatures = malloc(sizeof(*efeatures) + + FEATURE_BITS_TO_BLOCKS(defs->n_features) * + sizeof(efeatures->features[0])); + if (!efeatures) { + perror("Cannot parse arguments"); + return 1; + } + efeatures->cmd = ETHTOOL_SFEATURES; + efeatures->size = FEATURE_BITS_TO_BLOCKS(defs->n_features); + memset(efeatures->features, 0, + FEATURE_BITS_TO_BLOCKS(defs->n_features) * + sizeof(efeatures->features[0])); + } else { + efeatures = NULL; + } + /* Generate cmdline_info for legacy flags and kernel-named + * features, and parse our arguments. + */ + cmdline_features = calloc(ARRAY_SIZE(off_flag_def) + defs->n_features, + sizeof(cmdline_features[0])); + if (!cmdline_features) { + perror("Cannot parse arguments"); + return 1; + } for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) flag_to_cmdline_info(off_flag_def[i].short_name, off_flag_def[i].value, &off_flags_wanted, &off_flags_mask, - &cmdline_offload[i]); - - parse_generic_cmdline(ctx, &goffload_changed, - cmdline_offload, ARRAY_SIZE(cmdline_offload)); + &cmdline_features[i]); + for (i = 0; i < defs->n_features; i++) + flag_to_cmdline_info( + defs->def[i].name, FEATURE_FIELD_FLAG(i), + &FEATURE_WORD(efeatures->features, i, requested), + &FEATURE_WORD(efeatures->features, i, valid), + &cmdline_features[ARRAY_SIZE(off_flag_def) + i]); + parse_generic_cmdline(ctx, &any_changed, cmdline_features, + ARRAY_SIZE(off_flag_def) + defs->n_features); + free(cmdline_features); + + if (!any_changed) { + fprintf(stdout, "no features changed\n"); + return 0; + } - if (get_offload(ctx, &old_flags)) { - fprintf(stderr, "no offload info available\n"); + old_state = get_features(ctx, defs); + if (!old_state) return 1; - } + /* For each offload that the user specified, update any + * related features that the user did not specify and that + * are not fixed. Warn if all related features are fixed. + */ for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) { - if (!off_flag_def[i].set_cmd) + int fixed = 1; + + if (!(off_flags_mask & off_flag_def[i].value)) continue; - if (off_flags_mask & off_flag_def[i].value) { - eval.cmd = off_flag_def[i].set_cmd; - eval.data = !!(off_flags_wanted & - off_flag_def[i].value); - err = send_ioctl(ctx, &eval); - if (err) { - fprintf(stderr, - "Cannot set device %s settings: %m\n", - off_flag_def[i].long_name); - return 1; + + for (j = 0; j < defs->n_features; j++) { + if (defs->def[j].off_flag_index != i || + !FEATURE_BIT_IS_SET(old_state->features.features, + j, available) || + FEATURE_BIT_IS_SET(old_state->features.features, + j, never_changed)) + continue; + + fixed = 0; + if (!FEATURE_BIT_IS_SET(efeatures->features, j, valid)) { + FEATURE_BIT_SET(efeatures->features, j, valid); + if (off_flags_wanted & off_flag_def[i].value) + FEATURE_BIT_SET(efeatures->features, j, + requested); } } + + if (fixed) + fprintf(stderr, "Cannot change %s\n", + off_flag_def[i].long_name); } - if (off_flags_mask & ETH_FLAG_EXT_MASK) { - eval.cmd = ETHTOOL_SFLAGS; - eval.data = old_flags & ~off_flags_mask & ETH_FLAG_EXT_MASK; - eval.data |= off_flags_wanted & ETH_FLAG_EXT_MASK; - err = send_ioctl(ctx, &eval); - if (err) { - perror("Cannot set device flag settings"); - return 92; + if (efeatures) { + err = send_ioctl(ctx, efeatures); + if (err < 0) { + perror("Cannot set device feature settings"); + return 1; + } + } else { + for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) { + if (!off_flag_def[i].set_cmd) + continue; + if (off_flags_mask & off_flag_def[i].value) { + eval.cmd = off_flag_def[i].set_cmd; + eval.data = !!(off_flags_wanted & + off_flag_def[i].value); + err = send_ioctl(ctx, &eval); + if (err) { + fprintf(stderr, + "Cannot set device %s settings: %m\n", + off_flag_def[i].long_name); + return 1; + } + } } - } - if (off_flags_mask == 0) { - fprintf(stdout, "no offload settings changed\n"); - return 0; + if (off_flags_mask & ETH_FLAG_EXT_MASK) { + eval.cmd = ETHTOOL_SFLAGS; + eval.data = (old_state->off_flags & ~off_flags_mask & + ETH_FLAG_EXT_MASK); + eval.data |= off_flags_wanted & ETH_FLAG_EXT_MASK; + + err = send_ioctl(ctx, &eval); + if (err) { + perror("Cannot set device flag settings"); + return 92; + } + } } /* Compare new state with requested state */ - if (get_offload(ctx, &new_flags)) { - fprintf(stderr, "no offload info available\n"); + new_state = get_features(ctx, defs); + if (!new_state) return 1; - } - if (new_flags != ((old_flags & ~off_flags_mask) | off_flags_wanted)) { - if (new_flags == old_flags) { + any_changed = new_state->off_flags != old_state->off_flags; + any_mismatch = (new_state->off_flags != + ((old_state->off_flags & ~off_flags_mask) | + off_flags_wanted)); + for (i = 0; i < FEATURE_BITS_TO_BLOCKS(defs->n_features); i++) { + if (new_state->features.features[i].active != + old_state->features.features[i].active) + any_changed = 1; + if (new_state->features.features[i].active != + ((old_state->features.features[i].active & + ~efeatures->features[i].valid) | + efeatures->features[i].requested)) + any_mismatch = 1; + } + if (any_mismatch) { + if (!any_changed) { fprintf(stderr, - "Could not change any device offload settings\n"); + "Could not change any device features\n"); return 1; } printf("Actual changes:\n"); - dump_offload(new_flags, new_flags ^ old_flags); + dump_features(defs, new_state, old_state); } return 0; @@ -3237,22 +3512,11 @@ static const struct option { " [ rx-mini N ]\n" " [ rx-jumbo N ]\n" " [ tx N ]\n" }, - { "-k|--show-offload", 1, do_goffload, - "Get protocol offload information" }, - { "-K|--offload", 1, do_soffload, "Set protocol offload", - " [ rx on|off ]\n" - " [ tx on|off ]\n" - " [ sg on|off ]\n" - " [ tso on|off ]\n" - " [ ufo on|off ]\n" - " [ gso on|off ]\n" - " [ gro on|off ]\n" - " [ lro on|off ]\n" - " [ rxvlan on|off ]\n" - " [ txvlan on|off ]\n" - " [ ntuple on|off ]\n" - " [ rxhash on|off ]\n" - }, + { "-k|--show-features|--show-offload", 1, do_gfeatures, + "Get state of protocol offload and other features" }, + { "-K|--features|--offload", 1, do_sfeatures, + "Set protocol offload and other features", + " FEATURE on|off ...\n" }, { "-i|--driver", 1, do_gdrv, "Show driver information" }, { "-d|--register-dump", 1, do_gregs, "Do a register dump", " [ raw on|off ]\n" diff --git a/test-cmdline.c b/test-cmdline.c index c62bb97..978c312 100644 --- a/test-cmdline.c +++ b/test-cmdline.c @@ -86,14 +86,7 @@ static struct test_case { { 1, "--set-ring devname rx" }, { 1, "-G devname foo 1" }, { 1, "-G" }, - { 0, "-k devname" }, - { 0, "--show-offload devname" }, { 1, "-k" }, - { 0, "-K devname rx on tx off sg on tso off ufo on gso off gro on" }, - { 0, "--offload devname lro off rxvlan on txvlan off ntuple on rxhash off" }, - { 1, "-K devname rx foo" }, - { 1, "--offload devname rx" }, - { 1, "-K devname foo on" }, { 1, "-K" }, { 0, "-i devname" }, { 0, "--driver devname" }, diff --git a/test-features.c b/test-features.c index 409b3d2..a48c701 100644 --- a/test-features.c +++ b/test-features.c @@ -7,12 +7,19 @@ * by the Free Software Foundation, incorporated herein by reference. */ +#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define TEST_NO_WRAPPERS #include "internal.h" +static const struct { + struct ethtool_sset_info cmd; + u32 data[1]; +} +cmd_gssetinfo = { { ETHTOOL_GSSET_INFO, 0, 1ULL << ETH_SS_FEATURES }, 34 }; + static const struct ethtool_value cmd_grxcsum_off = { ETHTOOL_GRXCSUM, 0 }, cmd_grxcsum_on = { ETHTOOL_GRXCSUM, 1 }, @@ -55,7 +62,13 @@ cmd_sflags_not_rxhash = { ETHTOOL_SFLAGS, ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | ETH_FLAG_NTUPLE }; -static const struct cmd_expect cmd_expect_get_features_off[] = { +static const struct cmd_expect cmd_expect_get_strings_old[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), -EINVAL }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_get_features_off_old[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), -EINVAL }, { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, @@ -67,7 +80,8 @@ static const struct cmd_expect cmd_expect_get_features_off[] = { { 0, 0, 0, 0, 0 } }; -static const struct cmd_expect cmd_expect_set_features_off[] = { +static const struct cmd_expect cmd_expect_set_features_off_old[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), -EINVAL }, { &cmd_grxcsum_on, 4, 0, &cmd_grxcsum_on, sizeof(cmd_grxcsum_on) }, { &cmd_gtxcsum_on, 4, 0, &cmd_gtxcsum_on, sizeof(cmd_gtxcsum_on) }, { &cmd_gsg_on, 4, 0, &cmd_gsg_on, sizeof(cmd_gsg_on) }, @@ -95,7 +109,8 @@ static const struct cmd_expect cmd_expect_set_features_off[] = { { 0, 0, 0, 0, 0 } }; -static const struct cmd_expect cmd_expect_set_features_on[] = { +static const struct cmd_expect cmd_expect_set_features_on_old[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), -EINVAL }, { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, @@ -123,16 +138,334 @@ static const struct cmd_expect cmd_expect_set_features_on[] = { { 0, 0, 0, 0, 0 } }; +static const struct cmd_expect cmd_expect_set_features_unsup_on_old[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), -EINVAL }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_stxcsum_on, sizeof(cmd_stxcsum_on), -EOPNOTSUPP }, + { 0, 0, 0, 0, 0 } +}; + +static const struct { + struct ethtool_gstrings cmd; + u8 data[34][ETH_GSTRING_LEN]; +} +cmd_gstrings = { + { ETHTOOL_GSTRINGS, ETH_SS_FEATURES, 34 }, + { + "tx-scatter-gather", + "tx-checksum-ipv4", + "", + "tx-checksum-ip-generic", + "tx-checksum-ipv6", + "highdma", + "tx-scatter-gather-fraglist", + "tx-vlan-hw-insert", + "rx-vlan-hw-parse", + "rx-vlan-filter", + "vlan-challenged", + "tx-generic-segmentation", + "tx-lockless", + "netns-local", + "rx-gro", + "rx-lro", + "tx-tcp-segmentation", + "tx-udp-fragmentation", + "tx-gso-robust", + "tx-tcp-ecn-segmentation", + "tx-tcp6-segmentation", + "tx-fcoe-segmentation", + "", + "", + "tx-checksum-fcoe-crc", + "tx-checksum-sctp", + "fcoe-mtu", + "rx-ntuple-filter", + "rx-hashing", + "rx-checksum", + "tx-nocache-copy", + "loopback", + "rx-fcs", + "rx-all", + } +}; + +static const struct { + struct ethtool_gfeatures cmd; + struct ethtool_get_features_block data[2]; +} + /* available requested active never_changed */ +/* minimal: only GRO and GSO are available (and GSO won't work) */ +cmd_gfeatures_min_off = { { ETHTOOL_GFEATURES, 2 }, + {{ 0x00004800, 0x00000000, 0x00000000, 0x00003400}, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000}} +}, +cmd_gfeatures_min_on = { { ETHTOOL_GFEATURES, 2 }, + {{ 0x00004800, 0x00004800, 0x00004000, 0x00003400}, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000}} +}, +/* maximal: everything that isn't never-changed is available */ +cmd_gfeatures_max_off = { { ETHTOOL_GFEATURES, 2 }, + {{ 0xffffcbff, 0x00000000, 0x00000000, 0x00003400 }, + { 0x00000003, 0x00000000, 0x00000000, 0x00000000 }} +}, +cmd_gfeatures_max_on = { { ETHTOOL_GFEATURES, 2 }, + {{ 0xffffcbff, 0xffffcbff, 0xffffcbff, 0x00003400 }, + { 0x00000003, 0x00000003, 0x00000003, 0x00000000 }} +}, +/* IPv4: GRO, GSO, SG and some IPv4-specific offloads are available */ +cmd_gfeatures_ipv4_off = { { ETHTOOL_GFEATURES, 2 }, + {{ 0x00014803, 0x00000000, 0x00000000, 0x00003400 }, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }} +}, +cmd_gfeatures_ipv4_on = { { ETHTOOL_GFEATURES, 2 }, + {{ 0x00014803, 0x00014803, 0x00014803, 0x00003400 }, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }} +}; + +static const struct { + struct ethtool_sfeatures cmd; + struct ethtool_set_features_block data[2]; +} + /* valid requested */ +cmd_sfeatures_min_on = { { ETHTOOL_SFEATURES, 2 }, + {{ 0x00004800, 0x00004800 }, + { 0x00000000, 0x00000000 }} }, +cmd_sfeatures_min_off = { { ETHTOOL_SFEATURES, 2 }, + {{ 0x00004800, 0x00000000 }, + { 0x00000000, 0x00000000 }} }, +cmd_sfeatures_noop = { { ETHTOOL_SFEATURES, 2 }, + {{ 0x00000000, 0x00000000 }, + { 0x00000000, 0x00000000 }} }, +cmd_sfeatures_ipv4_on = { { ETHTOOL_SFEATURES, 2 }, + {{ 0x00014803, 0x00014803 }, + { 0x00000000, 0x00000000 }} }; + +static const struct cmd_expect cmd_expect_get_strings[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_get_features_min_off[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_get_features_max_on[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_on, 4, 0, &cmd_grxcsum_on, sizeof(cmd_grxcsum_on) }, + { &cmd_gtxcsum_on, 4, 0, &cmd_gtxcsum_on, sizeof(cmd_gtxcsum_on) }, + { &cmd_gsg_on, 4, 0, &cmd_gsg_on, sizeof(cmd_gsg_on) }, + { &cmd_gtso_on, 4, 0, &cmd_gtso_on, sizeof(cmd_gtso_on) }, + { &cmd_gufo_on, 4, 0, &cmd_gufo_on, sizeof(cmd_gufo_on) }, + { &cmd_ggso_on, 4, 0, &cmd_ggso_on, sizeof(cmd_ggso_on) }, + { &cmd_ggro_on, 4,0, &cmd_ggro_on, sizeof(cmd_ggro_on) }, + { &cmd_gflags_on, 4, 0, &cmd_gflags_on, sizeof(cmd_sflags_on) }, + { &cmd_gfeatures_max_on, sizeof(cmd_gfeatures_max_on.cmd), + 0, &cmd_gfeatures_max_on, sizeof(cmd_gfeatures_max_on) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_set_features_min_off_min_on[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { &cmd_sfeatures_min_on, sizeof(cmd_sfeatures_min_on), + ETHTOOL_F_WISH, 0, 0 }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_on, 4,0, &cmd_ggro_on, sizeof(cmd_ggro_on) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_on, sizeof(cmd_gfeatures_min_on.cmd), + 0, &cmd_gfeatures_min_on, sizeof(cmd_gfeatures_min_on) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_set_features_min_off_min_off[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { &cmd_sfeatures_min_off, sizeof(cmd_sfeatures_min_off), 0, 0, 0 }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_set_features_min_on_min_off[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_on, 4,0, &cmd_ggro_on, sizeof(cmd_ggro_on) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_on, sizeof(cmd_gfeatures_min_on.cmd), + 0, &cmd_gfeatures_min_on, sizeof(cmd_gfeatures_min_on) }, + { &cmd_sfeatures_min_off, sizeof(cmd_sfeatures_min_off), 0, 0, 0 }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_set_features_min_off_unsup_on[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { &cmd_sfeatures_noop, sizeof(cmd_sfeatures_noop), 0, 0, 0 }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off.cmd), + 0, &cmd_gfeatures_min_off, sizeof(cmd_gfeatures_min_off) }, + { 0, 0, 0, 0, 0 } +}; + +static const struct cmd_expect cmd_expect_set_features_ipv4_off_many_on[] = { + { &cmd_gssetinfo, sizeof(cmd_gssetinfo.cmd), + 0, &cmd_gssetinfo, sizeof(cmd_gssetinfo) }, + { &cmd_gstrings, sizeof(cmd_gstrings.cmd), + 0, &cmd_gstrings, sizeof(cmd_gstrings) }, + { &cmd_grxcsum_off, 4, 0, &cmd_grxcsum_off, sizeof(cmd_grxcsum_off) }, + { &cmd_gtxcsum_off, 4, 0, &cmd_gtxcsum_off, sizeof(cmd_gtxcsum_off) }, + { &cmd_gsg_off, 4, 0, &cmd_gsg_off, sizeof(cmd_gsg_off) }, + { &cmd_gtso_off, 4, 0, &cmd_gtso_off, sizeof(cmd_gtso_off) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_off, 4, 0, &cmd_ggso_off, sizeof(cmd_ggso_off) }, + { &cmd_ggro_off, 4,0, &cmd_ggro_off, sizeof(cmd_ggro_off) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_ipv4_off, sizeof(cmd_gfeatures_ipv4_off.cmd), + 0, &cmd_gfeatures_ipv4_off, sizeof(cmd_gfeatures_ipv4_off) }, + { &cmd_sfeatures_ipv4_on, sizeof(cmd_sfeatures_ipv4_on), 0, 0, 0 }, + { &cmd_grxcsum_on, 4, 0, &cmd_grxcsum_on, sizeof(cmd_grxcsum_on) }, + { &cmd_gtxcsum_on, 4, 0, &cmd_gtxcsum_on, sizeof(cmd_gtxcsum_on) }, + { &cmd_gsg_on, 4, 0, &cmd_gsg_on, sizeof(cmd_gsg_on) }, + { &cmd_gtso_on, 4, 0, &cmd_gtso_on, sizeof(cmd_gtso_on) }, + { &cmd_gufo_off, 4, 0, &cmd_gufo_off, sizeof(cmd_gufo_off) }, + { &cmd_ggso_on, 4, 0, &cmd_ggso_on, sizeof(cmd_ggso_on) }, + { &cmd_ggro_on, 4,0, &cmd_ggro_on, sizeof(cmd_ggro_on) }, + { &cmd_gflags_off, 4, 0, &cmd_gflags_off, sizeof(cmd_gflags_off) }, + { &cmd_gfeatures_ipv4_on, sizeof(cmd_gfeatures_ipv4_on.cmd), + 0, &cmd_gfeatures_ipv4_on, sizeof(cmd_gfeatures_ipv4_on) }, + { 0, 0, 0, 0, 0 } +}; + static struct test_case { int rc; const char *args; const struct cmd_expect *expect; } const test_cases[] = { - { 0, "-k devname", cmd_expect_get_features_off }, + { 0, "-k devname", cmd_expect_get_features_off_old }, { 0, "-K devname rx off tx off sg off tso off ufo off gso off lro off rxvlan off txvlan off ntuple off rxhash off gro off", - cmd_expect_set_features_off }, + cmd_expect_set_features_off_old }, { 0, "-K devname rx on tx on sg on tso on ufo on gso on lro on rxvlan on txvlan on ntuple on rxhash on gro on", - cmd_expect_set_features_on }, + cmd_expect_set_features_on_old }, + { 1, "-K devname tx on sg on", cmd_expect_set_features_unsup_on_old }, + { 0, "--show-offload devname", cmd_expect_get_features_min_off }, + { 0, "--show-features devname", cmd_expect_get_features_max_on }, + { 0, "-K devname rx on tx on sg on tso on ufo on gso on gro on", + cmd_expect_set_features_min_off_min_on }, + { 0, "-K devname rx off tx off sg off tso off ufo off gso off gro off", + cmd_expect_set_features_min_off_min_off }, + { 0, "-K devname rx off tx off sg off tso off ufo off gso off gro off", + cmd_expect_set_features_min_on_min_off }, + { 1, "-K devname tx on sg on", + cmd_expect_set_features_min_off_unsup_on }, + { 0, "--features devname rx on tx on sg on tso on gso on gro on", + cmd_expect_set_features_ipv4_off_many_on }, + { 1, "-K devname rx foo", cmd_expect_get_strings_old }, + { 1, "-K devname rx foo", cmd_expect_get_strings }, + { 1, "--offload devname rx", cmd_expect_get_strings_old }, + { 1, "--features devname rx", cmd_expect_get_strings }, + { 1, "--features devname foo on", cmd_expect_get_strings_old }, + { 1, "--offload devname foo on", cmd_expect_get_strings }, }; static int expect_matched; |