Skip to contentSkip to conversation

ibmveth: fix hangs, use-after-frees and netpoll races

PATCH Superseded Mingming Cao 2 participants 11 messages Oct 3 – Oct 5

Subsystems (4)

v1 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

cover ibmveth: fix hangs, use-after-frees and netpoll races

Hi,

Seven fixes for serious bugs in the ibmveth driver: two hang the system, three are use-after-frees, one corrupts memory, and one makes ethtool -L report success when it failed.

The series is based on net-next commit 071876fd5048 ("ref_tracker: Don't use __GFP_NOFAIL for PF_MEMALLOC thread."). It does not depend on the two open() error-path fixes recently applied to net (commit af0524bf4ce1, commit 84bec0bf0352) and merges cleanly with them; patch 2 explains how it relates to them. It also applies to net with git am -3.

1. Netpoll races with RX replenish (memory corruption, NULL dereference): remove ndo_poll_controller, as was done for ibmvnic, and enable NAPI only once the RX resources exist.

Fixes 6b4223748895 ("[PATCH] ibmveth: Add netpoll function")
Fixes bea3348eef27 ("[NET]: Make NAPI polling independent of struct net_device objects.")

2. Hang after a failed internal reopen: the next close() waits in napi_disable() forever with RTNL held, and only a reboot recovers. Skip close() when open() did not succeed.

Fixes 860f242eb534 ("[PATCH] ibmveth change buffer pools dynamically")

3. Use-after-free in remove(): a reset queued from NAPI could run on the freed adapter. Disable the reset work first.

Fixes 2c91e2319ed9 ("net: ibmveth: Reset the adapter when unexpected states are detected")

4. RX poll hang on a bad correlator: poll restarts on the same slot until RCU stalls, and an inactive pool dereferences NULL. Step past the slot, reject inactive pools, count the drop.

Fixes 2c91e2319ed9 ("net: ibmveth: Reset the adapter when unexpected states are detected")
Fixes 860f242eb534 ("[PATCH] ibmveth change buffer pools dynamically")

5. Use-after-free after a failed probe: the pool kobjects stay in sysfs after the adapter is freed. Put them, as remove() does. The pool kobjects have no release(), so with CONFIG_DEBUG_KOBJECT_RELEASE a short window remains here and in remove(); giving them a release() is left for a follow-up.

Fixes 860f242eb534 ("[PATCH] ibmveth change buffer pools dynamically")

6. ethtool -L returns 0 when it cannot allocate the new TX queues. Return the allocation error.

Fixes 10c2aba89cc0 ("ibmveth: Ethtool set queue support")

7. Use-after-free of TX buffers: close(), called directly for MTU, offload and buffer pool changes, frees them without waiting for a running transmit. Use netif_tx_disable().

Fixes d6832ca48d8a ("ibmveth: Copy tx skbs into a premapped buffer")

The triggers are rare and there are no field reports, so the series targets net-next. All carry Fixes: tags; I am happy to repost against net, or add Cc: stable, if you prefer.

All were found by AI-assisted review of the ibmveth multi-queue RX series [1]. Landing them first also shrinks that series, which then only extends this handling per queue.

Testing: the new and extended KUnit cases in patch 4 fail on the unfixed driver and pass on qemu pseries (ppc64le). On a POWER10
LPAR: netconsole under printk and ping floods, MTU and buffer pool changes under traffic, a forced open() failure followed by down and up, unbind/bind under traffic, a forced register_netdev() failure in probe, and ethtool -L with a forced TX buffer allocation failure.

[1] https://lore.kernel.org/netdev/[email protected]/

Thanks,
Mingming

Mingming Cao (7):
ibmveth: fix netpoll races with RX replenish
ibmveth: do not close twice after a failed reopen
ibmveth: disable the reset work before unregister in remove
ibmveth: step past bad RX correlators instead of spinning or oopsing
ibmveth: release the pool kobjects when probe fails
ibmveth: return the error when set_channels cannot add TX queues
ibmveth: wait for in-flight transmits in ibmveth_close()

 drivers/net/ethernet/ibm/ibmveth.c | 238 ++++++++++++++++++++++-------
 drivers/net/ethernet/ibm/ibmveth.h |   2 +
 2 files changed, 184 insertions(+), 56 deletions(-)
Signature · 1 line
2.39.3 (Apple Git-146)
#1 lore raw

PATCH 1/7 ibmveth: fix netpoll races with RX replenish

ibmveth_poll_controller() runs RX replenish outside NAPI and without a lock, racing NAPI's replenish on another CPU. Both can fill the same slot, so an skb and its DMA mapping leak and PHYP can write into an unmapped buffer. netpoll calls it from netconsole and from netpoll-enabled bonds.

ibmveth_open() also enables NAPI before the RX resources exist. ibmveth_change_mtu(), veth_pool_store(), ibmveth_set_csum_offload() and ibmveth_set_tso() call close() and open() directly, so while open() is still setting up, netpoll and the direct ibmveth_interrupt() calls can replenish NULL pools and read freed memory.

Remove the callback, as Eric Dumazet did for many drivers after commit ac3d9dd034e5 ("netpoll: make ndo_poll_controller() optional"), including ibmvnic in commit 0c3b9d1b37df ("ibmvnic: remove ndo_poll_controller"). netpoll then polls NAPI itself with budget 0, serialized with NAPI by napi->poll_owner. ibmveth_poll() handles budget 0 and TX completes synchronously, so nothing else is needed.

Enable NAPI just before request_irq(), once everything ibmveth_poll() touches exists.

Neither race was reproduced. Tested on a POWER10 LPAR with netconsole over ibmveth: a ping flood (678,470 packets, no loss) during a printk flood, and MTU changes and buffer pool toggles under traffic, with no warnings.

Fixes 6b4223748895 ("[PATCH] ibmveth: Add netpoll function")
Fixes bea3348eef27 ("[NET]: Make NAPI polling independent of struct net_device objects.")
---
 drivers/net/ethernet/ibm/ibmveth.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
