[PATCH net] net/mlx5: Attach late uplink netdev to loaded representors

From: Tariq Toukan

Date: Tue Oct 06 2026 - 07:04:10 EST


From: Jianbo Liu <jianbol@xxxxxxxxxx>

mlx5e_vport_uplink_rep_load() returns success when the uplink netdev is
missing, so the ETH rep is marked loaded with a NULL netdev, the IB rep
copies that NULL association, and nothing repairs it later. Unbind
mlx5_core.eth.<N> in legacy mode, enter switchdev, bind it again, and
rdma link show still reports no netdev on the uplink port. The netdev
also keeps the NIC profile instead of the uplink rep profile.

Add an attach_uplink_netdev rep op, run from the existing reload-reps
work once mlx5_core_uplink_netdev_set() reports a netdev. ETH re-runs
the uplink load, switching profile and setting rpriv->netdev; IB redoes
ib_device_set_netdev(). Deferring to the work keeps the devlink lock
out of probe context.

Fixes: 6b4be64fd9fe ("net/mlx5e: Harden uplink netdev access against device unbind")
Signed-off-by: Jianbo Liu <jianbol@xxxxxxxxxx>
Reviewed-by: Shay Drori <shayd@xxxxxxxxxx>
Signed-off-by: Tariq Toukan <tariqt@xxxxxxxxxx>
---
drivers/infiniband/hw/mlx5/ib_rep.c | 24 +++++++++++++
.../net/ethernet/mellanox/mlx5/core/en_rep.c | 15 ++++++++
.../net/ethernet/mellanox/mlx5/core/eswitch.h | 3 ++
.../mellanox/mlx5/core/eswitch_offloads.c | 35 +++++++++++++++++++
.../net/ethernet/mellanox/mlx5/core/main.c | 3 ++
include/linux/mlx5/eswitch.h | 5 +++
6 files changed, 85 insertions(+)

diff --git a/drivers/infiniband/hw/mlx5/ib_rep.c b/drivers/infiniband/hw/mlx5/ib_rep.c
index 65d8767d1830..f9931a94157a 100644
--- a/drivers/infiniband/hw/mlx5/ib_rep.c
+++ b/drivers/infiniband/hw/mlx5/ib_rep.c
@@ -273,10 +273,34 @@ mlx5_ib_vport_rep_unload(struct mlx5_eswitch_rep *rep)
}
}

+static int
+mlx5_ib_vport_uplink_rep_attach_netdev(struct mlx5_core_dev *mdev,
+ struct mlx5_eswitch_rep *rep)
+{
+ struct mlx5_ib_dev *dev = mlx5_ib_rep_to_dev(rep);
+ struct net_device *ndev;
+ int i;
+
+ /* Shared FDB slave uplinks share the master's IB device. */
+ if (!dev)
+ return 0;
+
+ ndev = mlx5_ib_get_rep_netdev(rep->esw, rep->vport);
+ if (!ndev)
+ return -ENODEV;
+
+ for (i = 0; i < dev->num_ports; i++)
+ if (dev->port[i].rep == rep)
+ return ib_device_set_netdev(&dev->ib_dev, ndev, i + 1);
+
+ return 0;
+}
+
static const struct mlx5_eswitch_rep_ops rep_ops = {
.load = mlx5_ib_vport_rep_load,
.unload = mlx5_ib_vport_rep_unload,
.get_proto_dev = mlx5_ib_rep_to_dev,
+ .attach_uplink_netdev = mlx5_ib_vport_uplink_rep_attach_netdev,
};

static void mlx5_ib_register_peer_vport_reps(struct mlx5_core_dev *mdev)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
index 88a170e40bd9..19812bf6a830 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
@@ -1490,10 +1490,24 @@ mlx5e_vport_uplink_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep *
rpriv->netdev = netdev;
err = mlx5e_netdev_change_profile(netdev, dev,
&mlx5e_uplink_rep_profile, rpriv);
+ if (err)
+ rpriv->netdev = NULL;
mlx5_uplink_netdev_put(dev, netdev);
return err;
}

