Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753284AbdLKNyT (ORCPT ); Mon, 11 Dec 2017 08:54:19 -0500 Received: from mx2.suse.de ([195.135.220.15]:60258 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753052AbdLKNyN (ORCPT ); Mon, 11 Dec 2017 08:54:13 -0500 Message-Id: In-Reply-To: References: From: Michal Kubecek Subject: [RFC PATCH 6/9] ethtool: implement GET_SETTINGS message To: netdev@vger.kernel.org Cc: linux-kernel@vger.kernel.org Date: Mon, 11 Dec 2017 14:54:11 +0100 (CET) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 28465 Lines: 823 Requests the information provided by ETHTOOL_GLINKSETTINGS, ETHTOOL_GWOL and ETHTOOL_GMSGLVL. The info_mask header field can be used to request only part of the information. Flag ETH_SETTINGS_RF_COMPACT_BITSETS switches between flag-by-flag list and compact bitmaps for link modes in the reply. Signed-off-by: Michal Kubecek --- Documentation/networking/ethtool-netlink.txt | 62 +++++- include/linux/ethtool_netlink.h | 3 + include/linux/netdevice.h | 2 + include/uapi/linux/ethtool.h | 3 + include/uapi/linux/ethtool_netlink.h | 36 ++++ net/core/ethtool.c | 108 +--------- net/core/ethtool_common.c | 112 ++++++++++ net/core/ethtool_common.h | 8 + net/core/ethtool_netlink.c | 299 +++++++++++++++++++++++++++ 9 files changed, 528 insertions(+), 105 deletions(-) diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt index cb992180b211..7aabc87c9f09 100644 --- a/Documentation/networking/ethtool-netlink.txt +++ b/Documentation/networking/ethtool-netlink.txt @@ -109,6 +109,8 @@ List of message types ETHTOOL_CMD_GET_DRVINFO ETHTOOL_CMD_SET_DRVINFO response only + ETHTOOL_CMD_GET_SETTINGS + ETHTOOL_CMD_SET_SETTINGS response only (for now) All constants use ETHTOOL_CMD_ prefix followed by "GET", "SET" or "ACT" to indicate the type. @@ -147,6 +149,56 @@ response. All information is read only, SET_DRVINFO request is not implemented. +GET_SETTINGS +------------ + +GET_SETTINGS request retrieves information provided by ETHTOOL_GLINKSETTINGS, +ETHTOOL_GWOL, ETHTOOL_GMSGLVL and ETHTOOL_GLINK ioctl requests. The request +doesn't use any attributes. + +Header flags: + ETH_SETTINGS_RF_COMPACT_BITSETS bitset form in response (1 is compact) + +Header info_mask bits: + ETH_SETTINGS_IM_LINKINFO link_ksettings except link modes + ETH_SETTINGS_IM_LINKMODES link modes from link_ksettings + ETH_SETTINGS_IM_MSGLEVEL msglevel + ETH_SETTINGS_IM_WOLINFO struct ethtool_wolinfo + ETH_SETTINGS_IM_LINK link state + +Zero info_mask + +Response contents: + + ETHA_SETTINGS_SPEED (u32) link speed (Mb/s) + ETHA_SETTINGS_DUPLEX (u8) duplex mode + ETHA_SETTINGS_PORT (u8) physical port + ETHA_SETTINGS_PHYADDR (u8) MDIO address of phy + ETHA_SETTINGS_AUTONEG (u8) autoneotiation status + ETHA_SETTINGS_MDIO_SUPPORT (bitfield32) MDIO support flags + ETHA_SETTINGS_TP_MDIX (u8) MDI(-X) status + ETHA_SETTINGS_TP_MDIX_CTRL (u8) MDI(-X) control + ETHA_SETTINGS_TRANSCEIVER (u8) transceiver + ETHA_SETTINGS_WOL_MODES (bitfield32) wake-on-lan modes + ETHA_SETTINGS_SOPASS (binary) SecureOn(tm) password + ETHA_SETTINGS_MSGLVL (bitfield32) debug level + ETHA_SETTINGS_LINK_MODES (bitset) device link modes + ETHA_SETTINGS_PEER_MODES (bitset) link partner link modes + ETHA_SETTINGS_LINK (u32) link state + +Most of the attributes have the same meaning (including values) as +corresponding members of ioctl structures. For ETHA_SETTINGS_MDIO_SUPPORT and +ETHA_SETTINGS_MSGLVL, selector reports flags supported by kernel. For +ETHA_SETTINGS_WOL_MODES it reports flags supported by the device. For +ETHA_SETTINGS_LINK_MODES, value represent advertised modes and mask represents +supported modes. For ETHA_SETTINGS_PEER_MODES, both value and mask represent +partner advertised link modes. + +GET_SETTINGS request is allowed for unprivileged user but ETHA_SETTINGS_SOPASS +is only provided by kernel in response to privileged (netns CAP_NET_ADMIN) +requests. + + Request translation ------------------- @@ -156,16 +208,16 @@ have their netlink replacement yet. ioctl command netlink command --------------------------------------------------------------------- -ETHTOOL_GSET n/a +ETHTOOL_GSET ETHTOOL_CMD_GET_SETTINGS ETHTOOL_SSET n/a ETHTOOL_GDRVINFO ETHTOOL_CMD_GET_DRVINFO ETHTOOL_GREGS n/a -ETHTOOL_GWOL n/a +ETHTOOL_GWOL ETHTOOL_CMD_GET_SETTINGS ETHTOOL_SWOL n/a -ETHTOOL_GMSGLVL n/a +ETHTOOL_GMSGLVL ETHTOOL_CMD_GET_SETTINGS ETHTOOL_SMSGLVL n/a ETHTOOL_NWAY_RST n/a -ETHTOOL_GLINK n/a +ETHTOOL_GLINK ETHTOOL_CMD_GET_SETTINGS ETHTOOL_GEEPROM n/a ETHTOOL_SEEPROM n/a ETHTOOL_GCOALESCE n/a @@ -230,7 +282,7 @@ ETHTOOL_GTUNABLE n/a ETHTOOL_STUNABLE n/a ETHTOOL_GPHYSTATS n/a ETHTOOL_PERQUEUE n/a -ETHTOOL_GLINKSETTINGS n/a +ETHTOOL_GLINKSETTINGS ETHTOOL_CMD_GET_SETTINGS ETHTOOL_SLINKSETTINGS n/a ETHTOOL_PHY_GTUNABLE n/a ETHTOOL_PHY_STUNABLE n/a diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h index 0412adb4f42f..1787359f9e5d 100644 --- a/include/linux/ethtool_netlink.h +++ b/include/linux/ethtool_netlink.h @@ -6,4 +6,7 @@ #include #include +#define __ETHTOOL_LINK_MODE_MASK_NWORDS \ + ((__ETHTOOL_LINK_MODE_MASK_NBITS + 31) / 32) + #endif /* _LINUX_ETHTOOL_NETLINK_H_ */ diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index cc4ce7456e38..0e1d0a04c3cc 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3506,6 +3506,8 @@ enum { NETIF_MSG_PKTDATA = 0x1000, NETIF_MSG_HW = 0x2000, NETIF_MSG_WOL = 0x4000, + + NETIF_MSG_ALL = 0x7fff, }; #define netif_msg_drv(p) ((p)->msg_enable & NETIF_MSG_DRV) diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 44a0b675a6bc..a9076a76cdb4 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -143,6 +143,9 @@ static inline __u32 ethtool_cmd_speed(const struct ethtool_cmd *ep) */ #define ETH_MDIO_SUPPORTS_C45 2 +/* All defined ETH_MDIO_SUPPORTS_* flags */ +#define ETH_MDIO_SUPPORTS_ALL (ETH_MDIO_SUPPORTS_C22 | ETH_MDIO_SUPPORTS_C45) + #define ETHTOOL_FWVERS_LEN 32 #define ETHTOOL_BUSINFO_LEN 32 #define ETHTOOL_EROMVERS_LEN 32 diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h index d6ab1d73d494..9520d13fc9ab 100644 --- a/include/uapi/linux/ethtool_netlink.h +++ b/include/uapi/linux/ethtool_netlink.h @@ -23,6 +23,8 @@ enum { ETHTOOL_CMD_NOOP, ETHTOOL_CMD_GET_DRVINFO, ETHTOOL_CMD_SET_DRVINFO, /* only for reply */ + ETHTOOL_CMD_GET_SETTINGS, + ETHTOOL_CMD_SET_SETTINGS, __ETHTOOL_CMD_MAX, ETHTOOL_CMD_MAX = (__ETHTOOL_CMD_MAX - 1), @@ -78,6 +80,40 @@ enum { ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_MAX - 1), }; +/* GET_SETTINGS / SET_SETTINGS */ + +enum { + ETHA_SETTINGS_UNSPEC, + ETHA_SETTINGS_SPEED, /* u32 */ + ETHA_SETTINGS_DUPLEX, /* u8 */ + ETHA_SETTINGS_PORT, /* u8 */ + ETHA_SETTINGS_PHYADDR, /* u8 */ + ETHA_SETTINGS_AUTONEG, /* u8 */ + ETHA_SETTINGS_MDIO_SUPPORT, /* bitfield32 */ + ETHA_SETTINGS_TP_MDIX, /* u8 */ + ETHA_SETTINGS_TP_MDIX_CTRL, /* u8 */ + ETHA_SETTINGS_TRANSCEIVER, /* u8 */ + ETHA_SETTINGS_WOL_MODES, /* bitfield32 */ + ETHA_SETTINGS_SOPASS, /* binary */ + ETHA_SETTINGS_MSGLVL, /* bitfield32 */ + ETHA_SETTINGS_LINK_MODES, /* bitset */ + ETHA_SETTINGS_PEER_MODES, /* bitset */ + ETHA_SETTINGS_LINK, /* u32 */ + + __ETHA_SETTINGS_MAX, + ETHA_SETTINGS_MAX = (__ETHA_SETTINGS_MAX - 1), +}; + +#define ETH_SETTINGS_RF_COMPACT_BITSETS 0x1 + +#define ETH_SETTINGS_IM_LINKINFO 0x01 +#define ETH_SETTINGS_IM_LINKMODES 0x02 +#define ETH_SETTINGS_IM_MSGLEVEL 0x04 +#define ETH_SETTINGS_IM_WOLINFO 0x08 +#define ETH_SETTINGS_IM_LINK 0x10 + +#define ETH_SETTINGS_IM_DEFAULT 0x1f + /* generic netlink info */ #define ETHTOOL_GENL_NAME "ethtool" #define ETHTOOL_GENL_VERSION 1 diff --git a/net/core/ethtool.c b/net/core/ethtool.c index 09e780a748f9..b08a2efa2e89 100644 --- a/net/core/ethtool.c +++ b/net/core/ethtool.c @@ -452,54 +452,6 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32, } EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32); -/* return false if legacy contained non-0 deprecated fields - * maxtxpkt/maxrxpkt. rest of ksettings always updated - */ -static bool -convert_legacy_settings_to_link_ksettings( - struct ethtool_link_ksettings *link_ksettings, - const struct ethtool_cmd *legacy_settings) -{ - bool retval = true; - - memset(link_ksettings, 0, sizeof(*link_ksettings)); - - /* This is used to tell users that driver is still using these - * deprecated legacy fields, and they should not use - * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS - */ - if (legacy_settings->maxtxpkt || - legacy_settings->maxrxpkt) - retval = false; - - ethtool_convert_legacy_u32_to_link_mode( - link_ksettings->link_modes.supported, - legacy_settings->supported); - ethtool_convert_legacy_u32_to_link_mode( - link_ksettings->link_modes.advertising, - legacy_settings->advertising); - ethtool_convert_legacy_u32_to_link_mode( - link_ksettings->link_modes.lp_advertising, - legacy_settings->lp_advertising); - link_ksettings->base.speed - = ethtool_cmd_speed(legacy_settings); - link_ksettings->base.duplex - = legacy_settings->duplex; - link_ksettings->base.port - = legacy_settings->port; - link_ksettings->base.phy_address - = legacy_settings->phy_address; - link_ksettings->base.autoneg - = legacy_settings->autoneg; - link_ksettings->base.mdio_support - = legacy_settings->mdio_support; - link_ksettings->base.eth_tp_mdix - = legacy_settings->eth_tp_mdix; - link_ksettings->base.eth_tp_mdix_ctrl - = legacy_settings->eth_tp_mdix_ctrl; - return retval; -} - /* return false if ksettings link modes had higher bits * set. legacy_settings always updated (best effort) */ @@ -560,50 +512,6 @@ struct ethtool_link_usettings { } link_modes; }; -/* Internal kernel helper to query a device ethtool_link_settings. - * - * Backward compatibility note: for compatibility with legacy drivers - * that implement only the ethtool_cmd API, this has to work with both - * drivers implementing get_link_ksettings API and drivers - * implementing get_settings API. When drivers implement get_settings - * and report ethtool_cmd deprecated fields - * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored - * because the resulting struct ethtool_link_settings does not report them. - */ -int __ethtool_get_link_ksettings(struct net_device *dev, - struct ethtool_link_ksettings *link_ksettings) -{ - int err; - struct ethtool_cmd cmd; - - ASSERT_RTNL(); - - if (dev->ethtool_ops->get_link_ksettings) { - memset(link_ksettings, 0, sizeof(*link_ksettings)); - return dev->ethtool_ops->get_link_ksettings(dev, - link_ksettings); - } - - /* driver doesn't support %ethtool_link_ksettings API. revert to - * legacy %ethtool_cmd API, unless it's not supported either. - * TODO: remove when ethtool_ops::get_settings disappears internally - */ - if (!dev->ethtool_ops->get_settings) - return -EOPNOTSUPP; - - memset(&cmd, 0, sizeof(cmd)); - cmd.cmd = ETHTOOL_GSET; - err = dev->ethtool_ops->get_settings(dev, &cmd); - if (err < 0) - return err; - - /* we ignore deprecated fields transceiver/maxrxpkt/maxtxpkt - */ - convert_legacy_settings_to_link_ksettings(link_ksettings, &cmd); - return err; -} -EXPORT_SYMBOL(__ethtool_get_link_ksettings); - /* convert ethtool_link_usettings in user space to a kernel internal * ethtool_link_ksettings. return 0 on success, errno on error. */ @@ -1437,11 +1345,11 @@ static int ethtool_reset(struct net_device *dev, char __user *useraddr) static int ethtool_get_wol(struct net_device *dev, char __user *useraddr) { struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; + int rc; - if (!dev->ethtool_ops->get_wol) - return -EOPNOTSUPP; - - dev->ethtool_ops->get_wol(dev, &wol); + rc = __ethtool_get_wol(dev, &wol); + if (rc < 0) + return rc; if (copy_to_user(useraddr, &wol, sizeof(wol))) return -EFAULT; @@ -1506,12 +1414,12 @@ static int ethtool_nway_reset(struct net_device *dev) static int ethtool_get_link(struct net_device *dev, char __user *useraddr) { struct ethtool_value edata = { .cmd = ETHTOOL_GLINK }; + int link = __ethtool_get_link(dev); - if (!dev->ethtool_ops->get_link) - return -EOPNOTSUPP; - - edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev); + if (link < 0) + return link; + edata.data = link; if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; diff --git a/net/core/ethtool_common.c b/net/core/ethtool_common.c index 2c0abab0e43c..30bc2b14cf2a 100644 --- a/net/core/ethtool_common.c +++ b/net/core/ethtool_common.c @@ -44,3 +44,115 @@ int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) return 0; } EXPORT_SYMBOL(__ethtool_get_drvinfo); + +/* return false if legacy contained non-0 deprecated fields + * maxtxpkt/maxrxpkt. rest of ksettings always updated + */ +bool +convert_legacy_settings_to_link_ksettings( + struct ethtool_link_ksettings *link_ksettings, + const struct ethtool_cmd *legacy_settings) +{ + bool retval = true; + + memset(link_ksettings, 0, sizeof(*link_ksettings)); + + /* This is used to tell users that driver is still using these + * deprecated legacy fields, and they should not use + * %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS + */ + if (legacy_settings->maxtxpkt || + legacy_settings->maxrxpkt) + retval = false; + + ethtool_convert_legacy_u32_to_link_mode( + link_ksettings->link_modes.supported, + legacy_settings->supported); + ethtool_convert_legacy_u32_to_link_mode( + link_ksettings->link_modes.advertising, + legacy_settings->advertising); + ethtool_convert_legacy_u32_to_link_mode( + link_ksettings->link_modes.lp_advertising, + legacy_settings->lp_advertising); + link_ksettings->base.speed + = ethtool_cmd_speed(legacy_settings); + link_ksettings->base.duplex + = legacy_settings->duplex; + link_ksettings->base.port + = legacy_settings->port; + link_ksettings->base.phy_address + = legacy_settings->phy_address; + link_ksettings->base.autoneg + = legacy_settings->autoneg; + link_ksettings->base.mdio_support + = legacy_settings->mdio_support; + link_ksettings->base.eth_tp_mdix + = legacy_settings->eth_tp_mdix; + link_ksettings->base.eth_tp_mdix_ctrl + = legacy_settings->eth_tp_mdix_ctrl; + return retval; +} + +/* Internal kernel helper to query a device ethtool_link_settings. + * + * Backward compatibility note: for compatibility with legacy drivers + * that implement only the ethtool_cmd API, this has to work with both + * drivers implementing get_link_ksettings API and drivers + * implementing get_settings API. When drivers implement get_settings + * and report ethtool_cmd deprecated fields + * (transceiver/maxrxpkt/maxtxpkt), these fields are silently ignored + * because the resulting struct ethtool_link_settings does not report them. + */ +int __ethtool_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *link_ksettings) +{ + int err; + struct ethtool_cmd cmd; + + ASSERT_RTNL(); + + if (dev->ethtool_ops->get_link_ksettings) { + memset(link_ksettings, 0, sizeof(*link_ksettings)); + return dev->ethtool_ops->get_link_ksettings(dev, + link_ksettings); + } + + /* driver doesn't support %ethtool_link_ksettings API. revert to + * legacy %ethtool_cmd API, unless it's not supported either. + * TODO: remove when ethtool_ops::get_settings disappears internally + */ + if (!dev->ethtool_ops->get_settings) + return -EOPNOTSUPP; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd = ETHTOOL_GSET; + err = dev->ethtool_ops->get_settings(dev, &cmd); + if (err < 0) + return err; + + /* we ignore deprecated fields transceiver/maxrxpkt/maxtxpkt + */ + convert_legacy_settings_to_link_ksettings(link_ksettings, &cmd); + return err; +} +EXPORT_SYMBOL(__ethtool_get_link_ksettings); + +int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + if (!dev->ethtool_ops->get_wol) + return -EOPNOTSUPP; + + dev->ethtool_ops->get_wol(dev, wol); + + return 0; +} +EXPORT_SYMBOL(__ethtool_get_wol); + +int __ethtool_get_link(struct net_device *dev) +{ + if (!dev->ethtool_ops->get_link) + return -EOPNOTSUPP; + + return netif_running(dev) && dev->ethtool_ops->get_link(dev); +} +EXPORT_SYMBOL(__ethtool_get_link); diff --git a/net/core/ethtool_common.h b/net/core/ethtool_common.h index 1f031c1d943a..92e236952f18 100644 --- a/net/core/ethtool_common.h +++ b/net/core/ethtool_common.h @@ -7,5 +7,13 @@ #include int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info); +int __ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol); +int __ethtool_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *link_ksettings); +int __ethtool_get_link(struct net_device *dev); + +bool convert_legacy_settings_to_link_ksettings( + struct ethtool_link_ksettings *link_ksettings, + const struct ethtool_cmd *legacy_settings); #endif /* _ETHTOOL_COMMON_H */ diff --git a/net/core/ethtool_netlink.c b/net/core/ethtool_netlink.c index 077814fd36bd..0c2bed7850bc 100644 --- a/net/core/ethtool_netlink.c +++ b/net/core/ethtool_netlink.c @@ -4,11 +4,69 @@ #include #include #include +#include #include #include "ethtool_common.h" static struct genl_family ethtool_genl_family; +/* dictionary */ + +static const char *const link_mode_names[] = { + [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = "10baseT/Half", + [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = "10baseT/Full", + [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = "100baseT/Half", + [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = "100baseT/Full", + [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = "1000baseT/Half", + [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = "1000baseT/Full", + [ETHTOOL_LINK_MODE_Autoneg_BIT] = "Autoneg", + [ETHTOOL_LINK_MODE_TP_BIT] = "TP", + [ETHTOOL_LINK_MODE_AUI_BIT] = "AUI", + [ETHTOOL_LINK_MODE_MII_BIT] = "MII", + [ETHTOOL_LINK_MODE_FIBRE_BIT] = "FIBRE", + [ETHTOOL_LINK_MODE_BNC_BIT] = "BNC", + [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = "10000baseT/Full", + [ETHTOOL_LINK_MODE_Pause_BIT] = "Pause", + [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = "Asym_Pause", + [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = "2500baseX/Full", + [ETHTOOL_LINK_MODE_Backplane_BIT] = "Backplane", + [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = "1000baseKX/Full", + [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = "10000baseKX4/Full", + [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = "10000baseKR/Full", + [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = "10000baseR/FEC", + [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = "20000baseMLD2/Full", + [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = "20000baseKR2/Full", + [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = "40000baseKR4/Full", + [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = "40000baseCR4/Full", + [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = "40000baseSR4/Full", + [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = "40000baseLR4/Full", + [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = "56000baseKR4/Full", + [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = "56000baseCR4/Full", + [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = "56000baseSR4/Full", + [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = "56000baseLR4/Full", + [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = "25000baseCR/Full", + [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = "25000baseKR/Full", + [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = "25000baseSR/Full", + [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = "50000baseCR2/Full", + [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = "50000baseKR2/Full", + [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = "100000baseKR4/Full", + [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = "100000baseSR4/Full", + [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = "100000baseCR4/Full", + [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = "100000baseLR4/ER4_Full", + [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = "50000baseSR2/Full", + [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = "1000baseX/Full", + [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = "10000baseCR/Full", + [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = "10000baseSR/Full", + [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = "10000baseLR/Full", + [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = "10000baseLRM/Full", + [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = "10000baseER/Full", + [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = "2500baseT/Full", + [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = "5000baseT/Full", + [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = "None", + [ETHTOOL_LINK_MODE_FEC_RS_BIT] = "RS", + [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = "BASER", +}; + /* misc helper functions */ static int ethnl_str_size(const char *s) @@ -602,6 +660,243 @@ static int ethnl_get_drvinfo(struct sk_buff *skb, struct genl_info *info) return -EMSGSIZE; } +/* GET_SETTINGS */ + +/* To keep things simple, reserve space for some attributes which may not + * be added to the message (e.g. ETHA_SETTINGS_SOPASS); therefore the length + * returned may be bigger than the actual length of the message sent + */ +static int ethnl_settings_size(struct ethnlmsghdr *ehdr, + struct ethtool_link_ksettings *ksettings, + struct net_device *dev, u16 req_mask) +{ + size_t len = 0; + int rc = 0; + + if (req_mask & ETH_SETTINGS_IM_LINKINFO) { + /* speed */ + len += nla_total_size(sizeof(u32)); + /* duplex, autoneg, port, phyaddr, mdix, mdixctrl, transcvr */ + len += 7 * nla_total_size(sizeof(u8)); + /* mdio_support */ + len += nla_total_size(sizeof(struct nla_bitfield32)); + } + if (req_mask & ETH_SETTINGS_IM_LINKMODES) { + u32 *supported = (u32 *)ksettings->link_modes.supported; + u32 *advertising = (u32 *)ksettings->link_modes.advertising; + u32 *lp_advertising = + (u32 *)ksettings->link_modes.lp_advertising; + bool compact = ehdr->flags & ETH_SETTINGS_RF_COMPACT_BITSETS; + + rc = ethnl_bitset_size(compact, __ETHTOOL_LINK_MODE_MASK_NBITS, + advertising, supported, link_mode_names); + if (rc < 0) + return rc; + len += rc; + rc = ethnl_bitset_size(compact, __ETHTOOL_LINK_MODE_MASK_NBITS, + lp_advertising, lp_advertising, + link_mode_names); + if (rc < 0) + return rc; + len += rc; + } + if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) + len += nla_total_size(sizeof(struct nla_bitfield32)); + if (req_mask & ETH_SETTINGS_IM_WOLINFO) { + /* wolopts / wol_supported */ + len += nla_total_size(sizeof(struct nla_bitfield32)); + /* sopass */ + len += nla_total_size(SOPASS_MAX); + } + if (req_mask & ETH_SETTINGS_IM_LINK) + len += nla_total_size(sizeof(u32)); + + return len; +} + +static int ethnl_get_link_ksettings(struct genl_info *info, + struct net_device *dev, + struct ethtool_link_ksettings *ksettings) +{ + int ret; + + ret = __ethtool_get_link_ksettings(dev, ksettings); + + if (ret < 0) + GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); + return ret; +} + +static int ethnl_get_wol(struct genl_info *info, struct net_device *dev, + struct ethtool_wolinfo *wolinfo) +{ + int ret = __ethtool_get_wol(dev, wolinfo); + + if (ret < 0) + GENL_SET_ERR_MSG(info, "failed to retrieve wol info"); + return ret; +} + +static int ethnl_get_settings(struct sk_buff *skb, struct genl_info *info) +{ + struct ethtool_link_ksettings ksettings = {}; + struct ethtool_link_settings *lsettings; + struct ethnlmsghdr *ehdr; + struct ethtool_wolinfo wolinfo = {}; + struct net_device *dev; + struct sk_buff *rskb; + unsigned int reply_len; + bool lpm_empty = true; + u16 req_flags; + u16 req_mask; + u32 msglevel; + int ret = 0; + int link = -EOPNOTSUPP; + + lsettings = &ksettings.base; + ehdr = info->userhdr; + if (!ehdr->info_mask) + ehdr->info_mask = ETH_SETTINGS_IM_DEFAULT; + req_mask = ehdr->info_mask; + req_flags = ehdr->flags; + + dev = ethnl_dev_get(info); + if (IS_ERR(dev)) + return PTR_ERR(dev); + if (req_mask & (ETH_SETTINGS_IM_LINKINFO | ETH_SETTINGS_IM_LINKMODES)) { + rtnl_lock(); + ret = ethnl_get_link_ksettings(info, dev, &ksettings); + rtnl_unlock(); + if (ret < 0) { + warn_partial_info(info); + req_mask &= ~(ETH_SETTINGS_IM_LINKINFO | + ETH_SETTINGS_IM_LINKMODES); + } + } + if (req_mask & ETH_SETTINGS_IM_LINKMODES) { + lpm_empty = bitmap_empty(ksettings.link_modes.lp_advertising, + __ETHTOOL_LINK_MODE_MASK_NBITS); + ethnl_bitmap_to_u32(ksettings.link_modes.supported, + __ETHTOOL_LINK_MODE_MASK_NWORDS); + ethnl_bitmap_to_u32(ksettings.link_modes.advertising, + __ETHTOOL_LINK_MODE_MASK_NWORDS); + ethnl_bitmap_to_u32(ksettings.link_modes.lp_advertising, + __ETHTOOL_LINK_MODE_MASK_NWORDS); + } + if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) { + if (dev->ethtool_ops->get_msglevel) { + msglevel = dev->ethtool_ops->get_msglevel(dev); + } else { + warn_partial_info(info); + req_mask &= ~ETH_SETTINGS_IM_MSGLEVEL; + } + } + if (req_mask & ETH_SETTINGS_IM_WOLINFO) { + ret = ethnl_get_wol(info, dev, &wolinfo); + if (ret < 0) { + warn_partial_info(info); + req_mask &= ~ETH_SETTINGS_IM_WOLINFO; + } + } + if (req_mask & ETH_SETTINGS_IM_LINK) + link = __ethtool_get_link(dev); + + ret = ethnl_settings_size(ehdr, &ksettings, dev, req_mask); + if (ret < 0) + goto err_putdev; + else + reply_len = ret; + rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_CMD_SET_SETTINGS, info, + &ehdr); + ret = -ENOMEM; + if (!rskb) + goto err_putdev; + if (req_mask != ETH_SETTINGS_IM_DEFAULT) + ehdr->info_mask = req_mask; + + ret = -EMSGSIZE; + if (req_mask & ETH_SETTINGS_IM_LINKINFO) { + if (nla_put_u32(rskb, ETHA_SETTINGS_SPEED, lsettings->speed) || + nla_put_u8(rskb, ETHA_SETTINGS_DUPLEX, lsettings->duplex) || + nla_put_u8(rskb, ETHA_SETTINGS_PORT, lsettings->port) || + nla_put_u8(rskb, ETHA_SETTINGS_PHYADDR, + lsettings->phy_address) || + nla_put_u8(rskb, ETHA_SETTINGS_AUTONEG, + lsettings->autoneg) || + nla_put_bitfield32(rskb, ETHA_SETTINGS_MDIO_SUPPORT, + lsettings->mdio_support, + ETH_MDIO_SUPPORTS_ALL) || + nla_put_u8(rskb, ETHA_SETTINGS_TP_MDIX, + lsettings->eth_tp_mdix) || + nla_put_u8(rskb, ETHA_SETTINGS_TP_MDIX_CTRL, + lsettings->eth_tp_mdix_ctrl) || + nla_put_u8(rskb, ETHA_SETTINGS_TRANSCEIVER, + lsettings->transceiver)) + goto err; + } + if (req_mask & ETH_SETTINGS_IM_LINKMODES) { + u32 *supported = (u32 *)ksettings.link_modes.supported; + u32 *advertising = (u32 *)ksettings.link_modes.advertising; + u32 *lp_advertising = + (u32 *)ksettings.link_modes.lp_advertising; + bool compact = req_flags & ETH_SETTINGS_RF_COMPACT_BITSETS; + + ret = ethnl_put_bitset(rskb, ETHA_SETTINGS_LINK_MODES, compact, + __ETHTOOL_LINK_MODE_MASK_NBITS, + advertising, supported, link_mode_names); + if (ret < 0) + goto err; + if (!lpm_empty) { + ret = ethnl_put_bitset(rskb, ETHA_SETTINGS_PEER_MODES, + compact, + __ETHTOOL_LINK_MODE_MASK_NBITS, + lp_advertising, lp_advertising, + link_mode_names); + if (ret < 0) + goto err; + } + ret = -EMSGSIZE; + } + if (req_mask & ETH_SETTINGS_IM_MSGLEVEL) { + if (nla_put_bitfield32(rskb, ETHA_SETTINGS_MSGLVL, msglevel, + NETIF_MSG_ALL)) + goto err; + } + if (req_mask & ETH_SETTINGS_IM_WOLINFO) { + /* ioctl() restricts read access to wolinfo but the actual + * reason is to hide sopass from unprivileged users; netlink + * can show wol modes without sopass + */ + if (nla_put_bitfield32(rskb, ETHA_SETTINGS_WOL_MODES, + wolinfo.wolopts, wolinfo.supported)) + goto err; + if (ethnl_is_privileged(skb, info) && + nla_put(rskb, ETHA_SETTINGS_SOPASS, sizeof(wolinfo.sopass), + wolinfo.sopass)) + goto err; + } + if (req_mask & ETH_SETTINGS_IM_LINK && link >= 0) { + if (nla_put_u32(rskb, ETHA_SETTINGS_LINK, link)) + goto err; + } + + dev_put(dev); + genlmsg_end(rskb, ehdr); + return genlmsg_reply(rskb, info); + +err: + nlmsg_free(rskb); +err_putdev: + dev_put(dev); + if (ret == -EMSGSIZE) + GENL_SET_ERR_MSG(info, + "kernel error, see kernel log for details"); + WARN_ONCE(ret == -EMSGSIZE, + "calculated message payload length (%d) not sufficient\n", + reply_len); + return ret; +} + /* genetlink paperwork */ static const struct genl_ops ethtool_genl_ops[] = { @@ -609,6 +904,10 @@ static const struct genl_ops ethtool_genl_ops[] = { .cmd = ETHTOOL_CMD_GET_DRVINFO, .doit = ethnl_get_drvinfo, }, + { + .cmd = ETHTOOL_CMD_GET_SETTINGS, + .doit = ethnl_get_settings, + }, }; static struct genl_family ethtool_genl_family = { -- 2.15.1