mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v5] net: mana: fix reset work race with device removal
@ 2026-09-27 10:52 Fan Wu
  2026-10-02  5:32 ` netdev-bot+sashiko
  0 siblings, 1 reply; 2+ messages in thread
From: Fan Wu @ 2026-09-27 10:52 UTC (permalink / raw)
  To: netdev
  Cc: kys, haiyangz, wei.liu, decui, longli, andrew+netdev, davem,
	edumazet, kuba, pabeni, linux-hyperv, linux-kernel, stable,
	Fan Wu, Song Li

The reset service work is queued on the system workqueue and can
outlive mana_gd_remove(), which frees the GDMA context. It may then
dereference gc through the stale service work.

Embed the service work in gdma_context and guard its lifecycle with a
new serv_lock (hard IRQ safe, never held across a sleep) plus a
waitqueue. Removal, shutdown and the probe unwind close admission and
wait for an in-flight cycle to retire before clearing drvdata and
freeing gc. Service exits retire before taking the PCI rescan/remove
lock, avoiding a lock-cycle with remove.

Do not admit service work while probe is still constructing or
unwinding the device. Latch reset events seen during probe under the
same lock and let the probe rollback/recovery path handle them; an
event racing probe completion is admitted rather than lost. A service
exit that bails out of a rescan without removing the device (no parent
bus) retires without closing admission, so service is never
permanently disabled on a bound device.

The service work stays on the system workqueue because a reset cycle
destroys and re-creates gc->service_wq.

This issue was found by an in-house static analysis tool.

Fixes: fbe346ce9d62 ("net: mana: Handle Reset Request from MANA NIC")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6
Co-developed-by: Song Li <songl@zju.edu.cn>
Signed-off-by: Song Li <songl@zju.edu.cn>
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
Changes since v4 (<20260922070728.309280-1-fanwu01@zju.edu.cn>,
https://lore.kernel.org/netdev/20260922070728.309280-1-fanwu01@zju.edu.cn),
in reply to the sashiko review relayed by Simon Horman:

- [Medium] mana_gf_stats_work_handler(): the gate could drop the
  reset request when mana_rdma_probe() outlives the 2 s stats
  period. The gated branch now re-arms the delayed work instead, so
  the request is retried once the probe completes; a failing probe
  still cancels the work in its unwind.

- [Low] mana_schedule_serv_work() uses schedule_work() again,
  keeping the pre-patch system_percpu_wq placement instead of the
  deprecated system_wq compatibility queue.

- [Low] The service_quiesce label comment now matches the gate: only
  the rollback path can have admitted a cycle, since it runs
  mana_service_probe_complete() before jumping there.

The pre-existing [High] on mana_gd_suspend()/mana_gd_resume() is
left out: the PM path cannot reuse the one-way quiesce and needs its
own reversible protocol.

Changes since v3 (<20260909042529.652301-1-fanwu01@zju.edu.cn>,
https://lore.kernel.org/netdev/20260909042529.652301-1-fanwu01@zju.edu.cn):

- Replaced the flag protocol with a gc-embedded spinlock and waitqueue
  (Jakub Kicinski): admission, retirement and the probe boundary
  handshake now run under serv_lock instead of atomic bit pairings.

- A rescan exit that bails out without removing the device (no parent
  bus) now retires without closing admission, so service is not
  permanently disabled on a bound device.

- mana_gd_shutdown() also drains an in-flight cycle before tearing
  down the same hardware paths as remove().

Changes since v2 (<20260905023602.425827-1-fanwu01@zju.edu.cn>,
https://lore.kernel.org/netdev/20260905023602.425827-1-fanwu01@zju.edu.cn):

- Dropped the now-unused cleanup_mana_rdma and cleanup_mana labels in
  the probe error path (Simon Horman).

Changes since v1 (<20260805143812.220509-1-fanwu01@zju.edu.cn>,
https://lore.kernel.org/netdev/20260805143812.220509-1-fanwu01@zju.edu.cn):

- Dropped the device_lock() serialisation: holding the driver-core
  device lock across mana_gd_suspend() + msleep() + mana_gd_resume()
  blocks unbind, reboot, device PM and all PCI hotplug for up to a
  full reset cycle, and can deadlock against the
  flush_workqueue()/destroy_workqueue() of gc->service_wq.

- The freeing paths close admission and wait for an admitted cycle to
  retire before clearing drvdata and calling vfree(); the failed-resume
  rescan no longer reopens admission, and mana_tx_timeout() also skips
  queue-reset work during removal.

- No service work is admitted before the probe completes; the FPGA
  reconfig exit and the probe-failure recovery path are covered as
  well. The stats-work gate during probe is deliberate: a failing
  probe cannot drain a cycle, and the probe's own -ETIMEDOUT rollback
  plus the recovery rescan service the skipped event.

- Reference series for the HWC lifecycle model:
  https://lore.kernel.org/netdev/20260813174243.3044348-1-longli@microsoft.com

 .../net/ethernet/microsoft/mana/gdma_main.c   | 273 ++++++++++++++----
 drivers/net/ethernet/microsoft/mana/mana_en.c |  20 +-
 include/net/mana/gdma.h                       |  29 +-
 3 files changed, 245 insertions(+), 77 deletions(-)

diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
index 8e9bfc1..4ebe595 100644
--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
+++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
@@ -659,63 +659,149 @@ EXPORT_SYMBOL_NS(mana_gd_ring_dim, "NET_MANA");
 
 #define MANA_SERVICE_PERIOD 10
 
-static void mana_serv_rescan(struct pci_dev *pdev)
+/* Retire one service cycle: serv_waitq is embedded in gc, so the wake
+ * must happen before the lock is dropped - a drainer that observes
+ * idle cannot free gc until the wake has stopped touching gc.
+ */
+static void mana_service_retire(struct gdma_context *gc)
 {
-	struct pci_bus *parent;
+	unsigned long flags;
 
-	pci_lock_rescan_remove();
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	gc->serv_in_flight = false;
+	wake_up(&gc->serv_waitq);
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+}
+
+/* Close admission atomically with the retire itself. */
+static void mana_service_retire_removing(struct gdma_context *gc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	gc->serv_removing = true;
+	gc->serv_in_flight = false;
+	wake_up(&gc->serv_waitq);
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+}
+
+static bool mana_service_idle(struct gdma_context *gc)
+{
+	unsigned long flags;
+	bool idle;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	idle = !gc->serv_in_flight;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+	return idle;
+}
+
+/* Close admission and wait until an in-flight cycle has stopped touching
+ * gc.  The cycle retires before taking the PCI rescan/remove lock, so
+ * this wait completes even when the caller holds that lock; serv_lock is
+ * never held across a sleep.
+ */
+static void mana_service_quiesce(struct gdma_context *gc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	gc->serv_removing = true;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+
+	wait_event(gc->serv_waitq, mana_service_idle(gc));
+}
+
+/* May run in softirq context (tx timeout). */
+bool mana_service_active(struct gdma_context *gc)
+{
+	unsigned long flags;
+	bool active;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	active = gc->serv_in_flight || gc->serv_removing;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+	return active;
+}
+
+bool mana_service_probe_done(struct gdma_context *gc)
+{
+	unsigned long flags;
+	bool done;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	done = gc->serv_probe_done;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+	return done;
+}
+
+static void mana_serv_rescan(struct pci_dev *pdev, struct gdma_context *gc)
+{
+	struct pci_bus *parent;
 
 	parent = pdev->bus;
 	if (!parent) {
 		dev_err(&pdev->dev, "MANA service: no parent bus\n");
-		goto out;
+		/* Still bound: keep admission open for later events. */
+		if (gc)
+			mana_service_retire(gc);
+		return;
 	}
 
+	if (gc)
+		mana_service_retire_removing(gc);
+
+	pci_lock_rescan_remove();
+
 	pci_stop_and_remove_bus_device(pdev);
 	pci_rescan_bus(parent);
 
-out:
 	pci_unlock_rescan_remove();
 }
 
-static void mana_serv_fpga(struct pci_dev *pdev)
+static void mana_serv_fpga(struct pci_dev *pdev, struct gdma_context *gc)
 {
 	struct pci_bus *bus, *parent;
 
-	pci_lock_rescan_remove();
-
 	bus = pdev->bus;
 	if (!bus) {
 		dev_err(&pdev->dev, "MANA service: no bus\n");
-		goto out;
+		if (gc)
+			mana_service_retire(gc);
+		return;
 	}
 
 	parent = bus->parent;
 	if (!parent) {
 		dev_err(&pdev->dev, "MANA service: no parent bus\n");
-		goto out;
+		if (gc)
+			mana_service_retire(gc);
+		return;
 	}
 
+	if (gc)
+		mana_service_retire_removing(gc);
+
+	pci_lock_rescan_remove();
+
 	pci_stop_and_remove_bus_device(bus->self);
 
 	msleep(MANA_SERVICE_PERIOD * 1000);
 
 	pci_rescan_bus(parent);
 
-out:
 	pci_unlock_rescan_remove();
 }
 
-static void mana_serv_reset(struct pci_dev *pdev)
+static void mana_serv_reset(struct pci_dev *pdev, struct gdma_context *gc)
 {
-	struct gdma_context *gc = pci_get_drvdata(pdev);
 	struct hw_channel_context *hwc;
 	int ret;
 
 	if (!gc) {
 		/* Perform PCI rescan on device if GC is not set up */
 		dev_err(&pdev->dev, "MANA service: GC not setup, rescanning\n");
-		mana_serv_rescan(pdev);
+		mana_serv_rescan(pdev, NULL);
 		return;
 	}
 
@@ -738,7 +824,7 @@ static void mana_serv_reset(struct pci_dev *pdev)
 	if (ret == -ETIMEDOUT || ret == -EPROTO) {
 		/* Perform PCI rescan on device if we failed on HWC */
 		dev_err(&pdev->dev, "MANA service: resume failed, rescanning\n");
-		mana_serv_rescan(pdev);
+		mana_serv_rescan(pdev, gc);
 		return;
 	}
 
@@ -748,22 +834,25 @@ static void mana_serv_reset(struct pci_dev *pdev)
 		dev_info(&pdev->dev, "MANA reset cycle completed\n");
 
 out:
-	clear_bit(GC_IN_SERVICE, &gc->flags);
+	mana_service_retire(gc);
 }
 
-static void mana_do_service(enum gdma_eqe_type type, struct pci_dev *pdev)
+static void mana_do_service(enum gdma_eqe_type type, struct pci_dev *pdev,
+			    struct gdma_context *gc)
 {
 	switch (type) {
 	case GDMA_EQE_HWC_FPGA_RECONFIG:
-		mana_serv_fpga(pdev);
+		mana_serv_fpga(pdev, gc);
 		break;
 
 	case GDMA_EQE_HWC_RESET_REQUEST:
-		mana_serv_reset(pdev);
+		mana_serv_reset(pdev, gc);
 		break;
 
 	default:
 		dev_err(&pdev->dev, "MANA service: unknown type %d\n", type);
+		if (gc)
+			mana_service_retire(gc);
 		break;
 	}
 }
@@ -779,12 +868,26 @@ static void mana_recovery_delayed_func(struct work_struct *w)
 	spin_lock_irqsave(&work->lock, flags);
 
 	while (!list_empty(&work->dev_list)) {
+		struct gdma_context *gc;
+
 		dev = list_first_entry(&work->dev_list,
 				       struct mana_dev_recovery, list);
 		list_del(&dev->list);
 		spin_unlock_irqrestore(&work->lock, flags);
 
-		mana_do_service(dev->type, dev->pdev);
+		/* Serialize the drvdata lookup and admission against
+		 * probe/remove.  Do not call sleeping functions while
+		 * holding the device lock.
+		 */
+		device_lock(&dev->pdev->dev);
+		gc = pci_get_drvdata(dev->pdev);
+		if (gc)
+			mana_schedule_serv_work(gc, dev->type);
+		device_unlock(&dev->pdev->dev);
+
+		if (!gc)
+			mana_do_service(dev->type, dev->pdev, NULL);
+
 		pci_dev_put(dev->pdev);
 		kfree(dev);
 
@@ -796,51 +899,94 @@ static void mana_recovery_delayed_func(struct work_struct *w)
 
 static void mana_serv_func(struct work_struct *w)
 {
-	struct mana_serv_work *mns_wk;
-	struct pci_dev *pdev;
-
-	mns_wk = container_of(w, struct mana_serv_work, serv_work);
-	pdev = mns_wk->pdev;
+	struct gdma_context *gc = container_of(w, struct gdma_context, serv_work);
+	struct pci_dev *pdev = to_pci_dev(gc->dev);
 
-	if (pdev)
-		mana_do_service(mns_wk->type, pdev);
+	mana_do_service(gc->serv_type, pdev, gc);
 
+	/* The rescan exits of mana_do_service() remove the device, which
+	 * frees gc before returning.  Only touch the pdev and the module
+	 * reference from here on; both are held until this point drops them.
+	 */
 	pci_dev_put(pdev);
-	kfree(mns_wk);
 	module_put(THIS_MODULE);
 }
 
+/* Serialize admission with retirement.  Called in hard IRQ context, so
+ * serv_lock is taken with interrupts disabled and nothing that may
+ * sleep runs under it.
+ */
 int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type)
 {
-	struct mana_serv_work *mns_wk;
+	unsigned long flags;
+	bool busy;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	busy = gc->serv_removing || gc->serv_in_flight;
+	if (!busy)
+		gc->serv_in_flight = true;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
 
-	if (test_and_set_bit(GC_IN_SERVICE, &gc->flags)) {
+	if (busy) {
 		dev_info(gc->dev, "Already in service\n");
 		return -EBUSY;
 	}
 
 	if (!try_module_get(THIS_MODULE)) {
 		dev_info(gc->dev, "Module is unloading\n");
-		clear_bit(GC_IN_SERVICE, &gc->flags);
+		mana_service_retire(gc);
 		return -ENODEV;
 	}
 
-	mns_wk = kzalloc_obj(*mns_wk, GFP_ATOMIC);
-	if (!mns_wk) {
-		module_put(THIS_MODULE);
-		clear_bit(GC_IN_SERVICE, &gc->flags);
-		return -ENOMEM;
-	}
-
 	dev_info(gc->dev, "Start MANA service type:%d\n", type);
-	mns_wk->pdev = to_pci_dev(gc->dev);
-	mns_wk->type = type;
-	pci_dev_get(mns_wk->pdev);
-	INIT_WORK(&mns_wk->serv_work, mana_serv_func);
-	schedule_work(&mns_wk->serv_work);
+
+	gc->serv_type = type;
+	pci_dev_get(to_pci_dev(gc->dev));
+	schedule_work(&gc->serv_work);
 	return 0;
 }
 
+/* EQ-event side of the probe boundary: latch the event while the probe
+ * is still running and let the probe roll back (the recovery path will
+ * rescan), or admit the cycle once the probe has completed.  The probe
+ * tail stores probe_done and reads the latch under the same lock, so an
+ * event racing probe completion is admitted rather than lost.
+ */
+static void mana_service_event(struct gdma_context *gc,
+			       enum gdma_eqe_type type)
+{
+	unsigned long flags;
+	bool admit, first;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	admit = gc->serv_probe_done;
+	first = !gc->serv_during_probe;
+	if (!admit)
+		gc->serv_during_probe = true;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+
+	if (admit)
+		mana_schedule_serv_work(gc, type);
+	else if (first)
+		dev_info(gc->dev, "Service is to be processed in probe\n");
+}
+
+/* Probe-tail side of the probe boundary: record probe completion and
+ * return whether an event was latched meanwhile; the same-lock pairing
+ * with mana_service_event() keeps a racing event from being lost.
+ */
+static bool mana_service_probe_complete(struct gdma_context *gc)
+{
+	unsigned long flags;
+	bool rollback;
+
+	spin_lock_irqsave(&gc->serv_lock, flags);
+	gc->serv_probe_done = true;
+	rollback = gc->serv_during_probe;
+	spin_unlock_irqrestore(&gc->serv_lock, flags);
+	return rollback;
+}
+
 /* Return the CPU address of byte @offset within a queue's ring buffer. */
 static void *mana_gd_ring_ptr(const struct gdma_queue *q, u32 offset)
 {
@@ -956,18 +1102,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq)
 	case GDMA_EQE_HWC_FPGA_RECONFIG:
 	case GDMA_EQE_HWC_RESET_REQUEST:
 		dev_info(gc->dev, "Recv MANA service type:%d\n", type);
-
-		if (!test_and_set_bit(GC_PROBE_SUCCEEDED, &gc->flags)) {
-			/*
-			 * Device is in probe and we received a hardware reset
-			 * event, the probe function will detect that the flag
-			 * has changed and perform service procedure.
-			 */
-			dev_info(gc->dev,
-				 "Service is to be processed in probe\n");
-			break;
-		}
-		mana_schedule_serv_work(gc, type);
+		mana_service_event(gc, type);
 		break;
 
 	default:
@@ -2502,6 +2637,7 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 	void __iomem *bar0_va;
 	int bar = 0;
 	int err;
+	bool rollback;
 
 	/* Each port has 2 CQs, each CQ has at most 1 EQE at a time */
 	BUILD_BUG_ON(2 * MAX_PORTS_IN_MANA_DEV * GDMA_EQE_SIZE > EQ_SIZE);
@@ -2532,6 +2668,9 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	mutex_init(&gc->eq_test_event_mutex);
 	mutex_init(&gc->gic_mutex);
+	INIT_WORK(&gc->serv_work, mana_serv_func);
+	spin_lock_init(&gc->serv_lock);
+	init_waitqueue_head(&gc->serv_waitq);
 	pci_set_drvdata(pdev, gc);
 	gc->bar0_pa = pci_resource_start(pdev, 0);
 	gc->bar0_size = pci_resource_len(pdev, 0);
@@ -2558,22 +2697,26 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	err = mana_rdma_probe(&gc->mana_ib);
 	if (err)
-		goto cleanup_mana;
+		goto service_quiesce;
 
 	/*
 	 * If a hardware reset event has occurred over HWC during probe,
 	 * rollback and perform hardware reset procedure.
 	 */
-	if (test_and_set_bit(GC_PROBE_SUCCEEDED, &gc->flags)) {
+	rollback = mana_service_probe_complete(gc);
+	if (rollback) {
 		err = -EPROTO;
-		goto cleanup_mana_rdma;
+		goto service_quiesce;
 	}
 
 	return 0;
 
-cleanup_mana_rdma:
+service_quiesce:
+	/* Only a rollback can have admitted a cycle: it ran
+	 * mana_service_probe_complete() before jumping here.
+	 */
+	mana_service_quiesce(gc);
 	mana_rdma_remove(&gc->mana_ib);
-cleanup_mana:
 	mana_remove(&gc->mana, false);
 cleanup_gd:
 	mana_gd_cleanup_device(pdev);
@@ -2581,6 +2724,8 @@ unmap_bar:
 	xa_destroy(&gc->irq_contexts);
 	pci_iounmap(pdev, bar0_va);
 free_gc:
+	/* Backstop: drain before every vfree(). */
+	mana_service_quiesce(gc);
 	pci_set_drvdata(pdev, NULL);
 	vfree(gc);
 release_region:
@@ -2624,6 +2769,9 @@ static void mana_gd_remove(struct pci_dev *pdev)
 {
 	struct gdma_context *gc = pci_get_drvdata(pdev);
 
+	/* Drain the only gc user remove() does not synchronise with. */
+	mana_service_quiesce(gc);
+
 	pci_disable_sriov(pdev);
 
 	mana_rdma_remove(&gc->mana_ib);
@@ -2635,6 +2783,8 @@ static void mana_gd_remove(struct pci_dev *pdev)
 
 	pci_iounmap(pdev, gc->bar0_va);
 
+	/* Prevent late recovery work from using freed gc. */
+	pci_set_drvdata(pdev, NULL);
 	vfree(gc);
 
 	pci_release_regions(pdev);
@@ -2687,6 +2837,9 @@ static void mana_gd_shutdown(struct pci_dev *pdev)
 
 	dev_info(&pdev->dev, "Shutdown was called\n");
 
+	/* Shutdown tears down the same HW paths as remove(). */
+	mana_service_quiesce(gc);
+
 	mana_rdma_remove(&gc->mana_ib);
 	mana_remove(&gc->mana, true);
 
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 591fb41..c9ac982 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -924,8 +924,8 @@ static void mana_tx_timeout(struct net_device *netdev, unsigned int txqueue)
 		return;
 	}
 
-	/* Already in service, hence tx queue reset is not required.*/
-	if (test_bit(GC_IN_SERVICE, &gc->flags))
+	/* Skip while a service cycle may still touch gc. */
+	if (mana_service_active(gc))
 		return;
 
 	/* Note: If there are pending queue reset work for this port(apc),
@@ -4060,10 +4060,20 @@ static void mana_gf_stats_work_handler(struct work_struct *work)
 		memset(&ac->hc_stats, 0, sizeof(ac->hc_stats));
 		dev_warn(gc->dev,
 			 "Gf stats wk handler: gf stats query timed out.\n");
-		/* As HWC timed out, indicating a faulty HW state and needs a
-		 * reset.
+		/* As HWC timed out, indicating a faulty HW state and
+		 * needs a reset.  Never admit service work before the probe
+		 * has completed: a probe that is failing unwinds netdevs and
+		 * the HWC channel itself and cannot drain a cycle.
 		 */
-		mana_schedule_serv_work(gc, GDMA_EQE_HWC_RESET_REQUEST);
+		if (mana_service_probe_done(gc)) {
+			mana_schedule_serv_work(gc, GDMA_EQE_HWC_RESET_REQUEST);
+		} else {
+			/* Retry once the probe completes: dropping the
+			 * request would leave the stats work disarmed.
+			 */
+			schedule_delayed_work(&ac->gf_stats_work,
+					      MANA_GF_STATS_PERIOD);
+		}
 		return;
 	}
 	schedule_delayed_work(&ac->gf_stats_work, MANA_GF_STATS_PERIOD);
diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h
index 308950f..850de01 100644
--- a/include/net/mana/gdma.h
+++ b/include/net/mana/gdma.h
@@ -228,12 +228,6 @@ enum gdma_page_type {
 
 #define GDMA_INVALID_DMA_REGION 0
 
-struct mana_serv_work {
-	struct work_struct serv_work;
-	struct pci_dev *pdev;
-	enum gdma_eqe_type type;
-};
-
 struct gdma_mem_info {
 	struct device *dev;
 
@@ -417,11 +411,6 @@ struct gdma_irq_context {
 	bool dyn_msix;
 };
 
-enum gdma_context_flags {
-	GC_PROBE_SUCCEEDED	= 0,
-	GC_IN_SERVICE		= 1,
-};
-
 struct gdma_context {
 	struct device		*dev;
 	struct dentry		*mana_pci_debugfs;
@@ -479,7 +468,21 @@ struct gdma_context {
 
 	struct workqueue_struct *service_wq;
 
-	unsigned long		flags;
+	/* The in-flight MANA service cycle, queued on the system workqueue:
+	 * a reset cycle destroys and re-creates @service_wq.
+	 */
+	struct work_struct	serv_work;
+	enum gdma_eqe_type	serv_type;
+
+	/* Service-cycle admission/retirement; taken irqsave (the EQ
+	 * event path is hard IRQ) and never held across a sleep.
+	 */
+	spinlock_t		serv_lock;
+	wait_queue_head_t	serv_waitq;
+	bool			serv_in_flight;
+	bool			serv_removing;
+	bool			serv_during_probe;
+	bool			serv_probe_done;
 
 	/* Protect access to GIC context */
 	struct mutex		gic_mutex;
@@ -528,6 +531,8 @@ ssize_t mana_gd_read_ring(struct gdma_queue *q, char __user *buf,
 			  size_t count, loff_t *pos);
 
 int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type);
+bool mana_service_active(struct gdma_context *gc);
+bool mana_service_probe_done(struct gdma_context *gc);
 
 void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld,
 		      u32 mod_comps, bool mod_comps_vld);


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH net v5] net: mana: fix reset work race with device removal
  2026-09-27 10:52 [PATCH net v5] net: mana: fix reset work race with device removal Fan Wu
@ 2026-10-02  5:32 ` netdev-bot+sashiko
  0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02  5:32 UTC (permalink / raw)
  To: fanwu01
  Cc: netdev, kys, haiyangz, wei.liu, decui, longli, andrew+netdev,
	davem, edumazet, kuba, pabeni, linux-hyperv, linux-kernel,
	stable, songl

Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 2 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The rescan exits signal 'idle' to remove/shutdown before the PCI
  manipulation they still have to do.
