Commit 9157bc2a authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'netdevsim-implement-proper-device-model'

Jiri Pirko says:

====================
netdevsim: implement proper device model

Currently the model of netdevsim is a bit odd in multiple ways.
1) devlink instance is not in any way related with actual netdevsim
   netdevices. Instead, it is created per-namespace.
2) multi-port netdevsim device is done using "link" attribute.
3) netdevsim bus is there only to have something to bind the netdev to,
   it really does not act as a bus.
4) netdevsim instances are created by "ip link add" which is great for
   soft devices with no hw backend. The rtnl core allocates netdev and
   calls into driver holding rtnl mutex. For hw-backed devices, this
   flow is wrong as it breaks order in which things are done.

This patchset adjust netdevsim to fix all above.

In order to support proper devlink and devlink port instances and to be
able to emulate real devices, there is need to implement bus probe and
instantiate everything from there. User can specify device id and port
count to be instantianted. For example:

$ echo "10 4" > /sys/bus/netdevsim/new_device

Then devlink shows this:

$ devlink dev
netdevsim/netdevsim10

$ devlink port
netdevsim/netdevsim10/0: type eth netdev eni0np1 flavour physical
netdevsim/netdevsim10/1: type eth netdev eni0np2 flavour physical
netdevsim/netdevsim10/2: type eth netdev eni0np3 flavour physical
netdevsim/netdevsim10/3: type eth netdev eni0np4 flavour physical

There is possible to add and delete ports using their indexes
during netdevsim device lifetime like this:

Then devlink shows this:

$ devlink port
netdevsim/netdevsim10/1: type eth netdev eni10np2 flavour physical
netdevsim/netdevsim10/2: type eth netdev eni10np3 flavour physical
netdevsim/netdevsim10/3: type eth netdev eni10np4 flavour physical
netdevsim/netdevsim10/43: type eth netdev eni10np44 flavour physical

Debugfs topology is also adjusted a bit. The rest stays the same as
before.

Udev bits are merged un systemd upstream git:
https://github.com/systemd/systemd/commit/eaa9d507d85509c8bf727356e3884ec54b0fc646



See individual patches for changelog.
====================

Acked-by: default avatarJakub Kicinski <jakub.kicinski@netronome.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 89eb6e09 a62fdbbe
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
obj-$(CONFIG_NETDEVSIM) += netdevsim.o

netdevsim-objs := \
	netdev.o devlink.o fib.o sdev.o \
	netdev.o dev.o fib.o bus.o

