mlx5 sets MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS when acknowledging a sync reset request. This bit blocks devlink reload and other devlink operations while the firmware reset is running, but it was kept set until after the driver reload finished. Clear the reset-in-progress bit once the reset unload flow is done and PCI access is back, before reloading the device. For a reset initiated through devlink, clear it before completing the reload waiter. For a reset reported through an asynchronous firmware event, keep the unload flow outside devl_lock, then take devl_lock before clearing the bit and reloading through the devl-locked load helper. Reviewed-by: Shay Drori Reviewed-by: Moshe Shemesh Signed-off-by: Mark Bloch --- .../ethernet/mellanox/mlx5/core/fw_reset.c | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c index 07440c58713a..7283e5b49eed 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c @@ -238,24 +238,30 @@ static void mlx5_fw_reset_complete_reload(struct mlx5_core_dev *dev) { struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset; struct devlink *devlink = priv_to_devlink(dev); + int err; /* if this is the driver that initiated the fw reset, devlink completed the reload */ if (test_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags)) { + clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, + &fw_reset->reset_flags); complete(&fw_reset->done); - } else { - mlx5_sync_reset_unload_flow(dev, false); - if (mlx5_health_wait_pci_up(dev)) - mlx5_core_err(dev, "reset reload flow aborted, PCI reads still not working\n"); - else - mlx5_load_one(dev, true); - devl_lock(devlink); - devlink_remote_reload_actions_performed(devlink, 0, - BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) | - BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE)); - devl_unlock(devlink); + return; } + mlx5_sync_reset_unload_flow(dev, false); + err = mlx5_health_wait_pci_up(dev); + + devl_lock(devlink); clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags); + if (err) + mlx5_core_err(dev, "reset reload flow aborted, PCI reads still not working\n"); + else + mlx5_load_one_devl_locked(dev, true); + + devlink_remote_reload_actions_performed(devlink, 0, + BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) | + BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE)); + devl_unlock(devlink); } static void mlx5_stop_sync_reset_poll(struct mlx5_core_dev *dev) -- 2.43.0 Move the common eswitch mode set checks into a small helper and use it from the netlink eswitch set command. Making the same validation available to the devlink core path that applies eswitch mode defaults. Signed-off-by: Mark Bloch --- net/devlink/dev.c | 27 ++++++++++++++++++++------- net/devlink/devl_internal.h | 3 +++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/net/devlink/dev.c b/net/devlink/dev.c index bcf001554e84..119ef105d0a7 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -702,6 +702,25 @@ int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) return genlmsg_reply(msg, info); } +int devlink_eswitch_mode_set(struct devlink *devlink, + enum devlink_eswitch_mode mode, + struct netlink_ext_ack *extack) +{ + const struct devlink_ops *ops = devlink->ops; + int err; + + devl_assert_locked(devlink); + + if (!ops->eswitch_mode_set) + return -EOPNOTSUPP; + + err = devlink_rates_check(devlink, devlink_rate_is_node, extack); + if (err) + return err; + + return ops->eswitch_mode_set(devlink, mode, extack); +} + int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) { struct devlink *devlink = devlink_nl_ctx(info)->devlink; @@ -712,14 +731,8 @@ int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) u16 mode; if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) { - if (!ops->eswitch_mode_set) - return -EOPNOTSUPP; - err = devlink_rates_check(devlink, devlink_rate_is_node, - info->extack); - if (err) - return err; mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]); - err = ops->eswitch_mode_set(devlink, mode, info->extack); + err = devlink_eswitch_mode_set(devlink, mode, info->extack); if (err) return err; } diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index cdf894ba5a9d..af43b7163f78 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -348,6 +348,9 @@ bool devlink_rate_is_node(const struct devlink_rate *devlink_rate); int devlink_rates_check(struct devlink *devlink, bool (*rate_filter)(const struct devlink_rate *), struct netlink_ext_ack *extack); +int devlink_eswitch_mode_set(struct devlink *devlink, + enum devlink_eswitch_mode mode, + struct netlink_ext_ack *extack); /* Linecards */ unsigned int devlink_linecard_index(struct devlink_linecard *linecard); -- 2.43.0 Add devlink_eswitch_mode= kernel command line parsing for a default eswitch mode. The supported syntax selects either all devlink handles or one explicit comma-separated handle list: devlink_eswitch_mode=*= devlink_eswitch_mode=[,...]= where is one of legacy, switchdev or switchdev_inactive. All selected handles receive the same mode. Assigning different modes to different handle lists in the same parameter value is not supported. Store the parsed selector and mode in devlink core so the default can be applied by a downstream patch. Document the devlink_eswitch_mode= syntax and duplicate handle handling. Signed-off-by: Mark Bloch --- .../admin-guide/kernel-parameters.txt | 25 ++ .../networking/devlink/devlink-defaults.rst | 78 ++++++ Documentation/networking/devlink/index.rst | 1 + net/devlink/Makefile | 2 +- net/devlink/core.c | 7 + net/devlink/default.c | 237 ++++++++++++++++++ net/devlink/devl_internal.h | 2 + 7 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 Documentation/networking/devlink/devlink-defaults.rst create mode 100644 net/devlink/default.c diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f22..117300dd589c 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1249,6 +1249,31 @@ Kernel parameters dell_smm_hwmon.fan_max= [HW] Maximum configurable fan speed. + devlink_eswitch_mode= + [NET] + Format: + = + + : + * | [,...] + + : + / + + Configure default devlink eswitch mode for matching + devlink instances during device initialization. + + : + legacy | switchdev | switchdev_inactive + + Examples: + devlink_eswitch_mode=*=switchdev + devlink_eswitch_mode=pci/0000:08:00.0=switchdev + devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive + + See Documentation/networking/devlink/devlink-defaults.rst + for the full syntax. + dfltcc= [HW,S390] Format: { on | off | def_only | inf_only | always } on: s390 zlib hardware support for compression on diff --git a/Documentation/networking/devlink/devlink-defaults.rst b/Documentation/networking/devlink/devlink-defaults.rst new file mode 100644 index 000000000000..380c9e99210e --- /dev/null +++ b/Documentation/networking/devlink/devlink-defaults.rst @@ -0,0 +1,78 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================== +Devlink Eswitch Mode Defaults +============================== + +Devlink eswitch mode defaults allow the eswitch mode to be provided on the +kernel command line and applied to matching devlink instances during device +initialization. + +The devlink device is selected by its devlink handle. For PCI devices this is +the same handle shown by ``devlink dev show``, for example +``pci/0000:08:00.0``. + +Kernel command line syntax +========================== + +Defaults are specified with the ``devlink_eswitch_mode=`` kernel command line +parameter. + +The general syntax is:: + + devlink_eswitch_mode== + +```` is either ``*`` or one or more devlink handles:: + + * | /[,/...] + +``*`` applies the mode to every devlink instance. All handles in the same +selector receive the same eswitch mode. + +```` is one of ``legacy``, ``switchdev`` or ``switchdev_inactive``. + +Syntax rules +------------ + +The following syntax rules apply: + +* Specify the default in one ``devlink_eswitch_mode=`` parameter. Repeated + ``devlink_eswitch_mode=`` parameters are not accumulated. +* The ``devlink_eswitch_mode=`` value is limited by the kernel command line + size. +* Whitespace is not allowed within the parameter value. +* ```` must be either ``*`` or a handle list. ``*`` cannot be + combined with explicit handles. +* ```` and ```` must not be empty. +* ```` may contain ``:``. This allows PCI names such as + ``0000:08:00.0``. +* Handles must not contain whitespace, ``*``, ``=`` or more than one ``/``. +* A comma separates handles. +* Comma-separated default assignments are not supported. +* Duplicate handles are rejected and the devlink eswitch mode default is + ignored. + +The eswitch mode default corresponds to the userspace command:: + + devlink dev eswitch set mode + + +Examples +======== + +Set all devlink instances to switchdev mode:: + + devlink_eswitch_mode=*=switchdev + +Set one PCI devlink instance to switchdev mode:: + + devlink_eswitch_mode=pci/0000:08:00.0=switchdev + +Set two PCI devlink instances to switchdev inactive mode:: + + devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive + +The following is invalid because comma-separated default assignments are not +supported:: + + devlink_eswitch_mode=pci/0000:08:00.0=switchdev,pci/0000:09:00.0=switchdev_inactive diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 4745148fecf4..134d2f319922 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -56,6 +56,7 @@ general. :maxdepth: 1 devlink-dpipe + devlink-defaults devlink-eswitch-attr devlink-flash devlink-health diff --git a/net/devlink/Makefile b/net/devlink/Makefile index 8f2adb5e5836..99ca0ef7cf1e 100644 --- a/net/devlink/Makefile +++ b/net/devlink/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -obj-y := core.o netlink.o netlink_gen.o dev.o port.o sb.o dpipe.o \ +obj-y := core.o netlink.o netlink_gen.o dev.o default.o port.o sb.o dpipe.o \ resource.o param.o region.o health.o trap.o rate.o linecard.o sh_dev.o diff --git a/net/devlink/core.c b/net/devlink/core.c index c53a42e17a58..fc14ee5d9dcf 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -598,6 +598,10 @@ static int __init devlink_init(void) { int err; + err = devlink_default_esw_mode_init(); + if (err) + goto out; + err = register_pernet_subsys(&devlink_pernet_ops); if (err) goto out; @@ -613,7 +617,10 @@ static int __init devlink_init(void) out_unreg_pernet_subsys: unregister_pernet_subsys(&devlink_pernet_ops); out: + if (err) + devlink_default_esw_mode_cleanup(); WARN_ON(err); + return err; } diff --git a/net/devlink/default.c b/net/devlink/default.c new file mode 100644 index 000000000000..8434af83ea69 --- /dev/null +++ b/net/devlink/default.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ + +#include +#include +#include +#include + +#include "devl_internal.h" + +static char *devlink_default_esw_mode_param; +static bool devlink_default_esw_mode_match_all; +static enum devlink_eswitch_mode devlink_default_esw_mode; +static LIST_HEAD(devlink_default_esw_mode_nodes); + +struct devlink_default_esw_mode_node { + struct list_head list; + char *bus_name; + char *dev_name; +}; + +static int __init +devlink_default_esw_mode_to_value(const char *str, + enum devlink_eswitch_mode *mode) +{ + if (!strcmp(str, "legacy")) { + *mode = DEVLINK_ESWITCH_MODE_LEGACY; + return 0; + } + if (!strcmp(str, "switchdev")) { + *mode = DEVLINK_ESWITCH_MODE_SWITCHDEV; + return 0; + } + if (!strcmp(str, "switchdev_inactive")) { + *mode = DEVLINK_ESWITCH_MODE_SWITCHDEV_INACTIVE; + return 0; + } + + return -EINVAL; +} + +static int __init +devlink_default_esw_mode_handle_parse(char *handle, char **bus_name, + char **dev_name) +{ + char *slash; + char *p; + + if (!*handle) + return -EINVAL; + + for (p = handle; *p; p++) { + if (*p == '*' || *p == '=') + return -EINVAL; + } + + slash = strchr(handle, '/'); + if (!slash || slash == handle || !slash[1]) + return -EINVAL; + if (strchr(slash + 1, '/')) + return -EINVAL; + + *slash = '\0'; + + *bus_name = handle; + *dev_name = slash + 1; + return 0; +} + +static struct devlink_default_esw_mode_node * +devlink_default_esw_mode_node_find(const char *bus_name, const char *dev_name) +{ + struct devlink_default_esw_mode_node *node; + + list_for_each_entry(node, &devlink_default_esw_mode_nodes, list) { + if (!strcmp(node->bus_name, bus_name) && + !strcmp(node->dev_name, dev_name)) + return node; + } + + return NULL; +} + +static int __init +devlink_default_esw_mode_node_add(const char *bus_name, const char *dev_name) +{ + struct devlink_default_esw_mode_node *node; + + if (devlink_default_esw_mode_node_find(bus_name, dev_name)) + return -EEXIST; + + node = kzalloc_obj(*node); + if (!node) + return -ENOMEM; + + INIT_LIST_HEAD(&node->list); + node->bus_name = kstrdup(bus_name, GFP_KERNEL); + node->dev_name = kstrdup(dev_name, GFP_KERNEL); + if (!node->bus_name || !node->dev_name) { + kfree(node->bus_name); + kfree(node->dev_name); + kfree(node); + return -ENOMEM; + } + + list_add_tail(&node->list, &devlink_default_esw_mode_nodes); + return 0; +} + +static int __init devlink_default_esw_mode_handles_parse(char *handles) +{ + char *handle; + int err; + + if (!strcmp(handles, "*")) { + devlink_default_esw_mode_match_all = true; + return 0; + } + + while ((handle = strsep(&handles, ",")) != NULL) { + char *bus_name; + char *dev_name; + + err = devlink_default_esw_mode_handle_parse(handle, &bus_name, + &dev_name); + if (err) + return err; + + err = devlink_default_esw_mode_node_add(bus_name, dev_name); + if (err) + return err; + } + + return 0; +} + +static void __init +devlink_default_esw_mode_node_free(struct devlink_default_esw_mode_node *node) +{ + kfree(node->bus_name); + kfree(node->dev_name); + kfree(node); +} + +static void __init devlink_default_esw_mode_nodes_clear(void) +{ + struct devlink_default_esw_mode_node *node_tmp; + struct devlink_default_esw_mode_node *node; + + list_for_each_entry_safe(node, node_tmp, + &devlink_default_esw_mode_nodes, list) { + list_del(&node->list); + devlink_default_esw_mode_node_free(node); + } + + devlink_default_esw_mode_match_all = false; +} + +static int __init devlink_default_esw_mode_parse(char *str) +{ + enum devlink_eswitch_mode esw_mode; + char *separator; + char *handles; + char *mode; + int err; + + if (!*str) + return -EINVAL; + + separator = strrchr(str, '='); + if (!separator || separator == str || !separator[1]) + return -EINVAL; + + *separator = '\0'; + handles = str; + mode = separator + 1; + + err = devlink_default_esw_mode_to_value(mode, &esw_mode); + if (err) + return err; + + err = devlink_default_esw_mode_handles_parse(handles); + if (err) + devlink_default_esw_mode_nodes_clear(); + else + devlink_default_esw_mode = esw_mode; + + return err; +} + +static int __init devlink_default_esw_mode_setup(char *str) +{ + devlink_default_esw_mode_param = str; + return 1; +} +__setup("devlink_eswitch_mode=", devlink_default_esw_mode_setup); + +int __init devlink_default_esw_mode_init(void) +{ + char *def; + int err; + + if (!devlink_default_esw_mode_param) + return 0; + + def = kstrdup(devlink_default_esw_mode_param, GFP_KERNEL); + if (!def) { + devlink_default_esw_mode_param = NULL; + pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n"); + return 0; + } + + err = devlink_default_esw_mode_parse(def); + kfree(def); + if (err == -EEXIST) { + devlink_default_esw_mode_param = NULL; + pr_warn("devlink: duplicate eswitch mode handles ignored\n"); + return 0; + } else if (err == -EINVAL) { + devlink_default_esw_mode_param = NULL; + pr_warn("devlink: invalid devlink_eswitch_mode parameter ignored\n"); + return 0; + } else if (err == -ENOMEM) { + devlink_default_esw_mode_param = NULL; + pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n"); + return 0; + } else if (err) { + return err; + } + + return 0; +} + +void __init devlink_default_esw_mode_cleanup(void) +{ + devlink_default_esw_mode_nodes_clear(); +} diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index af43b7163f78..fe9ad58515d4 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -71,6 +71,8 @@ extern struct genl_family devlink_nl_family; struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size, struct net *net, struct device *dev, const struct device_driver *dev_driver); +int devlink_default_esw_mode_init(void); +void devlink_default_esw_mode_cleanup(void); #define devl_warn(devlink, format, args...) \ do { \ -- 2.43.0 Apply parsed devlink_eswitch_mode= defaults after devlink registration and after successful reload. devl_register() may still be called before the device is ready for an eswitch mode change. Keep the registration path passive and let the regular devl_unlock() path queue the async apply work once the instance is registered and the default is still pending. The queueing path runs while the devlink instance lock is held, so the queued work gets its devlink reference before the caller drops the lock. The worker then takes the devlink instance lock normally and applies the default only if the instance is still registered and the default is still pending. For successful reloads that performed DRIVER_REINIT, devlink_reload() already holds the devlink instance lock and the driver has completed reload_up(). Clear pending work and apply the default directly from the reload path instead of queueing work. Preserve the user configured mode when it is set before devlink applies the default. Signed-off-by: Mark Bloch --- net/devlink/core.c | 6 ++ net/devlink/default.c | 112 +++++++++++++++++++++++++++++++++++- net/devlink/dev.c | 6 ++ net/devlink/devl_internal.h | 7 +++ 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/net/devlink/core.c b/net/devlink/core.c index fc14ee5d9dcf..9d9cc40626fc 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -317,6 +317,7 @@ EXPORT_SYMBOL_GPL(devl_trylock); void devl_unlock(struct devlink *devlink) { + devlink_default_esw_mode_queue_apply_work(devlink); mutex_unlock(&devlink->lock); } EXPORT_SYMBOL_GPL(devl_unlock); @@ -429,6 +430,7 @@ void devl_unregister(struct devlink *devlink) ASSERT_DEVLINK_REGISTERED(devlink); devl_assert_locked(devlink); + devlink_default_esw_mode_apply_pending_clear(devlink); devlink_notify_unregister(devlink); xa_clear_mark(&devlinks, devlink->index, DEVLINK_REGISTERED); devlink_rel_put(devlink); @@ -490,6 +492,7 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size, INIT_LIST_HEAD(&devlink->trap_group_list); INIT_LIST_HEAD(&devlink->trap_policer_list); INIT_RCU_WORK(&devlink->rwork, devlink_release); + devlink_default_esw_mode_instance_init(devlink); lockdep_register_key(&devlink->lock_key); mutex_init(&devlink->lock); lockdep_set_class(&devlink->lock, &devlink->lock_key); @@ -537,6 +540,9 @@ void devlink_free(struct devlink *devlink) devl_lock(devlink); WARN_ON(devlink_rates_check(devlink, NULL, NULL)); devl_unlock(devlink); + + devlink_default_esw_mode_instance_cleanup(devlink); + devlink_rel_put(devlink); WARN_ON(!list_empty(&devlink->trap_policer_list)); diff --git a/net/devlink/default.c b/net/devlink/default.c index 8434af83ea69..896146d1b8e7 100644 --- a/net/devlink/default.c +++ b/net/devlink/default.c @@ -10,8 +10,10 @@ static char *devlink_default_esw_mode_param; static bool devlink_default_esw_mode_match_all; +static bool devlink_default_esw_mode_enabled; static enum devlink_eswitch_mode devlink_default_esw_mode; static LIST_HEAD(devlink_default_esw_mode_nodes); +static struct workqueue_struct *devlink_default_esw_mode_wq; struct devlink_default_esw_mode_node { struct list_head list; @@ -154,6 +156,7 @@ static void __init devlink_default_esw_mode_nodes_clear(void) } devlink_default_esw_mode_match_all = false; + devlink_default_esw_mode_enabled = false; } static int __init devlink_default_esw_mode_parse(char *str) @@ -180,14 +183,108 @@ static int __init devlink_default_esw_mode_parse(char *str) return err; err = devlink_default_esw_mode_handles_parse(handles); - if (err) + if (err) { devlink_default_esw_mode_nodes_clear(); - else + } else { devlink_default_esw_mode = esw_mode; + devlink_default_esw_mode_enabled = true; + } return err; } +static bool devlink_default_esw_mode_match(struct devlink *devlink) +{ + const char *bus_name = devlink_bus_name(devlink); + const char *dev_name = devlink_dev_name(devlink); + struct devlink_default_esw_mode_node *node; + + if (devlink_default_esw_mode_match_all) + return true; + + node = devlink_default_esw_mode_node_find(bus_name, dev_name); + return !!node; +} + +void devlink_default_esw_mode_apply_locked(struct devlink *devlink) +{ + const struct devlink_ops *ops = devlink->ops; + int err; + + devl_assert_locked(devlink); + + if (!devlink_default_esw_mode_match(devlink)) + return; + + if (!ops->eswitch_mode_set) { + if (!devlink_default_esw_mode_match_all) + devl_warn(devlink, + "devlink_eswitch_mode= selected this device but eswitch mode setting is not supported\n"); + return; + } + + err = devlink_eswitch_mode_set(devlink, devlink_default_esw_mode, NULL); + if (err) + devl_warn(devlink, + "Couldn't apply default eswitch mode, err %d\n", + err); +} + +void devlink_default_esw_mode_queue_apply_work(struct devlink *devlink) +{ + devl_assert_locked(devlink); + + if (!devlink_default_esw_mode_enabled || !devlink_default_esw_mode_wq) + return; + if (!devlink->default_esw_mode_apply_pending || + !__devl_is_registered(devlink)) + return; + if (!devlink_try_get(devlink)) + return; + if (!queue_work(devlink_default_esw_mode_wq, + &devlink->default_esw_mode_apply_work)) + devlink_put(devlink); +} + +static void devlink_default_esw_mode_apply_work(struct work_struct *work) +{ + struct devlink *devlink; + + devlink = container_of(work, struct devlink, + default_esw_mode_apply_work); + + devl_lock(devlink); + + if (devl_is_registered(devlink) && + devlink->default_esw_mode_apply_pending) { + devlink_default_esw_mode_apply_locked(devlink); + devlink->default_esw_mode_apply_pending = false; + } + + devl_unlock(devlink); + devlink_put(devlink); +} + +void devlink_default_esw_mode_instance_init(struct devlink *devlink) +{ + INIT_WORK(&devlink->default_esw_mode_apply_work, + devlink_default_esw_mode_apply_work); + devlink->default_esw_mode_apply_pending = true; +} + +void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink) +{ + devl_assert_locked(devlink); + + devlink->default_esw_mode_apply_pending = false; +} + +void devlink_default_esw_mode_instance_cleanup(struct devlink *devlink) +{ + if (cancel_work_sync(&devlink->default_esw_mode_apply_work)) + devlink_put(devlink); +} + static int __init devlink_default_esw_mode_setup(char *str) { devlink_default_esw_mode_param = str; @@ -228,10 +325,21 @@ int __init devlink_default_esw_mode_init(void) return err; } + devlink_default_esw_mode_wq = alloc_workqueue("devlink_default_esw_mode", + WQ_UNBOUND | WQ_MEM_RECLAIM, + 0); + if (!devlink_default_esw_mode_wq) { + devlink_default_esw_mode_param = NULL; + devlink_default_esw_mode_nodes_clear(); + pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate workqueue\n"); + } + return 0; } void __init devlink_default_esw_mode_cleanup(void) { + if (devlink_default_esw_mode_wq) + destroy_workqueue(devlink_default_esw_mode_wq); devlink_default_esw_mode_nodes_clear(); } diff --git a/net/devlink/dev.c b/net/devlink/dev.c index 119ef105d0a7..611bb6bfd492 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -478,6 +478,11 @@ int devlink_reload(struct devlink *devlink, struct net *dest_net, return err; WARN_ON(!(*actions_performed & BIT(action))); + if (*actions_performed & BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT)) { + devlink_default_esw_mode_apply_pending_clear(devlink); + devlink_default_esw_mode_apply_locked(devlink); + } + /* Catch driver on updating the remote action within devlink reload */ WARN_ON(memcmp(remote_reload_stats, devlink->stats.remote_reload_stats, sizeof(remote_reload_stats))); @@ -731,6 +736,7 @@ int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) u16 mode; if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) { + devlink_default_esw_mode_apply_pending_clear(devlink); mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]); err = devlink_eswitch_mode_set(devlink, mode, info->extack); if (err) diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index fe9ad58515d4..c2bee5aabd49 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -58,8 +58,10 @@ struct devlink { struct mutex lock; struct lock_class_key lock_key; u8 reload_failed:1; + u8 default_esw_mode_apply_pending:1; refcount_t refcount; struct rcu_work rwork; + struct work_struct default_esw_mode_apply_work; struct devlink_rel *rel; struct xarray nested_rels; char priv[] __aligned(NETDEV_ALIGN); @@ -73,6 +75,11 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size, const struct device_driver *dev_driver); int devlink_default_esw_mode_init(void); void devlink_default_esw_mode_cleanup(void); +void devlink_default_esw_mode_instance_init(struct devlink *devlink); +void devlink_default_esw_mode_apply_locked(struct devlink *devlink); +void devlink_default_esw_mode_queue_apply_work(struct devlink *devlink); +void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink); +void devlink_default_esw_mode_instance_cleanup(struct devlink *devlink); #define devl_warn(devlink, format, args...) \ do { \ -- 2.43.0 Add devl_apply_default_esw_mode() for drivers that can apply the devlink_eswitch_mode= boot default once their device is ready instead of waiting for the asynchronous registration work. Signed-off-by: Mark Bloch --- include/net/devlink.h | 1 + net/devlink/default.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/net/devlink.h b/include/net/devlink.h index ffe1ad5fb70b..90da03c0112c 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -1661,6 +1661,7 @@ static inline struct devlink *devlink_alloc(const struct devlink_ops *ops, int devl_register(struct devlink *devlink); void devl_unregister(struct devlink *devlink); +void devl_apply_default_esw_mode(struct devlink *devlink); void devlink_register(struct devlink *devlink); void devlink_unregister(struct devlink *devlink); void devlink_free(struct devlink *devlink); diff --git a/net/devlink/default.c b/net/devlink/default.c index 896146d1b8e7..5a37d76731e1 100644 --- a/net/devlink/default.c +++ b/net/devlink/default.c @@ -2,6 +2,7 @@ /* Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include +#include #include #include #include @@ -279,6 +280,24 @@ void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink) devlink->default_esw_mode_apply_pending = false; } +/** + * devl_apply_default_esw_mode - Apply devlink eswitch mode boot default + * @devlink: devlink + * + * Apply the devlink eswitch mode selected by the devlink_eswitch_mode= + * kernel command line parameter, if any matches @devlink. + * + * The caller must hold the devlink instance lock. + */ +void devl_apply_default_esw_mode(struct devlink *devlink) +{ + devl_assert_locked(devlink); + + devlink->default_esw_mode_apply_pending = false; + devlink_default_esw_mode_apply_locked(devlink); +} +EXPORT_SYMBOL_GPL(devl_apply_default_esw_mode); + void devlink_default_esw_mode_instance_cleanup(struct devlink *devlink) { if (cancel_work_sync(&devlink->default_esw_mode_apply_work)) -- 2.43.0 Apply devlink_eswitch_mode= boot defaults for mlx5 after the initial probe finishes device initialization while holding the devlink instance lock. At this point the devlink instance is registered and mlx5 can perform an eswitch mode change. Calling devl_apply_default_esw_mode() also clears any pending default apply work queued by devl_register(), so the queued work will not apply the same default again. Keep this call in mlx5_init_one() rather than the lower-level devl-locked init helper. That helper is also used by devlink reload, and devlink core already applies the boot default after a successful DRIVER_REINIT reload. Signed-off-by: Mark Bloch --- drivers/net/ethernet/mellanox/mlx5/core/main.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 643b4aac2033..0712efea74cc 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -1392,6 +1392,17 @@ static void mlx5_unload(struct mlx5_core_dev *dev) mlx5_free_bfreg(dev, &dev->priv.bfreg); } +static void mlx5_devl_apply_default_esw_mode(struct mlx5_core_dev *dev) +{ + struct devlink *devlink = priv_to_devlink(dev); + + if (!MLX5_ESWITCH_MANAGER(dev)) + return; + + devl_assert_locked(devlink); + devl_apply_default_esw_mode(devlink); +} + int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev) { bool light_probe = mlx5_dev_is_lightweight(dev); @@ -1471,6 +1482,8 @@ int mlx5_init_one(struct mlx5_core_dev *dev) err = mlx5_init_one_devl_locked(dev); if (err) devl_unregister(devlink); + else + mlx5_devl_apply_default_esw_mode(dev); unlock: devl_unlock(devlink); return err; -- 2.43.0