- [Medium] Probe rollback leaves admission open.
- [Medium] Embedding serv_work in gc allows a workqueue self-deadlock
  through a recycled work address.
- [Low] Comments in mana_gf_stats_work_handler() (mana_en.c) contradict
  the code.

Pre-existing issues:
- [High] This bug predates the patch, but the patch leaves it in place.

--

net: mana: fix reset work race with device removal

This patch embeds the MANA service work in gdma_context and adds a
serv_lock and waitqueue to control when a service cycle may be admitted
and when it retires. Removal, shutdown and the probe unwind close
admission and wait for any in-flight cycle before freeing gc. Reset
events seen during probe are latched and left to the probe rollback path.

> diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> index 8e9bfc1d6a2ae..4ebe595971a4f 100644
> --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> @@ -659,63 +659,149 @@ EXPORT_SYMBOL_NS(mana_gd_ring_dim, "NET_MANA");

[ ... ]

> +static void mana_serv_rescan(struct pci_dev *pdev, struct gdma_context *gc)
> +{
> +	struct pci_bus *parent;
>
>  	parent = pdev->bus;
>  	if (!parent) {
>  		dev_err(&pdev->dev, "MANA service: no parent bus\n");
> -		goto out;
> +		/* Still bound: keep admission open for later events. */
> +		if (gc)
> +			mana_service_retire(gc);
> +		return;
>  	}
>
> +	if (gc)
> +		mana_service_retire_removing(gc);
> +
> +	pci_lock_rescan_remove();
> +
>  	pci_stop_and_remove_bus_device(pdev);
>  	pci_rescan_bus(parent);

[Severity: High]
Can mana_gd_remove() or mana_gd_shutdown() finish while this cycle still
has PCI work left to do?

mana_service_retire_removing() clears serv_in_flight and wakes
serv_waitq. That lets mana_service_quiesce() return, and the teardown
owner carries on.

The cycle then takes pci_lock_rescan_remove() and still runs
pci_stop_and_remove_bus_device(pdev) and pci_rescan_bus(parent). Both act
on a topology captured before the lock was taken, and nothing checks
again whether teardown already happened. mana_serv_fpga() has the same
ordering.

Shutdown: mana_gd_shutdown() gets past quiesce and runs mana_rdma_remove(),
mana_remove(&gc->mana, true), mana_gd_cleanup_device() and
pci_disable_device(), but the driver stays bound. The cycle then removes
the device, so device_release_driver() runs mana_gd_remove() a second
time through the leftover ac. The second pci_disable_device() then hits:

    dev_WARN_ONCE(&dev->dev, atomic_read(&dev->enable_cnt) <= 0,
                  "disabling already-disabled device");

After that, pci_rescan_bus() re-enumerates the function during
reboot/kexec.

Unbind or sysfs remove: after gc is freed, the cycle still removes and
rescans. Could an unbound or removed device come back and rebind?

Hyper-V VF hot-remove: a removed VF makes mana_gd_resume() return
-ETIMEDOUT, so this rescan exit is the expected branch.
hv_pci_remove() calls mana_gd_remove() under pci_rescan_remove_lock and
quiesce returns. hv_pci_remove() then continues:

    pci_remove_root_bus(hbus->bridge->bus);
    pci_unlock_rescan_remove();
    ...
    hv_pci_bus_exit(hdev, false);
    ...
    kfree(hbus);

The waiting cycle then runs pci_rescan_bus() on the removed root bus.
pci_bus_get() in pci_alloc_dev() keeps the pci_bus struct alive, so
config reads still reach hv_pcifront_read_config():

    struct hv_pcibus_device *hbus =
        container_of(bus->sysdata, struct hv_pcibus_device, sysdata);

Does that use hbus after it has been freed?

Could the cycle recheck under the rescan/remove lock whether teardown has
already taken over? Or could it hold off signalling idle until the
remove/rescan is done?

[ ... ]

> @@ -738,7 +824,7 @@ static void mana_serv_reset(struct pci_dev *pdev)
>  	if (ret == -ETIMEDOUT || ret == -EPROTO) {
>  		/* Perform PCI rescan on device if we failed on HWC */
>  		dev_err(&pdev->dev, "MANA service: resume failed, rescanning\n");
> -		mana_serv_rescan(pdev);
> +		mana_serv_rescan(pdev, gc);
>  		return;
>  	}

[Severity: High]
This isn't a bug introduced by this patch, but the PCI PM callbacks are
still outside the new serv_lock admission/drain protocol. mana_driver
registers:

    .suspend	= mana_gd_suspend,
    .resume		= mana_gd_resume,

mana_serv_reset() calls the same functions directly from the system
workqueue:

    mana_gd_suspend(pdev, PMSG_SUSPEND);
    msleep(MANA_SERVICE_PERIOD * 1000);
    ret = mana_gd_resume(pdev);

system_percpu_wq is not freezable, and the worker does not take
device_lock. So can a system suspend or hibernate run
pci_legacy_suspend()->mana_gd_suspend() while an admitted cycle is in its
suspend/sleep/resume window?

If so, both threads run mana_rdma_remove(), mana_remove(&gc->mana, true)
and mana_gd_cleanup_device() on the same gc. For example,
mana_hwc_destroy_channel() does:

    struct hw_channel_context *hwc = gc->hwc.driver_data;

    if (!hwc)
        return;

Both callers can see hwc as non-NULL and free it. The
destroy_workqueue(gc->service_wq) call behind the NULL check in
mana_gd_cleanup_device() can also run twice.

The changelog below the fold says the PM path was left out. Could the
commit message itself say that this sibling teardown path is still
unprotected?

[ ... ]

> @@ -796,51 +899,94 @@ static void mana_recovery_delayed_func(struct work_struct *w)
>
>  static void mana_serv_func(struct work_struct *w)
>  {
> -	struct mana_serv_work *mns_wk;
> -	struct pci_dev *pdev;
> -
> -	mns_wk = container_of(w, struct mana_serv_work, serv_work);
> -	pdev = mns_wk->pdev;
> +	struct gdma_context *gc = container_of(w, struct gdma_context, serv_work);
> +	struct pci_dev *pdev = to_pci_dev(gc->dev);
>
> -	if (pdev)
> -		mana_do_service(mns_wk->type, pdev);
> +	mana_do_service(gc->serv_type, pdev, gc);
>
> +	/* The rescan exits of mana_do_service() remove the device, which
> +	 * frees gc before returning.  Only touch the pdev and the module
> +	 * reference from here on; both are held until this point drops them.
> +	 */
>  	pci_dev_put(pdev);
> -	kfree(mns_wk);
>  	module_put(THIS_MODULE);
>  }

[Severity: Medium]
Can embedding serv_work in gc cause a workqueue self-deadlock through a
recycled work address?

On the rescan exit, mana_serv_func() is still running when the gc that
holds its work_struct is freed and a new gc is allocated:

mana_serv_func()
  mana_do_service()
    mana_serv_reset()
      mana_serv_rescan()
        pci_stop_and_remove_bus_device()
          mana_gd_remove()
            vfree(gc)
        pci_rescan_bus()
          mana_gd_probe()
            gc = vzalloc(sizeof(*gc))

vzalloc() might return the same address. In that case the new
gc->serv_work has the same address and the same func as the work this
worker is executing.

Suppose the new probe then latches a reset event, and a second event or
a gf_stats timeout is admitted in the rollback window. That window is
covered in the comment on the mana_gd_probe() rollback below. The new
work is queued with schedule_work(). If it lands on the same pool,
find_worker_executing_work() matches:

    if (worker->current_work == work &&
        worker->current_func == work->func)
        return worker;

and assign_work() moves it onto this worker's scheduled list.

This worker, however, is the one blocked in mana_service_quiesce() in the
probe rollback, waiting for serv_in_flight to clear. So the queued work
never runs, and the worker would hang forever holding the new pdev's
device_lock and pci_rescan_remove_lock.

Before this patch, mns_wk was only freed after the rescan returned, so
its address could not be reused while it was still running. Closing
admission in the same serv_lock section on rollback in
mana_service_probe_complete() would also avoid this.

[ ... ]

> +static bool mana_service_probe_complete(struct gdma_context *gc)
> +{
> +	unsigned long flags;
> +	bool rollback;
> +
> +	spin_lock_irqsave(&gc->serv_lock, flags);
> +	gc->serv_probe_done = true;
> +	rollback = gc->serv_during_probe;
> +	spin_unlock_irqrestore(&gc->serv_lock, flags);
> +	return rollback;
> +}

[ ... ]

> @@ -2558,22 +2697,26 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)