ifeq ($(CONFIG_BPF_SYSCALL),y)
netdevsim-objs += \
+40 −52
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@
	bpf_verifier_log_write(env, "[netdevsim] " fmt, ##__VA_ARGS__)

struct nsim_bpf_bound_prog {
	struct netdevsim_shared_dev *sdev;
	struct nsim_dev *nsim_dev;
	struct bpf_prog *prog;
	struct dentry *ddir;
	const char *state;
@@ -65,8 +65,8 @@ nsim_bpf_verify_insn(struct bpf_verifier_env *env, int insn_idx, int prev_insn)
	struct nsim_bpf_bound_prog *state;

	state = env->prog->aux->offload->dev_priv;
	if (state->sdev->bpf_bind_verifier_delay && !insn_idx)
		msleep(state->sdev->bpf_bind_verifier_delay);
	if (state->nsim_dev->bpf_bind_verifier_delay && !insn_idx)
		msleep(state->nsim_dev->bpf_bind_verifier_delay);

	if (insn_idx == env->prog->len - 1)
		pr_vlog(env, "Hello from netdevsim!\n");
@@ -213,7 +213,7 @@ nsim_xdp_set_prog(struct netdevsim *ns, struct netdev_bpf *bpf,
	return 0;
}

static int nsim_bpf_create_prog(struct netdevsim_shared_dev *sdev,
static int nsim_bpf_create_prog(struct nsim_dev *nsim_dev,
				struct bpf_prog *prog)
{
	struct nsim_bpf_bound_prog *state;
@@ -223,13 +223,13 @@ static int nsim_bpf_create_prog(struct netdevsim_shared_dev *sdev,
	if (!state)
		return -ENOMEM;

	state->sdev = sdev;
	state->nsim_dev = nsim_dev;
	state->prog = prog;
	state->state = "verify";

	/* Program id is not populated yet when we create the state. */
	sprintf(name, "%u", sdev->prog_id_gen++);
	state->ddir = debugfs_create_dir(name, sdev->ddir_bpf_bound_progs);
	sprintf(name, "%u", nsim_dev->prog_id_gen++);
	state->ddir = debugfs_create_dir(name, nsim_dev->ddir_bpf_bound_progs);
	if (IS_ERR_OR_NULL(state->ddir)) {
		kfree(state);
		return -ENOMEM;
@@ -240,7 +240,7 @@ static int nsim_bpf_create_prog(struct netdevsim_shared_dev *sdev,
			    &state->state, &nsim_bpf_string_fops);
	debugfs_create_bool("loaded", 0400, state->ddir, &state->is_loaded);

	list_add_tail(&state->l, &sdev->bpf_bound_progs);
	list_add_tail(&state->l, &nsim_dev->bpf_bound_progs);

	prog->aux->offload->dev_priv = state;

@@ -249,13 +249,13 @@ static int nsim_bpf_create_prog(struct netdevsim_shared_dev *sdev,

static int nsim_bpf_verifier_prep(struct bpf_prog *prog)
{
	struct netdevsim_shared_dev *sdev =
	struct nsim_dev *nsim_dev =
			bpf_offload_dev_priv(prog->aux->offload->offdev);

	if (!sdev->bpf_bind_accept)
	if (!nsim_dev->bpf_bind_accept)
		return -EOPNOTSUPP;

	return nsim_bpf_create_prog(sdev, prog);
	return nsim_bpf_create_prog(nsim_dev, prog);
}

static int nsim_bpf_translate(struct bpf_prog *prog)
@@ -514,7 +514,7 @@ nsim_bpf_map_alloc(struct netdevsim *ns, struct bpf_offloaded_map *offmap)
	}

	offmap->dev_ops = &nsim_bpf_map_ops;
	list_add_tail(&nmap->l, &ns->sdev->bpf_bound_maps);
	list_add_tail(&nmap->l, &ns->nsim_dev->bpf_bound_maps);

	return 0;

@@ -578,77 +578,68 @@ int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf)
	}
}

static int nsim_bpf_sdev_init(struct netdevsim_shared_dev *sdev)
int nsim_bpf_dev_init(struct nsim_dev *nsim_dev)
{
	int err;

	INIT_LIST_HEAD(&sdev->bpf_bound_progs);
	INIT_LIST_HEAD(&sdev->bpf_bound_maps);
	INIT_LIST_HEAD(&nsim_dev->bpf_bound_progs);
	INIT_LIST_HEAD(&nsim_dev->bpf_bound_maps);

	sdev->ddir_bpf_bound_progs =
		debugfs_create_dir("bpf_bound_progs", sdev->ddir);
	if (IS_ERR_OR_NULL(sdev->ddir_bpf_bound_progs))
	nsim_dev->ddir_bpf_bound_progs = debugfs_create_dir("bpf_bound_progs",
							    nsim_dev->ddir);
	if (IS_ERR_OR_NULL(nsim_dev->ddir_bpf_bound_progs))
		return -ENOMEM;

	sdev->bpf_dev = bpf_offload_dev_create(&nsim_bpf_dev_ops, sdev);
	err = PTR_ERR_OR_ZERO(sdev->bpf_dev);
	nsim_dev->bpf_dev = bpf_offload_dev_create(&nsim_bpf_dev_ops, nsim_dev);
	err = PTR_ERR_OR_ZERO(nsim_dev->bpf_dev);
	if (err)
		return err;

	sdev->bpf_bind_accept = true;
	debugfs_create_bool("bpf_bind_accept", 0600, sdev->ddir,
			    &sdev->bpf_bind_accept);
	debugfs_create_u32("bpf_bind_verifier_delay", 0600, sdev->ddir,
			   &sdev->bpf_bind_verifier_delay);
	nsim_dev->bpf_bind_accept = true;
	debugfs_create_bool("bpf_bind_accept", 0600, nsim_dev->ddir,
			    &nsim_dev->bpf_bind_accept);
	debugfs_create_u32("bpf_bind_verifier_delay", 0600, nsim_dev->ddir,
			   &nsim_dev->bpf_bind_verifier_delay);
	return 0;
}

static void nsim_bpf_sdev_uninit(struct netdevsim_shared_dev *sdev)
void nsim_bpf_dev_exit(struct nsim_dev *nsim_dev)
{
	WARN_ON(!list_empty(&sdev->bpf_bound_progs));
	WARN_ON(!list_empty(&sdev->bpf_bound_maps));
	bpf_offload_dev_destroy(sdev->bpf_dev);
	WARN_ON(!list_empty(&nsim_dev->bpf_bound_progs));
	WARN_ON(!list_empty(&nsim_dev->bpf_bound_maps));
	bpf_offload_dev_destroy(nsim_dev->bpf_dev);
}

int nsim_bpf_init(struct netdevsim *ns)
{
	struct dentry *ddir = ns->nsim_dev_port->ddir;
	int err;

	if (ns->sdev->refcnt == 1) {
		err = nsim_bpf_sdev_init(ns->sdev);
	err = bpf_offload_dev_netdev_register(ns->nsim_dev->bpf_dev,
					      ns->netdev);
	if (err)
		return err;
	}

	err = bpf_offload_dev_netdev_register(ns->sdev->bpf_dev, ns->netdev);
	if (err)
		goto err_bpf_sdev_uninit;

	debugfs_create_u32("bpf_offloaded_id", 0400, ns->ddir,
	debugfs_create_u32("bpf_offloaded_id", 0400, ddir,
			   &ns->bpf_offloaded_id);

	ns->bpf_tc_accept = true;
	debugfs_create_bool("bpf_tc_accept", 0600, ns->ddir,
	debugfs_create_bool("bpf_tc_accept", 0600, ddir,
			    &ns->bpf_tc_accept);
	debugfs_create_bool("bpf_tc_non_bound_accept", 0600, ns->ddir,
	debugfs_create_bool("bpf_tc_non_bound_accept", 0600, ddir,
			    &ns->bpf_tc_non_bound_accept);
	ns->bpf_xdpdrv_accept = true;
	debugfs_create_bool("bpf_xdpdrv_accept", 0600, ns->ddir,
	debugfs_create_bool("bpf_xdpdrv_accept", 0600, ddir,
			    &ns->bpf_xdpdrv_accept);
	ns->bpf_xdpoffload_accept = true;
	debugfs_create_bool("bpf_xdpoffload_accept", 0600, ns->ddir,
	debugfs_create_bool("bpf_xdpoffload_accept", 0600, ddir,
			    &ns->bpf_xdpoffload_accept);

	ns->bpf_map_accept = true;
	debugfs_create_bool("bpf_map_accept", 0600, ns->ddir,
	debugfs_create_bool("bpf_map_accept", 0600, ddir,
			    &ns->bpf_map_accept);

	return 0;

err_bpf_sdev_uninit:
	if (ns->sdev->refcnt == 1)
		nsim_bpf_sdev_uninit(ns->sdev);
	return err;
}

void nsim_bpf_uninit(struct netdevsim *ns)
@@ -656,8 +647,5 @@ void nsim_bpf_uninit(struct netdevsim *ns)
	WARN_ON(ns->xdp.prog);
	WARN_ON(ns->xdp_hw.prog);
	WARN_ON(ns->bpf_offloaded);
	bpf_offload_dev_netdev_unregister(ns->sdev->bpf_dev, ns->netdev);

	if (ns->sdev->refcnt == 1)
		nsim_bpf_sdev_uninit(ns->sdev);
	bpf_offload_dev_netdev_unregister(ns->nsim_dev->bpf_dev, ns->netdev);
}
+341 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2017 Netronome Systems, Inc.
 * Copyright (C) 2019 Mellanox Technologies. All rights reserved
 */

#include <linux/device.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <linux/sysfs.h>

#include "netdevsim.h"

static DEFINE_IDA(nsim_bus_dev_ids);
static LIST_HEAD(nsim_bus_dev_list);
static DEFINE_MUTEX(nsim_bus_dev_list_lock);

static struct nsim_bus_dev *to_nsim_bus_dev(struct device *dev)
{
	return container_of(dev, struct nsim_bus_dev, dev);
}

static int nsim_bus_dev_vfs_enable(struct nsim_bus_dev *nsim_bus_dev,
				   unsigned int num_vfs)
{
	nsim_bus_dev->vfconfigs = kcalloc(num_vfs,
					  sizeof(struct nsim_vf_config),
					  GFP_KERNEL);
	if (!nsim_bus_dev->vfconfigs)
		return -ENOMEM;
	nsim_bus_dev->num_vfs = num_vfs;

	return 0;
}

static void nsim_bus_dev_vfs_disable(struct nsim_bus_dev *nsim_bus_dev)
{
	kfree(nsim_bus_dev->vfconfigs);
	nsim_bus_dev->vfconfigs = NULL;
	nsim_bus_dev->num_vfs = 0;
}

static ssize_t
nsim_bus_dev_numvfs_store(struct device *dev, struct device_attribute *attr,
			  const char *buf, size_t count)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);
	unsigned int num_vfs;
	int ret;

	ret = kstrtouint(buf, 0, &num_vfs);
	if (ret)
		return ret;

	rtnl_lock();
	if (nsim_bus_dev->num_vfs == num_vfs)
		goto exit_good;
	if (nsim_bus_dev->num_vfs && num_vfs) {
		ret = -EBUSY;
		goto exit_unlock;
	}

	if (num_vfs) {
		ret = nsim_bus_dev_vfs_enable(nsim_bus_dev, num_vfs);
		if (ret)
			goto exit_unlock;
	} else {
		nsim_bus_dev_vfs_disable(nsim_bus_dev);
	}
exit_good:
	ret = count;
exit_unlock:
	rtnl_unlock();

	return ret;
}

static ssize_t
nsim_bus_dev_numvfs_show(struct device *dev,
			 struct device_attribute *attr, char *buf)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);

	return sprintf(buf, "%u\n", nsim_bus_dev->num_vfs);
}

static struct device_attribute nsim_bus_dev_numvfs_attr =
	__ATTR(sriov_numvfs, 0664, nsim_bus_dev_numvfs_show,
	       nsim_bus_dev_numvfs_store);

static ssize_t
new_port_store(struct device *dev, struct device_attribute *attr,
	       const char *buf, size_t count)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);
	unsigned int port_index;
	int ret;

	ret = kstrtouint(buf, 0, &port_index);
	if (ret)
		return ret;
	ret = nsim_dev_port_add(nsim_bus_dev, port_index);
	return ret ? ret : count;
}

static struct device_attribute nsim_bus_dev_new_port_attr = __ATTR_WO(new_port);

static ssize_t
del_port_store(struct device *dev, struct device_attribute *attr,
	       const char *buf, size_t count)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);
	unsigned int port_index;
	int ret;

	ret = kstrtouint(buf, 0, &port_index);
	if (ret)
		return ret;
	ret = nsim_dev_port_del(nsim_bus_dev, port_index);
	return ret ? ret : count;
}

