]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ethtool: rss: support creating contexts via Netlink
authorJakub Kicinski <kuba@kernel.org>
Thu, 17 Jul 2025 23:43:41 +0000 (16:43 -0700)
committerJakub Kicinski <kuba@kernel.org>
Tue, 22 Jul 2025 01:20:43 +0000 (18:20 -0700)
Support creating contexts via Netlink. Setting flow hashing
fields on the new context is not supported at this stage,
it can be added later.

An empty indirection table is not supported. This is a carry
over from the IOCTL interface where empty indirection table
meant delete. We can repurpose empty indirection table in
Netlink but for now to avoid confusion reject it using the
policy.

Support letting user choose the ID for the new context. This was
not possible in IOCTL since the context ID field for the create
action had to be set to the ETH_RXFH_CONTEXT_ALLOC magic value.

Link: https://patch.msgid.link/20250717234343.2328602-7-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Documentation/netlink/specs/ethtool.yaml
Documentation/networking/ethtool-netlink.rst
include/uapi/linux/ethtool_netlink_generated.h
net/ethtool/ioctl.c
net/ethtool/netlink.c
net/ethtool/netlink.h
net/ethtool/rss.c

index 069269edde016df9ea66f7965caca3904801b931..25ffed5fddd5c5c00587c283bcdecbf8e4108780 100644 (file)
@@ -2684,9 +2684,28 @@ operations:
       name: rss-ntf
       doc: |
         Notification for change in RSS configuration.
-        For additional contexts only modifications are modified, not creation
-        or removal of the contexts.
+        For additional contexts only modifications use this notification,
+        creation and deletion have dedicated messages.
       notify: rss-get
+    -
+      name: rss-create-act
+      doc: Create an RSS context.
+      attribute-set: rss
+      do:
+        request: &rss-create-attrs
+          attributes:
+            - header
+            - context
+            - hfunc
+            - indir
+            - hkey
+            - input-xfrm
+        reply: *rss-create-attrs
+    -
+      name: rss-create-ntf
+      doc: |
+        Notification for creation of an additional RSS context.
+      notify: rss-create-act
 
 mcast-groups:
   list:
index 056832c77ffd249cc1a1ef3a36e7d60a6898f24a..2646fafb85129536ceb74318eb51d0131f412f68 100644 (file)
@@ -240,6 +240,7 @@ Userspace to kernel:
   ``ETHTOOL_MSG_TSCONFIG_GET``          get hw timestamping configuration
   ``ETHTOOL_MSG_TSCONFIG_SET``          set hw timestamping configuration
   ``ETHTOOL_MSG_RSS_SET``               set RSS settings
+  ``ETHTOOL_MSG_RSS_CREATE_ACT``        create an additional RSS context
   ===================================== =================================
 
 Kernel to userspace:
@@ -294,6 +295,8 @@ Kernel to userspace:
   ``ETHTOOL_MSG_TSCONFIG_SET_REPLY``       new hw timestamping configuration
   ``ETHTOOL_MSG_PSE_NTF``                  PSE events notification
   ``ETHTOOL_MSG_RSS_NTF``                  RSS settings notification
+  ``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY``     create an additional RSS context
+  ``ETHTOOL_MSG_RSS_CREATE_NTF``           additional RSS context created
   ======================================== =================================
 
 ``GET`` requests are sent by userspace applications to retrieve device
@@ -2014,6 +2017,30 @@ device needs at least 8 entries - the real table in use will end up being
 of 2, so tables which size is not a power of 2 will likely be rejected.
 Using table of size 0 will reset the indirection table to the default.
 
+RSS_CREATE_ACT
+==============
+
+Request contents:
+
+=====================================  ======  ==============================
+  ``ETHTOOL_A_RSS_HEADER``             nested  request header
+  ``ETHTOOL_A_RSS_CONTEXT``            u32     context number
+  ``ETHTOOL_A_RSS_HFUNC``              u32     RSS hash func
+  ``ETHTOOL_A_RSS_INDIR``              binary  Indir table bytes
+  ``ETHTOOL_A_RSS_HKEY``               binary  Hash key bytes
+  ``ETHTOOL_A_RSS_INPUT_XFRM``         u32     RSS input data transformation
+=====================================  ======  ==============================
+
+Kernel response contents:
+
+=====================================  ======  ==============================
+  ``ETHTOOL_A_RSS_HEADER``             nested  request header
+  ``ETHTOOL_A_RSS_CONTEXT``            u32     context number
+=====================================  ======  ==============================
+
+Create an additional RSS context, if ``ETHTOOL_A_RSS_CONTEXT`` is not
+specified kernel will allocate one automatically.
+
 PLCA_GET_CFG
 ============
 
