Thread (22 messages) 22 messages, 3 authors, 2026-09-11

Re: [PATCH net-next v6 06/12] gve: simplify reset logic

flat view

From: netdev-bot+sashiko@kernel.org
Date: 2026-09-10 22:13:56
Also in: bpf, lkml

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

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

- [High] Inconsistent work quiescing across the new gve_teardown_device()
  call sites leaves priv->stats_report_task able to run after…
- [High] The new reset/close flow releases data-plane DMA before the
  hardware reset that revokes the device's access to it.
- [Medium] gve_mgmnt_intr() now returns IRQ_NONE without queueing
  priv->service_task whenever GVE_PRIV_FLAGS_DEVICE_RESOURCES_OK is…

Pre-existing issues:
- [High] After gve_reset() fails to recover (gve_recover() ->
  teardown_device), the netdev is left administratively UP (nothing…

--

Here is the report.

gve: simplify reset logic

This reworks the gve reset path: recursive resets and resets during
teardown are removed, allocation is split from device setup, and a single
gve_teardown_device() / gve_recover() pair replaces the old
gve_reset_and_teardown() / gve_reset_recovery() helpers.

A few questions on the new teardown ordering and work-item quiescing
below.
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
index 2fe280cf7e680..87388856ff6c5 100644
--- a/drivers/net/ethernet/google/gve/gve_main.c
+++ b/drivers/net/ethernet/google/gve/gve_main.c
@@ -270,6 +270,10 @@ static irqreturn_t gve_mgmnt_intr(int irq, void *arg)
 {
 	struct gve_priv *priv = arg;
 
+	/* Device resources are not okay; consider the interrupt spurious. */
+	if (!gve_get_device_resources_ok(priv))
+		return IRQ_NONE;
+
 	queue_work(priv->gve_wq, &priv->service_task);
 	return IRQ_HANDLED;
 }
[Severity: Medium]
Can this drop a management notification instead of latching it?

The mgmt vector is live from gve_alloc_notify_blocks()/setup_mgmt_irq()
until it is released, but device_resources_ok is only set at the end of
gve_setup_control_plane_resources() and is cleared at the very start of
gve_teardown_control_plane_resources(). So for the whole teardown window
inside gve_teardown_device(), and for gve_recover()'s
adminq_init/verify/setup_device phase, this returns IRQ_NONE without
queueing the service task.

gve_service_task() is the only reader of reg_bar0->device_status (it feeds
gve_handle_status() for the device-requested-reset bit and the link
handling), and the only other re-sample point is the
queue_work(priv->gve_wq, &priv->service_task) at the end of
gve_queues_start(). When gve_recover() runs with setup_queues == false
(was_up false, or gve_reset(priv, true) from gve_close()'s reset path),
gve_open() is never called:

	if (setup_queues) {
		err = gve_open(priv->dev);

Is a device reset request or link transition asserted in that window
recovered anywhere, or is it lost until the next unrelated open?

Since gve_reset() already brackets the teardown with
disable_work()/enable_work(&priv->service_task), would keeping the
unconditional queue_work() here be equivalent for exclusion purposes
without discarding the notification? Also, for a dedicated (non-shared)
MSI-X vector, repeatedly returning IRQ_NONE feeds the core spurious
interrupt detector.

[ ... ]
quoted hunk ↗ jump to hunk
-static void gve_trigger_reset(struct gve_priv *priv);
-
-static void gve_teardown_device_resources(struct gve_priv *priv)
+/**
+ * gve_teardown_control_plane_resources() - Request the device to release any
+ * shared allocated resources.
[ ... ]
quoted hunk ↗ jump to hunk
+static void gve_teardown_device(struct gve_priv *priv)
+{
+	gve_teardown_control_plane_resources(priv);
+	gve_adminq_free(priv);
+	/*
+	 * Free any resources shared with the device only after we have a
+	 * guarantee that the device will not try to access such resources.
+	 * Device commands in gve_teardown_control_plane_resources can fail, in
+	 * which case, device resources won't be relinquished until
+	 * gve_adminq_free is called to trigger a device reset.
+	 */
+	gve_free_control_plane_resources(priv);
+}
+
[Severity: High]
Is priv->stats_report_task guaranteed to be quiesced by the time
gve_free_control_plane_resources() -> gve_free_stats_report() frees the
coherent buffer?

gve_free_stats_report() only stops the timer and frees:

	if (!priv->stats_report)
		return;

	timer_delete_sync(&priv->stats_report_timer);
	dma_free_coherent(&priv->pdev->dev, priv->stats_report_len,
			  priv->stats_report, priv->stats_report_bus);
	priv->stats_report = NULL;

The only cancel_work_sync(&priv->stats_report_task) added by this patch is
in gve_queues_stop(), which the teardown paths only reach when the
interface was up (gve_reset() calls it under if (was_up); gve_remove(),
gve_shutdown() and gve_suspend() reach it only via gve_close()).

gve_set_priv_flags() in gve_ethtool.c arms the timer with no
netif_running() check:

	if (flags & BIT(0)) {
		mod_timer(&priv->stats_report_timer,
			  round_jiffies(jiffies +
					msecs_to_jiffies(priv->stats_report_timer_period)));
	}

so with report-stats enabled on a down interface the chain
gve_stats_report_timer() -> gve_stats_report_schedule() ->
queue_work(priv->gve_wq, &priv->stats_report_task) can leave the work
pending across gve_teardown_device(). gve_handle_report_stats() then does:

	struct stats *stats = priv->stats_report->stats;
	...
	be64_add_cpu(&priv->stats_report->written_count, 1);

which is either a write into the just-freed DMA coherent buffer or a NULL
dereference once priv->stats_report has been set to NULL. In gve_remove()
the pending item is only drained by destroy_workqueue(), i.e. after
gve_teardown_device() has already freed it.

Would it be simpler to do the cancel_work_sync(&priv->stats_report_task)
inside gve_free_stats_report() (or in gve_teardown_device()) so every call
site is covered regardless of whether the interface was up?

[ ... ]
quoted hunk ↗ jump to hunk
@@ -1435,6 +1472,12 @@ static int gve_open(struct net_device *dev)
 	struct gve_priv *priv = netdev_priv(dev);
 	int err;
 
+	if (!gve_get_device_resources_ok(priv)) {
+		dev_err(&priv->pdev->dev,
+			"Attempting to open netdev without resources. Device must be reset.");
+		return -ENODEV;
+	}
+
[Severity: High]
This isn't a bug introduced by this patch (the baseline
gve_reset_recovery() error path also left the interface up with
priv->xsk_pools == NULL after gve_teardown_priv_resources()), but since
this patch restructures exactly that path and adds the resources_ok gate
here, should the other user-reachable entry points get the same gate?

After gve_recover() takes its teardown_device label, the netdev is still
administratively up (nothing calls dev_close()), while
gve_teardown_device() has set priv->xsk_pools = NULL,
priv->ptype_lut_dqo = NULL, priv->tx/priv->rx = NULL, freed the notify
blocks, and dma_pool_free()d the adminq buffer while priv->adminq keeps
its stale pointer.

gve_xsk_pool_disable() only bounds-checks the queue id:

	if (qid >= priv->rx_cfg.num_queues)
		return -EINVAL;

	clear_bit(qid, priv->xsk_pools);

and closing an AF_XDP zero-copy socket reaches ndo_bpf(XDP_SETUP_XSK_POOL,
pool == NULL) without any netif_running() test (xp_clear_dev() /
xp_disable_drv_zc()), so this is a clear_bit() on a NULL bitmap.
gve_set_priv_flags() likewise memsets priv->stats_report->stats when
report-stats is turned off, and gve_adjust_queues()/gve_adjust_config()
issue adminq commands through the freed priv->adminq while netif_running()
is true.

The failed-recovery precondition is easy to reach now that
gve_add_flow_rule()/gve_del_flow_rule() call gve_reset(priv, false) on
-ETIME, and gve_recover() bails out to teardown_device when e.g.
gve_adminq_verify_driver_compatibility() times out.
quoted hunk ↗ jump to hunk
@@ -1451,41 +1494,16 @@ static int gve_open(struct net_device *dev)
 	return 0;
 }
 
