blob: b06dfa3139a01900050d191c53f000768dde98c6 [file] [log] [blame]
/*
* 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
*
* TODO:
* * show settings for all devices
*/
#include "internal.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stddef.h>
#include <errno.h>
#include <sys/utsname.h>
#include <limits.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/sockios.h>
#ifndef MAX_ADDR_LEN
#define MAX_ADDR_LEN 32
#endif
#define ALL_ADVERTISED_MODES \
(ADVERTISED_10baseT_Half | \
ADVERTISED_10baseT_Full | \
ADVERTISED_100baseT_Half | \
ADVERTISED_100baseT_Full | \
ADVERTISED_1000baseT_Half | \
ADVERTISED_1000baseT_Full | \
ADVERTISED_2500baseX_Full | \
ADVERTISED_10000baseKX4_Full | \
ADVERTISED_10000baseKR_Full | \
ADVERTISED_10000baseR_FEC | \
ADVERTISED_20000baseMLD2_Full | \
ADVERTISED_20000baseKR2_Full | \
ADVERTISED_40000baseKR4_Full | \
ADVERTISED_40000baseCR4_Full | \
ADVERTISED_40000baseSR4_Full | \
ADVERTISED_40000baseLR4_Full)
#define ALL_ADVERTISED_FLAGS \
(ADVERTISED_10baseT_Half | \
ADVERTISED_10baseT_Full | \
ADVERTISED_100baseT_Half | \
ADVERTISED_100baseT_Full | \
ADVERTISED_1000baseT_Half | \
ADVERTISED_1000baseT_Full | \
ADVERTISED_Autoneg | \
ADVERTISED_TP | \
ADVERTISED_AUI | \
ADVERTISED_MII | \
ADVERTISED_FIBRE | \
ADVERTISED_BNC | \
ADVERTISED_10000baseT_Full | \
ADVERTISED_Pause | \
ADVERTISED_Asym_Pause | \
ADVERTISED_2500baseX_Full | \
ADVERTISED_Backplane | \
ADVERTISED_1000baseKX_Full | \
ADVERTISED_10000baseKX4_Full | \
ADVERTISED_10000baseKR_Full | \
ADVERTISED_10000baseR_FEC | \
ADVERTISED_20000baseMLD2_Full | \
ADVERTISED_20000baseKR2_Full | \
ADVERTISED_40000baseKR4_Full | \
ADVERTISED_40000baseCR4_Full | \
ADVERTISED_40000baseSR4_Full | \
ADVERTISED_40000baseLR4_Full)
#ifndef HAVE_NETIF_MSG
enum {
NETIF_MSG_DRV = 0x0001,
NETIF_MSG_PROBE = 0x0002,
NETIF_MSG_LINK = 0x0004,
NETIF_MSG_TIMER = 0x0008,
NETIF_MSG_IFDOWN = 0x0010,
NETIF_MSG_IFUP = 0x0020,
NETIF_MSG_RX_ERR = 0x0040,
NETIF_MSG_TX_ERR = 0x0080,
NETIF_MSG_TX_QUEUED = 0x0100,
NETIF_MSG_INTR = 0x0200,
NETIF_MSG_TX_DONE = 0x0400,
NETIF_MSG_RX_STATUS = 0x0800,
NETIF_MSG_PKTDATA = 0x1000,
NETIF_MSG_HW = 0x2000,
NETIF_MSG_WOL = 0x4000,
};
#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);
}
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 flag_info {
const char *name;
u32 value;
};
static const struct flag_info flags_msglvl[] = {
{ "drv", NETIF_MSG_DRV },
{ "probe", NETIF_MSG_PROBE },
{ "link", NETIF_MSG_LINK },
{ "timer", NETIF_MSG_TIMER },
{ "ifdown", NETIF_MSG_IFDOWN },
{ "ifup", NETIF_MSG_IFUP },
{ "rx_err", NETIF_MSG_RX_ERR },
{ "tx_err", NETIF_MSG_TX_ERR },
{ "tx_queued", NETIF_MSG_TX_QUEUED },
{ "intr", NETIF_MSG_INTR },
{ "tx_done", NETIF_MSG_TX_DONE },
{ "rx_status", NETIF_MSG_RX_STATUS },
{ "pktdata", NETIF_MSG_PKTDATA },
{ "hw", NETIF_MSG_HW },
{ "wol", NETIF_MSG_WOL },
};
struct off_flag_def {
const char *short_name;
const char *long_name;
const char *kernel_name;
u32 get_cmd, set_cmd;
u32 value;
/* For features exposed through ETHTOOL_GFLAGS, the oldest
* kernel version for which we can trust the result. Where
* the flag was added at the same time the kernel started
* supporting the feature, this is 0 (to allow for backports).
* Where the feature was supported before the flag was added,
* it is the version that introduced the flag.
*/
u32 min_kernel_ver;
};
static const struct off_flag_def off_flag_def[] = {
{ "rx", "rx-checksumming", "rx-checksum",
ETHTOOL_GRXCSUM, ETHTOOL_SRXCSUM, ETH_FLAG_RXCSUM, 0 },
{ "tx", "tx-checksumming", "tx-checksum-*",
ETHTOOL_GTXCSUM, ETHTOOL_STXCSUM, ETH_FLAG_TXCSUM, 0 },
{ "sg", "scatter-gather", "tx-scatter-gather*",
ETHTOOL_GSG, ETHTOOL_SSG, ETH_FLAG_SG, 0 },
{ "tso", "tcp-segmentation-offload", "tx-tcp*-segmentation",
ETHTOOL_GTSO, ETHTOOL_STSO, ETH_FLAG_TSO, 0 },
{ "ufo", "udp-fragmentation-offload", "tx-udp-fragmentation",
ETHTOOL_GUFO, ETHTOOL_SUFO, ETH_FLAG_UFO, 0 },
{ "gso", "generic-segmentation-offload", "tx-generic-segmentation",
ETHTOOL_GGSO, ETHTOOL_SGSO, ETH_FLAG_GSO, 0 },
{ "gro", "generic-receive-offload", "rx-gro",
ETHTOOL_GGRO, ETHTOOL_SGRO, ETH_FLAG_GRO, 0 },
{ "lro", "large-receive-offload", "rx-lro",
0, 0, ETH_FLAG_LRO,
KERNEL_VERSION(2,6,24) },
{ "rxvlan", "rx-vlan-offload", "rx-vlan-hw-parse",
0, 0, ETH_FLAG_RXVLAN,
KERNEL_VERSION(2,6,37) },
{ "txvlan", "tx-vlan-offload", "tx-vlan-hw-insert",
0, 0, ETH_FLAG_TXVLAN,
KERNEL_VERSION(2,6,37) },
{ "ntuple", "ntuple-filters", "rx-ntuple-filter",
0, 0, ETH_FLAG_NTUPLE, 0 },
{ "rxhash", "receive-hashing", "rx-hashing",
0, 0, ETH_FLAG_RXHASH, 0 },
};
struct feature_def {
char name[ETH_GSTRING_LEN];
int off_flag_index; /* index in off_flag_def; negative if none match */
};
struct feature_defs {
size_t n_features;
/* Number of features each offload flag is associated with */
unsigned int off_flag_matched[ARRAY_SIZE(off_flag_def)];
/* Name and offload flag index for each feature */
struct feature_def def[0];
};
#define FEATURE_BITS_TO_BLOCKS(n_bits) DIV_ROUND_UP(n_bits, 32U)
#define FEATURE_WORD(blocks, index, field) ((blocks)[(index) / 32U].field)
#define FEATURE_FIELD_FLAG(index) (1U << (index) % 32U)
#define FEATURE_BIT_SET(blocks, index, field) \
(FEATURE_WORD(blocks, index, field) |= FEATURE_FIELD_FLAG(index))
#define FEATURE_BIT_CLEAR(blocks, index, field) \
(FEATURE_WORD(blocks, index, filed) &= ~FEATURE_FIELD_FLAG(index))
#define FEATURE_BIT_IS_SET(blocks, index, field) \
(FEATURE_WORD(blocks, index, field) & FEATURE_FIELD_FLAG(index))
static long long
get_int_range(char *str, int base, long long min, long long max)
{
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 void parse_generic_cmdline(struct cmd_context *ctx,
int *changed,
struct cmdline_info *info,
unsigned int n_info)
{
int argc = ctx->argc;
char **argp = ctx->argp;
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_aton(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 void
print_flags(const struct flag_info *info, unsigned int n_info, u32 value)
{
const char *sep = "";
while (n_info) {
if (value & info->value) {
printf("%s%s", sep, info->name);
sep = " ";
value &= ~info->value;
}
++info;
--n_info;
}
/* Print any unrecognised flags in hex */
if (value)
printf("%s%#x", sep, value);
}
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)
{
fprintf(stdout,
PACKAGE " version " VERSION "\n");
return 0;
}
static void dump_link_caps(const char *prefix, const char *an_prefix, u32 mask,
int link_mode_only);
static void dump_supported(struct ethtool_cmd *ep)
{
u32 mask = ep->supported;
fprintf(stdout, " Supported ports: [ ");
if (mask & SUPPORTED_TP)
fprintf(stdout, "TP ");
if (mask & SUPPORTED_AUI)
fprintf(stdout, "AUI ");
if (mask & SUPPORTED_BNC)
fprintf(stdout, "BNC ");
if (mask & SUPPORTED_MII)
fprintf(stdout, "MII ");
if (mask & SUPPORTED_FIBRE)
fprintf(stdout, "FIBRE ");
fprintf(stdout, "]\n");
dump_link_caps("Supported", "Supports", mask, 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, u32 mask,
int link_mode_only)
{
static const struct {
int same_line; /* print on same line as previous */
u32 value;
const char *name;
} mode_defs[] = {
{ 0, ADVERTISED_10baseT_Half, "10baseT/Half" },
{ 1, ADVERTISED_10baseT_Full, "10baseT/Full" },
{ 0, ADVERTISED_100baseT_Half, "100baseT/Half" },
{ 1, ADVERTISED_100baseT_Full, "100baseT/Full" },
{ 0, ADVERTISED_1000baseT_Half, "1000baseT/Half" },
{ 1, ADVERTISED_1000baseT_Full, "1000baseT/Full" },
{ 0, ADVERTISED_1000baseKX_Full, "1000baseKX/Full" },
{ 0, ADVERTISED_2500baseX_Full, "2500baseX/Full" },
{ 0, ADVERTISED_10000baseT_Full, "10000baseT/Full" },
{ 0, ADVERTISED_10000baseKX4_Full, "10000baseKX4/Full" },
{ 0, ADVERTISED_10000baseKR_Full, "10000baseKR/Full" },
{ 0, ADVERTISED_20000baseMLD2_Full, "20000baseMLD2/Full" },
{ 0, ADVERTISED_20000baseKR2_Full, "20000baseKR2/Full" },
{ 0, ADVERTISED_40000baseKR4_Full, "40000baseKR4/Full" },
{ 0, ADVERTISED_40000baseCR4_Full, "40000baseCR4/Full" },
{ 0, ADVERTISED_40000baseSR4_Full, "40000baseSR4/Full" },
{ 0, ADVERTISED_40000baseLR4_Full, "40000baseLR4/Full" },
};
int indent;
int did1, new_line_pend, 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 (mask & mode_defs[i].value) {
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 (mask & ADVERTISED_Pause) {
fprintf(stdout, "Symmetric");
if (mask & ADVERTISED_Asym_Pause)
fprintf(stdout, " Receive-only");
fprintf(stdout, "\n");
} else {
if (mask & ADVERTISED_Asym_Pause)
fprintf(stdout, "Transmit-only\n");
else
fprintf(stdout, "No\n");
}
fprintf(stdout, " %s auto-negotiation: ", an_prefix);
if (mask & ADVERTISED_Autoneg)
fprintf(stdout, "Yes\n");
else
fprintf(stdout, "No\n");
}
}
static int dump_ecmd(struct ethtool_cmd *ep)
{
u32 speed;
dump_supported(ep);
dump_link_caps("Advertised", "Advertised", ep->advertising, 0);
if (ep->lp_advertising)
dump_link_caps("Link partner advertised",
"Link partner advertised", ep->lp_advertising,
0);
fprintf(stdout, " Speed: ");
speed = ethtool_cmd_speed(ep);
if (speed == 0 || speed == (u16)(-1) || speed == (u32)(-1))
fprintf(stdout, "Unknown!\n");
else
fprintf(stdout, "%uMb/s\n", speed);
fprintf(stdout, " Duplex: ");
switch (ep->duplex) {
case DUPLEX_HALF:
fprintf(stdout, "Half\n");
break;
case DUPLEX_FULL:
fprintf(stdout, "Full\n");
break;
default:
fprintf(stdout, "Unknown! (%i)\n", ep->duplex);
break;
};
fprintf(stdout, " Port: ");
switch (ep->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", ep->port);
break;
};
fprintf(stdout, " PHYAD: %d\n", ep->phy_address);
fprintf(stdout, " Transceiver: ");
switch (ep->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",
(ep->autoneg == AUTONEG_DISABLE) ?
"off" : "on");
if (ep->port == PORT_TP) {
fprintf(stdout, " MDI-X: ");
if (ep->eth_tp_mdix_ctrl == ETH_TP_MDI) {
fprintf(stdout, "off (forced)\n");
} else if (ep->eth_tp_mdix_ctrl == ETH_TP_MDI_X) {
fprintf(stdout, "on (forced)\n");
} else {
switch (ep->eth_tp_mdix) {
case ETH_TP_MDI:
fprintf(stdout, "off");
break;
case ETH_TP_MDI_X:
fprintf(stdout, "on");
break;
default:
fprintf(stdout, "Unknown");
break;
}
if (ep->eth_tp_mdix_ctrl == ETH_TP_MDI_AUTO)
fprintf(stdout, " (auto)");
fprintf(stdout, "\n");
}
}
return 0;
}
static int dump_drvinfo(struct ethtool_drvinfo *info)
{
fprintf(stdout,
"driver: %.*s\n"
"version: %.*s\n"
"firmware-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->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 'd':
*data = 0;
break;
default:
return -1;
}
optstr++;
}
return 0;
}
static char *unparse_wolopts(int wolopts)
{
static char buf[16];
char *p = buf;
memset(buf, 0, sizeof(buf));
if (wolopts) {
if (wolopts & WAKE_PHY)
*p++ = 'p';
if (wolopts & WAKE_UCAST)
*p++ = 'u';
if (wolopts & WAKE_MCAST)
*p++ = 'm';
if (wolopts & WAKE_BCAST)
*p++ = 'b';
if (wolopts & WAKE_ARP)
*p++ = 'a';
if (wolopts & WAKE_MAGIC)
*p++ = 'g';
if (wolopts & WAKE_MAGICSECURE)
*p++ = 's';
} else {
*p = 'd';
}
return buf;
}
static int dump_wol(struct ethtool_wolinfo *wol)
{
fprintf(stdout, " Supports Wake-on: %s\n",
unparse_wolopts(wol->supported));
fprintf(stdout, " Wake-on: %s\n",
unparse_wolopts(wol->wolopts));
if (wol->supported & WAKE_MAGICSECURE) {
int i;
int delim = 0;
fprintf(stdout, " SecureOn password: ");
for (i = 0; i < SOPASS_MAX; i++) {
fprintf(stdout, "%s%02x", delim?":":"", wol->sopass[i]);
delim=1;
}
fprintf(stdout, "\n");
}
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 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 },
};
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,
const char *gregs_dump_file,
struct ethtool_drvinfo *info, struct ethtool_regs *regs)
{
int i;
if (gregs_dump_raw) {
fwrite(regs->data, regs->len, 1, stdout);
return 0;
}
if (gregs_dump_file) {
FILE *f = fopen(gregs_dump_file, "r");
struct stat st;
if (!f || fstat(fileno(f), &st) < 0) {
fprintf(stderr, "Can't open '%s': %s\n",
gregs_dump_file, strerror(errno));
return -1;
}
regs = realloc(regs, sizeof(*regs) + st.st_size);
regs->len = st.st_size;
fread(regs->data, regs->len, 1, f);
fclose(f);
}
if (!gregs_dump_hex)
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)
return 0;
/* This version (or some other
* variation in the dump format) is
* not handled; fall back to hex
*/
break;
}
dump_hex(stdout, regs->data, regs->len, 0);
return 0;
}
static int dump_eeprom(int geeprom_dump_raw, struct ethtool_drvinfo *info,
struct ethtool_eeprom *ee)
{
if (geeprom_dump_raw) {
fwrite(ee->data, 1, ee->len, stdout);
return 0;
}
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);
}
dump_hex(stdout, ee->data, ee->len, ee->offset);
return 0;
}
static int dump_test(struct ethtool_test *test,
struct ethtool_gstrings *strings)
{
int i, 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-frame-low: %u\n"
"tx-usecs-low: %u\n"
"tx-frame-low: %u\n"
"\n"
"rx-usecs-high: %u\n"
"rx-frame-high: %u\n"
"tx-usecs-high: %u\n"
"tx-frame-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;
}
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 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)
{
int kernel_ver = linux_version_code();
u32 value;
int indent;
int i, j;
for (i = 0; i < ARRAY_SIZE(off_flag_def); 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)
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 != 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) {
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)
{
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");
dump_link_caps("Supported EEE", "", ep->supported, 1);
dump_link_caps("Advertised EEE", "", ep->advertised, 1);
dump_link_caps("Link partner advertised EEE", "", ep->lp_advertised, 1);
}
#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_PTP_V2_DELAY_REQ + 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)",
};
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) {
len = sset_info.hdr.sset_mask ? sset_info.hdr.data[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;
u32 n_features;
int i, j;
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)
return NULL;
defs->n_features = n_features;
memset(defs->off_flag_matched, 0, sizeof(defs->off_flag_matched));
/* Copy out feature names and find those associated with legacy flags */
for (i = 0; i < defs->n_features; i++) {
memcpy(defs->def[i].name, names->data + i * ETH_GSTRING_LEN,
ETH_GSTRING_LEN);
defs->def[i].off_flag_index = -1;
for (j = 0;
j < ARRAY_SIZE(off_flag_def) &&
defs->def[i].off_flag_index < 0;
j++) {
const char *pattern =
off_flag_def[j].kernel_name;
const char *name = defs->def[i].name;
for (;;) {
if (*pattern == '*') {
/* There is only one wildcard; so
* switch to a suffix comparison */
size_t pattern_len =
strlen(pattern + 1);
size_t name_len = strlen(name);
if (name_len < pattern_len)
break; /* name is too short */
name += name_len - pattern_len;
++pattern;
} else if (*pattern != *name) {
break; /* mismatch */
} else if (*pattern == 0) {
defs->def[i].off_flag_index = j;
defs->off_flag_matched[j]++;
break;
} else {
++name;
++pattern;
}
}
}
}
free(names);
return defs;
}
static int do_gdrv(struct cmd_context *ctx)
{
int err;
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[] = {
{ "autoneg", CMDL_BOOL, &pause_autoneg_wanted,
&epause.autoneg },
{ "rx", CMDL_BOOL, &pause_rx_wanted, &epause.rx_pause },
{ "tx", CMDL_BOOL, &pause_tx_wanted, &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[] = {
{ "rx", CMDL_S32, &ring_rx_wanted, &ering.rx_pending },
{ "rx-mini", CMDL_S32, &ring_rx_mini_wanted,
&ering.rx_mini_pending },
{ "rx-jumbo", CMDL_S32, &ring_rx_jumbo_wanted,
&ering.rx_jumbo_pending },
{ "tx", CMDL_S32, &ring_tx_wanted, &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[] = {
{ "rx", CMDL_S32, &channels_rx_wanted, &echannels.rx_count },
{ "tx", CMDL_S32, &channels_tx_wanted, &echannels.tx_count },
{ "other", CMDL_S32, &channels_other_wanted,
&echannels.other_count },
{ "combined", CMDL_S32, &channels_combined_wanted,
&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, aborting\n");
fprintf(stderr, "current values: tx %u rx %u other %u"
"combined %u\n", echannels.rx_count,
echannels.tx_count, echannels.other_count,
echannels.combined_count);
return 1;
}
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\n");
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;
}
static int do_scoalesce(struct cmd_context *ctx)
{
struct ethtool_coalesce ecoal;
int gcoalesce_changed = 0;
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;
struct cmdline_info cmdline_coalesce[] = {
{ "adaptive-rx", CMDL_BOOL, &coal_adaptive_rx_wanted,
&ecoal.use_adaptive_rx_coalesce },
{ "adaptive-tx", CMDL_BOOL, &coal_adaptive_tx_wanted,
&ecoal.use_adaptive_tx_coalesce },
{ "sample-interval", CMDL_S32, &coal_sample_rate_wanted,
&ecoal.rate_sample_interval },
{ "stats-block-usecs", CMDL_S32, &coal_stats_wanted,
&ecoal.stats_block_coalesce_usecs },
{ "pkt-rate-low", CMDL_S32, &coal_pkt_rate_low_wanted,
&ecoal.pkt_rate_low },
{ "pkt-rate-high", CMDL_S32, &coal_pkt_rate_high_wanted,
&ecoal.pkt_rate_high },
{ "rx-usecs", CMDL_S32, &coal_rx_usec_wanted,
&ecoal.rx_coalesce_usecs },
{ "rx-frames", CMDL_S32, &coal_rx_frames_wanted,
&ecoal.rx_max_coalesced_frames },
{ "rx-usecs-irq", CMDL_S32, &coal_rx_usec_irq_wanted,
&ecoal.rx_coalesce_usecs_irq },
{ "rx-frames-irq", CMDL_S32, &coal_rx_frames_irq_wanted,
&ecoal.rx_max_coalesced_frames_irq },
{ "tx-usecs", CMDL_S32, &coal_tx_usec_wanted,
&ecoal.tx_coalesce_usecs },
{ "tx-frames", CMDL_S32, &coal_tx_frames_wanted,
&ecoal.tx_max_coalesced_frames },
{ "tx-usecs-irq", CMDL_S32, &coal_tx_usec_irq_wanted,
&ecoal.tx_coalesce_usecs_irq },
{ "tx-frames-irq", CMDL_S32, &coal_tx_frames_irq_wanted,
&ecoal.tx_max_coalesced_frames_irq },
{ "rx-usecs-low", CMDL_S32, &coal_rx_usec_low_wanted,
&ecoal.rx_coalesce_usecs_low },
{ "rx-frames-low", CMDL_S32, &coal_rx_frames_low_wanted,
&ecoal.rx_max_coalesced_frames_low },
{ "tx-usecs-low", CMDL_S32, &coal_tx_usec_low_wanted,
&ecoal.tx_coalesce_usecs_low },
{ "tx-frames-low", CMDL_S32, &coal_tx_frames_low_wanted,
&ecoal.tx_max_coalesced_frames_low },
{ "rx-usecs-high", CMDL_S32, &coal_rx_usec_high_wanted,
&ecoal.rx_coalesce_usecs_high },
{ "rx-frames-high", CMDL_S32, &coal_rx_frames_high_wanted,
&ecoal.rx_max_coalesced_frames_high },
{ "tx-usecs-high", CMDL_S32, &coal_tx_usec_high_wanted,
&ecoal.tx_coalesce_usecs_high },
{ "tx-frames-high", CMDL_S32, &coal_tx_frames_high_wanted,
&ecoal.tx_max_coalesced_frames_high },
};
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 < ARRAY_SIZE(off_flag_def); 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) {
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");
return 1;
}
dump_features(defs, features, NULL);
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;
struct cmdline_info *cmdline_features;
struct feature_state *old_state, *new_state;
struct ethtool_value eval;
int err;
int i, j;
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");
return 1;
}
efeatures->cmd = ETHTOOL_SFEATURES;
efeatures->size = FEATURE_BITS_TO_BLOCKS(defs->n_features);
memset(efeatures->features, 0,
FEATURE_BITS_TO_BLOCKS(defs->n_features) *
sizeof(efeatures->features[0]));
} else {
efeatures = NULL;
}
/* Generate cmdline_info for legacy flags and kernel-named
* features, and parse our arguments.
*/
cmdline_features = calloc(ARRAY_SIZE(off_flag_def) + defs->n_features,
sizeof(cmdline_features[0]));
if (!cmdline_features) {
perror("Cannot parse arguments");
return 1;
}
for (i = 0; i < ARRAY_SIZE(off_flag_def); i++)
flag_to_cmdline_info(off_flag_def[i].short_name,
off_flag_def[i].value,
&off_flags_wanted, &off_flags_mask,
&cmdline_features[i]);
for (i = 0; i < defs->n_features; i++)
flag_to_cmdline_info(
defs->def[i].name, FEATURE_FIELD_FLAG(i),
&FEATURE_WORD(efeatures->features, i, requested),
&FEATURE_WORD(efeatures->features, i, valid),
&cmdline_features[ARRAY_SIZE(off_flag_def) + i]);
parse_generic_cmdline(ctx, &any_changed, cmdline_features,
ARRAY_SIZE(off_flag_def) + defs->n_features);
free(cmdline_features);
if (!any_changed) {
fprintf(stdout, "no features changed\n");
return 0;
}
old_state = get_features(ctx, defs);
if (!old_state)
return 1;
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 < ARRAY_SIZE(off_flag_def); 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 != 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");
return 1;
}
} else {
for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
if (!off_flag_def[i].set_cmd)
continue;
if (off_flags_mask & off_flag_def[i].value) {
eval.cmd = off_flag_def[i].set_cmd;
eval.data = !!(off_flags_wanted &
off_flag_def[i].value);
err = send_ioctl(ctx, &eval);
if (err) {
fprintf(stderr,
"Cannot set device %s settings: %m\n",
off_flag_def[i].long_name);
return 1;
}
}
}
if (off_flags_mask & ETH_FLAG_EXT_MASK) {
eval.cmd = ETHTOOL_SFLAGS;
eval.data = (old_state->off_flags & ~off_flags_mask &
ETH_FLAG_EXT_MASK);
eval.data |= off_flags_wanted & ETH_FLAG_EXT_MASK;
err = send_ioctl(ctx, &eval);
if (err) {
perror("Cannot set device flag settings");
return 92;
}
}
}
/* Compare new state with requested state */
new_state = get_features(ctx, defs);
if (!new_state)
return 1;
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");
return 1;
}
printf("Actual changes:\n");
dump_features(defs, new_state, old_state);
}
return 0;
}
static int do_gset(struct cmd_context *ctx)
{
int err;
struct ethtool_cmd ecmd;
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);
ecmd.cmd = ETHTOOL_GSET;
err = send_ioctl(ctx, &ecmd);
if (err == 0) {
err = dump_ecmd(&ecmd);
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, ARRAY_SIZE(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;
int full_advertising_wanted = -1;
int advertising_wanted = -1;
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[ARRAY_SIZE(flags_msglvl)];
int argc = ctx->argc;
char **argp = ctx->argp;
int i;
int err;
for (i = 0; i < ARRAY_SIZE(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();
full_advertising_wanted = get_int(argp[i], 16);
} 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 {
exit_bad_args();
}
}
if (full_advertising_wanted < 0) {
/* User didn't supply a full advertisement bitfield:
* construct one from the specified speed and duplex.
*/
if (speed_wanted == SPEED_10 && duplex_wanted == DUPLEX_HALF)
advertising_wanted = ADVERTISED_10baseT_Half;
else if (speed_wanted == SPEED_10 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_10baseT_Full;
else if (speed_wanted == SPEED_100 &&
duplex_wanted == DUPLEX_HALF)
advertising_wanted = ADVERTISED_100baseT_Half;
else if (speed_wanted == SPEED_100 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_100baseT_Full;
else if (speed_wanted == SPEED_1000 &&
duplex_wanted == DUPLEX_HALF)
advertising_wanted = ADVERTISED_1000baseT_Half;
else if (speed_wanted == SPEED_1000 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_1000baseT_Full;
else if (speed_wanted == SPEED_2500 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_2500baseX_Full;
else if (speed_wanted == SPEED_10000 &&
duplex_wanted == DUPLEX_FULL)
advertising_wanted = ADVERTISED_10000baseT_Full;
else
/* auto negotiate without forcing,
* all supported speed will be assigned below
*/
advertising_wanted = 0;
}
if (gset_changed) {
struct ethtool_cmd ecmd;
ecmd.cmd = ETHTOOL_GSET;
err = send_ioctl(ctx, &ecmd);
if (err < 0) {
perror("Cannot get current device settings");
} else {
/* Change everything the user specified. */
if (speed_wanted != -1)
ethtool_cmd_speed_set(&ecmd, speed_wanted);
if (duplex_wanted != -1)
ecmd.duplex = duplex_wanted;
if (port_wanted != -1)
ecmd.port = port_wanted;
if (mdix_wanted != -1) {
/* check driver supports MDI-X */
if (ecmd.eth_tp_mdix_ctrl != ETH_TP_MDI_INVALID)
ecmd.eth_tp_mdix_ctrl = mdix_wanted;
else
fprintf(stderr, "setting MDI not supported\n");
}
if (autoneg_wanted != -1)
ecmd.autoneg = autoneg_wanted;
if (phyad_wanted != -1)
ecmd.phy_address = phyad_wanted;
if (xcvr_wanted != -1)
ecmd.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) &&
ecmd.autoneg && advertising_wanted == 0) {
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 == 0) {
/* Auto negotiation enabled, but with
* unspecified speed and duplex: enable all
* supported speeds and duplexes.
*/
ecmd.advertising =
(ecmd.advertising &
~ALL_ADVERTISED_MODES) |
(ALL_ADVERTISED_MODES & ecmd.supported);
/* If driver supports unknown flags, we cannot
* be sure that we enable all link modes.
*/
if ((ecmd.supported & ALL_ADVERTISED_FLAGS) !=
ecmd.supported) {
fprintf(stderr, "Driver supports one "
"or more unknown flags\n");
}
} else if (advertising_wanted > 0) {
/* Enable all requested modes */
ecmd.advertising =
(ecmd.advertising &
~ALL_ADVERTISED_MODES) |
advertising_wanted;
} else if (full_advertising_wanted > 0) {
ecmd.advertising = full_advertising_wanted;
}
/* Try to perform the update. */
ecmd.cmd = ETHTOOL_SSET;
err = send_ioctl(ctx, &ecmd);
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[] = {
{ "raw", CMDL_BOOL, &gregs_dump_raw, NULL },
{ "hex", CMDL_BOOL, &gregs_dump_hex, NULL },
{ "file", CMDL_STR, &gregs_dump_file, NULL },
};
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 (dump_regs(gregs_dump_raw, gregs_dump_hex, gregs_dump_file,
&drvinfo, regs) < 0) {
fprintf(stderr, "Cannot dump registers\n");
free(regs);
return 75;
}
free(regs);
return 0;
}
static