mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-21 11:09:26 -04:00
Currently, a devlink rate's parent device is assumed to be the same as
the one where the devlink rate is created.
This patch changes that to allow rate commands to accept an additional
argument that specifies the parent dev. This will allow devlink rate
groups with leafs from other devices.
Example of the new usage with ynl:
Creating a group on pci/0000:08:00.1 with a parent to an already
existing pci/0000:08:00.1/group1:
./tools/net/ynl/pyynl/cli.py --spec \
Documentation/netlink/specs/devlink.yaml --do rate-new --json '{
"bus-name": "pci",
"dev-name": "0000:08:00.1",
"rate-node-name": "group2",
"rate-parent-node-name": "group1",
"parent-dev": {
"bus-name": "pci",
"dev-name": "0000:08:00.1"
}
}'
Setting the parent of leaf node pci/0000:08:00.1/65537 to
pci/0000:08:00.0/group1:
./tools/net/ynl/pyynl/cli.py --spec \
Documentation/netlink/specs/devlink.yaml --do rate-set --json '{
"bus-name": "pci",
"dev-name": "0000:08:00.1",
"port-index": 65537,
"parent-dev": {
"bus-name": "pci",
"dev-name": "0000:08:00.0"
},
"rate-parent-node-name": "group1"
}'
Signed-off-by: Cosmin Ratiu <cratiu@nvidia.com>
Reviewed-by: Carolina Jubran <cjubran@nvidia.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
Link: https://patch.msgid.link/20260701073254.754518-7-tariqt@nvidia.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
477 lines
12 KiB
C
477 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2016 Mellanox Technologies. All rights reserved.
|
|
* Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
|
|
*/
|
|
|
|
#include <net/genetlink.h>
|
|
#include <net/sock.h>
|
|
|
|
#include "devl_internal.h"
|
|
|
|
#define DEVLINK_NL_FLAG_NEED_PORT BIT(0)
|
|
#define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1)
|
|
#define DEVLINK_NL_FLAG_NEED_DEV_LOCK BIT(2)
|
|
#define DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV BIT(3)
|
|
|
|
static const struct genl_multicast_group devlink_nl_mcgrps[] = {
|
|
[DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME },
|
|
};
|
|
|
|
struct devlink_nl_sock_priv {
|
|
struct devlink_obj_desc __rcu *flt;
|
|
spinlock_t flt_lock; /* Protects flt. */
|
|
};
|
|
|
|
static void devlink_nl_sock_priv_init(void *priv)
|
|
{
|
|
struct devlink_nl_sock_priv *sk_priv = priv;
|
|
|
|
spin_lock_init(&sk_priv->flt_lock);
|
|
}
|
|
|
|
static void devlink_nl_sock_priv_destroy(void *priv)
|
|
{
|
|
struct devlink_nl_sock_priv *sk_priv = priv;
|
|
struct devlink_obj_desc *flt;
|
|
|
|
flt = rcu_dereference_protected(sk_priv->flt, true);
|
|
kfree_rcu(flt, rcu);
|
|
}
|
|
|
|
int devlink_nl_notify_filter_set_doit(struct sk_buff *skb,
|
|
struct genl_info *info)
|
|
{
|
|
struct devlink_nl_sock_priv *sk_priv;
|
|
struct nlattr **attrs = info->attrs;
|
|
struct devlink_obj_desc *flt;
|
|
size_t data_offset = 0;
|
|
size_t data_size = 0;
|
|
char *pos;
|
|
|
|
if (attrs[DEVLINK_ATTR_BUS_NAME])
|
|
data_size = size_add(data_size,
|
|
nla_len(attrs[DEVLINK_ATTR_BUS_NAME]) + 1);
|
|
if (attrs[DEVLINK_ATTR_DEV_NAME])
|
|
data_size = size_add(data_size,
|
|
nla_len(attrs[DEVLINK_ATTR_DEV_NAME]) + 1);
|
|
|
|
flt = kzalloc(size_add(sizeof(*flt), data_size), GFP_KERNEL);
|
|
if (!flt)
|
|
return -ENOMEM;
|
|
|
|
pos = (char *) flt->data;
|
|
if (attrs[DEVLINK_ATTR_BUS_NAME]) {
|
|
data_offset += nla_strscpy(pos,
|
|
attrs[DEVLINK_ATTR_BUS_NAME],
|
|
data_size) + 1;
|
|
flt->bus_name = pos;
|
|
pos += data_offset;
|
|
}
|
|
if (attrs[DEVLINK_ATTR_DEV_NAME]) {
|
|
nla_strscpy(pos, attrs[DEVLINK_ATTR_DEV_NAME],
|
|
data_size - data_offset);
|
|
flt->dev_name = pos;
|
|
}
|
|
|
|
if (attrs[DEVLINK_ATTR_INDEX]) {
|
|
flt->devlink_index = nla_get_uint(attrs[DEVLINK_ATTR_INDEX]);
|
|
flt->devlink_index_valid = true;
|
|
}
|
|
|
|
if (attrs[DEVLINK_ATTR_PORT_INDEX]) {
|
|
flt->port_index = nla_get_u32(attrs[DEVLINK_ATTR_PORT_INDEX]);
|
|
flt->port_index_valid = true;
|
|
}
|
|
|
|
/* Don't attach empty filter. */
|
|
if (!flt->bus_name && !flt->dev_name &&
|
|
!flt->devlink_index_valid && !flt->port_index_valid) {
|
|
kfree(flt);
|
|
flt = NULL;
|
|
}
|
|
|
|
sk_priv = genl_sk_priv_get(&devlink_nl_family, NETLINK_CB(skb).sk);
|
|
if (IS_ERR(sk_priv)) {
|
|
kfree(flt);
|
|
return PTR_ERR(sk_priv);
|
|
}
|
|
spin_lock(&sk_priv->flt_lock);
|
|
flt = rcu_replace_pointer(sk_priv->flt, flt,
|
|
lockdep_is_held(&sk_priv->flt_lock));
|
|
spin_unlock(&sk_priv->flt_lock);
|
|
kfree_rcu(flt, rcu);
|
|
return 0;
|
|
}
|
|
|
|
static bool devlink_obj_desc_match(const struct devlink_obj_desc *desc,
|
|
const struct devlink_obj_desc *flt)
|
|
{
|
|
if (desc->devlink_index_valid && flt->devlink_index_valid &&
|
|
desc->devlink_index != flt->devlink_index)
|
|
return false;
|
|
if (desc->bus_name && flt->bus_name &&
|
|
strcmp(desc->bus_name, flt->bus_name))
|
|
return false;
|
|
if (desc->dev_name && flt->dev_name &&
|
|
strcmp(desc->dev_name, flt->dev_name))
|
|
return false;
|
|
if (desc->port_index_valid && flt->port_index_valid &&
|
|
desc->port_index != flt->port_index)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
int devlink_nl_notify_filter(struct sock *dsk, struct sk_buff *skb, void *data)
|
|
{
|
|
struct devlink_obj_desc *desc = data;
|
|
struct devlink_nl_sock_priv *sk_priv;
|
|
struct devlink_obj_desc *flt;
|
|
int ret = 0;
|
|
|
|
rcu_read_lock();
|
|
sk_priv = __genl_sk_priv_get(&devlink_nl_family, dsk);
|
|
if (!IS_ERR_OR_NULL(sk_priv)) {
|
|
flt = rcu_dereference(sk_priv->flt);
|
|
if (flt)
|
|
ret = !devlink_obj_desc_match(desc, flt);
|
|
}
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
int devlink_nl_put_nested_handle(struct sk_buff *msg, struct net *net,
|
|
struct devlink *devlink, int attrtype)
|
|
{
|
|
struct nlattr *nested_attr;
|
|
struct net *devl_net;
|
|
|
|
nested_attr = nla_nest_start(msg, attrtype);
|
|
if (!nested_attr)
|
|
return -EMSGSIZE;
|
|
if (devlink_nl_put_handle(msg, devlink))
|
|
goto nla_put_failure;
|
|
|
|
rcu_read_lock();
|
|
devl_net = read_pnet_rcu(&devlink->_net);
|
|
if (!net_eq(net, devl_net)) {
|
|
int id = peernet2id_alloc(net, devl_net, GFP_ATOMIC);
|
|
|
|
rcu_read_unlock();
|
|
if (nla_put_s32(msg, DEVLINK_ATTR_NETNS_ID, id))
|
|
return -EMSGSIZE;
|
|
} else {
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
nla_nest_end(msg, nested_attr);
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
nla_nest_cancel(msg, nested_attr);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
int devlink_nl_msg_reply_and_new(struct sk_buff **msg, struct genl_info *info)
|
|
{
|
|
int err;
|
|
|
|
if (*msg) {
|
|
err = genlmsg_reply(*msg, info);
|
|
if (err)
|
|
return err;
|
|
}
|
|
*msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
if (!*msg)
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
struct devlink *
|
|
devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs,
|
|
bool dev_lock)
|
|
{
|
|
struct devlink *devlink;
|
|
unsigned long index;
|
|
char *busname;
|
|
char *devname;
|
|
|
|
if (attrs[DEVLINK_ATTR_INDEX]) {
|
|
if (attrs[DEVLINK_ATTR_BUS_NAME] ||
|
|
attrs[DEVLINK_ATTR_DEV_NAME])
|
|
return ERR_PTR(-EINVAL);
|
|
index = nla_get_u32(attrs[DEVLINK_ATTR_INDEX]);
|
|
devlink = devlinks_xa_lookup_get(net, index);
|
|
if (!devlink)
|
|
return ERR_PTR(-ENODEV);
|
|
goto found;
|
|
}
|
|
|
|
if (!attrs[DEVLINK_ATTR_BUS_NAME] || !attrs[DEVLINK_ATTR_DEV_NAME])
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
busname = nla_data(attrs[DEVLINK_ATTR_BUS_NAME]);
|
|
devname = nla_data(attrs[DEVLINK_ATTR_DEV_NAME]);
|
|
|
|
if (!strcmp(busname, DEVLINK_INDEX_BUS_NAME)) {
|
|
if (kstrtoul(devname, 10, &index))
|
|
return ERR_PTR(-ENODEV);
|
|
devlink = devlinks_xa_lookup_get(net, index);
|
|
if (!devlink)
|
|
return ERR_PTR(-ENODEV);
|
|
goto found;
|
|
}
|
|
|
|
devlinks_xa_for_each_registered_get(net, index, devlink) {
|
|
if (strcmp(devlink_bus_name(devlink), busname) == 0 &&
|
|
strcmp(devlink_dev_name(devlink), devname) == 0)
|
|
goto found;
|
|
devlink_put(devlink);
|
|
}
|
|
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
found:
|
|
devl_dev_lock(devlink, dev_lock);
|
|
if (devl_is_registered(devlink))
|
|
return devlink;
|
|
devl_dev_unlock(devlink, dev_lock);
|
|
devlink_put(devlink);
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
struct devlink *
|
|
devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs)
|
|
{
|
|
unsigned int maxtype = ARRAY_SIZE(devlink_dl_parent_dev_nl_policy) - 1;
|
|
struct devlink *devlink;
|
|
struct nlattr **tb;
|
|
int err;
|
|
|
|
if (!attrs[DEVLINK_ATTR_PARENT_DEV])
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
tb = kcalloc(maxtype + 1, sizeof(*tb), GFP_KERNEL);
|
|
if (!tb)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
err = nla_parse_nested(tb, maxtype, attrs[DEVLINK_ATTR_PARENT_DEV],
|
|
devlink_dl_parent_dev_nl_policy, NULL);
|
|
if (err)
|
|
goto out;
|
|
|
|
devlink = devlink_get_from_attrs_lock(net, tb, false);
|
|
kfree(tb);
|
|
return devlink;
|
|
out:
|
|
kfree(tb);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info,
|
|
u8 flags)
|
|
{
|
|
bool parent_dev = flags & DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV;
|
|
bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK;
|
|
struct devlink *devlink, *parent_devlink = NULL;
|
|
struct net *net = genl_info_net(info);
|
|
struct nlattr **attrs = info->attrs;
|
|
struct devlink_port *devlink_port;
|
|
int err;
|
|
|
|
if (parent_dev && attrs[DEVLINK_ATTR_PARENT_DEV]) {
|
|
parent_devlink = devlink_get_parent_from_attrs_lock(net, attrs);
|
|
if (IS_ERR(parent_devlink))
|
|
return PTR_ERR(parent_devlink);
|
|
devlink_nl_ctx(info)->parent_devlink = parent_devlink;
|
|
/* Drop the parent devlink lock but don't release the reference.
|
|
* This will keep it alive until the end of the request.
|
|
*/
|
|
devl_unlock(parent_devlink);
|
|
}
|
|
|
|
devlink = devlink_get_from_attrs_lock(net, attrs, dev_lock);
|
|
if (IS_ERR(devlink)) {
|
|
err = PTR_ERR(devlink);
|
|
goto parent_put;
|
|
}
|
|
devlink_nl_ctx(info)->devlink = devlink;
|
|
if (flags & DEVLINK_NL_FLAG_NEED_PORT) {
|
|
devlink_port = devlink_port_get_from_info(devlink, info);
|
|
if (IS_ERR(devlink_port)) {
|
|
err = PTR_ERR(devlink_port);
|
|
goto unlock;
|
|
}
|
|
devlink_nl_ctx(info)->devlink_port = devlink_port;
|
|
} else if (flags & DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT) {
|
|
devlink_port = devlink_port_get_from_info(devlink, info);
|
|
if (!IS_ERR(devlink_port))
|
|
devlink_nl_ctx(info)->devlink_port = devlink_port;
|
|
}
|
|
return 0;
|
|
|
|
unlock:
|
|
devl_dev_unlock(devlink, dev_lock);
|
|
devlink_put(devlink);
|
|
parent_put:
|
|
if (parent_dev && parent_devlink)
|
|
devlink_put(parent_devlink);
|
|
return err;
|
|
}
|
|
|
|
int devlink_nl_pre_doit(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
return __devlink_nl_pre_doit(skb, info, 0);
|
|
}
|
|
|
|
int devlink_nl_pre_doit_port(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_PORT);
|
|
}
|
|
|
|
int devlink_nl_pre_doit_dev_lock(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEV_LOCK);
|
|
}
|
|
|
|
int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb,
|
|
struct genl_info *info)
|
|
{
|
|
return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT);
|
|
}
|
|
|
|
int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb,
|
|
struct genl_info *info)
|
|
{
|
|
return __devlink_nl_pre_doit(skb, info,
|
|
DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV);
|
|
}
|
|
|
|
static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info,
|
|
u8 flags)
|
|
{
|
|
bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK;
|
|
struct devlink *devlink;
|
|
|
|
devlink = devlink_nl_ctx(info)->devlink;
|
|
devl_dev_unlock(devlink, dev_lock);
|
|
devlink_put(devlink);
|
|
if (devlink_nl_ctx(info)->parent_devlink)
|
|
devlink_put(devlink_nl_ctx(info)->parent_devlink);
|
|
}
|
|
|
|
void devlink_nl_post_doit(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
__devlink_nl_post_doit(skb, info, 0);
|
|
}
|
|
|
|
void
|
|
devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
__devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEV_LOCK);
|
|
}
|
|
|
|
void
|
|
devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops,
|
|
struct sk_buff *skb,
|
|
struct genl_info *info)
|
|
{
|
|
__devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV);
|
|
}
|
|
|
|
static int devlink_nl_inst_single_dumpit(struct sk_buff *msg,
|
|
struct netlink_callback *cb, int flags,
|
|
devlink_nl_dump_one_func_t *dump_one,
|
|
struct nlattr **attrs)
|
|
{
|
|
struct devlink *devlink;
|
|
int err;
|
|
|
|
devlink = devlink_get_from_attrs_lock(sock_net(msg->sk), attrs, false);
|
|
if (IS_ERR(devlink))
|
|
return PTR_ERR(devlink);
|
|
err = dump_one(msg, devlink, cb, flags | NLM_F_DUMP_FILTERED);
|
|
|
|
devl_unlock(devlink);
|
|
devlink_put(devlink);
|
|
|
|
if (err != -EMSGSIZE)
|
|
return err;
|
|
return msg->len;
|
|
}
|
|
|
|
static int devlink_nl_inst_iter_dumpit(struct sk_buff *msg,
|
|
struct netlink_callback *cb, int flags,
|
|
devlink_nl_dump_one_func_t *dump_one)
|
|
{
|
|
struct devlink_nl_dump_state *state = devlink_dump_state(cb);
|
|
struct devlink *devlink;
|
|
int err = 0;
|
|
|
|
while ((devlink = devlinks_xa_find_get(sock_net(msg->sk),
|
|
&state->instance))) {
|
|
devl_lock(devlink);
|
|
|
|
if (devl_is_registered(devlink))
|
|
err = dump_one(msg, devlink, cb, flags);
|
|
else
|
|
err = 0;
|
|
|
|
devl_unlock(devlink);
|
|
devlink_put(devlink);
|
|
|
|
if (err)
|
|
break;
|
|
|
|
state->instance++;
|
|
|
|
/* restart sub-object walk for the next instance */
|
|
state->idx = 0;
|
|
state->port_ctx.index = 0;
|
|
state->port_ctx.index_valid = false;
|
|
}
|
|
|
|
if (err != -EMSGSIZE)
|
|
return err;
|
|
return msg->len;
|
|
}
|
|
|
|
int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb,
|
|
devlink_nl_dump_one_func_t *dump_one)
|
|
{
|
|
const struct genl_info *info = genl_info_dump(cb);
|
|
struct nlattr **attrs = info->attrs;
|
|
int flags = NLM_F_MULTI;
|
|
|
|
if (attrs &&
|
|
(attrs[DEVLINK_ATTR_BUS_NAME] || attrs[DEVLINK_ATTR_DEV_NAME] ||
|
|
attrs[DEVLINK_ATTR_INDEX]))
|
|
return devlink_nl_inst_single_dumpit(msg, cb, flags, dump_one,
|
|
attrs);
|
|
else
|
|
return devlink_nl_inst_iter_dumpit(msg, cb, flags, dump_one);
|
|
}
|
|
|
|
struct genl_family devlink_nl_family __ro_after_init = {
|
|
.name = DEVLINK_GENL_NAME,
|
|
.version = DEVLINK_GENL_VERSION,
|
|
.netnsok = true,
|
|
.parallel_ops = true,
|
|
.module = THIS_MODULE,
|
|
.split_ops = devlink_nl_ops,
|
|
.n_split_ops = ARRAY_SIZE(devlink_nl_ops),
|
|
.resv_start_op = DEVLINK_CMD_SELFTESTS_RUN + 1,
|
|
.mcgrps = devlink_nl_mcgrps,
|
|
.n_mcgrps = ARRAY_SIZE(devlink_nl_mcgrps),
|
|
.sock_priv_size = sizeof(struct devlink_nl_sock_priv),
|
|
.sock_priv_init = devlink_nl_sock_priv_init,
|
|
.sock_priv_destroy = devlink_nl_sock_priv_destroy,
|
|
};
|