-static int gve_queues_stop(struct gve_priv *priv)
+static void gve_queues_stop(struct gve_priv *priv)
 {
-	int err;
+	gve_unreg_xdp_info(priv);
+	gve_drain_page_cache(priv);
 
-	netif_carrier_off(priv->dev);
-	if (gve_get_device_rings_ok(priv)) {
-		gve_turndown(priv);
-		gve_drain_page_cache(priv);
-		err = gve_destroy_rings(priv);
-		if (err)
-			goto err;
-		err = gve_unregister_qpls(priv);
-		if (err)
-			goto err;
-		gve_clear_device_rings_ok(priv);
-	}
 	timer_delete_sync(&priv->stats_report_timer);
-
-	gve_unreg_xdp_info(priv);
+	cancel_work_sync(&priv->stats_report_task);
 
 	gve_tx_stop_rings(priv, gve_num_tx_queues(priv));
 	gve_rx_stop_rings(priv, priv->rx_cfg.num_queues);
-
-	priv->interface_down_cnt++;
-	return 0;
-
-err:
-	/* This must have been called from a reset due to the rtnl lock
-	 * so just return at this point.
-	 */
-	if (gve_get_reset_in_progress(priv))
-		return err;
-	/* Otherwise reset before returning */
-	gve_reset_and_teardown(priv, true);
-	return gve_reset_recovery(priv, false);
 }
 
 static int gve_close(struct net_device *dev)