index 130bdf5c3516ca7c61706e9570f749c5228b9ae8..dea77abd295fcfe9126e987ea663d92f3676e353 100644 (file)
@@ -841,6 +841,7 @@ enum {
        ETHTOOL_MSG_TSCONFIG_GET,
        ETHTOOL_MSG_TSCONFIG_SET,
        ETHTOOL_MSG_RSS_SET,
+       ETHTOOL_MSG_RSS_CREATE_ACT,
 
        __ETHTOOL_MSG_USER_CNT,
        ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
@@ -898,6 +899,8 @@ enum {
        ETHTOOL_MSG_TSCONFIG_SET_REPLY,
        ETHTOOL_MSG_PSE_NTF,
        ETHTOOL_MSG_RSS_NTF,
+       ETHTOOL_MSG_RSS_CREATE_ACT_REPLY,
+       ETHTOOL_MSG_RSS_CREATE_NTF,
 
        __ETHTOOL_MSG_KERNEL_CNT,
        ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
index c53868889969af201296d349bc398885cd341b39..4b586b0f18e8508fae5c6ea1b4319b77fa02e0f5 100644 (file)
@@ -1640,6 +1640,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
                ntf = ETHTOOL_MSG_RSS_NTF;
                ret = ops->set_rxfh(dev, &rxfh_dev, extack);
        } else if (create) {
+               ntf = ETHTOOL_MSG_RSS_CREATE_NTF;
                ret = ops->create_rxfh_context(dev, ctx, &rxfh_dev, extack);
                /* Make sure driver populates defaults */
                WARN_ON_ONCE(!ret && !rxfh_dev.key && ops->rxfh_per_ctx_key &&
index 0ae0d7a9667c0c73f505bc334e13e616060e6538..e9696113a96b43694cc4d7977ba48becf49776b7 100644 (file)
@@ -81,6 +81,12 @@ static void ethnl_sock_priv_destroy(void *priv)
        }
 }
 
+u32 ethnl_bcast_seq_next(void)
+{
+       ASSERT_RTNL();
+       return ++ethnl_bcast_seq;
+}
+
 int ethnl_ops_begin(struct net_device *dev)
 {
        int ret;
@@ -954,6 +960,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
        [ETHTOOL_MSG_PLCA_NTF]          = &ethnl_plca_cfg_request_ops,
        [ETHTOOL_MSG_MM_NTF]            = &ethnl_mm_request_ops,
        [ETHTOOL_MSG_RSS_NTF]           = &ethnl_rss_request_ops,
+       [ETHTOOL_MSG_RSS_CREATE_NTF]    = &ethnl_rss_request_ops,
 };
 
 /* default notification handler */
@@ -1061,6 +1068,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
        [ETHTOOL_MSG_PLCA_NTF]          = ethnl_default_notify,
        [ETHTOOL_MSG_MM_NTF]            = ethnl_default_notify,
        [ETHTOOL_MSG_RSS_NTF]           = ethnl_default_notify,
+       [ETHTOOL_MSG_RSS_CREATE_NTF]    = ethnl_default_notify,
 };
 
 void ethnl_notify(struct net_device *dev, unsigned int cmd,
@@ -1512,6 +1520,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
                .policy = ethnl_rss_set_policy,
                .maxattr = ARRAY_SIZE(ethnl_rss_set_policy) - 1,
        },
+       {
+               .cmd    = ETHTOOL_MSG_RSS_CREATE_ACT,
+               .flags  = GENL_UNS_ADMIN_PERM,
+               .doit   = ethnl_rss_create_doit,
+               .policy = ethnl_rss_create_policy,
+               .maxattr = ARRAY_SIZE(ethnl_rss_create_policy) - 1,
+       },
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
index ddb2fb00f929a1b20498f81e23d71b2ff71d2b9c..b530bf9f85ee7c0aa5969fa1342b870796acc462 100644 (file)
@@ -10,6 +10,7 @@
 
 struct ethnl_req_info;
 
+u32 ethnl_bcast_seq_next(void);
 int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
                               const struct nlattr *nest, struct net *net,
                               struct netlink_ext_ack *extack,