drivers/net/ethernet/ibm/ibmveth.c +8 -14
index 73e051d26b9d..aa2300e97081 100644
@@ -623,8 +623,6 @@ static int ibmveth_open(struct net_device *netdev)
 
 	netdev_dbg(netdev, "open starting\n");
 
-	napi_enable(&adapter->napi);
-
 	for(i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
 		rxq_entries += adapter->rx_buff_pool[i].size;
 
@@ -712,10 +710,18 @@ static int ibmveth_open(struct net_device *netdev)
 		}
 	}
 
+	/* NAPI can run as soon as it is enabled, from netpoll during the
+	 * direct close()/open() pairs or from a direct ibmveth_interrupt()
+	 * call, so enable it only once everything ibmveth_poll() touches
+	 * exists.
+	 */
+	napi_enable(&adapter->napi);
+
 	netdev_dbg(netdev, "registering irq 0x%x\n", netdev->irq);
 	rc = request_irq(netdev->irq, ibmveth_interrupt, 0, netdev->name,
 			 netdev);
 	if (rc != 0) {
+		napi_disable(&adapter->napi);
 		netdev_err(netdev, "unable to request irq 0x%x, rc %d\n",
 			   netdev->irq, rc);
 		do {
@@ -763,7 +769,6 @@ static int ibmveth_open(struct net_device *netdev)
 out_free_buffer_list:
 	free_page((unsigned long)adapter->buffer_list_addr);
 out:
-	napi_disable(&adapter->napi);
 	return rc;
 }
 
@@ -1680,14 +1685,6 @@ static int ibmveth_change_mtu(struct net_device *dev, int new_mtu)
 	return -EINVAL;
 }
 
-#ifdef CONFIG_NET_POLL_CONTROLLER
-static void ibmveth_poll_controller(struct net_device *dev)
-{
-	ibmveth_replenish_task(netdev_priv(dev));
-	ibmveth_interrupt(dev->irq, dev);
-}
-#endif
-
 /**
  * ibmveth_get_desired_dma - Calculate IO memory desired by the driver
  *
@@ -1789,9 +1786,6 @@ static const struct net_device_ops ibmveth_netdev_ops = {
 	.ndo_validate_addr	= eth_validate_addr,
 	.ndo_set_mac_address    = ibmveth_set_mac_addr,
 	.ndo_features_check	= ibmveth_features_check,
-#ifdef CONFIG_NET_POLL_CONTROLLER
-	.ndo_poll_controller	= ibmveth_poll_controller,
-#endif
 };
 
 static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
Signature · 1 line
2.39.3 (Apple Git-146)
#2 lore raw

PATCH 2/7 ibmveth: do not close twice after a failed reopen

In reply to Mingming Cao

ibmveth_change_mtu(), veth_pool_store(), ibmveth_set_csum_offload() and ibmveth_set_tso() call close() and open() directly. If open() fails, NAPI is left disabled while IFF_UP stays set, so the next close() (ifdown, unregister or another reconfiguration) calls napi_disable() again and waits forever with RTNL held. Networking and shutdown hang; only a reboot recovers. ethtool -L in that state also wakes queues that have no TX buffer and dereferences NULL in ibmveth_start_xmit().

Any open() failure on those paths triggers it, for example an allocation failure on an MTU change to jumbo frames.

Track a successful open in adapter->opened. close() returns early when it is clear, and set_channels() checks it instead of IFF_UP.

The open() error-path leaks are fixed separately in net by commit af0524bf4ce1 ("ibmveth: h_free logical LAN on open-fail after register") and commit 84bec0bf0352 ("ibmveth: fix TX LTB and filter unwind on open-fail"); this patch does not depend on them.

Tested on a POWER10 LPAR with ibmveth_open() forced to fail:
'ip link set dev eth1 mtu 9000' fails, then 'ip link set dev eth1 down' returns at once and 'ip link set dev eth1 up' recovers the interface.

Fixes 860f242eb534 ("[PATCH] ibmveth change buffer pools dynamically")
---
 drivers/net/ethernet/ibm/ibmveth.c | 24 +++++++++++++++++-------
 drivers/net/ethernet/ibm/ibmveth.h |  2 ++
 2 files changed, 19 insertions(+), 7 deletions(-)
drivers/net/ethernet/ibm/ibmveth.c +17 -7
index aa2300e97081..00cee2916920 100644
@@ -738,6 +738,7 @@ static int ibmveth_open(struct net_device *netdev)
 
 	netif_tx_start_all_queues(netdev);
 
+	adapter->opened = true;
 	netdev_dbg(netdev, "open complete\n");
 
 	return 0;
@@ -779,6 +780,14 @@ static int ibmveth_close(struct net_device *netdev)
 	long lpar_rc;
 	int i;
 
+	/* change_mtu, pool sysfs, set_csum and set_tso call close() and
+	 * open() directly. If that open() fails, IFF_UP stays set and
+	 * NAPI is disabled; a second close() would hang in napi_disable().
+	 */
+	if (!adapter->opened)
+		return 0;
+	adapter->opened = false;
+
 	netdev_dbg(netdev, "close starting\n");
 
 	napi_disable(&adapter->napi);
@@ -830,10 +839,10 @@ static int ibmveth_close(struct net_device *netdev)
  *
  * @w: pointer to work_struct embedded in adapter structure
  *
- * Context: This routine acquires rtnl_mutex and disables its NAPI through
- *          ibmveth_close. It can't be called directly in a context that has
- *          already acquired rtnl_mutex or disabled its NAPI, or directly from
- *          a poll routine.
+ * Context: This routine acquires rtnl_mutex and, if the device is open,
+ *          disables its NAPI through ibmveth_close. It can't be called
+ *          directly in a context that has already acquired rtnl_mutex or
+ *          disabled its NAPI, or directly from a poll routine.
  *
  * Return: void
  */
