Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NETDEVSIM) += netdevsim.o
netdevsim-objs := \
netdev.o dev.o ethtool.o fib.o bus.o health.o hwstats.o udp_tunnels.o tc.o
ifeq ($(CONFIG_BPF_SYSCALL),y)
netdevsim-objs += \
bpf.o
endif
ifneq ($(CONFIG_XFRM_OFFLOAD),)
netdevsim-objs += ipsec.o
endif
ifneq ($(CONFIG_PSAMPLE),)
netdevsim-objs += psample.o
endif
ifneq ($(CONFIG_INET_PSP),)
netdevsim-objs += psp.o
endif
ifneq ($(CONFIG_MACSEC),)
netdevsim-objs += macsec.o
endif
+654
View File
@@ -0,0 +1,654 @@
/*
* 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 nsim_dev *nsim_dev;
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_bpf_string_show(struct seq_file *file, void *data)
{
const char **str = file->private;
if (*str)
seq_printf(file, "%s\n", *str);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(nsim_bpf_string);
static int
nsim_bpf_verify_insn(struct bpf_verifier_env *env, int insn_idx, int prev_insn)
{
struct nsim_bpf_bound_prog *state;
int ret = 0;
state = env->prog->aux->offload->dev_priv;
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");
if (!state->nsim_dev->bpf_bind_verifier_accept)
ret = -EOPNOTSUPP;
}
return ret;
}
static int nsim_bpf_finalize(struct bpf_verifier_env *env)
{
return 0;
}
static bool nsim_xdp_offload_active(struct netdevsim *ns)
{
return ns->xdp_hw.prog;
}
static void nsim_prog_set_loaded(struct bpf_prog *prog, bool loaded)
{
struct nsim_bpf_bound_prog *state;
if (!prog || !bpf_prog_is_offloaded(prog->aux))
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 (!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,
struct xdp_attachment_info *xdp)
{
int err;
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;
}
xdp_attachment_setup(xdp, bpf);
return 0;
}
static int nsim_bpf_create_prog(struct nsim_dev *nsim_dev,
struct bpf_prog *prog)
{
struct nsim_bpf_bound_prog *state;
char name[16];
int ret;
state = kzalloc_obj(*state);
if (!state)
return -ENOMEM;
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", nsim_dev->prog_id_gen++);
state->ddir = debugfs_create_dir(name, nsim_dev->ddir_bpf_bound_progs);
if (IS_ERR(state->ddir)) {
ret = PTR_ERR(state->ddir);
kfree(state);
return ret;
}
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);
mutex_lock(&nsim_dev->progs_list_lock);
list_add_tail(&state->l, &nsim_dev->bpf_bound_progs);
mutex_unlock(&nsim_dev->progs_list_lock);
prog->aux->offload->dev_priv = state;
return 0;
}
static int nsim_bpf_verifier_prep(struct bpf_prog *prog)
{
struct nsim_dev *nsim_dev =
bpf_offload_dev_priv(prog->aux->offload->offdev);
if (!nsim_dev->bpf_bind_accept)
return -EOPNOTSUPP;
return nsim_bpf_create_prog(nsim_dev, prog);
}
static int nsim_bpf_translate(struct bpf_prog *prog)
{
struct nsim_bpf_bound_prog *state = prog->aux->offload->dev_priv;
state->state = "xlated";
return 0;
}
static void nsim_bpf_destroy_prog(struct bpf_prog *prog)
{
struct nsim_bpf_bound_prog *state;
struct nsim_dev *nsim_dev;
state = prog->aux->offload->dev_priv;
nsim_dev = state->nsim_dev;
WARN(state->is_loaded,
"offload state destroyed while program still bound");
debugfs_remove_recursive(state->ddir);
mutex_lock(&nsim_dev->progs_list_lock);
list_del(&state->l);
mutex_unlock(&nsim_dev->progs_list_lock);
kfree(state);
}
static const struct bpf_prog_offload_ops nsim_bpf_dev_ops = {
.insn_hook = nsim_bpf_verify_insn,
.finalize = nsim_bpf_finalize,
.prepare = nsim_bpf_verifier_prep,
.translate = nsim_bpf_translate,
.destroy = nsim_bpf_destroy_prog,
};
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 (bpf->prog && !bpf->prog->aux->xdp_has_frags &&
ns->netdev->mtu > NSIM_XDP_MAX_MTU) {
NSIM_EA(bpf->extack, "MTU too large w/ XDP enabled");
return -EINVAL;
}
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_is_offloaded(bpf->prog->aux)) {
NSIM_EA(bpf->extack, "xdpoffload of non-bound program");
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_KERNEL_ACCOUNT | __GFP_NOWARN);
if (!nmap->entry[idx].key)
return -ENOMEM;
nmap->entry[idx].value = kmalloc(offmap->map.value_size,
GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
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_obj(*nmap, GFP_KERNEL_ACCOUNT);
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;
memset(nmap->entry[i].value, 0, offmap->map.value_size);
}
}
offmap->dev_ops = &nsim_bpf_map_ops;
list_add_tail(&nmap->l, &ns->nsim_dev->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);
int err;
ASSERT_RTNL();
switch (bpf->command) {
case XDP_SETUP_PROG:
err = nsim_setup_prog_checks(ns, bpf);
if (err)
return err;
return nsim_xdp_set_prog(ns, bpf, &ns->xdp);
case XDP_SETUP_PROG_HW:
err = nsim_setup_prog_hw_checks(ns, bpf);
if (err)
return err;
return nsim_xdp_set_prog(ns, bpf, &ns->xdp_hw);
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_dev_init(struct nsim_dev *nsim_dev)
{
int err;
INIT_LIST_HEAD(&nsim_dev->bpf_bound_progs);
INIT_LIST_HEAD(&nsim_dev->bpf_bound_maps);
nsim_dev->ddir_bpf_bound_progs = debugfs_create_dir("bpf_bound_progs",
nsim_dev->ddir);
if (IS_ERR(nsim_dev->ddir_bpf_bound_progs))
return PTR_ERR(nsim_dev->ddir_bpf_bound_progs);
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;
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);
nsim_dev->bpf_bind_verifier_accept = true;
debugfs_create_bool("bpf_bind_verifier_accept", 0600, nsim_dev->ddir,
&nsim_dev->bpf_bind_verifier_accept);
return 0;
}
void nsim_bpf_dev_exit(struct nsim_dev *nsim_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;
err = bpf_offload_dev_netdev_register(ns->nsim_dev->bpf_dev,
ns->netdev);
if (err)
return err;
debugfs_create_u32("bpf_offloaded_id", 0400, ddir,
&ns->bpf_offloaded_id);
ns->bpf_tc_accept = true;
debugfs_create_bool("bpf_tc_accept", 0600, ddir,
&ns->bpf_tc_accept);
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, ddir,
&ns->bpf_xdpdrv_accept);
ns->bpf_xdpoffload_accept = true;
debugfs_create_bool("bpf_xdpoffload_accept", 0600, ddir,
&ns->bpf_xdpoffload_accept);
ns->bpf_map_accept = true;
debugfs_create_bool("bpf_map_accept", 0600, ddir,
&ns->bpf_map_accept);
return 0;
}
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->nsim_dev->bpf_dev, ns->netdev);
}
+540
View File
@@ -0,0 +1,540 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2017 Netronome Systems, Inc.
* Copyright (C) 2019 Mellanox Technologies. All rights reserved
*/
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/refcount.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 bool nsim_bus_enable;
static refcount_t nsim_bus_devs; /* Including the bus itself. */
static DECLARE_COMPLETION(nsim_bus_devs_released);
static struct nsim_bus_dev *to_nsim_bus_dev(struct device *dev)
{
return container_of(dev, struct nsim_bus_dev, dev);
}
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;
device_lock(dev);
ret = -ENOENT;
if (dev_get_drvdata(dev))
ret = nsim_drv_configure_vfs(nsim_bus_dev, num_vfs);
device_unlock(dev);
return ret ? ret : count;
}
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);
u8 eth_addr[ETH_ALEN] = {};
unsigned int port_index;
bool addr_set = false;
int ret;
/* Prevent to use nsim_bus_dev before initialization. */
if (!smp_load_acquire(&nsim_bus_dev->init))
return -EBUSY;
ret = sscanf(buf, "%u %hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &port_index,
&eth_addr[0], &eth_addr[1], &eth_addr[2], &eth_addr[3],
&eth_addr[4], &eth_addr[5]);
switch (ret) {
case 7:
if (!is_valid_ether_addr(eth_addr)) {
pr_err("The supplied perm_addr is not a valid MAC address\n");
return -EINVAL;
}
addr_set = true;
fallthrough;
case 1:
break;
default:
pr_err("Format for adding new port is \"id [perm_addr]\" (uint MAC).\n");
return -EINVAL;
}
ret = nsim_drv_port_add(nsim_bus_dev, NSIM_DEV_PORT_TYPE_PF, port_index,
addr_set ? eth_addr : NULL);
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;
/* Prevent to use nsim_bus_dev before initialization. */
if (!smp_load_acquire(&nsim_bus_dev->init))
return -EBUSY;
ret = kstrtouint(buf, 0, &port_index);
if (ret)
return ret;
ret = nsim_drv_port_del(nsim_bus_dev, NSIM_DEV_PORT_TYPE_PF, 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;
nsim_bus_dev = container_of(dev, struct nsim_bus_dev, dev);
kfree(nsim_bus_dev);
if (refcount_dec_and_test(&nsim_bus_devs))
complete(&nsim_bus_devs_released);
}
static const 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, unsigned int num_queues);
static ssize_t
new_device_store(const struct bus_type *bus, const char *buf, size_t count)
{
unsigned int id, port_count, num_queues;
struct nsim_bus_dev *nsim_bus_dev;
int err;
err = sscanf(buf, "%u %u %u", &id, &port_count, &num_queues);
switch (err) {
case 1:
port_count = 1;
fallthrough;
case 2:
num_queues = 1;
fallthrough;
case 3:
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 num_queues\" (uint uint uint).\n");
return -EINVAL;
}
mutex_lock(&nsim_bus_dev_list_lock);
/* Prevent to use resource before initialization. */
if (!smp_load_acquire(&nsim_bus_enable)) {
err = -EBUSY;
goto err;
}
nsim_bus_dev = nsim_bus_dev_new(id, port_count, num_queues);
if (IS_ERR(nsim_bus_dev)) {
err = PTR_ERR(nsim_bus_dev);
goto err;
}
refcount_inc(&nsim_bus_devs);
/* Allow using nsim_bus_dev */
smp_store_release(&nsim_bus_dev->init, true);
list_add_tail(&nsim_bus_dev->list, &nsim_bus_dev_list);
mutex_unlock(&nsim_bus_dev_list_lock);
return count;
err:
mutex_unlock(&nsim_bus_dev_list_lock);
return err;
}
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(const 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);
/* Prevent to use resource before initialization. */
if (!smp_load_acquire(&nsim_bus_enable)) {
mutex_unlock(&nsim_bus_dev_list_lock);
return -EBUSY;
}
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 ssize_t link_device_store(const struct bus_type *bus, const char *buf, size_t count)
{
struct netdevsim *nsim_a, *nsim_b, *peer;
struct net_device *dev_a, *dev_b;
unsigned int ifidx_a, ifidx_b;
int netnsfd_a, netnsfd_b, err;
struct net *ns_a, *ns_b;
err = sscanf(buf, "%d:%u %d:%u", &netnsfd_a, &ifidx_a, &netnsfd_b,
&ifidx_b);
if (err != 4) {
pr_err("Format for linking two devices is \"netnsfd_a:ifidx_a netnsfd_b:ifidx_b\" (int uint int uint).\n");
return -EINVAL;
}
ns_a = get_net_ns_by_fd(netnsfd_a);
if (IS_ERR(ns_a)) {
pr_err("Could not find netns with fd: %d\n", netnsfd_a);
return -EINVAL;
}
ns_b = get_net_ns_by_fd(netnsfd_b);
if (IS_ERR(ns_b)) {
pr_err("Could not find netns with fd: %d\n", netnsfd_b);
put_net(ns_a);
return -EINVAL;
}
err = -EINVAL;
rtnl_lock();
dev_a = __dev_get_by_index(ns_a, ifidx_a);
if (!dev_a) {
pr_err("Could not find device with ifindex %u in netnsfd %d\n",
ifidx_a, netnsfd_a);
goto out_err;
}
if (!netdev_is_nsim(dev_a)) {
pr_err("Device with ifindex %u in netnsfd %d is not a netdevsim\n",
ifidx_a, netnsfd_a);
goto out_err;
}
dev_b = __dev_get_by_index(ns_b, ifidx_b);
if (!dev_b) {
pr_err("Could not find device with ifindex %u in netnsfd %d\n",
ifidx_b, netnsfd_b);
goto out_err;
}
if (!netdev_is_nsim(dev_b)) {
pr_err("Device with ifindex %u in netnsfd %d is not a netdevsim\n",
ifidx_b, netnsfd_b);
goto out_err;
}
if (dev_a == dev_b) {
pr_err("Cannot link a netdevsim to itself\n");
goto out_err;
}
err = -EBUSY;
nsim_a = netdev_priv(dev_a);
peer = rtnl_dereference(nsim_a->peer);
if (peer) {
pr_err("Netdevsim %d:%u is already linked\n", netnsfd_a,
ifidx_a);
goto out_err;
}
nsim_b = netdev_priv(dev_b);
peer = rtnl_dereference(nsim_b->peer);
if (peer) {
pr_err("Netdevsim %d:%u is already linked\n", netnsfd_b,
ifidx_b);
goto out_err;
}
err = 0;
rcu_assign_pointer(nsim_a->peer, nsim_b);
rcu_assign_pointer(nsim_b->peer, nsim_a);
if (netif_running(dev_a) && netif_running(dev_b)) {
netif_carrier_on(dev_a);
netif_carrier_on(dev_b);
}
out_err:
put_net(ns_b);
put_net(ns_a);
rtnl_unlock();
return !err ? count : err;
}
static BUS_ATTR_WO(link_device);
static ssize_t unlink_device_store(const struct bus_type *bus, const char *buf, size_t count)
{
struct netdevsim *nsim, *peer;
struct net_device *dev;
unsigned int ifidx;
int netnsfd, err;
struct net *ns;
err = sscanf(buf, "%u:%u", &netnsfd, &ifidx);
if (err != 2) {
pr_err("Format for unlinking a device is \"netnsfd:ifidx\" (int uint).\n");
return -EINVAL;
}
ns = get_net_ns_by_fd(netnsfd);
if (IS_ERR(ns)) {
pr_err("Could not find netns with fd: %d\n", netnsfd);
return -EINVAL;
}
err = -EINVAL;
rtnl_lock();
dev = __dev_get_by_index(ns, ifidx);
if (!dev) {
pr_err("Could not find device with ifindex %u in netnsfd %d\n",
ifidx, netnsfd);
goto out_put_netns;
}
if (!netdev_is_nsim(dev)) {
pr_err("Device with ifindex %u in netnsfd %d is not a netdevsim\n",
ifidx, netnsfd);
goto out_put_netns;
}
nsim = netdev_priv(dev);
peer = rtnl_dereference(nsim->peer);
if (!peer)
goto out_put_netns;
netif_carrier_off(dev);
netif_carrier_off(peer->netdev);
err = 0;
RCU_INIT_POINTER(nsim->peer, NULL);
RCU_INIT_POINTER(peer->peer, NULL);
synchronize_net();
netif_tx_wake_all_queues(dev);
netif_tx_wake_all_queues(peer->netdev);
out_put_netns:
put_net(ns);
rtnl_unlock();
return !err ? count : err;
}
static BUS_ATTR_WO(unlink_device);
static struct attribute *nsim_bus_attrs[] = {
&bus_attr_new_device.attr,
&bus_attr_del_device.attr,
&bus_attr_link_device.attr,
&bus_attr_unlink_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_drv_probe(nsim_bus_dev);
}
static void nsim_bus_remove(struct device *dev)
{
struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);
nsim_drv_remove(nsim_bus_dev);
}
static 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 const 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,
};
#define NSIM_BUS_DEV_MAX_VFS 4
static struct nsim_bus_dev *
nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queues)
{
struct nsim_bus_dev *nsim_bus_dev;
int err;
nsim_bus_dev = kzalloc_obj(*nsim_bus_dev);
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;
nsim_bus_dev->num_queues = num_queues;
nsim_bus_dev->initial_net = current->nsproxy->net_ns;
nsim_bus_dev->max_vfs = NSIM_BUS_DEV_MAX_VFS;
/* Disallow using nsim_bus_dev */
smp_store_release(&nsim_bus_dev->init, false);
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);
put_device(&nsim_bus_dev->dev);
nsim_bus_dev = NULL;
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)
{
/* Disallow using nsim_bus_dev */
smp_store_release(&nsim_bus_dev->init, false);
ida_free(&nsim_bus_dev_ids, nsim_bus_dev->dev.id);
device_unregister(&nsim_bus_dev->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;
refcount_set(&nsim_bus_devs, 1);
/* Allow using resources */
smp_store_release(&nsim_bus_enable, true);
return 0;
err_bus_unregister:
bus_unregister(&nsim_bus);
return err;
}
void nsim_bus_exit(void)
{
struct nsim_bus_dev *nsim_bus_dev, *tmp;
/* Disallow using resources */
smp_store_release(&nsim_bus_enable, false);
if (refcount_dec_and_test(&nsim_bus_devs))
complete(&nsim_bus_devs_released);
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);
wait_for_completion(&nsim_bus_devs_released);
driver_unregister(&nsim_driver);
bus_unregister(&nsim_bus);
}
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2020 Facebook
#include <linux/debugfs.h>
#include <linux/random.h>
#include <net/netdev_queues.h>
#include "netdevsim.h"
static void
nsim_get_pause_stats(struct net_device *dev,
struct ethtool_pause_stats *pause_stats)
{
struct netdevsim *ns = netdev_priv(dev);
if (ns->ethtool.pauseparam.report_stats_rx)
pause_stats->rx_pause_frames = 1;
if (ns->ethtool.pauseparam.report_stats_tx)
pause_stats->tx_pause_frames = 2;
}
static void
nsim_get_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause)
{
struct netdevsim *ns = netdev_priv(dev);
pause->autoneg = 0; /* We don't support ksettings, so can't pretend */
pause->rx_pause = ns->ethtool.pauseparam.rx;
pause->tx_pause = ns->ethtool.pauseparam.tx;
}
static int
nsim_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause)
{
struct netdevsim *ns = netdev_priv(dev);
if (pause->autoneg)
return -EINVAL;
ns->ethtool.pauseparam.rx = pause->rx_pause;
ns->ethtool.pauseparam.tx = pause->tx_pause;
return 0;
}
static int nsim_get_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
memcpy(coal, &ns->ethtool.coalesce, sizeof(ns->ethtool.coalesce));
return 0;
}
static int nsim_set_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
memcpy(&ns->ethtool.coalesce, coal, sizeof(ns->ethtool.coalesce));
return 0;
}
static void nsim_get_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
memcpy(ring, &ns->ethtool.ring, sizeof(ns->ethtool.ring));
kernel_ring->hds_thresh_max = NSIM_HDS_THRESHOLD_MAX;
if (dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_UNKNOWN)
kernel_ring->tcp_data_split = ETHTOOL_TCP_DATA_SPLIT_ENABLED;
}
static int nsim_set_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
ns->ethtool.ring.rx_pending = ring->rx_pending;
ns->ethtool.ring.rx_jumbo_pending = ring->rx_jumbo_pending;
ns->ethtool.ring.rx_mini_pending = ring->rx_mini_pending;
ns->ethtool.ring.tx_pending = ring->tx_pending;
return 0;
}
static void
nsim_get_channels(struct net_device *dev, struct ethtool_channels *ch)
{
struct netdevsim *ns = netdev_priv(dev);
ch->max_combined = ns->nsim_bus_dev->num_queues;
ch->combined_count = ns->ethtool.channels;
}
static void
nsim_wake_queues(struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
struct netdevsim *peer;
synchronize_net();
netif_tx_wake_all_queues(dev);
rcu_read_lock();
peer = rcu_dereference(ns->peer);
if (peer)
netif_tx_wake_all_queues(peer->netdev);
rcu_read_unlock();
}
static int
nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch)
{
struct netdevsim *ns = netdev_priv(dev);
int err;
err = netif_set_real_num_queues(dev, ch->combined_count,
ch->combined_count);
if (err)
return err;
ns->ethtool.channels = ch->combined_count;
/* Only wake up queues if devices are linked */
if (rcu_access_pointer(ns->peer))
nsim_wake_queues(dev);
return 0;
}
static int
nsim_get_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
{
struct netdevsim *ns = netdev_priv(dev);
if (ns->ethtool.get_err)
return -ns->ethtool.get_err;
memcpy(fecparam, &ns->ethtool.fec, sizeof(ns->ethtool.fec));
return 0;
}
static int
nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
{
struct netdevsim *ns = netdev_priv(dev);
u32 fec;
if (ns->ethtool.set_err)
return -ns->ethtool.set_err;
memcpy(&ns->ethtool.fec, fecparam, sizeof(ns->ethtool.fec));
fec = fecparam->fec;
if (fec == ETHTOOL_FEC_AUTO)
fec |= ETHTOOL_FEC_OFF;
fec |= ETHTOOL_FEC_NONE;
ns->ethtool.fec.active_fec = 1 << (fls(fec) - 1);
return 0;
}
static const struct ethtool_fec_hist_range netdevsim_fec_ranges[] = {
{ 0, 0},
{ 1, 3},
{ 4, 7},
{ 0, 0}
};
static void
nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats,
struct ethtool_fec_hist *hist)
{
struct ethtool_fec_hist_value *values = hist->values;
hist->ranges = netdevsim_fec_ranges;
fec_stats->corrected_blocks.total = 123;
fec_stats->uncorrectable_blocks.total = 4;
values[0].per_lane[0] = 125;
values[0].per_lane[1] = 120;
values[0].per_lane[2] = 100;
values[0].per_lane[3] = 100;
values[1].sum = 12;
values[2].sum = 2;
values[2].per_lane[0] = 2;
values[2].per_lane[1] = 0;
values[2].per_lane[2] = 0;
values[2].per_lane[3] = 0;
}
static int nsim_get_ts_info(struct net_device *dev,
struct kernel_ethtool_ts_info *info)
{
struct netdevsim *ns = netdev_priv(dev);
info->phc_index = mock_phc_index(ns->phc);
return 0;
}
static const struct ethtool_ops nsim_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS,
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
ETHTOOL_RING_USE_HDS_THRS,
.op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS,
.get_pause_stats = nsim_get_pause_stats,
.get_pauseparam = nsim_get_pauseparam,
.set_pauseparam = nsim_set_pauseparam,
.set_coalesce = nsim_set_coalesce,
.get_coalesce = nsim_get_coalesce,
.get_ringparam = nsim_get_ringparam,
.set_ringparam = nsim_set_ringparam,
.get_channels = nsim_get_channels,
.set_channels = nsim_set_channels,
.get_fecparam = nsim_get_fecparam,
.set_fecparam = nsim_set_fecparam,
.get_fec_stats = nsim_get_fec_stats,
.get_ts_info = nsim_get_ts_info,
};
static void nsim_ethtool_ring_init(struct netdevsim *ns)
{
ns->ethtool.ring.rx_pending = 512;
ns->ethtool.ring.rx_max_pending = 4096;
ns->ethtool.ring.rx_jumbo_max_pending = 4096;
ns->ethtool.ring.rx_mini_max_pending = 4096;
ns->ethtool.ring.tx_pending = 512;
ns->ethtool.ring.tx_max_pending = 4096;
}
void nsim_ethtool_init(struct netdevsim *ns)
{
struct dentry *ethtool, *dir;
ns->netdev->ethtool_ops = &nsim_ethtool_ops;
nsim_ethtool_ring_init(ns);
ns->ethtool.pauseparam.report_stats_rx = true;
ns->ethtool.pauseparam.report_stats_tx = true;
ns->ethtool.fec.fec = ETHTOOL_FEC_NONE;
ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE;
ns->ethtool.channels = ns->nsim_bus_dev->num_queues;
ethtool = debugfs_create_dir("ethtool", ns->nsim_dev_port->ddir);
ns->ethtool_ddir = ethtool;
debugfs_create_u32("get_err", 0600, ethtool, &ns->ethtool.get_err);
debugfs_create_u32("set_err", 0600, ethtool, &ns->ethtool.set_err);
dir = debugfs_create_dir("pause", ethtool);
debugfs_create_bool("report_stats_rx", 0600, dir,
&ns->ethtool.pauseparam.report_stats_rx);
debugfs_create_bool("report_stats_tx", 0600, dir,
&ns->ethtool.pauseparam.report_stats_tx);
dir = debugfs_create_dir("ring", ethtool);
debugfs_create_u32("rx_max_pending", 0600, dir,
&ns->ethtool.ring.rx_max_pending);
debugfs_create_u32("rx_jumbo_max_pending", 0600, dir,
&ns->ethtool.ring.rx_jumbo_max_pending);
debugfs_create_u32("rx_mini_max_pending", 0600, dir,
&ns->ethtool.ring.rx_mini_max_pending);
debugfs_create_u32("tx_max_pending", 0600, dir,
&ns->ethtool.ring.tx_max_pending);
}
void nsim_ethtool_fini(struct netdevsim *ns)
{
debugfs_remove(ns->ethtool_ddir);
}
File diff suppressed because it is too large Load Diff
+231
View File
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 Mellanox Technologies. All rights reserved */
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "netdevsim.h"
static int
nsim_dev_empty_reporter_dump(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg, void *priv_ctx,
struct netlink_ext_ack *extack)
{
return 0;
}
static int
nsim_dev_empty_reporter_diagnose(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg,
struct netlink_ext_ack *extack)
{
return 0;
}
static const
struct devlink_health_reporter_ops nsim_dev_empty_reporter_ops = {
.name = "empty",
.dump = nsim_dev_empty_reporter_dump,
.diagnose = nsim_dev_empty_reporter_diagnose,
};
struct nsim_dev_dummy_reporter_ctx {
char *break_msg;
};
static int
nsim_dev_dummy_reporter_recover(struct devlink_health_reporter *reporter,
void *priv_ctx,
struct netlink_ext_ack *extack)
{
struct nsim_dev_health *health = devlink_health_reporter_priv(reporter);
struct nsim_dev_dummy_reporter_ctx *ctx = priv_ctx;
if (health->fail_recover) {
/* For testing purposes, user set debugfs fail_recover
* value to true. Fail right away.
*/
NL_SET_ERR_MSG_MOD(extack, "User setup the recover to fail for testing purposes");
return -EINVAL;
}
if (ctx) {
kfree(health->recovered_break_msg);
health->recovered_break_msg = kstrdup(ctx->break_msg,
GFP_KERNEL);
if (!health->recovered_break_msg)
return -ENOMEM;
}
return 0;
}
static int nsim_dev_dummy_fmsg_put(struct devlink_fmsg *fmsg, u32 binary_len)
{
char *binary;
int i;
devlink_fmsg_bool_pair_put(fmsg, "test_bool", true);
devlink_fmsg_u8_pair_put(fmsg, "test_u8", 1);
devlink_fmsg_u32_pair_put(fmsg, "test_u32", 3);
devlink_fmsg_u64_pair_put(fmsg, "test_u64", 4);
devlink_fmsg_string_pair_put(fmsg, "test_string", "somestring");
binary = kmalloc(binary_len, GFP_KERNEL | __GFP_NOWARN);
if (!binary)
return -ENOMEM;
get_random_bytes(binary, binary_len);
devlink_fmsg_binary_pair_put(fmsg, "test_binary", binary, binary_len);
kfree(binary);
devlink_fmsg_pair_nest_start(fmsg, "test_nest");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_bool_pair_put(fmsg, "nested_test_bool", false);
devlink_fmsg_u8_pair_put(fmsg, "nested_test_u8", false);
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
devlink_fmsg_arr_pair_nest_end(fmsg);
devlink_fmsg_arr_pair_nest_start(fmsg, "test_u32_array");
for (i = 0; i < 10; i++)
devlink_fmsg_u32_put(fmsg, i);
devlink_fmsg_arr_pair_nest_end(fmsg);
devlink_fmsg_arr_pair_nest_start(fmsg, "test_array_of_objects");
for (i = 0; i < 10; i++) {
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_bool_pair_put(fmsg, "in_array_nested_test_bool",
false);
devlink_fmsg_u8_pair_put(fmsg, "in_array_nested_test_u8", i);
devlink_fmsg_obj_nest_end(fmsg);
}
devlink_fmsg_arr_pair_nest_end(fmsg);
return 0;
}
static int
nsim_dev_dummy_reporter_dump(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg, void *priv_ctx,
struct netlink_ext_ack *extack)
{
struct nsim_dev_health *health = devlink_health_reporter_priv(reporter);
struct nsim_dev_dummy_reporter_ctx *ctx = priv_ctx;
if (ctx)
devlink_fmsg_string_pair_put(fmsg, "break_message", ctx->break_msg);
return nsim_dev_dummy_fmsg_put(fmsg, health->binary_len);
}
static int
nsim_dev_dummy_reporter_diagnose(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg,
struct netlink_ext_ack *extack)
{
struct nsim_dev_health *health = devlink_health_reporter_priv(reporter);
if (health->recovered_break_msg)
devlink_fmsg_string_pair_put(fmsg, "recovered_break_message",
health->recovered_break_msg);
return nsim_dev_dummy_fmsg_put(fmsg, health->binary_len);
}
static const
struct devlink_health_reporter_ops nsim_dev_dummy_reporter_ops = {
.name = "dummy",
.recover = nsim_dev_dummy_reporter_recover,
.dump = nsim_dev_dummy_reporter_dump,
.diagnose = nsim_dev_dummy_reporter_diagnose,
};
static ssize_t nsim_dev_health_break_write(struct file *file,
const char __user *data,
size_t count, loff_t *ppos)
{
struct nsim_dev_health *health = file->private_data;
struct nsim_dev_dummy_reporter_ctx ctx;
char *break_msg;
int err;
if (count == 0 || count > PAGE_SIZE)
return -EINVAL;
break_msg = memdup_user_nul(data, count);
if (IS_ERR(break_msg))
return PTR_ERR(break_msg);
if (break_msg[count - 1] == '\n')
break_msg[count - 1] = '\0';
ctx.break_msg = break_msg;
err = devlink_health_report(health->dummy_reporter, break_msg, &ctx);
if (err)
goto out;
out:
kfree(break_msg);
return err ?: count;
}
static const struct file_operations nsim_dev_health_break_fops = {
.open = simple_open,
.write = nsim_dev_health_break_write,
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
};
int nsim_dev_health_init(struct nsim_dev *nsim_dev, struct devlink *devlink)
{
struct nsim_dev_health *health = &nsim_dev->health;
int err;
health->empty_reporter =
devl_health_reporter_create(devlink,
&nsim_dev_empty_reporter_ops,
health);
if (IS_ERR(health->empty_reporter))
return PTR_ERR(health->empty_reporter);
health->dummy_reporter =
devl_health_reporter_create(devlink,
&nsim_dev_dummy_reporter_ops,
health);
if (IS_ERR(health->dummy_reporter)) {
err = PTR_ERR(health->dummy_reporter);
goto err_empty_reporter_destroy;
}
health->ddir = debugfs_create_dir("health", nsim_dev->ddir);
if (IS_ERR(health->ddir)) {
err = PTR_ERR(health->ddir);
goto err_dummy_reporter_destroy;
}
health->recovered_break_msg = NULL;
debugfs_create_file("break_health", 0200, health->ddir, health,
&nsim_dev_health_break_fops);
health->binary_len = 16;
debugfs_create_u32("binary_len", 0600, health->ddir,
&health->binary_len);
health->fail_recover = false;
debugfs_create_bool("fail_recover", 0600, health->ddir,
&health->fail_recover);
return 0;
err_dummy_reporter_destroy:
devl_health_reporter_destroy(health->dummy_reporter);
err_empty_reporter_destroy:
devl_health_reporter_destroy(health->empty_reporter);
return err;
}
void nsim_dev_health_exit(struct nsim_dev *nsim_dev)
{
struct nsim_dev_health *health = &nsim_dev->health;
debugfs_remove_recursive(health->ddir);
kfree(health->recovered_break_msg);
devl_health_reporter_destroy(health->dummy_reporter);
devl_health_reporter_destroy(health->empty_reporter);
}
+480
View File
@@ -0,0 +1,480 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/debugfs.h>
#include "netdevsim.h"
#define NSIM_DEV_HWSTATS_TRAFFIC_MS 100
static struct list_head *
nsim_dev_hwstats_get_list_head(struct nsim_dev_hwstats *hwstats,
enum netdev_offload_xstats_type type)
{
switch (type) {
case NETDEV_OFFLOAD_XSTATS_TYPE_L3:
return &hwstats->l3_list;
}
WARN_ON_ONCE(1);
return NULL;
}
static void nsim_dev_hwstats_traffic_bump(struct nsim_dev_hwstats *hwstats,
enum netdev_offload_xstats_type type)
{
struct nsim_dev_hwstats_netdev *hwsdev;
struct list_head *hwsdev_list;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
if (WARN_ON(!hwsdev_list))
return;
list_for_each_entry(hwsdev, hwsdev_list, list) {
if (hwsdev->enabled) {
hwsdev->stats.rx_packets += 1;
hwsdev->stats.tx_packets += 2;
hwsdev->stats.rx_bytes += 100;
hwsdev->stats.tx_bytes += 300;
}
}
}
static void nsim_dev_hwstats_traffic_work(struct work_struct *work)
{
struct nsim_dev_hwstats *hwstats;
hwstats = container_of(work, struct nsim_dev_hwstats, traffic_dw.work);
mutex_lock(&hwstats->hwsdev_list_lock);
nsim_dev_hwstats_traffic_bump(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
mutex_unlock(&hwstats->hwsdev_list_lock);
schedule_delayed_work(&hwstats->traffic_dw,
msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
}
static struct nsim_dev_hwstats_netdev *
nsim_dev_hwslist_find_hwsdev(struct list_head *hwsdev_list,
int ifindex)
{
struct nsim_dev_hwstats_netdev *hwsdev;
list_for_each_entry(hwsdev, hwsdev_list, list) {
if (hwsdev->netdev->ifindex == ifindex)
return hwsdev;
}
return NULL;
}
static int nsim_dev_hwsdev_enable(struct nsim_dev_hwstats_netdev *hwsdev,
struct netlink_ext_ack *extack)
{
if (hwsdev->fail_enable) {
hwsdev->fail_enable = false;
NL_SET_ERR_MSG_MOD(extack, "Stats enablement set to fail");
return -ECANCELED;
}
hwsdev->enabled = true;
return 0;
}
static void nsim_dev_hwsdev_disable(struct nsim_dev_hwstats_netdev *hwsdev)
{
hwsdev->enabled = false;
memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
}
static int
nsim_dev_hwsdev_report_delta(struct nsim_dev_hwstats_netdev *hwsdev,
struct netdev_notifier_offload_xstats_info *info)
{
netdev_offload_xstats_report_delta(info->report_delta, &hwsdev->stats);
memset(&hwsdev->stats, 0, sizeof(hwsdev->stats));
return 0;
}
static void
nsim_dev_hwsdev_report_used(struct nsim_dev_hwstats_netdev *hwsdev,
struct netdev_notifier_offload_xstats_info *info)
{
if (hwsdev->enabled)
netdev_offload_xstats_report_used(info->report_used);
}
static int nsim_dev_hwstats_event_off_xstats(struct nsim_dev_hwstats *hwstats,
struct net_device *dev,
unsigned long event, void *ptr)
{
struct netdev_notifier_offload_xstats_info *info;
struct nsim_dev_hwstats_netdev *hwsdev;
struct list_head *hwsdev_list;
int err = 0;
info = ptr;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, info->type);
if (!hwsdev_list)
return 0;
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);
if (!hwsdev)
goto out;
switch (event) {
case NETDEV_OFFLOAD_XSTATS_ENABLE:
err = nsim_dev_hwsdev_enable(hwsdev, info->info.extack);
break;
case NETDEV_OFFLOAD_XSTATS_DISABLE:
nsim_dev_hwsdev_disable(hwsdev);
break;
case NETDEV_OFFLOAD_XSTATS_REPORT_USED:
nsim_dev_hwsdev_report_used(hwsdev, info);
break;
case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:
err = nsim_dev_hwsdev_report_delta(hwsdev, info);
break;
}
out:
mutex_unlock(&hwstats->hwsdev_list_lock);
return err;
}
static void nsim_dev_hwsdev_fini(struct nsim_dev_hwstats_netdev *hwsdev)
{
dev_put(hwsdev->netdev);
kfree(hwsdev);
}
static void
__nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,
struct net_device *dev,
enum netdev_offload_xstats_type type)
{
struct nsim_dev_hwstats_netdev *hwsdev;
struct list_head *hwsdev_list;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
if (WARN_ON(!hwsdev_list))
return;
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, dev->ifindex);
if (!hwsdev)
return;
list_del(&hwsdev->list);
nsim_dev_hwsdev_fini(hwsdev);
}
static void nsim_dev_hwstats_event_unregister(struct nsim_dev_hwstats *hwstats,
struct net_device *dev)
{
mutex_lock(&hwstats->hwsdev_list_lock);
__nsim_dev_hwstats_event_unregister(hwstats, dev,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
mutex_unlock(&hwstats->hwsdev_list_lock);
}
static int nsim_dev_hwstats_event(struct nsim_dev_hwstats *hwstats,
struct net_device *dev,
unsigned long event, void *ptr)
{
switch (event) {
case NETDEV_OFFLOAD_XSTATS_ENABLE:
case NETDEV_OFFLOAD_XSTATS_DISABLE:
case NETDEV_OFFLOAD_XSTATS_REPORT_USED:
case NETDEV_OFFLOAD_XSTATS_REPORT_DELTA:
return nsim_dev_hwstats_event_off_xstats(hwstats, dev,
event, ptr);
case NETDEV_UNREGISTER:
nsim_dev_hwstats_event_unregister(hwstats, dev);
break;
}
return 0;
}
static int nsim_dev_netdevice_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct nsim_dev_hwstats *hwstats;
int err = 0;
hwstats = container_of(nb, struct nsim_dev_hwstats, netdevice_nb);
err = nsim_dev_hwstats_event(hwstats, dev, event, ptr);
if (err)
return notifier_from_errno(err);
return NOTIFY_OK;
}
static int
nsim_dev_hwstats_enable_ifindex(struct nsim_dev_hwstats *hwstats,
int ifindex,
enum netdev_offload_xstats_type type,
struct list_head *hwsdev_list)
{
struct nsim_dev_hwstats_netdev *hwsdev;
struct nsim_dev *nsim_dev;
struct net_device *netdev;
struct net *net;
int err = 0;
nsim_dev = container_of(hwstats, struct nsim_dev, hwstats);
net = nsim_dev_net(nsim_dev);
rtnl_lock();
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
if (hwsdev)
goto out_unlock_list;
netdev = dev_get_by_index(net, ifindex);
if (!netdev) {
err = -ENODEV;
goto out_unlock_list;
}
hwsdev = kzalloc_obj(*hwsdev);
if (!hwsdev) {
err = -ENOMEM;
goto out_put_netdev;
}
hwsdev->netdev = netdev;
list_add_tail(&hwsdev->list, hwsdev_list);
mutex_unlock(&hwstats->hwsdev_list_lock);
if (netdev_offload_xstats_enabled(netdev, type)) {
nsim_dev_hwsdev_enable(hwsdev, NULL);
rtnl_offload_xstats_notify(netdev);
}
rtnl_unlock();
return err;
out_put_netdev:
dev_put(netdev);
out_unlock_list:
mutex_unlock(&hwstats->hwsdev_list_lock);
rtnl_unlock();
return err;
}
static int
nsim_dev_hwstats_disable_ifindex(struct nsim_dev_hwstats *hwstats,
int ifindex,
enum netdev_offload_xstats_type type,
struct list_head *hwsdev_list)
{
struct nsim_dev_hwstats_netdev *hwsdev;
int err = 0;
rtnl_lock();
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
if (hwsdev)
list_del(&hwsdev->list);
mutex_unlock(&hwstats->hwsdev_list_lock);
if (!hwsdev) {
err = -ENOENT;
goto unlock_out;
}
if (netdev_offload_xstats_enabled(hwsdev->netdev, type)) {
netdev_offload_xstats_push_delta(hwsdev->netdev, type,
&hwsdev->stats);
rtnl_offload_xstats_notify(hwsdev->netdev);
}
nsim_dev_hwsdev_fini(hwsdev);
unlock_out:
rtnl_unlock();
return err;
}
static int
nsim_dev_hwstats_fail_ifindex(struct nsim_dev_hwstats *hwstats,
int ifindex,
enum netdev_offload_xstats_type type,
struct list_head *hwsdev_list)
{
struct nsim_dev_hwstats_netdev *hwsdev;
int err = 0;
mutex_lock(&hwstats->hwsdev_list_lock);
hwsdev = nsim_dev_hwslist_find_hwsdev(hwsdev_list, ifindex);
if (!hwsdev) {
err = -ENOENT;
goto err_hwsdev_list_unlock;
}
hwsdev->fail_enable = true;
err_hwsdev_list_unlock:
mutex_unlock(&hwstats->hwsdev_list_lock);
return err;
}
enum nsim_dev_hwstats_do {
NSIM_DEV_HWSTATS_DO_DISABLE,
NSIM_DEV_HWSTATS_DO_ENABLE,
NSIM_DEV_HWSTATS_DO_FAIL,
};
struct nsim_dev_hwstats_fops {
enum nsim_dev_hwstats_do action;
enum netdev_offload_xstats_type type;
};
static ssize_t
nsim_dev_hwstats_do_write(struct file *file,
const char __user *data,
size_t count, loff_t *ppos)
{
struct nsim_dev_hwstats *hwstats = file->private_data;
const struct nsim_dev_hwstats_fops *hwsfops;
struct list_head *hwsdev_list;
int ifindex;
int err;
hwsfops = debugfs_get_aux(file);
err = kstrtoint_from_user(data, count, 0, &ifindex);
if (err)
return err;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, hwsfops->type);
if (WARN_ON(!hwsdev_list))
return -EINVAL;
switch (hwsfops->action) {
case NSIM_DEV_HWSTATS_DO_DISABLE:
err = nsim_dev_hwstats_disable_ifindex(hwstats, ifindex,
hwsfops->type,
hwsdev_list);
break;
case NSIM_DEV_HWSTATS_DO_ENABLE:
err = nsim_dev_hwstats_enable_ifindex(hwstats, ifindex,
hwsfops->type,
hwsdev_list);
break;
case NSIM_DEV_HWSTATS_DO_FAIL:
err = nsim_dev_hwstats_fail_ifindex(hwstats, ifindex,
hwsfops->type,
hwsdev_list);
break;
}
if (err)
return err;
return count;
}
static struct debugfs_short_fops debugfs_ops = {
.write = nsim_dev_hwstats_do_write,
.llseek = generic_file_llseek,
};
#define NSIM_DEV_HWSTATS_FOPS(ACTION, TYPE) \
{ \
.action = ACTION, \
.type = TYPE, \
}
static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_disable_fops =
NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_DISABLE,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_enable_fops =
NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_ENABLE,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
static const struct nsim_dev_hwstats_fops nsim_dev_hwstats_l3_fail_fops =
NSIM_DEV_HWSTATS_FOPS(NSIM_DEV_HWSTATS_DO_FAIL,
NETDEV_OFFLOAD_XSTATS_TYPE_L3);
#undef NSIM_DEV_HWSTATS_FOPS
int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev)
{
struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
struct net *net = nsim_dev_net(nsim_dev);
int err;
mutex_init(&hwstats->hwsdev_list_lock);
INIT_LIST_HEAD(&hwstats->l3_list);
hwstats->netdevice_nb.notifier_call = nsim_dev_netdevice_event;
err = register_netdevice_notifier_net(net, &hwstats->netdevice_nb);
if (err)
goto err_mutex_destroy;
hwstats->ddir = debugfs_create_dir("hwstats", nsim_dev->ddir);
if (IS_ERR(hwstats->ddir)) {
err = PTR_ERR(hwstats->ddir);
goto err_unregister_notifier;
}
hwstats->l3_ddir = debugfs_create_dir("l3", hwstats->ddir);
if (IS_ERR(hwstats->l3_ddir)) {
err = PTR_ERR(hwstats->l3_ddir);
goto err_remove_hwstats_recursive;
}
debugfs_create_file_aux("enable_ifindex", 0200, hwstats->l3_ddir, hwstats,
&nsim_dev_hwstats_l3_enable_fops, &debugfs_ops);
debugfs_create_file_aux("disable_ifindex", 0200, hwstats->l3_ddir, hwstats,
&nsim_dev_hwstats_l3_disable_fops, &debugfs_ops);
debugfs_create_file_aux("fail_next_enable", 0200, hwstats->l3_ddir, hwstats,
&nsim_dev_hwstats_l3_fail_fops, &debugfs_ops);
INIT_DELAYED_WORK(&hwstats->traffic_dw,
&nsim_dev_hwstats_traffic_work);
schedule_delayed_work(&hwstats->traffic_dw,
msecs_to_jiffies(NSIM_DEV_HWSTATS_TRAFFIC_MS));
return 0;
err_remove_hwstats_recursive:
debugfs_remove_recursive(hwstats->ddir);
err_unregister_notifier:
unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
err_mutex_destroy:
mutex_destroy(&hwstats->hwsdev_list_lock);
return err;
}
static void nsim_dev_hwsdev_list_wipe(struct nsim_dev_hwstats *hwstats,
enum netdev_offload_xstats_type type)
{
struct nsim_dev_hwstats_netdev *hwsdev, *tmp;
struct list_head *hwsdev_list;
hwsdev_list = nsim_dev_hwstats_get_list_head(hwstats, type);
if (WARN_ON(!hwsdev_list))
return;
mutex_lock(&hwstats->hwsdev_list_lock);
list_for_each_entry_safe(hwsdev, tmp, hwsdev_list, list) {
list_del(&hwsdev->list);
nsim_dev_hwsdev_fini(hwsdev);
}
mutex_unlock(&hwstats->hwsdev_list_lock);
}
void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev)
{
struct nsim_dev_hwstats *hwstats = &nsim_dev->hwstats;
struct net *net = nsim_dev_net(nsim_dev);
cancel_delayed_work_sync(&hwstats->traffic_dw);
debugfs_remove_recursive(hwstats->ddir);
unregister_netdevice_notifier_net(net, &hwstats->netdevice_nb);
nsim_dev_hwsdev_list_wipe(hwstats, NETDEV_OFFLOAD_XSTATS_TYPE_L3);
mutex_destroy(&hwstats->hwsdev_list_lock);
}
+296
View File
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2018 Oracle and/or its affiliates. All rights reserved. */
#include <crypto/aead.h>
#include <linux/debugfs.h>
#include <net/xfrm.h>
#include "netdevsim.h"
#define NSIM_IPSEC_AUTH_BITS 128
static ssize_t nsim_dbg_netdev_ops_read(struct file *filp,
char __user *buffer,
size_t count, loff_t *ppos)
{
struct netdevsim *ns = filp->private_data;
struct nsim_ipsec *ipsec = &ns->ipsec;
size_t bufsize;
char *buf, *p;
int len;
int i;
/* the buffer needed is
* (num SAs * 3 lines each * ~60 bytes per line) + one more line
*/
bufsize = (ipsec->count * 4 * 60) + 60;
buf = kzalloc(bufsize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
p = buf;
p += scnprintf(p, bufsize - (p - buf),
"SA count=%u tx=%u\n",
ipsec->count, ipsec->tx);
for (i = 0; i < NSIM_IPSEC_MAX_SA_COUNT; i++) {
struct nsim_sa *sap = &ipsec->sa[i];
if (!sap->used)
continue;
if (sap->xs->props.family == AF_INET6)
p += scnprintf(p, bufsize - (p - buf),
"sa[%i] %cx ipaddr=%pI6c\n",
i, (sap->rx ? 'r' : 't'), &sap->ipaddr);
else
p += scnprintf(p, bufsize - (p - buf),
"sa[%i] %cx ipaddr=%pI4\n",
i, (sap->rx ? 'r' : 't'), &sap->ipaddr[3]);
p += scnprintf(p, bufsize - (p - buf),
"sa[%i] spi=0x%08x proto=0x%x salt=0x%08x crypt=%d\n",
i, be32_to_cpu(sap->xs->id.spi),
sap->xs->id.proto, sap->salt, sap->crypt);
p += scnprintf(p, bufsize - (p - buf),
"sa[%i] key=0x%08x %08x %08x %08x\n",
i, sap->key[0], sap->key[1],
sap->key[2], sap->key[3]);
}
len = simple_read_from_buffer(buffer, count, ppos, buf, p - buf);
kfree(buf);
return len;
}
static const struct file_operations ipsec_dbg_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = nsim_dbg_netdev_ops_read,
};
static int nsim_ipsec_find_empty_idx(struct nsim_ipsec *ipsec)
{
u32 i;
if (ipsec->count == NSIM_IPSEC_MAX_SA_COUNT)
return -ENOSPC;
/* search sa table */
for (i = 0; i < NSIM_IPSEC_MAX_SA_COUNT; i++) {
if (!ipsec->sa[i].used)
return i;
}
return -ENOSPC;
}
static int nsim_ipsec_parse_proto_keys(struct net_device *dev,
struct xfrm_state *xs,
u32 *mykey, u32 *mysalt)
{
const char aes_gcm_name[] = "rfc4106(gcm(aes))";
unsigned char *key_data;
char *alg_name = NULL;
int key_len;
if (!xs->aead) {
netdev_err(dev, "Unsupported IPsec algorithm\n");
return -EINVAL;
}
if (xs->aead->alg_icv_len != NSIM_IPSEC_AUTH_BITS) {
netdev_err(dev, "IPsec offload requires %d bit authentication\n",
NSIM_IPSEC_AUTH_BITS);
return -EINVAL;
}
key_data = &xs->aead->alg_key[0];
key_len = xs->aead->alg_key_len;
alg_name = xs->aead->alg_name;
if (strcmp(alg_name, aes_gcm_name)) {
netdev_err(dev, "Unsupported IPsec algorithm - please use %s\n",
aes_gcm_name);
return -EINVAL;
}
/* 160 accounts for 16 byte key and 4 byte salt */
if (key_len > NSIM_IPSEC_AUTH_BITS) {
*mysalt = ((u32 *)key_data)[4];
} else if (key_len == NSIM_IPSEC_AUTH_BITS) {
*mysalt = 0;
} else {
netdev_err(dev, "IPsec hw offload only supports 128 bit keys with optional 32 bit salt\n");
return -EINVAL;
}
memcpy(mykey, key_data, 16);
return 0;
}
static int nsim_ipsec_add_sa(struct net_device *dev,
struct xfrm_state *xs,
struct netlink_ext_ack *extack)
{
struct nsim_ipsec *ipsec;
struct netdevsim *ns;
struct nsim_sa sa;
u16 sa_idx;
int ret;
ns = netdev_priv(dev);
ipsec = &ns->ipsec;
if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) {
NL_SET_ERR_MSG_MOD(extack, "Unsupported protocol for ipsec offload");
return -EINVAL;
}
if (xs->calg) {
NL_SET_ERR_MSG_MOD(extack, "Compression offload not supported");
return -EINVAL;
}
if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
NL_SET_ERR_MSG_MOD(extack, "Unsupported ipsec offload type");
return -EINVAL;
}
/* find the first unused index */
ret = nsim_ipsec_find_empty_idx(ipsec);
if (ret < 0) {
NL_SET_ERR_MSG_MOD(extack, "No space for SA in Rx table!");
return ret;
}
sa_idx = (u16)ret;
memset(&sa, 0, sizeof(sa));
sa.used = true;
sa.xs = xs;
if (sa.xs->id.proto & IPPROTO_ESP)
sa.crypt = xs->ealg || xs->aead;
/* get the key and salt */
ret = nsim_ipsec_parse_proto_keys(dev, xs, sa.key, &sa.salt);
if (ret) {
NL_SET_ERR_MSG_MOD(extack, "Failed to get key data for SA table");
return ret;
}
if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN)
sa.rx = true;
if (xs->props.family == AF_INET6)
memcpy(sa.ipaddr, &xs->id.daddr.a6, 16);
else
memcpy(&sa.ipaddr[3], &xs->id.daddr.a4, 4);
/* the preparations worked, so save the info */
memcpy(&ipsec->sa[sa_idx], &sa, sizeof(sa));
/* the XFRM stack doesn't like offload_handle == 0,
* so add a bitflag in case our array index is 0
*/
xs->xso.offload_handle = sa_idx | NSIM_IPSEC_VALID;
ipsec->count++;
return 0;
}
static void nsim_ipsec_del_sa(struct net_device *dev, struct xfrm_state *xs)
{
struct netdevsim *ns = netdev_priv(dev);
struct nsim_ipsec *ipsec = &ns->ipsec;
u16 sa_idx;
sa_idx = xs->xso.offload_handle & ~NSIM_IPSEC_VALID;
if (!ipsec->sa[sa_idx].used) {
netdev_err(ns->netdev, "Invalid SA for delete sa_idx=%d\n",
sa_idx);
return;
}
memset(&ipsec->sa[sa_idx], 0, sizeof(struct nsim_sa));
ipsec->count--;
}
static const struct xfrmdev_ops nsim_xfrmdev_ops = {
.xdo_dev_state_add = nsim_ipsec_add_sa,
.xdo_dev_state_delete = nsim_ipsec_del_sa,
};
bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb)
{
struct sec_path *sp = skb_sec_path(skb);
struct nsim_ipsec *ipsec = &ns->ipsec;
struct xfrm_state *xs;
struct nsim_sa *tsa;
u32 sa_idx;
/* do we even need to check this packet? */
if (!sp)
return true;
if (unlikely(!sp->len)) {
netdev_err(ns->netdev, "no xfrm state len = %d\n",
sp->len);
return false;
}
xs = xfrm_input_state(skb);
if (unlikely(!xs)) {
netdev_err(ns->netdev, "no xfrm_input_state() xs = %p\n", xs);
return false;
}
sa_idx = xs->xso.offload_handle & ~NSIM_IPSEC_VALID;
if (unlikely(sa_idx >= NSIM_IPSEC_MAX_SA_COUNT)) {
netdev_err(ns->netdev, "bad sa_idx=%d max=%d\n",
sa_idx, NSIM_IPSEC_MAX_SA_COUNT);
return false;
}
tsa = &ipsec->sa[sa_idx];
if (unlikely(!tsa->used)) {
netdev_err(ns->netdev, "unused sa_idx=%d\n", sa_idx);
return false;
}
if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) {
netdev_err(ns->netdev, "unexpected proto=%d\n", xs->id.proto);
return false;
}
ipsec->tx++;
return true;
}
void nsim_ipsec_init(struct netdevsim *ns)
{
ns->netdev->xfrmdev_ops = &nsim_xfrmdev_ops;
#define NSIM_ESP_FEATURES (NETIF_F_HW_ESP | \
NETIF_F_HW_ESP_TX_CSUM | \
NETIF_F_GSO_ESP)
ns->netdev->features |= NSIM_ESP_FEATURES;
ns->netdev->hw_features |= NSIM_ESP_FEATURES;
ns->netdev->hw_enc_features |= NSIM_ESP_FEATURES;
ns->ipsec.pfile = debugfs_create_file("ipsec", 0400,
ns->nsim_dev_port->ddir, ns,
&ipsec_dbg_fops);
}
void nsim_ipsec_teardown(struct netdevsim *ns)
{
struct nsim_ipsec *ipsec = &ns->ipsec;
if (ipsec->count)
netdev_err(ns->netdev, "tearing down IPsec offload with %d SAs left\n",
ipsec->count);
debugfs_remove_recursive(ipsec->pfile);
}
+351
View File
@@ -0,0 +1,351 @@
// SPDX-License-Identifier: GPL-2.0
#include <net/macsec.h>
#include "netdevsim.h"
static int nsim_macsec_find_secy(struct netdevsim *ns, sci_t sci)
{
int i;
for (i = 0; i < NSIM_MACSEC_MAX_SECY_COUNT; i++) {
if (ns->macsec.nsim_secy[i].sci == sci)
return i;
}
return -1;
}
static int nsim_macsec_find_rxsc(struct nsim_secy *ns_secy, sci_t sci)
{
int i;
for (i = 0; i < NSIM_MACSEC_MAX_RXSC_COUNT; i++) {
if (ns_secy->nsim_rxsc[i].sci == sci)
return i;
}
return -1;
}
static int nsim_macsec_add_secy(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
int idx;
if (ns->macsec.nsim_secy_count == NSIM_MACSEC_MAX_SECY_COUNT)
return -ENOSPC;
for (idx = 0; idx < NSIM_MACSEC_MAX_SECY_COUNT; idx++) {
if (!ns->macsec.nsim_secy[idx].used)
break;
}
if (idx == NSIM_MACSEC_MAX_SECY_COUNT) {
netdev_err(ctx->netdev, "%s: nsim_secy_count not full but all SecYs used\n",
__func__);
return -ENOSPC;
}
netdev_dbg(ctx->netdev, "%s: adding new secy with sci %016llx at index %d\n",
__func__, sci_to_cpu(ctx->secy->sci), idx);
ns->macsec.nsim_secy[idx].used = true;
ns->macsec.nsim_secy[idx].nsim_rxsc_count = 0;
ns->macsec.nsim_secy[idx].sci = ctx->secy->sci;
ns->macsec.nsim_secy_count++;
return 0;
}
static int nsim_macsec_upd_secy(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: updating secy with sci %016llx at index %d\n",
__func__, sci_to_cpu(ctx->secy->sci), idx);
return 0;
}
static int nsim_macsec_del_secy(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: removing SecY with SCI %016llx at index %d\n",
__func__, sci_to_cpu(ctx->secy->sci), idx);
ns->macsec.nsim_secy[idx].used = false;
memset(&ns->macsec.nsim_secy[idx], 0, sizeof(ns->macsec.nsim_secy[idx]));
ns->macsec.nsim_secy_count--;
return 0;
}
static int nsim_macsec_add_rxsc(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
struct nsim_secy *secy;
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
secy = &ns->macsec.nsim_secy[idx];
if (secy->nsim_rxsc_count == NSIM_MACSEC_MAX_RXSC_COUNT)
return -ENOSPC;
for (idx = 0; idx < NSIM_MACSEC_MAX_RXSC_COUNT; idx++) {
if (!secy->nsim_rxsc[idx].used)
break;
}
if (idx == NSIM_MACSEC_MAX_RXSC_COUNT)
netdev_err(ctx->netdev, "%s: nsim_rxsc_count not full but all RXSCs used\n",
__func__);
netdev_dbg(ctx->netdev, "%s: adding new rxsc with sci %016llx at index %d\n",
__func__, sci_to_cpu(ctx->rx_sc->sci), idx);
secy->nsim_rxsc[idx].used = true;
secy->nsim_rxsc[idx].sci = ctx->rx_sc->sci;
secy->nsim_rxsc_count++;
return 0;
}
static int nsim_macsec_upd_rxsc(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
struct nsim_secy *secy;
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
secy = &ns->macsec.nsim_secy[idx];
idx = nsim_macsec_find_rxsc(secy, ctx->rx_sc->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in RXSC table\n",
__func__, sci_to_cpu(ctx->rx_sc->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: updating RXSC with sci %016llx at index %d\n",
__func__, sci_to_cpu(ctx->rx_sc->sci), idx);
return 0;
}
static int nsim_macsec_del_rxsc(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
struct nsim_secy *secy;
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
secy = &ns->macsec.nsim_secy[idx];
idx = nsim_macsec_find_rxsc(secy, ctx->rx_sc->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in RXSC table\n",
__func__, sci_to_cpu(ctx->rx_sc->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: removing RXSC with sci %016llx at index %d\n",
__func__, sci_to_cpu(ctx->rx_sc->sci), idx);
secy->nsim_rxsc[idx].used = false;
memset(&secy->nsim_rxsc[idx], 0, sizeof(secy->nsim_rxsc[idx]));
secy->nsim_rxsc_count--;
return 0;
}
static int nsim_macsec_add_rxsa(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
struct nsim_secy *secy;
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
secy = &ns->macsec.nsim_secy[idx];
idx = nsim_macsec_find_rxsc(secy, ctx->sa.rx_sa->sc->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in RXSC table\n",
__func__, sci_to_cpu(ctx->sa.rx_sa->sc->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: RXSC with sci %016llx, AN %u\n",
__func__, sci_to_cpu(ctx->sa.rx_sa->sc->sci), ctx->sa.assoc_num);
return 0;
}
static int nsim_macsec_upd_rxsa(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
struct nsim_secy *secy;
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
secy = &ns->macsec.nsim_secy[idx];
idx = nsim_macsec_find_rxsc(secy, ctx->sa.rx_sa->sc->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in RXSC table\n",
__func__, sci_to_cpu(ctx->sa.rx_sa->sc->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: RXSC with sci %016llx, AN %u\n",
__func__, sci_to_cpu(ctx->sa.rx_sa->sc->sci), ctx->sa.assoc_num);
return 0;
}
static int nsim_macsec_del_rxsa(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
struct nsim_secy *secy;
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
secy = &ns->macsec.nsim_secy[idx];
idx = nsim_macsec_find_rxsc(secy, ctx->sa.rx_sa->sc->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in RXSC table\n",
__func__, sci_to_cpu(ctx->sa.rx_sa->sc->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: RXSC with sci %016llx, AN %u\n",
__func__, sci_to_cpu(ctx->sa.rx_sa->sc->sci), ctx->sa.assoc_num);
return 0;
}
static int nsim_macsec_add_txsa(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: SECY with sci %016llx, AN %u\n",
__func__, sci_to_cpu(ctx->secy->sci), ctx->sa.assoc_num);
return 0;
}
static int nsim_macsec_upd_txsa(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: SECY with sci %016llx, AN %u\n",
__func__, sci_to_cpu(ctx->secy->sci), ctx->sa.assoc_num);
return 0;
}
static int nsim_macsec_del_txsa(struct macsec_context *ctx)
{
struct netdevsim *ns = netdev_priv(ctx->netdev);
int idx;
idx = nsim_macsec_find_secy(ns, ctx->secy->sci);
if (idx < 0) {
netdev_err(ctx->netdev, "%s: sci %016llx not found in secy table\n",
__func__, sci_to_cpu(ctx->secy->sci));
return -ENOENT;
}
netdev_dbg(ctx->netdev, "%s: SECY with sci %016llx, AN %u\n",
__func__, sci_to_cpu(ctx->secy->sci), ctx->sa.assoc_num);
return 0;
}
static const struct macsec_ops nsim_macsec_ops = {
.mdo_add_secy = nsim_macsec_add_secy,
.mdo_upd_secy = nsim_macsec_upd_secy,
.mdo_del_secy = nsim_macsec_del_secy,
.mdo_add_rxsc = nsim_macsec_add_rxsc,
.mdo_upd_rxsc = nsim_macsec_upd_rxsc,
.mdo_del_rxsc = nsim_macsec_del_rxsc,
.mdo_add_rxsa = nsim_macsec_add_rxsa,
.mdo_upd_rxsa = nsim_macsec_upd_rxsa,
.mdo_del_rxsa = nsim_macsec_del_rxsa,
.mdo_add_txsa = nsim_macsec_add_txsa,
.mdo_upd_txsa = nsim_macsec_upd_txsa,
.mdo_del_txsa = nsim_macsec_del_txsa,
};
void nsim_macsec_init(struct netdevsim *ns)
{
ns->netdev->macsec_ops = &nsim_macsec_ops;
ns->netdev->features |= NETIF_F_HW_MACSEC;
memset(&ns->macsec, 0, sizeof(ns->macsec));
}
void nsim_macsec_teardown(struct netdevsim *ns)
{
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,489 @@
/*
* 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/device.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/kernel.h>
#include <linux/if_vlan.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/ptp_mock.h>
#include <net/devlink.h>
#include <net/udp_tunnel.h>
#include <net/xdp.h>
#include <net/macsec.h>
#define DRV_NAME "netdevsim"
#define NSIM_XDP_MAX_MTU 4000
#define NSIM_EA(extack, msg) NL_SET_ERR_MSG_MOD((extack), msg)
#define NSIM_IPSEC_MAX_SA_COUNT 33
#define NSIM_IPSEC_VALID BIT(31)
#define NSIM_UDP_TUNNEL_N_PORTS 4
#define NSIM_HDS_THRESHOLD_MAX 1024
struct nsim_sa {
struct xfrm_state *xs;
__be32 ipaddr[4];
u32 key[4];
u32 salt;
bool used;
bool crypt;
bool rx;
};
struct nsim_ipsec {
struct nsim_sa sa[NSIM_IPSEC_MAX_SA_COUNT];
struct dentry *pfile;
u32 count;
u32 tx;
};
#define NSIM_MACSEC_MAX_SECY_COUNT 3
#define NSIM_MACSEC_MAX_RXSC_COUNT 1
struct nsim_rxsc {
sci_t sci;
bool used;
};
struct nsim_secy {
sci_t sci;
struct nsim_rxsc nsim_rxsc[NSIM_MACSEC_MAX_RXSC_COUNT];
u8 nsim_rxsc_count;
bool used;
};
struct nsim_macsec {
struct nsim_secy nsim_secy[NSIM_MACSEC_MAX_SECY_COUNT];
u8 nsim_secy_count;
};
struct nsim_vlan {
DECLARE_BITMAP(ctag, VLAN_N_VID);
DECLARE_BITMAP(stag, VLAN_N_VID);
};
struct nsim_ethtool_pauseparam {
bool rx;
bool tx;
bool report_stats_rx;
bool report_stats_tx;
};
struct nsim_ethtool {
u32 get_err;
u32 set_err;
u32 channels;
struct nsim_ethtool_pauseparam pauseparam;
struct ethtool_coalesce coalesce;
struct ethtool_ringparam ring;
struct ethtool_fecparam fec;
};
struct nsim_rq {
struct napi_struct napi;
struct sk_buff_head skb_queue;
struct page_pool *page_pool;
struct hrtimer napi_timer;
};
struct netdevsim {
struct net_device *netdev;
struct nsim_dev *nsim_dev;
struct nsim_dev_port *nsim_dev_port;
struct mock_phc *phc;
struct nsim_rq **rq;
int rq_reset_mode;
struct {
atomic64_t rx_packets;
atomic64_t rx_bytes;
atomic64_t tx_packets;
atomic64_t tx_bytes;
struct psp_dev __rcu *dev;
struct dentry *rereg;
struct mutex rereg_lock;
u32 spi;
u32 assoc_cnt;
} psp;
struct nsim_bus_dev *nsim_bus_dev;
struct bpf_prog *bpf_offloaded;
u32 bpf_offloaded_id;
struct xdp_attachment_info xdp;
struct xdp_attachment_info xdp_hw;
bool bpf_tc_accept;
bool bpf_tc_non_bound_accept;
bool bpf_xdpdrv_accept;
bool bpf_xdpoffload_accept;
bool bpf_map_accept;
struct nsim_ipsec ipsec;
struct nsim_macsec macsec;
struct nsim_vlan vlan;
struct {
u32 inject_error;
u32 __ports[2][NSIM_UDP_TUNNEL_N_PORTS];
u32 (*ports)[NSIM_UDP_TUNNEL_N_PORTS];
struct dentry *ddir;
struct debugfs_u32_array dfs_ports[2];
} udp_ports;
struct page *page;
struct dentry *pp_dfs;
struct dentry *qr_dfs;
struct dentry *vlan_dfs;
struct dentry *ethtool_ddir;
struct nsim_ethtool ethtool;
struct netdevsim __rcu *peer;
struct notifier_block nb;
struct netdev_net_notifier nn;
};
struct netdevsim *nsim_create(struct nsim_dev *nsim_dev,
struct nsim_dev_port *nsim_dev_port,
u8 perm_addr[ETH_ALEN]);
void nsim_destroy(struct netdevsim *ns);
bool netdev_is_nsim(struct net_device *dev);
void nsim_ethtool_init(struct netdevsim *ns);
void nsim_ethtool_fini(struct netdevsim *ns);
void nsim_udp_tunnels_debugfs_create(struct nsim_dev *nsim_dev);
int nsim_udp_tunnels_info_create(struct nsim_dev *nsim_dev,
struct net_device *dev);
void nsim_udp_tunnels_info_destroy(struct net_device *dev);
#ifdef CONFIG_BPF_SYSCALL
int nsim_bpf_dev_init(struct nsim_dev *nsim_dev);
void nsim_bpf_dev_exit(struct nsim_dev *nsim_dev);
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_dev_init(struct nsim_dev *nsim_dev)
{
return 0;
}
static inline void nsim_bpf_dev_exit(struct nsim_dev *nsim_dev)
{
}
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 -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
enum nsim_resource_id {
NSIM_RESOURCE_NONE, /* DEVLINK_RESOURCE_ID_PARENT_TOP */
NSIM_RESOURCE_IPV4,
NSIM_RESOURCE_IPV4_FIB,
NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV6,
NSIM_RESOURCE_IPV6_FIB,
NSIM_RESOURCE_IPV6_FIB_RULES,
NSIM_RESOURCE_NEXTHOPS,
};
enum nsim_port_resource_id {
NSIM_PORT_RESOURCE_TEST = 1,
};
struct nsim_dev_health {
struct devlink_health_reporter *empty_reporter;
struct devlink_health_reporter *dummy_reporter;
struct dentry *ddir;
char *recovered_break_msg;
u32 binary_len;
bool fail_recover;
};
int nsim_dev_health_init(struct nsim_dev *nsim_dev, struct devlink *devlink);
void nsim_dev_health_exit(struct nsim_dev *nsim_dev);
struct nsim_dev_hwstats_netdev {
struct list_head list;
struct net_device *netdev;
struct rtnl_hw_stats64 stats;
bool enabled;
bool fail_enable;
};
struct nsim_dev_hwstats {
struct dentry *ddir;
struct dentry *l3_ddir;
struct mutex hwsdev_list_lock; /* protects hwsdev list(s) */
struct list_head l3_list;
struct notifier_block netdevice_nb;
struct delayed_work traffic_dw;
};
int nsim_dev_hwstats_init(struct nsim_dev *nsim_dev);
void nsim_dev_hwstats_exit(struct nsim_dev *nsim_dev);
#if IS_ENABLED(CONFIG_PSAMPLE)
int nsim_dev_psample_init(struct nsim_dev *nsim_dev);
void nsim_dev_psample_exit(struct nsim_dev *nsim_dev);
#else
static inline int nsim_dev_psample_init(struct nsim_dev *nsim_dev)
{
return 0;
}
static inline void nsim_dev_psample_exit(struct nsim_dev *nsim_dev)
{
}
#endif
enum nsim_dev_port_type {
NSIM_DEV_PORT_TYPE_PF,
NSIM_DEV_PORT_TYPE_VF,
};
#define NSIM_DEV_VF_PORT_INDEX_BASE 128
#define NSIM_DEV_VF_PORT_INDEX_MAX UINT_MAX
struct nsim_dev_port {
struct list_head list;
struct devlink_port devlink_port;
unsigned int port_index;
enum nsim_dev_port_type port_type;
struct dentry *ddir;
struct dentry *rate_parent;
char *parent_name;
u32 tc_bw[DEVLINK_RATE_TCS_MAX];
struct netdevsim *ns;
};
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;
};
struct nsim_dev {
struct nsim_bus_dev *nsim_bus_dev;
struct nsim_fib_data *fib_data;
struct nsim_trap_data *trap_data;
struct dentry *ddir;
struct dentry *ports_ddir;
struct dentry *take_snapshot;
struct dentry *nodes_ddir;
struct nsim_vf_config *vfconfigs;
struct bpf_offload_dev *bpf_dev;
bool bpf_bind_accept;
bool bpf_bind_verifier_accept;
u32 bpf_bind_verifier_delay;
struct dentry *ddir_bpf_bound_progs;
u32 prog_id_gen;
struct list_head bpf_bound_progs;
struct list_head bpf_bound_maps;
struct mutex progs_list_lock;
struct netdev_phys_item_id switch_id;
struct list_head port_list;
bool fw_update_status;
u32 fw_update_overwrite_mask;
u32 fw_update_flash_chunk_time_ms;
u32 max_macs;
bool test1;
u32 test2;
bool dont_allow_reload;
bool fail_reload;
struct devlink_region *dummy_region;
struct nsim_dev_health health;
struct nsim_dev_hwstats hwstats;
struct flow_action_cookie *fa_cookie;
spinlock_t fa_cookie_lock; /* protects fa_cookie */
bool fail_trap_group_set;
bool fail_trap_policer_set;
bool fail_trap_policer_counter_get;
bool fail_trap_drop_counter_get;
struct {
struct udp_tunnel_nic_shared utn_shared;
u32 __ports[2][NSIM_UDP_TUNNEL_N_PORTS];
bool sync_all;
bool open_only;
bool ipv4_only;
bool shared;
bool static_iana_vxlan;
} udp_ports;
struct nsim_dev_psample *psample;
u16 esw_mode;
};
static inline bool nsim_esw_mode_is_legacy(struct nsim_dev *nsim_dev)
{
return nsim_dev->esw_mode == DEVLINK_ESWITCH_MODE_LEGACY;
}
static inline bool nsim_esw_mode_is_switchdev(struct nsim_dev *nsim_dev)
{
return nsim_dev->esw_mode == DEVLINK_ESWITCH_MODE_SWITCHDEV;
}
static inline struct net *nsim_dev_net(struct nsim_dev *nsim_dev)
{
return devlink_net(priv_to_devlink(nsim_dev));
}
int nsim_dev_init(void);
void nsim_dev_exit(void);
int nsim_drv_probe(struct nsim_bus_dev *nsim_bus_dev);
void nsim_drv_remove(struct nsim_bus_dev *nsim_bus_dev);
int nsim_drv_port_add(struct nsim_bus_dev *nsim_bus_dev,
enum nsim_dev_port_type type, unsigned int port_index,
u8 perm_addr[ETH_ALEN]);
int nsim_drv_port_del(struct nsim_bus_dev *nsim_bus_dev,
enum nsim_dev_port_type type,
unsigned int port_index);
int nsim_drv_configure_vfs(struct nsim_bus_dev *nsim_bus_dev,
unsigned int num_vfs);
unsigned int nsim_dev_get_vfs(struct nsim_dev *nsim_dev);
struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
struct netlink_ext_ack *extack);
void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *fib_data);
u64 nsim_fib_get_val(struct nsim_fib_data *fib_data,
enum nsim_resource_id res_id, bool max);
static inline bool nsim_dev_port_is_pf(struct nsim_dev_port *nsim_dev_port)
{
return nsim_dev_port->port_type == NSIM_DEV_PORT_TYPE_PF;
}
static inline bool nsim_dev_port_is_vf(struct nsim_dev_port *nsim_dev_port)
{
return nsim_dev_port->port_type == NSIM_DEV_PORT_TYPE_VF;
}
#if IS_ENABLED(CONFIG_XFRM_OFFLOAD)
void nsim_ipsec_init(struct netdevsim *ns);
void nsim_ipsec_teardown(struct netdevsim *ns);
bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb);
#else
static inline void nsim_ipsec_init(struct netdevsim *ns)
{
}
static inline void nsim_ipsec_teardown(struct netdevsim *ns)
{
}
static inline bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb)
{
return true;
}
#endif
#if IS_ENABLED(CONFIG_MACSEC)
void nsim_macsec_init(struct netdevsim *ns);
void nsim_macsec_teardown(struct netdevsim *ns);
#else
static inline void nsim_macsec_init(struct netdevsim *ns)
{
}
static inline void nsim_macsec_teardown(struct netdevsim *ns)
{
}
#endif
#if IS_ENABLED(CONFIG_INET_PSP)
int nsim_psp_init(struct netdevsim *ns);
void nsim_psp_uninit(struct netdevsim *ns);
void nsim_psp_handle_ext(struct sk_buff *skb, struct skb_ext *psp_ext);
enum skb_drop_reason
nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,
struct netdevsim *peer_ns, struct skb_ext **psp_ext);
#else
static inline int nsim_psp_init(struct netdevsim *ns) { return 0; }
static inline void nsim_psp_uninit(struct netdevsim *ns) {}
static inline enum skb_drop_reason
nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,
struct netdevsim *peer_ns, struct skb_ext **psp_ext)
{
return 0;
}
static inline void
nsim_psp_handle_ext(struct sk_buff *skb, struct skb_ext *psp_ext) {}
#endif
int nsim_setup_tc(struct net_device *dev, enum tc_setup_type type,
void *type_data);
struct nsim_bus_dev {
struct device dev;
struct list_head list;
unsigned int port_count;
unsigned int num_queues; /* Number of queues for each port on this bus */
struct net *initial_net; /* Purpose of this is to carry net pointer
* during the probe time only.
*/
unsigned int max_vfs;
unsigned int num_vfs;
bool init;
};
int nsim_bus_init(void);
void nsim_bus_exit(void);
+265
View File
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021 Mellanox Technologies. All rights reserved */
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/etherdevice.h>
#include <linux/inet.h>
#include <linux/kernel.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <net/devlink.h>
#include <net/ip.h>
#include <net/psample.h>
#include <uapi/linux/ip.h>
#include <uapi/linux/udp.h>
#include "netdevsim.h"
#define NSIM_PSAMPLE_REPORT_INTERVAL_MS 100
#define NSIM_PSAMPLE_INVALID_TC 0xFFFF
#define NSIM_PSAMPLE_L4_DATA_LEN 100
struct nsim_dev_psample {
struct delayed_work psample_dw;
struct dentry *ddir;
struct psample_group *group;
u32 rate;
u32 group_num;
u32 trunc_size;
int in_ifindex;
int out_ifindex;
u16 out_tc;
u64 out_tc_occ_max;
u64 latency_max;
bool is_active;
};
static struct sk_buff *nsim_dev_psample_skb_build(void)
{
int tot_len, data_len = NSIM_PSAMPLE_L4_DATA_LEN;
struct sk_buff *skb;
struct udphdr *udph;
struct ethhdr *eth;
struct iphdr *iph;
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
if (!skb)
return NULL;
tot_len = sizeof(struct iphdr) + sizeof(struct udphdr) + data_len;
skb_reset_mac_header(skb);
eth = skb_put(skb, sizeof(struct ethhdr));
eth_random_addr(eth->h_dest);
eth_random_addr(eth->h_source);
eth->h_proto = htons(ETH_P_IP);
skb->protocol = htons(ETH_P_IP);
skb_set_network_header(skb, skb->len);
iph = skb_put(skb, sizeof(struct iphdr));
iph->protocol = IPPROTO_UDP;
iph->saddr = in_aton("192.0.2.1");
iph->daddr = in_aton("198.51.100.1");
iph->version = 0x4;
iph->frag_off = 0;
iph->ihl = 0x5;
iph->tot_len = htons(tot_len);
iph->id = 0;
iph->ttl = 100;
iph->check = 0;
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
skb_set_transport_header(skb, skb->len);
udph = skb_put_zero(skb, sizeof(struct udphdr) + data_len);
get_random_bytes(&udph->source, sizeof(u16));
get_random_bytes(&udph->dest, sizeof(u16));
udph->len = htons(sizeof(struct udphdr) + data_len);
return skb;
}
static void nsim_dev_psample_md_prepare(const struct nsim_dev_psample *psample,
struct psample_metadata *md,
unsigned int len)
{
md->trunc_size = psample->trunc_size ? psample->trunc_size : len;
md->in_ifindex = psample->in_ifindex;
md->out_ifindex = psample->out_ifindex;
if (psample->out_tc != NSIM_PSAMPLE_INVALID_TC) {
md->out_tc = psample->out_tc;
md->out_tc_valid = 1;
}
if (psample->out_tc_occ_max) {
u64 out_tc_occ;
out_tc_occ = get_random_u64();
md->out_tc_occ = out_tc_occ & (psample->out_tc_occ_max - 1);
md->out_tc_occ_valid = 1;
}
if (psample->latency_max) {
u64 latency;
latency = get_random_u64();
md->latency = latency & (psample->latency_max - 1);
md->latency_valid = 1;
}
}
static void nsim_dev_psample_report_work(struct work_struct *work)
{
struct nsim_dev_psample *psample;
struct psample_metadata md = {};
struct sk_buff *skb;
unsigned long delay;
psample = container_of(work, struct nsim_dev_psample, psample_dw.work);
skb = nsim_dev_psample_skb_build();
if (!skb)
goto out;
nsim_dev_psample_md_prepare(psample, &md, skb->len);
psample_sample_packet(psample->group, skb, psample->rate, &md);
consume_skb(skb);
out:
delay = msecs_to_jiffies(NSIM_PSAMPLE_REPORT_INTERVAL_MS);
schedule_delayed_work(&psample->psample_dw, delay);
}
static int nsim_dev_psample_enable(struct nsim_dev *nsim_dev)
{
struct nsim_dev_psample *psample = nsim_dev->psample;
struct devlink *devlink;
unsigned long delay;
if (psample->is_active)
return -EBUSY;
devlink = priv_to_devlink(nsim_dev);
psample->group = psample_group_get(devlink_net(devlink),
psample->group_num);
if (!psample->group)
return -EINVAL;
delay = msecs_to_jiffies(NSIM_PSAMPLE_REPORT_INTERVAL_MS);
schedule_delayed_work(&psample->psample_dw, delay);
psample->is_active = true;
return 0;
}
static int nsim_dev_psample_disable(struct nsim_dev *nsim_dev)
{
struct nsim_dev_psample *psample = nsim_dev->psample;
if (!psample->is_active)
return -EINVAL;
psample->is_active = false;
cancel_delayed_work_sync(&psample->psample_dw);
psample_group_put(psample->group);
return 0;
}
static ssize_t nsim_dev_psample_enable_write(struct file *file,
const char __user *data,
size_t count, loff_t *ppos)
{
struct nsim_dev *nsim_dev = file->private_data;
bool enable;
int err;
err = kstrtobool_from_user(data, count, &enable);
if (err)
return err;
if (enable)
err = nsim_dev_psample_enable(nsim_dev);
else
err = nsim_dev_psample_disable(nsim_dev);
return err ? err : count;
}
static const struct file_operations nsim_psample_enable_fops = {
.open = simple_open,
.write = nsim_dev_psample_enable_write,
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
};
int nsim_dev_psample_init(struct nsim_dev *nsim_dev)
{
struct nsim_dev_psample *psample;
int err;
psample = kzalloc_obj(*psample);
if (!psample)
return -ENOMEM;
nsim_dev->psample = psample;
INIT_DELAYED_WORK(&psample->psample_dw, nsim_dev_psample_report_work);
psample->ddir = debugfs_create_dir("psample", nsim_dev->ddir);
if (IS_ERR(psample->ddir)) {
err = PTR_ERR(psample->ddir);
goto err_psample_free;
}
/* Populate sampling parameters with sane defaults. */
psample->rate = 100;
debugfs_create_u32("rate", 0600, psample->ddir, &psample->rate);
psample->group_num = 10;
debugfs_create_u32("group_num", 0600, psample->ddir,
&psample->group_num);
psample->trunc_size = 0;
debugfs_create_u32("trunc_size", 0600, psample->ddir,
&psample->trunc_size);
psample->in_ifindex = 1;
debugfs_create_u32("in_ifindex", 0600, psample->ddir,
&psample->in_ifindex);
psample->out_ifindex = 2;
debugfs_create_u32("out_ifindex", 0600, psample->ddir,
&psample->out_ifindex);
psample->out_tc = 0;
debugfs_create_u16("out_tc", 0600, psample->ddir, &psample->out_tc);
psample->out_tc_occ_max = 10000;
debugfs_create_u64("out_tc_occ_max", 0600, psample->ddir,
&psample->out_tc_occ_max);
psample->latency_max = 50;
debugfs_create_u64("latency_max", 0600, psample->ddir,
&psample->latency_max);
debugfs_create_file("enable", 0200, psample->ddir, nsim_dev,
&nsim_psample_enable_fops);
return 0;
err_psample_free:
kfree(nsim_dev->psample);
return err;
}
void nsim_dev_psample_exit(struct nsim_dev *nsim_dev)
{
debugfs_remove_recursive(nsim_dev->psample->ddir);
if (nsim_dev->psample->is_active) {
cancel_delayed_work_sync(&nsim_dev->psample->psample_dw);
psample_group_put(nsim_dev->psample->group);
}
kfree(nsim_dev->psample);
}
+287
View File
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ip.h>
#include <linux/skbuff.h>
#include <net/ip6_checksum.h>
#include <net/psp.h>
#include <net/sock.h>
#include "netdevsim.h"
void nsim_psp_handle_ext(struct sk_buff *skb, struct skb_ext *psp_ext)
{
if (psp_ext)
__skb_ext_set(skb, SKB_EXT_PSP, psp_ext);
}
enum skb_drop_reason
nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,
struct netdevsim *peer_ns, struct skb_ext **psp_ext)
{
enum skb_drop_reason rc = 0;
struct psp_dev *peer_psd;
struct psp_assoc *pas;
struct net *net;
int psp_len;
void **ptr;
rcu_read_lock();
pas = psp_skb_get_assoc_rcu(skb);
if (!pas) {
rc = SKB_NOT_DROPPED_YET;
goto out_unlock;
}
if (!skb_transport_header_was_set(skb)) {
rc = SKB_DROP_REASON_PSP_OUTPUT;
goto out_unlock;
}
ptr = psp_assoc_drv_data(pas);
if (*ptr != ns) {
rc = SKB_DROP_REASON_PSP_OUTPUT;
goto out_unlock;
}
net = sock_net(skb->sk);
if (!psp_dev_encapsulate(net, skb, pas->tx.spi, pas->version, 0)) {
rc = SKB_DROP_REASON_PSP_OUTPUT;
goto out_unlock;
}
psp_len = skb->len - skb_inner_transport_offset(skb);
atomic64_inc(&ns->psp.tx_packets);
atomic64_add(psp_len, &ns->psp.tx_bytes);
/* Now pretend we just received this frame */
peer_psd = rcu_dereference(peer_ns->psp.dev);
if (peer_psd && peer_psd->config.versions & (1 << pas->version)) {
bool strip_icv = false;
u8 generation;
/* We cheat a bit and put the generation in the key.
* In real life if generation was too old, then decryption would
* fail. Here, we just make it so a bad key causes a bad
* generation too, and psp_sk_rx_policy_check() will fail.
*/
generation = pas->tx.key[0];
skb_ext_reset(skb);
skb->mac_len = ETH_HLEN;
if (psp_dev_rcv(skb, peer_psd->id, generation, strip_icv)) {
rc = SKB_DROP_REASON_PSP_OUTPUT;
goto out_unlock;
}
*psp_ext = skb->extensions;
refcount_inc(&(*psp_ext)->refcnt);
skb->decrypted = 1;
atomic64_inc(&peer_ns->psp.rx_packets);
atomic64_add(psp_len, &peer_ns->psp.rx_bytes);
} else {
struct ipv6hdr *ip6h __maybe_unused;
struct iphdr *iph;
struct udphdr *uh;
__wsum csum;
/* Do not decapsulate. Receive the skb with the udp and psp
* headers still there as if this is a normal udp packet.
* psp_dev_encapsulate() sets udp checksum to 0, so we need to
* provide a valid checksum here, so the skb isn't dropped.
*/
uh = udp_hdr(skb);
csum = skb_checksum(skb, skb_transport_offset(skb),
ntohs(uh->len), 0);
switch (skb->protocol) {
case htons(ETH_P_IP):
iph = ip_hdr(skb);
uh->check = udp_v4_check(ntohs(uh->len), iph->saddr,
iph->daddr, csum);
break;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
ip6h = ipv6_hdr(skb);
uh->check = udp_v6_check(ntohs(uh->len), &ip6h->saddr,
&ip6h->daddr, csum);
break;
#endif
}
uh->check = uh->check ?: CSUM_MANGLED_0;
skb->ip_summed = CHECKSUM_NONE;
}
out_unlock:
rcu_read_unlock();
return rc;
}
static int
nsim_psp_set_config(struct psp_dev *psd, struct psp_dev_config *conf,
struct netlink_ext_ack *extack)
{
return 0;
}
static int
nsim_rx_spi_alloc(struct psp_dev *psd, u32 version,
struct psp_key_parsed *assoc,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = psd->drv_priv;
int i;
/* Check if incrementing the spi would change the phase bit */
if ((ns->psp.spi & PSP_SPI_KEY_ID) == PSP_SPI_KEY_ID) {
NL_SET_ERR_MSG(extack, "SPI space exhausted");
return -ENOSPC;
}
assoc->spi = cpu_to_be32(++ns->psp.spi);
assoc->key[0] = psd->generation;
for (i = 1; i < PSP_MAX_KEY; i++)
assoc->key[i] = ns->psp.spi + i;
return 0;
}
static int nsim_assoc_add(struct psp_dev *psd, struct psp_assoc *pas,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = psd->drv_priv;
void **ptr = psp_assoc_drv_data(pas);
/* Copy drv_priv from psd to assoc */
*ptr = psd->drv_priv;
ns->psp.assoc_cnt++;
return 0;
}
static int nsim_key_rotate(struct psp_dev *psd, struct netlink_ext_ack *extack)
{
struct netdevsim *ns = psd->drv_priv;
/* Flip key phase and reset SPI to 0 within that space
* (will be pre-incremented, as 0 is an invalid SPI).
*/
if (ns->psp.spi & PSP_SPI_KEY_PHASE)
ns->psp.spi = 0;
else
ns->psp.spi = PSP_SPI_KEY_PHASE;
return 0;
}
static void nsim_assoc_del(struct psp_dev *psd, struct psp_assoc *pas)
{
struct netdevsim *ns = psd->drv_priv;
void **ptr = psp_assoc_drv_data(pas);
*ptr = NULL;
ns->psp.assoc_cnt--;
}
static void nsim_get_stats(struct psp_dev *psd, struct psp_dev_stats *stats)
{
struct netdevsim *ns = psd->drv_priv;
/* WARNING: do *not* blindly zero stats in real drivers!
* All required stats must be reported by the device!
*/
memset(stats, 0, sizeof(struct psp_dev_stats));
stats->rx_bytes = atomic64_read(&ns->psp.rx_bytes);
stats->rx_packets = atomic64_read(&ns->psp.rx_packets);
stats->tx_bytes = atomic64_read(&ns->psp.tx_bytes);
stats->tx_packets = atomic64_read(&ns->psp.tx_packets);
}
static struct psp_dev_ops nsim_psp_ops = {
.set_config = nsim_psp_set_config,
.rx_spi_alloc = nsim_rx_spi_alloc,
.tx_key_add = nsim_assoc_add,
.tx_key_del = nsim_assoc_del,
.key_rotate = nsim_key_rotate,
.get_stats = nsim_get_stats,
};
static struct psp_dev_caps nsim_psp_caps = {
.versions = 1 << PSP_VERSION_HDR0_AES_GCM_128 |
1 << PSP_VERSION_HDR0_AES_GMAC_128 |
1 << PSP_VERSION_HDR0_AES_GCM_256 |
1 << PSP_VERSION_HDR0_AES_GMAC_256,
.assoc_drv_spc = sizeof(void *),
};
static void __nsim_psp_uninit(struct netdevsim *ns, bool teardown)
{
struct psp_dev *psd;
psd = rcu_dereference_protected(ns->psp.dev,
teardown ||
lockdep_is_held(&ns->psp.rereg_lock));
if (psd) {
rcu_assign_pointer(ns->psp.dev, NULL);
synchronize_rcu();
psp_dev_unregister(psd);
}
WARN_ON(ns->psp.assoc_cnt);
}
void nsim_psp_uninit(struct netdevsim *ns)
{
debugfs_remove(ns->psp.rereg);
mutex_destroy(&ns->psp.rereg_lock);
__nsim_psp_uninit(ns, true);
}
static ssize_t
nsim_psp_rereg_write(struct file *file, const char __user *data, size_t count,
loff_t *ppos)
{
struct netdevsim *ns = file->private_data;
struct psp_dev *psd;
ssize_t ret;
mutex_lock(&ns->psp.rereg_lock);
__nsim_psp_uninit(ns, false);
psd = psp_dev_create(ns->netdev, &nsim_psp_ops, &nsim_psp_caps, ns);
if (IS_ERR(psd)) {
ret = PTR_ERR(psd);
goto out;
}
rcu_assign_pointer(ns->psp.dev, psd);
ret = count;
out:
mutex_unlock(&ns->psp.rereg_lock);
return ret;
}
static const struct file_operations nsim_psp_rereg_fops = {
.open = simple_open,
.write = nsim_psp_rereg_write,
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
};
int nsim_psp_init(struct netdevsim *ns)
{
struct dentry *ddir = ns->nsim_dev_port->ddir;
struct psp_dev *psd;
psd = psp_dev_create(ns->netdev, &nsim_psp_ops, &nsim_psp_caps, ns);
if (IS_ERR(psd))
return PTR_ERR(psd);
rcu_assign_pointer(ns->psp.dev, psd);
mutex_init(&ns->psp.rereg_lock);
ns->psp.rereg = debugfs_create_file("psp_rereg", 0200, ddir, ns,
&nsim_psp_rereg_fops);
return 0;
}
+97
View File
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/netdevice.h>
#include <net/pkt_sched.h>
#include <net/pkt_cls.h>
#include "netdevsim.h"
static int
nsim_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
{
struct flow_cls_common_offload *common = type_data;
int err = 0;
switch (type) {
case TC_SETUP_CLSBPF:
err = nsim_bpf_setup_tc_block_cb(type, type_data, cb_priv);
break;
case TC_SETUP_CLSFLOWER:
break;
default:
NSIM_EA(common->extack, "offload type not supported");
err = -EOPNOTSUPP;
break;
}
return err;
}
static void nsim_taprio_stats(struct tc_taprio_qopt_stats *stats)
{
stats->window_drops = 0;
stats->tx_overruns = 0;
}
static int nsim_setup_tc_taprio(struct net_device *dev,
struct tc_taprio_qopt_offload *offload)
{
int err = 0;
switch (offload->cmd) {
case TAPRIO_CMD_REPLACE:
case TAPRIO_CMD_DESTROY:
break;
case TAPRIO_CMD_STATS:
nsim_taprio_stats(&offload->stats);
break;
default:
err = -EOPNOTSUPP;
}
return err;
}
static int nsim_setup_tc_ets(struct net_device *dev,
struct tc_ets_qopt_offload *offload)
{
int err = 0;
switch (offload->command) {
case TC_ETS_REPLACE:
case TC_ETS_DESTROY:
break;
case TC_ETS_STATS:
_bstats_update(offload->stats.bstats, 0, 0);
break;
default:
err = -EOPNOTSUPP;
}
return err;
}
static LIST_HEAD(nsim_block_cb_list);
int
nsim_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data)
{
struct netdevsim *ns = netdev_priv(dev);
switch (type) {
case TC_SETUP_QDISC_TAPRIO:
return nsim_setup_tc_taprio(dev, type_data);
case TC_SETUP_QDISC_ETS:
return nsim_setup_tc_ets(dev, type_data);
case TC_SETUP_BLOCK:
return flow_block_cb_setup_simple(type_data,
&nsim_block_cb_list,
nsim_setup_tc_block_cb,
ns, ns, true);
case TC_SETUP_FT:
return 0;
default:
return -EOPNOTSUPP;
}
}
ALLOW_ERROR_INJECTION(nsim_setup_tc, ERRNO);
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2020 Facebook Inc.
#include <linux/debugfs.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <net/udp_tunnel.h>
#include "netdevsim.h"
static int
nsim_udp_tunnel_set_port(struct net_device *dev, unsigned int table,
unsigned int entry, struct udp_tunnel_info *ti)
{
struct netdevsim *ns = netdev_priv(dev);
int ret;
ret = -ns->udp_ports.inject_error;
ns->udp_ports.inject_error = 0;
if (!ret) {
if (ns->udp_ports.ports[table][entry]) {
WARN(1, "entry already in use\n");
ret = -EBUSY;
} else {
ns->udp_ports.ports[table][entry] =
be16_to_cpu(ti->port) << 16 | ti->type;
}
}
netdev_info(dev, "set [%d, %d] type %d family %d port %d - %d\n",
table, entry, ti->type, ti->sa_family, ntohs(ti->port),
ret);
return ret;
}
static int
nsim_udp_tunnel_unset_port(struct net_device *dev, unsigned int table,
unsigned int entry, struct udp_tunnel_info *ti)
{
struct netdevsim *ns = netdev_priv(dev);
int ret;
ret = -ns->udp_ports.inject_error;
ns->udp_ports.inject_error = 0;
if (!ret) {
u32 val = be16_to_cpu(ti->port) << 16 | ti->type;
if (val == ns->udp_ports.ports[table][entry]) {
ns->udp_ports.ports[table][entry] = 0;
} else {
WARN(1, "entry not installed %x vs %x\n",
val, ns->udp_ports.ports[table][entry]);
ret = -ENOENT;
}
}
netdev_info(dev, "unset [%d, %d] type %d family %d port %d - %d\n",
table, entry, ti->type, ti->sa_family, ntohs(ti->port),
ret);
return ret;
}
static int
nsim_udp_tunnel_sync_table(struct net_device *dev, unsigned int table)
{
struct netdevsim *ns = netdev_priv(dev);
struct udp_tunnel_info ti;
unsigned int i;
int ret;
ret = -ns->udp_ports.inject_error;
ns->udp_ports.inject_error = 0;
for (i = 0; i < NSIM_UDP_TUNNEL_N_PORTS; i++) {
udp_tunnel_nic_get_port(dev, table, i, &ti);
ns->udp_ports.ports[table][i] =
be16_to_cpu(ti.port) << 16 | ti.type;
}
return ret;
}
static const struct udp_tunnel_nic_info nsim_udp_tunnel_info = {
.set_port = nsim_udp_tunnel_set_port,
.unset_port = nsim_udp_tunnel_unset_port,
.sync_table = nsim_udp_tunnel_sync_table,
.tables = {
{
.n_entries = NSIM_UDP_TUNNEL_N_PORTS,
.tunnel_types = UDP_TUNNEL_TYPE_VXLAN,
},
{
.n_entries = NSIM_UDP_TUNNEL_N_PORTS,
.tunnel_types = UDP_TUNNEL_TYPE_GENEVE |
UDP_TUNNEL_TYPE_VXLAN_GPE,
},
},
};
static ssize_t
nsim_udp_tunnels_info_reset_write(struct file *file, const char __user *data,
size_t count, loff_t *ppos)
{
struct net_device *dev = file->private_data;
struct netdevsim *ns = netdev_priv(dev);
if (dev->reg_state == NETREG_REGISTERED) {
memset(ns->udp_ports.ports, 0, sizeof(ns->udp_ports.__ports));
udp_tunnel_nic_reset_ntf(dev);
}
return count;
}
static const struct file_operations nsim_udp_tunnels_info_reset_fops = {
.open = simple_open,
.write = nsim_udp_tunnels_info_reset_write,
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
};
int nsim_udp_tunnels_info_create(struct nsim_dev *nsim_dev,
struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
struct udp_tunnel_nic_info *info;
if (nsim_dev->udp_ports.shared && nsim_dev->udp_ports.open_only) {
dev_err(&nsim_dev->nsim_bus_dev->dev,
"shared can't be used in conjunction with open_only\n");
return -EINVAL;
}
if (!nsim_dev->udp_ports.shared)
ns->udp_ports.ports = ns->udp_ports.__ports;
else
ns->udp_ports.ports = nsim_dev->udp_ports.__ports;
ns->udp_ports.ddir = debugfs_create_dir("udp_ports",
ns->nsim_dev_port->ddir);
debugfs_create_u32("inject_error", 0600, ns->udp_ports.ddir,
&ns->udp_ports.inject_error);
ns->udp_ports.dfs_ports[0].array = ns->udp_ports.ports[0];
ns->udp_ports.dfs_ports[0].n_elements = NSIM_UDP_TUNNEL_N_PORTS;
debugfs_create_u32_array("table0", 0400, ns->udp_ports.ddir,
&ns->udp_ports.dfs_ports[0]);
ns->udp_ports.dfs_ports[1].array = ns->udp_ports.ports[1];
ns->udp_ports.dfs_ports[1].n_elements = NSIM_UDP_TUNNEL_N_PORTS;
debugfs_create_u32_array("table1", 0400, ns->udp_ports.ddir,
&ns->udp_ports.dfs_ports[1]);
debugfs_create_file("reset", 0200, ns->udp_ports.ddir,
dev, &nsim_udp_tunnels_info_reset_fops);
/* Note: it's not normal to allocate the info struct like this!
* Drivers are expected to use a static const one, here we're testing.
*/
info = kmemdup(&nsim_udp_tunnel_info, sizeof(nsim_udp_tunnel_info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
if (nsim_dev->udp_ports.sync_all) {
info->set_port = NULL;
info->unset_port = NULL;
} else {
info->sync_table = NULL;
}
if (nsim_dev->udp_ports.open_only)
info->flags |= UDP_TUNNEL_NIC_INFO_OPEN_ONLY;
if (nsim_dev->udp_ports.ipv4_only)
info->flags |= UDP_TUNNEL_NIC_INFO_IPV4_ONLY;
if (nsim_dev->udp_ports.shared)
info->shared = &nsim_dev->udp_ports.utn_shared;
if (nsim_dev->udp_ports.static_iana_vxlan)
info->flags |= UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN;
dev->udp_tunnel_nic_info = info;
return 0;
}
void nsim_udp_tunnels_info_destroy(struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
debugfs_remove_recursive(ns->udp_ports.ddir);
kfree(dev->udp_tunnel_nic_info);
dev->udp_tunnel_nic_info = NULL;
}
void nsim_udp_tunnels_debugfs_create(struct nsim_dev *nsim_dev)
{
debugfs_create_bool("udp_ports_sync_all", 0600, nsim_dev->ddir,
&nsim_dev->udp_ports.sync_all);
debugfs_create_bool("udp_ports_open_only", 0600, nsim_dev->ddir,
&nsim_dev->udp_ports.open_only);
debugfs_create_bool("udp_ports_ipv4_only", 0600, nsim_dev->ddir,
&nsim_dev->udp_ports.ipv4_only);
debugfs_create_bool("udp_ports_shared", 0600, nsim_dev->ddir,
&nsim_dev->udp_ports.shared);
debugfs_create_bool("udp_ports_static_iana_vxlan", 0600, nsim_dev->ddir,
&nsim_dev->udp_ports.static_iana_vxlan);
}