static struct device_attribute nsim_bus_dev_del_port_attr = __ATTR_WO(del_port);

static struct attribute *nsim_bus_dev_attrs[] = {
	&nsim_bus_dev_numvfs_attr.attr,
	&nsim_bus_dev_new_port_attr.attr,
	&nsim_bus_dev_del_port_attr.attr,
	NULL,
};

static const struct attribute_group nsim_bus_dev_attr_group = {
	.attrs = nsim_bus_dev_attrs,
};

static const struct attribute_group *nsim_bus_dev_attr_groups[] = {
	&nsim_bus_dev_attr_group,
	NULL,
};

static void nsim_bus_dev_release(struct device *dev)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);

	nsim_bus_dev_vfs_disable(nsim_bus_dev);
}

static struct device_type nsim_bus_dev_type = {
	.groups = nsim_bus_dev_attr_groups,
	.release = nsim_bus_dev_release,
};

static struct nsim_bus_dev *
nsim_bus_dev_new(unsigned int id, unsigned int port_count);

static ssize_t
new_device_store(struct bus_type *bus, const char *buf, size_t count)
{
	struct nsim_bus_dev *nsim_bus_dev;
	unsigned int port_count;
	unsigned int id;
	int err;

	err = sscanf(buf, "%u %u", &id, &port_count);
	switch (err) {
	case 1:
		port_count = 1;
		/* pass through */
	case 2:
		if (id > INT_MAX) {
			pr_err("Value of \"id\" is too big.\n");
			return -EINVAL;
		}
		break;
	default:
		pr_err("Format for adding new device is \"id port_count\" (uint uint).\n");
		return -EINVAL;
	}
	nsim_bus_dev = nsim_bus_dev_new(id, port_count);
	if (IS_ERR(nsim_bus_dev))
		return PTR_ERR(nsim_bus_dev);

	mutex_lock(&nsim_bus_dev_list_lock);
	list_add_tail(&nsim_bus_dev->list, &nsim_bus_dev_list);
	mutex_unlock(&nsim_bus_dev_list_lock);

	return count;
}
static BUS_ATTR_WO(new_device);

