|  | // 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_policy | 
|  | privflags_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_policy | 
|  | privflags_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; | 
|  | } |