1611 lines · 44.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only #include <net/netdev_lock.h>#include <net/netdev_queues.h>#include <net/sock.h>#include <linux/ethtool_netlink.h>#include <linux/phy_link_topology.h>#include <linux/pm_runtime.h> #include "common.h"#include "module_fw.h"#include "netlink.h" static struct genl_family ethtool_genl_family; static bool ethnl_ok __read_mostly; /* Serializes broadcast notification sequence allocation with the multicast * send, so that userspace observes nlmsg_seq monotonic in receive order * regardless of which lock the caller holds (rtnl or instance lock). */static DEFINE_MUTEX(ethnl_bcast_lock);static u32 ethnl_bcast_seq; #define ETHTOOL_FLAGS_BASIC (ETHTOOL_FLAG_COMPACT_BITSETS |	\			     ETHTOOL_FLAG_OMIT_REPLY)#define ETHTOOL_FLAGS_STATS (ETHTOOL_FLAGS_BASIC | ETHTOOL_FLAG_STATS) const struct nla_policy ethnl_header_policy[] = {	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },	[ETHTOOL_A_HEADER_DEV_NAME]	= { .type = NLA_NUL_STRING,					    .len = ALTIFNAMSIZ - 1 },	[ETHTOOL_A_HEADER_FLAGS]	= NLA_POLICY_MASK(NLA_U32,							  ETHTOOL_FLAGS_BASIC),}; const struct nla_policy ethnl_header_policy_stats[] = {	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },	[ETHTOOL_A_HEADER_DEV_NAME]	= { .type = NLA_NUL_STRING,					    .len = ALTIFNAMSIZ - 1 },	[ETHTOOL_A_HEADER_FLAGS]	= NLA_POLICY_MASK(NLA_U32,							  ETHTOOL_FLAGS_STATS),}; const struct nla_policy ethnl_header_policy_phy[] = {	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },	[ETHTOOL_A_HEADER_DEV_NAME]	= { .type = NLA_NUL_STRING,					    .len = ALTIFNAMSIZ - 1 },	[ETHTOOL_A_HEADER_FLAGS]	= NLA_POLICY_MASK(NLA_U32,							  ETHTOOL_FLAGS_BASIC),	[ETHTOOL_A_HEADER_PHY_INDEX]		= NLA_POLICY_MIN(NLA_U32, 1),}; const struct nla_policy ethnl_header_policy_phy_stats[] = {	[ETHTOOL_A_HEADER_DEV_INDEX]	= { .type = NLA_U32 },	[ETHTOOL_A_HEADER_DEV_NAME]	= { .type = NLA_NUL_STRING,					    .len = ALTIFNAMSIZ - 1 },	[ETHTOOL_A_HEADER_FLAGS]	= NLA_POLICY_MASK(NLA_U32,							  ETHTOOL_FLAGS_STATS),	[ETHTOOL_A_HEADER_PHY_INDEX]		= NLA_POLICY_MIN(NLA_U32, 1),}; int ethnl_sock_priv_set(struct sk_buff *skb, struct net *net, u32 portid,			enum ethnl_sock_type type){	struct ethnl_sock_priv *sk_priv; 	sk_priv = genl_sk_priv_get(&ethtool_genl_family, NETLINK_CB(skb).sk);	if (IS_ERR(sk_priv))		return PTR_ERR(sk_priv); 	sk_priv->net = net;	sk_priv->portid = portid;	sk_priv->type = type; 	return 0;} static void ethnl_sock_priv_destroy(void *priv){	struct ethnl_sock_priv *sk_priv = priv; 	switch (sk_priv->type) {	case ETHTOOL_SOCK_TYPE_MODULE_FW_FLASH:		ethnl_module_fw_flash_sock_destroy(sk_priv);		break;	default:		break;	}} int ethnl_ops_begin(struct net_device *dev){	int ret; 	if (!dev)		return -ENODEV; 	if (dev->dev.parent)		pm_runtime_get_sync(dev->dev.parent); 	netdev_assert_locked_ops_compat(dev); 	if (!netif_device_present(dev) ||	    dev->reg_state >= NETREG_UNREGISTERING) {		ret = -ENODEV;		goto err;	} 	if (dev->ethtool_ops->begin) {		ret = dev->ethtool_ops->begin(dev);		if (ret)			goto err;	} 	return 0;err:	if (dev->dev.parent)		pm_runtime_put(dev->dev.parent); 	return ret;} void ethnl_ops_complete(struct net_device *dev){	if (dev->ethtool_ops->complete)		dev->ethtool_ops->complete(dev); 	if (dev->dev.parent)		pm_runtime_put(dev->dev.parent);} /** * ethnl_parse_header_dev_get() - parse request header * @req_info:    structure to put results into * @header:      nest attribute with request header * @net:         request netns * @extack:      netlink extack for error reporting * @require_dev: fail if no device identified in header * * Parse request header in nested attribute @nest and puts results into * the structure pointed to by @req_info. Extack from @info is used for error * reporting. If req_info->dev is not null on return, reference to it has * been taken. If error is returned, *req_info is null initialized and no * reference is held. * * Return: 0 on success or negative error code */int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,			       const struct nlattr *header, struct net *net,			       struct netlink_ext_ack *extack, bool require_dev){	struct nlattr *tb[ARRAY_SIZE(ethnl_header_policy_phy)];	const struct nlattr *devname_attr;	struct net_device *dev = NULL;	u32 flags = 0;	int ret; 	if (!header) {		if (!require_dev)			return 0;		NL_SET_ERR_MSG(extack, "request header missing");		return -EINVAL;	}	/* No validation here, command policy should have a nested policy set	 * for the header, therefore validation should have already been done.	 */	ret = nla_parse_nested(tb, ARRAY_SIZE(ethnl_header_policy_phy) - 1, header,			       NULL, extack);	if (ret < 0)		return ret;	if (tb[ETHTOOL_A_HEADER_FLAGS])		flags = nla_get_u32(tb[ETHTOOL_A_HEADER_FLAGS]); 	devname_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];	if (tb[ETHTOOL_A_HEADER_DEV_INDEX]) {		u32 ifindex = nla_get_u32(tb[ETHTOOL_A_HEADER_DEV_INDEX]); 		dev = netdev_get_by_index(net, ifindex, &req_info->dev_tracker,					  GFP_KERNEL);		if (!dev) {			NL_SET_ERR_MSG_ATTR(extack,					    tb[ETHTOOL_A_HEADER_DEV_INDEX],					    "no device matches ifindex");			return -ENODEV;		}		/* if both ifindex and ifname are passed, they must match */		if (devname_attr &&		    strncmp(dev->name, nla_data(devname_attr), IFNAMSIZ)) {			netdev_put(dev, &req_info->dev_tracker);			NL_SET_ERR_MSG_ATTR(extack, header,					    "ifindex and name do not match");			return -ENODEV;		}	} else if (devname_attr) {		dev = netdev_get_by_name(net, nla_data(devname_attr),					 &req_info->dev_tracker, GFP_KERNEL);		if (!dev) {			NL_SET_ERR_MSG_ATTR(extack, devname_attr,					    "no device matches name");			return -ENODEV;		}	} else if (require_dev) {		NL_SET_ERR_MSG_ATTR(extack, header,				    "neither ifindex nor name specified");		return -EINVAL;	} 	if (tb[ETHTOOL_A_HEADER_PHY_INDEX]) {		if (dev) {			req_info->phy_index = nla_get_u32(tb[ETHTOOL_A_HEADER_PHY_INDEX]);		} else {			NL_SET_ERR_MSG_ATTR(extack, header,					    "phy_index set without a netdev");			return -EINVAL;		}	} 	req_info->dev = dev;	req_info->flags = flags;	return 0;} struct phy_device *ethnl_req_get_phydev(const struct ethnl_req_info *req_info,					struct nlattr **tb, unsigned int header,					struct netlink_ext_ack *extack){	struct phy_device *phydev; 	if (!req_info->dev)		return NULL; 	/* If there is no PHY in sight there's no need for assert locking */	if (!phy_link_topo_empty(req_info->dev))		ASSERT_RTNL(); 	if (!req_info->phy_index)		return req_info->dev->phydev; 	phydev = phy_link_topo_get_phy(req_info->dev, req_info->phy_index);	if (!phydev && tb) {		NL_SET_ERR_MSG_ATTR(extack, tb[header],				    "no phy matching phyindex");		return ERR_PTR(-ENODEV);	} 	return phydev;} /** * ethnl_fill_reply_header() - Put common header into a reply message * @skb:      skb with the message * @dev:      network device to describe in header * @attrtype: attribute type to use for the nest * * Create a nested attribute with attributes describing given network device. * * Return: 0 on success, error value (-EMSGSIZE only) on error */int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev,			    u16 attrtype){	struct nlattr *nest; 	if (!dev)		return 0;	nest = nla_nest_start(skb, attrtype);	if (!nest)		return -EMSGSIZE; 	if (nla_put_u32(skb, ETHTOOL_A_HEADER_DEV_INDEX, (u32)dev->ifindex) ||	    nla_put_string(skb, ETHTOOL_A_HEADER_DEV_NAME, dev->name))		goto nla_put_failure;	/* If more attributes are put into reply header, ethnl_header_size()	 * must be updated to account for them.	 */ 	nla_nest_end(skb, nest);	return 0; nla_put_failure:	nla_nest_cancel(skb, nest);	return -EMSGSIZE;} /** * ethnl_reply_init() - Create skb for a reply and fill device identification * @payload:      payload length (without netlink and genetlink header) * @dev:          device the reply is about (may be null) * @cmd:          ETHTOOL_MSG_* message type for reply * @hdr_attrtype: attribute type for common header * @info:         genetlink info of the received packet we respond to * @ehdrp:        place to store payload pointer returned by genlmsg_new() * * Return: pointer to allocated skb on success, NULL on error */struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,				 u16 hdr_attrtype, struct genl_info *info,				 void **ehdrp){	struct sk_buff *skb; 	skb = genlmsg_new(payload, GFP_KERNEL);	if (!skb)		goto err;	*ehdrp = genlmsg_put_reply(skb, info, &ethtool_genl_family, 0, cmd);	if (!*ehdrp)		goto err_free; 	if (dev) {		int ret; 		ret = ethnl_fill_reply_header(skb, dev, hdr_attrtype);		if (ret < 0)			goto err_free;	}	return skb; err_free:	nlmsg_free(skb);err:	if (info)		GENL_SET_ERR_MSG(info, "failed to setup reply message");	return NULL;} void *ethnl_dump_put(struct sk_buff *skb, struct netlink_callback *cb, u8 cmd){	return genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,			   &ethtool_genl_family, 0, cmd);} void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd){	return genlmsg_put(skb, 0, 0, &ethtool_genl_family, 0, cmd);} void *ethnl_unicast_put(struct sk_buff *skb, u32 portid, u32 seq, u8 cmd){	return genlmsg_put(skb, portid, seq, &ethtool_genl_family, 0, cmd);} int ethnl_multicast(struct sk_buff *skb, struct net_device *dev){	struct nlmsghdr *nlh = nlmsg_hdr(skb);	int ret; 	mutex_lock(&ethnl_bcast_lock);	nlh->nlmsg_seq = ++ethnl_bcast_seq;	ret = genlmsg_multicast_netns(&ethtool_genl_family, dev_net(dev), skb,				      0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);	mutex_unlock(&ethnl_bcast_lock);	return ret;} /* GET request helpers */ /** * struct ethnl_dump_ctx - context structure for generic dumpit() callback * @ops:        request ops of currently processed message type * @req_info:   parsed request header of processed request * @reply_data: data needed to compose the reply * @pos_ifindex: saved iteration position - ifindex * * These parameters are kept in struct netlink_callback as context preserved * between iterations. They are initialized by ethnl_default_start() and used * in ethnl_default_dumpit() and ethnl_default_done(). */struct ethnl_dump_ctx {	const struct ethnl_request_ops	*ops;	struct ethnl_req_info		*req_info;	struct ethnl_reply_data		*reply_data;	unsigned long			pos_ifindex;}; /** * struct ethnl_perphy_dump_ctx - context for dumpit() PHY-aware callbacks * @ethnl_ctx: generic ethnl context * @ifindex: For Filtered DUMP requests, the ifindex of the targeted netdev * @pos_phyindex: iterator position for multi-msg DUMP */struct ethnl_perphy_dump_ctx {	struct ethnl_dump_ctx	ethnl_ctx;	unsigned int		ifindex;	unsigned long		pos_phyindex;}; static const struct ethnl_request_ops *ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {	[ETHTOOL_MSG_STRSET_GET]	= &ethnl_strset_request_ops,	[ETHTOOL_MSG_LINKINFO_GET]	= &ethnl_linkinfo_request_ops,	[ETHTOOL_MSG_LINKINFO_SET]	= &ethnl_linkinfo_request_ops,	[ETHTOOL_MSG_LINKMODES_GET]	= &ethnl_linkmodes_request_ops,	[ETHTOOL_MSG_LINKMODES_SET]	= &ethnl_linkmodes_request_ops,	[ETHTOOL_MSG_LINKSTATE_GET]	= &ethnl_linkstate_request_ops,	[ETHTOOL_MSG_DEBUG_GET]		= &ethnl_debug_request_ops,	[ETHTOOL_MSG_DEBUG_SET]		= &ethnl_debug_request_ops,	[ETHTOOL_MSG_WOL_GET]		= &ethnl_wol_request_ops,	[ETHTOOL_MSG_WOL_SET]		= &ethnl_wol_request_ops,	[ETHTOOL_MSG_FEATURES_GET]	= &ethnl_features_request_ops,	[ETHTOOL_MSG_PRIVFLAGS_GET]	= &ethnl_privflags_request_ops,	[ETHTOOL_MSG_PRIVFLAGS_SET]	= &ethnl_privflags_request_ops,	[ETHTOOL_MSG_RINGS_GET]		= &ethnl_rings_request_ops,	[ETHTOOL_MSG_RINGS_SET]		= &ethnl_rings_request_ops,	[ETHTOOL_MSG_CHANNELS_GET]	= &ethnl_channels_request_ops,	[ETHTOOL_MSG_CHANNELS_SET]	= &ethnl_channels_request_ops,	[ETHTOOL_MSG_COALESCE_GET]	= &ethnl_coalesce_request_ops,	[ETHTOOL_MSG_COALESCE_SET]	= &ethnl_coalesce_request_ops,	[ETHTOOL_MSG_PAUSE_GET]		= &ethnl_pause_request_ops,	[ETHTOOL_MSG_PAUSE_SET]		= &ethnl_pause_request_ops,	[ETHTOOL_MSG_EEE_GET]		= &ethnl_eee_request_ops,	[ETHTOOL_MSG_EEE_SET]		= &ethnl_eee_request_ops,	[ETHTOOL_MSG_FEC_GET]		= &ethnl_fec_request_ops,	[ETHTOOL_MSG_FEC_SET]		= &ethnl_fec_request_ops,	[ETHTOOL_MSG_TSINFO_GET]	= &ethnl_tsinfo_request_ops,	[ETHTOOL_MSG_MODULE_EEPROM_GET]	= &ethnl_module_eeprom_request_ops,	[ETHTOOL_MSG_STATS_GET]		= &ethnl_stats_request_ops,	[ETHTOOL_MSG_PHC_VCLOCKS_GET]	= &ethnl_phc_vclocks_request_ops,	[ETHTOOL_MSG_MODULE_GET]	= &ethnl_module_request_ops,	[ETHTOOL_MSG_MODULE_SET]	= &ethnl_module_request_ops,	[ETHTOOL_MSG_PSE_GET]		= &ethnl_pse_request_ops,	[ETHTOOL_MSG_PSE_SET]		= &ethnl_pse_request_ops,	[ETHTOOL_MSG_RSS_GET]		= &ethnl_rss_request_ops,	[ETHTOOL_MSG_RSS_SET]		= &ethnl_rss_request_ops,	[ETHTOOL_MSG_PLCA_GET_CFG]	= &ethnl_plca_cfg_request_ops,	[ETHTOOL_MSG_PLCA_SET_CFG]	= &ethnl_plca_cfg_request_ops,	[ETHTOOL_MSG_PLCA_GET_STATUS]	= &ethnl_plca_status_request_ops,	[ETHTOOL_MSG_MM_GET]		= &ethnl_mm_request_ops,	[ETHTOOL_MSG_MM_SET]		= &ethnl_mm_request_ops,	[ETHTOOL_MSG_TSCONFIG_GET]	= &ethnl_tsconfig_request_ops,	[ETHTOOL_MSG_TSCONFIG_SET]	= &ethnl_tsconfig_request_ops,	[ETHTOOL_MSG_PHY_GET]		= &ethnl_phy_request_ops,	[ETHTOOL_MSG_MSE_GET]		= &ethnl_mse_request_ops,}; static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb){	return (struct ethnl_dump_ctx *)cb->ctx;} static struct ethnl_perphy_dump_ctx *ethnl_perphy_dump_context(struct netlink_callback *cb){	return (struct ethnl_perphy_dump_ctx *)cb->ctx;} /** * ethnl_default_parse() - Parse request message * @req_info:    pointer to structure to put data into * @info:	 genl_info from the request * @request_ops: struct request_ops for request type * @require_dev: fail if no device identified in header * * Parse universal request header and call request specific ->parse_request() * callback (if defined) to parse the rest of the message. * * Return: 0 on success or negative error code */static int ethnl_default_parse(struct ethnl_req_info *req_info,			       const struct genl_info *info,			       const struct ethnl_request_ops *request_ops,			       bool require_dev){	struct nlattr **tb = info->attrs;	int ret; 	ret = ethnl_parse_header_dev_get(req_info, tb[request_ops->hdr_attr],					 genl_info_net(info), info->extack,					 require_dev);	if (ret < 0)		return ret; 	if (request_ops->parse_request) {		ret = request_ops->parse_request(req_info, info, tb,						 info->extack);		if (ret < 0)			goto err_dev;	} 	return 0; err_dev:	netdev_put(req_info->dev, &req_info->dev_tracker);	req_info->dev = NULL;	return ret;} /** * ethnl_init_reply_data() - Initialize reply data for GET request * @reply_data: pointer to embedded struct ethnl_reply_data * @ops:        instance of struct ethnl_request_ops describing the layout * @dev:        network device to initialize the reply for * * Fills the reply data part with zeros and sets the dev member. Must be called * before calling the ->fill_reply() callback (for each iteration when handling * dump requests). */static void ethnl_init_reply_data(struct ethnl_reply_data *reply_data,				  const struct ethnl_request_ops *ops,				  struct net_device *dev)

Lines 1–500 of 1611.