[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <08e06fc4d7b6e4dba466422a7cc93b2e2630c978.1327739670.git.mirq-linux@rere.qmqm.pl>
Date: Sat, 28 Jan 2012 09:37:14 +0100 (CET)
From: Michał Mirosław <mirq-linux@...e.qmqm.pl>
To: Ben Hutchings <bhutchings@...arflare.com>
Cc: Eric Dumazet <eric.dumazet@...il.com>, netdev@...r.kernel.org
Subject: [RFC PATCH v2] ethtool: implement [GS]FEATURES handling
v2: fix missing change CMDL_FLAG -> CMDL_BOOL for old offloads parsing
Signed-off-by: Michał Mirosław <mirq-linux@...e.qmqm.pl>
---
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;
}
--
1.7.8.3
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists