blob: 1d9067e774af3ee844de6d3197fb0d8da4de9c47 [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
* 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 <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/sockios.h>
#include <linux/netlink.h>
#include "common.h"
#include "netlink/extapi.h"
#ifndef MAX_ADDR_LEN
#define MAX_ADDR_LEN 32
#endif
#ifndef NETLINK_GENERIC
#define NETLINK_GENERIC 16
#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 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_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 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,
};
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" },
};
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;
}
static const struct {
const char *name;
int (*func)(struct ethtool_drvinfo *info, struct ethtool_regs *regs);
} driver_list[] = {
#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
{ "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 },
#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)
{
unsigned int i;
if (gregs_dump_raw) {
fwrite(regs->data, regs->len, 1, stdout);
goto nested;
}
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)
goto nested;
/* 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);
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\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;
}
#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],