@@ -1127,10 +1136,11 @@ static int ibmveth_set_channels(struct net_device *netdev,
 		     goal = channels->tx_count;
 	int rc, i;
 
-	/* If ndo_open has not been called yet then don't allocate, just set
-	 * desired netdev_queue's and return
+	/* If the device is not open (including a failed close/open with
+	 * IFF_UP still set) then don't allocate, just set desired
+	 * netdev_queue's and return
 	 */
-	if (!(netdev->flags & IFF_UP))
+	if (!adapter->opened)
 		return netif_set_real_num_tx_queues(netdev, goal);
 
 	/* We have IBMVETH_MAX_QUEUES netdev_queue's allocated
drivers/net/ethernet/ibm/ibmveth.h +2 -0
index d87713668ed3..3f2240823f6a 100644
@@ -172,6 +172,8 @@ struct ibmveth_adapter {
 	int rx_csum;
 	int large_send;
 	bool is_active_trunk;
+	/* Set by a successful ibmveth_open(), cleared by ibmveth_close(). */
+	bool opened;
 	unsigned int rx_buffers_per_hcall;
 
 	u64 fw_ipv6_csum_support;
Signature · 1 line
2.39.3 (Apple Git-146)
#3 lore raw

PATCH 3/7 ibmveth: disable the reset work before unregister in remove

In reply to Mingming Cao

ibmveth_remove() cancels the reset work before unregister_netdev(), but NAPI keeps running until unregister closes the device and can queue the reset again, on an interrupt enable failure, a bad free_map entry or a bad RX slot. The reset can then run on the adapter after free_netdev(), or reopen the device during unregister.

Use disable_work_sync(), which waits for a running reset and keeps the work from being queued again. A bad RX slot also makes poll spin, which can stall unregister; a later patch in this series fixes that.

It was not reproduced. Tested on a POWER10 LPAR with unbind and bind cycles under traffic.

Fixes 2c91e2319ed9 ("net: ibmveth: Reset the adapter when unexpected states are detected")
---
 drivers/net/ethernet/ibm/ibmveth.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/ethernet/ibm/ibmveth.c +1 -1
index 00cee2916920..77d3740cc52a 100644
@@ -1950,7 +1950,7 @@ static void ibmveth_remove(struct vio_dev *dev)
 	struct ibmveth_adapter *adapter = netdev_priv(netdev);
 	int i;
 
-	cancel_work_sync(&adapter->work);
+	disable_work_sync(&adapter->work);
 
 	for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
 		kobject_put(&adapter->rx_buff_pool[i].kobj);
Signature · 1 line
2.39.3 (Apple Git-146)
#4 lore raw

PATCH 4/7 ibmveth: step past bad RX correlators instead of spinning or oopsing

In reply to Mingming Cao

ibmveth_poll() mishandles a bad RX correlator from PHYP in two ways.

If harvest fails or ibmveth_rxq_get_buffer() returns NULL, poll breaks out without advancing the ring and restarts on the same slot forever. For an out-of-range correlator it also fires a WARN_ON() and schedules a reset each pass, but the reset is queued on that CPU and never runs, and the CPU stalls RCU, so RTNL holders hang too. This dates from the first commit in Fixes:, which turned the BUG_ON()s here into WARN_ON() plus a reset.

The range check also accepts a correlator naming an inactive buffer pool (pools 2 and 3 by default), whose skbuff array is NULL, so the lookup dereferences NULL in softirq. Pools became inactive with the second commit in Fixes:.

Validate the correlator in one helper that also rejects a pool with no skbuff array. On a bad slot, advance the ring, count the drop in rx_dropped and still schedule the reset, which rebuilds the pools. The correlator comes from PHYP, and with the ring advancing a burst of bad slots would WARN once per slot (and panic with panic_on_warn), so use a ratelimited netdev_err() instead. Also free the rx_copybreak skb if harvest fails there.

Add a KUnit case for harvest advancing on errors and extend the existing cases to an inactive pool; on the unfixed driver the first fails and the others oops.

Hitting either bug needs PHYP to return a bad correlator, so neither was reproduced on hardware. Tested with KUnit on qemu pseries (ppc64le), and on a POWER10 LPAR with a ping flood and MTU changes under traffic.

Fixes 2c91e2319ed9 ("net: ibmveth: Reset the adapter when unexpected states are detected")
Fixes 860f242eb534 ("[PATCH] ibmveth change buffer pools dynamically")
---
 drivers/net/ethernet/ibm/ibmveth.c | 171 ++++++++++++++++++++++++-----
 1 file changed, 143 insertions(+), 28 deletions(-)
drivers/net/ethernet/ibm/ibmveth.c +143 -28
index 77d3740cc52a..b89ce389d951 100644
@@ -443,6 +443,37 @@ static void ibmveth_free_buffer_pool(struct ibmveth_adapter *adapter,
 	}
 }
 
