]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
ipq40xx: ipqess: recover TX queue on watchdog timeout 24122/head
authorDenis Yarkovoy <dyarkovoy@gmail.com>
Sun, 12 Jul 2026 07:34:57 +0000 (10:34 +0300)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Fri, 17 Jul 2026 07:20:46 +0000 (09:20 +0200)
The current ipqess TX timeout handler only logs the timeout. If TX
completion interrupts are lost or left masked, descriptors can remain
pending indefinitely and the master Ethernet device stays wedged until a
reboot.

Log the TX ring indexes and interrupt state to make future failures
useful, then force the TX completion NAPI path to run and re-kick the TX
producer index. This gives the driver a chance to reclaim pending TX
descriptors and wake the queue instead of reporting the same watchdog
forever.

Signed-off-by: Denis Yarkovoy <dyarkovoy@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/24122
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
target/linux/ipq40xx/files/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
target/linux/ipq40xx/patches-6.18/702-net-ipqess-Add-out-of-band-DSA-tagging-support.patch
target/linux/ipq40xx/patches-6.18/703-net-qualcomm-ipqess-release-IRQ-s-on-network-device-.patch
target/linux/ipq40xx/patches-6.18/704-net-qualcomm-ipqess-enable-threaded-NAPI-by-default.patch
target/linux/ipq40xx/patches-6.18/711-net-qualcomm-ipqess-fix-TX-timeout-errors.patch

