]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
airoha: Introduce support multi-serdes on the same GDM port 23481/head
authorLorenzo Bianconi <lorenzo@kernel.org>
Fri, 22 May 2026 15:04:53 +0000 (17:04 +0200)
committerChristian Marangi <ansuelsmth@gmail.com>
Mon, 25 May 2026 10:41:35 +0000 (12:41 +0200)
EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
manages the traffic in a TDM manner. As a result multiple net_devices can
connect to the same GDM{3,4} port and there is a theoretical "1:n"
relation between GDM ports and net_devices.

           ┌─────────────────────────────────┐
           │                                 │    ┌──────┐
           │                         P1 GDM1 ├────►MT7530│
           │                                 │    └──────┘
           │                                 │      ETH0 (DSA conduit)
           │                                 │
           │              PSE/FE             │
           │                                 │
           │                                 │
           │                                 │    ┌─────┐
           │                         P0 CDM1 ├────►QDMA0│
           │  P4                     P9 GDM4 │    └─────┘
           └──┬─────────────────────────┬────┘
              │                         │
           ┌──▼──┐                 ┌────▼────┐
           │ PPE │                 │   ARB   │
           └─────┘                 └─┬─────┬─┘
                                     │     │
                                  ┌──▼──┐┌─▼───┐
                                  │ ETH ││ USB │
                                  └─────┘└─────┘
                                   ETH1   ETH2

This series introduces support for multiple net_devices connected to the
same Frame Engine (FE) GDM port (GDM3 or GDM4) via an external hw
arbiter. Please note GDM1 or GDM2 does not support the connection with
the external arbiter.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Link: https://github.com/openwrt/openwrt/pull/23481
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
14 files changed:
target/linux/airoha/patches-6.12/920-01-net-airoha-Introduce-airoha_gdm_dev-struct.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-02-net-airoha-Move-airoha_qdma-pointer-in-airoha_gdm_de.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-03-net-airoha-Rely-on-airoha_gdm_dev-pointer-in-airoha_.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-04-net-airoha-Move-qos_sq_bmap-in-airoha_gdm_dev-struct.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-05-net-airoha-Move-cpu-fwd-_tx_packets-in-airoha_gdm_de.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-06-net-airoha-Support-multiple-net_devices-for-a-single.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-07-net-airoha-Do-not-stop-GDM-port-if-it-is-shared.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-08-net-airoha-Introduce-WAN-device-flag.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-09-net-airoha-Support-multiple-LAN-WAN-interfaces-for-h.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-10-net-airoha-Rename-airoha_set_gdm2_loopback-in-airoha.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-12-net-airoha-Add-ethtool-priv_flags-callbacks.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-13-net-airoha-Rework-MTU-configuration.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-14-net-airoha-Better-handle-MIB-for-GDM-with-multiple-p.patch [new file with mode: 0644]
target/linux/airoha/patches-6.12/920-15-net-airoha-fix-wrong-airoha_get_fe_port.patch [new file with mode: 0644]

