211 lines · 5.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only #include "netlink.h"#include "common.h"#include "bitset.h" struct privflags_req_info {	struct ethnl_req_info		base;}; struct privflags_reply_data {	struct ethnl_reply_data		base;	const char			(*priv_flag_names)[ETH_GSTRING_LEN];	unsigned int			n_priv_flags;	u32				priv_flags;}; #define PRIVFLAGS_REPDATA(__reply_base) \	container_of(__reply_base, struct privflags_reply_data, base) static const struct nla_policyprivflags_get_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = {	[ETHTOOL_A_PRIVFLAGS_UNSPEC]		= { .type = NLA_REJECT },	[ETHTOOL_A_PRIVFLAGS_HEADER]		= { .type = NLA_NESTED },	[ETHTOOL_A_PRIVFLAGS_FLAGS]		= { .type = NLA_REJECT },}; static int ethnl_get_priv_flags_info(struct net_device *dev,				     unsigned int *count,				     const char (**names)[ETH_GSTRING_LEN]){	const struct ethtool_ops *ops = dev->ethtool_ops;	int nflags; 	nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);	if (nflags < 0)		return nflags; 	if (names) {		*names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL);		if (!*names)			return -ENOMEM;		ops->get_strings(dev, ETH_SS_PRIV_FLAGS, (u8 *)*names);	} 	/* We can pass more than 32 private flags to userspace via netlink but	 * we cannot get more with ethtool_ops::get_priv_flags(). Note that we	 * must not adjust nflags before allocating the space for flag names	 * as the buffer must be large enough for all flags.	 */	if (WARN_ONCE(nflags > 32,		      "device %s reports more than 32 private flags (%d)\n",		      netdev_name(dev), nflags))		nflags = 32;	*count = nflags; 	return 0;} static int privflags_prepare_data(const struct ethnl_req_info *req_base,				  struct ethnl_reply_data *reply_base,				  struct genl_info *info){	struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);	struct net_device *dev = reply_base->dev;	const char (*names)[ETH_GSTRING_LEN];	const struct ethtool_ops *ops;	unsigned int nflags;	int ret; 	ops = dev->ethtool_ops;	if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings)		return -EOPNOTSUPP;	ret = ethnl_ops_begin(dev);	if (ret < 0)		return ret; 	ret = ethnl_get_priv_flags_info(dev, &nflags, &names);	if (ret < 0)		goto out_ops;	data->priv_flags = ops->get_priv_flags(dev);	data->priv_flag_names = names;	data->n_priv_flags = nflags; out_ops:	ethnl_ops_complete(dev);	return ret;} static int privflags_reply_size(const struct ethnl_req_info *req_base,				const struct ethnl_reply_data *reply_base){	const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;	const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags); 	return ethnl_bitset32_size(&data->priv_flags, &all_flags,				   data->n_priv_flags,				   data->priv_flag_names, compact);} static int privflags_fill_reply(struct sk_buff *skb,				const struct ethnl_req_info *req_base,				const struct ethnl_reply_data *reply_base){	const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;	const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags); 	return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS,				  &data->priv_flags, &all_flags,				  data->n_priv_flags, data->priv_flag_names,				  compact);} static void privflags_cleanup_data(struct ethnl_reply_data *reply_data){	struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data); 	kfree(data->priv_flag_names);} const struct ethnl_request_ops ethnl_privflags_request_ops = {	.request_cmd		= ETHTOOL_MSG_PRIVFLAGS_GET,	.reply_cmd		= ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,	.hdr_attr		= ETHTOOL_A_PRIVFLAGS_HEADER,	.max_attr		= ETHTOOL_A_PRIVFLAGS_MAX,	.req_info_size		= sizeof(struct privflags_req_info),	.reply_data_size	= sizeof(struct privflags_reply_data),	.request_policy		= privflags_get_policy, 	.prepare_data		= privflags_prepare_data,	.reply_size		= privflags_reply_size,	.fill_reply		= privflags_fill_reply,	.cleanup_data		= privflags_cleanup_data,}; /* PRIVFLAGS_SET */ static const struct nla_policyprivflags_set_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = {	[ETHTOOL_A_PRIVFLAGS_UNSPEC]		= { .type = NLA_REJECT },	[ETHTOOL_A_PRIVFLAGS_HEADER]		= { .type = NLA_NESTED },	[ETHTOOL_A_PRIVFLAGS_FLAGS]		= { .type = NLA_NESTED },}; int ethnl_set_privflags(struct sk_buff *skb, struct genl_info *info){	struct nlattr *tb[ETHTOOL_A_PRIVFLAGS_MAX + 1];	const char (*names)[ETH_GSTRING_LEN] = NULL;	struct ethnl_req_info req_info = {};	const struct ethtool_ops *ops;	struct net_device *dev;	unsigned int nflags;	bool mod = false;	bool compact;	u32 flags;	int ret; 	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,			  ETHTOOL_A_PRIVFLAGS_MAX, privflags_set_policy,			  info->extack);	if (ret < 0)		return ret;	if (!tb[ETHTOOL_A_PRIVFLAGS_FLAGS])		return -EINVAL;	ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact);	if (ret < 0)		return ret;	ret = ethnl_parse_header_dev_get(&req_info,					 tb[ETHTOOL_A_PRIVFLAGS_HEADER],					 genl_info_net(info), info->extack,					 true);	if (ret < 0)		return ret;	dev = req_info.dev;	ops = dev->ethtool_ops;	ret = -EOPNOTSUPP;	if (!ops->get_priv_flags || !ops->set_priv_flags ||	    !ops->get_sset_count || !ops->get_strings)		goto out_dev; 	rtnl_lock();	ret = ethnl_ops_begin(dev);	if (ret < 0)		goto out_rtnl;	ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names);	if (ret < 0)		goto out_ops;	flags = ops->get_priv_flags(dev); 	ret = ethnl_update_bitset32(&flags, nflags,				    tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names,				    info->extack, &mod);	if (ret < 0 || !mod)		goto out_free;	ret = ops->set_priv_flags(dev, flags);	if (ret < 0)		goto out_free;	ethtool_notify(dev, ETHTOOL_MSG_PRIVFLAGS_NTF, NULL); out_free:	kfree(names);out_ops:	ethnl_ops_complete(dev);out_rtnl:	rtnl_unlock();out_dev:	dev_put(dev);	return ret;}