[PATCH net-next v4 14/14] ibmveth: Fix MQ RX poll and shutdown hangs after queue resize
From: Mingming Cao <hidden>
Date: 2026-07-31 00:49:28
Also in:
linuxppc-dev
Subsystem:
ibm power virtual ethernet device driver, linux for powerpc (32-bit and 64-bit), networking drivers, the rest · Maintainers:
Nick Child, Madhavan Srinivasan, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
After aggressive ethtool -L cycling, PHYP can leave a VALID RX descriptor with a correlator that no longer matches the per-queue buffer pools. Poll treated this as fatal: ibmveth_rxq_get_buffer() WARNed and returned NULL without advancing the ring, then restart_poll retried the same slot forever. Advance past bad correlators instead of spinning: validate correlators without WARN_ON, skip invalid slots in poll (count as invalid_buffers), and advance the RX ring when remove_buffer_from_pool cannot map the correlator. Rate-limit the bad correlator message. Complete NAPI when the interface is down or napi_disable is pending so close/quiesce can finish. Do not restart_poll in that window. Close keeps hypervisor IRQ disable before napi_disable (via cleanup_rx_interrupts() / related cleanup helpers). Also validate descriptor length against skb tailroom before skb_put(), and after napi_complete_done() on the budget-exhausted shutdown path return a value less than budget so NAPI does not immediately reschedule. Signed-off-by: Mingming Cao <redacted> Reviewed-by: Dave Marquardt <redacted> Tested-by: Shaik Abdulla <redacted> --- Changes in v4: - Skip invalid correlators in poll instead of spinning (no WARN_ON; rate-limited message; advance ring when harvest cannot map). - Validate descriptor length against skb tailroom before skb_put(). - After napi_complete_done() on the budget-exhausted shutdown path, return a value less than budget so NAPI does not immediately reschedule. - Align KUnit comments with correlator validation (no WARN_ON). drivers/net/ethernet/ibm/ibmveth.c | 109 ++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 24 deletions(-)
diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c
index c99d8e8be7b3..09e06d86701a 100644
--- a/drivers/net/ethernet/ibm/ibmveth.c
+++ b/drivers/net/ethernet/ibm/ibmveth.c@@ -1376,6 +1376,24 @@ ibmveth_free_single_rx_queue(struct ibmveth_adapter *adapter, int queue_idx) netdev_dbg(adapter->netdev, "Freed queue %d resources\n", queue_idx); } +static bool ibmveth_rxq_correlator_valid(struct ibmveth_adapter *adapter, + int queue_index, u64 correlator) +{ + unsigned int pool = correlator >> 32; + unsigned int index = correlator & 0xffffffffUL; + + return pool < IBMVETH_NUM_BUFF_POOLS && + index < adapter->rx_buff_pool[queue_index][pool].size; +} + +static void ibmveth_rxq_advance(struct ibmveth_rx_q *rxq) +{ + if (++rxq->index == rxq->num_slots) { + rxq->index = 0; + rxq->toggle = !rxq->toggle; + } +} + /** * ibmveth_remove_buffer_from_pool - remove a buffer from a pool * @adapter: adapter instance
@@ -1397,17 +1415,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[queue_index][pool].size)) { - schedule_work(&adapter->work); + if (!ibmveth_rxq_correlator_valid(adapter, queue_index, correlator)) return -EINVAL; - } skb = adapter->rx_buff_pool[queue_index][pool].skbuff[index]; - if (WARN_ON(!skb)) { - schedule_work(&adapter->work); + if (!skb) return -EFAULT; - } /* if we are going to reuse the buffer then keep the pointers around * but mark index as available. replenish will see the skb pointer and
@@ -1452,11 +1465,8 @@ ibmveth_rxq_get_buffer(struct ibmveth_adapter *adapter, unsigned int pool = correlator >> 32; unsigned int index = correlator & 0xffffffffUL; - if (WARN_ON(pool >= IBMVETH_NUM_BUFF_POOLS) || - WARN_ON(index >= adapter->rx_buff_pool[queue_index][pool].size)) { - schedule_work(&adapter->work); + if (!ibmveth_rxq_correlator_valid(adapter, queue_index, correlator)) return NULL; - } return adapter->rx_buff_pool[queue_index][pool].skbuff[index]; }
@@ -1483,14 +1493,15 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter, cor = rxq->queue_addr[rxq->index].correlator; rc = ibmveth_remove_buffer_from_pool(adapter, cor, queue_index, reuse); - if (unlikely(rc)) + if (unlikely(rc)) { + if (rc == -EINVAL || rc == -EFAULT) + goto advance; return rc; - - if (++rxq->index == rxq->num_slots) { - rxq->index = 0; - rxq->toggle = !rxq->toggle; } +advance: + ibmveth_rxq_advance(rxq); + return 0; }
@@ -3092,11 +3103,19 @@ static int ibmveth_poll(struct napi_struct *napi, int budget) if (WARN_ON(queue_index < 0 || queue_index >= adapter->num_rx_queues)) return 0; + if (!netif_running(netdev) || napi_disable_pending(napi)) { + napi_complete_done(napi, 0); + return 0; + } + if (adapter->rx_qstats) adapter->rx_qstats[queue_index].polls++; restart_poll: while (frames_processed < budget) { + if (!netif_running(netdev) || napi_disable_pending(napi)) + break; + if (!ibmveth_rxq_pending_buffer(adapter, queue_index)) break;
@@ -3126,8 +3145,45 @@ static int ibmveth_poll(struct napi_struct *napi, int budget) __sum16 iph_check = 0; skb = ibmveth_rxq_get_buffer(adapter, queue_index); - if (unlikely(!skb)) - break; + if (unlikely(!skb)) { + if (net_ratelimit()) + netdev_err(netdev, + "bad correlator on queue %d, skipping slot\n", + queue_index); + if (adapter->rx_qstats) + adapter->rx_qstats[queue_index] + .invalid_buffers++; + else + adapter->rx_invalid_buffer++; + rc = ibmveth_rxq_harvest_buffer(adapter, + queue_index, + true); + if (unlikely(rc)) + break; + continue; + } + + if (unlikely((unsigned int)offset + + (unsigned int)length > + skb_tailroom(skb))) { + if (net_ratelimit()) + netdev_err(netdev, + "RX frame %u+%u exceeds buffer %u on queue %d, dropping\n", + offset, length, + skb_tailroom(skb), + queue_index); + if (adapter->rx_qstats) + adapter->rx_qstats[queue_index] + .invalid_buffers++; + else + adapter->rx_invalid_buffer++; + rc = ibmveth_rxq_harvest_buffer(adapter, + queue_index, + true); + if (unlikely(rc)) + break; + continue; + } /* if the large packet bit is set in the rx queue * descriptor, the mss will be written by PHYP eight
@@ -3206,8 +3262,14 @@ static int ibmveth_poll(struct napi_struct *napi, int budget) ibmveth_replenish_task(adapter, queue_index); - if (frames_processed == budget) + if (frames_processed == budget) { + if (!netif_running(netdev) || napi_disable_pending(napi)) { + napi_complete_done(napi, frames_processed); + /* After complete_done, must not return full budget. */ + return frames_processed ? frames_processed - 1 : 0; + } goto out; + } if (!napi_complete_done(napi, frames_processed)) goto out;
@@ -3225,6 +3287,8 @@ static int ibmveth_poll(struct napi_struct *napi, int budget) } if (ibmveth_rxq_pending_buffer(adapter, queue_index) && + netif_running(netdev) && + !napi_disable_pending(napi) && napi_schedule(napi)) { lpar_rc = ibmveth_disable_irq(adapter, queue_index); WARN_ON(lpar_rc != H_SUCCESS);
@@ -4014,8 +4078,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. + * Bad correlators return -EINVAL/-EFAULT (no WARN_ON). * * Return: void */
@@ -4071,9 +4134,7 @@ 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 invalid correlator returns NULL without WARN. * * Return: void */
--
2.50.1 (Apple Git-155)