git: 2e3b3ce88149 - main - mlx5_ib: do not consume CMD/PAGE_REQUEST events in the DEVX notifier
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 07 Jul 2026 11:30:00 UTC
The branch main has been updated by kib:
URL: https://cgit.FreeBSD.org/src/commit/?id=2e3b3ce881490adcef17905450d0402030975051
commit 2e3b3ce881490adcef17905450d0402030975051
Author: Ariel Ehrenberg <aehrenberg@nvidia.com>
AuthorDate: 2026-06-09 11:20:08 +0000
Commit: Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-07-07 11:24:21 +0000
mlx5_ib: do not consume CMD/PAGE_REQUEST events in the DEVX notifier
DEVX event notifier returned true for the command-completion and
page-request events. This is causing mlx5_eq_int() to skip the core EQ
handler, so the firmware command interface and the page supply stop
being serviced and the device wedges.
This commit also make notifier registration and dispatch safe against
the EQ interrupt running concurrently: publish the table pointer before
the callback and load it with acquire semantics. run the callback under
RCU, and drain it with synchronize_rcu() on teardown. Otherwise the
interrupt handler could observe a half-initialized notifier or race with
cleanup.
Reviewed by: kib
Tested by: Wafa Hamzah <wafah@nvidia.com>
Sponsored by: Nvidia networking
MFC after: 1 month
---
sys/dev/mlx5/mlx5_core/mlx5_eq.c | 33 ++++++++++++++++++++++++++++++---
sys/dev/mlx5/mlx5_ib/mlx5_ib_devx.c | 35 ++++++++++++++++++++++++++++-------
2 files changed, 58 insertions(+), 10 deletions(-)
diff --git a/sys/dev/mlx5/mlx5_core/mlx5_eq.c b/sys/dev/mlx5/mlx5_core/mlx5_eq.c
index 1090f8638171..f564b88c48b4 100644
--- a/sys/dev/mlx5/mlx5_core/mlx5_eq.c
+++ b/sys/dev/mlx5/mlx5_core/mlx5_eq.c
@@ -238,6 +238,10 @@ static int mlx5_eq_int(struct mlx5_core_dev *dev, struct mlx5_eq *eq)
u8 port;
while ((eqe = next_eqe_sw(eq))) {
+ bool (*devx_cb)(struct mlx5_core_dev *mdev,
+ uint8_t event_type, void *data);
+ bool devx_consumed = false;
+
/*
* Make sure we read EQ entry contents after we've
* checked the ownership bit.
@@ -247,9 +251,32 @@ static int mlx5_eq_int(struct mlx5_core_dev *dev, struct mlx5_eq *eq)
mlx5_core_dbg(eq->dev, "eqn %d, eqe type %s\n",
eq->eqn, eqe_type_str(eqe->type));
- if (dev->priv.eq_table.cb != NULL &&
- dev->priv.eq_table.cb(dev, eqe->type, &eqe->data)) {
- /* FALLTHROUGH */
+ /*
+ * The DEVX event notifier can be (un)registered concurrently.
+ * Fast path: when no notifier is registered (the common case
+ * with DEVX unused) skip RCU entirely - on FreeBSD LinuxKPI
+ * rcu_read_lock() pins the thread and starts an epoch section,
+ * which is not free per EQE. If a notifier appears registered,
+ * re-load the callback under rcu_read_lock() before calling it.
+ * The acquire load pairs with the store-release in
+ * mlx5_ib_devx_init_event_table() so eq_table.dev is visible
+ * once cb is; the re-check under RCU pairs with synchronize_rcu()
+ * in mlx5_ib_devx_cleanup_event_table() so the callback only
+ * ever runs inside an RCU section and none is in flight when the
+ * notifier (and its event table) is torn down.
+ */
+ devx_cb = smp_load_acquire(&dev->priv.eq_table.cb);
+ if (devx_cb != NULL) {
+ rcu_read_lock();
+ devx_cb = smp_load_acquire(&dev->priv.eq_table.cb);
+ if (devx_cb != NULL)
+ devx_consumed =
+ devx_cb(dev, eqe->type, &eqe->data);
+ rcu_read_unlock();
+ }
+
+ if (devx_consumed) {
+ /* event consumed by the DEVX notifier */
} else switch (eqe->type) {
case MLX5_EVENT_TYPE_COMP:
mlx5_cq_completion(dev, eqe);
diff --git a/sys/dev/mlx5/mlx5_ib/mlx5_ib_devx.c b/sys/dev/mlx5/mlx5_ib/mlx5_ib_devx.c
index 835a7fd8cfa4..6522a0aa6daf 100644
--- a/sys/dev/mlx5/mlx5_ib/mlx5_ib_devx.c
+++ b/sys/dev/mlx5/mlx5_ib/mlx5_ib_devx.c
@@ -2357,12 +2357,21 @@ static bool mlx5_devx_event_notifier(struct mlx5_core_dev *mdev,
bool is_unaffiliated;
u32 obj_id;
- /* Explicit filtering to kernel events which may occur frequently */
+ /*
+ * Command completions and page requests must be processed by the
+ * mlx5_core default EQ handler. Returning true here tells
+ * mlx5_eq_int() the event was consumed and skips core processing,
+ * which stalls the firmware command interface and page supply and
+ * wedges the device. Return false so the core handler runs for
+ * these frequent kernel events.
+ */
if (event_type == MLX5_EVENT_TYPE_CMD ||
event_type == MLX5_EVENT_TYPE_PAGE_REQUEST)
- return true;
+ return false;
- dev = mdev->priv.eq_table.dev;
+ dev = READ_ONCE(mdev->priv.eq_table.dev);
+ if (dev == NULL)
+ return false;
table = &dev->devx_event_table;
is_unaffiliated = is_unaffiliated_event(dev->mdev, event_type);
@@ -2401,8 +2410,14 @@ void mlx5_ib_devx_init_event_table(struct mlx5_ib_dev *dev)
xa_init_flags(&table->event_xa, 0);
mutex_init(&table->event_xa_lock);
- dev->mdev->priv.eq_table.dev = dev;
- dev->mdev->priv.eq_table.cb = mlx5_devx_event_notifier;
+ /*
+ * Publish dev before cb. The EQ interrupt handler loads cb with
+ * acquire semantics and the notifier then dereferences eq_table.dev,
+ * so dev must be visible to that handler once cb is observed.
+ */
+ WRITE_ONCE(dev->mdev->priv.eq_table.dev, dev);
+ smp_store_release(&dev->mdev->priv.eq_table.cb,
+ mlx5_devx_event_notifier);
}
void mlx5_ib_devx_cleanup_event_table(struct mlx5_ib_dev *dev)
@@ -2413,8 +2428,14 @@ void mlx5_ib_devx_cleanup_event_table(struct mlx5_ib_dev *dev)
void *entry;
unsigned long id;
- dev->mdev->priv.eq_table.cb = NULL;
- dev->mdev->priv.eq_table.dev = NULL;
+ /*
+ * Stop new dispatch by clearing cb, then wait for any in-flight EQ
+ * callback to finish its RCU read section before clearing dev and
+ * tearing down the event table.
+ */
+ WRITE_ONCE(dev->mdev->priv.eq_table.cb, NULL);
+ synchronize_rcu();
+ WRITE_ONCE(dev->mdev->priv.eq_table.dev, NULL);
mutex_lock(&dev->devx_event_table.event_xa_lock);
xa_for_each(&table->event_xa, id, entry) {
event = entry;