--- /dev/null
+From f329924bb49458c65297f1361f545816a5b90998 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Apr 2026 08:36:31 +0200
+Subject: [PATCH 1/2] net: airoha: Move ndesc initialization at end of
+ airoha_qdma_init_tx()
+
+If queue entry list allocation fails in airoha_qdma_init_tx_queue routine,
+airoha_qdma_cleanup_tx_queue() will trigger a NULL pointer dereference
+accessing the queue entry array. The issue is due to the early ndesc
+initialization in airoha_qdma_init_tx_queue(). Fix the issue moving ndesc
+initialization at end of airoha_qdma_init_tx routine.
+
+Fixes: 3f47e67dff1f7 ("net: airoha: Add the capability to consume out-of-order DMA tx descriptors")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260417-airoha_qdma_cleanup_tx_queue-fix-net-v4-1-e04bcc2c9642@kernel.org
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -954,27 +954,27 @@ static int airoha_qdma_init_tx_queue(str
+ dma_addr_t dma_addr;
+
+ spin_lock_init(&q->lock);
+- q->ndesc = size;
+ q->qdma = qdma;
+ q->free_thr = 1 + MAX_SKB_FRAGS;
+ INIT_LIST_HEAD(&q->tx_list);
+
+- q->entry = devm_kzalloc(eth->dev, q->ndesc * sizeof(*q->entry),
++ q->entry = devm_kzalloc(eth->dev, size * sizeof(*q->entry),
+ GFP_KERNEL);
+ if (!q->entry)
+ return -ENOMEM;
+
+- q->desc = dmam_alloc_coherent(eth->dev, q->ndesc * sizeof(*q->desc),
++ q->desc = dmam_alloc_coherent(eth->dev, size * sizeof(*q->desc),
+ &dma_addr, GFP_KERNEL);
+ if (!q->desc)
+ return -ENOMEM;
+
+- for (i = 0; i < q->ndesc; i++) {
++ for (i = 0; i < size; i++) {
+ u32 val = FIELD_PREP(QDMA_DESC_DONE_MASK, 1);
+
+ list_add_tail(&q->entry[i].list, &q->tx_list);
+ WRITE_ONCE(q->desc[i].ctrl, cpu_to_le32(val));
+ }
++ q->ndesc = size;
+
+ /* xmit ring drop default setting */
+ airoha_qdma_set(qdma, REG_TX_RING_BLOCKING(qid),
--- /dev/null
+From 3309965fe44c00fd65af7cef5016e9e782c021a7 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Apr 2026 08:36:32 +0200
+Subject: [PATCH 2/2] net: airoha: Add missing bits in
+ airoha_qdma_cleanup_tx_queue()
+
+Similar to airoha_qdma_cleanup_rx_queue(), reset DMA TX descriptors in
+airoha_qdma_cleanup_tx_queue routine. Moreover, reset TX_DMA_IDX to
+TX_CPU_IDX to notify the NIC the QDMA TX ring is empty.
+
+Fixes: 23020f0493270 ("net: airoha: Introduce ethernet support for EN7581 SoC")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260417-airoha_qdma_cleanup_tx_queue-fix-net-v4-2-e04bcc2c9642@kernel.org
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 32 ++++++++++++++++++++++--
+ 1 file changed, 30 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1039,12 +1039,15 @@ static int airoha_qdma_init_tx(struct ai
+
+ static void airoha_qdma_cleanup_tx_queue(struct airoha_queue *q)
+ {
+- struct airoha_eth *eth = q->qdma->eth;
+- int i;
++ struct airoha_qdma *qdma = q->qdma;
++ struct airoha_eth *eth = qdma->eth;
++ int i, qid = q - &qdma->q_tx[0];
++ u16 index = 0;
+
+ spin_lock_bh(&q->lock);
+ for (i = 0; i < q->ndesc; i++) {
+ struct airoha_queue_entry *e = &q->entry[i];
++ struct airoha_qdma_desc *desc = &q->desc[i];
+
+ if (!e->dma_addr)
+ continue;
+@@ -1055,8 +1058,33 @@ static void airoha_qdma_cleanup_tx_queue
+ e->dma_addr = 0;
+ e->skb = NULL;
+ list_add_tail(&e->list, &q->tx_list);
++
++ /* Reset DMA descriptor */
++ WRITE_ONCE(desc->ctrl, 0);
++ WRITE_ONCE(desc->addr, 0);
++ WRITE_ONCE(desc->data, 0);
++ WRITE_ONCE(desc->msg0, 0);
++ WRITE_ONCE(desc->msg1, 0);
++ WRITE_ONCE(desc->msg2, 0);
++
+ q->queued--;
+ }
++
++ if (!list_empty(&q->tx_list)) {
++ struct airoha_queue_entry *e;
++
++ e = list_first_entry(&q->tx_list, struct airoha_queue_entry,
++ list);
++ index = e - q->entry;
++ }
++ /* Set TX_DMA_IDX to TX_CPU_IDX to notify the hw the QDMA TX ring is
++ * empty.
++ */
++ airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid), TX_RING_CPU_IDX_MASK,
++ FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
++ airoha_qdma_rmw(qdma, REG_TX_DMA_IDX(qid), TX_RING_DMA_IDX_MASK,
++ FIELD_PREP(TX_RING_DMA_IDX_MASK, index));
++
+ spin_unlock_bh(&q->lock);
+ }
+
--- /dev/null
+From f3206328bb52c2787197d80d7cbd687946047d5f Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 14 Apr 2026 16:08:52 +0200
+Subject: [PATCH] net: airoha: Wait for NPU PPE configuration to complete in
+ airoha_ppe_offload_setup()
+
+In order to properly enable flowtable hw offloading, poll
+REG_PPE_FLOW_CFG register in airoha_ppe_offload_setup routine and
+wait for NPU PPE configuration triggered by ppe_init callback to complete
+before running airoha_ppe_hw_init().
+
+Fixes: 00a7678310fe3 ("net: airoha: Introduce flowtable offload support")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260414-airoha-wait-for-npu-config-offload-setup-v2-1-5a9bf6d43aee@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 28 ++++++++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -1354,6 +1354,29 @@ static struct airoha_npu *airoha_ppe_npu
+ return npu;
+ }
+
++static int airoha_ppe_wait_for_npu_init(struct airoha_eth *eth)
++{
++ int err;
++ u32 val;
++
++ /* PPE_FLOW_CFG default register value is 0. Since we reset FE
++ * during the device probe we can just check the configured value
++ * is not 0 here.
++ */
++ err = read_poll_timeout(airoha_fe_rr, val, val, USEC_PER_MSEC,
++ 100 * USEC_PER_MSEC, false, eth,
++ REG_PPE_PPE_FLOW_CFG(0));
++ if (err)
++ return err;
++
++ if (airoha_ppe_is_enabled(eth, 1))
++ err = read_poll_timeout(airoha_fe_rr, val, val, USEC_PER_MSEC,
++ 100 * USEC_PER_MSEC, false, eth,
++ REG_PPE_PPE_FLOW_CFG(1));
++
++ return err;
++}
++
+ static int airoha_ppe_offload_setup(struct airoha_eth *eth)
+ {
+ struct airoha_npu *npu = airoha_ppe_npu_get(eth);
+@@ -1367,6 +1390,11 @@ static int airoha_ppe_offload_setup(stru
+ if (err)
+ goto error_npu_put;
+
++ /* Wait for NPU PPE configuration to complete */
++ err = airoha_ppe_wait_for_npu_init(eth);
++ if (err)
++ goto error_npu_put;
++
+ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
+ if (ppe_num_stats_entries > 0) {
+ err = npu->ops.ppe_init_stats(npu, ppe->foe_stats_dma,
--- /dev/null
+From d647f2545219754603b2064de948425cdfd93fba Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Apr 2026 17:24:41 +0200
+Subject: [PATCH] net: airoha: Fix PPE cpu port configuration for GDM2 loopback
+ path
+
+When QoS loopback is enabled for GDM3 or GDM4, incoming packets are
+forwarded to GDM2. However, the PPE cpu port for GDM2 is not configured
+in this path, causing traffic originating from GDM3/GDM4, which may
+be set up as WAN ports backed by QDMA1, to be incorrectly directed
+to QDMA0 instead.
+Configure the PPE cpu port for GDM2 when QoS loopback is active on
+GDM3 or GDM4 to ensure traffic is routed to the correct QDMA instance.
+
+Fixes: 9cd451d414f6 ("net: airoha: Add loopback support for GDM2")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260417-airoha-ppe-cpu-port-for-gdm2-loopback-v1-1-c7a9de0f6f57@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 8 ++++++--
+ drivers/net/ethernet/airoha/airoha_eth.h | 3 ++-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 6 +++---
+ 3 files changed, 11 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1755,7 +1755,7 @@ static int airoha_set_gdm2_loopback(stru
+ {
+ struct airoha_eth *eth = port->qdma->eth;
+ u32 val, pse_port, chan;
+- int src_port;
++ int i, src_port;
+
+ /* Forward the traffic to the proper GDM port */
+ pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
+@@ -1797,6 +1797,9 @@ static int airoha_set_gdm2_loopback(stru
+ SP_CPORT_MASK(val),
+ __field_prep(SP_CPORT_MASK(val), FE_PSE_PORT_CDM2));
+
++ for (i = 0; i < eth->soc->num_ppe; i++)
++ airoha_ppe_set_cpu_port(port, i, AIROHA_GDM2_IDX);
++
+ if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+ u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
+
+@@ -1835,7 +1838,8 @@ static int airoha_dev_init(struct net_de
+ }
+
+ for (i = 0; i < eth->soc->num_ppe; i++)
+- airoha_ppe_set_cpu_port(port, i);
++ airoha_ppe_set_cpu_port(port, i,
++ airoha_get_fe_port(port));
+
+ return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -653,7 +653,8 @@ int airoha_get_fe_port(struct airoha_gdm
+ bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+ struct airoha_gdm_port *port);
+
+-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id);
++void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
++ u8 fport);
+ bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
+ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
+ u16 hash, bool rx_wlan);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -85,10 +85,9 @@ static u32 airoha_ppe_get_timestamp(stru
+ return FIELD_GET(AIROHA_FOE_IB1_BIND_TIMESTAMP, timestamp);
+ }
+
+-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id)
++void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id, u8 fport)
+ {
+ struct airoha_qdma *qdma = port->qdma;
+- u8 fport = airoha_get_fe_port(port);
+ struct airoha_eth *eth = qdma->eth;
+ u8 qdma_id = qdma - ð->qdma[0];
+ u32 fe_cpu_port;
+@@ -182,7 +181,8 @@ static void airoha_ppe_hw_init(struct ai
+ if (!port)
+ continue;
+
+- airoha_ppe_set_cpu_port(port, i);
++ airoha_ppe_set_cpu_port(port, i,
++ airoha_get_fe_port(port));
+ }
+ }
+ }
--- /dev/null
+From b94769eb2f30e61e86cd8551c084c34134290d89 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 16 Apr 2026 12:30:12 +0200
+Subject: [PATCH] net: airoha: Fix possible TX queue stall in
+ airoha_qdma_tx_napi_poll()
+
+Since multiple net_device TX queues can share the same hw QDMA TX queue,
+there is no guarantee we have inflight packets queued in hw belonging to a
+net_device TX queue stopped in the xmit path because hw QDMA TX queue
+can be full. In this corner case the net_device TX queue will never be
+re-activated. In order to avoid any potential net_device TX queue stall,
+we need to wake all the net_device TX queues feeding the same hw QDMA TX
+queue in airoha_qdma_tx_napi_poll routine.
+
+Fixes: 23020f0493270 ("net: airoha: Introduce ethernet support for EN7581 SoC")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20260416-airoha-txq-potential-stall-v2-1-42c732074540@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 37 ++++++++++++++++++++----
+ drivers/net/ethernet/airoha/airoha_eth.h | 1 +
+ 2 files changed, 33 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -843,6 +843,21 @@ static int airoha_qdma_init_rx(struct ai
+ return 0;
+ }
+
++static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
++{
++ struct airoha_qdma *qdma = q->qdma;
++ struct airoha_eth *eth = qdma->eth;
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++ struct airoha_gdm_port *port = eth->ports[i];
++
++ if (port && port->qdma == qdma)
++ netif_tx_wake_all_queues(port->dev);
++ }
++ q->txq_stopped = false;
++}
++
+ static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
+ {
+ struct airoha_tx_irq_queue *irq_q;
+@@ -919,12 +934,21 @@ static int airoha_qdma_tx_napi_poll(stru
+
+ txq = netdev_get_tx_queue(skb->dev, queue);
+ netdev_tx_completed_queue(txq, 1, skb->len);
+- if (netif_tx_queue_stopped(txq) &&
+- q->ndesc - q->queued >= q->free_thr)
+- netif_tx_wake_queue(txq);
+-
+ dev_kfree_skb_any(skb);
+ }
++
++ if (q->txq_stopped && q->ndesc - q->queued >= q->free_thr) {
++ /* Since multiple net_device TX queues can share the
++ * same hw QDMA TX queue, there is no guarantee we have
++ * inflight packets queued in hw belonging to a
++ * net_device TX queue stopped in the xmit path.
++ * In order to avoid any potential net_device TX queue
++ * stall, we need to wake all the net_device TX queues
++ * feeding the same hw QDMA TX queue.
++ */
++ airoha_qdma_wake_netdev_txqs(q);
++ }
++
+ unlock:
+ spin_unlock_bh(&q->lock);
+ }
+@@ -2016,6 +2040,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ if (q->queued + nr_frags >= q->ndesc) {
+ /* not enough space in the queue */
+ netif_tx_stop_queue(txq);
++ q->txq_stopped = true;
+ spin_unlock_bh(&q->lock);
+ return NETDEV_TX_BUSY;
+ }
+@@ -2071,8 +2096,10 @@ static netdev_tx_t airoha_dev_xmit(struc
+ TX_RING_CPU_IDX_MASK,
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
+
+- if (q->ndesc - q->queued < q->free_thr)
++ if (q->ndesc - q->queued < q->free_thr) {
+ netif_tx_stop_queue(txq);
++ q->txq_stopped = true;
++ }
+
+ spin_unlock_bh(&q->lock);
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -193,6 +193,7 @@ struct airoha_queue {
+ int ndesc;
+ int free_thr;
+ int buf_size;
++ bool txq_stopped;
+
+ struct napi_struct napi;
+ struct page_pool *page_pool;
--- /dev/null
+From 379050947a1828826ad7ea50c95245a56929b35a Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 20 Apr 2026 10:07:47 +0200
+Subject: [PATCH] net: airoha: Move ndesc initialization at end of
+ airoha_qdma_init_rx_queue()
+
+If queue entry or DMA descriptor list allocation fails in
+airoha_qdma_init_rx_queue routine, airoha_qdma_cleanup() will trigger a
+NULL pointer dereference running netif_napi_del() for RX queue NAPIs
+since netif_napi_add() has never been executed to this particular RX NAPI.
+The issue is due to the early ndesc initialization in
+airoha_qdma_init_rx_queue() since airoha_qdma_cleanup() relies on ndesc
+value to check if the queue is properly initialized. Fix the issue moving
+ndesc initialization at end of airoha_qdma_init_tx routine.
+Move page_pool allocation after descriptor list allocation in order to
+avoid memory leaks if desc allocation fails.
+
+Fixes: 23020f049327 ("net: airoha: Introduce ethernet support for EN7581 SoC")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260420-airoha_qdma_init_rx_queue-fix-v2-1-d99347e5c18d@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -745,14 +745,18 @@ static int airoha_qdma_init_rx_queue(str
+ dma_addr_t dma_addr;
+
+ q->buf_size = PAGE_SIZE / 2;
+- q->ndesc = ndesc;
+ q->qdma = qdma;
+
+- q->entry = devm_kzalloc(eth->dev, q->ndesc * sizeof(*q->entry),
++ q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
+ GFP_KERNEL);
+ if (!q->entry)
+ return -ENOMEM;
+
++ q->desc = dmam_alloc_coherent(eth->dev, ndesc * sizeof(*q->desc),
++ &dma_addr, GFP_KERNEL);
++ if (!q->desc)
++ return -ENOMEM;
++
+ q->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(q->page_pool)) {
+ int err = PTR_ERR(q->page_pool);
+@@ -761,11 +765,7 @@ static int airoha_qdma_init_rx_queue(str
+ return err;
+ }
+
+- q->desc = dmam_alloc_coherent(eth->dev, q->ndesc * sizeof(*q->desc),
+- &dma_addr, GFP_KERNEL);
+- if (!q->desc)
+- return -ENOMEM;
+-
++ q->ndesc = ndesc;
+ netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
+
+ airoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);
--- /dev/null
+From 4b91cb65789b794bfc8d50554b8994f8e0f16309 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 20 Apr 2026 10:07:48 +0200
+Subject: [PATCH] net: airoha: Add size check for TX NAPIs in
+ airoha_qdma_cleanup()
+
+If airoha_qdma_init routine fails before airoha_qdma_tx_irq_init() runs
+successfully for all TX NAPIs, airoha_qdma_cleanup() will
+unconditionally runs netif_napi_del() on TX NAPIs, triggering a NULL
+pointer dereference. Fix the issue relying on q_tx_irq size value to
+check if the TX NAPIs is properly initialized in airoha_qdma_cleanup().
+Moreover, run netif_napi_add_tx() just if irq_q queue is properly
+allocated.
+
+Fixes: 23020f049327 ("net: airoha: Introduce ethernet support for EN7581 SoC")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260420-airoha_qdma_init_rx_queue-fix-v2-2-d99347e5c18d@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1020,8 +1020,6 @@ static int airoha_qdma_tx_irq_init(struc
+ struct airoha_eth *eth = qdma->eth;
+ dma_addr_t dma_addr;
+
+- netif_napi_add_tx(eth->napi_dev, &irq_q->napi,
+- airoha_qdma_tx_napi_poll);
+ irq_q->q = dmam_alloc_coherent(eth->dev, size * sizeof(u32),
+ &dma_addr, GFP_KERNEL);
+ if (!irq_q->q)
+@@ -1031,6 +1029,9 @@ static int airoha_qdma_tx_irq_init(struc
+ irq_q->size = size;
+ irq_q->qdma = qdma;
+
++ netif_napi_add_tx(eth->napi_dev, &irq_q->napi,
++ airoha_qdma_tx_napi_poll);
++
+ airoha_qdma_wr(qdma, REG_TX_IRQ_BASE(id), dma_addr);
+ airoha_qdma_rmw(qdma, REG_TX_IRQ_CFG(id), TX_IRQ_DEPTH_MASK,
+ FIELD_PREP(TX_IRQ_DEPTH_MASK, size));
+@@ -1450,8 +1451,12 @@ static void airoha_qdma_cleanup(struct a
+ }
+ }
+
+- for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
++ for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++) {
++ if (!qdma->q_tx_irq[i].size)
++ continue;
++
+ netif_napi_del(&qdma->q_tx_irq[i].napi);
++ }
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+ if (!qdma->q_tx[i].ndesc)
--- /dev/null
+From 2d9f5a118205da2683ffcec78b9347f1f01a820e Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 21 Apr 2026 08:35:11 +0200
+Subject: [PATCH] net: airoha: fix BQL imbalance in TX path
+
+Fix a possible BQL imbalance in airoha_dev_xmit(), where inflight
+packets are accounted only for the AIROHA_NUM_TX_RING netdev TX
+queues. The queue index is computed as:
+
+ qid = skb_get_queue_mapping(skb) % ARRAY_SIZE(qdma->q_tx)
+ txq = netdev_get_tx_queue(dev, qid);
+
+However, airoha_qdma_tx_napi_poll() accounts completions across all
+netdev TX queues (num_tx_queues), leading to inconsistent BQL
+accounting.
+
+Also reset all netdev TX queues in the ndo_stop callback.
+
+Fixes: 1d304174106c ("net: airoha: Implement BQL support")
+Fixes: c9f947769b77 ("net: airoha: Reset BQL stopping the netdevice")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260421-airoha-fix-bql-v1-1-f135afe4275b@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -929,10 +929,9 @@ static int airoha_qdma_tx_napi_poll(stru
+ q->queued--;
+
+ if (skb) {
+- u16 queue = skb_get_queue_mapping(skb);
+ struct netdev_queue *txq;
+
+- txq = netdev_get_tx_queue(skb->dev, queue);
++ txq = skb_get_tx_queue(skb->dev, skb);
+ netdev_tx_completed_queue(txq, 1, skb->len);
+ dev_kfree_skb_any(skb);
+ }
+@@ -1744,7 +1743,7 @@ static int airoha_dev_stop(struct net_de
+ if (err)
+ return err;
+
+- for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++)
++ for (i = 0; i < dev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(dev, i);
+
+ airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+@@ -2039,7 +2038,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+
+ spin_lock_bh(&q->lock);
+
+- txq = netdev_get_tx_queue(dev, qid);
++ txq = skb_get_tx_queue(dev, skb);
+ nr_frags = 1 + skb_shinfo(skb)->nr_frags;
+
+ if (q->queued + nr_frags >= q->ndesc) {
--- /dev/null
+From 3854de7b38be742cf7558476956d12414cb274f2 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 21 Apr 2026 08:43:07 +0200
+Subject: [PATCH] net: airoha: stop net_device TX queue before updating CPU
+ index
+
+Currently, airoha_eth driver updates the CPU index register prior of
+verifying whether the number of free descriptors has fallen below the
+threshold.
+Move net_device TX queue length check before updating the TX CPU index
+in order to update TX CPU index even if there are more packets to be
+transmitted but the net_device TX queue is going to be stopped
+accounting the inflight packets.
+
+Fixes: 1d304174106c ("net: airoha: Implement BQL support")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260421-airoha-xmit-stop-condition-v1-1-e670d6a48467@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 9 ++++-----
+ 1 file changed, 4 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2094,17 +2094,16 @@ static netdev_tx_t airoha_dev_xmit(struc
+
+ skb_tx_timestamp(skb);
+ netdev_tx_sent_queue(txq, skb->len);
++ if (q->ndesc - q->queued < q->free_thr) {
++ netif_tx_stop_queue(txq);
++ q->txq_stopped = true;
++ }
+
+ if (netif_xmit_stopped(txq) || !netdev_xmit_more())
+ airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
+ TX_RING_CPU_IDX_MASK,
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
+
+- if (q->ndesc - q->queued < q->free_thr) {
+- netif_tx_stop_queue(txq);
+- q->txq_stopped = true;
+- }
+-
+ spin_unlock_bh(&q->lock);
+
+ return NETDEV_TX_OK;
--- /dev/null
+From e070aac63b42bf81f4dc565f9f841ff47e6c992f Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 21 Apr 2026 10:53:33 +0200
+Subject: [PATCH] net: airoha: Do not wake all netdev TX queues in
+ airoha_qdma_wake_netdev_txqs()
+
+Do not wake every netdev TX queue across all ports sharing the QDMA
+running netif_tx_wake_all_queues routine in airoha_qdma_wake_netdev_txqs()
+but only the ones that are mapped the specific QDMA stopped hw TX queue.
+This patch can potentially avoid waking already stopped netdev TX queues
+that are mapped to a different QDMA hw TX queue.
+Introduce airoha_qdma_get_txq utility routine.
+
+Fixes: b94769eb2f30 ("net: airoha: Fix possible TX queue stall in airoha_qdma_tx_napi_poll()")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260421-airoha-wake_netdev_txqs-optmization-v1-1-e0be95115d53@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 19 +++++++++++++++----
+ drivers/net/ethernet/airoha/airoha_eth.h | 5 +++++
+ 2 files changed, 20 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -847,13 +847,24 @@ static void airoha_qdma_wake_netdev_txqs
+ {
+ struct airoha_qdma *qdma = q->qdma;
+ struct airoha_eth *eth = qdma->eth;
+- int i;
++ int i, qid = q - &qdma->q_tx[0];
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
++ int j;
+
+- if (port && port->qdma == qdma)
+- netif_tx_wake_all_queues(port->dev);
++ if (!port)
++ continue;
++
++ if (port->qdma != qdma)
++ continue;
++
++ for (j = 0; j < port->dev->num_tx_queues; j++) {
++ if (airoha_qdma_get_txq(qdma, j) != qid)
++ continue;
++
++ netif_wake_subqueue(port->dev, j);
++ }
+ }
+ q->txq_stopped = false;
+ }
+@@ -2001,7 +2012,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ u16 index;
+ u8 fport;
+
+- qid = skb_get_queue_mapping(skb) % ARRAY_SIZE(qdma->q_tx);
++ qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
+ tag = airoha_get_dsa_tag(skb, dev);
+
+ msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -631,6 +631,11 @@ u32 airoha_rmw(void __iomem *base, u32 o
+ #define airoha_qdma_clear(qdma, offset, val) \
+ airoha_rmw((qdma)->regs, (offset), (val), 0)
+
++static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid)
++{
++ return qid % ARRAY_SIZE(qdma->q_tx);
++}
++
+ static inline bool airoha_is_lan_gdm_port(struct airoha_gdm_port *port)
+ {
+ /* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
--- /dev/null
+From bde34e84edc8b5571fbde7e941e175a4293ee1eb Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 24 Apr 2026 11:00:28 +0200
+Subject: [PATCH] net: airoha: Do not read uninitialized fragment address in
+ airoha_dev_xmit()
+
+The transmit loop in airoha_dev_xmit() reads fragment address and length
+during its final iteration, when the loop index equals
+skb_shinfo(skb)->nr_frags, at which point the fragment data is
+uninitialized. While these values are never consumed, the read itself is
+unsafe and may trigger a page fault. Fix this by avoiding the fragment
+read on the last iteration.
+Additionally, move the skb pointer from the first to the last used packet
+descriptor, so that airoha_qdma_tx_napi_poll() defers freeing the skb
+until the final descriptor is processed.
+
+Fixes: 23020f0493270 ("net: airoha: Introduce ethernet support for EN7581 SoC")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20260424-airoha-xmit-fix-read-frag-v1-1-fdc0a83c79e8@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2007,8 +2007,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+ struct netdev_queue *txq;
+ struct airoha_queue *q;
+ LIST_HEAD(tx_list);
++ int i = 0, qid;
+ void *data;
+- int i, qid;
+ u16 index;
+ u8 fport;
+
+@@ -2067,7 +2067,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ list);
+ index = e - q->entry;
+
+- for (i = 0; i < nr_frags; i++) {
++ while (true) {
+ struct airoha_qdma_desc *desc = &q->desc[index];
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ dma_addr_t addr;
+@@ -2079,7 +2079,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ goto error_unmap;
+
+ list_move_tail(&e->list, &tx_list);
+- e->skb = i ? NULL : skb;
++ e->skb = i == nr_frags - 1 ? skb : NULL;
+ e->dma_addr = addr;
+ e->dma_len = len;
+
+@@ -2098,6 +2098,9 @@ static netdev_tx_t airoha_dev_xmit(struc
+ WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
+ WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
+
++ if (++i == nr_frags)
++ break;
++
+ data = skb_frag_address(frag);
+ len = skb_frag_size(frag);
+ }
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -1423,6 +1423,10 @@ static int airoha_hw_init(struct platfor
+@@ -1490,6 +1490,10 @@ static int airoha_hw_init(struct platfor
if (err)
return err;
airoha_fe_crsn_qsel_init(eth);
-@@ -1655,7 +1657,8 @@ static int airoha_dev_open(struct net_de
+@@ -1722,7 +1724,8 @@ static int airoha_dev_open(struct net_de
if (err)
return err;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -3126,7 +3126,6 @@ static void airoha_remove(struct platfor
+@@ -3202,7 +3202,6 @@ static void airoha_remove(struct platfor
}
static const char * const en7581_xsi_rsts_names[] = {
"hsi0-mac",
"hsi1-mac",
"hsi-mac",
-@@ -3180,7 +3179,6 @@ static u32 airoha_en7581_get_vip_port(st
+@@ -3256,7 +3255,6 @@ static u32 airoha_en7581_get_vip_port(st
}
static const char * const an7583_xsi_rsts_names[] = {
static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
{
struct airoha_eth *eth = port->qdma->eth;
-@@ -1652,6 +1658,17 @@ static int airoha_dev_open(struct net_de
+@@ -1719,6 +1725,17 @@ static int airoha_dev_open(struct net_de
struct airoha_qdma *qdma = port->qdma;
u32 pse_port = FE_PSE_PORT_PPE1;
netif_tx_start_all_queues(dev);
err = airoha_set_vip_for_gdm_port(port, true);
if (err)
-@@ -1716,6 +1733,11 @@ static int airoha_dev_stop(struct net_de
+@@ -1783,6 +1800,11 @@ static int airoha_dev_stop(struct net_de
}
}
return 0;
}
-@@ -2846,6 +2868,20 @@ static const struct ethtool_ops airoha_e
+@@ -2922,6 +2944,20 @@ static const struct ethtool_ops airoha_e
.get_link = ethtool_op_get_link,
};
static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
{
int i;
-@@ -2890,6 +2926,99 @@ bool airoha_is_valid_gdm_port(struct air
+@@ -2966,6 +3002,99 @@ bool airoha_is_valid_gdm_port(struct air
return false;
}
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
struct device_node *np)
{
-@@ -2963,6 +3092,12 @@ static int airoha_alloc_gdm_port(struct
+@@ -3039,6 +3168,12 @@ static int airoha_alloc_gdm_port(struct
port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
eth->ports[p] = port;
return airoha_metadata_dst_alloc(port);
}
-@@ -3092,6 +3227,10 @@ error_napi_stop:
+@@ -3168,6 +3303,10 @@ error_napi_stop:
if (port->dev->reg_state == NETREG_REGISTERED)
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
-@@ -3118,6 +3257,10 @@ static void airoha_remove(struct platfor
+@@ -3194,6 +3333,10 @@ static void airoha_remove(struct platfor
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -539,6 +539,10 @@ struct airoha_gdm_port {
+@@ -540,6 +540,10 @@ struct airoha_gdm_port {
int id;
int nbq;
static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
{
-@@ -1661,6 +1663,7 @@ static int airoha_dev_open(struct net_de
+@@ -1728,6 +1730,7 @@ static int airoha_dev_open(struct net_de
struct airoha_qdma *qdma = port->qdma;
u32 pse_port = FE_PSE_PORT_PPE1;
if (airhoa_is_phy_external(port)) {
err = phylink_of_phy_connect(port->phylink, dev->dev.of_node, 0);
if (err) {
-@@ -1671,6 +1674,7 @@ static int airoha_dev_open(struct net_de
+@@ -1738,6 +1741,7 @@ static int airoha_dev_open(struct net_de
phylink_start(port->phylink);
}
netif_tx_start_all_queues(dev);
err = airoha_set_vip_for_gdm_port(port, true);
-@@ -1736,10 +1740,12 @@ static int airoha_dev_stop(struct net_de
+@@ -1803,10 +1807,12 @@ static int airoha_dev_stop(struct net_de
}
}
return 0;
}
-@@ -2871,6 +2877,7 @@ static const struct ethtool_ops airoha_e
+@@ -2947,6 +2953,7 @@ static const struct ethtool_ops airoha_e
.get_link = ethtool_op_get_link,
};
static struct phylink_pcs *airoha_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
-@@ -2884,6 +2891,7 @@ static void airoha_mac_config(struct phy
+@@ -2960,6 +2967,7 @@ static void airoha_mac_config(struct phy
const struct phylink_link_state *state)
{
}
static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
{
-@@ -2929,6 +2937,7 @@ bool airoha_is_valid_gdm_port(struct air
+@@ -3005,6 +3013,7 @@ bool airoha_is_valid_gdm_port(struct air
return false;
}
static void airoha_mac_link_up(struct phylink_config *config, struct phy_device *phy,
unsigned int mode, phy_interface_t interface,
int speed, int duplex, bool tx_pause, bool rx_pause)
-@@ -3021,6 +3030,7 @@ static int airoha_setup_phylink(struct n
+@@ -3097,6 +3106,7 @@ static int airoha_setup_phylink(struct n
return 0;
}
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
struct device_node *np)
-@@ -3095,11 +3105,13 @@ static int airoha_alloc_gdm_port(struct
+@@ -3171,11 +3181,13 @@ static int airoha_alloc_gdm_port(struct
port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
eth->ports[p] = port;
return airoha_metadata_dst_alloc(port);
}
-@@ -3230,10 +3242,12 @@ error_napi_stop:
+@@ -3306,10 +3318,12 @@ error_napi_stop:
if (port->dev->reg_state == NETREG_REGISTERED)
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
-@@ -3260,10 +3274,12 @@ static void airoha_remove(struct platfor
+@@ -3336,10 +3350,12 @@ static void airoha_remove(struct platfor
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -539,9 +539,11 @@ struct airoha_gdm_port {
+@@ -540,9 +540,11 @@ struct airoha_gdm_port {
int id;
int nbq;