* [PATCH net-next V2 0/3] net/mlx5: Fix E-Switch work queue deadlock with devlink lock
From: Tariq Toukan @ 2026-04-28 5:10 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea
Hi,
See detailed description by Mark below [1].
Regards,
Tariq
[1]
mlx5_eswitch_cleanup() calls destroy_workqueue() while holding the
devlink lock through mlx5_uninit_one(). E-Switch workqueue workers also
need the devlink lock, but previously took it before checking whether
their work item was stale. Cleanup can therefore wait for a worker that
is blocked on the same devlink lock.
Mode changes have the same ordering hazard: the mode-change path holds
devlink lock while tearing down the current mode, and old work may still
be pending on the E-Switch workqueue.
Fix this by making esw_wq_handler() check the generation counter before
attempting to take devlink lock. The worker uses devl_trylock(); if the
lock is busy and the work is still current, it sleeps on an E-Switch wait
queue with a short timeout. Invalidation increments the generation
counter and wakes the wait queue, so stale workers exit without spinning
or blocking cleanup.
The generation counter already existed but was buried in
mlx5_esw_functions and only covered function-change events. The three
patches get from there to the fix in small steps.
Patch 1 moves the counter up to mlx5_eswitch. Pure refactor,
no behavior change.
Patch 2 cleans up the work queue plumbing: factors out the repeated
lock/check/dispatch boilerplate into a single esw_wq_handler() and
adds mlx5_esw_add_work() as the one place to enqueue work.
Patch 3 is the actual fix: check the generation before the lock, use
devl_trylock() instead of devl_lock(), add a wait queue so lock retries
do not spin, and invalidate pending work at the earliest safe operation
boundary. Cleanup invalidates before destroy_workqueue(), and mode
teardown unregisters the work-producing notifiers before invalidating so
new notifier work cannot capture the new generation.
V2:
Split out from a larger series. The representor lifecycle improvements
to be sent separately.
Patch 3:
- Move generation invalidation after notifier unregister but before
teardown, so old work is discarded early without allowing new notifier
work to use the new generation.
- Replace cond_resched() polling with a wait queue to avoid CPU spinning
while devlink lock is held by a long operation.
Link to V1:
https://lore.kernel.org/all/20260409115550.156419-1-tariqt@nvidia.com/
Mark Bloch (3):
net/mlx5: E-Switch, move work queue generation counter
net/mlx5: E-Switch, introduce generic work queue dispatch helper
net/mlx5: E-Switch, fix deadlock between devlink lock and esw->wq
.../net/ethernet/mellanox/mlx5/core/eswitch.c | 20 +++-
.../net/ethernet/mellanox/mlx5/core/eswitch.h | 5 +-
.../mellanox/mlx5/core/eswitch_offloads.c | 96 ++++++++++++-------
3 files changed, 82 insertions(+), 39 deletions(-)
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.44.0
^ permalink raw reply
* [PATCH net-next V2 1/3] net/mlx5: E-Switch, move work queue generation counter
From: Tariq Toukan @ 2026-04-28 5:10 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Cosmin Ratiu
In-Reply-To: <20260428051018.219093-1-tariqt@nvidia.com>
From: Mark Bloch <mbloch@nvidia.com>
The generation counter in mlx5_esw_functions is used to detect stale
work items on the E-Switch work queue. Move it from mlx5_esw_functions
to the top-level mlx5_eswitch struct so it can guard all work types,
not just function-change events.
This is a mechanical refactor: no behavioral change.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
Reviewed-by: Cosmin Ratiu <cratiu@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 3 ++-
drivers/net/ethernet/mellanox/mlx5/core/eswitch.h | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c | 4 ++--
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
index 123c96716a54..1986d4d0e886 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
@@ -1075,7 +1075,7 @@ static void mlx5_eswitch_event_handler_unregister(struct mlx5_eswitch *esw)
if (esw->mode == MLX5_ESWITCH_OFFLOADS &&
mlx5_eswitch_is_funcs_handler(esw->dev)) {
mlx5_eq_notifier_unregister(esw->dev, &esw->esw_funcs.nb);
- atomic_inc(&esw->esw_funcs.generation);
+ atomic_inc(&esw->generation);
}
}
@@ -2072,6 +2072,7 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev)
mutex_init(&esw->state_lock);
init_rwsem(&esw->mode_lock);
refcount_set(&esw->qos.refcnt, 0);
+ atomic_set(&esw->generation, 0);
esw->enabled_vports = 0;
esw->offloads.inline_mode = MLX5_INLINE_MODE_NONE;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
index 5128f5020dae..0c3d2bdebf8c 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
@@ -340,7 +340,6 @@ struct mlx5_host_work {
struct mlx5_esw_functions {
struct mlx5_nb nb;
- atomic_t generation;
bool host_funcs_disabled;
u16 num_vfs;
u16 num_ec_vfs;
@@ -410,6 +409,7 @@ struct mlx5_eswitch {
struct mlx5_devcom_comp_dev *devcom;
u16 enabled_ipsec_vf_count;
bool eswitch_operation_in_progress;
+ atomic_t generation;
};
void esw_offloads_disable(struct mlx5_eswitch *esw);
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
index a078d06f4567..b2e7294d3a5c 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
@@ -3667,7 +3667,7 @@ esw_vfs_changed_event_handler(struct mlx5_eswitch *esw, int work_gen,
devl_lock(devlink);
/* Stale work from one or more mode changes ago. Bail out. */
- if (work_gen != atomic_read(&esw->esw_funcs.generation))
+ if (work_gen != atomic_read(&esw->generation))
goto unlock;
new_num_vfs = MLX5_GET(query_esw_functions_out, out,
@@ -3729,7 +3729,7 @@ int mlx5_esw_funcs_changed_handler(struct notifier_block *nb, unsigned long type
esw = container_of(esw_funcs, struct mlx5_eswitch, esw_funcs);
host_work->esw = esw;
- host_work->work_gen = atomic_read(&esw_funcs->generation);
+ host_work->work_gen = atomic_read(&esw->generation);
INIT_WORK(&host_work->work, esw_functions_changed_event_handler);
queue_work(esw->work_queue, &host_work->work);
--
2.44.0
^ permalink raw reply related
* [PATCH net-next V2 3/3] net/mlx5: E-Switch, fix deadlock between devlink lock and esw->wq
From: Tariq Toukan @ 2026-04-28 5:10 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Cosmin Ratiu
In-Reply-To: <20260428051018.219093-1-tariqt@nvidia.com>
From: Mark Bloch <mbloch@nvidia.com>
mlx5_eswitch_cleanup() calls destroy_workqueue() while holding the
devlink lock through mlx5_uninit_one(). E-Switch workqueue workers also
need the devlink lock, but previously took it before checking whether
their work item was stale. This can deadlock when cleanup waits for a
worker that is blocked on the same devlink lock.
Mode changes have the same ordering hazard: the mode-change path holds
devlink lock while tearing down the current mode, and old work may still
be pending on the E-Switch workqueue.
Fix this by making esw_wq_handler() check the generation counter before
attempting to take devlink lock. The worker uses devl_trylock(); if the
lock is busy and the work is still current, it sleeps on an E-Switch wait
queue with a short timeout. Invalidation increments the generation
counter and wakes the wait queue, so stale workers exit without spinning
or blocking cleanup.
Invalidate work at the earliest safe operation boundary. Cleanup
invalidates before destroy_workqueue(), and QoS cleanup runs after the
workqueue is destroyed. Mode teardown unregisters the work-producing
notifiers first, then invalidates the queue before tearing down
FDB/QoS/rate-node state. This prevents new notifier work from capturing
the new generation while still making old work stale before expensive
teardown starts.
mlx5_devlink_eswitch_mode_set() now relies on
mlx5_eswitch_disable_locked() for the mode-change invalidation instead
of incrementing the generation after disable. mlx5_eswitch_disable()
gets the same coverage. SR-IOV enable/disable paths invalidate before VF
state changes so work against the old VF count or mode is discarded.
Remove the conditional generation increment in
mlx5_eswitch_event_handler_unregister(); mlx5_eswitch_disable_locked()
now handles it unconditionally after the relevant notifiers are
unregistered.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
Reviewed-by: Cosmin Ratiu <cratiu@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
.../net/ethernet/mellanox/mlx5/core/eswitch.c | 19 +++++++++++++----
.../net/ethernet/mellanox/mlx5/core/eswitch.h | 2 ++
.../mellanox/mlx5/core/eswitch_offloads.c | 21 +++++++++++++++++--
3 files changed, 36 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
index 1986d4d0e886..8ec52498be3f 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
@@ -1070,13 +1070,17 @@ static void mlx5_eswitch_event_handler_register(struct mlx5_eswitch *esw)
}
}
+static void mlx5_eswitch_invalidate_wq(struct mlx5_eswitch *esw)
+{
+ atomic_inc(&esw->generation);
+ wake_up_all(&esw->work_queue_wait);
+}
+
static void mlx5_eswitch_event_handler_unregister(struct mlx5_eswitch *esw)
{
if (esw->mode == MLX5_ESWITCH_OFFLOADS &&
- mlx5_eswitch_is_funcs_handler(esw->dev)) {
+ mlx5_eswitch_is_funcs_handler(esw->dev))
mlx5_eq_notifier_unregister(esw->dev, &esw->esw_funcs.nb);
- atomic_inc(&esw->generation);
- }
}
static void mlx5_eswitch_clear_vf_vports_info(struct mlx5_eswitch *esw)
@@ -1701,6 +1705,8 @@ int mlx5_eswitch_enable(struct mlx5_eswitch *esw, int num_vfs)
if (toggle_lag)
mlx5_lag_disable_change(esw->dev);
+ mlx5_eswitch_invalidate_wq(esw);
+
if (!mlx5_esw_is_fdb_created(esw)) {
ret = mlx5_eswitch_enable_locked(esw, num_vfs);
} else {
@@ -1746,6 +1752,8 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf)
esw->mode == MLX5_ESWITCH_LEGACY ? "LEGACY" : "OFFLOADS",
esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports);
+ mlx5_eswitch_invalidate_wq(esw);
+
if (!mlx5_core_is_ecpf(esw->dev)) {
mlx5_eswitch_unload_vf_vports(esw, esw->esw_funcs.num_vfs);
if (clear_vf)
@@ -1785,6 +1793,7 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw)
mlx5_eq_notifier_unregister(esw->dev, &esw->nb);
mlx5_eswitch_event_handler_unregister(esw);
+ mlx5_eswitch_invalidate_wq(esw);
esw_info(esw->dev, "Disable: mode(%s), nvfs(%d), necvfs(%d), active vports(%d)\n",
esw->mode == MLX5_ESWITCH_LEGACY ? "LEGACY" : "OFFLOADS",
@@ -2072,6 +2081,7 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev)
mutex_init(&esw->state_lock);
init_rwsem(&esw->mode_lock);
refcount_set(&esw->qos.refcnt, 0);
+ init_waitqueue_head(&esw->work_queue_wait);
atomic_set(&esw->generation, 0);
esw->enabled_vports = 0;
@@ -2110,8 +2120,9 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw)
esw_info(esw->dev, "cleanup\n");
- mlx5_esw_qos_cleanup(esw);
+ mlx5_eswitch_invalidate_wq(esw);
destroy_workqueue(esw->work_queue);
+ mlx5_esw_qos_cleanup(esw);
WARN_ON(refcount_read(&esw->qos.refcnt));
mutex_destroy(&esw->state_lock);
WARN_ON(!xa_empty(&esw->offloads.vhca_map));
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
index e3ab8a30c174..8f4c47975c58 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
@@ -36,6 +36,7 @@
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/atomic.h>
+#include <linux/wait.h>
#include <linux/xarray.h>
#include <net/devlink.h>
#include <linux/mlx5/device.h>
@@ -385,6 +386,7 @@ struct mlx5_eswitch {
*/
struct rw_semaphore mode_lock;
atomic64_t user_count;
+ wait_queue_head_t work_queue_wait;
/* Protected with the E-Switch qos domain lock. */
struct {
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
index 23af5a12dc07..9e2ae217c224 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
@@ -3694,21 +3694,38 @@ static void esw_wq_handler(struct work_struct *work)
struct mlx5_host_work *host_work;
struct mlx5_eswitch *esw;
struct devlink *devlink;
+ int work_gen;
host_work = container_of(work, struct mlx5_host_work, work);
esw = host_work->esw;
+ work_gen = host_work->work_gen;
devlink = priv_to_devlink(esw->dev);
- devl_lock(devlink);
+ /* Do not block on devlink lock until stale work is filtered out.
+ * Teardown can invalidate the generation and then wait for this
+ * workqueue while holding devlink lock.
+ */
+ for (;;) {
+ if (work_gen != atomic_read(&esw->generation))
+ goto free;
+
+ if (devl_trylock(devlink))
+ break;
+
+ wait_event_timeout(esw->work_queue_wait,
+ work_gen != atomic_read(&esw->generation),
+ msecs_to_jiffies(60));
+ }
/* Stale work from one or more mode changes ago. Bail out. */
- if (host_work->work_gen != atomic_read(&esw->generation))
+ if (work_gen != atomic_read(&esw->generation))
goto unlock;
host_work->func(esw);
unlock:
devl_unlock(devlink);
+free:
kfree(host_work);
}
--
2.44.0
^ permalink raw reply related
* [PATCH net-next] net: airoha: Introduce airoha_fe_get()/airoha_qdma_get() register read helpers
From: Lorenzo Bianconi @ 2026-04-28 5:10 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Simon Horman, linux-arm-kernel, linux-mediatek, netdev,
Lorenzo Bianconi
Add airoha_fe_get() and airoha_qdma_get() as utility routines for reading
a masked field from a specified register.
This is a non-functional refactor, no logical changes are introduced to
the existing codebase.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 13 ++++---------
drivers/net/ethernet/airoha/airoha_eth.h | 4 ++++
drivers/net/ethernet/airoha/airoha_ppe.c | 5 ++---
3 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 2bb0a3ff9810..40b7a00c7d95 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -201,15 +201,13 @@ static void airoha_fe_vip_setup(struct airoha_eth *eth)
static u32 airoha_fe_get_pse_queue_rsv_pages(struct airoha_eth *eth,
u32 port, u32 queue)
{
- u32 val;
-
airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_WR,
PSE_CFG_PORT_ID_MASK | PSE_CFG_QUEUE_ID_MASK,
FIELD_PREP(PSE_CFG_PORT_ID_MASK, port) |
FIELD_PREP(PSE_CFG_QUEUE_ID_MASK, queue));
- val = airoha_fe_rr(eth, REG_FE_PSE_QUEUE_CFG_VAL);
- return FIELD_GET(PSE_CFG_OQ_RSV_MASK, val);
+ return airoha_fe_get(eth, REG_FE_PSE_QUEUE_CFG_VAL,
+ PSE_CFG_OQ_RSV_MASK);
}
static void airoha_fe_set_pse_queue_rsv_pages(struct airoha_eth *eth,
@@ -227,9 +225,7 @@ static void airoha_fe_set_pse_queue_rsv_pages(struct airoha_eth *eth,
static u32 airoha_fe_get_pse_all_rsv(struct airoha_eth *eth)
{
- u32 val = airoha_fe_rr(eth, REG_FE_PSE_BUF_SET);
-
- return FIELD_GET(PSE_ALLRSV_MASK, val);
+ return airoha_fe_get(eth, REG_FE_PSE_BUF_SET, PSE_ALLRSV_MASK);
}
static int airoha_fe_set_pse_oq_rsv(struct airoha_eth *eth,
@@ -247,8 +243,7 @@ static int airoha_fe_set_pse_oq_rsv(struct airoha_eth *eth,
FIELD_PREP(PSE_ALLRSV_MASK, all_rsv));
/* modify hthd */
- tmp = airoha_fe_rr(eth, PSE_FQ_CFG);
- fq_limit = FIELD_GET(PSE_FQ_LIMIT_MASK, tmp);
+ fq_limit = airoha_fe_get(eth, PSE_FQ_CFG, PSE_FQ_LIMIT_MASK);
tmp = fq_limit - all_rsv - 0x20;
airoha_fe_rmw(eth, REG_PSE_SHARE_USED_THD,
PSE_SHARE_USED_HTHD_MASK,
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index e389d2fe3b86..c81433d44e3e 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -619,6 +619,8 @@ u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val);
airoha_rmw((eth)->fe_regs, (offset), 0, (val))
#define airoha_fe_clear(eth, offset, val) \
airoha_rmw((eth)->fe_regs, (offset), (val), 0)
+#define airoha_fe_get(eth, offset, mask) \
+ FIELD_GET((mask), airoha_fe_rr((eth), (offset)))
#define airoha_qdma_rr(qdma, offset) \
airoha_rr((qdma)->regs, (offset))
@@ -630,6 +632,8 @@ u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val);
airoha_rmw((qdma)->regs, (offset), 0, (val))
#define airoha_qdma_clear(qdma, offset, val) \
airoha_rmw((qdma)->regs, (offset), (val), 0)
+#define airoha_qdma_get(eth, offset, mask) \
+ FIELD_GET((mask), airoha_qdma_rr((eth), (offset)))
static inline bool airoha_is_lan_gdm_port(struct airoha_gdm_port *port)
{
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 5c9dff6bccd1..697af6fdd4c3 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -80,9 +80,8 @@ bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index)
static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
{
- u16 timestamp = airoha_fe_rr(ppe->eth, REG_FE_FOE_TS);
-
- return FIELD_GET(AIROHA_FOE_IB1_BIND_TIMESTAMP, timestamp);
+ return airoha_fe_get(ppe->eth, REG_FE_FOE_TS,
+ AIROHA_FOE_IB1_BIND_TIMESTAMP);
}
void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id, u8 fport)
---
base-commit: 790ead9394860e7d70c5e0e50a35b243e909a618
change-id: 20260428-airoha_fe_get-airoha_qdma_get-7a087a23aef4
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* [PATCH net-next V2 2/3] net/mlx5: E-Switch, introduce generic work queue dispatch helper
From: Tariq Toukan @ 2026-04-28 5:10 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Cosmin Ratiu
In-Reply-To: <20260428051018.219093-1-tariqt@nvidia.com>
From: Mark Bloch <mbloch@nvidia.com>
Each E-Switch work item requires the same boilerplate: acquire the
devlink lock, check whether the work is stale, dispatch to the
appropriate handler, and release the lock. Factor this out.
Add a func callback to mlx5_host_work so the generic handler
esw_wq_handler() can dispatch to the right function without
duplicating locking logic. Introduce mlx5_esw_add_work() as the
single enqueue point: it stamps the work item with the current
generation counter and queues it onto the E-Switch work queue.
Refactor esw_vfs_changed_event_handler() to match the new contract:
it no longer receives work_gen or out as parameters. It queries
mlx5_esw_query_functions() itself and owns the kvfree() of the
result. The devlink lock is acquired and released by esw_wq_handler()
before dispatching, so the handler runs with the lock already held.
Update mlx5_esw_funcs_changed_handler() to use mlx5_esw_add_work().
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
Reviewed-by: Cosmin Ratiu <cratiu@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
.../net/ethernet/mellanox/mlx5/core/eswitch.h | 1 +
.../mellanox/mlx5/core/eswitch_offloads.c | 77 +++++++++++--------
2 files changed, 45 insertions(+), 33 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
index 0c3d2bdebf8c..e3ab8a30c174 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
@@ -336,6 +336,7 @@ struct mlx5_host_work {
struct work_struct work;
struct mlx5_eswitch *esw;
int work_gen;
+ void (*func)(struct mlx5_eswitch *esw);
};
struct mlx5_esw_functions {
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
index b2e7294d3a5c..23af5a12dc07 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
@@ -3655,20 +3655,15 @@ static void esw_offloads_steering_cleanup(struct mlx5_eswitch *esw)
mutex_destroy(&esw->fdb_table.offloads.vports.lock);
}
-static void
-esw_vfs_changed_event_handler(struct mlx5_eswitch *esw, int work_gen,
- const u32 *out)
+static void esw_vfs_changed_event_handler(struct mlx5_eswitch *esw)
{
- struct devlink *devlink;
bool host_pf_disabled;
u16 new_num_vfs;
+ const u32 *out;
- devlink = priv_to_devlink(esw->dev);
- devl_lock(devlink);
-
- /* Stale work from one or more mode changes ago. Bail out. */
- if (work_gen != atomic_read(&esw->generation))
- goto unlock;
+ out = mlx5_esw_query_functions(esw->dev);
+ if (IS_ERR(out))
+ return;
new_num_vfs = MLX5_GET(query_esw_functions_out, out,
host_params_context.host_num_of_vfs);
@@ -3676,7 +3671,7 @@ esw_vfs_changed_event_handler(struct mlx5_eswitch *esw, int work_gen,
host_params_context.host_pf_disabled);
if (new_num_vfs == esw->esw_funcs.num_vfs || host_pf_disabled)
- goto unlock;
+ goto free;
/* Number of VFs can only change from "0 to x" or "x to 0". */
if (esw->esw_funcs.num_vfs > 0) {
@@ -3686,54 +3681,70 @@ esw_vfs_changed_event_handler(struct mlx5_eswitch *esw, int work_gen,
err = mlx5_eswitch_load_vf_vports(esw, new_num_vfs,
MLX5_VPORT_UC_ADDR_CHANGE);
- if (err) {
- devl_unlock(devlink);
- return;
- }
+ if (err)
+ goto free;
}
esw->esw_funcs.num_vfs = new_num_vfs;
-unlock:
- devl_unlock(devlink);
+free:
+ kvfree(out);
}
-static void esw_functions_changed_event_handler(struct work_struct *work)
+static void esw_wq_handler(struct work_struct *work)
{
struct mlx5_host_work *host_work;
struct mlx5_eswitch *esw;
- const u32 *out;
+ struct devlink *devlink;
host_work = container_of(work, struct mlx5_host_work, work);
esw = host_work->esw;
+ devlink = priv_to_devlink(esw->dev);
- out = mlx5_esw_query_functions(esw->dev);
- if (IS_ERR(out))
- goto out;
+ devl_lock(devlink);
- esw_vfs_changed_event_handler(esw, host_work->work_gen, out);
- kvfree(out);
-out:
+ /* Stale work from one or more mode changes ago. Bail out. */
+ if (host_work->work_gen != atomic_read(&esw->generation))
+ goto unlock;
+
+ host_work->func(esw);
+
+unlock:
+ devl_unlock(devlink);
kfree(host_work);
}
-int mlx5_esw_funcs_changed_handler(struct notifier_block *nb, unsigned long type, void *data)
+static int mlx5_esw_add_work(struct mlx5_eswitch *esw,
+ void (*func)(struct mlx5_eswitch *esw))
{
- struct mlx5_esw_functions *esw_funcs;
struct mlx5_host_work *host_work;
- struct mlx5_eswitch *esw;
host_work = kzalloc_obj(*host_work, GFP_ATOMIC);
if (!host_work)
- return NOTIFY_DONE;
-
- esw_funcs = mlx5_nb_cof(nb, struct mlx5_esw_functions, nb);
- esw = container_of(esw_funcs, struct mlx5_eswitch, esw_funcs);
+ return -ENOMEM;
host_work->esw = esw;
host_work->work_gen = atomic_read(&esw->generation);
- INIT_WORK(&host_work->work, esw_functions_changed_event_handler);
+ host_work->func = func;
+ INIT_WORK(&host_work->work, esw_wq_handler);
queue_work(esw->work_queue, &host_work->work);
+ return 0;
+}
+
+int mlx5_esw_funcs_changed_handler(struct notifier_block *nb,
+ unsigned long type, void *data)
+{
+ struct mlx5_esw_functions *esw_funcs;
+ struct mlx5_eswitch *esw;
+ int ret;
+
+ esw_funcs = mlx5_nb_cof(nb, struct mlx5_esw_functions, nb);
+ esw = container_of(esw_funcs, struct mlx5_eswitch, esw_funcs);
+
+ ret = mlx5_esw_add_work(esw, esw_vfs_changed_event_handler);
+ if (ret)
+ return NOTIFY_DONE;
+
return NOTIFY_OK;
}
--
2.44.0
^ permalink raw reply related
* Re: [PATCH net-next] docs: 3c509: remove note about card detection failing with overclock
From: Ethan Nelson-Moore @ 2026-04-28 5:20 UTC (permalink / raw)
To: macro; +Cc: andrew, enelsonmoore, horms, netdev
In-Reply-To: <alpine.DEB.2.21.2601260039220.40317@angie.orcam.me.uk>
Hi, Maciej,
Sorry for the extreme delay in responding - I somehow missed this message.
On 26 Jan 2026 Maciej W. Rozycki wrote:
> -- so it's not about using a time-based vs some other kind of delay. The
> comment to be removed was only added along with the whole document on the
> 3c509 driver with Linux 2.5.13, much later (2002 vs 1998), and therefore
> still stands.
I traced back the usage of udelay in this driver and found that id_read_eeprom
did in fact use a busy waiting loop before kernel 2.0.14:
https://github.com/schwabe/davej-history/commit/8cf64f0c94dcbf029dc3b12cb8e2afba590f8f72#diff-9acb56ec07fd10ccce51b1dc719976d60ee5a947dec31174e2a96f871fde72f9L331
This is probably what the comment was referring to, and therefore it is in fact
incorrect. The documentation appears to have been imported from the Scyld
out-of-tree driver (the basis of the mainline driver), which still mentioned the
issue in 2003:
https://web.archive.org/web/20030605134029/http://www.scyld.com/network/3c509.html
I was not able to find a copy of an old Scyld driver easily to see if it was
affected by the issue.
Hope that clarifies the situation.
Ethan
^ permalink raw reply
* [PATCH net 01/11] iavf: rename IAVF_VLAN_IS_NEW to IAVF_VLAN_ADDING
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Rafal Romanowski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
Rename the IAVF_VLAN_IS_NEW state to IAVF_VLAN_ADDING to better
describe what the state represents: an ADD request has been sent to
the PF and is waiting for a response.
This is a pure rename with no behavioral change, preparing for a
cleanup of the VLAN filter state machine.
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rafal Romanowski <rafal.romanowski@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/iavf/iavf.h | 2 +-
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h
index e9fb0a0919e3..47a862ca5e2c 100644
--- a/drivers/net/ethernet/intel/iavf/iavf.h
+++ b/drivers/net/ethernet/intel/iavf/iavf.h
@@ -158,7 +158,7 @@ struct iavf_vlan {
enum iavf_vlan_state_t {
IAVF_VLAN_INVALID,
IAVF_VLAN_ADD, /* filter needs to be added */
- IAVF_VLAN_IS_NEW, /* filter is new, wait for PF answer */
+ IAVF_VLAN_ADDING, /* ADD sent to PF, waiting for response */
IAVF_VLAN_ACTIVE, /* filter is accepted by PF */
IAVF_VLAN_DISABLE, /* filter needs to be deleted by PF, then marked INACTIVE */
IAVF_VLAN_INACTIVE, /* filter is inactive, we are in IFF_DOWN */
diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
index a52c100dcbc5..6b06ae872a0c 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
@@ -746,7 +746,7 @@ static void iavf_vlan_add_reject(struct iavf_adapter *adapter)
spin_lock_bh(&adapter->mac_vlan_list_lock);
list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
- if (f->state == IAVF_VLAN_IS_NEW) {
+ if (f->state == IAVF_VLAN_ADDING) {
list_del(&f->list);
kfree(f);
adapter->num_vlan_filters--;
@@ -812,7 +812,7 @@ void iavf_add_vlans(struct iavf_adapter *adapter)
if (f->state == IAVF_VLAN_ADD) {
vvfl->vlan_id[i] = f->vlan.vid;
i++;
- f->state = IAVF_VLAN_IS_NEW;
+ f->state = IAVF_VLAN_ADDING;
if (i == count)
break;
}
@@ -874,7 +874,7 @@ void iavf_add_vlans(struct iavf_adapter *adapter)
vlan->tpid = f->vlan.tpid;
i++;
- f->state = IAVF_VLAN_IS_NEW;
+ f->state = IAVF_VLAN_ADDING;
}
}
@@ -2910,7 +2910,7 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
spin_lock_bh(&adapter->mac_vlan_list_lock);
list_for_each_entry(f, &adapter->vlan_filter_list, list) {
- if (f->state == IAVF_VLAN_IS_NEW)
+ if (f->state == IAVF_VLAN_ADDING)
f->state = IAVF_VLAN_ACTIVE;
}
spin_unlock_bh(&adapter->mac_vlan_list_lock);
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 00/11] Intel Wired LAN Update 2026-04-27 (ice, iavf)
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Rafal Romanowski, Paul Menzel,
Alexander Nowlin, Ivan Vecera, Arkadiusz Kubalewski,
Vadim Fedorenko
Petr Oros from RedHat has accumulated a number of fixes for the Intel ice
and iavf drivers, bundled together in this series.
First, a series of 4 fixes to resolve issues with the iavf driver logic for
handling VLAN filters. This includes keeping VLAN filters while the
interface is brought down, waiting for confirmation on filter deletion
before deleting filters from the driver tracking structures, and handling
the VIRTCHNL_OP_ADD_VLAN for the old v1 VLAN_ADD command.
A fix for a crash in ice_reset_all_vfs(), properly checking for errors when
ice_vf_rebuild_vsi() fails.
A fix for a possible infinite recursion in ice_cfg_tx_topo() that occurs
when trying to apply invalid Tx topology configuration.
A fix to initialize the SMA pins in the DPLL subsystem properly.
A fix to change the SMA and U.FL pin state for paired pins, ensuring that
all flows changing one pin will also update its shared pin appropriately.
A preparatory patch to export __dpll_pin_change_ntf() so that drivers can
notify pin changes while already holding the dpll_lock.
A fix to ensure DPLL notifications are sent for the software-controlled
pins which wrap the physical CGU input/output pins.
A fix to add DPLL notifications for peer pins when changing the SMA or U.FL
pins, ensuring DPLL subsystem is notified about the paired connected pins.
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
Ivan Vecera (1):
dpll: export __dpll_pin_change_ntf() for use under dpll_lock
Petr Oros (10):
iavf: rename IAVF_VLAN_IS_NEW to IAVF_VLAN_ADDING
iavf: stop removing VLAN filters from PF on interface down
iavf: wait for PF confirmation before removing VLAN filters
iavf: add VIRTCHNL_OP_ADD_VLAN to success completion handler
ice: fix NULL pointer dereference in ice_reset_all_vfs()
ice: fix infinite recursion in ice_cfg_tx_topo via ice_init_dev_hw
ice: fix missing SMA pin initialization in DPLL subsystem
ice: fix SMA and U.FL pin state changes affecting paired pin
ice: fix missing dpll notifications for SW pins
ice: add dpll peer notification for paired SMA and U.FL pins
drivers/dpll/dpll_netlink.h | 2 -
drivers/net/ethernet/intel/iavf/iavf.h | 9 +-
include/linux/dpll.h | 1 +
drivers/dpll/dpll_netlink.c | 10 ++
drivers/net/ethernet/intel/iavf/iavf_main.c | 52 ++------
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 76 ++++++------
drivers/net/ethernet/intel/ice/devlink/devlink.c | 2 +
drivers/net/ethernet/intel/ice/ice_common.c | 2 -
drivers/net/ethernet/intel/ice/ice_dpll.c | 146 +++++++++++++++++++++--
drivers/net/ethernet/intel/ice/ice_main.c | 2 +
drivers/net/ethernet/intel/ice/ice_vf_lib.c | 7 +-
11 files changed, 207 insertions(+), 102 deletions(-)
---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260427-jk-iwl-net-petr-oros-fixes-ff4262936b8c
Best regards,
--
Jacob Keller <jacob.e.keller@intel.com>
^ permalink raw reply
* [PATCH net 04/11] iavf: add VIRTCHNL_OP_ADD_VLAN to success completion handler
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Rafal Romanowski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
The V1 ADD_VLAN opcode had no success handler; filters sent via V1
stayed in ADDING state permanently. Add a fallthrough case so V1
filters also transition ADDING -> ACTIVE on PF confirmation.
Critically, add an `if (v_retval) break` guard: the error switch in
iavf_virtchnl_completion() does NOT return after handling errors,
it falls through to the success switch. Without this guard, a
PF-rejected ADD would incorrectly mark ADDING filters as ACTIVE,
creating a driver/HW mismatch where the driver believes the filter
is installed but the PF never accepted it.
For V2, this is harmless: iavf_vlan_add_reject() in the error
block already kfree'd all ADDING filters, so the success handler
finds nothing to transition.
Fixes: 968996c070ef ("iavf: Fix VLAN_V2 addition/rejection")
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rafal Romanowski <rafal.romanowski@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
index 93ca79c3e3b5..4f2defd2331b 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
@@ -2876,9 +2876,13 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
spin_unlock_bh(&adapter->adv_rss_lock);
}
break;
+ case VIRTCHNL_OP_ADD_VLAN:
case VIRTCHNL_OP_ADD_VLAN_V2: {
struct iavf_vlan_filter *f;
+ if (v_retval)
+ break;
+
spin_lock_bh(&adapter->mac_vlan_list_lock);
list_for_each_entry(f, &adapter->vlan_filter_list, list) {
if (f->state == IAVF_VLAN_ADDING)
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 02/11] iavf: stop removing VLAN filters from PF on interface down
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Rafal Romanowski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
When a VF goes down, the driver currently sends DEL_VLAN to the PF for
every VLAN filter (ACTIVE -> DISABLE -> send DEL -> INACTIVE), then
re-adds them all on UP (INACTIVE -> ADD -> send ADD -> ADDING ->
ACTIVE). This round-trip is unnecessary because:
1. The PF disables the VF's queues via VIRTCHNL_OP_DISABLE_QUEUES,
which already prevents all RX/TX traffic regardless of VLAN filter
state.
2. The VLAN filters remaining in PF HW while the VF is down is
harmless - packets matching those filters have nowhere to go with
queues disabled.
3. The DEL+ADD cycle during down/up creates race windows where the
VLAN filter list is incomplete. With spoofcheck enabled, the PF
enables TX VLAN filtering on the first non-zero VLAN add, blocking
traffic for any VLANs not yet re-added.
Remove the entire DISABLE/INACTIVE state machinery:
- Remove IAVF_VLAN_DISABLE and IAVF_VLAN_INACTIVE enum values
- Remove iavf_restore_filters() and its call from iavf_open()
- Remove VLAN filter handling from iavf_clear_mac_vlan_filters(),
rename it to iavf_clear_mac_filters()
- Remove DEL_VLAN_FILTER scheduling from iavf_down()
- Remove all DISABLE/INACTIVE handling from iavf_del_vlans()
VLAN filters now stay ACTIVE across down/up cycles. Only explicit
user removal (ndo_vlan_rx_kill_vid) or PF/VF reset triggers VLAN
filter deletion/re-addition.
Fixes: ed1f5b58ea01 ("i40evf: remove VLAN filters on close")
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rafal Romanowski <rafal.romanowski@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/iavf/iavf.h | 6 ++--
drivers/net/ethernet/intel/iavf/iavf_main.c | 39 +++----------------------
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 33 ++++-----------------
3 files changed, 12 insertions(+), 66 deletions(-)
diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h
index 47a862ca5e2c..5765715914d6 100644
--- a/drivers/net/ethernet/intel/iavf/iavf.h
+++ b/drivers/net/ethernet/intel/iavf/iavf.h
@@ -159,10 +159,8 @@ enum iavf_vlan_state_t {
IAVF_VLAN_INVALID,
IAVF_VLAN_ADD, /* filter needs to be added */
IAVF_VLAN_ADDING, /* ADD sent to PF, waiting for response */
- IAVF_VLAN_ACTIVE, /* filter is accepted by PF */
- IAVF_VLAN_DISABLE, /* filter needs to be deleted by PF, then marked INACTIVE */
- IAVF_VLAN_INACTIVE, /* filter is inactive, we are in IFF_DOWN */
- IAVF_VLAN_REMOVE, /* filter needs to be removed from list */
+ IAVF_VLAN_ACTIVE, /* PF confirmed, filter is in HW */
+ IAVF_VLAN_REMOVE, /* filter queued for DEL from PF */
};
struct iavf_vlan_filter {
diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c
index 3c1465cf0515..ca29038c0016 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_main.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_main.c
@@ -801,27 +801,6 @@ static void iavf_del_vlan(struct iavf_adapter *adapter, struct iavf_vlan vlan)
spin_unlock_bh(&adapter->mac_vlan_list_lock);
}
-/**
- * iavf_restore_filters
- * @adapter: board private structure
- *
- * Restore existing non MAC filters when VF netdev comes back up
- **/
-static void iavf_restore_filters(struct iavf_adapter *adapter)
-{
- struct iavf_vlan_filter *f;
-
- /* re-add all VLAN filters */
- spin_lock_bh(&adapter->mac_vlan_list_lock);
-
- list_for_each_entry(f, &adapter->vlan_filter_list, list) {
- if (f->state == IAVF_VLAN_INACTIVE)
- f->state = IAVF_VLAN_ADD;
- }
-
- spin_unlock_bh(&adapter->mac_vlan_list_lock);
- adapter->aq_required |= IAVF_FLAG_AQ_ADD_VLAN_FILTER;
-}
/**
* iavf_get_num_vlans_added - get number of VLANs added
@@ -1246,13 +1225,12 @@ static void iavf_up_complete(struct iavf_adapter *adapter)
}
/**
- * iavf_clear_mac_vlan_filters - Remove mac and vlan filters not sent to PF
- * yet and mark other to be removed.
+ * iavf_clear_mac_filters - Remove MAC filters not sent to PF yet and mark
+ * others to be removed.
* @adapter: board private structure
**/
-static void iavf_clear_mac_vlan_filters(struct iavf_adapter *adapter)
+static void iavf_clear_mac_filters(struct iavf_adapter *adapter)
{
- struct iavf_vlan_filter *vlf, *vlftmp;
struct iavf_mac_filter *f, *ftmp;
spin_lock_bh(&adapter->mac_vlan_list_lock);
@@ -1271,11 +1249,6 @@ static void iavf_clear_mac_vlan_filters(struct iavf_adapter *adapter)
}
}
- /* disable all VLAN filters */
- list_for_each_entry_safe(vlf, vlftmp, &adapter->vlan_filter_list,
- list)
- vlf->state = IAVF_VLAN_DISABLE;
-
spin_unlock_bh(&adapter->mac_vlan_list_lock);
}
@@ -1371,7 +1344,7 @@ void iavf_down(struct iavf_adapter *adapter)
iavf_napi_disable_all(adapter);
iavf_irq_disable(adapter);
- iavf_clear_mac_vlan_filters(adapter);
+ iavf_clear_mac_filters(adapter);
iavf_clear_cloud_filters(adapter);
iavf_clear_fdir_filters(adapter);
iavf_clear_adv_rss_conf(adapter);
@@ -1388,8 +1361,6 @@ void iavf_down(struct iavf_adapter *adapter)
*/
if (!list_empty(&adapter->mac_filter_list))
adapter->aq_required |= IAVF_FLAG_AQ_DEL_MAC_FILTER;
- if (!list_empty(&adapter->vlan_filter_list))
- adapter->aq_required |= IAVF_FLAG_AQ_DEL_VLAN_FILTER;
if (!list_empty(&adapter->cloud_filter_list))
adapter->aq_required |= IAVF_FLAG_AQ_DEL_CLOUD_FILTER;
if (!list_empty(&adapter->fdir_list_head))
@@ -4494,8 +4465,6 @@ static int iavf_open(struct net_device *netdev)
iavf_add_filter(adapter, adapter->hw.mac.addr);
spin_unlock_bh(&adapter->mac_vlan_list_lock);
- /* Restore filters that were removed with IFF_DOWN */
- iavf_restore_filters(adapter);
iavf_restore_fdir_filters(adapter);
iavf_configure(adapter);
diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
index 6b06ae872a0c..4f197d908124 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
@@ -911,22 +911,12 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
spin_lock_bh(&adapter->mac_vlan_list_lock);
list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
- /* since VLAN capabilities are not allowed, we dont want to send
- * a VLAN delete request because it will most likely fail and
- * create unnecessary errors/noise, so just free the VLAN
- * filters marked for removal to enable bailing out before
- * sending a virtchnl message
- */
if (f->state == IAVF_VLAN_REMOVE &&
!VLAN_FILTERING_ALLOWED(adapter)) {
list_del(&f->list);
kfree(f);
adapter->num_vlan_filters--;
- } else if (f->state == IAVF_VLAN_DISABLE &&
- !VLAN_FILTERING_ALLOWED(adapter)) {
- f->state = IAVF_VLAN_INACTIVE;
- } else if (f->state == IAVF_VLAN_REMOVE ||
- f->state == IAVF_VLAN_DISABLE) {
+ } else if (f->state == IAVF_VLAN_REMOVE) {
count++;
}
}
@@ -959,13 +949,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
vvfl->vsi_id = adapter->vsi_res->vsi_id;
vvfl->num_elements = count;
list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
- if (f->state == IAVF_VLAN_DISABLE) {
- vvfl->vlan_id[i] = f->vlan.vid;
- f->state = IAVF_VLAN_INACTIVE;
- i++;
- if (i == count)
- break;
- } else if (f->state == IAVF_VLAN_REMOVE) {
+ if (f->state == IAVF_VLAN_REMOVE) {
vvfl->vlan_id[i] = f->vlan.vid;
list_del(&f->list);
kfree(f);
@@ -1007,8 +991,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
vvfl_v2->vport_id = adapter->vsi_res->vsi_id;
vvfl_v2->num_elements = count;
list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
- if (f->state == IAVF_VLAN_DISABLE ||
- f->state == IAVF_VLAN_REMOVE) {
+ if (f->state == IAVF_VLAN_REMOVE) {
struct virtchnl_vlan_supported_caps *filtering_support =
&adapter->vlan_v2_caps.filtering.filtering_support;
struct virtchnl_vlan *vlan;
@@ -1022,13 +1005,9 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
vlan->tci = f->vlan.vid;
vlan->tpid = f->vlan.tpid;
- if (f->state == IAVF_VLAN_DISABLE) {
- f->state = IAVF_VLAN_INACTIVE;
- } else {
- list_del(&f->list);
- kfree(f);
- adapter->num_vlan_filters--;
- }
+ list_del(&f->list);
+ kfree(f);
+ adapter->num_vlan_filters--;
i++;
if (i == count)
break;
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 03/11] iavf: wait for PF confirmation before removing VLAN filters
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Rafal Romanowski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
The VLAN filter DELETE path was asymmetric with the ADD path: ADD
waits for PF confirmation (ADD -> ADDING -> ACTIVE), but DELETE
immediately frees the filter struct after sending the DEL message
without waiting for the PF response.
This is problematic because:
- If the PF rejects the DEL, the filter remains in HW but the driver
has already freed the tracking structure, losing sync.
- Race conditions between DEL pending and other operations
(add, reset) cannot be properly resolved if the filter struct
is already gone.
Add IAVF_VLAN_REMOVING state to make the DELETE path symmetric:
REMOVE -> REMOVING (send DEL) -> PF confirms -> kfree
-> PF rejects -> ACTIVE
In iavf_del_vlans(), transition filters from REMOVE to REMOVING
instead of immediately freeing them. The new DEL completion handler
in iavf_virtchnl_completion() frees filters on success or reverts
them to ACTIVE on error.
Update iavf_add_vlan() to handle the REMOVING state: if a DEL is
pending and the user re-adds the same VLAN, queue it for ADD so
it gets re-programmed after the PF processes the DEL.
The !VLAN_FILTERING_ALLOWED early-exit path still frees filters
directly since no PF message is sent in that case.
Also update iavf_del_vlan() to skip filters already in REMOVING
state: DEL has been sent to PF and the completion handler will
free the filter when PF confirms. Without this guard, the sequence
DEL(pending) -> user-del -> second DEL could cause the PF to return
an error for the second DEL (filter already gone), causing the
completion handler to incorrectly revert a deleted filter back to
ACTIVE.
Fixes: 968996c070ef ("iavf: Fix VLAN_V2 addition/rejection")
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rafal Romanowski <rafal.romanowski@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/iavf/iavf.h | 1 +
drivers/net/ethernet/intel/iavf/iavf_main.c | 13 +++++----
drivers/net/ethernet/intel/iavf/iavf_virtchnl.c | 37 +++++++++++++++++--------
3 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h
index 5765715914d6..050f8241ef5e 100644
--- a/drivers/net/ethernet/intel/iavf/iavf.h
+++ b/drivers/net/ethernet/intel/iavf/iavf.h
@@ -161,6 +161,7 @@ enum iavf_vlan_state_t {
IAVF_VLAN_ADDING, /* ADD sent to PF, waiting for response */
IAVF_VLAN_ACTIVE, /* PF confirmed, filter is in HW */
IAVF_VLAN_REMOVE, /* filter queued for DEL from PF */
+ IAVF_VLAN_REMOVING, /* DEL sent to PF, waiting for response */
};
struct iavf_vlan_filter {
diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c
index ca29038c0016..d2914c511e1e 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_main.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_main.c
@@ -757,10 +757,10 @@ iavf_vlan_filter *iavf_add_vlan(struct iavf_adapter *adapter,
adapter->num_vlan_filters++;
iavf_schedule_aq_request(adapter, IAVF_FLAG_AQ_ADD_VLAN_FILTER);
} else if (f->state == IAVF_VLAN_REMOVE) {
- /* Re-add the filter since we cannot tell whether the
- * pending delete has already been processed by the PF.
- * A duplicate add is harmless.
- */
+ /* DEL not yet sent to PF, cancel it */
+ f->state = IAVF_VLAN_ACTIVE;
+ } else if (f->state == IAVF_VLAN_REMOVING) {
+ /* DEL already sent to PF, re-add after completion */
f->state = IAVF_VLAN_ADD;
iavf_schedule_aq_request(adapter,
IAVF_FLAG_AQ_ADD_VLAN_FILTER);
@@ -791,11 +791,14 @@ static void iavf_del_vlan(struct iavf_adapter *adapter, struct iavf_vlan vlan)
list_del(&f->list);
kfree(f);
adapter->num_vlan_filters--;
- } else {
+ } else if (f->state != IAVF_VLAN_REMOVING) {
f->state = IAVF_VLAN_REMOVE;
iavf_schedule_aq_request(adapter,
IAVF_FLAG_AQ_DEL_VLAN_FILTER);
}
+ /* If REMOVING, DEL is already sent to PF; completion
+ * handler will free the filter when PF confirms.
+ */
}
spin_unlock_bh(&adapter->mac_vlan_list_lock);
diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
index 4f197d908124..93ca79c3e3b5 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
@@ -948,12 +948,10 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
vvfl->vsi_id = adapter->vsi_res->vsi_id;
vvfl->num_elements = count;
- list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
+ list_for_each_entry(f, &adapter->vlan_filter_list, list) {
if (f->state == IAVF_VLAN_REMOVE) {
vvfl->vlan_id[i] = f->vlan.vid;
- list_del(&f->list);
- kfree(f);
- adapter->num_vlan_filters--;
+ f->state = IAVF_VLAN_REMOVING;
i++;
if (i == count)
break;
@@ -990,7 +988,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
vvfl_v2->vport_id = adapter->vsi_res->vsi_id;
vvfl_v2->num_elements = count;
- list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list, list) {
+ list_for_each_entry(f, &adapter->vlan_filter_list, list) {
if (f->state == IAVF_VLAN_REMOVE) {
struct virtchnl_vlan_supported_caps *filtering_support =
&adapter->vlan_v2_caps.filtering.filtering_support;
@@ -1005,9 +1003,7 @@ void iavf_del_vlans(struct iavf_adapter *adapter)
vlan->tci = f->vlan.vid;
vlan->tpid = f->vlan.tpid;
- list_del(&f->list);
- kfree(f);
- adapter->num_vlan_filters--;
+ f->state = IAVF_VLAN_REMOVING;
i++;
if (i == count)
break;
@@ -2370,10 +2366,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr);
wake_up(&adapter->vc_waitqueue);
break;
- case VIRTCHNL_OP_DEL_VLAN:
- dev_err(&adapter->pdev->dev, "Failed to delete VLAN filter, error %s\n",
- iavf_stat_str(&adapter->hw, v_retval));
- break;
case VIRTCHNL_OP_DEL_ETH_ADDR:
dev_err(&adapter->pdev->dev, "Failed to delete MAC filter, error %s\n",
iavf_stat_str(&adapter->hw, v_retval));
@@ -2895,6 +2887,27 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
spin_unlock_bh(&adapter->mac_vlan_list_lock);
}
break;
+ case VIRTCHNL_OP_DEL_VLAN:
+ case VIRTCHNL_OP_DEL_VLAN_V2: {
+ struct iavf_vlan_filter *f, *ftmp;
+
+ spin_lock_bh(&adapter->mac_vlan_list_lock);
+ list_for_each_entry_safe(f, ftmp, &adapter->vlan_filter_list,
+ list) {
+ if (f->state == IAVF_VLAN_REMOVING) {
+ if (v_retval) {
+ /* PF rejected DEL, keep filter */
+ f->state = IAVF_VLAN_ACTIVE;
+ } else {
+ list_del(&f->list);
+ kfree(f);
+ adapter->num_vlan_filters--;
+ }
+ }
+ }
+ spin_unlock_bh(&adapter->mac_vlan_list_lock);
+ }
+ break;
case VIRTCHNL_OP_ENABLE_VLAN_STRIPPING:
/* PF enabled vlan strip on this VF.
* Update netdev->features if needed to be in sync with ethtool.
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 05/11] ice: fix NULL pointer dereference in ice_reset_all_vfs()
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Rafal Romanowski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
ice_reset_all_vfs() ignores the return value of ice_vf_rebuild_vsi().
When the VSI rebuild fails (e.g. during NVM firmware update via
nvmupdate64e), ice_vsi_rebuild() tears down the VSI on its error path,
leaving txq_map and rxq_map as NULL. The subsequent unconditional call
to ice_vf_post_vsi_rebuild() leads to a NULL pointer dereference in
ice_ena_vf_q_mappings() when it accesses vsi->txq_map[0].
The single-VF reset path in ice_reset_vf() already handles this
correctly by checking the return value of ice_vf_reconfig_vsi() and
skipping ice_vf_post_vsi_rebuild() on failure.
Apply the same pattern to ice_reset_all_vfs(): check the return value
of ice_vf_rebuild_vsi() and skip ice_vf_post_vsi_rebuild() and
ice_eswitch_attach_vf() on failure. The VF is left safely disabled
(ICE_VF_STATE_INIT not set, VFGEN_RSTAT not set to VFACTIVE) and can
be recovered via a VFLR triggered by a PCI reset of the VF
(sysfs reset or driver rebind).
Note that this patch does not prevent the VF VSI rebuild from failing
during NVM update — the underlying cause is firmware being in a
transitional state while the EMP reset is processed, which can cause
Admin Queue commands (ice_add_vsi, ice_cfg_vsi_lan) to fail. This
patch only prevents the subsequent NULL pointer dereference that
crashes the kernel when the rebuild does fail.
crash> bt
PID: 50795 TASK: ff34c9ee708dc680 CPU: 1 COMMAND: "kworker/u512:5"
#0 [ff72159bcfe5bb50] machine_kexec at ffffffffaa8850ee
#1 [ff72159bcfe5bba8] __crash_kexec at ffffffffaaa15fba
#2 [ff72159bcfe5bc68] crash_kexec at ffffffffaaa16540
#3 [ff72159bcfe5bc70] oops_end at ffffffffaa837eda
#4 [ff72159bcfe5bc90] page_fault_oops at ffffffffaa893997
#5 [ff72159bcfe5bce8] exc_page_fault at ffffffffab528595
#6 [ff72159bcfe5bd10] asm_exc_page_fault at ffffffffab600bb2
[exception RIP: ice_ena_vf_q_mappings+0x79]
RIP: ffffffffc0a85b29 RSP: ff72159bcfe5bdc8 RFLAGS: 00010206
RAX: 00000000000f0000 RBX: ff34c9efc9c00000 RCX: 0000000000000000
RDX: 0000000000000000 RSI: 0000000000000010 RDI: ff34c9efc9c00000
RBP: ff34c9efc27d4828 R8: 0000000000000093 R9: 0000000000000040
R10: ff34c9efc27d4828 R11: 0000000000000040 R12: 0000000000100000
R13: 0000000000000010 R14: R15:
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
#7 [ff72159bcfe5bdf8] ice_sriov_post_vsi_rebuild at ffffffffc0a85e2e [ice]
#8 [ff72159bcfe5be08] ice_reset_all_vfs at ffffffffc0a920b4 [ice]
#9 [ff72159bcfe5be48] ice_service_task at ffffffffc0a31519 [ice]
#10 [ff72159bcfe5be88] process_one_work at ffffffffaa93dca4
#11 [ff72159bcfe5bec8] worker_thread at ffffffffaa93e9de
#12 [ff72159bcfe5bf18] kthread at ffffffffaa946663
#13 [ff72159bcfe5bf50] ret_from_fork at ffffffffaa8086b9
The panic occurs attempting to dereference the NULL pointer in RDX at
ice_sriov.c:294, which loads vsi->txq_map (offset 0x4b8 in ice_vsi).
The faulting VSI is an allocated slab object but not fully initialized
after a failed ice_vsi_rebuild():
crash> struct ice_vsi 0xff34c9efc27d4828
netdev = 0x0,
rx_rings = 0x0,
tx_rings = 0x0,
q_vectors = 0x0,
txq_map = 0x0,
rxq_map = 0x0,
alloc_txq = 0x10,
num_txq = 0x10,
alloc_rxq = 0x10,
num_rxq = 0x10,
The nvmupdate64e process was performing NVM firmware update:
crash> bt 0xff34c9edd1a30000
PID: 49858 TASK: ff34c9edd1a30000 CPU: 1 COMMAND: "nvmupdate64e"
#0 [ff72159bcd617618] __schedule at ffffffffab5333f8
#4 [ff72159bcd617750] ice_sq_send_cmd at ffffffffc0a35347 [ice]
#5 [ff72159bcd6177a8] ice_sq_send_cmd_retry at ffffffffc0a35b47 [ice]
#6 [ff72159bcd617810] ice_aq_send_cmd at ffffffffc0a38018 [ice]
#7 [ff72159bcd617848] ice_aq_read_nvm at ffffffffc0a40254 [ice]
#8 [ff72159bcd6178b8] ice_read_flat_nvm at ffffffffc0a4034c [ice]
#9 [ff72159bcd617918] ice_devlink_nvm_snapshot at ffffffffc0a6ffa5 [ice]
dmesg:
ice 0000:13:00.0: firmware recommends not updating fw.mgmt, as it
may result in a downgrade. continuing anyways
ice 0000:13:00.1: ice_init_nvm failed -5
ice 0000:13:00.1: Rebuild failed, unload and reload driver
Fixes: 12bb018c538c ("ice: Refactor VF reset")
Signed-off-by: Petr Oros <poros@redhat.com>
Tested-by: Rafal Romanowski <rafal.romanowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/ice/ice_vf_lib.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c
index 772f6b07340d..b1f46707dcc0 100644
--- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c
@@ -804,7 +804,12 @@ void ice_reset_all_vfs(struct ice_pf *pf)
ice_vf_ctrl_invalidate_vsi(vf);
ice_vf_pre_vsi_rebuild(vf);
- ice_vf_rebuild_vsi(vf);
+ if (ice_vf_rebuild_vsi(vf)) {
+ dev_err(dev, "VF %u VSI rebuild failed, leaving VF disabled\n",
+ vf->vf_id);
+ mutex_unlock(&vf->cfg_lock);
+ continue;
+ }
ice_vf_post_vsi_rebuild(vf);
ice_eswitch_attach_vf(pf, vf);
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 06/11] ice: fix infinite recursion in ice_cfg_tx_topo via ice_init_dev_hw
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Paul Menzel, Alexander Nowlin
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
On certain E810 configurations where firmware supports Tx scheduler
topology switching (tx_sched_topo_comp_mode_en), ice_cfg_tx_topo()
may need to apply a new 5-layer or 9-layer topology from the DDP
package. If the AQ command to set the topology fails (e.g. due to
invalid DDP data or firmware limitations), the global configuration
lock must still be cleared via a CORER reset.
Commit 86aae43f21cf ("ice: don't leave device non-functional if Tx
scheduler config fails") correctly fixed this by refactoring
ice_cfg_tx_topo() to always trigger CORER after acquiring the global
lock and re-initialize hardware via ice_init_hw() afterwards.
However, commit 8a37f9e2ff40 ("ice: move ice_deinit_dev() to the end
of deinit paths") later moved ice_init_dev_hw() into ice_init_hw(),
breaking the reinit path introduced by 86aae43f21cf. This creates an
infinite recursive call chain:
ice_init_hw()
ice_init_dev_hw()
ice_cfg_tx_topo() # topology change needed
ice_deinit_hw()
ice_init_hw() # reinit after CORER
ice_init_dev_hw() # recurse
ice_cfg_tx_topo()
... # stack overflow
Fix by moving ice_init_dev_hw() back out of ice_init_hw() and calling
it explicitly from ice_probe() and ice_devlink_reinit_up(). The third
caller, ice_cfg_tx_topo(), intentionally does not need ice_init_dev_hw()
during its reinit, it only needs the core HW reinitialization. This
breaks the recursion cleanly without adding flags or guards.
The deinit ordering changes from commit 8a37f9e2ff40 ("ice: move
ice_deinit_dev() to the end of deinit paths") which fixed slow rmmod
are preserved, only the init-side placement of ice_init_dev_hw() is
reverted.
Fixes: 8a37f9e2ff40 ("ice: move ice_deinit_dev() to the end of deinit paths")
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/ice/devlink/devlink.c | 2 ++
drivers/net/ethernet/intel/ice/ice_common.c | 2 --
drivers/net/ethernet/intel/ice/ice_main.c | 2 ++
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c
index 6144cee8034d..641d6e289d5c 100644
--- a/drivers/net/ethernet/intel/ice/devlink/devlink.c
+++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c
@@ -1245,6 +1245,8 @@ static int ice_devlink_reinit_up(struct ice_pf *pf)
return err;
}
+ ice_init_dev_hw(pf);
+
/* load MSI-X values */
ice_set_min_max_msix(pf);
diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c
index ce11fea122d0..b617a6bff891 100644
--- a/drivers/net/ethernet/intel/ice/ice_common.c
+++ b/drivers/net/ethernet/intel/ice/ice_common.c
@@ -1126,8 +1126,6 @@ int ice_init_hw(struct ice_hw *hw)
if (status)
goto err_unroll_fltr_mgmt_struct;
- ice_init_dev_hw(hw->back);
-
mutex_init(&hw->tnl_lock);
ice_init_chk_recipe_reuse_support(hw);
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 5f92377d4dfc..1d1947a7fe11 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -5245,6 +5245,8 @@ ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent)
return err;
}
+ ice_init_dev_hw(pf);
+
adapter = ice_adapter_get(pdev);
if (IS_ERR(adapter)) {
err = PTR_ERR(adapter);
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 07/11] ice: fix missing SMA pin initialization in DPLL subsystem
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Ivan Vecera,
Arkadiusz Kubalewski, Alexander Nowlin
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
The DPLL SMA/U.FL pin redesign introduced ice_dpll_sw_pin_frequency_get()
which gates frequency reporting on the pin's active flag. This flag is
determined by ice_dpll_sw_pins_update() from the PCA9575 GPIO expander
state. Before the redesign, SMA pins were exposed as direct HW
input/output pins and ice_dpll_frequency_get() returned the CGU
frequency unconditionally — the PCA9575 state was never consulted.
The PCA9575 powers on with all outputs high, setting ICE_SMA1_DIR_EN,
ICE_SMA1_TX_EN, ICE_SMA2_DIR_EN and ICE_SMA2_TX_EN. Nothing in the
driver writes the register during initialization, so
ice_dpll_sw_pins_update() sees all pins as inactive and
ice_dpll_sw_pin_frequency_get() permanently returns 0 Hz for every
SW pin.
Fix this by writing a default SMA configuration in
ice_dpll_init_info_sw_pins(): clear all SMA bits, then set SMA1 and
SMA2 as active inputs (DIR_EN=0) with U.FL1 output and U.FL2 input
disabled. Each SMA/U.FL pair shares a physical signal path so only
one pin per pair can be active at a time. U.FL pins still report
frequency 0 after this fix: U.FL1 (output-only) is disabled by
ICE_SMA1_TX_EN which keeps the TX output buffer off, and U.FL2
(input-only) is disabled by ICE_SMA2_UFL2_RX_DIS. They can be
activated by changing the corresponding SMA pin direction via dpll
netlink.
Fixes: 2dd5d03c77e2 ("ice: redesign dpll sma/u.fl pins control")
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 62f75701d652..498ec2c045f3 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -4014,6 +4014,7 @@ static int ice_dpll_init_info_sw_pins(struct ice_pf *pf)
struct ice_dpll_pin *pin;
u32 phase_adj_max, caps;
int i, ret;
+ u8 data;
if (pf->hw.device_id == ICE_DEV_ID_E810C_QSFP)
input_idx_offset = ICE_E810_RCLK_PINS_NUM;
@@ -4073,6 +4074,22 @@ static int ice_dpll_init_info_sw_pins(struct ice_pf *pf)
}
ice_dpll_phase_range_set(&pin->prop.phase_range, phase_adj_max);
}
+
+ /* Initialize the SMA control register to a known-good default state.
+ * Without this write the PCA9575 GPIO expander retains its power-on
+ * default (all outputs high) which makes all SW pins appear inactive.
+ * Set SMA1 and SMA2 as active inputs, disable U.FL1 output and
+ * U.FL2 input.
+ */
+ ret = ice_read_sma_ctrl(&pf->hw, &data);
+ if (ret)
+ return ret;
+ data &= ~ICE_ALL_SMA_MASK;
+ data |= ICE_SMA1_TX_EN | ICE_SMA2_TX_EN | ICE_SMA2_UFL2_RX_DIS;
+ ret = ice_write_sma_ctrl(&pf->hw, data);
+ if (ret)
+ return ret;
+
ret = ice_dpll_pin_state_update(pf, pin, ICE_DPLL_PIN_TYPE_SOFTWARE,
NULL);
if (ret)
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 08/11] ice: fix SMA and U.FL pin state changes affecting paired pin
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Alexander Nowlin,
Arkadiusz Kubalewski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
SMA and U.FL pins share physical signal paths in pairs (SMA1/U.FL1 and
SMA2/U.FL2) controlled by the PCA9575 GPIO expander. Each pair can
only have one active pin at a time: SMA1 output and U.FL1 output share
the same CGU output, SMA2 input and U.FL2 input share the same CGU
input. The PCA9575 register bits determine which connector in each
pair owns the signal path.
The driver does not account for this pairing in two places:
ice_dpll_ufl_pin_state_set() modifies PCA9575 bits and disables the
backing CGU pin without checking whether the U.FL pin is currently
active. Disconnecting an already inactive U.FL pin flips bits that
the paired SMA pin relies on, breaking its connection.
ice_dpll_sma_direction_set() does not propagate direction changes to
the paired U.FL pin. For SMA2/U.FL2 the ICE_SMA2_UFL2_RX_DIS bit is
never managed, so U.FL2 stays disconnected after SMA2 switches to
output. For both pairs the backing CGU pin of the U.FL side is never
enabled when a direction change activates it, so userspace sees the
pin as disconnected even though the routing is correct.
Fix by guarding the U.FL disconnect path against inactive pins and by
updating the paired U.FL pin fully on SMA direction changes: manage
ICE_SMA2_UFL2_RX_DIS for the SMA2/U.FL2 pair and enable the backing
CGU pin whenever the peer becomes active.
Fixes: 2dd5d03c77e2 ("ice: redesign dpll sma/u.fl pins control")
Signed-off-by: Petr Oros <poros@redhat.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 50 ++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 498ec2c045f3..3f8cd5b8298b 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -1171,6 +1171,8 @@ static int ice_dpll_sma_direction_set(struct ice_dpll_pin *p,
enum dpll_pin_direction direction,
struct netlink_ext_ack *extack)
{
+ struct ice_dplls *d = &p->pf->dplls;
+ struct ice_dpll_pin *peer;
u8 data;
int ret;
@@ -1189,8 +1191,9 @@ static int ice_dpll_sma_direction_set(struct ice_dpll_pin *p,
case ICE_DPLL_PIN_SW_2_IDX:
if (direction == DPLL_PIN_DIRECTION_INPUT) {
data &= ~ICE_SMA2_DIR_EN;
+ data |= ICE_SMA2_UFL2_RX_DIS;
} else {
- data &= ~ICE_SMA2_TX_EN;
+ data &= ~(ICE_SMA2_TX_EN | ICE_SMA2_UFL2_RX_DIS);
data |= ICE_SMA2_DIR_EN;
}
break;
@@ -1202,6 +1205,34 @@ static int ice_dpll_sma_direction_set(struct ice_dpll_pin *p,
ret = ice_dpll_pin_state_update(p->pf, p,
ICE_DPLL_PIN_TYPE_SOFTWARE,
extack);
+ if (ret)
+ return ret;
+
+ /* When a direction change activates the paired U.FL pin, enable
+ * its backing CGU pin so the pin reports as connected. Without
+ * this the U.FL routing is correct but the CGU pin stays disabled
+ * and userspace sees the pin as disconnected. Do not disable the
+ * backing pin when U.FL becomes inactive because the SMA pin may
+ * still be using it.
+ */
+ peer = &d->ufl[p->idx];
+ if (peer->active) {
+ struct ice_dpll_pin *target;
+ enum ice_dpll_pin_type type;
+
+ if (peer->output) {
+ target = peer->output;
+ type = ICE_DPLL_PIN_TYPE_OUTPUT;
+ } else {
+ target = peer->input;
+ type = ICE_DPLL_PIN_TYPE_INPUT;
+ }
+ ret = ice_dpll_pin_enable(&p->pf->hw, target,
+ d->eec.dpll_idx, type, extack);
+ if (!ret)
+ ret = ice_dpll_pin_state_update(p->pf, target,
+ type, extack);
+ }
return ret;
}
@@ -1253,6 +1284,14 @@ ice_dpll_ufl_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
data &= ~ICE_SMA1_MASK;
enable = true;
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
+ /* Skip if U.FL1 is not active, setting TX_EN
+ * while DIR_EN is set would also deactivate
+ * the paired SMA1 output.
+ */
+ if (data & (ICE_SMA1_DIR_EN | ICE_SMA1_TX_EN)) {
+ ret = 0;
+ goto unlock;
+ }
data |= ICE_SMA1_TX_EN;
enable = false;
} else {
@@ -1267,6 +1306,15 @@ ice_dpll_ufl_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
data &= ~ICE_SMA2_UFL2_RX_DIS;
enable = true;
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
+ /* Skip if U.FL2 is not active, setting
+ * UFL2_RX_DIS could also disable the paired
+ * SMA2 input.
+ */
+ if (!(data & ICE_SMA2_DIR_EN) ||
+ (data & ICE_SMA2_UFL2_RX_DIS)) {
+ ret = 0;
+ goto unlock;
+ }
data |= ICE_SMA2_UFL2_RX_DIS;
enable = false;
} else {
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 09/11] dpll: export __dpll_pin_change_ntf() for use under dpll_lock
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Ivan Vecera, Vadim Fedorenko, Petr Oros,
Alexander Nowlin, Arkadiusz Kubalewski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Ivan Vecera <ivecera@redhat.com>
Export __dpll_pin_change_ntf() so that drivers can send pin change
notifications from within pin callbacks, which are already called
under dpll_lock. Using dpll_pin_change_ntf() in that context would
deadlock.
Add lockdep_assert_held() to catch misuse without the lock held.
Acked-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Signed-off-by: Petr Oros <poros@redhat.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/dpll/dpll_netlink.h | 2 --
include/linux/dpll.h | 1 +
drivers/dpll/dpll_netlink.c | 10 ++++++++++
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/dpll/dpll_netlink.h b/drivers/dpll/dpll_netlink.h
index dd28b56d27c5..a9cfd55f57fc 100644
--- a/drivers/dpll/dpll_netlink.h
+++ b/drivers/dpll/dpll_netlink.h
@@ -11,5 +11,3 @@ int dpll_device_delete_ntf(struct dpll_device *dpll);
int dpll_pin_create_ntf(struct dpll_pin *pin);
int dpll_pin_delete_ntf(struct dpll_pin *pin);
-
-int __dpll_pin_change_ntf(struct dpll_pin *pin);
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index b7277a8b484d..f8037f1ab20b 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -286,6 +286,7 @@ int dpll_pin_ref_sync_pair_add(struct dpll_pin *pin,
int dpll_device_change_ntf(struct dpll_device *dpll);
+int __dpll_pin_change_ntf(struct dpll_pin *pin);
int dpll_pin_change_ntf(struct dpll_pin *pin);
int register_dpll_notifier(struct notifier_block *nb);
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index af7ce62ec55c..0ff1658c2dc1 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -900,11 +900,21 @@ int dpll_pin_delete_ntf(struct dpll_pin *pin)
return dpll_pin_event_send(DPLL_CMD_PIN_DELETE_NTF, pin);
}
+/**
+ * __dpll_pin_change_ntf - notify that the pin has been changed
+ * @pin: registered pin pointer
+ *
+ * Context: caller must hold dpll_lock. Suitable for use inside pin
+ * callbacks which are already invoked under dpll_lock.
+ * Return: 0 if succeeds, error code otherwise.
+ */
int __dpll_pin_change_ntf(struct dpll_pin *pin)
{
+ lockdep_assert_held(&dpll_lock);
dpll_pin_notify(pin, DPLL_PIN_CHANGED);
return dpll_pin_event_send(DPLL_CMD_PIN_CHANGE_NTF, pin);
}
+EXPORT_SYMBOL_GPL(__dpll_pin_change_ntf);
/**
* dpll_pin_change_ntf - notify that the pin has been changed
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 11/11] ice: add dpll peer notification for paired SMA and U.FL pins
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Alexander Nowlin,
Arkadiusz Kubalewski
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
SMA and U.FL pins share physical signal paths in pairs (SMA1/U.FL1 and
SMA2/U.FL2). When one pin's state changes via a PCA9575 GPIO write,
the paired pin's state also changes, but no notification is sent for
the peer pin. Userspace consumers monitoring the peer via dpll netlink
subscribe never learn about the update.
Add ice_dpll_sw_pin_notify_peer() which sends a change notification for
the paired SW pin. Call it from ice_dpll_pin_sma_direction_set(),
ice_dpll_sma_pin_state_set(), and ice_dpll_ufl_pin_state_set() after
pf->dplls.lock is released. Use __dpll_pin_change_ntf() because
dpll_lock is still held by the dpll netlink layer (dpll_pin_pre_doit).
Fixes: 2dd5d03c77e2 ("ice: redesign dpll sma/u.fl pins control")
Signed-off-by: Petr Oros <poros@redhat.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 32 +++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 721a3f4d6a28..27b460926bac 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -1154,6 +1154,32 @@ ice_dpll_input_state_get(const struct dpll_pin *pin, void *pin_priv,
extack, ICE_DPLL_PIN_TYPE_INPUT);
}
+/**
+ * ice_dpll_sw_pin_notify_peer - notify the paired SW pin after a state change
+ * @d: pointer to dplls struct
+ * @changed: the SW pin that was explicitly changed (already notified by dpll core)
+ *
+ * SMA and U.FL pins share physical signal paths in pairs (SMA1/U.FL1 and
+ * SMA2/U.FL2). When one pin's routing changes via the PCA9575 GPIO
+ * expander, the paired pin's state may also change. Send a change
+ * notification for the peer pin so userspace consumers monitoring the
+ * peer via dpll netlink learn about the update.
+ *
+ * Context: Called from dpll_pin_ops callbacks after pf->dplls.lock is
+ * released. Uses __dpll_pin_change_ntf() because dpll_lock is
+ * still held by the dpll netlink layer.
+ */
+static void ice_dpll_sw_pin_notify_peer(struct ice_dplls *d,
+ struct ice_dpll_pin *changed)
+{
+ struct ice_dpll_pin *peer;
+
+ peer = (changed >= d->sma && changed < d->sma + ICE_DPLL_PIN_SW_NUM) ?
+ &d->ufl[changed->idx] : &d->sma[changed->idx];
+ if (peer->pin)
+ __dpll_pin_change_ntf(peer->pin);
+}
+
/**
* ice_dpll_sma_direction_set - set direction of SMA pin
* @p: pointer to a pin
@@ -1344,6 +1370,8 @@ ice_dpll_ufl_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
unlock:
mutex_unlock(&pf->dplls.lock);
+ if (!ret)
+ ice_dpll_sw_pin_notify_peer(&pf->dplls, p);
return ret;
}
@@ -1462,6 +1490,8 @@ ice_dpll_sma_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
unlock:
mutex_unlock(&pf->dplls.lock);
+ if (!ret)
+ ice_dpll_sw_pin_notify_peer(&pf->dplls, sma);
return ret;
}
@@ -1657,6 +1687,8 @@ ice_dpll_pin_sma_direction_set(const struct dpll_pin *pin, void *pin_priv,
mutex_lock(&pf->dplls.lock);
ret = ice_dpll_sma_direction_set(p, direction, extack);
mutex_unlock(&pf->dplls.lock);
+ if (!ret)
+ ice_dpll_sw_pin_notify_peer(&pf->dplls, p);
return ret;
}
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH net 10/11] ice: fix missing dpll notifications for SW pins
From: Jacob Keller @ 2026-04-28 5:22 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Aleksandr Loktionov,
Simon Horman, Jiri Pirko
Cc: netdev, Jacob Keller, Petr Oros, Alexander Nowlin,
Arkadiusz Kubalewski, Ivan Vecera
In-Reply-To: <20260427-jk-iwl-net-petr-oros-fixes-v1-0-cdcb48303fd8@intel.com>
From: Petr Oros <poros@redhat.com>
The SMA/U.FL pin redesign (commit 2dd5d03c77e2 ("ice: redesign dpll
sma/u.fl pins control")) introduced software-controlled pins that wrap
backing CGU input/output pins, but never updated the notification and
data paths to propagate pin events to these SW wrappers.
The periodic work sends dpll_pin_change_ntf() only for direct CGU input
pins. SW pins that wrap these inputs never receive change or phase
offset notifications, so userspace consumers such as synce4l monitoring
SMA pins via dpll netlink never learn about state transitions or phase
offset updates. Similarly, ice_dpll_phase_offset_get() reads the SW
pin's own phase_offset field which is never updated; the PPS monitor
writes to the backing CGU input's field instead.
Fix by introducing ice_dpll_pin_ntf(), a wrapper around
dpll_pin_change_ntf() that also notifies any registered SMA/U.FL pin
whose backing CGU input matches. Replace all direct
dpll_pin_change_ntf() calls in the periodic notification paths with
this wrapper. Fix ice_dpll_phase_offset_get() to return the backing
CGU input's phase_offset for input-direction SW pins.
Fixes: 2dd5d03c77e2 ("ice: redesign dpll sma/u.fl pins control")
Signed-off-by: Petr Oros <poros@redhat.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Ivan Vecera <ivecera@redhat.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 47 +++++++++++++++++++++++--------
1 file changed, 36 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 3f8cd5b8298b..721a3f4d6a28 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -1963,7 +1963,10 @@ ice_dpll_phase_offset_get(const struct dpll_pin *pin, void *pin_priv,
d->active_input == p->input->pin))
*phase_offset = d->phase_offset * ICE_DPLL_PHASE_OFFSET_FACTOR;
else if (d->phase_offset_monitor_period)
- *phase_offset = p->phase_offset * ICE_DPLL_PHASE_OFFSET_FACTOR;
+ *phase_offset = (p->input &&
+ p->direction == DPLL_PIN_DIRECTION_INPUT ?
+ p->input->phase_offset :
+ p->phase_offset) * ICE_DPLL_PHASE_OFFSET_FACTOR;
else
*phase_offset = 0;
mutex_unlock(&pf->dplls.lock);
@@ -2657,6 +2660,27 @@ static u64 ice_generate_clock_id(struct ice_pf *pf)
return pci_get_dsn(pf->pdev);
}
+/**
+ * ice_dpll_pin_ntf - notify pin change including any SW pin wrappers
+ * @dplls: pointer to dplls struct
+ * @pin: the dpll_pin that changed
+ *
+ * Send a change notification for @pin and for any registered SMA/U.FL pin
+ * whose backing CGU input matches @pin.
+ */
+static void ice_dpll_pin_ntf(struct ice_dplls *dplls, struct dpll_pin *pin)
+{
+ dpll_pin_change_ntf(pin);
+ for (int i = 0; i < ICE_DPLL_PIN_SW_NUM; i++) {
+ if (dplls->sma[i].pin && dplls->sma[i].input &&
+ dplls->sma[i].input->pin == pin)
+ dpll_pin_change_ntf(dplls->sma[i].pin);
+ if (dplls->ufl[i].pin && dplls->ufl[i].input &&
+ dplls->ufl[i].input->pin == pin)
+ dpll_pin_change_ntf(dplls->ufl[i].pin);
+ }
+}
+
/**
* ice_dpll_notify_changes - notify dpll subsystem about changes
* @d: pointer do dpll
@@ -2665,6 +2689,7 @@ static u64 ice_generate_clock_id(struct ice_pf *pf)
*/
static void ice_dpll_notify_changes(struct ice_dpll *d)
{
+ struct ice_dplls *dplls = &d->pf->dplls;
bool pin_notified = false;
if (d->prev_dpll_state != d->dpll_state) {
@@ -2673,17 +2698,17 @@ static void ice_dpll_notify_changes(struct ice_dpll *d)
}
if (d->prev_input != d->active_input) {
if (d->prev_input)
- dpll_pin_change_ntf(d->prev_input);
+ ice_dpll_pin_ntf(dplls, d->prev_input);
d->prev_input = d->active_input;
if (d->active_input) {
- dpll_pin_change_ntf(d->active_input);
+ ice_dpll_pin_ntf(dplls, d->active_input);
pin_notified = true;
}
}
if (d->prev_phase_offset != d->phase_offset) {
d->prev_phase_offset = d->phase_offset;
if (!pin_notified && d->active_input)
- dpll_pin_change_ntf(d->active_input);
+ ice_dpll_pin_ntf(dplls, d->active_input);
}
}
@@ -2712,6 +2737,7 @@ static bool ice_dpll_is_pps_phase_monitor(struct ice_pf *pf)
/**
* ice_dpll_pins_notify_mask - notify dpll subsystem about bulk pin changes
+ * @dplls: pointer to dplls struct
* @pins: array of ice_dpll_pin pointers registered within dpll subsystem
* @pin_num: number of pins
* @phase_offset_ntf_mask: bitmask of pin indexes to notify
@@ -2721,15 +2747,14 @@ static bool ice_dpll_is_pps_phase_monitor(struct ice_pf *pf)
*
* Context: Must be called while pf->dplls.lock is released.
*/
-static void ice_dpll_pins_notify_mask(struct ice_dpll_pin *pins,
+static void ice_dpll_pins_notify_mask(struct ice_dplls *dplls,
+ struct ice_dpll_pin *pins,
u8 pin_num,
u32 phase_offset_ntf_mask)
{
- int i = 0;
-
- for (i = 0; i < pin_num; i++)
- if (phase_offset_ntf_mask & (1 << i))
- dpll_pin_change_ntf(pins[i].pin);
+ for (int i = 0; i < pin_num; i++)
+ if (phase_offset_ntf_mask & BIT(i))
+ ice_dpll_pin_ntf(dplls, pins[i].pin);
}
/**
@@ -2905,7 +2930,7 @@ static void ice_dpll_periodic_work(struct kthread_work *work)
ice_dpll_notify_changes(de);
ice_dpll_notify_changes(dp);
if (phase_offset_ntf)
- ice_dpll_pins_notify_mask(d->inputs, d->num_inputs,
+ ice_dpll_pins_notify_mask(d, d->inputs, d->num_inputs,
phase_offset_ntf);
resched:
--
2.54.0.rc2.531.gaf818d63126a
^ permalink raw reply related
* [PATCH] net: datagram: Drain queue before reporting EOF or ENOTCONN
From: Petr Malat @ 2026-04-28 5:23 UTC (permalink / raw)
To: netdev, linux-kernel; +Cc: davem, kuba, ebiggers, Petr Malat
If a packet is queued and RCV_SHUTDOWN flag is set after the function
__skb_wait_for_more_packets() checked the queue, the function returns
EOF, which is then propagated by __unix_dgram_recvmsg() and the user
reads EOF although there is a message or messages still pending.
The function should check if the queue is empty before returning EOF.
As the same is true for disconnect and it's also reasonable for a pending
signal, check in a common place before returning from the function.
Signed-off-by: Petr Malat <oss@malat.biz>
---
net/core/datagram.c | 38 +++++++++++++++++++++-----------------
1 file changed, 21 insertions(+), 17 deletions(-)
diff --git a/net/core/datagram.c b/net/core/datagram.c
index c285c6465923..5952950f7233 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -98,40 +98,44 @@ int __skb_wait_for_more_packets(struct sock *sk,
struct sk_buff_head *queue,
/* Socket errors? */
error = sock_error(sk);
if (error)
- goto out_err;
+ goto out;
if (READ_ONCE(queue->prev) != skb)
goto out;
/* Socket shut down? */
- if (sk->sk_shutdown & RCV_SHUTDOWN)
- goto out_noerr;
+ if (sk->sk_shutdown & RCV_SHUTDOWN) {
+ error = 1;
+ goto check_queue;
+ }
/* Sequenced packets can come disconnected.
* If so we report the problem
*/
- error = -ENOTCONN;
if (connection_based(sk) &&
- !(sk->sk_state == TCP_ESTABLISHED || sk->sk_state == TCP_LISTEN))
- goto out_err;
+ !(sk->sk_state == TCP_ESTABLISHED || sk->sk_state == TCP_LISTEN)) {
+ error = -ENOTCONN;
+ goto check_queue;
+ }
/* handle signals */
- if (signal_pending(current))
- goto interrupted;
+ if (signal_pending(current)) {
+ error = sock_intr_errno(*timeo_p);
+ goto check_queue;
+ }
- error = 0;
*timeo_p = schedule_timeout(*timeo_p);
out:
+ *err = error < 0 ? error : 0;
finish_wait(sk_sleep(sk), &wait);
return error;
-interrupted:
- error = sock_intr_errno(*timeo_p);
-out_err:
- *err = error;
- goto out;
-out_noerr:
- *err = 0;
- error = 1;
+check_queue:
+ /* A packet may have arrived between the initial queue check and any
+ * of the early-exit conditions above. Return 0 to let the caller
+ * drain the queue before acting on the shutdown / disconnect / signal.
+ */
+ if (READ_ONCE(queue->prev) != skb)
+ error = 0;
goto out;
}
EXPORT_SYMBOL(__skb_wait_for_more_packets);
--
2.47.3
^ permalink raw reply related
* [PATCH net-next] net: airoha: Rename get_src_port_id callback in get_sport
From: Lorenzo Bianconi @ 2026-04-28 5:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Simon Horman, linux-arm-kernel, linux-mediatek, netdev,
Lorenzo Bianconi
For code consistency, rename get_src_port_id callback in get_sport.
Please note this patch does not introduce any logical change and it is
just a cosmetic patch.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 10 +++++-----
drivers/net/ethernet/airoha/airoha_eth.h | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 2bb0a3ff9810..56441b99448a 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1813,7 +1813,7 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
- src_port = eth->soc->ops.get_src_port_id(port, port->nbq);
+ src_port = eth->soc->ops.get_sport(port, port->nbq);
if (src_port < 0)
return src_port;
@@ -3187,7 +3187,7 @@ static const char * const en7581_xsi_rsts_names[] = {
"xfp-mac",
};
-static int airoha_en7581_get_src_port_id(struct airoha_gdm_port *port, int nbq)
+static int airoha_en7581_get_sport(struct airoha_gdm_port *port, int nbq)
{
switch (port->id) {
case AIROHA_GDM3_IDX:
@@ -3240,7 +3240,7 @@ static const char * const an7583_xsi_rsts_names[] = {
"xfp-mac",
};
-static int airoha_an7583_get_src_port_id(struct airoha_gdm_port *port, int nbq)
+static int airoha_an7583_get_sport(struct airoha_gdm_port *port, int nbq)
{
switch (port->id) {
case AIROHA_GDM3_IDX:
@@ -3288,7 +3288,7 @@ static const struct airoha_eth_soc_data en7581_soc_data = {
.num_xsi_rsts = ARRAY_SIZE(en7581_xsi_rsts_names),
.num_ppe = 2,
.ops = {
- .get_src_port_id = airoha_en7581_get_src_port_id,
+ .get_sport = airoha_en7581_get_sport,
.get_vip_port = airoha_en7581_get_vip_port,
},
};
@@ -3299,7 +3299,7 @@ static const struct airoha_eth_soc_data an7583_soc_data = {
.num_xsi_rsts = ARRAY_SIZE(an7583_xsi_rsts_names),
.num_ppe = 1,
.ops = {
- .get_src_port_id = airoha_an7583_get_src_port_id,
+ .get_sport = airoha_an7583_get_sport,
.get_vip_port = airoha_an7583_get_vip_port,
},
};
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index e389d2fe3b86..1a052b898816 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -578,7 +578,7 @@ struct airoha_eth_soc_data {
int num_xsi_rsts;
int num_ppe;
struct {
- int (*get_src_port_id)(struct airoha_gdm_port *port, int nbq);
+ int (*get_sport)(struct airoha_gdm_port *port, int nbq);
u32 (*get_vip_port)(struct airoha_gdm_port *port, int nbq);
} ops;
};
---
base-commit: 790ead9394860e7d70c5e0e50a35b243e909a618
change-id: 20260428-airoha-get_src_port_id-callback-6526e82ba957
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* [PATCH net-next 0/3] net/mlx5: enable sub-page allocations for mlx5_frag_buf
From: Tariq Toukan @ 2026-04-28 5:29 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Moshe Shemesh
Hi,
See detailed description by Nimrod below [1].
Regards,
Tariq
[1]
This series aims to improve memory utilization for DMA-coherent
fragmented-buffer allocations on systems with large PAGE_SIZE.
Before this change, such allocations were page-granular, as they were
backed by full pages. On large-page systems this caused significant
internal waste for small objects. For example, a single 4K request
consumed an entire 64K page.
The common kernel solution for sub-page coherent DMA allocations is the
DMA pool API. However, those pools do not return pages to the system
until teardown. That behavior is not a good fit for mlx5_frag_buf
allocations, since they back interface resources (WQs and CQs).
Interfaces may be removed dynamically, so their memory footprint should
reflect live usage to avoid situations where large amounts of memory
remain tied up in pools.
This series introduces a lightweight mlx5-local pool implementation for
sub-page coherent DMA allocations, which immediately returns free
backing pages. It wires mlx5_frag_buf allocations to use these internal
pools, while keeping the mechanism reusable for other mlx5-internal
coherent DMA allocation users in follow-up work.
Nimrod Oren (3):
net/mlx5: wire frag buf pools lifecycle hooks
net/mlx5: add frag buf pools create/destroy paths
net/mlx5: use internal dma pools for frag buf alloc
.../net/ethernet/mellanox/mlx5/core/alloc.c | 328 ++++++++++++++++--
.../net/ethernet/mellanox/mlx5/core/main.c | 7 +
.../ethernet/mellanox/mlx5/core/mlx5_core.h | 2 +
include/linux/mlx5/driver.h | 9 +-
4 files changed, 312 insertions(+), 34 deletions(-)
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.44.0
^ permalink raw reply
* [PATCH net-next 1/3] net/mlx5: wire frag buf pools lifecycle hooks
From: Tariq Toukan @ 2026-04-28 5:29 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Moshe Shemesh, Nimrod Oren
In-Reply-To: <20260428052920.219201-1-tariqt@nvidia.com>
From: Nimrod Oren <noren@nvidia.com>
Wire mlx5_frag_buf pools init/cleanup hooks into
mlx5_mdev_init()/uninit() and the init unwind path.
Keep temporary no-op stubs in alloc.c so lifecycle ordering is in place
before the coherent DMA sub-page allocator implementation is added in
follow-up patches.
Signed-off-by: Nimrod Oren <noren@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
drivers/net/ethernet/mellanox/mlx5/core/alloc.c | 11 +++++++++++
drivers/net/ethernet/mellanox/mlx5/core/main.c | 7 +++++++
drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h | 2 ++
3 files changed, 20 insertions(+)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
index 202feab1558a..cebb3559d2c9 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
@@ -71,6 +71,17 @@ static void *mlx5_dma_zalloc_coherent_node(struct mlx5_core_dev *dev,
return cpu_handle;
}
+/* Implemented later in the series */
+void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev)
+{
+}
+
+/* Implemented later in the series */
+int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev)
+{
+ return 0;
+}
+
int mlx5_frag_buf_alloc_node(struct mlx5_core_dev *dev, int size,
struct mlx5_frag_buf *buf, int node)
{
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c
index 74827e8ca125..b1b9ebfd3866 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c
@@ -1817,6 +1817,10 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx)
priv->dbg.dbg_root = debugfs_create_dir(dev_name(dev->device),
mlx5_debugfs_root);
+ err = mlx5_frag_buf_pools_init(dev);
+ if (err)
+ goto err_frag_buf_pools_init;
+
INIT_LIST_HEAD(&priv->traps);
err = mlx5_cmd_init(dev);
@@ -1878,6 +1882,8 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx)
err_timeout_init:
mlx5_cmd_cleanup(dev);
err_cmd_init:
+ mlx5_frag_buf_pools_cleanup(dev);
+err_frag_buf_pools_init:
debugfs_remove(dev->priv.dbg.dbg_root);
mutex_destroy(&priv->pgdir_mutex);
mutex_destroy(&priv->alloc_mutex);
@@ -1902,6 +1908,7 @@ void mlx5_mdev_uninit(struct mlx5_core_dev *dev)
mlx5_health_cleanup(dev);
mlx5_tout_cleanup(dev);
mlx5_cmd_cleanup(dev);
+ mlx5_frag_buf_pools_cleanup(dev);
debugfs_remove_recursive(dev->priv.dbg.dbg_root);
mutex_destroy(&priv->pgdir_mutex);
mutex_destroy(&priv->alloc_mutex);
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h
index 1507e881d962..87f01c4e8d65 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h
@@ -436,6 +436,8 @@ mlx5_sf_coredev_to_adev(struct mlx5_core_dev *mdev)
int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx);
void mlx5_mdev_uninit(struct mlx5_core_dev *dev);
+int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev);
+void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev);
int mlx5_init_one(struct mlx5_core_dev *dev);
int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev);
void mlx5_uninit_one(struct mlx5_core_dev *dev);
--
2.44.0
^ permalink raw reply related
* [PATCH net-next 2/3] net/mlx5: add frag buf pools create/destroy paths
From: Tariq Toukan @ 2026-04-28 5:29 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Moshe Shemesh, Nimrod Oren
In-Reply-To: <20260428052920.219201-1-tariqt@nvidia.com>
From: Nimrod Oren <noren@nvidia.com>
Introduce mlx5 DMA pool and pool-page data structures, and add the
creation and teardown paths.
Each NUMA node owns a set of mlx5_dma_pool instances, each one with a
different block size. The sizes are defined as all powers of two
starting from MLX5_ADAPTER_PAGE_SHIFT and up to PAGE_SHIFT. Since
mlx5_frag_bufs are used to back objects whose sizes are encoded relative
to MLX5_ADAPTER_PAGE_SHIFT, a smaller block_shift value cannot be used.
Requests larger than PAGE_SIZE continue to be handled as page-sized
fragments, as in the existing frag-buf allocation model.
Signed-off-by: Nimrod Oren <noren@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
.../net/ethernet/mellanox/mlx5/core/alloc.c | 136 +++++++++++++++++-
include/linux/mlx5/driver.h | 7 +-
2 files changed, 140 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
index cebb3559d2c9..918cf027bcbc 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
@@ -37,10 +37,15 @@
#include <linux/bitmap.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
+#include <linux/nodemask.h>
#include <linux/mlx5/driver.h>
#include "mlx5_core.h"
+#define MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT MLX5_ADAPTER_PAGE_SHIFT
+#define MLX5_FRAG_BUF_POOLS_NUM \
+ (PAGE_SHIFT - MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT + 1)
+
struct mlx5_db_pgdir {
struct list_head list;
unsigned long *bitmap;
@@ -48,6 +53,27 @@ struct mlx5_db_pgdir {
dma_addr_t db_dma;
};
+struct mlx5_dma_pool {
+ /* Protects page_list and per-page allocation bitmaps. */
+ struct mutex lock;
+ struct list_head page_list;
+ struct mlx5_core_dev *dev;
+ int node;
+ u8 block_shift;
+};
+
+struct mlx5_dma_pool_page {
+ struct mlx5_dma_pool *pool;
+ struct list_head pool_link;
+ unsigned long *bitmap;
+ void *buf;
+ dma_addr_t dma;
+};
+
+struct mlx5_frag_buf_node_pools {
+ struct mlx5_dma_pool *pools[MLX5_FRAG_BUF_POOLS_NUM];
+};
+
/* Handling for queue buffers -- we allocate a bunch of memory and
* register it in a memory region at HCA virtual address 0.
*/
@@ -72,13 +98,121 @@ static void *mlx5_dma_zalloc_coherent_node(struct mlx5_core_dev *dev,
}
/* Implemented later in the series */
+static void mlx5_dma_pool_page_free(struct mlx5_core_dev *dev,
+ struct mlx5_dma_pool_page *page)
+{
+}
+
+static void mlx5_dma_pool_destroy(struct mlx5_dma_pool *pool)
+{
+ struct list_head *page_list = &pool->page_list;
+ struct mlx5_dma_pool_page *page, *tmp;
+
+ if (WARN(!list_empty(page_list),
+ "mlx5 dma pool destroy with non-empty pool: block_shift=%u\n",
+ pool->block_shift))
+ list_for_each_entry_safe(page, tmp, page_list, pool_link) {
+ list_del(&page->pool_link);
+ mlx5_dma_pool_page_free(pool->dev, page);
+ }
+
+ mutex_destroy(&pool->lock);
+ kfree(pool);
+}
+
+static struct mlx5_dma_pool *mlx5_dma_pool_create(struct mlx5_core_dev *dev,
+ int node, u8 block_shift)
+{
+ struct mlx5_dma_pool *pool;
+
+ if (WARN_ONCE(block_shift > PAGE_SHIFT,
+ "mlx5 dma pool invalid block_shift: %u (max %d)\n",
+ block_shift, PAGE_SHIFT))
+ return NULL;
+
+ pool = kzalloc_obj(*pool);
+ if (!pool)
+ return NULL;
+
+ INIT_LIST_HEAD(&pool->page_list);
+ mutex_init(&pool->lock);
+ pool->dev = dev;
+ pool->node = node;
+ pool->block_shift = block_shift;
+ return pool;
+}
+
+static void
+mlx5_frag_buf_node_pools_destroy(struct mlx5_frag_buf_node_pools *node_pools)
+{
+ for (int i = 0; i < MLX5_FRAG_BUF_POOLS_NUM; i++)
+ if (node_pools->pools[i])
+ mlx5_dma_pool_destroy(node_pools->pools[i]);
+ kfree(node_pools);
+}
+
+static struct mlx5_frag_buf_node_pools *
+mlx5_frag_buf_node_pools_create(struct mlx5_core_dev *dev, int node)
+{
+ struct mlx5_frag_buf_node_pools *node_pools;
+
+ node_pools = kzalloc_obj(*node_pools);
+ if (!node_pools)
+ return NULL;
+
+ for (int i = 0; i < MLX5_FRAG_BUF_POOLS_NUM; i++) {
+ u8 block_shift = MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT + i;
+
+ node_pools->pools[i] = mlx5_dma_pool_create(dev, node,
+ block_shift);
+ if (!node_pools->pools[i]) {
+ mlx5_frag_buf_node_pools_destroy(node_pools);
+ return NULL;
+ }
+ }
+
+ return node_pools;
+}
+
void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev)
{
+ struct mlx5_priv *priv = &dev->priv;
+ int node;
+
+ for_each_node_state(node, N_POSSIBLE) {
+ struct mlx5_frag_buf_node_pools *node_pools;
+
+ node_pools = priv->frag_buf_node_pools[node];
+ if (!node_pools)
+ continue;
+ mlx5_frag_buf_node_pools_destroy(node_pools);
+ }
+
+ kfree(priv->frag_buf_node_pools);
+ priv->frag_buf_node_pools = NULL;
}
-/* Implemented later in the series */
int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev)
{
+ struct mlx5_priv *priv = &dev->priv;
+ int node;
+
+ priv->frag_buf_node_pools = kzalloc_objs(*priv->frag_buf_node_pools,
+ nr_node_ids);
+ if (!priv->frag_buf_node_pools)
+ return -ENOMEM;
+
+ for_each_node_state(node, N_POSSIBLE) {
+ struct mlx5_frag_buf_node_pools *node_pools;
+
+ node_pools = mlx5_frag_buf_node_pools_create(dev, node);
+ if (!node_pools) {
+ mlx5_frag_buf_pools_cleanup(dev);
+ return -ENOMEM;
+ }
+ priv->frag_buf_node_pools[node] = node_pools;
+ }
+
return 0;
}
diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h
index 04b96c5abb57..71f7615ab553 100644
--- a/include/linux/mlx5/driver.h
+++ b/include/linux/mlx5/driver.h
@@ -558,6 +558,7 @@ enum mlx5_func_type {
MLX5_FUNC_TYPE_NUM,
};
+struct mlx5_frag_buf_node_pools;
struct mlx5_ft_pool;
struct mlx5_priv {
/* IRQ table valid only for real pci devices PF or VF */
@@ -581,14 +582,16 @@ struct mlx5_priv {
struct mlx5_debugfs_entries dbg;
- /* start: alloc staff */
+ /* start: alloc stuff */
/* protect buffer allocation according to numa node */
struct mutex alloc_mutex;
int numa_node;
struct mutex pgdir_mutex;
struct list_head pgdir_list;
- /* end: alloc staff */
+
+ struct mlx5_frag_buf_node_pools **frag_buf_node_pools;
+ /* end: alloc stuff */
struct mlx5_adev **adev;
int adev_idx;
--
2.44.0
^ permalink raw reply related
* [PATCH net-next 3/3] net/mlx5: use internal dma pools for frag buf alloc
From: Tariq Toukan @ 2026-04-28 5:29 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
David S. Miller
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch, netdev,
linux-rdma, linux-kernel, Gal Pressman, Dragos Tatulea,
Moshe Shemesh, Nimrod Oren
In-Reply-To: <20260428052920.219201-1-tariqt@nvidia.com>
From: Nimrod Oren <noren@nvidia.com>
Add mlx5_dma_pool alloc/free paths, and wire mlx5_frag_buf allocation
and free paths to use them.
mlx5_frag_buf_alloc_node() now selects an mlx5_dma_pool to allocate
fragments from, instead of directly allocating full coherent pages.
mlx5_frag_buf_free() frees from the respective pool.
mlx5_dma_pool_alloc() keeps allocation fast by maintaining pages with
available indexes at the head of the list, so the common allocation path
can take a free index immediately. New backing pages are allocated only
when no free index is available.
mlx5_dma_pool_free() returns released indexes to the pool and frees a
backing page once all of its indexes become free. This avoids keeping
fully free pages for the lifetime of the pool and reduces coherent DMA
memory footprint.
Signed-off-by: Nimrod Oren <noren@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
.../net/ethernet/mellanox/mlx5/core/alloc.c | 185 ++++++++++++++----
include/linux/mlx5/driver.h | 2 +
2 files changed, 154 insertions(+), 33 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
index 918cf027bcbc..5cced45caf36 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c
@@ -97,10 +97,44 @@ static void *mlx5_dma_zalloc_coherent_node(struct mlx5_core_dev *dev,
return cpu_handle;
}
-/* Implemented later in the series */
+static struct mlx5_dma_pool_page *
+mlx5_dma_pool_page_alloc(struct mlx5_dma_pool *pool)
+{
+ int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift);
+ struct mlx5_dma_pool_page *page;
+
+ page = kzalloc_obj(*page);
+ if (!page)
+ goto err_out;
+
+ page->pool = pool;
+ page->bitmap = bitmap_zalloc(blocks_per_page, GFP_KERNEL);
+ if (!page->bitmap)
+ goto err_free_page;
+
+ bitmap_fill(page->bitmap, blocks_per_page);
+ page->buf = mlx5_dma_zalloc_coherent_node(pool->dev, PAGE_SIZE,
+ &page->dma, pool->node);
+ if (!page->buf)
+ goto err_free_bitmap;
+
+ return page;
+
+err_free_bitmap:
+ bitmap_free(page->bitmap);
+err_free_page:
+ kfree(page);
+err_out:
+ return NULL;
+}
+
static void mlx5_dma_pool_page_free(struct mlx5_core_dev *dev,
struct mlx5_dma_pool_page *page)
{
+ dma_free_coherent(mlx5_core_dma_dev(dev), PAGE_SIZE, page->buf,
+ page->dma);
+ bitmap_free(page->bitmap);
+ kfree(page);
}
static void mlx5_dma_pool_destroy(struct mlx5_dma_pool *pool)
@@ -142,6 +176,83 @@ static struct mlx5_dma_pool *mlx5_dma_pool_create(struct mlx5_core_dev *dev,
return pool;
}
+static int mlx5_dma_pool_alloc_from_page(struct mlx5_dma_pool *pool,
+ struct mlx5_dma_pool_page *page,
+ unsigned long *idx_out)
+{
+ int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift);
+
+ *idx_out = find_first_bit(page->bitmap, blocks_per_page);
+ if (*idx_out >= blocks_per_page)
+ return -ENOMEM;
+
+ __clear_bit(*idx_out, page->bitmap);
+
+ if (bitmap_empty(page->bitmap, blocks_per_page))
+ list_move_tail(&page->pool_link, &pool->page_list);
+
+ return 0;
+}
+
+static struct mlx5_dma_pool_page *
+mlx5_dma_pool_alloc(struct mlx5_dma_pool *pool, unsigned long *idx_out)
+{
+ struct mlx5_dma_pool_page *page;
+
+ mutex_lock(&pool->lock);
+
+ page = list_first_entry_or_null(&pool->page_list,
+ struct mlx5_dma_pool_page, pool_link);
+ if (page && !mlx5_dma_pool_alloc_from_page(pool, page, idx_out))
+ goto unlock; /* successfully allocated from existing page */
+
+ page = mlx5_dma_pool_page_alloc(pool);
+ if (!page)
+ goto unlock;
+
+ list_add(&page->pool_link, &pool->page_list);
+ mlx5_dma_pool_alloc_from_page(pool, page, idx_out);
+
+unlock:
+ mutex_unlock(&pool->lock);
+ return page;
+}
+
+static void mlx5_dma_pool_free(struct mlx5_dma_pool *pool,
+ struct mlx5_dma_pool_page *page,
+ unsigned long idx)
+{
+ int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift);
+ bool was_full;
+
+ if (WARN_ONCE(idx >= blocks_per_page,
+ "mlx5 dma pool invalid idx: %lu (max %d)\n",
+ idx, blocks_per_page - 1))
+ return;
+
+ mutex_lock(&pool->lock);
+ if (WARN_ONCE(test_bit(idx, page->bitmap),
+ "mlx5 dma pool double free: idx=%lu block_shift=%u\n",
+ idx, pool->block_shift))
+ goto unlock;
+
+ was_full = bitmap_empty(page->bitmap, blocks_per_page);
+ __set_bit(idx, page->bitmap);
+
+ if (bitmap_full(page->bitmap, blocks_per_page)) {
+ list_del(&page->pool_link);
+ mlx5_dma_pool_page_free(pool->dev, page);
+ } else {
+ memset((u8 *)page->buf + (idx << pool->block_shift), 0,
+ BIT(pool->block_shift));
+ if (was_full)
+ list_move(&page->pool_link, &pool->page_list);
+ }
+
+unlock:
+ mutex_unlock(&pool->lock);
+}
+
static void
mlx5_frag_buf_node_pools_destroy(struct mlx5_frag_buf_node_pools *node_pools)
{
@@ -219,56 +330,64 @@ int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev)
int mlx5_frag_buf_alloc_node(struct mlx5_core_dev *dev, int size,
struct mlx5_frag_buf *buf, int node)
{
- int i;
+ struct mlx5_dma_pool *pool;
+ int pool_idx;
+
+ if (WARN_ONCE(size <= 0, "mlx5_frag_buf non-positive size: %d\n", size))
+ return -EINVAL;
+
+ node = node == NUMA_NO_NODE ? first_online_node : node;
+
+ if (WARN_ONCE(node < 0 || node >= nr_node_ids || !node_possible(node),
+ "mlx5_frag_buf invalid node ID: %d\n", node))
+ return -EINVAL;
buf->size = size;
buf->npages = DIV_ROUND_UP(size, PAGE_SIZE);
- buf->page_shift = PAGE_SHIFT;
- buf->frags = kzalloc_objs(struct mlx5_buf_list, buf->npages);
+ buf->page_shift = clamp_t(int, order_base_2(size),
+ MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT,
+ PAGE_SHIFT);
+ buf->frags = kcalloc_node(buf->npages, sizeof(*buf->frags),
+ GFP_KERNEL, node);
if (!buf->frags)
- goto err_out;
+ return -ENOMEM;
- for (i = 0; i < buf->npages; i++) {
+ pool_idx = buf->page_shift - MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT;
+ pool = dev->priv.frag_buf_node_pools[node]->pools[pool_idx];
+ for (int i = 0; i < buf->npages; i++) {
struct mlx5_buf_list *frag = &buf->frags[i];
- int frag_sz = min_t(int, size, PAGE_SIZE);
+ struct mlx5_dma_pool_page *page;
+ unsigned long idx;
- frag->buf = mlx5_dma_zalloc_coherent_node(dev, frag_sz,
- &frag->map, node);
- if (!frag->buf)
- goto err_free_buf;
- if (frag->map & ((1 << buf->page_shift) - 1)) {
- dma_free_coherent(mlx5_core_dma_dev(dev), frag_sz,
- buf->frags[i].buf, buf->frags[i].map);
- mlx5_core_warn(dev, "unexpected map alignment: %pad, page_shift=%d\n",
- &frag->map, buf->page_shift);
- goto err_free_buf;
+ page = mlx5_dma_pool_alloc(pool, &idx);
+ if (!page) {
+ mlx5_frag_buf_free(dev, buf);
+ return -ENOMEM;
}
- size -= frag_sz;
+ frag->buf = (u8 *)page->buf + (idx << pool->block_shift);
+ frag->map = page->dma + (idx << pool->block_shift);
+ frag->frag_page = page;
}
return 0;
-
-err_free_buf:
- while (i--)
- dma_free_coherent(mlx5_core_dma_dev(dev), PAGE_SIZE, buf->frags[i].buf,
- buf->frags[i].map);
- kfree(buf->frags);
-err_out:
- return -ENOMEM;
}
EXPORT_SYMBOL_GPL(mlx5_frag_buf_alloc_node);
void mlx5_frag_buf_free(struct mlx5_core_dev *dev, struct mlx5_frag_buf *buf)
{
- int size = buf->size;
- int i;
+ for (int i = 0; i < buf->npages; i++) {
+ struct mlx5_buf_list *frag = &buf->frags[i];
+ struct mlx5_dma_pool_page *page;
+ struct mlx5_dma_pool *pool;
+ unsigned long idx;
- for (i = 0; i < buf->npages; i++) {
- int frag_sz = min_t(int, size, PAGE_SIZE);
+ if (!frag->buf)
+ continue;
- dma_free_coherent(mlx5_core_dma_dev(dev), frag_sz, buf->frags[i].buf,
- buf->frags[i].map);
- size -= frag_sz;
+ page = frag->frag_page;
+ pool = page->pool;
+ idx = (frag->map - page->dma) >> pool->block_shift;
+ mlx5_dma_pool_free(pool, page, idx);
}
kfree(buf->frags);
}
diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h
index 71f7615ab553..531ce66fc8ef 100644
--- a/include/linux/mlx5/driver.h
+++ b/include/linux/mlx5/driver.h
@@ -343,9 +343,11 @@ struct mlx5_cmd_mailbox {
struct mlx5_cmd_mailbox *next;
};
+struct mlx5_dma_pool_page;
struct mlx5_buf_list {
void *buf;
dma_addr_t map;
+ struct mlx5_dma_pool_page *frag_page;
};
struct mlx5_frag_buf {
--
2.44.0
^ permalink raw reply related
* Re: [PATCH net] bareudp: fix NULL pointer dereference in bareudp_fill_metadata_dst()
From: Kuniyuki Iwashima @ 2026-04-28 5:33 UTC (permalink / raw)
To: bestswngs
Cc: andrew+netdev, davem, edumazet, kuba, martin.varghese, netdev,
pabeni, willemb, xmei5, Kuniyuki Iwashima
In-Reply-To: <20260426165350.1663137-2-bestswngs@gmail.com>
From: Weiming Shi <bestswngs@gmail.com>
Date: Sun, 26 Apr 2026 09:53:51 -0700
> bareudp_fill_metadata_dst() passes bareudp->sock to
> udp_tunnel6_dst_lookup() in the IPv6 path without a NULL check.
> The socket is only created in bareudp_open() and NULLed in
> bareudp_stop(), so calling this function while the device is down
> triggers a NULL dereference via sock->sk.
>
> BUG: kernel NULL pointer dereference, address: 0000000000000018
> RIP: 0010:udp_tunnel6_dst_lookup (net/ipv6/ip6_udp_tunnel.c:160)
> Call Trace:
> <TASK>
> bareudp_fill_metadata_dst (drivers/net/bareudp.c:532)
> do_execute_actions (net/openvswitch/actions.c:901)
> ovs_execute_actions (net/openvswitch/actions.c:1589)
> ovs_packet_cmd_execute (net/openvswitch/datapath.c:700)
> genl_family_rcv_msg_doit (net/netlink/genetlink.c:1114)
> genl_rcv_msg (net/netlink/genetlink.c:1209)
> netlink_rcv_skb (net/netlink/af_netlink.c:2550)
> </TASK>
>
> Add a NULL check returning -ESHUTDOWN, consistent with the xmit paths
> in the same driver.
>
> Fixes: 571912c69f0e ("net: UDP tunnel encapsulation module for tunnelling different protocols like MPLS, IP, NSH etc.")
> Reported-by: Xiang Mei <xmei5@asu.edu>
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox