forked from Minki/linux
413691384a
After updating userspace Ethtool from 5.7 to 5.9, I noticed that
NETDEV_FEAT_CHANGE is no more raised when changing netdev features
through Ethtool.
That's because the old Ethtool ioctl interface always calls
netdev_features_change() at the end of user request processing to
inform the kernel that our netdevice has some features changed, but
the new Netlink interface does not. Instead, it just notifies itself
with ETHTOOL_MSG_FEATURES_NTF.
Replace this ethtool_notify() call with netdev_features_change(), so
the kernel will be aware of any features changes, just like in case
with the ioctl interface. This does not omit Ethtool notifications,
as Ethtool itself listens to NETDEV_FEAT_CHANGE and drops
ETHTOOL_MSG_FEATURES_NTF on it
(net/ethtool/netlink.c:ethnl_netdev_event()).
From v1 [1]:
- dropped extra new line as advised by Jakub;
- no functional changes.
[1] https://lore.kernel.org/netdev/AlZXQ2o5uuTVHCfNGOiGgJ8vJ3KgO5YIWAnQjH0cDE@cp3-web-009.plabs.ch
Fixes: 0980bfcd69
("ethtool: set netdev features with FEATURES_SET request")
Signed-off-by: Alexander Lobakin <alobakin@pm.me>
Reviewed-by: Michal Kubecek <mkubecek@suse.cz>
Link: https://lore.kernel.org/r/ahA2YWXYICz5rbUSQqNG4roJ8OlJzzYQX7PTiG80@cp4-web-028.plabs.ch
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
290 lines
8.8 KiB
C
290 lines
8.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include "netlink.h"
|
|
#include "common.h"
|
|
#include "bitset.h"
|
|
|
|
struct features_req_info {
|
|
struct ethnl_req_info base;
|
|
};
|
|
|
|
struct features_reply_data {
|
|
struct ethnl_reply_data base;
|
|
u32 hw[ETHTOOL_DEV_FEATURE_WORDS];
|
|
u32 wanted[ETHTOOL_DEV_FEATURE_WORDS];
|
|
u32 active[ETHTOOL_DEV_FEATURE_WORDS];
|
|
u32 nochange[ETHTOOL_DEV_FEATURE_WORDS];
|
|
u32 all[ETHTOOL_DEV_FEATURE_WORDS];
|
|
};
|
|
|
|
#define FEATURES_REPDATA(__reply_base) \
|
|
container_of(__reply_base, struct features_reply_data, base)
|
|
|
|
const struct nla_policy ethnl_features_get_policy[] = {
|
|
[ETHTOOL_A_FEATURES_HEADER] =
|
|
NLA_POLICY_NESTED(ethnl_header_policy),
|
|
};
|
|
|
|
static void ethnl_features_to_bitmap32(u32 *dest, netdev_features_t src)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; i++)
|
|
dest[i] = src >> (32 * i);
|
|
}
|
|
|
|
static int features_prepare_data(const struct ethnl_req_info *req_base,
|
|
struct ethnl_reply_data *reply_base,
|
|
struct genl_info *info)
|
|
{
|
|
struct features_reply_data *data = FEATURES_REPDATA(reply_base);
|
|
struct net_device *dev = reply_base->dev;
|
|
netdev_features_t all_features;
|
|
|
|
ethnl_features_to_bitmap32(data->hw, dev->hw_features);
|
|
ethnl_features_to_bitmap32(data->wanted, dev->wanted_features);
|
|
ethnl_features_to_bitmap32(data->active, dev->features);
|
|
ethnl_features_to_bitmap32(data->nochange, NETIF_F_NEVER_CHANGE);
|
|
all_features = GENMASK_ULL(NETDEV_FEATURE_COUNT - 1, 0);
|
|
ethnl_features_to_bitmap32(data->all, all_features);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int features_reply_size(const struct ethnl_req_info *req_base,
|
|
const struct ethnl_reply_data *reply_base)
|
|
{
|
|
const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
|
|
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
|
|
unsigned int len = 0;
|
|
int ret;
|
|
|
|
ret = ethnl_bitset32_size(data->hw, data->all, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
len += ret;
|
|
ret = ethnl_bitset32_size(data->wanted, NULL, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
len += ret;
|
|
ret = ethnl_bitset32_size(data->active, NULL, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
len += ret;
|
|
ret = ethnl_bitset32_size(data->nochange, NULL, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
len += ret;
|
|
|
|
return len;
|
|
}
|
|
|
|
static int features_fill_reply(struct sk_buff *skb,
|
|
const struct ethnl_req_info *req_base,
|
|
const struct ethnl_reply_data *reply_base)
|
|
{
|
|
const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
|
|
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
|
|
int ret;
|
|
|
|
ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_HW, data->hw,
|
|
data->all, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_WANTED, data->wanted,
|
|
NULL, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_ACTIVE, data->active,
|
|
NULL, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
return ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_NOCHANGE,
|
|
data->nochange, NULL, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
}
|
|
|
|
const struct ethnl_request_ops ethnl_features_request_ops = {
|
|
.request_cmd = ETHTOOL_MSG_FEATURES_GET,
|
|
.reply_cmd = ETHTOOL_MSG_FEATURES_GET_REPLY,
|
|
.hdr_attr = ETHTOOL_A_FEATURES_HEADER,
|
|
.req_info_size = sizeof(struct features_req_info),
|
|
.reply_data_size = sizeof(struct features_reply_data),
|
|
|
|
.prepare_data = features_prepare_data,
|
|
.reply_size = features_reply_size,
|
|
.fill_reply = features_fill_reply,
|
|
};
|
|
|
|
/* FEATURES_SET */
|
|
|
|
const struct nla_policy ethnl_features_set_policy[] = {
|
|
[ETHTOOL_A_FEATURES_HEADER] =
|
|
NLA_POLICY_NESTED(ethnl_header_policy),
|
|
[ETHTOOL_A_FEATURES_WANTED] = { .type = NLA_NESTED },
|
|
};
|
|
|
|
static void ethnl_features_to_bitmap(unsigned long *dest, netdev_features_t val)
|
|
{
|
|
const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
|
|
unsigned int i;
|
|
|
|
bitmap_zero(dest, NETDEV_FEATURE_COUNT);
|
|
for (i = 0; i < words; i++)
|
|
dest[i] = (unsigned long)(val >> (i * BITS_PER_LONG));
|
|
}
|
|
|
|
static netdev_features_t ethnl_bitmap_to_features(unsigned long *src)
|
|
{
|
|
const unsigned int nft_bits = sizeof(netdev_features_t) * BITS_PER_BYTE;
|
|
const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
|
|
netdev_features_t ret = 0;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < words; i++)
|
|
ret |= (netdev_features_t)(src[i]) << (i * BITS_PER_LONG);
|
|
ret &= ~(netdev_features_t)0 >> (nft_bits - NETDEV_FEATURE_COUNT);
|
|
return ret;
|
|
}
|
|
|
|
static int features_send_reply(struct net_device *dev, struct genl_info *info,
|
|
const unsigned long *wanted,
|
|
const unsigned long *wanted_mask,
|
|
const unsigned long *active,
|
|
const unsigned long *active_mask, bool compact)
|
|
{
|
|
struct sk_buff *rskb;
|
|
void *reply_payload;
|
|
int reply_len = 0;
|
|
int ret;
|
|
|
|
reply_len = ethnl_reply_header_size();
|
|
ret = ethnl_bitset_size(wanted, wanted_mask, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
goto err;
|
|
reply_len += ret;
|
|
ret = ethnl_bitset_size(active, active_mask, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
goto err;
|
|
reply_len += ret;
|
|
|
|
ret = -ENOMEM;
|
|
rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_MSG_FEATURES_SET_REPLY,
|
|
ETHTOOL_A_FEATURES_HEADER, info,
|
|
&reply_payload);
|
|
if (!rskb)
|
|
goto err;
|
|
|
|
ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_WANTED, wanted,
|
|
wanted_mask, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
goto nla_put_failure;
|
|
ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_ACTIVE, active,
|
|
active_mask, NETDEV_FEATURE_COUNT,
|
|
netdev_features_strings, compact);
|
|
if (ret < 0)
|
|
goto nla_put_failure;
|
|
|
|
genlmsg_end(rskb, reply_payload);
|
|
ret = genlmsg_reply(rskb, info);
|
|
return ret;
|
|
|
|
nla_put_failure:
|
|
nlmsg_free(rskb);
|
|
WARN_ONCE(1, "calculated message payload length (%d) not sufficient\n",
|
|
reply_len);
|
|
err:
|
|
GENL_SET_ERR_MSG(info, "failed to send reply message");
|
|
return ret;
|
|
}
|
|
|
|
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
DECLARE_BITMAP(wanted_diff_mask, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(active_diff_mask, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(old_active, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(old_wanted, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(new_active, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(new_wanted, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(req_wanted, NETDEV_FEATURE_COUNT);
|
|
DECLARE_BITMAP(req_mask, NETDEV_FEATURE_COUNT);
|
|
struct ethnl_req_info req_info = {};
|
|
struct nlattr **tb = info->attrs;
|
|
struct net_device *dev;
|
|
bool mod;
|
|
int ret;
|
|
|
|
if (!tb[ETHTOOL_A_FEATURES_WANTED])
|
|
return -EINVAL;
|
|
ret = ethnl_parse_header_dev_get(&req_info,
|
|
tb[ETHTOOL_A_FEATURES_HEADER],
|
|
genl_info_net(info), info->extack,
|
|
true);
|
|
if (ret < 0)
|
|
return ret;
|
|
dev = req_info.dev;
|
|
|
|
rtnl_lock();
|
|
ethnl_features_to_bitmap(old_active, dev->features);
|
|
ethnl_features_to_bitmap(old_wanted, dev->wanted_features);
|
|
ret = ethnl_parse_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
|
|
tb[ETHTOOL_A_FEATURES_WANTED],
|
|
netdev_features_strings, info->extack);
|
|
if (ret < 0)
|
|
goto out_rtnl;
|
|
if (ethnl_bitmap_to_features(req_mask) & ~NETIF_F_ETHTOOL_BITS) {
|
|
GENL_SET_ERR_MSG(info, "attempt to change non-ethtool features");
|
|
ret = -EINVAL;
|
|
goto out_rtnl;
|
|
}
|
|
|
|
/* set req_wanted bits not in req_mask from old_wanted */
|
|
bitmap_and(req_wanted, req_wanted, req_mask, NETDEV_FEATURE_COUNT);
|
|
bitmap_andnot(new_wanted, old_wanted, req_mask, NETDEV_FEATURE_COUNT);
|
|
bitmap_or(req_wanted, new_wanted, req_wanted, NETDEV_FEATURE_COUNT);
|
|
if (!bitmap_equal(req_wanted, old_wanted, NETDEV_FEATURE_COUNT)) {
|
|
dev->wanted_features &= ~dev->hw_features;
|
|
dev->wanted_features |= ethnl_bitmap_to_features(req_wanted) & dev->hw_features;
|
|
__netdev_update_features(dev);
|
|
}
|
|
ethnl_features_to_bitmap(new_active, dev->features);
|
|
mod = !bitmap_equal(old_active, new_active, NETDEV_FEATURE_COUNT);
|
|
|
|
ret = 0;
|
|
if (!(req_info.flags & ETHTOOL_FLAG_OMIT_REPLY)) {
|
|
bool compact = req_info.flags & ETHTOOL_FLAG_COMPACT_BITSETS;
|
|
|
|
bitmap_xor(wanted_diff_mask, req_wanted, new_active,
|
|
NETDEV_FEATURE_COUNT);
|
|
bitmap_xor(active_diff_mask, old_active, new_active,
|
|
NETDEV_FEATURE_COUNT);
|
|
bitmap_and(wanted_diff_mask, wanted_diff_mask, req_mask,
|
|
NETDEV_FEATURE_COUNT);
|
|
bitmap_and(req_wanted, req_wanted, wanted_diff_mask,
|
|
NETDEV_FEATURE_COUNT);
|
|
bitmap_and(new_active, new_active, active_diff_mask,
|
|
NETDEV_FEATURE_COUNT);
|
|
|
|
ret = features_send_reply(dev, info, req_wanted,
|
|
wanted_diff_mask, new_active,
|
|
active_diff_mask, compact);
|
|
}
|
|
if (mod)
|
|
netdev_features_change(dev);
|
|
|
|
out_rtnl:
|
|
rtnl_unlock();
|
|
dev_put(dev);
|
|
return ret;
|
|
}
|