+static int
+mlx5e_vport_uplink_rep_attach_netdev(struct mlx5_core_dev *dev,
+ struct mlx5_eswitch_rep *rep)
+{
+ struct mlx5e_rep_priv *rpriv = mlx5e_rep_to_rep_priv(rep);
+
+ if (rpriv->netdev)
+ return 0;
+
+ return mlx5e_vport_uplink_rep_load(dev, rep);
+}
+
static void
mlx5e_vport_uplink_rep_unload(struct mlx5e_rep_priv *rpriv)
{
@@ -1741,6 +1755,7 @@ static const struct mlx5_eswitch_rep_ops rep_ops = {
.unload = mlx5e_vport_rep_unload,
.get_proto_dev = mlx5e_vport_rep_get_proto_dev,
.event = mlx5e_vport_rep_event,
+ .attach_uplink_netdev = mlx5e_vport_uplink_rep_attach_netdev,
};

static int mlx5e_rep_probe(struct auxiliary_device *adev,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
index 8b1f93b13ea9..3dd6931f13cd 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
@@ -1012,6 +1012,7 @@ mlx5_esw_lag_demux_rule_create(struct mlx5_eswitch *esw, u16 vport_num,
struct mlx5_flow_table *lag_ft);
void mlx5_esw_reps_block(struct mlx5_eswitch *esw);
void mlx5_esw_reps_unblock(struct mlx5_eswitch *esw);
+void mlx5_esw_offloads_uplink_netdev_attach(struct mlx5_core_dev *dev);
#else /* CONFIG_MLX5_ESWITCH */
/* eswitch API stubs */
static inline int mlx5_eswitch_init(struct mlx5_core_dev *dev) { return 0; }
@@ -1098,6 +1099,8 @@ mlx5_esw_host_functions_enabled(const struct mlx5_core_dev *dev)

static inline void mlx5_esw_reps_block(struct mlx5_eswitch *esw) {}
static inline void mlx5_esw_reps_unblock(struct mlx5_eswitch *esw) {}
+static inline void
+mlx5_esw_offloads_uplink_netdev_attach(struct mlx5_core_dev *dev) {}

static inline bool
mlx5_esw_vport_vhca_id(struct mlx5_eswitch *esw, u16 vportn, u16 *vhca_id)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
index eb74b6260168..cf61bd76889c 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
@@ -4842,6 +4842,29 @@ mlx5_eswitch_register_vport_reps_blocked(struct mlx5_eswitch *esw,
}
}

+static void mlx5_eswitch_attach_uplink_netdev(struct mlx5_eswitch *esw,
+ struct mlx5_eswitch_rep *uplink)
+{
+ const struct mlx5_eswitch_rep_ops *ops;
+ int type;
+ int err;
+
+ for (type = 0; type < NUM_REP_TYPES; type++) {
+ if (atomic_read(&uplink->rep_data[type].state) != REP_LOADED)
+ continue;
+
+ ops = esw->offloads.rep_ops[type];
+ if (!ops || !ops->attach_uplink_netdev)
+ continue;
+
+ err = ops->attach_uplink_netdev(esw->dev, uplink);
+ if (err)
+ esw_warn(esw->dev,
+ "Failed to attach uplink netdev to rep type %d, err(%d)\n",
+ type, err);
+ }
+}
+
static void mlx5_eswitch_reload_reps_blocked(struct mlx5_eswitch *esw)
{
struct mlx5_eswitch_rep *uplink;
@@ -4862,6 +4885,8 @@ static void mlx5_eswitch_reload_reps_blocked(struct mlx5_eswitch *esw)
return;
}

+ mlx5_eswitch_attach_uplink_netdev(esw, uplink);
+
if (mlx5_get_sd(esw->dev) && !mlx5_lag_is_active(esw->dev))
return;

@@ -4886,6 +4911,16 @@ static void mlx5_eswitch_reload_reps(struct mlx5_eswitch *esw)
mlx5_esw_reps_unblock(esw);
}

+void mlx5_esw_offloads_uplink_netdev_attach(struct mlx5_core_dev *dev)
+{
+ struct mlx5_eswitch *esw = dev->priv.eswitch;
+
+ if (!mlx5_esw_allowed(esw))
+ return;
+
+ mlx5_esw_add_work(esw, mlx5_eswitch_reload_reps, GFP_KERNEL);
+}
+
static void
mlx5_eswitch_register_vport_reps_locked(struct mlx5_eswitch *esw,
const struct mlx5_eswitch_rep_ops *ops,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c
index 5f28d906c35b..ab93c807b58c 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c
@@ -278,6 +278,9 @@ void mlx5_core_uplink_netdev_set(struct mlx5_core_dev *dev, struct net_device *n
mlx5_blocking_notifier_call_chain(dev, MLX5_DRIVER_EVENT_UPLINK_NETDEV,
netdev);
mutex_unlock(&dev->mlx5e_res.uplink_netdev_lock);
+
+ if (netdev)
+ mlx5_esw_offloads_uplink_netdev_attach(dev);
}

void mlx5_core_uplink_netdev_event_replay(struct mlx5_core_dev *dev)
diff --git a/include/linux/mlx5/eswitch.h b/include/linux/mlx5/eswitch.h
index a0dd162baa78..e823fbe7ff40 100644
--- a/include/linux/mlx5/eswitch.h
+++ b/include/linux/mlx5/eswitch.h
@@ -43,6 +43,11 @@ struct mlx5_eswitch_rep_ops {
struct mlx5_eswitch_rep *rep,
enum mlx5_switchdev_event event,
void *data);
+ /* Attach an uplink netdev that showed up only after the uplink
+ * representor was already loaded.
+ */
+ int (*attach_uplink_netdev)(struct mlx5_core_dev *dev,
+ struct mlx5_eswitch_rep *rep);
};

struct mlx5_eswitch_rep_data {

base-commit: d5a007b9b457c915ab1a53227e8939e4018aa97a
--
2.44.0