airoha: backport LRO offload patch

Backport the upstream Airoha Ethernet LRO implementation for EN7581 and
AN7583.

Use the eight dedicated hardware LRO queues mapped to RX queues 24-31.
Adjust the RX page pool configuration for LRO queues and provide the GSO
metadata required to correctly re-segment forwarded aggregated packets.

Since LRO is configured per QDMA block, expose NETIF_F_LRO only when a
single device uses the QDMA, and update the feature during device
registration and runtime QDMA migration.

Link: https://patch.msgid.link/20260909-airoha-eth-lro-v7-1-e6317be79067@kernel.org

Signed-off-by: Yalei Zang <yalei.zang@airoha.com>
Link: https://github.com/openwrt/openwrt/pull/25901
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Yalei Zang authored and Robert Marko committed 2026-10-16 15:51:00 +02:00
1 parent d4083ed9ee
commit e1bd479ba8
8 files changed
+287 -333

No files matched your search

@@ -1,20 +1,57 @@
From 598e1ddfe85ad0f4778eeadd5d878209dd931280 Mon Sep 17 00:00:00 2001
Message-ID: <598e1ddfe85ad0f4778eeadd5d878209dd931280.1779112628.git.lorenzo@kernel.org>
From a952507ca094d4e77bea78b462a0482d3c546c9e Mon Sep 17 00:00:00 2001
From: Lorenzo Bianconi <lorenzo@kernel.org>
Date: Thu, 14 May 2026 20:25:19 +0200
Subject: [PATCH] net: airoha: Implement LRO TCP support
Date: Wed, 9 Sep 2026 21:06:05 +0200
Subject: [PATCH 48/50] net: airoha: add LRO offload support
Introduce LRO offload support to the airoha_eth driver, leveraging
the EN7581/AN7583 SoC's 8 dedicated LRO hardware queues mapped to RX
queues 24-31. LRO offloading does not support Scatter-Gather (SG) so
it is required to increase the page_pool allocation order to 2 for RX
queues 24-31 (LRO queues).
Since LRO is configured per-QDMA and shared across all devices using
it, LRO is mutually exclusive with multiple devices bound to the
same QDMA block. NETIF_F_LRO availability is re-evaluated whenever the
QDMA user count changes (device registration and runtime QDMA
migration): airoha_update_netdev_features() drops the feature when the
QDMA has more than one user, while airoha_dev_set_qdma() re-enables LRO
on the destination QDMA after a migration so that a running device
keeps it active on the new QDMA block.
Set the GSO metadata (gso_type/gso_size/gso_segs) on aggregated packets,
together with CHECKSUM_PARTIAL and the pseudo-header checksum, so that
L3-forwarded traffic is correctly re-segmented by the GSO/TSO path on
the egress device.
The HW does not report the per-segment MSS (msg3[31:16] only reports
the max aggregated size), so the gso_size of an aggregated packet is
approximated as DIV_ROUND_UP(data_len, agg_count), i.e. the mean
segment size. Since this can never exceed the largest merged segment,
re-segmentation only produces smaller packets, which is safe.
Aggregated skbs are marked SKB_GSO_DODGY so that the stack recomputes
gso_segs from gso_size and does not merge the aggregate into the GRO
engine.
Performance comparison between sw GRO and LRO has been carried out using
a 10Gbps NIC:
GRO: ~2.7 Gbps
LRO: ~8.2 Gbps
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20260909-airoha-eth-lro-v7-1-e6317be79067@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 178 ++++++++++++++++++++--
drivers/net/ethernet/airoha/airoha_eth.h | 23 +++
drivers/net/ethernet/airoha/airoha_regs.h | 22 ++-
3 files changed, 208 insertions(+), 15 deletions(-)
drivers/net/ethernet/airoha/airoha_eth.c | 363 ++++++++++++++++++++--
drivers/net/ethernet/airoha/airoha_eth.h | 26 ++
drivers/net/ethernet/airoha/airoha_regs.h | 23 +-
3 files changed, 390 insertions(+), 22 deletions(-)
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -11,8 +11,10 @@
#include <linux/pcs/pcs.h>
@@ -10,8 +10,10 @@
#include <linux/tcp.h>
#include <linux/u64_stats_sync.h>
#include <net/dst_metadata.h>
+#include <net/ip6_checksum.h>
@@ -24,7 +61,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
#include <uapi/linux/ppp_defs.h>
#include "airoha_regs.h"
@@ -492,6 +494,73 @@ static void airoha_fe_crsn_qsel_init(str
@@ -491,6 +493,88 @@ static void airoha_fe_crsn_qsel_init(str
CDM_CRSN_QSEL_Q1));
}
@@ -77,6 +114,9 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+ int qdma_id = qdma - &eth->qdma[0];
+ int i, lro_queue_index = 0;
+
+ if (airoha_fe_lro_is_enabled(eth, qdma_id))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ struct airoha_queue *q = &qdma->q_rx[i];
+ u32 size;
@@ -88,17 +128,29 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+ continue;
+
+ size = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
+ size = min_t(u32, size, CDM_LRO_AGG_SIZE_MASK);
+ size = min_t(u32, size, AIROHA_MAX_RX_SIZE);
+ airoha_fe_lro_rxq_enable(eth, qdma_id, lro_queue_index, i,
+ size);
+ lro_queue_index++;
+ }
+}
+
+static void airoha_dev_lro_disable(struct airoha_gdm_dev *dev)
+{
+ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - &eth->qdma[0];
+
+ if (!airoha_fe_lro_is_enabled(eth, qdma_id))
+ return;
+
+ airoha_fe_lro_disable(eth, qdma_id);
+}
+
static int airoha_fe_init(struct airoha_eth *eth)
{
airoha_fe_maccr_init(eth);
@@ -619,6 +688,7 @@ static int airoha_qdma_fill_rx_queue(str
@@ -616,6 +700,7 @@ static int airoha_qdma_fill_rx_queue(str
e->dma_addr = page_pool_get_dma_addr(page) + offset;
e->dma_len = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
@@ -106,51 +158,56 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
val = FIELD_PREP(QDMA_DESC_LEN_MASK, e->dma_len);
WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
WRITE_ONCE(desc->addr, cpu_to_le32(e->dma_addr));
@@ -660,12 +730,169 @@ airoha_qdma_get_gdm_dev(struct airoha_et
@@ -657,13 +742,146 @@ airoha_qdma_get_gdm_dev(struct airoha_et
return port->devs[d] ? port->devs[d] : ERR_PTR(-ENODEV);
}
+static struct sk_buff *airoha_qdma_lro_rx_skb(struct airoha_queue *q,
+ struct airoha_qdma_desc *desc,
+ struct airoha_queue_entry *e)
+static int airoha_qdma_lro_rx_skb(struct airoha_queue *q,
+ struct airoha_qdma_desc *desc,
+ u32 msg1, u32 len)
+{
+ u32 len, th_off, tcp_ack_seq, agg_count, data_off, data_len;
+ u32 desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
+ u32 msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
+ u32 th_off, tcp_ack_seq, data_off, agg_count = 1;
+ u32 msg2 = le32_to_cpu(READ_ONCE(desc->msg2));
+ u32 msg3 = le32_to_cpu(READ_ONCE(desc->msg3));
+ struct skb_shared_info *shinfo;
+ struct sk_buff *skb = q->skb;
+ u16 tcp_win, l2_len;
+ struct sk_buff *skb;
+ struct tcphdr *th;
+ struct page *page;
+ bool ipv4, ipv6;
+
+ if (airoha_qdma_is_lro_queue(q)) {
+ agg_count = FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2);
+ if (agg_count > AIROHA_RXQ_LRO_MAX_AGG_COUNT)
+ return -EINVAL;
+ }
+
+ if (likely(agg_count <= 1)) /* not LRO */
+ return 0;
+
+ ipv4 = FIELD_GET(QDMA_ETH_RXMSG_IP4_MASK, msg1);
+ ipv6 = FIELD_GET(QDMA_ETH_RXMSG_IP6_MASK, msg1);
+ if (!ipv4 && !ipv6)
+ return NULL;
+ return 0;
+
+ l2_len = FIELD_GET(QDMA_ETH_RXMSG_L2_LEN_MASK, msg2);
+ len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
+
+ if (ipv4) {
+ struct iphdr *iph;
+ struct iphdr *iph, _iph;
+
+ if (len < l2_len + sizeof(*iph))
+ return NULL;
+ iph = skb_header_pointer(skb, l2_len, sizeof(*iph), &_iph);
+ if (!iph)
+ return -EINVAL;
+
+ iph = (struct iphdr *)(e->buf + l2_len);
+ if (iph->protocol != IPPROTO_TCP)
+ return NULL;
+ return -EINVAL;
+
+ if (iph->ihl < 5)
+ return NULL;
+ return -EINVAL;
+
+ th_off = l2_len + (iph->ihl << 2);
+ if (len < th_off)
+ return NULL;
+ if (!pskb_may_pull(skb, th_off))
+ return -EINVAL;
+
+ iph = (struct iphdr *)(skb->data + l2_len);
+ iph->tot_len = cpu_to_be16(len - l2_len);
+ iph->check = 0;
+ iph->check = ip_fast_csum((void *)iph, iph->ihl);
@@ -158,115 +215,86 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+ struct ipv6hdr *ip6h;
+
+ th_off = l2_len + sizeof(*ip6h);
+ if (len < th_off)
+ return NULL;
+ if (!pskb_may_pull(skb, th_off))
+ return -EINVAL;
+
+ ip6h = (struct ipv6hdr *)(e->buf + l2_len);
+ ip6h = (struct ipv6hdr *)(skb->data + l2_len);
+ if (ip6h->nexthdr != NEXTHDR_TCP)
+ return NULL;
+ return -EINVAL;
+
+ ip6h->payload_len = cpu_to_be16(len - th_off);
+ }
+
+ if (len < th_off + sizeof(*th))
+ return NULL;
+ data_off = th_off + sizeof(*th);
+ if (len < data_off)
+ return -EINVAL;
+
+ th = (struct tcphdr *)(e->buf + th_off);
+ if (!pskb_may_pull(skb, data_off))
+ return -EINVAL;
+
+ th = (struct tcphdr *)(skb->data + th_off);
+ if (th->doff < 5)
+ return NULL;
+ return -EINVAL;
+
+ data_off = th_off + (th->doff << 2);
+ if (len < data_off)
+ return NULL;
+ if (len <= data_off)
+ return -EINVAL;
+
+ tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK, msg3);
+ tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK,
+ le32_to_cpu(READ_ONCE(desc->msg3)));
+ tcp_ack_seq = le32_to_cpu(READ_ONCE(desc->data));
+ th->ack_seq = cpu_to_be32(tcp_ack_seq);
+ th->window = cpu_to_be16(tcp_win);
+
+ /* Check tcp timestamp option */
+ if (th->doff == (sizeof(*th) + TCPOLEN_TSTAMP_ALIGNED) / 4) {
+ u32 topt = get_unaligned_be32(th + 1);
+ u32 topt;
+
+ if (!pskb_may_pull(skb, data_off))
+ return -EINVAL;
+
+ th = (struct tcphdr *)(skb->data + th_off);
+ topt = get_unaligned_be32(th + 1);
+ if (topt == ((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
+ (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP)) {
+ u8 *ptr = (u8 *)th + sizeof(*th) + 2 * sizeof(__be32);
+ __le32 tcp_ts_reply = READ_ONCE(desc->tcp_ts_reply);
+
+ put_unaligned_be32(le32_to_cpu(tcp_ts_reply), ptr);
+ /* The field is pre-zeroed in the posted descriptor,
+ * so a non-zero value is the hardware confirming the
+ * timestamp echo reply is valid. Leave TSecr
+ * untouched otherwise, since the hardware may not
+ * populate it for every aggregate.
+ */
+ if (tcp_ts_reply)
+ put_unaligned_be32(le32_to_cpu(tcp_ts_reply),
+ ptr);
+ }
+ }
+
+ if (ipv4) {
+ struct iphdr *iph = (struct iphdr *)(e->buf + l2_len);
+ struct iphdr *iph = (struct iphdr *)(skb->data + l2_len);
+
+ th->check = ~tcp_v4_check(len - th_off, iph->saddr,
+ iph->daddr, 0);
+ } else {
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(e->buf + l2_len);
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(skb->data + l2_len);
+
+ th->check = ~tcp_v6_check(len - th_off, &ip6h->saddr,
+ &ip6h->daddr, 0);
+ }
+
+ skb = napi_alloc_skb(&q->napi, data_off);
+ if (!skb)
+ return NULL;
+
+ __skb_put(skb, data_off);
+ memcpy(skb->data, e->buf, data_off);
+
+ page = virt_to_head_page(e->buf);
+ data_len = len - data_off;
+ shinfo = skb_shinfo(skb);
+ skb_add_rx_frag(skb, shinfo->nr_frags, page,
+ e->buf + data_off - page_address(page), data_len,
+ q->buf_size);
+
+ shinfo->gso_type = ipv4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
+ agg_count = FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2);
+ shinfo->gso_size = DIV_ROUND_UP(data_len, agg_count);
+ shinfo->gso_type |= SKB_GSO_DODGY;
+ shinfo->gso_size = DIV_ROUND_UP(len - data_off, agg_count);
+ shinfo->gso_segs = agg_count;
+
+ skb->csum_start = skb_headroom(skb) + th_off;
+ skb->csum_offset = offsetof(struct tcphdr, check);
+ skb->ip_summed = CHECKSUM_PARTIAL;
+
+ return skb;
+}
+
+static struct sk_buff *airoha_qdma_build_rx_skb(struct airoha_queue *q,
+ struct airoha_qdma_desc *desc,
+ struct airoha_queue_entry *e,
+ struct net_device *netdev)
+{
+ u32 msg2 = le32_to_cpu(READ_ONCE(desc->msg2));
+ int qid = q - &q->qdma->q_rx[0];
+ struct sk_buff *skb;
+
+ if (FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2) > 1) { /* LRO */
+ skb = airoha_qdma_lro_rx_skb(q, desc, e);
+ if (!skb)
+ return NULL;
+ } else {
+ u32 desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
+ u32 len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
+
+ skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM, q->buf_size);
+ if (!skb)
+ return NULL;
+
+ skb_reserve(skb, AIROHA_RX_HEADROOM);
+ __skb_put(skb, len);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+
+ skb_mark_for_recycle(skb);
+ skb->dev = netdev;
+ skb_record_rx_queue(skb, qid);
+ skb->protocol = eth_type_trans(skb, netdev);
+
+ return skb;
+ return 0;
+}
+
static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
@@ -275,31 +303,50 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
- struct airoha_qdma *qdma = q->qdma;
- struct airoha_eth *eth = qdma->eth;
- int qid = q - &qdma->q_rx[0];
- int done = 0;
+ int qid = q - &q->qdma->q_rx[0], done = 0;
+ struct airoha_eth *eth = q->qdma->eth;
int done = 0;
while (done < budget) {
@@ -701,18 +928,9 @@ static int airoha_qdma_rx_process(struct
struct airoha_queue_entry *e = &q->entry[q->tail];
@@ -696,7 +914,9 @@ static int airoha_qdma_rx_process(struct
if (IS_ERR(dev))
goto free_frag;
+ msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
netdev = netdev_from_priv(dev);
+
if (!q->skb) { /* first buffer */
- q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
- q->buf_size);
+ q->skb = airoha_qdma_build_rx_skb(q, desc, e, netdev);
if (!q->skb)
goto free_frag;
-
- skb_reserve(q->skb, AIROHA_RX_HEADROOM);
- __skb_put(q->skb, len);
- skb_mark_for_recycle(q->skb);
- q->skb->dev = netdev;
q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
q->buf_size);
@@ -707,9 +927,17 @@ static int airoha_qdma_rx_process(struct
__skb_put(q->skb, len);
skb_mark_for_recycle(q->skb);
q->skb->dev = netdev;
- q->skb->protocol = eth_type_trans(q->skb, netdev);
- q->skb->ip_summed = CHECKSUM_UNNECESSARY;
- skb_record_rx_queue(q->skb, qid);
q->skb->ip_summed = CHECKSUM_UNNECESSARY;
skb_record_rx_queue(q->skb, qid);
+
+ if (airoha_qdma_lro_rx_skb(q, desc, msg1, len)) {
+ dev_core_stats_rx_dropped_inc(netdev);
+ dev_kfree_skb(q->skb);
+ q->skb = NULL;
+ continue;
+ }
+
+ q->skb->protocol = eth_type_trans(q->skb, netdev);
} else { /* scattered frame */
struct skb_shared_info *shinfo = skb_shinfo(q->skb);
int nr_frags = shinfo->nr_frags;
@@ -803,12 +1021,10 @@ static int airoha_qdma_rx_napi_poll(stru
@@ -742,7 +970,6 @@ static int airoha_qdma_rx_process(struct
&port->dsa_meta[sptag]->dst);
}
- msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
hash = FIELD_GET(AIROHA_RXD4_FOE_ENTRY, msg1);
if (hash != AIROHA_RXD4_FOE_ENTRY)
skb_set_hash(q->skb, jhash_1word(hash, 0),
@@ -800,12 +1027,10 @@ static int airoha_qdma_rx_napi_poll(stru
static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
struct airoha_qdma *qdma, int ndesc)
{
@@ -313,7 +360,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
.nid = NUMA_NO_NODE,
.dev = qdma->eth->dev,
.napi = &q->napi,
@@ -816,9 +1032,10 @@ static int airoha_qdma_init_rx_queue(str
@@ -813,9 +1038,10 @@ static int airoha_qdma_init_rx_queue(str
struct airoha_eth *eth = qdma->eth;
int qid = q - &qdma->q_rx[0], thr;
dma_addr_t dma_addr;
@@ -325,7 +372,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
GFP_KERNEL);
@@ -830,6 +1047,9 @@ static int airoha_qdma_init_rx_queue(str
@@ -827,6 +1053,9 @@ static int airoha_qdma_init_rx_queue(str
if (!q->desc)
return -ENOMEM;
@@ -335,7 +382,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
q->page_pool = page_pool_create(&pp_params);
if (IS_ERR(q->page_pool)) {
int err = PTR_ERR(q->page_pool);
@@ -838,6 +1058,7 @@ static int airoha_qdma_init_rx_queue(str
@@ -835,6 +1064,7 @@ static int airoha_qdma_init_rx_queue(str
return err;
}
@@ -343,7 +390,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
q->ndesc = ndesc;
netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
@@ -851,7 +1072,12 @@ static int airoha_qdma_init_rx_queue(str
@@ -848,7 +1078,12 @@ static int airoha_qdma_init_rx_queue(str
FIELD_PREP(RX_RING_THR_MASK, thr));
airoha_qdma_rmw(qdma, REG_RX_DMA_IDX(qid), RX_RING_DMA_IDX_MASK,
FIELD_PREP(RX_RING_DMA_IDX_MASK, q->head));
@@ -357,7 +404,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
airoha_qdma_fill_rx_queue(q);
@@ -873,6 +1099,7 @@ static void airoha_qdma_cleanup_rx_queue
@@ -870,6 +1105,7 @@ static void airoha_qdma_cleanup_rx_queue
page_pool_get_dma_dir(q->page_pool));
page_pool_put_full_page(q->page_pool, page, false);
/* Reset DMA descriptor */
@@ -365,8 +412,8 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
WRITE_ONCE(desc->ctrl, 0);
WRITE_ONCE(desc->addr, 0);
WRITE_ONCE(desc->data, 0);
@@ -1914,13 +2141,56 @@ static void airoha_dev_set_xmit_frame_si
VLAN_ETH_HLEN + netdev->mtu));
@@ -1896,6 +2132,34 @@ static void airoha_update_hw_stats(struc
spin_unlock(&port->stats_lock);
}
+static void airoha_update_netdev_features(struct airoha_gdm_dev *dev)
@@ -385,10 +432,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+ struct airoha_gdm_dev *iter_dev = port->devs[j];
+ struct net_device *netdev;
+
+ if (!iter_dev || iter_dev == dev)
+ continue;
+ if (rcu_access_pointer(iter_dev->qdma) !=
+ rcu_access_pointer(dev->qdma))
+ if (!iter_dev)
+ continue;
+
+ netdev = netdev_from_priv(iter_dev);
@@ -400,84 +444,94 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+ }
+}
+
static int airoha_dev_open(struct net_device *netdev)
static void airoha_dev_set_xmit_frame_size(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
@@ -1913,14 +2177,28 @@ static int airoha_dev_open(struct net_de
struct airoha_gdm_port *port = dev->port;
u32 pse_port = FE_PSE_PORT_PPE1;
struct airoha_qdma *qdma;
- int err;
+ struct airoha_eth *eth;
+ int err, qdma_id;
+ int qdma_id, err;
+
+ /* LRO is configured on the QDMA and it is shared between all
+ * the devices using it. Refuse to open a second device on the
+ * same QDMA if LRO is enabled on any device sharing it or if
+ * this device has LRO enabled in its feature set.
+ */
+ qdma = airoha_qdma_deref(dev);
+ eth = qdma->eth;
+ qdma_id = qdma - &eth->qdma[0];
+ qdma_id = qdma - &qdma->eth->qdma[0];
+
+ /* HW LRO is shared by all netdevs using the same QDMA. */
+ if (qdma->users && airoha_fe_lro_is_enabled(eth, qdma_id)) {
+ netdev_warn(netdev, "required to disable HW GRO on QDMA%d\n",
+ if (qdma->users > 1 && (airoha_fe_lro_is_enabled(qdma->eth, qdma_id) ||
+ (netdev->features & NETIF_F_LRO))) {
+ netdev_warn(netdev, "required to disable LRO on QDMA%d\n",
+ qdma_id);
+ return -EBUSY;
+ }
err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
if (err) {
@@ -1935,24 +2205,29 @@ static int airoha_dev_open(struct net_de
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
return err;
- qdma = airoha_qdma_deref(dev);
/* It seems GDM3 and GDM4 needs SPORT enabled to correctly work */
if (netdev_uses_dsa(netdev) || port->id > 2)
- airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ airoha_fe_set(eth, REG_GDM_INGRESS_CFG(port->id),
if (netdev_uses_dsa(netdev))
airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
else
- airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ airoha_fe_clear(eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
airoha_dev_set_xmit_frame_size(netdev);
port->users++;
+ qdma->users++;
if (!airoha_is_lan_gdm_dev(dev) &&
- airoha_ppe_is_enabled(qdma->eth, 1))
+ airoha_ppe_is_enabled(eth, 1))
pse_port = FE_PSE_PORT_PPE2;
- airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
@@ -1937,6 +2215,9 @@ static int airoha_dev_open(struct net_de
airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
pse_port);
+ if (netdev->features & NETIF_F_GRO_HW)
+ if (netdev->features & NETIF_F_LRO)
+ airoha_dev_lro_enable(dev);
+
+ airoha_update_netdev_features(dev);
+
return 0;
}
@@ -1960,6 +2235,7 @@ static int airoha_dev_stop(struct net_de
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma;
netif_tx_disable(netdev);
airoha_set_vip_for_gdm_port(dev, false);
@@ -1971,6 +2247,10 @@ static int airoha_dev_stop(struct net_de
@@ -1954,6 +2235,10 @@ static int airoha_dev_stop(struct net_de
airoha_set_gdm_port_fwd_cfg(dev->eth,
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
+ qdma = airoha_qdma_deref(dev);
+ qdma->users--;
+ airoha_update_netdev_features(dev);
+
phylink_stop(dev->phylink);
phylink_disconnect_phy(dev->phylink);
+ if (netdev->features & NETIF_F_LRO)
+ airoha_dev_lro_disable(dev);
+
return 0;
}
@@ -2308,6 +2588,38 @@ int airoha_get_fe_port(struct airoha_gdm
@@ -2115,11 +2400,14 @@ static void airoha_dev_set_qdma(struct a
qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
cur_qdma = airoha_qdma_deref(dev);
- if (cur_qdma)
+ if (cur_qdma) {
netif_tx_stop_all_queues(netdev);
+ airoha_dev_lro_disable(dev);
+ }
rcu_assign_pointer(dev->qdma, qdma);
netdev->irq = qdma->irq_banks[0].irq;
+ qdma->users++;
synchronize_rcu();
ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
@@ -2136,8 +2424,14 @@ static void airoha_dev_set_qdma(struct a
airoha_qdma_rr(qdma, REG_CNTR_VAL((i << 1) + 1));
}
- if (cur_qdma)
+ if (cur_qdma) {
+ cur_qdma->users--;
netif_tx_wake_all_queues(netdev);
+ }
+
+ airoha_update_netdev_features(dev);
+ if (netdev->features & NETIF_F_LRO)
+ airoha_dev_lro_enable(dev);
}
static int airoha_dev_init(struct net_device *netdev)
@@ -2288,6 +2582,32 @@ int airoha_get_fe_port(struct airoha_gdm
}
}
@@ -489,7 +543,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+
+ qdma = airoha_qdma_deref(dev);
+ if (qdma->users > 1)
+ features &= ~NETIF_F_GRO_HW;
+ features &= ~NETIF_F_LRO;
+
+ return features;
+}
@@ -497,18 +551,12 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+static int airoha_dev_set_features(struct net_device *netdev,
+ netdev_features_t features)
+{
+ netdev_features_t diff = netdev->features ^ features;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ int qdma_id = qdma - &dev->eth->qdma[0];
+
+ if (!(diff & NETIF_F_GRO_HW))
+ return 0;
+
+ if (features & NETIF_F_GRO_HW)
+ if (features & NETIF_F_LRO)
+ airoha_dev_lro_enable(dev);
+ else
+ airoha_fe_lro_disable(dev->eth, qdma_id);
+ airoha_dev_lro_disable(dev);
+
+ return 0;
+}
@@ -516,7 +564,24 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
@@ -3401,6 +3713,8 @@ static const struct net_device_ops airoh
@@ -3219,7 +3539,7 @@ static int airoha_enable_qos_for_gdm34(s
struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
- int err = -EBUSY;
+ int err;
if (port->id != AIROHA_GDM3_IDX &&
port->id != AIROHA_GDM4_IDX) {
@@ -3238,6 +3558,7 @@ static int airoha_enable_qos_for_gdm34(s
wan_dev->port->id == AIROHA_GDM2_IDX) {
NL_SET_ERR_MSG_MOD(extack,
"QoS configured for WAN device");
+ err = -EBUSY;
goto error_unlock;
}
airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
@@ -3363,6 +3684,8 @@ static const struct net_device_ops airoh
.ndo_stop = airoha_dev_stop,
.ndo_change_mtu = airoha_dev_change_mtu,
.ndo_select_queue = airoha_dev_select_queue,
@@ -525,7 +590,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
.ndo_start_xmit = airoha_dev_xmit,
.ndo_get_stats64 = airoha_dev_get_stats64,
.ndo_set_mac_address = airoha_dev_set_macaddr,
@@ -3638,11 +3952,9 @@ static int airoha_alloc_gdm_device(struc
@@ -3450,11 +3773,9 @@ static int airoha_alloc_gdm_device(struc
netdev->ethtool_ops = &airoha_ethtool_ops;
netdev->max_mtu = AIROHA_MAX_MTU;
netdev->watchdog_timeo = 5 * HZ;
@@ -546,9 +611,9 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
(_n) == 15 ? 128 : \
(_n) == 0 ? 1024 : 32)
+#define AIROHA_LRO_PAGE_ORDER order_base_2(SZ_16K / PAGE_SIZE)
+#define AIROHA_LRO_PAGE_ORDER get_order(SZ_16K)
+#define AIROHA_MAX_NUM_LRO_QUEUES 8
+#define AIROHA_RXQ_LRO_EN_MASK GENMASK(7, 0)
+#define AIROHA_RXQ_LRO_EN_MASK GENMASK(31, 24)
+#define AIROHA_RXQ_LRO_MAX_AGG_COUNT 64
+#define AIROHA_RXQ_LRO_MAX_AGG_TIME 100
+#define AIROHA_RXQ_LRO_MAX_AGE_TIME 2000
@@ -561,16 +626,16 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
#define PSE_RSV_PAGES 128
#define PSE_QUEUE_RSV_PAGES 64
@@ -562,6 +574,8 @@ struct airoha_irq_bank {
struct airoha_qdma {
@@ -563,6 +575,8 @@ struct airoha_qdma {
struct airoha_eth *eth;
void __iomem *regs;
+ /* Number of running netdevs; DMA lifetime is managed at probe/remove. */
+ int users;
+ int users;
+
struct airoha_irq_bank irq_banks[AIROHA_MAX_NUM_IRQ_BANKS];
@@ -720,6 +734,18 @@ static inline bool airoha_is_7583(struct
struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
@@ -717,6 +731,18 @@ static inline bool airoha_is_7583(struct
return eth->soc->version == 0x7583;
}
@@ -612,7 +677,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
#define REG_GDM_FWD_CFG(_n) GDM_BASE(_n)
#define GDM_PAD_EN_MASK BIT(28)
#define GDM_DROP_CRC_ERR_MASK BIT(23)
@@ -899,9 +913,15 @@
@@ -887,9 +901,16 @@
#define QDMA_ETH_RXMSG_SPORT_MASK GENMASK(25, 21)
#define QDMA_ETH_RXMSG_CRSN_MASK GENMASK(20, 16)
#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK GENMASK(15, 0)
@@ -620,6 +685,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+#define QDMA_ETH_RXMSG_AGG_COUNT_MASK GENMASK(31, 24)
+#define QDMA_ETH_RXMSG_L2_LEN_MASK GENMASK(6, 0)
+/* RX MSG3 */
+/* max aggregated size in bytes */
+#define QDMA_ETH_RXMSG_AGG_LEN_MASK GENMASK(31, 16)
+#define QDMA_ETH_RXMSG_TCP_WIN_MASK GENMASK(15, 0)
@@ -13,7 +13,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1623,6 +1623,10 @@ static int airoha_hw_init(struct platfor
@@ -1859,6 +1859,10 @@ static int airoha_hw_init(struct platfor
if (err)
return err;
@@ -15,7 +15,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -558,8 +558,10 @@ static int airoha_fe_init(struct airoha_
@@ -642,8 +642,10 @@ static int airoha_fe_init(struct airoha_
FIELD_PREP(IP_ASSEMBLE_PORT_MASK, 0) |
FIELD_PREP(IP_ASSEMBLE_NBQ_MASK, 22));
@@ -28,10 +28,10 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
/* Enable split for MIB counters for GDM3 and GDM4 */
airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM3_IDX),
@@ -1925,7 +1927,8 @@ static int airoha_dev_open(struct net_de
@@ -2203,7 +2205,8 @@ static int airoha_dev_open(struct net_de
if (err)
return err;
qdma = airoha_qdma_deref(dev);
- if (netdev_uses_dsa(netdev))
+ /* It seems GDM3 and GDM4 needs SPORT enabled to correctly work */
+ if (netdev_uses_dsa(netdev) || port->id > 2)
@@ -15,7 +15,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -3782,7 +3782,6 @@ static void airoha_remove(struct platfor
@@ -4103,7 +4103,6 @@ static void airoha_remove(struct platfor
}
static const char * const en7581_xsi_rsts_names[] = {
@@ -23,7 +23,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
"hsi0-mac",
"hsi1-mac",
"hsi-mac",
@@ -3869,7 +3868,6 @@ static int airoha_en7581_get_dev_from_sp
@@ -4190,7 +4189,6 @@ static int airoha_en7581_get_dev_from_sp
}
static const char * const an7583_xsi_rsts_names[] = {
@@ -41,8 +41,8 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+#include <linux/pcs/pcs.h>
#include <linux/u64_stats_sync.h>
#include <net/dst_metadata.h>
#include <net/page_pool/helpers.h>
@@ -1705,7 +1706,7 @@ static void airoha_update_hw_stats(struc
#include <net/ip6_checksum.h>
@@ -1941,7 +1942,7 @@ static void airoha_update_hw_stats(struc
u32 val, i = 0;
u64 data;
@@ -51,7 +51,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
/* Read relevant MIB for GDM with multiple port attached */
if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
@@ -1899,7 +1900,7 @@ static void airoha_update_hw_stats(struc
@@ -2135,7 +2136,7 @@ static void airoha_update_hw_stats(struc
u64_stats_update_end(&dev->stats.syncp);
@@ -59,10 +59,10 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+ spin_unlock(&port->lock);
}
static void airoha_dev_set_xmit_frame_size(struct net_device *netdev)
@@ -1921,6 +1922,14 @@ static int airoha_dev_open(struct net_de
struct airoha_qdma *qdma;
int err;
static void airoha_update_netdev_features(struct airoha_gdm_dev *dev)
@@ -2200,6 +2201,14 @@ static int airoha_dev_open(struct net_de
return -EBUSY;
}
+ err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
+ if (err) {
@@ -75,18 +75,17 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
@@ -1961,6 +1970,10 @@ static int airoha_dev_stop(struct net_de
airoha_set_gdm_port_fwd_cfg(dev->eth,
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
+
@@ -2246,6 +2255,9 @@ static int airoha_dev_stop(struct net_de
if (netdev->features & NETIF_F_LRO)
airoha_dev_lro_disable(dev);
+ phylink_stop(dev->phylink);
+ phylink_disconnect_phy(dev->phylink);
+
return 0;
}
@@ -2516,6 +2529,24 @@ airoha_ethtool_get_rmon_stats(struct net
@@ -2836,6 +2848,24 @@ airoha_ethtool_get_rmon_stats(struct net
} while (u64_stats_fetch_retry(&dev->stats.syncp, start));
}
@@ -111,7 +110,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
int channel, enum tx_sched_mode mode,
const u16 *weights, u8 n_weights)
@@ -3380,7 +3411,8 @@ static const struct ethtool_ops airoha_e
@@ -3703,7 +3733,8 @@ static const struct ethtool_ops airoha_e
.get_drvinfo = airoha_ethtool_get_drvinfo,
.get_eth_mac_stats = airoha_ethtool_get_mac_stats,
.get_rmon_stats = airoha_ethtool_get_rmon_stats,
@@ -121,7 +120,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
.get_link = ethtool_op_get_link,
};
@@ -3436,6 +3468,155 @@ bool airoha_is_valid_gdm_dev(struct airo
@@ -3759,6 +3790,155 @@ bool airoha_is_valid_gdm_dev(struct airo
return false;
}
@@ -277,7 +276,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
static int airoha_alloc_gdm_device(struct airoha_eth *eth,
struct airoha_gdm_port *port,
int nbq, struct device_node *np)
@@ -3501,7 +3682,7 @@ static int airoha_alloc_gdm_device(struc
@@ -3822,7 +4002,7 @@ static int airoha_alloc_gdm_device(struc
dev->nbq = nbq;
port->devs[index] = dev;
@@ -286,7 +285,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
}
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
@@ -3536,7 +3717,7 @@ static int airoha_alloc_gdm_port(struct
@@ -3857,7 +4037,7 @@ static int airoha_alloc_gdm_port(struct
return -ENOMEM;
port->id = id;
@@ -295,7 +294,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
eth->ports[p] = port;
err = airoha_metadata_dst_alloc(port);
@@ -3733,6 +3914,8 @@ error_napi_stop:
@@ -4054,6 +4234,8 @@ error_napi_stop:
netdev = netdev_from_priv(dev);
if (netdev->reg_state == NETREG_REGISTERED)
unregister_netdev(netdev);
@@ -304,7 +303,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
of_node_put(netdev->dev.of_node);
}
airoha_metadata_dst_free(port);
@@ -3771,6 +3954,7 @@ static void airoha_remove(struct platfor
@@ -4092,6 +4274,7 @@ static void airoha_remove(struct platfor
netdev = netdev_from_priv(dev);
unregister_netdev(netdev);
@@ -314,7 +313,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
airoha_metadata_dst_free(port);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -598,6 +598,9 @@ struct airoha_gdm_dev {
@@ -612,6 +612,9 @@ struct airoha_gdm_dev {
* QDMA migration.
*/
spinlock_t txq_lock[AIROHA_NUM_NETDEV_TX_RINGS];
@@ -324,7 +323,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
};
struct airoha_gdm_port {
@@ -605,8 +608,8 @@ struct airoha_gdm_port {
@@ -619,8 +622,8 @@ struct airoha_gdm_port {
int id;
int users;
@@ -337,7 +336,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
};
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -357,6 +357,18 @@
@@ -371,6 +371,18 @@
#define IP_FRAGMENT_PORT_MASK GENMASK(8, 5)
#define IP_FRAGMENT_NBQ_MASK GENMASK(4, 0)
@@ -1,111 +0,0 @@
From e928621a0bbd010b75624c77105ce31f2b8d2faf Mon Sep 17 00:00:00 2001
Message-ID: <e928621a0bbd010b75624c77105ce31f2b8d2faf.1779348625.git.lorenzo@kernel.org>
In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
From: Lorenzo Bianconi <lorenzo@kernel.org>
Date: Sat, 13 Dec 2025 09:45:09 +0100
Subject: [PATCH 12/13] net: airoha: Add ethtool priv_flags callbacks
Introduce ethtool priv_flags callbacks in order to allow the user to
select if the configured device will be used as hw lan or wan and enable
or disable gdm2 loopback (used for hw QoS).
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 173 ++++++++++++++++++++++
drivers/net/ethernet/airoha/airoha_regs.h | 1 +
2 files changed, 174 insertions(+)
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2157,10 +2157,7 @@ static void airoha_update_netdev_feature
struct airoha_gdm_dev *iter_dev = port->devs[j];
struct net_device *netdev;
- if (!iter_dev || iter_dev == dev)
- continue;
- if (rcu_access_pointer(iter_dev->qdma) !=
- rcu_access_pointer(dev->qdma))
+ if (!iter_dev)
continue;
netdev = netdev_from_priv(iter_dev);
@@ -2172,6 +2169,16 @@ static void airoha_update_netdev_feature
}
}
+static void airoha_qdma_user_get(struct airoha_qdma *qdma)
+{
+ qdma->users++;
+}
+
+static void airoha_qdma_user_put(struct airoha_qdma *qdma)
+{
+ qdma->users--;
+}
+
static int airoha_dev_open(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
@@ -2215,7 +2222,7 @@ static int airoha_dev_open(struct net_de
airoha_dev_set_xmit_frame_size(netdev);
port->users++;
- qdma->users++;
+ airoha_qdma_user_get(qdma);
if (!airoha_is_lan_gdm_dev(dev) &&
airoha_ppe_is_enabled(eth, 1))
@@ -2248,7 +2255,7 @@ static int airoha_dev_stop(struct net_de
FE_PSE_PORT_DROP);
qdma = airoha_qdma_deref(dev);
- qdma->users--;
+ airoha_qdma_user_put(qdma);
airoha_update_netdev_features(dev);
phylink_stop(dev->phylink);
@@ -2409,17 +2416,28 @@ static void airoha_dev_set_qdma(struct a
struct net_device *netdev = netdev_from_priv(dev);
struct airoha_qdma *cur_qdma, *qdma;
struct airoha_eth *eth = dev->eth;
+ bool running = netif_running(netdev);
int i, ppe_id;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
cur_qdma = airoha_qdma_deref(dev);
- if (cur_qdma)
+ if (cur_qdma) {
netif_tx_stop_all_queues(netdev);
+ if (running && cur_qdma != qdma) {
+ int id = cur_qdma - &eth->qdma[0];
+
+ if (netdev->features & NETIF_F_GRO_HW)
+ airoha_fe_lro_disable(eth, id);
+ airoha_qdma_user_put(cur_qdma);
+ }
+ }
rcu_assign_pointer(dev->qdma, qdma);
netdev->irq = qdma->irq_banks[0].irq;
+ if (running && cur_qdma != qdma)
+ airoha_qdma_user_get(qdma);
synchronize_rcu();
ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
@@ -2438,6 +2456,12 @@ static void airoha_dev_set_qdma(struct a
if (cur_qdma)
netif_tx_wake_all_queues(netdev);
+
+ if (cur_qdma != qdma) {
+ airoha_update_netdev_features(dev);
+ if (running && (netdev->features & NETIF_F_GRO_HW))
+ airoha_dev_lro_enable(dev);
+ }
}
static int airoha_dev_init(struct net_device *netdev)
@@ -16,7 +16,7 @@ Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -3577,9 +3577,9 @@ static void airoha_disable_qos_for_gdm34
@@ -3560,9 +3560,9 @@ static void airoha_disable_qos_for_gdm34
clear_bit(AIROHA_DEV_F_WAN, &dev->flags);
airoha_dev_set_qdma(dev);
@@ -18,7 +18,7 @@ Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2599,17 +2599,9 @@ static u32 airoha_get_dsa_tag(struct sk_
@@ -2588,17 +2588,9 @@ static u32 airoha_get_dsa_tag(struct sk_
int airoha_get_fe_port(struct airoha_gdm_dev *dev)
{
struct airoha_gdm_port *port = dev->port;