[RFC PATCH v3 ethtool] ethtool: implement [GS]FEATURES calls
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Date: 2011-05-19 13:26:01
Subsystem:
the rest · Maintainer:
Linus Torvalds
This is all-in-one PoC patch for [GS]FEATURES support and checking of all feature changes when altering some. Example result: icybox:~# ./ethtool -K ge0 tx_checksum-ipv6 on feature group tx is enabled (expected: disabled) feature group sg is enabled (expected: disabled) feature group gso is enabled (expected: disabled) feature tx-scatter-gather is enabled (expected: disabled, saved: enabled) feature tx-generic-segmentation is enabled (expected: disabled, saved: enabled) Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl> --- ethtool.c | 583 +++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 494 insertions(+), 89 deletions(-)
diff --git a/ethtool.c b/ethtool.c
index 34fe107..40456bb 100644
--- a/ethtool.c
+++ b/ethtool.c@@ -33,6 +33,7 @@ #include <limits.h> #include <ctype.h> +#include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>
@@ -83,6 +84,8 @@ static int do_gcoalesce(int fd, struct ifreq *ifr); static int do_scoalesce(int fd, struct ifreq *ifr); static int do_goffload(int fd, struct ifreq *ifr); static int do_soffload(int fd, struct ifreq *ifr); +static void parse_sfeatures_args(int argc, char **argp, int argi); +static int do_gfeatures(int fd, struct ifreq *ifr); static int do_gstats(int fd, struct ifreq *ifr); static int rxflow_str_to_type(const char *str); static int parse_rxfhashopts(char *optstr, u32 *data);
@@ -196,7 +199,8 @@ static struct option { " [ txvlan on|off ]\n" " [ ntuple on|off ]\n" " [ rxhash on|off ]\n" - }, + " [ feature-name on|off [...] ]\n" + " see --show-offload output for feature-name strings\n" }, { "-i", "--driver", MODE_GDRV, "Show driver information" }, { "-d", "--register-dump", MODE_GREGS, "Do a register dump", " [ raw on|off ]\n"
@@ -296,7 +300,6 @@ static void show_usage(void) static char *devname = NULL; -static int goffload_changed = 0; static int off_csum_rx_wanted = -1; static int off_csum_tx_wanted = -1; static int off_sg_wanted = -1;
@@ -306,6 +309,9 @@ static int off_gso_wanted = -1; static u32 off_flags_wanted = 0; static u32 off_flags_mask = 0; static int off_gro_wanted = -1; +static int n_feature_strings; +static const char **feature_strings; +static struct ethtool_sfeatures *features_req; static struct ethtool_pauseparam epause; static int gpause_changed = 0;
@@ -851,10 +857,7 @@ static void parse_cmdline(int argc, char **argp) break; } if (mode == MODE_SOFFLOAD) { - parse_generic_cmdline(argc, argp, i, - &goffload_changed, - cmdline_offload, - ARRAY_SIZE(cmdline_offload)); + parse_sfeatures_args(argc, argp, i); i = argc; break; }
@@ -1788,9 +1791,15 @@ static int dump_coalesce(void) return 0; } -static int dump_offload(int rx, int tx, int sg, int tso, int ufo, int gso, - int gro, int lro, int rxvlan, int txvlan, int ntuple, - int rxhash) +struct offload_state { + int rx, tx, sg, tso, ufo, gso, gro, lro, rxvlan, txvlan, ntuple, rxhash; +}; + +const char *const old_feature_names[] = { + "rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "ntuple", "rxhash" +}; + +static int dump_offload(const struct offload_state *offload) { fprintf(stdout, "rx-checksumming: %s\n"
@@ -1805,18 +1814,18 @@ static int dump_offload(int rx, int tx, int sg, int tso, int ufo, int gso, "tx-vlan-offload: %s\n" "ntuple-filters: %s\n" "receive-hashing: %s\n", - rx ? "on" : "off", - tx ? "on" : "off", - sg ? "on" : "off", - tso ? "on" : "off", - ufo ? "on" : "off", - gso ? "on" : "off", - gro ? "on" : "off", - lro ? "on" : "off", - rxvlan ? "on" : "off", - txvlan ? "on" : "off", - ntuple ? "on" : "off", - rxhash ? "on" : "off"); + offload->rx ? "on" : "off", + offload->tx ? "on" : "off", + offload->sg ? "on" : "off", + offload->tso ? "on" : "off", + offload->ufo ? "on" : "off", + offload->gso ? "on" : "off", + offload->gro ? "on" : "off", + offload->lro ? "on" : "off", + offload->rxvlan ? "on" : "off", + offload->txvlan ? "on" : "off", + offload->ntuple ? "on" : "off", + offload->rxhash ? "on" : "off"); return 0; }
@@ -1867,21 +1876,33 @@ static int dump_rxfhash(int fhash, u64 val) return 0; } -static int doit(void) -{ - struct ifreq ifr; - int fd; +static int control_fd = -1; +static int get_control_socket(struct ifreq *ifr) +{ /* Setup our control structures. */ - memset(&ifr, 0, sizeof(ifr)); - strcpy(ifr.ifr_name, devname); + memset(ifr, 0, sizeof(*ifr)); + strcpy(ifr->ifr_name, devname); + + if (control_fd >= 0) + return control_fd; /* Open control socket. */ - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) { + control_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (control_fd < 0) perror("Cannot get control socket"); + + return control_fd; +} + +static int doit(void) +{ + struct ifreq ifr; + int fd; + + fd = get_control_socket(&ifr); + if (fd < 0) return 70; - } /* all of these are expected to populate ifr->ifr_data as needed */ if (mode == MODE_GDRV) {
@@ -2139,14 +2160,13 @@ static int do_scoalesce(int fd, struct ifreq *ifr) return 0; } -static int do_goffload(int fd, struct ifreq *ifr) +static int send_goffloads(int fd, struct ifreq *ifr, + struct offload_state *offload) { struct ethtool_value eval; - int err, allfail = 1, rx = 0, tx = 0, sg = 0; - int tso = 0, ufo = 0, gso = 0, gro = 0, lro = 0, rxvlan = 0, txvlan = 0, - ntuple = 0, rxhash = 0; + int err, allfail = 1; - fprintf(stdout, "Offload parameters for %s:\n", devname); + memset(offload, 0, sizeof(*offload)); eval.cmd = ETHTOOL_GRXCSUM; ifr->ifr_data = (caddr_t)&eval;
@@ -2154,7 +2174,7 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device rx csum settings"); else { - rx = eval.data; + offload->rx = eval.data; allfail = 0; }
@@ -2164,7 +2184,7 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device tx csum settings"); else { - tx = eval.data; + offload->tx = eval.data; allfail = 0; }
@@ -2174,7 +2194,7 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device scatter-gather settings"); else { - sg = eval.data; + offload->sg = eval.data; allfail = 0; }
@@ -2184,7 +2204,7 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device tcp segmentation offload settings"); else { - tso = eval.data; + offload->tso = eval.data; allfail = 0; }
@@ -2194,7 +2214,7 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device udp large send offload settings"); else { - ufo = eval.data; + offload->ufo = eval.data; allfail = 0; }
@@ -2204,7 +2224,7 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device generic segmentation offload settings"); else { - gso = eval.data; + offload->gso = eval.data; allfail = 0; }
@@ -2214,11 +2234,11 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) { perror("Cannot get device flags"); } else { - lro = (eval.data & ETH_FLAG_LRO) != 0; - rxvlan = (eval.data & ETH_FLAG_RXVLAN) != 0; - txvlan = (eval.data & ETH_FLAG_TXVLAN) != 0; - ntuple = (eval.data & ETH_FLAG_NTUPLE) != 0; - rxhash = (eval.data & ETH_FLAG_RXHASH) != 0; + offload->lro = (eval.data & ETH_FLAG_LRO) != 0; + offload->rxvlan = (eval.data & ETH_FLAG_RXVLAN) != 0; + offload->txvlan = (eval.data & ETH_FLAG_TXVLAN) != 0; + offload->ntuple = (eval.data & ETH_FLAG_NTUPLE) != 0; + offload->rxhash = (eval.data & ETH_FLAG_RXHASH) != 0; allfail = 0; }
@@ -2228,130 +2248,515 @@ static int do_goffload(int fd, struct ifreq *ifr) if (err) perror("Cannot get device GRO settings"); else { - gro = eval.data; + offload->gro = eval.data; allfail = 0; } + return -allfail; +} + +static int do_goffload(int fd, struct ifreq *ifr) +{ + struct offload_state offload; + int err, allfail; + + allfail = send_goffloads(fd, ifr, &offload); + + if (!allfail) { + fprintf(stdout, "Offload parameters for %s:\n", devname); + + dump_offload(&offload); + } + + err = do_gfeatures(fd, ifr); + if (!err) + allfail = 0; + if (allfail) { fprintf(stdout, "no offload info available\n"); return 83; } - return dump_offload(rx, tx, sg, tso, ufo, gso, gro, lro, rxvlan, txvlan, - ntuple, rxhash); + return 0; } -static int do_soffload(int fd, struct ifreq *ifr) +static int send_soffloads(int fd, struct ifreq *ifr) { struct ethtool_value eval; int err, changed = 0; if (off_csum_rx_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_SRXCSUM; eval.data = (off_csum_rx_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = send_ioctl(fd, ifr); - if (err) { + if (err) perror("Cannot set device rx csum settings"); - return 84; - } + else + changed = 1; } if (off_csum_tx_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_STXCSUM; eval.data = (off_csum_tx_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = send_ioctl(fd, ifr); - if (err) { + if (err) perror("Cannot set device tx csum settings"); - return 85; - } + else + changed = 1; } if (off_sg_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_SSG; eval.data = (off_sg_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = send_ioctl(fd, ifr); - if (err) { + if (err) perror("Cannot set device scatter-gather settings"); - return 86; - } + else + changed = 1; } if (off_tso_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_STSO; eval.data = (off_tso_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = send_ioctl(fd, ifr); - if (err) { + if (err) perror("Cannot set device tcp segmentation offload settings"); - return 88; - } + else + changed = 1; } if (off_ufo_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_SUFO; eval.data = (off_ufo_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = ioctl(fd, SIOCETHTOOL, ifr); - if (err) { + if (err) perror("Cannot set device udp large send offload settings"); - return 89; - } + else + changed = 1; } if (off_gso_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_SGSO; eval.data = (off_gso_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = ioctl(fd, SIOCETHTOOL, ifr); - if (err) { + if (err) perror("Cannot set device generic segmentation offload settings"); - return 90; - } + else + changed = 1; } if (off_flags_mask) { - changed = 1; eval.cmd = ETHTOOL_GFLAGS; eval.data = 0; ifr->ifr_data = (caddr_t)&eval; err = ioctl(fd, SIOCETHTOOL, ifr); if (err) { perror("Cannot get device flag settings"); - return 91; - } - - eval.cmd = ETHTOOL_SFLAGS; - eval.data = ((eval.data & ~off_flags_mask) | - off_flags_wanted); - - err = ioctl(fd, SIOCETHTOOL, ifr); - if (err) { - perror("Cannot set device flag settings"); - return 92; + } else { + eval.cmd = ETHTOOL_SFLAGS; + eval.data = ((eval.data & ~off_flags_mask) | + off_flags_wanted); + + err = ioctl(fd, SIOCETHTOOL, ifr); + if (err) + perror("Cannot set device flag settings"); + else + changed = 1; } } if (off_gro_wanted >= 0) { - changed = 1; eval.cmd = ETHTOOL_SGRO; eval.data = (off_gro_wanted == 1); ifr->ifr_data = (caddr_t)&eval; err = ioctl(fd, SIOCETHTOOL, ifr); - if (err) { + if (err) perror("Cannot set device GRO settings"); - return 93; + else + changed = 1; + } + + return changed; +} + +static int get_feature_strings(int fd, struct ifreq *ifr, + struct ethtool_gstrings **strs) +{ + struct ethtool_sset_info *sset_info; + struct ethtool_gstrings *strings; + int sz_str, n_strings, err; + + sset_info = malloc(sizeof(struct ethtool_sset_info) + sizeof(u32)); + sset_info->cmd = ETHTOOL_GSSET_INFO; + sset_info->sset_mask = (1ULL << ETH_SS_FEATURES); + ifr->ifr_data = (caddr_t)sset_info; + err = send_ioctl(fd, ifr); + + n_strings = sset_info->data[0]; + free(sset_info); + + if ((err < 0) || + (!(sset_info->sset_mask & (1ULL << ETH_SS_FEATURES))) || + (n_strings == 0)) { + return -100; + } + + sz_str = n_strings * ETH_GSTRING_LEN; + strings = calloc(1, sz_str + sizeof(struct ethtool_gstrings)); + if (!strings) { + fprintf(stderr, "no memory available\n"); + exit(95); + } + + strings->cmd = ETHTOOL_GSTRINGS; + strings->string_set = ETH_SS_FEATURES; + strings->len = n_strings; + ifr->ifr_data = (caddr_t) strings; + err = send_ioctl(fd, ifr); + if (err < 0) { + perror("Cannot get feature strings information"); + free(strings); + exit(96); + } + + *strs = strings; + return n_strings; +} + +static int init_feature_strings(void) +{ + struct ethtool_gstrings *strings; + struct ifreq ifr; + int fd, i, n; + + if (feature_strings) + return n_feature_strings; + + fd = get_control_socket(&ifr); + if (fd < 0) + exit(100); + + n = get_feature_strings(fd, &ifr, &strings); + + if (n < 0) + return n; + + n_feature_strings = n; + feature_strings = calloc(n, sizeof(*feature_strings)); + if (!feature_strings) { + fprintf(stderr, "no memory available for string table [size=%d]\n", n); + exit(95); + } + + for (i = 0; i < n; ++i) { + if (!strings->data[i*ETH_GSTRING_LEN]) + continue; + + feature_strings[i] = strndup( + (const char *)&strings->data[i * ETH_GSTRING_LEN], + ETH_GSTRING_LEN); + + if (!feature_strings[i]) { + fprintf(stderr, "no memory available for a string\n"); + exit(95); } } + free(strings); + return n; +} + +static void parse_sfeatures_args(int argc, char **argp, int argi) +{ + struct cmdline_info *cmdline_desc, *cp; + int sz_features, i; + int changed = 0; + + if (init_feature_strings() < 0) { + /* ETHTOOL_GFEATURES unavailable */ + parse_generic_cmdline(argc, argp, argi, &changed, + cmdline_offload, ARRAY_SIZE(cmdline_offload)); + return; + } + + sz_features = sizeof(*features_req->features) * ((n_feature_strings + 31) / 32); + + cp = cmdline_desc = calloc(n_feature_strings + ARRAY_SIZE(cmdline_offload), + sizeof(*cmdline_desc)); + memcpy(cp, cmdline_offload, sizeof(cmdline_offload)); + cp += ARRAY_SIZE(cmdline_offload); + + features_req = calloc(1, sizeof(*features_req) + sz_features); + if (!cmdline_desc || !features_req) { + fprintf(stderr, "no memory available\n"); + exit(95); + } + + features_req->size = (n_feature_strings + 31) / 32; + + for (i = 0; i < n_feature_strings; ++i) { + if (!feature_strings[i]) + continue; + + cp->name = feature_strings[i]; + cp->type = CMDL_FLAG; + cp->flag_val = 1 << (i % 32); + cp->wanted_val = &features_req->features[i / 32].requested; + cp->seen_val = &features_req->features[i / 32].valid; + ++cp; + } + + parse_generic_cmdline(argc, argp, argi, &changed, + cmdline_desc, cp - cmdline_desc); + + free(cmdline_desc); + + if (!changed) { + free(features_req); + features_req = NULL; + } +} + +static int send_gfeatures(int fd, struct ifreq *ifr, + struct ethtool_gfeatures **features_p) +{ + struct ethtool_gfeatures *features; + int err, sz_features; + + sz_features = sizeof(*features->features) * ((n_feature_strings + 31) / 32); + features = calloc(1, sz_features + sizeof(*features)); + if (!features) { + fprintf(stderr, "no memory available\n"); + return 95; + } + + features->cmd = ETHTOOL_GFEATURES; + features->size = (n_feature_strings + 31) / 32; + ifr->ifr_data = (caddr_t) features; + err = send_ioctl(fd, ifr); + + if (err < 0) { + perror("Cannot get feature status"); + free(features); + return 97; + } + + *features_p = features; + return 0; +} + +static int do_gfeatures(int fd, struct ifreq *ifr) +{ + struct ethtool_gfeatures *features; + int err, i; + + err = init_feature_strings(); + if (err < 0) + return -err; + + err = send_gfeatures(fd, ifr, &features); + if (err) + return err; + + fprintf(stdout, "\nFull offload state: (feature-name: active,wanted,changable)\n"); + for (i = 0; i < n_feature_strings; i++) { + if (!feature_strings[i]) + continue; /* empty */ +#define P_FLAG(f) \ + (features->features[i / 32].f & (1 << (i % 32))) ? "yes" : " no" +#define PA_FLAG(f) \ + (features->features[i / 32].available & (1 << (i % 32))) ? P_FLAG(f) : "---" +#define PN_FLAG(f) \ + (features->features[i / 32].never_changed & (1 << (i % 32))) ? "---" : P_FLAG(f) + fprintf(stdout, " %-*.*s %s,%s,%s\n", + ETH_GSTRING_LEN, ETH_GSTRING_LEN, feature_strings[i], + P_FLAG(active), PA_FLAG(requested), PN_FLAG(available)); +#undef P_FLAG +#undef PA_FLAG +#undef PN_FLAG + } + free(features); + + return 0; +} + +static void print_gfeatures_diff( + const struct ethtool_get_features_block *expected, + const struct ethtool_get_features_block *set, + const char **strings, int n_strings) +{ + int i; + + if (n_strings > 32) + n_strings = 32; + + for (i = 0; i < n_strings; ++i) { + u32 mask = 1 << i; + + if (!strings[i]) + continue; + + if (!((expected->active ^ set->active) & mask)) + continue; + + fprintf(stderr, "feature %.*s is %s (expected: %s, saved: %s)\n", + ETH_GSTRING_LEN, strings[i], + set->active & mask ? "enabled" : "disabled", + expected->active & mask ? "enabled" : "disabled", + !(set->available & mask) ? "not user-changeable" : + set->requested & mask ? "enabled" : "disabled" + ); + } +} + +static int get_offload_state(int fd, struct ifreq *ifr, + struct ethtool_gfeatures **features, + struct offload_state *offload) +{ + int err, allfail; + + allfail = send_goffloads(fd, ifr, offload); + + err = init_feature_strings(); + if (err < 0) + return allfail ? err : 0; + + err = send_gfeatures(fd, ifr, features); + if (err) + perror("Cannot read features"); + + return allfail ? -err : 0; +} + +static int send_sfeatures(int fd, struct ifreq *ifr) +{ + int err; + + features_req->cmd = ETHTOOL_SFEATURES; + ifr->ifr_data = (caddr_t) features_req; + err = send_ioctl(fd, ifr); + if (err < 0) { + perror("Cannot change features"); + return 97; + } + + return 0; +} + +static void compare_offload_state(struct offload_state *offload0, + struct offload_state *offload1) +{ + int *expected = (int *)offload0, *new = (int *)offload1; + int i; + + if (off_csum_rx_wanted >= 0) + offload0->rx = off_csum_rx_wanted; + + if (off_csum_tx_wanted >= 0) + offload0->tx = off_csum_tx_wanted; + + if (off_sg_wanted >= 0) + offload0->sg = off_sg_wanted; + + if (off_tso_wanted >= 0) + offload0->tso = off_tso_wanted; + + if (off_ufo_wanted >= 0) + offload0->ufo = off_ufo_wanted; + + if (off_gso_wanted >= 0) + offload0->gso = off_gso_wanted; + + if (off_gro_wanted >= 0) + offload0->gro = off_gro_wanted; + + if (off_flags_mask & ETH_FLAG_LRO) + offload0->lro = !!(off_flags_wanted & ETH_FLAG_LRO); + + if (off_flags_mask & ETH_FLAG_RXVLAN) + offload0->rxvlan = !!(off_flags_wanted & ETH_FLAG_RXVLAN); + + if (off_flags_mask & ETH_FLAG_TXVLAN) + offload0->txvlan = !!(off_flags_wanted & ETH_FLAG_TXVLAN); + + if (off_flags_mask & ETH_FLAG_NTUPLE) + offload0->ntuple = !!(off_flags_wanted & ETH_FLAG_NTUPLE); + + if (off_flags_mask & ETH_FLAG_RXHASH) + offload0->rxhash = !!(off_flags_wanted & ETH_FLAG_RXHASH); + + for (i = 0; i < ARRAY_SIZE(old_feature_names); i++) { + if (expected[i] == new[i]) + continue; + + fprintf(stderr, "feature group %s is %s (expected: %s)\n", + old_feature_names[i], + new[i] ? "enabled" : "disabled", + expected[i] ? "enabled" : "disabled" + ); + } +} + +static void compare_features(struct ethtool_gfeatures *features0, + struct ethtool_gfeatures *features1) +{ + int i; + + /* make features0 .active what we expect to be set */ + i = (n_feature_strings + 31) / 32; + while (i--) { + features0->features[i].active &= ~features_req->features[i].valid; + features0->features[i].active |= + features_req->features[i].requested & + features_req->features[i].valid; + } + + for (i = 0; i < n_feature_strings; i += 32) + print_gfeatures_diff(&features0->features[i / 32], + &features1->features[i / 32], + feature_strings + i, + n_feature_strings - i); +} + +static int do_soffload(int fd, struct ifreq *ifr) +{ + struct ethtool_gfeatures *features_old, *features_new; + struct offload_state offload_old, offload_new; + int err, changed; + + err = get_offload_state(fd, ifr, &features_old, &offload_old); + if (err) + return -err; + + changed = send_soffloads(fd, ifr); + + if (features_req) { + err = send_sfeatures(fd, ifr); + if (!err) + changed = 1; + } + if (!changed) { fprintf(stdout, "no offload settings changed\n"); + return err; + } + + err = get_offload_state(fd, ifr, &features_new, &offload_new); + if (err) { + perror("can't verify offload setting"); + return 101; + } + if ((!features_old) ^ (!features_new)) { + fprintf(stderr, "can't compare features (one GFEATURES failed)\n"); + features_old = NULL; } + compare_offload_state(&offload_old, &offload_new); + if (features_old) + compare_features(features_old, features_new); + return 0; }