diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst
index 5e397798a402..976bc5ca0962 100644
--- a/Documentation/networking/devlink/devlink-port.rst
+++ b/Documentation/networking/devlink/devlink-port.rst
@@ -417,6 +417,8 @@ API allows to configure following rate object's parameters:
Parent node name. Parent node rate limits are considered as additional limits
to all node children limits. ``tx_max`` is an upper limit for children.
``tx_share`` is a total bandwidth distributed among children.
+ If the device supports cross-function scheduling, the parent can be from a
+ different function of the same underlying device.
``tc_bw``
Allow users to set the bandwidth allocation per traffic class on rate
diff --git a/include/net/devlink.h b/include/net/devlink.h
index fbb434185a67..1165dc1ae165 100644
--- a/include/net/devlink.h
+++ b/include/net/devlink.h
@@ -1585,6 +1585,11 @@ struct devlink_ops {
struct devlink_rate *parent,
void *priv_child, void *priv_parent,
struct netlink_ext_ack *extack);
+ /* Indicates if cross-device rate nodes are supported.
+ * This also requires a shared common ancestor object all devices that
+ * could share rate nodes are nested in.
+ */
+ bool supported_cross_device_rate_nodes;
/**
* selftests_check() - queries if selftest is supported
* @devlink: devlink instancediff --git a/net/devlink/rate.c b/net/devlink/rate.c
index c062fd8a6c36..3d28a4c5bb5f 100644
--- a/net/devlink/rate.c
+++ b/net/devlink/rate.c
@@ -30,20 +30,56 @@ devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info)
return devlink_rate ?: ERR_PTR(-ENODEV);
}
+/* Repeatedly locks the nested-in devlink instances while cross device rate
+ * nodes are supported. Returns the devlink instance where rates should be
+ * stored.
+ */
struct devlink *devl_rate_lock(struct devlink *devlink)
{
- return devlink;
+ struct devlink *rate_devlink = devlink;
+
+ while (rate_devlink->ops &&
+ rate_devlink->ops->supported_cross_device_rate_nodes) {
+ devlink = devlink_nested_in_get_lock(rate_devlink->rel);
+ if (!devlink)
+ break;
+ rate_devlink = devlink;
+ }
+ return rate_devlink;
}
+EXPORT_SYMBOL_GPL(devl_rate_lock);
+/* Variant of the above for when the nested-in devlink instances are already
+ * locked.
+ */
static struct devlink *
devl_get_rate_node_instance_locked(struct devlink *devlink)
{
- return devlink;
+ struct devlink *rate_devlink = devlink;
+
+ while (rate_devlink->ops &&
+ rate_devlink->ops->supported_cross_device_rate_nodes) {
+ devlink = devlink_nested_in_get_locked(rate_devlink->rel);
+ if (!devlink)
+ break;
+ rate_devlink = devlink;
+ }
+ return rate_devlink;
}
+/* Repeatedly unlocks the nested-in devlink instances of 'devlink' while cross
+ * device nodes are supported.
+ */
void devl_rate_unlock(struct devlink *devlink)
{
+ if (!devlink || !devlink->ops ||
+ !devlink->ops->supported_cross_device_rate_nodes)
+ return;
+
+ devl_rate_unlock(devlink_nested_in_get_locked(devlink->rel));
+ devlink_nested_in_put_unlock(devlink->rel);
}
+EXPORT_SYMBOL_GPL(devl_rate_unlock);
static struct devlink_rate *
devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name)@@ -120,6 +156,24 @@ static int devlink_rate_put_tc_bws(struct sk_buff *msg, u32 *tc_bw)
return -EMSGSIZE;
}
+static int devlink_nl_rate_parent_fill(struct sk_buff *msg,
+ struct devlink_rate *devlink_rate)
+{
+ struct devlink_rate *parent = devlink_rate->parent;
+ struct devlink *devlink = parent->devlink;
+
+ if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME,
+ parent->name))
+ return -EMSGSIZE;
+
+ if (devlink != devlink_rate->devlink &&
+ devlink_nl_put_nested_handle(msg, devlink_net(devlink), devlink,
+ DEVLINK_ATTR_PARENT_DEV))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
static int devlink_nl_rate_fill(struct sk_buff *msg,
struct devlink_rate *devlink_rate,
enum devlink_command cmd, u32 portid, u32 seq,@@ -164,10 +218,9 @@ static int devlink_nl_rate_fill(struct sk_buff *msg,
devlink_rate->tx_weight))
goto nla_put_failure;
- if (devlink_rate->parent)
- if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME,
- devlink_rate->parent->name))
- goto nla_put_failure;
+ if (devlink_rate->parent &&
+ devlink_nl_rate_parent_fill(msg, devlink_rate))
+ goto nla_put_failure;
if (devlink_rate_put_tc_bws(msg, devlink_rate->tc_bw))
goto nla_put_failure;
@@ -320,13 +373,14 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate,
struct genl_info *info,
struct nlattr *nla_parent)
{
- struct devlink *devlink = devlink_rate->devlink;
+ struct devlink *devlink = devlink_rate->devlink, *parent_devlink;
const char *parent_name = nla_data(nla_parent);
const struct devlink_ops *ops = devlink->ops;
size_t len = strlen(parent_name);
struct devlink_rate *parent;
int err = -EOPNOTSUPP;
+ parent_devlink = info->user_ptr[1] ? : devlink;
parent = devlink_rate->parent;
if (parent && !len) {@@ -344,7 +398,13 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate,
refcount_dec(&parent->refcnt);
devlink_rate->parent = NULL;
} else if (len) {
- parent = devlink_rate_node_get_by_name(devlink, parent_name);
+ /* parent_devlink (when different than devlink) isn't locked,
+ * but the rate node devlink instance is, so nobody from the
+ * same group of devices sharing rates could change the used
+ * fields or unregister the parent.
+ */
+ parent = devlink_rate_node_get_by_name(parent_devlink,
+ parent_name);
if (IS_ERR(parent))
return -ENODEV;
@@ -644,6 +704,14 @@ int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info)
goto unlock;
}
+ if (info->user_ptr[1] && info->user_ptr[1] != devlink &&
+ !ops->supported_cross_device_rate_nodes) {
+ NL_SET_ERR_MSG(info->extack,
+ "Cross-device rate parents aren't supported");
+ err = -EOPNOTSUPP;
+ goto unlock;
+ }
+
err = devlink_nl_rate_set(devlink_rate, ops, info);
if (!err)@@ -669,6 +737,13 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info)
if (!devlink_rate_set_ops_supported(ops, info, DEVLINK_RATE_TYPE_NODE))
return -EOPNOTSUPP;
+ if (info->user_ptr[1] && info->user_ptr[1] != devlink &&
+ !ops->supported_cross_device_rate_nodes) {
+ NL_SET_ERR_MSG(info->extack,
+ "Cross-device rate parents aren't supported");
+ return -EOPNOTSUPP;
+ }
+
rate_devlink = devl_rate_lock(devlink);
rate_node = devlink_rate_node_get_from_attrs(devlink, info->attrs);
if (!IS_ERR(rate_node)) {--
2.44.0