@@ -485,6 +486,7 @@ extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1];
 extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1];
 extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_CONTEXT + 1];
 extern const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1];
+extern const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1];
 extern const struct nla_policy ethnl_plca_get_cfg_policy[ETHTOOL_A_PLCA_HEADER + 1];
 extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1];
 extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1];
@@ -507,6 +509,7 @@ int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
 int ethnl_tsinfo_start(struct netlink_callback *cb);
 int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
 int ethnl_tsinfo_done(struct netlink_callback *cb);
+int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);
 
 extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
 extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
index e5516e529b4a7170f6916cfd2eb0d72057538e3b..be092dfa4407fae13270b302b16c92f9985136a8 100644 (file)
@@ -893,3 +893,206 @@ const struct ethnl_request_ops ethnl_rss_request_ops = {
        .set                    = ethnl_rss_set,
        .set_ntf_cmd            = ETHTOOL_MSG_RSS_NTF,
 };
+
+/* RSS_CREATE */
+
+const struct nla_policy ethnl_rss_create_policy[ETHTOOL_A_RSS_INPUT_XFRM + 1] = {
+       [ETHTOOL_A_RSS_HEADER]  = NLA_POLICY_NESTED(ethnl_header_policy),
+       [ETHTOOL_A_RSS_CONTEXT] = NLA_POLICY_MIN(NLA_U32, 1),
+       [ETHTOOL_A_RSS_HFUNC]   = NLA_POLICY_MIN(NLA_U32, 1),
+       [ETHTOOL_A_RSS_INDIR]   = NLA_POLICY_MIN(NLA_BINARY, 1),
+       [ETHTOOL_A_RSS_HKEY]    = NLA_POLICY_MIN(NLA_BINARY, 1),
+       [ETHTOOL_A_RSS_INPUT_XFRM] =
+               NLA_POLICY_MAX(NLA_U32, RXH_XFRM_SYM_OR_XOR),
+};
+
+static int
+ethnl_rss_create_validate(struct net_device *dev, struct genl_info *info)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+       struct nlattr **tb = info->attrs;
+       struct nlattr *bad_attr = NULL;
+       u32 rss_context, input_xfrm;
+
+       if (!ops->create_rxfh_context)
+               return -EOPNOTSUPP;
+
+       rss_context = nla_get_u32_default(tb[ETHTOOL_A_RSS_CONTEXT], 0);
+       if (ops->rxfh_max_num_contexts &&
+           ops->rxfh_max_num_contexts <= rss_context) {
+               NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT]);
+               return -ERANGE;
+       }
+
+       if (!ops->rxfh_per_ctx_key) {
+               bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HFUNC];
+               bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HKEY];
+               bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM];
+       }
+
+       input_xfrm = nla_get_u32_default(tb[ETHTOOL_A_RSS_INPUT_XFRM], 0);
+       if (input_xfrm & ~ops->supported_input_xfrm)
+               bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM];
+
+       if (bad_attr) {
+               NL_SET_BAD_ATTR(info->extack, bad_attr);
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static void
+ethnl_rss_create_send_ntf(struct sk_buff *rsp, struct net_device *dev)
+{
+       struct nlmsghdr *nlh = (void *)rsp->data;
+       struct genlmsghdr *genl_hdr;
+
+       /* Convert the reply into a notification */
+       nlh->nlmsg_pid = 0;
+       nlh->nlmsg_seq = ethnl_bcast_seq_next();
+
+       genl_hdr = nlmsg_data(nlh);
+       genl_hdr->cmd = ETHTOOL_MSG_RSS_CREATE_NTF;
+
+       ethnl_multicast(rsp, dev);
+}
+
+int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info)
+{
+       bool indir_dflt = false, mod = false, ntf_fail = false;
+       struct ethtool_rxfh_param rxfh = {};
+       struct ethtool_rxfh_context *ctx;
+       struct nlattr **tb = info->attrs;
+       struct rss_reply_data data = {};
+       const struct ethtool_ops *ops;
+       struct rss_req_info req = {};
+       struct net_device *dev;
+       struct sk_buff *rsp;
+       void *hdr;
+       u32 limit;
+       int ret;
+
+       rsp = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!rsp)
+               return -ENOMEM;
+
+       ret = ethnl_parse_header_dev_get(&req.base, tb[ETHTOOL_A_RSS_HEADER],
+                                        genl_info_net(info), info->extack,
+                                        true);
+       if (ret < 0)
+               goto exit_free_rsp;
+
+       dev = req.base.dev;
+       ops = dev->ethtool_ops;
+
+       req.rss_context = nla_get_u32_default(tb[ETHTOOL_A_RSS_CONTEXT], 0);
+
+       ret = ethnl_rss_create_validate(dev, info);
+       if (ret)
+               goto exit_free_dev;
+
+       rtnl_lock();
+       netdev_lock_ops(dev);
+
+       ret = ethnl_ops_begin(dev);
+       if (ret < 0)
+               goto exit_dev_unlock;
+
+       ret = rss_get_data_alloc(dev, &data);
+       if (ret)
+               goto exit_ops;
+
+       ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod);
+       if (ret)
+               goto exit_clean_data;
+
+       ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
+
+       ret = rss_set_prep_hkey(dev, info, &data, &rxfh, &mod);
+       if (ret)
+               goto exit_free_indir;
+
+       rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
+       ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod);
+
+       ctx = ethtool_rxfh_ctx_alloc(ops, data.indir_size, data.hkey_size);
+       if (!ctx) {
+               ret = -ENOMEM;
+               goto exit_free_hkey;
+       }
+
+       mutex_lock(&dev->ethtool->rss_lock);
+       if (!req.rss_context) {
+               limit = ops->rxfh_max_num_contexts ?: U32_MAX;
+               ret = xa_alloc(&dev->ethtool->rss_ctx, &req.rss_context, ctx,
+                              XA_LIMIT(1, limit - 1), GFP_KERNEL_ACCOUNT);
+       } else {
+               ret = xa_insert(&dev->ethtool->rss_ctx,
+                               req.rss_context, ctx, GFP_KERNEL_ACCOUNT);
+       }
+       if (ret < 0) {
+               NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_RSS_CONTEXT],
+                                   "error allocating context ID");
+               goto err_unlock_free_ctx;
+       }
+       rxfh.rss_context = req.rss_context;
+
+       ret = ops->create_rxfh_context(dev, ctx, &rxfh, info->extack);
+       if (ret)
+               goto err_ctx_id_free;
+
+       /* Make sure driver populates defaults */
+       WARN_ON_ONCE(!rxfh.key && ops->rxfh_per_ctx_key &&
+                    !memchr_inv(ethtool_rxfh_context_key(ctx), 0,
+                                ctx->key_size));
+
+       /* Store the config from rxfh to Xarray.. */
+       rss_set_ctx_update(ctx, tb, &data, &rxfh);
+       /* .. copy from Xarray to data. */
+       __rss_prepare_ctx(dev, &data, ctx);
+
+       hdr = ethnl_unicast_put(rsp, info->snd_portid, info->snd_seq,
+                               ETHTOOL_MSG_RSS_CREATE_ACT_REPLY);
+       ntf_fail = ethnl_fill_reply_header(rsp, dev, ETHTOOL_A_RSS_HEADER);
+       ntf_fail |= rss_fill_reply(rsp, &req.base, &data.base);
+       if (WARN_ON(!hdr || ntf_fail)) {
+               ret = -EMSGSIZE;
+               goto exit_unlock;
+       }
+
+       genlmsg_end(rsp, hdr);
+
+       /* Use the same skb for the response and the notification,
+        * genlmsg_reply() will copy the skb if it has elevated user count.
+        */
+       skb_get(rsp);
+       ret = genlmsg_reply(rsp, info);
+       ethnl_rss_create_send_ntf(rsp, dev);
+       rsp = NULL;
+
+exit_unlock:
+       mutex_unlock(&dev->ethtool->rss_lock);
+exit_free_hkey:
+       kfree(rxfh.key);
+exit_free_indir:
+       kfree(rxfh.indir);
+exit_clean_data:
+       rss_get_data_free(&data);
+exit_ops:
+       ethnl_ops_complete(dev);
+exit_dev_unlock:
+       netdev_unlock_ops(dev);
+       rtnl_unlock();
+exit_free_dev:
+       ethnl_parse_header_dev_put(&req.base);
+exit_free_rsp:
+       nlmsg_free(rsp);
+       return ret;
+
+err_ctx_id_free:
+       xa_erase(&dev->ethtool->rss_ctx, req.rss_context);
+err_unlock_free_ctx:
+       kfree(ctx);
+       goto exit_unlock;
+}