* [PATCH net-next v5 0/3] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand
@ 2026-06-11 21:55 Lorenzo Bianconi
2026-06-11 21:55 ` [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter Lorenzo Bianconi
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal,
Alexander Lobakin
Add the capability to configure GDM3/GDM4 as WAN/LAN on demand when QoS
offload is created or destroyed.
Make dev->qdma an RCU pointer so the TX path can safely dereference it
without holding RTNL.
Introduce airoha_qdma_start() and airoha_qdma_stop() helpers.
---
Changes in v5:
- Add patch 1/3: use int instead of atomic_t for qdma users counter
- Protect dev->flags with flow_offload_mutex mutex.
- Introduce AIROHA_PRIV_F_QOS in order to handle better WAN/LAN
switching.
- Link to v4: https://lore.kernel.org/r/20260610-airoha-ethtool-priv_flags-v4-0-60e89cf28fea@kernel.org
Changes in v4:
- Move back QDMA TX/RX DMA enable to airoha_dev_open()/airoha_dev_stop().
- Configure GDM3/4 as WAN if GDM2 is not available in ndo_init()
callback.
- Protect qdma pointer in airoha_gdm_dev struct using RCU.
- Rely on rtnl_dereference() to access qdma pointer in the control path.
- Add airoha_qdma_start() and airoha_qdma_stop() utility routines in
patch 1/2
- Link to v3: https://lore.kernel.org/r/20260608-airoha-ethtool-priv_flags-v3-1-3e8e3dc3f715@kernel.org
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
---
Lorenzo Bianconi (3):
net: airoha: use int instead of atomic_t for qdma users counter
net: airoha: refactor QDMA start/stop into reusable helpers
net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
drivers/net/ethernet/airoha/airoha_eth.c | 287 +++++++++++++++++++++++++-----
drivers/net/ethernet/airoha/airoha_eth.h | 15 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 9 +-
drivers/net/ethernet/airoha/airoha_regs.h | 1 +
4 files changed, 262 insertions(+), 50 deletions(-)
---
base-commit: dad4d4b92a9b9f0edb8c66deda049da1b62f6089
change-id: 20260606-airoha-ethtool-priv_flags-b6aa70caa780
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter 2026-06-11 21:55 [PATCH net-next v5 0/3] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand Lorenzo Bianconi @ 2026-06-11 21:55 ` Lorenzo Bianconi 2026-06-13 9:06 ` Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 2/3] net: airoha: refactor QDMA start/stop into reusable helpers Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload Lorenzo Bianconi 2 siblings, 1 reply; 7+ messages in thread From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Lorenzo Bianconi Cc: linux-arm-kernel, linux-mediatek, netdev QDMA users counter is always accessed holding RTNL lock so we do not require atomic_t for it. Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> --- drivers/net/ethernet/airoha/airoha_eth.c | 4 ++-- drivers/net/ethernet/airoha/airoha_eth.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 504247c8bae9..9eb9b6a139b3 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -1812,7 +1812,7 @@ static int airoha_dev_open(struct net_device *netdev) airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG, GLOBAL_CFG_TX_DMA_EN_MASK | GLOBAL_CFG_RX_DMA_EN_MASK); - atomic_inc(&qdma->users); + qdma->users++; if (!airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(qdma->eth, 1)) @@ -1866,7 +1866,7 @@ 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)) { + if (!--qdma->users) { airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG, GLOBAL_CFG_TX_DMA_EN_MASK | GLOBAL_CFG_RX_DMA_EN_MASK); diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 8f42973f9cf5..24fd8dcf7fca 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -526,7 +526,7 @@ struct airoha_qdma { struct airoha_eth *eth; void __iomem *regs; - atomic_t users; + int users; struct airoha_irq_bank irq_banks[AIROHA_MAX_NUM_IRQ_BANKS]; -- 2.54.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter 2026-06-11 21:55 ` [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter Lorenzo Bianconi @ 2026-06-13 9:06 ` Lorenzo Bianconi 0 siblings, 0 replies; 7+ messages in thread From: Lorenzo Bianconi @ 2026-06-13 9:06 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni Cc: linux-arm-kernel, linux-mediatek, netdev [-- Attachment #1: Type: text/plain, Size: 3901 bytes --] > QDMA users counter is always accessed holding RTNL lock so we do not > require atomic_t for it. > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> > --- > drivers/net/ethernet/airoha/airoha_eth.c | 4 ++-- > drivers/net/ethernet/airoha/airoha_eth.h | 2 +- > 2 files changed, 3 insertions(+), 3 deletions(-) > > diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c > index 504247c8bae9..9eb9b6a139b3 100644 > --- a/drivers/net/ethernet/airoha/airoha_eth.c > +++ b/drivers/net/ethernet/airoha/airoha_eth.c > @@ -1812,7 +1812,7 @@ static int airoha_dev_open(struct net_device *netdev) > airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG, > GLOBAL_CFG_TX_DMA_EN_MASK | > GLOBAL_CFG_RX_DMA_EN_MASK); > - atomic_inc(&qdma->users); > + qdma->users++; > > if (!airoha_is_lan_gdm_dev(dev) && > airoha_ppe_is_enabled(qdma->eth, 1)) > @@ -1866,7 +1866,7 @@ 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)) { > + if (!--qdma->users) { > airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG, > GLOBAL_CFG_TX_DMA_EN_MASK | > GLOBAL_CFG_RX_DMA_EN_MASK); commenting on sashiko's report: https://sashiko.dev/#/patchset/20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1%40kernel.org - This is a pre-existing issue, but by not cleaning the TX queues when qdma->users > 0, does this leave the stopped device's packets in flight? When airoha_dev_stop() is called, it resets the device's BQL counters via netdev_tx_reset_subqueue(). By skipping the cleanup when other devices are still sharing this QDMA ring, any pending skbs originating from the stopped device are left to linger in the hardware TX ring. Because the hardware continues to process the shared ring on behalf of the remaining interfaces, the active NAPI poll will process their completions in airoha_qdma_tx_napi_poll(): if (skb) { struct netdev_queue *txq; txq = skb_get_tx_queue(skb->dev, skb); netdev_tx_completed_queue(txq, 1, skb->len); dev_kfree_skb_any(skb); } Can this cause an unsigned underflow in dql->num_queued since the BQL state was already reset to 0 by ndo_stop? - This issue is valid, but as pointed out by sashiko, this problem has not been introduced by this patch. In particular, I think we should remove netdev_tx_reset_subqueue() in airoha_dev_stop() and run netdev_tx_completed_queue() in airoha_qdma_cleanup_tx_queue(). We need to pay attention to not loop over already unregistered net_devices in airoha_qdma_cleanup_tx_queue() run from airoha_remove(). I will post a dedicated patch for this issue. - Also, if the device was being unregistered, unregister_netdev() frees the netdev memory without waiting for TX skbs (which do not hold a reference to dev). Could this lead to a use-after-free when dereferencing skb->dev here? Should the driver selectively purge skbs belonging to the stopping netdev from the shared TX ring before ndo_stop returns? - I do not think this is a problem in the current codebase since the net_devices are unregistered just during module unload running airoha_remove() where we run airoha_qdma_stop_napi() to stop TX/RX NAPIs. Regards, Lorenzo > diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h > index 8f42973f9cf5..24fd8dcf7fca 100644 > --- a/drivers/net/ethernet/airoha/airoha_eth.h > +++ b/drivers/net/ethernet/airoha/airoha_eth.h > @@ -526,7 +526,7 @@ struct airoha_qdma { > struct airoha_eth *eth; > void __iomem *regs; > > - atomic_t users; > + int users; > > struct airoha_irq_bank irq_banks[AIROHA_MAX_NUM_IRQ_BANKS]; > > > -- > 2.54.0 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v5 2/3] net: airoha: refactor QDMA start/stop into reusable helpers 2026-06-11 21:55 [PATCH net-next v5 0/3] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter Lorenzo Bianconi @ 2026-06-11 21:55 ` Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload Lorenzo Bianconi 2 siblings, 0 replies; 7+ messages in thread From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Lorenzo Bianconi Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal, Alexander Lobakin Factor out airoha_qdma_start() and airoha_qdma_stop() from airoha_dev_open() and airoha_dev_stop(). These helpers will be reused by the QDMA hot-migration logic introduced in the next patch to dynamically switch GDM3/GDM4 ports between LAN and WAN QDMA blocks. Add a DMA engine busy poll in airoha_qdma_stop() to wait for in-flight DMA transfers to complete before cleaning up TX queues. Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com> Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> --- drivers/net/ethernet/airoha/airoha_eth.c | 53 ++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 9eb9b6a139b3..72ec6c888948 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -1774,6 +1774,40 @@ static void airoha_update_hw_stats(struct airoha_gdm_dev *dev) spin_unlock(&port->stats.lock); } +static void airoha_qdma_start(struct airoha_qdma *qdma) +{ + airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG, + GLOBAL_CFG_TX_DMA_EN_MASK | + GLOBAL_CFG_RX_DMA_EN_MASK); + qdma->users++; +} + +static void airoha_qdma_stop(struct airoha_qdma *qdma) +{ + u32 status; + + if (--qdma->users) + return; + + airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG, + GLOBAL_CFG_TX_DMA_EN_MASK | + GLOBAL_CFG_RX_DMA_EN_MASK); + + if (read_poll_timeout(airoha_qdma_rr, status, + !(status & (GLOBAL_CFG_TX_DMA_BUSY_MASK | + GLOBAL_CFG_RX_DMA_BUSY_MASK)), + USEC_PER_MSEC, 50 * USEC_PER_MSEC, true, + qdma, REG_QDMA_GLOBAL_CFG)) + dev_warn(qdma->eth->dev, "QDMA DMA engine busy timeout\n"); + + for (int i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) { + if (!qdma->q_tx[i].ndesc) + continue; + + airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]); + } +} + static int airoha_dev_open(struct net_device *netdev) { int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN; @@ -1809,10 +1843,7 @@ 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); - qdma->users++; + airoha_qdma_start(qdma); if (!airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(qdma->eth, 1)) @@ -1865,19 +1896,7 @@ static int airoha_dev_stop(struct net_device *netdev) airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id), FE_PSE_PORT_DROP); - - if (!--qdma->users) { - airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG, - GLOBAL_CFG_TX_DMA_EN_MASK | - GLOBAL_CFG_RX_DMA_EN_MASK); - - for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) { - if (!qdma->q_tx[i].ndesc) - continue; - - airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]); - } - } + airoha_qdma_stop(qdma); return 0; } -- 2.54.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload 2026-06-11 21:55 [PATCH net-next v5 0/3] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 2/3] net: airoha: refactor QDMA start/stop into reusable helpers Lorenzo Bianconi @ 2026-06-11 21:55 ` Lorenzo Bianconi 2026-06-13 9:39 ` Lorenzo Bianconi 2026-06-13 10:04 ` Lorenzo Bianconi 2 siblings, 2 replies; 7+ messages in thread From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Lorenzo Bianconi Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal, Alexander Lobakin 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(). Add the capability to configure GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed. 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. Introduce the AIROHA_PRIV_F_QOS flag to track whether a device has an active HTB qdisc; clear it on TC_HTB_DESTROY. The device keeps its WAN role after qdisc teardown so that its configuration is preserved until another device explicitly needs the WAN role for QoS offload. If another GDM3/GDM4 device already holds the WAN role without an active QoS qdisc, demote it to LAN before promoting the requesting device. Skip the demotion when the requesting device is itself already the WAN device. Since airoha_dev_set_qdma() can now be called on a running device to migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX path can safely dereference it without holding RTNL. Hold flow_offload_mutex in airoha_enable_qos_for_gdm34() and airoha_disable_qos_for_gdm34() around the dev->flags update, airoha_dev_set_qdma() and GDM2 loopback configuration, serializing against concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER offload path. Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected() with a lockdep condition accepting either rtnl_lock or flow_offload_mutex, and use it across all control-path dereferences of the RCU-protected dev->qdma pointer. Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback. Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com> Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> --- drivers/net/ethernet/airoha/airoha_eth.c | 234 ++++++++++++++++++++++++++---- drivers/net/ethernet/airoha/airoha_eth.h | 13 +- drivers/net/ethernet/airoha/airoha_ppe.c | 9 +- drivers/net/ethernet/airoha/airoha_regs.h | 1 + 4 files changed, 225 insertions(+), 32 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 72ec6c888948..490da17e3dc6 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -921,7 +921,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q) if (!dev) continue; - if (dev->qdma != qdma) + if (rcu_access_pointer(dev->qdma) != qdma) continue; netdev = netdev_from_priv(dev); @@ -1814,13 +1814,14 @@ static int airoha_dev_open(struct net_device *netdev) struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_gdm_port *port = dev->port; u32 cur_len, pse_port = FE_PSE_PORT_PPE1; - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; netif_tx_start_all_queues(netdev); err = airoha_set_vip_for_gdm_port(dev, true); if (err) return err; + qdma = airoha_qdma_deref(dev); if (netdev_uses_dsa(netdev)) airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id), GDM_STAG_EN_MASK); @@ -1882,7 +1883,7 @@ static int airoha_dev_stop(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); struct airoha_gdm_port *port = dev->port; - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; int i; netif_tx_disable(netdev); @@ -1890,6 +1891,7 @@ static int airoha_dev_stop(struct net_device *netdev) for (i = 0; i < netdev->num_tx_queues; i++) netdev_tx_reset_subqueue(netdev, i); + qdma = airoha_qdma_deref(dev); if (--port->users) airoha_set_port_mtu(dev->eth, port); else @@ -1982,6 +1984,53 @@ 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)) { + u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq); + + airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask, + FC_MAP6_DEF_VALUE & mask); + } + + return 0; +} + static struct airoha_gdm_dev * airoha_get_wan_gdm_dev(struct airoha_eth *eth) { @@ -2008,15 +2057,30 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth) static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev) { struct net_device *netdev = netdev_from_priv(dev); + struct airoha_qdma *cur_qdma, *qdma; struct airoha_eth *eth = dev->eth; int ppe_id; /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */ - dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; - netdev->irq = dev->qdma->irq_banks[0].irq; + qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; + cur_qdma = airoha_qdma_deref(dev); + if (netif_running(netdev)) + airoha_qdma_start(qdma); + + rcu_assign_pointer(dev->qdma, qdma); + netdev->irq = qdma->irq_banks[0].irq; ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1); airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev)); + + if (!cur_qdma) + return; + + synchronize_rcu(); + netif_tx_wake_all_queues(netdev); + + if (netif_running(netdev)) + airoha_qdma_stop(cur_qdma); } static int airoha_dev_init(struct net_device *netdev) @@ -2180,9 +2244,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; u32 nr_frags, tag, msg0, msg1, len; struct airoha_queue_entry *e; + struct airoha_qdma *qdma; struct netdev_queue *txq; struct airoha_queue *q; LIST_HEAD(tx_list); @@ -2191,6 +2255,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, u16 index; u8 fport; + rcu_read_lock(); + qdma = rcu_dereference(dev->qdma); qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb)); tag = airoha_get_dsa_tag(skb, netdev); @@ -2237,6 +2303,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, netif_tx_stop_queue(txq); q->txq_stopped = true; spin_unlock_bh(&q->lock); + rcu_read_unlock(); + return NETDEV_TX_BUSY; } @@ -2299,6 +2367,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, FIELD_PREP(TX_RING_CPU_IDX_MASK, index)); spin_unlock_bh(&q->lock); + rcu_read_unlock(); return NETDEV_TX_OK; @@ -2314,6 +2383,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, error: dev_kfree_skb_any(skb); netdev->stats.tx_dropped++; + rcu_read_unlock(); return NETDEV_TX_OK; } @@ -2395,17 +2465,19 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev, const u16 *weights, u8 n_weights) { struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_qdma *qdma; int i; + qdma = airoha_qdma_deref(dev); for (i = 0; i < AIROHA_NUM_TX_RING; i++) - airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel), + airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel), TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i)); for (i = 0; i < n_weights; i++) { u32 status; int err; - airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG, + airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG, TWRR_RW_CMD_MASK | FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) | FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) | @@ -2413,12 +2485,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev, err = read_poll_timeout(airoha_qdma_rr, status, status & TWRR_RW_CMD_DONE, USEC_PER_MSEC, 10 * USEC_PER_MSEC, - true, dev->qdma, REG_TXWRR_WEIGHT_CFG); + true, qdma, REG_TXWRR_WEIGHT_CFG); if (err) return err; } - airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3), + airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3), CHAN_QOS_MODE_MASK(channel), __field_prep(CHAN_QOS_MODE_MASK(channel), mode)); @@ -2482,13 +2554,15 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel, struct tc_ets_qopt_offload *opt) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; + u64 cpu_tx_packets, fwd_tx_packets, tx_packets; + struct airoha_qdma *qdma; - u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); - u64 fwd_tx_packets = airoha_qdma_rr(qdma, - REG_CNTR_VAL((channel << 1) + 1)); - u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) + - (fwd_tx_packets - dev->fwd_tx_packets); + qdma = airoha_qdma_deref(dev); + cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1)); + fwd_tx_packets = airoha_qdma_rr(qdma, + REG_CNTR_VAL((channel << 1) + 1)); + tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) + + (fwd_tx_packets - dev->fwd_tx_packets); _bstats_update(opt->stats.bstats, 0, tx_packets); dev->cpu_tx_packets = cpu_tx_packets; @@ -2748,16 +2822,18 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev, u32 bucket_size) { struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_qdma *qdma; int i, err; + qdma = airoha_qdma_deref(dev); for (i = 0; i <= TRTCM_PEAK_MODE; i++) { - err = airoha_qdma_set_trtcm_config(dev->qdma, channel, + err = airoha_qdma_set_trtcm_config(qdma, channel, REG_EGRESS_TRTCM_CFG, i, !!rate, TRTCM_METER_MODE); if (err) return err; - err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel, + err = airoha_qdma_set_trtcm_token_bucket(qdma, channel, REG_EGRESS_TRTCM_CFG, i, rate, bucket_size); if (err) @@ -2793,11 +2869,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev, u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS; int err, num_tx_queues = netdev->real_num_tx_queues; struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; /* Here we need to check the requested QDMA channel is not already * in use by another net_device running on the same QDMA block. */ + qdma = airoha_qdma_deref(dev); if (test_and_set_bit(channel, qdma->qos_channel_map)) { NL_SET_ERR_MSG_MOD(opt->extack, "qdma qos channel already in use"); @@ -2831,7 +2908,7 @@ 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 = dev->qdma; + struct airoha_qdma *qdma = airoha_qdma_deref(dev); int i; for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) { @@ -3005,11 +3082,12 @@ static int airoha_dev_setup_tc_block(struct net_device *dev, static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue) { struct airoha_gdm_dev *dev = netdev_priv(netdev); - struct airoha_qdma *qdma = dev->qdma; + struct airoha_qdma *qdma; netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1); airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0); + qdma = airoha_qdma_deref(dev); clear_bit(queue, qdma->qos_channel_map); clear_bit(queue, dev->qos_sq_bmap); } @@ -3030,6 +3108,95 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev, return 0; } +static void airoha_disable_qos_for_gdm34(struct net_device *netdev) +{ + struct airoha_gdm_dev *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + int err; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) + return; + + err = airoha_disable_gdm2_loopback(dev); + if (err) + netdev_warn(netdev, + "failed disabling GDM2 loopback: %d\n", err); + + dev->flags &= ~AIROHA_PRIV_F_WAN; + airoha_dev_set_qdma(dev); + + airoha_set_macaddr(dev, netdev->dev_addr); + if (netif_running(netdev)) + airoha_set_gdm_port_fwd_cfg(dev->eth, + REG_GDM_FWD_CFG(port->id), + FE_PSE_PORT_PPE1); +} + +static int airoha_enable_qos_for_gdm34(struct net_device *netdev, + struct netlink_ext_ack *extack) +{ + struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev); + struct airoha_gdm_port *port = dev->port; + struct airoha_eth *eth = dev->eth; + int err = -EBUSY; + + if (port->id != AIROHA_GDM3_IDX && + port->id != AIROHA_GDM4_IDX) { + /* HW QoS is always supported by GDM1 and GDM2 */ + return 0; + } + + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */ + return 0; + + mutex_lock(&flow_offload_mutex); + + wan_dev = airoha_get_wan_gdm_dev(eth); + if (wan_dev) { + if ((wan_dev->flags & AIROHA_PRIV_F_QOS) || + wan_dev->port->id == AIROHA_GDM2_IDX) { + NL_SET_ERR_MSG_MOD(extack, + "QoS configured for WAN device"); + goto error_unlock; + } + airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev)); + } + + dev->flags |= AIROHA_PRIV_F_WAN; + airoha_dev_set_qdma(dev); + err = airoha_enable_gdm2_loopback(dev); + if (err) + goto error_disable_wan; + + err = airoha_set_macaddr(dev, netdev->dev_addr); + if (err) + goto error_disable_loopback; + + if (netif_running(netdev)) { + u32 pse_port; + + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 + : FE_PSE_PORT_PPE1; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), + pse_port); + } + + mutex_unlock(&flow_offload_mutex); + + return 0; + +error_disable_loopback: + airoha_disable_gdm2_loopback(dev); +error_disable_wan: + dev->flags &= ~AIROHA_PRIV_F_WAN; + airoha_dev_set_qdma(dev); +error_unlock: + mutex_unlock(&flow_offload_mutex); + + return err; +} + static int airoha_tc_htb_destroy(struct net_device *netdev) { struct airoha_gdm_dev *dev = netdev_priv(netdev); @@ -3038,6 +3205,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); + dev->flags &= ~AIROHA_PRIV_F_QOS; + return 0; } @@ -3057,24 +3226,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev, return 0; } -static int airoha_tc_setup_qdisc_htb(struct net_device *dev, +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev, struct tc_htb_qopt_offload *opt) { switch (opt->command) { - case TC_HTB_CREATE: + case TC_HTB_CREATE: { + struct airoha_gdm_dev *dev = netdev_priv(netdev); + int err; + + err = airoha_enable_qos_for_gdm34(netdev, opt->extack); + if (err) + return err; + + dev->flags |= AIROHA_PRIV_F_QOS; break; + } case TC_HTB_DESTROY: - return airoha_tc_htb_destroy(dev); + return airoha_tc_htb_destroy(netdev); case TC_HTB_NODE_MODIFY: - return airoha_tc_htb_modify_queue(dev, opt); + return airoha_tc_htb_modify_queue(netdev, opt); case TC_HTB_LEAF_ALLOC_QUEUE: - return airoha_tc_htb_alloc_leaf_queue(dev, opt); + return airoha_tc_htb_alloc_leaf_queue(netdev, opt); case TC_HTB_LEAF_DEL: case TC_HTB_LEAF_DEL_LAST: case TC_HTB_LEAF_DEL_LAST_FORCE: - return airoha_tc_htb_delete_leaf_queue(dev, opt); + return airoha_tc_htb_delete_leaf_queue(netdev, opt); case TC_HTB_LEAF_QUERY_QUEUE: - return airoha_tc_get_htb_get_leaf_queue(dev, opt); + return airoha_tc_get_htb_get_leaf_queue(netdev, opt); default: return -EOPNOTSUPP; } diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 24fd8dcf7fca..d1390ffcea7c 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -540,11 +540,12 @@ struct airoha_qdma { enum airoha_priv_flags { AIROHA_PRIV_F_WAN = BIT(0), + AIROHA_PRIV_F_QOS = BIT(1), }; struct airoha_gdm_dev { + struct airoha_qdma __rcu *qdma; struct airoha_gdm_port *port; - struct airoha_qdma *qdma; struct airoha_eth *eth; DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS); @@ -676,6 +677,16 @@ 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); +extern struct mutex flow_offload_mutex; + +static inline struct airoha_qdma * +airoha_qdma_deref(struct airoha_gdm_dev *dev) +{ + return rcu_dereference_protected(dev->qdma, + lockdep_rtnl_is_held() || + lockdep_is_held(&flow_offload_mutex)); +} + void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport); bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index); void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb, diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index 91bcc55a6ac6..1d1b1a57d795 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -15,7 +15,10 @@ #include "airoha_regs.h" #include "airoha_eth.h" -static DEFINE_MUTEX(flow_offload_mutex); +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port + * configuration. + */ +DEFINE_MUTEX(flow_offload_mutex); static DEFINE_SPINLOCK(ppe_lock); static const struct rhashtable_params airoha_flow_table_params = { @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe) void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport) { - struct airoha_qdma *qdma = dev->qdma; - struct airoha_eth *eth = qdma->eth; + struct airoha_qdma *qdma = airoha_qdma_deref(dev); + struct airoha_eth *eth = dev->eth; u8 qdma_id = qdma - ð->qdma[0]; u32 fe_cpu_port; 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 -- 2.54.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload 2026-06-11 21:55 ` [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload Lorenzo Bianconi @ 2026-06-13 9:39 ` Lorenzo Bianconi 2026-06-13 10:04 ` Lorenzo Bianconi 1 sibling, 0 replies; 7+ messages in thread From: Lorenzo Bianconi @ 2026-06-13 9:39 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal, Alexander Lobakin [-- Attachment #1: Type: text/plain, Size: 11446 bytes --] Commenting on sashiko's report: https://sashiko.dev/#/patchset/20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1%40kernel.org [...] > static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev) > { > struct net_device *netdev = netdev_from_priv(dev); > + struct airoha_qdma *cur_qdma, *qdma; > struct airoha_eth *eth = dev->eth; > int ppe_id; > > /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */ > - dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; > - netdev->irq = dev->qdma->irq_banks[0].irq; > + qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)]; > + cur_qdma = airoha_qdma_deref(dev); > + if (netif_running(netdev)) > + airoha_qdma_start(qdma); > + > + rcu_assign_pointer(dev->qdma, qdma); > + netdev->irq = qdma->irq_banks[0].irq; > > ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1); > airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev)); > + > + if (!cur_qdma) > + return; > + > + synchronize_rcu(); > + netif_tx_wake_all_queues(netdev); > + > + if (netif_running(netdev)) > + airoha_qdma_stop(cur_qdma); > } - When we switch the QDMA of a running device, airoha_qdma_stop() might free pending SKBs via dev_kfree_skb_any() in airoha_qdma_cleanup_tx_queue(). Are these SKBs freed without calling netdev_tx_completed_queue() to update Byte Queue Limits (BQL)? Since the device is running, could the BQL counters leak and cause the TX queue to permanently hang once the limit is reached? - As pointed out in path 1/3, this is a pre-existing issue since we have the same problem running airoha_dev_stop() for a QDMA single-user device (airoha_qdma_cleanup_tx_queue() can run before the pending TX NAPI). I will fix the problem with a dedicated patch. > > static int airoha_dev_init(struct net_device *netdev) > @@ -2180,9 +2244,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, > struct net_device *netdev) > { > struct airoha_gdm_dev *dev = netdev_priv(netdev); > - struct airoha_qdma *qdma = dev->qdma; > u32 nr_frags, tag, msg0, msg1, len; > struct airoha_queue_entry *e; > + struct airoha_qdma *qdma; > struct netdev_queue *txq; - This is a pre-existing issue, but does the TX path modify the TCP header directly without ensuring it is in the linear region? - This issue (not introduced by this patch) has been already reported in the past and we already agreed that it can't occur. > struct airoha_queue *q; > LIST_HEAD(tx_list); > @@ -2191,6 +2255,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb, > u16 index; > u8 fport; > [...] > @@ -2793,11 +2869,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev, > u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS; > int err, num_tx_queues = netdev->real_num_tx_queues; > struct airoha_gdm_dev *dev = netdev_priv(netdev); > - struct airoha_qdma *qdma = dev->qdma; > + struct airoha_qdma *qdma; > > /* Here we need to check the requested QDMA channel is not already > * in use by another net_device running on the same QDMA block. > */ > + qdma = airoha_qdma_deref(dev); - This is a pre-existing issue, but does modifying real_num_tx_queues as a dense counter cause problems when QoS leaf queues are allocated with sparse IDs? - I have already a fix for this issue I need to post. > if (test_and_set_bit(channel, qdma->qos_channel_map)) { > NL_SET_ERR_MSG_MOD(opt->extack, > "qdma qos channel already in use"); > @@ -2831,7 +2908,7 @@ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev, > u32 rate, u32 bucket_size, [...] > + > +static int airoha_enable_qos_for_gdm34(struct net_device *netdev, > + struct netlink_ext_ack *extack) > +{ > + struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev); > + struct airoha_gdm_port *port = dev->port; > + struct airoha_eth *eth = dev->eth; > + int err = -EBUSY; > + > + if (port->id != AIROHA_GDM3_IDX && > + port->id != AIROHA_GDM4_IDX) { > + /* HW QoS is always supported by GDM1 and GDM2 */ > + return 0; > + } > + > + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */ > + return 0; > + > + mutex_lock(&flow_offload_mutex); > + > + wan_dev = airoha_get_wan_gdm_dev(eth); > + if (wan_dev) { > + if ((wan_dev->flags & AIROHA_PRIV_F_QOS) || > + wan_dev->port->id == AIROHA_GDM2_IDX) { > + NL_SET_ERR_MSG_MOD(extack, > + "QoS configured for WAN device"); > + goto error_unlock; > + } > + airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev)); > + } > + > + dev->flags |= AIROHA_PRIV_F_WAN; > + airoha_dev_set_qdma(dev); > + err = airoha_enable_gdm2_loopback(dev); > + if (err) > + goto error_disable_wan; > + > + err = airoha_set_macaddr(dev, netdev->dev_addr); > + if (err) > + goto error_disable_loopback; > + > + if (netif_running(netdev)) { > + u32 pse_port; > + > + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 > + : FE_PSE_PORT_PPE1; > + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), > + pse_port); > + } > + > + mutex_unlock(&flow_offload_mutex); > + > + return 0; > + > +error_disable_loopback: > + airoha_disable_gdm2_loopback(dev); > +error_disable_wan: > + dev->flags &= ~AIROHA_PRIV_F_WAN; > + airoha_dev_set_qdma(dev); > +error_unlock: > + mutex_unlock(&flow_offload_mutex); > + > + return err; > +} - If airoha_set_macaddr() fails and we jump to error_disable_loopback, the original wan_dev was preemptively demoted to LAN mode, but the error path doesn't restore it. Since the requesting device is also rolled back to LAN, does this leave the system entirely without a WAN device? Would this permanently break routing on the original WAN interface until manually reconfigured? - If airoha_enable_qos_for_gdm34() fails, the system will continue routing packets and, even if we re-promote the original interface to WAN, the user will need to re-apply the same configuration to offload the intended Qdisc on the new interface. Moreover, please note this corner case occurs just if the original interface has no Qdisc already configured on it. In the common case, the user has previously removed the configured Qdisc on the original interface and so, the roll-back process will still be partial. In the end, airoha_set_macaddr() can fail just if the device is not well-configured. For the reasons above, I guess it does not worth to reset the original interface to WAN if airoha_set_macaddr() fails. Regards, Lorenzo > + > static int airoha_tc_htb_destroy(struct net_device *netdev) > { > struct airoha_gdm_dev *dev = netdev_priv(netdev); > @@ -3038,6 +3205,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); > > + dev->flags &= ~AIROHA_PRIV_F_QOS; > + > return 0; > } > > @@ -3057,24 +3226,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev, > return 0; > } > > -static int airoha_tc_setup_qdisc_htb(struct net_device *dev, > +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev, > struct tc_htb_qopt_offload *opt) > { > switch (opt->command) { > - case TC_HTB_CREATE: > + case TC_HTB_CREATE: { > + struct airoha_gdm_dev *dev = netdev_priv(netdev); > + int err; > + > + err = airoha_enable_qos_for_gdm34(netdev, opt->extack); > + if (err) > + return err; > + > + dev->flags |= AIROHA_PRIV_F_QOS; > break; > + } > case TC_HTB_DESTROY: > - return airoha_tc_htb_destroy(dev); > + return airoha_tc_htb_destroy(netdev); > case TC_HTB_NODE_MODIFY: > - return airoha_tc_htb_modify_queue(dev, opt); > + return airoha_tc_htb_modify_queue(netdev, opt); > case TC_HTB_LEAF_ALLOC_QUEUE: > - return airoha_tc_htb_alloc_leaf_queue(dev, opt); > + return airoha_tc_htb_alloc_leaf_queue(netdev, opt); > case TC_HTB_LEAF_DEL: > case TC_HTB_LEAF_DEL_LAST: > case TC_HTB_LEAF_DEL_LAST_FORCE: > - return airoha_tc_htb_delete_leaf_queue(dev, opt); > + return airoha_tc_htb_delete_leaf_queue(netdev, opt); > case TC_HTB_LEAF_QUERY_QUEUE: > - return airoha_tc_get_htb_get_leaf_queue(dev, opt); > + return airoha_tc_get_htb_get_leaf_queue(netdev, opt); > default: > return -EOPNOTSUPP; > } > diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h > index 24fd8dcf7fca..d1390ffcea7c 100644 > --- a/drivers/net/ethernet/airoha/airoha_eth.h > +++ b/drivers/net/ethernet/airoha/airoha_eth.h > @@ -540,11 +540,12 @@ struct airoha_qdma { > > enum airoha_priv_flags { > AIROHA_PRIV_F_WAN = BIT(0), > + AIROHA_PRIV_F_QOS = BIT(1), > }; > > struct airoha_gdm_dev { > + struct airoha_qdma __rcu *qdma; > struct airoha_gdm_port *port; > - struct airoha_qdma *qdma; > struct airoha_eth *eth; > > DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS); > @@ -676,6 +677,16 @@ 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); > > +extern struct mutex flow_offload_mutex; > + > +static inline struct airoha_qdma * > +airoha_qdma_deref(struct airoha_gdm_dev *dev) > +{ > + return rcu_dereference_protected(dev->qdma, > + lockdep_rtnl_is_held() || > + lockdep_is_held(&flow_offload_mutex)); > +} > + > void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport); > bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index); > void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb, > diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c > index 91bcc55a6ac6..1d1b1a57d795 100644 > --- a/drivers/net/ethernet/airoha/airoha_ppe.c > +++ b/drivers/net/ethernet/airoha/airoha_ppe.c > @@ -15,7 +15,10 @@ > #include "airoha_regs.h" > #include "airoha_eth.h" > > -static DEFINE_MUTEX(flow_offload_mutex); > +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port > + * configuration. > + */ > +DEFINE_MUTEX(flow_offload_mutex); > static DEFINE_SPINLOCK(ppe_lock); > > static const struct rhashtable_params airoha_flow_table_params = { > @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe) > > void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport) > { > - struct airoha_qdma *qdma = dev->qdma; > - struct airoha_eth *eth = qdma->eth; > + struct airoha_qdma *qdma = airoha_qdma_deref(dev); > + struct airoha_eth *eth = dev->eth; > u8 qdma_id = qdma - ð->qdma[0]; > u32 fe_cpu_port; > > 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 > > > -- > 2.54.0 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload 2026-06-11 21:55 ` [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload Lorenzo Bianconi 2026-06-13 9:39 ` Lorenzo Bianconi @ 2026-06-13 10:04 ` Lorenzo Bianconi 1 sibling, 0 replies; 7+ messages in thread From: Lorenzo Bianconi @ 2026-06-13 10:04 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal, Alexander Lobakin [-- Attachment #1: Type: text/plain, Size: 7641 bytes --] Commenting on sashiko's report: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1%40kernel.org [...] > +static int airoha_enable_qos_for_gdm34(struct net_device *netdev, > + struct netlink_ext_ack *extack) > +{ > + struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev); > + struct airoha_gdm_port *port = dev->port; > + struct airoha_eth *eth = dev->eth; > + int err = -EBUSY; > + > + if (port->id != AIROHA_GDM3_IDX && > + port->id != AIROHA_GDM4_IDX) { > + /* HW QoS is always supported by GDM1 and GDM2 */ > + return 0; > + } > + > + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */ > + return 0; > + > + mutex_lock(&flow_offload_mutex); > + > + wan_dev = airoha_get_wan_gdm_dev(eth); > + if (wan_dev) { > + if ((wan_dev->flags & AIROHA_PRIV_F_QOS) || > + wan_dev->port->id == AIROHA_GDM2_IDX) { > + NL_SET_ERR_MSG_MOD(extack, > + "QoS configured for WAN device"); > + goto error_unlock; > + } > + airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev)); > + } > + > + dev->flags |= AIROHA_PRIV_F_WAN; > + airoha_dev_set_qdma(dev); > + err = airoha_enable_gdm2_loopback(dev); > + if (err) > + goto error_disable_wan; > + > + err = airoha_set_macaddr(dev, netdev->dev_addr); > + if (err) > + goto error_disable_loopback; > + > + if (netif_running(netdev)) { > + u32 pse_port; > + > + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2 > + : FE_PSE_PORT_PPE1; > + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), > + pse_port); > + } > + > + mutex_unlock(&flow_offload_mutex); > + > + return 0; > + > +error_disable_loopback: > + airoha_disable_gdm2_loopback(dev); > +error_disable_wan: > + dev->flags &= ~AIROHA_PRIV_F_WAN; > + airoha_dev_set_qdma(dev); > +error_unlock: > + mutex_unlock(&flow_offload_mutex); > + > + return err; > +} - The error_disable_loopback / error_disable_wan / error_unlock paths only revert the requesting dev. The earlier airoha_disable_qos_for_gdm34(wan_dev) demotion is never undone. Could a TC_HTB_CREATE that returns an error to userspace then leave the system with no WAN GDM3/GDM4 device, with the previously-working sibling silently flipped to LAN, its QDMA migrated to QDMA0, GDM2 loopback torn down, and its forwarding rewritten to PPE1? Should the failure paths re-promote wan_dev (re-call airoha_enable_qos_for_gdm34 or an equivalent restore helper on it) before unlocking and returning? - This is the same item reported by sashiko-gemini in [0]. In my previous reply I have explained why I do not think it worths to re-promote the original interface in case of failure in airoha_enable_qos_for_gdm34(). Regards, Lorenzo [0] https://sashiko.dev/#/patchset/20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1%40kernel.org > + > static int airoha_tc_htb_destroy(struct net_device *netdev) > { > struct airoha_gdm_dev *dev = netdev_priv(netdev); > @@ -3038,6 +3205,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); > > + dev->flags &= ~AIROHA_PRIV_F_QOS; > + > return 0; > } > > @@ -3057,24 +3226,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev, > return 0; > } > > -static int airoha_tc_setup_qdisc_htb(struct net_device *dev, > +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev, > struct tc_htb_qopt_offload *opt) > { > switch (opt->command) { > - case TC_HTB_CREATE: > + case TC_HTB_CREATE: { > + struct airoha_gdm_dev *dev = netdev_priv(netdev); > + int err; > + > + err = airoha_enable_qos_for_gdm34(netdev, opt->extack); > + if (err) > + return err; > + > + dev->flags |= AIROHA_PRIV_F_QOS; > break; > + } > case TC_HTB_DESTROY: > - return airoha_tc_htb_destroy(dev); > + return airoha_tc_htb_destroy(netdev); > case TC_HTB_NODE_MODIFY: > - return airoha_tc_htb_modify_queue(dev, opt); > + return airoha_tc_htb_modify_queue(netdev, opt); > case TC_HTB_LEAF_ALLOC_QUEUE: > - return airoha_tc_htb_alloc_leaf_queue(dev, opt); > + return airoha_tc_htb_alloc_leaf_queue(netdev, opt); > case TC_HTB_LEAF_DEL: > case TC_HTB_LEAF_DEL_LAST: > case TC_HTB_LEAF_DEL_LAST_FORCE: > - return airoha_tc_htb_delete_leaf_queue(dev, opt); > + return airoha_tc_htb_delete_leaf_queue(netdev, opt); > case TC_HTB_LEAF_QUERY_QUEUE: > - return airoha_tc_get_htb_get_leaf_queue(dev, opt); > + return airoha_tc_get_htb_get_leaf_queue(netdev, opt); > default: > return -EOPNOTSUPP; > } > diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h > index 24fd8dcf7fca..d1390ffcea7c 100644 > --- a/drivers/net/ethernet/airoha/airoha_eth.h > +++ b/drivers/net/ethernet/airoha/airoha_eth.h > @@ -540,11 +540,12 @@ struct airoha_qdma { > > enum airoha_priv_flags { > AIROHA_PRIV_F_WAN = BIT(0), > + AIROHA_PRIV_F_QOS = BIT(1), > }; > > struct airoha_gdm_dev { > + struct airoha_qdma __rcu *qdma; > struct airoha_gdm_port *port; > - struct airoha_qdma *qdma; > struct airoha_eth *eth; > > DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS); > @@ -676,6 +677,16 @@ 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); > > +extern struct mutex flow_offload_mutex; > + > +static inline struct airoha_qdma * > +airoha_qdma_deref(struct airoha_gdm_dev *dev) > +{ > + return rcu_dereference_protected(dev->qdma, > + lockdep_rtnl_is_held() || > + lockdep_is_held(&flow_offload_mutex)); > +} > + > void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport); > bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index); > void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb, > diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c > index 91bcc55a6ac6..1d1b1a57d795 100644 > --- a/drivers/net/ethernet/airoha/airoha_ppe.c > +++ b/drivers/net/ethernet/airoha/airoha_ppe.c > @@ -15,7 +15,10 @@ > #include "airoha_regs.h" > #include "airoha_eth.h" > > -static DEFINE_MUTEX(flow_offload_mutex); > +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port > + * configuration. > + */ > +DEFINE_MUTEX(flow_offload_mutex); > static DEFINE_SPINLOCK(ppe_lock); > > static const struct rhashtable_params airoha_flow_table_params = { > @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe) > > void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport) > { > - struct airoha_qdma *qdma = dev->qdma; > - struct airoha_eth *eth = qdma->eth; > + struct airoha_qdma *qdma = airoha_qdma_deref(dev); > + struct airoha_eth *eth = dev->eth; > u8 qdma_id = qdma - ð->qdma[0]; > u32 fe_cpu_port; > > 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 > > > -- > 2.54.0 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-06-13 10:04 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-11 21:55 [PATCH net-next v5 0/3] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter Lorenzo Bianconi 2026-06-13 9:06 ` Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 2/3] net: airoha: refactor QDMA start/stop into reusable helpers Lorenzo Bianconi 2026-06-11 21:55 ` [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload Lorenzo Bianconi 2026-06-13 9:39 ` Lorenzo Bianconi 2026-06-13 10:04 ` Lorenzo Bianconi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox