diff options
Diffstat (limited to 'drivers/net/netdevsim/dev.c')
-rw-r--r-- | drivers/net/netdevsim/dev.c | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c new file mode 100644 index 000000000000..b509b941d5ca --- /dev/null +++ b/drivers/net/netdevsim/dev.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2018 Cumulus Networks. All rights reserved. + * Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com> + * Copyright (c) 2019 Mellanox Technologies. All rights reserved. + * + * 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/debugfs.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/random.h> +#include <linux/rtnetlink.h> +#include <net/devlink.h> + +#include "netdevsim.h" + +static struct dentry *nsim_dev_ddir; + +static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev) +{ + char dev_ddir_name[16]; + + sprintf(dev_ddir_name, DRV_NAME "%u", nsim_dev->nsim_bus_dev->dev.id); + nsim_dev->ddir = debugfs_create_dir(dev_ddir_name, nsim_dev_ddir); + if (IS_ERR_OR_NULL(nsim_dev->ddir)) + return PTR_ERR_OR_ZERO(nsim_dev->ddir) ?: -EINVAL; + nsim_dev->ports_ddir = debugfs_create_dir("ports", nsim_dev->ddir); + if (IS_ERR_OR_NULL(nsim_dev->ports_ddir)) + return PTR_ERR_OR_ZERO(nsim_dev->ports_ddir) ?: -EINVAL; + return 0; +} + +static void nsim_dev_debugfs_exit(struct nsim_dev *nsim_dev) +{ + debugfs_remove_recursive(nsim_dev->ports_ddir); + debugfs_remove_recursive(nsim_dev->ddir); +} + +static int nsim_dev_port_debugfs_init(struct nsim_dev *nsim_dev, + struct nsim_dev_port *nsim_dev_port) +{ + char port_ddir_name[16]; + char dev_link_name[32]; + + sprintf(port_ddir_name, "%u", nsim_dev_port->port_index); + nsim_dev_port->ddir = debugfs_create_dir(port_ddir_name, + nsim_dev->ports_ddir); + if (IS_ERR_OR_NULL(nsim_dev_port->ddir)) + return -ENOMEM; + + sprintf(dev_link_name, "../../../" DRV_NAME "%u", + nsim_dev->nsim_bus_dev->dev.id); + debugfs_create_symlink("dev", nsim_dev_port->ddir, dev_link_name); + + return 0; +} + +static void nsim_dev_port_debugfs_exit(struct nsim_dev_port *nsim_dev_port) +{ + debugfs_remove_recursive(nsim_dev_port->ddir); +} + +static u64 nsim_dev_ipv4_fib_resource_occ_get(void *priv) +{ + struct nsim_dev *nsim_dev = priv; + + return nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV4_FIB, false); +} + +static u64 nsim_dev_ipv4_fib_rules_res_occ_get(void *priv) +{ + struct nsim_dev *nsim_dev = priv; + + return nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV4_FIB_RULES, false); +} + +static u64 nsim_dev_ipv6_fib_resource_occ_get(void *priv) +{ + struct nsim_dev *nsim_dev = priv; + + return nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV6_FIB, false); +} + +static u64 nsim_dev_ipv6_fib_rules_res_occ_get(void *priv) +{ + struct nsim_dev *nsim_dev = priv; + + return nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV6_FIB_RULES, false); +} + +static int nsim_dev_resources_register(struct devlink *devlink) +{ + struct nsim_dev *nsim_dev = devlink_priv(devlink); + struct devlink_resource_size_params params = { + .size_max = (u64)-1, + .size_granularity = 1, + .unit = DEVLINK_RESOURCE_UNIT_ENTRY + }; + int err; + u64 n; + + /* Resources for IPv4 */ + err = devlink_resource_register(devlink, "IPv4", (u64)-1, + NSIM_RESOURCE_IPV4, + DEVLINK_RESOURCE_ID_PARENT_TOP, + ¶ms); + if (err) { + pr_err("Failed to register IPv4 top resource\n"); + goto out; + } + + n = nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV4_FIB, true); + err = devlink_resource_register(devlink, "fib", n, + NSIM_RESOURCE_IPV4_FIB, + NSIM_RESOURCE_IPV4, ¶ms); + if (err) { + pr_err("Failed to register IPv4 FIB resource\n"); + return err; + } + + n = nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV4_FIB_RULES, true); + err = devlink_resource_register(devlink, "fib-rules", n, + NSIM_RESOURCE_IPV4_FIB_RULES, + NSIM_RESOURCE_IPV4, ¶ms); + 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, + ¶ms); + if (err) { + pr_err("Failed to register IPv6 top resource\n"); + goto out; + } + + n = nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV6_FIB, true); + err = devlink_resource_register(devlink, "fib", n, + NSIM_RESOURCE_IPV6_FIB, + NSIM_RESOURCE_IPV6, ¶ms); + if (err) { + pr_err("Failed to register IPv6 FIB resource\n"); + return err; + } + + n = nsim_fib_get_val(nsim_dev->fib_data, + NSIM_RESOURCE_IPV6_FIB_RULES, true); + err = devlink_resource_register(devlink, "fib-rules", n, + NSIM_RESOURCE_IPV6_FIB_RULES, + NSIM_RESOURCE_IPV6, ¶ms); + 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_dev_ipv4_fib_resource_occ_get, + nsim_dev); + devlink_resource_occ_get_register(devlink, + NSIM_RESOURCE_IPV4_FIB_RULES, + nsim_dev_ipv4_fib_rules_res_occ_get, + nsim_dev); + devlink_resource_occ_get_register(devlink, + NSIM_RESOURCE_IPV6_FIB, + nsim_dev_ipv6_fib_resource_occ_get, + nsim_dev); + devlink_resource_occ_get_register(devlink, + NSIM_RESOURCE_IPV6_FIB_RULES, + nsim_dev_ipv6_fib_rules_res_occ_get, + nsim_dev); +out: + return err; +} + +static int nsim_dev_reload(struct devlink *devlink, + struct netlink_ext_ack *extack) +{ + struct nsim_dev *nsim_dev = devlink_priv(devlink); + 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) { + int err; + u64 val; + + err = devlink_resource_size_get(devlink, res_ids[i], &val); + if (!err) { + err = nsim_fib_set_max(nsim_dev->fib_data, + res_ids[i], val, extack); + if (err) + return err; + } + } + + return 0; +} + +static const struct devlink_ops nsim_dev_devlink_ops = { + .reload = nsim_dev_reload, +}; + +static struct nsim_dev * +nsim_dev_create(struct nsim_bus_dev *nsim_bus_dev, unsigned int port_count) +{ + struct nsim_dev *nsim_dev; + struct devlink *devlink; + int err; + + devlink = devlink_alloc(&nsim_dev_devlink_ops, sizeof(*nsim_dev)); + if (!devlink) + return ERR_PTR(-ENOMEM); + nsim_dev = devlink_priv(devlink); + nsim_dev->nsim_bus_dev = nsim_bus_dev; + nsim_dev->switch_id.id_len = sizeof(nsim_dev->switch_id.id); + get_random_bytes(nsim_dev->switch_id.id, nsim_dev->switch_id.id_len); + INIT_LIST_HEAD(&nsim_dev->port_list); + mutex_init(&nsim_dev->port_list_lock); + + nsim_dev->fib_data = nsim_fib_create(); + if (IS_ERR(nsim_dev->fib_data)) { + err = PTR_ERR(nsim_dev->fib_data); + goto err_devlink_free; + } + + err = nsim_dev_resources_register(devlink); + if (err) + goto err_fib_destroy; + + err = devlink_register(devlink, &nsim_bus_dev->dev); + if (err) + goto err_resources_unregister; + + err = nsim_dev_debugfs_init(nsim_dev); + if (err) + goto err_dl_unregister; + + err = nsim_bpf_dev_init(nsim_dev); + if (err) + goto err_debugfs_exit; + + return nsim_dev; + +err_debugfs_exit: + nsim_dev_debugfs_exit(nsim_dev); +err_dl_unregister: + devlink_unregister(devlink); +err_resources_unregister: + devlink_resources_unregister(devlink, NULL); +err_fib_destroy: + nsim_fib_destroy(nsim_dev->fib_data); +err_devlink_free: + devlink_free(devlink); + return ERR_PTR(err); +} + +static void nsim_dev_destroy(struct nsim_dev *nsim_dev) +{ + struct devlink *devlink = priv_to_devlink(nsim_dev); + + nsim_bpf_dev_exit(nsim_dev); + nsim_dev_debugfs_exit(nsim_dev); + devlink_unregister(devlink); + devlink_resources_unregister(devlink, NULL); + nsim_fib_destroy(nsim_dev->fib_data); + mutex_destroy(&nsim_dev->port_list_lock); + devlink_free(devlink); +} + +static int __nsim_dev_port_add(struct nsim_dev *nsim_dev, + unsigned int port_index) +{ + struct nsim_dev_port *nsim_dev_port; + struct devlink_port *devlink_port; + int err; + + nsim_dev_port = kzalloc(sizeof(*nsim_dev_port), GFP_KERNEL); + if (!nsim_dev_port) + return -ENOMEM; + nsim_dev_port->port_index = port_index; + + devlink_port = &nsim_dev_port->devlink_port; + devlink_port_attrs_set(devlink_port, DEVLINK_PORT_FLAVOUR_PHYSICAL, + port_index + 1, 0, 0, + nsim_dev->switch_id.id, + nsim_dev->switch_id.id_len); + err = devlink_port_register(priv_to_devlink(nsim_dev), devlink_port, + port_index); + if (err) + goto err_port_free; + + err = nsim_dev_port_debugfs_init(nsim_dev, nsim_dev_port); + if (err) + goto err_dl_port_unregister; + + nsim_dev_port->ns = nsim_create(nsim_dev, nsim_dev_port); + if (IS_ERR(nsim_dev_port->ns)) { + err = PTR_ERR(nsim_dev_port->ns); + goto err_port_debugfs_exit; + } + + devlink_port_type_eth_set(devlink_port, nsim_dev_port->ns->netdev); + list_add(&nsim_dev_port->list, &nsim_dev->port_list); + + return 0; + +err_port_debugfs_exit: + nsim_dev_port_debugfs_exit(nsim_dev_port); +err_dl_port_unregister: + devlink_port_unregister(devlink_port); +err_port_free: + kfree(nsim_dev_port); + return err; +} + +static void __nsim_dev_port_del(struct nsim_dev_port *nsim_dev_port) +{ + struct devlink_port *devlink_port = &nsim_dev_port->devlink_port; + + list_del(&nsim_dev_port->list); + devlink_port_type_clear(devlink_port); + nsim_destroy(nsim_dev_port->ns); + nsim_dev_port_debugfs_exit(nsim_dev_port); + devlink_port_unregister(devlink_port); + kfree(nsim_dev_port); +} + +static void nsim_dev_port_del_all(struct nsim_dev *nsim_dev) +{ + struct nsim_dev_port *nsim_dev_port, *tmp; + + list_for_each_entry_safe(nsim_dev_port, tmp, + &nsim_dev->port_list, list) + __nsim_dev_port_del(nsim_dev_port); +} + +int nsim_dev_probe(struct nsim_bus_dev *nsim_bus_dev) +{ + struct nsim_dev *nsim_dev; + int i; + int err; + + nsim_dev = nsim_dev_create(nsim_bus_dev, nsim_bus_dev->port_count); + if (IS_ERR(nsim_dev)) + return PTR_ERR(nsim_dev); + dev_set_drvdata(&nsim_bus_dev->dev, nsim_dev); + + for (i = 0; i < nsim_bus_dev->port_count; i++) { + err = __nsim_dev_port_add(nsim_dev, i); + if (err) + goto err_port_del_all; + } + return 0; + +err_port_del_all: + nsim_dev_port_del_all(nsim_dev); + nsim_dev_destroy(nsim_dev); + return err; +} + +void nsim_dev_remove(struct nsim_bus_dev *nsim_bus_dev) +{ + struct nsim_dev *nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev); + + nsim_dev_port_del_all(nsim_dev); + nsim_dev_destroy(nsim_dev); +} + +static struct nsim_dev_port * +__nsim_dev_port_lookup(struct nsim_dev *nsim_dev, unsigned int port_index) +{ + struct nsim_dev_port *nsim_dev_port; + + list_for_each_entry(nsim_dev_port, &nsim_dev->port_list, list) + if (nsim_dev_port->port_index == port_index) + return nsim_dev_port; + return NULL; +} + +int nsim_dev_port_add(struct nsim_bus_dev *nsim_bus_dev, + unsigned int port_index) +{ + struct nsim_dev *nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev); + int err; + + mutex_lock(&nsim_dev->port_list_lock); + if (__nsim_dev_port_lookup(nsim_dev, port_index)) + err = -EEXIST; + else + err = __nsim_dev_port_add(nsim_dev, port_index); + mutex_unlock(&nsim_dev->port_list_lock); + return err; +} + +int nsim_dev_port_del(struct nsim_bus_dev *nsim_bus_dev, + unsigned int port_index) +{ + struct nsim_dev *nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev); + struct nsim_dev_port *nsim_dev_port; + int err = 0; + + mutex_lock(&nsim_dev->port_list_lock); + nsim_dev_port = __nsim_dev_port_lookup(nsim_dev, port_index); + if (!nsim_dev_port) + err = -ENOENT; + else + __nsim_dev_port_del(nsim_dev_port); + mutex_unlock(&nsim_dev->port_list_lock); + return err; +} + +int nsim_dev_init(void) +{ + nsim_dev_ddir = debugfs_create_dir(DRV_NAME, NULL); + if (IS_ERR_OR_NULL(nsim_dev_ddir)) + return -ENOMEM; + return 0; +} + +void nsim_dev_exit(void) +{ + debugfs_remove_recursive(nsim_dev_ddir); +} |