static void nsim_bus_dev_del(struct nsim_bus_dev *nsim_bus_dev);

static ssize_t
del_device_store(struct bus_type *bus, const char *buf, size_t count)
{
	struct nsim_bus_dev *nsim_bus_dev, *tmp;
	unsigned int id;
	int err;

	err = sscanf(buf, "%u", &id);
	switch (err) {
	case 1:
		if (id > INT_MAX) {
			pr_err("Value of \"id\" is too big.\n");
			return -EINVAL;
		}
		break;
	default:
		pr_err("Format for deleting device is \"id\" (uint).\n");
		return -EINVAL;
	}

	err = -ENOENT;
	mutex_lock(&nsim_bus_dev_list_lock);
	list_for_each_entry_safe(nsim_bus_dev, tmp, &nsim_bus_dev_list, list) {
		if (nsim_bus_dev->dev.id != id)
			continue;
		list_del(&nsim_bus_dev->list);
		nsim_bus_dev_del(nsim_bus_dev);
		err = 0;
		break;
	}
	mutex_unlock(&nsim_bus_dev_list_lock);
	return !err ? count : err;
}
static BUS_ATTR_WO(del_device);

static struct attribute *nsim_bus_attrs[] = {
	&bus_attr_new_device.attr,
	&bus_attr_del_device.attr,
	NULL
};
ATTRIBUTE_GROUPS(nsim_bus);

