From: Ruiwei Chen Date: Sat, 25 Mar 2023 05:44:53 +0000 (+0800) Subject: system-linux: switch to new ETHTOOL_xLINKSETTINGS API X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=f429bd94f99e55548bf4fa8156c165017ce3c41c;p=project%2Fnetifd.git system-linux: switch to new ETHTOOL_xLINKSETTINGS API ETHTOOL_GSET / ETHTOOL_SSET API is deprecated since Linux v5.2 released in 2016, see torvalds/linux@3f1ac7a700d03 ("net: ethtool: add new ETHTOOL_xLINKSETTINGS API"). All still maintained OpenWrt versions use kernel versions new enough to support the new API. Hence migrate to ETHTOOL_xLINKSETTINGS API API to handle auto-negotiation for flow-control as well as higher bandwidth like 2.5G, 5G and 10G. Use ethtool API to switch on or off auto-negotiation of Ethernet interfaces, and set speed and duplex accordingly in case auto- negotiation is switched off. Add support for flow-control settings, both manual/force mode for RX and TX pause frames as well as advertising Pause and Asym_Pause bits. Instead of hard-coding the supported modes, generate a header file describing them from . Signed-off-by: Ruiwei Chen [generate list of link modes from toolchain headers, select by speed and duplex, also use new API for dump function, add support for flow- control settings] Signed-off-by: Daniel Golle --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ad8695..8064485 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,13 @@ IF (NOT DEFINED LIBNL_LIBS) ENDIF() ENDIF() +ADD_CUSTOM_COMMAND( + OUTPUT ethtool-modes.h + COMMAND ./make_ethtool_modes_h.sh ${CMAKE_C_COMPILER} > ./ethtool-modes.h + DEPENDS ./make_ethtool_modes_h.sh +) +ADD_CUSTOM_TARGET(ethtool-modes-h DEPENDS ethtool-modes.h) + IF("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND NOT DUMMY_MODE) SET(SOURCES ${SOURCES} system-linux.c) SET(LIBS ${LIBS} ${LIBNL_LIBS}) @@ -72,3 +79,4 @@ TARGET_LINK_LIBRARIES(netifd ${LIBS}) INSTALL(TARGETS netifd RUNTIME DESTINATION sbin ) +ADD_DEPENDENCIES(netifd ethtool-modes-h) diff --git a/device.c b/device.c index 92c814c..2417556 100644 --- a/device.c +++ b/device.c @@ -64,6 +64,11 @@ static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = { [DEV_ATTR_SPEED] = { .name = "speed", .type = BLOBMSG_TYPE_INT32 }, [DEV_ATTR_DUPLEX] = { .name = "duplex", .type = BLOBMSG_TYPE_BOOL }, [DEV_ATTR_VLAN] = { .name = "vlan", .type = BLOBMSG_TYPE_ARRAY }, + [DEV_ATTR_PAUSE] = { .name = "pause", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_ASYM_PAUSE] = { .name = "asym_pause", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_RXPAUSE] = { .name = "rxpause", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_TXPAUSE] = { .name = "txpause", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_AUTONEG] = { .name = "autoneg", .type = BLOBMSG_TYPE_BOOL }, }; const struct uci_blob_param_list device_attr_list = { @@ -281,6 +286,11 @@ device_merge_settings(struct device *dev, struct device_settings *n) n->auth = s->flags & DEV_OPT_AUTH ? s->auth : os->auth; n->speed = s->flags & DEV_OPT_SPEED ? s->speed : os->speed; n->duplex = s->flags & DEV_OPT_DUPLEX ? s->duplex : os->duplex; + n->pause = s->flags & DEV_OPT_PAUSE ? s->pause : os->pause; + n->asym_pause = s->flags & DEV_OPT_ASYM_PAUSE ? s->asym_pause : os->asym_pause; + n->rxpause = s->flags & DEV_OPT_RXPAUSE ? s->rxpause : os->rxpause; + n->txpause = s->flags & DEV_OPT_TXPAUSE ? s->txpause : os->txpause; + n->autoneg = s->flags & DEV_OPT_AUTONEG ? s->autoneg : os->autoneg; n->flags = s->flags | os->flags | os->valid_flags; } @@ -505,6 +515,31 @@ device_init_settings(struct device *dev, struct blob_attr **tb) s->duplex = blobmsg_get_bool(cur); s->flags |= DEV_OPT_DUPLEX; } + + if ((cur = tb[DEV_ATTR_PAUSE])) { + s->pause = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_PAUSE; + } + + if ((cur = tb[DEV_ATTR_ASYM_PAUSE])) { + s->asym_pause = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_ASYM_PAUSE; + } + + if ((cur = tb[DEV_ATTR_RXPAUSE])) { + s->rxpause = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_RXPAUSE; + } + + if ((cur = tb[DEV_ATTR_TXPAUSE])) { + s->txpause = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_TXPAUSE; + } + + if ((cur = tb[DEV_ATTR_AUTONEG])) { + s->autoneg = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_AUTONEG; + } device_set_extra_vlans(dev, tb[DEV_ATTR_VLAN]); device_set_disabled(dev, disabled); } diff --git a/device.h b/device.h index aa4da18..14d7486 100644 --- a/device.h +++ b/device.h @@ -63,6 +63,11 @@ enum { DEV_ATTR_SPEED, DEV_ATTR_DUPLEX, DEV_ATTR_VLAN, + DEV_ATTR_PAUSE, + DEV_ATTR_ASYM_PAUSE, + DEV_ATTR_RXPAUSE, + DEV_ATTR_TXPAUSE, + DEV_ATTR_AUTONEG, __DEV_ATTR_MAX, }; @@ -127,6 +132,11 @@ enum { DEV_OPT_ARP_ACCEPT = (1ULL << 29), DEV_OPT_SPEED = (1ULL << 30), DEV_OPT_DUPLEX = (1ULL << 31), + DEV_OPT_PAUSE = (1ULL << 32), + DEV_OPT_ASYM_PAUSE = (1ULL << 33), + DEV_OPT_RXPAUSE = (1ULL << 34), + DEV_OPT_TXPAUSE = (1ULL << 35), + DEV_OPT_AUTONEG = (1ULL << 36), }; /* events broadcasted to all users of a device */ @@ -204,6 +214,11 @@ struct device_settings { bool auth; unsigned int speed; bool duplex; + bool pause; + bool asym_pause; + bool rxpause; + bool txpause; + bool autoneg; }; struct device_vlan_range { diff --git a/make_ethtool_modes_h.sh b/make_ethtool_modes_h.sh new file mode 100755 index 0000000..ec69130 --- /dev/null +++ b/make_ethtool_modes_h.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +CC="$1" +[ -n "$TARGET_CC_NOCACHE" ] && CC="$TARGET_CC_NOCACHE" + +cat < + +#define ETHTOOL_MODE_FULL(_speed, _mode) { \\ + .speed = (_speed), \\ + .bit_half = -1, \\ + .bit_full = ETHTOOL_LINK_MODE_ ## _speed ## base ## _mode ## _Full_BIT, \\ + .name = #_speed "base" #_mode, \\ +} + +#define ETHTOOL_MODE_HALF(_speed, _mode) { \\ + .speed = (_speed), \\ + .bit_half = ETHTOOL_LINK_MODE_ ## _speed ## base ## _mode ## _Half_BIT, \\ + .bit_full = -1, \\ + .name = #_speed "base" #_mode, \\ +} + +#define ETHTOOL_MODE_BOTH(_speed, _mode) { \\ + .speed = (_speed), \\ + .bit_half = ETHTOOL_LINK_MODE_ ## _speed ## base ## _mode ## _Half_BIT, \\ + .bit_full = ETHTOOL_LINK_MODE_ ## _speed ## base ## _mode ## _Full_BIT, \\ + .name = #_speed "base" #_mode, \\ +} + +static const struct { + unsigned int speed; + int bit_half; + int bit_full; + const char *name; +} ethtool_modes[] = { +EOF + +echo "#include " | "$CC" -E - | \ + grep "ETHTOOL_LINK_MODE_[0-9]*base[A-Za-z0-9]*_...._BIT.*" | \ + sed -r 's/.*ETHTOOL_LINK_MODE_([0-9]*)base([A-Za-z0-9]*)_(....)_BIT.*/\1 \2 \3/' | \ + sort -u | LC_ALL=C sort -r -g | ( gothalf=0 ; while read speed mode duplex; do + if [ "$duplex" = "Half" ]; then + if [ "$gothalf" = "1" ]; then + echo -e "$speed \tETHTOOL_MODE_HALF($p_speed, $p_mode)," + fi + gothalf=1 + elif [ "$duplex" = "Full" ]; then + if [ "$gothalf" = "1" ]; then + if [ "$p_speed" = "$speed" ] && [ "$p_mode" = "$mode" ]; then + echo -e "$speed \tETHTOOL_MODE_BOTH($speed, $mode)," + else + echo -e "$p_speed \tETHTOOL_MODE_HALF($p_speed, $p_mode)," + echo -e "$speed \tETHTOOL_MODE_FULL($speed, $mode)," + fi + gothalf=0 + else + echo -e "$speed \tETHTOOL_MODE_FULL($speed, $mode)," + fi + else + continue + fi + p_speed="$speed" + p_mode="$mode" + p_duplex="$duplex" + done ; [ "$gothalf" = "1" ] && echo -e "$p_speed \tETHTOOL_MODE_HALF($p_speed, $p_mode)," ) | \ + LC_ALL=C sort -g | cut -d' ' -f2- +echo "};" diff --git a/system-linux.c b/system-linux.c index 0760e73..e437377 100644 --- a/system-linux.c +++ b/system-linux.c @@ -48,6 +48,8 @@ #include +#include "ethtool-modes.h" + #ifndef RTN_FAILED_POLICY #define RTN_FAILED_POLICY 12 #endif @@ -1702,54 +1704,149 @@ int system_vlandev_del(struct device *vlandev) return system_link_del(vlandev->ifname); } +static void ethtool_link_mode_clear_bit(__s8 nwords, int nr, __u32 *mask) +{ + if (nr < 0) + return; + + if (nr >= (nwords * 32)) + return; + + mask[nr / 32] &= ~(1U << (nr % 32)); +} + +static bool ethtool_link_mode_test_bit(__s8 nwords, int nr, const __u32 *mask) +{ + if (nr < 0) + return false; + + if (nr >= (nwords * 32)) + return false; + + return !!(mask[nr / 32] & (1U << (nr % 32))); +} + static void -system_set_ethtool_settings(struct device *dev, struct device_settings *s) +system_set_ethtool_pause(struct device *dev, struct device_settings *s) { - struct ethtool_cmd ecmd = { - .cmd = ETHTOOL_GSET, + struct ethtool_pauseparam pp; + struct ifreq ifr = { + .ifr_data = (caddr_t)&pp, }; + + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + memset(&pp, 0, sizeof(pp)); + pp.cmd = ETHTOOL_GPAUSEPARAM; + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr)) + return; + + if (s->flags & DEV_OPT_RXPAUSE || s->flags & DEV_OPT_TXPAUSE) { + pp.autoneg = AUTONEG_DISABLE; + + if (s->flags & DEV_OPT_PAUSE) { + if (s->flags & DEV_OPT_RXPAUSE) + pp.rx_pause = s->rxpause && s->pause; + else + pp.rx_pause = s->pause; + + if (s->flags & DEV_OPT_TXPAUSE) + pp.tx_pause = s->txpause && s->pause; + else + pp.tx_pause = s->pause; + } else { + if (s->flags & DEV_OPT_RXPAUSE) + pp.rx_pause = s->rxpause; + + if (s->flags & DEV_OPT_TXPAUSE) + pp.tx_pause = s->txpause; + } + + if (s->flags & DEV_OPT_ASYM_PAUSE && + !s->asym_pause && (pp.rx_pause != pp.tx_pause)) + pp.rx_pause = pp.tx_pause = false; + } else { + pp.autoneg = AUTONEG_ENABLE; + /* Pause and Asym_Pause advertising bits will be set via + * ETHTOOL_SLINKSETTINGS in system_set_ethtool_settings() + */ + } + + pp.cmd = ETHTOOL_SPAUSEPARAM; + ioctl(sock_ioctl, SIOCETHTOOL, &ifr); +} + +static void +system_set_ethtool_settings(struct device *dev, struct device_settings *s) +{ + struct { + struct ethtool_link_settings req; + __u32 link_mode_data[3 * 127]; + } ecmd; struct ifreq ifr = { .ifr_data = (caddr_t)&ecmd, }; - static const struct { - unsigned int speed; - uint8_t bit_half; - uint8_t bit_full; - } speed_mask[] = { - { 10, ETHTOOL_LINK_MODE_10baseT_Half_BIT, ETHTOOL_LINK_MODE_10baseT_Full_BIT }, - { 100, ETHTOOL_LINK_MODE_100baseT_Half_BIT, ETHTOOL_LINK_MODE_100baseT_Full_BIT }, - { 1000, ETHTOOL_LINK_MODE_1000baseT_Half_BIT, ETHTOOL_LINK_MODE_1000baseT_Full_BIT }, - }; - uint32_t adv; size_t i; + __s8 nwords; + __u32 *supported, *advertising; + system_set_ethtool_pause(dev, s); + + memset(&ecmd, 0, sizeof(ecmd)); + ecmd.req.cmd = ETHTOOL_GLINKSETTINGS; strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); - if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) != 0) + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords >= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return; + + ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords <= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) return; - adv = ecmd.supported; - for (i = 0; i < ARRAY_SIZE(speed_mask); i++) { + nwords = ecmd.req.link_mode_masks_nwords; + supported = &ecmd.link_mode_data[0]; + advertising = &ecmd.link_mode_data[nwords]; + memcpy(advertising, supported, sizeof(__u32) * nwords); + + for (i = 0; i < ARRAY_SIZE(ethtool_modes); i++) { if (s->flags & DEV_OPT_DUPLEX) { - int bit = s->duplex ? speed_mask[i].bit_half : speed_mask[i].bit_full; - adv &= ~(1 << bit); + if (s->duplex) + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_half, advertising); + else + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_full, advertising); } - if (!(s->flags & DEV_OPT_SPEED) || - s->speed == speed_mask[i].speed) + s->speed == ethtool_modes[i].speed) continue; - adv &= ~(1 << speed_mask[i].bit_full); - adv &= ~(1 << speed_mask[i].bit_half); + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_full, advertising); + ethtool_link_mode_clear_bit(nwords, ethtool_modes[i].bit_half, advertising); } + if (s->flags & DEV_OPT_PAUSE) + if (!s->pause) + ethtool_link_mode_clear_bit(nwords, ETHTOOL_LINK_MODE_Pause_BIT, advertising); - if (ecmd.autoneg && ecmd.advertising == adv) - return; + if (s->flags & DEV_OPT_ASYM_PAUSE) + if (!s->asym_pause) + ethtool_link_mode_clear_bit(nwords, ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising); + + if (s->flags & DEV_OPT_AUTONEG) { + ecmd.req.autoneg = s->autoneg ? AUTONEG_ENABLE : AUTONEG_DISABLE; + if (!s->autoneg) { + if (s->flags & DEV_OPT_SPEED) + ecmd.req.speed = s->speed; - ecmd.autoneg = 1; - ecmd.advertising = adv; - ecmd.cmd = ETHTOOL_SSET; + if (s->flags & DEV_OPT_DUPLEX) + ecmd.req.duplex = s->duplex ? DUPLEX_FULL : DUPLEX_HALF; + } + } + + ecmd.req.cmd = ETHTOOL_SLINKSETTINGS; ioctl(sock_ioctl, SIOCETHTOOL, &ifr); } @@ -2332,45 +2429,6 @@ read_uint64_file(int dir_fd, const char *file, uint64_t *val) return ret; } -/* Assume advertised flags == supported flags */ -static const struct { - uint32_t mask; - const char *name; -} ethtool_link_modes[] = { - { ADVERTISED_10baseT_Half, "10baseT-H" }, - { ADVERTISED_10baseT_Full, "10baseT-F" }, - { ADVERTISED_100baseT_Half, "100baseT-H" }, - { ADVERTISED_100baseT_Full, "100baseT-F" }, - { ADVERTISED_1000baseT_Half, "1000baseT-H" }, - { ADVERTISED_1000baseT_Full, "1000baseT-F" }, - { ADVERTISED_1000baseKX_Full, "1000baseKX-F" }, - { ADVERTISED_2500baseX_Full, "2500baseX-F" }, - { ADVERTISED_10000baseT_Full, "10000baseT-F" }, - { ADVERTISED_10000baseKX4_Full, "10000baseKX4-F" }, - { ADVERTISED_10000baseKR_Full, "10000baseKR-F" }, - { ADVERTISED_20000baseMLD2_Full, "20000baseMLD2-F" }, - { ADVERTISED_20000baseKR2_Full, "20000baseKR2-F" }, - { ADVERTISED_40000baseKR4_Full, "40000baseKR4-F" }, - { ADVERTISED_40000baseCR4_Full, "40000baseCR4-F" }, - { ADVERTISED_40000baseSR4_Full, "40000baseSR4-F" }, - { ADVERTISED_40000baseLR4_Full, "40000baseLR4-F" }, -#ifdef ADVERTISED_56000baseKR4_Full - { ADVERTISED_56000baseKR4_Full, "56000baseKR4-F" }, - { ADVERTISED_56000baseCR4_Full, "56000baseCR4-F" }, - { ADVERTISED_56000baseSR4_Full, "56000baseSR4-F" }, - { ADVERTISED_56000baseLR4_Full, "56000baseLR4-F" }, -#endif -}; - -static void system_add_link_modes(struct blob_buf *b, __u32 mask) -{ - size_t i; - for (i = 0; i < ARRAY_SIZE(ethtool_link_modes); i++) { - if (mask & ethtool_link_modes[i].mask) - blobmsg_add_string(b, NULL, ethtool_link_modes[i].name); - } -} - bool system_if_force_external(const char *ifname) { @@ -2545,41 +2603,213 @@ ethtool_feature_value(const char *ifname, const char *keyname) return active; } +static void +system_add_link_mode_name(struct blob_buf *b, int i, bool half) +{ + char *buf; + + /* allocate string buffer large enough for the mode name and a suffix + * "-F" or "-H" indicating full duplex or half duplex. + */ + buf = blobmsg_alloc_string_buffer(b, NULL, strlen(ethtool_modes[i].name) + 3); + if (!buf) + return; + + strcpy(buf, ethtool_modes[i].name); + if (half) + strcat(buf, "-H"); + else + strcat(buf, "-F"); + + blobmsg_add_string_buffer(b); +} + +static void +system_add_link_modes(__s8 nwords, struct blob_buf *b, __u32 *mask) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(ethtool_modes); i++) { + if (ethtool_link_mode_test_bit(nwords, ethtool_modes[i].bit_half, mask)) + system_add_link_mode_name(b, i, true); + + if (ethtool_link_mode_test_bit(nwords, ethtool_modes[i].bit_full, mask)) + system_add_link_mode_name(b, i, false); + } +} + +static void +system_add_pause_modes(__s8 nwords, struct blob_buf *b, __u32 *mask) +{ + if (ethtool_link_mode_test_bit(nwords, ETHTOOL_LINK_MODE_Pause_BIT, mask)) + blobmsg_add_string(b, NULL, "pause"); + + if (ethtool_link_mode_test_bit(nwords, ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask)) + blobmsg_add_string(b, NULL, "asym_pause"); +} + + +static void +system_add_ethtool_pause_an(struct blob_buf *b, __s8 nwords, + __u32 *advertising, __u32 *lp_advertising) +{ + bool an_rx = false, an_tx = false; + void *d; + + d = blobmsg_open_array(b, "negotiated"); + + /* Work out negotiated pause frame usage per + * IEEE 802.3-2005 table 28B-3. + */ + if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + advertising) && + ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + lp_advertising)) { + an_tx = true; + an_rx = true; + } else if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + advertising) && + ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + lp_advertising)) { + if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + advertising)) + an_rx = true; + else if (ethtool_link_mode_test_bit(nwords, + ETHTOOL_LINK_MODE_Pause_BIT, + lp_advertising)) + an_tx = true; + } + if (an_tx) + blobmsg_add_string(b, NULL, "rx"); + + if (an_rx) + blobmsg_add_string(b, NULL, "tx"); + + blobmsg_close_array(b, d); +} + +static void +system_get_ethtool_pause(struct device *dev, bool *rx_pause, bool *tx_pause, bool *pause_autoneg) +{ + struct ethtool_pauseparam pp; + struct ifreq ifr = { + .ifr_data = (caddr_t)&pp, + }; + + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); + memset(&pp, 0, sizeof(pp)); + pp.cmd = ETHTOOL_GPAUSEPARAM; + + /* may fail */ + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) == -1) { + *pause_autoneg = true; + return; + } + + *rx_pause = pp.rx_pause; + *tx_pause = pp.tx_pause; + *pause_autoneg = pp.autoneg; +} + int system_if_dump_info(struct device *dev, struct blob_buf *b) { - struct ethtool_cmd ecmd; - struct ifreq ifr; + __u32 *supported, *advertising, *lp_advertising; + bool rx_pause, tx_pause, pause_autoneg; + struct { + struct ethtool_link_settings req; + __u32 link_mode_data[3 * 127]; + } ecmd; + struct ifreq ifr = { + .ifr_data = (caddr_t)&ecmd, + }; + __s8 nwords; + void *c, *d; char *s; - void *c; + + system_get_ethtool_pause(dev, &rx_pause, &tx_pause, &pause_autoneg); memset(&ecmd, 0, sizeof(ecmd)); - memset(&ifr, 0, sizeof(ifr)); + ecmd.req.cmd = ETHTOOL_GLINKSETTINGS; strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name) - 1); - ifr.ifr_data = (caddr_t) &ecmd; - ecmd.cmd = ETHTOOL_GSET; - if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) == 0) { - c = blobmsg_open_array(b, "link-advertising"); - system_add_link_modes(b, ecmd.advertising); - blobmsg_close_array(b, c); + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords >= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return -EOPNOTSUPP; + + ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) < 0 || + ecmd.req.link_mode_masks_nwords <= 0 || + ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return -EIO; + + nwords = ecmd.req.link_mode_masks_nwords; + supported = &ecmd.link_mode_data[0]; + advertising = &ecmd.link_mode_data[nwords]; + lp_advertising = &ecmd.link_mode_data[2 * nwords]; + + c = blobmsg_open_array(b, "link-advertising"); + system_add_link_modes(nwords, b, advertising); + blobmsg_close_array(b, c); + + c = blobmsg_open_array(b, "link-partner-advertising"); + system_add_link_modes(nwords, b, lp_advertising); + blobmsg_close_array(b, c); + + c = blobmsg_open_array(b, "link-supported"); + system_add_link_modes(nwords, b, supported); + blobmsg_close_array(b, c); + + if (ethtool_validate_speed(ecmd.req.speed) && + (ecmd.req.speed != (__u32)SPEED_UNKNOWN) && + (ecmd.req.speed != 0)) { + s = blobmsg_alloc_string_buffer(b, "speed", 10); + snprintf(s, 8, "%d%c", ecmd.req.speed, + ecmd.req.duplex == DUPLEX_HALF ? 'H' : 'F'); + blobmsg_add_string_buffer(b); + } + blobmsg_add_u8(b, "autoneg", !!ecmd.req.autoneg); - c = blobmsg_open_array(b, "link-partner-advertising"); - system_add_link_modes(b, ecmd.lp_advertising); - blobmsg_close_array(b, c); + c = blobmsg_open_table(b, "flow-control"); + blobmsg_add_u8(b, "autoneg", pause_autoneg); - c = blobmsg_open_array(b, "link-supported"); - system_add_link_modes(b, ecmd.supported); - blobmsg_close_array(b, c); + d = blobmsg_open_array(b, "supported"); + system_add_pause_modes(nwords, b, supported); + blobmsg_close_array(b, d); - s = blobmsg_alloc_string_buffer(b, "speed", 8); - snprintf(s, 8, "%d%c", ethtool_cmd_speed(&ecmd), - ecmd.duplex == DUPLEX_HALF ? 'H' : 'F'); - blobmsg_add_string_buffer(b); + if (pause_autoneg) { + d = blobmsg_open_array(b, "link-advertising"); + system_add_pause_modes(nwords, b, advertising); + blobmsg_close_array(b, d); + } - blobmsg_add_u8(b, "autoneg", !!ecmd.autoneg); + d = blobmsg_open_array(b, "link-partner-advertising"); + system_add_pause_modes(nwords, b, lp_advertising); + blobmsg_close_array(b, d); + + if (pause_autoneg) { + system_add_ethtool_pause_an(b, nwords, advertising, + lp_advertising); + } else { + d = blobmsg_open_array(b, "selected"); + if (rx_pause) + blobmsg_add_string(b, NULL, "rx"); + + if (tx_pause) + blobmsg_add_string(b, NULL, "tx"); + + blobmsg_close_array(b, d); } + blobmsg_close_table(b, c); + blobmsg_add_u8(b, "hw-tc-offload", ethtool_feature_value(dev->ifname, "hw-tc-offload"));