--- /dev/null
+From a459b560e58be327689e9bd8c0a6be9a4f163366 Mon Sep 17 00:00:00 2001
+Message-ID: <a459b560e58be327689e9bd8c0a6be9a4f163366.1781641894.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 11 Jun 2026 23:55:51 +0200
+Subject: [PATCH] net: airoha: use int instead of atomic_t for qdma users
+ counter
+
+QDMA users counter is always accessed holding RTNL lock so we do not
+require atomic_t for it.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 4 ++--
+ drivers/net/ethernet/airoha/airoha_eth.h | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1761,7 +1761,7 @@ static int airoha_dev_open(struct net_de
+ airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+- atomic_inc(&qdma->users);
++ qdma->users++;
+
+ if (port->id == AIROHA_GDM2_IDX &&
+ airoha_ppe_is_enabled(qdma->eth, 1)) {
+@@ -1788,7 +1788,7 @@ static int airoha_dev_stop(struct net_de
+ airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_DROP);
+
+- if (atomic_dec_and_test(&qdma->users)) {
++ if (!--qdma->users) {
+ airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -525,7 +525,7 @@ struct airoha_qdma {
+ struct airoha_eth *eth;
+ void __iomem *regs;
+
+- atomic_t users;
++ int users;
+
+ struct airoha_irq_bank irq_banks[AIROHA_MAX_NUM_IRQ_BANKS];
+
--- /dev/null
+From 598e1ddfe85ad0f4778eeadd5d878209dd931280 Mon Sep 17 00:00:00 2001
+Message-ID: <598e1ddfe85ad0f4778eeadd5d878209dd931280.1779112628.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 14 May 2026 20:25:19 +0200
+Subject: [PATCH] net: airoha: Implement LRO TCP support
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@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(-)
+
+--- 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>
+ #include <linux/u64_stats_sync.h>
+ #include <net/dst_metadata.h>
++#include <net/ip6_checksum.h>
+ #include <net/page_pool/helpers.h>
+ #include <net/pkt_cls.h>
++#include <net/tcp.h>
+ #include <uapi/linux/ppp_defs.h>
+
+ #include "airoha_regs.h"
+@@ -439,6 +441,73 @@ static void airoha_fe_crsn_qsel_init(str
+ CDM_CRSN_QSEL_Q1));
+ }
+
++static void airoha_fe_lro_rxq_enable(struct airoha_eth *eth, int qdma_id,
++ int lro_queue_index, int qid,
++ int buf_size)
++{
++ int id = qdma_id + 1;
++
++ airoha_fe_rmw(eth, REG_CDM_LRO_LIMIT(id),
++ CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK,
++ FIELD_PREP(CDM_LRO_AGG_SIZE_MASK, buf_size) |
++ FIELD_PREP(CDM_LRO_AGG_NUM_MASK,
++ AIROHA_RXQ_LRO_MAX_AGG_COUNT));
++ airoha_fe_rmw(eth, REG_CDM_LRO_AGE_TIME(id),
++ CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK,
++ FIELD_PREP(CDM_LRO_AGE_TIME_MASK,
++ AIROHA_RXQ_LRO_MAX_AGE_TIME) |
++ FIELD_PREP(CDM_LRO_AGG_TIME_MASK,
++ AIROHA_RXQ_LRO_MAX_AGG_TIME));
++ airoha_fe_rmw(eth, REG_CDM_LRO_RXQ(id, lro_queue_index),
++ LRO_RXQ_MASK(lro_queue_index),
++ __field_prep(LRO_RXQ_MASK(lro_queue_index), qid));
++ airoha_fe_set(eth, REG_CDM_LRO_EN(id), BIT(lro_queue_index));
++}
++
++static void airoha_fe_lro_disable(struct airoha_eth *eth, int qdma_id)
++{
++ int i, id = qdma_id + 1;
++
++ airoha_fe_clear(eth, REG_CDM_LRO_EN(id), LRO_RXQ_EN_MASK);
++ airoha_fe_clear(eth, REG_CDM_LRO_LIMIT(id),
++ CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK);
++ airoha_fe_clear(eth, REG_CDM_LRO_AGE_TIME(id),
++ CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK);
++ for (i = 0; i < AIROHA_MAX_NUM_LRO_QUEUES; i++)
++ airoha_fe_clear(eth, REG_CDM_LRO_RXQ(id, i), LRO_RXQ_MASK(i));
++}
++
++static bool airoha_fe_lro_is_enabled(struct airoha_eth *eth, int qdma_id)
++{
++ return airoha_fe_get(eth, REG_CDM_LRO_EN(qdma_id + 1),
++ LRO_RXQ_EN_MASK);
++}
++
++static void airoha_dev_lro_enable(struct airoha_gdm_port *port)
++{
++ struct airoha_qdma *qdma = port->qdma;
++ struct airoha_eth *eth = qdma->eth;
++ int qdma_id = qdma - ð->qdma[0];
++ int i, lro_queue_index = 0;
++
++ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++ struct airoha_queue *q = &qdma->q_rx[i];
++ u32 size;
++
++ if (!q->ndesc)
++ continue;
++
++ if (!airoha_qdma_is_lro_queue(q))
++ continue;
++
++ size = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
++ size = min_t(u32, size, CDM_LRO_AGG_SIZE_MASK);
++ airoha_fe_lro_rxq_enable(eth, qdma_id, lro_queue_index, i,
++ size);
++ lro_queue_index++;
++ }
++}
++
+ static int airoha_fe_init(struct airoha_eth *eth)
+ {
+ airoha_fe_maccr_init(eth);
+@@ -558,6 +627,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));
+
++ WRITE_ONCE(desc->tcp_ts_reply, 0);
+ 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));
+@@ -603,12 +673,169 @@ static int airoha_qdma_get_gdm_port(stru
+ return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
+ }
+
++static struct sk_buff *airoha_qdma_lro_rx_skb(struct airoha_queue *q,
++ struct airoha_qdma_desc *desc,
++ struct airoha_queue_entry *e)
++{
++ 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 msg2 = le32_to_cpu(READ_ONCE(desc->msg2));
++ u32 msg3 = le32_to_cpu(READ_ONCE(desc->msg3));
++ struct skb_shared_info *shinfo;
++ u16 tcp_win, l2_len;
++ struct sk_buff *skb;
++ struct tcphdr *th;
++ struct page *page;
++ bool ipv4, ipv6;
++
++ ipv4 = FIELD_GET(QDMA_ETH_RXMSG_IP4_MASK, msg1);
++ ipv6 = FIELD_GET(QDMA_ETH_RXMSG_IP6_MASK, msg1);
++ if (!ipv4 && !ipv6)
++ return NULL;
++
++ 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;
++
++ if (len < l2_len + sizeof(*iph))
++ return NULL;
++
++ iph = (struct iphdr *)(e->buf + l2_len);
++ if (iph->protocol != IPPROTO_TCP)
++ return NULL;
++
++ if (iph->ihl < 5)
++ return NULL;
++
++ th_off = l2_len + (iph->ihl << 2);
++ if (len < th_off)
++ return NULL;
++
++ iph->tot_len = cpu_to_be16(len - l2_len);
++ iph->check = 0;
++ iph->check = ip_fast_csum((void *)iph, iph->ihl);
++ } else {
++ struct ipv6hdr *ip6h;
++
++ th_off = l2_len + sizeof(*ip6h);
++ if (len < th_off)
++ return NULL;
++
++ ip6h = (struct ipv6hdr *)(e->buf + l2_len);
++ if (ip6h->nexthdr != NEXTHDR_TCP)
++ return NULL;
++
++ ip6h->payload_len = cpu_to_be16(len - th_off);
++ }
++
++ if (len < th_off + sizeof(*th))
++ return NULL;
++
++ th = (struct tcphdr *)(e->buf + th_off);
++ if (th->doff < 5)
++ return NULL;
++
++ data_off = th_off + (th->doff << 2);
++ if (len < data_off)
++ return NULL;
++
++ tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK, 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);
++
++ 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);
++ }
++ }
++
++ if (ipv4) {
++ struct iphdr *iph = (struct iphdr *)(e->buf + 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);
++
++ 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_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;
++}
++
+ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
+ {
+ enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
+- struct airoha_qdma *qdma = q->qdma;
+- struct airoha_eth *eth = qdma->eth;
+- int qid = q - &qdma->q_rx[0];
++ struct airoha_eth *eth = q->qdma->eth;
+ int done = 0;
+
+ while (done < budget) {
+@@ -643,18 +870,9 @@ static int airoha_qdma_rx_process(struct
+
+ port = eth->ports[p];
+ 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, port->dev);
+ 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 = port->dev;
+- q->skb->protocol = eth_type_trans(q->skb, port->dev);
+- q->skb->ip_summed = CHECKSUM_UNNECESSARY;
+- skb_record_rx_queue(q->skb, qid);
+ } else { /* scattered frame */
+ struct skb_shared_info *shinfo = skb_shinfo(q->skb);
+ int nr_frags = shinfo->nr_frags;
+@@ -743,12 +961,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)
+ {
+- const struct page_pool_params pp_params = {
+- .order = 0,
++ struct page_pool_params pp_params = {
+ .pool_size = 256,
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .dma_dir = DMA_FROM_DEVICE,
+- .max_len = PAGE_SIZE,
+ .nid = NUMA_NO_NODE,
+ .dev = qdma->eth->dev,
+ .napi = &q->napi,
+@@ -756,9 +972,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;
++ bool lro_q;
+
+- q->buf_size = PAGE_SIZE / 2;
+ q->qdma = qdma;
++ lro_q = airoha_qdma_is_lro_queue(q);
+
+ q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
+ GFP_KERNEL);
+@@ -770,6 +987,9 @@ static int airoha_qdma_init_rx_queue(str
+ if (!q->desc)
+ return -ENOMEM;
+
++ pp_params.order = lro_q ? AIROHA_LRO_PAGE_ORDER : 0;
++ pp_params.max_len = PAGE_SIZE << pp_params.order;
++
+ q->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(q->page_pool)) {
+ int err = PTR_ERR(q->page_pool);
+@@ -778,6 +998,7 @@ static int airoha_qdma_init_rx_queue(str
+ return err;
+ }
+
++ q->buf_size = lro_q ? pp_params.max_len : pp_params.max_len / 2;
+ q->ndesc = ndesc;
+ netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
+
+@@ -791,7 +1012,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));
+- airoha_qdma_set(qdma, REG_RX_SCATTER_CFG(qid), RX_RING_SG_EN_MASK);
++ if (lro_q)
++ airoha_qdma_clear(qdma, REG_RX_SCATTER_CFG(qid),
++ RX_RING_SG_EN_MASK);
++ else
++ airoha_qdma_set(qdma, REG_RX_SCATTER_CFG(qid),
++ RX_RING_SG_EN_MASK);
+
+ airoha_qdma_fill_rx_queue(q);
+
+@@ -813,6 +1039,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 */
++ WRITE_ONCE(desc->tcp_ts_reply, 0);
+ WRITE_ONCE(desc->ctrl, 0);
+ WRITE_ONCE(desc->addr, 0);
+ WRITE_ONCE(desc->data, 0);
+@@ -1720,13 +1947,45 @@ static void airoha_update_hw_stats(struc
+ spin_unlock(&port->stats.lock);
+ }
+
++static void airoha_update_netdev_features(struct airoha_gdm_port *port)
++{
++ struct airoha_eth *eth = port->eth;
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++ struct airoha_gdm_port *p = eth->ports[i];
++ struct net_device *netdev;
++
++ if (!p)
++ continue;
++
++ netdev = p->dev;
++ if (netdev->reg_state != NETREG_REGISTERED)
++ continue;
++
++ netdev_update_features(netdev);
++ }
++}
++
+ static int airoha_dev_open(struct net_device *dev)
+ {
+ int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
+ struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_qdma *qdma = port->qdma;
++ struct airoha_eth *eth = qdma->eth;
++ int qdma_id = qdma - ð->qdma[0];
+ u32 pse_port = FE_PSE_PORT_PPE1;
+
++ /* HW 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.
++ */
++ if (qdma->users && airoha_fe_lro_is_enabled(eth, qdma_id)) {
++ netdev_warn(dev, "required to disable HW GRO on QDMA%d\n",
++ qdma_id);
++ return -EBUSY;
++ }
++
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+ err = phylink_of_phy_connect(port->phylink, dev->dev.of_node, 0);
+@@ -1747,13 +2006,13 @@ static int airoha_dev_open(struct net_de
+
+ /* It seems GDM3 and GDM4 needs SPORT enabled to correctly work */
+ if (netdev_uses_dsa(dev) || port->id > 2)
+- airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
++ airoha_fe_set(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_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
++ airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+ GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+ FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+ FIELD_PREP(GDM_LONG_LEN_MASK, len));
+@@ -1763,14 +2022,18 @@ static int airoha_dev_open(struct net_de
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+ qdma->users++;
+
+- if (port->id == AIROHA_GDM2_IDX &&
+- airoha_ppe_is_enabled(qdma->eth, 1)) {
++ if (port->id == AIROHA_GDM2_IDX && airoha_ppe_is_enabled(eth, 1)) {
+ /* For PPE2 always use secondary cpu port. */
+ 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),
+ pse_port);
+
++ if (dev->features & NETIF_F_GRO_HW)
++ airoha_dev_lro_enable(port);
++
++ airoha_update_netdev_features(port);
++
+ return 0;
+ }
+
+@@ -1778,6 +2041,7 @@ static int airoha_dev_stop(struct net_de
+ {
+ struct airoha_gdm_port *port = netdev_priv(dev);
+ struct airoha_qdma *qdma = port->qdma;
++ struct airoha_eth *eth = qdma->eth;
+ int i;
+
+ netif_tx_disable(dev);
+@@ -1785,7 +2049,7 @@ static int airoha_dev_stop(struct net_de
+ 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),
++ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_DROP);
+
+ if (!--qdma->users) {
+@@ -1801,6 +2065,8 @@ static int airoha_dev_stop(struct net_de
+ }
+ }
+
++ airoha_update_netdev_features(port);
++
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+ phylink_stop(port->phylink);
+@@ -2034,6 +2300,38 @@ int airoha_get_fe_port(struct airoha_gdm
+ }
+ }
+
++static netdev_features_t airoha_dev_fix_features(struct net_device *dev,
++ netdev_features_t features)
++{
++ struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_qdma *qdma = port->qdma;
++
++ if (qdma->users > 1)
++ features &= ~NETIF_F_GRO_HW;
++
++ return features;
++}
++
++static int airoha_dev_set_features(struct net_device *dev,
++ netdev_features_t features)
++{
++ netdev_features_t diff = dev->features ^ features;
++ struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_qdma *qdma = port->qdma;
++ struct airoha_eth *eth = qdma->eth;
++ int qdma_id = qdma - ð->qdma[0];
++
++ if (!(diff & NETIF_F_GRO_HW))
++ return 0;
++
++ if (features & NETIF_F_GRO_HW)
++ airoha_dev_lro_enable(port);
++ else
++ airoha_fe_lro_disable(eth, qdma_id);
++
++ return 0;
++}
++
+ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+ {
+@@ -2933,6 +3231,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,
++ .ndo_fix_features = airoha_dev_fix_features,
++ .ndo_set_features = airoha_dev_set_features,
+ .ndo_start_xmit = airoha_dev_xmit,
+ .ndo_get_stats64 = airoha_dev_get_stats64,
+ .ndo_set_mac_address = airoha_dev_set_macaddr,
+@@ -3150,12 +3450,9 @@ static int airoha_alloc_gdm_port(struct
+ dev->ethtool_ops = &airoha_ethtool_ops;
+ dev->max_mtu = AIROHA_MAX_MTU;
+ dev->watchdog_timeo = 5 * HZ;
+- dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
+- NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
+- NETIF_F_SG | NETIF_F_TSO |
+- NETIF_F_HW_TC;
+- dev->features |= dev->hw_features;
+- dev->vlan_features = dev->hw_features;
++ dev->hw_features = AIROHA_HW_FEATURES | NETIF_F_GRO_HW;
++ dev->features |= AIROHA_HW_FEATURES;
++ dev->vlan_features = AIROHA_HW_FEATURES;
+ dev->dev.of_node = np;
+ SET_NETDEV_DEV(dev, eth->dev);
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -43,6 +43,18 @@
+ (_n) == 15 ? 128 : \
+ (_n) == 0 ? 1024 : 16)
+
++#define AIROHA_LRO_PAGE_ORDER order_base_2(SZ_16K / PAGE_SIZE)
++#define AIROHA_MAX_NUM_LRO_QUEUES 8
++#define AIROHA_RXQ_LRO_EN_MASK GENMASK(7, 0)
++#define AIROHA_RXQ_LRO_MAX_AGG_COUNT 64
++#define AIROHA_RXQ_LRO_MAX_AGG_TIME 100
++#define AIROHA_RXQ_LRO_MAX_AGE_TIME 2000
++
++#define AIROHA_HW_FEATURES \
++ (NETIF_F_IP_CSUM | NETIF_F_RXCSUM | \
++ NETIF_F_TSO6 | NETIF_F_IPV6_CSUM | \
++ NETIF_F_SG | NETIF_F_TSO | NETIF_F_HW_TC)
++
+ #define PSE_RSV_PAGES 128
+ #define PSE_QUEUE_RSV_PAGES 64
+
+@@ -666,6 +678,18 @@ static inline bool airoha_is_7583(struct
+ return eth->soc->version == 0x7583;
+ }
+
++static inline bool airoha_qdma_is_lro_queue(struct airoha_queue *q)
++{
++ struct airoha_qdma *qdma = q->qdma;
++ int qid = q - &qdma->q_rx[0];
++
++ /* EN7581 SoC supports at most 8 LRO rx queues */
++ BUILD_BUG_ON(hweight32(AIROHA_RXQ_LRO_EN_MASK) >
++ AIROHA_MAX_NUM_LRO_QUEUES);
++
++ return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
++}
++
+ int airoha_get_fe_port(struct airoha_gdm_port *port);
+ bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+ struct airoha_gdm_port *port);
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -122,6 +122,20 @@
+ #define CDM_CRSN_QSEL_REASON_MASK(_n) \
+ GENMASK(4 + (((_n) % 4) << 3), (((_n) % 4) << 3))
+
++#define REG_CDM_LRO_RXQ(_n, _m) (CDM_BASE(_n) + 0x78 + ((_m) & 0x4))
++#define LRO_RXQ_MASK(_n) GENMASK(4 + (((_n) & 0x3) << 3), ((_n) & 0x3) << 3)
++
++#define REG_CDM_LRO_EN(_n) (CDM_BASE(_n) + 0x80)
++#define LRO_RXQ_EN_MASK GENMASK(7, 0)
++
++#define REG_CDM_LRO_LIMIT(_n) (CDM_BASE(_n) + 0x84)
++#define CDM_LRO_AGG_NUM_MASK GENMASK(23, 16)
++#define CDM_LRO_AGG_SIZE_MASK GENMASK(15, 0)
++
++#define REG_CDM_LRO_AGE_TIME(_n) (CDM_BASE(_n) + 0x88)
++#define CDM_LRO_AGE_TIME_MASK GENMASK(31, 16)
++#define CDM_LRO_AGG_TIME_MASK GENMASK(15, 0)
++
+ #define REG_GDM_FWD_CFG(_n) GDM_BASE(_n)
+ #define GDM_PAD_EN_MASK BIT(28)
+ #define GDM_DROP_CRC_ERR_MASK BIT(23)
+@@ -895,9 +909,15 @@
+ #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)
++/* RX MSG2 */
++#define QDMA_ETH_RXMSG_AGG_COUNT_MASK GENMASK(31, 24)
++#define QDMA_ETH_RXMSG_L2_LEN_MASK GENMASK(6, 0)
++/* RX MSG3 */
++#define QDMA_ETH_RXMSG_AGG_LEN_MASK GENMASK(31, 16)
++#define QDMA_ETH_RXMSG_TCP_WIN_MASK GENMASK(15, 0)
+
+ struct airoha_qdma_desc {
+- __le32 rsv;
++ __le32 tcp_ts_reply;
+ __le32 ctrl;
+ __le32 addr;
+ __le32 data;
+++ /dev/null
-From 598e1ddfe85ad0f4778eeadd5d878209dd931280 Mon Sep 17 00:00:00 2001
-Message-ID: <598e1ddfe85ad0f4778eeadd5d878209dd931280.1779112628.git.lorenzo@kernel.org>
-From: Lorenzo Bianconi <lorenzo@kernel.org>
-Date: Thu, 14 May 2026 20:25:19 +0200
-Subject: [PATCH] net: airoha: Implement LRO TCP support
-
-Signed-off-by: Lorenzo Bianconi <lorenzo@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(-)
-
---- a/drivers/net/ethernet/airoha/airoha_eth.c
-+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -13,6 +13,7 @@
- #include <net/dst_metadata.h>
- #include <net/page_pool/helpers.h>
- #include <net/pkt_cls.h>
-+#include <net/tcp.h>
- #include <uapi/linux/ppp_defs.h>
-
- #include "airoha_regs.h"
-@@ -439,6 +440,48 @@ static void airoha_fe_crsn_qsel_init(str
- CDM_CRSN_QSEL_Q1));
- }
-
-+static void airoha_fe_lro_init_rx_queue(struct airoha_eth *eth, int qdma_id,
-+ int lro_queue_index, int qid,
-+ int buf_size)
-+{
-+ int id = qdma_id + 1;
-+
-+ airoha_fe_rmw(eth, REG_CDM_LRO_LIMIT(id),
-+ CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK,
-+ FIELD_PREP(CDM_LRO_AGG_SIZE_MASK, buf_size) |
-+ FIELD_PREP(CDM_LRO_AGG_NUM_MASK,
-+ AIROHA_RXQ_LRO_MAX_AGG_COUNT));
-+ airoha_fe_rmw(eth, REG_CDM_LRO_AGE_TIME(id),
-+ CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK,
-+ FIELD_PREP(CDM_LRO_AGE_TIME_MASK,
-+ AIROHA_RXQ_LRO_MAX_AGE_TIME) |
-+ FIELD_PREP(CDM_LRO_AGG_TIME_MASK,
-+ AIROHA_RXQ_LRO_MAX_AGG_TIME));
-+ airoha_fe_rmw(eth, REG_CDM_LRO_RXQ(id, lro_queue_index),
-+ LRO_RXQ_MASK(lro_queue_index),
-+ __field_prep(LRO_RXQ_MASK(lro_queue_index), qid));
-+ airoha_fe_set(eth, REG_CDM_LRO_EN(id), BIT(lro_queue_index));
-+}
-+
-+static void airoha_fe_lro_disable(struct airoha_eth *eth, int qdma_id)
-+{
-+ int i, id = qdma_id + 1;
-+
-+ airoha_fe_clear(eth, REG_CDM_LRO_EN(id), LRO_RXQ_EN_MASK);
-+ airoha_fe_clear(eth, REG_CDM_LRO_LIMIT(id),
-+ CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK);
-+ airoha_fe_clear(eth, REG_CDM_LRO_AGE_TIME(id),
-+ CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK);
-+ for (i = 0; i < AIROHA_MAX_NUM_LRO_QUEUES; i++)
-+ airoha_fe_clear(eth, REG_CDM_LRO_RXQ(id, i), LRO_RXQ_MASK(i));
-+}
-+
-+static bool airoha_fe_lro_is_enabled(struct airoha_eth *eth, int qdma_id)
-+{
-+ return airoha_fe_get(eth, REG_CDM_LRO_EN(qdma_id + 1),
-+ LRO_RXQ_EN_MASK);
-+}
-+
- static int airoha_fe_init(struct airoha_eth *eth)
- {
- airoha_fe_maccr_init(eth);
-@@ -603,6 +646,85 @@ static int airoha_qdma_get_gdm_port(stru
- return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
- }
-
-+static int airoha_qdma_lro_rx_process(struct airoha_queue *q,
-+ struct airoha_qdma_desc *desc)
-+{
-+ u32 desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
-+ u32 msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
-+ u32 msg2 = le32_to_cpu(READ_ONCE(desc->msg2));
-+ u32 msg3 = le32_to_cpu(READ_ONCE(desc->msg3));
-+ struct sk_buff *skb = q->skb;
-+ u32 len, th_off, tcp_ack_seq;
-+ u16 tcp_win, l2_len;
-+ struct tcphdr *th;
-+ bool ipv4, ipv6;
-+
-+ if (FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2) <= 1)
-+ return 0;
-+
-+ ipv4 = FIELD_GET(QDMA_ETH_RXMSG_IP4_MASK, msg1);
-+ ipv6 = FIELD_GET(QDMA_ETH_RXMSG_IP6_MASK, msg1);
-+ if (!ipv4 && !ipv6)
-+ return -EOPNOTSUPP;
-+
-+ 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;
-+
-+ if (!pskb_may_pull(skb, l2_len + sizeof(*iph)))
-+ return -EINVAL;
-+
-+ iph = (struct iphdr *)(skb->data + l2_len);
-+ if (iph->protocol != IPPROTO_TCP)
-+ return -EOPNOTSUPP;
-+
-+ iph->tot_len = cpu_to_be16(len - l2_len);
-+ iph->check = 0;
-+ iph->check = ip_fast_csum((void *)iph, iph->ihl);
-+ th_off = l2_len + (iph->ihl << 2);
-+ } else {
-+ struct ipv6hdr *ip6h;
-+
-+ if (!pskb_may_pull(skb, l2_len + sizeof(*ip6h)))
-+ return -EINVAL;
-+
-+ ip6h = (struct ipv6hdr *)(skb->data + l2_len);
-+ if (ip6h->nexthdr != NEXTHDR_TCP)
-+ return -EOPNOTSUPP;
-+
-+ ip6h->payload_len = cpu_to_be16(len - l2_len - sizeof(*ip6h));
-+ th_off = l2_len + sizeof(*ip6h);
-+ }
-+
-+ tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK, msg3);
-+ tcp_ack_seq = le32_to_cpu(READ_ONCE(desc->data));
-+
-+ if (!pskb_may_pull(skb, th_off + sizeof(*th)))
-+ return -EINVAL;
-+
-+ th = (struct tcphdr *)(skb->data + th_off);
-+ 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) {
-+ __be32 *topt = (__be32 *)(th + 1);
-+
-+ if (*topt == cpu_to_be32((TCPOPT_NOP << 24) |
-+ (TCPOPT_NOP << 16) |
-+ (TCPOPT_TIMESTAMP << 8) |
-+ TCPOLEN_TIMESTAMP)) {
-+ __le32 tcp_ts_reply = READ_ONCE(desc->tcp_ts_reply);
-+
-+ put_unaligned_be32(le32_to_cpu(tcp_ts_reply),
-+ topt + 2);
-+ }
-+ }
-+
-+ return 0;
-+}
-+
- static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
- {
- enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
-@@ -650,11 +772,15 @@ static int airoha_qdma_rx_process(struct
-
- skb_reserve(q->skb, AIROHA_RX_HEADROOM);
- __skb_put(q->skb, len);
-- skb_mark_for_recycle(q->skb);
- q->skb->dev = port->dev;
-- q->skb->protocol = eth_type_trans(q->skb, port->dev);
- q->skb->ip_summed = CHECKSUM_UNNECESSARY;
- skb_record_rx_queue(q->skb, qid);
-+
-+ if (airoha_qdma_lro_rx_process(q, desc) < 0)
-+ goto free_frag;
-+
-+ q->skb->protocol = eth_type_trans(q->skb, port->dev);
-+ skb_mark_for_recycle(q->skb);
- } else { /* scattered frame */
- struct skb_shared_info *shinfo = skb_shinfo(q->skb);
- int nr_frags = shinfo->nr_frags;
-@@ -743,12 +869,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)
- {
-- const struct page_pool_params pp_params = {
-- .order = 0,
-+ struct page_pool_params pp_params = {
- .pool_size = 256,
- .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
- .dma_dir = DMA_FROM_DEVICE,
-- .max_len = PAGE_SIZE,
- .nid = NUMA_NO_NODE,
- .dev = qdma->eth->dev,
- .napi = &q->napi,
-@@ -756,9 +880,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;
-+ bool lro_q;
-
-- q->buf_size = PAGE_SIZE / 2;
- q->qdma = qdma;
-+ lro_q = airoha_qdma_is_lro_queue(q);
-
- q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
- GFP_KERNEL);
-@@ -770,6 +895,9 @@ static int airoha_qdma_init_rx_queue(str
- if (!q->desc)
- return -ENOMEM;
-
-+ pp_params.order = lro_q ? AIROHA_LRO_PAGE_ORDER : 0;
-+ pp_params.max_len = PAGE_SIZE << pp_params.order;
-+
- q->page_pool = page_pool_create(&pp_params);
- if (IS_ERR(q->page_pool)) {
- int err = PTR_ERR(q->page_pool);
-@@ -778,6 +906,7 @@ static int airoha_qdma_init_rx_queue(str
- return err;
- }
-
-+ q->buf_size = lro_q ? pp_params.max_len : pp_params.max_len / 2;
- q->ndesc = ndesc;
- netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
-
-@@ -2034,6 +2163,67 @@ int airoha_get_fe_port(struct airoha_gdm
- }
- }
-
-+static int airoha_dev_set_features(struct net_device *dev,
-+ netdev_features_t features)
-+{
-+ netdev_features_t diff = dev->features ^ features;
-+ struct airoha_gdm_port *port = netdev_priv(dev);
-+ struct airoha_qdma *qdma = port->qdma;
-+ struct airoha_eth *eth = qdma->eth;
-+ int qdma_id = qdma - ð->qdma[0];
-+ int i;
-+
-+ if (!(diff & NETIF_F_LRO))
-+ return 0;
-+
-+ if (netif_running(dev))
-+ return -EBUSY;
-+
-+ /* reset LRO configuration */
-+ if (features & NETIF_F_LRO) {
-+ int lro_queue_index = 0;
-+
-+ if (airoha_fe_lro_is_enabled(eth, qdma_id))
-+ return 0;
-+
-+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
-+ struct airoha_queue *q = &qdma->q_rx[i];
-+ u32 size;
-+
-+ if (!q->ndesc)
-+ continue;
-+
-+ if (!airoha_qdma_is_lro_queue(q))
-+ continue;
-+
-+ size = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
-+ size = min_t(u32, size, CDM_LRO_AGG_SIZE_MASK);
-+ airoha_fe_lro_init_rx_queue(eth, qdma_id,
-+ lro_queue_index, i, size);
-+ lro_queue_index++;
-+ }
-+ } else {
-+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
-+ struct airoha_gdm_port *p = eth->ports[i];
-+
-+ if (!p)
-+ continue;
-+
-+ if (p->qdma != qdma)
-+ continue;
-+
-+ if (p->dev == dev)
-+ continue;
-+
-+ if (p->dev->features & NETIF_F_LRO)
-+ return 0;
-+ }
-+ airoha_fe_lro_disable(eth, qdma_id);
-+ }
-+
-+ return 0;
-+}
-+
- static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
- struct net_device *dev)
- {
-@@ -2933,6 +3123,7 @@ 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,
-+ .ndo_set_features = airoha_dev_set_features,
- .ndo_start_xmit = airoha_dev_xmit,
- .ndo_get_stats64 = airoha_dev_get_stats64,
- .ndo_set_mac_address = airoha_dev_set_macaddr,
-@@ -3150,12 +3341,9 @@ static int airoha_alloc_gdm_port(struct
- dev->ethtool_ops = &airoha_ethtool_ops;
- dev->max_mtu = AIROHA_MAX_MTU;
- dev->watchdog_timeo = 5 * HZ;
-- dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
-- NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
-- NETIF_F_SG | NETIF_F_TSO |
-- NETIF_F_HW_TC;
-- dev->features |= dev->hw_features;
-- dev->vlan_features = dev->hw_features;
-+ dev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
-+ dev->features |= AIROHA_HW_FEATURES;
-+ dev->vlan_features = AIROHA_HW_FEATURES;
- dev->dev.of_node = np;
- SET_NETDEV_DEV(dev, eth->dev);
-
---- a/drivers/net/ethernet/airoha/airoha_eth.h
-+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -43,6 +43,18 @@
- (_n) == 15 ? 128 : \
- (_n) == 0 ? 1024 : 16)
-
-+#define AIROHA_LRO_PAGE_ORDER 2
-+#define AIROHA_MAX_NUM_LRO_QUEUES 8
-+#define AIROHA_RXQ_LRO_EN_MASK 0xff000000
-+#define AIROHA_RXQ_LRO_MAX_AGG_COUNT 64
-+#define AIROHA_RXQ_LRO_MAX_AGG_TIME 100
-+#define AIROHA_RXQ_LRO_MAX_AGE_TIME 2000 /* 1ms */
-+
-+#define AIROHA_HW_FEATURES \
-+ (NETIF_F_IP_CSUM | NETIF_F_RXCSUM | \
-+ NETIF_F_TSO6 | NETIF_F_IPV6_CSUM | \
-+ NETIF_F_SG | NETIF_F_TSO | NETIF_F_HW_TC)
-+
- #define PSE_RSV_PAGES 128
- #define PSE_QUEUE_RSV_PAGES 64
-
-@@ -666,6 +678,18 @@ static inline bool airoha_is_7583(struct
- return eth->soc->version == 0x7583;
- }
-
-+static inline bool airoha_qdma_is_lro_queue(struct airoha_queue *q)
-+{
-+ struct airoha_qdma *qdma = q->qdma;
-+ int qid = q - &qdma->q_rx[0];
-+
-+ /* EN7581 SoC supports at most 8 LRO rx queues */
-+ BUILD_BUG_ON(hweight32(AIROHA_RXQ_LRO_EN_MASK) >
-+ AIROHA_MAX_NUM_LRO_QUEUES);
-+
-+ return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
-+}
-+
- int airoha_get_fe_port(struct airoha_gdm_port *port);
- bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
- struct airoha_gdm_port *port);
---- a/drivers/net/ethernet/airoha/airoha_regs.h
-+++ b/drivers/net/ethernet/airoha/airoha_regs.h
-@@ -122,6 +122,20 @@
- #define CDM_CRSN_QSEL_REASON_MASK(_n) \
- GENMASK(4 + (((_n) % 4) << 3), (((_n) % 4) << 3))
-
-+#define REG_CDM_LRO_RXQ(_n, _m) (CDM_BASE(_n) + 0x78 + ((_m) & 0x4))
-+#define LRO_RXQ_MASK(_n) GENMASK(4 + (((_n) & 0x3) << 3), ((_n) & 0x3) << 3)
-+
-+#define REG_CDM_LRO_EN(_n) (CDM_BASE(_n) + 0x80)
-+#define LRO_RXQ_EN_MASK GENMASK(7, 0)
-+
-+#define REG_CDM_LRO_LIMIT(_n) (CDM_BASE(_n) + 0x84)
-+#define CDM_LRO_AGG_NUM_MASK GENMASK(23, 16)
-+#define CDM_LRO_AGG_SIZE_MASK GENMASK(15, 0)
-+
-+#define REG_CDM_LRO_AGE_TIME(_n) (CDM_BASE(_n) + 0x88)
-+#define CDM_LRO_AGE_TIME_MASK GENMASK(31, 16)
-+#define CDM_LRO_AGG_TIME_MASK GENMASK(15, 0)
-+
- #define REG_GDM_FWD_CFG(_n) GDM_BASE(_n)
- #define GDM_PAD_EN_MASK BIT(28)
- #define GDM_DROP_CRC_ERR_MASK BIT(23)
-@@ -895,9 +909,15 @@
- #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)
-+/* RX MSG2 */
-+#define QDMA_ETH_RXMSG_AGG_COUNT_MASK GENMASK(31, 24)
-+#define QDMA_ETH_RXMSG_L2_LEN_MASK GENMASK(6, 0)
-+/* RX MSG3 */
-+#define QDMA_ETH_RXMSG_AGG_LEN_MASK GENMASK(31, 16)
-+#define QDMA_ETH_RXMSG_TCP_WIN_MASK GENMASK(15, 0)
-
- struct airoha_qdma_desc {
-- __le32 rsv;
-+ __le32 tcp_ts_reply;
- __le32 ctrl;
- __le32 addr;
- __le32 data;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -738,6 +738,7 @@ static int airoha_qdma_rx_process(struct
+@@ -483,8 +483,9 @@ static bool airoha_fe_lro_is_enabled(str
+ LRO_RXQ_EN_MASK);
+ }
+
+-static void airoha_dev_lro_enable(struct airoha_gdm_port *port)
++static void airoha_dev_lro_enable(struct airoha_gdm_dev *dev)
+ {
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+@@ -843,6 +844,7 @@ static int airoha_qdma_rx_process(struct
struct airoha_qdma_desc *desc = &q->desc[q->tail];
u32 hash, reason, msg1, desc_ctrl;
struct airoha_gdm_port *port;
int data_len, len, p;
struct page *page;
-@@ -764,6 +765,10 @@ static int airoha_qdma_rx_process(struct
+@@ -869,8 +871,12 @@ static int airoha_qdma_rx_process(struct
goto free_frag;
port = eth->ports[p];
+ if (!port->dev)
+ goto free_frag;
+
-+ netdev = port->dev->dev;
++ netdev = netdev_from_priv(port->dev);
if (!q->skb) { /* first buffer */
- q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
- q->buf_size);
-@@ -772,14 +777,14 @@ static int airoha_qdma_rx_process(struct
-
- skb_reserve(q->skb, AIROHA_RX_HEADROOM);
- __skb_put(q->skb, len);
-- q->skb->dev = port->dev;
-+ q->skb->dev = netdev;
- q->skb->ip_summed = CHECKSUM_UNNECESSARY;
- skb_record_rx_queue(q->skb, qid);
-
- if (airoha_qdma_lro_rx_process(q, desc) < 0)
+- q->skb = airoha_qdma_build_rx_skb(q, desc, e, port->dev);
++ q->skb = airoha_qdma_build_rx_skb(q, desc, e, netdev);
+ if (!q->skb)
goto free_frag;
-
-- q->skb->protocol = eth_type_trans(q->skb, port->dev);
-+ q->skb->protocol = eth_type_trans(q->skb, netdev);
- skb_mark_for_recycle(q->skb);
} else { /* scattered frame */
- struct skb_shared_info *shinfo = skb_shinfo(q->skb);
-@@ -796,7 +801,7 @@ static int airoha_qdma_rx_process(struct
+@@ -888,7 +894,7 @@ static int airoha_qdma_rx_process(struct
if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
continue;
/* PPE module requires untagged packets to work
* properly and it provides DSA port index via the
* DMA descriptor. Report DSA tag to the DSA stack
-@@ -993,6 +998,7 @@ static void airoha_qdma_wake_netdev_txqs
+@@ -1091,6 +1097,7 @@ static void airoha_qdma_wake_netdev_txqs
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
-+ struct airoha_gdm_dev *dev;
++ struct net_device *netdev;
int j;
if (!port)
-@@ -1001,11 +1007,12 @@ static void airoha_qdma_wake_netdev_txqs
+@@ -1099,11 +1106,12 @@ static void airoha_qdma_wake_netdev_txqs
if (port->qdma != qdma)
continue;
- for (j = 0; j < port->dev->num_tx_queues; j++) {
-+ dev = port->dev;
-+ for (j = 0; j < dev->dev->num_tx_queues; j++) {
++ netdev = netdev_from_priv(port->dev);
++ for (j = 0; j < netdev->num_tx_queues; j++) {
if (airoha_qdma_get_txq(qdma, j) != qid)
continue;
- netif_wake_subqueue(port->dev, j);
-+ netif_wake_subqueue(dev->dev, j);
++ netif_wake_subqueue(netdev, j);
}
}
q->txq_stopped = false;
-@@ -1849,33 +1856,34 @@ static void airoha_update_hw_stats(struc
+@@ -1947,9 +1955,10 @@ static void airoha_update_hw_stats(struc
spin_unlock(&port->stats.lock);
}
+-static void airoha_update_netdev_features(struct airoha_gdm_port *port)
++static void airoha_update_netdev_features(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_eth *eth = port->eth;
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+@@ -1959,7 +1968,7 @@ static void airoha_update_netdev_feature
+ if (!p)
+ continue;
+
+- netdev = p->dev;
++ netdev = netdev_from_priv(p->dev);
+ if (netdev->reg_state != NETREG_REGISTERED)
+ continue;
+
+@@ -1967,10 +1976,11 @@ static void airoha_update_netdev_feature
+ }
+ }
+
-static int airoha_dev_open(struct net_device *dev)
+static int airoha_dev_open(struct net_device *netdev)
{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_qdma *qdma = port->qdma;
- u32 pse_port = FE_PSE_PORT_PPE1;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+@@ -1981,31 +1991,32 @@ static int airoha_dev_open(struct net_de
+ * the same QDMA if LRO is enabled on any device sharing it.
+ */
+ if (qdma->users && airoha_fe_lro_is_enabled(eth, qdma_id)) {
+- netdev_warn(dev, "required to disable HW GRO on QDMA%d\n",
++ netdev_warn(netdev, "required to disable HW GRO on QDMA%d\n",
+ qdma_id);
+ return -EBUSY;
+ }
#if defined(CONFIG_PCS_AIROHA)
if (airhoa_is_phy_external(port)) {
- err = phylink_of_phy_connect(port->phylink, dev->dev.of_node, 0);
-+ err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
++ err = phylink_of_phy_connect(dev->phylink,
++ netdev->dev.of_node, 0);
if (err) {
- netdev_err(dev, "%s: could not attach PHY: %d\n", __func__,
-+ netdev_err(netdev, "%s: could not attach PHY: %d\n", __func__,
- err);
+- err);
++ netdev_err(netdev, "%s: could not attach PHY: %d\n",
++ __func__, err);
return err;
}
/* It seems GDM3 and GDM4 needs SPORT enabled to correctly work */
- if (netdev_uses_dsa(dev) || port->id > 2)
+ 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),
GDM_STAG_EN_MASK);
else
-@@ -1903,16 +1911,17 @@ static int airoha_dev_open(struct net_de
+@@ -2029,25 +2040,26 @@ static int airoha_dev_open(struct net_de
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
+ pse_port);
+
+- if (dev->features & NETIF_F_GRO_HW)
+- airoha_dev_lro_enable(port);
++ if (netdev->features & NETIF_F_GRO_HW)
++ airoha_dev_lro_enable(dev);
+
+- airoha_update_netdev_features(port);
++ airoha_update_netdev_features(dev);
+
return 0;
}
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
struct airoha_qdma *qdma = port->qdma;
+ struct airoha_eth *eth = qdma->eth;
int i;
- netif_tx_disable(dev);
+ for (i = 0; i < netdev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(netdev, i);
- 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),
FE_PSE_PORT_DROP);
-@@ -1932,24 +1941,25 @@ static int airoha_dev_stop(struct net_de
+@@ -2065,28 +2077,29 @@ static int airoha_dev_stop(struct net_de
+ }
+ }
+
+- airoha_update_netdev_features(port);
++ airoha_update_netdev_features(dev);
#if defined(CONFIG_PCS_AIROHA)
if (airhoa_is_phy_external(port)) {
return 0;
}
-@@ -2015,16 +2025,17 @@ static int airoha_set_gdm2_loopback(stru
+@@ -2152,16 +2165,17 @@ static int airoha_set_gdm2_loopback(stru
return 0;
}
port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
- port->dev->irq = port->qdma->irq_banks[0].irq;
- airoha_set_macaddr(port, dev->dev_addr);
-+ dev->dev->irq = port->qdma->irq_banks[0].irq;
++ netdev->irq = port->qdma->irq_banks[0].irq;
+ airoha_set_macaddr(port, netdev->dev_addr);
switch (port->id) {
case AIROHA_GDM3_IDX:
-@@ -2049,10 +2060,11 @@ static int airoha_dev_init(struct net_de
+@@ -2186,10 +2200,11 @@ static int airoha_dev_init(struct net_de
return 0;
}
unsigned int start;
airoha_update_hw_stats(port);
-@@ -2071,36 +2083,39 @@ static void airoha_dev_get_stats64(struc
+@@ -2208,36 +2223,39 @@ static void airoha_dev_get_stats64(struc
} while (u64_stats_fetch_retry(&port->stats.syncp, start));
}
}
static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
-@@ -2163,11 +2178,12 @@ int airoha_get_fe_port(struct airoha_gdm
+@@ -2300,10 +2318,11 @@ int airoha_get_fe_port(struct airoha_gdm
}
}
+-static netdev_features_t airoha_dev_fix_features(struct net_device *dev,
++static netdev_features_t airoha_dev_fix_features(struct net_device *netdev,
+ netdev_features_t features)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+
+ if (qdma->users > 1)
+@@ -2312,11 +2331,12 @@ static netdev_features_t airoha_dev_fix_
+ return features;
+ }
+
-static int airoha_dev_set_features(struct net_device *dev,
+static int airoha_dev_set_features(struct net_device *netdev,
netdev_features_t features)
struct airoha_qdma *qdma = port->qdma;
struct airoha_eth *eth = qdma->eth;
int qdma_id = qdma - ð->qdma[0];
-@@ -2176,7 +2192,7 @@ static int airoha_dev_set_features(struc
- if (!(diff & NETIF_F_LRO))
+@@ -2325,7 +2345,7 @@ static int airoha_dev_set_features(struc
return 0;
-- if (netif_running(dev))
-+ if (netif_running(netdev))
- return -EBUSY;
-
- /* reset LRO configuration */
-@@ -2205,6 +2221,7 @@ static int airoha_dev_set_features(struc
- } else {
- for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
- struct airoha_gdm_port *p = eth->ports[i];
-+ struct airoha_gdm_dev *d;
-
- if (!p)
- continue;
-@@ -2212,10 +2229,11 @@ static int airoha_dev_set_features(struc
- if (p->qdma != qdma)
- continue;
-
-- if (p->dev == dev)
-+ d = p->dev;
-+ if (d->dev == netdev)
- continue;
-
-- if (p->dev->features & NETIF_F_LRO)
-+ if (d->dev->features & NETIF_F_LRO)
- return 0;
- }
+ if (features & NETIF_F_GRO_HW)
+- airoha_dev_lro_enable(port);
++ airoha_dev_lro_enable(dev);
+ else
airoha_fe_lro_disable(eth, qdma_id);
-@@ -2225,9 +2243,10 @@ static int airoha_dev_set_features(struc
+
+@@ -2333,9 +2353,10 @@ static int airoha_dev_set_features(struc
}
static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
struct airoha_qdma *qdma = port->qdma;
u32 nr_frags, tag, msg0, msg1, len;
struct airoha_queue_entry *e;
-@@ -2240,7 +2259,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2348,7 +2369,7 @@ static netdev_tx_t airoha_dev_xmit(struc
u8 fport;
qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
qid / AIROHA_NUM_QOS_QUEUES) |
-@@ -2276,7 +2295,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2384,7 +2405,7 @@ static netdev_tx_t airoha_dev_xmit(struc
spin_lock_bh(&q->lock);
nr_frags = 1 + skb_shinfo(skb)->nr_frags;
if (q->queued + nr_frags >= q->ndesc) {
-@@ -2300,9 +2319,9 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2408,9 +2429,9 @@ static netdev_tx_t airoha_dev_xmit(struc
dma_addr_t addr;
u32 val;
goto error_unmap;
list_move_tail(&e->list, &tx_list);
-@@ -2351,7 +2370,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2459,7 +2480,7 @@ static netdev_tx_t airoha_dev_xmit(struc
error_unmap:
list_for_each_entry(e, &tx_list, list) {
DMA_TO_DEVICE);
e->dma_addr = 0;
}
-@@ -2360,25 +2379,27 @@ error_unmap:
+@@ -2468,25 +2489,27 @@ error_unmap:
spin_unlock_bh(&q->lock);
error:
dev_kfree_skb_any(skb);
unsigned int start;
airoha_update_hw_stats(port);
-@@ -2406,11 +2427,12 @@ static const struct ethtool_rmon_hist_ra
+@@ -2514,11 +2537,12 @@ static const struct ethtool_rmon_hist_ra
};
static void
struct airoha_hw_stats *hw_stats = &port->stats;
unsigned int start;
-@@ -2435,11 +2457,12 @@ airoha_ethtool_get_rmon_stats(struct net
+@@ -2543,11 +2567,12 @@ airoha_ethtool_get_rmon_stats(struct net
} while (u64_stats_fetch_retry(&port->stats.syncp, start));
}
int i;
for (i = 0; i < AIROHA_NUM_TX_RING; i++)
-@@ -2524,10 +2547,12 @@ static int airoha_qdma_set_tx_ets_sched(
+@@ -2632,10 +2657,12 @@ static int airoha_qdma_set_tx_ets_sched(
ARRAY_SIZE(w));
}
u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
REG_CNTR_VAL(channel << 1));
u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
-@@ -2789,11 +2814,12 @@ static int airoha_qdma_set_trtcm_token_b
+@@ -2897,11 +2924,12 @@ static int airoha_qdma_set_trtcm_token_b
mode, val);
}
int i, err;
for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
-@@ -2813,20 +2839,22 @@ static int airoha_qdma_set_tx_rate_limit
+@@ -2921,20 +2949,22 @@ static int airoha_qdma_set_tx_rate_limit
return 0;
}
if (err) {
NL_SET_ERR_MSG_MOD(opt->extack,
"failed configuring htb offload");
-@@ -2836,9 +2864,10 @@ static int airoha_tc_htb_alloc_leaf_queu
+@@ -2944,9 +2974,10 @@ static int airoha_tc_htb_alloc_leaf_queu
if (opt->command == TC_HTB_NODE_MODIFY)
return 0;
NL_SET_ERR_MSG_MOD(opt->extack,
"failed setting real_num_tx_queues");
return err;
-@@ -2928,11 +2957,12 @@ static int airoha_tc_matchall_act_valida
+@@ -3036,11 +3067,12 @@ static int airoha_tc_matchall_act_valida
return 0;
}
u32 rate = 0, bucket_size = 0;
switch (f->command) {
-@@ -2967,18 +2997,19 @@ static int airoha_dev_tc_matchall(struct
+@@ -3075,18 +3107,19 @@ static int airoha_dev_tc_matchall(struct
static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
void *type_data, void *cb_priv)
{
default:
return -EOPNOTSUPP;
}
-@@ -3025,47 +3056,51 @@ static int airoha_dev_setup_tc_block(str
+@@ -3133,47 +3166,51 @@ static int airoha_dev_setup_tc_block(str
}
}
if (!test_bit(channel, port->qos_sq_bmap)) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
-@@ -3101,8 +3136,8 @@ static int airoha_tc_setup_qdisc_htb(str
+@@ -3209,8 +3246,8 @@ static int airoha_tc_setup_qdisc_htb(str
return 0;
}
{
switch (type) {
case TC_SETUP_QDISC_ETS:
-@@ -3174,13 +3209,18 @@ static void airoha_metadata_dst_free(str
+@@ -3283,13 +3320,18 @@ static void airoha_metadata_dst_free(str
}
}
return true;
}
-@@ -3192,8 +3232,9 @@ static void airoha_mac_link_up(struct ph
+@@ -3301,8 +3343,9 @@ static void airoha_mac_link_up(struct ph
unsigned int mode, phy_interface_t interface,
int speed, int duplex, bool tx_pause, bool rx_pause)
{
struct airoha_qdma *qdma = port->qdma;
struct airoha_eth *eth = qdma->eth;
u32 frag_size_tx, frag_size_rx;
-@@ -3249,65 +3290,122 @@ static int airoha_fill_available_pcs(str
+@@ -3358,65 +3401,121 @@ static int airoha_fill_available_pcs(str
&num_available_pcs);
}
+ netdev->ethtool_ops = &airoha_ethtool_ops;
+ netdev->max_mtu = AIROHA_MAX_MTU;
+ netdev->watchdog_timeo = 5 * HZ;
-+ netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
++ netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_GRO_HW;
+ netdev->features |= AIROHA_HW_FEATURES;
+ netdev->vlan_features = AIROHA_HW_FEATURES;
+ netdev->dev.of_node = np;
+ }
+
+ dev = netdev_priv(netdev);
-+ dev->dev = netdev;
+ dev->port = port;
+ port->dev = dev;
+ dev->eth = eth;
int err, p;
u32 id;
-@@ -3329,58 +3427,22 @@ static int airoha_alloc_gdm_port(struct
+@@ -3438,58 +3537,22 @@ static int airoha_alloc_gdm_port(struct
return -EINVAL;
}
- dev->ethtool_ops = &airoha_ethtool_ops;
- dev->max_mtu = AIROHA_MAX_MTU;
- dev->watchdog_timeo = 5 * HZ;
-- dev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
+- dev->hw_features = AIROHA_HW_FEATURES | NETIF_F_GRO_HW;
- dev->features |= AIROHA_HW_FEATURES;
- dev->vlan_features = AIROHA_HW_FEATURES;
- dev->dev.of_node = np;
}
static int airoha_register_gdm_devices(struct airoha_eth *eth)
-@@ -3394,7 +3456,7 @@ static int airoha_register_gdm_devices(s
+@@ -3503,7 +3566,7 @@ static int airoha_register_gdm_devices(s
if (!port)
continue;
- err = register_netdev(port->dev);
-+ err = register_netdev(port->dev->dev);
++ err = register_netdev(netdev_from_priv(port->dev));
if (err)
return err;
}
-@@ -3503,16 +3565,18 @@ error_napi_stop:
+@@ -3612,16 +3675,22 @@ error_napi_stop:
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
- if (port->dev->reg_state == NETREG_REGISTERED) {
+ dev = port->dev;
-+ if (dev && dev->dev->reg_state == NETREG_REGISTERED) {
++ if (dev) {
++ struct net_device *netdev = netdev_from_priv(dev);
++
++ if (netdev->reg_state == NETREG_REGISTERED) {
#if defined(CONFIG_PCS_AIROHA)
- if (airhoa_is_phy_external(port))
+- if (airhoa_is_phy_external(port))
- phylink_destroy(port->phylink);
-+ phylink_destroy(dev->phylink);
++ if (airhoa_is_phy_external(port))
++ phylink_destroy(dev->phylink);
#endif
- unregister_netdev(port->dev);
-+ unregister_netdev(dev->dev);
++ unregister_netdev(netdev);
++ }
}
airoha_metadata_dst_free(port);
}
-@@ -3534,15 +3598,19 @@ static void airoha_remove(struct platfor
+@@ -3643,15 +3712,19 @@ static void airoha_remove(struct platfor
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
+
+ dev = port->dev;
+ if (dev)
-+ unregister_netdev(dev->dev);
++ unregister_netdev(netdev_from_priv(dev));
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -547,17 +547,22 @@ struct airoha_qdma {
+@@ -547,17 +547,21 @@ struct airoha_qdma {
struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
};
-struct airoha_gdm_port {
- struct airoha_qdma *qdma;
-- struct airoha_eth *eth;
+struct airoha_gdm_dev {
+ struct airoha_gdm_port *port;
- struct net_device *dev;
+ struct airoha_eth *eth;
+- struct net_device *dev;
- int id;
- int nbq;
-+ struct airoha_eth *eth;
#if defined(CONFIG_PCS_AIROHA)
struct phylink *phylink;
struct airoha_hw_stats stats;
-@@ -691,8 +696,8 @@ static inline bool airoha_qdma_is_lro_qu
+@@ -691,8 +695,8 @@ static inline bool airoha_qdma_is_lro_qu
}
int airoha_get_fe_port(struct airoha_gdm_port *port);
if (dsa_port >= 0 || eth->ports[1])
pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
: port->id;
-@@ -1485,7 +1488,7 @@ void airoha_ppe_check_skb(struct airoha_
+@@ -1484,8 +1487,8 @@ void airoha_ppe_check_skb(struct airoha_
+
void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
{
++ struct net_device *dev = netdev_from_priv(port->dev);
struct airoha_eth *eth = port->qdma->eth;
- struct net_device *dev = port->dev;
-+ struct net_device *dev = port->dev->dev;
const u8 *addr = dev->dev_addr;
u32 val;
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -2760,6 +2760,17 @@ static inline void *netdev_priv(const st
+ return (void *)dev->priv;
+ }
+
++/**
++ * netdev_from_priv() - get network device from priv
++ * @priv: network device private data
++ *
++ * Returns: net_device to which @priv belongs
++ */
++static inline struct net_device *netdev_from_priv(const void *priv)
++{
++ return container_of(priv, struct net_device, priv);
++}
++
+ /* Set the sysfs physical device reference for the network logical device
+ * if set prior to registration will cause a symlink during initialization.
+ */
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -80,9 +80,10 @@ static bool airhoa_is_phy_external(struc
+@@ -81,9 +81,10 @@ static bool airhoa_is_phy_external(struc
}
#endif
u32 val, reg;
reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
-@@ -94,7 +95,7 @@ static void airoha_set_macaddr(struct ai
+@@ -95,7 +96,7 @@ static void airoha_set_macaddr(struct ai
airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
}
static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
-@@ -110,10 +111,10 @@ static void airoha_set_gdm_port_fwd_cfg(
+@@ -111,10 +112,10 @@ static void airoha_set_gdm_port_fwd_cfg(
FIELD_PREP(GDM_UCFQ_MASK, val));
}
u32 vip_port;
vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
-@@ -1004,10 +1005,13 @@ static void airoha_qdma_wake_netdev_txqs
+@@ -485,8 +486,7 @@ static bool airoha_fe_lro_is_enabled(str
+
+ static void airoha_dev_lro_enable(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+ int i, lro_queue_index = 0;
+@@ -1097,13 +1097,18 @@ static void airoha_qdma_wake_netdev_txqs
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
++ struct airoha_gdm_dev *dev;
+ struct net_device *netdev;
+ int j;
+
if (!port)
continue;
+ if (dev->qdma != qdma)
continue;
-- dev = port->dev;
- for (j = 0; j < dev->dev->num_tx_queues; j++) {
- if (airoha_qdma_get_txq(qdma, j) != qid)
- continue;
-@@ -1712,9 +1716,10 @@ static void airoha_qdma_stop_napi(struct
+ netdev = netdev_from_priv(port->dev);
+@@ -1811,9 +1816,10 @@ static void airoha_qdma_stop_napi(struct
}
}
u32 val, i = 0;
spin_lock(&port->stats.lock);
-@@ -1861,7 +1866,7 @@ static int airoha_dev_open(struct net_de
+@@ -1981,7 +1987,7 @@ static int airoha_dev_open(struct net_de
int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- struct airoha_qdma *qdma = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
u32 pse_port = FE_PSE_PORT_PPE1;
-
- #if defined(CONFIG_PCS_AIROHA)
-@@ -1878,7 +1883,7 @@ static int airoha_dev_open(struct net_de
+@@ -2011,7 +2017,7 @@ static int airoha_dev_open(struct net_de
#endif
netif_tx_start_all_queues(netdev);
if (err)
return err;
-@@ -1915,11 +1920,11 @@ static int airoha_dev_stop(struct net_de
+@@ -2052,12 +2058,12 @@ 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 = port->qdma;
+ struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
int i;
netif_tx_disable(netdev);
for (i = 0; i < netdev->num_tx_queues; i++)
netdev_tx_reset_subqueue(netdev, i);
-@@ -1952,21 +1957,21 @@ static int airoha_dev_stop(struct net_de
+@@ -2092,21 +2098,21 @@ static int airoha_dev_stop(struct net_de
static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
u32 val, pse_port, chan;
int i, src_port;
-@@ -2013,7 +2018,7 @@ static int airoha_set_gdm2_loopback(stru
+@@ -2153,7 +2159,7 @@ static int airoha_set_gdm2_loopback(stru
__field_prep(SP_CPORT_MASK(val), FE_PSE_PORT_CDM2));
for (i = 0; i < eth->soc->num_ppe; i++)
if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
u32 mask = FC_ID_OF_SRC_PORT_MASK(nbq);
-@@ -2033,9 +2038,9 @@ static int airoha_dev_init(struct net_de
+@@ -2173,9 +2179,9 @@ static int airoha_dev_init(struct net_de
int i;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
- port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
-- dev->dev->irq = port->qdma->irq_banks[0].irq;
+- netdev->irq = port->qdma->irq_banks[0].irq;
- airoha_set_macaddr(port, netdev->dev_addr);
+ dev->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
-+ dev->dev->irq = dev->qdma->irq_banks[0].irq;
++ netdev->irq = dev->qdma->irq_banks[0].irq;
+ airoha_set_macaddr(dev, netdev->dev_addr);
switch (port->id) {
case AIROHA_GDM3_IDX:
-@@ -2044,7 +2049,7 @@ static int airoha_dev_init(struct net_de
+@@ -2184,7 +2190,7 @@ static int airoha_dev_init(struct net_de
if (!eth->ports[1]) {
int err;
if (err)
return err;
}
-@@ -2054,8 +2059,7 @@ static int airoha_dev_init(struct net_de
+@@ -2194,8 +2200,7 @@ static int airoha_dev_init(struct net_de
}
for (i = 0; i < eth->soc->num_ppe; i++)
return 0;
}
-@@ -2067,7 +2071,7 @@ static void airoha_dev_get_stats64(struc
+@@ -2207,7 +2212,7 @@ static void airoha_dev_get_stats64(struc
struct airoha_gdm_port *port = dev->port;
unsigned int start;
do {
start = u64_stats_fetch_begin(&port->stats.syncp);
storage->rx_packets = port->stats.rx_ok_pkts;
-@@ -2087,8 +2091,8 @@ static int airoha_dev_change_mtu(struct
+@@ -2227,8 +2232,8 @@ static int airoha_dev_change_mtu(struct
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
GDM_LONG_LEN_MASK,
-@@ -2162,10 +2166,10 @@ static u32 airoha_get_dsa_tag(struct sk_
+@@ -2302,10 +2307,10 @@ static u32 airoha_get_dsa_tag(struct sk_
#endif
}
switch (eth->soc->version) {
case 0x7583:
-@@ -2183,8 +2187,7 @@ static int airoha_dev_set_features(struc
+@@ -2322,8 +2327,7 @@ static netdev_features_t airoha_dev_fix_
+ netdev_features_t features)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+
+ if (qdma->users > 1)
+ features &= ~NETIF_F_GRO_HW;
+@@ -2336,8 +2340,7 @@ static int airoha_dev_set_features(struc
{
netdev_features_t diff = netdev->features ^ features;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_qdma *qdma = dev->qdma;
struct airoha_eth *eth = qdma->eth;
int qdma_id = qdma - ð->qdma[0];
- int i;
-@@ -2226,10 +2229,10 @@ static int airoha_dev_set_features(struc
- if (!p)
- continue;
-
-- if (p->qdma != qdma)
-+ d = p->dev;
-+ if (d->qdma != qdma)
- continue;
-- d = p->dev;
- if (d->dev == netdev)
- continue;
-
-@@ -2246,8 +2249,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2356,8 +2359,7 @@ static netdev_tx_t airoha_dev_xmit(struc
struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
u32 nr_frags, tag, msg0, msg1, len;
struct airoha_queue_entry *e;
struct netdev_queue *txq;
-@@ -2285,7 +2287,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2395,7 +2397,7 @@ static netdev_tx_t airoha_dev_xmit(struc
}
}
msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
-@@ -2388,8 +2390,7 @@ static void airoha_ethtool_get_drvinfo(s
+@@ -2498,8 +2500,7 @@ static void airoha_ethtool_get_drvinfo(s
struct ethtool_drvinfo *info)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
-@@ -2402,7 +2403,7 @@ static void airoha_ethtool_get_mac_stats
+@@ -2512,7 +2513,7 @@ static void airoha_ethtool_get_mac_stats
struct airoha_gdm_port *port = dev->port;
unsigned int start;
do {
start = u64_stats_fetch_begin(&port->stats.syncp);
stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
-@@ -2442,7 +2443,7 @@ airoha_ethtool_get_rmon_stats(struct net
+@@ -2552,7 +2553,7 @@ airoha_ethtool_get_rmon_stats(struct net
ARRAY_SIZE(hw_stats->rx_len) + 1);
*ranges = airoha_ethtool_rmon_ranges;
do {
int i;
-@@ -2462,18 +2463,17 @@ static int airoha_qdma_set_chan_tx_sched
+@@ -2572,18 +2573,17 @@ static int airoha_qdma_set_chan_tx_sched
const u16 *weights, u8 n_weights)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
TWRR_RW_CMD_MASK |
FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
-@@ -2481,13 +2481,12 @@ static int airoha_qdma_set_chan_tx_sched
+@@ -2591,13 +2591,12 @@ static int airoha_qdma_set_chan_tx_sched
err = read_poll_timeout(airoha_qdma_rr, status,
status & TWRR_RW_CMD_DONE,
USEC_PER_MSEC, 10 * USEC_PER_MSEC,
CHAN_QOS_MODE_MASK(channel),
__field_prep(CHAN_QOS_MODE_MASK(channel), mode));
-@@ -2553,9 +2552,9 @@ static int airoha_qdma_get_tx_ets_stats(
+@@ -2663,9 +2662,9 @@ static int airoha_qdma_get_tx_ets_stats(
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
REG_CNTR_VAL((channel << 1) + 1));
u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
(fwd_tx_packets - port->fwd_tx_packets);
-@@ -2819,17 +2818,16 @@ static int airoha_qdma_set_tx_rate_limit
+@@ -2929,17 +2928,16 @@ static int airoha_qdma_set_tx_rate_limit
u32 bucket_size)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
REG_EGRESS_TRTCM_CFG,
i, rate, bucket_size);
if (err)
-@@ -2879,11 +2877,11 @@ static int airoha_tc_htb_alloc_leaf_queu
+@@ -2989,11 +2987,11 @@ static int airoha_tc_htb_alloc_leaf_queu
return 0;
}
int i;
for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
-@@ -2962,7 +2960,6 @@ static int airoha_dev_tc_matchall(struct
+@@ -3072,7 +3070,6 @@ static int airoha_dev_tc_matchall(struct
{
enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
u32 rate = 0, bucket_size = 0;
switch (f->command) {
-@@ -2987,7 +2984,7 @@ static int airoha_dev_tc_matchall(struct
+@@ -3097,7 +3094,7 @@ static int airoha_dev_tc_matchall(struct
fallthrough;
}
case TC_CLSMATCHALL_DESTROY:
unit_type);
default:
return -EOPNOTSUPP;
-@@ -2999,8 +2996,7 @@ static int airoha_dev_setup_tc_block_cb(
+@@ -3109,8 +3106,7 @@ static int airoha_dev_setup_tc_block_cb(
{
struct net_device *netdev = cb_priv;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
if (!tc_can_offload(netdev))
return -EOPNOTSUPP;
-@@ -3235,7 +3231,7 @@ static void airoha_mac_link_up(struct ph
+@@ -3346,7 +3342,7 @@ static void airoha_mac_link_up(struct ph
struct airoha_gdm_dev *dev = container_of(config, struct airoha_gdm_dev,
phylink_config);
struct airoha_gdm_port *port = dev->port;
struct airoha_gdm_dev {
struct airoha_gdm_port *port;
+ struct airoha_qdma *qdma;
- struct net_device *dev;
struct airoha_eth *eth;
-@@ -559,7 +560,6 @@ struct airoha_gdm_dev {
+ #if defined(CONFIG_PCS_AIROHA)
+@@ -558,7 +559,6 @@ struct airoha_gdm_dev {
};
struct airoha_gdm_port {
struct airoha_gdm_dev *dev;
int id;
int nbq;
-@@ -695,19 +695,18 @@ static inline bool airoha_qdma_is_lro_qu
+@@ -694,19 +694,18 @@ static inline bool airoha_qdma_is_lro_qu
return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
}
-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
+void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
{
+- struct net_device *dev = netdev_from_priv(port->dev);
- struct airoha_eth *eth = port->qdma->eth;
-- struct net_device *dev = port->dev->dev;
- const u8 *addr = dev->dev_addr;
++ struct net_device *netdev = netdev_from_priv(dev);
+ struct airoha_gdm_port *port = dev->port;
-+ struct net_device *netdev = dev->dev;
+ struct airoha_eth *eth = dev->eth;
+ const u8 *addr = netdev->dev_addr;
u32 val;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -82,12 +82,10 @@ static bool airhoa_is_phy_external(struc
+@@ -83,12 +83,10 @@ static bool airhoa_is_phy_external(struc
static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
{
val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
airoha_fe_wr(eth, reg, val);
-@@ -2038,7 +2036,7 @@ static int airoha_dev_init(struct net_de
+@@ -2179,7 +2177,7 @@ static int airoha_dev_init(struct net_de
int i;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
- dev->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
+ dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
- dev->dev->irq = dev->qdma->irq_banks[0].irq;
+ netdev->irq = dev->qdma->irq_banks[0].irq;
airoha_set_macaddr(dev, netdev->dev_addr);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -664,8 +664,10 @@ static inline u16 airoha_qdma_get_txq(st
+@@ -663,8 +663,10 @@ static inline u16 airoha_qdma_get_txq(st
return qid % ARRAY_SIZE(qdma->q_tx);
}
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -2835,30 +2835,40 @@ static int airoha_qdma_set_tx_rate_limit
+@@ -2945,30 +2945,46 @@ static int airoha_qdma_set_tx_rate_limit
return 0;
}
- int err, num_tx_queues = netdev->real_num_tx_queues;
- struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_gdm_port *port = dev->port;
++ int err;
if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
- err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
- opt->quantum);
- if (err) {
-+ return airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
++ err = airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
++ if (err)
+ NL_SET_ERR_MSG_MOD(opt->extack,
+ "failed configuring htb offload");
+- return err;
++
++ return err;
+}
+
+static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
+ * in use by another net_device running on the same QDMA block.
+ */
+ if (test_and_set_bit(channel, qdma->qos_channel_map)) {
- NL_SET_ERR_MSG_MOD(opt->extack,
-- "failed configuring htb offload");
-- return err;
++ NL_SET_ERR_MSG_MOD(opt->extack,
+ "qdma qos channel already in use");
+ return -EBUSY;
}
err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
if (err) {
-@@ -2866,13 +2876,17 @@ static int airoha_tc_htb_alloc_leaf_queu
+@@ -2976,13 +2992,17 @@ static int airoha_tc_htb_alloc_leaf_queu
opt->quantum);
NL_SET_ERR_MSG_MOD(opt->extack,
"failed setting real_num_tx_queues");
}
static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
-@@ -3053,11 +3067,13 @@ static int airoha_dev_setup_tc_block(str
+@@ -3163,11 +3183,13 @@ static int airoha_dev_setup_tc_block(str
static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
}
static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
-@@ -3065,9 +3081,8 @@ static int airoha_tc_htb_delete_leaf_que
+@@ -3175,9 +3197,8 @@ static int airoha_tc_htb_delete_leaf_que
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
return -EINVAL;
}
-@@ -3080,10 +3095,9 @@ static int airoha_tc_htb_delete_leaf_que
+@@ -3190,10 +3211,9 @@ static int airoha_tc_htb_delete_leaf_que
static int airoha_tc_htb_destroy(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
airoha_tc_remove_htb_queue(netdev, q);
return 0;
-@@ -3094,9 +3108,8 @@ static int airoha_tc_get_htb_get_leaf_qu
+@@ -3204,9 +3224,8 @@ static int airoha_tc_get_htb_get_leaf_qu
{
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
return -EINVAL;
}
-@@ -3115,6 +3128,7 @@ static int airoha_tc_setup_qdisc_htb(str
+@@ -3225,6 +3244,7 @@ static int airoha_tc_setup_qdisc_htb(str
case TC_HTB_DESTROY:
return airoha_tc_htb_destroy(dev);
case TC_HTB_NODE_MODIFY:
};
struct airoha_gdm_dev {
-@@ -557,6 +559,8 @@ struct airoha_gdm_dev {
+@@ -556,6 +558,8 @@ struct airoha_gdm_dev {
struct phylink *phylink;
struct phylink_config phylink_config;
#endif
};
struct airoha_gdm_port {
-@@ -566,8 +570,6 @@ struct airoha_gdm_port {
+@@ -565,8 +569,6 @@ struct airoha_gdm_port {
struct airoha_hw_stats stats;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -2548,19 +2548,17 @@ static int airoha_qdma_get_tx_ets_stats(
+@@ -2658,19 +2658,17 @@ static int airoha_qdma_get_tx_ets_stats(
struct tc_ets_qopt_offload *opt)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
}
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -561,6 +561,9 @@ struct airoha_gdm_dev {
+@@ -560,6 +560,9 @@ struct airoha_gdm_dev {
#endif
DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
};
struct airoha_gdm_port {
-@@ -570,10 +573,6 @@ struct airoha_gdm_port {
+@@ -569,10 +572,6 @@ struct airoha_gdm_port {
struct airoha_hw_stats stats;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -115,7 +115,7 @@ static int airoha_set_vip_for_gdm_port(s
+@@ -116,7 +116,7 @@ static int airoha_set_vip_for_gdm_port(s
struct airoha_eth *eth = dev->eth;
u32 vip_port;
if (enable) {
airoha_fe_set(eth, REG_FE_VIP_PORT_EN, vip_port);
airoha_fe_set(eth, REG_FE_IFC_PORT_EN, vip_port);
-@@ -619,30 +619,26 @@ static int airoha_qdma_fill_rx_queue(str
+@@ -646,30 +646,26 @@ static int airoha_qdma_fill_rx_queue(str
return nframes;
}
+
+ if (p >= ARRAY_SIZE(eth->ports))
+ return ERR_PTR(-ENODEV);
-+
+
+- return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
+ port = eth->ports[p];
+ if (!port)
+ return ERR_PTR(-ENODEV);
+
+ if (d >= ARRAY_SIZE(port->devs))
+ return ERR_PTR(-ENODEV);
-
-- return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
++
+ return port->devs[d] ? port->devs[d] : ERR_PTR(-ENODEV);
}
- static int airoha_qdma_lro_rx_process(struct airoha_queue *q,
-@@ -736,9 +732,8 @@ static int airoha_qdma_rx_process(struct
+ static struct sk_buff *airoha_qdma_lro_rx_skb(struct airoha_queue *q,
+@@ -841,9 +837,9 @@ static int airoha_qdma_rx_process(struct
struct airoha_queue_entry *e = &q->entry[q->tail];
struct airoha_qdma_desc *desc = &q->desc[q->tail];
u32 hash, reason, msg1, desc_ctrl;
- struct airoha_gdm_port *port;
-- struct net_device *netdev;
-- int data_len, len, p;
+ struct airoha_gdm_dev *dev;
+ struct net_device *netdev;
+- int data_len, len, p;
+ int data_len, len;
struct page *page;
desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
-@@ -759,15 +754,10 @@ static int airoha_qdma_rx_process(struct
+@@ -864,15 +860,11 @@ static int airoha_qdma_rx_process(struct
if (!len || data_len < len)
goto free_frag;
- p = airoha_qdma_get_gdm_port(eth, desc);
- if (p < 0 || !eth->ports[p])
-- goto free_frag;
--
-- port = eth->ports[p];
-- if (!port->dev)
+ dev = airoha_qdma_get_gdm_dev(eth, desc);
+ if (IS_ERR(dev))
goto free_frag;
-- netdev = port->dev->dev;
+- port = eth->ports[p];
+- if (!port->dev)
+- goto free_frag;
+-
+- netdev = netdev_from_priv(port->dev);
++ netdev = netdev_from_priv(dev);
if (!q->skb) { /* first buffer */
- q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
- q->buf_size);
-@@ -776,14 +766,14 @@ static int airoha_qdma_rx_process(struct
-
- skb_reserve(q->skb, AIROHA_RX_HEADROOM);
- __skb_put(q->skb, len);
-- q->skb->dev = netdev;
-+ q->skb->dev = dev->dev;
- q->skb->ip_summed = CHECKSUM_UNNECESSARY;
- skb_record_rx_queue(q->skb, qid);
-
- if (airoha_qdma_lro_rx_process(q, desc) < 0)
- goto free_frag;
-
-- q->skb->protocol = eth_type_trans(q->skb, netdev);
-+ q->skb->protocol = eth_type_trans(q->skb, dev->dev);
- skb_mark_for_recycle(q->skb);
- } else { /* scattered frame */
- struct skb_shared_info *shinfo = skb_shinfo(q->skb);
-@@ -800,7 +790,9 @@ static int airoha_qdma_rx_process(struct
- if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
+ q->skb = airoha_qdma_build_rx_skb(q, desc, e, netdev);
+ if (!q->skb)
+@@ -893,6 +885,8 @@ static int airoha_qdma_rx_process(struct
continue;
-- if (netdev_uses_dsa(netdev)) {
-+ if (netdev_uses_dsa(dev->dev)) {
+ if (netdev_uses_dsa(netdev)) {
+ struct airoha_gdm_port *port = dev->port;
+
/* PPE module requires untagged packets to work
* properly and it provides DSA port index via the
* DMA descriptor. Report DSA tag to the DSA stack
-@@ -997,24 +989,27 @@ static void airoha_qdma_wake_netdev_txqs
+@@ -1095,26 +1089,29 @@ static void airoha_qdma_wake_netdev_txqs
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
- struct airoha_gdm_dev *dev;
+- struct net_device *netdev;
- int j;
+ int d;
- continue;
+ for (d = 0; d < ARRAY_SIZE(port->devs); d++) {
+ struct airoha_gdm_dev *dev = port->devs[d];
++ struct net_device *netdev;
+ int j;
- if (dev->qdma != qdma)
+ if (!dev)
+ continue;
-- for (j = 0; j < dev->dev->num_tx_queues; j++) {
+- netdev = netdev_from_priv(port->dev);
+- for (j = 0; j < netdev->num_tx_queues; j++) {
- if (airoha_qdma_get_txq(qdma, j) != qid)
+ if (dev->qdma != qdma)
continue;
-- netif_wake_subqueue(dev->dev, j);
-+ for (j = 0; j < dev->dev->num_tx_queues; j++) {
+- netif_wake_subqueue(netdev, j);
++ netdev = netdev_from_priv(dev);
++ for (j = 0; j < netdev->num_tx_queues; j++) {
+ if (airoha_qdma_get_txq(qdma, j) != qid)
+ continue;
+
-+ netif_wake_subqueue(dev->dev, j);
++ netif_wake_subqueue(netdev, j);
+ }
}
}
q->txq_stopped = false;
-@@ -1903,11 +1898,9 @@ static int airoha_dev_open(struct net_de
+@@ -1961,22 +1958,32 @@ static void airoha_update_hw_stats(struc
+
+ static void airoha_update_netdev_features(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+- struct airoha_gdm_port *p = eth->ports[i];
+- struct net_device *netdev;
++ struct airoha_gdm_port *port = eth->ports[i];
++ int j;
+
+- if (!p)
++ if (!port)
+ continue;
+
+- netdev = netdev_from_priv(p->dev);
+- if (netdev->reg_state != NETREG_REGISTERED)
+- continue;
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *iter_dev = port->devs[j];
++ struct net_device *netdev;
++
++ if (!iter_dev || iter_dev == dev)
++ continue;
++
++ if (iter_dev->qdma != dev->qdma)
++ continue;
++
++ netdev = netdev_from_priv(iter_dev);
++ if (netdev->reg_state != NETREG_REGISTERED)
++ continue;
+
+- netdev_update_features(netdev);
++ netdev_update_features(netdev);
++ }
+ }
+ }
+
+@@ -2037,7 +2044,8 @@ static int airoha_dev_open(struct net_de
GLOBAL_CFG_RX_DMA_EN_MASK);
- atomic_inc(&qdma->users);
+ qdma->users++;
-- if (port->id == AIROHA_GDM2_IDX &&
-- airoha_ppe_is_enabled(qdma->eth, 1)) {
-- /* For PPE2 always use secondary cpu port. */
+- if (port->id == AIROHA_GDM2_IDX && airoha_ppe_is_enabled(eth, 1)) {
+ if (!airoha_is_lan_gdm_dev(dev) &&
-+ airoha_ppe_is_enabled(qdma->eth, 1))
++ airoha_ppe_is_enabled(eth, 1)) {
+ /* For PPE2 always use secondary cpu port. */
pse_port = FE_PSE_PORT_PPE2;
-- }
- airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
- pse_port);
-
-@@ -2002,7 +1995,7 @@ static int airoha_set_gdm2_loopback(stru
+ }
+@@ -2143,7 +2151,7 @@ static int airoha_set_gdm2_loopback(stru
airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
if (src_port < 0)
return src_port;
-@@ -2019,7 +2012,7 @@ static int airoha_set_gdm2_loopback(stru
+@@ -2160,7 +2168,7 @@ static int airoha_set_gdm2_loopback(stru
airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
__field_prep(mask, AIROHA_GDM2_IDX));
-@@ -2033,7 +2026,7 @@ static int airoha_dev_init(struct net_de
+@@ -2174,7 +2182,7 @@ static int airoha_dev_init(struct net_de
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
-@@ -2056,8 +2049,8 @@ static int airoha_dev_init(struct net_de
+@@ -2197,8 +2205,8 @@ static int airoha_dev_init(struct net_de
break;
}
return 0;
}
-@@ -2222,20 +2215,26 @@ static int airoha_dev_set_features(struc
- } else {
- for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
- struct airoha_gdm_port *p = eth->ports[i];
-- struct airoha_gdm_dev *d;
-+ int j;
-
- if (!p)
- continue;
-
-- d = p->dev;
-- if (d->qdma != qdma)
-- continue;
-+ for (j = 0; j < ARRAY_SIZE(p->devs); j++) {
-+ struct airoha_gdm_dev *d = p->devs[j];
-
-- if (d->dev == netdev)
-- continue;
-+ if (!d)
-+ continue;
-
-- if (d->dev->features & NETIF_F_LRO)
-- return 0;
-+ if (d->qdma != qdma)
-+ continue;
-+
-+ if (d->dev == netdev)
-+ continue;
-+
-+ if (d->dev->features & NETIF_F_LRO)
-+ return 0;
-+ }
- }
- airoha_fe_lro_disable(eth, qdma_id);
- }
-@@ -2286,7 +2285,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+@@ -2396,7 +2404,8 @@ static netdev_tx_t airoha_dev_xmit(struc
}
fport = airoha_get_fe_port(dev);
FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
q = &qdma->q_tx[qid];
-@@ -3222,12 +3222,15 @@ bool airoha_is_valid_gdm_dev(struct airo
+@@ -3339,12 +3348,15 @@ bool airoha_is_valid_gdm_dev(struct airo
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
}
return false;
-@@ -3351,10 +3354,11 @@ static int airoha_setup_phylink(struct n
+@@ -3468,10 +3480,11 @@ static int airoha_setup_phylink(struct n
static int airoha_alloc_gdm_device(struct airoha_eth *eth,
struct airoha_gdm_port *port,
int err;
netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
-@@ -3372,7 +3376,6 @@ static int airoha_alloc_gdm_device(struc
- netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
+@@ -3489,7 +3502,6 @@ static int airoha_alloc_gdm_device(struc
+ netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_GRO_HW;
netdev->features |= AIROHA_HW_FEATURES;
netdev->vlan_features = AIROHA_HW_FEATURES;
- netdev->dev.of_node = np;
SET_NETDEV_DEV(netdev, eth->dev);
/* reserve hw queues for HTB offloading */
-@@ -3390,11 +3393,25 @@ static int airoha_alloc_gdm_device(struc
+@@ -3507,10 +3519,24 @@ static int airoha_alloc_gdm_device(struc
netdev->dev_addr);
}
+
+ netdev->dev.of_node = of_node_get(np);
dev = netdev_priv(netdev);
- dev->dev = netdev;
dev->port = port;
- port->dev = dev;
dev->eth = eth;
#if defined(CONFIG_PCS_AIROHA)
if (airhoa_is_phy_external(port)) {
-@@ -3412,7 +3429,8 @@ static int airoha_alloc_gdm_port(struct
+@@ -3528,7 +3554,8 @@ static int airoha_alloc_gdm_port(struct
{
const __be32 *id_ptr = of_get_property(np, "reg", NULL);
struct airoha_gdm_port *port;
u32 id;
if (!id_ptr) {
-@@ -3440,15 +3458,51 @@ static int airoha_alloc_gdm_port(struct
+@@ -3556,15 +3583,51 @@ static int airoha_alloc_gdm_port(struct
u64_stats_init(&port->stats.syncp);
spin_lock_init(&port->stats.lock);
port->id = id;
}
static int airoha_register_gdm_devices(struct airoha_eth *eth)
-@@ -3457,14 +3511,22 @@ static int airoha_register_gdm_devices(s
+@@ -3573,14 +3636,22 @@ static int airoha_register_gdm_devices(s
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
if (!port)
continue;
-- err = register_netdev(port->dev->dev);
+- err = register_netdev(netdev_from_priv(port->dev));
- if (err)
- return err;
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ if (!dev)
+ continue;
+
-+ err = register_netdev(dev->dev);
++ err = register_netdev(netdev_from_priv(dev));
+ if (err)
+ return err;
+ }
}
set_bit(DEV_STATE_REGISTERED, ð->state);
-@@ -3571,18 +3633,27 @@ error_napi_stop:
+@@ -3687,15 +3758,19 @@ error_napi_stop:
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
continue;
- dev = port->dev;
-- if (dev && dev->dev->reg_state == NETREG_REGISTERED) {
+- if (dev) {
+- struct net_device *netdev = netdev_from_priv(dev);
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+ struct net_device *netdev;
+
+ if (!dev)
+ continue;
-+
-+ netdev = dev->dev;
-+ if (netdev->reg_state == NETREG_REGISTERED) {
+
++ netdev = netdev_from_priv(dev);
+ if (netdev->reg_state == NETREG_REGISTERED) {
#if defined(CONFIG_PCS_AIROHA)
-- if (airhoa_is_phy_external(port))
-- phylink_destroy(dev->phylink);
-+ if (airhoa_is_phy_external(port))
-+ phylink_destroy(dev->phylink);
+ if (airhoa_is_phy_external(port))
+@@ -3703,6 +3778,7 @@ error_napi_stop:
#endif
-- unregister_netdev(dev->dev);
-+ unregister_netdev(netdev);
-+ }
+ unregister_netdev(netdev);
+ }
+ of_node_put(netdev->dev.of_node);
}
airoha_metadata_dst_free(port);
}
-@@ -3604,19 +3675,26 @@ static void airoha_remove(struct platfor
+@@ -3724,19 +3800,27 @@ static void airoha_remove(struct platfor
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
struct airoha_gdm_port *port = eth->ports[i];
+ continue;
+
#if defined(CONFIG_PCS_AIROHA)
- if (airhoa_is_phy_external(port))
- phylink_destroy(dev->phylink);
+- if (airhoa_is_phy_external(port))
+- phylink_destroy(dev->phylink);
++ if (airhoa_is_phy_external(port))
++ phylink_destroy(dev->phylink);
#endif
--
+
- dev = port->dev;
- if (dev)
-- unregister_netdev(dev->dev);
-+ netdev = dev->dev;
+- unregister_netdev(netdev_from_priv(dev));
++ netdev = netdev_from_priv(dev);
+ unregister_netdev(netdev);
+ of_node_put(netdev->dev.of_node);
+ }
airoha_metadata_dst_free(port);
}
airoha_hw_cleanup(eth);
-@@ -3678,6 +3756,39 @@ static u32 airoha_en7581_get_vip_port(st
+@@ -3798,6 +3882,39 @@ static u32 airoha_en7581_get_vip_port(st
return 0;
}
static const char * const an7583_xsi_rsts_names[] = {
"hsi0-mac",
"hsi1-mac",
-@@ -3726,6 +3837,36 @@ static u32 airoha_an7583_get_vip_port(st
+@@ -3846,6 +3963,36 @@ static u32 airoha_an7583_get_vip_port(st
return 0;
}
static const struct airoha_eth_soc_data en7581_soc_data = {
.version = 0x7581,
.xsi_rsts_names = en7581_xsi_rsts_names,
-@@ -3734,6 +3875,7 @@ static const struct airoha_eth_soc_data
+@@ -3854,6 +4001,7 @@ static const struct airoha_eth_soc_data
.ops = {
.get_sport = airoha_en7581_get_sport,
.get_vip_port = airoha_en7581_get_vip_port,
},
};
-@@ -3745,6 +3887,7 @@ static const struct airoha_eth_soc_data
+@@ -3865,6 +4013,7 @@ static const struct airoha_eth_soc_data
.ops = {
.get_sport = airoha_an7583_get_sport,
.get_vip_port = airoha_an7583_get_vip_port,
#define AIROHA_MAX_NUM_QDMA 2
#define AIROHA_MAX_NUM_IRQ_BANKS 4
#define AIROHA_MAX_DSA_PORTS 7
-@@ -552,8 +553,8 @@ struct airoha_qdma {
- struct airoha_gdm_dev {
- struct airoha_gdm_port *port;
- struct airoha_qdma *qdma;
-- struct net_device *dev;
- struct airoha_eth *eth;
-+ struct net_device *dev;
-
- #if defined(CONFIG_PCS_AIROHA)
- struct phylink *phylink;
-@@ -564,12 +565,13 @@ struct airoha_gdm_dev {
+@@ -563,12 +564,13 @@ struct airoha_gdm_dev {
/* qos stats counters */
u64 cpu_tx_packets;
u64 fwd_tx_packets;
struct airoha_hw_stats stats;
-@@ -605,6 +607,8 @@ struct airoha_eth_soc_data {
+@@ -604,6 +606,8 @@ struct airoha_eth_soc_data {
struct {
int (*get_sport)(struct airoha_gdm_port *port, int nbq);
u32 (*get_vip_port)(struct airoha_gdm_port *port, int nbq);
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -1859,8 +1859,8 @@ static int airoha_dev_open(struct net_de
+@@ -1992,10 +1992,10 @@ static int airoha_dev_open(struct net_de
int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
+ u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
- u32 pse_port = FE_PSE_PORT_PPE1;
- #if defined(CONFIG_PCS_AIROHA)
- if (airhoa_is_phy_external(port)) {
-@@ -1888,10 +1888,20 @@ static int airoha_dev_open(struct net_de
- airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ /* HW LRO is configured on the QDMA and it is shared between
+ * all the devices using it. Refuse to open a second device on
+@@ -2034,10 +2034,20 @@ static int airoha_dev_open(struct net_de
+ airoha_fe_clear(eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
-- airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
+- airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
- FIELD_PREP(GDM_LONG_LEN_MASK, len));
-+ cur_len = airoha_fe_get(qdma->eth, REG_GDM_LEN_CFG(port->id),
++ cur_len = airoha_fe_get(eth, REG_GDM_LEN_CFG(port->id),
+ GDM_LONG_LEN_MASK);
+ if (!port->users || len > cur_len) {
+ /* Opening a sibling net_device with a larger MTU updates the
+ * multiple net_devices with different MTUs to share the same
+ * GDM port.
+ */
-+ airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
++ airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+ GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+ FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+ FIELD_PREP(GDM_LONG_LEN_MASK, len));
airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
GLOBAL_CFG_TX_DMA_EN_MASK |
-@@ -1907,6 +1917,30 @@ static int airoha_dev_open(struct net_de
+@@ -2060,6 +2070,30 @@ static int airoha_dev_open(struct net_de
return 0;
}
+ if (!dev)
+ continue;
+
-+ netdev = dev->dev;
++ netdev = netdev_from_priv(dev);
+ if (netif_running(netdev))
+ len = max_t(u32, len, netdev->mtu);
+ }
static int airoha_dev_stop(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
-@@ -1919,8 +1953,12 @@ static int airoha_dev_stop(struct net_de
+@@ -2073,8 +2107,11 @@ static int airoha_dev_stop(struct net_de
for (i = 0; i < netdev->num_tx_queues; i++)
netdev_tx_reset_subqueue(netdev, i);
-- 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),
- FE_PSE_PORT_DROP);
+ if (--port->users)
-+ airoha_set_port_mtu(dev->eth, port);
++ airoha_set_port_mtu(eth, port);
+ else
-+ 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),
+ FE_PSE_PORT_DROP);
- if (atomic_dec_and_test(&qdma->users)) {
+ if (!--qdma->users) {
airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
-@@ -2082,13 +2120,10 @@ static int airoha_dev_change_mtu(struct
+@@ -2238,13 +2275,10 @@ static int airoha_dev_change_mtu(struct
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
}
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -572,6 +572,7 @@ struct airoha_gdm_dev {
+@@ -571,6 +571,7 @@ struct airoha_gdm_dev {
struct airoha_gdm_port {
struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
int id;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -2059,36 +2059,80 @@ static int airoha_set_gdm2_loopback(stru
+@@ -2214,36 +2214,81 @@ static int airoha_set_gdm2_loopback(stru
return 0;
}
-static int airoha_dev_init(struct net_device *netdev)
+static struct airoha_gdm_dev *
+airoha_get_wan_gdm_dev(struct airoha_eth *eth)
-+{
+ {
+- struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+}
+
+static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
- {
-- struct airoha_gdm_dev *dev = netdev_priv(netdev);
-- struct airoha_gdm_port *port = dev->port;
++{
++ struct net_device *netdev = netdev_from_priv(dev);
struct airoha_eth *eth = dev->eth;
int ppe_id;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
- dev->dev->irq = dev->qdma->irq_banks[0].irq;
+ netdev->irq = dev->qdma->irq_banks[0].irq;
- airoha_set_macaddr(dev, netdev->dev_addr);
+
+ ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
struct airoha_gdm_dev {
struct airoha_gdm_port *port;
struct airoha_qdma *qdma;
-@@ -566,6 +570,7 @@ struct airoha_gdm_dev {
+@@ -565,6 +569,7 @@ struct airoha_gdm_dev {
u64 cpu_tx_packets;
u64 fwd_tx_packets;
int nbq;
};
-@@ -672,13 +677,7 @@ static inline u16 airoha_qdma_get_txq(st
+@@ -671,13 +676,7 @@ static inline u16 airoha_qdma_get_txq(st
static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
{
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -80,20 +80,74 @@ static bool airhoa_is_phy_external(struc
+@@ -81,20 +81,76 @@ static bool airhoa_is_phy_external(struc
}
#endif
+ airoha_is_lan_gdm_dev(dev))
+ continue;
+
-+ netdev = iter_dev->dev;
++ netdev = netdev_from_priv(iter_dev);
+ if (netdev->reg_state != NETREG_REGISTERED)
+ continue;
+
+ * the same for each net_device with the same LAN/WAN
+ * configuration.
+ */
++ struct net_device *netdev = netdev_from_priv(dev);
++
+ dev_warn(eth->dev,
+ "%s: wrong mac addr, MSBs must be %02x:%02x:%02x\n",
-+ dev->dev->name, ref_addr[0], ref_addr[1],
++ netdev->name, ref_addr[0], ref_addr[1],
+ ref_addr[2]);
+ dev_warn(eth->dev, "FE hw forwarding won't work properly\n");
+
- airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
+ airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), lmin);
+ airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), lmax);
++
++ airoha_ppe_init_upd_mem(dev, addr);
- airoha_ppe_init_upd_mem(dev);
-+ airoha_ppe_init_upd_mem(dev, addr);
-+
+ return 0;
}
static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
-@@ -1986,13 +2040,18 @@ static int airoha_dev_stop(struct net_de
+@@ -2141,13 +2197,18 @@ static int airoha_dev_stop(struct net_de
static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
}
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -713,7 +713,7 @@ void airoha_ppe_check_skb(struct airoha_
+@@ -712,7 +712,7 @@ void airoha_ppe_check_skb(struct airoha_
int airoha_ppe_setup_tc_block_cb(struct airoha_ppe_dev *dev, void *type_data);
int airoha_ppe_init(struct airoha_eth *eth);
void airoha_ppe_deinit(struct airoha_eth *eth);
-void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
+void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev, const u8 *addr)
{
+- struct net_device *netdev = netdev_from_priv(dev);
struct airoha_gdm_port *port = dev->port;
-- struct net_device *netdev = dev->dev;
struct airoha_eth *eth = dev->eth;
- const u8 *addr = netdev->dev_addr;
u32 val;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -2056,7 +2056,7 @@ static int airoha_dev_set_macaddr(struct
+@@ -2213,7 +2213,7 @@ static int airoha_dev_set_macaddr(struct
return 0;
}
{
struct airoha_gdm_port *port = dev->port;
struct airoha_eth *eth = dev->eth;
-@@ -2188,7 +2188,7 @@ static int airoha_dev_init(struct net_de
+@@ -2346,7 +2346,7 @@ static int airoha_dev_init(struct net_de
(port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
int err;
+++ /dev/null
-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
-@@ -1916,6 +1916,11 @@ static int airoha_dev_open(struct net_de
- u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
- struct airoha_qdma *qdma = dev->qdma;
-
-+ if (port->id == AIROHA_GDM2_IDX && airoha_is_lan_gdm_dev(dev)) {
-+ /* GDM2 can be used just as wan */
-+ return -EBUSY;
-+ }
-+
- #if defined(CONFIG_PCS_AIROHA)
- if (airhoa_is_phy_external(port)) {
- err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
-@@ -2118,6 +2123,45 @@ static int airoha_enable_gdm2_loopback(s
- return 0;
- }
-
-+static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
-+{
-+ struct airoha_eth *eth = dev->eth;
-+ int i, src_port;
-+ u32 pse_port;
-+
-+ src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
-+ if (src_port < 0)
-+ return src_port;
-+
-+ airoha_fe_clear(eth,
-+ REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
-+ SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
-+
-+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
-+ FE_PSE_PORT_DROP);
-+ airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
-+ LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
-+ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
-+ : FE_PSE_PORT_PPE1;
-+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
-+ pse_port);
-+
-+ airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
-+ FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
-+
-+ for (i = 0; i < eth->soc->num_ppe; i++)
-+ airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX),
-+ DFT_CPORT_MASK(AIROHA_GDM2_IDX));
-+
-+ /* Enable VIP and IFC for GDM2 */
-+ airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
-+ airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
-+
-+ airoha_fe_wr(eth, REG_SRC_PORT_FC_MAP6, FC_MAP6_DEF_VALUE);
-+
-+ return 0;
-+}
-+
- static struct airoha_gdm_dev *
- airoha_get_wan_gdm_dev(struct airoha_eth *eth)
- {
-@@ -2522,6 +2566,80 @@ error:
- return NETDEV_TX_OK;
- }
-
-+struct airoha_ethool_priv_flags {
-+ char name[ETH_GSTRING_LEN];
-+ int (*handler)(struct net_device *netdev, u32 flags);
-+};
-+
-+static int airoha_dev_set_wan_flag(struct net_device *netdev, u32 flags)
-+{
-+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
-+ struct airoha_gdm_port *port = dev->port;
-+ struct airoha_eth *eth = dev->eth;
-+
-+ if (!((dev->flags ^ flags) & AIROHA_PRIV_F_WAN))
-+ return 0;
-+
-+ if (netif_running(netdev))
-+ return -EBUSY;
-+
-+ if (flags & AIROHA_PRIV_F_WAN) {
-+ struct airoha_gdm_dev *wan_dev;
-+
-+ /* Verify the wan device is not already configured */
-+ wan_dev = airoha_get_wan_gdm_dev(eth);
-+ if (wan_dev && wan_dev != dev)
-+ return -EBUSY;
-+
-+ switch (port->id) {
-+ case AIROHA_GDM2_IDX:
-+ dev->flags |= AIROHA_PRIV_F_WAN;
-+ airoha_dev_set_qdma(dev);
-+ break;
-+ case AIROHA_GDM3_IDX:
-+ case AIROHA_GDM4_IDX: {
-+ int err;
-+
-+ dev->flags |= AIROHA_PRIV_F_WAN;
-+ airoha_dev_set_qdma(dev);
-+
-+ err = airoha_enable_gdm2_loopback(dev);
-+ if (err) {
-+ dev->flags &= ~AIROHA_PRIV_F_WAN;
-+ return err;
-+ }
-+ break;
-+ }
-+ default:
-+ return -EOPNOTSUPP;
-+ }
-+ } else {
-+ switch (port->id) {
-+ case AIROHA_GDM3_IDX:
-+ case AIROHA_GDM4_IDX: {
-+ int err;
-+
-+ err = airoha_disable_gdm2_loopback(dev);
-+ if (err)
-+ return err;
-+ break;
-+ }
-+ default:
-+ break;
-+ }
-+
-+ dev->flags &= ~AIROHA_PRIV_F_WAN;
-+ airoha_dev_set_qdma(dev);
-+ }
-+
-+ return airoha_set_macaddr(dev, netdev->dev_addr);
-+}
-+
-+static const struct airoha_ethool_priv_flags airoha_eth_priv_flags[] = {
-+ { "wan", airoha_dev_set_wan_flag },
-+};
-+#define AIROHA_PRIV_FLAGS_STR_LEN ARRAY_SIZE(airoha_eth_priv_flags)
-+
- static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
- struct ethtool_drvinfo *info)
- {
-@@ -2530,6 +2648,7 @@ static void airoha_ethtool_get_drvinfo(s
-
- strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
- strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
-+ info->n_priv_flags = AIROHA_PRIV_FLAGS_STR_LEN;
- }
-
- static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
-@@ -2594,6 +2713,56 @@ airoha_ethtool_get_rmon_stats(struct net
- } while (u64_stats_fetch_retry(&port->stats.syncp, start));
- }
-
-+static int airoha_ethtool_set_priv_flags(struct net_device *netdev, u32 flags)
-+{
-+ int i;
-+
-+ for (i = 0; i < AIROHA_PRIV_FLAGS_STR_LEN; i++) {
-+ int err;
-+
-+ if (!airoha_eth_priv_flags[i].handler)
-+ continue;
-+
-+ err = airoha_eth_priv_flags[i].handler(netdev, flags);
-+ if (err)
-+ return err;
-+ }
-+
-+ return 0;
-+}
-+
-+static u32 airoha_ethtool_get_priv_flags(struct net_device *netdev)
-+{
-+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
-+
-+ return dev->flags;
-+}
-+
-+static int airoha_ethtool_get_sset_count(struct net_device *netdev, int sset)
-+{
-+ switch (sset) {
-+ case ETH_SS_PRIV_FLAGS:
-+ return AIROHA_PRIV_FLAGS_STR_LEN;
-+ default:
-+ return -EOPNOTSUPP;
-+ }
-+}
-+
-+static void airoha_ethtool_get_strings(struct net_device *netdev,
-+ u32 stringset, u8 *data)
-+{
-+ int i;
-+
-+ switch (stringset) {
-+ case ETH_SS_PRIV_FLAGS:
-+ for (i = 0; i < AIROHA_PRIV_FLAGS_STR_LEN; i++)
-+ ethtool_puts(&data, airoha_eth_priv_flags[i].name);
-+ break;
-+ default:
-+ break;
-+ }
-+}
-+
- 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)
-@@ -3315,6 +3484,10 @@ static const struct ethtool_ops airoha_e
- .get_rmon_stats = airoha_ethtool_get_rmon_stats,
- .get_link_ksettings = phy_ethtool_get_link_ksettings,
- .get_link = ethtool_op_get_link,
-+ .set_priv_flags = airoha_ethtool_set_priv_flags,
-+ .get_priv_flags = airoha_ethtool_get_priv_flags,
-+ .get_sset_count = airoha_ethtool_get_sset_count,
-+ .get_strings = airoha_ethtool_get_strings,
- };
-
- static void airoha_mac_config(struct phylink_config *config, unsigned int mode,
---- a/drivers/net/ethernet/airoha/airoha_regs.h
-+++ b/drivers/net/ethernet/airoha/airoha_regs.h
-@@ -402,6 +402,7 @@
-
- #define REG_SRC_PORT_FC_MAP6 0x2298
- #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
-+#define FC_MAP6_DEF_VALUE 0x1b1a1918
-
- #define REG_CDM5_RX_OQ1_DROP_CNT 0x29d4
-
--- /dev/null
+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
+@@ -540,7 +540,7 @@ static bool airoha_fe_lro_is_enabled(str
+
+ static void airoha_dev_lro_enable(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_qdma *qdma = dev->qdma;
++ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+ int i, lro_queue_index = 0;
+@@ -1158,7 +1158,7 @@ static void airoha_qdma_wake_netdev_txqs
+ if (!dev)
+ continue;
+
+- if (dev->qdma != qdma)
++ if (rcu_access_pointer(dev->qdma) != qdma)
+ continue;
+
+ netdev = netdev_from_priv(dev);
+@@ -2031,7 +2031,8 @@ static void airoha_update_netdev_feature
+ if (!iter_dev || iter_dev == dev)
+ continue;
+
+- if (iter_dev->qdma != dev->qdma)
++ if (rcu_access_pointer(iter_dev->qdma) !=
++ rcu_access_pointer(dev->qdma))
+ continue;
+
+ netdev = netdev_from_priv(iter_dev);
+@@ -2043,20 +2044,57 @@ static void airoha_update_netdev_feature
+ }
+ }
+
++static void airoha_qdma_start(struct airoha_qdma *qdma)
++{
++ airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
++ GLOBAL_CFG_TX_DMA_EN_MASK |
++ GLOBAL_CFG_RX_DMA_EN_MASK);
++ qdma->users++;
++}
++
++static void airoha_qdma_stop(struct airoha_qdma *qdma)
++{
++ u32 status;
++
++ if (--qdma->users)
++ return;
++
++ airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
++ GLOBAL_CFG_TX_DMA_EN_MASK |
++ GLOBAL_CFG_RX_DMA_EN_MASK);
++
++ if (read_poll_timeout(airoha_qdma_rr, status,
++ !(status & (GLOBAL_CFG_TX_DMA_BUSY_MASK |
++ GLOBAL_CFG_RX_DMA_BUSY_MASK)),
++ USEC_PER_MSEC, 50 * USEC_PER_MSEC, true,
++ qdma, REG_QDMA_GLOBAL_CFG))
++ dev_warn(qdma->eth->dev, "QDMA DMA engine busy timeout\n");
++
++ for (int i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
++ if (!qdma->q_tx[i].ndesc)
++ continue;
++
++ airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
++ }
++}
++
+ static int airoha_dev_open(struct net_device *netdev)
+ {
+ int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
+- struct airoha_qdma *qdma = dev->qdma;
+- struct airoha_eth *eth = qdma->eth;
+- int qdma_id = qdma - ð->qdma[0];
++ struct airoha_eth *eth = dev->eth;
++ struct airoha_qdma *qdma;
++ int qdma_id;
+
+ /* HW 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.
+ */
++ qdma = airoha_qdma_deref(dev);
++ qdma_id = qdma - ð->qdma[0];
++
+ if (qdma->users && airoha_fe_lro_is_enabled(eth, qdma_id)) {
+ netdev_warn(netdev, "required to disable HW GRO on QDMA%d\n",
+ qdma_id);
+@@ -2105,10 +2143,7 @@ static int airoha_dev_open(struct net_de
+ }
+ port->users++;
+
+- airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+- GLOBAL_CFG_TX_DMA_EN_MASK |
+- GLOBAL_CFG_RX_DMA_EN_MASK);
+- qdma->users++;
++ airoha_qdma_start(qdma);
+
+ if (!airoha_is_lan_gdm_dev(dev) &&
+ airoha_ppe_is_enabled(eth, 1)) {
+@@ -2154,8 +2189,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 = dev->qdma;
+- struct airoha_eth *eth = qdma->eth;
++ struct airoha_qdma *qdma;
+ int i;
+
+ netif_tx_disable(netdev);
+@@ -2163,25 +2197,14 @@ static int airoha_dev_stop(struct net_de
+ for (i = 0; i < netdev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(netdev, i);
+
++ qdma = airoha_qdma_deref(dev);
+ if (--port->users)
+- airoha_set_port_mtu(eth, port);
++ airoha_set_port_mtu(dev->eth, port);
+ else
+- airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
++ airoha_set_gdm_port_fwd_cfg(qdma->eth,
++ REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_DROP);
+-
+- if (!--qdma->users) {
+- airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+- GLOBAL_CFG_TX_DMA_EN_MASK |
+- GLOBAL_CFG_RX_DMA_EN_MASK);
+-
+- for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+- if (!qdma->q_tx[i].ndesc)
+- continue;
+-
+- airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
+- }
+- }
+-
++ airoha_qdma_stop(qdma);
+ airoha_update_netdev_features(dev);
+
+ #if defined(CONFIG_PCS_AIROHA)
+@@ -2275,6 +2298,53 @@ static int airoha_enable_gdm2_loopback(s
+ return 0;
+ }
+
++static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
++{
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
++ int i, src_port;
++ u32 pse_port;
++
++ src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
++ if (src_port < 0)
++ return src_port;
++
++ airoha_fe_clear(eth,
++ REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
++ SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
++
++ airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
++ GDM_STRIP_CRC_MASK);
++ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
++ FE_PSE_PORT_DROP);
++ airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
++ LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
++ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
++ : FE_PSE_PORT_PPE1;
++ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
++ pse_port);
++
++ airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
++ FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
++
++ for (i = 0; i < eth->soc->num_ppe; i++)
++ airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX),
++ DFT_CPORT_MASK(AIROHA_GDM2_IDX));
++
++ /* Enable VIP and IFC for GDM2 */
++ airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
++ airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
++
++ if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
++ u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
++
++ airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
++ FC_MAP6_DEF_VALUE & mask);
++ }
++
++ return 0;
++}
++
+ static struct airoha_gdm_dev *
+ airoha_get_wan_gdm_dev(struct airoha_eth *eth)
+ {
+@@ -2301,15 +2371,30 @@ airoha_get_wan_gdm_dev(struct airoha_eth
+ static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
+ {
+ struct net_device *netdev = netdev_from_priv(dev);
++ struct airoha_qdma *cur_qdma, *qdma;
+ struct airoha_eth *eth = dev->eth;
+ int ppe_id;
+
+ /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+- dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
+- netdev->irq = dev->qdma->irq_banks[0].irq;
++ qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
++ cur_qdma = airoha_qdma_deref(dev);
++ if (netif_running(netdev))
++ airoha_qdma_start(qdma);
++
++ rcu_assign_pointer(dev->qdma, qdma);
++ netdev->irq = qdma->irq_banks[0].irq;
+
+ ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
+ airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
++
++ if (!cur_qdma)
++ return;
++
++ synchronize_rcu();
++ netif_tx_wake_all_queues(netdev);
++
++ if (netif_running(netdev))
++ airoha_qdma_stop(cur_qdma);
+ }
+
+ static int airoha_dev_init(struct net_device *netdev)
+@@ -2473,8 +2558,9 @@ static netdev_features_t airoha_dev_fix_
+ netdev_features_t features)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_qdma *qdma = dev->qdma;
++ struct airoha_qdma *qdma;
+
++ qdma = airoha_qdma_deref(dev);
+ if (qdma->users > 1)
+ features &= ~NETIF_F_GRO_HW;
+
+@@ -2486,9 +2572,8 @@ static int airoha_dev_set_features(struc
+ {
+ netdev_features_t diff = netdev->features ^ features;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_qdma *qdma = dev->qdma;
+- struct airoha_eth *eth = qdma->eth;
+- int qdma_id = qdma - ð->qdma[0];
++ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
++ int qdma_id = qdma - &dev->eth->qdma[0];
+
+ if (!(diff & NETIF_F_GRO_HW))
+ return 0;
+@@ -2496,7 +2581,7 @@ static int airoha_dev_set_features(struc
+ if (features & NETIF_F_GRO_HW)
+ airoha_dev_lro_enable(dev);
+ else
+- airoha_fe_lro_disable(eth, qdma_id);
++ airoha_fe_lro_disable(dev->eth, qdma_id);
+
+ return 0;
+ }
+@@ -2505,9 +2590,9 @@ static netdev_tx_t airoha_dev_xmit(struc
+ struct net_device *netdev)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_qdma *qdma = dev->qdma;
+ u32 nr_frags, tag, msg0, msg1, len;
+ struct airoha_queue_entry *e;
++ struct airoha_qdma *qdma;
+ struct netdev_queue *txq;
+ struct airoha_queue *q;
+ LIST_HEAD(tx_list);
+@@ -2516,6 +2601,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+ u16 index;
+ u8 fport;
+
++ rcu_read_lock();
++ qdma = rcu_dereference(dev->qdma);
+ qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
+ tag = airoha_get_dsa_tag(skb, netdev);
+
+@@ -2562,6 +2649,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+ netif_tx_stop_queue(txq);
+ q->txq_stopped = true;
+ spin_unlock_bh(&q->lock);
++ rcu_read_unlock();
++
+ return NETDEV_TX_BUSY;
+ }
+
+@@ -2624,6 +2713,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
+
+ spin_unlock_bh(&q->lock);
++ rcu_read_unlock();
+
+ return NETDEV_TX_OK;
+
+@@ -2639,6 +2729,7 @@ error_unmap:
+ error:
+ dev_kfree_skb_any(skb);
+ netdev->stats.tx_dropped++;
++ rcu_read_unlock();
+
+ return NETDEV_TX_OK;
+ }
+@@ -2720,17 +2811,19 @@ static int airoha_qdma_set_chan_tx_sched
+ const u16 *weights, u8 n_weights)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_qdma *qdma;
+ int i;
+
++ qdma = airoha_qdma_deref(dev);
+ for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+- airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
++ airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel),
+ TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
+
+ for (i = 0; i < n_weights; i++) {
+ u32 status;
+ int err;
+
+- airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
++ airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG,
+ TWRR_RW_CMD_MASK |
+ FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
+ FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
+@@ -2738,12 +2831,12 @@ static int airoha_qdma_set_chan_tx_sched
+ err = read_poll_timeout(airoha_qdma_rr, status,
+ status & TWRR_RW_CMD_DONE,
+ USEC_PER_MSEC, 10 * USEC_PER_MSEC,
+- true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
++ true, qdma, REG_TXWRR_WEIGHT_CFG);
+ if (err)
+ return err;
+ }
+
+- airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
++ airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3),
+ CHAN_QOS_MODE_MASK(channel),
+ __field_prep(CHAN_QOS_MODE_MASK(channel), mode));
+
+@@ -2807,13 +2900,15 @@ static int airoha_qdma_get_tx_ets_stats(
+ struct tc_ets_qopt_offload *opt)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_qdma *qdma = dev->qdma;
++ u64 cpu_tx_packets, fwd_tx_packets, tx_packets;
++ struct airoha_qdma *qdma;
+
+- u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
+- u64 fwd_tx_packets = airoha_qdma_rr(qdma,
+- REG_CNTR_VAL((channel << 1) + 1));
+- u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
+- (fwd_tx_packets - dev->fwd_tx_packets);
++ qdma = airoha_qdma_deref(dev);
++ cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
++ fwd_tx_packets = airoha_qdma_rr(qdma,
++ REG_CNTR_VAL((channel << 1) + 1));
++ tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
++ (fwd_tx_packets - dev->fwd_tx_packets);
+
+ _bstats_update(opt->stats.bstats, 0, tx_packets);
+ dev->cpu_tx_packets = cpu_tx_packets;
+@@ -3073,16 +3168,18 @@ static int airoha_qdma_set_tx_rate_limit
+ u32 bucket_size)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_qdma *qdma;
+ int i, err;
+
++ qdma = airoha_qdma_deref(dev);
+ for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
+- err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
++ err = airoha_qdma_set_trtcm_config(qdma, channel,
+ REG_EGRESS_TRTCM_CFG, i,
+ !!rate, TRTCM_METER_MODE);
+ if (err)
+ return err;
+
+- err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
++ err = airoha_qdma_set_trtcm_token_bucket(qdma, channel,
+ REG_EGRESS_TRTCM_CFG,
+ i, rate, bucket_size);
+ if (err)
+@@ -3118,11 +3215,12 @@ static int airoha_tc_htb_alloc_leaf_queu
+ u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+ int err, num_tx_queues = netdev->real_num_tx_queues;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_qdma *qdma = dev->qdma;
++ struct airoha_qdma *qdma;
+
+ /* Here we need to check the requested QDMA channel is not already
+ * in use by another net_device running on the same QDMA block.
+ */
++ qdma = airoha_qdma_deref(dev);
+ if (test_and_set_bit(channel, qdma->qos_channel_map)) {
+ NL_SET_ERR_MSG_MOD(opt->extack,
+ "qdma qos channel already in use");
+@@ -3156,7 +3254,7 @@ static int airoha_qdma_set_rx_meter(stru
+ u32 rate, u32 bucket_size,
+ enum trtcm_unit_type unit_type)
+ {
+- struct airoha_qdma *qdma = dev->qdma;
++ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+@@ -3330,11 +3428,12 @@ static int airoha_dev_setup_tc_block(str
+ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_qdma *qdma = dev->qdma;
++ struct airoha_qdma *qdma;
+
+ netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
+ airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
+
++ qdma = airoha_qdma_deref(dev);
+ clear_bit(queue, qdma->qos_channel_map);
+ clear_bit(queue, dev->qos_sq_bmap);
+ }
+@@ -3355,6 +3454,95 @@ static int airoha_tc_htb_delete_leaf_que
+ return 0;
+ }
+
++static void airoha_disable_qos_for_gdm34(struct net_device *netdev)
++{
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
++ int err;
++
++ if (port->id != AIROHA_GDM3_IDX &&
++ port->id != AIROHA_GDM4_IDX)
++ return;
++
++ err = airoha_disable_gdm2_loopback(dev);
++ if (err)
++ netdev_warn(netdev,
++ "failed disabling GDM2 loopback: %d\n", err);
++
++ dev->flags &= ~AIROHA_PRIV_F_WAN;
++ airoha_dev_set_qdma(dev);
++
++ airoha_set_macaddr(dev, netdev->dev_addr);
++ if (netif_running(netdev))
++ airoha_set_gdm_port_fwd_cfg(dev->eth,
++ REG_GDM_FWD_CFG(port->id),
++ FE_PSE_PORT_PPE1);
++}
++
++static int airoha_enable_qos_for_gdm34(struct net_device *netdev,
++ struct netlink_ext_ack *extack)
++{
++ 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;
++
++ if (port->id != AIROHA_GDM3_IDX &&
++ port->id != AIROHA_GDM4_IDX) {
++ /* HW QoS is always supported by GDM1 and GDM2 */
++ return 0;
++ }
++
++ if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */
++ return 0;
++
++ mutex_lock(&flow_offload_mutex);
++
++ wan_dev = airoha_get_wan_gdm_dev(eth);
++ if (wan_dev) {
++ if ((wan_dev->flags & AIROHA_PRIV_F_QOS) ||
++ wan_dev->port->id == AIROHA_GDM2_IDX) {
++ NL_SET_ERR_MSG_MOD(extack,
++ "QoS configured for WAN device");
++ goto error_unlock;
++ }
++ airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
++ }
++
++ dev->flags |= AIROHA_PRIV_F_WAN;
++ airoha_dev_set_qdma(dev);
++ err = airoha_enable_gdm2_loopback(dev);
++ if (err)
++ goto error_disable_wan;
++
++ err = airoha_set_macaddr(dev, netdev->dev_addr);
++ if (err)
++ goto error_disable_loopback;
++
++ if (netif_running(netdev)) {
++ u32 pse_port;
++
++ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
++ : FE_PSE_PORT_PPE1;
++ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
++ pse_port);
++ }
++
++ mutex_unlock(&flow_offload_mutex);
++
++ return 0;
++
++error_disable_loopback:
++ airoha_disable_gdm2_loopback(dev);
++error_disable_wan:
++ dev->flags &= ~AIROHA_PRIV_F_WAN;
++ airoha_dev_set_qdma(dev);
++error_unlock:
++ mutex_unlock(&flow_offload_mutex);
++
++ return err;
++}
++
+ static int airoha_tc_htb_destroy(struct net_device *netdev)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+@@ -3363,6 +3551,8 @@ static int airoha_tc_htb_destroy(struct
+ for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+ airoha_tc_remove_htb_queue(netdev, q);
+
++ dev->flags &= ~AIROHA_PRIV_F_QOS;
++
+ return 0;
+ }
+
+@@ -3382,24 +3572,33 @@ static int airoha_tc_get_htb_get_leaf_qu
+ return 0;
+ }
+
+-static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
++static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
+ struct tc_htb_qopt_offload *opt)
+ {
+ switch (opt->command) {
+- case TC_HTB_CREATE:
++ case TC_HTB_CREATE: {
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ int err;
++
++ err = airoha_enable_qos_for_gdm34(netdev, opt->extack);
++ if (err)
++ return err;
++
++ dev->flags |= AIROHA_PRIV_F_QOS;
+ break;
++ }
+ case TC_HTB_DESTROY:
+- return airoha_tc_htb_destroy(dev);
++ return airoha_tc_htb_destroy(netdev);
+ case TC_HTB_NODE_MODIFY:
+- return airoha_tc_htb_modify_queue(dev, opt);
++ return airoha_tc_htb_modify_queue(netdev, opt);
+ case TC_HTB_LEAF_ALLOC_QUEUE:
+- return airoha_tc_htb_alloc_leaf_queue(dev, opt);
++ return airoha_tc_htb_alloc_leaf_queue(netdev, opt);
+ case TC_HTB_LEAF_DEL:
+ case TC_HTB_LEAF_DEL_LAST:
+ case TC_HTB_LEAF_DEL_LAST_FORCE:
+- return airoha_tc_htb_delete_leaf_queue(dev, opt);
++ return airoha_tc_htb_delete_leaf_queue(netdev, opt);
+ case TC_HTB_LEAF_QUERY_QUEUE:
+- return airoha_tc_get_htb_get_leaf_queue(dev, opt);
++ return airoha_tc_get_htb_get_leaf_queue(netdev, opt);
+ default:
+ return -EOPNOTSUPP;
+ }
+@@ -3509,8 +3708,8 @@ static void airoha_mac_link_up(struct ph
+ {
+ struct airoha_gdm_dev *dev = container_of(config, struct airoha_gdm_dev,
+ phylink_config);
++ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ u32 frag_size_tx, frag_size_rx;
+
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -402,6 +402,7 @@
+
+ #define REG_SRC_PORT_FC_MAP6 0x2298
+ #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
++#define FC_MAP6_DEF_VALUE 0x1b1a1918
+
+ #define REG_CDM5_RX_OQ1_DROP_CNT 0x29d4
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -552,11 +552,12 @@ struct airoha_qdma {
+
+ enum airoha_priv_flags {
+ AIROHA_PRIV_F_WAN = BIT(0),
++ AIROHA_PRIV_F_QOS = BIT(1),
+ };
+
+ struct airoha_gdm_dev {
++ struct airoha_qdma __rcu *qdma;
+ struct airoha_gdm_port *port;
+- struct airoha_qdma *qdma;
+ struct airoha_eth *eth;
+
+ #if defined(CONFIG_PCS_AIROHA)
+@@ -705,6 +706,16 @@ int airoha_get_fe_port(struct airoha_gdm
+ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+ struct airoha_gdm_dev *dev);
+
++extern struct mutex flow_offload_mutex;
++
++static inline struct airoha_qdma *
++airoha_qdma_deref(struct airoha_gdm_dev *dev)
++{
++ return rcu_dereference_protected(dev->qdma,
++ lockdep_rtnl_is_held() ||
++ lockdep_is_held(&flow_offload_mutex));
++}
++
+ void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, 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,
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -16,7 +16,10 @@
+ #include "airoha_regs.h"
+ #include "airoha_eth.h"
+
+-static DEFINE_MUTEX(flow_offload_mutex);
++/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port
++ * configuration.
++ */
++DEFINE_MUTEX(flow_offload_mutex);
+ static DEFINE_SPINLOCK(ppe_lock);
+
+ static const struct rhashtable_params airoha_flow_table_params = {
+@@ -87,8 +90,8 @@ static u32 airoha_ppe_get_timestamp(stru
+
+ void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
+ {
+- struct airoha_qdma *qdma = dev->qdma;
+- struct airoha_eth *eth = qdma->eth;
++ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
++ struct airoha_eth *eth = dev->eth;
+ u8 qdma_id = qdma - ð->qdma[0];
+ u32 fe_cpu_port;
+
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -185,10 +185,15 @@ static void airoha_fe_maccr_init(struct
+@@ -188,10 +188,15 @@ static void airoha_fe_maccr_init(struct
{
int p;
airoha_fe_rmw(eth, REG_CDM_VLAN_CTRL(1), CDM_VLAN_MASK,
FIELD_PREP(CDM_VLAN_MASK, 0x8100));
-@@ -1908,13 +1913,24 @@ static void airoha_update_hw_stats(struc
- spin_unlock(&port->stats.lock);
+@@ -2078,15 +2083,25 @@ static void airoha_qdma_stop(struct airo
+ }
}
+static void airoha_dev_set_mtu(struct net_device *netdev)
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
- struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = dev->eth;
+ struct airoha_qdma *qdma;
+- int qdma_id;
+ u32 pse_port = FE_PSE_PORT_PPE1;
-+ int err;
++ int err, qdma_id;
- if (port->id == AIROHA_GDM2_IDX && airoha_is_lan_gdm_dev(dev)) {
- /* GDM2 can be used just as wan */
-@@ -1947,19 +1963,7 @@ static int airoha_dev_open(struct net_de
- airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ /* HW LRO is configured on the QDMA and it is shared between
+ * all the devices using it. Refuse to open a second device on
+@@ -2128,19 +2143,7 @@ static int airoha_dev_open(struct net_de
+ airoha_fe_clear(eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
-- cur_len = airoha_fe_get(qdma->eth, REG_GDM_LEN_CFG(port->id),
+- cur_len = airoha_fe_get(eth, REG_GDM_LEN_CFG(port->id),
- GDM_LONG_LEN_MASK);
- if (!port->users || len > cur_len) {
- /* Opening a sibling net_device with a larger MTU updates the
- * multiple net_devices with different MTUs to share the same
- * GDM port.
- */
-- airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
+- airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
- FIELD_PREP(GDM_LONG_LEN_MASK, len));
+ airoha_dev_set_mtu(netdev);
port->users++;
- airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
-@@ -1976,30 +1980,6 @@ static int airoha_dev_open(struct net_de
+ airoha_qdma_start(qdma);
+@@ -2161,30 +2164,6 @@ static int airoha_dev_open(struct net_de
return 0;
}
- if (!dev)
- continue;
-
-- netdev = dev->dev;
+- netdev = netdev_from_priv(dev);
- if (netif_running(netdev))
- len = max_t(u32, len, netdev->mtu);
- }
static int airoha_dev_stop(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
-@@ -2013,7 +1993,7 @@ static int airoha_dev_stop(struct net_de
- netdev_tx_reset_subqueue(netdev, i);
+@@ -2199,7 +2178,7 @@ static int airoha_dev_stop(struct net_de
+ qdma = airoha_qdma_deref(dev);
if (--port->users)
- airoha_set_port_mtu(dev->eth, port);
+ airoha_ppe_set_mtu(dev);
else
airoha_set_gdm_port_fwd_cfg(qdma->eth,
REG_GDM_FWD_CFG(port->id),
-@@ -2083,10 +2063,6 @@ static int airoha_enable_gdm2_loopback(s
+@@ -2258,10 +2237,6 @@ static int airoha_enable_gdm2_loopback(s
FIELD_PREP(LPBK_CHAN_MASK, chan) |
LBK_GAP_MODE_MASK | LBK_LEN_MODE_MASK |
LBK_CHAN_MODE_MASK | LPBK_EN_MASK);
/* Forward the traffic to the proper GDM port */
pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
: FE_PSE_PORT_GDM4;
-@@ -2270,7 +2246,7 @@ static int airoha_dev_change_mtu(struct
+@@ -2469,7 +2444,7 @@ static int airoha_dev_change_mtu(struct
WRITE_ONCE(netdev->mtu, mtu);
if (port->users)
return 0;
}
-@@ -2613,6 +2589,7 @@ static int airoha_dev_set_wan_flag(struc
- default:
- return -EOPNOTSUPP;
- }
-+ airoha_dev_set_mtu(netdev);
- } else {
- switch (port->id) {
- case AIROHA_GDM3_IDX:
+@@ -3471,6 +3446,7 @@ static void airoha_disable_qos_for_gdm34
+
+ dev->flags &= ~AIROHA_PRIV_F_WAN;
+ airoha_dev_set_qdma(dev);
++ airoha_dev_set_mtu(netdev);
+
+ airoha_set_macaddr(dev, netdev->dev_addr);
+ if (netif_running(netdev))
+@@ -3519,6 +3495,7 @@ static int airoha_enable_qos_for_gdm34(s
+ if (err)
+ goto error_disable_loopback;
+
++ airoha_dev_set_mtu(netdev);
+ if (netif_running(netdev)) {
+ u32 pse_port;
+
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -23,6 +23,7 @@
#define AIROHA_MAX_PACKET_SIZE 2048
#define AIROHA_NUM_QOS_CHANNELS 4
#define AIROHA_NUM_QOS_QUEUES 8
-@@ -706,6 +707,7 @@ int airoha_get_fe_port(struct airoha_gdm
- bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
- struct airoha_gdm_dev *dev);
+@@ -716,6 +717,7 @@ airoha_qdma_deref(struct airoha_gdm_dev
+ lockdep_is_held(&flow_offload_mutex));
+ }
+void airoha_ppe_set_mtu(struct airoha_gdm_dev *dev);
void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
-@@ -98,6 +98,35 @@ void airoha_ppe_set_cpu_port(struct airo
+@@ -101,6 +101,39 @@ void airoha_ppe_set_cpu_port(struct airo
__field_prep(DFT_CPORT_MASK(fport), fe_cpu_port));
}
+{
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
++ struct airoha_qdma *qdma;
+ int i, ppe_id, index;
+ u32 mtu = 0;
+
++ qdma = airoha_qdma_deref(dev);
+ for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
+ struct airoha_gdm_dev *d = port->devs[i];
+ struct net_device *netdev;
++ struct airoha_qdma *q;
+
+ if (!d)
+ continue;
+
-+ if (d->qdma != dev->qdma)
++ q = airoha_qdma_deref(d);
++ if (q != qdma)
+ continue;
+
-+ netdev = d->dev;
++ netdev = netdev_from_priv(d);
+ if (netif_running(netdev))
+ mtu = max_t(u32, mtu, netdev->mtu);
+ }
static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
{
u32 sram_ppe_num_data_entries = PPE_SRAM_NUM_ENTRIES, sram_num_entries;
-@@ -116,8 +145,6 @@ static void airoha_ppe_hw_init(struct ai
+@@ -119,8 +152,6 @@ static void airoha_ppe_hw_init(struct ai
PPE_RAM_NUM_ENTRIES_SHIFT(sram_ppe_num_data_entries);
for (i = 0; i < eth->soc->num_ppe; i++) {
airoha_fe_wr(eth, REG_PPE_TB_BASE(i),
ppe->foe_dma + sram_tb_size);
-@@ -167,15 +194,6 @@ static void airoha_ppe_hw_init(struct ai
+@@ -170,15 +201,6 @@ static void airoha_ppe_hw_init(struct ai
airoha_fe_wr(eth, REG_PPE_HASH_SEED(i), PPE_HASH_SEED);
airoha_fe_clear(eth, REG_PPE_PPE_FLOW_CFG(i),
PPE_FLOW_CFG_IP6_6RD_MASK);
}
for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
-@@ -197,6 +215,7 @@ static void airoha_ppe_hw_init(struct ai
+@@ -200,6 +222,7 @@ static void airoha_ppe_hw_init(struct ai
airoha_ppe_is_enabled(eth, 1);
fport = airoha_get_fe_port(dev);
airoha_ppe_set_cpu_port(dev, ppe_id, fport);
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -612,6 +612,14 @@ static int airoha_fe_init(struct airoha_
+@@ -640,6 +640,14 @@ static int airoha_fe_init(struct airoha_
airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM4_IDX),
GDM_PAD_EN_MASK | GDM_STRIP_CRC_MASK);
airoha_fe_crsn_qsel_init(eth);
airoha_fe_clear(eth, REG_FE_CPORT_CFG, FE_CPORT_QUEUE_XFC_MASK);
-@@ -1768,149 +1776,169 @@ static void airoha_qdma_stop_napi(struct
+@@ -1872,149 +1880,169 @@ static void airoha_qdma_stop_napi(struct
}
}
+ spin_unlock(&port->stats_lock);
}
- static void airoha_dev_set_mtu(struct net_device *netdev)
-@@ -2220,23 +2248,22 @@ static void airoha_dev_get_stats64(struc
+ static void airoha_update_netdev_features(struct airoha_gdm_dev *dev)
+@@ -2418,23 +2446,22 @@ static void airoha_dev_get_stats64(struc
struct rtnl_link_stats64 *storage)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
}
static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
-@@ -2632,20 +2659,19 @@ static void airoha_ethtool_get_mac_stats
+@@ -2723,20 +2750,19 @@ static void airoha_ethtool_get_mac_stats
struct ethtool_eth_mac_stats *stats)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
}
static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
-@@ -2665,8 +2691,7 @@ airoha_ethtool_get_rmon_stats(struct net
+@@ -2756,8 +2782,7 @@ airoha_ethtool_get_rmon_stats(struct net
const struct ethtool_rmon_hist_range **ranges)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
unsigned int start;
BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
-@@ -2679,7 +2704,7 @@ airoha_ethtool_get_rmon_stats(struct net
+@@ -2770,7 +2795,7 @@ airoha_ethtool_get_rmon_stats(struct net
do {
int i;
stats->fragments = hw_stats->rx_fragment;
stats->jabbers = hw_stats->rx_jabber;
for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
-@@ -2687,7 +2712,7 @@ airoha_ethtool_get_rmon_stats(struct net
+@@ -2778,7 +2803,7 @@ airoha_ethtool_get_rmon_stats(struct net
stats->hist[i] = hw_stats->rx_len[i];
stats->hist_tx[i] = hw_stats->tx_len[i];
}
+ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
}
- static int airoha_ethtool_set_priv_flags(struct net_device *netdev, u32 flags)
-@@ -3695,6 +3720,7 @@ static int airoha_alloc_gdm_device(struc
+ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
+@@ -3849,6 +3874,7 @@ static int airoha_alloc_gdm_device(struc
netdev->dev.of_node = of_node_get(np);
dev = netdev_priv(netdev);
+ u64_stats_init(&dev->stats.syncp);
- dev->dev = netdev;
dev->port = port;
dev->eth = eth;
-@@ -3743,9 +3769,8 @@ static int airoha_alloc_gdm_port(struct
+ dev->nbq = nbq;
+@@ -3896,9 +3922,8 @@ static int airoha_alloc_gdm_port(struct
if (!port)
return -ENOMEM;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -2345,17 +2345,9 @@ static u32 airoha_get_dsa_tag(struct sk_
+@@ -2543,17 +2543,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;
+ : port->id;
}
- static int airoha_dev_set_features(struct net_device *netdev,
+ static netdev_features_t airoha_dev_fix_features(struct net_device *netdev,
--- /dev/null
+From 18aa76cd820c412358dc36d115b8e02d24a78f39 Mon Sep 17 00:00:00 2001
+Message-ID: <18aa76cd820c412358dc36d115b8e02d24a78f39.1780942778.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 8 Jun 2026 20:10:35 +0200
+Subject: [PATCH] net: airoha: move get_sport() callback at the beginning of
+ airoha_enable_gdm2_loopback()
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ 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
+@@ -2250,6 +2250,10 @@ static int airoha_enable_gdm2_loopback(s
+ u32 val, pse_port, chan;
+ int i, src_port;
+
++ src_port = eth->soc->ops.get_sport(port, dev->nbq);
++ if (src_port < 0)
++ return src_port;
++
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ FE_PSE_PORT_DROP);
+ airoha_fe_clear(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+@@ -2275,10 +2279,6 @@ static int airoha_enable_gdm2_loopback(s
+ airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
+ airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
+
+- src_port = eth->soc->ops.get_sport(port, dev->nbq);
+- if (src_port < 0)
+- return src_port;
+-
+ airoha_fe_rmw(eth, REG_FE_WAN_PORT,
+ WAN1_EN_MASK | WAN1_MASK | WAN0_MASK,
+ FIELD_PREP(WAN0_MASK, src_port));
--- /dev/null
+From 3d67e4ba79bd4b40f520d26af098f133b5f551d4 Mon Sep 17 00:00:00 2001
+Message-ID: <3d67e4ba79bd4b40f520d26af098f133b5f551d4.1781097105.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 6 Jun 2026 08:53:03 +0200
+Subject: [PATCH] net: airoha: simplify WAN device check in airoha_dev_init()
+
+airoha_register_gdm_devices() iterates eth->ports[] in order, so GDM2's
+netdev is always registered before GDM3/GDM4. This means the explicit
+check for eth->ports[1] && eth->ports[1]->devs[0] is a redundant
+special-case of what airoha_get_wan_gdm_dev() already covers, since
+GDM2 is always marked as WAN during its own ndo_init.
+Remove the redundant check and rely solely on airoha_get_wan_gdm_dev()
+which handles both the GDM2-present and GDM2-absent cases.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 12 ++----------
+ 1 file changed, 2 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2407,18 +2407,10 @@ static int airoha_dev_init(struct net_de
+
+ switch (port->id) {
+ case AIROHA_GDM3_IDX:
+- case AIROHA_GDM4_IDX: {
+- struct airoha_eth *eth = dev->eth;
+-
+- /* GDM2 supports a single net_device */
+- if (eth->ports[1] && eth->ports[1]->devs[0])
+- break;
+-
+- if (airoha_get_wan_gdm_dev(eth))
++ case AIROHA_GDM4_IDX:
++ if (airoha_get_wan_gdm_dev(dev->eth))
+ break;
+-
+ fallthrough;
+- }
+ case AIROHA_GDM2_IDX:
+ /* GDM2 is always used as wan */
+ dev->flags |= AIROHA_PRIV_F_WAN;