diff options
Diffstat (limited to 'drivers/net/netdevsim')
| -rw-r--r-- | drivers/net/netdevsim/Makefile | 11 | ||||
| -rw-r--r-- | drivers/net/netdevsim/bpf.c | 642 | ||||
| -rw-r--r-- | drivers/net/netdevsim/netdev.c | 504 | ||||
| -rw-r--r-- | drivers/net/netdevsim/netdevsim.h | 109 | 
4 files changed, 1266 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..75c25306d234 --- /dev/null +++ b/drivers/net/netdevsim/bpf.c @@ -0,0 +1,642 @@ +/* + * 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_cls_can_offload_and_chain0(ns->netdev, &cls_bpf->common)) +		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 (!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; +	int i, 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 >= 0) { +		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); +} |