static int nsim_bus_probe(struct device *dev)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);

	return nsim_dev_probe(nsim_bus_dev);
}

static int nsim_bus_remove(struct device *dev)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);

	nsim_dev_remove(nsim_bus_dev);
	return 0;
}

int nsim_num_vf(struct device *dev)
{
	struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);

	return nsim_bus_dev->num_vfs;
}

static struct bus_type nsim_bus = {
	.name		= DRV_NAME,
	.dev_name	= DRV_NAME,
	.bus_groups	= nsim_bus_groups,
	.probe		= nsim_bus_probe,
	.remove		= nsim_bus_remove,
	.num_vf		= nsim_num_vf,
};

static struct nsim_bus_dev *
nsim_bus_dev_new(unsigned int id, unsigned int port_count)
{
	struct nsim_bus_dev *nsim_bus_dev;
	int err;

	nsim_bus_dev = kzalloc(sizeof(*nsim_bus_dev), GFP_KERNEL);
	if (!nsim_bus_dev)
		return ERR_PTR(-ENOMEM);

	err = ida_alloc_range(&nsim_bus_dev_ids, id, id, GFP_KERNEL);
	if (err < 0)
		goto err_nsim_bus_dev_free;
	nsim_bus_dev->dev.id = err;
	nsim_bus_dev->dev.bus = &nsim_bus;
	nsim_bus_dev->dev.type = &nsim_bus_dev_type;
	nsim_bus_dev->port_count = port_count;

	err = device_register(&nsim_bus_dev->dev);
	if (err)
		goto err_nsim_bus_dev_id_free;
	return nsim_bus_dev;

err_nsim_bus_dev_id_free:
	ida_free(&nsim_bus_dev_ids, nsim_bus_dev->dev.id);
err_nsim_bus_dev_free:
	kfree(nsim_bus_dev);
	return ERR_PTR(err);
}

static void nsim_bus_dev_del(struct nsim_bus_dev *nsim_bus_dev)
{
	device_unregister(&nsim_bus_dev->dev);
	ida_free(&nsim_bus_dev_ids, nsim_bus_dev->dev.id);
	kfree(nsim_bus_dev);
}

static struct device_driver nsim_driver = {
	.name		= DRV_NAME,
	.bus		= &nsim_bus,
	.owner		= THIS_MODULE,
};

int nsim_bus_init(void)
{
	int err;

	err = bus_register(&nsim_bus);
	if (err)
		return err;
	err = driver_register(&nsim_driver);
	if (err)
		goto err_bus_unregister;
	return 0;

err_bus_unregister:
	bus_unregister(&nsim_bus);
	return err;
}