diff --git a/target/linux/airoha/patches-6.12/920-01-net-airoha-Introduce-airoha_gdm_dev-struct.patch b/target/linux/airoha/patches-6.12/920-01-net-airoha-Introduce-airoha_gdm_dev-struct.patch
new file mode 100644 (file)
index 0000000..055dfbc
--- /dev/null
@@ -0,0 +1,1012 @@
+From e15783f7c987e199ecf80b3d858ed5a86d33c508 Mon Sep 17 00:00:00 2001
+Message-ID: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 1 Nov 2025 11:25:52 +0100
+Subject: [PATCH 01/13] net: airoha: Introduce airoha_gdm_dev struct
+
+EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
+or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
+As a result multiple net_devices can connect to the same GDM{3,4} port
+and there is a theoretical "1:n" relation between GDM port and
+net_devices.
+Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
+net_device and external phy pointer). Please note this is just a
+preliminary patch and we are still supporting a single net_device for
+each GDM port. Subsequent patches will add support for multiple net_devices
+connected to the same GDM port.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 312 ++++++++++++++---------
+ drivers/net/ethernet/airoha/airoha_eth.h |  13 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c |  17 +-
+ 3 files changed, 206 insertions(+), 136 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -727,6 +727,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;
++              struct net_device *netdev;
+               int data_len, len, p;
+               struct page *page;
+@@ -753,6 +754,10 @@ static int airoha_qdma_rx_process(struct
+                       goto free_frag;
+               port = eth->ports[p];
++              if (!port->dev)
++                      goto free_frag;
++
++              netdev = port->dev->dev;
+               if (!q->skb) { /* first buffer */
+                       q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
+                                               q->buf_size);
+@@ -762,15 +767,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->dev = netdev;
+                       q->skb->ip_summed = CHECKSUM_UNNECESSARY;
+                       skb_record_rx_queue(q->skb, qid);
+-                      if (lro_q && (port->dev->features & NETIF_F_LRO) &&
++                      if (lro_q && (netdev->features & NETIF_F_LRO) &&
+                           airoha_qdma_lro_rx_process(q, desc) < 0)
+                               goto free_frag;
+-                      q->skb->protocol = eth_type_trans(q->skb, port->dev);
++                      q->skb->protocol = eth_type_trans(q->skb, netdev);
+               } else { /* scattered frame */
+                       struct skb_shared_info *shinfo = skb_shinfo(q->skb);
+                       int nr_frags = shinfo->nr_frags;
+@@ -786,7 +791,7 @@ static int airoha_qdma_rx_process(struct
+               if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
+                       continue;
+-              if (netdev_uses_dsa(port->dev)) {
++              if (netdev_uses_dsa(netdev)) {
+                       /* 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
+@@ -983,6 +988,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;
+               int j;
+               if (!port)
+@@ -991,11 +997,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++) {
+                       if (airoha_qdma_get_txq(qdma, j) != qid)
+                               continue;
+-                      netif_wake_subqueue(port->dev, j);
++                      netif_wake_subqueue(dev->dev, j);
+               }
+       }
+       q->txq_stopped = false;
+@@ -1839,33 +1846,34 @@ static void airoha_update_hw_stats(struc
+       spin_unlock(&port->stats.lock);
+ }
+-static int airoha_dev_open(struct net_device *dev)
++static int airoha_dev_open(struct net_device *netdev)
+ {
+-      int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      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;
+       u32 pse_port = FE_PSE_PORT_PPE1;
+ #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);
+               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);
+                       return err;
+               }
+-              phylink_start(port->phylink);
++              phylink_start(dev->phylink);
+       }
+ #endif
+-      netif_tx_start_all_queues(dev);
++      netif_tx_start_all_queues(netdev);
+       err = airoha_set_vip_for_gdm_port(port, true);
+       if (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),
+                             GDM_STAG_EN_MASK);
+       else
+@@ -1893,16 +1901,17 @@ static int airoha_dev_open(struct net_de
+       return 0;
+ }
+-static int airoha_dev_stop(struct net_device *dev)
++static int airoha_dev_stop(struct net_device *netdev)
+ {
+-      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;
+       int i;
+-      netif_tx_disable(dev);
++      netif_tx_disable(netdev);
+       airoha_set_vip_for_gdm_port(port, false);
+-      for (i = 0; i < dev->num_tx_queues; i++)
+-              netdev_tx_reset_subqueue(dev, i);
++      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),
+                                   FE_PSE_PORT_DROP);
+@@ -1922,24 +1931,25 @@ static int airoha_dev_stop(struct net_de
+ #if defined(CONFIG_PCS_AIROHA)
+       if (airhoa_is_phy_external(port)) {
+-              phylink_stop(port->phylink);
+-              phylink_disconnect_phy(port->phylink);
++              phylink_stop(dev->phylink);
++              phylink_disconnect_phy(dev->phylink);
+       }
+ #endif
+       return 0;
+ }
+-static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
++static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       int err;
+-      err = eth_mac_addr(dev, p);
++      err = eth_mac_addr(netdev, p);
+       if (err)
+               return err;
+-      airoha_set_macaddr(port, dev->dev_addr);
++      airoha_set_macaddr(port, netdev->dev_addr);
+       return 0;
+ }
+@@ -2005,16 +2015,17 @@ static int airoha_set_gdm2_loopback(stru
+       return 0;
+ }
+-static int airoha_dev_init(struct net_device *dev)
++static int airoha_dev_init(struct net_device *netdev)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
+-      struct airoha_eth *eth = port->eth;
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
++      struct airoha_eth *eth = dev->eth;
+       int i;
+       /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+       port->qdma = &eth->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;
++      airoha_set_macaddr(port, netdev->dev_addr);
+       switch (port->id) {
+       case AIROHA_GDM3_IDX:
+@@ -2039,10 +2050,11 @@ static int airoha_dev_init(struct net_de
+       return 0;
+ }
+-static void airoha_dev_get_stats64(struct net_device *dev,
++static void airoha_dev_get_stats64(struct net_device *netdev,
+                                  struct rtnl_link_stats64 *storage)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       unsigned int start;
+       airoha_update_hw_stats(port);
+@@ -2061,36 +2073,39 @@ static void airoha_dev_get_stats64(struc
+       } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ }
+-static int airoha_dev_change_mtu(struct net_device *dev, int mtu)
++static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
+ {
+-      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_eth *eth = port->qdma->eth;
+       u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
+       airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+                     GDM_LONG_LEN_MASK,
+                     FIELD_PREP(GDM_LONG_LEN_MASK, len));
+-      WRITE_ONCE(dev->mtu, mtu);
++      WRITE_ONCE(netdev->mtu, mtu);
+       return 0;
+ }
+-static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
++static u16 airoha_dev_select_queue(struct net_device *netdev,
++                                 struct sk_buff *skb,
+                                  struct net_device *sb_dev)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       int queue, channel;
+       /* For dsa device select QoS channel according to the dsa user port
+        * index, rely on port id otherwise. Select QoS queue based on the
+        * skb priority.
+        */
+-      channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
++      channel = netdev_uses_dsa(netdev) ? skb_get_queue_mapping(skb) : port->id;
+       channel = channel % AIROHA_NUM_QOS_CHANNELS;
+       queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
+       queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
+-      return queue < dev->num_tx_queues ? queue : 0;
++      return queue < netdev->num_tx_queues ? queue : 0;
+ }
+ static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
+@@ -2153,12 +2168,13 @@ int airoha_get_fe_port(struct airoha_gdm
+       }
+ }
+-static int airoha_dev_set_features(struct net_device *dev,
++static int airoha_dev_set_features(struct net_device *netdev,
+                                  netdev_features_t features)
+ {
+-      netdev_features_t diff = dev->features ^ features;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      netdev_features_t diff = netdev->features ^ features;
++      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 qdma_id = qdma - &eth->qdma[0];
+@@ -2192,6 +2208,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;
+@@ -2199,10 +2216,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;
+               }
+               airoha_fe_lro_disable(eth, qdma_id);
+@@ -2212,9 +2230,10 @@ static int airoha_dev_set_features(struc
+ }
+ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+-                                 struct net_device *dev)
++                                 struct net_device *netdev)
+ {
+-      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;
+       u32 nr_frags, tag, msg0, msg1, len;
+       struct airoha_queue_entry *e;
+@@ -2227,7 +2246,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+       u8 fport;
+       qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
+-      tag = airoha_get_dsa_tag(skb, dev);
++      tag = airoha_get_dsa_tag(skb, netdev);
+       msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+                         qid / AIROHA_NUM_QOS_QUEUES) |
+@@ -2263,7 +2282,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+       spin_lock_bh(&q->lock);
+-      txq = skb_get_tx_queue(dev, skb);
++      txq = skb_get_tx_queue(netdev, skb);
+       nr_frags = 1 + skb_shinfo(skb)->nr_frags;
+       if (q->queued + nr_frags >= q->ndesc) {
+@@ -2287,9 +2306,9 @@ static netdev_tx_t airoha_dev_xmit(struc
+               dma_addr_t addr;
+               u32 val;
+-              addr = dma_map_single(dev->dev.parent, data, len,
++              addr = dma_map_single(netdev->dev.parent, data, len,
+                                     DMA_TO_DEVICE);
+-              if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
++              if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
+                       goto error_unmap;
+               list_move_tail(&e->list, &tx_list);
+@@ -2338,7 +2357,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ error_unmap:
+       list_for_each_entry(e, &tx_list, list) {
+-              dma_unmap_single(dev->dev.parent, e->dma_addr, e->dma_len,
++              dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
+                                DMA_TO_DEVICE);
+               e->dma_addr = 0;
+       }
+@@ -2347,25 +2366,27 @@ error_unmap:
+       spin_unlock_bh(&q->lock);
+ error:
+       dev_kfree_skb_any(skb);
+-      dev->stats.tx_dropped++;
++      netdev->stats.tx_dropped++;
+       return NETDEV_TX_OK;
+ }
+-static void airoha_ethtool_get_drvinfo(struct net_device *dev,
++static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
+                                      struct ethtool_drvinfo *info)
+ {
+-      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_eth *eth = port->qdma->eth;
+       strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
+       strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
+ }
+-static void airoha_ethtool_get_mac_stats(struct net_device *dev,
++static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
+                                        struct ethtool_eth_mac_stats *stats)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       unsigned int start;
+       airoha_update_hw_stats(port);
+@@ -2393,11 +2414,12 @@ static const struct ethtool_rmon_hist_ra
+ };
+ static void
+-airoha_ethtool_get_rmon_stats(struct net_device *dev,
++airoha_ethtool_get_rmon_stats(struct net_device *netdev,
+                             struct ethtool_rmon_stats *stats,
+                             const struct ethtool_rmon_hist_range **ranges)
+ {
+-      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_hw_stats *hw_stats = &port->stats;
+       unsigned int start;
+@@ -2422,11 +2444,12 @@ airoha_ethtool_get_rmon_stats(struct net
+       } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ }
+-static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
++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)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       int i;
+       for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+@@ -2511,10 +2534,12 @@ static int airoha_qdma_set_tx_ets_sched(
+                                            ARRAY_SIZE(w));
+ }
+-static int airoha_qdma_get_tx_ets_stats(struct net_device *dev, int channel,
++static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
+                                       struct tc_ets_qopt_offload *opt)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
++
+       u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
+                                           REG_CNTR_VAL(channel << 1));
+       u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
+@@ -2776,11 +2801,12 @@ static int airoha_qdma_set_trtcm_token_b
+                                          mode, val);
+ }
+-static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
++static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
+                                        int channel, u32 rate,
+                                        u32 bucket_size)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       int i, err;
+       for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
+@@ -2800,20 +2826,22 @@ static int airoha_qdma_set_tx_rate_limit
+       return 0;
+ }
+-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
++static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
+                                         struct tc_htb_qopt_offload *opt)
+ {
+       u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+       u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
+-      int err, num_tx_queues = dev->real_num_tx_queues;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      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;
+       if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
+               NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
+               return -EINVAL;
+       }
+-      err = airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
++      err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
++                                          opt->quantum);
+       if (err) {
+               NL_SET_ERR_MSG_MOD(opt->extack,
+                                  "failed configuring htb offload");
+@@ -2823,9 +2851,10 @@ static int airoha_tc_htb_alloc_leaf_queu
+       if (opt->command == TC_HTB_NODE_MODIFY)
+               return 0;
+-      err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
++      err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
+       if (err) {
+-              airoha_qdma_set_tx_rate_limit(dev, channel, 0, opt->quantum);
++              airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
++                                            opt->quantum);
+               NL_SET_ERR_MSG_MOD(opt->extack,
+                                  "failed setting real_num_tx_queues");
+               return err;
+@@ -2915,11 +2944,12 @@ static int airoha_tc_matchall_act_valida
+       return 0;
+ }
+-static int airoha_dev_tc_matchall(struct net_device *dev,
++static int airoha_dev_tc_matchall(struct net_device *netdev,
+                                 struct tc_cls_matchall_offload *f)
+ {
+       enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       u32 rate = 0, bucket_size = 0;
+       switch (f->command) {
+@@ -2954,18 +2984,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)
+ {
+-      struct net_device *dev = cb_priv;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct net_device *netdev = cb_priv;
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       struct airoha_eth *eth = port->qdma->eth;
+-      if (!tc_can_offload(dev))
++      if (!tc_can_offload(netdev))
+               return -EOPNOTSUPP;
+       switch (type) {
+       case TC_SETUP_CLSFLOWER:
+               return airoha_ppe_setup_tc_block_cb(&eth->ppe->dev, type_data);
+       case TC_SETUP_CLSMATCHALL:
+-              return airoha_dev_tc_matchall(dev, type_data);
++              return airoha_dev_tc_matchall(netdev, type_data);
+       default:
+               return -EOPNOTSUPP;
+       }
+@@ -3012,47 +3043,51 @@ static int airoha_dev_setup_tc_block(str
+       }
+ }
+-static void airoha_tc_remove_htb_queue(struct net_device *dev, int queue)
++static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+-      netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
+-      airoha_qdma_set_tx_rate_limit(dev, queue + 1, 0, 0);
++      netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
++      airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
+       clear_bit(queue, port->qos_sq_bmap);
+ }
+-static int airoha_tc_htb_delete_leaf_queue(struct net_device *dev,
++static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
+                                          struct tc_htb_qopt_offload *opt)
+ {
+       u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       if (!test_bit(channel, port->qos_sq_bmap)) {
+               NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+               return -EINVAL;
+       }
+-      airoha_tc_remove_htb_queue(dev, channel);
++      airoha_tc_remove_htb_queue(netdev, channel);
+       return 0;
+ }
+-static int airoha_tc_htb_destroy(struct net_device *dev)
++static int airoha_tc_htb_destroy(struct net_device *netdev)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       int q;
+       for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+-              airoha_tc_remove_htb_queue(dev, q);
++              airoha_tc_remove_htb_queue(netdev, q);
+       return 0;
+ }
+-static int airoha_tc_get_htb_get_leaf_queue(struct net_device *dev,
++static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
+                                           struct tc_htb_qopt_offload *opt)
+ {
+       u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+-      struct airoha_gdm_port *port = netdev_priv(dev);
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       if (!test_bit(channel, port->qos_sq_bmap)) {
+               NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+@@ -3088,8 +3123,8 @@ static int airoha_tc_setup_qdisc_htb(str
+       return 0;
+ }
+-static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
+-                             void *type_data)
++static int airoha_dev_tc_setup(struct net_device *dev,
++                             enum tc_setup_type type, void *type_data)
+ {
+       switch (type) {
+       case TC_SETUP_QDISC_ETS:
+@@ -3161,13 +3196,18 @@ static void airoha_metadata_dst_free(str
+       }
+ }
+-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+-                            struct airoha_gdm_port *port)
++bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
++                           struct airoha_gdm_dev *dev)
+ {
+       int i;
+       for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+-              if (eth->ports[i] == port)
++              struct airoha_gdm_port *port = eth->ports[i];
++
++              if (!port)
++                      continue;
++
++              if (port->dev == dev)
+                       return true;
+       }
+@@ -3179,8 +3219,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_gdm_port *port = container_of(config, struct airoha_gdm_port,
+-                                                  phylink_config);
++      struct airoha_gdm_dev *dev = container_of(config, struct airoha_gdm_dev,
++                                                phylink_config);
++      struct airoha_gdm_port *port = dev->port;
+       struct airoha_qdma *qdma = port->qdma;
+       struct airoha_eth *eth = qdma->eth;
+       u32 frag_size_tx, frag_size_rx;
+@@ -3236,65 +3277,122 @@ static int airoha_fill_available_pcs(str
+                                       &num_available_pcs);
+ }
+-static int airoha_setup_phylink(struct net_device *dev)
++static int airoha_setup_phylink(struct net_device *netdev)
+ {
+-      struct airoha_gdm_port *port = netdev_priv(dev);
+-      struct device_node *np = dev->dev.of_node;
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct device_node *np = netdev->dev.of_node;
+       phy_interface_t phy_mode;
+       struct phylink *phylink;
+       int err;
+       err = of_get_phy_mode(np, &phy_mode);
+       if (err) {
+-              dev_err(&dev->dev, "incorrect phy-mode\n");
++              dev_err(&netdev->dev, "incorrect phy-mode\n");
+               return err;
+       }
+-      port->phylink_config.dev = &dev->dev;
+-      port->phylink_config.type = PHYLINK_NETDEV;
+-      port->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+-                                              MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD |
+-                                              MAC_5000FD | MAC_10000FD;
++      dev->phylink_config.dev = &netdev->dev;
++      dev->phylink_config.type = PHYLINK_NETDEV;
++      dev->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++                                             MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD |
++                                             MAC_5000FD | MAC_10000FD;
+-      err = fwnode_phylink_pcs_parse(dev_fwnode(&dev->dev), NULL,
+-                                     &port->phylink_config.num_available_pcs);
++      err = fwnode_phylink_pcs_parse(dev_fwnode(&netdev->dev), NULL,
++                                     &dev->phylink_config.num_available_pcs);
+       if (err)
+               return err;
+-      port->phylink_config.fill_available_pcs = airoha_fill_available_pcs;
++      dev->phylink_config.fill_available_pcs = airoha_fill_available_pcs;
+       __set_bit(PHY_INTERFACE_MODE_SGMII,
+-                port->phylink_config.supported_interfaces);
++                dev->phylink_config.supported_interfaces);
+       __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+-                port->phylink_config.supported_interfaces);
++                dev->phylink_config.supported_interfaces);
+       __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+-                port->phylink_config.supported_interfaces);
++                dev->phylink_config.supported_interfaces);
+       __set_bit(PHY_INTERFACE_MODE_10GBASER,
+-                port->phylink_config.supported_interfaces);
++                dev->phylink_config.supported_interfaces);
+       __set_bit(PHY_INTERFACE_MODE_USXGMII,
+-                port->phylink_config.supported_interfaces);
++                dev->phylink_config.supported_interfaces);
+-      phy_interface_copy(port->phylink_config.pcs_interfaces,
+-                         port->phylink_config.supported_interfaces);
++      phy_interface_copy(dev->phylink_config.pcs_interfaces,
++                         dev->phylink_config.supported_interfaces);
+-      phylink = phylink_create(&port->phylink_config,
++      phylink = phylink_create(&dev->phylink_config,
+                                of_fwnode_handle(np),
+                                phy_mode, &airoha_phylink_ops);
+       if (IS_ERR(phylink))
+               return PTR_ERR(phylink);
+-      port->phylink = phylink;
++      dev->phylink = phylink;
+       return err;
+ }
+ #endif
++static int airoha_alloc_gdm_device(struct airoha_eth *eth,
++                                 struct airoha_gdm_port *port,
++                                 struct device_node *np)
++{
++      struct airoha_gdm_dev *dev;
++      struct net_device *netdev;
++      int err;
++
++      netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
++                                       AIROHA_NUM_NETDEV_TX_RINGS,
++                                       AIROHA_NUM_RX_RING);
++      if (!netdev) {
++              dev_err(eth->dev, "alloc_etherdev failed\n");
++              return -ENOMEM;
++      }
++
++      netdev->netdev_ops = &airoha_netdev_ops;
++      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->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 */
++      err = netif_set_real_num_tx_queues(netdev, AIROHA_NUM_TX_RING);
++      if (err)
++              return err;
++
++      err = of_get_ethdev_address(np, netdev);
++      if (err) {
++              if (err == -EPROBE_DEFER)
++                      return err;
++
++              eth_hw_addr_random(netdev);
++              dev_info(eth->dev, "generated random MAC address %pM\n",
++                       netdev->dev_addr);
++      }
++
++      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)) {
++              err = airoha_setup_phylink(netdev);
++              if (err)
++                      return err;
++      }
++#endif
++
++      return 0;
++}
++
+ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
+                                struct device_node *np)
+ {
+       const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+       struct airoha_gdm_port *port;
+-      struct net_device *dev;
+       int err, p;
+       u32 id;
+@@ -3316,58 +3414,22 @@ static int airoha_alloc_gdm_port(struct
+               return -EINVAL;
+       }
+-      dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
+-                                    AIROHA_NUM_NETDEV_TX_RINGS,
+-                                    AIROHA_NUM_RX_RING);
+-      if (!dev) {
+-              dev_err(eth->dev, "alloc_etherdev failed\n");
++      port = devm_kzalloc(eth->dev, sizeof(*port), GFP_KERNEL);
++      if (!port)
+               return -ENOMEM;
+-      }
+-
+-      dev->netdev_ops = &airoha_netdev_ops;
+-      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->features |= AIROHA_HW_FEATURES;
+-      dev->vlan_features = AIROHA_HW_FEATURES;
+-      dev->dev.of_node = np;
+-      SET_NETDEV_DEV(dev, eth->dev);
+-
+-      /* reserve hw queues for HTB offloading */
+-      err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
+-      if (err)
+-              return err;
+-      err = of_get_ethdev_address(np, dev);
+-      if (err) {
+-              if (err == -EPROBE_DEFER)
+-                      return err;
+-
+-              eth_hw_addr_random(dev);
+-              dev_info(eth->dev, "generated random MAC address %pM\n",
+-                       dev->dev_addr);
+-      }
+-
+-      port = netdev_priv(dev);
+       u64_stats_init(&port->stats.syncp);
+       spin_lock_init(&port->stats.lock);
+-      port->eth = eth;
+-      port->dev = dev;
+       port->id = id;
+       /* XXX: Read nbq from DTS */
+       port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
+       eth->ports[p] = port;
+-#if defined(CONFIG_PCS_AIROHA)
+-      if (airhoa_is_phy_external(port)) {
+-              err = airoha_setup_phylink(dev);
+-              if (err)
+-                      return err;
+-      }
+-#endif
++      err = airoha_metadata_dst_alloc(port);
++      if (err)
++              return err;
+-      return airoha_metadata_dst_alloc(port);
++      return airoha_alloc_gdm_device(eth, port, np);
+ }
+ static int airoha_register_gdm_devices(struct airoha_eth *eth)
+@@ -3381,7 +3443,7 @@ static int airoha_register_gdm_devices(s
+               if (!port)
+                       continue;
+-              err = register_netdev(port->dev);
++              err = register_netdev(port->dev->dev);
+               if (err)
+                       return err;
+       }
+@@ -3490,16 +3552,18 @@ error_napi_stop:
+       for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+               struct airoha_gdm_port *port = eth->ports[i];
++              struct airoha_gdm_dev *dev;
+               if (!port)
+                       continue;
+-              if (port->dev->reg_state == NETREG_REGISTERED) {
++              dev = port->dev;
++              if (dev && dev->dev->reg_state == NETREG_REGISTERED) {
+ #if defined(CONFIG_PCS_AIROHA)
+                       if (airhoa_is_phy_external(port))
+-                              phylink_destroy(port->phylink);
++                              phylink_destroy(dev->phylink);
+ #endif
+-                      unregister_netdev(port->dev);
++                      unregister_netdev(dev->dev);
+               }
+               airoha_metadata_dst_free(port);
+       }
+@@ -3521,15 +3585,19 @@ static void airoha_remove(struct platfor
+       for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+               struct airoha_gdm_port *port = eth->ports[i];
++              struct airoha_gdm_dev *dev;
+               if (!port)
+                       continue;
+ #if defined(CONFIG_PCS_AIROHA)
+               if (airhoa_is_phy_external(port))
+-                      phylink_destroy(port->phylink);
++                      phylink_destroy(dev->phylink);
+ #endif
+-              unregister_netdev(port->dev);
++
++              dev = port->dev;
++              if (dev)
++                      unregister_netdev(dev->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
+@@ -546,17 +546,22 @@ 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;
+-      int id;
+-      int nbq;
++      struct airoha_eth *eth;
+ #if defined(CONFIG_PCS_AIROHA)
+       struct phylink *phylink;
+       struct phylink_config phylink_config;
+ #endif
++};
++
++struct airoha_gdm_port {
++      struct airoha_qdma *qdma;
++      struct airoha_gdm_dev *dev;
++      int id;
++      int nbq;
+       struct airoha_hw_stats stats;
+@@ -690,8 +695,8 @@ static inline bool airoha_qdma_is_lro_qu
+ }
+ 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);
++bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
++                           struct airoha_gdm_dev *dev);
+ void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
+                            u8 fport);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -297,12 +297,12 @@ static void airoha_ppe_foe_set_bridge_ad
+ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
+                                       struct airoha_foe_entry *hwe,
+-                                      struct net_device *dev, int type,
++                                      struct net_device *netdev, int type,
+                                       struct airoha_flow_data *data,
+                                       int l4proto, u8 dsfield)
+ {
+       u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
+-      int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
++      int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
+       struct airoha_foe_mac_info_common *l2;
+       u8 smac_id = 0xf;
+@@ -318,10 +318,11 @@ static int airoha_ppe_foe_entry_prepare(
+       hwe->ib1 = val;
+       val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
+-      if (dev) {
++      if (netdev) {
+               struct airoha_wdma_info info = {};
+-              if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
++              if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
++                                            &info)) {
+                       val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
+                              FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
+                                         FE_PSE_PORT_CDM4);
+@@ -331,12 +332,14 @@ static int airoha_ppe_foe_entry_prepare(
+                                    FIELD_PREP(AIROHA_FOE_MAC_WDMA_WCID,
+                                               info.wcid);
+               } else {
+-                      struct airoha_gdm_port *port = netdev_priv(dev);
++                      struct airoha_gdm_dev *dev = netdev_priv(netdev);
+                       u8 pse_port, channel, priority;
++                      struct airoha_gdm_port *port;
+-                      if (!airoha_is_valid_gdm_port(eth, port))
++                      if (!airoha_is_valid_gdm_dev(eth, dev))
+                               return -EINVAL;
++                      port = dev->port;
+                       if (dsa_port >= 0 || eth->ports[1])
+                               pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
+                                                        : port->id;
+@@ -1483,7 +1486,7 @@ void airoha_ppe_check_skb(struct airoha_
+ void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
+ {
+       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;
diff --git a/target/linux/airoha/patches-6.12/920-02-net-airoha-Move-airoha_qdma-pointer-in-airoha_gdm_de.patch b/target/linux/airoha/patches-6.12/920-02-net-airoha-Move-airoha_qdma-pointer-in-airoha_gdm_de.patch
new file mode 100644 (file)
index 0000000..847e8f1
--- /dev/null
@@ -0,0 +1,495 @@
+From f62cea6483cc55360863d66300790a5fb9de5f7c Mon Sep 17 00:00:00 2001
+Message-ID: <f62cea6483cc55360863d66300790a5fb9de5f7c.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: Tue, 24 Feb 2026 18:43:05 +0100
+Subject: [PATCH 02/13] net: airoha: Move airoha_qdma pointer in airoha_gdm_dev
+ struct
+
+Move airoha_qdma pointer from airoha_gdm_port struct to airoha_gdm_dev
+one since the QDMA block used depends on the particular net_device
+WAN/LAN configuration and in the current codebase net_device pointer is
+associated to airoha_gdm_dev struct.
+This is a preliminary patch to support multiple net_devices connected
+to the same GDM{3,4} port via an external hw arbiter.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 105 +++++++++++------------
+ drivers/net/ethernet/airoha/airoha_eth.h |   9 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c |  17 ++--
+ 3 files changed, 64 insertions(+), 67 deletions(-)
+
+--- 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
+ }
+ #endif
+-static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
++static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
+-      struct airoha_eth *eth = port->qdma->eth;
++      struct airoha_gdm_port *port = dev->port;
++      struct airoha_eth *eth = dev->eth;
+       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
+       airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
+       airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
+-      airoha_ppe_init_upd_mem(port);
++      airoha_ppe_init_upd_mem(dev);
+ }
+ 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(
+                     FIELD_PREP(GDM_UCFQ_MASK, val));
+ }
+-static int airoha_set_vip_for_gdm_port(struct airoha_gdm_port *port,
+-                                     bool enable)
++static int airoha_set_vip_for_gdm_port(struct airoha_gdm_dev *dev, bool enable)
+ {
+-      struct airoha_eth *eth = port->qdma->eth;
++      struct airoha_gdm_port *port = dev->port;
++      struct airoha_eth *eth = dev->eth;
+       u32 vip_port;
+       vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
+@@ -994,10 +995,13 @@ static void airoha_qdma_wake_netdev_txqs
+               if (!port)
+                       continue;
+-              if (port->qdma != qdma)
++              dev = port->dev;
++              if (!dev)
++                      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;
+@@ -1702,9 +1706,10 @@ static void airoha_qdma_stop_napi(struct
+       }
+ }
+-static void airoha_update_hw_stats(struct airoha_gdm_port *port)
++static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
+ {
+-      struct airoha_eth *eth = port->qdma->eth;
++      struct airoha_gdm_port *port = dev->port;
++      struct airoha_eth *eth = dev->eth;
+       u32 val, i = 0;
+       spin_lock(&port->stats.lock);
+@@ -1851,7 +1856,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;
+       u32 pse_port = FE_PSE_PORT_PPE1;
+ #if defined(CONFIG_PCS_AIROHA)
+@@ -1868,7 +1873,7 @@ static int airoha_dev_open(struct net_de
+ #endif
+       netif_tx_start_all_queues(netdev);
+-      err = airoha_set_vip_for_gdm_port(port, true);
++      err = airoha_set_vip_for_gdm_port(dev, true);
+       if (err)
+               return err;
+@@ -1905,11 +1910,11 @@ 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;
+       int i;
+       netif_tx_disable(netdev);
+-      airoha_set_vip_for_gdm_port(port, false);
++      airoha_set_vip_for_gdm_port(dev, false);
+       for (i = 0; i < netdev->num_tx_queues; i++)
+               netdev_tx_reset_subqueue(netdev, i);
+@@ -1942,21 +1947,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);
+-      struct airoha_gdm_port *port = dev->port;
+       int err;
+       err = eth_mac_addr(netdev, p);
+       if (err)
+               return err;
+-      airoha_set_macaddr(port, netdev->dev_addr);
++      airoha_set_macaddr(dev, netdev->dev_addr);
+       return 0;
+ }
+-static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
++static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
+ {
+-      struct airoha_eth *eth = port->qdma->eth;
++      struct airoha_gdm_port *port = dev->port;
++      struct airoha_eth *eth = dev->eth;
+       u32 val, pse_port, chan;
+       int i, src_port;
+@@ -2003,7 +2008,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++)
+-              airoha_ppe_set_cpu_port(port, i, AIROHA_GDM2_IDX);
++              airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
+       if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+               u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
+@@ -2023,9 +2028,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 = &eth->qdma[!airoha_is_lan_gdm_port(port)];
+-      dev->dev->irq = port->qdma->irq_banks[0].irq;
+-      airoha_set_macaddr(port, netdev->dev_addr);
++      dev->qdma = &eth->qdma[!airoha_is_lan_gdm_port(port)];
++      dev->dev->irq = dev->qdma->irq_banks[0].irq;
++      airoha_set_macaddr(dev, netdev->dev_addr);
+       switch (port->id) {
+       case AIROHA_GDM3_IDX:
+@@ -2034,7 +2039,7 @@ static int airoha_dev_init(struct net_de
+               if (!eth->ports[1]) {
+                       int err;
+-                      err = airoha_set_gdm2_loopback(port);
++                      err = airoha_set_gdm2_loopback(dev);
+                       if (err)
+                               return err;
+               }
+@@ -2044,8 +2049,7 @@ static int airoha_dev_init(struct net_de
+       }
+       for (i = 0; i < eth->soc->num_ppe; i++)
+-              airoha_ppe_set_cpu_port(port, i,
+-                                      airoha_get_fe_port(port));
++              airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
+       return 0;
+ }
+@@ -2057,7 +2061,7 @@ static void airoha_dev_get_stats64(struc
+       struct airoha_gdm_port *port = dev->port;
+       unsigned int start;
+-      airoha_update_hw_stats(port);
++      airoha_update_hw_stats(dev);
+       do {
+               start = u64_stats_fetch_begin(&port->stats.syncp);
+               storage->rx_packets = port->stats.rx_ok_pkts;
+@@ -2077,8 +2081,8 @@ static int airoha_dev_change_mtu(struct
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+       struct airoha_gdm_port *port = dev->port;
+-      struct airoha_eth *eth = port->qdma->eth;
+       u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
++      struct airoha_eth *eth = dev->eth;
+       airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+                     GDM_LONG_LEN_MASK,
+@@ -2152,10 +2156,10 @@ static u32 airoha_get_dsa_tag(struct sk_
+ #endif
+ }
+-int airoha_get_fe_port(struct airoha_gdm_port *port)
++int airoha_get_fe_port(struct airoha_gdm_dev *dev)
+ {
+-      struct airoha_qdma *qdma = port->qdma;
+-      struct airoha_eth *eth = qdma->eth;
++      struct airoha_gdm_port *port = dev->port;
++      struct airoha_eth *eth = dev->eth;
+       switch (eth->soc->version) {
+       case 0x7583:
+@@ -2174,8 +2178,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_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 - &eth->qdma[0];
+       int i;
+@@ -2213,10 +2216,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;
+@@ -2233,8 +2236,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+                                  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;
++      struct airoha_qdma *qdma = dev->qdma;
+       u32 nr_frags, tag, msg0, msg1, len;
+       struct airoha_queue_entry *e;
+       struct netdev_queue *txq;
+@@ -2272,7 +2274,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+               }
+       }
+-      fport = airoha_get_fe_port(port);
++      fport = airoha_get_fe_port(dev);
+       msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+              FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+@@ -2375,8 +2377,7 @@ static void airoha_ethtool_get_drvinfo(s
+                                      struct ethtool_drvinfo *info)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+-      struct airoha_eth *eth = port->qdma->eth;
++      struct airoha_eth *eth = dev->eth;
+       strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
+       strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
+@@ -2389,7 +2390,7 @@ static void airoha_ethtool_get_mac_stats
+       struct airoha_gdm_port *port = dev->port;
+       unsigned int start;
+-      airoha_update_hw_stats(port);
++      airoha_update_hw_stats(dev);
+       do {
+               start = u64_stats_fetch_begin(&port->stats.syncp);
+               stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
+@@ -2429,7 +2430,7 @@ airoha_ethtool_get_rmon_stats(struct net
+                    ARRAY_SIZE(hw_stats->rx_len) + 1);
+       *ranges = airoha_ethtool_rmon_ranges;
+-      airoha_update_hw_stats(port);
++      airoha_update_hw_stats(dev);
+       do {
+               int i;
+@@ -2449,18 +2450,17 @@ static int airoha_qdma_set_chan_tx_sched
+                                        const u16 *weights, u8 n_weights)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+       int i;
+       for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+-              airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
++              airoha_qdma_clear(dev->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(port->qdma, REG_TXWRR_WEIGHT_CFG,
++              airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
+                              TWRR_RW_CMD_MASK |
+                              FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
+                              FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
+@@ -2468,13 +2468,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, port->qdma,
+-                                      REG_TXWRR_WEIGHT_CFG);
++                                      true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
+               if (err)
+                       return err;
+       }
+-      airoha_qdma_rmw(port->qdma, REG_CHAN_QOS_MODE(channel >> 3),
++      airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+                       CHAN_QOS_MODE_MASK(channel),
+                       __field_prep(CHAN_QOS_MODE_MASK(channel), mode));
+@@ -2540,9 +2539,9 @@ static int airoha_qdma_get_tx_ets_stats(
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+       struct airoha_gdm_port *port = dev->port;
+-      u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
++      u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
+                                           REG_CNTR_VAL(channel << 1));
+-      u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
++      u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
+                                           REG_CNTR_VAL((channel << 1) + 1));
+       u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
+                        (fwd_tx_packets - port->fwd_tx_packets);
+@@ -2806,17 +2805,16 @@ static int airoha_qdma_set_tx_rate_limit
+                                        u32 bucket_size)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+       int i, err;
+       for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
+-              err = airoha_qdma_set_trtcm_config(port->qdma, channel,
++              err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
+                                                  REG_EGRESS_TRTCM_CFG, i,
+                                                  !!rate, TRTCM_METER_MODE);
+               if (err)
+                       return err;
+-              err = airoha_qdma_set_trtcm_token_bucket(port->qdma, channel,
++              err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
+                                                        REG_EGRESS_TRTCM_CFG,
+                                                        i, rate, bucket_size);
+               if (err)
+@@ -2866,11 +2864,11 @@ static int airoha_tc_htb_alloc_leaf_queu
+       return 0;
+ }
+-static int airoha_qdma_set_rx_meter(struct airoha_gdm_port *port,
++static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
+                                   u32 rate, u32 bucket_size,
+                                   enum trtcm_unit_type unit_type)
+ {
+-      struct airoha_qdma *qdma = port->qdma;
++      struct airoha_qdma *qdma = dev->qdma;
+       int i;
+       for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+@@ -2949,7 +2947,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);
+-      struct airoha_gdm_port *port = dev->port;
+       u32 rate = 0, bucket_size = 0;
+       switch (f->command) {
+@@ -2974,7 +2971,7 @@ static int airoha_dev_tc_matchall(struct
+               fallthrough;
+       }
+       case TC_CLSMATCHALL_DESTROY:
+-              return airoha_qdma_set_rx_meter(port, rate, bucket_size,
++              return airoha_qdma_set_rx_meter(dev, rate, bucket_size,
+                                               unit_type);
+       default:
+               return -EOPNOTSUPP;
+@@ -2986,8 +2983,7 @@ static int airoha_dev_setup_tc_block_cb(
+ {
+       struct net_device *netdev = cb_priv;
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+-      struct airoha_eth *eth = port->qdma->eth;
++      struct airoha_eth *eth = dev->eth;
+       if (!tc_can_offload(netdev))
+               return -EOPNOTSUPP;
+@@ -3222,7 +3218,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_qdma *qdma = port->qdma;
++      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_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -548,6 +548,7 @@ struct airoha_qdma {
+ struct airoha_gdm_dev {
+       struct airoha_gdm_port *port;
++      struct airoha_qdma *qdma;
+       struct net_device *dev;
+       struct airoha_eth *eth;
+@@ -558,7 +559,6 @@ struct airoha_gdm_dev {
+ };
+ struct airoha_gdm_port {
+-      struct airoha_qdma *qdma;
+       struct airoha_gdm_dev *dev;
+       int id;
+       int nbq;
+@@ -694,19 +694,18 @@ static inline bool airoha_qdma_is_lro_qu
+       return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
+ }
+-int airoha_get_fe_port(struct airoha_gdm_port *port);
++int airoha_get_fe_port(struct airoha_gdm_dev *dev);
+ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+                            struct airoha_gdm_dev *dev);
+-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
+-                           u8 fport);
++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,
+                         u16 hash, bool rx_wlan);
+ 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_port *port);
++void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev);
+ u32 airoha_ppe_get_total_num_entries(struct airoha_ppe *ppe);
+ struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
+                                                 u32 hash);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -85,9 +85,9 @@ static u32 airoha_ppe_get_timestamp(stru
+                            AIROHA_FOE_IB1_BIND_TIMESTAMP);
+ }
+-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id, u8 fport)
++void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
+ {
+-      struct airoha_qdma *qdma = port->qdma;
++      struct airoha_qdma *qdma = dev->qdma;
+       struct airoha_eth *eth = qdma->eth;
+       u8 qdma_id = qdma - &eth->qdma[0];
+       u32 fe_cpu_port;
+@@ -181,8 +181,8 @@ static void airoha_ppe_hw_init(struct ai
+                       if (!port)
+                               continue;
+-                      airoha_ppe_set_cpu_port(port, i,
+-                                              airoha_get_fe_port(port));
++                      airoha_ppe_set_cpu_port(port->dev, i,
++                                              airoha_get_fe_port(port->dev));
+               }
+       }
+ }
+@@ -1483,11 +1483,12 @@ void airoha_ppe_check_skb(struct airoha_
+       airoha_ppe_foe_insert_entry(ppe, skb, hash, rx_wlan);
+ }
+-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
++void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
+ {
+-      struct airoha_eth *eth = port->qdma->eth;
+-      struct net_device *dev = port->dev->dev;
+-      const u8 *addr = dev->dev_addr;
++      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;
+       val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];
diff --git a/target/linux/airoha/patches-6.12/920-03-net-airoha-Rely-on-airoha_gdm_dev-pointer-in-airoha_.patch b/target/linux/airoha/patches-6.12/920-03-net-airoha-Rely-on-airoha_gdm_dev-pointer-in-airoha_.patch
new file mode 100644 (file)
index 0000000..c3f76df
--- /dev/null
@@ -0,0 +1,73 @@
+From 32bfd008c19f9ad55514181d8cd02e14bf384475 Mon Sep 17 00:00:00 2001
+Message-ID: <32bfd008c19f9ad55514181d8cd02e14bf384475.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 10:06:45 +0100
+Subject: [PATCH 03/13] net: airoha: Rely on airoha_gdm_dev pointer in
+ airoha_is_lan_gdm_port()
+
+Rename airoha_is_lan_gdm_port in airoha_is_lan_gdm_dev. Moreover, rely
+on airoha_gdm_dev pointer in airoha_is_lan_gdm_dev() instead of
+airoha_gdm_port one.
+This is a preliminary patch to support multiple net_devices connected to
+the same GDM{3,4} port via an external hw arbiter.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 6 ++----
+ drivers/net/ethernet/airoha/airoha_eth.h | 4 +++-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 2 +-
+ 3 files changed, 6 insertions(+), 6 deletions(-)
+
+--- 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
+ static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
+-      struct airoha_gdm_port *port = dev->port;
+       struct airoha_eth *eth = dev->eth;
+       u32 val, reg;
+-      reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
+-                                         : REG_FE_WAN_MAC_H;
++      reg = airoha_is_lan_gdm_dev(dev) ? REG_FE_LAN_MAC_H : REG_FE_WAN_MAC_H;
+       val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
+       airoha_fe_wr(eth, reg, val);
+@@ -2028,7 +2026,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 = &eth->qdma[!airoha_is_lan_gdm_port(port)];
++      dev->qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
+       dev->dev->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
+@@ -663,8 +663,10 @@ static inline u16 airoha_qdma_get_txq(st
+       return qid % ARRAY_SIZE(qdma->q_tx);
+ }
+-static inline bool airoha_is_lan_gdm_port(struct airoha_gdm_port *port)
++static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
+ {
++      struct airoha_gdm_port *port = dev->port;
++
+       /* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
+        * GDM{2,3,4} can be used as wan port connected to an external
+        * phy module.
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -365,7 +365,7 @@ static int airoha_ppe_foe_entry_prepare(
+                       /* For downlink traffic consume SRAM memory for hw
+                        * forwarding descriptors queue.
+                        */
+-                      if (airoha_is_lan_gdm_port(port))
++                      if (airoha_is_lan_gdm_dev(dev))
+                               val |= AIROHA_FOE_IB2_FAST_PATH;
+                       if (dsa_port >= 0)
+                               val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ,
diff --git a/target/linux/airoha/patches-6.12/920-04-net-airoha-Move-qos_sq_bmap-in-airoha_gdm_dev-struct.patch b/target/linux/airoha/patches-6.12/920-04-net-airoha-Move-qos_sq_bmap-in-airoha_gdm_dev-struct.patch
new file mode 100644 (file)
index 0000000..da39161
--- /dev/null
@@ -0,0 +1,184 @@
+From 634d75285db77f3385aa85a1bf2b185396225100 Mon Sep 17 00:00:00 2001
+Message-ID: <634d75285db77f3385aa85a1bf2b185396225100.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: Fri, 10 Apr 2026 14:35:32 +0200
+Subject: [PATCH 04/13] net: airoha: Move qos_sq_bmap in airoha_gdm_dev struct
+
+Since now multiple net_devices connected to different QDMA blocks can
+share the same GDM port, qos_sq_bmap field can be overwritten with the
+configuration obtained from a net_device connected to a different QDMA
+block. In order to fix the issue move qos_sq_bmap field from
+airoha_gdm_port struct to airoha_gdm_dev one.
+Add qos_channel_map bitmap in airoha_qdma struct to track if a shared
+QDMA channel is already in use by another net_device.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 58 +++++++++++++++---------
+ drivers/net/ethernet/airoha/airoha_eth.h |  6 ++-
+ 2 files changed, 40 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2822,30 +2822,40 @@ static int airoha_qdma_set_tx_rate_limit
+       return 0;
+ }
+-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
+-                                        struct tc_htb_qopt_offload *opt)
++static int airoha_tc_htb_modify_queue(struct net_device *dev,
++                                    struct tc_htb_qopt_offload *opt)
+ {
+       u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+       u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
+-      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;
+       if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
+               NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
+               return -EINVAL;
+       }
+-      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);
++}
++
++static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
++                                        struct tc_htb_qopt_offload *opt)
++{
++      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;
++
++      /* Here we need to check the requested QDMA channel is not already
++       * 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;
++                                 "qdma qos channel already in use");
++              return -EBUSY;
+       }
+-      if (opt->command == TC_HTB_NODE_MODIFY)
+-              return 0;
++      err = airoha_tc_htb_modify_queue(netdev, opt);
++      if (err)
++              goto error;
+       err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
+       if (err) {
+@@ -2853,13 +2863,17 @@ static int airoha_tc_htb_alloc_leaf_queu
+                                             opt->quantum);
+               NL_SET_ERR_MSG_MOD(opt->extack,
+                                  "failed setting real_num_tx_queues");
+-              return err;
++              goto error;
+       }
+-      set_bit(channel, port->qos_sq_bmap);
++      set_bit(channel, dev->qos_sq_bmap);
+       opt->qid = AIROHA_NUM_TX_RING + channel;
+       return 0;
++error:
++      clear_bit(channel, qdma->qos_channel_map);
++
++      return err;
+ }
+ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
+@@ -3040,11 +3054,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);
+-      struct airoha_gdm_port *port = dev->port;
++      struct airoha_qdma *qdma = dev->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);
+-      clear_bit(queue, port->qos_sq_bmap);
++
++      clear_bit(queue, qdma->qos_channel_map);
++      clear_bit(queue, dev->qos_sq_bmap);
+ }
+ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
+@@ -3052,9 +3068,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);
+-      struct airoha_gdm_port *port = dev->port;
+-      if (!test_bit(channel, port->qos_sq_bmap)) {
++      if (!test_bit(channel, dev->qos_sq_bmap)) {
+               NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+               return -EINVAL;
+       }
+@@ -3067,10 +3082,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);
+-      struct airoha_gdm_port *port = dev->port;
+       int q;
+-      for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
++      for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+               airoha_tc_remove_htb_queue(netdev, q);
+       return 0;
+@@ -3081,9 +3095,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);
+-      struct airoha_gdm_port *port = dev->port;
+-      if (!test_bit(channel, port->qos_sq_bmap)) {
++      if (!test_bit(channel, dev->qos_sq_bmap)) {
+               NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+               return -EINVAL;
+       }
+@@ -3102,6 +3115,7 @@ static int airoha_tc_setup_qdisc_htb(str
+       case TC_HTB_DESTROY:
+               return airoha_tc_htb_destroy(dev);
+       case TC_HTB_NODE_MODIFY:
++              return airoha_tc_htb_modify_queue(dev, opt);
+       case TC_HTB_LEAF_ALLOC_QUEUE:
+               return airoha_tc_htb_alloc_leaf_queue(dev, opt);
+       case TC_HTB_LEAF_DEL:
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -544,6 +544,8 @@ struct airoha_qdma {
+       struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
+       struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
++
++      DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
+ };
+ struct airoha_gdm_dev {
+@@ -556,6 +558,8 @@ struct airoha_gdm_dev {
+       struct phylink *phylink;
+       struct phylink_config phylink_config;
+ #endif
++
++      DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+ };
+ struct airoha_gdm_port {
+@@ -565,8 +569,6 @@ struct airoha_gdm_port {
+       struct airoha_hw_stats stats;
+-      DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+-
+       /* qos stats counters */
+       u64 cpu_tx_packets;
+       u64 fwd_tx_packets;
diff --git a/target/linux/airoha/patches-6.12/920-05-net-airoha-Move-cpu-fwd-_tx_packets-in-airoha_gdm_de.patch b/target/linux/airoha/patches-6.12/920-05-net-airoha-Move-cpu-fwd-_tx_packets-in-airoha_gdm_de.patch
new file mode 100644 (file)
index 0000000..b6b7635
--- /dev/null
@@ -0,0 +1,73 @@
+From 00272dbf6a52241a21145631f22dc5f03891078b Mon Sep 17 00:00:00 2001
+Message-ID: <00272dbf6a52241a21145631f22dc5f03891078b.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: Fri, 10 Apr 2026 14:47:08 +0200
+Subject: [PATCH 05/13] net: airoha: Move {cpu,fwd}_tx_packets in
+ airoha_gdm_dev struct
+
+Since now multiple net_devices connected to different QDMA blocks can
+share the same GDM port, cpu_tx_packets and fwd_tx_packets fields can
+be overwritten with the value from a different QDMA block. In order to
+fix the issue move cpu_tx_packets and fwd_tx_packets fields from
+airoha_gdm_port struct to airoha_gdm_dev one.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 16 +++++++---------
+ drivers/net/ethernet/airoha/airoha_eth.h |  7 +++----
+ 2 files changed, 10 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2535,19 +2535,17 @@ static int airoha_qdma_get_tx_ets_stats(
+                                       struct tc_ets_qopt_offload *opt)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
++      struct airoha_qdma *qdma = dev->qdma;
+-      u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
+-                                          REG_CNTR_VAL(channel << 1));
+-      u64 fwd_tx_packets = airoha_qdma_rr(dev->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 - port->cpu_tx_packets) +
+-                       (fwd_tx_packets - port->fwd_tx_packets);
++      u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
++                       (fwd_tx_packets - dev->fwd_tx_packets);
+       _bstats_update(opt->stats.bstats, 0, tx_packets);
+-
+-      port->cpu_tx_packets = cpu_tx_packets;
+-      port->fwd_tx_packets = fwd_tx_packets;
++      dev->cpu_tx_packets = cpu_tx_packets;
++      dev->fwd_tx_packets = fwd_tx_packets;
+       return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -560,6 +560,9 @@ struct airoha_gdm_dev {
+ #endif
+       DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
++      /* qos stats counters */
++      u64 cpu_tx_packets;
++      u64 fwd_tx_packets;
+ };
+ struct airoha_gdm_port {
+@@ -569,10 +572,6 @@ struct airoha_gdm_port {
+       struct airoha_hw_stats stats;
+-      /* qos stats counters */
+-      u64 cpu_tx_packets;
+-      u64 fwd_tx_packets;
+-
+       struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
+ };
diff --git a/target/linux/airoha/patches-6.12/920-06-net-airoha-Support-multiple-net_devices-for-a-single.patch b/target/linux/airoha/patches-6.12/920-06-net-airoha-Support-multiple-net_devices-for-a-single.patch
new file mode 100644 (file)
index 0000000..23e88cb
--- /dev/null
@@ -0,0 +1,714 @@
+From 8eb0a71bfbe92b6fbc668c5d9ebdcbf6523a89ad Mon Sep 17 00:00:00 2001
+Message-ID: <8eb0a71bfbe92b6fbc668c5d9ebdcbf6523a89ad.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, 1 Nov 2025 11:27:48 +0100
+Subject: [PATCH 06/13] net: airoha: Support multiple net_devices for a single
+ FE GDM port
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
+Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
+manages the traffic in a TDM manner. As a result multiple net_devices can
+connect to the same GDM{3,4} port and there is a theoretical "1:n"
+relation between GDM ports and net_devices.
+
+           ┌─────────────────────────────────┐
+           │                                 │    ┌──────┐
+           │                         P1 GDM1 ├────►MT7530│
+           │                                 │    └──────┘
+           │                                 │      ETH0 (DSA conduit)
+           │                                 │
+           │              PSE/FE             │
+           │                                 │
+           │                                 │
+           │                                 │    ┌─────┐
+           │                         P0 CDM1 ├────►QDMA0│
+           │  P4                     P9 GDM4 │    └─────┘
+           └──┬─────────────────────────┬────┘
+              │                         │
+           ┌──▼──┐                 ┌────▼────┐
+           │ PPE │                 │   ARB   │
+           └─────┘                 └─┬─────┬─┘
+                                     │     │
+                                  ┌──▼──┐┌─▼───┐
+                                  │ ETH ││ USB │
+                                  └─────┘└─────┘
+                                   ETH1   ETH2
+
+Introduce support for multiple net_devices connected to the same Frame
+Engine (FE) GDM port (GDM3 or GDM4) via an external hw arbiter.
+Please note GDM1 or GDM2 does not support the connection with the external
+arbiter.
+Add get_dev_from_sport callback since EN7581 and AN7583 have different
+logics for the net_device type connected to GDM3 or GDM4.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 274 +++++++++++++++++------
+ drivers/net/ethernet/airoha/airoha_eth.h |  10 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c |  13 +-
+ 3 files changed, 228 insertions(+), 69 deletions(-)
+
+--- 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
+       struct airoha_eth *eth = dev->eth;
+       u32 vip_port;
+-      vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
++      vip_port = eth->soc->ops.get_vip_port(port, dev->nbq);
+       if (enable) {
+               airoha_fe_set(eth, REG_FE_VIP_PORT_EN, vip_port);
+               airoha_fe_set(eth, REG_FE_IFC_PORT_EN, vip_port);
+@@ -618,30 +618,26 @@ static int airoha_qdma_fill_rx_queue(str
+       return nframes;
+ }
+-static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
+-                                  struct airoha_qdma_desc *desc)
++static struct airoha_gdm_dev *
++airoha_qdma_get_gdm_dev(struct airoha_eth *eth, struct airoha_qdma_desc *desc)
+ {
+-      u32 port, sport, msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
++      struct airoha_gdm_port *port;
++      u16 p, d;
+-      sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK, msg1);
+-      switch (sport) {
+-      case 0x18:
+-              port = 3; /* GDM4 */
+-              break;
+-      case 0x16:
+-              port = 2; /* GDM3 */
+-              break;
+-      case 0x10 ... 0x14:
+-              port = 0; /* GDM1 */
+-              break;
+-      case 0x2 ... 0x4:
+-              port = sport - 1;
+-              break;
+-      default:
+-              return -EINVAL;
+-      }
++      if (eth->soc->ops.get_dev_from_sport(desc, &p, &d))
++              return ERR_PTR(-ENODEV);
++
++      if (p >= ARRAY_SIZE(eth->ports))
++              return ERR_PTR(-ENODEV);
++
++      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,
+@@ -725,9 +721,8 @@ 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;
++              int data_len, len;
+               struct page *page;
+               desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
+@@ -748,15 +743,10 @@ 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;
+               if (!q->skb) { /* first buffer */
+                       q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
+                                               q->buf_size);
+@@ -766,15 +756,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 = netdev;
++                      q->skb->dev = dev->dev;
+                       q->skb->ip_summed = CHECKSUM_UNNECESSARY;
+                       skb_record_rx_queue(q->skb, qid);
+-                      if (lro_q && (netdev->features & NETIF_F_LRO) &&
++                      if (lro_q && (dev->dev->features & NETIF_F_LRO) &&
+                           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);
+               } else { /* scattered frame */
+                       struct skb_shared_info *shinfo = skb_shinfo(q->skb);
+                       int nr_frags = shinfo->nr_frags;
+@@ -790,7 +780,9 @@ static int airoha_qdma_rx_process(struct
+               if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
+                       continue;
+-              if (netdev_uses_dsa(netdev)) {
++              if (netdev_uses_dsa(dev->dev)) {
++                      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
+@@ -987,24 +979,27 @@ 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;
+-              int j;
++              int d;
+               if (!port)
+                       continue;
+-              dev = port->dev;
+-              if (!dev)
+-                      continue;
++              for (d = 0; d < ARRAY_SIZE(port->devs); d++) {
++                      struct airoha_gdm_dev *dev = port->devs[d];
++                      int j;
+-              if (dev->qdma != qdma)
+-                      continue;
++                      if (!dev)
++                              continue;
+-              for (j = 0; j < dev->dev->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++) {
++                              if (airoha_qdma_get_txq(qdma, j) != qid)
++                                      continue;
++
++                              netif_wake_subqueue(dev->dev, j);
++                      }
+               }
+       }
+       q->txq_stopped = false;
+@@ -1893,11 +1888,9 @@ static int airoha_dev_open(struct net_de
+                       GLOBAL_CFG_RX_DMA_EN_MASK);
+       atomic_inc(&qdma->users);
+-      if (port->id == AIROHA_GDM2_IDX &&
+-          airoha_ppe_is_enabled(qdma->eth, 1)) {
+-              /* For PPE2 always use secondary cpu port. */
++      if (!airoha_is_lan_gdm_dev(dev) &&
++          airoha_ppe_is_enabled(qdma->eth, 1))
+               pse_port = FE_PSE_PORT_PPE2;
+-      }
+       airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+                                   pse_port);
+@@ -1992,7 +1985,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));
+-      src_port = eth->soc->ops.get_sport(port, port->nbq);
++      src_port = eth->soc->ops.get_sport(port, dev->nbq);
+       if (src_port < 0)
+               return src_port;
+@@ -2009,7 +2002,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)) {
+-              u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
++              u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
+               airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
+                             __field_prep(mask, AIROHA_GDM2_IDX));
+@@ -2023,7 +2016,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;
+-      int i;
++      int ppe_id;
+       /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+       dev->qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
+@@ -2046,8 +2039,8 @@ static int airoha_dev_init(struct net_de
+               break;
+       }
+-      for (i = 0; i < eth->soc->num_ppe; i++)
+-              airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
++      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));
+       return 0;
+ }
+@@ -2209,20 +2202,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);
+       }
+@@ -2273,7 +2272,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+       }
+       fport = airoha_get_fe_port(dev);
+-      msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
++      msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
++             FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+              FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+       q = &qdma->q_tx[qid];
+@@ -3209,12 +3209,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];
++              int j;
+               if (!port)
+                       continue;
+-              if (port->dev == dev)
+-                      return true;
++              for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++                      if (port->devs[j] == dev)
++                              return true;
++              }
+       }
+       return false;
+@@ -3338,10 +3341,11 @@ static int airoha_setup_phylink(struct n
+ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
+                                  struct airoha_gdm_port *port,
+-                                 struct device_node *np)
++                                 int nbq, struct device_node *np)
+ {
+-      struct airoha_gdm_dev *dev;
+       struct net_device *netdev;
++      struct airoha_gdm_dev *dev;
++      u8 index;
+       int err;
+       netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
+@@ -3359,7 +3363,6 @@ static int airoha_alloc_gdm_device(struc
+       netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
+       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 */
+@@ -3377,11 +3380,25 @@ static int airoha_alloc_gdm_device(struc
+                        netdev->dev_addr);
+       }
++      /* Allowed nbq for EN7581 on GDM3 port are 4 and 5 for PCIE0
++       * and PCIE1 respectively.
++       */
++      index = nbq;
++      if (index && airoha_is_7581(eth) && port->id == AIROHA_GDM3_IDX)
++              index -= 4;
++
++      if (index >= ARRAY_SIZE(port->devs) || port->devs[index]) {
++              dev_err(eth->dev, "invalid nbq id: %d\n", nbq);
++              return -EINVAL;
++      }
++
++      netdev->dev.of_node = of_node_get(np);
+       dev = netdev_priv(netdev);
+       dev->dev = netdev;
+       dev->port = port;
+-      port->dev = dev;
+       dev->eth = eth;
++      dev->nbq = nbq;
++      port->devs[index] = dev;
+ #if defined(CONFIG_PCS_AIROHA)
+       if (airhoa_is_phy_external(port)) {
+@@ -3399,7 +3416,8 @@ static int airoha_alloc_gdm_port(struct
+ {
+       const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+       struct airoha_gdm_port *port;
+-      int err, p;
++      struct device_node *node;
++      int err, nbq, p, d = 0;
+       u32 id;
+       if (!id_ptr) {
+@@ -3427,15 +3445,51 @@ static int airoha_alloc_gdm_port(struct
+       u64_stats_init(&port->stats.syncp);
+       spin_lock_init(&port->stats.lock);
+       port->id = id;
+-      /* XXX: Read nbq from DTS */
+-      port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
+       eth->ports[p] = port;
+       err = airoha_metadata_dst_alloc(port);
+       if (err)
+               return err;
+-      return airoha_alloc_gdm_device(eth, port, np);
++      /* Default nbq value to ensure backward compatibility */
++      nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
++
++      for_each_child_of_node(np, node) {
++              /* Multiple external serdes connected to the FE GDM port via an
++               * external arbiter.
++               */
++              const __be32 *nbq_ptr;
++
++              if (!of_device_is_compatible(node, "airoha,eth-port"))
++                      continue;
++
++              d++;
++              if (!of_device_is_available(node))
++                      continue;
++
++              nbq_ptr = of_get_property(node, "reg", NULL);
++              if (!nbq_ptr) {
++                      dev_err(eth->dev, "missing nbq id\n");
++                      of_node_put(node);
++                      return -EINVAL;
++              }
++
++              /* Verify the provided nbq parameter is valid */
++              nbq = be32_to_cpup(nbq_ptr);
++              err = eth->soc->ops.get_sport(port, nbq);
++              if (err < 0) {
++                      of_node_put(node);
++                      return err;
++              }
++
++              err = airoha_alloc_gdm_device(eth, port, nbq, node);
++              if (err) {
++                      of_node_put(node);
++                      return err;
++              }
++      }
++
++      return !d ? airoha_alloc_gdm_device(eth, port, nbq, np) : 0;
+ }
+ static int airoha_register_gdm_devices(struct airoha_eth *eth)
+@@ -3444,14 +3498,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];
+-              int err;
++              int j;
+               if (!port)
+                       continue;
+-              err = register_netdev(port->dev->dev);
+-              if (err)
+-                      return err;
++              for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++                      struct airoha_gdm_dev *dev = port->devs[j];
++                      int err;
++
++                      if (!dev)
++                              continue;
++
++                      err = register_netdev(dev->dev);
++                      if (err)
++                              return err;
++              }
+       }
+       set_bit(DEV_STATE_REGISTERED, &eth->state);
+@@ -3558,18 +3620,27 @@ error_napi_stop:
+       for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+               struct airoha_gdm_port *port = eth->ports[i];
+-              struct airoha_gdm_dev *dev;
++              int j;
+               if (!port)
+                       continue;
+-              dev = port->dev;
+-              if (dev && dev->dev->reg_state == NETREG_REGISTERED) {
++              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) {
+ #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);
+ #endif
+-                      unregister_netdev(dev->dev);
++                              unregister_netdev(netdev);
++                      }
++                      of_node_put(netdev->dev.of_node);
+               }
+               airoha_metadata_dst_free(port);
+       }
+@@ -3591,19 +3662,26 @@ static void airoha_remove(struct platfor
+       for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+               struct airoha_gdm_port *port = eth->ports[i];
+-              struct airoha_gdm_dev *dev;
++              int j;
+               if (!port)
+                       continue;
++              for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++                      struct airoha_gdm_dev *dev = port->devs[j];
++                      struct net_device *netdev;
++
++                      if (!dev)
++                              continue;
++
+ #if defined(CONFIG_PCS_AIROHA)
+               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);
++                      of_node_put(netdev->dev.of_node);
++              }
+               airoha_metadata_dst_free(port);
+       }
+       airoha_hw_cleanup(eth);
+@@ -3665,6 +3743,39 @@ static u32 airoha_en7581_get_vip_port(st
+       return 0;
+ }
++static int airoha_en7581_get_dev_from_sport(struct airoha_qdma_desc *desc,
++                                          u16 *port, u16 *dev)
++{
++      u32 sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK,
++                            le32_to_cpu(READ_ONCE(desc->msg1)));
++
++      *dev = 0;
++      switch (sport) {
++      case 0x10 ... 0x14:
++              *port = 0; /* GDM1 */
++              break;
++      case 0x2 ... 0x4:
++              *port = sport - 1;
++              break;
++      case HSGMII_LAN_7581_PCIE1_SRCPORT:
++              *dev = 1;
++              fallthrough;
++      case HSGMII_LAN_7581_PCIE0_SRCPORT:
++              *port = 2; /* GDM3 */
++              break;
++      case HSGMII_LAN_7581_USB_SRCPORT:
++              *dev = 1;
++              fallthrough;
++      case HSGMII_LAN_7581_ETH_SRCPORT:
++              *port = 3; /* GDM4 */
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
+ static const char * const an7583_xsi_rsts_names[] = {
+       "hsi0-mac",
+       "hsi1-mac",
+@@ -3713,6 +3824,36 @@ static u32 airoha_an7583_get_vip_port(st
+       return 0;
+ }
++static int airoha_an7583_get_dev_from_sport(struct airoha_qdma_desc *desc,
++                                          u16 *port, u16 *dev)
++{
++      u32 sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK,
++                            le32_to_cpu(READ_ONCE(desc->msg1)));
++
++      *dev = 0;
++      switch (sport) {
++      case 0x10 ... 0x14:
++              *port = 0; /* GDM1 */
++              break;
++      case 0x2 ... 0x4:
++              *port = sport - 1;
++              break;
++      case HSGMII_LAN_7583_ETH_SRCPORT:
++              *port = 2; /* GDM3 */
++              break;
++      case HSGMII_LAN_7583_USB_SRCPORT:
++              *dev = 1;
++              fallthrough;
++      case HSGMII_LAN_7583_PCIE_SRCPORT:
++              *port = 3; /* GDM4 */
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
+ static const struct airoha_eth_soc_data en7581_soc_data = {
+       .version = 0x7581,
+       .xsi_rsts_names = en7581_xsi_rsts_names,
+@@ -3721,6 +3862,7 @@ static const struct airoha_eth_soc_data
+       .ops = {
+               .get_sport = airoha_en7581_get_sport,
+               .get_vip_port = airoha_en7581_get_vip_port,
++              .get_dev_from_sport = airoha_en7581_get_dev_from_sport,
+       },
+ };
+@@ -3732,6 +3874,7 @@ static const struct airoha_eth_soc_data
+       .ops = {
+               .get_sport = airoha_an7583_get_sport,
+               .get_vip_port = airoha_an7583_get_vip_port,
++              .get_dev_from_sport = airoha_an7583_get_dev_from_sport,
+       },
+ };
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -17,6 +17,7 @@
+ #include <net/dsa.h>
+ #define AIROHA_MAX_NUM_GDM_PORTS      4
++#define AIROHA_MAX_NUM_GDM_DEVS               2
+ #define AIROHA_MAX_NUM_QDMA           2
+ #define AIROHA_MAX_NUM_IRQ_BANKS      4
+ #define AIROHA_MAX_DSA_PORTS          7
+@@ -551,8 +552,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;
+@@ -563,12 +564,13 @@ struct airoha_gdm_dev {
+       /* qos stats counters */
+       u64 cpu_tx_packets;
+       u64 fwd_tx_packets;
++
++      int nbq;
+ };
+ struct airoha_gdm_port {
+-      struct airoha_gdm_dev *dev;
++      struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
+       int id;
+-      int nbq;
+       struct airoha_hw_stats stats;
+@@ -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);
++              int (*get_dev_from_sport)(struct airoha_qdma_desc *desc,
++                                        u16 *port, u16 *dev);
+       } ops;
+ };
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -168,9 +168,7 @@ static void airoha_ppe_hw_init(struct ai
+               airoha_fe_clear(eth, REG_PPE_PPE_FLOW_CFG(i),
+                               PPE_FLOW_CFG_IP6_6RD_MASK);
+-              for (p = 0; p < ARRAY_SIZE(eth->ports); p++) {
+-                      struct airoha_gdm_port *port = eth->ports[p];
+-
++              for (p = 0; p < ARRAY_SIZE(eth->ports); p++)
+                       airoha_fe_rmw(eth, REG_PPE_MTU(i, p),
+                                     FP0_EGRESS_MTU_MASK |
+                                     FP1_EGRESS_MTU_MASK,
+@@ -178,11 +176,24 @@ static void airoha_ppe_hw_init(struct ai
+                                                AIROHA_MAX_MTU) |
+                                     FIELD_PREP(FP1_EGRESS_MTU_MASK,
+                                                AIROHA_MAX_MTU));
+-                      if (!port)
++      }
++
++      for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++              struct airoha_gdm_port *port = eth->ports[i];
++              int j;
++
++              if (!port)
++                      continue;
++
++              for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++                      struct airoha_gdm_dev *dev = port->devs[j];
++                      u8 fport;
++
++                      if (!dev)
+                               continue;
+-                      airoha_ppe_set_cpu_port(port->dev, i,
+-                                              airoha_get_fe_port(port->dev));
++                      fport = airoha_get_fe_port(dev);
++                      airoha_ppe_set_cpu_port(dev, i, fport);
+               }
+       }
+ }
diff --git a/target/linux/airoha/patches-6.12/920-07-net-airoha-Do-not-stop-GDM-port-if-it-is-shared.patch b/target/linux/airoha/patches-6.12/920-07-net-airoha-Do-not-stop-GDM-port-if-it-is-shared.patch
new file mode 100644 (file)
index 0000000..ceaedae
--- /dev/null
@@ -0,0 +1,132 @@
+From de856a5b802cf030c8e242e98df3bc88446a4ea1 Mon Sep 17 00:00:00 2001
+Message-ID: <de856a5b802cf030c8e242e98df3bc88446a4ea1.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: Fri, 20 Mar 2026 11:09:40 +0100
+Subject: [PATCH 07/13] net: airoha: Do not stop GDM port if it is shared
+
+Theoretically, in the current codebase, two independent net_devices can
+be connected to the same GDM port so we need to check the GDM port is not
+used by any other running net_device before setting the forward
+configuration to FE_PSE_PORT_DROP.
+Moreover, always set in GDM_LONG_LEN_MASK field of REG_GDM_LEN_CFG
+register the maximum MTU of all running net_devices connected to the same
+GDM port.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 59 +++++++++++++++++++-----
+ drivers/net/ethernet/airoha/airoha_eth.h |  1 +
+ 2 files changed, 48 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1849,8 +1849,8 @@ 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;
+-      u32 pse_port = FE_PSE_PORT_PPE1;
+ #if defined(CONFIG_PCS_AIROHA)
+       if (airhoa_is_phy_external(port)) {
+@@ -1878,10 +1878,20 @@ static int airoha_dev_open(struct net_de
+               airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+                               GDM_STAG_EN_MASK);
+-      airoha_fe_rmw(qdma->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),
++                              GDM_LONG_LEN_MASK);
++      if (!port->users || len > cur_len) {
++              /* Opening a sibling net_device with a larger MTU updates the
++               * MTU of already running devices. This is required to allow
++               * multiple net_devices with different MTUs to share the same
++               * GDM port.
++               */
++              airoha_fe_rmw(qdma->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));
++      }
++      port->users++;
+       airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+                       GLOBAL_CFG_TX_DMA_EN_MASK |
+@@ -1897,6 +1907,30 @@ static int airoha_dev_open(struct net_de
+       return 0;
+ }
++static void airoha_set_port_mtu(struct airoha_eth *eth,
++                              struct airoha_gdm_port *port)
++{
++      u32 len = 0;
++      int i;
++
++      for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
++              struct airoha_gdm_dev *dev = port->devs[i];
++              struct net_device *netdev;
++
++              if (!dev)
++                      continue;
++
++              netdev = dev->dev;
++              if (netif_running(netdev))
++                      len = max_t(u32, len, netdev->mtu);
++      }
++      len += ETH_HLEN + ETH_FCS_LEN;
++
++      airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
++                    GDM_LONG_LEN_MASK,
++                    FIELD_PREP(GDM_LONG_LEN_MASK, len));
++}
++
+ static int airoha_dev_stop(struct net_device *netdev)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+@@ -1909,8 +1943,12 @@ 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),
+-                                  FE_PSE_PORT_DROP);
++      if (--port->users)
++              airoha_set_port_mtu(dev->eth, port);
++      else
++              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)) {
+               airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+@@ -2072,13 +2110,10 @@ static int airoha_dev_change_mtu(struct
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+       struct airoha_gdm_port *port = dev->port;
+-      u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
+-      struct airoha_eth *eth = dev->eth;
+-      airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+-                    GDM_LONG_LEN_MASK,
+-                    FIELD_PREP(GDM_LONG_LEN_MASK, len));
+       WRITE_ONCE(netdev->mtu, mtu);
++      if (port->users)
++              airoha_set_port_mtu(dev->eth, port);
+       return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -571,6 +571,7 @@ struct airoha_gdm_dev {
+ struct airoha_gdm_port {
+       struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
+       int id;
++      int users;
+       struct airoha_hw_stats stats;
diff --git a/target/linux/airoha/patches-6.12/920-08-net-airoha-Introduce-WAN-device-flag.patch b/target/linux/airoha/patches-6.12/920-08-net-airoha-Introduce-WAN-device-flag.patch
new file mode 100644 (file)
index 0000000..e5e60c0
--- /dev/null
@@ -0,0 +1,174 @@
+From c4f3077948eda05a6b9d1a11304d82c3e0300151 Mon Sep 17 00:00:00 2001
+Message-ID: <c4f3077948eda05a6b9d1a11304d82c3e0300151.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: Fri, 3 Apr 2026 12:07:27 +0200
+Subject: [PATCH 08/13] net: airoha: Introduce WAN device flag
+
+Introduce WAN flag to specify if a given device is used to transmit/receive
+WAN or LAN traffic. Current codebase supports specifying LAN/WAN device
+configuration in ndo_init() callback during device bootstrap.
+In order to consider setups where LAN configuration is used even for
+GDM3/GDM4 devices, check airoha_is_lan_gdm_dev() to select pse_port in
+airoha_ppe_foe_entry_prepare().
+Please note after this patch, it will be possible to specify multiple LAN
+devices but just a single WAN one. Please note this change is not visible
+to the user since airoha_eth driver currently supports just the internal
+phy available via the MT7530 DSA switch and there are no WAN interfaces
+officially supported since PCS/external phy is not merged mainline yet
+(it will be posted with following patches).
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 72 +++++++++++++++++++-----
+ drivers/net/ethernet/airoha/airoha_eth.h | 13 ++---
+ drivers/net/ethernet/airoha/airoha_ppe.c |  2 +-
+ 3 files changed, 65 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2049,36 +2049,80 @@ 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)
++{
++      int i;
++
++      for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++              struct airoha_gdm_port *port = eth->ports[i];
++              int j;
++
++              if (!port)
++                      continue;
++
++              for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++                      struct airoha_gdm_dev *dev = port->devs[j];
++
++                      if (dev && !airoha_is_lan_gdm_dev(dev))
++                              return dev;
++              }
++      }
++
++      return NULL;
++}
++
++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 airoha_eth *eth = dev->eth;
+       int ppe_id;
+       /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+       dev->qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
+       dev->dev->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);
++      airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
++}
++
++static int airoha_dev_init(struct net_device *netdev)
++{
++      struct airoha_gdm_dev *dev = netdev_priv(netdev);
++      struct airoha_gdm_port *port = dev->port;
+       switch (port->id) {
+       case AIROHA_GDM3_IDX:
+-      case AIROHA_GDM4_IDX:
+-              /* If GDM2 is active we can't enable loopback */
+-              if (!eth->ports[1]) {
+-                      int err;
+-
+-                      err = airoha_set_gdm2_loopback(dev);
+-                      if (err)
+-                              return err;
+-              }
++      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))
++                      break;
++
++              fallthrough;
++      }
++      case AIROHA_GDM2_IDX:
++              /* GDM2 is always used as wan */
++              dev->flags |= AIROHA_PRIV_F_WAN;
+               break;
+       default:
+               break;
+       }
+-      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));
++      airoha_dev_set_qdma(dev);
++      airoha_set_macaddr(dev, netdev->dev_addr);
++
++      if (!airoha_is_lan_gdm_dev(dev) &&
++          (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
++              int err;
++
++              err = airoha_set_gdm2_loopback(dev);
++              if (err)
++                      return err;
++      }
+       return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -549,6 +549,10 @@ struct airoha_qdma {
+       DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
+ };
++enum airoha_priv_flags {
++      AIROHA_PRIV_F_WAN = BIT(0),
++};
++
+ struct airoha_gdm_dev {
+       struct airoha_gdm_port *port;
+       struct airoha_qdma *qdma;
+@@ -565,6 +569,7 @@ struct airoha_gdm_dev {
+       u64 cpu_tx_packets;
+       u64 fwd_tx_packets;
++      u32 flags;
+       int nbq;
+ };
+@@ -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)
+ {
+-      struct airoha_gdm_port *port = dev->port;
+-
+-      /* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
+-       * GDM{2,3,4} can be used as wan port connected to an external
+-       * phy module.
+-       */
+-      return port->id == 1;
++      return !(dev->flags & AIROHA_PRIV_F_WAN);
+ }
+ static inline bool airoha_is_7581(struct airoha_eth *eth)
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -351,7 +351,7 @@ static int airoha_ppe_foe_entry_prepare(
+                               return -EINVAL;
+                       port = dev->port;
+-                      if (dsa_port >= 0 || eth->ports[1])
++                      if (dsa_port >= 0 || airoha_is_lan_gdm_dev(dev))
+                               pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
+                                                        : port->id;
+                       else
diff --git a/target/linux/airoha/patches-6.12/920-09-net-airoha-Support-multiple-LAN-WAN-interfaces-for-h.patch b/target/linux/airoha/patches-6.12/920-09-net-airoha-Support-multiple-LAN-WAN-interfaces-for-h.patch
new file mode 100644 (file)
index 0000000..55a3271
--- /dev/null
@@ -0,0 +1,161 @@
+From 144f0e6e55896625e3411aad02399a5ebb48d8f9 Mon Sep 17 00:00:00 2001
+Message-ID: <144f0e6e55896625e3411aad02399a5ebb48d8f9.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: Mon, 13 Apr 2026 17:38:51 +0200
+Subject: [PATCH 09/13] net: airoha: Support multiple LAN/WAN interfaces for hw
+ MAC address configuration
+
+The EN7581 and AN7583 SoCs provide registers to configure hardware LAN/WAN
+MAC addresses, used to determine whether received traffic is destined for
+this host or should be forwarded to another device.
+The SoC hardware design assumes all interfaces configured as LAN (or WAN)
+share a common upper MAC address, which is programmed into the
+REG_FE_{LAN,WAN}_MAC_H register. The lower bytes of 'local' addresses can
+be expressed as a range via the REG_FE_MAC_LMIN and REG_FE_MAC_LMAX
+registers.
+Previously, only a single interface was considered when programming these
+registers. Extend the logic to derive the correct minimum and maximum
+values for REG_FE_MAC_LMIN/REG_FE_MAC_LMAX when two or more interfaces are
+configured as LAN or WAN.
+
+Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 77 ++++++++++++++++++++----
+ drivers/net/ethernet/airoha/airoha_eth.h |  2 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c |  4 +-
+ 3 files changed, 68 insertions(+), 15 deletions(-)
+
+--- 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
+ }
+ #endif
+-static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
++static int airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
++      u8 ref_addr[ETH_ALEN] __aligned(2);
+       struct airoha_eth *eth = dev->eth;
+-      u32 val, reg;
++      u32 reg, val, lmin, lmax;
++      int i;
++
++      eth_zero_addr(ref_addr);
++      lmin = (addr[3] << 16) | (addr[4] << 8) | addr[5];
++      lmax = lmin;
++
++      for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++              struct airoha_gdm_port *port = eth->ports[i];
++              int j;
++
++              if (!port)
++                      continue;
++
++              for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++                      struct airoha_gdm_dev *iter_dev;
++                      struct net_device *netdev;
++
++                      iter_dev = port->devs[j];
++                      if (!iter_dev || iter_dev == dev)
++                              continue;
++
++                      if (airoha_is_lan_gdm_dev(iter_dev) !=
++                          airoha_is_lan_gdm_dev(dev))
++                              continue;
++
++                      netdev = iter_dev->dev;
++                      if (netdev->reg_state != NETREG_REGISTERED)
++                              continue;
++
++                      ether_addr_copy(ref_addr, netdev->dev_addr);
++                      val = (netdev->dev_addr[3] << 16) |
++                            (netdev->dev_addr[4] << 8) | netdev->dev_addr[5];
++                      if (val < lmin)
++                              lmin = val;
++                      if (val > lmax)
++                              lmax = val;
++              }
++      }
++
++      if (!is_zero_ether_addr(ref_addr) && memcmp(ref_addr, addr, 3)) {
++              /* According to the HW design, hw mac address MSBs must be
++               * the same for each net_device with the same LAN/WAN
++               * configuration.
++               */
++              dev_warn(eth->dev,
++                       "%s: wrong mac addr, MSBs must be %02x:%02x:%02x\n",
++                       dev->dev->name, ref_addr[0], ref_addr[1],
++                       ref_addr[2]);
++              dev_warn(eth->dev, "FE hw forwarding won't work properly\n");
++
++              return -EINVAL;
++      }
+       reg = airoha_is_lan_gdm_dev(dev) ? REG_FE_LAN_MAC_H : REG_FE_WAN_MAC_H;
+       val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
+       airoha_fe_wr(eth, reg, val);
+-      val = (addr[3] << 16) | (addr[4] << 8) | addr[5];
+-      airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
+-      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);
++      return 0;
+ }
+ static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
+@@ -1976,13 +2030,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);
++      struct sockaddr *addr = p;
+       int err;
+-      err = eth_mac_addr(netdev, p);
++      err = eth_prepare_mac_addr_change(netdev, p);
++      if (err)
++              return err;
++
++      err = airoha_set_macaddr(dev, addr->sa_data);
+       if (err)
+               return err;
+-      airoha_set_macaddr(dev, netdev->dev_addr);
++      eth_commit_mac_addr_change(netdev, p);
+       return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -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);
+ u32 airoha_ppe_get_total_num_entries(struct airoha_ppe *ppe);
+ struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
+                                                 u32 hash);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -1494,12 +1494,10 @@ void airoha_ppe_check_skb(struct airoha_
+       airoha_ppe_foe_insert_entry(ppe, skb, hash, rx_wlan);
+ }
+-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 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;
+       val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];
diff --git a/target/linux/airoha/patches-6.12/920-10-net-airoha-Rename-airoha_set_gdm2_loopback-in-airoha.patch b/target/linux/airoha/patches-6.12/920-10-net-airoha-Rename-airoha_set_gdm2_loopback-in-airoha.patch
new file mode 100644 (file)
index 0000000..98866e0
--- /dev/null
@@ -0,0 +1,37 @@
+From 917f959d54efd09719bd5047aa4b9543615e131e Mon Sep 17 00:00:00 2001
+Message-ID: <917f959d54efd09719bd5047aa4b9543615e131e.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: Fri, 19 Dec 2025 23:12:32 +0100
+Subject: [PATCH 10/13] net: airoha: Rename airoha_set_gdm2_loopback in
+ airoha_enable_gdm2_loopback
+
+This is a preliminary patch in order to allow the user to select if the
+configured device will be used as hw lan or wan.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2046,7 +2046,7 @@ static int airoha_dev_set_macaddr(struct
+       return 0;
+ }
+-static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
++static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
+ {
+       struct airoha_gdm_port *port = dev->port;
+       struct airoha_eth *eth = dev->eth;
+@@ -2178,7 +2178,7 @@ static int airoha_dev_init(struct net_de
+           (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
+               int err;
+-              err = airoha_set_gdm2_loopback(dev);
++              err = airoha_enable_gdm2_loopback(dev);
+               if (err)
+                       return err;
+       }
diff --git a/target/linux/airoha/patches-6.12/920-12-net-airoha-Add-ethtool-priv_flags-callbacks.patch b/target/linux/airoha/patches-6.12/920-12-net-airoha-Add-ethtool-priv_flags-callbacks.patch
new file mode 100644 (file)
index 0000000..a5f74d6
--- /dev/null
@@ -0,0 +1,246 @@
+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
+@@ -1906,6 +1906,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);
+@@ -2108,6 +2113,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)
+ {
+@@ -2509,6 +2553,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)
+ {
+@@ -2517,6 +2635,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,
+@@ -2581,6 +2700,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)
+@@ -3302,6 +3471,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
diff --git a/target/linux/airoha/patches-6.12/920-13-net-airoha-Rework-MTU-configuration.patch b/target/linux/airoha/patches-6.12/920-13-net-airoha-Rework-MTU-configuration.patch
new file mode 100644 (file)
index 0000000..04b4256
--- /dev/null
@@ -0,0 +1,223 @@
+From 99e204d255c9b47a188dd79762b9406d9c5fed9b Mon Sep 17 00:00:00 2001
+Message-ID: <99e204d255c9b47a188dd79762b9406d9c5fed9b.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: Wed, 20 May 2026 17:16:39 +0200
+Subject: [PATCH 13/13] net: airoha: Rework MTU configuration
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c  | 85 +++++++++++------------
+ drivers/net/ethernet/airoha/airoha_eth.h  |  1 +
+ drivers/net/ethernet/airoha/airoha_ppe.c  |  8 +--
+ drivers/net/ethernet/airoha/airoha_regs.h |  9 ++-
+ 4 files changed, 49 insertions(+), 54 deletions(-)
+
+--- 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
+ {
+       int p;
+-      for (p = 1; p <= ARRAY_SIZE(eth->ports); p++)
++      for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) {
+               airoha_fe_set(eth, REG_GDM_FWD_CFG(p),
+                             GDM_TCP_CKSUM_MASK | GDM_UDP_CKSUM_MASK |
+                             GDM_IP4_CKSUM_MASK | GDM_DROP_CRC_ERR_MASK);
++              airoha_fe_rmw(eth, REG_GDM_LEN_CFG(p),
++                            GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
++                            FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
++                            FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_MTU));
++      }
+       airoha_fe_rmw(eth, REG_CDM_VLAN_CTRL(1), CDM_VLAN_MASK,
+                     FIELD_PREP(CDM_VLAN_MASK, 0x8100));
+@@ -1898,13 +1903,37 @@ static void airoha_update_hw_stats(struc
+       spin_unlock(&port->stats.lock);
+ }
++void airoha_set_port_mtu(struct airoha_eth *eth, struct airoha_gdm_port *port)
++{
++      u32 mtu = 0, index;
++      int i;
++
++      for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
++              struct airoha_gdm_dev *dev = port->devs[i];
++              struct net_device *netdev;
++
++              if (!dev)
++                      continue;
++
++              netdev = dev->dev;
++              if (netif_running(netdev))
++                      mtu = max_t(u32, mtu, netdev->mtu);
++      }
++
++      index = port->id == AIROHA_GDM4_IDX ? 7 : port->id;
++      for (i = 0; i < eth->soc->num_ppe; i++)
++              airoha_fe_rmw(eth, REG_PPE_MTU(i, index),
++                            FP_EGRESS_MTU_MASK(index),
++                            __field_prep(FP_EGRESS_MTU_MASK(index), mtu));
++}
++
+ 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;
++      u32 pse_port = FE_PSE_PORT_PPE1;
++      int err;
+       if (port->id == AIROHA_GDM2_IDX && airoha_is_lan_gdm_dev(dev)) {
+               /* GDM2 can be used just as wan */
+@@ -1937,19 +1966,12 @@ static int airoha_dev_open(struct net_de
+               airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+                               GDM_STAG_EN_MASK);
+-      cur_len = airoha_fe_get(qdma->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
+-               * MTU of already running devices. This is required to allow
+-               * multiple net_devices with different MTUs to share the same
+-               * GDM port.
+-               */
+-              airoha_fe_rmw(qdma->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_set_port_mtu(qdma->eth, port);
++      if (!airoha_is_lan_gdm_dev(dev))
++              airoha_fe_rmw(qdma->eth, REG_WAN_MTU0,
++                            WAN_MTU0_MASK,
++                            FIELD_PREP(WAN_MTU0_MASK, netdev->mtu));
++
+       port->users++;
+       airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+@@ -1966,30 +1988,6 @@ static int airoha_dev_open(struct net_de
+       return 0;
+ }
+-static void airoha_set_port_mtu(struct airoha_eth *eth,
+-                              struct airoha_gdm_port *port)
+-{
+-      u32 len = 0;
+-      int i;
+-
+-      for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
+-              struct airoha_gdm_dev *dev = port->devs[i];
+-              struct net_device *netdev;
+-
+-              if (!dev)
+-                      continue;
+-
+-              netdev = dev->dev;
+-              if (netif_running(netdev))
+-                      len = max_t(u32, len, netdev->mtu);
+-      }
+-      len += ETH_HLEN + ETH_FCS_LEN;
+-
+-      airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+-                    GDM_LONG_LEN_MASK,
+-                    FIELD_PREP(GDM_LONG_LEN_MASK, len));
+-}
+-
+ static int airoha_dev_stop(struct net_device *netdev)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+@@ -2073,10 +2071,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);
+-      airoha_fe_rmw(eth, REG_GDM_LEN_CFG(AIROHA_GDM2_IDX),
+-                    GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+-                    FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+-                    FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_MTU));
+       /* Forward the traffic to the proper GDM port */
+       pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
+                                              : FE_PSE_PORT_GDM4;
+@@ -2600,6 +2594,9 @@ static int airoha_dev_set_wan_flag(struc
+               default:
+                       return -EOPNOTSUPP;
+               }
++              airoha_fe_rmw(eth, REG_WAN_MTU0,
++                            WAN_MTU0_MASK,
++                            FIELD_PREP(WAN_MTU0_MASK, netdev->mtu));
+       } else {
+               switch (port->id) {
+               case AIROHA_GDM3_IDX:
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -705,6 +705,7 @@ int airoha_get_fe_port(struct airoha_gdm
+ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+                            struct airoha_gdm_dev *dev);
++void airoha_set_port_mtu(struct airoha_eth *eth, struct airoha_gdm_port *port);
+ 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
+@@ -116,8 +116,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++) {
+-              int p;
+-
+               airoha_fe_wr(eth, REG_PPE_TB_BASE(i),
+                            ppe->foe_dma + sram_tb_size);
+@@ -167,15 +165,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 (p = 0; p < ARRAY_SIZE(eth->ports); p++)
+-                      airoha_fe_rmw(eth, REG_PPE_MTU(i, p),
+-                                    FP0_EGRESS_MTU_MASK |
+-                                    FP1_EGRESS_MTU_MASK,
+-                                    FIELD_PREP(FP0_EGRESS_MTU_MASK,
+-                                               AIROHA_MAX_MTU) |
+-                                    FIELD_PREP(FP1_EGRESS_MTU_MASK,
+-                                               AIROHA_MAX_MTU));
+       }
+       for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+@@ -185,6 +174,7 @@ static void airoha_ppe_hw_init(struct ai
+               if (!port)
+                       continue;
++              airoha_set_port_mtu(eth, port);
+               for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+                       struct airoha_gdm_dev *dev = port->devs[j];
+                       u8 fport;
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -341,9 +341,8 @@
+ #define PPE_SRAM_TABLE_EN_MASK                        BIT(0)
+ #define REG_PPE_MTU_BASE(_n)                  (((_n) ? PPE2_BASE : PPE1_BASE) + 0x304)
+-#define REG_PPE_MTU(_m, _n)                   (REG_PPE_MTU_BASE(_m) + ((_n) << 2))
+-#define FP1_EGRESS_MTU_MASK                   GENMASK(29, 16)
+-#define FP0_EGRESS_MTU_MASK                   GENMASK(13, 0)
++#define REG_PPE_MTU(_m, _n)                   (REG_PPE_MTU_BASE(_m) + (((_n) / 2) << 2))
++#define FP_EGRESS_MTU_MASK(_n)                        GENMASK(13 + (((_n) % 2) << 4), ((_n) % 2) << 4)
+ #define REG_PPE_RAM_CTRL(_n)                  (((_n) ? PPE2_BASE : PPE1_BASE) + 0x31c)
+ #define PPE_SRAM_CTRL_ACK_MASK                        BIT(31)
+@@ -404,6 +403,10 @@
+ #define FC_ID_OF_SRC_PORT_MASK(_n)    GENMASK(4 + ((_n) << 3), ((_n) << 3))
+ #define FC_MAP6_DEF_VALUE             0x1b1a1918
++#define REG_WAN_MTU0                  0x2300
++#define WAN_MTU1_MASK                 GENMASK(29, 16)
++#define WAN_MTU0_MASK                 GENMASK(13, 0)
++
+ #define REG_CDM5_RX_OQ1_DROP_CNT      0x29d4
+ /* QDMA */
diff --git a/target/linux/airoha/patches-6.12/920-14-net-airoha-Better-handle-MIB-for-GDM-with-multiple-p.patch b/target/linux/airoha/patches-6.12/920-14-net-airoha-Better-handle-MIB-for-GDM-with-multiple-p.patch
new file mode 100644 (file)
index 0000000..7e29879
--- /dev/null
@@ -0,0 +1,415 @@
+From 9652322e0b47eacfef497828f6476a2a3169fd13 Mon Sep 17 00:00:00 2001
+Message-ID: <9652322e0b47eacfef497828f6476a2a3169fd13.1779351672.git.lorenzo@kernel.org>
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 17 Jan 2026 14:46:12 +0100
+Subject: [PATCH] net: airoha: Better handle MIB for GDM with multiple port
+ attached
+
+In the context of a GDM that can have multiple port attached (GDM3/4)
+the HW counter (MIB) are global for the GDM port. This cause duplicated
+stats reported to the kernel for the related interface.
+
+The SoC supports a split MIB feature where each counter is tracked based
+on the relevant HW channel (NBQ) to account for this scenario and
+provide a way to select the related counter on accessing the MIB
+registers.
+
+Enable this feature for GDM3 and GDM4 and configure the relevant HW
+channel before updating the HW stats to report correct HW counter to the
+kernel for the related interface.
+
+Also move the stats struct from port to dev since HW counter are
+now specific to the network interface instead of the GDM port.
+
+Co-developed-by: Brown Huang <Brown.huang@airoha.com>
+Signed-off-by: Brown Huang <Brown.huang@airoha.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 191 +++++++++++++----------
+ drivers/net/ethernet/airoha/airoha_eth.h |   7 +-
+ 2 files changed, 112 insertions(+), 86 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -611,6 +611,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);
++      /* Enable split for MIB counters for GDM3 and GDM4 */
++      airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM3_IDX),
++                    FE_GDM_TX_MIB_SPLIT_EN_MASK |
++                    FE_GDM_RX_MIB_SPLIT_EN_MASK);
++      airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM4_IDX),
++                    FE_GDM_TX_MIB_SPLIT_EN_MASK |
++                    FE_GDM_RX_MIB_SPLIT_EN_MASK);
++
+       airoha_fe_crsn_qsel_init(eth);
+       airoha_fe_clear(eth, REG_FE_CPORT_CFG, FE_CPORT_QUEUE_XFC_MASK);
+@@ -1758,149 +1766,169 @@ static void airoha_qdma_stop_napi(struct
+       }
+ }
+-static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
++static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
+ {
+       struct airoha_gdm_port *port = dev->port;
+       struct airoha_eth *eth = dev->eth;
+       u32 val, i = 0;
+-      spin_lock(&port->stats.lock);
+-      u64_stats_update_begin(&port->stats.syncp);
++      u64_stats_update_begin(&dev->stats.syncp);
++
++      /* Read relevant MIB for GDM with multiple port attached */
++      if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
++              airoha_fe_rmw(eth, REG_FE_GDM_MIB_CFG(port->id),
++                            FE_TX_MIB_ID_MASK | FE_RX_MIB_ID_MASK,
++                            FIELD_PREP(FE_TX_MIB_ID_MASK, dev->nbq) |
++                            FIELD_PREP(FE_RX_MIB_ID_MASK, dev->nbq));
+       /* TX */
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
+-      port->stats.tx_ok_pkts += ((u64)val << 32);
++      dev->stats.tx_ok_pkts += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
+-      port->stats.tx_ok_pkts += val;
++      dev->stats.tx_ok_pkts += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
+-      port->stats.tx_ok_bytes += ((u64)val << 32);
++      dev->stats.tx_ok_bytes += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
+-      port->stats.tx_ok_bytes += val;
++      dev->stats.tx_ok_bytes += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
+-      port->stats.tx_drops += val;
++      dev->stats.tx_drops += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
+-      port->stats.tx_broadcast += val;
++      dev->stats.tx_broadcast += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
+-      port->stats.tx_multicast += val;
++      dev->stats.tx_multicast += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
+-      port->stats.tx_len[i] += val;
++      dev->stats.tx_len[i] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
+-      port->stats.tx_len[i] += ((u64)val << 32);
++      dev->stats.tx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
+-      port->stats.tx_len[i] += ((u64)val << 32);
++      dev->stats.tx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
+-      port->stats.tx_len[i] += ((u64)val << 32);
++      dev->stats.tx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
+-      port->stats.tx_len[i] += ((u64)val << 32);
++      dev->stats.tx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
+-      port->stats.tx_len[i] += ((u64)val << 32);
++      dev->stats.tx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
+-      port->stats.tx_len[i] += ((u64)val << 32);
++      dev->stats.tx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
+-      port->stats.tx_len[i++] += val;
++      dev->stats.tx_len[i++] += val;
+       /* RX */
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
+-      port->stats.rx_ok_pkts += ((u64)val << 32);
++      dev->stats.rx_ok_pkts += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
+-      port->stats.rx_ok_pkts += val;
++      dev->stats.rx_ok_pkts += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
+-      port->stats.rx_ok_bytes += ((u64)val << 32);
++      dev->stats.rx_ok_bytes += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
+-      port->stats.rx_ok_bytes += val;
++      dev->stats.rx_ok_bytes += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
+-      port->stats.rx_drops += val;
++      dev->stats.rx_drops += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
+-      port->stats.rx_broadcast += val;
++      dev->stats.rx_broadcast += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
+-      port->stats.rx_multicast += val;
++      dev->stats.rx_multicast += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
+-      port->stats.rx_errors += val;
++      dev->stats.rx_errors += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
+-      port->stats.rx_crc_error += val;
++      dev->stats.rx_crc_error += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
+-      port->stats.rx_over_errors += val;
++      dev->stats.rx_over_errors += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
+-      port->stats.rx_fragment += val;
++      dev->stats.rx_fragment += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
+-      port->stats.rx_jabber += val;
++      dev->stats.rx_jabber += val;
+       i = 0;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
+-      port->stats.rx_len[i] += val;
++      dev->stats.rx_len[i] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
+-      port->stats.rx_len[i] += ((u64)val << 32);
++      dev->stats.rx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
+-      port->stats.rx_len[i] += ((u64)val << 32);
++      dev->stats.rx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
+-      port->stats.rx_len[i] += ((u64)val << 32);
++      dev->stats.rx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
+-      port->stats.rx_len[i] += ((u64)val << 32);
++      dev->stats.rx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
+-      port->stats.rx_len[i] += ((u64)val << 32);
++      dev->stats.rx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
+-      port->stats.rx_len[i] += ((u64)val << 32);
++      dev->stats.rx_len[i] += ((u64)val << 32);
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
+       val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
+-      port->stats.rx_len[i++] += val;
++      dev->stats.rx_len[i++] += val;
++
++      u64_stats_update_end(&dev->stats.syncp);
++}
+-      /* reset mib counters */
+-      airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
++static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
++{
++      struct airoha_gdm_port *port = dev->port;
++      int i;
++
++      spin_lock(&port->stats_lock);
++
++      for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
++              if (port->devs[i])
++                      airoha_dev_get_hw_stats(port->devs[i]);
++      }
++
++      /* Reset MIB counters */
++      airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
+                     FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
+-      u64_stats_update_end(&port->stats.syncp);
+-      spin_unlock(&port->stats.lock);
++      spin_unlock(&port->stats_lock);
+ }
+ void airoha_set_port_mtu(struct airoha_eth *eth, struct airoha_gdm_port *port)
+@@ -2228,23 +2256,22 @@ static void airoha_dev_get_stats64(struc
+                                  struct rtnl_link_stats64 *storage)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+       unsigned int start;
+       airoha_update_hw_stats(dev);
+       do {
+-              start = u64_stats_fetch_begin(&port->stats.syncp);
+-              storage->rx_packets = port->stats.rx_ok_pkts;
+-              storage->tx_packets = port->stats.tx_ok_pkts;
+-              storage->rx_bytes = port->stats.rx_ok_bytes;
+-              storage->tx_bytes = port->stats.tx_ok_bytes;
+-              storage->multicast = port->stats.rx_multicast;
+-              storage->rx_errors = port->stats.rx_errors;
+-              storage->rx_dropped = port->stats.rx_drops;
+-              storage->tx_dropped = port->stats.tx_drops;
+-              storage->rx_crc_errors = port->stats.rx_crc_error;
+-              storage->rx_over_errors = port->stats.rx_over_errors;
+-      } while (u64_stats_fetch_retry(&port->stats.syncp, start));
++              start = u64_stats_fetch_begin(&dev->stats.syncp);
++              storage->rx_packets = dev->stats.rx_ok_pkts;
++              storage->tx_packets = dev->stats.tx_ok_pkts;
++              storage->rx_bytes = dev->stats.rx_ok_bytes;
++              storage->tx_bytes = dev->stats.tx_ok_bytes;
++              storage->multicast = dev->stats.rx_multicast;
++              storage->rx_errors = dev->stats.rx_errors;
++              storage->rx_dropped = dev->stats.rx_drops;
++              storage->tx_dropped = dev->stats.tx_drops;
++              storage->rx_crc_errors = dev->stats.rx_crc_error;
++              storage->rx_over_errors = dev->stats.rx_over_errors;
++      } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
+ }
+ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
+@@ -2639,20 +2666,19 @@ static void airoha_ethtool_get_mac_stats
+                                        struct ethtool_eth_mac_stats *stats)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+       unsigned int start;
+       airoha_update_hw_stats(dev);
+       do {
+-              start = u64_stats_fetch_begin(&port->stats.syncp);
+-              stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
+-              stats->OctetsTransmittedOK = port->stats.tx_ok_bytes;
+-              stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
+-              stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
+-              stats->FramesReceivedOK = port->stats.rx_ok_pkts;
+-              stats->OctetsReceivedOK = port->stats.rx_ok_bytes;
+-              stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
+-      } while (u64_stats_fetch_retry(&port->stats.syncp, start));
++              start = u64_stats_fetch_begin(&dev->stats.syncp);
++              stats->FramesTransmittedOK = dev->stats.tx_ok_pkts;
++              stats->OctetsTransmittedOK = dev->stats.tx_ok_bytes;
++              stats->MulticastFramesXmittedOK = dev->stats.tx_multicast;
++              stats->BroadcastFramesXmittedOK = dev->stats.tx_broadcast;
++              stats->FramesReceivedOK = dev->stats.rx_ok_pkts;
++              stats->OctetsReceivedOK = dev->stats.rx_ok_bytes;
++              stats->BroadcastFramesReceivedOK = dev->stats.rx_broadcast;
++      } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
+ }
+ static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
+@@ -2672,8 +2698,7 @@ airoha_ethtool_get_rmon_stats(struct net
+                             const struct ethtool_rmon_hist_range **ranges)
+ {
+       struct airoha_gdm_dev *dev = netdev_priv(netdev);
+-      struct airoha_gdm_port *port = dev->port;
+-      struct airoha_hw_stats *hw_stats = &port->stats;
++      struct airoha_hw_stats *hw_stats = &dev->stats;
+       unsigned int start;
+       BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
+@@ -2686,7 +2711,7 @@ airoha_ethtool_get_rmon_stats(struct net
+       do {
+               int i;
+-              start = u64_stats_fetch_begin(&port->stats.syncp);
++              start = u64_stats_fetch_begin(&dev->stats.syncp);
+               stats->fragments = hw_stats->rx_fragment;
+               stats->jabbers = hw_stats->rx_jabber;
+               for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
+@@ -2694,7 +2719,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(&port->stats.syncp, start));
++      } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
+ }
+ static int airoha_ethtool_set_priv_flags(struct net_device *netdev, u32 flags)
+@@ -3702,6 +3727,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;
+@@ -3750,9 +3776,8 @@ static int airoha_alloc_gdm_port(struct
+       if (!port)
+               return -ENOMEM;
+-      u64_stats_init(&port->stats.syncp);
+-      spin_lock_init(&port->stats.lock);
+       port->id = id;
++      spin_lock_init(&port->stats_lock);
+       eth->ports[p] = port;
+       err = airoha_metadata_dst_alloc(port);
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -226,8 +226,6 @@ struct airoha_tx_irq_queue {
+ };
+ struct airoha_hw_stats {
+-      /* protect concurrent hw_stats accesses */
+-      spinlock_t lock;
+       struct u64_stats_sync syncp;
+       /* get_stats64 */
+@@ -571,6 +569,8 @@ struct airoha_gdm_dev {
+       u32 flags;
+       int nbq;
++
++      struct airoha_hw_stats stats;
+ };
+ struct airoha_gdm_port {
+@@ -578,7 +578,8 @@ struct airoha_gdm_port {
+       int id;
+       int users;
+-      struct airoha_hw_stats stats;
++      /* protect concurrent hw_stats accesses */
++      spinlock_t stats_lock;
+       struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
+ };
diff --git a/target/linux/airoha/patches-6.12/920-15-net-airoha-fix-wrong-airoha_get_fe_port.patch b/target/linux/airoha/patches-6.12/920-15-net-airoha-fix-wrong-airoha_get_fe_port.patch
new file mode 100644 (file)
index 0000000..85d99db
--- /dev/null
@@ -0,0 +1,40 @@
+From 1e596dac503da44a9fcd3e2654a4963db3e2ee6a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 28 Jan 2026 02:38:24 +0100
+Subject: [PATCH] net: airoha: fix wrong airoha_get_fe_port()
+
+It seems the SDK where the airoha_get_fe_port() logic was taken was
+actually wrong and the AN7583 SoC doesn't have such difference. Instead
+it does follow the same port order of AN7581 SoC.
+
+Drop the switch case and apply the same condition on both AN7581 and
+AN7583 SoC.
+
+Fixes: e4e5ce823bdd ("net: airoha: Add AN7583 SoC support")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 14 ++------------
+ 1 file changed, 2 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2353,17 +2353,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;
+-      struct airoha_eth *eth = dev->eth;
+-      switch (eth->soc->version) {
+-      case 0x7583:
+-              return port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
+-                                                 : port->id;
+-      case 0x7581:
+-      default:
+-              return port->id == AIROHA_GDM4_IDX ? FE_PSE_PORT_GDM4
+-                                                 : port->id;
+-      }
++      return port->id == AIROHA_GDM4_IDX ? FE_PSE_PORT_GDM4
++                                         : port->id;
+ }
+ static int airoha_dev_set_features(struct net_device *netdev,