+/* The correlator comes back from PHYP; a bad one schedules a reset. */
+static bool ibmveth_rxq_correlator_valid(struct ibmveth_adapter *adapter,
+					 u64 correlator)
+{
+	unsigned int index = correlator & 0xffffffffUL;
+	unsigned int pool = correlator >> 32;
+
+	/* An inactive pool keeps its size but has no skbuff array. */
+	if (pool < IBMVETH_NUM_BUFF_POOLS &&
+	    index < adapter->rx_buff_pool[pool].size &&
+	    adapter->rx_buff_pool[pool].skbuff)
+		return true;
+
+	if (net_ratelimit())
+		netdev_err(adapter->netdev,
+			   "invalid RX correlator %llx, resetting\n",
+			   correlator);
+	schedule_work(&adapter->work);
+	return false;
+}
+
+static void ibmveth_rxq_no_skb(struct ibmveth_adapter *adapter,
+			       u64 correlator)
+{
+	if (net_ratelimit())
+		netdev_err(adapter->netdev,
+			   "no buffer for RX correlator %llx, resetting\n",
+			   correlator);
+	schedule_work(&adapter->work);
+}
+
 /**
  * ibmveth_remove_buffer_from_pool - remove a buffer from a pool
  * @adapter: adapter instance
@@ -451,7 +482,8 @@ static void ibmveth_free_buffer_pool(struct ibmveth_adapter *adapter,
  *
  * Return:
  * * %0       - success
- * * %-EINVAL - correlator maps to pool or index out of range
+ * * %-EINVAL - correlator maps to pool or index out of range, or to an
+ *              inactive pool
  * * %-EFAULT - pool and index map to null skb
  */
 static int ibmveth_remove_buffer_from_pool(struct ibmveth_adapter *adapter,
@@ -462,15 +494,12 @@ static int ibmveth_remove_buffer_from_pool(struct ibmveth_adapter *adapter,
 	unsigned int free_index;
 	struct sk_buff *skb;
 
-	if (WARN_ON(pool >= IBMVETH_NUM_BUFF_POOLS) ||
-	    WARN_ON(index >= adapter->rx_buff_pool[pool].size)) {
-		schedule_work(&adapter->work);
+	if (!ibmveth_rxq_correlator_valid(adapter, correlator))
 		return -EINVAL;
-	}
 
 	skb = adapter->rx_buff_pool[pool].skbuff[index];
-	if (WARN_ON(!skb)) {
-		schedule_work(&adapter->work);
+	if (!skb) {
+		ibmveth_rxq_no_skb(adapter, correlator);
 		return -EFAULT;
 	}
 
@@ -510,14 +539,23 @@ static inline struct sk_buff *ibmveth_rxq_get_buffer(struct ibmveth_adapter *ada
 	u64 correlator = adapter->rx_queue.queue_addr[adapter->rx_queue.index].correlator;
 	unsigned int pool = correlator >> 32;
 	unsigned int index = correlator & 0xffffffffUL;
+	struct sk_buff *skb;
 
-	if (WARN_ON(pool >= IBMVETH_NUM_BUFF_POOLS) ||
-	    WARN_ON(index >= adapter->rx_buff_pool[pool].size)) {
-		schedule_work(&adapter->work);
+	if (!ibmveth_rxq_correlator_valid(adapter, correlator))
 		return NULL;
-	}
 
-	return adapter->rx_buff_pool[pool].skbuff[index];
+	skb = adapter->rx_buff_pool[pool].skbuff[index];
+	if (!skb)
+		ibmveth_rxq_no_skb(adapter, correlator);
+	return skb;
+}
+
+static void ibmveth_rxq_advance(struct ibmveth_adapter *adapter)
+{
+	if (++adapter->rx_queue.index == adapter->rx_queue.num_slots) {
+		adapter->rx_queue.index = 0;
+		adapter->rx_queue.toggle = !adapter->rx_queue.toggle;
+	}
 }
 
 /**
@@ -528,6 +566,9 @@ static inline struct sk_buff *ibmveth_rxq_get_buffer(struct ibmveth_adapter *ada
  *
  * Context: called from ibmveth_poll
  *
+ * The ring advances even on error, so poll does not return to a bad
+ * slot before the scheduled reset can run.
+ *
  * Return:
  * * %0    - success
  * * other - non-zero return from ibmveth_remove_buffer_from_pool
@@ -540,15 +581,9 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter,
 
 	cor = adapter->rx_queue.queue_addr[adapter->rx_queue.index].correlator;
 	rc = ibmveth_remove_buffer_from_pool(adapter, cor, reuse);
-	if (unlikely(rc))
-		return rc;
+	ibmveth_rxq_advance(adapter);
 
-	if (++adapter->rx_queue.index == adapter->rx_queue.num_slots) {
-		adapter->rx_queue.index = 0;
-		adapter->rx_queue.toggle = !adapter->rx_queue.toggle;
-	}
-
-	return 0;
+	return rc;
 }
 
 static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx)
@@ -1468,6 +1503,7 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
 	int frames_processed = 0;
 	unsigned long lpar_rc;
 	u16 mss = 0;
+	int rc;
 
 restart_poll:
 	while (frames_processed < budget) {
@@ -1490,8 +1526,11 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
 			__sum16 iph_check = 0;
 
 			skb = ibmveth_rxq_get_buffer(adapter);
-			if (unlikely(!skb))
+			if (unlikely(!skb)) {
+				ibmveth_rxq_advance(adapter);
+				netdev->stats.rx_dropped++;
 				break;
+			}
 
 			/* if the large packet bit is set in the rx queue
 			 * descriptor, the mss will be written by PHYP eight
@@ -1515,12 +1554,19 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
 				if (rx_flush)
 					ibmveth_flush_buffer(skb->data,
 						length + offset);
-				if (unlikely(ibmveth_rxq_harvest_buffer(adapter, true)))
+				rc = ibmveth_rxq_harvest_buffer(adapter, true);
+				if (unlikely(rc)) {
+					dev_kfree_skb_any(new_skb);
+					netdev->stats.rx_dropped++;
 					break;
+				}
 				skb = new_skb;
 			} else {
-				if (unlikely(ibmveth_rxq_harvest_buffer(adapter, false)))
+				rc = ibmveth_rxq_harvest_buffer(adapter, false);
+				if (unlikely(rc)) {
+					netdev->stats.rx_dropped++;
 					break;
+				}
 				skb_reserve(skb, offset);
 			}
 
@@ -2200,8 +2246,7 @@ static void ibmveth_reset_kunit(struct work_struct *w)
  * @test: pointer to kunit structure
  *
  * Tests the error returns from ibmveth_remove_buffer_from_pool.
- * ibmveth_remove_buffer_from_pool also calls WARN_ON, so dmesg should be
- * checked to see that these warnings happened.
+ * Each error also logs a ratelimited netdev_err.
  *
  * Return: void
  */
@@ -2210,6 +2255,7 @@ static void ibmveth_remove_buffer_from_pool_test(struct kunit *test)
 	struct ibmveth_adapter *adapter = kunit_kzalloc(test, sizeof(*adapter), GFP_KERNEL);
 	struct ibmveth_buff_pool *pool;
 	u64 correlator;
+	int ret;
 
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adapter);
 
@@ -2233,6 +2279,13 @@ static void ibmveth_remove_buffer_from_pool_test(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, -EINVAL, ibmveth_remove_buffer_from_pool(adapter, correlator, false));
 	KUNIT_EXPECT_EQ(test, -EINVAL, ibmveth_remove_buffer_from_pool(adapter, correlator, true));
 
+	/* Pool 2 is in range but has no skbuff array, like an inactive pool. */
+	correlator = ((u64)2 << 32) | 0;
+	ret = ibmveth_remove_buffer_from_pool(adapter, correlator, false);
+	KUNIT_EXPECT_EQ(test, -EINVAL, ret);
+	ret = ibmveth_remove_buffer_from_pool(adapter, correlator, true);
+	KUNIT_EXPECT_EQ(test, -EINVAL, ret);
+
 	correlator = (u64)0 | 0;
 	pool->skbuff[0] = NULL;
 	KUNIT_EXPECT_EQ(test, -EFAULT, ibmveth_remove_buffer_from_pool(adapter, correlator, false));
@@ -2245,9 +2298,8 @@ static void ibmveth_remove_buffer_from_pool_test(struct kunit *test)
  * ibmveth_rxq_get_buffer_test - unit test for ibmveth_rxq_get_buffer
  * @test: pointer to kunit structure
  *
- * Tests ibmveth_rxq_get_buffer. ibmveth_rxq_get_buffer also calls WARN_ON for
- * the NULL returns, so dmesg should be checked to see that these warnings
- * happened.
+ * Tests ibmveth_rxq_get_buffer. Each NULL return also logs a ratelimited
+ * netdev_err.
  *
  * Return: void
  */
@@ -2284,6 +2336,10 @@ static void ibmveth_rxq_get_buffer_test(struct kunit *test)
 	adapter->rx_queue.queue_addr[0].correlator = (u64)0 << 32 | adapter->rx_buff_pool[0].size;
 	KUNIT_EXPECT_PTR_EQ(test, NULL, ibmveth_rxq_get_buffer(adapter));
 
+	/* Pool 2 is in range but has no skbuff array, like an inactive pool. */
+	adapter->rx_queue.queue_addr[0].correlator = (u64)2 << 32 | 0;
+	KUNIT_EXPECT_PTR_EQ(test, NULL, ibmveth_rxq_get_buffer(adapter));
+
 	pool->skbuff[0] = skb;
 	adapter->rx_queue.queue_addr[0].correlator = (u64)0 << 32 | 0;
 	KUNIT_EXPECT_PTR_EQ(test, skb, ibmveth_rxq_get_buffer(adapter));
@@ -2291,9 +2347,68 @@ static void ibmveth_rxq_get_buffer_test(struct kunit *test)
 	flush_work(&adapter->work);
 }
 
+/**
+ * ibmveth_rxq_harvest_buffer_test - unit test for ibmveth_rxq_harvest_buffer
+ * @test: pointer to kunit structure
+ *
+ * A bad correlator must still advance the RX ring, wrapping and flipping
+ * the toggle at the end. This covers the harvest path; the advance after
+ * ibmveth_rxq_get_buffer() fails in ibmveth_poll() is not tested here.
+ *
+ * Return: void
+ */
+static void ibmveth_rxq_harvest_buffer_test(struct kunit *test)
+{
+	struct ibmveth_adapter *adapter;
+	struct ibmveth_buff_pool *pool;
+	int ret;
+
+	adapter = kunit_kzalloc(test, sizeof(*adapter), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adapter);
+
+	INIT_WORK(&adapter->work, ibmveth_reset_kunit);
+
+	adapter->rx_queue.num_slots = 2;
+	adapter->rx_queue.index = 0;
+	adapter->rx_queue.toggle = 1;
+	adapter->rx_queue.queue_addr =
+		kunit_kcalloc(test, 2, sizeof(struct ibmveth_rx_q_entry),
+			      GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adapter->rx_queue.queue_addr);
+
+	/* Set sane values for buffer pools */
+	for (int i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
+		ibmveth_init_buffer_pool(&adapter->rx_buff_pool[i], i,
+					 pool_count[i], pool_size[i],
+					 pool_active[i]);
+
+	pool = &adapter->rx_buff_pool[0];
+	pool->skbuff = kunit_kcalloc(test, pool->size, sizeof(void *),
+				     GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool->skbuff);
+
+	/* Slot 0: pool out of range. Slot 1: valid, but no skb. */
+	adapter->rx_queue.queue_addr[0].correlator =
+		(u64)IBMVETH_NUM_BUFF_POOLS << 32 | 0;
+	adapter->rx_queue.queue_addr[1].correlator = (u64)0 << 32 | 0;
+
+	ret = ibmveth_rxq_harvest_buffer(adapter, true);
+	KUNIT_EXPECT_EQ(test, -EINVAL, ret);
+	KUNIT_EXPECT_EQ(test, 1ULL, adapter->rx_queue.index);
+	KUNIT_EXPECT_EQ(test, 1ULL, adapter->rx_queue.toggle);
+
+	ret = ibmveth_rxq_harvest_buffer(adapter, true);
+	KUNIT_EXPECT_EQ(test, -EFAULT, ret);
+	KUNIT_EXPECT_EQ(test, 0ULL, adapter->rx_queue.index);
+	KUNIT_EXPECT_EQ(test, 0ULL, adapter->rx_queue.toggle);
+
+	flush_work(&adapter->work);
+}
+
 static struct kunit_case ibmveth_test_cases[] = {
 	KUNIT_CASE(ibmveth_remove_buffer_from_pool_test),
 	KUNIT_CASE(ibmveth_rxq_get_buffer_test),
+	KUNIT_CASE(ibmveth_rxq_harvest_buffer_test),
 	{}
 };
 
Signature · 1 line
2.39.3 (Apple Git-146)
#5 lore raw

Re: [PATCH net-next 4/7] ibmveth: step past bad RX correlators instead of spinning or oopsing

Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you…

Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The new bad-slot handling in ibmveth_poll() (drivers/net/ethernet/ibm/ibmveth.c) neither charges consumed error…

Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but:
ibmveth_poll() uses the PHYP-written descriptor length…

--

ibmveth: step past bad RX correlators instead of spinning or oopsing

This patch adds a helper that validates the RX correlator returned by PHYP. The helper also rejects inactive pools that have no skbuff array. On a bad slot, the RX ring now advances, the drop is counted in rx_dropped, and a reset is scheduled. WARN_ON() is replaced with a ratelimited netdev_err(), and the KUnit cases are extended to cover these paths.

Quoted 4 lines
drivers/net/ethernet/ibm/ibmveth.c +0 -0
index 77d3740cc52a6..b89ce389d951f 100644

[ ... ]

Quoted 9 lines
drivers/net/ethernet/ibm/ibmveth.c +1 -0
@@ -1468,6 +1503,7 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
 	int frames_processed = 0;
 	unsigned long lpar_rc;
 	u16 mss = 0;
+	int rc;
 
 restart_poll:
 	while (frames_processed < budget) {

drivers/net/ethernet/ibm/ibmveth.c static int ibmveth_poll(struct napi_struct *napi, int budget) · line 1533

Quoted 9 lines
drivers/net/ethernet/ibm/ibmveth.c +4 -1
@@ -1490,8 +1526,11 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
 			__sum16 iph_check = 0;
 
 			skb = ibmveth_rxq_get_buffer(adapter);
-			if (unlikely(!skb))
+			if (unlikely(!skb)) {
+				ibmveth_rxq_advance(adapter);
+				netdev->stats.rx_dropped++;
 				break;
+			}

[Severity: Low]
The ring now moves past the bad slot. Should this consumed entry also count against the NAPI budget, and should the loop continue here rather than break?

frames_processed is not incremented here or in the two harvest error branches below. The break goes to the tail of ibmveth_poll():

    ibmveth_replenish_task(adapter);

    if (frames_processed == budget)
        goto out;

    if (!napi_complete_done(napi, frames_processed))
        goto out;

    lpar_rc = h_vio_signal(adapter->vdev->unit_address, VIO_IRQ_ENABLE);
    ...
    if (ibmveth_rxq_pending_buffer(adapter) && napi_schedule(napi)) {
        lpar_rc = h_vio_signal(adapter->vdev->unit_address,
                               VIO_IRQ_DISABLE);
        goto restart_poll;
    }

With a run of N bad correlators, one poll call would then do N replenish passes and 2N hcalls but report 0 work. This all runs on the CPU where schedule_work() has just queued the reset.

A bad correlator never decrements pool->available. That means the burst is only bounded by how many buffers are posted to PHYP, about 1024 with the default active pools.

Documentation/networking/napi.rst says the budget "limits how many packets driver can process in a single poll". Would it bound this work to count the consumed error descriptors in frames_processed and use continue instead of break?

Quoted 14 lines
drivers/net/ethernet/ibm/ibmveth.c +5 -1
 			/* if the large packet bit is set in the rx queue
 			 * descriptor, the mss will be written by PHYP eight
@@ -1515,12 +1554,19 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
 				if (rx_flush)
 					ibmveth_flush_buffer(skb->data,
 						length + offset);
-				if (unlikely(ibmveth_rxq_harvest_buffer(adapter, true)))
+				rc = ibmveth_rxq_harvest_buffer(adapter, true);
+				if (unlikely(rc)) {
+					dev_kfree_skb_any(new_skb);
+					netdev->stats.rx_dropped++;
 					break;
+				}

drivers/net/ethernet/ibm/ibmveth.c static int ibmveth_poll(struct napi_struct *napi, int budget) · line 1571

Quoted 10 lines
drivers/net/ethernet/ibm/ibmveth.c +4 -1
 				skb = new_skb;
 			} else {
-				if (unlikely(ibmveth_rxq_harvest_buffer(adapter, false)))
+				rc = ibmveth_rxq_harvest_buffer(adapter, false);
+				if (unlikely(rc)) {
+					netdev->stats.rx_dropped++;
 					break;
+				}
 				skb_reserve(skb, offset);
 			}

[Severity: Medium]
This isn't a bug introduced by this patch, but the correlator from PHYP is now validated. Should the length and offset from the same descriptor be checked as well?

ibmveth_rxq_frame_length() returns the raw be32 length as an int. ibmveth_rxq_frame_offset() can return values up to IBMVETH_RXQ_OFF_MASK (0xFFFF). ibmveth_poll() does not check either one against the source pool's buff_size, which is 512 bytes for pool 0.

The copybreak branch does this:

    skb_copy_to_linear_data(new_skb,
                            skb->data + offset,
                            length);

If offset is close to 0xFFFF, can this read up to about 64 KiB past the RX buffer and then hand that data up through napi_gro_receive()?

The non-copybreak branch does this:

    skb_reserve(skb, offset);
    ...
    skb_put(skb, length);

This path has no tailroom check. Could an oversized or negative length reach skb_over_panic() -> BUG() in softirq?

Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/cover.1790991039.git.mmc%40linux.ibm.com
#6 lore raw

PATCH 5/7 ibmveth: release the pool kobjects when probe fails

In reply to Mingming Cao

If register_netdev() fails in ibmveth_probe(), for example with -EINTR when the binding task is killed, probe frees the netdev but leaves the pool%d kobjects embedded in it registered in sysfs. Reading /sys/devices/vio/<unit>/pool0/num is then a use-after-free, and the next probe cannot add pool0.

Put the kobjects before freeing the netdev, as ibmveth_remove() does, on this path and on the netif_set_real_num_tx_queues() one.

As in remove(), the kobjects have no release(), so with CONFIG_DEBUG_KOBJECT_RELEASE their deferred cleanup, including removing the sysfs files, can still run after the free.

Tested on a POWER10 LPAR with register_netdev() forced to fail with -EINTR: no pool%d directories remain and the device binds again.

Fixes 860f242eb534 ("[PATCH] ibmveth change buffer pools dynamically")
---
 drivers/net/ethernet/ibm/ibmveth.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
drivers/net/ethernet/ibm/ibmveth.c +8 -4
index b89ce389d951..d5ba2f2650d0 100644
@@ -1965,8 +1965,7 @@ static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
 	if (rc) {
 		netdev_dbg(netdev, "failed to set number of tx queues rc=%d\n",
 			   rc);
-		free_netdev(netdev);
-		return rc;
+		goto err_put_pools;
 	}
 	adapter->tx_ltb_size = PAGE_ALIGN(IBMVETH_MAX_TX_BUF_SIZE);
 	for (i = 0; i < IBMVETH_MAX_QUEUES; i++)
@@ -1981,13 +1980,18 @@ static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
 
 	if (rc) {
 		netdev_dbg(netdev, "failed to register netdev rc=%d\n", rc);
-		free_netdev(netdev);
-		return rc;
+		goto err_put_pools;
 	}
 
 	netdev_dbg(netdev, "registered\n");
 
 	return 0;
+
+err_put_pools:
+	for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
+		kobject_put(&adapter->rx_buff_pool[i].kobj);
+	free_netdev(netdev);
+	return rc;
 }
 
 static void ibmveth_remove(struct vio_dev *dev)
Signature · 1 line
2.39.3 (Apple Git-146)
#7 lore raw

Re: [PATCH net-next 5/7] ibmveth: release the pool kobjects when probe fails

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

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

Critical: 0 · High: 0 · Medium: 1 · Low: 0

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).

- [Medium] On the new err_put_pools path in ibmveth_probe() (drivers/net/ethernet/ibm/ibmveth.c), the code calls kobject_put() on…

--

ibmveth: release the pool kobjects when probe fails

This commit adds an err_put_pools unwind label to ibmveth_probe(). The label calls kobject_put() on each rx_buff_pool[i].kobj before free_netdev(). It is used when either netif_set_real_num_tx_queues() or register_netdev() fails.

Quoted 4 lines
drivers/net/ethernet/ibm/ibmveth.c +0 -0
index b89ce389d951f..d5ba2f2650d0f 100644

[ ... ]

Quoted 9 lines
drivers/net/ethernet/ibm/ibmveth.c +1 -2
@@ -1981,13 +1980,18 @@ static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
 
 	if (rc) {
 		netdev_dbg(netdev, "failed to register netdev rc=%d\n", rc);
-		free_netdev(netdev);
-		return rc;
+		goto err_put_pools;
 	}
 

drivers/net/ethernet/ibm/ibmveth.c static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id) · line 1995

Quoted 10 lines
drivers/net/ethernet/ibm/ibmveth.c +6 -0
 	netdev_dbg(netdev, "registered\n");
 
 	return 0;
+
+err_put_pools:
+	for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
+		kobject_put(&adapter->rx_buff_pool[i].kobj);
+	free_netdev(netdev);
+	return rc;
 }

[Severity: Medium]
With CONFIG_DEBUG_KOBJECT_RELEASE=y, can this path still free the pool kobjects while their cleanup is pending?

The kobjects are embedded in netdev_priv(netdev), and ktype_veth_pool has no release() callback:

static struct kobj_type ktype_veth_pool = {
.release = NULL,
.sysfs_ops = &veth_pool_ops,
...
};

On that config, kobject_release() defers the cleanup to a delayed_work that is embedded in the kobject itself:

lib/kobject.c:kobject_release() {
	...
	INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);

	schedule_delayed_work(&kobj->release, delay);
	...
}

free_netdev() then frees the adapter while that work is still armed. The work runs 1 to 4 seconds later. kobject_cleanup() then works on freed
memory: it reads kobj->state_in_sysfs and kobj->name, calls __kobject_del(), and drops the parent reference.

Until the work runs, the pool%d active, num and size files under /sys/devices/vio/<unit>/ still exist. A read or write goes through veth_pool_show() or veth_pool_store(). Both use container_of() to reach the freed pool, adapter and netdev.

A re-probe in that window could also fail to add pool0. That is the same failure this commit is meant to fix.

The commit message mentions this and refers to ibmveth_remove(), which uses the same put-then-free pattern. kobject_cleanup() also reports this ktype as one that "does not have a release() function, it is broken and must be fixed".

Calling kobject_del() before kobject_put() would remove the sysfs files synchronously. However, the delayed_work inside the freed adapter would still be armed.

Would it make sense to give the pool kobjects their own lifetime? One option is to allocate them separately with a real release() callback.

This also stays the same later in the series. "ibmveth: wait for in-flight transmits in ibmveth_close()" leaves both err_put_pools and the NULL release in place.

Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/cover.1790991039.git.mmc%40linux.ibm.com
#8 lore raw

Re: [PATCH net-next 5/7] ibmveth: release the pool kobjects when probe fails

Quoted 89 lines · On 10/3/26 7:12 PM, [email protected] wrote:

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

Critical: 0 · High: 0 · Medium: 1 · Low: 0

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).

- [Medium] On the new err_put_pools path in ibmveth_probe() (drivers/net/ethernet/ibm/ibmveth.c), the code calls kobject_put() on…

--

ibmveth: release the pool kobjects when probe fails

This commit adds an err_put_pools unwind label to ibmveth_probe(). The label calls kobject_put() on each rx_buff_pool[i].kobj before free_netdev(). It is used when either netif_set_real_num_tx_queues() or register_netdev() fails.

Quoted 4 lines
drivers/net/ethernet/ibm/ibmveth.c +0 -0
index b89ce389d951f..d5ba2f2650d0f 100644

[ ... ]

Quoted 9 lines
drivers/net/ethernet/ibm/ibmveth.c +1 -2
@@ -1981,13 +1980,18 @@ static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
 
  	if (rc) {
  		netdev_dbg(netdev, "failed to register netdev rc=%d\n", rc);
-		free_netdev(netdev);
-		return rc;
+		goto err_put_pools;
  	}
 

drivers/net/ethernet/ibm/ibmveth.c static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id) · line 1995

Quoted 10 lines
drivers/net/ethernet/ibm/ibmveth.c +6 -0
  	netdev_dbg(netdev, "registered\n");
 
  	return 0;
+
+err_put_pools:
+	for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
+		kobject_put(&adapter->rx_buff_pool[i].kobj);
+	free_netdev(netdev);
+	return rc;
  }

[Severity: Medium]
With CONFIG_DEBUG_KOBJECT_RELEASE=y, can this path still free the pool kobjects while their cleanup is pending?

The kobjects are embedded in netdev_priv(netdev), and ktype_veth_pool has no release() callback:

static struct kobj_type ktype_veth_pool = {
.release = NULL,
.sysfs_ops = &veth_pool_ops,
...
};

On that config, kobject_release() defers the cleanup to a delayed_work that is embedded in the kobject itself:

lib/kobject.c:kobject_release() {
	...
	INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);

	schedule_delayed_work(&kobj->release, delay);
	...
}

free_netdev() then frees the adapter while that work is still armed. The work runs 1 to 4 seconds later. kobject_cleanup() then works on freed
memory: it reads kobj->state_in_sysfs and kobj->name, calls __kobject_del(), and drops the parent reference.

Until the work runs, the pool%d active, num and size files under /sys/devices/vio/<unit>/ still exist. A read or write goes through veth_pool_show() or veth_pool_store(). Both use container_of() to reach the freed pool, adapter and netdev.

A re-probe in that window could also fail to add pool0. That is the same failure this commit is meant to fix.

The commit message mentions this and refers to ibmveth_remove(), which uses the same put-then-free pattern. kobject_cleanup() also reports this ktype as one that "does not have a release() function, it is broken and must be fixed".

Quoted 6 lines

Calling kobject_del() before kobject_put() would remove the sysfs files synchronously. However, the delayed_work inside the freed adapter would still be armed.

Would it make sense to give the pool kobjects their own lifetime? One option is to allocate them separately with a real release() callback.

Yes. v2 adds a release() for the pool kobjects and waits for it before free_netdev() in both probe and remove():

|https://lore.kernel.org/netdev/[email protected]/|

pw-bot: cr

Thanks,

Mingming

#9 lore raw

PATCH 6/7 ibmveth: return the error when set_channels cannot add TX queues

In reply to Mingming Cao

When ibmveth_set_channels() cannot allocate a TX buffer for a new queue, it falls back to the old queue count, and the successful netif_set_real_num_tx_queues() call then overwrites rc. ethtool -L reports success while the queue count is unchanged.

Return the allocation error when the fallback succeeds.

Tested on a POWER10 LPAR with the TX buffer allocation forced to fail:
ethtool -L tx 8 now fails with -ENOMEM and the device keeps its four queues and passes traffic.

Fixes 10c2aba89cc0 ("ibmveth: Ethtool set queue support")
---
 drivers/net/ethernet/ibm/ibmveth.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/net/ethernet/ibm/ibmveth.c +4 -1
index d5ba2f2650d0..132c740b5844 100644
@@ -1169,7 +1169,7 @@ static int ibmveth_set_channels(struct net_device *netdev,
 	struct ibmveth_adapter *adapter = netdev_priv(netdev);
 	unsigned int old = netdev->real_num_tx_queues,
 		     goal = channels->tx_count;
-	int rc, i;
+	int rc, i, alloc_rc = 0;
 
 	/* If the device is not open (including a failed close/open with
 	 * IFF_UP still set) then don't allocate, just set desired
@@ -1195,6 +1195,7 @@ static int ibmveth_set_channels(struct net_device *netdev,
 		/* if something goes wrong, free everything we just allocated */
 		netdev_err(netdev, "Failed to allocate more tx queues, returning to %d queues\n",
 			   old);
+		alloc_rc = rc;
 		goal = old;
 		old = i;
 		break;
@@ -1205,6 +1206,8 @@ static int ibmveth_set_channels(struct net_device *netdev,
 			   old);
 		goal = old;
 		old = i;
+	} else if (alloc_rc) {
+		rc = alloc_rc;
 	}
 	/* Free any that are no longer needed */
 	for (i = old; i > goal; i--) {
Signature · 1 line
2.39.3 (Apple Git-146)
#10 lore raw

PATCH 7/7 ibmveth: wait for in-flight transmits in ibmveth_close()

In reply to Mingming Cao

ibmveth_close() frees the TX buffers after netif_tx_stop_all_queues(), which does not wait for an ibmveth_start_xmit() already running on another CPU. That transmit can copy into a freed buffer and hand PHYP a stale DMA address.

ifdown and the reset work go through dev_close(), which drains TX first, but MTU, csum/TSO and buffer pool changes call ibmveth_close() directly while traffic flows.

Use netif_tx_disable(), which waits for running transmits.

The race was not reproduced. Tested on a POWER10 LPAR with MTU changes during a ping flood, with no warnings.

Fixes d6832ca48d8a ("ibmveth: Copy tx skbs into a premapped buffer")
---
 drivers/net/ethernet/ibm/ibmveth.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/ethernet/ibm/ibmveth.c +1 -1
index 132c740b5844..5acd5e49b0ad 100644
@@ -827,7 +827,7 @@ static int ibmveth_close(struct net_device *netdev)
 
 	napi_disable(&adapter->napi);
 
-	netif_tx_stop_all_queues(netdev);
+	netif_tx_disable(netdev);
 
 	h_vio_signal(adapter->vdev->unit_address, VIO_IRQ_DISABLE);
 
Signature · 1 line
2.39.3 (Apple Git-146)