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 index 0000000000..055dfbcefe --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-01-net-airoha-Introduce-airoha_gdm_dev-struct.patch @@ -0,0 +1,1012 @@ +From e15783f7c987e199ecf80b3d858ed5a86d33c508 Mon Sep 17 00:00:00 2001 +Message-ID: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 = ð->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 - ð->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(ð->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 index 0000000000..847e8f1a6b --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-02-net-airoha-Move-airoha_qdma-pointer-in-airoha_gdm_de.patch @@ -0,0 +1,495 @@ +From f62cea6483cc55360863d66300790a5fb9de5f7c Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 = ð->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 = ð->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 - ð->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 - ð->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 index 0000000000..c3f76dfffa --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-03-net-airoha-Rely-on-airoha_gdm_dev-pointer-in-airoha_.patch @@ -0,0 +1,73 @@ +From 32bfd008c19f9ad55514181d8cd02e14bf384475 Mon Sep 17 00:00:00 2001 +Message-ID: <32bfd008c19f9ad55514181d8cd02e14bf384475.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 = ð->qdma[!airoha_is_lan_gdm_port(port)]; ++ dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; + dev->dev->irq = dev->qdma->irq_banks[0].irq; + 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 index 0000000000..da39161d7c --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-04-net-airoha-Move-qos_sq_bmap-in-airoha_gdm_dev-struct.patch @@ -0,0 +1,184 @@ +From 634d75285db77f3385aa85a1bf2b185396225100 Mon Sep 17 00:00:00 2001 +Message-ID: <634d75285db77f3385aa85a1bf2b185396225100.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +--- + 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 index 0000000000..b6b7635359 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-05-net-airoha-Move-cpu-fwd-_tx_packets-in-airoha_gdm_de.patch @@ -0,0 +1,73 @@ +From 00272dbf6a52241a21145631f22dc5f03891078b Mon Sep 17 00:00:00 2001 +Message-ID: <00272dbf6a52241a21145631f22dc5f03891078b.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +--- + 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 index 0000000000..23e88cb0b2 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-06-net-airoha-Support-multiple-net_devices-for-a-single.patch @@ -0,0 +1,714 @@ +From 8eb0a71bfbe92b6fbc668c5d9ebdcbf6523a89ad Mon Sep 17 00:00:00 2001 +Message-ID: <8eb0a71bfbe92b6fbc668c5d9ebdcbf6523a89ad.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 = ð->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, ð->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 + + #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 index 0000000000..ceaedae38f --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-07-net-airoha-Do-not-stop-GDM-port-if-it-is-shared.patch @@ -0,0 +1,132 @@ +From de856a5b802cf030c8e242e98df3bc88446a4ea1 Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 index 0000000000..e5e60c05fb --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-08-net-airoha-Introduce-WAN-device-flag.patch @@ -0,0 +1,174 @@ +From c4f3077948eda05a6b9d1a11304d82c3e0300151 Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 = ð->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 index 0000000000..55a32713ba --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-09-net-airoha-Support-multiple-LAN-WAN-interfaces-for-h.patch @@ -0,0 +1,161 @@ +From 144f0e6e55896625e3411aad02399a5ebb48d8f9 Mon Sep 17 00:00:00 2001 +Message-ID: <144f0e6e55896625e3411aad02399a5ebb48d8f9.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 index 0000000000..98866e0ad1 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-10-net-airoha-Rename-airoha_set_gdm2_loopback-in-airoha.patch @@ -0,0 +1,37 @@ +From 917f959d54efd09719bd5047aa4b9543615e131e Mon Sep 17 00:00:00 2001 +Message-ID: <917f959d54efd09719bd5047aa4b9543615e131e.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +--- + 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 index 0000000000..a5f74d6351 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-12-net-airoha-Add-ethtool-priv_flags-callbacks.patch @@ -0,0 +1,246 @@ +From e928621a0bbd010b75624c77105ce31f2b8d2faf Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Lorenzo Bianconi +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 +Signed-off-by: Lorenzo Bianconi +--- + 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 index 0000000000..04b4256d28 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-13-net-airoha-Rework-MTU-configuration.patch @@ -0,0 +1,223 @@ +From 99e204d255c9b47a188dd79762b9406d9c5fed9b Mon Sep 17 00:00:00 2001 +Message-ID: <99e204d255c9b47a188dd79762b9406d9c5fed9b.1779348625.git.lorenzo@kernel.org> +In-Reply-To: +References: +From: Lorenzo Bianconi +Date: Wed, 20 May 2026 17:16:39 +0200 +Subject: [PATCH 13/13] net: airoha: Rework MTU configuration + +Signed-off-by: Lorenzo Bianconi +--- + 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 index 0000000000..7e29879972 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-14-net-airoha-Better-handle-MIB-for-GDM-with-multiple-p.patch @@ -0,0 +1,415 @@ +From 9652322e0b47eacfef497828f6476a2a3169fd13 Mon Sep 17 00:00:00 2001 +Message-ID: <9652322e0b47eacfef497828f6476a2a3169fd13.1779351672.git.lorenzo@kernel.org> +From: Christian Marangi +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 +Signed-off-by: Brown Huang +Signed-off-by: Christian Marangi +--- + 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 index 0000000000..85d99dbcc7 --- /dev/null +++ b/target/linux/airoha/patches-6.12/920-15-net-airoha-fix-wrong-airoha_get_fe_port.patch @@ -0,0 +1,40 @@ +From 1e596dac503da44a9fcd3e2654a4963db3e2ee6a Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + 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,