void nsim_bus_exit(void)
{
	struct nsim_bus_dev *nsim_bus_dev, *tmp;

	mutex_lock(&nsim_bus_dev_list_lock);
	list_for_each_entry_safe(nsim_bus_dev, tmp, &nsim_bus_dev_list, list) {
		list_del(&nsim_bus_dev->list);
		nsim_bus_dev_del(nsim_bus_dev);
	}
	mutex_unlock(&nsim_bus_dev_list_lock);
	driver_unregister(&nsim_driver);
	bus_unregister(&nsim_bus);
}
+447 −0

File added.

Preview size limit exceeded, changes collapsed.

drivers/net/netdevsim/devlink.c

deleted100644 → 0
+0 −295
Original line number Diff line number Diff line
/*
 * Copyright (c) 2018 Cumulus Networks. All rights reserved.
 * Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
 *
 * This software is licensed under the GNU General License Version 2,
 * June 1991 as shown in the file COPYING in the top-level directory of this
 * source tree.
 *
 * THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
 * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
 * OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
 * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 */

#include <linux/device.h>
#include <net/devlink.h>
#include <net/netns/generic.h>

#include "netdevsim.h"

static unsigned int nsim_devlink_id;

/* place holder until devlink and namespaces is sorted out */
static struct net *nsim_devlink_net(struct devlink *devlink)
{
	return &init_net;
}

/* IPv4
 */
static u64 nsim_ipv4_fib_resource_occ_get(void *priv)
{
	struct net *net = priv;

	return nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB, false);
}

static u64 nsim_ipv4_fib_rules_res_occ_get(void *priv)
{
	struct net *net = priv;

	return nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB_RULES, false);
}

/* IPv6
 */
static u64 nsim_ipv6_fib_resource_occ_get(void *priv)
{
	struct net *net = priv;

	return nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB, false);
}

static u64 nsim_ipv6_fib_rules_res_occ_get(void *priv)
{
	struct net *net = priv;

	return nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB_RULES, false);
}

static int devlink_resources_register(struct devlink *devlink)
{
	struct devlink_resource_size_params params = {
		.size_max = (u64)-1,
		.size_granularity = 1,
		.unit = DEVLINK_RESOURCE_UNIT_ENTRY
	};
	struct net *net = nsim_devlink_net(devlink);
	int err;
	u64 n;

	/* Resources for IPv4 */
	err = devlink_resource_register(devlink, "IPv4", (u64)-1,
					NSIM_RESOURCE_IPV4,
					DEVLINK_RESOURCE_ID_PARENT_TOP,
					&params);
	if (err) {
		pr_err("Failed to register IPv4 top resource\n");
		goto out;
	}

	n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB, true);
	err = devlink_resource_register(devlink, "fib", n,
					NSIM_RESOURCE_IPV4_FIB,
					NSIM_RESOURCE_IPV4, &params);
	if (err) {
		pr_err("Failed to register IPv4 FIB resource\n");
		return err;
	}

	n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB_RULES, true);
	err = devlink_resource_register(devlink, "fib-rules", n,
					NSIM_RESOURCE_IPV4_FIB_RULES,
					NSIM_RESOURCE_IPV4, &params);
	if (err) {
		pr_err("Failed to register IPv4 FIB rules resource\n");
		return err;
	}

	/* Resources for IPv6 */
	err = devlink_resource_register(devlink, "IPv6", (u64)-1,
					NSIM_RESOURCE_IPV6,
					DEVLINK_RESOURCE_ID_PARENT_TOP,
					&params);
	if (err) {
		pr_err("Failed to register IPv6 top resource\n");
		goto out;
	}

	n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB, true);
	err = devlink_resource_register(devlink, "fib", n,
					NSIM_RESOURCE_IPV6_FIB,
					NSIM_RESOURCE_IPV6, &params);
	if (err) {
		pr_err("Failed to register IPv6 FIB resource\n");
		return err;
	}

	n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB_RULES, true);
	err = devlink_resource_register(devlink, "fib-rules", n,
					NSIM_RESOURCE_IPV6_FIB_RULES,
					NSIM_RESOURCE_IPV6, &params);
	if (err) {
		pr_err("Failed to register IPv6 FIB rules resource\n");
		return err;
	}

	devlink_resource_occ_get_register(devlink,
					  NSIM_RESOURCE_IPV4_FIB,
					  nsim_ipv4_fib_resource_occ_get,
					  net);
	devlink_resource_occ_get_register(devlink,
					  NSIM_RESOURCE_IPV4_FIB_RULES,
					  nsim_ipv4_fib_rules_res_occ_get,
					  net);
	devlink_resource_occ_get_register(devlink,
					  NSIM_RESOURCE_IPV6_FIB,
					  nsim_ipv6_fib_resource_occ_get,
					  net);
	devlink_resource_occ_get_register(devlink,
					  NSIM_RESOURCE_IPV6_FIB_RULES,
					  nsim_ipv6_fib_rules_res_occ_get,
					  net);
