summaryrefslogtreecommitdiffstats
path: root/ethtool.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:34:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:34:36 +0000
commit74ebeae0b4c411df9900224b90a6072b16098458 (patch)
treefee8f5c9e37f1a9f0842e026876c8af541fa2e86 /ethtool.c
parentInitial commit. (diff)
downloadethtool-daf8f710f3b577507c7c0805154353b13d628d2d.tar.xz
ethtool-daf8f710f3b577507c7c0805154353b13d628d2d.zip
Adding upstream version 1:6.7.upstream/1%6.7
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ethtool.c')
-rw-r--r--ethtool.c6488
1 files changed, 6488 insertions, 0 deletions
diff --git a/ethtool.c b/ethtool.c
new file mode 100644
index 0000000..3ac15a7
--- /dev/null
+++ b/ethtool.c
@@ -0,0 +1,6488 @@
+/*
+ * ethtool.c: Linux ethernet device configuration tool.
+ *
+ * Copyright (C) 1998 David S. Miller (davem@dm.cobaltmicro.com)
+ * Portions Copyright 2001 Sun Microsystems
+ * Kernel 2.4 update Copyright 2001 Jeff Garzik <jgarzik@mandrakesoft.com>
+ * Wake-on-LAN,natsemi,misc support by Tim Hockin <thockin@sun.com>
+ * Portions Copyright 2002 Intel
+ * Portions Copyright (C) Sun Microsystems 2008
+ * do_test support by Eli Kupermann <eli.kupermann@intel.com>
+ * ETHTOOL_PHYS_ID support by Chris Leech <christopher.leech@intel.com>
+ * e1000 support by Scott Feldman <scott.feldman@intel.com>
+ * e100 support by Wen Tao <wen-hwa.tao@intel.com>
+ * ixgb support by Nicholas Nunley <Nicholas.d.nunley@intel.com>
+ * amd8111e support by Reeja John <reeja.john@amd.com>
+ * long arguments by Andi Kleen.
+ * SMSC LAN911x support by Steve Glendinning <steve.glendinning@smsc.com>
+ * Rx Network Flow Control configuration support <santwona.behera@sun.com>
+ * Various features by Ben Hutchings <bhutchings@solarflare.com>;
+ * Copyright 2009, 2010 Solarflare Communications
+ * MDI-X set support by Jesse Brandeburg <jesse.brandeburg@intel.com>
+ * Copyright 2012 Intel Corporation
+ * vmxnet3 support by Shrikrishna Khare <skhare@vmware.com>
+ * Various features by Ben Hutchings <ben@decadent.org.uk>;
+ * Copyright 2008-2010, 2013-2016 Ben Hutchings
+ * QSFP+/QSFP28 DOM support by Vidya Sagar Ravipati <vidya@cumulusnetworks.com>
+ *
+ * TODO:
+ * * show settings for all devices
+ */
+
+#include "internal.h"
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <sys/utsname.h>
+#include <limits.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <linux/ioctl.h>
+#include <linux/sockios.h>
+#include <linux/netlink.h>
+
+#include "common.h"
+#include "netlink/extapi.h"
+
+#ifndef MAX_ADDR_LEN
+#define MAX_ADDR_LEN 32
+#endif
+
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+
+static void exit_bad_args(void) __attribute__((noreturn));
+
+static void exit_bad_args(void)
+{
+ fprintf(stderr,
+ "ethtool: bad command line argument(s)\n"
+ "For more information run ethtool -h\n");
+ exit(1);
+}
+
+static void exit_nlonly_param(const char *name) __attribute__((noreturn));
+
+static void exit_nlonly_param(const char *name)
+{
+ fprintf(stderr,
+ "ethtool: parameter '%s' can be used only with netlink\n",
+ name);
+ exit(1);
+}
+
+typedef enum {
+ CMDL_NONE,
+ CMDL_BOOL,
+ CMDL_S32,
+ CMDL_U8,
+ CMDL_U16,
+ CMDL_U32,
+ CMDL_U64,
+ CMDL_BE16,
+ CMDL_IP4,
+ CMDL_STR,
+ CMDL_FLAG,
+ CMDL_MAC,
+} cmdline_type_t;
+
+struct cmdline_info {
+ const char *name;
+ cmdline_type_t type;
+ /* Points to int (BOOL), s32, u16, u32 (U32/FLAG/IP4), u64,
+ * char * (STR) or u8[6] (MAC). For FLAG, the value accumulates
+ * all flags to be set. */
+ void *wanted_val;
+ void *ioctl_val;
+ /* For FLAG, the flag value to be set/cleared */
+ u32 flag_val;
+ /* For FLAG, points to u32 and accumulates all flags seen.
+ * For anything else, points to int and is set if the option is
+ * seen. */
+ void *seen_val;
+};
+
+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[OFF_FLAG_DEF_SIZE];
+ /* 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)
+{
+ long long v;
+ char *endp;
+
+ if (!str)
+ exit_bad_args();
+ errno = 0;
+ v = strtoll(str, &endp, base);
+ if (errno || *endp || v < min || v > max)
+ exit_bad_args();
+ return v;
+}
+
+static unsigned long long
+get_uint_range(char *str, int base, unsigned long long max)
+{
+ unsigned long long v;
+ char *endp;
+
+ if (!str)
+ exit_bad_args();
+ errno = 0;
+ v = strtoull(str, &endp, base);
+ if (errno || *endp || v > max)
+ exit_bad_args();
+ return v;
+}
+
+static int get_int(char *str, int base)
+{
+ return get_int_range(str, base, INT_MIN, INT_MAX);
+}
+
+static u32 get_u32(char *str, int base)
+{
+ return get_uint_range(str, base, 0xffffffff);
+}
+
+static void get_mac_addr(char *src, unsigned char *dest)
+{
+ int count;
+ int i;
+ int buf[ETH_ALEN];
+
+ count = sscanf(src, "%2x:%2x:%2x:%2x:%2x:%2x",
+ &buf[0], &buf[1], &buf[2], &buf[3], &buf[4], &buf[5]);
+ if (count != ETH_ALEN)
+ exit_bad_args();
+
+ for (i = 0; i < count; i++)
+ dest[i] = buf[i];
+}
+
+static int parse_hex_u32_bitmap(const char *s,
+ unsigned int nbits, u32 *result)
+{
+ const unsigned int nwords = __KERNEL_DIV_ROUND_UP(nbits, 32);
+ size_t slen = strlen(s);
+ size_t i;
+
+ /* ignore optional '0x' prefix */
+ if ((slen > 2) && (strncasecmp(s, "0x", 2) == 0)) {
+ slen -= 2;
+ s += 2;
+ }
+
+ if (slen > 8 * nwords) /* up to 2 digits per byte */
+ return -1;
+
+ memset(result, 0, 4 * nwords);
+ for (i = 0; i < slen; ++i) {
+ const unsigned int shift = (slen - 1 - i) * 4;
+ u32 *dest = &result[shift / 32];
+ u32 nibble;
+
+ if ('a' <= s[i] && s[i] <= 'f')
+ nibble = 0xa + (s[i] - 'a');
+ else if ('A' <= s[i] && s[i] <= 'F')
+ nibble = 0xa + (s[i] - 'A');
+ else if ('0' <= s[i] && s[i] <= '9')
+ nibble = (s[i] - '0');
+ else
+ return -1;
+
+ *dest |= (nibble << (shift % 32));
+ }
+
+ return 0;
+}
+
+static void parse_generic_cmdline(struct cmd_context *ctx,
+ int *changed,
+ struct cmdline_info *info,
+ unsigned int n_info)
+{
+ unsigned int argc = ctx->argc;
+ char **argp = ctx->argp;
+ unsigned int i, idx;
+ int found;
+
+ for (i = 0; i < argc; i++) {
+ found = 0;
+ for (idx = 0; idx < n_info; idx++) {
+ if (!strcmp(info[idx].name, argp[i])) {
+ found = 1;
+ *changed = 1;
+ if (info[idx].type != CMDL_FLAG &&
+ info[idx].seen_val)
+ *(int *)info[idx].seen_val = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ switch (info[idx].type) {
+ case CMDL_BOOL: {
+ int *p = info[idx].wanted_val;
+ if (!strcmp(argp[i], "on"))
+ *p = 1;
+ else if (!strcmp(argp[i], "off"))
+ *p = 0;
+ else
+ exit_bad_args();
+ break;
+ }
+ case CMDL_S32: {
+ s32 *p = info[idx].wanted_val;
+ *p = get_int_range(argp[i], 0,
+ -0x80000000LL,
+ 0x7fffffff);
+ break;
+ }
+ case CMDL_U8: {
+ u8 *p = info[idx].wanted_val;
+ *p = get_uint_range(argp[i], 0, 0xff);
+ break;
+ }
+ case CMDL_U16: {
+ u16 *p = info[idx].wanted_val;
+ *p = get_uint_range(argp[i], 0, 0xffff);
+ break;
+ }
+ case CMDL_U32: {
+ u32 *p = info[idx].wanted_val;
+ *p = get_uint_range(argp[i], 0,
+ 0xffffffff);
+ break;
+ }
+ case CMDL_U64: {
+ u64 *p = info[idx].wanted_val;
+ *p = get_uint_range(
+ argp[i], 0,
+ 0xffffffffffffffffLL);
+ break;
+ }
+ case CMDL_BE16: {
+ u16 *p = info[idx].wanted_val;
+ *p = cpu_to_be16(
+ get_uint_range(argp[i], 0,
+ 0xffff));
+ break;
+ }
+ case CMDL_IP4: {
+ u32 *p = info[idx].wanted_val;
+ struct in_addr in;
+ if (!inet_pton(AF_INET, argp[i], &in))
+ exit_bad_args();
+ *p = in.s_addr;
+ break;
+ }
+ case CMDL_MAC:
+ get_mac_addr(argp[i],
+ info[idx].wanted_val);
+ break;
+ case CMDL_FLAG: {
+ u32 *p;
+ p = info[idx].seen_val;
+ *p |= info[idx].flag_val;
+ if (!strcmp(argp[i], "on")) {
+ p = info[idx].wanted_val;
+ *p |= info[idx].flag_val;
+ } else if (strcmp(argp[i], "off")) {
+ exit_bad_args();
+ }
+ break;
+ }
+ case CMDL_STR: {
+ char **s = info[idx].wanted_val;
+ *s = strdup(argp[i]);
+ break;
+ }
+ default:
+ exit_bad_args();
+ }
+ break;
+ }
+ }
+ if (!found)
+ exit_bad_args();
+ }
+}
+
+static void flag_to_cmdline_info(const char *name, u32 value,
+ u32 *wanted, u32 *mask,
+ struct cmdline_info *cli)
+{
+ memset(cli, 0, sizeof(*cli));
+ cli->name = name;
+ cli->type = CMDL_FLAG;
+ cli->flag_val = value;
+ cli->wanted_val = wanted;
+ cli->seen_val = mask;
+}
+
+static int rxflow_str_to_type(const char *str)
+{
+ int flow_type = 0;
+
+ if (!strcmp(str, "tcp4"))
+ flow_type = TCP_V4_FLOW;
+ else if (!strcmp(str, "udp4"))
+ flow_type = UDP_V4_FLOW;
+ else if (!strcmp(str, "ah4") || !strcmp(str, "esp4"))
+ flow_type = AH_ESP_V4_FLOW;
+ else if (!strcmp(str, "sctp4"))
+ flow_type = SCTP_V4_FLOW;
+ else if (!strcmp(str, "tcp6"))
+ flow_type = TCP_V6_FLOW;
+ else if (!strcmp(str, "udp6"))
+ flow_type = UDP_V6_FLOW;
+ else if (!strcmp(str, "ah6") || !strcmp(str, "esp6"))
+ flow_type = AH_ESP_V6_FLOW;
+ else if (!strcmp(str, "sctp6"))
+ flow_type = SCTP_V6_FLOW;
+ else if (!strcmp(str, "ether"))
+ flow_type = ETHER_FLOW;
+
+ return flow_type;
+}
+
+static int do_version(struct cmd_context *ctx __maybe_unused)
+{
+ fprintf(stdout,
+ PACKAGE " version " VERSION
+#ifndef ETHTOOL_ENABLE_PRETTY_DUMP
+ " (pretty dumps disabled)"
+#endif
+ "\n");
+ return 0;
+}
+
+/* link mode routines */
+
+static ETHTOOL_DECLARE_LINK_MODE_MASK(all_advertised_modes);
+static ETHTOOL_DECLARE_LINK_MODE_MASK(all_advertised_flags);
+
+static void init_global_link_mode_masks(void)
+{
+ static const enum ethtool_link_mode_bit_indices
+ all_advertised_modes_bits[] = {
+ ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
+ ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT,
+ ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT,
+ ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
+ ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
+ ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
+ ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
+ ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT,
+ ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT,
+ ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT,
+ ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT,
+ ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
+ ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
+ ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
+ ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
+ ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT,
+ ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseT1_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseKR_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseSR_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseCR_Full_BIT,
+ ETHTOOL_LINK_MODE_100000baseDR_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT,
+ ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT,
+ ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT,
+ ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
+ ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
+ ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT,
+ ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT,
+ ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT,
+ ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT,
+ ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT,
+ ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT,
+ ETHTOOL_LINK_MODE_10baseT1S_Full_BIT,
+ ETHTOOL_LINK_MODE_10baseT1S_Half_BIT,
+ ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
+ };
+ static const enum ethtool_link_mode_bit_indices
+ additional_advertised_flags_bits[] = {
+ ETHTOOL_LINK_MODE_Autoneg_BIT,
+ ETHTOOL_LINK_MODE_TP_BIT,
+ ETHTOOL_LINK_MODE_AUI_BIT,
+ ETHTOOL_LINK_MODE_MII_BIT,
+ ETHTOOL_LINK_MODE_FIBRE_BIT,
+ ETHTOOL_LINK_MODE_BNC_BIT,
+ ETHTOOL_LINK_MODE_Pause_BIT,
+ ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ ETHTOOL_LINK_MODE_Backplane_BIT,
+ ETHTOOL_LINK_MODE_FEC_NONE_BIT,
+ ETHTOOL_LINK_MODE_FEC_RS_BIT,
+ ETHTOOL_LINK_MODE_FEC_BASER_BIT,
+ ETHTOOL_LINK_MODE_FEC_LLRS_BIT,
+ };
+ unsigned int i;
+
+ ethtool_link_mode_zero(all_advertised_modes);
+ ethtool_link_mode_zero(all_advertised_flags);
+ for (i = 0; i < ARRAY_SIZE(all_advertised_modes_bits); ++i) {
+ ethtool_link_mode_set_bit(all_advertised_modes_bits[i],
+ all_advertised_modes);
+ ethtool_link_mode_set_bit(all_advertised_modes_bits[i],
+ all_advertised_flags);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(additional_advertised_flags_bits); ++i) {
+ ethtool_link_mode_set_bit(
+ additional_advertised_flags_bits[i],
+ all_advertised_flags);
+ }
+}
+
+static void dump_link_caps(const char *prefix, const char *an_prefix,
+ const u32 *mask, int link_mode_only);
+
+static void dump_supported(const struct ethtool_link_usettings *link_usettings)
+{
+ fprintf(stdout, " Supported ports: [ ");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_TP_BIT,
+ link_usettings->link_modes.supported))
+ fprintf(stdout, "TP ");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_AUI_BIT,
+ link_usettings->link_modes.supported))
+ fprintf(stdout, "AUI ");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_BNC_BIT,
+ link_usettings->link_modes.supported))
+ fprintf(stdout, "BNC ");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_MII_BIT,
+ link_usettings->link_modes.supported))
+ fprintf(stdout, "MII ");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_FIBRE_BIT,
+ link_usettings->link_modes.supported))
+ fprintf(stdout, "FIBRE ");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_Backplane_BIT,
+ link_usettings->link_modes.supported))
+ fprintf(stdout, "Backplane ");
+ fprintf(stdout, "]\n");
+
+ dump_link_caps("Supported", "Supports",
+ link_usettings->link_modes.supported, 0);
+}
+
+/* Print link capability flags (supported, advertised or lp_advertised).
+ * Assumes that the corresponding SUPPORTED and ADVERTISED flags are equal.
+ */
+static void dump_link_caps(const char *prefix, const char *an_prefix,
+ const u32 *mask, int link_mode_only)
+{
+ static const struct {
+ int same_line; /* print on same line as previous */
+ unsigned int bit_index;
+ const char *name;
+ } mode_defs[] = {
+ { 0, ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ "10baseT/Half" },
+ { 1, ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ "10baseT/Full" },
+ { 0, ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ "100baseT/Half" },
+ { 1, ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ "100baseT/Full" },
+ { 0, ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ "1000baseT/Half" },
+ { 1, ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ "1000baseT/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ "10000baseT/Full" },
+ { 0, ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+ "2500baseX/Full" },
+ { 0, ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+ "1000baseKX/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+ "10000baseKX4/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+ "10000baseKR/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
+ "10000baseR_FEC" },
+ { 0, ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT,
+ "20000baseMLD2/Full" },
+ { 0, ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT,
+ "20000baseKR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
+ "40000baseKR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
+ "40000baseCR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
+ "40000baseSR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
+ "40000baseLR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT,
+ "56000baseKR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT,
+ "56000baseCR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT,
+ "56000baseSR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT,
+ "56000baseLR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
+ "25000baseCR/Full" },
+ { 0, ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
+ "25000baseKR/Full" },
+ { 0, ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
+ "25000baseSR/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
+ "50000baseCR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
+ "50000baseKR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
+ "100000baseKR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
+ "100000baseSR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
+ "100000baseCR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
+ "100000baseLR4_ER4/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
+ "50000baseSR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ "1000baseX/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
+ "10000baseCR/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
+ "10000baseSR/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
+ "10000baseLR/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
+ "10000baseLRM/Full" },
+ { 0, ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
+ "10000baseER/Full" },
+ { 0, ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ "2500baseT/Full" },
+ { 0, ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ "5000baseT/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
+ "50000baseKR/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
+ "50000baseSR/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
+ "50000baseCR/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
+ "50000baseLR_ER_FR/Full" },
+ { 0, ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
+ "50000baseDR/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
+ "100000baseKR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
+ "100000baseSR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
+ "100000baseCR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
+ "100000baseLR2_ER2_FR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
+ "100000baseDR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT,
+ "200000baseKR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT,
+ "200000baseSR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT,
+ "200000baseLR4_ER4_FR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT,
+ "200000baseDR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT,
+ "200000baseCR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
+ "100baseT1/Full" },
+ { 0, ETHTOOL_LINK_MODE_1000baseT1_Full_BIT,
+ "1000baseT1/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT,
+ "400000baseKR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT,
+ "400000baseSR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT,
+ "400000baseLR8_ER8_FR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT,
+ "400000baseDR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT,
+ "400000baseCR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseKR_Full_BIT,
+ "100000baseKR/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseSR_Full_BIT,
+ "100000baseSR/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT,
+ "100000baseLR_ER_FR/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseDR_Full_BIT,
+ "100000baseDR/Full" },
+ { 0, ETHTOOL_LINK_MODE_100000baseCR_Full_BIT,
+ "100000baseCR/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT,
+ "200000baseKR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT,
+ "200000baseSR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT,
+ "200000baseLR2_ER2_FR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT,
+ "200000baseDR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT,
+ "200000baseCR2/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT,
+ "400000baseKR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT,
+ "400000baseSR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT,
+ "400000baseLR4_ER4_FR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT,
+ "400000baseDR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT,
+ "400000baseCR4/Full" },
+ { 0, ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
+ "100baseFX/Half" },
+ { 1, ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
+ "100baseFX/Full" },
+ { 0, ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ "10baseT1L/Full" },
+ { 0, ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT,
+ "800000baseCR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT,
+ "800000baseKR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT,
+ "800000baseDR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT,
+ "800000baseDR8_2/Full" },
+ { 0, ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT,
+ "800000baseSR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT,
+ "800000baseVR8/Full" },
+ { 0, ETHTOOL_LINK_MODE_10baseT1S_Full_BIT,
+ "10baseT1S/Full" },
+ { 1, ETHTOOL_LINK_MODE_10baseT1S_Half_BIT,
+ "10baseT1S/Half" },
+ { 0, ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
+ "10baseT1S/Half" },
+ };
+ int indent;
+ int did1, new_line_pend;
+ int fecreported = 0;
+ unsigned int i;
+
+ /* Indent just like the separate functions used to */
+ indent = strlen(prefix) + 14;
+ if (indent < 24)
+ indent = 24;
+
+ fprintf(stdout, " %s link modes:%*s", prefix,
+ indent - (int)strlen(prefix) - 12, "");
+ did1 = 0;
+ new_line_pend = 0;
+ for (i = 0; i < ARRAY_SIZE(mode_defs); i++) {
+ if (did1 && !mode_defs[i].same_line)
+ new_line_pend = 1;
+ if (ethtool_link_mode_test_bit(mode_defs[i].bit_index,
+ mask)) {
+ if (new_line_pend) {
+ fprintf(stdout, "\n");
+ fprintf(stdout, " %*s", indent, "");
+ new_line_pend = 0;
+ }
+ did1++;
+ fprintf(stdout, "%s ", mode_defs[i].name);
+ }
+ }
+ if (did1 == 0)
+ fprintf(stdout, "Not reported");
+ fprintf(stdout, "\n");
+
+ if (!link_mode_only) {
+ fprintf(stdout, " %s pause frame use: ", prefix);
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_Pause_BIT, mask)) {
+ fprintf(stdout, "Symmetric");
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask))
+ fprintf(stdout, " Receive-only");
+ fprintf(stdout, "\n");
+ } else {
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask))
+ fprintf(stdout, "Transmit-only\n");
+ else
+ fprintf(stdout, "No\n");
+ }
+
+ fprintf(stdout, " %s auto-negotiation: ", an_prefix);
+ if (ethtool_link_mode_test_bit(
+ ETHTOOL_LINK_MODE_Autoneg_BIT, mask))
+ fprintf(stdout, "Yes\n");
+ else
+ fprintf(stdout, "No\n");
+
+ fprintf(stdout, " %s FEC modes:", prefix);
+ if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT,
+ mask)) {
+ fprintf(stdout, " None");
+ fecreported = 1;
+ }
+ if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT,
+ mask)) {
+ fprintf(stdout, " BaseR");
+ fecreported = 1;
+ }
+ if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT,
+ mask)) {
+ fprintf(stdout, " RS");
+ fecreported = 1;
+ }
+ if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT,
+ mask)) {
+ fprintf(stdout, " LLRS");
+ fecreported = 1;
+ }
+
+ if (!fecreported)
+ fprintf(stdout, " Not reported");
+ fprintf(stdout, "\n");
+ }
+}
+
+static int
+dump_link_usettings(const struct ethtool_link_usettings *link_usettings)
+{
+ dump_supported(link_usettings);
+ dump_link_caps("Advertised", "Advertised",
+ link_usettings->link_modes.advertising, 0);
+ if (!ethtool_link_mode_is_empty(
+ link_usettings->link_modes.lp_advertising))
+ dump_link_caps("Link partner advertised",
+ "Link partner advertised",
+ link_usettings->link_modes.lp_advertising, 0);
+
+ fprintf(stdout, " Speed: ");
+ if (link_usettings->base.speed == 0
+ || link_usettings->base.speed == (u16)(-1)
+ || link_usettings->base.speed == (u32)(-1))
+ fprintf(stdout, "Unknown!\n");
+ else
+ fprintf(stdout, "%uMb/s\n", link_usettings->base.speed);
+
+ fprintf(stdout, " Duplex: ");
+ switch (link_usettings->base.duplex) {
+ case DUPLEX_HALF:
+ fprintf(stdout, "Half\n");
+ break;
+ case DUPLEX_FULL:
+ fprintf(stdout, "Full\n");
+ break;
+ default:
+ fprintf(stdout, "Unknown! (%i)\n", link_usettings->base.duplex);
+ break;
+ };
+
+ fprintf(stdout, " Port: ");
+ switch (link_usettings->base.port) {
+ case PORT_TP:
+ fprintf(stdout, "Twisted Pair\n");
+ break;
+ case PORT_AUI:
+ fprintf(stdout, "AUI\n");
+ break;
+ case PORT_BNC:
+ fprintf(stdout, "BNC\n");
+ break;
+ case PORT_MII:
+ fprintf(stdout, "MII\n");
+ break;
+ case PORT_FIBRE:
+ fprintf(stdout, "FIBRE\n");
+ break;
+ case PORT_DA:
+ fprintf(stdout, "Direct Attach Copper\n");
+ break;
+ case PORT_NONE:
+ fprintf(stdout, "None\n");
+ break;
+ case PORT_OTHER:
+ fprintf(stdout, "Other\n");
+ break;
+ default:
+ fprintf(stdout, "Unknown! (%i)\n", link_usettings->base.port);
+ break;
+ };
+
+ fprintf(stdout, " PHYAD: %d\n", link_usettings->base.phy_address);
+ fprintf(stdout, " Transceiver: ");
+ switch (link_usettings->deprecated.transceiver) {
+ case XCVR_INTERNAL:
+ fprintf(stdout, "internal\n");
+ break;
+ case XCVR_EXTERNAL:
+ fprintf(stdout, "external\n");
+ break;
+ default:
+ fprintf(stdout, "Unknown!\n");
+ break;
+ };
+
+ fprintf(stdout, " Auto-negotiation: %s\n",
+ (link_usettings->base.autoneg == AUTONEG_DISABLE) ?
+ "off" : "on");
+
+ if (link_usettings->base.port == PORT_TP)
+ dump_mdix(link_usettings->base.eth_tp_mdix,
+ link_usettings->base.eth_tp_mdix_ctrl);
+
+ return 0;
+}
+
+static int dump_drvinfo(struct ethtool_drvinfo *info)
+{
+ fprintf(stdout,
+ "driver: %.*s\n"
+ "version: %.*s\n"
+ "firmware-version: %.*s\n"
+ "expansion-rom-version: %.*s\n"
+ "bus-info: %.*s\n"
+ "supports-statistics: %s\n"
+ "supports-test: %s\n"
+ "supports-eeprom-access: %s\n"
+ "supports-register-dump: %s\n"
+ "supports-priv-flags: %s\n",
+ (int)sizeof(info->driver), info->driver,
+ (int)sizeof(info->version), info->version,
+ (int)sizeof(info->fw_version), info->fw_version,
+ (int)sizeof(info->erom_version), info->erom_version,
+ (int)sizeof(info->bus_info), info->bus_info,
+ info->n_stats ? "yes" : "no",
+ info->testinfo_len ? "yes" : "no",
+ info->eedump_len ? "yes" : "no",
+ info->regdump_len ? "yes" : "no",
+ info->n_priv_flags ? "yes" : "no");
+
+ return 0;
+}
+
+static int parse_wolopts(char *optstr, u32 *data)
+{
+ *data = 0;
+ while (*optstr) {
+ switch (*optstr) {
+ case 'p':
+ *data |= WAKE_PHY;
+ break;
+ case 'u':
+ *data |= WAKE_UCAST;
+ break;
+ case 'm':
+ *data |= WAKE_MCAST;
+ break;
+ case 'b':
+ *data |= WAKE_BCAST;
+ break;
+ case 'a':
+ *data |= WAKE_ARP;
+ break;
+ case 'g':
+ *data |= WAKE_MAGIC;
+ break;
+ case 's':
+ *data |= WAKE_MAGICSECURE;
+ break;
+ case 'f':
+ *data |= WAKE_FILTER;
+ break;
+ case 'd':
+ *data = 0;
+ break;
+ default:
+ return -1;
+ }
+ optstr++;
+ }
+ return 0;
+}
+
+static int parse_rxfhashopts(char *optstr, u32 *data)
+{
+ *data = 0;
+ while (*optstr) {
+ switch (*optstr) {
+ case 'm':
+ *data |= RXH_L2DA;
+ break;
+ case 'v':
+ *data |= RXH_VLAN;
+ break;
+ case 't':
+ *data |= RXH_L3_PROTO;
+ break;
+ case 's':
+ *data |= RXH_IP_SRC;
+ break;
+ case 'd':
+ *data |= RXH_IP_DST;
+ break;
+ case 'f':
+ *data |= RXH_L4_B_0_1;
+ break;
+ case 'n':
+ *data |= RXH_L4_B_2_3;
+ break;
+ case 'r':
+ *data |= RXH_DISCARD;
+ break;
+ default:
+ return -1;
+ }
+ optstr++;
+ }
+ return 0;
+}
+
+static char *unparse_rxfhashopts(u64 opts)
+{
+ static char buf[300];
+
+ memset(buf, 0, sizeof(buf));
+
+ if (opts) {
+ if (opts & RXH_L2DA)
+ strcat(buf, "L2DA\n");
+ if (opts & RXH_VLAN)
+ strcat(buf, "VLAN tag\n");
+ if (opts & RXH_L3_PROTO)
+ strcat(buf, "L3 proto\n");
+ if (opts & RXH_IP_SRC)
+ strcat(buf, "IP SA\n");
+ if (opts & RXH_IP_DST)
+ strcat(buf, "IP DA\n");
+ if (opts & RXH_L4_B_0_1)
+ strcat(buf, "L4 bytes 0 & 1 [TCP/UDP src port]\n");
+ if (opts & RXH_L4_B_2_3)
+ strcat(buf, "L4 bytes 2 & 3 [TCP/UDP dst port]\n");
+ } else {
+ sprintf(buf, "None");
+ }
+
+ return buf;
+}
+
+static int convert_string_to_hashkey(char *rss_hkey, u32 key_size,
+ const char *rss_hkey_string)
+{
+ u32 i = 0;
+ int hex_byte, len;
+
+ do {
+ if (i > (key_size - 1)) {
+ fprintf(stderr,
+ "Key is too long for device (%u > %u)\n",
+ i + 1, key_size);
+ goto err;
+ }
+
+ if (sscanf(rss_hkey_string, "%2x%n", &hex_byte, &len) < 1 ||
+ len != 2) {
+ fprintf(stderr, "Invalid RSS hash key format\n");
+ goto err;
+ }
+
+ rss_hkey[i++] = hex_byte;
+ rss_hkey_string += 2;
+
+ if (*rss_hkey_string == ':') {
+ rss_hkey_string++;
+ } else if (*rss_hkey_string != '\0') {
+ fprintf(stderr, "Invalid RSS hash key format\n");
+ goto err;
+ }
+
+ } while (*rss_hkey_string);
+
+ if (i != key_size) {
+ fprintf(stderr, "Key is too short for device (%u < %u)\n",
+ i, key_size);
+ goto err;
+ }
+
+ return 0;
+err:
+ return 2;
+}
+
+static int parse_hkey(char **rss_hkey, u32 key_size,
+ const char *rss_hkey_string)
+{
+ if (!key_size) {
+ fprintf(stderr,
+ "Cannot set RX flow hash configuration:\n"
+ " Hash key setting not supported\n");
+ return 1;
+ }
+
+ *rss_hkey = malloc(key_size);
+ if (!(*rss_hkey)) {
+ perror("Cannot allocate memory for RSS hash key");
+ return 1;
+ }
+
+ if (convert_string_to_hashkey(*rss_hkey, key_size,
+ rss_hkey_string)) {
+ free(*rss_hkey);
+ *rss_hkey = NULL;
+ return 2;
+ }
+ return 0;
+}
+
+#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
+static const struct {
+ const char *name;
+ int (*func)(struct ethtool_drvinfo *info, struct ethtool_regs *regs);
+
+} driver_list[] = {
+ { "8139cp", realtek_dump_regs },
+ { "8139too", realtek_dump_regs },
+ { "r8169", realtek_dump_regs },
+ { "de2104x", de2104x_dump_regs },
+ { "e1000", e1000_dump_regs },
+ { "e1000e", e1000_dump_regs },
+ { "igb", igb_dump_regs },
+ { "ixgb", ixgb_dump_regs },
+ { "ixgbe", ixgbe_dump_regs },
+ { "ixgbevf", ixgbevf_dump_regs },
+ { "natsemi", natsemi_dump_regs },
+ { "e100", e100_dump_regs },
+ { "amd8111e", amd8111e_dump_regs },
+ { "pcnet32", pcnet32_dump_regs },
+ { "fec_8xx", fec_8xx_dump_regs },
+ { "ibm_emac", ibm_emac_dump_regs },
+ { "tg3", tg3_dump_regs },
+ { "skge", skge_dump_regs },
+ { "sky2", sky2_dump_regs },
+ { "vioc", vioc_dump_regs },
+ { "smsc911x", smsc911x_dump_regs },
+ { "at76c50x-usb", at76c50x_usb_dump_regs },
+ { "sfc", sfc_dump_regs },
+ { "st_mac100", st_mac100_dump_regs },
+ { "st_gmac", st_gmac_dump_regs },
+ { "et131x", et131x_dump_regs },
+ { "altera_tse", altera_tse_dump_regs },
+ { "vmxnet3", vmxnet3_dump_regs },
+ { "fjes", fjes_dump_regs },
+ { "lan78xx", lan78xx_dump_regs },
+ { "dsa", dsa_dump_regs },
+ { "fec", fec_dump_regs },
+ { "igc", igc_dump_regs },
+ { "bnxt_en", bnxt_dump_regs },
+ { "cpsw-switch", cpsw_dump_regs },
+ { "lan743x", lan743x_dump_regs },
+ { "fsl_enetc", fsl_enetc_dump_regs },
+ { "fsl_enetc_vf", fsl_enetc_dump_regs },
+ { "hns3", hns3_dump_regs },
+};
+#endif
+
+void dump_hex(FILE *file, const u8 *data, int len, int offset)
+{
+ int i;
+
+ fprintf(file, "Offset\t\tValues\n");
+ fprintf(file, "------\t\t------");
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0)
+ fprintf(file, "\n0x%04x:\t\t", i + offset);
+ fprintf(file, "%02x ", data[i]);
+ }
+ fprintf(file, "\n");
+}
+
+static int dump_regs(int gregs_dump_raw, int gregs_dump_hex,
+ struct ethtool_drvinfo *info, struct ethtool_regs *regs)
+{
+ if (gregs_dump_raw) {
+ fwrite(regs->data, regs->len, 1, stdout);
+ goto nested;
+ }
+
+#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
+ if (!gregs_dump_hex) {
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(driver_list); i++)
+ if (!strncmp(driver_list[i].name, info->driver,
+ ETHTOOL_BUSINFO_LEN)) {
+ if (driver_list[i].func(info, regs) == 0)
+ goto nested;
+ /* This version (or some other
+ * variation in the dump format) is
+ * not handled; fall back to hex
+ */
+ break;
+ }
+ }
+#endif
+
+ dump_hex(stdout, regs->data, regs->len, 0);
+
+nested:
+ /* Recurse dump if some drvinfo and regs structures are nested */
+ if (info->regdump_len > regs->len + sizeof(*info) + sizeof(*regs)) {
+ info = (struct ethtool_drvinfo *)(&regs->data[0] + regs->len);
+ regs = (struct ethtool_regs *)(&regs->data[0] + regs->len + sizeof(*info));
+
+ return dump_regs(gregs_dump_raw, gregs_dump_hex, info, regs);
+ }
+
+ return 0;
+}
+
+static int dump_eeprom(int geeprom_dump_raw,
+ struct ethtool_drvinfo *info __maybe_unused,
+ struct ethtool_eeprom *ee)
+{
+ if (geeprom_dump_raw) {
+ fwrite(ee->data, 1, ee->len, stdout);
+ return 0;
+ }
+#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
+ if (!strncmp("natsemi", info->driver, ETHTOOL_BUSINFO_LEN)) {
+ return natsemi_dump_eeprom(info, ee);
+ } else if (!strncmp("tg3", info->driver, ETHTOOL_BUSINFO_LEN)) {
+ return tg3_dump_eeprom(info, ee);
+ }
+#endif
+ dump_hex(stdout, ee->data, ee->len, ee->offset);
+
+ return 0;
+}
+
+static int dump_test(struct ethtool_test *test,
+ struct ethtool_gstrings *strings)
+{
+ unsigned int i;
+ int rc;
+
+ rc = test->flags & ETH_TEST_FL_FAILED;
+ fprintf(stdout, "The test result is %s\n", rc ? "FAIL" : "PASS");
+
+ if (test->flags & ETH_TEST_FL_EXTERNAL_LB)
+ fprintf(stdout, "External loopback test was %sexecuted\n",
+ (test->flags & ETH_TEST_FL_EXTERNAL_LB_DONE) ?
+ "" : "not ");
+
+ if (strings->len)
+ fprintf(stdout, "The test extra info:\n");
+
+ for (i = 0; i < strings->len; i++) {
+ fprintf(stdout, "%s\t %d\n",
+ (char *)(strings->data + i * ETH_GSTRING_LEN),
+ (u32) test->data[i]);
+ }
+
+ fprintf(stdout, "\n");
+ return rc;
+}
+
+static int dump_pause(const struct ethtool_pauseparam *epause,
+ u32 advertising, u32 lp_advertising)
+{
+ fprintf(stdout,
+ "Autonegotiate: %s\n"
+ "RX: %s\n"
+ "TX: %s\n",
+ epause->autoneg ? "on" : "off",
+ epause->rx_pause ? "on" : "off",
+ epause->tx_pause ? "on" : "off");
+
+ if (lp_advertising) {
+ int an_rx = 0, an_tx = 0;
+
+ /* Work out negotiated pause frame usage per
+ * IEEE 802.3-2005 table 28B-3.
+ */
+ if (advertising & lp_advertising & ADVERTISED_Pause) {
+ an_tx = 1;
+ an_rx = 1;
+ } else if (advertising & lp_advertising &
+ ADVERTISED_Asym_Pause) {
+ if (advertising & ADVERTISED_Pause)
+ an_rx = 1;
+ else if (lp_advertising & ADVERTISED_Pause)
+ an_tx = 1;
+ }
+
+ fprintf(stdout,
+ "RX negotiated: %s\n"
+ "TX negotiated: %s\n",
+ an_rx ? "on" : "off",
+ an_tx ? "on" : "off");
+ }
+
+ fprintf(stdout, "\n");
+ return 0;
+}
+
+static int dump_ring(const struct ethtool_ringparam *ering)
+{
+ fprintf(stdout,
+ "Pre-set maximums:\n"
+ "RX: %u\n"
+ "RX Mini: %u\n"
+ "RX Jumbo: %u\n"
+ "TX: %u\n",
+ ering->rx_max_pending,
+ ering->rx_mini_max_pending,
+ ering->rx_jumbo_max_pending,
+ ering->tx_max_pending);
+
+ fprintf(stdout,
+ "Current hardware settings:\n"
+ "RX: %u\n"
+ "RX Mini: %u\n"
+ "RX Jumbo: %u\n"
+ "TX: %u\n",
+ ering->rx_pending,
+ ering->rx_mini_pending,
+ ering->rx_jumbo_pending,
+ ering->tx_pending);
+
+ fprintf(stdout, "\n");
+ return 0;
+}
+
+static int dump_channels(const struct ethtool_channels *echannels)
+{
+ fprintf(stdout,
+ "Pre-set maximums:\n"
+ "RX: %u\n"
+ "TX: %u\n"
+ "Other: %u\n"
+ "Combined: %u\n",
+ echannels->max_rx, echannels->max_tx,
+ echannels->max_other,
+ echannels->max_combined);
+
+ fprintf(stdout,
+ "Current hardware settings:\n"
+ "RX: %u\n"
+ "TX: %u\n"
+ "Other: %u\n"
+ "Combined: %u\n",
+ echannels->rx_count, echannels->tx_count,
+ echannels->other_count,
+ echannels->combined_count);
+
+ fprintf(stdout, "\n");
+ return 0;
+}
+
+static int dump_coalesce(const struct ethtool_coalesce *ecoal)
+{
+ fprintf(stdout, "Adaptive RX: %s TX: %s\n",
+ ecoal->use_adaptive_rx_coalesce ? "on" : "off",
+ ecoal->use_adaptive_tx_coalesce ? "on" : "off");
+
+ fprintf(stdout,
+ "stats-block-usecs: %u\n"
+ "sample-interval: %u\n"
+ "pkt-rate-low: %u\n"
+ "pkt-rate-high: %u\n"
+ "\n"
+ "rx-usecs: %u\n"
+ "rx-frames: %u\n"
+ "rx-usecs-irq: %u\n"
+ "rx-frames-irq: %u\n"
+ "\n"
+ "tx-usecs: %u\n"
+ "tx-frames: %u\n"
+ "tx-usecs-irq: %u\n"
+ "tx-frames-irq: %u\n"
+ "\n"
+ "rx-usecs-low: %u\n"
+ "rx-frames-low: %u\n"
+ "tx-usecs-low: %u\n"
+ "tx-frames-low: %u\n"
+ "\n"
+ "rx-usecs-high: %u\n"
+ "rx-frames-high: %u\n"
+ "tx-usecs-high: %u\n"
+ "tx-frames-high: %u\n"
+ "\n",
+ ecoal->stats_block_coalesce_usecs,
+ ecoal->rate_sample_interval,
+ ecoal->pkt_rate_low,
+ ecoal->pkt_rate_high,
+
+ ecoal->rx_coalesce_usecs,
+ ecoal->rx_max_coalesced_frames,
+ ecoal->rx_coalesce_usecs_irq,
+ ecoal->rx_max_coalesced_frames_irq,
+
+ ecoal->tx_coalesce_usecs,
+ ecoal->tx_max_coalesced_frames,
+ ecoal->tx_coalesce_usecs_irq,
+ ecoal->tx_max_coalesced_frames_irq,
+
+ ecoal->rx_coalesce_usecs_low,
+ ecoal->rx_max_coalesced_frames_low,
+ ecoal->tx_coalesce_usecs_low,
+ ecoal->tx_max_coalesced_frames_low,
+
+ ecoal->rx_coalesce_usecs_high,
+ ecoal->rx_max_coalesced_frames_high,
+ ecoal->tx_coalesce_usecs_high,
+ ecoal->tx_max_coalesced_frames_high);
+
+ return 0;
+}
+
+void dump_per_queue_coalesce(struct ethtool_per_queue_op *per_queue_opt,
+ __u32 *queue_mask, int n_queues)
+{
+ struct ethtool_coalesce *ecoal;
+ int i, idx = 0;
+
+ ecoal = (struct ethtool_coalesce *)(per_queue_opt + 1);
+ for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); i++) {
+ int queue = i * 32;
+ __u32 mask = queue_mask[i];
+
+ while (mask > 0) {
+ if (mask & 0x1) {
+ fprintf(stdout, "Queue: %d\n", queue);
+ dump_coalesce(ecoal + idx);
+ idx++;
+ }
+ mask = mask >> 1;
+ queue++;
+ }
+ if (idx == n_queues)
+ break;
+ }
+}
+
+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 unsigned int linux_version_code(void)
+{
+ struct utsname utsname;
+ unsigned version, patchlevel, sublevel = 0;
+
+ if (uname(&utsname))
+ return -1;
+ if (sscanf(utsname.release, "%u.%u.%u", &version, &patchlevel, &sublevel) < 2)
+ return -1;
+ return KERNEL_VERSION(version, patchlevel, sublevel);
+}
+
+static void dump_features(const struct feature_defs *defs,
+ const struct feature_state *state,
+ const struct feature_state *ref_state)
+{
+ unsigned int kernel_ver = linux_version_code();
+ unsigned int i, j;
+ int indent;
+ u32 value;
+
+ for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+ /* Don't show features whose state is unknown on this
+ * kernel version
+ */
+ if (defs->off_flag_matched[i] == 0 &&
+ ((off_flag_def[i].get_cmd == 0 &&
+ kernel_ver < off_flag_def[i].min_kernel_ver) ||
+ (off_flag_def[i].get_cmd == ETHTOOL_GUFO &&
+ kernel_ver >= KERNEL_VERSION(4, 14, 0))))
+ continue;
+
+ value = off_flag_def[i].value;
+
+ /* 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 != (int)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);
+ }
+ }
+
+ /* 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)
+{
+ switch (fhash & ~FLOW_RSS) {
+ case TCP_V4_FLOW:
+ fprintf(stdout, "TCP over IPV4 flows");
+ break;
+ case UDP_V4_FLOW:
+ fprintf(stdout, "UDP over IPV4 flows");
+ break;
+ case SCTP_V4_FLOW:
+ fprintf(stdout, "SCTP over IPV4 flows");
+ break;
+ case AH_ESP_V4_FLOW:
+ case AH_V4_FLOW:
+ case ESP_V4_FLOW:
+ fprintf(stdout, "IPSEC AH/ESP over IPV4 flows");
+ break;
+ case TCP_V6_FLOW:
+ fprintf(stdout, "TCP over IPV6 flows");
+ break;
+ case UDP_V6_FLOW:
+ fprintf(stdout, "UDP over IPV6 flows");
+ break;
+ case SCTP_V6_FLOW:
+ fprintf(stdout, "SCTP over IPV6 flows");
+ break;
+ case AH_ESP_V6_FLOW:
+ case AH_V6_FLOW:
+ case ESP_V6_FLOW:
+ fprintf(stdout, "IPSEC AH/ESP over IPV6 flows");
+ break;
+ default:
+ break;
+ }
+
+ if (val & RXH_DISCARD) {
+ fprintf(stdout, " - All matching flows discarded on RX\n");
+ return 0;
+ }
+ fprintf(stdout, " use these fields for computing Hash flow key:\n");
+
+ fprintf(stdout, "%s\n", unparse_rxfhashopts(val));
+
+ return 0;
+}
+
+static void dump_eeecmd(struct ethtool_eee *ep)
+{
+ ETHTOOL_DECLARE_LINK_MODE_MASK(link_mode);
+
+ fprintf(stdout, " EEE status: ");
+ if (!ep->supported) {
+ fprintf(stdout, "not supported\n");
+ return;
+ } else if (!ep->eee_enabled) {
+ fprintf(stdout, "disabled\n");
+ } else {
+ fprintf(stdout, "enabled - ");
+ if (ep->eee_active)
+ fprintf(stdout, "active\n");
+ else
+ fprintf(stdout, "inactive\n");
+ }
+
+ fprintf(stdout, " Tx LPI:");
+ if (ep->tx_lpi_enabled)
+ fprintf(stdout, " %d (us)\n", ep->tx_lpi_timer);
+ else
+ fprintf(stdout, " disabled\n");
+
+ ethtool_link_mode_zero(link_mode);
+
+ link_mode[0] = ep->supported;
+ dump_link_caps("Supported EEE", "", link_mode, 1);
+
+ link_mode[0] = ep->advertised;
+ dump_link_caps("Advertised EEE", "", link_mode, 1);
+
+ link_mode[0] = ep->lp_advertised;
+ dump_link_caps("Link partner advertised EEE", "", link_mode, 1);
+}
+
+static void dump_fec(u32 fec)
+{
+ if (fec & ETHTOOL_FEC_NONE)
+ fprintf(stdout, " None");
+ if (fec & ETHTOOL_FEC_AUTO)
+ fprintf(stdout, " Auto");
+ if (fec & ETHTOOL_FEC_OFF)
+ fprintf(stdout, " Off");
+ if (fec & ETHTOOL_FEC_BASER)
+ fprintf(stdout, " BaseR");
+ if (fec & ETHTOOL_FEC_RS)
+ fprintf(stdout, " RS");
+ if (fec & ETHTOOL_FEC_LLRS)
+ fprintf(stdout, " LLRS");
+}
+
+#define N_SOTS 7
+
+static char *so_timestamping_labels[N_SOTS] = {
+ "hardware-transmit (SOF_TIMESTAMPING_TX_HARDWARE)",
+ "software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)",
+ "hardware-receive (SOF_TIMESTAMPING_RX_HARDWARE)",
+ "software-receive (SOF_TIMESTAMPING_RX_SOFTWARE)",
+ "software-system-clock (SOF_TIMESTAMPING_SOFTWARE)",
+ "hardware-legacy-clock (SOF_TIMESTAMPING_SYS_HARDWARE)",
+ "hardware-raw-clock (SOF_TIMESTAMPING_RAW_HARDWARE)",
+};
+
+#define N_TX_TYPES (HWTSTAMP_TX_ONESTEP_SYNC + 1)
+
+static char *tx_type_labels[N_TX_TYPES] = {
+ "off (HWTSTAMP_TX_OFF)",
+ "on (HWTSTAMP_TX_ON)",
+ "one-step-sync (HWTSTAMP_TX_ONESTEP_SYNC)",
+};
+
+#define N_RX_FILTERS (HWTSTAMP_FILTER_NTP_ALL + 1)
+
+static char *rx_filter_labels[N_RX_FILTERS] = {
+ "none (HWTSTAMP_FILTER_NONE)",
+ "all (HWTSTAMP_FILTER_ALL)",
+ "some (HWTSTAMP_FILTER_SOME)",
+ "ptpv1-l4-event (HWTSTAMP_FILTER_PTP_V1_L4_EVENT)",
+ "ptpv1-l4-sync (HWTSTAMP_FILTER_PTP_V1_L4_SYNC)",
+ "ptpv1-l4-delay-req (HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ)",
+ "ptpv2-l4-event (HWTSTAMP_FILTER_PTP_V2_L4_EVENT)",
+ "ptpv2-l4-sync (HWTSTAMP_FILTER_PTP_V2_L4_SYNC)",
+ "ptpv2-l4-delay-req (HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ)",
+ "ptpv2-l2-event (HWTSTAMP_FILTER_PTP_V2_L2_EVENT)",
+ "ptpv2-l2-sync (HWTSTAMP_FILTER_PTP_V2_L2_SYNC)",
+ "ptpv2-l2-delay-req (HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ)",
+ "ptpv2-event (HWTSTAMP_FILTER_PTP_V2_EVENT)",
+ "ptpv2-sync (HWTSTAMP_FILTER_PTP_V2_SYNC)",
+ "ptpv2-delay-req (HWTSTAMP_FILTER_PTP_V2_DELAY_REQ)",
+ "ntp-all (HWTSTAMP_FILTER_NTP_ALL)",
+};
+
+static int dump_tsinfo(const struct ethtool_ts_info *info)
+{
+ int i;
+
+ fprintf(stdout, "Capabilities:\n");
+
+ for (i = 0; i < N_SOTS; i++) {
+ if (info->so_timestamping & (1 << i))
+ fprintf(stdout, "\t%s\n", so_timestamping_labels[i]);
+ }
+
+ fprintf(stdout, "PTP Hardware Clock: ");
+
+ if (info->phc_index < 0)
+ fprintf(stdout, "none\n");
+ else
+ fprintf(stdout, "%d\n", info->phc_index);
+
+ fprintf(stdout, "Hardware Transmit Timestamp Modes:");
+
+ if (!info->tx_types)
+ fprintf(stdout, " none\n");
+ else
+ fprintf(stdout, "\n");
+
+ for (i = 0; i < N_TX_TYPES; i++) {
+ if (info->tx_types & (1 << i))
+ fprintf(stdout, "\t%s\n", tx_type_labels[i]);
+ }
+
+ fprintf(stdout, "Hardware Receive Filter Modes:");
+
+ if (!info->rx_filters)
+ fprintf(stdout, " none\n");
+ else
+ fprintf(stdout, "\n");
+
+ for (i = 0; i < N_RX_FILTERS; i++) {
+ if (info->rx_filters & (1 << i))
+ fprintf(stdout, "\t%s\n", rx_filter_labels[i]);
+ }
+
+ return 0;
+}
+
+static struct ethtool_gstrings *
+get_stringset(struct cmd_context *ctx, enum ethtool_stringset set_id,
+ ptrdiff_t drvinfo_offset, int null_terminate)
+{
+ struct {
+ struct ethtool_sset_info hdr;
+ u32 buf[1];
+ } sset_info;
+ struct ethtool_drvinfo drvinfo;
+ u32 len, i;
+ struct ethtool_gstrings *strings;
+
+ sset_info.hdr.cmd = ETHTOOL_GSSET_INFO;
+ sset_info.hdr.reserved = 0;
+ sset_info.hdr.sset_mask = 1ULL << set_id;
+ if (send_ioctl(ctx, &sset_info) == 0) {
+ const u32 *sset_lengths = sset_info.hdr.data;
+
+ len = sset_info.hdr.sset_mask ? sset_lengths[0] : 0;
+ } else if (errno == EOPNOTSUPP && drvinfo_offset != 0) {
+ /* Fallback for old kernel versions */
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ if (send_ioctl(ctx, &drvinfo))
+ return NULL;
+ len = *(u32 *)((char *)&drvinfo + drvinfo_offset);
+ } else {
+ return NULL;
+ }
+
+ strings = calloc(1, sizeof(*strings) + len * ETH_GSTRING_LEN);
+ if (!strings)
+ return NULL;
+
+ strings->cmd = ETHTOOL_GSTRINGS;
+ strings->string_set = set_id;
+ strings->len = len;
+ if (len != 0 && send_ioctl(ctx, strings)) {
+ free(strings);
+ return NULL;
+ }
+
+ if (null_terminate)
+ for (i = 0; i < len; i++)
+ strings->data[(i + 1) * ETH_GSTRING_LEN - 1] = 0;
+
+ return strings;
+}
+
+static struct feature_defs *get_feature_defs(struct cmd_context *ctx)
+{
+ struct ethtool_gstrings *names;
+ struct feature_defs *defs;
+ unsigned int i, j;
+ u32 n_features;
+
+ names = get_stringset(ctx, ETH_SS_FEATURES, 0, 1);
+ 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 if (errno == EPERM) {
+ /* Kernel bug: ETHTOOL_GSSET_INFO was privileged.
+ * Work around it. */
+ n_features = 0;
+ } else {
+ return NULL;
+ }
+
+ defs = malloc(sizeof(*defs) + sizeof(defs->def[0]) * n_features);
+ if (!defs) {
+ free(names);
+ 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 < OFF_FLAG_DEF_SIZE &&
+ 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;
+ struct ethtool_drvinfo drvinfo;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ err = send_ioctl(ctx, &drvinfo);
+ if (err < 0) {
+ perror("Cannot get driver information");
+ return 71;
+ }
+ return dump_drvinfo(&drvinfo);
+}
+
+static int do_gpause(struct cmd_context *ctx)
+{
+ struct ethtool_pauseparam epause;
+ struct ethtool_cmd ecmd;
+ int err;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ fprintf(stdout, "Pause parameters for %s:\n", ctx->devname);
+
+ epause.cmd = ETHTOOL_GPAUSEPARAM;
+ err = send_ioctl(ctx, &epause);
+ if (err) {
+ perror("Cannot get device pause settings");
+ return 76;
+ }
+
+ if (epause.autoneg) {
+ ecmd.cmd = ETHTOOL_GSET;
+ err = send_ioctl(ctx, &ecmd);
+ if (err) {
+ perror("Cannot get device settings");
+ return 1;
+ }
+ dump_pause(&epause, ecmd.advertising, ecmd.lp_advertising);
+ } else {
+ dump_pause(&epause, 0, 0);
+ }
+
+ return 0;
+}
+
+static void do_generic_set1(struct cmdline_info *info, int *changed_out)
+{
+ int wanted, *v1, *v2;
+
+ v1 = info->wanted_val;
+ wanted = *v1;
+
+ if (wanted < 0)
+ return;
+
+ v2 = info->ioctl_val;
+ if (wanted == *v2) {
+ fprintf(stderr, "%s unmodified, ignoring\n", info->name);
+ } else {
+ *v2 = wanted;
+ *changed_out = 1;
+ }
+}
+
+static void do_generic_set(struct cmdline_info *info,
+ unsigned int n_info,
+ int *changed_out)
+{
+ unsigned int i;
+
+ for (i = 0; i < n_info; i++)
+ do_generic_set1(&info[i], changed_out);
+}
+
+static int do_spause(struct cmd_context *ctx)
+{
+ struct ethtool_pauseparam epause;
+ int gpause_changed = 0;
+ int pause_autoneg_wanted = -1;
+ int pause_rx_wanted = -1;
+ int pause_tx_wanted = -1;
+ struct cmdline_info cmdline_pause[] = {
+ {
+ .name = "autoneg",
+ .type = CMDL_BOOL,
+ .wanted_val = &pause_autoneg_wanted,
+ .ioctl_val = &epause.autoneg,
+ },
+ {
+ .name = "rx",
+ .type = CMDL_BOOL,
+ .wanted_val = &pause_rx_wanted,
+ .ioctl_val = &epause.rx_pause,
+ },
+ {
+ .name = "tx",
+ .type = CMDL_BOOL,
+ .wanted_val = &pause_tx_wanted,
+ .ioctl_val = &epause.tx_pause,
+ },
+ };
+ int err, changed = 0;
+
+ parse_generic_cmdline(ctx, &gpause_changed,
+ cmdline_pause, ARRAY_SIZE(cmdline_pause));
+
+ epause.cmd = ETHTOOL_GPAUSEPARAM;
+ err = send_ioctl(ctx, &epause);
+ if (err) {
+ perror("Cannot get device pause settings");
+ return 77;
+ }
+
+ do_generic_set(cmdline_pause, ARRAY_SIZE(cmdline_pause), &changed);
+
+ if (!changed) {
+ fprintf(stderr, "no pause parameters changed, aborting\n");
+ return 78;
+ }
+
+ epause.cmd = ETHTOOL_SPAUSEPARAM;
+ err = send_ioctl(ctx, &epause);
+ if (err) {
+ perror("Cannot set device pause parameters");
+ return 79;
+ }
+
+ return 0;
+}
+
+static int do_sring(struct cmd_context *ctx)
+{
+ struct ethtool_ringparam ering;
+ int gring_changed = 0;
+ s32 ring_rx_wanted = -1;
+ s32 ring_rx_mini_wanted = -1;
+ s32 ring_rx_jumbo_wanted = -1;
+ s32 ring_tx_wanted = -1;
+ struct cmdline_info cmdline_ring[] = {
+ {
+ .name = "rx",
+ .type = CMDL_S32,
+ .wanted_val = &ring_rx_wanted,
+ .ioctl_val = &ering.rx_pending,
+ },
+ {
+ .name = "rx-mini",
+ .type = CMDL_S32,
+ .wanted_val = &ring_rx_mini_wanted,
+ .ioctl_val = &ering.rx_mini_pending,
+ },
+ {
+ .name = "rx-jumbo",
+ .type = CMDL_S32,
+ .wanted_val = &ring_rx_jumbo_wanted,
+ .ioctl_val = &ering.rx_jumbo_pending,
+ },
+ {
+ .name = "tx",
+ .type = CMDL_S32,
+ .wanted_val = &ring_tx_wanted,
+ .ioctl_val = &ering.tx_pending,
+ },
+ };
+ int err, changed = 0;
+
+ parse_generic_cmdline(ctx, &gring_changed,
+ cmdline_ring, ARRAY_SIZE(cmdline_ring));
+
+ ering.cmd = ETHTOOL_GRINGPARAM;
+ err = send_ioctl(ctx, &ering);
+ if (err) {
+ perror("Cannot get device ring settings");
+ return 76;
+ }
+
+ do_generic_set(cmdline_ring, ARRAY_SIZE(cmdline_ring), &changed);
+
+ if (!changed) {
+ fprintf(stderr, "no ring parameters changed, aborting\n");
+ return 80;
+ }
+
+ ering.cmd = ETHTOOL_SRINGPARAM;
+ err = send_ioctl(ctx, &ering);
+ if (err) {
+ perror("Cannot set device ring parameters");
+ return 81;
+ }
+
+ return 0;
+}
+
+static int do_gring(struct cmd_context *ctx)
+{
+ struct ethtool_ringparam ering;
+ int err;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ fprintf(stdout, "Ring parameters for %s:\n", ctx->devname);
+
+ ering.cmd = ETHTOOL_GRINGPARAM;
+ err = send_ioctl(ctx, &ering);
+ if (err == 0) {
+ err = dump_ring(&ering);
+ if (err)
+ return err;
+ } else {
+ perror("Cannot get device ring settings");
+ return 76;
+ }
+
+ return 0;
+}
+
+static int do_schannels(struct cmd_context *ctx)
+{
+ struct ethtool_channels echannels;
+ int gchannels_changed;
+ s32 channels_rx_wanted = -1;
+ s32 channels_tx_wanted = -1;
+ s32 channels_other_wanted = -1;
+ s32 channels_combined_wanted = -1;
+ struct cmdline_info cmdline_channels[] = {
+ {
+ .name = "rx",
+ .type = CMDL_S32,
+ .wanted_val = &channels_rx_wanted,
+ .ioctl_val = &echannels.rx_count,
+ },
+ {
+ .name = "tx",
+ .type = CMDL_S32,
+ .wanted_val = &channels_tx_wanted,
+ .ioctl_val = &echannels.tx_count,
+ },
+ {
+ .name = "other",
+ .type = CMDL_S32,
+ .wanted_val = &channels_other_wanted,
+ .ioctl_val = &echannels.other_count,
+ },
+ {
+ .name = "combined",
+ .type = CMDL_S32,
+ .wanted_val = &channels_combined_wanted,
+ .ioctl_val = &echannels.combined_count,
+ },
+ };
+ int err, changed = 0;
+
+ parse_generic_cmdline(ctx, &gchannels_changed,
+ cmdline_channels, ARRAY_SIZE(cmdline_channels));
+
+ echannels.cmd = ETHTOOL_GCHANNELS;
+ err = send_ioctl(ctx, &echannels);
+ if (err) {
+ perror("Cannot get device channel parameters");
+ return 1;
+ }
+
+ do_generic_set(cmdline_channels, ARRAY_SIZE(cmdline_channels),
+ &changed);
+
+ if (!changed) {
+ fprintf(stderr, "no channel parameters changed.\n");
+ fprintf(stderr, "current values: rx %u tx %u other %u"
+ " combined %u\n", echannels.rx_count,
+ echannels.tx_count, echannels.other_count,
+ echannels.combined_count);
+ return 0;
+ }
+
+ echannels.cmd = ETHTOOL_SCHANNELS;
+ err = send_ioctl(ctx, &echannels);
+ if (err) {
+ perror("Cannot set device channel parameters");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int do_gchannels(struct cmd_context *ctx)
+{
+ struct ethtool_channels echannels;
+ int err;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ fprintf(stdout, "Channel parameters for %s:\n", ctx->devname);
+
+ echannels.cmd = ETHTOOL_GCHANNELS;
+ err = send_ioctl(ctx, &echannels);
+ if (err == 0) {
+ err = dump_channels(&echannels);
+ if (err)
+ return err;
+ } else {
+ perror("Cannot get device channel parameters");
+ return 1;
+ }
+ return 0;
+
+}
+
+static int do_gcoalesce(struct cmd_context *ctx)
+{
+ struct ethtool_coalesce ecoal = {};
+ int err;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ fprintf(stdout, "Coalesce parameters for %s:\n", ctx->devname);
+
+ ecoal.cmd = ETHTOOL_GCOALESCE;
+ err = send_ioctl(ctx, &ecoal);
+ if (err == 0) {
+ err = dump_coalesce(&ecoal);
+ if (err)
+ return err;
+ } else {
+ perror("Cannot get device coalesce settings");
+ return 82;
+ }
+
+ return 0;
+}
+
+#define DECLARE_COALESCE_OPTION_VARS() \
+ s32 coal_stats_wanted = -1; \
+ int coal_adaptive_rx_wanted = -1; \
+ int coal_adaptive_tx_wanted = -1; \
+ s32 coal_sample_rate_wanted = -1; \
+ s32 coal_pkt_rate_low_wanted = -1; \
+ s32 coal_pkt_rate_high_wanted = -1; \
+ s32 coal_rx_usec_wanted = -1; \
+ s32 coal_rx_frames_wanted = -1; \
+ s32 coal_rx_usec_irq_wanted = -1; \
+ s32 coal_rx_frames_irq_wanted = -1; \
+ s32 coal_tx_usec_wanted = -1; \
+ s32 coal_tx_frames_wanted = -1; \
+ s32 coal_tx_usec_irq_wanted = -1; \
+ s32 coal_tx_frames_irq_wanted = -1; \
+ s32 coal_rx_usec_low_wanted = -1; \
+ s32 coal_rx_frames_low_wanted = -1; \
+ s32 coal_tx_usec_low_wanted = -1; \
+ s32 coal_tx_frames_low_wanted = -1; \
+ s32 coal_rx_usec_high_wanted = -1; \
+ s32 coal_rx_frames_high_wanted = -1; \
+ s32 coal_tx_usec_high_wanted = -1; \
+ s32 coal_tx_frames_high_wanted = -1
+
+#define COALESCE_CMDLINE_INFO(__ecoal) \
+{ \
+ { \
+ .name = "adaptive-rx", \
+ .type = CMDL_BOOL, \
+ .wanted_val = &coal_adaptive_rx_wanted, \
+ .ioctl_val = &__ecoal.use_adaptive_rx_coalesce, \
+ }, \
+ { \
+ .name = "adaptive-tx", \
+ .type = CMDL_BOOL, \
+ .wanted_val = &coal_adaptive_tx_wanted, \
+ .ioctl_val = &__ecoal.use_adaptive_tx_coalesce, \
+ }, \
+ { \
+ .name = "sample-interval", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_sample_rate_wanted, \
+ .ioctl_val = &__ecoal.rate_sample_interval, \
+ }, \
+ { \
+ .name = "stats-block-usecs", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_stats_wanted, \
+ .ioctl_val = &__ecoal.stats_block_coalesce_usecs, \
+ }, \
+ { \
+ .name = "pkt-rate-low", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_pkt_rate_low_wanted, \
+ .ioctl_val = &__ecoal.pkt_rate_low, \
+ }, \
+ { \
+ .name = "pkt-rate-high", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_pkt_rate_high_wanted, \
+ .ioctl_val = &__ecoal.pkt_rate_high, \
+ }, \
+ { \
+ .name = "rx-usecs", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_usec_wanted, \
+ .ioctl_val = &__ecoal.rx_coalesce_usecs, \
+ }, \
+ { \
+ .name = "rx-frames", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_frames_wanted, \
+ .ioctl_val = &__ecoal.rx_max_coalesced_frames, \
+ }, \
+ { \
+ .name = "rx-usecs-irq", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_usec_irq_wanted, \
+ .ioctl_val = &__ecoal.rx_coalesce_usecs_irq, \
+ }, \
+ { \
+ .name = "rx-frames-irq", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_frames_irq_wanted, \
+ .ioctl_val = &__ecoal.rx_max_coalesced_frames_irq, \
+ }, \
+ { \
+ .name = "tx-usecs", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_usec_wanted, \
+ .ioctl_val = &__ecoal.tx_coalesce_usecs, \
+ }, \
+ { \
+ .name = "tx-frames", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_frames_wanted, \
+ .ioctl_val = &__ecoal.tx_max_coalesced_frames, \
+ }, \
+ { \
+ .name = "tx-usecs-irq", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_usec_irq_wanted, \
+ .ioctl_val = &__ecoal.tx_coalesce_usecs_irq, \
+ }, \
+ { \
+ .name = "tx-frames-irq", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_frames_irq_wanted, \
+ .ioctl_val = &__ecoal.tx_max_coalesced_frames_irq, \
+ }, \
+ { \
+ .name = "rx-usecs-low", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_usec_low_wanted, \
+ .ioctl_val = &__ecoal.rx_coalesce_usecs_low, \
+ }, \
+ { \
+ .name = "rx-frames-low", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_frames_low_wanted, \
+ .ioctl_val = &__ecoal.rx_max_coalesced_frames_low, \
+ }, \
+ { \
+ .name = "tx-usecs-low", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_usec_low_wanted, \
+ .ioctl_val = &__ecoal.tx_coalesce_usecs_low, \
+ }, \
+ { \
+ .name = "tx-frames-low", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_frames_low_wanted, \
+ .ioctl_val = &__ecoal.tx_max_coalesced_frames_low, \
+ }, \
+ { \
+ .name = "rx-usecs-high", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_usec_high_wanted, \
+ .ioctl_val = &__ecoal.rx_coalesce_usecs_high, \
+ }, \
+ { \
+ .name = "rx-frames-high", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_rx_frames_high_wanted, \
+ .ioctl_val = &__ecoal.rx_max_coalesced_frames_high,\
+ }, \
+ { \
+ .name = "tx-usecs-high", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_usec_high_wanted, \
+ .ioctl_val = &__ecoal.tx_coalesce_usecs_high, \
+ }, \
+ { \
+ .name = "tx-frames-high", \
+ .type = CMDL_S32, \
+ .wanted_val = &coal_tx_frames_high_wanted, \
+ .ioctl_val = &__ecoal.tx_max_coalesced_frames_high,\
+ }, \
+}
+
+static int do_scoalesce(struct cmd_context *ctx)
+{
+ struct ethtool_coalesce ecoal;
+ int gcoalesce_changed = 0;
+ DECLARE_COALESCE_OPTION_VARS();
+ struct cmdline_info cmdline_coalesce[] = COALESCE_CMDLINE_INFO(ecoal);
+ int err, changed = 0;
+
+ parse_generic_cmdline(ctx, &gcoalesce_changed,
+ cmdline_coalesce, ARRAY_SIZE(cmdline_coalesce));
+
+ ecoal.cmd = ETHTOOL_GCOALESCE;
+ err = send_ioctl(ctx, &ecoal);
+ if (err) {
+ perror("Cannot get device coalesce settings");
+ return 76;
+ }
+
+ do_generic_set(cmdline_coalesce, ARRAY_SIZE(cmdline_coalesce),
+ &changed);
+
+ if (!changed) {
+ fprintf(stderr, "no coalesce parameters changed, aborting\n");
+ return 80;
+ }
+
+ ecoal.cmd = ETHTOOL_SCOALESCE;
+ err = send_ioctl(ctx, &ecoal);
+ if (err) {
+ perror("Cannot set device coalesce parameters");
+ return 81;
+ }
+
+ return 0;
+}
+
+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;
+
+ 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 < OFF_FLAG_DEF_SIZE; i++) {
+ value = off_flag_def[i].value;
+ if (!off_flag_def[i].get_cmd)
+ continue;
+ eval.cmd = off_flag_def[i].get_cmd;
+ err = send_ioctl(ctx, &eval);
+ if (err) {
+ if (errno == EOPNOTSUPP &&
+ off_flag_def[i].get_cmd == ETHTOOL_GUFO)
+ continue;
+
+ fprintf(stderr,
+ "Cannot get device %s settings: %m\n",
+ off_flag_def[i].long_name);
+ } else {
+ if (eval.data)
+ state->off_flags |= value;
+ allfail = 0;
+ }
+ }
+
+ eval.cmd = ETHTOOL_GFLAGS;
+ err = send_ioctl(ctx, &eval);
+ if (err) {
+ perror("Cannot get device flags");
+ } else {
+ state->off_flags |= eval.data & ETH_FLAG_EXT_MASK;
+ allfail = 0;
+ }
+
+ 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_gfeatures(struct cmd_context *ctx)
+{
+ struct feature_defs *defs;
+ struct feature_state *features;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ defs = get_feature_defs(ctx);
+ if (!defs) {
+ perror("Cannot get device feature names");
+ return 1;
+ }
+
+ fprintf(stdout, "Features for %s:\n", ctx->devname);
+
+ features = get_features(ctx, defs);
+ if (!features) {
+ fprintf(stdout, "no feature info available\n");
+ free(defs);
+ return 1;
+ }
+
+ dump_features(defs, features, NULL);
+ free(features);
+ free(defs);
+ return 0;
+}
+
+static int do_sfeatures(struct cmd_context *ctx)
+{
+ struct feature_defs *defs;
+ int any_changed = 0, any_mismatch = 0;
+ u32 off_flags_wanted = 0;
+ u32 off_flags_mask = 0;
+ struct ethtool_sfeatures *efeatures = NULL;
+ struct feature_state *old_state = NULL;
+ struct feature_state *new_state = NULL;
+ struct cmdline_info *cmdline_features;
+ struct ethtool_value eval;
+ unsigned int i, j;
+ int err, rc;
+
+ defs = get_feature_defs(ctx);
+ if (!defs) {
+ perror("Cannot get device feature names");
+ 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");
+ rc = 1;
+ goto err;
+ }
+ 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]));
+ }
+
+ /* Generate cmdline_info for legacy flags and kernel-named
+ * features, and parse our arguments.
+ */
+ cmdline_features = calloc(2 * OFF_FLAG_DEF_SIZE + defs->n_features,
+ sizeof(cmdline_features[0]));
+ if (!cmdline_features) {
+ perror("Cannot parse arguments");
+ rc = 1;
+ goto err;
+ }
+ j = 0;
+ for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+ flag_to_cmdline_info(off_flag_def[i].short_name,
+ off_flag_def[i].value,
+ &off_flags_wanted, &off_flags_mask,
+ &cmdline_features[j++]);
+ flag_to_cmdline_info(off_flag_def[i].long_name,
+ off_flag_def[i].value,
+ &off_flags_wanted, &off_flags_mask,
+ &cmdline_features[j++]);
+ }
+ 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[j++]);
+ parse_generic_cmdline(ctx, &any_changed, cmdline_features,
+ 2 * OFF_FLAG_DEF_SIZE + defs->n_features);
+ free(cmdline_features);
+
+ if (!any_changed) {
+ fprintf(stdout, "no features changed\n");
+ rc = 0;
+ goto err;
+ }
+
+ old_state = get_features(ctx, defs);
+ if (!old_state) {
+ rc = 1;
+ goto err;
+ }
+
+ if (efeatures) {
+ /* 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 < OFF_FLAG_DEF_SIZE; i++) {
+ int fixed = 1;
+
+ if (!(off_flags_mask & off_flag_def[i].value))
+ continue;
+
+ for (j = 0; j < defs->n_features; j++) {
+ if (defs->def[j].off_flag_index != (int)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);
+ }
+
+ err = send_ioctl(ctx, efeatures);
+ if (err < 0) {
+ perror("Cannot set device feature settings");
+ rc = 1;
+ goto err;
+ }
+ } else {
+ for (i = 0; i < OFF_FLAG_DEF_SIZE; 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);
+ rc = 1;
+ goto err;
+ }
+ }
+ }
+
+ 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");
+ rc = 92;
+ goto err;
+ }
+ }
+ }
+
+ /* Compare new state with requested state */
+ new_state = get_features(ctx, defs);
+ if (!new_state) {
+ rc = 1;
+ goto err;
+ }
+ 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 features\n");
+ rc = 1;
+ goto err;
+ }
+ printf("Actual changes:\n");
+ dump_features(defs, new_state, old_state);
+ }
+
+ rc = 0;
+
+err:
+ free(new_state);
+ free(old_state);
+ free(defs);
+ free(efeatures);
+
+ return rc;
+}
+
+static struct ethtool_link_usettings *
+do_ioctl_glinksettings(struct cmd_context *ctx)
+{
+ int err;
+ struct {
+ struct ethtool_link_settings req;
+ __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+ } ecmd;
+ struct ethtool_link_usettings *link_usettings;
+ unsigned int u32_offs;
+
+ /* Handshake with kernel to determine number of words for link
+ * mode bitmaps. When requested number of bitmap words is not
+ * the one expected by kernel, the latter returns the integer
+ * opposite of what it is expecting. We request length 0 below
+ * (aka. invalid bitmap length) to get this info.
+ */
+ memset(&ecmd, 0, sizeof(ecmd));
+ ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
+ err = send_ioctl(ctx, &ecmd);
+ if (err < 0)
+ return NULL;
+
+ /* see above: we expect a strictly negative value from kernel.
+ */
+ if (ecmd.req.link_mode_masks_nwords >= 0
+ || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
+ return NULL;
+
+ /* got the real ecmd.req.link_mode_masks_nwords,
+ * now send the real request
+ */
+ ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
+ ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
+ err = send_ioctl(ctx, &ecmd);
+ if (err < 0)
+ return NULL;
+
+ if (ecmd.req.link_mode_masks_nwords <= 0
+ || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
+ return NULL;
+
+ /* Convert to usettings struct */
+ link_usettings = calloc(1, sizeof(*link_usettings));
+ if (link_usettings == NULL)
+ return NULL;
+
+ memcpy(&link_usettings->base, &ecmd.req, sizeof(link_usettings->base));
+ link_usettings->deprecated.transceiver = ecmd.req.transceiver;
+
+ /* copy link mode bitmaps */
+ u32_offs = 0;
+ memcpy(link_usettings->link_modes.supported,
+ &ecmd.link_mode_data[u32_offs],
+ 4 * ecmd.req.link_mode_masks_nwords);
+
+ u32_offs += ecmd.req.link_mode_masks_nwords;
+ memcpy(link_usettings->link_modes.advertising,
+ &ecmd.link_mode_data[u32_offs],
+ 4 * ecmd.req.link_mode_masks_nwords);
+
+ u32_offs += ecmd.req.link_mode_masks_nwords;
+ memcpy(link_usettings->link_modes.lp_advertising,
+ &ecmd.link_mode_data[u32_offs],
+ 4 * ecmd.req.link_mode_masks_nwords);
+
+ return link_usettings;
+}
+
+static int
+do_ioctl_slinksettings(struct cmd_context *ctx,
+ const struct ethtool_link_usettings *link_usettings)
+{
+ struct {
+ struct ethtool_link_settings req;
+ __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+ } ecmd;
+ unsigned int u32_offs;
+
+ /* refuse to send ETHTOOL_SLINKSETTINGS ioctl if
+ * link_usettings was retrieved with ETHTOOL_GSET
+ */
+ if (link_usettings->base.cmd != ETHTOOL_GLINKSETTINGS)
+ return -1;
+
+ /* refuse to send ETHTOOL_SLINKSETTINGS ioctl if deprecated fields
+ * were set
+ */
+ if (link_usettings->deprecated.transceiver)
+ return -1;
+
+ if (link_usettings->base.link_mode_masks_nwords <= 0)
+ return -1;
+
+ memcpy(&ecmd.req, &link_usettings->base, sizeof(ecmd.req));
+ ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
+
+ /* copy link mode bitmaps */
+ u32_offs = 0;
+ memcpy(&ecmd.link_mode_data[u32_offs],
+ link_usettings->link_modes.supported,
+ 4 * ecmd.req.link_mode_masks_nwords);
+
+ u32_offs += ecmd.req.link_mode_masks_nwords;
+ memcpy(&ecmd.link_mode_data[u32_offs],
+ link_usettings->link_modes.advertising,
+ 4 * ecmd.req.link_mode_masks_nwords);
+
+ u32_offs += ecmd.req.link_mode_masks_nwords;
+ memcpy(&ecmd.link_mode_data[u32_offs],
+ link_usettings->link_modes.lp_advertising,
+ 4 * ecmd.req.link_mode_masks_nwords);
+
+ return send_ioctl(ctx, &ecmd);
+}
+
+static struct ethtool_link_usettings *
+do_ioctl_gset(struct cmd_context *ctx)
+{
+ int err;
+ struct ethtool_cmd ecmd;
+ struct ethtool_link_usettings *link_usettings;
+
+ memset(&ecmd, 0, sizeof(ecmd));
+ ecmd.cmd = ETHTOOL_GSET;
+ err = send_ioctl(ctx, &ecmd);
+ if (err < 0)
+ return NULL;
+
+ link_usettings = calloc(1, sizeof(*link_usettings));
+ if (link_usettings == NULL)
+ return NULL;
+
+ /* remember that ETHTOOL_GSET was used */
+ link_usettings->base.cmd = ETHTOOL_GSET;
+
+ link_usettings->base.link_mode_masks_nwords = 1;
+ link_usettings->link_modes.supported[0] = ecmd.supported;
+ link_usettings->link_modes.advertising[0] = ecmd.advertising;
+ link_usettings->link_modes.lp_advertising[0] = ecmd.lp_advertising;
+ link_usettings->base.speed = ethtool_cmd_speed(&ecmd);
+ link_usettings->base.duplex = ecmd.duplex;
+ link_usettings->base.port = ecmd.port;
+ link_usettings->base.phy_address = ecmd.phy_address;
+ link_usettings->deprecated.transceiver = ecmd.transceiver;
+ link_usettings->base.autoneg = ecmd.autoneg;
+ link_usettings->base.mdio_support = ecmd.mdio_support;
+ /* ignored (fully deprecated): maxrxpkt, maxtxpkt */
+ link_usettings->base.eth_tp_mdix = ecmd.eth_tp_mdix;
+ link_usettings->base.eth_tp_mdix_ctrl = ecmd.eth_tp_mdix_ctrl;
+
+ return link_usettings;
+}
+
+static bool ethtool_link_mode_is_backward_compatible(const u32 *mask)
+{
+ unsigned int i;
+
+ for (i = 1; i < ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32; ++i)
+ if (mask[i])
+ return false;
+
+ return true;
+}
+
+static int
+do_ioctl_sset(struct cmd_context *ctx,
+ const struct ethtool_link_usettings *link_usettings)
+{
+ struct ethtool_cmd ecmd;
+
+ /* refuse to send ETHTOOL_SSET ioctl if link_usettings was
+ * retrieved with ETHTOOL_GLINKSETTINGS
+ */
+ if (link_usettings->base.cmd != ETHTOOL_GSET)
+ return -1;
+
+ if (link_usettings->base.link_mode_masks_nwords <= 0)
+ return -1;
+
+ /* refuse to sset if any bit > 31 is set */
+ if (!ethtool_link_mode_is_backward_compatible(
+ link_usettings->link_modes.supported))
+ return -1;
+ if (!ethtool_link_mode_is_backward_compatible(
+ link_usettings->link_modes.advertising))
+ return -1;
+ if (!ethtool_link_mode_is_backward_compatible(
+ link_usettings->link_modes.lp_advertising))
+ return -1;
+
+ memset(&ecmd, 0, sizeof(ecmd));
+ ecmd.cmd = ETHTOOL_SSET;
+
+ ecmd.supported = link_usettings->link_modes.supported[0];
+ ecmd.advertising = link_usettings->link_modes.advertising[0];
+ ecmd.lp_advertising = link_usettings->link_modes.lp_advertising[0];
+ ethtool_cmd_speed_set(&ecmd, link_usettings->base.speed);
+ ecmd.duplex = link_usettings->base.duplex;
+ ecmd.port = link_usettings->base.port;
+ ecmd.phy_address = link_usettings->base.phy_address;
+ ecmd.transceiver = link_usettings->deprecated.transceiver;
+ ecmd.autoneg = link_usettings->base.autoneg;
+ ecmd.mdio_support = link_usettings->base.mdio_support;
+ /* ignored (fully deprecated): maxrxpkt, maxtxpkt */
+ ecmd.eth_tp_mdix = link_usettings->base.eth_tp_mdix;
+ ecmd.eth_tp_mdix_ctrl = link_usettings->base.eth_tp_mdix_ctrl;
+ return send_ioctl(ctx, &ecmd);
+}
+
+static int do_gset(struct cmd_context *ctx)
+{
+ int err;
+ struct ethtool_link_usettings *link_usettings;
+ struct ethtool_wolinfo wolinfo;
+ struct ethtool_value edata;
+ int allfail = 1;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ fprintf(stdout, "Settings for %s:\n", ctx->devname);
+
+ link_usettings = do_ioctl_glinksettings(ctx);
+ if (link_usettings == NULL)
+ link_usettings = do_ioctl_gset(ctx);
+ if (link_usettings != NULL) {
+ err = dump_link_usettings(link_usettings);
+ free(link_usettings);
+ if (err)
+ return err;
+ allfail = 0;
+ } else if (errno != EOPNOTSUPP) {
+ perror("Cannot get device settings");
+ }
+
+ wolinfo.cmd = ETHTOOL_GWOL;
+ err = send_ioctl(ctx, &wolinfo);
+ if (err == 0) {
+ err = dump_wol(&wolinfo);
+ if (err)
+ return err;
+ allfail = 0;
+ } else if (errno != EOPNOTSUPP) {
+ perror("Cannot get wake-on-lan settings");
+ }
+
+ edata.cmd = ETHTOOL_GMSGLVL;
+ err = send_ioctl(ctx, &edata);
+ if (err == 0) {
+ fprintf(stdout, " Current message level: 0x%08x (%d)\n"
+ " ",
+ edata.data, edata.data);
+ print_flags(flags_msglvl, n_flags_msglvl, edata.data);
+ fprintf(stdout, "\n");
+ allfail = 0;
+ } else if (errno != EOPNOTSUPP) {
+ perror("Cannot get message level");
+ }
+
+ edata.cmd = ETHTOOL_GLINK;
+ err = send_ioctl(ctx, &edata);
+ if (err == 0) {
+ fprintf(stdout, " Link detected: %s\n",
+ edata.data ? "yes":"no");
+ allfail = 0;
+ } else if (errno != EOPNOTSUPP) {
+ perror("Cannot get link status");
+ }
+
+ if (allfail) {
+ fprintf(stdout, "No data available\n");
+ return 75;
+ }
+ return 0;
+}
+
+static int do_sset(struct cmd_context *ctx)
+{
+ int speed_wanted = -1;
+ int duplex_wanted = -1;
+ int port_wanted = -1;
+ int mdix_wanted = -1;
+ int autoneg_wanted = -1;
+ int phyad_wanted = -1;
+ int xcvr_wanted = -1;
+ u32 *full_advertising_wanted = NULL;
+ u32 *advertising_wanted = NULL;
+ ETHTOOL_DECLARE_LINK_MODE_MASK(mask_full_advertising_wanted);
+ ETHTOOL_DECLARE_LINK_MODE_MASK(mask_advertising_wanted);
+ int gset_changed = 0; /* did anything in GSET change? */
+ u32 wol_wanted = 0;
+ int wol_change = 0;
+ u8 sopass_wanted[SOPASS_MAX];
+ int sopass_change = 0;
+ int gwol_changed = 0; /* did anything in GWOL change? */
+ int msglvl_changed = 0;
+ u32 msglvl_wanted = 0;
+ u32 msglvl_mask = 0;
+ struct cmdline_info cmdline_msglvl[n_flags_msglvl];
+ unsigned int argc = ctx->argc;
+ char **argp = ctx->argp;
+ unsigned int i;
+ int err = 0;
+
+ for (i = 0; i < n_flags_msglvl; i++)
+ flag_to_cmdline_info(flags_msglvl[i].name,
+ flags_msglvl[i].value,
+ &msglvl_wanted, &msglvl_mask,
+ &cmdline_msglvl[i]);
+
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argp[i], "speed")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ speed_wanted = get_int(argp[i], 10);
+ } else if (!strcmp(argp[i], "duplex")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ if (!strcmp(argp[i], "half"))
+ duplex_wanted = DUPLEX_HALF;
+ else if (!strcmp(argp[i], "full"))
+ duplex_wanted = DUPLEX_FULL;
+ else
+ exit_bad_args();
+ } else if (!strcmp(argp[i], "port")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ if (!strcmp(argp[i], "tp"))
+ port_wanted = PORT_TP;
+ else if (!strcmp(argp[i], "aui"))
+ port_wanted = PORT_AUI;
+ else if (!strcmp(argp[i], "bnc"))
+ port_wanted = PORT_BNC;
+ else if (!strcmp(argp[i], "mii"))
+ port_wanted = PORT_MII;
+ else if (!strcmp(argp[i], "fibre"))
+ port_wanted = PORT_FIBRE;
+ else
+ exit_bad_args();
+ } else if (!strcmp(argp[i], "mdix")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ if (!strcmp(argp[i], "auto"))
+ mdix_wanted = ETH_TP_MDI_AUTO;
+ else if (!strcmp(argp[i], "on"))
+ mdix_wanted = ETH_TP_MDI_X;
+ else if (!strcmp(argp[i], "off"))
+ mdix_wanted = ETH_TP_MDI;
+ else
+ exit_bad_args();
+ } else if (!strcmp(argp[i], "autoneg")) {
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ if (!strcmp(argp[i], "on")) {
+ gset_changed = 1;
+ autoneg_wanted = AUTONEG_ENABLE;
+ } else if (!strcmp(argp[i], "off")) {
+ gset_changed = 1;
+ autoneg_wanted = AUTONEG_DISABLE;
+ } else {
+ exit_bad_args();
+ }
+ } else if (!strcmp(argp[i], "advertise")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ if (parse_hex_u32_bitmap(
+ argp[i],
+ ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBITS,
+ mask_full_advertising_wanted))
+ exit_bad_args();
+ full_advertising_wanted = mask_full_advertising_wanted;
+ } else if (!strcmp(argp[i], "phyad")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ phyad_wanted = get_int(argp[i], 0);
+ } else if (!strcmp(argp[i], "xcvr")) {
+ gset_changed = 1;
+ i += 1;
+ if (i >= argc)
+ exit_bad_args();
+ if (!strcmp(argp[i], "internal"))
+ xcvr_wanted = XCVR_INTERNAL;
+ else if (!strcmp(argp[i], "external"))
+ xcvr_wanted = XCVR_EXTERNAL;
+ else
+ exit_bad_args();
+ } else if (!strcmp(argp[i], "wol")) {
+ gwol_changed = 1;
+ i++;
+ if (i >= argc)
+ exit_bad_args();
+ if (parse_wolopts(argp[i], &wol_wanted) < 0)
+ exit_bad_args();
+ wol_change = 1;
+ } else if (!strcmp(argp[i], "sopass")) {
+ gwol_changed = 1;
+ i++;
+ if (i >= argc)
+ exit_bad_args();
+ get_mac_addr(argp[i], sopass_wanted);
+ sopass_change = 1;
+ } else if (!strcmp(argp[i], "msglvl")) {
+ i++;
+ if (i >= argc)
+ exit_bad_args();
+ if (isdigit((unsigned char)argp[i][0])) {
+ msglvl_changed = 1;
+ msglvl_mask = ~0;
+ msglvl_wanted =
+ get_uint_range(argp[i], 0,
+ 0xffffffff);
+ } else {
+ ctx->argc -= i;
+ ctx->argp += i;
+ parse_generic_cmdline(
+ ctx, &msglvl_changed,
+ cmdline_msglvl,
+ ARRAY_SIZE(cmdline_msglvl));
+ break;
+ }
+ } else if (!strcmp(argp[i], "master-slave")) {
+ exit_nlonly_param(argp[i]);
+ } else {
+ exit_bad_args();
+ }
+ }
+
+ if (full_advertising_wanted == NULL) {
+ /* User didn't supply a full advertisement bitfield:
+ * construct one from the specified speed and duplex.
+ */
+ int adv_bit = -1;
+
+ if (speed_wanted == SPEED_10 && duplex_wanted == DUPLEX_HALF)
+ adv_bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT;
+ else if (speed_wanted == SPEED_10 &&
+ duplex_wanted == DUPLEX_FULL)
+ adv_bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT;
+ else if (speed_wanted == SPEED_100 &&
+ duplex_wanted == DUPLEX_HALF)
+ adv_bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT;
+ else if (speed_wanted == SPEED_100 &&
+ duplex_wanted == DUPLEX_FULL)
+ adv_bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT;
+ else if (speed_wanted == SPEED_1000 &&
+ duplex_wanted == DUPLEX_HALF)
+ adv_bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT;
+ else if (speed_wanted == SPEED_1000 &&
+ duplex_wanted == DUPLEX_FULL)
+ adv_bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT;
+ else if (speed_wanted == SPEED_2500 &&
+ duplex_wanted == DUPLEX_FULL)
+ adv_bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT;
+ else if (speed_wanted == SPEED_10000 &&
+ duplex_wanted == DUPLEX_FULL)
+ adv_bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT;
+
+ if (adv_bit >= 0) {
+ advertising_wanted = mask_advertising_wanted;
+ ethtool_link_mode_zero(advertising_wanted);
+ ethtool_link_mode_set_bit(
+ adv_bit, advertising_wanted);
+ }
+ /* otherwise: auto negotiate without forcing,
+ * all supported speed will be assigned below
+ */
+ }
+
+ if (gset_changed) {
+ struct ethtool_link_usettings *link_usettings;
+
+ link_usettings = do_ioctl_glinksettings(ctx);
+ if (link_usettings == NULL)
+ link_usettings = do_ioctl_gset(ctx);
+ else
+ memset(&link_usettings->deprecated, 0,
+ sizeof(link_usettings->deprecated));
+ if (link_usettings == NULL) {
+ perror("Cannot get current device settings");
+ err = -1;
+ } else {
+ /* Change everything the user specified. */
+ if (speed_wanted != -1)
+ link_usettings->base.speed = speed_wanted;
+ if (duplex_wanted != -1)
+ link_usettings->base.duplex = duplex_wanted;
+ if (port_wanted != -1)
+ link_usettings->base.port = port_wanted;
+ if (mdix_wanted != -1) {
+ /* check driver supports MDI-X */
+ if (link_usettings->base.eth_tp_mdix_ctrl
+ != ETH_TP_MDI_INVALID)
+ link_usettings->base.eth_tp_mdix_ctrl
+ = mdix_wanted;
+ else
+ fprintf(stderr,
+ "setting MDI not supported\n");
+ }
+ if (autoneg_wanted != -1)
+ link_usettings->base.autoneg = autoneg_wanted;
+ if (phyad_wanted != -1)
+ link_usettings->base.phy_address = phyad_wanted;
+ if (xcvr_wanted != -1)
+ link_usettings->deprecated.transceiver
+ = xcvr_wanted;
+ /* XXX If the user specified speed or duplex
+ * then we should mask the advertised modes
+ * accordingly. For now, warn that we aren't
+ * doing that.
+ */
+ if ((speed_wanted != -1 || duplex_wanted != -1) &&
+ link_usettings->base.autoneg &&
+ advertising_wanted == NULL) {
+ fprintf(stderr, "Cannot advertise");
+ if (speed_wanted >= 0)
+ fprintf(stderr, " speed %d",
+ speed_wanted);
+ if (duplex_wanted >= 0)
+ fprintf(stderr, " duplex %s",
+ duplex_wanted ?
+ "full" : "half");
+ fprintf(stderr, "\n");
+ }
+ if (autoneg_wanted == AUTONEG_ENABLE &&
+ advertising_wanted == NULL &&
+ full_advertising_wanted == NULL) {
+ unsigned int i;
+
+ /* Auto negotiation enabled, but with
+ * unspecified speed and duplex: enable all
+ * supported speeds and duplexes.
+ */
+ ethtool_link_mode_for_each_u32(i) {
+ u32 sup = link_usettings->link_modes.supported[i];
+ u32 *adv = link_usettings->link_modes.advertising + i;
+
+ *adv = ((*adv & ~all_advertised_modes[i])
+ | (sup & all_advertised_modes[i]));
+ }
+
+ /* If driver supports unknown flags, we cannot
+ * be sure that we enable all link modes.
+ */
+ ethtool_link_mode_for_each_u32(i) {
+ u32 sup = link_usettings->link_modes.supported[i];
+
+ if ((sup & all_advertised_flags[i]) != sup) {
+ fprintf(stderr, "Driver supports one or more unknown flags\n");
+ break;
+ }
+ }
+ } else if (advertising_wanted != NULL) {
+ unsigned int i;
+
+ /* Enable all requested modes */
+ ethtool_link_mode_for_each_u32(i) {
+ u32 *adv = link_usettings->link_modes.advertising + i;
+
+ *adv = ((*adv & ~all_advertised_modes[i])
+ | advertising_wanted[i]);
+ }
+ } else if (full_advertising_wanted != NULL) {
+ ethtool_link_mode_copy(
+ link_usettings->link_modes.advertising,
+ full_advertising_wanted);
+ }
+
+ /* Try to perform the update. */
+ if (link_usettings->base.cmd == ETHTOOL_GLINKSETTINGS)
+ err = do_ioctl_slinksettings(ctx,
+ link_usettings);
+ else
+ err = do_ioctl_sset(ctx, link_usettings);
+ free(link_usettings);
+ if (err < 0)
+ perror("Cannot set new settings");
+ }
+ if (err < 0) {
+ if (speed_wanted != -1)
+ fprintf(stderr, " not setting speed\n");
+ if (duplex_wanted != -1)
+ fprintf(stderr, " not setting duplex\n");
+ if (port_wanted != -1)
+ fprintf(stderr, " not setting port\n");
+ if (autoneg_wanted != -1)
+ fprintf(stderr, " not setting autoneg\n");
+ if (phyad_wanted != -1)
+ fprintf(stderr, " not setting phy_address\n");
+ if (xcvr_wanted != -1)
+ fprintf(stderr, " not setting transceiver\n");
+ if (mdix_wanted != -1)
+ fprintf(stderr, " not setting mdix\n");
+ }
+ }
+
+ if (gwol_changed) {
+ struct ethtool_wolinfo wol;
+
+ wol.cmd = ETHTOOL_GWOL;
+ err = send_ioctl(ctx, &wol);
+ if (err < 0) {
+ perror("Cannot get current wake-on-lan settings");
+ } else {
+ /* Change everything the user specified. */
+ if (wol_change)
+ wol.wolopts = wol_wanted;
+ if (sopass_change) {
+ int i;
+ for (i = 0; i < SOPASS_MAX; i++)
+ wol.sopass[i] = sopass_wanted[i];
+ }
+
+ /* Try to perform the update. */
+ wol.cmd = ETHTOOL_SWOL;
+ err = send_ioctl(ctx, &wol);
+ if (err < 0)
+ perror("Cannot set new wake-on-lan settings");
+ }
+ if (err < 0) {
+ if (wol_change)
+ fprintf(stderr, " not setting wol\n");
+ if (sopass_change)
+ fprintf(stderr, " not setting sopass\n");
+ }
+ }
+
+ if (msglvl_changed) {
+ struct ethtool_value edata;
+
+ edata.cmd = ETHTOOL_GMSGLVL;
+ err = send_ioctl(ctx, &edata);
+ if (err < 0) {
+ perror("Cannot get msglvl");
+ } else {
+ edata.cmd = ETHTOOL_SMSGLVL;
+ edata.data = ((edata.data & ~msglvl_mask) |
+ msglvl_wanted);
+ err = send_ioctl(ctx, &edata);
+ if (err < 0)
+ perror("Cannot set new msglvl");
+ }
+ }
+
+ return 0;
+}
+
+static int do_gregs(struct cmd_context *ctx)
+{
+ int gregs_changed = 0;
+ int gregs_dump_raw = 0;
+ int gregs_dump_hex = 0;
+ char *gregs_dump_file = NULL;
+ struct cmdline_info cmdline_gregs[] = {
+ {
+ .name = "raw",
+ .type = CMDL_BOOL,
+ .wanted_val = &gregs_dump_raw,
+ },
+ {
+ .name = "hex",
+ .type = CMDL_BOOL,
+ .wanted_val = &gregs_dump_hex,
+ },
+ {
+ .name = "file",
+ .type = CMDL_STR,
+ .wanted_val = &gregs_dump_file,
+ },
+ };
+ int err;
+ struct ethtool_drvinfo drvinfo;
+ struct ethtool_regs *regs;
+
+ parse_generic_cmdline(ctx, &gregs_changed,
+ cmdline_gregs, ARRAY_SIZE(cmdline_gregs));
+
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ err = send_ioctl(ctx, &drvinfo);
+ if (err < 0) {
+ perror("Cannot get driver information");
+ return 72;
+ }
+
+ regs = calloc(1, sizeof(*regs)+drvinfo.regdump_len);
+ if (!regs) {
+ perror("Cannot allocate memory for register dump");
+ return 73;
+ }
+ regs->cmd = ETHTOOL_GREGS;
+ regs->len = drvinfo.regdump_len;
+ err = send_ioctl(ctx, regs);
+ if (err < 0) {
+ perror("Cannot get register dump");
+ free(regs);
+ return 74;
+ }
+
+ if (!gregs_dump_raw && gregs_dump_file != NULL) {
+ /* overwrite reg values from file dump */
+ FILE *f = fopen(gregs_dump_file, "r");
+ struct ethtool_regs *nregs;
+ struct stat st;
+ size_t nread;
+
+ if (!f || fstat(fileno(f), &st) < 0) {
+ fprintf(stderr, "Can't open '%s': %s\n",
+ gregs_dump_file, strerror(errno));
+ if (f)
+ fclose(f);
+ free(regs);
+ return 75;
+ }
+
+ nregs = realloc(regs, sizeof(*regs) + st.st_size);
+ if (!nregs) {
+ perror("Cannot allocate memory for register dump");
+ free(regs); /* was not freed by realloc */
+ return 73;
+ }
+ regs = nregs;
+ regs->len = st.st_size;
+ nread = fread(regs->data, regs->len, 1, f);
+ fclose(f);
+ if (nread != 1) {
+ free(regs);
+ return 75;
+ }
+ }
+
+ if (dump_regs(gregs_dump_raw, gregs_dump_hex,
+ &drvinfo, regs) < 0) {
+ fprintf(stderr, "Cannot dump registers\n");
+ free(regs);
+ return 75;
+ }
+ free(regs);
+
+ return 0;
+}
+
+static int do_nway_rst(struct cmd_context *ctx)
+{
+ struct ethtool_value edata;
+ int err;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ edata.cmd = ETHTOOL_NWAY_RST;
+ err = send_ioctl(ctx, &edata);
+ if (err < 0)
+ perror("Cannot restart autonegotiation");
+
+ return err;
+}
+
+static int do_geeprom(struct cmd_context *ctx)
+{
+ int geeprom_changed = 0;
+ int geeprom_dump_raw = 0;
+ u32 geeprom_offset = 0;
+ u32 geeprom_length = 0;
+ int geeprom_length_seen = 0;
+ struct cmdline_info cmdline_geeprom[] = {
+ {
+ .name = "offset",
+ .type = CMDL_U32,
+ .wanted_val = &geeprom_offset,
+ },
+ {
+ .name = "length",
+ .type = CMDL_U32,
+ .wanted_val = &geeprom_length,
+ .seen_val = &geeprom_length_seen,
+ },
+ {
+ .name = "raw",
+ .type = CMDL_BOOL,
+ .wanted_val = &geeprom_dump_raw,
+ },
+ };
+ int err;
+ struct ethtool_drvinfo drvinfo;
+ struct ethtool_eeprom *eeprom;
+
+ parse_generic_cmdline(ctx, &geeprom_changed,
+ cmdline_geeprom, ARRAY_SIZE(cmdline_geeprom));
+
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ err = send_ioctl(ctx, &drvinfo);
+ if (err < 0) {
+ perror("Cannot get driver information");
+ return 74;
+ }
+
+ if (!geeprom_length_seen)
+ geeprom_length = drvinfo.eedump_len;
+
+ if (drvinfo.eedump_len < geeprom_offset + geeprom_length)
+ geeprom_length = drvinfo.eedump_len - geeprom_offset;
+
+ eeprom = calloc(1, sizeof(*eeprom)+geeprom_length);
+ if (!eeprom) {
+ perror("Cannot allocate memory for EEPROM data");
+ return 75;
+ }
+ eeprom->cmd = ETHTOOL_GEEPROM;
+ eeprom->len = geeprom_length;
+ eeprom->offset = geeprom_offset;
+ err = send_ioctl(ctx, eeprom);
+ if (err < 0) {
+ perror("Cannot get EEPROM data");
+ free(eeprom);
+ return 74;
+ }
+ err = dump_eeprom(geeprom_dump_raw, &drvinfo, eeprom);
+ free(eeprom);
+
+ return err;
+}
+
+static int do_seeprom(struct cmd_context *ctx)
+{
+ int seeprom_changed = 0;
+ u32 seeprom_magic = 0;
+ u32 seeprom_length = 0;
+ u32 seeprom_offset = 0;
+ u8 seeprom_value = 0;
+ int seeprom_length_seen = 0;
+ int seeprom_value_seen = 0;
+ struct cmdline_info cmdline_seeprom[] = {
+ {
+ .name = "magic",
+ .type = CMDL_U32,
+ .wanted_val = &seeprom_magic,
+ },
+ {
+ .name = "offset",
+ .type = CMDL_U32,
+ .wanted_val = &seeprom_offset,
+ },
+ {
+ .name = "length",
+ .type = CMDL_U32,
+ .wanted_val = &seeprom_length,
+ .seen_val = &seeprom_length_seen,
+ },
+ {
+ .name = "value",
+ .type = CMDL_U8,
+ .wanted_val = &seeprom_value,
+ .seen_val = &seeprom_value_seen,
+ },
+ };
+ int err;
+ struct ethtool_drvinfo drvinfo;
+ struct ethtool_eeprom *eeprom;
+
+ parse_generic_cmdline(ctx, &seeprom_changed,
+ cmdline_seeprom, ARRAY_SIZE(cmdline_seeprom));
+
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ err = send_ioctl(ctx, &drvinfo);
+ if (err < 0) {
+ perror("Cannot get driver information");
+ return 74;
+ }
+
+ if (seeprom_value_seen && !seeprom_length_seen)
+ seeprom_length = 1;
+ else if (!seeprom_length_seen)
+ seeprom_length = drvinfo.eedump_len;
+
+ if (seeprom_value_seen && (seeprom_length != 1)) {
+ fprintf(stderr, "value requires length 1\n");
+ return 1;
+ }
+
+ if (drvinfo.eedump_len < seeprom_offset + seeprom_length) {
+ fprintf(stderr, "offset & length out of bounds\n");
+ return 1;
+ }
+
+ eeprom = calloc(1, sizeof(*eeprom)+seeprom_length);
+ if (!eeprom) {
+ perror("Cannot allocate memory for EEPROM data");
+ return 75;
+ }
+
+ eeprom->cmd = ETHTOOL_SEEPROM;
+ eeprom->len = seeprom_length;
+ eeprom->offset = seeprom_offset;
+ eeprom->magic = seeprom_magic;
+ eeprom->data[0] = seeprom_value;
+
+ /* Multi-byte write: read input from stdin */
+ if (!seeprom_value_seen) {
+ if (fread(eeprom->data, eeprom->len, 1, stdin) != 1) {
+ fprintf(stderr, "not enough data from stdin\n");
+ free(eeprom);
+ return 75;
+ }
+ if ((fgetc(stdin) != EOF) || !feof(stdin)) {
+ fprintf(stderr, "too much data from stdin\n");
+ free(eeprom);
+ return 75;
+ }
+ }
+
+ err = send_ioctl(ctx, eeprom);
+ if (err < 0) {
+ perror("Cannot set EEPROM data");
+ err = 87;
+ }
+ free(eeprom);
+
+ return err;
+}
+
+static int do_test(struct cmd_context *ctx)
+{
+ enum {
+ ONLINE = 0,
+ OFFLINE,
+ EXTERNAL_LB,
+ } test_type;
+ int err;
+ struct ethtool_test *test;
+ struct ethtool_gstrings *strings;
+
+ if (ctx->argc > 1)
+ exit_bad_args();
+ if (ctx->argc == 1) {
+ if (!strcmp(ctx->argp[0], "online"))
+ test_type = ONLINE;
+ else if (!strcmp(*ctx->argp, "offline"))
+ test_type = OFFLINE;
+ else if (!strcmp(*ctx->argp, "external_lb"))
+ test_type = EXTERNAL_LB;
+ else
+ exit_bad_args();
+ } else {
+ test_type = OFFLINE;
+ }
+
+ strings = get_stringset(ctx, ETH_SS_TEST,
+ offsetof(struct ethtool_drvinfo, testinfo_len),
+ 1);
+ if (!strings) {
+ perror("Cannot get strings");
+ return 74;
+ }
+
+ test = calloc(1, sizeof(*test) + strings->len * sizeof(u64));
+ if (!test) {
+ perror("Cannot allocate memory for test info");
+ free(strings);
+ return 73;
+ }
+ memset(test->data, 0, strings->len * sizeof(u64));
+ test->cmd = ETHTOOL_TEST;
+ test->len = strings->len;
+ if (test_type == EXTERNAL_LB)
+ test->flags = (ETH_TEST_FL_OFFLINE | ETH_TEST_FL_EXTERNAL_LB);
+ else if (test_type == OFFLINE)
+ test->flags = ETH_TEST_FL_OFFLINE;
+ else
+ test->flags = 0;
+ err = send_ioctl(ctx, test);
+ if (err < 0) {
+ perror("Cannot test");
+ free(test);
+ free(strings);
+ return 74;
+ }
+
+ err = dump_test(test, strings);
+ free(test);
+ free(strings);
+
+ return err;
+}
+
+static int do_phys_id(struct cmd_context *ctx)
+{
+ int err;
+ struct ethtool_value edata;
+ int phys_id_time;
+
+ if (ctx->argc > 1)
+ exit_bad_args();
+ if (ctx->argc == 1)
+ phys_id_time = get_int(*ctx->argp, 0);
+ else
+ phys_id_time = 0;
+
+ edata.cmd = ETHTOOL_PHYS_ID;
+ edata.data = phys_id_time;
+ err = send_ioctl(ctx, &edata);
+ if (err < 0)
+ perror("Cannot identify NIC");
+
+ return err;
+}
+
+static int do_gstats(struct cmd_context *ctx, int cmd, int stringset,
+ const char *name)
+{
+ struct ethtool_gstrings *strings;
+ struct ethtool_stats *stats;
+ unsigned int n_stats, sz_stats, i;
+ int err;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ strings = get_stringset(ctx, stringset,
+ offsetof(struct ethtool_drvinfo, n_stats),
+ 0);
+ if (!strings) {
+ perror("Cannot get stats strings information");
+ return 96;
+ }
+
+ n_stats = strings->len;
+ if (n_stats < 1) {
+ fprintf(stderr, "no stats available\n");
+ free(strings);
+ return 94;
+ }
+
+ sz_stats = n_stats * sizeof(u64);
+
+ stats = calloc(1, sz_stats + sizeof(struct ethtool_stats));
+ if (!stats) {
+ fprintf(stderr, "no memory available\n");
+ free(strings);
+ return 95;
+ }
+
+ stats->cmd = cmd;
+ stats->n_stats = n_stats;
+ err = send_ioctl(ctx, stats);
+ if (err < 0) {
+ perror("Cannot get stats information");
+ free(strings);
+ free(stats);
+ return 97;
+ }
+
+ /* todo - pretty-print the strings per-driver */
+ fprintf(stdout, "%s statistics:\n", name);
+ for (i = 0; i < n_stats; i++) {
+ fprintf(stdout, " %.*s: %llu\n",
+ ETH_GSTRING_LEN,
+ &strings->data[i * ETH_GSTRING_LEN],
+ stats->data[i]);
+ }
+ free(strings);
+ free(stats);
+
+ return 0;
+}
+
+static int do_gnicstats(struct cmd_context *ctx)
+{
+ return do_gstats(ctx, ETHTOOL_GSTATS, ETH_SS_STATS, "NIC");
+}
+
+static int do_gphystats(struct cmd_context *ctx)
+{
+ return do_gstats(ctx, ETHTOOL_GPHYSTATS, ETH_SS_PHY_STATS, "PHY");
+}
+
+static int do_srxntuple(struct cmd_context *ctx,
+ struct ethtool_rx_flow_spec *rx_rule_fs);
+
+static int do_srxclass(struct cmd_context *ctx)
+{
+ int err;
+
+ if (ctx->argc < 2)
+ exit_bad_args();
+
+ if (!strcmp(ctx->argp[0], "rx-flow-hash")) {
+ int rx_fhash_set;
+ u32 rx_fhash_val;
+ struct ethtool_rxnfc nfccmd;
+ bool flow_rss = false;
+
+ if (ctx->argc == 5) {
+ if (strcmp(ctx->argp[3], "context"))
+ exit_bad_args();
+ flow_rss = true;
+ nfccmd.rss_context = get_u32(ctx->argp[4], 0);
+ } else if (ctx->argc != 3) {
+ exit_bad_args();
+ }
+ rx_fhash_set = rxflow_str_to_type(ctx->argp[1]);
+ if (!rx_fhash_set)
+ exit_bad_args();
+ if (parse_rxfhashopts(ctx->argp[2], &rx_fhash_val) < 0)
+ exit_bad_args();
+
+ nfccmd.cmd = ETHTOOL_SRXFH;
+ nfccmd.flow_type = rx_fhash_set;
+ nfccmd.data = rx_fhash_val;
+ if (flow_rss)
+ nfccmd.flow_type |= FLOW_RSS;
+
+ err = send_ioctl(ctx, &nfccmd);
+ if (err < 0)
+ perror("Cannot change RX network flow hashing options");
+ } else if (!strcmp(ctx->argp[0], "flow-type")) {
+ struct ethtool_rx_flow_spec rx_rule_fs;
+ __u32 rss_context = 0;
+
+ ctx->argc--;
+ ctx->argp++;
+ if (rxclass_parse_ruleopts(ctx, &rx_rule_fs, &rss_context) < 0)
+ exit_bad_args();
+
+ /* attempt to add rule via N-tuple specifier */
+ err = do_srxntuple(ctx, &rx_rule_fs);
+ if (!err)
+ return 0;
+
+ /* attempt to add rule via network flow classifier */
+ err = rxclass_rule_ins(ctx, &rx_rule_fs, rss_context);
+ if (err < 0) {
+ fprintf(stderr, "Cannot insert"
+ " classification rule\n");
+ return 1;
+ }
+ } else if (!strcmp(ctx->argp[0], "delete")) {
+ int rx_class_rule_del =
+ get_uint_range(ctx->argp[1], 0, INT_MAX);
+
+ err = rxclass_rule_del(ctx, rx_class_rule_del);
+
+ if (err < 0) {
+ fprintf(stderr, "Cannot delete"
+ " classification rule\n");
+ return 1;
+ }
+ } else {
+ exit_bad_args();
+ }
+
+ return 0;
+}
+
+static int do_grxclass(struct cmd_context *ctx)
+{
+ struct ethtool_rxnfc nfccmd;
+ int err;
+
+ if (ctx->argc > 0 && !strcmp(ctx->argp[0], "rx-flow-hash")) {
+ int rx_fhash_get;
+ bool flow_rss = false;
+
+ if (ctx->argc == 4) {
+ if (strcmp(ctx->argp[2], "context"))
+ exit_bad_args();
+ flow_rss = true;
+ nfccmd.rss_context = get_u32(ctx->argp[3], 0);
+ } else if (ctx->argc != 2) {
+ exit_bad_args();
+ }
+
+ rx_fhash_get = rxflow_str_to_type(ctx->argp[1]);
+ if (!rx_fhash_get)
+ exit_bad_args();
+
+ nfccmd.cmd = ETHTOOL_GRXFH;
+ nfccmd.flow_type = rx_fhash_get;
+ if (flow_rss)
+ nfccmd.flow_type |= FLOW_RSS;
+ err = send_ioctl(ctx, &nfccmd);
+ if (err < 0) {
+ perror("Cannot get RX network flow hashing options");
+ } else {
+ if (flow_rss)
+ fprintf(stdout, "For RSS context %u:\n",
+ nfccmd.rss_context);
+ dump_rxfhash(rx_fhash_get, nfccmd.data);
+ }
+ } else if (ctx->argc == 2 && !strcmp(ctx->argp[0], "rule")) {
+ int rx_class_rule_get =
+ get_uint_range(ctx->argp[1], 0, INT_MAX);
+
+ err = rxclass_rule_get(ctx, rx_class_rule_get);
+ if (err < 0)
+ fprintf(stderr, "Cannot get RX classification rule\n");
+ } else if (ctx->argc == 0) {
+ nfccmd.cmd = ETHTOOL_GRXRINGS;
+ err = send_ioctl(ctx, &nfccmd);
+ if (err < 0)
+ perror("Cannot get RX rings");
+ else
+ fprintf(stdout, "%d RX rings available\n",
+ (int)nfccmd.data);
+
+ err = rxclass_rule_getall(ctx);
+ if (err < 0)
+ fprintf(stderr, "RX classification rule retrieval failed\n");
+
+ } else {
+ exit_bad_args();
+ }
+
+ return err ? 1 : 0;
+}
+
+static int do_grxfhindir(struct cmd_context *ctx,
+ struct ethtool_rxnfc *ring_count)
+{
+ struct ethtool_rxfh_indir indir_head;
+ struct ethtool_rxfh_indir *indir;
+ int err;
+
+ indir_head.cmd = ETHTOOL_GRXFHINDIR;
+ indir_head.size = 0;
+ err = send_ioctl(ctx, &indir_head);
+ if (err < 0) {
+ perror("Cannot get RX flow hash indirection table size");
+ return 1;
+ }
+
+ indir = malloc(sizeof(*indir) +
+ indir_head.size * sizeof(*indir->ring_index));
+ if (!indir) {
+ perror("Cannot allocate memory for indirection table");
+ return 1;
+ }
+
+ indir->cmd = ETHTOOL_GRXFHINDIR;
+ indir->size = indir_head.size;
+ err = send_ioctl(ctx, indir);
+ if (err < 0) {
+ perror("Cannot get RX flow hash indirection table");
+ free(indir);
+ return 1;
+ }
+
+ print_indir_table(ctx, ring_count->data, indir->size,
+ indir->ring_index);
+
+ free(indir);
+ return 0;
+}
+
+static int do_grxfh(struct cmd_context *ctx)
+{
+ struct ethtool_gstrings *hfuncs = NULL;
+ struct ethtool_rxfh rss_head = {0};
+ struct ethtool_rxnfc ring_count;
+ struct ethtool_rxfh *rss;
+ u32 rss_context = 0;
+ u32 i, indir_bytes;
+ unsigned int arg_num = 0;
+ u8 *hkey;
+ int err;
+
+ while (arg_num < ctx->argc) {
+ if (!strcmp(ctx->argp[arg_num], "context")) {
+ ++arg_num;
+ rss_context = get_int_range(ctx->argp[arg_num], 0, 1,
+ ETH_RXFH_CONTEXT_ALLOC - 1);
+ ++arg_num;
+ } else {
+ exit_bad_args();
+ }
+ }
+
+ ring_count.cmd = ETHTOOL_GRXRINGS;
+ err = send_ioctl(ctx, &ring_count);
+ if (err < 0) {
+ perror("Cannot get RX ring count");
+ return 1;
+ }
+
+ rss_head.cmd = ETHTOOL_GRSSH;
+ rss_head.rss_context = rss_context;
+ err = send_ioctl(ctx, &rss_head);
+ if (err < 0 && errno == EOPNOTSUPP && !rss_context) {
+ return do_grxfhindir(ctx, &ring_count);
+ } else if (err < 0) {
+ perror("Cannot get RX flow hash indir size and/or key size");
+ return 1;
+ }
+
+ rss = calloc(1, sizeof(*rss) +
+ rss_head.indir_size * sizeof(rss_head.rss_config[0]) +
+ rss_head.key_size);
+ if (!rss) {
+ perror("Cannot allocate memory for RX flow hash config");
+ return 1;
+ }
+
+ rss->cmd = ETHTOOL_GRSSH;
+ rss->rss_context = rss_context;
+ rss->indir_size = rss_head.indir_size;
+ rss->key_size = rss_head.key_size;
+ err = send_ioctl(ctx, rss);
+ if (err < 0) {
+ perror("Cannot get RX flow hash configuration");
+ free(rss);
+ return 1;
+ }
+
+ print_indir_table(ctx, ring_count.data, rss->indir_size,
+ rss->rss_config);
+
+ indir_bytes = rss->indir_size * sizeof(rss->rss_config[0]);
+ hkey = ((u8 *)rss->rss_config + indir_bytes);
+
+ print_rss_hkey(hkey, rss->key_size);
+
+ printf("RSS hash function:\n");
+ if (!rss->hfunc) {
+ printf(" Operation not supported\n");
+ goto out;
+ }
+
+ hfuncs = get_stringset(ctx, ETH_SS_RSS_HASH_FUNCS, 0, 1);
+ if (!hfuncs) {
+ perror("Cannot get hash functions names");
+ free(rss);
+ return 1;
+ }
+
+ for (i = 0; i < hfuncs->len; i++)
+ printf(" %s: %s\n",
+ (const char *)hfuncs->data + i * ETH_GSTRING_LEN,
+ (rss->hfunc & (1 << i)) ? "on" : "off");
+
+out:
+ free(hfuncs);
+ free(rss);
+ return 0;
+}
+
+static int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
+ int rxfhindir_start, int rxfhindir_equal,
+ char **rxfhindir_weight, u32 num_weights)
+{
+ u32 i;
+
+ if (rxfhindir_equal) {
+ for (i = 0; i < *indir_size; i++)
+ indir[i] = rxfhindir_start + (i % rxfhindir_equal);
+ } else if (rxfhindir_weight) {
+ u32 j, weight, sum = 0, partial = 0;
+
+ for (j = 0; j < num_weights; j++) {
+ weight = get_u32(rxfhindir_weight[j], 0);
+ sum += weight;
+ }
+
+ if (sum == 0) {
+ fprintf(stderr,
+ "At least one weight must be non-zero\n");
+ return 2;
+ }
+
+ if (sum > *indir_size) {
+ fprintf(stderr,
+ "Total weight exceeds the size of the "
+ "indirection table\n");
+ return 2;
+ }
+
+ j = -1;
+ for (i = 0; i < *indir_size; i++) {
+ while (i >= (*indir_size) * partial / sum) {
+ j += 1;
+ weight = get_u32(rxfhindir_weight[j], 0);
+ partial += weight;
+ }
+ indir[i] = rxfhindir_start + j;
+ }
+ } else if (rxfhindir_default) {
+ /* "*indir_size == 0" ==> reset indir to default */
+ *indir_size = 0;
+ } else {
+ *indir_size = ETH_RXFH_INDIR_NO_CHANGE;
+ }
+
+ return 0;
+}
+
+static int do_srxfhindir(struct cmd_context *ctx, int rxfhindir_default,
+ int rxfhindir_start, int rxfhindir_equal,
+ char **rxfhindir_weight, u32 num_weights)
+{
+ struct ethtool_rxfh_indir indir_head;
+ struct ethtool_rxfh_indir *indir;
+ int err;
+
+ indir_head.cmd = ETHTOOL_GRXFHINDIR;
+ indir_head.size = 0;
+ err = send_ioctl(ctx, &indir_head);
+ if (err < 0) {
+ perror("Cannot get RX flow hash indirection table size");
+ return 1;
+ }
+
+ indir = malloc(sizeof(*indir) +
+ indir_head.size * sizeof(*indir->ring_index));
+
+ if (!indir) {
+ perror("Cannot allocate memory for indirection table");
+ return 1;
+ }
+
+ indir->cmd = ETHTOOL_SRXFHINDIR;
+ indir->size = indir_head.size;
+
+ if (fill_indir_table(&indir->size, indir->ring_index,
+ rxfhindir_default, rxfhindir_start,
+ rxfhindir_equal, rxfhindir_weight, num_weights)) {
+ free(indir);
+ return 1;
+ }
+
+ err = send_ioctl(ctx, indir);
+ if (err < 0) {
+ perror("Cannot set RX flow hash indirection table");
+ free(indir);
+ return 1;
+ }
+
+ free(indir);
+ return 0;
+}
+
+static int do_srxfh(struct cmd_context *ctx)
+{
+ struct ethtool_rxfh rss_head = {0};
+ struct ethtool_rxfh *rss = NULL;
+ struct ethtool_rxnfc ring_count;
+ int rxfhindir_equal = 0, rxfhindir_default = 0, rxfhindir_start = 0;
+ struct ethtool_gstrings *hfuncs = NULL;
+ char **rxfhindir_weight = NULL;
+ char *rxfhindir_key = NULL;
+ char *req_hfunc_name = NULL;
+ char *hfunc_name = NULL;
+ char *hkey = NULL;
+ int err = 0;
+ unsigned int i;
+ u32 arg_num = 0, indir_bytes = 0;
+ u32 req_hfunc = 0;
+ u32 entry_size = sizeof(rss_head.rss_config[0]);
+ u32 num_weights = 0;
+ u32 rss_context = 0;
+ int delete = 0;
+
+ if (ctx->argc < 1)
+ exit_bad_args();
+
+ while (arg_num < ctx->argc) {
+ if (!strcmp(ctx->argp[arg_num], "equal")) {
+ ++arg_num;
+ rxfhindir_equal = get_int_range(ctx->argp[arg_num],
+ 0, 1, INT_MAX);
+ ++arg_num;
+ } else if (!strcmp(ctx->argp[arg_num], "start")) {
+ ++arg_num;
+ rxfhindir_start = get_int_range(ctx->argp[arg_num],
+ 0, 0, INT_MAX);
+ ++arg_num;
+ } else if (!strcmp(ctx->argp[arg_num], "weight")) {
+ ++arg_num;
+ rxfhindir_weight = ctx->argp + arg_num;
+ while (arg_num < ctx->argc &&
+ isdigit((unsigned char)ctx->argp[arg_num][0])) {
+ ++arg_num;
+ ++num_weights;
+ }
+ if (!num_weights)
+ exit_bad_args();
+ } else if (!strcmp(ctx->argp[arg_num], "hkey")) {
+ ++arg_num;
+ rxfhindir_key = ctx->argp[arg_num];
+ if (!rxfhindir_key)
+ exit_bad_args();
+ ++arg_num;
+ } else if (!strcmp(ctx->argp[arg_num], "default")) {
+ ++arg_num;
+ rxfhindir_default = 1;
+ } else if (!strcmp(ctx->argp[arg_num], "hfunc")) {
+ ++arg_num;
+ req_hfunc_name = ctx->argp[arg_num];
+ if (!req_hfunc_name)
+ exit_bad_args();
+ ++arg_num;
+ } else if (!strcmp(ctx->argp[arg_num], "context")) {
+ ++arg_num;
+ if(!strcmp(ctx->argp[arg_num], "new"))
+ rss_context = ETH_RXFH_CONTEXT_ALLOC;
+ else
+ rss_context = get_int_range(
+ ctx->argp[arg_num], 0, 1,
+ ETH_RXFH_CONTEXT_ALLOC - 1);
+ ++arg_num;
+ } else if (!strcmp(ctx->argp[arg_num], "delete")) {
+ ++arg_num;
+ delete = 1;
+ } else {
+ exit_bad_args();
+ }
+ }
+
+ if (rxfhindir_equal && rxfhindir_weight) {
+ fprintf(stderr,
+ "Equal and weight options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (rxfhindir_equal && rxfhindir_default) {
+ fprintf(stderr,
+ "Equal and default options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (rxfhindir_weight && rxfhindir_default) {
+ fprintf(stderr,
+ "Weight and default options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (rxfhindir_start && rxfhindir_default) {
+ fprintf(stderr,
+ "Start and default options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (rxfhindir_start && !(rxfhindir_equal || rxfhindir_weight)) {
+ fprintf(stderr,
+ "Start must be used with equal or weight options\n");
+ return 1;
+ }
+
+ if (rxfhindir_default && rss_context) {
+ fprintf(stderr,
+ "Default and context options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (delete && !rss_context) {
+ fprintf(stderr, "Delete option requires context option\n");
+ return 1;
+ }
+
+ if (delete && rxfhindir_weight) {
+ fprintf(stderr,
+ "Delete and weight options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (delete && rxfhindir_equal) {
+ fprintf(stderr,
+ "Delete and equal options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (delete && rxfhindir_default) {
+ fprintf(stderr,
+ "Delete and default options are mutually exclusive\n");
+ return 1;
+ }
+
+ if (delete && rxfhindir_key) {
+ fprintf(stderr,
+ "Delete and hkey options are mutually exclusive\n");
+ return 1;
+ }
+
+ ring_count.cmd = ETHTOOL_GRXRINGS;
+ err = send_ioctl(ctx, &ring_count);
+ if (err < 0) {
+ perror("Cannot get RX ring count");
+ return 1;
+ }
+
+ rss_head.cmd = ETHTOOL_GRSSH;
+ err = send_ioctl(ctx, &rss_head);
+ if (err < 0 && errno == EOPNOTSUPP && !rxfhindir_key &&
+ !req_hfunc_name && !rss_context) {
+ return do_srxfhindir(ctx, rxfhindir_default, rxfhindir_start,
+ rxfhindir_equal, rxfhindir_weight,
+ num_weights);
+ } else if (err < 0) {
+ perror("Cannot get RX flow hash indir size and key size");
+ return 1;
+ }
+
+ if (rxfhindir_key) {
+ err = parse_hkey(&hkey, rss_head.key_size,
+ rxfhindir_key);
+ if (err)
+ return err;
+ }
+
+ if (rxfhindir_equal || rxfhindir_weight)
+ indir_bytes = rss_head.indir_size * entry_size;
+
+ if (rss_head.hfunc && req_hfunc_name) {
+ hfuncs = get_stringset(ctx, ETH_SS_RSS_HASH_FUNCS, 0, 1);
+ if (!hfuncs) {
+ perror("Cannot get hash functions names");
+ err = 1;
+ goto free;
+ }
+
+ for (i = 0; i < hfuncs->len && !req_hfunc ; i++) {
+ hfunc_name = (char *)(hfuncs->data +
+ i * ETH_GSTRING_LEN);
+ if (!strncmp(hfunc_name, req_hfunc_name,
+ ETH_GSTRING_LEN))
+ req_hfunc = (u32)1 << i;
+ }
+
+ if (!req_hfunc) {
+ fprintf(stderr,
+ "Unknown hash function: %s\n", req_hfunc_name);
+ err = 1;
+ goto free;
+ }
+ }
+
+ rss = calloc(1, sizeof(*rss) + indir_bytes + rss_head.key_size);
+ if (!rss) {
+ perror("Cannot allocate memory for RX flow hash config");
+ err = 1;
+ goto free;
+ }
+ rss->cmd = ETHTOOL_SRSSH;
+ rss->rss_context = rss_context;
+ rss->hfunc = req_hfunc;
+ if (delete) {
+ rss->indir_size = rss->key_size = 0;
+ } else {
+ rss->indir_size = rss_head.indir_size;
+ rss->key_size = rss_head.key_size;
+ if (fill_indir_table(&rss->indir_size, rss->rss_config,
+ rxfhindir_default, rxfhindir_start,
+ rxfhindir_equal, rxfhindir_weight,
+ num_weights)) {
+ err = 1;
+ goto free;
+ }
+ }
+
+ if (hkey)
+ memcpy((char *)rss->rss_config + indir_bytes,
+ hkey, rss->key_size);
+ else
+ rss->key_size = 0;
+
+ err = send_ioctl(ctx, rss);
+ if (err < 0) {
+ perror("Cannot set RX flow hash configuration");
+ err = 1;
+ } else if (rss_context == ETH_RXFH_CONTEXT_ALLOC) {
+ printf("New RSS context is %d\n", rss->rss_context);
+ }
+
+free:
+ free(hkey);
+ free(rss);
+ free(hfuncs);
+ return err;
+}
+
+static int do_flash(struct cmd_context *ctx)
+{
+ char *flash_file;
+ int flash_region;
+ struct ethtool_flash efl;
+ int err;
+
+ if (ctx->argc < 1 || ctx->argc > 2)
+ exit_bad_args();
+ flash_file = ctx->argp[0];
+ if (ctx->argc == 2) {
+ flash_region = strtol(ctx->argp[1], NULL, 0);
+ if (flash_region < 0)
+ exit_bad_args();
+ } else {
+ flash_region = -1;
+ }
+
+ if (strlen(flash_file) > ETHTOOL_FLASH_MAX_FILENAME - 1) {
+ fprintf(stdout, "Filename too long\n");
+ return 99;
+ }
+
+ efl.cmd = ETHTOOL_FLASHDEV;
+ strcpy(efl.data, flash_file);
+
+ if (flash_region < 0)
+ efl.region = ETHTOOL_FLASH_ALL_REGIONS;
+ else
+ efl.region = flash_region;
+
+ err = send_ioctl(ctx, &efl);
+ if (err < 0)
+ perror("Flashing failed");
+
+ return err;
+}
+
+static int do_permaddr(struct cmd_context *ctx)
+{
+ unsigned int i;
+ int err;
+ struct ethtool_perm_addr *epaddr;
+
+ epaddr = malloc(sizeof(struct ethtool_perm_addr) + MAX_ADDR_LEN);
+ if (!epaddr) {
+ perror("Cannot allocate memory for operation");
+ return 1;
+ }
+
+ epaddr->cmd = ETHTOOL_GPERMADDR;
+ epaddr->size = MAX_ADDR_LEN;
+
+ err = send_ioctl(ctx, epaddr);
+ if (err < 0)
+ perror("Cannot read permanent address");
+ else {
+ printf("Permanent address:");
+ for (i = 0; i < epaddr->size; i++)
+ printf("%c%02x", (i == 0) ? ' ' : ':',
+ epaddr->data[i]);
+ printf("\n");
+ }
+ free(epaddr);
+
+ return err;
+}
+
+static bool flow_type_is_ntuple_supported(__u32 flow_type)
+{
+ switch (flow_type) {
+ case TCP_V4_FLOW:
+ case UDP_V4_FLOW:
+ case SCTP_V4_FLOW:
+ case AH_V4_FLOW:
+ case ESP_V4_FLOW:
+ case IPV4_USER_FLOW:
+ case ETHER_FLOW:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int flow_spec_to_ntuple(struct ethtool_rx_flow_spec *fsp,
+ struct ethtool_rx_ntuple_flow_spec *ntuple)
+{
+ size_t i;
+
+ /* verify location is not specified */
+ if (fsp->location != RX_CLS_LOC_ANY)
+ return -1;
+
+ /* destination MAC address in L3/L4 rules is not supported by ntuple */
+ if (fsp->flow_type & FLOW_MAC_EXT)
+ return -1;
+
+ /* verify ring cookie can transfer to action */
+ if (fsp->ring_cookie > INT_MAX && fsp->ring_cookie < (u64)(-2))
+ return -1;
+
+ /* verify only one field is setting data field */
+ if ((fsp->flow_type & FLOW_EXT) &&
+ (fsp->m_ext.data[0] || fsp->m_ext.data[1]) &&
+ fsp->m_ext.vlan_etype)
+ return -1;
+
+ /* IPv6 flow types are not supported by ntuple */
+ if (!flow_type_is_ntuple_supported(fsp->flow_type & ~FLOW_EXT))
+ return -1;
+
+ /* Set entire ntuple to ~0 to guarantee all masks are set */
+ memset(ntuple, ~0, sizeof(*ntuple));
+
+ /* set non-filter values */
+ ntuple->flow_type = fsp->flow_type;
+ ntuple->action = fsp->ring_cookie;
+
+ /*
+ * Copy over header union, they are identical in layout however
+ * the ntuple union contains additional padding on the end
+ */
+ memcpy(&ntuple->h_u, &fsp->h_u, sizeof(fsp->h_u));
+
+ /*
+ * The same rule mentioned above applies to the mask union. However,
+ * in addition we need to invert the mask bits to match the ntuple
+ * mask which is 1 for masked, versus 0 for masked as seen in nfc.
+ */
+ memcpy(&ntuple->m_u, &fsp->m_u, sizeof(fsp->m_u));
+ for (i = 0; i < sizeof(fsp->m_u); i++)
+ ntuple->m_u.hdata[i] ^= 0xFF;
+
+ /* copy extended fields */
+ if (fsp->flow_type & FLOW_EXT) {
+ ntuple->vlan_tag =
+ ntohs(fsp->h_ext.vlan_tci);
+ ntuple->vlan_tag_mask =
+ ~ntohs(fsp->m_ext.vlan_tci);
+ if (fsp->m_ext.vlan_etype) {
+ /*
+ * vlan_etype and user data are mutually exclusive
+ * in ntuple configuration as they occupy the same
+ * space.
+ */
+ if (fsp->m_ext.data[0] || fsp->m_ext.data[1])
+ return -1;
+ ntuple->data =
+ ntohl(fsp->h_ext.vlan_etype);
+ ntuple->data_mask =
+ ~(u64)ntohl(fsp->m_ext.vlan_etype);
+ } else {
+ ntuple->data =
+ (u64)ntohl(fsp->h_ext.data[0]) << 32;
+ ntuple->data |=
+ (u64)ntohl(fsp->h_ext.data[1]);
+ ntuple->data_mask =
+ (u64)ntohl(~fsp->m_ext.data[0]) << 32;
+ ntuple->data_mask |=
+ (u64)ntohl(~fsp->m_ext.data[1]);
+ }
+ }
+
+ /* Mask out the extended bit, because ntuple does not know it! */
+ ntuple->flow_type &= ~FLOW_EXT;
+
+ return 0;
+}
+
+static int do_srxntuple(struct cmd_context *ctx,
+ struct ethtool_rx_flow_spec *rx_rule_fs)
+{
+ struct ethtool_rx_ntuple ntuplecmd;
+ struct ethtool_value eval;
+ int err;
+
+ /* attempt to convert the flow classifier to an ntuple classifier */
+ err = flow_spec_to_ntuple(rx_rule_fs, &ntuplecmd.fs);
+ if (err)
+ return -1;
+
+ /*
+ * Check to see if the flag is set for N-tuple, this allows
+ * us to avoid the possible EINVAL response for the N-tuple
+ * flag not being set on the device
+ */
+ eval.cmd = ETHTOOL_GFLAGS;
+ err = send_ioctl(ctx, &eval);
+ if (err || !(eval.data & ETH_FLAG_NTUPLE))
+ return -1;
+
+ /* send rule via N-tuple */
+ ntuplecmd.cmd = ETHTOOL_SRXNTUPLE;
+ err = send_ioctl(ctx, &ntuplecmd);
+
+ /*
+ * Display error only if response is something other than op not
+ * supported. It is possible that the interface uses the network
+ * flow classifier interface instead of N-tuple.
+ */
+ if (err < 0) {
+ if (errno != EOPNOTSUPP)
+ perror("Cannot add new rule via N-tuple");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int do_writefwdump(struct ethtool_dump *dump, const char *dump_file)
+{
+ int err = 0;
+ FILE *f;
+ size_t bytes;
+
+ f = fopen(dump_file, "wb+");
+
+ if (!f) {
+ fprintf(stderr, "Can't open file %s: %s\n",
+ dump_file, strerror(errno));
+ return 1;
+ }
+ bytes = fwrite(dump->data, 1, dump->len, f);
+ if (bytes != dump->len) {
+ fprintf(stderr, "Can not write all of dump data\n");
+ err = 1;
+ }
+ if (fclose(f)) {
+ fprintf(stderr, "Can't close file %s: %s\n",
+ dump_file, strerror(errno));
+ err = 1;
+ }
+ return err;
+}
+
+static int do_getfwdump(struct cmd_context *ctx)
+{
+ u32 dump_flag;
+ char *dump_file;
+ int err;
+ struct ethtool_dump edata;
+ struct ethtool_dump *data;
+
+ if (ctx->argc == 2 && !strcmp(ctx->argp[0], "data")) {
+ dump_flag = ETHTOOL_GET_DUMP_DATA;
+ dump_file = ctx->argp[1];
+ } else if (ctx->argc == 0) {
+ dump_flag = 0;
+ dump_file = NULL;
+ } else {
+ exit_bad_args();
+ }
+
+ edata.cmd = ETHTOOL_GET_DUMP_FLAG;
+
+ err = send_ioctl(ctx, &edata);
+ if (err < 0) {
+ perror("Can not get dump level");
+ return 1;
+ }
+ if (dump_flag != ETHTOOL_GET_DUMP_DATA) {
+ fprintf(stdout, "flag: %u, version: %u, length: %u\n",
+ edata.flag, edata.version, edata.len);
+ return 0;
+ }
+ data = calloc(1, offsetof(struct ethtool_dump, data) + edata.len);
+ if (!data) {
+ perror("Can not allocate enough memory");
+ return 1;
+ }
+ data->cmd = ETHTOOL_GET_DUMP_DATA;
+ data->len = edata.len;
+ err = send_ioctl(ctx, data);
+ if (err < 0) {
+ perror("Can not get dump data");
+ err = 1;
+ goto free;
+ }
+ err = do_writefwdump(data, dump_file);
+free:
+ free(data);
+ return err;
+}
+
+static int do_setfwdump(struct cmd_context *ctx)
+{
+ u32 dump_flag;
+ int err;
+ struct ethtool_dump dump;
+
+ if (ctx->argc != 1)
+ exit_bad_args();
+ dump_flag = get_u32(ctx->argp[0], 0);
+
+ dump.cmd = ETHTOOL_SET_DUMP;
+ dump.flag = dump_flag;
+ err = send_ioctl(ctx, &dump);
+ if (err < 0) {
+ perror("Can not set dump level");
+ return 1;
+ }
+ return 0;
+}
+
+static int do_gprivflags(struct cmd_context *ctx)
+{
+ struct ethtool_gstrings *strings;
+ struct ethtool_value flags;
+ unsigned int i;
+ int max_len = 0, cur_len, rc;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ strings = get_stringset(ctx, ETH_SS_PRIV_FLAGS,
+ offsetof(struct ethtool_drvinfo, n_priv_flags),
+ 1);
+ if (!strings) {
+ perror("Cannot get private flag names");
+ return 1;
+ }
+ if (strings->len == 0) {
+ fprintf(stderr, "No private flags defined\n");
+ rc = 1;
+ goto err;
+ }
+ if (strings->len > 32) {
+ /* ETHTOOL_GPFLAGS can only cover 32 flags */
+ fprintf(stderr, "Only showing first 32 private flags\n");
+ strings->len = 32;
+ }
+
+ flags.cmd = ETHTOOL_GPFLAGS;
+ if (send_ioctl(ctx, &flags)) {
+ perror("Cannot get private flags");
+ rc = 1;
+ goto err;
+ }
+
+ /* Find longest string and align all strings accordingly */
+ for (i = 0; i < strings->len; i++) {
+ cur_len = strlen((const char *)strings->data +
+ i * ETH_GSTRING_LEN);
+ if (cur_len > max_len)
+ max_len = cur_len;
+ }
+
+ printf("Private flags for %s:\n", ctx->devname);
+ for (i = 0; i < strings->len; i++)
+ printf("%-*s: %s\n",
+ max_len,
+ (const char *)strings->data + i * ETH_GSTRING_LEN,
+ (flags.data & (1U << i)) ? "on" : "off");
+
+ rc = 0;
+
+err:
+ free(strings);
+ return rc;
+}
+
+static int do_sprivflags(struct cmd_context *ctx)
+{
+ struct ethtool_gstrings *strings;
+ struct cmdline_info *cmdline;
+ struct ethtool_value flags;
+ u32 wanted_flags = 0, seen_flags = 0;
+ int any_changed, rc;
+ unsigned int i;
+
+ strings = get_stringset(ctx, ETH_SS_PRIV_FLAGS,
+ offsetof(struct ethtool_drvinfo, n_priv_flags),
+ 1);
+ if (!strings) {
+ perror("Cannot get private flag names");
+ return 1;
+ }
+ if (strings->len == 0) {
+ fprintf(stderr, "No private flags defined\n");
+ rc = 1;
+ goto err;
+ }
+ if (strings->len > 32) {
+ /* ETHTOOL_{G,S}PFLAGS can only cover 32 flags */
+ fprintf(stderr, "Only setting first 32 private flags\n");
+ strings->len = 32;
+ }
+
+ cmdline = calloc(strings->len, sizeof(*cmdline));
+ if (!cmdline) {
+ perror("Cannot parse arguments");
+ rc = 1;
+ goto err;
+ }
+ for (i = 0; i < strings->len; i++) {
+ cmdline[i].name = ((const char *)strings->data +
+ i * ETH_GSTRING_LEN);
+ cmdline[i].type = CMDL_FLAG;
+ cmdline[i].wanted_val = &wanted_flags;
+ cmdline[i].flag_val = 1U << i;
+ cmdline[i].seen_val = &seen_flags;
+ }
+ parse_generic_cmdline(ctx, &any_changed, cmdline, strings->len);
+ free(cmdline);
+
+ flags.cmd = ETHTOOL_GPFLAGS;
+ if (send_ioctl(ctx, &flags)) {
+ perror("Cannot get private flags");
+ rc = 1;
+ goto err;
+ }
+
+ flags.cmd = ETHTOOL_SPFLAGS;
+ flags.data = (flags.data & ~seen_flags) | wanted_flags;
+ if (send_ioctl(ctx, &flags)) {
+ perror("Cannot set private flags");
+ rc = 1;
+ goto err;
+ }
+
+ rc = 0;
+err:
+ free(strings);
+ return rc;
+}
+
+static int do_tsinfo(struct cmd_context *ctx)
+{
+ struct ethtool_ts_info info;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ fprintf(stdout, "Time stamping parameters for %s:\n", ctx->devname);
+ info.cmd = ETHTOOL_GET_TS_INFO;
+ if (send_ioctl(ctx, &info)) {
+ perror("Cannot get device time stamping settings");
+ return -1;
+ }
+ dump_tsinfo(&info);
+ return 0;
+}
+
+static int do_getmodule(struct cmd_context *ctx)
+{
+ struct ethtool_modinfo modinfo;
+ struct ethtool_eeprom *eeprom;
+ u32 geeprom_offset = 0;
+ u32 geeprom_length = 0;
+ int geeprom_changed = 0;
+ int geeprom_dump_raw = 0;
+ int geeprom_dump_hex = 0;
+ int geeprom_length_seen = 0;
+ int err;
+
+ struct cmdline_info cmdline_geeprom[] = {
+ {
+ .name = "offset",
+ .type = CMDL_U32,
+ .wanted_val = &geeprom_offset,
+ },
+ {
+ .name = "length",
+ .type = CMDL_U32,
+ .wanted_val = &geeprom_length,
+ .seen_val = &geeprom_length_seen,
+ },
+ {
+ .name = "raw",
+ .type = CMDL_BOOL,
+ .wanted_val = &geeprom_dump_raw,
+ },
+ {
+ .name = "hex",
+ .type = CMDL_BOOL,
+ .wanted_val = &geeprom_dump_hex,
+ },
+ };
+
+ parse_generic_cmdline(ctx, &geeprom_changed,
+ cmdline_geeprom, ARRAY_SIZE(cmdline_geeprom));
+
+ if (geeprom_dump_raw && geeprom_dump_hex) {
+ printf("Hex and raw dump cannot be specified together\n");
+ return 1;
+ }
+
+ modinfo.cmd = ETHTOOL_GMODULEINFO;
+ err = send_ioctl(ctx, &modinfo);
+ if (err < 0) {
+ perror("Cannot get module EEPROM information");
+ return 1;
+ }
+
+ if (!geeprom_length_seen)
+ geeprom_length = modinfo.eeprom_len;
+
+ if (modinfo.eeprom_len < geeprom_offset + geeprom_length)
+ geeprom_length = modinfo.eeprom_len - geeprom_offset;
+
+ eeprom = calloc(1, sizeof(*eeprom)+geeprom_length);
+ if (!eeprom) {
+ perror("Cannot allocate memory for Module EEPROM data");
+ return 1;
+ }
+
+ eeprom->cmd = ETHTOOL_GMODULEEEPROM;
+ eeprom->len = geeprom_length;
+ eeprom->offset = geeprom_offset;
+ err = send_ioctl(ctx, eeprom);
+ if (err < 0) {
+ int saved_errno = errno;
+
+ perror("Cannot get Module EEPROM data");
+ if (saved_errno == ENODEV || saved_errno == EIO ||
+ saved_errno == ENXIO)
+ fprintf(stderr, "SFP module not in cage?\n");
+ free(eeprom);
+ return 1;
+ }
+
+ /*
+ * SFF-8079 EEPROM layout contains the memory available at A0 address on
+ * the PHY EEPROM.
+ * SFF-8472 defines a virtual extension of the EEPROM, where the
+ * microcontroller on the SFP/SFP+ generates a page at the A2 address,
+ * which contains data relative to optical diagnostics.
+ * The current kernel implementation returns a blob, which contains:
+ * - ETH_MODULE_SFF_8079 => The A0 page only.
+ * - ETH_MODULE_SFF_8472 => The A0 and A2 page concatenated.
+ */
+ if (geeprom_dump_raw) {
+ fwrite(eeprom->data, 1, eeprom->len, stdout);
+ } else {
+ if (eeprom->offset != 0 ||
+ (eeprom->len != modinfo.eeprom_len)) {
+ geeprom_dump_hex = 1;
+ } else if (!geeprom_dump_hex) {
+ switch (modinfo.type) {
+#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
+ case ETH_MODULE_SFF_8079:
+ sff8079_show_all_ioctl(eeprom->data);
+ break;
+ case ETH_MODULE_SFF_8472:
+ sff8079_show_all_ioctl(eeprom->data);
+ sff8472_show_all(eeprom->data);
+ break;
+ case ETH_MODULE_SFF_8436:
+ case ETH_MODULE_SFF_8636:
+ sff8636_show_all_ioctl(eeprom->data,
+ modinfo.eeprom_len);
+ break;
+#endif
+ default:
+ geeprom_dump_hex = 1;
+ break;
+ }
+ }
+ if (geeprom_dump_hex)
+ dump_hex(stdout, eeprom->data,
+ eeprom->len, eeprom->offset);
+ }
+
+ free(eeprom);
+
+ return 0;
+}
+
+static int do_geee(struct cmd_context *ctx)
+{
+ struct ethtool_eee eeecmd;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ eeecmd.cmd = ETHTOOL_GEEE;
+ if (send_ioctl(ctx, &eeecmd)) {
+ perror("Cannot get EEE settings");
+ return 1;
+ }
+
+ fprintf(stdout, "EEE Settings for %s:\n", ctx->devname);
+ dump_eeecmd(&eeecmd);
+
+ return 0;
+}
+
+static int do_seee(struct cmd_context *ctx)
+{
+ int adv_c = -1, lpi_c = -1, lpi_time_c = -1, eee_c = -1;
+ int change = -1, change2 = 0;
+ struct ethtool_eee eeecmd;
+ struct cmdline_info cmdline_eee[] = {
+ {
+ .name = "advertise",
+ .type = CMDL_U32,
+ .wanted_val = &adv_c,
+ .ioctl_val = &eeecmd.advertised,
+ },
+ {
+ .name = "tx-lpi",
+ .type = CMDL_BOOL,
+ .wanted_val = &lpi_c,
+ .ioctl_val = &eeecmd.tx_lpi_enabled,
+ },
+ {
+ .name = "tx-timer",
+ .type = CMDL_U32,
+ .wanted_val = &lpi_time_c,
+ .ioctl_val = &eeecmd.tx_lpi_timer,
+ },
+ {
+ .name = "eee",
+ .type = CMDL_BOOL,
+ .wanted_val = &eee_c,
+ .ioctl_val = &eeecmd.eee_enabled,
+ },
+ };
+
+ if (ctx->argc == 0)
+ exit_bad_args();
+
+ parse_generic_cmdline(ctx, &change, cmdline_eee,
+ ARRAY_SIZE(cmdline_eee));
+
+ eeecmd.cmd = ETHTOOL_GEEE;
+ if (send_ioctl(ctx, &eeecmd)) {
+ perror("Cannot get EEE settings");
+ return 1;
+ }
+
+ do_generic_set(cmdline_eee, ARRAY_SIZE(cmdline_eee), &change2);
+
+ if (change2) {
+ eeecmd.cmd = ETHTOOL_SEEE;
+ if (send_ioctl(ctx, &eeecmd)) {
+ perror("Cannot set EEE settings");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* copy of net/ethtool/common.c */
+char
+tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
+ [ETHTOOL_ID_UNSPEC] = "Unspec",
+ [ETHTOOL_RX_COPYBREAK] = "rx-copybreak",
+ [ETHTOOL_TX_COPYBREAK] = "tx-copybreak",
+ [ETHTOOL_TX_COPYBREAK_BUF_SIZE] = "tx-buf-size",
+ [ETHTOOL_PFC_PREVENTION_TOUT] = "pfc-prevention-tout",
+};
+
+union ethtool_tunable_info_val {
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ int8_t s8;
+ int16_t s16;
+ int32_t s32;
+ int64_t s64;
+};
+
+struct ethtool_tunable_info {
+ enum tunable_id t_id;
+ enum tunable_type_id t_type_id;
+ size_t size;
+ cmdline_type_t type;
+ union ethtool_tunable_info_val wanted;
+ int seen;
+};
+
+static struct ethtool_tunable_info tunables_info[] = {
+ { .t_id = ETHTOOL_RX_COPYBREAK,
+ .t_type_id = ETHTOOL_TUNABLE_U32,
+ .size = sizeof(u32),
+ .type = CMDL_U32,
+ },
+ { .t_id = ETHTOOL_TX_COPYBREAK,
+ .t_type_id = ETHTOOL_TUNABLE_U32,
+ .size = sizeof(u32),
+ .type = CMDL_U32,
+ },
+ { .t_id = ETHTOOL_PFC_PREVENTION_TOUT,
+ .t_type_id = ETHTOOL_TUNABLE_U16,
+ .size = sizeof(u16),
+ .type = CMDL_U16,
+ },
+ { .t_id = ETHTOOL_TX_COPYBREAK_BUF_SIZE,
+ .t_type_id = ETHTOOL_TUNABLE_U32,
+ .size = sizeof(u32),
+ .type = CMDL_U32,
+ },
+};
+#define TUNABLES_INFO_SIZE ARRAY_SIZE(tunables_info)
+
+static int do_stunable(struct cmd_context *ctx)
+{
+ struct cmdline_info cmdline_tunable[TUNABLES_INFO_SIZE];
+ struct ethtool_tunable_info *tinfo = tunables_info;
+ int changed = 0;
+ unsigned int i;
+
+ for (i = 0; i < TUNABLES_INFO_SIZE; i++) {
+ cmdline_tunable[i].name = tunable_strings[tinfo[i].t_id];
+ cmdline_tunable[i].type = tinfo[i].type;
+ cmdline_tunable[i].wanted_val = &tinfo[i].wanted;
+ cmdline_tunable[i].seen_val = &tinfo[i].seen;
+ }
+
+ parse_generic_cmdline(ctx, &changed, cmdline_tunable, TUNABLES_INFO_SIZE);
+ if (!changed)
+ exit_bad_args();
+
+ for (i = 0; i < TUNABLES_INFO_SIZE; i++) {
+ struct ethtool_tunable *tuna;
+ size_t size;
+ int ret;
+
+ if (!tinfo[i].seen)
+ continue;
+
+ size = sizeof(*tuna) + tinfo[i].size;
+ tuna = calloc(1, size);
+ if (!tuna) {
+ perror(tunable_strings[tinfo[i].t_id]);
+ return 1;
+ }
+ tuna->cmd = ETHTOOL_STUNABLE;
+ tuna->id = tinfo[i].t_id;
+ tuna->type_id = tinfo[i].t_type_id;
+ tuna->len = tinfo[i].size;
+ memcpy(tuna->data, &tinfo[i].wanted, tuna->len);
+ ret = send_ioctl(ctx, tuna);
+ if (ret) {
+ perror(tunable_strings[tuna->id]);
+ free(tuna);
+ return ret;
+ }
+ free(tuna);
+ }
+ return 0;
+}
+
+static void print_tunable(struct ethtool_tunable *tuna)
+{
+ char *name = tunable_strings[tuna->id];
+ union ethtool_tunable_info_val *val;
+
+ val = (union ethtool_tunable_info_val *)tuna->data;
+ switch (tuna->type_id) {
+ case ETHTOOL_TUNABLE_U8:
+ fprintf(stdout, "%s: %" PRIu8 "\n", name, val->u8);
+ break;
+ case ETHTOOL_TUNABLE_U16:
+ fprintf(stdout, "%s: %" PRIu16 "\n", name, val->u16);
+ break;
+ case ETHTOOL_TUNABLE_U32:
+ fprintf(stdout, "%s: %" PRIu32 "\n", name, val->u32);
+ break;
+ case ETHTOOL_TUNABLE_U64:
+ fprintf(stdout, "%s: %" PRIu64 "\n", name, val->u64);
+ break;
+ case ETHTOOL_TUNABLE_S8:
+ fprintf(stdout, "%s: %" PRId8 "\n", name, val->s8);
+ break;
+ case ETHTOOL_TUNABLE_S16:
+ fprintf(stdout, "%s: %" PRId16 "\n", name, val->s16);
+ break;
+ case ETHTOOL_TUNABLE_S32:
+ fprintf(stdout, "%s: %" PRId32 "\n", name, val->s32);
+ break;
+ case ETHTOOL_TUNABLE_S64:
+ fprintf(stdout, "%s: %" PRId64 "\n", name, val->s64);
+ break;
+ default:
+ fprintf(stdout, "%s: Unknown format\n", name);
+ }
+}
+
+static int do_gtunable(struct cmd_context *ctx)
+{
+ struct ethtool_tunable_info *tinfo = tunables_info;
+ char **argp = ctx->argp;
+ unsigned int argc = ctx->argc;
+ unsigned int i, j;
+
+ if (argc < 1)
+ exit_bad_args();
+
+ for (i = 0; i < argc; i++) {
+ int valid = 0;
+
+ for (j = 0; j < TUNABLES_INFO_SIZE; j++) {
+ char *ts = tunable_strings[tinfo[j].t_id];
+ struct ethtool_tunable *tuna;
+ int ret;
+
+ if (strcmp(argp[i], ts))
+ continue;
+ valid = 1;
+
+ tuna = calloc(1, sizeof(*tuna) + tinfo[j].size);
+ if (!tuna) {
+ perror(ts);
+ return 1;
+ }
+ tuna->cmd = ETHTOOL_GTUNABLE;
+ tuna->id = tinfo[j].t_id;
+ tuna->type_id = tinfo[j].t_type_id;
+ tuna->len = tinfo[j].size;
+ ret = send_ioctl(ctx, tuna);
+ if (ret) {
+ fprintf(stderr, "%s: Cannot get tunable\n", ts);
+ free(tuna);
+ return ret;
+ }
+ print_tunable(tuna);
+ free(tuna);
+ }
+ if (!valid)
+ exit_bad_args();
+ }
+ return 0;
+}
+
+static int do_get_phy_tunable(struct cmd_context *ctx)
+{
+ unsigned int argc = ctx->argc;
+ char **argp = ctx->argp;
+
+ if (argc < 1)
+ exit_bad_args();
+
+ if (!strcmp(argp[0], "downshift")) {
+ struct {
+ struct ethtool_tunable ds;
+ u8 count;
+ } cont;
+
+ cont.ds.cmd = ETHTOOL_PHY_GTUNABLE;
+ cont.ds.id = ETHTOOL_PHY_DOWNSHIFT;
+ cont.ds.type_id = ETHTOOL_TUNABLE_U8;
+ cont.ds.len = 1;
+ if (send_ioctl(ctx, &cont.ds) < 0) {
+ perror("Cannot Get PHY downshift count");
+ return 87;
+ }
+ if (cont.count)
+ fprintf(stdout, "Downshift count: %d\n", cont.count);
+ else
+ fprintf(stdout, "Downshift disabled\n");
+ } else if (!strcmp(argp[0], "fast-link-down")) {
+ struct {
+ struct ethtool_tunable fld;
+ u8 msecs;
+ } cont;
+
+ cont.fld.cmd = ETHTOOL_PHY_GTUNABLE;
+ cont.fld.id = ETHTOOL_PHY_FAST_LINK_DOWN;
+ cont.fld.type_id = ETHTOOL_TUNABLE_U8;
+ cont.fld.len = 1;
+ if (send_ioctl(ctx, &cont.fld) < 0) {
+ perror("Cannot Get PHY Fast Link Down value");
+ return 87;
+ }
+
+ if (cont.msecs == ETHTOOL_PHY_FAST_LINK_DOWN_ON)
+ fprintf(stdout, "Fast Link Down enabled\n");
+ else if (cont.msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
+ fprintf(stdout, "Fast Link Down disabled\n");
+ else
+ fprintf(stdout, "Fast Link Down enabled, %d msecs\n",
+ cont.msecs);
+ } else if (!strcmp(argp[0], "energy-detect-power-down")) {
+ struct {
+ struct ethtool_tunable ds;
+ u16 msecs;
+ } cont;
+
+ cont.ds.cmd = ETHTOOL_PHY_GTUNABLE;
+ cont.ds.id = ETHTOOL_PHY_EDPD;
+ cont.ds.type_id = ETHTOOL_TUNABLE_U16;
+ cont.ds.len = 2;
+ if (send_ioctl(ctx, &cont.ds) < 0) {
+ perror("Cannot Get PHY Energy Detect Power Down value");
+ return 87;
+ }
+
+ if (cont.msecs == ETHTOOL_PHY_EDPD_DISABLE)
+ fprintf(stdout, "Energy Detect Power Down: disabled\n");
+ else if (cont.msecs == ETHTOOL_PHY_EDPD_NO_TX)
+ fprintf(stdout,
+ "Energy Detect Power Down: enabled, TX disabled\n");
+ else
+ fprintf(stdout,
+ "Energy Detect Power Down: enabled, TX %u msecs\n",
+ cont.msecs);
+ } else {
+ exit_bad_args();
+ }
+
+ return 0;
+}
+
+static __u32 parse_reset(char *val, __u32 bitset, char *arg, __u32 *data)
+{
+ __u32 bitval = 0;
+ int i;
+
+ /* Check for component match */
+ for (i = 0; val[i] != '\0'; i++)
+ if (arg[i] != val[i])
+ return 0;
+
+ /* Check if component has -shared specified or not */
+ if (arg[i] == '\0')
+ bitval = bitset;
+ else if (!strcmp(arg+i, "-shared"))
+ bitval = bitset << ETH_RESET_SHARED_SHIFT;
+
+ if (bitval) {
+ *data |= bitval;
+ return 1;
+ }
+ return 0;
+}
+
+static int do_reset(struct cmd_context *ctx)
+{
+ struct ethtool_value resetinfo;
+ __u32 data;
+ unsigned int argc = ctx->argc;
+ char **argp = ctx->argp;
+ unsigned int i;
+
+ if (argc == 0)
+ exit_bad_args();
+
+ data = 0;
+
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argp[i], "flags")) {
+ __u32 flags;
+
+ i++;
+ if (i >= argc)
+ exit_bad_args();
+ flags = strtoul(argp[i], NULL, 0);
+ if (flags == 0)
+ exit_bad_args();
+ else
+ data |= flags;
+ } else if (parse_reset("mgmt", ETH_RESET_MGMT,
+ argp[i], &data)) {
+ } else if (parse_reset("irq", ETH_RESET_IRQ,
+ argp[i], &data)) {
+ } else if (parse_reset("dma", ETH_RESET_DMA,
+ argp[i], &data)) {
+ } else if (parse_reset("filter", ETH_RESET_FILTER,
+ argp[i], &data)) {
+ } else if (parse_reset("offload", ETH_RESET_OFFLOAD,
+ argp[i], &data)) {
+ } else if (parse_reset("mac", ETH_RESET_MAC,
+ argp[i], &data)) {
+ } else if (parse_reset("phy", ETH_RESET_PHY,
+ argp[i], &data)) {
+ } else if (parse_reset("ram", ETH_RESET_RAM,
+ argp[i], &data)) {
+ } else if (parse_reset("ap", ETH_RESET_AP,
+ argp[i], &data)) {
+ } else if (!strcmp(argp[i], "dedicated")) {
+ data |= ETH_RESET_DEDICATED;
+ } else if (!strcmp(argp[i], "all")) {
+ data |= ETH_RESET_ALL;
+ } else {
+ exit_bad_args();
+ }
+ }
+
+ resetinfo.cmd = ETHTOOL_RESET;
+ resetinfo.data = data;
+ fprintf(stdout, "ETHTOOL_RESET 0x%x\n", resetinfo.data);
+
+ if (send_ioctl(ctx, &resetinfo)) {
+ perror("Cannot issue ETHTOOL_RESET");
+ return 1;
+ }
+
+ fprintf(stdout, "Components reset: 0x%x\n", data & ~resetinfo.data);
+ if (resetinfo.data)
+ fprintf(stdout, "Components not reset: 0x%x\n", resetinfo.data);
+
+ return 0;
+}
+
+static int parse_named_bool(struct cmd_context *ctx, const char *name, u8 *on)
+{
+ if (ctx->argc < 2)
+ return 0;
+
+ if (strcmp(*ctx->argp, name))
+ return 0;
+
+ if (!strcmp(*(ctx->argp + 1), "on")) {
+ *on = 1;
+ } else if (!strcmp(*(ctx->argp + 1), "off")) {
+ *on = 0;
+ } else {
+ fprintf(stderr, "Invalid boolean\n");
+ exit_bad_args();
+ }
+
+ ctx->argc -= 2;
+ ctx->argp += 2;
+
+ return 1;
+}
+
+static int parse_named_uint(struct cmd_context *ctx,
+ const char *name,
+ unsigned long long *val,
+ unsigned long long max)
+{
+ if (ctx->argc < 2)
+ return 0;
+
+ if (strcmp(*ctx->argp, name))
+ return 0;
+
+ *val = get_uint_range(*(ctx->argp + 1), 0, max);
+
+ ctx->argc -= 2;
+ ctx->argp += 2;
+
+ return 1;
+}
+
+static int parse_named_u8(struct cmd_context *ctx, const char *name, u8 *val)
+{
+ unsigned long long val1;
+ int ret;
+
+ ret = parse_named_uint(ctx, name, &val1, 0xff);
+ if (ret)
+ *val = val1;
+
+ return ret;
+}
+
+static int parse_named_u16(struct cmd_context *ctx, const char *name, u16 *val)
+{
+ unsigned long long val1;
+ int ret;
+
+ ret = parse_named_uint(ctx, name, &val1, 0xffff);
+ if (ret)
+ *val = val1;
+
+ return ret;
+}
+
+static int do_set_phy_tunable(struct cmd_context *ctx)
+{
+ int err = 0;
+ u8 ds_cnt = DOWNSHIFT_DEV_DEFAULT_COUNT;
+ u8 ds_changed = 0, ds_has_cnt = 0, ds_enable = 0;
+ u8 fld_changed = 0, fld_enable = 0;
+ u8 fld_msecs = ETHTOOL_PHY_FAST_LINK_DOWN_ON;
+ u8 edpd_changed = 0, edpd_enable = 0;
+ u16 edpd_tx_interval = ETHTOOL_PHY_EDPD_DFLT_TX_MSECS;
+
+ /* Parse arguments */
+ if (parse_named_bool(ctx, "downshift", &ds_enable)) {
+ ds_changed = 1;
+ ds_has_cnt = parse_named_u8(ctx, "count", &ds_cnt);
+ } else if (parse_named_bool(ctx, "fast-link-down", &fld_enable)) {
+ fld_changed = 1;
+ if (fld_enable)
+ parse_named_u8(ctx, "msecs", &fld_msecs);
+ } else if (parse_named_bool(ctx, "energy-detect-power-down",
+ &edpd_enable)) {
+ edpd_changed = 1;
+ if (edpd_enable)
+ parse_named_u16(ctx, "msecs", &edpd_tx_interval);
+ } else {
+ exit_bad_args();
+ }
+
+ /* Validate parameters */
+ if (ds_changed) {
+ if (!ds_enable && ds_has_cnt) {
+ fprintf(stderr, "'count' may not be set when downshift "
+ "is off.\n");
+ exit_bad_args();
+ }
+
+ if (ds_enable && ds_has_cnt && ds_cnt == 0) {
+ fprintf(stderr, "'count' may not be zero.\n");
+ exit_bad_args();
+ }
+
+ if (!ds_enable)
+ ds_cnt = DOWNSHIFT_DEV_DISABLE;
+ } else if (fld_changed) {
+ if (!fld_enable)
+ fld_msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF;
+ else if (fld_msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
+ exit_bad_args();
+ } else if (edpd_changed) {
+ if (!edpd_enable)
+ edpd_tx_interval = ETHTOOL_PHY_EDPD_DISABLE;
+ else if (edpd_tx_interval == 0)
+ edpd_tx_interval = ETHTOOL_PHY_EDPD_NO_TX;
+ else if (edpd_tx_interval > ETHTOOL_PHY_EDPD_NO_TX) {
+ fprintf(stderr, "'msecs' max value is %d.\n",
+ (ETHTOOL_PHY_EDPD_NO_TX - 1));
+ exit_bad_args();
+ }
+ }
+
+ /* Do it */
+ if (ds_changed) {
+ struct {
+ struct ethtool_tunable ds;
+ u8 count;
+ } cont;
+
+ cont.ds.cmd = ETHTOOL_PHY_STUNABLE;
+ cont.ds.id = ETHTOOL_PHY_DOWNSHIFT;
+ cont.ds.type_id = ETHTOOL_TUNABLE_U8;
+ cont.ds.len = 1;
+ cont.count = ds_cnt;
+ err = send_ioctl(ctx, &cont.ds);
+ if (err < 0) {
+ perror("Cannot Set PHY downshift count");
+ err = 87;
+ }
+ } else if (fld_changed) {
+ struct {
+ struct ethtool_tunable fld;
+ u8 msecs;
+ } cont;
+
+ cont.fld.cmd = ETHTOOL_PHY_STUNABLE;
+ cont.fld.id = ETHTOOL_PHY_FAST_LINK_DOWN;
+ cont.fld.type_id = ETHTOOL_TUNABLE_U8;
+ cont.fld.len = 1;
+ cont.msecs = fld_msecs;
+ err = send_ioctl(ctx, &cont.fld);
+ if (err < 0) {
+ perror("Cannot Set PHY Fast Link Down value");
+ err = 87;
+ }
+ } else if (edpd_changed) {
+ struct {
+ struct ethtool_tunable fld;
+ u16 msecs;
+ } cont;
+
+ cont.fld.cmd = ETHTOOL_PHY_STUNABLE;
+ cont.fld.id = ETHTOOL_PHY_EDPD;
+ cont.fld.type_id = ETHTOOL_TUNABLE_U16;
+ cont.fld.len = 2;
+ cont.msecs = edpd_tx_interval;
+ err = send_ioctl(ctx, &cont.fld);
+ if (err < 0) {
+ perror("Cannot Set PHY Energy Detect Power Down");
+ err = 87;
+ }
+ }
+
+ return err;
+}
+
+static int fecmode_str_to_type(const char *str)
+{
+ if (!strcasecmp(str, "auto"))
+ return ETHTOOL_FEC_AUTO;
+ if (!strcasecmp(str, "off"))
+ return ETHTOOL_FEC_OFF;
+ if (!strcasecmp(str, "rs"))
+ return ETHTOOL_FEC_RS;
+ if (!strcasecmp(str, "baser"))
+ return ETHTOOL_FEC_BASER;
+ if (!strcasecmp(str, "llrs"))
+ return ETHTOOL_FEC_LLRS;
+ return 0;
+}
+
+static int do_gfec(struct cmd_context *ctx)
+{
+ struct ethtool_fecparam feccmd = { 0 };
+ int rv;
+
+ if (ctx->argc != 0)
+ exit_bad_args();
+
+ feccmd.cmd = ETHTOOL_GFECPARAM;
+ rv = send_ioctl(ctx, &feccmd);
+ if (rv != 0) {
+ perror("Cannot get FEC settings");
+ return rv;
+ }
+
+ fprintf(stdout, "FEC parameters for %s:\n", ctx->devname);
+ fprintf(stdout, "Supported/Configured FEC encodings:");
+ dump_fec(feccmd.fec);
+ fprintf(stdout, "\n");
+
+ fprintf(stdout, "Active FEC encoding:");
+ dump_fec(feccmd.active_fec);
+ fprintf(stdout, "\n");
+
+ return 0;
+}
+
+static int do_sfec(struct cmd_context *ctx)
+{
+ enum { ARG_NONE, ARG_ENCODING } state = ARG_NONE;
+ struct ethtool_fecparam feccmd;
+ int fecmode = 0, newmode;
+ unsigned int i;
+ int rv;
+
+ for (i = 0; i < ctx->argc; i++) {
+ if (!strcmp(ctx->argp[i], "encoding")) {
+ state = ARG_ENCODING;
+ continue;
+ }
+ if (state == ARG_ENCODING) {
+ newmode = fecmode_str_to_type(ctx->argp[i]);
+ if (!newmode)
+ exit_bad_args();
+ fecmode |= newmode;
+ continue;
+ }
+ exit_bad_args();
+ }
+
+ if (!fecmode)
+ exit_bad_args();
+
+ feccmd.cmd = ETHTOOL_SFECPARAM;
+ feccmd.fec = fecmode;
+ rv = send_ioctl(ctx, &feccmd);
+ if (rv != 0) {
+ perror("Cannot set FEC settings");
+ return rv;
+ }
+
+ return 0;
+}
+
+static int do_perqueue(struct cmd_context *ctx);
+
+#ifndef TEST_ETHTOOL
+int send_ioctl(struct cmd_context *ctx, void *cmd)
+{
+ ctx->ifr.ifr_data = cmd;
+ return ioctl(ctx->fd, SIOCETHTOOL, &ctx->ifr);
+}
+#endif
+
+static int show_usage(struct cmd_context *ctx);
+
+struct option {
+ const char *opts;
+ bool no_dev;
+ bool json;
+ int (*func)(struct cmd_context *);
+ nl_chk_t nlchk;
+ nl_func_t nlfunc;
+ const char *help;
+ const char *xhelp;
+};
+
+static const struct option args[] = {
+ {
+ /* "default" entry when no switch is used */
+ .opts = "",
+ .func = do_gset,
+ .nlfunc = nl_gset,
+ .help = "Display standard information about device",
+ },
+ {
+ .opts = "-s|--change",
+ .func = do_sset,
+ .nlfunc = nl_sset,
+ .help = "Change generic options",
+ .xhelp = " [ speed %d ]\n"
+ " [ lanes %d ]\n"
+ " [ duplex half|full ]\n"
+ " [ port tp|aui|bnc|mii|fibre|da ]\n"
+ " [ mdix auto|on|off ]\n"
+ " [ autoneg on|off ]\n"
+ " [ advertise %x[/%x] | mode on|off ... [--] ]\n"
+ " [ phyad %d ]\n"
+ " [ xcvr internal|external ]\n"
+ " [ wol %d[/%d] | p|u|m|b|a|g|s|f|d... ]\n"
+ " [ sopass %x:%x:%x:%x:%x:%x ]\n"
+ " [ msglvl %d[/%d] | type on|off ... [--] ]\n"
+ " [ master-slave preferred-master|preferred-slave|forced-master|forced-slave ]\n"
+ },
+ {
+ .opts = "-a|--show-pause",
+ .json = true,
+ .func = do_gpause,
+ .nlfunc = nl_gpause,
+ .help = "Show pause options",
+ .xhelp = " [ --src aggregate | emac | pmac ]\n"
+ },
+ {
+ .opts = "-A|--pause",
+ .func = do_spause,
+ .nlfunc = nl_spause,
+ .help = "Set pause options",
+ .xhelp = " [ autoneg on|off ]\n"
+ " [ rx on|off ]\n"
+ " [ tx on|off ]\n"
+ },
+ {
+ .opts = "-c|--show-coalesce",
+ .json = true,
+ .func = do_gcoalesce,
+ .nlfunc = nl_gcoalesce,
+ .help = "Show coalesce options"
+ },
+ {
+ .opts = "-C|--coalesce",
+ .func = do_scoalesce,
+ .nlfunc = nl_scoalesce,
+ .help = "Set coalesce options",
+ .xhelp = " [adaptive-rx on|off]\n"
+ " [adaptive-tx on|off]\n"
+ " [rx-usecs N]\n"
+ " [rx-frames N]\n"
+ " [rx-usecs-irq N]\n"
+ " [rx-frames-irq N]\n"
+ " [tx-usecs N]\n"
+ " [tx-frames N]\n"
+ " [tx-usecs-irq N]\n"
+ " [tx-frames-irq N]\n"
+ " [stats-block-usecs N]\n"
+ " [pkt-rate-low N]\n"
+ " [rx-usecs-low N]\n"
+ " [rx-frames-low N]\n"
+ " [tx-usecs-low N]\n"
+ " [tx-frames-low N]\n"
+ " [pkt-rate-high N]\n"
+ " [rx-usecs-high N]\n"
+ " [rx-frames-high N]\n"
+ " [tx-usecs-high N]\n"
+ " [tx-frames-high N]\n"
+ " [sample-interval N]\n"
+ " [cqe-mode-rx on|off]\n"
+ " [cqe-mode-tx on|off]\n"
+ " [tx-aggr-max-bytes N]\n"
+ " [tx-aggr-max-frames N]\n"
+ " [tx-aggr-time-usecs N]\n"
+ },
+ {
+ .opts = "-g|--show-ring",
+ .json = true,
+ .func = do_gring,
+ .nlfunc = nl_gring,
+ .help = "Query RX/TX ring parameters"
+ },
+ {
+ .opts = "-G|--set-ring",
+ .func = do_sring,
+ .nlfunc = nl_sring,
+ .help = "Set RX/TX ring parameters",
+ .xhelp = " [ rx N ]\n"
+ " [ rx-mini N ]\n"
+ " [ rx-jumbo N ]\n"
+ " [ tx N ]\n"
+ " [ rx-buf-len N ]\n"
+ " [ tcp-data-split auto|on|off ]\n"
+ " [ cqe-size N ]\n"
+ " [ tx-push on|off ]\n"
+ " [ rx-push on|off ]\n"
+ " [ tx-push-buf-len N]\n"
+ },
+ {
+ .opts = "-k|--show-features|--show-offload",
+ .json = true,
+ .func = do_gfeatures,
+ .nlfunc = nl_gfeatures,
+ .help = "Get state of protocol offload and other features"
+ },
+ {
+ .opts = "-K|--features|--offload",
+ .func = do_sfeatures,
+ .nlfunc = nl_sfeatures,
+ .help = "Set protocol offload and other features",
+ .xhelp = " FEATURE on|off ...\n"
+ },
+ {
+ .opts = "-i|--driver",
+ .func = do_gdrv,
+ .help = "Show driver information"
+ },
+ {
+ .opts = "-d|--register-dump",
+ .func = do_gregs,
+ .help = "Do a register dump",
+ .xhelp = " [ raw on|off ]\n"
+ " [ file FILENAME ]\n"
+ },
+ {
+ .opts = "-e|--eeprom-dump",
+ .func = do_geeprom,
+ .help = "Do a EEPROM dump",
+ .xhelp = " [ raw on|off ]\n"
+ " [ offset N ]\n"
+ " [ length N ]\n"
+ },
+ {
+ .opts = "-E|--change-eeprom",
+ .func = do_seeprom,
+ .help = "Change bytes in device EEPROM",
+ .xhelp = " [ magic N ]\n"
+ " [ offset N ]\n"
+ " [ length N ]\n"
+ " [ value N ]\n"
+ },
+ {
+ .opts = "-r|--negotiate",
+ .func = do_nway_rst,
+ .help = "Restart N-WAY negotiation"
+ },
+ {
+ .opts = "-p|--identify",
+ .func = do_phys_id,
+ .help = "Show visible port identification (e.g. blinking)",
+ .xhelp = " [ TIME-IN-SECONDS ]\n"
+ },
+ {
+ .opts = "-t|--test",
+ .func = do_test,
+ .help = "Execute adapter self test",
+ .xhelp = " [ online | offline | external_lb ]\n"
+ },
+ {
+ .opts = "-S|--statistics",
+ .json = true,
+ .func = do_gnicstats,
+ .nlchk = nl_gstats_chk,
+ .nlfunc = nl_gstats,
+ .help = "Show adapter statistics",
+ .xhelp = " [ --all-groups | --groups [eth-phy] [eth-mac] [eth-ctrl] [rmon] ]\n"
+ " [ --src aggregate | emac | pmac ]\n"
+ },
+ {
+ .opts = "--phy-statistics",
+ .func = do_gphystats,
+ .help = "Show phy statistics"
+ },
+ {
+ .opts = "-n|-u|--show-nfc|--show-ntuple",
+ .func = do_grxclass,
+ .help = "Show Rx network flow classification options or rules",
+ .xhelp = " [ rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
+ "tcp6|udp6|ah6|esp6|sctp6 [context %d] |\n"
+ " rule %d ]\n"
+ },
+ {
+ .opts = "-N|-U|--config-nfc|--config-ntuple",
+ .func = do_srxclass,
+ .help = "Configure Rx network flow classification options or rules",
+ .xhelp = " rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
+ "tcp6|udp6|ah6|esp6|sctp6 m|v|t|s|d|f|n|r... [context %d] |\n"
+ " flow-type ether|ip4|tcp4|udp4|sctp4|ah4|esp4|"
+ "ip6|tcp6|udp6|ah6|esp6|sctp6\n"
+ " [ src %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
+ " [ dst %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
+ " [ proto %d [m %x] ]\n"
+ " [ src-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
+ " [ dst-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
+ " [ tos %d [m %x] ]\n"
+ " [ tclass %d [m %x] ]\n"
+ " [ l4proto %d [m %x] ]\n"
+ " [ src-port %d [m %x] ]\n"
+ " [ dst-port %d [m %x] ]\n"
+ " [ spi %d [m %x] ]\n"
+ " [ vlan-etype %x [m %x] ]\n"
+ " [ vlan %x [m %x] ]\n"
+ " [ user-def %x [m %x] ]\n"
+ " [ dst-mac %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
+ " [ action %d ] | [ vf %d queue %d ]\n"
+ " [ context %d ]\n"
+ " [ loc %d ] |\n"
+ " delete %d\n"
+ },
+ {
+ .opts = "-T|--show-time-stamping",
+ .func = do_tsinfo,
+ .nlfunc = nl_tsinfo,
+ .help = "Show time stamping capabilities"
+ },
+ {
+ .opts = "-x|--show-rxfh-indir|--show-rxfh",
+ .json = true,
+ .func = do_grxfh,
+ .nlfunc = nl_grss,
+ .help = "Show Rx flow hash indirection table and/or RSS hash key",
+ .xhelp = " [ context %d ]\n"
+ },
+ {
+ .opts = "-X|--set-rxfh-indir|--rxfh",
+ .func = do_srxfh,
+ .help = "Set Rx flow hash indirection table and/or RSS hash key",
+ .xhelp = " [ context %d|new ]\n"
+ " [ equal N | weight W0 W1 ... | default ]\n"
+ " [ hkey %x:%x:%x:%x:%x:.... ]\n"
+ " [ hfunc FUNC ]\n"
+ " [ delete ]\n"
+ },
+ {
+ .opts = "-f|--flash",
+ .func = do_flash,
+ .help = "Flash firmware image from the specified file to a region on the device",
+ .xhelp = " FILENAME [ REGION-NUMBER-TO-FLASH ]\n"
+ },
+ {
+ .opts = "-P|--show-permaddr",
+ .func = do_permaddr,
+ .nlfunc = nl_permaddr,
+ .help = "Show permanent hardware address"
+ },
+ {
+ .opts = "-w|--get-dump",
+ .func = do_getfwdump,
+ .help = "Get dump flag, data",
+ .xhelp = " [ data FILENAME ]\n"
+ },
+ {
+ .opts = "-W|--set-dump",
+ .func = do_setfwdump,
+ .help = "Set dump flag of the device",
+ .xhelp = " N\n"
+ },
+ {
+ .opts = "-l|--show-channels",
+ .func = do_gchannels,
+ .nlfunc = nl_gchannels,
+ .help = "Query Channels"
+ },
+ {
+ .opts = "-L|--set-channels",
+ .func = do_schannels,
+ .nlfunc = nl_schannels,
+ .help = "Set Channels",
+ .xhelp = " [ rx N ]\n"
+ " [ tx N ]\n"
+ " [ other N ]\n"
+ " [ combined N ]\n"
+ },
+ {
+ .opts = "--show-priv-flags",
+ .func = do_gprivflags,
+ .nlfunc = nl_gprivflags,
+ .help = "Query private flags"
+ },
+ {
+ .opts = "--set-priv-flags",
+ .func = do_sprivflags,
+ .nlfunc = nl_sprivflags,
+ .help = "Set private flags",
+ .xhelp = " FLAG on|off ...\n"
+ },
+ {
+ .opts = "-m|--dump-module-eeprom|--module-info",
+ .func = do_getmodule,
+ .nlfunc = nl_getmodule,
+ .help = "Query/Decode Module EEPROM information and optical diagnostics if available",
+ .xhelp = " [ raw on|off ]\n"
+ " [ hex on|off ]\n"
+ " [ offset N ]\n"
+ " [ length N ]\n"
+ " [ page N ]\n"
+ " [ bank N ]\n"
+ " [ i2c N ]\n"
+ },
+ {
+ .opts = "--show-eee",
+ .func = do_geee,
+ .nlfunc = nl_geee,
+ .help = "Show EEE settings",
+ },
+ {
+ .opts = "--set-eee",
+ .func = do_seee,
+ .nlfunc = nl_seee,
+ .help = "Set EEE settings",
+ .xhelp = " [ eee on|off ]\n"
+ " [ advertise %x ]\n"
+ " [ tx-lpi on|off ]\n"
+ " [ tx-timer %d ]\n"
+ },
+ {
+ .opts = "--set-phy-tunable",
+ .func = do_set_phy_tunable,
+ .help = "Set PHY tunable",
+ .xhelp = " [ downshift on|off [count N] ]\n"
+ " [ fast-link-down on|off [msecs N] ]\n"
+ " [ energy-detect-power-down on|off [msecs N] ]\n"
+ },
+ {
+ .opts = "--get-phy-tunable",
+ .func = do_get_phy_tunable,
+ .help = "Get PHY tunable",
+ .xhelp = " [ downshift ]\n"
+ " [ fast-link-down ]\n"
+ " [ energy-detect-power-down ]\n"
+ },
+ {
+ .opts = "--get-tunable",
+ .func = do_gtunable,
+ .help = "Get tunable",
+ .xhelp = " [ rx-copybreak ]\n"
+ " [ tx-copybreak ]\n"
+ " [ tx-buf-size ]\n"
+ " [ pfc-prevention-tout ]\n"
+ },
+ {
+ .opts = "--set-tunable",
+ .func = do_stunable,
+ .help = "Set tunable",
+ .xhelp = " [ rx-copybreak N ]\n"
+ " [ tx-copybreak N ]\n"
+ " [ tx-buf-size N ]\n"
+ " [ pfc-prevention-tout N ]\n"
+ },
+ {
+ .opts = "--reset",
+ .func = do_reset,
+ .help = "Reset components",
+ .xhelp = " [ flags %x ]\n"
+ " [ mgmt ]\n"
+ " [ mgmt-shared ]\n"
+ " [ irq ]\n"
+ " [ irq-shared ]\n"
+ " [ dma ]\n"
+ " [ dma-shared ]\n"
+ " [ filter ]\n"
+ " [ filter-shared ]\n"
+ " [ offload ]\n"
+ " [ offload-shared ]\n"
+ " [ mac ]\n"
+ " [ mac-shared ]\n"
+ " [ phy ]\n"
+ " [ phy-shared ]\n"
+ " [ ram ]\n"
+ " [ ram-shared ]\n"
+ " [ ap ]\n"
+ " [ ap-shared ]\n"
+ " [ dedicated ]\n"
+ " [ all ]\n"
+ },
+ {
+ .opts = "--show-fec",
+ .json = true,
+ .func = do_gfec,
+ .nlfunc = nl_gfec,
+ .help = "Show FEC settings",
+ },
+ {
+ .opts = "--set-fec",
+ .func = do_sfec,
+ .nlfunc = nl_sfec,
+ .help = "Set FEC settings",
+ .xhelp = " [ encoding auto|off|rs|baser|llrs [...] ]\n"
+ },
+ {
+ .opts = "-Q|--per-queue",
+ .func = do_perqueue,
+ .help = "Apply per-queue command. ",
+ .xhelp = "The supported sub commands include --show-coalesce, --coalesce"
+ " [queue_mask %x] SUB_COMMAND\n",
+ },
+ {
+ .opts = "--cable-test",
+ .json = true,
+ .nlfunc = nl_cable_test,
+ .help = "Perform a cable test",
+ },
+ {
+ .opts = "--cable-test-tdr",
+ .json = true,
+ .nlfunc = nl_cable_test_tdr,
+ .help = "Print cable test time domain reflectrometery data",
+ .xhelp = " [ first N ]\n"
+ " [ last N ]\n"
+ " [ step N ]\n"
+ " [ pair N ]\n"
+ },
+ {
+ .opts = "--show-tunnels",
+ .nlfunc = nl_gtunnels,
+ .help = "Show NIC tunnel offload information",
+ },
+ {
+ .opts = "--show-module",
+ .json = true,
+ .nlfunc = nl_gmodule,
+ .help = "Show transceiver module settings",
+ },
+ {
+ .opts = "--set-module",
+ .nlfunc = nl_smodule,
+ .help = "Set transceiver module settings",
+ .xhelp = " [ power-mode-policy high|auto ]\n"
+ },
+ {
+ .opts = "--get-plca-cfg",
+ .nlfunc = nl_plca_get_cfg,
+ .help = "Get PLCA configuration",
+ },
+ {
+ .opts = "--set-plca-cfg",
+ .nlfunc = nl_plca_set_cfg,
+ .help = "Set PLCA configuration",
+ .xhelp = " [ enable on|off ]\n"
+ " [ node-id N ]\n"
+ " [ node-cnt N ]\n"
+ " [ to-tmr N ]\n"
+ " [ burst-cnt N ]\n"
+ " [ burst-tmr N ]\n"
+ },
+ {
+ .opts = "--get-plca-status",
+ .nlfunc = nl_plca_get_status,
+ .help = "Get PLCA status information",
+ },
+ {
+ .opts = "--show-mm",
+ .json = true,
+ .nlfunc = nl_get_mm,
+ .help = "Show MAC merge layer state",
+ },
+ {
+ .opts = "--set-mm",
+ .nlfunc = nl_set_mm,
+ .help = "Set MAC merge layer parameters",
+ " [ verify-enabled on|off ]\n"
+ " [ verify-time N ]\n"
+ " [ tx-enabled on|off ]\n"
+ " [ pmac-enabled on|off ]\n"
+ " [ tx-min-frag-size 60-252 ]\n"
+ },
+ {
+ .opts = "--show-pse",
+ .json = true,
+ .nlfunc = nl_gpse,
+ .help = "Show settings for Power Sourcing Equipment",
+ },
+ {
+ .opts = "--set-pse",
+ .nlfunc = nl_spse,
+ .help = "Set Power Sourcing Equipment settings",
+ .xhelp = " [ podl-pse-admin-control enable|disable ]\n"
+ },
+ {
+ .opts = "-h|--help",
+ .no_dev = true,
+ .func = show_usage,
+ .help = "Show this help"
+ },
+ {
+ .opts = "--version",
+ .no_dev = true,
+ .func = do_version,
+ .help = "Show version number"
+ },
+ {}
+};
+
+static int show_usage(struct cmd_context *ctx __maybe_unused)
+{
+ int i;
+
+ /* ethtool -h */
+ fprintf(stdout, PACKAGE " version " VERSION "\n");
+ fprintf(stdout, "Usage:\n");
+ for (i = 0; args[i].opts; i++) {
+ fputs(" ethtool [ FLAGS ] ", stdout);
+ fprintf(stdout, "%s %s\t%s\n",
+ args[i].opts,
+ args[i].no_dev ? "\t" : "DEVNAME",
+ args[i].help);
+ if (args[i].xhelp)
+ fputs(args[i].xhelp, stdout);
+ }
+ nl_monitor_usage();
+ fprintf(stdout, "\n");
+ fprintf(stdout, "FLAGS:\n");
+ fprintf(stdout, " --debug MASK turn on debugging messages\n");
+ fprintf(stdout, " --json enable JSON output format (not supported by all commands)\n");
+ fprintf(stdout, " -I|--include-statistics request device statistics related to the command (not supported by all commands)\n");
+
+ return 0;
+}
+
+static int find_option(char *arg)
+{
+ const char *opt;
+ size_t len;
+ int k;
+
+ for (k = 1; args[k].opts; k++) {
+ opt = args[k].opts;
+ for (;;) {
+ len = strcspn(opt, "|");
+ if (strncmp(arg, opt, len) == 0 && arg[len] == 0)
+ return k;
+
+ if (opt[len] == 0)
+ break;
+ opt += len + 1;
+ }
+ }
+
+ return -1;
+}
+
+#define MAX(x, y) (x > y ? x : y)
+
+static int find_max_num_queues(struct cmd_context *ctx)
+{
+ struct ethtool_channels echannels;
+
+ echannels.cmd = ETHTOOL_GCHANNELS;
+ if (send_ioctl(ctx, &echannels))
+ return -1;
+
+ return MAX(echannels.rx_count, echannels.tx_count) +
+ echannels.combined_count;
+}
+
+static struct ethtool_per_queue_op *
+get_per_queue_coalesce(struct cmd_context *ctx, __u32 *queue_mask, int n_queues)
+{
+ struct ethtool_per_queue_op *per_queue_opt;
+
+ per_queue_opt = malloc(sizeof(*per_queue_opt) + n_queues *
+ sizeof(struct ethtool_coalesce));
+ if (!per_queue_opt)
+ return NULL;
+
+ memcpy(per_queue_opt->queue_mask, queue_mask,
+ __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32) * sizeof(__u32));
+ per_queue_opt->cmd = ETHTOOL_PERQUEUE;
+ per_queue_opt->sub_command = ETHTOOL_GCOALESCE;
+ if (send_ioctl(ctx, per_queue_opt)) {
+ free(per_queue_opt);
+ perror("Cannot get device per queue parameters");
+ return NULL;
+ }
+
+ return per_queue_opt;
+}
+
+static void set_per_queue_coalesce(struct cmd_context *ctx,
+ struct ethtool_per_queue_op *per_queue_opt,
+ int n_queues)
+{
+ struct ethtool_coalesce ecoal;
+ DECLARE_COALESCE_OPTION_VARS();
+ struct cmdline_info cmdline_coalesce[] = COALESCE_CMDLINE_INFO(ecoal);
+ __u32 *queue_mask = per_queue_opt->queue_mask;
+ struct ethtool_coalesce *ecoal_q;
+ int gcoalesce_changed = 0;
+ int i, idx = 0;
+
+ parse_generic_cmdline(ctx, &gcoalesce_changed,
+ cmdline_coalesce, ARRAY_SIZE(cmdline_coalesce));
+
+ ecoal_q = (struct ethtool_coalesce *)(per_queue_opt + 1);
+ for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); i++) {
+ int queue = i * 32;
+ __u32 mask = queue_mask[i];
+
+ while (mask > 0) {
+ if (mask & 0x1) {
+ int changed = 0;
+
+ memcpy(&ecoal, ecoal_q + idx,
+ sizeof(struct ethtool_coalesce));
+ do_generic_set(cmdline_coalesce,
+ ARRAY_SIZE(cmdline_coalesce),
+ &changed);
+ if (!changed)
+ fprintf(stderr,
+ "Queue %d, no coalesce parameters changed\n",
+ queue);
+ memcpy(ecoal_q + idx, &ecoal,
+ sizeof(struct ethtool_coalesce));
+ idx++;
+ }
+ mask = mask >> 1;
+ queue++;
+ }
+ if (idx == n_queues)
+ break;
+ }
+
+ per_queue_opt->cmd = ETHTOOL_PERQUEUE;
+ per_queue_opt->sub_command = ETHTOOL_SCOALESCE;
+
+ if (send_ioctl(ctx, per_queue_opt))
+ perror("Cannot set device per queue parameters");
+}
+
+static int do_perqueue(struct cmd_context *ctx)
+{
+ struct ethtool_per_queue_op *per_queue_opt;
+ __u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)] = {0};
+ int i, n_queues = 0;
+
+ if (ctx->argc == 0)
+ exit_bad_args();
+
+ /*
+ * The sub commands will be applied to
+ * all queues if no queue_mask set
+ */
+ if (strncmp(*ctx->argp, "queue_mask", 11)) {
+ n_queues = find_max_num_queues(ctx);
+ if (n_queues < 0) {
+ perror("Cannot get number of queues");
+ return -EFAULT;
+ } else if (n_queues > MAX_NUM_QUEUE) {
+ n_queues = MAX_NUM_QUEUE;
+ }
+ for (i = 0; i < n_queues / 32; i++)
+ queue_mask[i] = ~0;
+ if (n_queues % 32)
+ queue_mask[i] = (1 << (n_queues - i * 32)) - 1;
+ fprintf(stdout,
+ "The sub commands will be applied to all %d queues\n",
+ n_queues);
+ } else {
+ if (ctx->argc <= 2)
+ exit_bad_args();
+ ctx->argc--;
+ ctx->argp++;
+ if (parse_hex_u32_bitmap(*ctx->argp, MAX_NUM_QUEUE,
+ queue_mask)) {
+ fprintf(stdout, "Invalid queue mask\n");
+ return -1;
+ }
+ for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); i++) {
+ __u32 mask = queue_mask[i];
+
+ while (mask > 0) {
+ if (mask & 0x1)
+ n_queues++;
+ mask = mask >> 1;
+ }
+ }
+ ctx->argc--;
+ ctx->argp++;
+ }
+
+ i = find_option(ctx->argp[0]);
+ if (i < 0)
+ exit_bad_args();
+
+ if (strstr(args[i].opts, "--show-coalesce") != NULL) {
+ per_queue_opt = get_per_queue_coalesce(ctx, queue_mask,
+ n_queues);
+ if (per_queue_opt == NULL) {
+ perror("Cannot get device per queue parameters");
+ return -EFAULT;
+ }
+ dump_per_queue_coalesce(per_queue_opt, queue_mask, n_queues);
+ free(per_queue_opt);
+ } else if (strstr(args[i].opts, "--coalesce") != NULL) {
+ ctx->argc--;
+ ctx->argp++;
+ per_queue_opt = get_per_queue_coalesce(ctx, queue_mask,
+ n_queues);
+ if (per_queue_opt == NULL) {
+ perror("Cannot get device per queue parameters");
+ return -EFAULT;
+ }
+ set_per_queue_coalesce(ctx, per_queue_opt, n_queues);
+ free(per_queue_opt);
+ } else {
+ perror("The subcommand is not supported yet");
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int ioctl_init(struct cmd_context *ctx, bool no_dev)
+{
+ if (no_dev) {
+ ctx->fd = -1;
+ return 0;
+ }
+ if (strlen(ctx->devname) >= IFNAMSIZ) {
+ fprintf(stderr, "Device name longer than %u characters\n",
+ IFNAMSIZ - 1);
+ exit_bad_args();
+ }
+
+ /* Setup our control structures. */
+ memset(&ctx->ifr, 0, sizeof(ctx->ifr));
+ strcpy(ctx->ifr.ifr_name, ctx->devname);
+
+ /* Open control socket. */
+ ctx->fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (ctx->fd < 0)
+ ctx->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ if (ctx->fd < 0) {
+ perror("Cannot get control socket");
+ return 70;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argp)
+{
+ struct cmd_context ctx = {};
+ int ret;
+ int k;
+
+ init_global_link_mode_masks();
+
+ if (argc < 2)
+ exit_bad_args();
+
+ /* Skip command name */
+ argp++;
+ argc--;
+
+ while (true) {
+ if (*argp && !strcmp(*argp, "--debug")) {
+ char *eptr;
+
+ if (argc < 2)
+ exit_bad_args();
+ ctx.debug = strtoul(argp[1], &eptr, 0);
+ if (!argp[1][0] || *eptr)
+ exit_bad_args();
+
+ argp += 2;
+ argc -= 2;
+ continue;
+ }
+ if (*argp && !strcmp(*argp, "--json")) {
+ ctx.json = true;
+ argp += 1;
+ argc -= 1;
+ continue;
+ }
+ if (*argp && (!strcmp(*argp, "--include-statistics") ||
+ !strcmp(*argp, "-I"))) {
+ ctx.show_stats = true;
+ argp += 1;
+ argc -= 1;
+ continue;
+ }
+ break;
+ }
+ if (*argp && !strcmp(*argp, "--monitor")) {
+ ctx.argp = ++argp;
+ ctx.argc = --argc;
+ ret = nl_monitor(&ctx);
+ return ret ? 1 : 0;
+ }
+
+ /* First argument must be either a valid option or a device
+ * name to get settings for (which we don't expect to begin
+ * with '-').
+ */
+ if (!*argp)
+ exit_bad_args();
+
+ k = find_option(*argp);
+ if (k > 0) {
+ argp++;
+ argc--;
+ } else {
+ if ((*argp)[0] == '-')
+ exit_bad_args();
+ k = 0;
+ }
+
+ if (!args[k].no_dev) {
+ ctx.devname = *argp++;
+ argc--;
+
+ if (!ctx.devname)
+ exit_bad_args();
+ }
+ if (ctx.json && !args[k].json)
+ exit_bad_args();
+ ctx.argc = argc;
+ ctx.argp = argp;
+ netlink_run_handler(&ctx, args[k].nlchk, args[k].nlfunc, !args[k].func);
+
+ if (ctx.json) /* no IOCTL command supports JSON output */
+ exit_bad_args();
+
+ ret = ioctl_init(&ctx, args[k].no_dev);
+ if (ret)
+ return ret;
+
+ return args[k].func(&ctx);
+}