summaryrefslogtreecommitdiff
path: root/drivers/net/netdevsim
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2021-06-02 14:08:37 -0700
committerDavid S. Miller <davem@davemloft.net>2021-06-02 14:08:37 -0700
commit270d47dc1fc4756a0158778084a236bc83c156d2 (patch)
treec4596358121dc31d42875ed93b4ab32d8c665f83 /drivers/net/netdevsim
parent53c7bb553056d3a6713ea413576c6d1b0c3f0f61 (diff)
parentb62767e7bab3a397166a2fa36b409e5e2859f100 (diff)
Merge branch 'devlink-rate-objects'
Dmytro Linkin says: ==================== devlink: rate objects API Resending without RFC. Currently kernel provides a way to change tx rate of single VF in switchdev mode via tc-police action. When lots of VFs are configured management of theirs rates becomes non-trivial task and some grouping mechanism is required. Implementing such grouping in tc-police will bring flow related limitations and unwanted complications, like: - tc-police is a policer and there is a user request for a traffic shaper, so shared tc-police action is not suitable; - flows requires net device to be placed on, means "groups" wouldn't have net device instance itself. Taking into the account previous point was reviewed a sollution, when representor have a policer and the driver use a shaper if qdisc contains group of VFs - such approach ugly, compilated and misleading; - TC is ingress only, while configuring "other" side of the wire looks more like a "real" picture where shaping is outside of the steering world, similar to "ip link" command; According to that devlink is the most appropriate place. This series introduces devlink API for managing tx rate of single devlink port or of a group by invoking callbacks (see below) of corresponding driver. Also devlink port or a group can be added to the parent group, where driver responsible to handle rates of a group elements. To achieve all of that new rate object is added. It can be one of the two types: - leaf - represents a single devlink port; created/destroyed by the driver and bound to the devlink port. As example, some driver may create leaf rate object for every devlink port associated with VF. Since leaf have 1to1 mapping to it's devlink port, in user space it is referred as pci/<bus_addr>/<port_index>; - node - represents a group of rate objects; created/deleted by request from the userspace; initially empty (no rate objects added). In userspace it is referred as pci/<bus_addr>/<node_name>, where node name can be any, except decimal number, to avoid collisions with leafs. devlink_ops extended with following callbacks: - rate_{leaf|node}_tx_{share|max}_set - rate_node_{new|del} - rate_{leaf|node}_parent_set KAPI provides: - creation/destruction of the leaf rate object associated with devlink port - destruction of rate nodes to allow a vendor driver to free allocated resources on driver removal or due to the other reasons when nodes destruction required UAPI provides: - dumping all or single rate objects - setting tx_{share|max} of rate object of any type - creating/deleting node rate object - setting/unsetting parent of any rate object Added devlink rate object support for netdevsim driver Issues/open questions: - Does user need DEVLINK_CMD_RATE_DEL_ALL_CHILD command to clean all children of particular parent node? For example: $ devlink port function rate flush netdevsim/netdevsim10/group - priv pointer passed to the callbacks is a source of bugs; in leaf case driver can embed rate object into internal structure and use container_of() on it; in node case it cannot be done since nodes are created from userspace v1->v2: - fixed kernel-doc for devlink_rate_leaf_{create|destroy}() - s/func/function/ for all devlink port command occurences v2->v3: - devlink: - added devlink_rate_nodes_destroy() function - netdevsim: - added call of devlink_rate_nodes_destroy() function ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/netdevsim')
-rw-r--r--drivers/net/netdevsim/bus.c129
-rw-r--r--drivers/net/netdevsim/dev.c396
-rw-r--r--drivers/net/netdevsim/netdev.c95
-rw-r--r--drivers/net/netdevsim/netdevsim.h48
4 files changed, 613 insertions, 55 deletions
diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c
index 0e9511661601..b56003dfe3cc 100644
--- a/drivers/net/netdevsim/bus.c
+++ b/drivers/net/netdevsim/bus.c
@@ -27,21 +27,34 @@ static struct nsim_bus_dev *to_nsim_bus_dev(struct device *dev)
static int nsim_bus_dev_vfs_enable(struct nsim_bus_dev *nsim_bus_dev,
unsigned int num_vfs)
{
- nsim_bus_dev->vfconfigs = kcalloc(num_vfs,
- sizeof(struct nsim_vf_config),
- GFP_KERNEL | __GFP_NOWARN);
+ struct nsim_dev *nsim_dev;
+ int err = 0;
+
+ if (nsim_bus_dev->max_vfs < num_vfs)
+ return -ENOMEM;
+
if (!nsim_bus_dev->vfconfigs)
return -ENOMEM;
nsim_bus_dev->num_vfs = num_vfs;
- return 0;
+ nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev);
+ if (nsim_esw_mode_is_switchdev(nsim_dev)) {
+ err = nsim_esw_switchdev_enable(nsim_dev, NULL);
+ if (err)
+ nsim_bus_dev->num_vfs = 0;
+ }
+
+ return err;
}
-static void nsim_bus_dev_vfs_disable(struct nsim_bus_dev *nsim_bus_dev)
+void nsim_bus_dev_vfs_disable(struct nsim_bus_dev *nsim_bus_dev)
{
- kfree(nsim_bus_dev->vfconfigs);
- nsim_bus_dev->vfconfigs = NULL;
+ struct nsim_dev *nsim_dev;
+
nsim_bus_dev->num_vfs = 0;
+ nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev);
+ if (nsim_esw_mode_is_switchdev(nsim_dev))
+ nsim_esw_legacy_enable(nsim_dev, NULL);
}
static ssize_t
@@ -56,7 +69,7 @@ nsim_bus_dev_numvfs_store(struct device *dev, struct device_attribute *attr,
if (ret)
return ret;
- rtnl_lock();
+ mutex_lock(&nsim_bus_dev->vfs_lock);
if (nsim_bus_dev->num_vfs == num_vfs)
goto exit_good;
if (nsim_bus_dev->num_vfs && num_vfs) {
@@ -74,7 +87,7 @@ nsim_bus_dev_numvfs_store(struct device *dev, struct device_attribute *attr,
exit_good:
ret = count;
exit_unlock:
- rtnl_unlock();
+ mutex_unlock(&nsim_bus_dev->vfs_lock);
return ret;
}
@@ -92,6 +105,79 @@ static struct device_attribute nsim_bus_dev_numvfs_attr =
__ATTR(sriov_numvfs, 0664, nsim_bus_dev_numvfs_show,
nsim_bus_dev_numvfs_store);
+ssize_t nsim_bus_dev_max_vfs_read(struct file *file,
+ char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct nsim_bus_dev *nsim_bus_dev = file->private_data;
+ char buf[11];
+ size_t len;
+
+ len = snprintf(buf, sizeof(buf), "%u\n", nsim_bus_dev->max_vfs);
+ if (len < 0)
+ return len;
+
+ return simple_read_from_buffer(data, count, ppos, buf, len);
+}
+
+ssize_t nsim_bus_dev_max_vfs_write(struct file *file,
+ const char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct nsim_bus_dev *nsim_bus_dev = file->private_data;
+ struct nsim_vf_config *vfconfigs;
+ ssize_t ret;
+ char buf[10];
+ u32 val;
+
+ if (*ppos != 0)
+ return 0;
+
+ if (count >= sizeof(buf))
+ return -ENOSPC;
+
+ mutex_lock(&nsim_bus_dev->vfs_lock);
+ /* Reject if VFs are configured */
+ if (nsim_bus_dev->num_vfs) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = copy_from_user(buf, data, count);
+ if (ret) {
+ ret = -EFAULT;
+ goto unlock;
+ }
+
+ buf[count] = '\0';
+ ret = kstrtouint(buf, 10, &val);
+ if (ret) {
+ ret = -EIO;
+ goto unlock;
+ }
+
+ /* max_vfs limited by the maximum number of provided port indexes */
+ if (val > NSIM_DEV_VF_PORT_INDEX_MAX - NSIM_DEV_VF_PORT_INDEX_BASE) {
+ ret = -ERANGE;
+ goto unlock;
+ }
+
+ vfconfigs = kcalloc(val, sizeof(struct nsim_vf_config), GFP_KERNEL | __GFP_NOWARN);
+ if (!vfconfigs) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ kfree(nsim_bus_dev->vfconfigs);
+ nsim_bus_dev->vfconfigs = vfconfigs;
+ nsim_bus_dev->max_vfs = val;
+ *ppos += count;
+ ret = count;
+unlock:
+ mutex_unlock(&nsim_bus_dev->vfs_lock);
+ return ret;
+}
+
static ssize_t
new_port_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
@@ -113,7 +199,7 @@ new_port_store(struct device *dev, struct device_attribute *attr,
mutex_lock(&nsim_bus_dev->nsim_bus_reload_lock);
devlink_reload_disable(devlink);
- ret = nsim_dev_port_add(nsim_bus_dev, port_index);
+ ret = nsim_dev_port_add(nsim_bus_dev, NSIM_DEV_PORT_TYPE_PF, port_index);
devlink_reload_enable(devlink);
mutex_unlock(&nsim_bus_dev->nsim_bus_reload_lock);
return ret ? ret : count;
@@ -142,7 +228,7 @@ del_port_store(struct device *dev, struct device_attribute *attr,
mutex_lock(&nsim_bus_dev->nsim_bus_reload_lock);
devlink_reload_disable(devlink);
- ret = nsim_dev_port_del(nsim_bus_dev, port_index);
+ ret = nsim_dev_port_del(nsim_bus_dev, NSIM_DEV_PORT_TYPE_PF, port_index);
devlink_reload_enable(devlink);
mutex_unlock(&nsim_bus_dev->nsim_bus_reload_lock);
return ret ? ret : count;
@@ -168,9 +254,6 @@ static const struct attribute_group *nsim_bus_dev_attr_groups[] = {
static void nsim_bus_dev_release(struct device *dev)
{
- struct nsim_bus_dev *nsim_bus_dev = to_nsim_bus_dev(dev);
-
- nsim_bus_dev_vfs_disable(nsim_bus_dev);
}
static struct device_type nsim_bus_dev_type = {
@@ -311,6 +394,8 @@ static struct bus_type nsim_bus = {
.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)
{
@@ -329,15 +414,28 @@ nsim_bus_dev_new(unsigned int id, unsigned int port_count)
nsim_bus_dev->dev.type = &nsim_bus_dev_type;
nsim_bus_dev->port_count = port_count;
nsim_bus_dev->initial_net = current->nsproxy->net_ns;
+ nsim_bus_dev->max_vfs = NSIM_BUS_DEV_MAX_VFS;
mutex_init(&nsim_bus_dev->nsim_bus_reload_lock);
+ mutex_init(&nsim_bus_dev->vfs_lock);
/* Disallow using nsim_bus_dev */
smp_store_release(&nsim_bus_dev->init, false);
+ nsim_bus_dev->vfconfigs = kcalloc(nsim_bus_dev->max_vfs,
+ sizeof(struct nsim_vf_config),
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!nsim_bus_dev->vfconfigs) {
+ err = -ENOMEM;
+ goto err_nsim_bus_dev_id_free;
+ }
+
err = device_register(&nsim_bus_dev->dev);
if (err)
- goto err_nsim_bus_dev_id_free;
+ goto err_nsim_vfs_free;
+
return nsim_bus_dev;
+err_nsim_vfs_free:
+ kfree(nsim_bus_dev->vfconfigs);
err_nsim_bus_dev_id_free:
ida_free(&nsim_bus_dev_ids, nsim_bus_dev->dev.id);
err_nsim_bus_dev_free:
@@ -351,6 +449,7 @@ static void nsim_bus_dev_del(struct nsim_bus_dev *nsim_bus_dev)
smp_store_release(&nsim_bus_dev->init, false);
device_unregister(&nsim_bus_dev->dev);
ida_free(&nsim_bus_dev_ids, nsim_bus_dev->dev.id);
+ kfree(nsim_bus_dev->vfconfigs);
kfree(nsim_bus_dev);
}
diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c
index 6189a4c0d39e..527b019ae0b2 100644
--- a/drivers/net/netdevsim/dev.c
+++ b/drivers/net/netdevsim/dev.c
@@ -35,6 +35,25 @@
#include "netdevsim.h"
+static unsigned int
+nsim_dev_port_index(enum nsim_dev_port_type type, unsigned int port_index)
+{
+ switch (type) {
+ case NSIM_DEV_PORT_TYPE_VF:
+ port_index = NSIM_DEV_VF_PORT_INDEX_BASE + port_index;
+ break;
+ case NSIM_DEV_PORT_TYPE_PF:
+ break;
+ }
+
+ return port_index;
+}
+
+static inline unsigned int nsim_dev_port_index_to_vf_index(unsigned int port_index)
+{
+ return port_index - NSIM_DEV_VF_PORT_INDEX_BASE;
+}
+
static struct dentry *nsim_dev_ddir;
#define NSIM_DEV_DUMMY_REGION_SIZE (1024 * 32)
@@ -192,9 +211,18 @@ static const struct file_operations nsim_dev_trap_fa_cookie_fops = {
.owner = THIS_MODULE,
};
+static const struct file_operations nsim_dev_max_vfs_fops = {
+ .open = simple_open,
+ .read = nsim_bus_dev_max_vfs_read,
+ .write = nsim_bus_dev_max_vfs_write,
+ .llseek = generic_file_llseek,
+ .owner = THIS_MODULE,
+};
+
static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev)
{
char dev_ddir_name[sizeof(DRV_NAME) + 10];
+ int err;
sprintf(dev_ddir_name, DRV_NAME "%u", nsim_dev->nsim_bus_dev->dev.id);
nsim_dev->ddir = debugfs_create_dir(dev_ddir_name, nsim_dev_ddir);
@@ -231,30 +259,81 @@ static int nsim_dev_debugfs_init(struct nsim_dev *nsim_dev)
debugfs_create_bool("fail_trap_policer_counter_get", 0600,
nsim_dev->ddir,
&nsim_dev->fail_trap_policer_counter_get);
+ nsim_dev->max_vfs = debugfs_create_file("max_vfs",
+ 0600,
+ nsim_dev->ddir,
+ nsim_dev->nsim_bus_dev,
+ &nsim_dev_max_vfs_fops);
+ nsim_dev->nodes_ddir = debugfs_create_dir("rate_nodes", nsim_dev->ddir);
+ if (IS_ERR(nsim_dev->nodes_ddir)) {
+ err = PTR_ERR(nsim_dev->nodes_ddir);
+ goto err_out;
+ }
nsim_udp_tunnels_debugfs_create(nsim_dev);
return 0;
+
+err_out:
+ debugfs_remove_recursive(nsim_dev->ports_ddir);
+ debugfs_remove_recursive(nsim_dev->ddir);
+ return err;
}
static void nsim_dev_debugfs_exit(struct nsim_dev *nsim_dev)
{
+ debugfs_remove_recursive(nsim_dev->nodes_ddir);
debugfs_remove_recursive(nsim_dev->ports_ddir);
debugfs_remove_recursive(nsim_dev->ddir);
}
+static ssize_t nsim_dev_rate_parent_read(struct file *file,
+ char __user *data,
+ size_t count, loff_t *ppos)
+{
+ char **name_ptr = file->private_data;
+ size_t len;
+
+ if (!*name_ptr)
+ return 0;
+
+ len = strlen(*name_ptr);
+ return simple_read_from_buffer(data, count, ppos, *name_ptr, len);
+}
+
+static const struct file_operations nsim_dev_rate_parent_fops = {
+ .open = simple_open,
+ .read = nsim_dev_rate_parent_read,
+ .llseek = generic_file_llseek,
+ .owner = THIS_MODULE,
+};
+
static int nsim_dev_port_debugfs_init(struct nsim_dev *nsim_dev,
struct nsim_dev_port *nsim_dev_port)
{
+ struct nsim_bus_dev *nsim_bus_dev = nsim_dev->nsim_bus_dev;
+ unsigned int port_index = nsim_dev_port->port_index;
char port_ddir_name[16];
char dev_link_name[32];
- sprintf(port_ddir_name, "%u", nsim_dev_port->port_index);
+ sprintf(port_ddir_name, "%u", port_index);
nsim_dev_port->ddir = debugfs_create_dir(port_ddir_name,
nsim_dev->ports_ddir);
if (IS_ERR(nsim_dev_port->ddir))
return PTR_ERR(nsim_dev_port->ddir);
- sprintf(dev_link_name, "../../../" DRV_NAME "%u",
- nsim_dev->nsim_bus_dev->dev.id);
+ sprintf(dev_link_name, "../../../" DRV_NAME "%u", nsim_bus_dev->dev.id);
+ if (nsim_dev_port_is_vf(nsim_dev_port)) {
+ unsigned int vf_id = nsim_dev_port_index_to_vf_index(port_index);
+
+ debugfs_create_u16("tx_share", 0400, nsim_dev_port->ddir,
+ &nsim_bus_dev->vfconfigs[vf_id].min_tx_rate);
+ debugfs_create_u16("tx_max", 0400, nsim_dev_port->ddir,
+ &nsim_bus_dev->vfconfigs[vf_id].max_tx_rate);
+ nsim_dev_port->rate_parent = debugfs_create_file("rate_parent",
+ 0400,
+ nsim_dev_port->ddir,
+ &nsim_dev_port->parent_name,
+ &nsim_dev_rate_parent_fops);
+ }
debugfs_create_symlink("dev", nsim_dev_port->ddir, dev_link_name);
return 0;
@@ -407,6 +486,74 @@ static void nsim_dev_dummy_region_exit(struct nsim_dev *nsim_dev)
devlink_region_destroy(nsim_dev->dummy_region);
}
+static void __nsim_dev_port_del(struct nsim_dev_port *nsim_dev_port);
+int nsim_esw_legacy_enable(struct nsim_dev *nsim_dev, struct netlink_ext_ack *extack)
+{
+ struct devlink *devlink = priv_to_devlink(nsim_dev);
+ struct nsim_dev_port *nsim_dev_port, *tmp;
+
+ devlink_rate_nodes_destroy(devlink);
+ mutex_lock(&nsim_dev->port_list_lock);
+ list_for_each_entry_safe(nsim_dev_port, tmp, &nsim_dev->port_list, list)
+ if (nsim_dev_port_is_vf(nsim_dev_port))
+ __nsim_dev_port_del(nsim_dev_port);
+ mutex_unlock(&nsim_dev->port_list_lock);
+ nsim_dev->esw_mode = DEVLINK_ESWITCH_MODE_LEGACY;
+ return 0;
+}
+
+int nsim_esw_switchdev_enable(struct nsim_dev *nsim_dev, struct netlink_ext_ack *extack)
+{
+ struct nsim_bus_dev *nsim_bus_dev = nsim_dev->nsim_bus_dev;
+ int i, err;
+
+ for (i = 0; i < nsim_bus_dev->num_vfs; i++) {
+ err = nsim_dev_port_add(nsim_bus_dev, NSIM_DEV_PORT_TYPE_VF, i);
+ if (err) {
+ NL_SET_ERR_MSG_MOD(extack, "Failed to initialize VFs' netdevsim ports");
+ pr_err("Failed to initialize VF id=%d. %d.\n", i, err);
+ goto err_port_add_vfs;
+ }
+ }
+ nsim_dev->esw_mode = DEVLINK_ESWITCH_MODE_SWITCHDEV;
+ return 0;
+
+err_port_add_vfs:
+ for (i--; i >= 0; i--)
+ nsim_dev_port_del(nsim_bus_dev, NSIM_DEV_PORT_TYPE_VF, i);
+ return err;
+}
+
+static int nsim_devlink_eswitch_mode_set(struct devlink *devlink, u16 mode,
+ struct netlink_ext_ack *extack)
+{
+ struct nsim_dev *nsim_dev = devlink_priv(devlink);
+ int err = 0;
+
+ mutex_lock(&nsim_dev->nsim_bus_dev->vfs_lock);
+ if (mode == nsim_dev->esw_mode)
+ goto unlock;
+
+ if (mode == DEVLINK_ESWITCH_MODE_LEGACY)
+ err = nsim_esw_legacy_enable(nsim_dev, extack);
+ else if (mode == DEVLINK_ESWITCH_MODE_SWITCHDEV)
+ err = nsim_esw_switchdev_enable(nsim_dev, extack);
+ else
+ err = -EINVAL;
+
+unlock:
+ mutex_unlock(&nsim_dev->nsim_bus_dev->vfs_lock);
+ return err;
+}
+
+static int nsim_devlink_eswitch_mode_get(struct devlink *devlink, u16 *mode)
+{
+ struct nsim_dev *nsim_dev = devlink_priv(devlink);
+
+ *mode = nsim_dev->esw_mode;
+ return 0;
+}
+
struct nsim_trap_item {
void *trap_ctx;
enum devlink_trap_action action;
@@ -892,7 +1039,187 @@ nsim_dev_devlink_trap_policer_counter_get(struct devlink *devlink,
return 0;
}
+#define NSIM_LINK_SPEED_MAX 5000 /* Mbps */
+#define NSIM_LINK_SPEED_UNIT 125000 /* 1 Mbps given in bytes/sec to avoid
+ * u64 overflow during conversion from
+ * bytes to bits.
+ */
+
+static int nsim_rate_bytes_to_units(char *name, u64 *rate, struct netlink_ext_ack *extack)
+{
+ u64 val;
+ u32 rem;
+
+ val = div_u64_rem(*rate, NSIM_LINK_SPEED_UNIT, &rem);
+ if (rem) {
+ pr_err("%s rate value %lluBps not in link speed units of 1Mbps.\n",
+ name, *rate);
+ NL_SET_ERR_MSG_MOD(extack, "TX rate value not in link speed units of 1Mbps.");
+ return -EINVAL;
+ }
+
+ if (val > NSIM_LINK_SPEED_MAX) {
+ pr_err("%s rate value %lluMbps exceed link maximum speed 5000Mbps.\n",
+ name, val);
+ NL_SET_ERR_MSG_MOD(extack, "TX rate value exceed link maximum speed 5000Mbps.");
+ return -EINVAL;
+ }
+ *rate = val;
+ return 0;
+}
+
+static int nsim_leaf_tx_share_set(struct devlink_rate *devlink_rate, void *priv,
+ u64 tx_share, struct netlink_ext_ack *extack)
+{
+ struct nsim_dev_port *nsim_dev_port = priv;
+ struct nsim_bus_dev *nsim_bus_dev = nsim_dev_port->ns->nsim_bus_dev;
+ int vf_id = nsim_dev_port_index_to_vf_index(nsim_dev_port->port_index);
+ int err;
+
+ err = nsim_rate_bytes_to_units("tx_share", &tx_share, extack);
+ if (err)
+ return err;
+
+ nsim_bus_dev->vfconfigs[vf_id].min_tx_rate = tx_share;
+ return 0;
+}
+
+static int nsim_leaf_tx_max_set(struct devlink_rate *devlink_rate, void *priv,
+ u64 tx_max, struct netlink_ext_ack *extack)
+{
+ struct nsim_dev_port *nsim_dev_port = priv;
+ struct nsim_bus_dev *nsim_bus_dev = nsim_dev_port->ns->nsim_bus_dev;
+ int vf_id = nsim_dev_port_index_to_vf_index(nsim_dev_port->port_index);
+ int err;
+
+ err = nsim_rate_bytes_to_units("tx_max", &tx_max, extack);
+ if (err)
+ return err;
+
+ nsim_bus_dev->vfconfigs[vf_id].max_tx_rate = tx_max;
+ return 0;
+}
+
+struct nsim_rate_node {
+ struct dentry *ddir;
+ struct dentry *rate_parent;
+ char *parent_name;
+ u16 tx_share;
+ u16 tx_max;
+};
+
+static int nsim_node_tx_share_set(struct devlink_rate *devlink_rate, void *priv,
+ u64 tx_share, struct netlink_ext_ack *extack)
+{
+ struct nsim_rate_node *nsim_node = priv;
+ int err;
+
+ err = nsim_rate_bytes_to_units("tx_share", &tx_share, extack);
+ if (err)
+ return err;
+
+ nsim_node->tx_share = tx_share;
+ return 0;
+}
+
+static int nsim_node_tx_max_set(struct devlink_rate *devlink_rate, void *priv,
+ u64 tx_max, struct netlink_ext_ack *extack)
+{
+ struct nsim_rate_node *nsim_node = priv;
+ int err;
+
+ err = nsim_rate_bytes_to_units("tx_max", &tx_max, extack);
+ if (err)
+ return err;
+
+ nsim_node->tx_max = tx_max;
+ return 0;
+}
+
+static int nsim_rate_node_new(struct devlink_rate *node, void **priv,
+ struct netlink_ext_ack *extack)
+{
+ struct nsim_dev *nsim_dev = devlink_priv(node->devlink);
+ struct nsim_rate_node *nsim_node;
+ int err;
+
+ if (!nsim_esw_mode_is_switchdev(nsim_dev)) {
+ NL_SET_ERR_MSG_MOD(extack, "Node creation allowed only in switchdev mode.");
+ return -EOPNOTSUPP;
+ }
+
+ nsim_node = kzalloc(sizeof(*nsim_node), GFP_KERNEL);
+ if (!nsim_node)
+ return -ENOMEM;
+
+ nsim_node->ddir = debugfs_create_dir(node->name, nsim_dev->nodes_ddir);
+ if (!nsim_node->ddir) {
+ err = -ENOMEM;
+ goto err_node;
+ }
+ debugfs_create_u16("tx_share", 0400, nsim_node->ddir, &nsim_node->tx_share);
+ debugfs_create_u16("tx_max", 0400, nsim_node->ddir, &nsim_node->tx_max);
+ nsim_node->rate_parent = debugfs_create_file("rate_parent", 0400,
+ nsim_node->ddir,
+ &nsim_node->parent_name,
+ &nsim_dev_rate_parent_fops);
+ if (IS_ERR(nsim_node->rate_parent)) {
+ err = PTR_ERR(nsim_node->rate_parent);
+ goto err_ddir;
+ }
+
+ *priv = nsim_node;
+ return 0;
+
+err_ddir:
+ debugfs_remove_recursive(nsim_node->ddir);
+err_node:
+ kfree(nsim_node);
+ return err;
+}
+
+static int nsim_rate_node_del(struct devlink_rate *node, void *priv,
+ struct netlink_ext_ack *extack)
+{
+ struct nsim_rate_node *nsim_node = priv;
+
+ debugfs_remove(nsim_node->rate_parent);
+ debugfs_remove_recursive(nsim_node->ddir);
+ kfree(nsim_node);
+ return 0;
+}
+
+static int nsim_rate_leaf_parent_set(struct devlink_rate *child,
+ struct devlink_rate *parent,
+ void *priv_child, void *priv_parent,
+ struct netlink_ext_ack *extack)
+{
+ struct nsim_dev_port *nsim_dev_port = priv_child;
+
+ if (parent)
+ nsim_dev_port->parent_name = parent->name;
+ else
+ nsim_dev_port->parent_name = NULL;
+ return 0;
+}
+
+static int nsim_rate_node_parent_set(struct devlink_rate *child,
+ struct devlink_rate *parent,
+ void *priv_child, void *priv_parent,
+ struct netlink_ext_ack *extack)
+{
+ struct nsim_rate_node *nsim_node = priv_child;
+
+ if (parent)
+ nsim_node->parent_name = parent->name;
+ else
+ nsim_node->parent_name = NULL;
+ return 0;
+}
+
static const struct devlink_ops nsim_dev_devlink_ops = {
+ .eswitch_mode_set = nsim_devlink_eswitch_mode_set,
+ .eswitch_mode_get = nsim_devlink_eswitch_mode_get,
.supported_flash_update_params = DEVLINK_SUPPORT_FLASH_UPDATE_COMPONENT |
DEVLINK_SUPPORT_FLASH_UPDATE_OVERWRITE_MASK,
.reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT),
@@ -905,32 +1232,51 @@ static const struct devlink_ops nsim_dev_devlink_ops = {
.trap_group_set = nsim_dev_devlink_trap_group_set,
.trap_policer_set = nsim_dev_devlink_trap_policer_set,
.trap_policer_counter_get = nsim_dev_devlink_trap_policer_counter_get,
+ .rate_leaf_tx_share_set = nsim_leaf_tx_share_set,
+ .rate_leaf_tx_max_set = nsim_leaf_tx_max_set,
+ .rate_node_tx_share_set = nsim_node_tx_share_set,
+ .rate_node_tx_max_set = nsim_node_tx_max_set,
+ .rate_node_new = nsim_rate_node_new,
+ .rate_node_del = nsim_rate_node_del,
+ .rate_leaf_parent_set = nsim_rate_leaf_parent_set,
+ .rate_node_parent_set = nsim_rate_node_parent_set,
};
#define NSIM_DEV_MAX_MACS_DEFAULT 32
#define NSIM_DEV_TEST1_DEFAULT true
-static int __nsim_dev_port_add(struct nsim_dev *nsim_dev,
+static int __nsim_dev_port_add(struct nsim_dev *nsim_dev, enum nsim_dev_port_type type,
unsigned int port_index)
{
+ struct nsim_bus_dev *nsim_bus_dev = nsim_dev->nsim_bus_dev;
struct devlink_port_attrs attrs = {};
struct nsim_dev_port *nsim_dev_port;
struct devlink_port *devlink_port;
int err;
+ if (type == NSIM_DEV_PORT_TYPE_VF && !nsim_bus_dev->num_vfs)
+ return -EINVAL;
+
nsim_dev_port = kzalloc(sizeof(*nsim_dev_port), GFP_KERNEL);
if (!nsim_dev_port)
return -ENOMEM;
- nsim_dev_port->port_index = port_index;
+ nsim_dev_port->port_index = nsim_dev_port_index(type, port_index);
+ nsim_dev_port->port_type = type;
devlink_port = &nsim_dev_port->devlink_port;
- attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
- attrs.phys.port_number = port_index + 1;
+ if (nsim_dev_port_is_pf(nsim_dev_port)) {
+ attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
+ attrs.phys.port_number = port_index + 1;
+ } else {
+ attrs.flavour = DEVLINK_PORT_FLAVOUR_PCI_VF;
+ attrs.pci_vf.pf = 0;
+ attrs.pci_vf.vf = port_index;
+ }
memcpy(attrs.switch_id.id, nsim_dev->switch_id.id, nsim_dev->switch_id.id_len);
attrs.switch_id.id_len = nsim_dev->switch_id.id_len;
devlink_port_attrs_set(devlink_port, &attrs);
err = devlink_port_register(priv_to_devlink(nsim_dev), devlink_port,
- port_index);
+ nsim_dev_port->port_index);
if (err)
goto err_port_free;
@@ -944,11 +1290,20 @@ static int __nsim_dev_port_add(struct nsim_dev *nsim_dev,
goto err_port_debugfs_exit;
}
+ if (nsim_dev_port_is_vf(nsim_dev_port)) {
+ err = devlink_rate_leaf_create(&nsim_dev_port->devlink_port,
+ nsim_dev_port);
+ if (err)
+ goto err_nsim_destroy;
+ }
+
devlink_port_type_eth_set(devlink_port, nsim_dev_port->ns->netdev);
list_add(&nsim_dev_port->list, &nsim_dev->port_list);
return 0;
+err_nsim_destroy:
+ nsim_destroy(nsim_dev_port->ns);
err_port_debugfs_exit:
nsim_dev_port_debugfs_exit(nsim_dev_port);
err_dl_port_unregister:
@@ -963,6 +1318,8 @@ static void __nsim_dev_port_del(struct nsim_dev_port *nsim_dev_port)
struct devlink_port *devlink_port = &nsim_dev_port->devlink_port;
list_del(&nsim_dev_port->list);
+ if (nsim_dev_port_is_vf(nsim_dev_port))
+ devlink_rate_leaf_destroy(&nsim_dev_port->devlink_port);
devlink_port_type_clear(devlink_port);
nsim_destroy(nsim_dev_port->ns);
nsim_dev_port_debugfs_exit(nsim_dev_port);
@@ -987,7 +1344,7 @@ static int nsim_dev_port_add_all(struct nsim_dev *nsim_dev,
int i, err;
for (i = 0; i < port_count; i++) {
- err = __nsim_dev_port_add(nsim_dev, i);
+ err = __nsim_dev_port_add(nsim_dev, NSIM_DEV_PORT_TYPE_PF, i);
if (err)
goto err_port_del_all;
}
@@ -1134,6 +1491,7 @@ int nsim_dev_probe(struct nsim_bus_dev *nsim_bus_dev)
devlink_params_publish(devlink);
devlink_reload_enable(devlink);
+ nsim_dev->esw_mode = DEVLINK_ESWITCH_MODE_LEGACY;
return 0;
err_psample_exit:
@@ -1169,6 +1527,12 @@ static void nsim_dev_reload_destroy(struct nsim_dev *nsim_dev)
if (devlink_is_reload_failed(devlink))
return;
debugfs_remove(nsim_dev->take_snapshot);
+
+ mutex_lock(&nsim_dev->nsim_bus_dev->vfs_lock);
+ if (nsim_dev->nsim_bus_dev->num_vfs)
+ nsim_bus_dev_vfs_disable(nsim_dev->nsim_bus_dev);
+ mutex_unlock(&nsim_dev->nsim_bus_dev->vfs_lock);
+
nsim_dev_port_del_all(nsim_dev);
nsim_dev_psample_exit(nsim_dev);
nsim_dev_health_exit(nsim_dev);
@@ -1197,32 +1561,34 @@ void nsim_dev_remove(struct nsim_bus_dev *nsim_bus_dev)
}
static struct nsim_dev_port *
-__nsim_dev_port_lookup(struct nsim_dev *nsim_dev, unsigned int port_index)
+__nsim_dev_port_lookup(struct nsim_dev *nsim_dev, enum nsim_dev_port_type type,
+ unsigned int port_index)
{
struct nsim_dev_port *nsim_dev_port;
+ port_index = nsim_dev_port_index(type, port_index);
list_for_each_entry(nsim_dev_port, &nsim_dev->port_list, list)
if (nsim_dev_port->port_index == port_index)
return nsim_dev_port;
return NULL;
}
-int nsim_dev_port_add(struct nsim_bus_dev *nsim_bus_dev,
+int nsim_dev_port_add(struct nsim_bus_dev *nsim_bus_dev, enum nsim_dev_port_type type,
unsigned int port_index)
{
struct nsim_dev *nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev);
int err;
mutex_lock(&nsim_dev->port_list_lock);
- if (__nsim_dev_port_lookup(nsim_dev, port_index))
+ if (__nsim_dev_port_lookup(nsim_dev, type, port_index))
err = -EEXIST;
else
- err = __nsim_dev_port_add(nsim_dev, port_index);
+ err = __nsim_dev_port_add(nsim_dev, type, port_index);
mutex_unlock(&nsim_dev->port_list_lock);
return err;
}
-int nsim_dev_port_del(struct nsim_bus_dev *nsim_bus_dev,
+int nsim_dev_port_del(struct nsim_bus_dev *nsim_bus_dev, enum nsim_dev_port_type type,
unsigned int port_index)
{
struct nsim_dev *nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev);
@@ -1230,7 +1596,7 @@ int nsim_dev_port_del(struct nsim_bus_dev *nsim_bus_dev,
int err = 0;
mutex_lock(&nsim_dev->port_list_lock);
- nsim_dev_port = __nsim_dev_port_lookup(nsim_dev, port_index);
+ nsim_dev_port = __nsim_dev_port_lookup(nsim_dev, type, port_index);
if (!nsim_dev_port)
err = -ENOENT;
else
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index 659d3dceb687..c3aeb15843e2 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -113,6 +113,11 @@ static int nsim_set_vf_rate(struct net_device *dev, int vf, int min, int max)
struct netdevsim *ns = netdev_priv(dev);
struct nsim_bus_dev *nsim_bus_dev = ns->nsim_bus_dev;
+ if (nsim_esw_mode_is_switchdev(ns->nsim_dev)) {
+ pr_err("Not supported in switchdev mode. Please use devlink API.\n");
+ return -EOPNOTSUPP;
+ }
+
if (vf >= nsim_bus_dev->num_vfs)
return -EINVAL;
@@ -261,6 +266,18 @@ static const struct net_device_ops nsim_netdev_ops = {
.ndo_get_devlink_port = nsim_get_devlink_port,
};
+static const struct net_device_ops nsim_vf_netdev_ops = {
+ .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_setup_tc = nsim_setup_tc,
+ .ndo_set_features = nsim_set_features,
+ .ndo_get_devlink_port = nsim_get_devlink_port,
+};
+
static void nsim_setup(struct net_device *dev)
{
ether_setup(dev);
@@ -280,6 +297,49 @@ static void nsim_setup(struct net_device *dev)
dev->max_mtu = ETH_MAX_MTU;
}
+static int nsim_init_netdevsim(struct netdevsim *ns)
+{
+ int err;
+
+ ns->netdev->netdev_ops = &nsim_netdev_ops;
+
+ err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev);
+ if (err)
+ return err;
+
+ rtnl_lock();
+ err = nsim_bpf_init(ns);
+ if (err)
+ goto err_utn_destroy;
+
+ nsim_ipsec_init(ns);
+
+ err = register_netdevice(ns->netdev);
+ if (err)
+ goto err_ipsec_teardown;
+ rtnl_unlock();
+ return 0;
+
+err_ipsec_teardown:
+ nsim_ipsec_teardown(ns);
+ nsim_bpf_uninit(ns);
+err_utn_destroy:
+ rtnl_unlock();
+ nsim_udp_tunnels_info_destroy(ns->netdev);
+ return err;
+}
+
+static int nsim_init_netdevsim_vf(struct netdevsim *ns)
+{
+ int err;
+
+ ns->netdev->netdev_ops = &nsim_vf_netdev_ops;
+ rtnl_lock();
+ err = register_netdevice(ns->netdev);
+ rtnl_unlock();
+ return err;
+}
+
struct netdevsim *
nsim_create(struct nsim_dev *nsim_dev, struct nsim_dev_port *nsim_dev_port)
{
@@ -299,33 +359,15 @@ nsim_create(struct nsim_dev *nsim_dev, struct nsim_dev_port *nsim_dev_port)
ns->nsim_dev_port = nsim_dev_port;
ns->nsim_bus_dev = nsim_dev->nsim_bus_dev;
SET_NETDEV_DEV(dev, &ns->nsim_bus_dev->dev);
- dev->netdev_ops = &nsim_netdev_ops;
nsim_ethtool_init(ns);
-
- err = nsim_udp_tunnels_info_create(nsim_dev, dev);
+ if (nsim_dev_port_is_pf(nsim_dev_port))
+ err = nsim_init_netdevsim(ns);
+ else
+ err = nsim_init_netdevsim_vf(ns);
if (err)
goto err_free_netdev;
-
- rtnl_lock();
- err = nsim_bpf_init(ns);
- if (err)
- goto err_utn_destroy;
-
- nsim_ipsec_init(ns);
-
- err = register_netdevice(dev);
- if (err)
- goto err_ipsec_teardown;
- rtnl_unlock();
-
return ns;
-err_ipsec_teardown:
- nsim_ipsec_teardown(ns);
- nsim_bpf_uninit(ns);
-err_utn_destroy:
- rtnl_unlock();
- nsim_udp_tunnels_info_destroy(dev);
err_free_netdev:
free_netdev(dev);
return ERR_PTR(err);
@@ -337,10 +379,13 @@ void nsim_destroy(struct netdevsim *ns)
rtnl_lock();
unregister_netdevice(dev);
- nsim_ipsec_teardown(ns);
- nsim_bpf_uninit(ns);
+ if (nsim_dev_port_is_pf(ns->nsim_dev_port)) {
+ nsim_ipsec_teardown(ns);
+ nsim_bpf_uninit(ns);
+ }
rtnl_unlock();
- nsim_udp_tunnels_info_destroy(dev);
+ if (nsim_dev_port_is_pf(ns->nsim_dev_port))
+ nsim_udp_tunnels_info_destroy(dev);
free_netdev(dev);
}
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index 7ff24e03577b..cdfdf2a99578 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -197,11 +197,22 @@ 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;
struct netdevsim *ns;
};
@@ -212,6 +223,8 @@ struct nsim_dev {
struct dentry *ddir;
struct dentry *ports_ddir;
struct dentry *take_snapshot;
+ struct dentry *max_vfs;
+ struct dentry *nodes_ddir;
struct bpf_offload_dev *bpf_dev;
bool bpf_bind_accept;
bool bpf_bind_verifier_accept;
@@ -247,8 +260,22 @@ struct nsim_dev {
u32 sleep;
} udp_ports;
struct nsim_dev_psample *psample;
+ u16 esw_mode;
};
+int nsim_esw_legacy_enable(struct nsim_dev *nsim_dev, struct netlink_ext_ack *extack);
+int nsim_esw_switchdev_enable(struct nsim_dev *nsim_dev, struct netlink_ext_ack *extack);
+
+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));
@@ -259,8 +286,10 @@ void nsim_dev_exit(void);
int nsim_dev_probe(struct nsim_bus_dev *nsim_bus_dev);
void nsim_dev_remove(struct nsim_bus_dev *nsim_bus_dev);
int nsim_dev_port_add(struct nsim_bus_dev *nsim_bus_dev,
+ enum nsim_dev_port_type type,
unsigned int port_index);
int nsim_dev_port_del(struct nsim_bus_dev *nsim_bus_dev,
+ enum nsim_dev_port_type type,
unsigned int port_index);
struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
@@ -269,6 +298,23 @@ 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);
+ssize_t nsim_bus_dev_max_vfs_read(struct file *file,
+ char __user *data,
+ size_t count, loff_t *ppos);
+ssize_t nsim_bus_dev_max_vfs_write(struct file *file,
+ const char __user *data,
+ size_t count, loff_t *ppos);
+void nsim_bus_dev_vfs_disable(struct nsim_bus_dev *nsim_bus_dev);
+
+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);
@@ -308,7 +354,9 @@ struct nsim_bus_dev {
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;
+ struct mutex vfs_lock; /* Protects vfconfigs */
struct nsim_vf_config *vfconfigs;
/* Lock for devlink->reload_enabled in netdevsim module */
struct mutex nsim_bus_reload_lock;