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>
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 = {
#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) {
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));
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;
}
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;
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,
};
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);
}
--- 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);
--- 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))) {
}
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))) {
}
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;
--- 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;