10524399dSMichal Kubecek // SPDX-License-Identifier: GPL-2.0-only
20524399dSMichal Kubecek
30524399dSMichal Kubecek #include "netlink.h"
40524399dSMichal Kubecek #include "common.h"
50524399dSMichal Kubecek #include "bitset.h"
60524399dSMichal Kubecek
70524399dSMichal Kubecek struct features_req_info {
80524399dSMichal Kubecek struct ethnl_req_info base;
90524399dSMichal Kubecek };
100524399dSMichal Kubecek
110524399dSMichal Kubecek struct features_reply_data {
120524399dSMichal Kubecek struct ethnl_reply_data base;
130524399dSMichal Kubecek u32 hw[ETHTOOL_DEV_FEATURE_WORDS];
140524399dSMichal Kubecek u32 wanted[ETHTOOL_DEV_FEATURE_WORDS];
150524399dSMichal Kubecek u32 active[ETHTOOL_DEV_FEATURE_WORDS];
160524399dSMichal Kubecek u32 nochange[ETHTOOL_DEV_FEATURE_WORDS];
170524399dSMichal Kubecek u32 all[ETHTOOL_DEV_FEATURE_WORDS];
180524399dSMichal Kubecek };
190524399dSMichal Kubecek
200524399dSMichal Kubecek #define FEATURES_REPDATA(__reply_base) \
210524399dSMichal Kubecek container_of(__reply_base, struct features_reply_data, base)
220524399dSMichal Kubecek
23ff419afaSJakub Kicinski const struct nla_policy ethnl_features_get_policy[] = {
24329d9c33SJakub Kicinski [ETHTOOL_A_FEATURES_HEADER] =
25329d9c33SJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy),
260524399dSMichal Kubecek };
270524399dSMichal Kubecek
ethnl_features_to_bitmap32(u32 * dest,netdev_features_t src)280524399dSMichal Kubecek static void ethnl_features_to_bitmap32(u32 *dest, netdev_features_t src)
290524399dSMichal Kubecek {
300524399dSMichal Kubecek unsigned int i;
310524399dSMichal Kubecek
320524399dSMichal Kubecek for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; i++)
330524399dSMichal Kubecek dest[i] = src >> (32 * i);
340524399dSMichal Kubecek }
350524399dSMichal Kubecek
features_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)360524399dSMichal Kubecek static int features_prepare_data(const struct ethnl_req_info *req_base,
370524399dSMichal Kubecek struct ethnl_reply_data *reply_base,
38f946270dSJakub Kicinski const struct genl_info *info)
390524399dSMichal Kubecek {
400524399dSMichal Kubecek struct features_reply_data *data = FEATURES_REPDATA(reply_base);
410524399dSMichal Kubecek struct net_device *dev = reply_base->dev;
420524399dSMichal Kubecek netdev_features_t all_features;
430524399dSMichal Kubecek
440524399dSMichal Kubecek ethnl_features_to_bitmap32(data->hw, dev->hw_features);
450524399dSMichal Kubecek ethnl_features_to_bitmap32(data->wanted, dev->wanted_features);
460524399dSMichal Kubecek ethnl_features_to_bitmap32(data->active, dev->features);
470524399dSMichal Kubecek ethnl_features_to_bitmap32(data->nochange, NETIF_F_NEVER_CHANGE);
480524399dSMichal Kubecek all_features = GENMASK_ULL(NETDEV_FEATURE_COUNT - 1, 0);
490524399dSMichal Kubecek ethnl_features_to_bitmap32(data->all, all_features);
500524399dSMichal Kubecek
510524399dSMichal Kubecek return 0;
520524399dSMichal Kubecek }
530524399dSMichal Kubecek
features_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)540524399dSMichal Kubecek static int features_reply_size(const struct ethnl_req_info *req_base,
550524399dSMichal Kubecek const struct ethnl_reply_data *reply_base)
560524399dSMichal Kubecek {
570524399dSMichal Kubecek const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
580524399dSMichal Kubecek bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
590524399dSMichal Kubecek unsigned int len = 0;
600524399dSMichal Kubecek int ret;
610524399dSMichal Kubecek
620524399dSMichal Kubecek ret = ethnl_bitset32_size(data->hw, data->all, NETDEV_FEATURE_COUNT,
630524399dSMichal Kubecek netdev_features_strings, compact);
640524399dSMichal Kubecek if (ret < 0)
650524399dSMichal Kubecek return ret;
660524399dSMichal Kubecek len += ret;
670524399dSMichal Kubecek ret = ethnl_bitset32_size(data->wanted, NULL, NETDEV_FEATURE_COUNT,
680524399dSMichal Kubecek netdev_features_strings, compact);
690524399dSMichal Kubecek if (ret < 0)
700524399dSMichal Kubecek return ret;
710524399dSMichal Kubecek len += ret;
720524399dSMichal Kubecek ret = ethnl_bitset32_size(data->active, NULL, NETDEV_FEATURE_COUNT,
730524399dSMichal Kubecek netdev_features_strings, compact);
740524399dSMichal Kubecek if (ret < 0)
750524399dSMichal Kubecek return ret;
760524399dSMichal Kubecek len += ret;
770524399dSMichal Kubecek ret = ethnl_bitset32_size(data->nochange, NULL, NETDEV_FEATURE_COUNT,
780524399dSMichal Kubecek netdev_features_strings, compact);
790524399dSMichal Kubecek if (ret < 0)
800524399dSMichal Kubecek return ret;
810524399dSMichal Kubecek len += ret;
820524399dSMichal Kubecek
830524399dSMichal Kubecek return len;
840524399dSMichal Kubecek }
850524399dSMichal Kubecek
features_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)860524399dSMichal Kubecek static int features_fill_reply(struct sk_buff *skb,
870524399dSMichal Kubecek const struct ethnl_req_info *req_base,
880524399dSMichal Kubecek const struct ethnl_reply_data *reply_base)
890524399dSMichal Kubecek {
900524399dSMichal Kubecek const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
910524399dSMichal Kubecek bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
920524399dSMichal Kubecek int ret;
930524399dSMichal Kubecek
940524399dSMichal Kubecek ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_HW, data->hw,
950524399dSMichal Kubecek data->all, NETDEV_FEATURE_COUNT,
960524399dSMichal Kubecek netdev_features_strings, compact);
970524399dSMichal Kubecek if (ret < 0)
980524399dSMichal Kubecek return ret;
990524399dSMichal Kubecek ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_WANTED, data->wanted,
1000524399dSMichal Kubecek NULL, NETDEV_FEATURE_COUNT,
1010524399dSMichal Kubecek netdev_features_strings, compact);
1020524399dSMichal Kubecek if (ret < 0)
1030524399dSMichal Kubecek return ret;
1040524399dSMichal Kubecek ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_ACTIVE, data->active,
1050524399dSMichal Kubecek NULL, NETDEV_FEATURE_COUNT,
1060524399dSMichal Kubecek netdev_features_strings, compact);
1070524399dSMichal Kubecek if (ret < 0)
1080524399dSMichal Kubecek return ret;
1090524399dSMichal Kubecek return ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_NOCHANGE,
1100524399dSMichal Kubecek data->nochange, NULL, NETDEV_FEATURE_COUNT,
1110524399dSMichal Kubecek netdev_features_strings, compact);
1120524399dSMichal Kubecek }
1130524399dSMichal Kubecek
1140524399dSMichal Kubecek const struct ethnl_request_ops ethnl_features_request_ops = {
1150524399dSMichal Kubecek .request_cmd = ETHTOOL_MSG_FEATURES_GET,
1160524399dSMichal Kubecek .reply_cmd = ETHTOOL_MSG_FEATURES_GET_REPLY,
1170524399dSMichal Kubecek .hdr_attr = ETHTOOL_A_FEATURES_HEADER,
1180524399dSMichal Kubecek .req_info_size = sizeof(struct features_req_info),
1190524399dSMichal Kubecek .reply_data_size = sizeof(struct features_reply_data),
1200524399dSMichal Kubecek
1210524399dSMichal Kubecek .prepare_data = features_prepare_data,
1220524399dSMichal Kubecek .reply_size = features_reply_size,
1230524399dSMichal Kubecek .fill_reply = features_fill_reply,
1240524399dSMichal Kubecek };
1250980bfcdSMichal Kubecek
1260980bfcdSMichal Kubecek /* FEATURES_SET */
1270980bfcdSMichal Kubecek
128ff419afaSJakub Kicinski const struct nla_policy ethnl_features_set_policy[] = {
129329d9c33SJakub Kicinski [ETHTOOL_A_FEATURES_HEADER] =
130329d9c33SJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy),
1310980bfcdSMichal Kubecek [ETHTOOL_A_FEATURES_WANTED] = { .type = NLA_NESTED },
1320980bfcdSMichal Kubecek };
1330980bfcdSMichal Kubecek
ethnl_features_to_bitmap(unsigned long * dest,netdev_features_t val)1340980bfcdSMichal Kubecek static void ethnl_features_to_bitmap(unsigned long *dest, netdev_features_t val)
1350980bfcdSMichal Kubecek {
1360980bfcdSMichal Kubecek const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
1370980bfcdSMichal Kubecek unsigned int i;
1380980bfcdSMichal Kubecek
1390980bfcdSMichal Kubecek for (i = 0; i < words; i++)
1400980bfcdSMichal Kubecek dest[i] = (unsigned long)(val >> (i * BITS_PER_LONG));
1410980bfcdSMichal Kubecek }
1420980bfcdSMichal Kubecek
ethnl_bitmap_to_features(unsigned long * src)1430980bfcdSMichal Kubecek static netdev_features_t ethnl_bitmap_to_features(unsigned long *src)
1440980bfcdSMichal Kubecek {
1450980bfcdSMichal Kubecek const unsigned int nft_bits = sizeof(netdev_features_t) * BITS_PER_BYTE;
1460980bfcdSMichal Kubecek const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
1470980bfcdSMichal Kubecek netdev_features_t ret = 0;
1480980bfcdSMichal Kubecek unsigned int i;
1490980bfcdSMichal Kubecek
1500980bfcdSMichal Kubecek for (i = 0; i < words; i++)
1510980bfcdSMichal Kubecek ret |= (netdev_features_t)(src[i]) << (i * BITS_PER_LONG);
1520980bfcdSMichal Kubecek ret &= ~(netdev_features_t)0 >> (nft_bits - NETDEV_FEATURE_COUNT);
1530980bfcdSMichal Kubecek return ret;
1540980bfcdSMichal Kubecek }
1550980bfcdSMichal Kubecek
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)1560980bfcdSMichal Kubecek static int features_send_reply(struct net_device *dev, struct genl_info *info,
1570980bfcdSMichal Kubecek const unsigned long *wanted,
1580980bfcdSMichal Kubecek const unsigned long *wanted_mask,
1590980bfcdSMichal Kubecek const unsigned long *active,
1600980bfcdSMichal Kubecek const unsigned long *active_mask, bool compact)
1610980bfcdSMichal Kubecek {
1620980bfcdSMichal Kubecek struct sk_buff *rskb;
1630980bfcdSMichal Kubecek void *reply_payload;
1640980bfcdSMichal Kubecek int reply_len = 0;
1650980bfcdSMichal Kubecek int ret;
1660980bfcdSMichal Kubecek
1670980bfcdSMichal Kubecek reply_len = ethnl_reply_header_size();
1680980bfcdSMichal Kubecek ret = ethnl_bitset_size(wanted, wanted_mask, NETDEV_FEATURE_COUNT,
1690980bfcdSMichal Kubecek netdev_features_strings, compact);
1700980bfcdSMichal Kubecek if (ret < 0)
1710980bfcdSMichal Kubecek goto err;
1720980bfcdSMichal Kubecek reply_len += ret;
1730980bfcdSMichal Kubecek ret = ethnl_bitset_size(active, active_mask, NETDEV_FEATURE_COUNT,
1740980bfcdSMichal Kubecek netdev_features_strings, compact);
1750980bfcdSMichal Kubecek if (ret < 0)
1760980bfcdSMichal Kubecek goto err;
1770980bfcdSMichal Kubecek reply_len += ret;
1780980bfcdSMichal Kubecek
1790980bfcdSMichal Kubecek ret = -ENOMEM;
1800980bfcdSMichal Kubecek rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_MSG_FEATURES_SET_REPLY,
1810980bfcdSMichal Kubecek ETHTOOL_A_FEATURES_HEADER, info,
1820980bfcdSMichal Kubecek &reply_payload);
1830980bfcdSMichal Kubecek if (!rskb)
1840980bfcdSMichal Kubecek goto err;
1850980bfcdSMichal Kubecek
1860980bfcdSMichal Kubecek ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_WANTED, wanted,
1870980bfcdSMichal Kubecek wanted_mask, NETDEV_FEATURE_COUNT,
1880980bfcdSMichal Kubecek netdev_features_strings, compact);
1890980bfcdSMichal Kubecek if (ret < 0)
1900980bfcdSMichal Kubecek goto nla_put_failure;
1910980bfcdSMichal Kubecek ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_ACTIVE, active,
1920980bfcdSMichal Kubecek active_mask, NETDEV_FEATURE_COUNT,
1930980bfcdSMichal Kubecek netdev_features_strings, compact);
1940980bfcdSMichal Kubecek if (ret < 0)
1950980bfcdSMichal Kubecek goto nla_put_failure;
1960980bfcdSMichal Kubecek
1970980bfcdSMichal Kubecek genlmsg_end(rskb, reply_payload);
1980980bfcdSMichal Kubecek ret = genlmsg_reply(rskb, info);
1990980bfcdSMichal Kubecek return ret;
2000980bfcdSMichal Kubecek
2010980bfcdSMichal Kubecek nla_put_failure:
2020980bfcdSMichal Kubecek nlmsg_free(rskb);
2030980bfcdSMichal Kubecek WARN_ONCE(1, "calculated message payload length (%d) not sufficient\n",
2040980bfcdSMichal Kubecek reply_len);
2050980bfcdSMichal Kubecek err:
2060980bfcdSMichal Kubecek GENL_SET_ERR_MSG(info, "failed to send reply message");
2070980bfcdSMichal Kubecek return ret;
2080980bfcdSMichal Kubecek }
2090980bfcdSMichal Kubecek
ethnl_set_features(struct sk_buff * skb,struct genl_info * info)2100980bfcdSMichal Kubecek int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)
2110980bfcdSMichal Kubecek {
2120980bfcdSMichal Kubecek DECLARE_BITMAP(wanted_diff_mask, NETDEV_FEATURE_COUNT);
2130980bfcdSMichal Kubecek DECLARE_BITMAP(active_diff_mask, NETDEV_FEATURE_COUNT);
2140980bfcdSMichal Kubecek DECLARE_BITMAP(old_active, NETDEV_FEATURE_COUNT);
215840110a4SMaxim Mikityanskiy DECLARE_BITMAP(old_wanted, NETDEV_FEATURE_COUNT);
2160980bfcdSMichal Kubecek DECLARE_BITMAP(new_active, NETDEV_FEATURE_COUNT);
217840110a4SMaxim Mikityanskiy DECLARE_BITMAP(new_wanted, NETDEV_FEATURE_COUNT);
2180980bfcdSMichal Kubecek DECLARE_BITMAP(req_wanted, NETDEV_FEATURE_COUNT);
2190980bfcdSMichal Kubecek DECLARE_BITMAP(req_mask, NETDEV_FEATURE_COUNT);
2200980bfcdSMichal Kubecek struct ethnl_req_info req_info = {};
2215028588bSJakub Kicinski struct nlattr **tb = info->attrs;
2220980bfcdSMichal Kubecek struct net_device *dev;
2239c6451efSMichal Kubecek bool mod;
2240980bfcdSMichal Kubecek int ret;
2250980bfcdSMichal Kubecek
2260980bfcdSMichal Kubecek if (!tb[ETHTOOL_A_FEATURES_WANTED])
2270980bfcdSMichal Kubecek return -EINVAL;
2280980bfcdSMichal Kubecek ret = ethnl_parse_header_dev_get(&req_info,
2290980bfcdSMichal Kubecek tb[ETHTOOL_A_FEATURES_HEADER],
2300980bfcdSMichal Kubecek genl_info_net(info), info->extack,
2310980bfcdSMichal Kubecek true);
2320980bfcdSMichal Kubecek if (ret < 0)
2330980bfcdSMichal Kubecek return ret;
2340980bfcdSMichal Kubecek dev = req_info.dev;
2350980bfcdSMichal Kubecek
2360980bfcdSMichal Kubecek rtnl_lock();
237*2ae2e7cfSLudvig Pärsson ret = ethnl_ops_begin(dev);
238*2ae2e7cfSLudvig Pärsson if (ret < 0)
239*2ae2e7cfSLudvig Pärsson goto out_rtnl;
2400980bfcdSMichal Kubecek ethnl_features_to_bitmap(old_active, dev->features);
241840110a4SMaxim Mikityanskiy ethnl_features_to_bitmap(old_wanted, dev->wanted_features);
2420980bfcdSMichal Kubecek ret = ethnl_parse_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
2430980bfcdSMichal Kubecek tb[ETHTOOL_A_FEATURES_WANTED],
2440980bfcdSMichal Kubecek netdev_features_strings, info->extack);
2450980bfcdSMichal Kubecek if (ret < 0)
246*2ae2e7cfSLudvig Pärsson goto out_ops;
2470980bfcdSMichal Kubecek if (ethnl_bitmap_to_features(req_mask) & ~NETIF_F_ETHTOOL_BITS) {
2480980bfcdSMichal Kubecek GENL_SET_ERR_MSG(info, "attempt to change non-ethtool features");
2490980bfcdSMichal Kubecek ret = -EINVAL;
250*2ae2e7cfSLudvig Pärsson goto out_ops;
2510980bfcdSMichal Kubecek }
2520980bfcdSMichal Kubecek
253840110a4SMaxim Mikityanskiy /* set req_wanted bits not in req_mask from old_wanted */
2540980bfcdSMichal Kubecek bitmap_and(req_wanted, req_wanted, req_mask, NETDEV_FEATURE_COUNT);
255840110a4SMaxim Mikityanskiy bitmap_andnot(new_wanted, old_wanted, req_mask, NETDEV_FEATURE_COUNT);
256840110a4SMaxim Mikityanskiy bitmap_or(req_wanted, new_wanted, req_wanted, NETDEV_FEATURE_COUNT);
257f01204ecSMaxim Mikityanskiy if (!bitmap_equal(req_wanted, old_wanted, NETDEV_FEATURE_COUNT)) {
2582847bfedSMaxim Mikityanskiy dev->wanted_features &= ~dev->hw_features;
2592847bfedSMaxim Mikityanskiy dev->wanted_features |= ethnl_bitmap_to_features(req_wanted) & dev->hw_features;
2600980bfcdSMichal Kubecek __netdev_update_features(dev);
261f01204ecSMaxim Mikityanskiy }
2620980bfcdSMichal Kubecek ethnl_features_to_bitmap(new_active, dev->features);
2639c6451efSMichal Kubecek mod = !bitmap_equal(old_active, new_active, NETDEV_FEATURE_COUNT);
2640980bfcdSMichal Kubecek
2650980bfcdSMichal Kubecek ret = 0;
2660980bfcdSMichal Kubecek if (!(req_info.flags & ETHTOOL_FLAG_OMIT_REPLY)) {
2670980bfcdSMichal Kubecek bool compact = req_info.flags & ETHTOOL_FLAG_COMPACT_BITSETS;
2680980bfcdSMichal Kubecek
2690980bfcdSMichal Kubecek bitmap_xor(wanted_diff_mask, req_wanted, new_active,
2700980bfcdSMichal Kubecek NETDEV_FEATURE_COUNT);
2710980bfcdSMichal Kubecek bitmap_xor(active_diff_mask, old_active, new_active,
2720980bfcdSMichal Kubecek NETDEV_FEATURE_COUNT);
2730980bfcdSMichal Kubecek bitmap_and(wanted_diff_mask, wanted_diff_mask, req_mask,
2740980bfcdSMichal Kubecek NETDEV_FEATURE_COUNT);
2750980bfcdSMichal Kubecek bitmap_and(req_wanted, req_wanted, wanted_diff_mask,
2760980bfcdSMichal Kubecek NETDEV_FEATURE_COUNT);
2770980bfcdSMichal Kubecek bitmap_and(new_active, new_active, active_diff_mask,
2780980bfcdSMichal Kubecek NETDEV_FEATURE_COUNT);
2790980bfcdSMichal Kubecek
2800980bfcdSMichal Kubecek ret = features_send_reply(dev, info, req_wanted,
2810980bfcdSMichal Kubecek wanted_diff_mask, new_active,
2820980bfcdSMichal Kubecek active_diff_mask, compact);
2830980bfcdSMichal Kubecek }
2849c6451efSMichal Kubecek if (mod)
28541369138SAlexander Lobakin netdev_features_change(dev);
2860980bfcdSMichal Kubecek
287*2ae2e7cfSLudvig Pärsson out_ops:
288*2ae2e7cfSLudvig Pärsson ethnl_ops_complete(dev);
2890980bfcdSMichal Kubecek out_rtnl:
2900980bfcdSMichal Kubecek rtnl_unlock();
29134ac17ecSEric Dumazet ethnl_parse_header_dev_put(&req_info);
2920980bfcdSMichal Kubecek return ret;
2930980bfcdSMichal Kubecek }
294