index b3dfd6f1ae365b66900f28f5944a72810644b526..78c48c0bb937ff9bf581900b392c0a94e1bd47eb 100644 (file)
@@ -894,7 +894,38 @@ static int ipqess_set_mac_address(struct net_device *netdev, void *p)
 
 static void ipqess_tx_timeout(struct net_device *netdev, unsigned int txq_id)
 {
-        netdev_err(netdev, "TX timeout on queue %d\n", txq_id);
+       struct ipqess_tx_ring *tr;
+       struct ipqess *ess;
+       u32 sw_cons;
+       u32 tx_imr;
+       u32 tx_isr;
+       u32 cons;
+       u32 prod;
+       u32 idx;
+
+       ess = netdev_priv(netdev);
+       tr = &ess->tx_ring[txq_id];
+
+       idx = ipqess_r32(ess, IPQESS_REG_TPD_IDX_Q(tr->idx));
+       prod = (idx >> IPQESS_TPD_PROD_IDX_SHIFT) & IPQESS_TPD_PROD_IDX_MASK;
+       cons = (idx >> IPQESS_TPD_CONS_IDX_SHIFT) & IPQESS_TPD_CONS_IDX_MASK;
+       sw_cons = ipqess_r32(ess, IPQESS_REG_TX_SW_CONS_IDX_Q(tr->idx));
+       tx_isr = ipqess_r32(ess, IPQESS_REG_TX_ISR);
+       tx_imr = ipqess_r32(ess, IPQESS_REG_TX_INT_MASK_Q(tr->idx));
+
+       netdev_warn(netdev,
+                   "TX timeout on queue %d head=%u tail=%u hw_prod=%u hw_cons=%u sw_cons=%u tx_isr=%08x tx_imr=%08x\n",
+                   tr->idx, tr->head, tr->tail, prod, cons, sw_cons, tx_isr, tx_imr);
+
+       /* A lost or masked TX completion interrupt leaves descriptors pending
+        * forever. Force the TX completion path to run and re-kick the producer.
+        */
+       if (napi_schedule_prep(&tr->napi_tx)) {
+               __napi_schedule(&tr->napi_tx);
+               ipqess_w32(ess, IPQESS_REG_TX_INT_MASK_Q(tr->idx), 0x0);
+       }
+
+       ipqess_kick_tx(tr);
 }
 
 static const struct net_device_ops ipqess_axi_netdev_ops = {
index 1969cb386e7497c5f1511e4cdc99297ea2b51509..406817f9116f977734412f2c1d400aac8a1b19f0 100644 (file)
@@ -43,15 +43,15 @@ Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
  #include <linux/if_vlan.h>
  #include <linux/interrupt.h>
  #include <linux/module.h>
-@@ -23,6 +24,7 @@
- #include <linux/version.h>
+@@ -22,6 +23,7 @@
+ #include <linux/skbuff.h>
  #include <linux/vmalloc.h>
  #include <net/checksum.h>
 +#include <net/dsa.h>
  #include <net/ip6_checksum.h>
  
  #include "ipqess.h"
-@@ -328,6 +330,7 @@ static int ipqess_rx_poll(struct ipqess_
+@@ -327,6 +329,7 @@ static int ipqess_rx_poll(struct ipqess_
        tail &= IPQESS_RFD_CONS_IDX_MASK;
  
        while (done < budget) {
@@ -59,7 +59,7 @@ Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
                struct ipqess_rx_desc *rd;
                struct sk_buff *skb;
  
-@@ -407,6 +410,12 @@ static int ipqess_rx_poll(struct ipqess_
+@@ -406,6 +409,12 @@ static int ipqess_rx_poll(struct ipqess_
                        __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021AD),
                                               le16_to_cpu(rd->rrd4));
  
@@ -72,7 +72,7 @@ Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
                napi_gro_receive(&rx_ring->napi_rx, skb);
  
                rx_ring->ess->stats.rx_packets++;
-@@ -702,6 +711,26 @@ static void ipqess_rollback_tx(struct ip
+@@ -701,6 +710,26 @@ static void ipqess_rollback_tx(struct ip
        tx_ring->head = start_index;
  }
  
@@ -99,7 +99,7 @@ Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
  static int ipqess_tx_map_and_fill(struct ipqess_tx_ring *tx_ring,
                                  struct sk_buff *skb)
  {
-@@ -712,6 +741,8 @@ static int ipqess_tx_map_and_fill(struct
+@@ -711,6 +740,8 @@ static int ipqess_tx_map_and_fill(struct
        u16 len;
        int i;
  
@@ -108,7 +108,7 @@ Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
        if (skb_is_gso(skb)) {
                if (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4) {
                        lso_word1 |= IPQESS_TPD_IPV4_EN;
-@@ -910,6 +941,33 @@ static const struct net_device_ops ipqes
+@@ -940,6 +971,33 @@ static const struct net_device_ops ipqes
        .ndo_tx_timeout         = ipqess_tx_timeout,
  };
  
@@ -142,7 +142,7 @@ Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
  static void ipqess_hw_stop(struct ipqess *ess)
  {
        int i;
-@@ -1186,12 +1244,19 @@ static int ipqess_axi_probe(struct platf
+@@ -1216,12 +1274,19 @@ static int ipqess_axi_probe(struct platf
                netif_napi_add(netdev, &ess->rx_ring[i].napi_rx, ipqess_rx_napi);
        }
  
index d033928b49d37e67f170c7a19d9a46affab53d9d..1ad96ecd87b43f2712953d54be5cbb5e4026e4e4 100644 (file)
@@ -50,7 +50,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
 
 --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
 +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
-@@ -632,9 +632,22 @@ static int ipqess_stop(struct net_device
+@@ -631,9 +631,22 @@ static int ipqess_stop(struct net_device
        netif_tx_stop_all_queues(netdev);
        phylink_stop(ess->phylink);
        ipqess_irq_disable(ess);
index 1722a35dbe52c514002531b8e82e7590686cef18..42976357fa67c66a67527188bf09d122c04bf3a1 100644 (file)
@@ -16,7 +16,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
 
 --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
 +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
-@@ -531,9 +531,9 @@ static irqreturn_t ipqess_interrupt_tx(i
+@@ -530,9 +530,9 @@ static irqreturn_t ipqess_interrupt_tx(i
        struct ipqess_tx_ring *tx_ring = (struct ipqess_tx_ring *)priv;
  
        if (likely(napi_schedule_prep(&tx_ring->napi_tx))) {
@@ -27,7 +27,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
        }
  
        return IRQ_HANDLED;
-@@ -544,9 +544,9 @@ static irqreturn_t ipqess_interrupt_rx(i
+@@ -543,9 +543,9 @@ static irqreturn_t ipqess_interrupt_rx(i
        struct ipqess_rx_ring *rx_ring = (struct ipqess_rx_ring *)priv;
  
        if (likely(napi_schedule_prep(&rx_ring->napi_rx))) {
@@ -38,7 +38,7 @@ Signed-off-by: Robert Marko <robimarko@gmail.com>
        }
  
        return IRQ_HANDLED;
-@@ -1266,6 +1266,8 @@ static int ipqess_axi_probe(struct platf
+@@ -1296,6 +1296,8 @@ static int ipqess_axi_probe(struct platf
        if (err)
                goto err_notifier_unregister;
  
index 535ad8987fcca0e7bf63a98f2b33cba5e259cdd4..149208aa69da250449037fbe454dc53c261c63d6 100644 (file)
@@ -35,7 +35,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 
 --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
 +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
-@@ -454,13 +454,22 @@ static int ipqess_tx_complete(struct ipq
+@@ -453,13 +453,22 @@ static int ipqess_tx_complete(struct ipq
        tail >>= IPQESS_TPD_CONS_IDX_SHIFT;
        tail &= IPQESS_TPD_CONS_IDX_MASK;