/* * settings.c - netlink implementation of settings commands * * Implementation of "ethtool " and "ethtool -s ...". */ #include #include #include #include #include "../internal.h" #include "../common.h" #include "json_print.h" #include "json_writer.h" #include "netlink.h" #include "strset.h" #include "bitset.h" #include "parser.h" /* GET_SETTINGS */ struct link_mode_info { enum link_mode_class class; u32 speed; u8 duplex; }; static const char *const names_duplex[] = { [DUPLEX_HALF] = "Half", [DUPLEX_FULL] = "Full", }; static const char *const names_master_slave_state[] = { [MASTER_SLAVE_STATE_UNKNOWN] = "unknown", [MASTER_SLAVE_STATE_MASTER] = "master", [MASTER_SLAVE_STATE_SLAVE] = "slave", [MASTER_SLAVE_STATE_ERR] = "resolution error", }; static const char *const names_master_slave_cfg[] = { [MASTER_SLAVE_CFG_UNKNOWN] = "unknown", [MASTER_SLAVE_CFG_MASTER_PREFERRED] = "preferred master", [MASTER_SLAVE_CFG_SLAVE_PREFERRED] = "preferred slave", [MASTER_SLAVE_CFG_MASTER_FORCE] = "forced master", [MASTER_SLAVE_CFG_SLAVE_FORCE] = "forced slave", }; static const char *const names_port[] = { [PORT_TP] = "Twisted Pair", [PORT_AUI] = "AUI", [PORT_BNC] = "BNC", [PORT_MII] = "MII", [PORT_FIBRE] = "FIBRE", [PORT_DA] = "Direct Attach Copper", [PORT_NONE] = "None", [PORT_OTHER] = "Other", }; static const char *const names_transceiver[] = { [XCVR_INTERNAL] = "internal", [XCVR_EXTERNAL] = "external", }; /* the practice of putting completely unrelated flags into link mode bitmaps * is rather unfortunate but as even ethtool_link_ksettings preserved that, * there is little chance of getting them separated any time soon so let's * sort them out ourselves */ #define __REAL(_speed) \ { .class = LM_CLASS_REAL, .speed = _speed, .duplex = DUPLEX_FULL } #define __HALF_DUPLEX(_speed) \ { .class = LM_CLASS_REAL, .speed = _speed, .duplex = DUPLEX_HALF } #define __SPECIAL(_class) \ { .class = LM_CLASS_ ## _class } static const struct link_mode_info link_modes[] = { [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = __HALF_DUPLEX(10), [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = __REAL(10), [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = __HALF_DUPLEX(100), [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = __REAL(100), [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = __HALF_DUPLEX(1000), [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = __REAL(1000), [ETHTOOL_LINK_MODE_Autoneg_BIT] = __SPECIAL(AUTONEG), [ETHTOOL_LINK_MODE_TP_BIT] = __SPECIAL(PORT), [ETHTOOL_LINK_MODE_AUI_BIT] = __SPECIAL(PORT), [ETHTOOL_LINK_MODE_MII_BIT] = __SPECIAL(PORT), [ETHTOOL_LINK_MODE_FIBRE_BIT] = __SPECIAL(PORT), [ETHTOOL_LINK_MODE_BNC_BIT] = __SPECIAL(PORT), [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_Pause_BIT] = __SPECIAL(PAUSE), [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = __SPECIAL(PAUSE), [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = __REAL(2500), [ETHTOOL_LINK_MODE_Backplane_BIT] = __SPECIAL(PORT), [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = __REAL(1000), [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = __REAL(20000), [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = __REAL(20000), [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = __REAL(40000), [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = __REAL(40000), [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = __REAL(40000), [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = __REAL(40000), [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = __REAL(56000), [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = __REAL(56000), [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = __REAL(56000), [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = __REAL(56000), [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = __REAL(25000), [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = __REAL(25000), [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = __REAL(25000), [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = __REAL(1000), [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = __REAL(10000), [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = __REAL(2500), [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = __REAL(5000), [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = __SPECIAL(FEC), [ETHTOOL_LINK_MODE_FEC_RS_BIT] = __SPECIAL(FEC), [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = __SPECIAL(FEC), [ETHTOOL_LINK_MODE_50000baseKR_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_50000baseSR_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_50000baseCR_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_50000baseDR_Full_BIT] = __REAL(50000), [ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_100baseT1_Full_BIT] = __REAL(100), [ETHTOOL_LINK_MODE_1000baseT1_Full_BIT] = __REAL(1000), [ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_FEC_LLRS_BIT] = __SPECIAL(FEC), [ETHTOOL_LINK_MODE_100000baseKR_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseSR_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseCR_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_100000baseDR_Full_BIT] = __REAL(100000), [ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT] = __REAL(200000), [ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT] = __REAL(400000), [ETHTOOL_LINK_MODE_100baseFX_Half_BIT] = __HALF_DUPLEX(100), [ETHTOOL_LINK_MODE_100baseFX_Full_BIT] = __REAL(100), [ETHTOOL_LINK_MODE_10baseT1L_Full_BIT] = __REAL(10), [ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT] = __REAL(800000), [ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT] = __REAL(800000), [ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT] = __REAL(800000), [ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT] = __REAL(800000), [ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT] = __REAL(800000), [ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT] = __REAL(800000), [ETHTOOL_LINK_MODE_10baseT1S_Full_BIT] = __REAL(10), [ETHTOOL_LINK_MODE_10baseT1S_Half_BIT] = __HALF_DUPLEX(10), [ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT] = __HALF_DUPLEX(10), }; const unsigned int link_modes_count = ARRAY_SIZE(link_modes); #undef __REAL #undef __HALF_DUPLEX #undef __SPECIAL static bool lm_class_match(unsigned int mode, enum link_mode_class class) { unsigned int mode_class = (mode < link_modes_count) ? link_modes[mode].class : LM_CLASS_UNKNOWN; return mode_class == class || (class == LM_CLASS_REAL && mode_class == LM_CLASS_UNKNOWN); } static void print_enum(const char *const *info, unsigned int n_info, unsigned int val, const char *label, const char *json_key) { if (val >= n_info || !info[val]) { if (!is_json_context()) printf("\t%s: Unknown! (%d)\n", label, val); } else { if (!is_json_context()) printf("\t%s: %s\n", label, info[val]); else print_string(PRINT_JSON, json_key, "%s", info[val]); } } static int dump_pause(const struct nlattr *attr, bool mask, const char *label, const char *label_json) { bool pause, asym; int ret = 0; pause = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Pause_BIT, &ret); if (ret < 0) goto err; asym = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Asym_Pause_BIT, &ret); if (ret < 0) goto err; if (!is_json_context()) printf("\t%s", label); if (pause) print_string(PRINT_ANY, label_json, "%s\n", asym ? "Symmetric Receive-only" : "Symmetric"); else print_string(PRINT_ANY, label_json, "%s\n", asym ? "Transmit-only" : "No"); return 0; err: fprintf(stderr, "malformed netlink message (pause modes)\n"); return ret; } static void print_banner(struct nl_context *nlctx) { if (nlctx->no_banner) return; print_string(PRINT_ANY, "ifname", "Settings for %s:\n", nlctx->devname); nlctx->no_banner = true; } int dump_link_modes(struct nl_context *nlctx, const struct nlattr *bitset, bool mask, unsigned int class, const char *before, const char *between, const char *after, const char *if_none, const char *json_key) { const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(bitset_tb); const unsigned int before_len = strlen(before); unsigned int prev = UINT_MAX - 1; const struct nlattr *bits; const struct nlattr *bit; bool first = true; bool nomask; int ret; ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info); if (ret < 0) goto err_nonl; nomask = bitset_tb[ETHTOOL_A_BITSET_NOMASK]; /* Trying to print the mask of a "no mask" bitset doesn't make sense */ if (mask && nomask) { ret = -EFAULT; goto err_nonl; } bits = bitset_tb[ETHTOOL_A_BITSET_BITS]; open_json_array(json_key, ""); if (!bits) { const struct stringset *lm_strings; unsigned int count; unsigned int idx; const char *name; ret = netlink_init_ethnl2_socket(nlctx); if (ret < 0) goto err_nonl; lm_strings = global_stringset(ETH_SS_LINK_MODES, nlctx->ethnl2_socket); bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] : bitset_tb[ETHTOOL_A_BITSET_VALUE]; ret = -EFAULT; if (!bits || !bitset_tb[ETHTOOL_A_BITSET_SIZE]) goto err_nonl; count = mnl_attr_get_u32(bitset_tb[ETHTOOL_A_BITSET_SIZE]); if (mnl_attr_get_payload_len(bits) / 4 < (count + 31) / 32) goto err_nonl; if (!is_json_context()) printf("\t%s", before); for (idx = 0; idx < count; idx++) { const uint32_t *raw_data = mnl_attr_get_payload(bits); char buff[14]; if (!(raw_data[idx / 32] & (1U << (idx % 32)))) continue; if (!lm_class_match(idx, class)) continue; name = get_string(lm_strings, idx); if (!name) { snprintf(buff, sizeof(buff), "BIT%u", idx); name = buff; } if (first) first = false; /* ugly hack to preserve old output format */ if (class == LM_CLASS_REAL && (idx == prev + 1) && prev < link_modes_count && link_modes[prev].class == LM_CLASS_REAL && link_modes[prev].duplex == DUPLEX_HALF) { if (!is_json_context()) putchar(' '); } else if (between) { if (!is_json_context()) printf("\t%s", between); } else if (!is_json_context()) printf("\n\t%*s", before_len, ""); print_string(PRINT_ANY, NULL, "%s", name); prev = idx; } goto after; } if (!is_json_context()) printf("\t%s", before); mnl_attr_for_each_nested(bit, bits) { const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); unsigned int idx; const char *name; if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT) continue; ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info); if (ret < 0) goto err; ret = -EFAULT; if (!tb[ETHTOOL_A_BITSET_BIT_INDEX] || !tb[ETHTOOL_A_BITSET_BIT_NAME]) goto err; if (!mask && !nomask && !tb[ETHTOOL_A_BITSET_BIT_VALUE]) continue; idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]); name = mnl_attr_get_str(tb[ETHTOOL_A_BITSET_BIT_NAME]); if (!lm_class_match(idx, class)) continue; if (first) { first = false; } else { /* ugly hack to preserve old output format */ if ((class == LM_CLASS_REAL) && (idx == prev + 1) && (prev < link_modes_count) && (link_modes[prev].class == LM_CLASS_REAL) && (link_modes[prev].duplex == DUPLEX_HALF)) { if (!is_json_context()) putchar(' '); } else if (between) { if (!is_json_context()) printf("\t%s", between); } else if (!is_json_context()) printf("\n\t%*s", before_len, ""); } print_string(PRINT_ANY, NULL, "%s", name); prev = idx; } after: if (first && if_none) print_string(PRINT_FP, NULL, "%s", if_none); close_json_array(after); return 0; err: putchar('\n'); err_nonl: fflush(stdout); fprintf(stderr, "malformed netlink message (link_modes)\n"); close_json_array(""); return ret; } static int dump_our_modes(struct nl_context *nlctx, const struct nlattr *attr) { bool autoneg; int ret; print_banner(nlctx); ret = dump_link_modes(nlctx, attr, true, LM_CLASS_PORT, "Supported ports: [ ", " ", " ]\n", NULL, "supported-ports"); if (ret < 0) return ret; ret = dump_link_modes(nlctx, attr, true, LM_CLASS_REAL, "Supported link modes: ", NULL, "\n", "Not reported", "supported-link-modes"); if (ret < 0) return ret; ret = dump_pause(attr, true, "Supported pause frame use: ", "supported-pause-frame-use"); if (ret < 0) return ret; autoneg = bitset_get_bit(attr, true, ETHTOOL_LINK_MODE_Autoneg_BIT, &ret); if (ret < 0) return ret; if (is_json_context()) print_bool(PRINT_JSON, "supports-auto-negotiation", NULL, autoneg); else printf("\tSupports auto-negotiation: %s\n", autoneg ? "Yes" : "No"); ret = dump_link_modes(nlctx, attr, true, LM_CLASS_FEC, "Supported FEC modes: ", " ", "\n", "Not reported", "supported-fec-modes"); if (ret < 0) return ret; ret = dump_link_modes(nlctx, attr, false, LM_CLASS_REAL, "Advertised link modes: ", NULL, "\n", "Not reported", "advertised-link-modes"); if (ret < 0) return ret; ret = dump_pause(attr, false, "Advertised pause frame use: ", "advertised-pause-frame-use"); if (ret < 0) return ret; autoneg = bitset_get_bit(attr, false, ETHTOOL_LINK_MODE_Autoneg_BIT, &ret); if (ret < 0) return ret; if (!is_json_context()) printf("\tAdvertised auto-negotiation: %s\n", autoneg ? "Yes" : "No"); else print_bool(PRINT_JSON, "advertised-auto-negotiation", NULL, autoneg); ret = dump_link_modes(nlctx, attr, false, LM_CLASS_FEC, "Advertised FEC modes: ", " ", "\n", "Not reported", "advertised-fec-modes"); return ret; } static int dump_peer_modes(struct nl_context *nlctx, const struct nlattr *attr) { bool autoneg; int ret; print_banner(nlctx); ret = dump_link_modes(nlctx, attr, false, LM_CLASS_REAL, "Link partner advertised link modes: ", NULL, "\n", "Not reported", "link-partner-advertised-link-modes"); if (ret < 0) return ret; ret = dump_pause(attr, false, "Link partner advertised pause frame use: ", "link-partner-advertised-pause-frame-use"); if (ret < 0) return ret; autoneg = bitset_get_bit(attr, false, ETHTOOL_LINK_MODE_Autoneg_BIT, &ret); if (ret < 0) return ret; if (!is_json_context()) print_string(PRINT_FP, NULL, "\tLink partner advertised auto-negotiation: %s\n", autoneg ? "Yes" : "No"); else print_bool(PRINT_JSON, "link-partner-advertised-auto-negotiation", NULL, autoneg); ret = dump_link_modes(nlctx, attr, false, LM_CLASS_FEC, "Link partner advertised FEC modes: ", " ", "\n", "Not reported", "link-partner-advertised-fec-modes"); return ret; } int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_context *nlctx = data; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKMODES_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; if (tb[ETHTOOL_A_LINKMODES_OURS]) { ret = dump_our_modes(nlctx, tb[ETHTOOL_A_LINKMODES_OURS]); if (ret < 0) goto err; } if (tb[ETHTOOL_A_LINKMODES_PEER]) { ret = dump_peer_modes(nlctx, tb[ETHTOOL_A_LINKMODES_PEER]); if (ret < 0) goto err; } if (tb[ETHTOOL_A_LINKMODES_SPEED]) { uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKMODES_SPEED]); print_banner(nlctx); if (val == 0 || val == (uint16_t)(-1) || val == (uint32_t)(-1)) { if (!is_json_context()) printf("\tSpeed: Unknown!\n"); } else { print_uint(PRINT_ANY, "speed", "\tSpeed: %uMb/s\n", val); } } if (tb[ETHTOOL_A_LINKMODES_LANES]) { uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKMODES_LANES]); print_banner(nlctx); print_uint(PRINT_ANY, "lanes", "\tLanes: %s\n", val); } if (tb[ETHTOOL_A_LINKMODES_DUPLEX]) { uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_DUPLEX]); print_banner(nlctx); print_enum(names_duplex, ARRAY_SIZE(names_duplex), val, "Duplex", "duplex"); } if (tb[ETHTOOL_A_LINKMODES_AUTONEG]) { int autoneg = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_AUTONEG]); print_banner(nlctx); if (!is_json_context()) printf("\tAuto-negotiation: %s\n", (autoneg == AUTONEG_DISABLE) ? "off" : "on"); else print_bool(PRINT_JSON, "auto-negotiation", NULL, autoneg == AUTONEG_DISABLE); } if (tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG]) { uint8_t val; val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG]); print_banner(nlctx); print_enum(names_master_slave_cfg, ARRAY_SIZE(names_master_slave_cfg), val, "master-slave cfg", "master-slave-cfg"); } if (tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE]) { uint8_t val; val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_MASTER_SLAVE_STATE]); print_banner(nlctx); print_enum(names_master_slave_state, ARRAY_SIZE(names_master_slave_state), val, "master-slave status", "master-slave-status"); } return MNL_CB_OK; err: if (nlctx->is_monitor || nlctx->is_dump) return MNL_CB_OK; fputs("No data available\n", stderr); nlctx->exit_code = 75; return MNL_CB_ERROR; } int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_context *nlctx = data; int port = -1; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKINFO_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; if (tb[ETHTOOL_A_LINKINFO_PORT]) { uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_PORT]); print_banner(nlctx); print_enum(names_port, ARRAY_SIZE(names_port), val, "Port", "port"); port = val; } if (tb[ETHTOOL_A_LINKINFO_PHYADDR]) { uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_PHYADDR]); print_banner(nlctx); print_uint(PRINT_ANY, "phyad", "\tPHYAD: %x\n", val); } if (tb[ETHTOOL_A_LINKINFO_TRANSCEIVER]) { uint8_t val; val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TRANSCEIVER]); print_banner(nlctx); print_enum(names_transceiver, ARRAY_SIZE(names_transceiver), val, "Transceiver", "transceiver"); } if (tb[ETHTOOL_A_LINKINFO_TP_MDIX] && tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] && port == PORT_TP) { uint8_t mdix = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TP_MDIX]); uint8_t mdix_ctrl = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]); print_banner(nlctx); dump_mdix(mdix, mdix_ctrl); } return MNL_CB_OK; } static const char *get_enum_string(const char *const *string_table, unsigned int n_string_table, unsigned int val) { if (val >= n_string_table || !string_table[val]) return NULL; else return string_table[val]; } static const char *const names_link_ext_state[] = { [ETHTOOL_LINK_EXT_STATE_AUTONEG] = "Autoneg", [ETHTOOL_LINK_EXT_STATE_LINK_TRAINING_FAILURE] = "Link training failure", [ETHTOOL_LINK_EXT_STATE_LINK_LOGICAL_MISMATCH] = "Logical mismatch", [ETHTOOL_LINK_EXT_STATE_BAD_SIGNAL_INTEGRITY] = "Bad signal integrity", [ETHTOOL_LINK_EXT_STATE_NO_CABLE] = "No cable", [ETHTOOL_LINK_EXT_STATE_CABLE_ISSUE] = "Cable issue", [ETHTOOL_LINK_EXT_STATE_EEPROM_ISSUE] = "EEPROM issue", [ETHTOOL_LINK_EXT_STATE_CALIBRATION_FAILURE] = "Calibration failure", [ETHTOOL_LINK_EXT_STATE_POWER_BUDGET_EXCEEDED] = "Power budget exceeded", [ETHTOOL_LINK_EXT_STATE_OVERHEAT] = "Overheat", [ETHTOOL_LINK_EXT_STATE_MODULE] = "Module", }; static const char *const names_autoneg_link_ext_substate[] = { [ETHTOOL_LINK_EXT_SUBSTATE_AN_NO_PARTNER_DETECTED] = "No partner detected", [ETHTOOL_LINK_EXT_SUBSTATE_AN_ACK_NOT_RECEIVED] = "Ack not received", [ETHTOOL_LINK_EXT_SUBSTATE_AN_NEXT_PAGE_EXCHANGE_FAILED] = "Next page exchange failed", [ETHTOOL_LINK_EXT_SUBSTATE_AN_NO_PARTNER_DETECTED_FORCE_MODE] = "No partner detected during force mode", [ETHTOOL_LINK_EXT_SUBSTATE_AN_FEC_MISMATCH_DURING_OVERRIDE] = "FEC mismatch during override", [ETHTOOL_LINK_EXT_SUBSTATE_AN_NO_HCD] = "No HCD", }; static const char *const names_link_training_link_ext_substate[] = { [ETHTOOL_LINK_EXT_SUBSTATE_LT_KR_FRAME_LOCK_NOT_ACQUIRED] = "KR frame lock not acquired", [ETHTOOL_LINK_EXT_SUBSTATE_LT_KR_LINK_INHIBIT_TIMEOUT] = "KR link inhibit timeout", [ETHTOOL_LINK_EXT_SUBSTATE_LT_KR_LINK_PARTNER_DID_NOT_SET_RECEIVER_READY] = "KR Link partner did not set receiver ready", [ETHTOOL_LINK_EXT_SUBSTATE_LT_REMOTE_FAULT] = "Remote side is not ready yet", }; static const char *const names_link_logical_mismatch_link_ext_substate[] = { [ETHTOOL_LINK_EXT_SUBSTATE_LLM_PCS_DID_NOT_ACQUIRE_BLOCK_LOCK] = "PCS did not acquire block lock", [ETHTOOL_LINK_EXT_SUBSTATE_LLM_PCS_DID_NOT_ACQUIRE_AM_LOCK] = "PCS did not acquire AM lock", [ETHTOOL_LINK_EXT_SUBSTATE_LLM_PCS_DID_NOT_GET_ALIGN_STATUS] = "PCS did not get align_status", [ETHTOOL_LINK_EXT_SUBSTATE_LLM_FC_FEC_IS_NOT_LOCKED] = "FC FEC is not locked", [ETHTOOL_LINK_EXT_SUBSTATE_LLM_RS_FEC_IS_NOT_LOCKED] = "RS FEC is not locked", }; static const char *const names_bad_signal_integrity_link_ext_substate[] = { [ETHTOOL_LINK_EXT_SUBSTATE_BSI_LARGE_NUMBER_OF_PHYSICAL_ERRORS] = "Large number of physical errors", [ETHTOOL_LINK_EXT_SUBSTATE_BSI_UNSUPPORTED_RATE] = "Unsupported rate", [ETHTOOL_LINK_EXT_SUBSTATE_BSI_SERDES_REFERENCE_CLOCK_LOST] = "Serdes reference clock lost", [ETHTOOL_LINK_EXT_SUBSTATE_BSI_SERDES_ALOS] = "Serdes ALOS", }; static const char *const names_cable_issue_link_ext_substate[] = { [ETHTOOL_LINK_EXT_SUBSTATE_CI_UNSUPPORTED_CABLE] = "Unsupported cable", [ETHTOOL_LINK_EXT_SUBSTATE_CI_CABLE_TEST_FAILURE] = "Cable test failure", }; static const char *const names_module_link_ext_substate[] = { [ETHTOOL_LINK_EXT_SUBSTATE_MODULE_CMIS_NOT_READY] = "CMIS module is not in ModuleReady state", }; static const char *link_ext_substate_get(uint8_t link_ext_state_val, uint8_t link_ext_substate_val) { switch (link_ext_state_val) { case ETHTOOL_LINK_EXT_STATE_AUTONEG: return get_enum_string(names_autoneg_link_ext_substate, ARRAY_SIZE(names_autoneg_link_ext_substate), link_ext_substate_val); case ETHTOOL_LINK_EXT_STATE_LINK_TRAINING_FAILURE: return get_enum_string(names_link_training_link_ext_substate, ARRAY_SIZE(names_link_training_link_ext_substate), link_ext_substate_val); case ETHTOOL_LINK_EXT_STATE_LINK_LOGICAL_MISMATCH: return get_enum_string(names_link_logical_mismatch_link_ext_substate, ARRAY_SIZE(names_link_logical_mismatch_link_ext_substate), link_ext_substate_val); case ETHTOOL_LINK_EXT_STATE_BAD_SIGNAL_INTEGRITY: return get_enum_string(names_bad_signal_integrity_link_ext_substate, ARRAY_SIZE(names_bad_signal_integrity_link_ext_substate), link_ext_substate_val); case ETHTOOL_LINK_EXT_STATE_CABLE_ISSUE: return get_enum_string(names_cable_issue_link_ext_substate, ARRAY_SIZE(names_cable_issue_link_ext_substate), link_ext_substate_val); case ETHTOOL_LINK_EXT_STATE_MODULE: return get_enum_string(names_module_link_ext_substate, ARRAY_SIZE(names_module_link_ext_substate), link_ext_substate_val); default: return NULL; } } static void linkstate_link_ext_substate_print(const struct nlattr *tb[], uint8_t link_ext_state_val) { uint8_t link_ext_substate_val; const char *link_ext_substate_str; if (!tb[ETHTOOL_A_LINKSTATE_EXT_SUBSTATE]) return; link_ext_substate_val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_EXT_SUBSTATE]); link_ext_substate_str = link_ext_substate_get(link_ext_state_val, link_ext_substate_val); if (!link_ext_substate_str) print_uint(PRINT_ANY, NULL, ", %u", link_ext_state_val); else print_string(PRINT_ANY, NULL, ", %s", link_ext_substate_str); } static void linkstate_link_ext_state_print(const struct nlattr *tb[]) { uint8_t link_ext_state_val; const char *link_ext_state_str; if (!tb[ETHTOOL_A_LINKSTATE_EXT_STATE]) return; link_ext_state_val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_EXT_STATE]); link_ext_state_str = get_enum_string(names_link_ext_state, ARRAY_SIZE(names_link_ext_state), link_ext_state_val); open_json_array("link-state", ""); if (!link_ext_state_str) print_uint(PRINT_ANY, NULL, " (%u", link_ext_state_val); else print_string(PRINT_ANY, NULL, " (%s", link_ext_state_str); linkstate_link_ext_substate_print(tb, link_ext_state_val); close_json_array(")"); } int linkstate_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_LINKSTATE_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_context *nlctx = data; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKSTATE_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; if (tb[ETHTOOL_A_LINKSTATE_LINK]) { uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_LINK]); print_banner(nlctx); if (!is_json_context()) print_string(PRINT_FP, NULL, "\tLink detected: %s", val ? "yes" : "no"); else print_bool(PRINT_JSON, "link-detected", NULL, val); linkstate_link_ext_state_print(tb); if (!is_json_context()) printf("\n"); } if (tb[ETHTOOL_A_LINKSTATE_SQI]) { uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKSTATE_SQI]); print_banner(nlctx); print_uint(PRINT_ANY, "sqi", "\tSQI: %u", val); if (tb[ETHTOOL_A_LINKSTATE_SQI_MAX]) { uint32_t max; max = mnl_attr_get_u32(tb[ETHTOOL_A_LINKSTATE_SQI_MAX]); print_uint(PRINT_ANY, "sqi-max", "/%u\n", max); } else { if (!is_json_context()) printf("\n"); } } if (tb[ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT]) { uint32_t val; val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT]); print_uint(PRINT_ANY, "link-down-events", "\tLink Down Events: %u\n", val); } return MNL_CB_OK; } void wol_modes_cb(unsigned int idx, const char *name __maybe_unused, bool val, void *data) { struct ethtool_wolinfo *wol = data; if (idx >= 32) return; wol->supported |= (1U << idx); if (val) wol->wolopts |= (1U << idx); } int wol_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_WOL_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_context *nlctx = data; struct ethtool_wolinfo wol = {}; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_WOL_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; if (tb[ETHTOOL_A_WOL_MODES]) walk_bitset(tb[ETHTOOL_A_WOL_MODES], NULL, wol_modes_cb, &wol); if (tb[ETHTOOL_A_WOL_SOPASS]) { unsigned int len; len = mnl_attr_get_payload_len(tb[ETHTOOL_A_WOL_SOPASS]); if (len != SOPASS_MAX) fprintf(stderr, "invalid SecureOn password length %u (should be %u)\n", len, SOPASS_MAX); else memcpy(wol.sopass, mnl_attr_get_payload(tb[ETHTOOL_A_WOL_SOPASS]), SOPASS_MAX); } print_banner(nlctx); dump_wol(&wol); return MNL_CB_OK; } void msgmask_cb(unsigned int idx, const char *name __maybe_unused, bool val, void *data) { u32 *msg_mask = data; if (idx >= 32) return; if (val) *msg_mask |= (1U << idx); } void msgmask_cb2(unsigned int idx __maybe_unused, const char *name, bool val, void *data __maybe_unused) { if (val) print_string(PRINT_FP, NULL, " %s", name); } int debug_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_DEBUG_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); const struct stringset *msgmask_strings = NULL; struct nl_context *nlctx = data; u32 msg_mask = 0; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_DEBUG_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; if (!tb[ETHTOOL_A_DEBUG_MSGMASK]) return MNL_CB_OK; if (bitset_is_compact(tb[ETHTOOL_A_DEBUG_MSGMASK])) { ret = netlink_init_ethnl2_socket(nlctx); if (ret < 0) return MNL_CB_OK; msgmask_strings = global_stringset(ETH_SS_MSG_CLASSES, nlctx->ethnl2_socket); } print_banner(nlctx); walk_bitset(tb[ETHTOOL_A_DEBUG_MSGMASK], NULL, msgmask_cb, &msg_mask); print_uint(PRINT_ANY, "current-message-level", " Current message level: 0x%1$08x (%1$u)\n ", msg_mask); walk_bitset(tb[ETHTOOL_A_DEBUG_MSGMASK], msgmask_strings, msgmask_cb2, NULL); if (!is_json_context()) fputc('\n', stdout); return MNL_CB_OK; } int plca_cfg_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_PLCA_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_context *nlctx = data; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_PLCA_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; print_banner(nlctx); if (!is_json_context()) printf("\tPLCA support: "); if (tb[ETHTOOL_A_PLCA_VERSION]) { uint16_t val = mnl_attr_get_u16(tb[ETHTOOL_A_PLCA_VERSION]); if (!is_json_context()) { printf("OPEN Alliance v%u.%u\n", (unsigned int)((val >> 4) & 0xF), (unsigned int)(val & 0xF)); } else { unsigned int length = snprintf(NULL, 0, "%1$u.%1$u", val); char buff[length]; snprintf(buff, length, "%u.%u", (unsigned int)((val >> 4) & 0xF), (unsigned int)(val & 0xF)); print_string(PRINT_JSON, "open-alliance-v", NULL, buff); } } else print_string(PRINT_ANY, "plca-support", "%s\n", "non-standard"); return MNL_CB_OK; } int plca_status_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_PLCA_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_context *nlctx = data; int ret; if (nlctx->is_dump || nlctx->is_monitor) nlctx->no_banner = false; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; nlctx->devname = get_dev_name(tb[ETHTOOL_A_PLCA_HEADER]); if (!dev_ok(nlctx)) return MNL_CB_OK; print_banner(nlctx); const char *status; if (tb[ETHTOOL_A_PLCA_STATUS]) { uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_PLCA_STATUS]); status = val ? "up" : "down"; print_string(PRINT_ANY, "plca-status", "PLCA status: %s", status); } else { print_string(PRINT_FP, NULL, "PLCA status: %s", "unknown"); } return MNL_CB_OK; } static int gset_request(struct cmd_context *ctx, uint8_t msg_type, uint16_t hdr_attr, mnl_cb_t cb) { struct nl_context *nlctx = ctx->nlctx; struct nl_socket *nlsk = nlctx->ethnl_socket; u32 flags; int ret; if (netlink_cmd_check(ctx, msg_type, true)) return 0; flags = get_stats_flag(nlctx, msg_type, hdr_attr); ret = nlsock_prep_get_request(nlsk, msg_type, hdr_attr, flags); if (ret < 0) return ret; return nlsock_send_get_request(nlsk, cb); } int nl_gset(struct cmd_context *ctx) { int ret = 0; new_json_obj(ctx->json); open_json_object(NULL); /* Check for the base set of commands */ if (netlink_cmd_check(ctx, ETHTOOL_MSG_LINKMODES_GET, true) || netlink_cmd_check(ctx, ETHTOOL_MSG_LINKINFO_GET, true) || netlink_cmd_check(ctx, ETHTOOL_MSG_WOL_GET, true) || netlink_cmd_check(ctx, ETHTOOL_MSG_DEBUG_GET, true) || netlink_cmd_check(ctx, ETHTOOL_MSG_LINKSTATE_GET, true)) return -EOPNOTSUPP; ctx->nlctx->suppress_nlerr = 1; ret = gset_request(ctx, ETHTOOL_MSG_LINKMODES_GET, ETHTOOL_A_LINKMODES_HEADER, linkmodes_reply_cb); if (ret == -ENODEV) goto out; ret = gset_request(ctx, ETHTOOL_MSG_LINKINFO_GET, ETHTOOL_A_LINKINFO_HEADER, linkinfo_reply_cb); if (ret == -ENODEV) goto out; ret = gset_request(ctx, ETHTOOL_MSG_WOL_GET, ETHTOOL_A_WOL_HEADER, wol_reply_cb); if (ret == -ENODEV) goto out; ret = gset_request(ctx, ETHTOOL_MSG_PLCA_GET_CFG, ETHTOOL_A_PLCA_HEADER, plca_cfg_reply_cb); if (ret == -ENODEV) goto out; ret = gset_request(ctx, ETHTOOL_MSG_DEBUG_GET, ETHTOOL_A_DEBUG_HEADER, debug_reply_cb); if (ret == -ENODEV) goto out; ret = gset_request(ctx, ETHTOOL_MSG_LINKSTATE_GET, ETHTOOL_A_LINKSTATE_HEADER, linkstate_reply_cb); if (ret == -ENODEV) goto out; ret = gset_request(ctx, ETHTOOL_MSG_PLCA_GET_STATUS, ETHTOOL_A_PLCA_HEADER, plca_status_reply_cb); if (ret == -ENODEV) goto out; if (!ctx->nlctx->no_banner) { print_string(PRINT_FP, NULL, "%s", "No data available\n"); ret = 75; goto out; } ret = 0; out: close_json_object(); delete_json_obj(); return ret; } /* SET_SETTINGS */ enum { WAKE_PHY_BIT = 0, WAKE_UCAST_BIT = 1, WAKE_MCAST_BIT = 2, WAKE_BCAST_BIT = 3, WAKE_ARP_BIT = 4, WAKE_MAGIC_BIT = 5, WAKE_MAGICSECURE_BIT = 6, WAKE_FILTER_BIT = 7, }; #define WAKE_ALL (WAKE_PHY | WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_ARP | \ WAKE_MAGIC | WAKE_MAGICSECURE) static const struct lookup_entry_u8 port_values[] = { { .arg = "tp", .val = PORT_TP }, { .arg = "aui", .val = PORT_AUI }, { .arg = "mii", .val = PORT_MII }, { .arg = "fibre", .val = PORT_FIBRE }, { .arg = "bnc", .val = PORT_BNC }, { .arg = "da", .val = PORT_DA }, {} }; static const struct lookup_entry_u8 mdix_values[] = { { .arg = "auto", .val = ETH_TP_MDI_AUTO }, { .arg = "on", .val = ETH_TP_MDI_X }, { .arg = "off", .val = ETH_TP_MDI }, {} }; static const struct error_parser_data xcvr_parser_data = { .err_msg = "deprecated parameter '%s' not supported by kernel\n", .ret_val = -EINVAL, .extra_args = 1, }; static const struct lookup_entry_u8 autoneg_values[] = { { .arg = "off", .val = AUTONEG_DISABLE }, { .arg = "on", .val = AUTONEG_ENABLE }, {} }; static const struct bitset_parser_data advertise_parser_data = { .no_mask = false, .force_hex = true, }; static const struct lookup_entry_u8 duplex_values[] = { { .arg = "half", .val = DUPLEX_HALF }, { .arg = "full", .val = DUPLEX_FULL }, {} }; static const struct lookup_entry_u8 master_slave_values[] = { { .arg = "preferred-master", .val = MASTER_SLAVE_CFG_MASTER_PREFERRED }, { .arg = "preferred-slave", .val = MASTER_SLAVE_CFG_SLAVE_PREFERRED }, { .arg = "forced-master", .val = MASTER_SLAVE_CFG_MASTER_FORCE }, { .arg = "forced-slave", .val = MASTER_SLAVE_CFG_SLAVE_FORCE }, {} }; char wol_bit_chars[WOL_MODE_COUNT] = { [WAKE_PHY_BIT] = 'p', [WAKE_UCAST_BIT] = 'u', [WAKE_MCAST_BIT] = 'm', [WAKE_BCAST_BIT] = 'b', [WAKE_ARP_BIT] = 'a', [WAKE_MAGIC_BIT] = 'g', [WAKE_MAGICSECURE_BIT] = 's', [WAKE_FILTER_BIT] = 'f', }; const struct char_bitset_parser_data wol_parser_data = { .bit_chars = wol_bit_chars, .nbits = WOL_MODE_COUNT, .reset_char = 'd', }; const struct byte_str_parser_data sopass_parser_data = { .min_len = 6, .max_len = 6, .delim = ':', }; static const struct bitset_parser_data msglvl_parser_data = { .no_mask = false, .force_hex = false, }; static const struct param_parser sset_params[] = { { .arg = "port", .group = ETHTOOL_MSG_LINKINFO_SET, .type = ETHTOOL_A_LINKINFO_PORT, .handler = nl_parse_lookup_u8, .handler_data = port_values, .min_argc = 1, }, { .arg = "mdix", .group = ETHTOOL_MSG_LINKINFO_SET, .type = ETHTOOL_A_LINKINFO_TP_MDIX_CTRL, .handler = nl_parse_lookup_u8, .handler_data = mdix_values, .min_argc = 1, }, { .arg = "phyad", .group = ETHTOOL_MSG_LINKINFO_SET, .type = ETHTOOL_A_LINKINFO_PHYADDR, .handler = nl_parse_direct_u8, .min_argc = 1, }, { .arg = "xcvr", .group = ETHTOOL_MSG_LINKINFO_SET, .handler = nl_parse_error, .handler_data = &xcvr_parser_data, .min_argc = 1, }, { .arg = "autoneg", .group = ETHTOOL_MSG_LINKMODES_SET, .type = ETHTOOL_A_LINKMODES_AUTONEG, .handler = nl_parse_lookup_u8, .handler_data = autoneg_values, .min_argc = 1, }, { .arg = "advertise", .group = ETHTOOL_MSG_LINKMODES_SET, .type = ETHTOOL_A_LINKMODES_OURS, .handler = nl_parse_bitset, .handler_data = &advertise_parser_data, .min_argc = 1, }, { .arg = "speed", .group = ETHTOOL_MSG_LINKMODES_SET, .type = ETHTOOL_A_LINKMODES_SPEED, .handler = nl_parse_direct_u32, .min_argc = 1, }, { .arg = "lanes", .group = ETHTOOL_MSG_LINKMODES_SET, .type = ETHTOOL_A_LINKMODES_LANES, .handler = nl_parse_direct_u32, .min_argc = 1, }, { .arg = "duplex", .group = ETHTOOL_MSG_LINKMODES_SET, .type = ETHTOOL_A_LINKMODES_DUPLEX, .handler = nl_parse_lookup_u8, .handler_data = duplex_values, .min_argc = 1, }, { .arg = "master-slave", .group = ETHTOOL_MSG_LINKMODES_SET, .type = ETHTOOL_A_LINKMODES_MASTER_SLAVE_CFG, .handler = nl_parse_lookup_u8, .handler_data = master_slave_values, .min_argc = 1, }, { .arg = "wol", .group = ETHTOOL_MSG_WOL_SET, .type = ETHTOOL_A_WOL_MODES, .handler = nl_parse_char_bitset, .handler_data = &wol_parser_data, .min_argc = 1, }, { .arg = "sopass", .group = ETHTOOL_MSG_WOL_SET, .type = ETHTOOL_A_WOL_SOPASS, .handler = nl_parse_byte_str, .handler_data = &sopass_parser_data, .min_argc = 1, }, { .arg = "msglvl", .group = ETHTOOL_MSG_DEBUG_SET, .type = ETHTOOL_A_DEBUG_MSGMASK, .handler = nl_parse_bitset, .handler_data = &msglvl_parser_data, .min_argc = 1, }, {} }; /* Maximum number of request messages sent to kernel; must be equal to the * number of different .group values in sset_params[] array. */ #define SSET_MAX_MSGS 4 static int linkmodes_reply_advert_all_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_msg_buff *req_msgbuff = data; const struct nlattr *ours_attr; struct nlattr *req_bitset; uint32_t *supported_modes; unsigned int modes_count; unsigned int i; int ret; ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return MNL_CB_ERROR; ours_attr = tb[ETHTOOL_A_LINKMODES_OURS]; if (!ours_attr) return MNL_CB_ERROR; modes_count = bitset_get_count(tb[ETHTOOL_A_LINKMODES_OURS], &ret); if (ret < 0) return MNL_CB_ERROR; supported_modes = get_compact_bitset_mask(tb[ETHTOOL_A_LINKMODES_OURS]); if (!supported_modes) return MNL_CB_ERROR; /* keep only "real" link modes */ for (i = 0; i < modes_count; i++) if (!lm_class_match(i, LM_CLASS_REAL)) supported_modes[i / 32] &= ~((uint32_t)1 << (i % 32)); req_bitset = ethnla_nest_start(req_msgbuff, ETHTOOL_A_LINKMODES_OURS); if (!req_bitset) return MNL_CB_ERROR; if (ethnla_put_u32(req_msgbuff, ETHTOOL_A_BITSET_SIZE, modes_count) || ethnla_put(req_msgbuff, ETHTOOL_A_BITSET_VALUE, DIV_ROUND_UP(modes_count, 32) * sizeof(uint32_t), supported_modes) || ethnla_put(req_msgbuff, ETHTOOL_A_BITSET_MASK, DIV_ROUND_UP(modes_count, 32) * sizeof(uint32_t), supported_modes)) { ethnla_nest_cancel(req_msgbuff, req_bitset); return MNL_CB_ERROR; } ethnla_nest_end(req_msgbuff, req_bitset); return MNL_CB_OK; } /* For compatibility reasons with ioctl-based ethtool, when "autoneg on" is * specified without "advertise", "speed", "duplex" and "lanes", we need to * query the supported link modes from the kernel and advertise all the "real" * ones. */ static int nl_sset_compat_linkmodes(struct nl_context *nlctx, struct nl_msg_buff *msgbuff) { const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {}; DECLARE_ATTR_TB_INFO(tb); struct nl_socket *nlsk = nlctx->ethnl_socket; int ret; ret = mnl_attr_parse(msgbuff->nlhdr, GENL_HDRLEN, attr_cb, &tb_info); if (ret < 0) return ret; if (!tb[ETHTOOL_A_LINKMODES_AUTONEG] || tb[ETHTOOL_A_LINKMODES_OURS] || tb[ETHTOOL_A_LINKMODES_SPEED] || tb[ETHTOOL_A_LINKMODES_DUPLEX] || tb[ETHTOOL_A_LINKMODES_LANES]) return 0; if (!mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_AUTONEG])) return 0; /* all conditions satisfied, create ETHTOOL_A_LINKMODES_OURS */ if (netlink_cmd_check(nlctx->ctx, ETHTOOL_MSG_LINKMODES_GET, false) || netlink_cmd_check(nlctx->ctx, ETHTOOL_MSG_LINKMODES_SET, false)) return -EOPNOTSUPP; ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_LINKMODES_GET, ETHTOOL_A_LINKMODES_HEADER, ETHTOOL_FLAG_COMPACT_BITSETS); if (ret < 0) return ret; ret = nlsock_sendmsg(nlsk, NULL); if (ret < 0) return ret; return nlsock_process_reply(nlsk, linkmodes_reply_advert_all_cb, msgbuff); } int nl_sset(struct cmd_context *ctx) { struct nl_msg_buff *msgbuffs[SSET_MAX_MSGS] = {}; struct nl_context *nlctx = ctx->nlctx; unsigned int i; int ret; nlctx->cmd = "-s"; nlctx->argp = ctx->argp; nlctx->argc = ctx->argc; nlctx->devname = ctx->devname; ret = nl_parser(nlctx, sset_params, NULL, PARSER_GROUP_MSG, msgbuffs); if (ret == -EOPNOTSUPP) return ret; if (ret < 0) { ret = 1; goto out_free; } for (i = 0; i < SSET_MAX_MSGS && msgbuffs[i]; i++) { struct nl_socket *nlsk = nlctx->ethnl_socket; if (msgbuffs[i]->genlhdr->cmd == ETHTOOL_MSG_LINKMODES_SET) { ret = nl_sset_compat_linkmodes(nlctx, msgbuffs[i]); if (ret < 0) goto out_free; } ret = nlsock_sendmsg(nlsk, msgbuffs[i]); if (ret < 0) goto out_free; ret = nlsock_process_reply(nlsk, nomsg_reply_cb, NULL); if (ret < 0) goto out_free; } out_free: for (i = 0; i < SSET_MAX_MSGS && msgbuffs[i]; i++) { msgbuff_done(msgbuffs[i]); free(msgbuffs[i]); } if (ret >= 0) return ret; return nlctx->exit_code ?: 75; }