NAPI polling on hns3 is driven by hardware RX interrupts. Under memory pressure, RX skb and frag allocations fail, packets are no longer reaped from the hardware rx ring. Once the ring fills, the hardware stops generating RX interrupts and NAPI is never scheduled again -- even after memory becomes available, leaving the interface deadlocked. Break the deadlock by adding a software recovery path that re-arms NAPI via a 250ms delayed_work, independent of hardware interrupts. A per-ring bitmap tracks which RX rings are in OOM state; the work function schedules NAPI for the vector backing each affected ring. OOM state is marked at every RX allocation failure site and cleared when the delayed_work fires. This supersedes the busy-poll recovery from commit 68752b24f51a ("net: hns3: schedule the polling again when allocation fails"), which returned budget on allocation failure to keep NAPI polling and caused CPU spinning under prolonged memory pressure. Now hns3_clean_rx_ring() returns recv_pkts instead of budget on allocation failure, letting NAPI exit and the delayed_work take over. The exit refill is still attempted, so the descriptors cleaned by this poll stay armed. Add rx_oom_cnt ethtool counter, incremented once per OOM event (on the 0 -> 1 bitmap transition) for diagnostics. It counts OOM episodes rather than individual allocation failures, so it is not the standard rx-alloc-fail counter. The bitmap is allocated in hns3_init_all_ring() and freed in hns3_uninit_all_ring(). It is allocated after every ring is set up, so a failure inside hns3_init_all_ring() leaves nothing behind, and it is reused because hns3_set_ringparam() calls hns3_init_all_ring() without hns3_uninit_all_ring(). The delayed_work is cancelled in hns3_nic_uninit_vector_data(), which every teardown path reaches before the bitmap is freed. hns3_nic_net_down() does not cancel the work, so a pending work may still run after the interface goes down. If it runs while DOWN is set, it zeroes the bitmap and returns without scheduling NAPI. If the interface is brought back up within the 250ms window, it sees the stale bits with DOWN cleared and schedules NAPI for those rings once, ending in a single extra poll round with no packet to clean. Both outcomes are harmless. Fixes: 68752b24f51a ("net: hns3: schedule the polling again when allocation fails") Signed-off-by: Jijie Shao --- Changes in v4: - Allocate rx_ring_oom_bm in hns3_init_all_ring() only after every ring has been set up, and free it in hns3_uninit_all_ring(). This fixes the leak and the NULL bitmap dereference in the hns3_set_ringparam() path, which calls hns3_init_all_ring() without a matching hns3_uninit_all_ring(), and reuses the existing allocation on a ring resize. - Cancel the delayed work in hns3_nic_uninit_vector_data() instead of hns3_nic_net_down(), so every teardown path cancels it before the bitmap is freed. The self-test and external loopback paths set DOWN without going through hns3_nic_net_down(). - Drop the cancel and bitmap_zero from hns3_nic_net_down(). A pending work that runs while DOWN is set zeroes the bitmap and exits, and one that runs after the interface is brought back up only schedules a single extra poll round. - Always attempt the exit refill in hns3_clean_rx_ring(), so the descriptors cleaned by this poll stay armed and the head pointer is written as before. - Document why rx_oom_cnt is not the standard rx-alloc-fail counter. v3: https://lore.kernel.org/all/20261030095633.377289-4-shaojijie@huawei.com/ Changes in v3: - Rework the OOM bitmap from per-vector to per-ring, indexed by ring->queue_index, so each ring's OOM event is counted exactly once per oom_task firing cycle regardless of vector sharing. - Add cancel_delayed_work_sync(&priv->oom_task) to hns3_nic_net_down(), placed after hns3_disable_irqs_and_tqps(), eliminating the stop/open window where a preempted worker could clear a bit newly set by NAPI on the reopened interface. - Remove the hns3_ring_set_oom_state() call from hns3_handle_rx_copybreak(): napi_alloc_frag() failure returns -ENOMEM but the packet is still delivered via the original page, so it should not drive the OOM recovery work. v2: https://lore.kernel.org/all/20261021121426.3096838-3-shaojijie@huawei.com/ Changes in v2: - Clear per-vector OOM bitmap when device is DOWN to avoid spurious napi_schedule() on next up event. - Move rx_oom_cnt accounting to hns3_ring_set_oom_state() with test_and_set_bit() for per-event counting. - Expand changelog: reference commit 68752b24f51a, explain hns3_clean_rx_ring() return value change, document rx_oom_cnt per-event counting semantics. v1: https://lore.kernel.org/all/20260915132434.1141742-3-shaojijie@huawei.com/ --- .../net/ethernet/hisilicon/hns3/hns3_enet.c | 61 ++++++++++++++++++- .../net/ethernet/hisilicon/hns3/hns3_enet.h | 18 ++++++ .../ethernet/hisilicon/hns3/hns3_ethtool.c | 1 + 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c index dbf32c00bb7f..90f0a8ac9295 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c @@ -78,6 +78,8 @@ module_param(page_pool_enabled, bool, 0400); #define HNS3_MIN_TX_LEN 33U #define HNS3_MIN_TUN_PKT_LEN 65U +#define HNS3_OOM_POLL_INTERVAL_MS 250 + /* hns3_pci_tbl - PCI Device ID Table * * Last entry must be all 0s @@ -4162,6 +4164,7 @@ static int hns3_add_frag(struct hns3_enet_ring *ring) if (unlikely(!new_skb)) { hns3_rl_err(ring_to_netdev(ring), "alloc rx fraglist skb fail\n"); + hns3_ring_set_oom_state(ring); return -ENXIO; } @@ -4451,6 +4454,35 @@ static int hns3_handle_rx_bd(struct hns3_enet_ring *ring) return 0; } +static void hns3_oom_task(struct work_struct *work) +{ + struct hns3_nic_priv *priv = container_of(work, struct hns3_nic_priv, + oom_task.work); + struct hnae3_handle *h = priv->ae_handle; + struct hns3_enet_ring *ring; + u16 i; + + if (test_bit(HNS3_NIC_STATE_DOWN, &priv->state)) { + bitmap_zero(priv->rx_ring_oom_bm, h->kinfo.num_tqps); + return; + } + + for (i = 0; i < h->kinfo.num_tqps; i++) + if (test_and_clear_bit(i, priv->rx_ring_oom_bm)) { + ring = &priv->ring[i + h->kinfo.num_tqps]; + napi_schedule(&ring->tqp_vector->napi); + } +} + +static void hns3_oom_task_schedule(struct hns3_enet_ring *ring) +{ + struct hns3_nic_priv *priv = netdev_priv(ring_to_netdev(ring)); + + hns3_ring_set_oom_state(ring); + schedule_delayed_work(&priv->oom_task, + msecs_to_jiffies(HNS3_OOM_POLL_INTERVAL_MS)); +} + int hns3_clean_rx_ring(struct hns3_enet_ring *ring, int budget, void (*rx_fn)(struct hns3_enet_ring *, struct sk_buff *)) { @@ -4472,6 +4504,9 @@ int hns3_clean_rx_ring(struct hns3_enet_ring *ring, int budget, /* Poll one pkt */ err = hns3_handle_rx_bd(ring); + if (unlikely(err == -ENOMEM)) + failure = true; + /* Do not get FE for the packet or failed to alloc skb */ if (unlikely(!ring->skb || err == -ENXIO)) { goto out; @@ -4490,10 +4525,12 @@ int hns3_clean_rx_ring(struct hns3_enet_ring *ring, int budget, * FBD number with head pointer */ if (unused_count > 0) - failure = failure || - hns3_nic_alloc_rx_buffers(ring, unused_count); + failure |= hns3_nic_alloc_rx_buffers(ring, unused_count); - return failure ? budget : recv_pkts; + if (unlikely(failure || hns3_ring_is_oom_state(ring))) + hns3_oom_task_schedule(ring); + + return recv_pkts; } static void hns3_update_rx_int_coalesce(struct hns3_enet_tqp_vector *tqp_vector) @@ -4788,6 +4825,7 @@ static int hns3_nic_init_vector_data(struct hns3_nic_priv *priv) hns3_nic_common_poll); } + INIT_DELAYED_WORK(&priv->oom_task, hns3_oom_task); return 0; map_ring_fail: @@ -4889,6 +4927,8 @@ static void hns3_nic_uninit_vector_data(struct hns3_nic_priv *priv) struct hns3_enet_tqp_vector *tqp_vector; int i; + cancel_delayed_work_sync(&priv->oom_task); + for (i = 0; i < priv->vector_num; i++) { tqp_vector = &priv->tqp_vector[i]; @@ -5204,6 +5244,16 @@ int hns3_init_all_ring(struct hns3_nic_priv *priv) cond_resched(); } + /* hns3_set_ringparam() calls this without hns3_uninit_all_ring(). */ + if (!priv->rx_ring_oom_bm) { + priv->rx_ring_oom_bm = bitmap_zalloc(h->kinfo.num_tqps, + GFP_KERNEL); + if (!priv->rx_ring_oom_bm) { + ret = -ENOMEM; + goto out_when_alloc_ring_memory; + } + } + return 0; out_when_alloc_ring_memory: @@ -5222,6 +5272,11 @@ static void hns3_uninit_all_ring(struct hns3_nic_priv *priv) hns3_fini_ring(&priv->ring[i]); hns3_fini_ring(&priv->ring[i + h->kinfo.num_tqps]); } + + if (priv->rx_ring_oom_bm) { + bitmap_free(priv->rx_ring_oom_bm); + priv->rx_ring_oom_bm = NULL; + } } /* Set mac addr if it is configured. or leave it to the AE driver */ diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.h b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.h index 933e3527ed82..5470df5d8efb 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.h @@ -449,6 +449,7 @@ struct ring_stats { u64 non_reuse_pg; u64 frag_alloc_err; u64 frag_alloc; + u64 rx_oom_cnt; }; __le16 csum; }; @@ -585,6 +586,8 @@ struct hns3_nic_priv { struct hns3_enet_tqp_vector *tqp_vector; u16 vector_num; u8 max_non_tso_bd_num; + struct delayed_work oom_task; + unsigned long *rx_ring_oom_bm; u64 tx_timeout_count; @@ -711,6 +714,21 @@ static inline unsigned int hns3_page_order(struct hns3_enet_ring *ring) #define hns3_rl_usec_to_reg(int_rl) ((int_rl) >> 2) #define hns3_rl_round_down(int_rl) round_down(int_rl, 4) +static inline void hns3_ring_set_oom_state(struct hns3_enet_ring *ring) +{ + struct hns3_nic_priv *priv = netdev_priv(ring_to_netdev(ring)); + + if (!test_and_set_bit(ring->queue_index, priv->rx_ring_oom_bm)) + hns3_ring_stats_update(ring, rx_oom_cnt); +} + +static inline bool hns3_ring_is_oom_state(struct hns3_enet_ring *ring) +{ + struct hns3_nic_priv *priv = netdev_priv(ring_to_netdev(ring)); + + return test_bit(ring->queue_index, priv->rx_ring_oom_bm); +} + void hns3_ethtool_set_ops(struct net_device *netdev); int hns3_set_channels(struct net_device *netdev, struct ethtool_channels *ch); diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c index 7b92a3c73ef6..14642f81efe1 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c @@ -62,6 +62,7 @@ static const struct hns3_stats hns3_rxq_stats[] = { HNS3_TQP_STAT("non_reuse_pg", non_reuse_pg), HNS3_TQP_STAT("frag_alloc_err", frag_alloc_err), HNS3_TQP_STAT("frag_alloc", frag_alloc), + HNS3_TQP_STAT("rx_oom_cnt", rx_oom_cnt), }; #define HNS3_PRIV_FLAGS_LEN ARRAY_SIZE(hns3_priv_flags) -- 2.43.0