[ ... ]

> -	if (test_and_set_bit(GC_PROBE_SUCCEEDED, &gc->flags)) {
> +	rollback = mana_service_probe_complete(gc);
> +	if (rollback) {
>  		err = -EPROTO;
> -		goto cleanup_mana_rdma;
> +		goto service_quiesce;
>  	}
>
>  	return 0;
>
> -cleanup_mana_rdma:
> +service_quiesce:
> +	/* Only a rollback can have admitted a cycle: it ran
> +	 * mana_service_probe_complete() before jumping here.
> +	 */
> +	mana_service_quiesce(gc);

[Severity: Medium]
Is admission left open between mana_service_probe_complete() and this
mana_service_quiesce()?

When mana_service_probe_complete() returns rollback == true, it has
already set serv_probe_done = true. serv_removing is not set until
mana_service_quiesce() runs here.

In that window, a hard IRQ reset or FPGA EQE goes through
mana_service_event():

    admit = gc->serv_probe_done;
    ...
    if (admit)
        mana_schedule_serv_work(gc, type);

This patch keeps mana_gf_stats_work_handler() re-arming during probe. In
the same window it sees mana_service_probe_done(gc) return true and calls
mana_schedule_serv_work() too.

If a cycle is admitted here, the probe sleeps in mana_service_quiesce()
under device_lock through the whole reset cycle. When the probe runs from
a service rescan, it may also hold pci_rescan_remove_lock. The cycle runs
mana_gd_suspend(), msleep(MANA_SERVICE_PERIOD * 1000) and
mana_gd_resume().

The probe then unwinds with mana_rdma_remove(), mana_remove() and
mana_gd_cleanup_device() on state the cycle just rebuilt, and queues the
-EPROTO recovery rescan. If the cycle's resume fails, the cycle also takes
its own rescan exit, so the device is serviced twice.

The commit message says:

    Do not admit service work while probe is still constructing or
    unwinding the device.

The label comment above also admits that a rollback can have let a cycle
in.

Could mana_service_probe_complete() set serv_removing in the same
serv_lock section when rollback is true? Or could it leave
serv_probe_done unset on rollback?

[ ... ]

> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 591fb4191d90d..c9ac982925224 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c

[ ... ]

> @@ -4060,10 +4060,20 @@ static void mana_gf_stats_work_handler(struct work_struct *work)
>  		memset(&ac->hc_stats, 0, sizeof(ac->hc_stats));
>  		dev_warn(gc->dev,
>  			 "Gf stats wk handler: gf stats query timed out.\n");
> -		/* As HWC timed out, indicating a faulty HW state and needs a
> -		 * reset.
> +		/* As HWC timed out, indicating a faulty HW state and
> +		 * needs a reset.  Never admit service work before the probe
> +		 * has completed: a probe that is failing unwinds netdevs and
> +		 * the HWC channel itself and cannot drain a cycle.
>  		 */
> -		mana_schedule_serv_work(gc, GDMA_EQE_HWC_RESET_REQUEST);
> +		if (mana_service_probe_done(gc)) {
> +			mana_schedule_serv_work(gc, GDMA_EQE_HWC_RESET_REQUEST);
> +		} else {
> +			/* Retry once the probe completes: dropping the
> +			 * request would leave the stats work disarmed.
> +			 */
> +			schedule_delayed_work(&ac->gf_stats_work,
> +					      MANA_GF_STATS_PERIOD);
> +		}

[Severity: Low]
This isn't a bug, but the comments here don't match the code.

The new comment says a failing probe "cannot drain a cycle". However,
mana_gd_probe() now drains one with mana_service_quiesce(), both at the
service_quiesce label and again before vfree() at free_gc.

The existing comment just above this block also still says:

    /* HWC timeout detected - reset stats and stop rescheduling */

But the new else branch reschedules gf_stats_work.

Should both comments be updated?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927105243.592348-1-fanwu01%40zju.edu.cn

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-02  5:32 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-27 10:52 [PATCH net v5] net: mana: fix reset work race with device removal Fan Wu
2026-10-02  5:32 ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®