@@ -1493,12 +1511,30 @@ static int gve_close(struct net_device *dev)
 	struct gve_priv *priv = netdev_priv(dev);
 	int err;
 
-	err = gve_queues_stop(priv);
-	if (err)
-		return err;
+	gve_turndown(priv);
+
+	/* Surrender to reset if the queue destroying adminq cmds fail. Reset
+	 * will not re-enable the interface.
+	 */
+	if (gve_get_device_rings_ok(priv)) {
+		gve_clear_device_rings_ok(priv);
+		err = gve_destroy_rings(priv);
+		if (err)
+			goto reset;
+		err = gve_unregister_qpls(priv);
+		if (err)
+			goto reset;
+	}
 
+	gve_queues_stop(priv);
 	gve_queues_mem_remove(priv);
+	priv->interface_down_cnt++;
 	return 0;
+
+reset:
+	gve_queues_stop(priv);
+	err = gve_reset(priv, true);
+	return err;
 }
[Severity: High]
On this new reset: label, is it safe to release the ring buffers before
the hardware reset?

gve_destroy_rings() or gve_unregister_qpls() failing (typically -ETIME
from gve_adminq_kick_and_wait() -> gve_adminq_wait_for_cmd()) means the
NIC never acknowledged that it stopped using those queues. gve_queues_stop()
then does:

	gve_tx_stop_rings(priv, gve_num_tx_queues(priv));
	gve_rx_stop_rings(priv, priv->rx_cfg.num_queues);

which for DQO returns RX buffer pages via gve_free_to_page_pool()/
gve_free_qpl_page_dqo(), dma_unmaps pending TX buffers
(gve_tx_clean_pending_packets() -> gve_unmap_packet()) and memsets the
descriptor/completion rings and q_resources.

The point at which the device is guaranteed to have stopped touching that
memory only comes later, in gve_reset() -> gve_teardown_device() ->
gve_adminq_free() -> gve_adminq_release():

	iowrite32be(GVE_DRIVER_STATUS_RESET_MASK, &priv->reg_bar0->driver_status);
	while (!(ioread32be(&priv->reg_bar0->device_status)
			& GVE_DEVICE_STATUS_DEVICE_IS_RESET)) { ... }

The pre-patch code returned from gve_queues_stop() before stopping the
rings on a destroy failure, and gve_reset_and_teardown() called
gve_trigger_reset() (which blocks until DEVICE_IS_RESET) first. Should the
hardware reset happen before gve_queues_stop() here?

The commit message applies exactly this reasoning to the control plane:

    With the new reset functionality, shared resources between the device
    and driver are not freed until after the hardware reset has completed

Does the same invariant hold for the data-plane rings and buffers on this
path?

