Initial commit: Linux kernel 4.19 for Unplugged OS

This commit is contained in:
ziv committed 2025-10-06 13:52:42 +00:00
commit d30235b04c
80519 files changed
+35800681

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NETDEVSIM) += netdevsim.o
netdevsim-objs := \
netdev.o \
ifeq ($(CONFIG_BPF_SYSCALL),y)
netdevsim-objs += \
bpf.o
endif
ifneq ($(CONFIG_NET_DEVLINK),)
netdevsim-objs += devlink.o fib.o
endif
ifneq ($(CONFIG_XFRM_OFFLOAD),)
netdevsim-objs += ipsec.o
endif
+651
View File
@@ -0,0 +1,651 @@
/*
* Copyright (C) 2017 Netronome Systems, Inc.
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <linux/bpf.h>
#include <linux/bpf_verifier.h>
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/rtnetlink.h>
#include <net/pkt_cls.h>
#include "netdevsim.h"
#define pr_vlog(env, fmt, ...) \
bpf_verifier_log_write(env, "[netdevsim] " fmt, ##__VA_ARGS__)
struct nsim_bpf_bound_prog {
struct netdevsim *ns;
struct bpf_prog *prog;
struct dentry *ddir;
const char *state;
bool is_loaded;
struct list_head l;
};
#define NSIM_BPF_MAX_KEYS 2
struct nsim_bpf_bound_map {
struct netdevsim *ns;
struct bpf_offloaded_map *map;
struct mutex mutex;
struct nsim_map_entry {
void *key;
void *value;
} entry[NSIM_BPF_MAX_KEYS];
struct list_head l;
};
static int nsim_debugfs_bpf_string_read(struct seq_file *file, void *data)
{
const char **str = file->private;
if (*str)
seq_printf(file, "%s\n", *str);
return 0;
}
static int nsim_debugfs_bpf_string_open(struct inode *inode, struct file *f)
{
return single_open(f, nsim_debugfs_bpf_string_read, inode->i_private);
}
static const struct file_operations nsim_bpf_string_fops = {
.owner = THIS_MODULE,
.open = nsim_debugfs_bpf_string_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek
};
static int
nsim_bpf_verify_insn(struct bpf_verifier_env *env, int insn_idx, int prev_insn)
{
struct nsim_bpf_bound_prog *state;
state = env->prog->aux->offload->dev_priv;
if (state->ns->bpf_bind_verifier_delay && !insn_idx)
msleep(state->ns->bpf_bind_verifier_delay);
if (insn_idx == env->prog->len - 1)
pr_vlog(env, "Hello from netdevsim!\n");
return 0;
}
static const struct bpf_prog_offload_ops nsim_bpf_analyzer_ops = {
.insn_hook = nsim_bpf_verify_insn,
};
static bool nsim_xdp_offload_active(struct netdevsim *ns)
{
return ns->xdp_hw.prog;
}
static void nsim_prog_set_loaded(struct bpf_prog *prog, bool loaded)
{
struct nsim_bpf_bound_prog *state;
if (!prog || !prog->aux->offload)
return;
state = prog->aux->offload->dev_priv;
state->is_loaded = loaded;
}
static int
nsim_bpf_offload(struct netdevsim *ns, struct bpf_prog *prog, bool oldprog)
{
nsim_prog_set_loaded(ns->bpf_offloaded, false);
WARN(!!ns->bpf_offloaded != oldprog,
"bad offload state, expected offload %sto be active",
oldprog ? "" : "not ");
ns->bpf_offloaded = prog;
ns->bpf_offloaded_id = prog ? prog->aux->id : 0;
nsim_prog_set_loaded(prog, true);
return 0;
}
int nsim_bpf_setup_tc_block_cb(enum tc_setup_type type,
void *type_data, void *cb_priv)
{
struct tc_cls_bpf_offload *cls_bpf = type_data;
struct bpf_prog *prog = cls_bpf->prog;
struct netdevsim *ns = cb_priv;
struct bpf_prog *oldprog;
if (type != TC_SETUP_CLSBPF) {
NSIM_EA(cls_bpf->common.extack,
"only offload of BPF classifiers supported");
return -EOPNOTSUPP;
}
if (!tc_cls_can_offload_and_chain0(ns->netdev, &cls_bpf->common))
return -EOPNOTSUPP;
if (cls_bpf->common.protocol != htons(ETH_P_ALL)) {
NSIM_EA(cls_bpf->common.extack,
"only ETH_P_ALL supported as filter protocol");
return -EOPNOTSUPP;
}
if (!ns->bpf_tc_accept) {
NSIM_EA(cls_bpf->common.extack,
"netdevsim configured to reject BPF TC offload");
return -EOPNOTSUPP;
}
/* Note: progs without skip_sw will probably not be dev bound */
if (prog && !prog->aux->offload && !ns->bpf_tc_non_bound_accept) {
NSIM_EA(cls_bpf->common.extack,
"netdevsim configured to reject unbound programs");
return -EOPNOTSUPP;
}
if (cls_bpf->command != TC_CLSBPF_OFFLOAD)
return -EOPNOTSUPP;
oldprog = cls_bpf->oldprog;
/* Don't remove if oldprog doesn't match driver's state */
if (ns->bpf_offloaded != oldprog) {
oldprog = NULL;
if (!cls_bpf->prog)
return 0;
if (ns->bpf_offloaded) {
NSIM_EA(cls_bpf->common.extack,
"driver and netdev offload states mismatch");
return -EBUSY;
}
}
return nsim_bpf_offload(ns, cls_bpf->prog, oldprog);
}
int nsim_bpf_disable_tc(struct netdevsim *ns)
{
if (ns->bpf_offloaded && !nsim_xdp_offload_active(ns))
return -EBUSY;
return 0;
}
static int nsim_xdp_offload_prog(struct netdevsim *ns, struct netdev_bpf *bpf)
{
if (!nsim_xdp_offload_active(ns) && !bpf->prog)
return 0;
if (!nsim_xdp_offload_active(ns) && bpf->prog && ns->bpf_offloaded) {
NSIM_EA(bpf->extack, "TC program is already loaded");
return -EBUSY;
}
return nsim_bpf_offload(ns, bpf->prog, nsim_xdp_offload_active(ns));
}
static int
nsim_xdp_set_prog(struct netdevsim *ns, struct netdev_bpf *bpf,
struct xdp_attachment_info *xdp)
{
int err;
if (!xdp_attachment_flags_ok(xdp, bpf))
return -EBUSY;
if (bpf->command == XDP_SETUP_PROG && !ns->bpf_xdpdrv_accept) {
NSIM_EA(bpf->extack, "driver XDP disabled in DebugFS");
return -EOPNOTSUPP;
}
if (bpf->command == XDP_SETUP_PROG_HW && !ns->bpf_xdpoffload_accept) {
NSIM_EA(bpf->extack, "XDP offload disabled in DebugFS");
return -EOPNOTSUPP;
}
if (bpf->command == XDP_SETUP_PROG_HW) {
err = nsim_xdp_offload_prog(ns, bpf);
if (err)
return err;
}
xdp_attachment_setup(xdp, bpf);
return 0;
}
static int nsim_bpf_create_prog(struct netdevsim *ns, struct bpf_prog *prog)
{
struct nsim_bpf_bound_prog *state;
char name[16];
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
state->ns = ns;
state->prog = prog;
state->state = "verify";
/* Program id is not populated yet when we create the state. */
sprintf(name, "%u", ns->sdev->prog_id_gen++);
state->ddir = debugfs_create_dir(name, ns->sdev->ddir_bpf_bound_progs);
if (IS_ERR_OR_NULL(state->ddir)) {
kfree(state);
return -ENOMEM;
}
debugfs_create_u32("id", 0400, state->ddir, &prog->aux->id);
debugfs_create_file("state", 0400, state->ddir,
&state->state, &nsim_bpf_string_fops);
debugfs_create_bool("loaded", 0400, state->ddir, &state->is_loaded);
list_add_tail(&state->l, &ns->sdev->bpf_bound_progs);
prog->aux->offload->dev_priv = state;
return 0;
}
static void nsim_bpf_destroy_prog(struct bpf_prog *prog)
{
struct nsim_bpf_bound_prog *state;
state = prog->aux->offload->dev_priv;
WARN(state->is_loaded,
"offload state destroyed while program still bound");
debugfs_remove_recursive(state->ddir);
list_del(&state->l);
kfree(state);
}
static int nsim_setup_prog_checks(struct netdevsim *ns, struct netdev_bpf *bpf)
{
if (bpf->prog && bpf->prog->aux->offload) {
NSIM_EA(bpf->extack, "attempt to load offloaded prog to drv");
return -EINVAL;
}
if (ns->netdev->mtu > NSIM_XDP_MAX_MTU) {
NSIM_EA(bpf->extack, "MTU too large w/ XDP enabled");
return -EINVAL;
}
return 0;
}
static int
nsim_setup_prog_hw_checks(struct netdevsim *ns, struct netdev_bpf *bpf)
{
struct nsim_bpf_bound_prog *state;
if (!bpf->prog)
return 0;
if (!bpf->prog->aux->offload) {
NSIM_EA(bpf->extack, "xdpoffload of non-bound program");
return -EINVAL;
}
if (!bpf_offload_dev_match(bpf->prog, ns->netdev)) {
NSIM_EA(bpf->extack, "program bound to different dev");
return -EINVAL;
}
state = bpf->prog->aux->offload->dev_priv;
if (WARN_ON(strcmp(state->state, "xlated"))) {
NSIM_EA(bpf->extack, "offloading program in bad state");
return -EINVAL;
}
return 0;
}
static bool
nsim_map_key_match(struct bpf_map *map, struct nsim_map_entry *e, void *key)
{
return e->key && !memcmp(key, e->key, map->key_size);
}
static int nsim_map_key_find(struct bpf_offloaded_map *offmap, void *key)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(nmap->entry); i++)
if (nsim_map_key_match(&offmap->map, &nmap->entry[i], key))
return i;
return -ENOENT;
}
static int
nsim_map_alloc_elem(struct bpf_offloaded_map *offmap, unsigned int idx)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
nmap->entry[idx].key = kmalloc(offmap->map.key_size, GFP_USER);
if (!nmap->entry[idx].key)
return -ENOMEM;
nmap->entry[idx].value = kmalloc(offmap->map.value_size, GFP_USER);
if (!nmap->entry[idx].value) {
kfree(nmap->entry[idx].key);
nmap->entry[idx].key = NULL;
return -ENOMEM;
}
return 0;
}
static int
nsim_map_get_next_key(struct bpf_offloaded_map *offmap,
void *key, void *next_key)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
int idx = -ENOENT;
mutex_lock(&nmap->mutex);
if (key)
idx = nsim_map_key_find(offmap, key);
if (idx == -ENOENT)
idx = 0;
else
idx++;
for (; idx < ARRAY_SIZE(nmap->entry); idx++) {
if (nmap->entry[idx].key) {
memcpy(next_key, nmap->entry[idx].key,
offmap->map.key_size);
break;
}
}
mutex_unlock(&nmap->mutex);
if (idx == ARRAY_SIZE(nmap->entry))
return -ENOENT;
return 0;
}
static int
nsim_map_lookup_elem(struct bpf_offloaded_map *offmap, void *key, void *value)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
int idx;
mutex_lock(&nmap->mutex);
idx = nsim_map_key_find(offmap, key);
if (idx >= 0)
memcpy(value, nmap->entry[idx].value, offmap->map.value_size);
mutex_unlock(&nmap->mutex);
return idx < 0 ? idx : 0;
}
static int
nsim_map_update_elem(struct bpf_offloaded_map *offmap,
void *key, void *value, u64 flags)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
int idx, err = 0;
mutex_lock(&nmap->mutex);
idx = nsim_map_key_find(offmap, key);
if (idx < 0 && flags == BPF_EXIST) {
err = idx;
goto exit_unlock;
}
if (idx >= 0 && flags == BPF_NOEXIST) {
err = -EEXIST;
goto exit_unlock;
}
if (idx < 0) {
for (idx = 0; idx < ARRAY_SIZE(nmap->entry); idx++)
if (!nmap->entry[idx].key)
break;
if (idx == ARRAY_SIZE(nmap->entry)) {
err = -E2BIG;
goto exit_unlock;
}
err = nsim_map_alloc_elem(offmap, idx);
if (err)
goto exit_unlock;
}
memcpy(nmap->entry[idx].key, key, offmap->map.key_size);
memcpy(nmap->entry[idx].value, value, offmap->map.value_size);
exit_unlock:
mutex_unlock(&nmap->mutex);
return err;
}
static int nsim_map_delete_elem(struct bpf_offloaded_map *offmap, void *key)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
int idx;
if (offmap->map.map_type == BPF_MAP_TYPE_ARRAY)
return -EINVAL;
mutex_lock(&nmap->mutex);
idx = nsim_map_key_find(offmap, key);
if (idx >= 0) {
kfree(nmap->entry[idx].key);
kfree(nmap->entry[idx].value);
memset(&nmap->entry[idx], 0, sizeof(nmap->entry[idx]));
}
mutex_unlock(&nmap->mutex);
return idx < 0 ? idx : 0;
}
static const struct bpf_map_dev_ops nsim_bpf_map_ops = {
.map_get_next_key = nsim_map_get_next_key,
.map_lookup_elem = nsim_map_lookup_elem,
.map_update_elem = nsim_map_update_elem,
.map_delete_elem = nsim_map_delete_elem,
};
static int
nsim_bpf_map_alloc(struct netdevsim *ns, struct bpf_offloaded_map *offmap)
{
struct nsim_bpf_bound_map *nmap;
int i, err;
if (WARN_ON(offmap->map.map_type != BPF_MAP_TYPE_ARRAY &&
offmap->map.map_type != BPF_MAP_TYPE_HASH))
return -EINVAL;
if (offmap->map.max_entries > NSIM_BPF_MAX_KEYS)
return -ENOMEM;
if (offmap->map.map_flags)
return -EINVAL;
nmap = kzalloc(sizeof(*nmap), GFP_USER);
if (!nmap)
return -ENOMEM;
offmap->dev_priv = nmap;
nmap->ns = ns;
nmap->map = offmap;
mutex_init(&nmap->mutex);
if (offmap->map.map_type == BPF_MAP_TYPE_ARRAY) {
for (i = 0; i < ARRAY_SIZE(nmap->entry); i++) {
u32 *key;
err = nsim_map_alloc_elem(offmap, i);
if (err)
goto err_free;
key = nmap->entry[i].key;
*key = i;
}
}
offmap->dev_ops = &nsim_bpf_map_ops;
list_add_tail(&nmap->l, &ns->sdev->bpf_bound_maps);
return 0;
err_free:
while (--i >= 0) {
kfree(nmap->entry[i].key);
kfree(nmap->entry[i].value);
}
kfree(nmap);
return err;
}
static void nsim_bpf_map_free(struct bpf_offloaded_map *offmap)
{
struct nsim_bpf_bound_map *nmap = offmap->dev_priv;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(nmap->entry); i++) {
kfree(nmap->entry[i].key);
kfree(nmap->entry[i].value);
}
list_del_init(&nmap->l);
mutex_destroy(&nmap->mutex);
kfree(nmap);
}
int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf)
{
struct netdevsim *ns = netdev_priv(dev);
struct nsim_bpf_bound_prog *state;
int err;
ASSERT_RTNL();
switch (bpf->command) {
case BPF_OFFLOAD_VERIFIER_PREP:
if (!ns->bpf_bind_accept)
return -EOPNOTSUPP;
err = nsim_bpf_create_prog(ns, bpf->verifier.prog);
if (err)
return err;
bpf->verifier.ops = &nsim_bpf_analyzer_ops;
return 0;
case BPF_OFFLOAD_TRANSLATE:
state = bpf->offload.prog->aux->offload->dev_priv;
state->state = "xlated";
return 0;
case BPF_OFFLOAD_DESTROY:
nsim_bpf_destroy_prog(bpf->offload.prog);
return 0;
case XDP_QUERY_PROG:
return xdp_attachment_query(&ns->xdp, bpf);
case XDP_QUERY_PROG_HW:
return xdp_attachment_query(&ns->xdp_hw, bpf);
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_init(struct netdevsim *ns)
{
int err;
if (ns->sdev->refcnt == 1) {
INIT_LIST_HEAD(&ns->sdev->bpf_bound_progs);
INIT_LIST_HEAD(&ns->sdev->bpf_bound_maps);
ns->sdev->ddir_bpf_bound_progs =
debugfs_create_dir("bpf_bound_progs", ns->sdev->ddir);
if (IS_ERR_OR_NULL(ns->sdev->ddir_bpf_bound_progs))
return -ENOMEM;
ns->sdev->bpf_dev = bpf_offload_dev_create();
err = PTR_ERR_OR_ZERO(ns->sdev->bpf_dev);
if (err)
return err;
}
err = bpf_offload_dev_netdev_register(ns->sdev->bpf_dev, ns->netdev);
if (err)
goto err_destroy_bdev;
debugfs_create_u32("bpf_offloaded_id", 0400, ns->ddir,
&ns->bpf_offloaded_id);
ns->bpf_bind_accept = true;
debugfs_create_bool("bpf_bind_accept", 0600, ns->ddir,
&ns->bpf_bind_accept);
debugfs_create_u32("bpf_bind_verifier_delay", 0600, ns->ddir,
&ns->bpf_bind_verifier_delay);
ns->bpf_tc_accept = true;
debugfs_create_bool("bpf_tc_accept", 0600, ns->ddir,
&ns->bpf_tc_accept);
debugfs_create_bool("bpf_tc_non_bound_accept", 0600, ns->ddir,
&ns->bpf_tc_non_bound_accept);
ns->bpf_xdpdrv_accept = true;
debugfs_create_bool("bpf_xdpdrv_accept", 0600, ns->ddir,
&ns->bpf_xdpdrv_accept);
ns->bpf_xdpoffload_accept = true;
debugfs_create_bool("bpf_xdpoffload_accept", 0600, ns->ddir,
&ns->bpf_xdpoffload_accept);
ns->bpf_map_accept = true;
debugfs_create_bool("bpf_map_accept", 0600, ns->ddir,
&ns->bpf_map_accept);
return 0;
err_destroy_bdev:
if (ns->sdev->refcnt == 1)
bpf_offload_dev_destroy(ns->sdev->bpf_dev);
return err;
}
void nsim_bpf_uninit(struct netdevsim *ns)
{
WARN_ON(ns->xdp.prog);
WARN_ON(ns->xdp_hw.prog);
WARN_ON(ns->bpf_offloaded);
bpf_offload_dev_netdev_unregister(ns->sdev->bpf_dev, ns->netdev);
if (ns->sdev->refcnt == 1) {
WARN_ON(!list_empty(&ns->sdev->bpf_bound_progs));
WARN_ON(!list_empty(&ns->sdev->bpf_bound_maps));
bpf_offload_dev_destroy(ns->sdev->bpf_dev);
}
}
+295
View File
@@ -0,0 +1,295 @@
/*
* Copyright (c) 2018 Cumulus Networks. All rights reserved.
* Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <linux/device.h>
#include <net/devlink.h>
#include <net/netns/generic.h>
#include "netdevsim.h"
static unsigned int nsim_devlink_id;
/* place holder until devlink and namespaces is sorted out */
static struct net *nsim_devlink_net(struct devlink *devlink)
{
return &init_net;
}
/* IPv4
*/
static u64 nsim_ipv4_fib_resource_occ_get(void *priv)
{
struct net *net = priv;
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB, false);
}
static u64 nsim_ipv4_fib_rules_res_occ_get(void *priv)
{
struct net *net = priv;
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB_RULES, false);
}
/* IPv6
*/
static u64 nsim_ipv6_fib_resource_occ_get(void *priv)
{
struct net *net = priv;
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB, false);
}
static u64 nsim_ipv6_fib_rules_res_occ_get(void *priv)
{
struct net *net = priv;
return nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB_RULES, false);
}
static int devlink_resources_register(struct devlink *devlink)
{
struct devlink_resource_size_params params = {
.size_max = (u64)-1,
.size_granularity = 1,
.unit = DEVLINK_RESOURCE_UNIT_ENTRY
};
struct net *net = nsim_devlink_net(devlink);
int err;
u64 n;
/* Resources for IPv4 */
err = devlink_resource_register(devlink, "IPv4", (u64)-1,
NSIM_RESOURCE_IPV4,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&params);
if (err) {
pr_err("Failed to register IPv4 top resource\n");
goto out;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB, true);
err = devlink_resource_register(devlink, "fib", n,
NSIM_RESOURCE_IPV4_FIB,
NSIM_RESOURCE_IPV4, &params);
if (err) {
pr_err("Failed to register IPv4 FIB resource\n");
return err;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV4_FIB_RULES, true);
err = devlink_resource_register(devlink, "fib-rules", n,
NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV4, &params);
if (err) {
pr_err("Failed to register IPv4 FIB rules resource\n");
return err;
}
/* Resources for IPv6 */
err = devlink_resource_register(devlink, "IPv6", (u64)-1,
NSIM_RESOURCE_IPV6,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&params);
if (err) {
pr_err("Failed to register IPv6 top resource\n");
goto out;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB, true);
err = devlink_resource_register(devlink, "fib", n,
NSIM_RESOURCE_IPV6_FIB,
NSIM_RESOURCE_IPV6, &params);
if (err) {
pr_err("Failed to register IPv6 FIB resource\n");
return err;
}
n = nsim_fib_get_val(net, NSIM_RESOURCE_IPV6_FIB_RULES, true);
err = devlink_resource_register(devlink, "fib-rules", n,
NSIM_RESOURCE_IPV6_FIB_RULES,
NSIM_RESOURCE_IPV6, &params);
if (err) {
pr_err("Failed to register IPv6 FIB rules resource\n");
return err;
}
devlink_resource_occ_get_register(devlink,
NSIM_RESOURCE_IPV4_FIB,
nsim_ipv4_fib_resource_occ_get,
net);
devlink_resource_occ_get_register(devlink,
NSIM_RESOURCE_IPV4_FIB_RULES,
nsim_ipv4_fib_rules_res_occ_get,
net);
devlink_resource_occ_get_register(devlink,
NSIM_RESOURCE_IPV6_FIB,
nsim_ipv6_fib_resource_occ_get,
net);
devlink_resource_occ_get_register(devlink,
NSIM_RESOURCE_IPV6_FIB_RULES,
nsim_ipv6_fib_rules_res_occ_get,
net);
out:
return err;
}
static int nsim_devlink_reload(struct devlink *devlink,
struct netlink_ext_ack *extack)
{
enum nsim_resource_id res_ids[] = {
NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
};
struct net *net = nsim_devlink_net(devlink);
int i;
for (i = 0; i < ARRAY_SIZE(res_ids); ++i) {
int err;
u64 val;
err = devlink_resource_size_get(devlink, res_ids[i], &val);
if (!err) {
err = nsim_fib_set_max(net, res_ids[i], val, extack);
if (err)
return err;
}
}
return 0;
}
static void nsim_devlink_net_reset(struct net *net)
{
enum nsim_resource_id res_ids[] = {
NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
};
int i;
for (i = 0; i < ARRAY_SIZE(res_ids); ++i) {
if (nsim_fib_set_max(net, res_ids[i], (u64)-1, NULL)) {
pr_err("Failed to reset limit for resource %u\n",
res_ids[i]);
}
}
}
static const struct devlink_ops nsim_devlink_ops = {
.reload = nsim_devlink_reload,
};
/* once devlink / namespace issues are sorted out
* this needs to be net in which a devlink instance
* is to be created. e.g., dev_net(ns->netdev)
*/
static struct net *nsim_to_net(struct netdevsim *ns)
{
return &init_net;
}
void nsim_devlink_teardown(struct netdevsim *ns)
{
if (ns->devlink) {
struct net *net = nsim_to_net(ns);
bool *reg_devlink = net_generic(net, nsim_devlink_id);
devlink_resources_unregister(ns->devlink, NULL);
devlink_unregister(ns->devlink);
devlink_free(ns->devlink);
ns->devlink = NULL;
nsim_devlink_net_reset(net);
*reg_devlink = true;
}
}
int nsim_devlink_setup(struct netdevsim *ns)
{
struct net *net = nsim_to_net(ns);
bool *reg_devlink = net_generic(net, nsim_devlink_id);
struct devlink *devlink;
int err;
/* only one device per namespace controls devlink */
if (!*reg_devlink) {
ns->devlink = NULL;
return 0;
}
devlink = devlink_alloc(&nsim_devlink_ops, 0);
if (!devlink)
return -ENOMEM;
err = devlink_register(devlink, &ns->dev);
if (err)
goto err_devlink_free;
err = devlink_resources_register(devlink);
if (err)
goto err_dl_unregister;
ns->devlink = devlink;
*reg_devlink = false;
return 0;
err_dl_unregister:
devlink_unregister(devlink);
err_devlink_free:
devlink_free(devlink);
return err;
}
/* Initialize per network namespace state */
static int __net_init nsim_devlink_netns_init(struct net *net)
{
bool *reg_devlink = net_generic(net, nsim_devlink_id);
*reg_devlink = true;
return 0;
}
static struct pernet_operations nsim_devlink_net_ops = {
.init = nsim_devlink_netns_init,
.id = &nsim_devlink_id,
.size = sizeof(bool),
};
void nsim_devlink_exit(void)
{
unregister_pernet_subsys(&nsim_devlink_net_ops);
nsim_fib_exit();
}
int nsim_devlink_init(void)
{
int err;
err = nsim_fib_init();
if (err)
goto err_out;
err = register_pernet_subsys(&nsim_devlink_net_ops);
if (err)
nsim_fib_exit();
err_out:
return err;
}
+266
View File
@@ -0,0 +1,266 @@
/*
* Copyright (c) 2018 Cumulus Networks. All rights reserved.
* Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <net/fib_notifier.h>
#include <net/ip_fib.h>
#include <net/ip6_fib.h>
#include <net/fib_rules.h>
#include <net/netns/generic.h>
#include "netdevsim.h"
struct nsim_fib_entry {
u64 max;
u64 num;
};
struct nsim_per_fib_data {
struct nsim_fib_entry fib;
struct nsim_fib_entry rules;
};
struct nsim_fib_data {
struct nsim_per_fib_data ipv4;
struct nsim_per_fib_data ipv6;
};
static unsigned int nsim_fib_net_id;
u64 nsim_fib_get_val(struct net *net, enum nsim_resource_id res_id, bool max)
{
struct nsim_fib_data *fib_data = net_generic(net, nsim_fib_net_id);
struct nsim_fib_entry *entry;
switch (res_id) {
case NSIM_RESOURCE_IPV4_FIB:
entry = &fib_data->ipv4.fib;
break;
case NSIM_RESOURCE_IPV4_FIB_RULES:
entry = &fib_data->ipv4.rules;
break;
case NSIM_RESOURCE_IPV6_FIB:
entry = &fib_data->ipv6.fib;
break;
case NSIM_RESOURCE_IPV6_FIB_RULES:
entry = &fib_data->ipv6.rules;
break;
default:
return 0;
}
return max ? entry->max : entry->num;
}
int nsim_fib_set_max(struct net *net, enum nsim_resource_id res_id, u64 val,
struct netlink_ext_ack *extack)
{
struct nsim_fib_data *fib_data = net_generic(net, nsim_fib_net_id);
struct nsim_fib_entry *entry;
int err = 0;
switch (res_id) {
case NSIM_RESOURCE_IPV4_FIB:
entry = &fib_data->ipv4.fib;
break;
case NSIM_RESOURCE_IPV4_FIB_RULES:
entry = &fib_data->ipv4.rules;
break;
case NSIM_RESOURCE_IPV6_FIB:
entry = &fib_data->ipv6.fib;
break;
case NSIM_RESOURCE_IPV6_FIB_RULES:
entry = &fib_data->ipv6.rules;
break;
default:
return 0;
}
/* not allowing a new max to be less than curren occupancy
* --> no means of evicting entries
*/
if (val < entry->num) {
NL_SET_ERR_MSG_MOD(extack, "New size is less than current occupancy");
err = -EINVAL;
} else {
entry->max = val;
}
return err;
}
static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
struct netlink_ext_ack *extack)
{
int err = 0;
if (add) {
if (entry->num < entry->max) {
entry->num++;
} else {
err = -ENOSPC;
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
}
} else {
entry->num--;
}
return err;
}
static int nsim_fib_rule_event(struct fib_notifier_info *info, bool add)
{
struct nsim_fib_data *data = net_generic(info->net, nsim_fib_net_id);
struct netlink_ext_ack *extack = info->extack;
int err = 0;
switch (info->family) {
case AF_INET:
err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
break;
case AF_INET6:
err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
break;
}
return err;
}
static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
struct netlink_ext_ack *extack)
{
int err = 0;
if (add) {
if (entry->num < entry->max) {
entry->num++;
} else {
err = -ENOSPC;
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
}
} else {
entry->num--;
}
return err;
}
static int nsim_fib_event(struct fib_notifier_info *info, bool add)
{
struct nsim_fib_data *data = net_generic(info->net, nsim_fib_net_id);
struct netlink_ext_ack *extack = info->extack;
int err = 0;
switch (info->family) {
case AF_INET:
err = nsim_fib_account(&data->ipv4.fib, add, extack);
break;
case AF_INET6:
err = nsim_fib_account(&data->ipv6.fib, add, extack);
break;
}
return err;
}
static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
void *ptr)
{
struct fib_notifier_info *info = ptr;
int err = 0;
switch (event) {
case FIB_EVENT_RULE_ADD: /* fall through */
case FIB_EVENT_RULE_DEL:
err = nsim_fib_rule_event(info, event == FIB_EVENT_RULE_ADD);
break;
case FIB_EVENT_ENTRY_ADD: /* fall through */
case FIB_EVENT_ENTRY_DEL:
err = nsim_fib_event(info, event == FIB_EVENT_ENTRY_ADD);
break;
}
return notifier_from_errno(err);
}
/* inconsistent dump, trying again */
static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
{
struct nsim_fib_data *data;
struct net *net;
rcu_read_lock();
for_each_net_rcu(net) {
data = net_generic(net, nsim_fib_net_id);
data->ipv4.fib.num = 0ULL;
data->ipv4.rules.num = 0ULL;
data->ipv6.fib.num = 0ULL;
data->ipv6.rules.num = 0ULL;
}
rcu_read_unlock();
}
static struct notifier_block nsim_fib_nb = {
.notifier_call = nsim_fib_event_nb,
};
/* Initialize per network namespace state */
static int __net_init nsim_fib_netns_init(struct net *net)
{
struct nsim_fib_data *data = net_generic(net, nsim_fib_net_id);
data->ipv4.fib.max = (u64)-1;
data->ipv4.rules.max = (u64)-1;
data->ipv6.fib.max = (u64)-1;
data->ipv6.rules.max = (u64)-1;
return 0;
}
static struct pernet_operations nsim_fib_net_ops = {
.init = nsim_fib_netns_init,
.id = &nsim_fib_net_id,
.size = sizeof(struct nsim_fib_data),
};
void nsim_fib_exit(void)
{
unregister_pernet_subsys(&nsim_fib_net_ops);
unregister_fib_notifier(&nsim_fib_nb);
}
int nsim_fib_init(void)
{
int err;
err = register_pernet_subsys(&nsim_fib_net_ops);
if (err < 0) {
pr_err("Failed to register pernet subsystem\n");
goto err_out;
}
err = register_fib_notifier(&nsim_fib_nb, nsim_fib_dump_inconsistent);
if (err < 0) {
pr_err("Failed to register fib notifier\n");
goto err_out;
}
err_out:
return err;
}
+297
View File
@@ -0,0 +1,297 @@
// 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 += snprintf(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;
p += snprintf(p, bufsize - (p - buf),
"sa[%i] %cx ipaddr=0x%08x %08x %08x %08x\n",
i, (sap->rx ? 'r' : 't'), sap->ipaddr[0],
sap->ipaddr[1], sap->ipaddr[2], sap->ipaddr[3]);
p += snprintf(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 += snprintf(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 xfrm_state *xs,
u32 *mykey, u32 *mysalt)
{
const char aes_gcm_name[] = "rfc4106(gcm(aes))";
struct net_device *dev = xs->xso.dev;
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 xfrm_state *xs)
{
struct nsim_ipsec *ipsec;
struct net_device *dev;
struct netdevsim *ns;
struct nsim_sa sa;
u16 sa_idx;
int ret;
dev = xs->xso.dev;
ns = netdev_priv(dev);
ipsec = &ns->ipsec;
if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) {
netdev_err(dev, "Unsupported protocol 0x%04x for ipsec offload\n",
xs->id.proto);
return -EINVAL;
}
if (xs->calg) {
netdev_err(dev, "Compression offload not supported\n");
return -EINVAL;
}
/* find the first unused index */
ret = nsim_ipsec_find_empty_idx(ipsec);
if (ret < 0) {
netdev_err(dev, "No space for SA in Rx table!\n");
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(xs, sa.key, &sa.salt);
if (ret) {
netdev_err(dev, "Failed to get key data for SA table\n");
return ret;
}
if (xs->xso.flags & XFRM_OFFLOAD_INBOUND) {
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 xfrm_state *xs)
{
struct netdevsim *ns = netdev_priv(xs->xso.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 bool nsim_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *xs)
{
struct netdevsim *ns = netdev_priv(xs->xso.dev);
struct nsim_ipsec *ipsec = &ns->ipsec;
ipsec->ok++;
return true;
}
static const struct xfrmdev_ops nsim_xfrmdev_ops = {
.xdo_dev_state_add = nsim_ipsec_add_sa,
.xdo_dev_state_delete = nsim_ipsec_del_sa,
.xdo_dev_offload_ok = nsim_ipsec_offload_ok,
};
bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *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 (!skb->sp)
return true;
if (unlikely(!skb->sp->len)) {
netdev_err(ns->netdev, "no xfrm state len = %d\n",
skb->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_enc_features |= NSIM_ESP_FEATURES;
ns->ipsec.pfile = debugfs_create_file("ipsec", 0400, ns->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);
}
+623
View File
@@ -0,0 +1,623 @@
/*
* Copyright (C) 2017 Netronome Systems, Inc.
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <linux/debugfs.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <net/netlink.h>
#include <net/pkt_cls.h>
#include <net/rtnetlink.h>
#include <net/switchdev.h>
#include "netdevsim.h"
struct nsim_vf_config {
int link_state;
u16 min_tx_rate;
u16 max_tx_rate;
u16 vlan;
__be16 vlan_proto;
u16 qos;
u8 vf_mac[ETH_ALEN];
bool spoofchk_enabled;
bool trusted;
bool rss_query_enabled;
};
static u32 nsim_dev_id;
static struct dentry *nsim_ddir;
static struct dentry *nsim_sdev_ddir;
static int nsim_num_vf(struct device *dev)
{
struct netdevsim *ns = to_nsim(dev);
return ns->num_vfs;
}
static struct bus_type nsim_bus = {
.name = DRV_NAME,
.dev_name = DRV_NAME,
.num_vf = nsim_num_vf,
};
static int nsim_vfs_enable(struct netdevsim *ns, unsigned int num_vfs)
{
ns->vfconfigs = kcalloc(num_vfs, sizeof(struct nsim_vf_config),
GFP_KERNEL);
if (!ns->vfconfigs)
return -ENOMEM;
ns->num_vfs = num_vfs;
return 0;
}
static void nsim_vfs_disable(struct netdevsim *ns)
{
kfree(ns->vfconfigs);
ns->vfconfigs = NULL;
ns->num_vfs = 0;
}
static ssize_t
nsim_numvfs_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct netdevsim *ns = to_nsim(dev);
unsigned int num_vfs;
int ret;
ret = kstrtouint(buf, 0, &num_vfs);
if (ret)
return ret;
rtnl_lock();
if (ns->num_vfs == num_vfs)
goto exit_good;
if (ns->num_vfs && num_vfs) {
ret = -EBUSY;
goto exit_unlock;
}
if (num_vfs) {
ret = nsim_vfs_enable(ns, num_vfs);
if (ret)
goto exit_unlock;
} else {
nsim_vfs_disable(ns);
}
exit_good:
ret = count;
exit_unlock:
rtnl_unlock();
return ret;
}
static ssize_t
nsim_numvfs_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct netdevsim *ns = to_nsim(dev);
return sprintf(buf, "%u\n", ns->num_vfs);
}
static struct device_attribute nsim_numvfs_attr =
__ATTR(sriov_numvfs, 0664, nsim_numvfs_show, nsim_numvfs_store);
static struct attribute *nsim_dev_attrs[] = {
&nsim_numvfs_attr.attr,
NULL,
};
static const struct attribute_group nsim_dev_attr_group = {
.attrs = nsim_dev_attrs,
};
static const struct attribute_group *nsim_dev_attr_groups[] = {
&nsim_dev_attr_group,
NULL,
};
static void nsim_dev_release(struct device *dev)
{
struct netdevsim *ns = to_nsim(dev);
nsim_vfs_disable(ns);
free_netdev(ns->netdev);
}
static struct device_type nsim_dev_type = {
.groups = nsim_dev_attr_groups,
.release = nsim_dev_release,
};
static int
nsim_port_attr_get(struct net_device *dev, struct switchdev_attr *attr)
{
struct netdevsim *ns = netdev_priv(dev);
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_PARENT_ID:
attr->u.ppid.id_len = sizeof(ns->sdev->switch_id);
memcpy(&attr->u.ppid.id, &ns->sdev->switch_id,
attr->u.ppid.id_len);
return 0;
default:
return -EOPNOTSUPP;
}
}
static const struct switchdev_ops nsim_switchdev_ops = {
.switchdev_port_attr_get = nsim_port_attr_get,
};
static int nsim_init(struct net_device *dev)
{
char sdev_ddir_name[10], sdev_link_name[32];
struct netdevsim *ns = netdev_priv(dev);
int err;
ns->netdev = dev;
ns->ddir = debugfs_create_dir(netdev_name(dev), nsim_ddir);
if (IS_ERR_OR_NULL(ns->ddir))
return -ENOMEM;
if (!ns->sdev) {
ns->sdev = kzalloc(sizeof(*ns->sdev), GFP_KERNEL);
if (!ns->sdev) {
err = -ENOMEM;
goto err_debugfs_destroy;
}
ns->sdev->refcnt = 1;
ns->sdev->switch_id = nsim_dev_id;
sprintf(sdev_ddir_name, "%u", ns->sdev->switch_id);
ns->sdev->ddir = debugfs_create_dir(sdev_ddir_name,
nsim_sdev_ddir);
if (IS_ERR_OR_NULL(ns->sdev->ddir)) {
err = PTR_ERR_OR_ZERO(ns->sdev->ddir) ?: -EINVAL;
goto err_sdev_free;
}
} else {
sprintf(sdev_ddir_name, "%u", ns->sdev->switch_id);
ns->sdev->refcnt++;
}
sprintf(sdev_link_name, "../../" DRV_NAME "_sdev/%s", sdev_ddir_name);
debugfs_create_symlink("sdev", ns->ddir, sdev_link_name);
err = nsim_bpf_init(ns);
if (err)
goto err_sdev_destroy;
ns->dev.id = nsim_dev_id++;
ns->dev.bus = &nsim_bus;
ns->dev.type = &nsim_dev_type;
err = device_register(&ns->dev);
if (err)
goto err_bpf_uninit;
SET_NETDEV_DEV(dev, &ns->dev);
SWITCHDEV_SET_OPS(dev, &nsim_switchdev_ops);
err = nsim_devlink_setup(ns);
if (err)
goto err_unreg_dev;
nsim_ipsec_init(ns);
return 0;
err_unreg_dev:
device_unregister(&ns->dev);
err_bpf_uninit:
nsim_bpf_uninit(ns);
err_sdev_destroy:
if (!--ns->sdev->refcnt) {
debugfs_remove_recursive(ns->sdev->ddir);
err_sdev_free:
kfree(ns->sdev);
}
err_debugfs_destroy:
debugfs_remove_recursive(ns->ddir);
return err;
}
static void nsim_uninit(struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
nsim_ipsec_teardown(ns);
nsim_devlink_teardown(ns);
debugfs_remove_recursive(ns->ddir);
nsim_bpf_uninit(ns);
if (!--ns->sdev->refcnt) {
debugfs_remove_recursive(ns->sdev->ddir);
kfree(ns->sdev);
}
}
static void nsim_free(struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
device_unregister(&ns->dev);
/* netdev and vf state will be freed out of device_release() */
}
static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
if (!nsim_ipsec_tx(ns, skb))
goto out;
u64_stats_update_begin(&ns->syncp);
ns->tx_packets++;
ns->tx_bytes += skb->len;
u64_stats_update_end(&ns->syncp);
out:
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static void nsim_set_rx_mode(struct net_device *dev)
{
}
static int nsim_change_mtu(struct net_device *dev, int new_mtu)
{
struct netdevsim *ns = netdev_priv(dev);
if (ns->xdp.prog && new_mtu > NSIM_XDP_MAX_MTU)
return -EBUSY;
dev->mtu = new_mtu;
return 0;
}
static void
nsim_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
{
struct netdevsim *ns = netdev_priv(dev);
unsigned int start;
do {
start = u64_stats_fetch_begin(&ns->syncp);
stats->tx_bytes = ns->tx_bytes;
stats->tx_packets = ns->tx_packets;
} while (u64_stats_fetch_retry(&ns->syncp, start));
}
static int
nsim_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
{
return nsim_bpf_setup_tc_block_cb(type, type_data, cb_priv);
}
static int
nsim_setup_tc_block(struct net_device *dev, struct tc_block_offload *f)
{
struct netdevsim *ns = netdev_priv(dev);
if (f->binder_type != TCF_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
return -EOPNOTSUPP;
switch (f->command) {
case TC_BLOCK_BIND:
return tcf_block_cb_register(f->block, nsim_setup_tc_block_cb,
ns, ns, f->extack);
case TC_BLOCK_UNBIND:
tcf_block_cb_unregister(f->block, nsim_setup_tc_block_cb, ns);
return 0;
default:
return -EOPNOTSUPP;
}
}
static int nsim_set_vf_mac(struct net_device *dev, int vf, u8 *mac)
{
struct netdevsim *ns = netdev_priv(dev);
/* Only refuse multicast addresses, zero address can mean unset/any. */
if (vf >= ns->num_vfs || is_multicast_ether_addr(mac))
return -EINVAL;
memcpy(ns->vfconfigs[vf].vf_mac, mac, ETH_ALEN);
return 0;
}
static int nsim_set_vf_vlan(struct net_device *dev, int vf,
u16 vlan, u8 qos, __be16 vlan_proto)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs || vlan > 4095 || qos > 7)
return -EINVAL;
ns->vfconfigs[vf].vlan = vlan;
ns->vfconfigs[vf].qos = qos;
ns->vfconfigs[vf].vlan_proto = vlan_proto;
return 0;
}
static int nsim_set_vf_rate(struct net_device *dev, int vf, int min, int max)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs)
return -EINVAL;
ns->vfconfigs[vf].min_tx_rate = min;
ns->vfconfigs[vf].max_tx_rate = max;
return 0;
}
static int nsim_set_vf_spoofchk(struct net_device *dev, int vf, bool val)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs)
return -EINVAL;
ns->vfconfigs[vf].spoofchk_enabled = val;
return 0;
}
static int nsim_set_vf_rss_query_en(struct net_device *dev, int vf, bool val)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs)
return -EINVAL;
ns->vfconfigs[vf].rss_query_enabled = val;
return 0;
}
static int nsim_set_vf_trust(struct net_device *dev, int vf, bool val)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs)
return -EINVAL;
ns->vfconfigs[vf].trusted = val;
return 0;
}
static int
nsim_get_vf_config(struct net_device *dev, int vf, struct ifla_vf_info *ivi)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs)
return -EINVAL;
ivi->vf = vf;
ivi->linkstate = ns->vfconfigs[vf].link_state;
ivi->min_tx_rate = ns->vfconfigs[vf].min_tx_rate;
ivi->max_tx_rate = ns->vfconfigs[vf].max_tx_rate;
ivi->vlan = ns->vfconfigs[vf].vlan;
ivi->vlan_proto = ns->vfconfigs[vf].vlan_proto;
ivi->qos = ns->vfconfigs[vf].qos;
memcpy(&ivi->mac, ns->vfconfigs[vf].vf_mac, ETH_ALEN);
ivi->spoofchk = ns->vfconfigs[vf].spoofchk_enabled;
ivi->trusted = ns->vfconfigs[vf].trusted;
ivi->rss_query_en = ns->vfconfigs[vf].rss_query_enabled;
return 0;
}
static int nsim_set_vf_link_state(struct net_device *dev, int vf, int state)
{
struct netdevsim *ns = netdev_priv(dev);
if (vf >= ns->num_vfs)
return -EINVAL;
switch (state) {
case IFLA_VF_LINK_STATE_AUTO:
case IFLA_VF_LINK_STATE_ENABLE:
case IFLA_VF_LINK_STATE_DISABLE:
break;
default:
return -EINVAL;
}
ns->vfconfigs[vf].link_state = state;
return 0;
}
static int
nsim_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data)
{
switch (type) {
case TC_SETUP_BLOCK:
return nsim_setup_tc_block(dev, type_data);
default:
return -EOPNOTSUPP;
}
}
static int
nsim_set_features(struct net_device *dev, netdev_features_t features)
{
struct netdevsim *ns = netdev_priv(dev);
if ((dev->features & NETIF_F_HW_TC) > (features & NETIF_F_HW_TC))
return nsim_bpf_disable_tc(ns);
return 0;
}
static const struct net_device_ops nsim_netdev_ops = {
.ndo_init = nsim_init,
.ndo_uninit = nsim_uninit,
.ndo_start_xmit = nsim_start_xmit,
.ndo_set_rx_mode = nsim_set_rx_mode,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_change_mtu = nsim_change_mtu,
.ndo_get_stats64 = nsim_get_stats64,
.ndo_set_vf_mac = nsim_set_vf_mac,
.ndo_set_vf_vlan = nsim_set_vf_vlan,
.ndo_set_vf_rate = nsim_set_vf_rate,
.ndo_set_vf_spoofchk = nsim_set_vf_spoofchk,
.ndo_set_vf_trust = nsim_set_vf_trust,
.ndo_get_vf_config = nsim_get_vf_config,
.ndo_set_vf_link_state = nsim_set_vf_link_state,
.ndo_set_vf_rss_query_en = nsim_set_vf_rss_query_en,
.ndo_setup_tc = nsim_setup_tc,
.ndo_set_features = nsim_set_features,
.ndo_bpf = nsim_bpf,
};
static void nsim_setup(struct net_device *dev)
{
ether_setup(dev);
eth_hw_addr_random(dev);
dev->netdev_ops = &nsim_netdev_ops;
dev->priv_destructor = nsim_free;
dev->tx_queue_len = 0;
dev->flags |= IFF_NOARP;
dev->flags &= ~IFF_MULTICAST;
dev->priv_flags |= IFF_LIVE_ADDR_CHANGE |
IFF_NO_QUEUE;
dev->features |= NETIF_F_HIGHDMA |
NETIF_F_SG |
NETIF_F_FRAGLIST |
NETIF_F_HW_CSUM |
NETIF_F_TSO;
dev->hw_features |= NETIF_F_HW_TC;
dev->max_mtu = ETH_MAX_MTU;
}
static int nsim_validate(struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
if (tb[IFLA_ADDRESS]) {
if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN)
return -EINVAL;
if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS])))
return -EADDRNOTAVAIL;
}
return 0;
}
static int nsim_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
if (tb[IFLA_LINK]) {
struct net_device *joindev;
struct netdevsim *joinns;
joindev = __dev_get_by_index(src_net,
nla_get_u32(tb[IFLA_LINK]));
if (!joindev)
return -ENODEV;
if (joindev->netdev_ops != &nsim_netdev_ops)
return -EINVAL;
joinns = netdev_priv(joindev);
if (!joinns->sdev || !joinns->sdev->refcnt)
return -EINVAL;
ns->sdev = joinns->sdev;
}
return register_netdevice(dev);
}
static void nsim_dellink(struct net_device *dev, struct list_head *head)
{
unregister_netdevice_queue(dev, head);
}
static struct rtnl_link_ops nsim_link_ops __read_mostly = {
.kind = DRV_NAME,
.priv_size = sizeof(struct netdevsim),
.setup = nsim_setup,
.validate = nsim_validate,
.newlink = nsim_newlink,
.dellink = nsim_dellink,
};
static int __init nsim_module_init(void)
{
int err;
nsim_ddir = debugfs_create_dir(DRV_NAME, NULL);
if (IS_ERR_OR_NULL(nsim_ddir))
return -ENOMEM;
nsim_sdev_ddir = debugfs_create_dir(DRV_NAME "_sdev", NULL);
if (IS_ERR_OR_NULL(nsim_sdev_ddir)) {
err = -ENOMEM;
goto err_debugfs_destroy;
}
err = bus_register(&nsim_bus);
if (err)
goto err_sdir_destroy;
err = nsim_devlink_init();
if (err)
goto err_unreg_bus;
err = rtnl_link_register(&nsim_link_ops);
if (err)
goto err_dl_fini;
return 0;
err_dl_fini:
nsim_devlink_exit();
err_unreg_bus:
bus_unregister(&nsim_bus);
err_sdir_destroy:
debugfs_remove_recursive(nsim_sdev_ddir);
err_debugfs_destroy:
debugfs_remove_recursive(nsim_ddir);
return err;
}
static void __exit nsim_module_exit(void)
{
rtnl_link_unregister(&nsim_link_ops);
nsim_devlink_exit();
bus_unregister(&nsim_bus);
debugfs_remove_recursive(nsim_sdev_ddir);
debugfs_remove_recursive(nsim_ddir);
}
module_init(nsim_module_init);
module_exit(nsim_module_exit);
MODULE_LICENSE("GPL");
MODULE_ALIAS_RTNL_LINK(DRV_NAME);
+205
View File
@@ -0,0 +1,205 @@
/*
* Copyright (C) 2017 Netronome Systems, Inc.
*
* This software is licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree.
*
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/u64_stats_sync.h>
#include <net/xdp.h>
#define DRV_NAME "netdevsim"
#define NSIM_XDP_MAX_MTU 4000
#define NSIM_EA(extack, msg) NL_SET_ERR_MSG_MOD((extack), msg)
struct bpf_prog;
struct bpf_offload_dev;
struct dentry;
struct nsim_vf_config;
struct netdevsim_shared_dev {
unsigned int refcnt;
u32 switch_id;
struct dentry *ddir;
struct bpf_offload_dev *bpf_dev;
struct dentry *ddir_bpf_bound_progs;
u32 prog_id_gen;
struct list_head bpf_bound_progs;
struct list_head bpf_bound_maps;
};
#define NSIM_IPSEC_MAX_SA_COUNT 33
#define NSIM_IPSEC_VALID BIT(31)
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;
u32 ok;
};
struct netdevsim {
struct net_device *netdev;
u64 tx_packets;
u64 tx_bytes;
struct u64_stats_sync syncp;
struct device dev;
struct netdevsim_shared_dev *sdev;
struct dentry *ddir;
unsigned int num_vfs;
struct nsim_vf_config *vfconfigs;
struct bpf_prog *bpf_offloaded;
u32 bpf_offloaded_id;
struct xdp_attachment_info xdp;
struct xdp_attachment_info xdp_hw;
bool bpf_bind_accept;
u32 bpf_bind_verifier_delay;
bool bpf_tc_accept;
bool bpf_tc_non_bound_accept;
bool bpf_xdpdrv_accept;
bool bpf_xdpoffload_accept;
bool bpf_map_accept;
#if IS_ENABLED(CONFIG_NET_DEVLINK)
struct devlink *devlink;
#endif
struct nsim_ipsec ipsec;
};
#ifdef CONFIG_BPF_SYSCALL
int nsim_bpf_init(struct netdevsim *ns);
void nsim_bpf_uninit(struct netdevsim *ns);
int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf);
int nsim_bpf_disable_tc(struct netdevsim *ns);
int nsim_bpf_setup_tc_block_cb(enum tc_setup_type type,
void *type_data, void *cb_priv);
#else
static inline int nsim_bpf_init(struct netdevsim *ns)
{
return 0;
}
static inline void nsim_bpf_uninit(struct netdevsim *ns)
{
}
static inline int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf)
{
return bpf->command == XDP_QUERY_PROG ? 0 : -EOPNOTSUPP;
}
static inline int nsim_bpf_disable_tc(struct netdevsim *ns)
{
return 0;
}
static inline int
nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
void *cb_priv)
{
return -EOPNOTSUPP;
}
#endif
#if IS_ENABLED(CONFIG_NET_DEVLINK)
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,
};
int nsim_devlink_setup(struct netdevsim *ns);
void nsim_devlink_teardown(struct netdevsim *ns);
int nsim_devlink_init(void);
void nsim_devlink_exit(void);
int nsim_fib_init(void);
void nsim_fib_exit(void);
u64 nsim_fib_get_val(struct net *net, enum nsim_resource_id res_id, bool max);
int nsim_fib_set_max(struct net *net, enum nsim_resource_id res_id, u64 val,
struct netlink_ext_ack *extack);
#else
static inline int nsim_devlink_setup(struct netdevsim *ns)
{
return 0;
}
static inline void nsim_devlink_teardown(struct netdevsim *ns)
{
}
static inline int nsim_devlink_init(void)
{
return 0;
}
static inline void nsim_devlink_exit(void)
{
}
#endif
#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
static inline struct netdevsim *to_nsim(struct device *ptr)
{
return container_of(ptr, struct netdevsim, dev);
}