GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware QoS offload to function. Without it, HTB and ETS offload on these ports do not work. Previously, GDM3/GDM4 ports were automatically configured as WAN with GDM2 loopback enabled during ndo_init(). Move this setup to be performed on demand when QoS offload is created or destroyed. GDM3 and GDM4 now default to LAN mode at init time, while GDM2 is always WAN. Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables GDM2 loopback, with proper rollback on failure. Hook the counterpart airoha_disable_qos_for_gdm34() into TC_HTB_DESTROY to restore LAN mode when the offloaded qdisc is torn down. Add airoha_disable_gdm2_loopback() as the teardown counterpart of the existing airoha_enable_gdm2_loopback(). Move QDMA TX/RX DMA enable from airoha_dev_open() to airoha_probe() and the corresponding disable from airoha_dev_stop() to airoha_qdma_cleanup(), removing the per-QDMA atomic refcount that was needed for the open/close path. DMA is now always active from probe to remove. Tested-by: Madhur Agrawal Signed-off-by: Lorenzo Bianconi --- Changes in v3: - Do not introduce ethtool private flags support to configure LAN/WAN for GDM3/4 and rely on tc qdisc offload for it instead. - Set GDM3/4 ports as LAN by default. - Move QDMA TX/RX DMA enable from airoha_dev_open() to airoha_probe() and the corresponding disable from airoha_dev_stop() to airoha_qdma_cleanup(). - Link to v2: https://lore.kernel.org/r/20260607-airoha-ethtool-priv_flags-v2-1-742c7aa1e182@kernel.org Changes in v2: - Rework airoha_dev_set_wan_flag routine - Enable GDM_STRIP_CRC_MASK in airoha_disable_gdm2_loopback() - Do not always reset REG_SRC_PORT_FC_MAP6 in airoha_disable_gdm2_loopback() but use the same condition used in airoha_enable_gdm2_loopback(). - Link to v1: https://lore.kernel.org/r/20260606-airoha-ethtool-priv_flags-v1-1-401b2c9fe9f1@kernel.org --- drivers/net/ethernet/airoha/airoha_eth.c | 177 ++++++++++++++++++++++-------- drivers/net/ethernet/airoha/airoha_eth.h | 2 - drivers/net/ethernet/airoha/airoha_regs.h | 1 + 3 files changed, 131 insertions(+), 49 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 5a8e84fa9918..9bfb1e5b2b54 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -1516,6 +1516,10 @@ static void airoha_qdma_cleanup(struct airoha_qdma *qdma) { int i; + airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG, + GLOBAL_CFG_TX_DMA_EN_MASK | + GLOBAL_CFG_RX_DMA_EN_MASK); + for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) { if (!qdma->q_rx[i].ndesc) continue; @@ -1806,11 +1810,6 @@ static int airoha_dev_open(struct net_device *netdev) } port->users++; - airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG, - GLOBAL_CFG_TX_DMA_EN_MASK | - GLOBAL_CFG_RX_DMA_EN_MASK); - atomic_inc(&qdma->users); - if (!airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(qdma->eth, 1)) pse_port = FE_PSE_PORT_PPE2; @@ -1863,19 +1862,6 @@ static int airoha_dev_stop(struct net_device *netdev) 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, - GLOBAL_CFG_TX_DMA_EN_MASK | - GLOBAL_CFG_RX_DMA_EN_MASK); - - for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) { - if (!qdma->q_tx[i].ndesc) - continue; - - airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]); - } - } - return 0; } @@ -1960,6 +1946,49 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev) return 0; } +static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev) +{ + struct airoha_gdm_port *port = dev->port; + struct airoha_eth *eth = dev->eth; + int i, src_port; + u32 pse_port; + + src_port = eth->soc->ops.get_sport(dev->port, dev->nbq); + if (src_port < 0) + return src_port; + + airoha_fe_clear(eth, + REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)), + SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK)); + + airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + GDM_STRIP_CRC_MASK); + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + FE_PSE_PORT_DROP); + airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX), + LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK); + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + pse_port); + + airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK, + FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX)); + + for (i = 0; i < eth->soc->num_ppe; i++) + airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX), + DFT_CPORT_MASK(AIROHA_GDM2_IDX)); + + /* Enable VIP and IFC for GDM2 */ + airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX)); + airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX)); + + if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) + 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) { @@ -2002,40 +2031,14 @@ 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: { - 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 */ + if (port->id == AIROHA_GDM2_IDX) { + /* GDM2 is always used as WAN */ dev->flags |= AIROHA_PRIV_F_WAN; - break; - default: - break; } 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_enable_gdm2_loopback(dev); - if (err) - return err; - } - return 0; } @@ -3008,6 +3011,79 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev, return 0; } +static int airoha_enable_qos_for_gdm34(struct net_device *netdev) +{ + struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + u32 pse_port; + int err; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) { + /* HW QoS is always supported by GDM1 and GDM2 */ + return 0; + } + + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */ + return 0; + + /* Verify the WAN device is not already configured */ + if (airoha_get_wan_gdm_dev(dev->eth)) + return -EBUSY; + + dev->flags |= AIROHA_PRIV_F_WAN; + airoha_dev_set_qdma(dev); + err = airoha_enable_gdm2_loopback(dev); + if (err) + goto error_disable_wan; + + err = airoha_set_macaddr(dev, netdev->dev_addr); + if (err) + goto error_disable_loopback; + + pse_port = airoha_ppe_is_enabled(dev->eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(dev->eth, REG_GDM_FWD_CFG(port->id), + pse_port); + + return 0; + +error_disable_loopback: + /* Restore previous LAN configuration */ + airoha_disable_gdm2_loopback(dev); +error_disable_wan: + dev->flags &= ~AIROHA_PRIV_F_WAN; + airoha_dev_set_qdma(dev); + + return err; +} + +static void airoha_disable_qos_for_gdm34(struct net_device *netdev) +{ + struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + int err; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) { + return; + } + + if (airoha_is_lan_gdm_dev(dev)) /* Already disabled */ + return; + + err = airoha_disable_gdm2_loopback(dev); + if (err) + netdev_warn(netdev, + "failed disabling GDM2 loopback: %d\n", err); + + dev->flags &= ~AIROHA_PRIV_F_WAN; + airoha_dev_set_qdma(dev); + airoha_set_macaddr(dev, netdev->dev_addr); + airoha_set_gdm_port_fwd_cfg(dev->eth, REG_GDM_FWD_CFG(port->id), + FE_PSE_PORT_PPE1); +} + static int airoha_tc_htb_destroy(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); @@ -3016,6 +3092,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev) for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS) airoha_tc_remove_htb_queue(netdev, q); + airoha_disable_qos_for_gdm34(netdev); + return 0; } @@ -3040,7 +3118,7 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev, { switch (opt->command) { case TC_HTB_CREATE: - break; + return airoha_enable_qos_for_gdm34(dev); case TC_HTB_DESTROY: return airoha_tc_htb_destroy(dev); case TC_HTB_NODE_MODIFY: @@ -3414,6 +3492,11 @@ static int airoha_probe(struct platform_device *pdev) if (err) goto error_napi_stop; + for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) + airoha_qdma_set(ð->qdma[i], REG_QDMA_GLOBAL_CFG, + GLOBAL_CFG_TX_DMA_EN_MASK | + GLOBAL_CFG_RX_DMA_EN_MASK); + return 0; error_napi_stop: diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 8f42973f9cf5..cd0706a0c07f 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -526,8 +526,6 @@ struct airoha_qdma { struct airoha_eth *eth; void __iomem *regs; - atomic_t users; - struct airoha_irq_bank irq_banks[AIROHA_MAX_NUM_IRQ_BANKS]; struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ]; diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h index 436f3c8779c1..4e17dfbcf2b8 100644 --- a/drivers/net/ethernet/airoha/airoha_regs.h +++ b/drivers/net/ethernet/airoha/airoha_regs.h @@ -376,6 +376,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 --- base-commit: 903db046d5579bef0ea699eae4b279dd6455fc9f change-id: 20260606-airoha-ethtool-priv_flags-b6aa70caa780 Best regards, -- Lorenzo Bianconi