[ ... ]
quoted hunk ↗ jump to hunk
-int gve_reset(struct gve_priv *priv, bool attempt_teardown)
+int gve_reset(struct gve_priv *priv, bool skip_queue_setup)
 {
 	bool was_up = netif_running(priv->dev);
 	int err;
 
+	if (gve_get_reset_in_progress(priv))
+		return 0;
+
 	dev_info(&priv->pdev->dev, "Performing reset\n");
 	gve_clear_do_reset(priv);
 	gve_set_reset_in_progress(priv);
-	/* If we aren't attempting to teardown normally, just go turndown and
-	 * reset right away.
-	 */
-	if (!attempt_teardown) {
+
+	if (was_up) {
 		gve_turndown(priv);
-		gve_reset_and_teardown(priv, was_up);
-	} else {
-		/* Otherwise attempt to close normally */
-		if (was_up) {
-			err = gve_close(priv->dev);
-			/* If that fails reset as we did above */
-			if (err)
-				gve_reset_and_teardown(priv, was_up);
+		if (gve_get_device_rings_ok(priv)) {
+			gve_clear_device_rings_ok(priv);
+			gve_destroy_rings(priv);
+			gve_unregister_qpls(priv);
 		}
-		/* Clean up any remaining resources */
-		gve_teardown_priv_resources(priv);
+		gve_queues_stop(priv);
 	}
[Severity: High]
Same ordering question here, with the added detail that the return values
of gve_destroy_rings() and gve_unregister_qpls() are not inspected at all,
so gve_queues_stop() unmaps/recycles the ring memory unconditionally,
including for a device whose adminq is not responding.

There is a mirror of this on the recovery side: with RESET_IN_PROGRESS
set, a create-rings failure makes gve_queues_start() take
stop_and_free_rings:

	stop_and_free_rings:
		gve_tx_stop_rings(priv, gve_num_tx_queues(priv));
		gve_rx_stop_rings(priv, priv->rx_cfg.num_queues);
		gve_queues_mem_remove(priv);

which frees the coherent rings and QPL pages even though some TX queues
may already have been created in hardware, with the reset handshake only
happening later when gve_recover() calls gve_teardown_device(). Can the
NIC still be writing into those pages at that point?
quoted hunk ↗ jump to hunk
 
-	/* Set it all back up */
-	err = gve_reset_recovery(priv, was_up);
+	disable_work(&priv->service_task);
+	gve_teardown_device(priv);
+	gve_queues_mem_remove(priv);
+
+	enable_work(&priv->service_task);
+	err = gve_recover(priv, was_up && !skip_queue_setup);
+	if (err)
+		dev_info(&priv->pdev->dev,
+			 "Failed to recover in reset: %d\n", err);
+
[Severity: High]
Only priv->service_task is disabled around gve_teardown_device() here.
When was_up is false, gve_queues_stop() (the sole
cancel_work_sync(&priv->stats_report_task) call site) is skipped, so a
stats work item queued by the timer can run concurrently with
gve_free_stats_report(). Should priv->stats_report_task be quiesced here
too?

[ ... ]
quoted hunk ↗ jump to hunk
@@ -2943,8 +2952,9 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 	queue_work(priv->gve_wq, &priv->service_task);
 	return 0;
 
-abort_with_gve_init:
-	gve_teardown_priv_resources(priv);
+abort_teardown_device:
+	disable_work(&priv->stats_report_task);
+	gve_teardown_device(priv);
[Severity: High]
Is this disabling the intended work item? During probe, the stats work has
no producer yet (its only producer is the timer armed by
gve_queues_start() or by ethtool on a registered netdev), while
priv->service_task is queueable at this point because gve_setup_device()
already set device_resources_ok and the mgmt IRQ is requested. Every other
teardown site in this patch uses disable_work_sync(&priv->service_task).

Should this be disable_work_sync(&priv->service_task) instead, or in
addition?
quoted hunk ↗ jump to hunk
@@ -2976,7 +2986,8 @@ static void gve_remove(struct pci_dev *pdev)
 	void __iomem *reg_bar = priv->reg_bar0;
 
 	unregister_netdev(netdev);
-	gve_teardown_priv_resources(priv);
+	disable_work_sync(&priv->service_task);
+	gve_teardown_device(priv);
 	destroy_workqueue(priv->gve_wq);
[Severity: High]
For an interface that was already down, unregister_netdev() does not call
gve_close(), so nothing cancels priv->stats_report_task before
gve_teardown_device() frees priv->stats_report. destroy_workqueue() then
drains the pending stats work after the free. Does the ordering here need
a disable_work_sync(&priv->stats_report_task) as well?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909191136.3811892-1-hramamurthy%40google.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help