out:
	return err;
}

static int nsim_devlink_reload(struct devlink *devlink,
			       struct netlink_ext_ack *extack)
{
	enum nsim_resource_id res_ids[] = {
		NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
		NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
	};
	struct net *net = nsim_devlink_net(devlink);
	int i;

	for (i = 0; i < ARRAY_SIZE(res_ids); ++i) {
		int err;
		u64 val;

		err = devlink_resource_size_get(devlink, res_ids[i], &val);
		if (!err) {
			err = nsim_fib_set_max(net, res_ids[i], val, extack);
			if (err)
				return err;
		}
	}

	return 0;
}

static void nsim_devlink_net_reset(struct net *net)
{
	enum nsim_resource_id res_ids[] = {
		NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
		NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
	};
	int i;

	for (i = 0; i < ARRAY_SIZE(res_ids); ++i) {
		if (nsim_fib_set_max(net, res_ids[i], (u64)-1, NULL)) {
			pr_err("Failed to reset limit for resource %u\n",
			       res_ids[i]);
		}
	}
}

static const struct devlink_ops nsim_devlink_ops = {
	.reload = nsim_devlink_reload,
};

/* once devlink / namespace issues are sorted out
 * this needs to be net in which a devlink instance
 * is to be created. e.g., dev_net(ns->netdev)
 */
static struct net *nsim_to_net(struct netdevsim *ns)
{
	return &init_net;
}

void nsim_devlink_teardown(struct netdevsim *ns)
{
	if (ns->devlink) {
		struct net *net = nsim_to_net(ns);
		bool *reg_devlink = net_generic(net, nsim_devlink_id);

		devlink_resources_unregister(ns->devlink, NULL);
		devlink_unregister(ns->devlink);
		devlink_free(ns->devlink);
		ns->devlink = NULL;

		nsim_devlink_net_reset(net);
		*reg_devlink = true;
	}
}

int nsim_devlink_setup(struct netdevsim *ns)
{
	struct net *net = nsim_to_net(ns);
	bool *reg_devlink = net_generic(net, nsim_devlink_id);
	struct devlink *devlink;
	int err;

	/* only one device per namespace controls devlink */
	if (!*reg_devlink) {
		ns->devlink = NULL;
		return 0;
	}

	devlink = devlink_alloc(&nsim_devlink_ops, 0);
	if (!devlink)
		return -ENOMEM;

	err = devlink_register(devlink, &ns->dev);
	if (err)
		goto err_devlink_free;

	err = devlink_resources_register(devlink);
	if (err)
		goto err_dl_unregister;

	ns->devlink = devlink;

	*reg_devlink = false;

	return 0;

err_dl_unregister:
	devlink_unregister(devlink);
err_devlink_free:
	devlink_free(devlink);

	return err;
}

/* Initialize per network namespace state */
static int __net_init nsim_devlink_netns_init(struct net *net)
{
	bool *reg_devlink = net_generic(net, nsim_devlink_id);

	*reg_devlink = true;

	return 0;
}

static struct pernet_operations nsim_devlink_net_ops = {
	.init = nsim_devlink_netns_init,
	.id   = &nsim_devlink_id,
	.size = sizeof(bool),
};

void nsim_devlink_exit(void)
{
	unregister_pernet_subsys(&nsim_devlink_net_ops);
	nsim_fib_exit();
}

int nsim_devlink_init(void)
{
	int err;

	err = nsim_fib_init();
	if (err)
		goto err_out;

	err = register_pernet_subsys(&nsim_devlink_net_ops);
	if (err)
		nsim_fib_exit();

err_out:
	return err;
}
Loading