diff options
Diffstat (limited to 'drivers/net/netdevsim')
-rw-r--r-- | drivers/net/netdevsim/Makefile | 11 | ||||
-rw-r--r-- | drivers/net/netdevsim/bpf.c | 646 | ||||
-rw-r--r-- | drivers/net/netdevsim/netdev.c | 504 | ||||
-rw-r--r-- | drivers/net/netdevsim/netdevsim.h | 109 |
4 files changed, 1270 insertions, 0 deletions
diff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile new file mode 100644 index 000000000000..09388c06171d --- /dev/null +++ b/drivers/net/netdevsim/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_NETDEVSIM) += netdevsim.o + +netdevsim-objs := \ + netdev.o \ + +ifeq ($(CONFIG_BPF_SYSCALL),y) +netdevsim-objs += \ + bpf.o +endif diff --git a/drivers/net/netdevsim/bpf.c b/drivers/net/netdevsim/bpf.c new file mode 100644 index 000000000000..8166f121bbcc --- /dev/null +++ b/drivers/net/netdevsim/bpf.c @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * 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/bpf.h> +#include <linux/bpf_verifier.h> +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/rtnetlink.h> +#include <net/pkt_cls.h> + +#include "netdevsim.h" + +#define pr_vlog(env, fmt, ...) \ + bpf_verifier_log_write(env, "[netdevsim] " fmt, ##__VA_ARGS__) + +struct nsim_bpf_bound_prog { + struct netdevsim *ns; + struct bpf_prog *prog; + struct dentry *ddir; + const char *state; + bool is_loaded; + struct list_head l; +}; + +#define NSIM_BPF_MAX_KEYS 2 + +struct nsim_bpf_bound_map { + struct netdevsim *ns; + struct bpf_offloaded_map *map; + struct mutex mutex; + struct nsim_map_entry { + void *key; + void *value; + } entry[NSIM_BPF_MAX_KEYS]; + struct list_head l; +}; + +static int nsim_debugfs_bpf_string_read(struct seq_file *file, void *data) +{ + const char **str = file->private; + + if (*str) + seq_printf(file, "%s\n", *str); + + return 0; +} + +static int nsim_debugfs_bpf_string_open(struct inode *inode, struct file *f) +{ + return single_open(f, nsim_debugfs_bpf_string_read, inode->i_private); +} + +static const struct file_operations nsim_bpf_string_fops = { + .owner = THIS_MODULE, + .open = nsim_debugfs_bpf_string_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek +}; + +static int +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->ns->bpf_bind_verifier_delay && !insn_idx) + msleep(state->ns->bpf_bind_verifier_delay); + + if (insn_idx == env->prog->len - 1) + pr_vlog(env, "Hello from netdevsim!\n"); + + return 0; +} + +static const struct bpf_prog_offload_ops nsim_bpf_analyzer_ops = { + .insn_hook = nsim_bpf_verify_insn, +}; + +static bool nsim_xdp_offload_active(struct netdevsim *ns) +{ + return ns->xdp_prog_mode == XDP_ATTACHED_HW; +} + +static void nsim_prog_set_loaded(struct bpf_prog *prog, bool loaded) +{ + struct nsim_bpf_bound_prog *state; + + if (!prog || !prog->aux->offload) + return; + + state = prog->aux->offload->dev_priv; + state->is_loaded = loaded; +} + +static int +nsim_bpf_offload(struct netdevsim *ns, struct bpf_prog *prog, bool oldprog) +{ + nsim_prog_set_loaded(ns->bpf_offloaded, false); + + WARN(!!ns->bpf_offloaded != oldprog, + "bad offload state, expected offload %sto be active", + oldprog ? "" : "not "); + ns->bpf_offloaded = prog; + ns->bpf_offloaded_id = prog ? prog->aux->id : 0; + nsim_prog_set_loaded(prog, true); + + return 0; +} + +int nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, + void *type_data, void *cb_priv) +{ + struct tc_cls_bpf_offload *cls_bpf = type_data; + struct bpf_prog *prog = cls_bpf->prog; + struct netdevsim *ns = cb_priv; + struct bpf_prog *oldprog; + + if (type != TC_SETUP_CLSBPF) { + NSIM_EA(cls_bpf->common.extack, + "only offload of BPF classifiers supported"); + return -EOPNOTSUPP; + } + + if (!tc_can_offload_extack(ns->netdev, cls_bpf->common.extack)) + return -EOPNOTSUPP; + + if (cls_bpf->common.protocol != htons(ETH_P_ALL)) { + NSIM_EA(cls_bpf->common.extack, + "only ETH_P_ALL supported as filter protocol"); + return -EOPNOTSUPP; + } + + if (cls_bpf->common.chain_index) + return -EOPNOTSUPP; + + if (!ns->bpf_tc_accept) { + NSIM_EA(cls_bpf->common.extack, + "netdevsim configured to reject BPF TC offload"); + return -EOPNOTSUPP; + } + /* Note: progs without skip_sw will probably not be dev bound */ + if (prog && !prog->aux->offload && !ns->bpf_tc_non_bound_accept) { + NSIM_EA(cls_bpf->common.extack, + "netdevsim configured to reject unbound programs"); + return -EOPNOTSUPP; + } + + if (cls_bpf->command != TC_CLSBPF_OFFLOAD) + return -EOPNOTSUPP; + + oldprog = cls_bpf->oldprog; + + /* Don't remove if oldprog doesn't match driver's state */ + if (ns->bpf_offloaded != oldprog) { + oldprog = NULL; + if (!cls_bpf->prog) + return 0; + if (ns->bpf_offloaded) { + NSIM_EA(cls_bpf->common.extack, + "driver and netdev offload states mismatch"); + return -EBUSY; + } + } + + return nsim_bpf_offload(ns, cls_bpf->prog, oldprog); +} + +int nsim_bpf_disable_tc(struct netdevsim *ns) +{ + if (ns->bpf_offloaded && !nsim_xdp_offload_active(ns)) + return -EBUSY; + return 0; +} + +static int nsim_xdp_offload_prog(struct netdevsim *ns, struct netdev_bpf *bpf) +{ + if (!nsim_xdp_offload_active(ns) && !bpf->prog) + return 0; + if (!nsim_xdp_offload_active(ns) && bpf->prog && ns->bpf_offloaded) { + NSIM_EA(bpf->extack, "TC program is already loaded"); + return -EBUSY; + } + + return nsim_bpf_offload(ns, bpf->prog, nsim_xdp_offload_active(ns)); +} + +static int nsim_xdp_set_prog(struct netdevsim *ns, struct netdev_bpf *bpf) +{ + int err; + + if (ns->xdp_prog && (bpf->flags ^ ns->xdp_flags) & XDP_FLAGS_MODES) { + NSIM_EA(bpf->extack, "program loaded with different flags"); + return -EBUSY; + } + + if (bpf->command == XDP_SETUP_PROG && !ns->bpf_xdpdrv_accept) { + NSIM_EA(bpf->extack, "driver XDP disabled in DebugFS"); + return -EOPNOTSUPP; + } + if (bpf->command == XDP_SETUP_PROG_HW && !ns->bpf_xdpoffload_accept) { + NSIM_EA(bpf->extack, "XDP offload disabled in DebugFS"); + return -EOPNOTSUPP; + } + + if (bpf->command == XDP_SETUP_PROG_HW) { + err = nsim_xdp_offload_prog(ns, bpf); + if (err) + return err; + } + + if (ns->xdp_prog) + bpf_prog_put(ns->xdp_prog); + + ns->xdp_prog = bpf->prog; + ns->xdp_flags = bpf->flags; + + if (!bpf->prog) + ns->xdp_prog_mode = XDP_ATTACHED_NONE; + else if (bpf->command == XDP_SETUP_PROG) + ns->xdp_prog_mode = XDP_ATTACHED_DRV; + else + ns->xdp_prog_mode = XDP_ATTACHED_HW; + + return 0; +} + +static int nsim_bpf_create_prog(struct netdevsim *ns, struct bpf_prog *prog) +{ + struct nsim_bpf_bound_prog *state; + char name[16]; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->ns = ns; + state->prog = prog; + state->state = "verify"; + + /* Program id is not populated yet when we create the state. */ + sprintf(name, "%u", ns->prog_id_gen++); + state->ddir = debugfs_create_dir(name, ns->ddir_bpf_bound_progs); + if (IS_ERR_OR_NULL(state->ddir)) { + kfree(state); + return -ENOMEM; + } + + debugfs_create_u32("id", 0400, state->ddir, &prog->aux->id); + debugfs_create_file("state", 0400, state->ddir, + &state->state, &nsim_bpf_string_fops); + debugfs_create_bool("loaded", 0400, state->ddir, &state->is_loaded); + + list_add_tail(&state->l, &ns->bpf_bound_progs); + + prog->aux->offload->dev_priv = state; + + return 0; +} + +static void nsim_bpf_destroy_prog(struct bpf_prog *prog) +{ + struct nsim_bpf_bound_prog *state; + + state = prog->aux->offload->dev_priv; + WARN(state->is_loaded, + "offload state destroyed while program still bound"); + debugfs_remove_recursive(state->ddir); + list_del(&state->l); + kfree(state); +} + +static int nsim_setup_prog_checks(struct netdevsim *ns, struct netdev_bpf *bpf) +{ + if (bpf->prog && bpf->prog->aux->offload) { + NSIM_EA(bpf->extack, "attempt to load offloaded prog to drv"); + return -EINVAL; + } + if (ns->netdev->mtu > NSIM_XDP_MAX_MTU) { + NSIM_EA(bpf->extack, "MTU too large w/ XDP enabled"); + return -EINVAL; + } + if (nsim_xdp_offload_active(ns)) { + NSIM_EA(bpf->extack, "xdp offload active, can't load drv prog"); + return -EBUSY; + } + return 0; +} + +static int +nsim_setup_prog_hw_checks(struct netdevsim *ns, struct netdev_bpf *bpf) +{ + struct nsim_bpf_bound_prog *state; + + if (!bpf->prog) + return 0; + + if (!bpf->prog->aux->offload) { + NSIM_EA(bpf->extack, "xdpoffload of non-bound program"); + return -EINVAL; + } + if (bpf->prog->aux->offload->netdev != ns->netdev) { + NSIM_EA(bpf->extack, "program bound to different dev"); + return -EINVAL; + } + + state = bpf->prog->aux->offload->dev_priv; + if (WARN_ON(strcmp(state->state, "xlated"))) { + NSIM_EA(bpf->extack, "offloading program in bad state"); + return -EINVAL; + } + return 0; +} + +static bool +nsim_map_key_match(struct bpf_map *map, struct nsim_map_entry *e, void *key) +{ + return e->key && !memcmp(key, e->key, map->key_size); +} + +static int nsim_map_key_find(struct bpf_offloaded_map *offmap, void *key) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(nmap->entry); i++) + if (nsim_map_key_match(&offmap->map, &nmap->entry[i], key)) + return i; + + return -ENOENT; +} + +static int +nsim_map_alloc_elem(struct bpf_offloaded_map *offmap, unsigned int idx) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + + nmap->entry[idx].key = kmalloc(offmap->map.key_size, GFP_USER); + if (!nmap->entry[idx].key) + return -ENOMEM; + nmap->entry[idx].value = kmalloc(offmap->map.value_size, GFP_USER); + if (!nmap->entry[idx].value) { + kfree(nmap->entry[idx].key); + nmap->entry[idx].key = NULL; + return -ENOMEM; + } + + return 0; +} + +static int +nsim_map_get_next_key(struct bpf_offloaded_map *offmap, + void *key, void *next_key) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + int idx = -ENOENT; + + mutex_lock(&nmap->mutex); + + if (key) + idx = nsim_map_key_find(offmap, key); + if (idx == -ENOENT) + idx = 0; + else + idx++; + + for (; idx < ARRAY_SIZE(nmap->entry); idx++) { + if (nmap->entry[idx].key) { + memcpy(next_key, nmap->entry[idx].key, + offmap->map.key_size); + break; + } + } + + mutex_unlock(&nmap->mutex); + + if (idx == ARRAY_SIZE(nmap->entry)) + return -ENOENT; + return 0; +} + +static int +nsim_map_lookup_elem(struct bpf_offloaded_map *offmap, void *key, void *value) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + int idx; + + mutex_lock(&nmap->mutex); + + idx = nsim_map_key_find(offmap, key); + if (idx >= 0) + memcpy(value, nmap->entry[idx].value, offmap->map.value_size); + + mutex_unlock(&nmap->mutex); + + return idx < 0 ? idx : 0; +} + +static int +nsim_map_update_elem(struct bpf_offloaded_map *offmap, + void *key, void *value, u64 flags) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + int idx, err = 0; + + mutex_lock(&nmap->mutex); + + idx = nsim_map_key_find(offmap, key); + if (idx < 0 && flags == BPF_EXIST) { + err = idx; + goto exit_unlock; + } + if (idx >= 0 && flags == BPF_NOEXIST) { + err = -EEXIST; + goto exit_unlock; + } + + if (idx < 0) { + for (idx = 0; idx < ARRAY_SIZE(nmap->entry); idx++) + if (!nmap->entry[idx].key) + break; + if (idx == ARRAY_SIZE(nmap->entry)) { + err = -E2BIG; + goto exit_unlock; + } + + err = nsim_map_alloc_elem(offmap, idx); + if (err) + goto exit_unlock; + } + + memcpy(nmap->entry[idx].key, key, offmap->map.key_size); + memcpy(nmap->entry[idx].value, value, offmap->map.value_size); +exit_unlock: + mutex_unlock(&nmap->mutex); + + return err; +} + +static int nsim_map_delete_elem(struct bpf_offloaded_map *offmap, void *key) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + int idx; + + if (offmap->map.map_type == BPF_MAP_TYPE_ARRAY) + return -EINVAL; + + mutex_lock(&nmap->mutex); + + idx = nsim_map_key_find(offmap, key); + if (idx >= 0) { + kfree(nmap->entry[idx].key); + kfree(nmap->entry[idx].value); + memset(&nmap->entry[idx], 0, sizeof(nmap->entry[idx])); + } + + mutex_unlock(&nmap->mutex); + + return idx < 0 ? idx : 0; +} + +static const struct bpf_map_dev_ops nsim_bpf_map_ops = { + .map_get_next_key = nsim_map_get_next_key, + .map_lookup_elem = nsim_map_lookup_elem, + .map_update_elem = nsim_map_update_elem, + .map_delete_elem = nsim_map_delete_elem, +}; + +static int +nsim_bpf_map_alloc(struct netdevsim *ns, struct bpf_offloaded_map *offmap) +{ + struct nsim_bpf_bound_map *nmap; + unsigned int i; + int err; + + if (WARN_ON(offmap->map.map_type != BPF_MAP_TYPE_ARRAY && + offmap->map.map_type != BPF_MAP_TYPE_HASH)) + return -EINVAL; + if (offmap->map.max_entries > NSIM_BPF_MAX_KEYS) + return -ENOMEM; + if (offmap->map.map_flags) + return -EINVAL; + + nmap = kzalloc(sizeof(*nmap), GFP_USER); + if (!nmap) + return -ENOMEM; + + offmap->dev_priv = nmap; + nmap->ns = ns; + nmap->map = offmap; + mutex_init(&nmap->mutex); + + if (offmap->map.map_type == BPF_MAP_TYPE_ARRAY) { + for (i = 0; i < ARRAY_SIZE(nmap->entry); i++) { + u32 *key; + + err = nsim_map_alloc_elem(offmap, i); + if (err) + goto err_free; + key = nmap->entry[i].key; + *key = i; + } + } + + offmap->dev_ops = &nsim_bpf_map_ops; + list_add_tail(&nmap->l, &ns->bpf_bound_maps); + + return 0; + +err_free: + while (--i) { + kfree(nmap->entry[i].key); + kfree(nmap->entry[i].value); + } + kfree(nmap); + return err; +} + +static void nsim_bpf_map_free(struct bpf_offloaded_map *offmap) +{ + struct nsim_bpf_bound_map *nmap = offmap->dev_priv; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(nmap->entry); i++) { + kfree(nmap->entry[i].key); + kfree(nmap->entry[i].value); + } + list_del_init(&nmap->l); + mutex_destroy(&nmap->mutex); + kfree(nmap); +} + +int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf) +{ + struct netdevsim *ns = netdev_priv(dev); + struct nsim_bpf_bound_prog *state; + int err; + + ASSERT_RTNL(); + + switch (bpf->command) { + case BPF_OFFLOAD_VERIFIER_PREP: + if (!ns->bpf_bind_accept) + return -EOPNOTSUPP; + + err = nsim_bpf_create_prog(ns, bpf->verifier.prog); + if (err) + return err; + + bpf->verifier.ops = &nsim_bpf_analyzer_ops; + return 0; + case BPF_OFFLOAD_TRANSLATE: + state = bpf->offload.prog->aux->offload->dev_priv; + + state->state = "xlated"; + return 0; + case BPF_OFFLOAD_DESTROY: + nsim_bpf_destroy_prog(bpf->offload.prog); + return 0; + case XDP_QUERY_PROG: + bpf->prog_attached = ns->xdp_prog_mode; + bpf->prog_id = ns->xdp_prog ? ns->xdp_prog->aux->id : 0; + bpf->prog_flags = ns->xdp_prog ? ns->xdp_flags : 0; + return 0; + case XDP_SETUP_PROG: + err = nsim_setup_prog_checks(ns, bpf); + if (err) + return err; + + return nsim_xdp_set_prog(ns, bpf); + case XDP_SETUP_PROG_HW: + err = nsim_setup_prog_hw_checks(ns, bpf); + if (err) + return err; + + return nsim_xdp_set_prog(ns, bpf); + case BPF_OFFLOAD_MAP_ALLOC: + if (!ns->bpf_map_accept) + return -EOPNOTSUPP; + + return nsim_bpf_map_alloc(ns, bpf->offmap); + case BPF_OFFLOAD_MAP_FREE: + nsim_bpf_map_free(bpf->offmap); + return 0; + default: + return -EINVAL; + } +} + +int nsim_bpf_init(struct netdevsim *ns) +{ + INIT_LIST_HEAD(&ns->bpf_bound_progs); + INIT_LIST_HEAD(&ns->bpf_bound_maps); + + debugfs_create_u32("bpf_offloaded_id", 0400, ns->ddir, + &ns->bpf_offloaded_id); + + ns->bpf_bind_accept = true; + debugfs_create_bool("bpf_bind_accept", 0600, ns->ddir, + &ns->bpf_bind_accept); + debugfs_create_u32("bpf_bind_verifier_delay", 0600, ns->ddir, + &ns->bpf_bind_verifier_delay); + ns->ddir_bpf_bound_progs = + debugfs_create_dir("bpf_bound_progs", ns->ddir); + if (IS_ERR_OR_NULL(ns->ddir_bpf_bound_progs)) + return -ENOMEM; + + ns->bpf_tc_accept = true; + debugfs_create_bool("bpf_tc_accept", 0600, ns->ddir, + &ns->bpf_tc_accept); + debugfs_create_bool("bpf_tc_non_bound_accept", 0600, ns->ddir, + &ns->bpf_tc_non_bound_accept); + ns->bpf_xdpdrv_accept = true; + debugfs_create_bool("bpf_xdpdrv_accept", 0600, ns->ddir, + &ns->bpf_xdpdrv_accept); + ns->bpf_xdpoffload_accept = true; + debugfs_create_bool("bpf_xdpoffload_accept", 0600, ns->ddir, + &ns->bpf_xdpoffload_accept); + + ns->bpf_map_accept = true; + debugfs_create_bool("bpf_map_accept", 0600, ns->ddir, + &ns->bpf_map_accept); + + return 0; +} + +void nsim_bpf_uninit(struct netdevsim *ns) +{ + WARN_ON(!list_empty(&ns->bpf_bound_progs)); + WARN_ON(!list_empty(&ns->bpf_bound_maps)); + WARN_ON(ns->xdp_prog); + WARN_ON(ns->bpf_offloaded); +} diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c new file mode 100644 index 000000000000..3fd567928f3d --- /dev/null +++ b/drivers/net/netdevsim/netdev.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * 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/etherdevice.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/slab.h> +#include <net/netlink.h> +#include <net/pkt_cls.h> +#include <net/rtnetlink.h> + +#include "netdevsim.h" + +struct nsim_vf_config { + int link_state; + u16 min_tx_rate; + u16 max_tx_rate; + u16 vlan; + __be16 vlan_proto; + u16 qos; + u8 vf_mac[ETH_ALEN]; + bool spoofchk_enabled; + bool trusted; + bool rss_query_enabled; +}; + +static u32 nsim_dev_id; + +static int nsim_num_vf(struct device *dev) +{ + struct netdevsim *ns = to_nsim(dev); + + return ns->num_vfs; +} + +static struct bus_type nsim_bus = { + .name = DRV_NAME, + .dev_name = DRV_NAME, + .num_vf = nsim_num_vf, +}; + +static int nsim_vfs_enable(struct netdevsim *ns, unsigned int num_vfs) +{ + ns->vfconfigs = kcalloc(num_vfs, sizeof(struct nsim_vf_config), + GFP_KERNEL); + if (!ns->vfconfigs) + return -ENOMEM; + ns->num_vfs = num_vfs; + + return 0; +} + +static void nsim_vfs_disable(struct netdevsim *ns) +{ + kfree(ns->vfconfigs); + ns->vfconfigs = NULL; + ns->num_vfs = 0; +} + +static ssize_t +nsim_numvfs_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct netdevsim *ns = to_nsim(dev); + unsigned int num_vfs; + int ret; + + ret = kstrtouint(buf, 0, &num_vfs); + if (ret) + return ret; + + rtnl_lock(); + if (ns->num_vfs == num_vfs) + goto exit_good; + if (ns->num_vfs && num_vfs) { + ret = -EBUSY; + goto exit_unlock; + } + + if (num_vfs) { + ret = nsim_vfs_enable(ns, num_vfs); + if (ret) + goto exit_unlock; + } else { + nsim_vfs_disable(ns); + } +exit_good: + ret = count; +exit_unlock: + rtnl_unlock(); + + return ret; +} + +static ssize_t +nsim_numvfs_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct netdevsim *ns = to_nsim(dev); + + return sprintf(buf, "%u\n", ns->num_vfs); +} + +static struct device_attribute nsim_numvfs_attr = + __ATTR(sriov_numvfs, 0664, nsim_numvfs_show, nsim_numvfs_store); + +static struct attribute *nsim_dev_attrs[] = { + &nsim_numvfs_attr.attr, + NULL, +}; + +static const struct attribute_group nsim_dev_attr_group = { + .attrs = nsim_dev_attrs, +}; + +static const struct attribute_group *nsim_dev_attr_groups[] = { + &nsim_dev_attr_group, + NULL, +}; + +static void nsim_dev_release(struct device *dev) +{ + struct netdevsim *ns = to_nsim(dev); + + nsim_vfs_disable(ns); + free_netdev(ns->netdev); +} + +static struct device_type nsim_dev_type = { + .groups = nsim_dev_attr_groups, + .release = nsim_dev_release, +}; + +static int nsim_init(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + int err; + + ns->netdev = dev; + ns->ddir = debugfs_create_dir(netdev_name(dev), nsim_ddir); + if (IS_ERR_OR_NULL(ns->ddir)) + return -ENOMEM; + + err = nsim_bpf_init(ns); + if (err) + goto err_debugfs_destroy; + + ns->dev.id = nsim_dev_id++; + ns->dev.bus = &nsim_bus; + ns->dev.type = &nsim_dev_type; + err = device_register(&ns->dev); + if (err) + goto err_bpf_uninit; + + SET_NETDEV_DEV(dev, &ns->dev); + + return 0; + +err_bpf_uninit: + nsim_bpf_uninit(ns); +err_debugfs_destroy: + debugfs_remove_recursive(ns->ddir); + return err; +} + +static void nsim_uninit(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + + debugfs_remove_recursive(ns->ddir); + nsim_bpf_uninit(ns); +} + +static void nsim_free(struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + + device_unregister(&ns->dev); + /* netdev and vf state will be freed out of device_release() */ +} + +static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct netdevsim *ns = netdev_priv(dev); + + u64_stats_update_begin(&ns->syncp); + ns->tx_packets++; + ns->tx_bytes += skb->len; + u64_stats_update_end(&ns->syncp); + + dev_kfree_skb(skb); + + return NETDEV_TX_OK; +} + +static void nsim_set_rx_mode(struct net_device *dev) +{ +} + +static int nsim_change_mtu(struct net_device *dev, int new_mtu) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (ns->xdp_prog_mode == XDP_ATTACHED_DRV && + new_mtu > NSIM_XDP_MAX_MTU) + return -EBUSY; + + dev->mtu = new_mtu; + + return 0; +} + +static void +nsim_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) +{ + struct netdevsim *ns = netdev_priv(dev); + unsigned int start; + + do { + start = u64_stats_fetch_begin(&ns->syncp); + stats->tx_bytes = ns->tx_bytes; + stats->tx_packets = ns->tx_packets; + } while (u64_stats_fetch_retry(&ns->syncp, start)); +} + +static int +nsim_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv) +{ + return nsim_bpf_setup_tc_block_cb(type, type_data, cb_priv); +} + +static int +nsim_setup_tc_block(struct net_device *dev, struct tc_block_offload *f) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (f->binder_type != TCF_BLOCK_BINDER_TYPE_CLSACT_INGRESS) + return -EOPNOTSUPP; + + switch (f->command) { + case TC_BLOCK_BIND: + return tcf_block_cb_register(f->block, nsim_setup_tc_block_cb, + ns, ns); + case TC_BLOCK_UNBIND: + tcf_block_cb_unregister(f->block, nsim_setup_tc_block_cb, ns); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int nsim_set_vf_mac(struct net_device *dev, int vf, u8 *mac) +{ + struct netdevsim *ns = netdev_priv(dev); + + /* Only refuse multicast addresses, zero address can mean unset/any. */ + if (vf >= ns->num_vfs || is_multicast_ether_addr(mac)) + return -EINVAL; + memcpy(ns->vfconfigs[vf].vf_mac, mac, ETH_ALEN); + + return 0; +} + +static int nsim_set_vf_vlan(struct net_device *dev, int vf, + u16 vlan, u8 qos, __be16 vlan_proto) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs || vlan > 4095 || qos > 7) + return -EINVAL; + + ns->vfconfigs[vf].vlan = vlan; + ns->vfconfigs[vf].qos = qos; + ns->vfconfigs[vf].vlan_proto = vlan_proto; + + return 0; +} + +static int nsim_set_vf_rate(struct net_device *dev, int vf, int min, int max) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs) + return -EINVAL; + + ns->vfconfigs[vf].min_tx_rate = min; + ns->vfconfigs[vf].max_tx_rate = max; + + return 0; +} + +static int nsim_set_vf_spoofchk(struct net_device *dev, int vf, bool val) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs) + return -EINVAL; + ns->vfconfigs[vf].spoofchk_enabled = val; + + return 0; +} + +static int nsim_set_vf_rss_query_en(struct net_device *dev, int vf, bool val) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs) + return -EINVAL; + ns->vfconfigs[vf].rss_query_enabled = val; + + return 0; +} + +static int nsim_set_vf_trust(struct net_device *dev, int vf, bool val) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs) + return -EINVAL; + ns->vfconfigs[vf].trusted = val; + + return 0; +} + +static int +nsim_get_vf_config(struct net_device *dev, int vf, struct ifla_vf_info *ivi) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs) + return -EINVAL; + + ivi->vf = vf; + ivi->linkstate = ns->vfconfigs[vf].link_state; + ivi->min_tx_rate = ns->vfconfigs[vf].min_tx_rate; + ivi->max_tx_rate = ns->vfconfigs[vf].max_tx_rate; + ivi->vlan = ns->vfconfigs[vf].vlan; + ivi->vlan_proto = ns->vfconfigs[vf].vlan_proto; + ivi->qos = ns->vfconfigs[vf].qos; + memcpy(&ivi->mac, ns->vfconfigs[vf].vf_mac, ETH_ALEN); + ivi->spoofchk = ns->vfconfigs[vf].spoofchk_enabled; + ivi->trusted = ns->vfconfigs[vf].trusted; + ivi->rss_query_en = ns->vfconfigs[vf].rss_query_enabled; + + return 0; +} + +static int nsim_set_vf_link_state(struct net_device *dev, int vf, int state) +{ + struct netdevsim *ns = netdev_priv(dev); + + if (vf >= ns->num_vfs) + return -EINVAL; + + switch (state) { + case IFLA_VF_LINK_STATE_AUTO: + case IFLA_VF_LINK_STATE_ENABLE: + case IFLA_VF_LINK_STATE_DISABLE: + break; + default: + return -EINVAL; + } + + ns->vfconfigs[vf].link_state = state; + + return 0; +} + +static int +nsim_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_BLOCK: + return nsim_setup_tc_block(dev, type_data); + default: + return -EOPNOTSUPP; + } +} + +static int +nsim_set_features(struct net_device *dev, netdev_features_t features) +{ + struct netdevsim *ns = netdev_priv(dev); + + if ((dev->features & NETIF_F_HW_TC) > (features & NETIF_F_HW_TC)) + return nsim_bpf_disable_tc(ns); + + return 0; +} + +static const struct net_device_ops nsim_netdev_ops = { + .ndo_init = nsim_init, + .ndo_uninit = nsim_uninit, + .ndo_start_xmit = nsim_start_xmit, + .ndo_set_rx_mode = nsim_set_rx_mode, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_change_mtu = nsim_change_mtu, + .ndo_get_stats64 = nsim_get_stats64, + .ndo_set_vf_mac = nsim_set_vf_mac, + .ndo_set_vf_vlan = nsim_set_vf_vlan, + .ndo_set_vf_rate = nsim_set_vf_rate, + .ndo_set_vf_spoofchk = nsim_set_vf_spoofchk, + .ndo_set_vf_trust = nsim_set_vf_trust, + .ndo_get_vf_config = nsim_get_vf_config, + .ndo_set_vf_link_state = nsim_set_vf_link_state, + .ndo_set_vf_rss_query_en = nsim_set_vf_rss_query_en, + .ndo_setup_tc = nsim_setup_tc, + .ndo_set_features = nsim_set_features, + .ndo_bpf = nsim_bpf, +}; + +static void nsim_setup(struct net_device *dev) +{ + ether_setup(dev); + eth_hw_addr_random(dev); + + dev->netdev_ops = &nsim_netdev_ops; + dev->priv_destructor = nsim_free; + + dev->tx_queue_len = 0; + dev->flags |= IFF_NOARP; + dev->flags &= ~IFF_MULTICAST; + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | + IFF_NO_QUEUE; + dev->features |= NETIF_F_HIGHDMA | + NETIF_F_SG | + NETIF_F_FRAGLIST | + NETIF_F_HW_CSUM | + NETIF_F_TSO; + dev->hw_features |= NETIF_F_HW_TC; + dev->max_mtu = ETH_MAX_MTU; +} + +static int nsim_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } + return 0; +} + +static struct rtnl_link_ops nsim_link_ops __read_mostly = { + .kind = DRV_NAME, + .priv_size = sizeof(struct netdevsim), + .setup = nsim_setup, + .validate = nsim_validate, +}; + +struct dentry *nsim_ddir; + +static int __init nsim_module_init(void) +{ + int err; + + nsim_ddir = debugfs_create_dir(DRV_NAME, NULL); + if (IS_ERR_OR_NULL(nsim_ddir)) + return -ENOMEM; + + err = bus_register(&nsim_bus); + if (err) + goto err_debugfs_destroy; + + err = rtnl_link_register(&nsim_link_ops); + if (err) + goto err_unreg_bus; + + return 0; + +err_unreg_bus: + bus_unregister(&nsim_bus); +err_debugfs_destroy: + debugfs_remove_recursive(nsim_ddir); + return err; +} + +static void __exit nsim_module_exit(void) +{ + rtnl_link_unregister(&nsim_link_ops); + bus_unregister(&nsim_bus); + debugfs_remove_recursive(nsim_ddir); +} + +module_init(nsim_module_init); +module_exit(nsim_module_exit); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_RTNL_LINK(DRV_NAME); diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h new file mode 100644 index 000000000000..ea081c10efb8 --- /dev/null +++ b/drivers/net/netdevsim/netdevsim.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * 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 <linux/kernel.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/u64_stats_sync.h> + +#define DRV_NAME "netdevsim" + +#define NSIM_XDP_MAX_MTU 4000 + +#define NSIM_EA(extack, msg) NL_SET_ERR_MSG_MOD((extack), msg) + +struct bpf_prog; +struct dentry; +struct nsim_vf_config; + +struct netdevsim { + struct net_device *netdev; + + u64 tx_packets; + u64 tx_bytes; + struct u64_stats_sync syncp; + + struct device dev; + + struct dentry *ddir; + + unsigned int num_vfs; + struct nsim_vf_config *vfconfigs; + + struct bpf_prog *bpf_offloaded; + u32 bpf_offloaded_id; + + u32 xdp_flags; + int xdp_prog_mode; + struct bpf_prog *xdp_prog; + + u32 prog_id_gen; + + bool bpf_bind_accept; + u32 bpf_bind_verifier_delay; + struct dentry *ddir_bpf_bound_progs; + struct list_head bpf_bound_progs; + + bool bpf_tc_accept; + bool bpf_tc_non_bound_accept; + bool bpf_xdpdrv_accept; + bool bpf_xdpoffload_accept; + + bool bpf_map_accept; + struct list_head bpf_bound_maps; +}; + +extern struct dentry *nsim_ddir; + +#ifdef CONFIG_BPF_SYSCALL +int nsim_bpf_init(struct netdevsim *ns); +void nsim_bpf_uninit(struct netdevsim *ns); +int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf); +int nsim_bpf_disable_tc(struct netdevsim *ns); +int nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, + void *type_data, void *cb_priv); +#else +static inline int nsim_bpf_init(struct netdevsim *ns) +{ + return 0; +} + +static inline void nsim_bpf_uninit(struct netdevsim *ns) +{ +} + +static inline int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf) +{ + return bpf->command == XDP_QUERY_PROG ? 0 : -EOPNOTSUPP; +} + +static inline int nsim_bpf_disable_tc(struct netdevsim *ns) +{ + return 0; +} + +static inline int +nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, void *type_data, + void *cb_priv) +{ + return -EOPNOTSUPP; +} +#endif + +static inline struct netdevsim *to_nsim(struct device *ptr) +{ + return container_of(ptr, struct netdevsim, dev); +} |