Implement GET_INFO request to get basic driver and device information as provided by ETHTOOL_GDRVINFO ioct command. The information is read only so that the corresponding SET_INFO message is only used in kernel replies.
Move most of ethtool_get_drvinfo() int common.c so that the code can be shared by both ioctl and netlink interface. Signed-off-by: Michal Kubecek <mkube...@suse.cz> --- Documentation/networking/ethtool-netlink.txt | 43 ++++- include/uapi/linux/ethtool_netlink.h | 32 ++++ net/ethtool/Makefile | 2 +- net/ethtool/common.c | 54 +++++++ net/ethtool/common.h | 2 + net/ethtool/info.c | 155 +++++++++++++++++++ net/ethtool/ioctl.c | 52 +------ net/ethtool/netlink.c | 9 ++ 8 files changed, 300 insertions(+), 49 deletions(-) create mode 100644 net/ethtool/info.c diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt index f0fe4f50db9f..b6999a2167e8 100644 --- a/Documentation/networking/ethtool-netlink.txt +++ b/Documentation/networking/ethtool-netlink.txt @@ -128,6 +128,8 @@ List of message types ETHNL_CMD_EVENT notification only ETHNL_CMD_GET_STRSET ETHNL_CMD_SET_STRSET response only + ETHNL_CMD_GET_INFO + ETHNL_CMD_SET_INFO response only All constants use ETHNL_CMD_ prefix, usually followed by "GET", "SET" or "ACT" to indicate the type. @@ -208,6 +210,45 @@ ETHA_STRSET_COUNTS tells kernel to only return string counts of the sets, not the actual strings. +GET_INFO +-------- + +GET_INFO requests information provided by ioctl commands ETHTOOL_GDRVINFO, +ETHTOOL_GPERMADDR and ETHTOOL_GET_TS_INFO to provide basic device information. +Common pattern is that all information is read only so that SET_INFO message +exists but is only used by kernel for replies to GET_INFO requests. There is +also no corresponding notification. + +Request contents: + + ETHA_INFO_DEV (nested) device identification + ETHA_INFO_INFOMASK (u32) info mask + ETHA_INFO_COMPACT (flag) request compact bitsets + +Info mask bits meaning: + + ETH_INFO_IM_DRVINFO driver info (GDRVINFO) + ETH_INFO_IM_PERMADDR permanent HW address (GPERMADDR) + ETH_INFO_IM_TSINFO timestamping info (GET_TS_INFO) + +Kernel response contents: + + ETHA_INFO_DEV (nested) device identification + ETHA_INFO_DRVINFO (nested) driver information + ETHA_DRVINFO_DRIVER (string) driver name + ETHA_DRVINFO_FWVERSION (string) firmware version + ETHA_DRVINFO_BUSINFO (string) device bus address + ETHA_DRVINFO_EROM_VER (string) expansion ROM version + +The meaning of DRVINFO attributes follows the corresponding fields of +ETHTOOL_GDRVINFO response. Second part with various counts and sizes is +omitted as these are not really needed (and if they are, they can be easily +found by different means). Driver version is also omitted as it is rather +misleading in most cases. + +GET_INFO requests allow dumps. + + Request translation ------------------- @@ -219,7 +260,7 @@ ioctl command netlink command --------------------------------------------------------------------- ETHTOOL_GSET n/a ETHTOOL_SSET n/a -ETHTOOL_GDRVINFO n/a +ETHTOOL_GDRVINFO ETHNL_CMD_GET_INFO ETHTOOL_GREGS n/a ETHTOOL_GWOL n/a ETHTOOL_SWOL n/a diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h index 630b66732dc9..fdae12b6c6b6 100644 --- a/include/uapi/linux/ethtool_netlink.h +++ b/include/uapi/linux/ethtool_netlink.h @@ -10,6 +10,8 @@ enum { ETHNL_CMD_EVENT, /* only for notifications */ ETHNL_CMD_GET_STRSET, ETHNL_CMD_SET_STRSET, /* only for reply */ + ETHNL_CMD_GET_INFO, + ETHNL_CMD_SET_INFO, /* only for reply */ __ETHNL_CMD_CNT, ETHNL_CMD_MAX = (__ETHNL_CMD_CNT - 1) @@ -135,6 +137,36 @@ enum { ETHA_STRSET_MAX = (__ETHA_STRSET_CNT - 1) }; +/* GET_INFO / SET_INFO */ + +enum { + ETHA_INFO_UNSPEC, + ETHA_INFO_DEV, /* nest - ETHA_DEV_* */ + ETHA_INFO_INFOMASK, /* u32 */ + ETHA_INFO_COMPACT, /* flag */ + ETHA_INFO_DRVINFO, /* nest - ETHA_DRVINFO_* */ + ETHA_INFO_PERMADDR, /* nest - ETHA_PERMADDR_* */ + ETHA_INFO_TSINFO, /* nest - ETHA_TSINFO_* */ + + __ETHA_INFO_CNT, + ETHA_INFO_MAX = (__ETHA_INFO_CNT - 1) +}; + +#define ETH_INFO_IM_DRVINFO 0x01 + +#define ETH_INFO_IM_ALL 0x01 + +enum { + ETHA_DRVINFO_UNSPEC, + ETHA_DRVINFO_DRIVER, /* string */ + ETHA_DRVINFO_FWVERSION, /* string */ + ETHA_DRVINFO_BUSINFO, /* string */ + ETHA_DRVINFO_EROM_VER, /* string */ + + __ETHA_DRVINFO_CNT, + ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_CNT - 1) +}; + /* generic netlink info */ #define ETHTOOL_GENL_NAME "ethtool" #define ETHTOOL_GENL_VERSION 1 diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile index 11ceb00821b3..96d41dc45d4f 100644 --- a/net/ethtool/Makefile +++ b/net/ethtool/Makefile @@ -4,4 +4,4 @@ obj-y += ioctl.o common.o obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o -ethtool_nl-y := netlink.o bitset.o strset.o +ethtool_nl-y := netlink.o bitset.o strset.o info.o diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 73f721a1c557..e0bd7c6c5874 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note +#include <linux/rtnetlink.h> +#include <net/devlink.h> #include "common.h" const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = { @@ -81,3 +83,55 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = { [ETHTOOL_ID_UNSPEC] = "Unspec", [ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift", }; + +int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + + memset(info, 0, sizeof(*info)); + info->cmd = ETHTOOL_GDRVINFO; + if (ops->get_drvinfo) { + ops->get_drvinfo(dev, info); + } else if (dev->dev.parent && dev->dev.parent->driver) { + strlcpy(info->bus_info, dev_name(dev->dev.parent), + sizeof(info->bus_info)); + strlcpy(info->driver, dev->dev.parent->driver->name, + sizeof(info->driver)); + } else { + return -EOPNOTSUPP; + } + + /* this method of obtaining string set info is deprecated; + * Use ETHTOOL_GSSET_INFO instead. + */ + if (ops->get_sset_count) { + int rc; + + rc = ops->get_sset_count(dev, ETH_SS_TEST); + if (rc >= 0) + info->testinfo_len = rc; + rc = ops->get_sset_count(dev, ETH_SS_STATS); + if (rc >= 0) + info->n_stats = rc; + rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); + if (rc >= 0) + info->n_priv_flags = rc; + } + if (ops->get_regs_len) { + int ret = ops->get_regs_len(dev); + + if (ret > 0) + info->regdump_len = ret; + } + + if (ops->get_eeprom_len) + info->eedump_len = ops->get_eeprom_len(dev); + + rtnl_unlock(); + if (!info->fw_version[0]) + devlink_compat_running_version(dev, info->fw_version, + sizeof(info->fw_version)); + rtnl_lock(); + + return 0; +} diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 41b2efc1e4e1..e87e58b3a274 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -3,6 +3,7 @@ #ifndef _ETHTOOL_COMMON_H #define _ETHTOOL_COMMON_H +#include <linux/netdevice.h> #include <linux/ethtool.h> extern const char @@ -14,4 +15,5 @@ tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN]; extern const char phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN]; +int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info); #endif /* _ETHTOOL_COMMON_H */ diff --git a/net/ethtool/info.c b/net/ethtool/info.c new file mode 100644 index 000000000000..1a2e78d238e3 --- /dev/null +++ b/net/ethtool/info.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + +#include "netlink.h" +#include "common.h" +#include "bitset.h" + +struct info_data { + struct common_req_info reqinfo_base; + + /* everything below here will be reset for each device in dumps */ + struct common_reply_data repdata_base; + struct ethtool_drvinfo drvinfo; +}; + +static const struct nla_policy get_info_policy[ETHA_INFO_MAX + 1] = { + [ETHA_INFO_UNSPEC] = { .type = NLA_REJECT }, + [ETHA_INFO_DEV] = { .type = NLA_NESTED }, + [ETHA_INFO_INFOMASK] = { .type = NLA_U32 }, + [ETHA_INFO_COMPACT] = { .type = NLA_FLAG }, + [ETHA_INFO_DRVINFO] = { .type = NLA_REJECT }, +}; + +static int parse_info(struct common_req_info *req_info, struct sk_buff *skb, + struct genl_info *info, const struct nlmsghdr *nlhdr) +{ + struct nlattr *tb[ETHA_INFO_MAX + 1]; + int ret; + + ret = genlmsg_parse(nlhdr, ðtool_genl_family, tb, ETHA_INFO_MAX, + get_info_policy, info ? info->extack : NULL); + if (ret < 0) + return ret; + + if (tb[ETHA_INFO_DEV]) { + req_info->dev = ethnl_dev_get(info, tb[ETHA_INFO_DEV]); + if (IS_ERR(req_info->dev)) { + ret = PTR_ERR(req_info->dev); + req_info->dev = NULL; + return ret; + } + } + if (tb[ETHA_INFO_INFOMASK]) + req_info->req_mask = nla_get_u32(tb[ETHA_INFO_INFOMASK]); + if (tb[ETHA_INFO_COMPACT]) + req_info->compact = true; + if (req_info->req_mask == 0) + req_info->req_mask = ETH_INFO_IM_ALL; + + return 0; +} + +static int prepare_info(struct common_req_info *req_info, + struct genl_info *info) +{ + struct info_data *data = + container_of(req_info, struct info_data, reqinfo_base); + struct net_device *dev = data->repdata_base.dev; + u32 req_mask = req_info->req_mask & ETH_INFO_IM_ALL; + int ret; + + ret = ethnl_before_ops(dev); + if (ret < 0) + return ret; + if (req_mask & ETH_INFO_IM_DRVINFO) { + ret = __ethtool_get_drvinfo(dev, &data->drvinfo); + if (ret < 0) + req_mask &= ~ETH_INFO_IM_DRVINFO; + } + ethnl_after_ops(dev); + + data->repdata_base.info_mask = req_mask; + if (req_info->req_mask & ~req_mask) + warn_partial_info(info); + return 0; +} + +static int drvinfo_size(const struct ethtool_drvinfo *drvinfo) +{ + int len = 0; + + len += ethnl_str_ifne_size(drvinfo->driver); + len += ethnl_str_ifne_size(drvinfo->fw_version); + len += ethnl_str_ifne_size(drvinfo->bus_info); + len += ethnl_str_ifne_size(drvinfo->erom_version); + + return nla_total_size(len); +} + +static int info_size(const struct common_req_info *req_info) +{ + const struct info_data *data = + container_of(req_info, struct info_data, reqinfo_base); + u32 info_mask = data->repdata_base.info_mask; + int len = 0; + + len += dev_ident_size(); + if (info_mask & ETH_INFO_IM_DRVINFO) + len += drvinfo_size(&data->drvinfo); + + return len; +} + +static int fill_drvinfo(struct sk_buff *skb, + const struct ethtool_drvinfo *drvinfo) +{ + struct nlattr *nest = ethnl_nest_start(skb, ETHA_INFO_DRVINFO); + int ret; + + if (!nest) + return -EMSGSIZE; + ret = -EMSGSIZE; + if (ethnl_put_str_ifne(skb, ETHA_DRVINFO_DRIVER, drvinfo->driver) || + ethnl_put_str_ifne(skb, ETHA_DRVINFO_FWVERSION, + drvinfo->fw_version) || + ethnl_put_str_ifne(skb, ETHA_DRVINFO_BUSINFO, drvinfo->bus_info) || + ethnl_put_str_ifne(skb, ETHA_DRVINFO_EROM_VER, + drvinfo->erom_version)) + goto err; + + nla_nest_end(skb, nest); + return 0; +err: + nla_nest_cancel(skb, nest); + return ret; +} + +static int fill_info(struct sk_buff *skb, + const struct common_req_info *req_info) +{ + const struct info_data *data = + container_of(req_info, struct info_data, reqinfo_base); + u32 info_mask = data->repdata_base.info_mask; + int ret; + + if (info_mask & ETH_INFO_IM_DRVINFO) { + ret = fill_drvinfo(skb, &data->drvinfo); + if (ret < 0) + return ret; + } + + return 0; +} + +const struct get_request_ops info_request_ops = { + .request_cmd = ETHNL_CMD_GET_INFO, + .reply_cmd = ETHNL_CMD_SET_INFO, + .dev_attrtype = ETHA_INFO_DEV, + .data_size = sizeof(struct info_data), + .repdata_offset = offsetof(struct info_data, repdata_base), + + .parse_request = parse_info, + .prepare_data = prepare_info, + .reply_size = info_size, + .fill_reply = fill_info, +}; diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 71a1643adb2b..c883239001a4 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -685,56 +685,14 @@ static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev, void __user *useraddr) { struct ethtool_drvinfo info; - const struct ethtool_ops *ops = dev->ethtool_ops; - - memset(&info, 0, sizeof(info)); - info.cmd = ETHTOOL_GDRVINFO; - if (ops->get_drvinfo) { - ops->get_drvinfo(dev, &info); - } else if (dev->dev.parent && dev->dev.parent->driver) { - strlcpy(info.bus_info, dev_name(dev->dev.parent), - sizeof(info.bus_info)); - strlcpy(info.driver, dev->dev.parent->driver->name, - sizeof(info.driver)); - } else { - return -EOPNOTSUPP; - } - - /* - * this method of obtaining string set info is deprecated; - * Use ETHTOOL_GSSET_INFO instead. - */ - if (ops->get_sset_count) { - int rc; - - rc = ops->get_sset_count(dev, ETH_SS_TEST); - if (rc >= 0) - info.testinfo_len = rc; - rc = ops->get_sset_count(dev, ETH_SS_STATS); - if (rc >= 0) - info.n_stats = rc; - rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); - if (rc >= 0) - info.n_priv_flags = rc; - } - if (ops->get_regs_len) { - int ret = ops->get_regs_len(dev); - - if (ret > 0) - info.regdump_len = ret; - } - - if (ops->get_eeprom_len) - info.eedump_len = ops->get_eeprom_len(dev); - - rtnl_unlock(); - if (!info.fw_version[0]) - devlink_compat_running_version(dev, info.fw_version, - sizeof(info.fw_version)); - rtnl_lock(); + int rc; + rc = __ethtool_get_drvinfo(dev, &info); + if (rc < 0) + return rc; if (copy_to_user(useraddr, &info, sizeof(info))) return -EFAULT; + return 0; } diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 082a9f2aa0a7..e27dec427414 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -87,9 +87,11 @@ int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype) /* GET request helpers */ extern const struct get_request_ops strset_request_ops; +extern const struct get_request_ops info_request_ops; const struct get_request_ops *get_requests[__ETHNL_CMD_CNT] = { [ETHNL_CMD_GET_STRSET] = &strset_request_ops, + [ETHNL_CMD_GET_INFO] = &info_request_ops, }; static struct common_req_info *alloc_get_data(const struct get_request_ops *ops) @@ -508,6 +510,13 @@ static const struct genl_ops ethtool_genl_ops[] = { .dumpit = ethnl_get_dumpit, .done = ethnl_get_done, }, + { + .cmd = ETHNL_CMD_GET_INFO, + .doit = ethnl_get_doit, + .start = ethnl_get_start, + .dumpit = ethnl_get_dumpit, + .done = ethnl_get_done, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = { -- 2.20.1