From patchwork Sat Jan 28 08:37:14 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TWljaGHFgiBNaXJvc8WCYXc=?= X-Patchwork-Id: 138384 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 8210AB6EF1 for ; Sat, 28 Jan 2012 19:37:19 +1100 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752221Ab2A1IhQ (ORCPT ); Sat, 28 Jan 2012 03:37:16 -0500 Received: from rere.qmqm.pl ([89.167.52.164]:43477 "EHLO rere.qmqm.pl" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752025Ab2A1IhP (ORCPT ); Sat, 28 Jan 2012 03:37:15 -0500 Received: by rere.qmqm.pl (Postfix, from userid 1000) id 2605A13A6C; Sat, 28 Jan 2012 09:37:14 +0100 (CET) Message-Id: <08e06fc4d7b6e4dba466422a7cc93b2e2630c978.1327739670.git.mirq-linux@rere.qmqm.pl> From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Subject: [RFC PATCH v2] ethtool: implement [GS]FEATURES handling MIME-Version: 1.0 References: <9327efbf43fadeadd7017926962c65c95288d50e.1296741561.git.mirq-linux@rere.qmqm.pl> <1297107597.4077.8.camel@bwh-desktop> <1327413243.7231.12.camel@edumazet-HP-Compaq-6005-Pro-SFF-PC> <1327419002.5400.27.camel@deadeye> <20120124190513.GC19993@rere.qmqm.pl> To: Ben Hutchings Cc: Eric Dumazet , netdev@vger.kernel.org Date: Sat, 28 Jan 2012 09:37:14 +0100 (CET) Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org v2: fix missing change CMDL_FLAG -> CMDL_BOOL for old offloads parsing Signed-off-by: Michał Mirosław --- ethtool.c | 613 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 files changed, 490 insertions(+), 123 deletions(-) diff --git a/ethtool.c b/ethtool.c index d0cc7d4..8df2ce8 100644 --- a/ethtool.c +++ b/ethtool.c @@ -1036,9 +1036,15 @@ static int dump_coalesce(const struct ethtool_coalesce *ecoal) 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; +}; + +static 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" @@ -1053,18 +1059,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; } @@ -1547,24 +1553,20 @@ static int do_scoalesce(struct cmd_context *ctx) return 0; } -static int do_goffload(struct cmd_context *ctx) +static int send_goffloads(struct cmd_context *ctx, + 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; - - if (ctx->argc != 0) - exit_bad_args(); + int err, allfail = 1; - fprintf(stdout, "Offload parameters for %s:\n", ctx->devname); + memset(offload, 0, sizeof(*offload)); eval.cmd = ETHTOOL_GRXCSUM; err = send_ioctl(ctx, &eval); if (err) perror("Cannot get device rx csum settings"); else { - rx = eval.data; + offload->rx = eval.data; allfail = 0; } @@ -1573,7 +1575,7 @@ static int do_goffload(struct cmd_context *ctx) if (err) perror("Cannot get device tx csum settings"); else { - tx = eval.data; + offload->tx = eval.data; allfail = 0; } @@ -1582,7 +1584,7 @@ static int do_goffload(struct cmd_context *ctx) if (err) perror("Cannot get device scatter-gather settings"); else { - sg = eval.data; + offload->sg = eval.data; allfail = 0; } @@ -1591,7 +1593,7 @@ static int do_goffload(struct cmd_context *ctx) if (err) perror("Cannot get device tcp segmentation offload settings"); else { - tso = eval.data; + offload->tso = eval.data; allfail = 0; } @@ -1600,7 +1602,7 @@ static int do_goffload(struct cmd_context *ctx) if (err) perror("Cannot get device udp large send offload settings"); else { - ufo = eval.data; + offload->ufo = eval.data; allfail = 0; } @@ -1609,7 +1611,7 @@ static int do_goffload(struct cmd_context *ctx) if (err) perror("Cannot get device generic segmentation offload settings"); else { - gso = eval.data; + offload->gso = eval.data; allfail = 0; } @@ -1618,11 +1620,11 @@ static int do_goffload(struct cmd_context *ctx) 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; } @@ -1631,7 +1633,7 @@ static int do_goffload(struct cmd_context *ctx) if (err) perror("Cannot get device GRO settings"); else { - gro = eval.data; + offload->gro = eval.data; allfail = 0; } @@ -1640,144 +1642,509 @@ static int do_goffload(struct cmd_context *ctx) return 83; } - return dump_offload(rx, tx, sg, tso, ufo, gso, gro, lro, rxvlan, txvlan, - ntuple, rxhash); + return 0; } -static int do_soffload(struct cmd_context *ctx) +static int send_soffloads(struct cmd_context *ctx, + const struct offload_state *wanted) { - int goffload_changed = 0; - int off_csum_rx_wanted = -1; - int off_csum_tx_wanted = -1; - int off_sg_wanted = -1; - int off_tso_wanted = -1; - int off_ufo_wanted = -1; - int off_gso_wanted = -1; + struct ethtool_value eval; + int changed = 0, err; u32 off_flags_wanted = 0; u32 off_flags_mask = 0; - int off_gro_wanted = -1; - struct cmdline_info cmdline_offload[] = { - { "rx", CMDL_BOOL, &off_csum_rx_wanted, NULL }, - { "tx", CMDL_BOOL, &off_csum_tx_wanted, NULL }, - { "sg", CMDL_BOOL, &off_sg_wanted, NULL }, - { "tso", CMDL_BOOL, &off_tso_wanted, NULL }, - { "ufo", CMDL_BOOL, &off_ufo_wanted, NULL }, - { "gso", CMDL_BOOL, &off_gso_wanted, NULL }, - { "lro", CMDL_FLAG, &off_flags_wanted, NULL, - ETH_FLAG_LRO, &off_flags_mask }, - { "gro", CMDL_BOOL, &off_gro_wanted, NULL }, - { "rxvlan", CMDL_FLAG, &off_flags_wanted, NULL, - ETH_FLAG_RXVLAN, &off_flags_mask }, - { "txvlan", CMDL_FLAG, &off_flags_wanted, NULL, - ETH_FLAG_TXVLAN, &off_flags_mask }, - { "ntuple", CMDL_FLAG, &off_flags_wanted, NULL, - ETH_FLAG_NTUPLE, &off_flags_mask }, - { "rxhash", CMDL_FLAG, &off_flags_wanted, NULL, - ETH_FLAG_RXHASH, &off_flags_mask }, - }; - struct ethtool_value eval; - int err, changed = 0; - parse_generic_cmdline(ctx, &goffload_changed, - cmdline_offload, ARRAY_SIZE(cmdline_offload)); + if (wanted->lro >= 0) { + off_flags_mask |= ETH_FLAG_LRO; + if (wanted->lro) + off_flags_wanted |= ETH_FLAG_LRO; + } + + if (wanted->rxvlan >= 0) { + off_flags_mask |= ETH_FLAG_RXVLAN; + if (wanted->rxvlan) + off_flags_wanted |= ETH_FLAG_RXVLAN; + } - if (off_csum_rx_wanted >= 0) { - changed = 1; + if (wanted->txvlan >= 0) { + off_flags_mask |= ETH_FLAG_TXVLAN; + if (wanted->txvlan) + off_flags_wanted |= ETH_FLAG_TXVLAN; + } + + if (wanted->ntuple >= 0) { + off_flags_mask |= ETH_FLAG_NTUPLE; + if (wanted->ntuple) + off_flags_wanted |= ETH_FLAG_NTUPLE; + } + + if (wanted->rxhash >= 0) { + off_flags_mask |= ETH_FLAG_RXHASH; + if (wanted->rxhash) + off_flags_wanted |= ETH_FLAG_RXHASH; + } + + if (wanted->rx >= 0) { eval.cmd = ETHTOOL_SRXCSUM; - eval.data = (off_csum_rx_wanted == 1); + eval.data = !!wanted->rx; err = send_ioctl(ctx, &eval); - if (err) { + if (err) perror("Cannot set device rx csum settings"); - return 84; - } + else + changed = 1; } - if (off_csum_tx_wanted >= 0) { - changed = 1; + if (wanted->tx >= 0) { eval.cmd = ETHTOOL_STXCSUM; - eval.data = (off_csum_tx_wanted == 1); + eval.data = !!wanted->tx; err = send_ioctl(ctx, &eval); - if (err) { + if (err) perror("Cannot set device tx csum settings"); - return 85; - } + else + changed = 1; } - if (off_sg_wanted >= 0) { - changed = 1; + if (wanted->sg >= 0) { eval.cmd = ETHTOOL_SSG; - eval.data = (off_sg_wanted == 1); + eval.data = !!wanted->sg; err = send_ioctl(ctx, &eval); - if (err) { + if (err) perror("Cannot set device scatter-gather settings"); - return 86; - } + else + changed = 1; } - if (off_tso_wanted >= 0) { - changed = 1; + if (wanted->tso >= 0) { eval.cmd = ETHTOOL_STSO; - eval.data = (off_tso_wanted == 1); + eval.data = !!wanted->tso; err = send_ioctl(ctx, &eval); - if (err) { + if (err) perror("Cannot set device tcp segmentation offload settings"); - return 88; - } + else + changed = 1; } - if (off_ufo_wanted >= 0) { - changed = 1; + if (wanted->ufo >= 0) { eval.cmd = ETHTOOL_SUFO; - eval.data = (off_ufo_wanted == 1); + eval.data = !!wanted->ufo; err = send_ioctl(ctx, &eval); - 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; + if (wanted->gso >= 0) { eval.cmd = ETHTOOL_SGSO; - eval.data = (off_gso_wanted == 1); + eval.data = !!wanted->gso; err = send_ioctl(ctx, &eval); - 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; err = send_ioctl(ctx, &eval); - if (err) { + 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 = send_ioctl(ctx, &eval); - 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 = send_ioctl(ctx, &eval); + if (err) + perror("Cannot set device flag settings"); + else + changed = 1; } } - if (off_gro_wanted >= 0) { - changed = 1; + if (wanted->gro >= 0) { eval.cmd = ETHTOOL_SGRO; - eval.data = (off_gro_wanted == 1); + eval.data = !!wanted->gro; err = send_ioctl(ctx, &eval); - if (err) { + if (err) perror("Cannot set device GRO settings"); - return 93; + else + changed = 1; + } + + return changed; +} + +static int n_feature_strings; +static const char **feature_strings; + +static int init_feature_strings(struct cmd_context *ctx) +{ + struct ethtool_gstrings *strings; + int i, n; + + if (feature_strings) + return n_feature_strings; + + strings = get_stringset(ctx, ETH_SS_FEATURES, 0); + if (!strings) + return -100; + + n = n_feature_strings = strings->len; + 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(struct cmd_context *ctx, + struct offload_state *offload, + struct ethtool_sfeatures **features_req_p) +{ + struct ethtool_sfeatures *features_req; + struct cmdline_info *cmdline_desc, *cp; + int sz_features, i; + int changed = 0; + + struct cmdline_info cmdline_offload[] = { + { "rx", CMDL_BOOL, &offload->rx, NULL }, + { "tx", CMDL_BOOL, &offload->tx, NULL }, + { "sg", CMDL_BOOL, &offload->sg, NULL }, + { "tso", CMDL_BOOL, &offload->tso, NULL }, + { "ufo", CMDL_BOOL, &offload->ufo, NULL }, + { "gso", CMDL_BOOL, &offload->gso, NULL }, + { "lro", CMDL_BOOL, &offload->lro, NULL }, + { "gro", CMDL_BOOL, &offload->gro, NULL }, + { "rxvlan", CMDL_BOOL, &offload->rxvlan, NULL }, + { "txvlan", CMDL_BOOL, &offload->txvlan, NULL }, + { "ntuple", CMDL_BOOL, &offload->ntuple, NULL }, + { "rxhash", CMDL_BOOL, &offload->rxhash, NULL }, + }; + + for (i = 0; i < ARRAY_SIZE(old_feature_names); ++i) + ((int *)offload)[i] = -1; + *features_req_p = NULL; + + if (init_feature_strings(ctx) < 0) { + /* ETHTOOL_GFEATURES unavailable */ + parse_generic_cmdline(ctx, &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(ctx, &changed, cmdline_desc, cp - cmdline_desc); + + free(cmdline_desc); + + if (changed) + *features_req_p = features_req; + else + free(features_req); +} + +static int send_gfeatures(struct cmd_context *ctx, + 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; + err = send_ioctl(ctx, features); + + if (err < 0) { + perror("Cannot get feature status"); + free(features); + return 97; + } + + *features_p = features; + return 0; +} + +static const char *get_feature_state(struct ethtool_get_features_block *gfb, + uint32_t bit) +{ + if (gfb->never_changed & bit) + return "fixed"; + if (!(gfb->available & bit)) + return "driver-controlled"; + + if ((gfb->active ^ gfb->requested) & bit) + return (gfb->requested & bit) ? "requested on" : "requested off"; + else + return "changeable"; +} + +static int do_gfeatures(struct cmd_context *ctx) +{ + struct ethtool_get_features_block *gfb; + struct ethtool_gfeatures *features; + uint32_t bit; + int err, i; + + err = init_feature_strings(ctx); + if (err < 0) + return -err; + + err = send_gfeatures(ctx, &features); + if (err) + return err; + + fprintf(stdout, "\nExtended offload state for %s:\n", ctx->devname); + for (i = 0; i < n_feature_strings; i++) { + if (!feature_strings[i]) + continue; /* empty */ + + gfb = features->features + i / 32; + bit = 1 << (i % 32); + + fprintf(stdout, "%s: %s (%s)\n", feature_strings[i], + (gfb->active & bit) ? "on" : "off", + get_feature_state(gfb, bit)); + } + 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(stdout, "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(struct cmd_context *ctx, + struct ethtool_gfeatures **features, + struct offload_state *offload) +{ + int err, allfail; + + if (ctx->argc != 0) + exit_bad_args(); + + allfail = send_goffloads(ctx, offload); + + err = init_feature_strings(ctx); + if (err < 0) + return allfail ? err : 0; + + err = send_gfeatures(ctx, features); + if (err) + perror("Cannot read features"); + + return allfail ? -err : 0; +} + +static int send_sfeatures(struct cmd_context *ctx, + struct ethtool_sfeatures *features) +{ + int err; + + features->cmd = ETHTOOL_SFEATURES; + err = send_ioctl(ctx, features); + if (err < 0) { + perror("Cannot change features"); + return 97; + } + + return 0; +} + +static void compare_offload_state(struct offload_state *offload0, + struct offload_state *offload_req, struct offload_state *offload1) +{ + int *old = (int *)offload0; + int *req = (int *)offload_req; + int *new = (int *)offload1; + int i; + + for (i = 0; i < ARRAY_SIZE(old_feature_names); i++) { + if (req[i] < 0) + req[i] = old[i]; + if (req[i] == new[i]) + continue; + + fprintf(stdout, "feature group %s is %s (expected: %s)\n", + old_feature_names[i], + new[i] ? "enabled" : "disabled", + req[i] ? "enabled" : "disabled" + ); + } +} + +static void compare_features(struct ethtool_gfeatures *features0, + struct ethtool_sfeatures *features_req, + struct ethtool_gfeatures *features1) +{ + int i; + + if (features_req) { + /* 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_goffload(struct cmd_context *ctx) +{ + struct offload_state offload; + int err, allfail; + + allfail = send_goffloads(ctx, &offload); + + if (!allfail) { + fprintf(stdout, "Offload parameters for %s:\n", ctx->devname); + + dump_offload(&offload); + } + + err = do_gfeatures(ctx); + if (!err) + allfail = 0; + + if (allfail) { + fprintf(stdout, "no offload info available\n"); + return 83; + } + + return 0; +} + +static int do_soffload(struct cmd_context *ctx) +{ + struct ethtool_gfeatures *features_old, *features_new; + struct ethtool_sfeatures *features_req; + struct offload_state offload_old, offload_new, offload_req; + int err, changed; + + parse_sfeatures_args(ctx, &offload_req, &features_req); + + err = get_offload_state(ctx, &features_old, &offload_old); + if (err) + return -err; + + changed = send_soffloads(ctx, &offload_req); + + if (features_req) { + err = send_sfeatures(ctx, features_req); + if (!err) + changed = 1; + } + if (!changed) { fprintf(stdout, "no offload settings changed\n"); + return err; + } + + err = get_offload_state(ctx, &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"); + if (features_old) { + free(features_old); + features_old = NULL; + } + if (features_new) { + free(features_new); + features_new = NULL; + } + } + + compare_offload_state(&offload_old, &offload_req, &offload_new); + if (features_old) { + compare_features(features_old, features_req, features_new); + free(features_old); + free(features_new); } + if (features_req) + free(features_req); return 0; }