net: cpsw: Execute ndo_set_rx_mode callback in a work queue
v5 cover net: cpsw: Execute ndo_set_rx_mode callback in a work queue
These two patches resolve an RTNL assertion call trace issue in both the legacy and new cpsw drivers.
Thanks,
Kevin
---
Changes in v5:
- Apply the same fix to the legacy cpsw driver as well.
- Use goto label in cpsw_ndo_set_rx_mode_work() for the undo path as suggested by Jakub.
- Move the disable_work_sync() call to after unregister_netdev() as suggested by Jakub.
- Link to v4: https://lore.kernel.org/r/[email protected]
Changes in v4:
- Using schedule_work() instead of creating a dedicated workqueue.
- Link to v3: https://lore.kernel.org/r/[email protected]
Changes in v3:
- Resolve the deadlock issue identified in the AI review [2] by moving the netif_running() check under the RTNL lock and removing the cancel_work_sync() call in cpsw_ndo_stop().
- Link to v2: https://lore.kernel.org/r/[email protected]
Changes in v2:
- Addresses the issue identified in the AI review [1]:
- Adds a netif_running() check in cpsw_ndo_set_rx_mode_work()
- Cancels the rx_mode_work in cpsw_ndo_stop()
- Link to v1: https://lore.kernel.org/r/[email protected]
[1] https://netdev-ai.bots.linux.dev/ai-review.html?id=bd885e1e-1aed-4755-ad60-7150737ad0f5
[2] https://netdev-ai.bots.linux.dev/ai-review.html?id=c9fc3cf8-a06c-4cb8-b26b-910e775951a0
---
Kevin Hao (2):
net: cpsw_new: Execute ndo_set_rx_mode callback in a work queue
net: cpsw: Execute ndo_set_rx_mode callback in a work queue
drivers/net/ethernet/ti/cpsw.c | 41 +++++++++++++++++++++++++++++++------ drivers/net/ethernet/ti/cpsw_new.c | 34 +++++++++++++++++++++++++----- drivers/net/ethernet/ti/cpsw_priv.h | 1 + 3 files changed, 65 insertions(+), 11 deletions(-)
---
base-commit: 193579fe01389bc21aff0051d13f24e8ea95b47d
change-id: 20260123-bbb-dc3675f671d0
Best regards,
Signature · 1 line
Kevin Hao <[email protected]>
PATCH v5 1/2 net: cpsw_new: Execute ndo_set_rx_mode callback in a work queue
Commit 1767bb2d47b7 ("ipv6: mcast: Don't hold RTNL for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP.") removed the RTNL lock for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP operations. However, this change triggered the following call trace on my BeagleBone Black board:
WARNING: net/8021q/vlan_core.c:236 at vlan_for_each+0x120/0x124, CPU#0: rpcbind/496 RTNL: assertion failed at net/8021q/vlan_core.c (236) Modules linked in: CPU: 0 UID: 997 PID: 496 Comm: rpcbind Not tainted 6.19.0-rc6-next-20260122-yocto-standard+ #8 PREEMPT Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x28/0x2c show_stack from dump_stack_lvl+0x30/0x38 dump_stack_lvl from __warn+0xb8/0x11c __warn from warn_slowpath_fmt+0x130/0x194 warn_slowpath_fmt from vlan_for_each+0x120/0x124 vlan_for_each from cpsw_add_mc_addr+0x54/0xd8 cpsw_add_mc_addr from __hw_addr_ref_sync_dev+0xc4/0xec __hw_addr_ref_sync_dev from __dev_mc_add+0x78/0x88 __dev_mc_add from igmp6_group_added+0x84/0xec igmp6_group_added from __ipv6_dev_mc_inc+0x1fc/0x2f0 __ipv6_dev_mc_inc from __ipv6_sock_mc_join+0x124/0x1b4 __ipv6_sock_mc_join from do_ipv6_setsockopt+0x84c/0x1168 do_ipv6_setsockopt from ipv6_setsockopt+0x88/0xc8 ipv6_setsockopt from do_sock_setsockopt+0xe8/0x19c do_sock_setsockopt from __sys_setsockopt+0x84/0xac __sys_setsockopt from ret_fast_syscall+0x0/0x5
This trace occurs because vlan_for_each() is called within cpsw_ndo_set_rx_mode(), which expects the RTNL lock to be held. Since modifying vlan_for_each() to operate without the RTNL lock is not straightforward, and because ndo_set_rx_mode() is invoked both with and without the RTNL lock across different code paths, simply adding rtnl_lock() in cpsw_ndo_set_rx_mode() is not a viable solution.
To resolve this issue, we opt to execute the actual processing within a work queue, following the approach used by the icssg-prueth driver.
---
Cc: Siddharth Vadapalli <[email protected]>
Cc: Roger Quadros <[email protected]>
Cc: Andrew Lunn <[email protected]>
Cc: "David S. Miller" <[email protected]>
Cc: Eric Dumazet <[email protected]>
Cc: Jakub Kicinski <[email protected]>
Cc: Paolo Abeni <[email protected]>
Cc: Vladimir Oltean <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: [email protected]
--- drivers/net/ethernet/ti/cpsw_new.c | 34 +++++++++++++++++++++++++++++----- drivers/net/ethernet/ti/cpsw_priv.h | 1 + 2 files changed, 30 insertions(+), 5 deletions(-)
drivers/net/ethernet/ti/cpsw_new.c +29 -5
@@ -248,16 +248,22 @@ static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num)return 0 ; } -static void cpsw_ndo_set_rx_mode (struct net_device *ndev) +static void cpsw_ndo_set_rx_mode_work (struct work_struct *work) { -struct cpsw_priv *priv =netdev_priv (ndev); + struct cpsw_priv *priv = container_of(work, struct cpsw_priv, rx_mode_work);struct cpsw_common *cpsw = priv->cpsw; +struct net_device *ndev = priv->ndev; +rtnl_lock (); +if (!netif_running (ndev)) + goto unlock_rtnl; + +netif_addr_lock_bh (ndev);if (ndev->flags &IFF_PROMISC ) {/* Enable promiscuous mode */ cpsw_set_promiscious (ndev, true);cpsw_ale_set_allmulti (cpsw->ale,IFF_ALLMULTI , priv->emac_port); -return ; + goto unlock_addr; }/* Disable promiscuous mode */
@@ -270,6 +276,18 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev)/* add/remove mcast address either for real netdev or for vlan */ __hw_addr_ref_sync_dev (&ndev->mc, ndev, cpsw_add_mc_addr, cpsw_del_mc_addr); + +unlock_addr: +netif_addr_unlock_bh (ndev); +unlock_rtnl: +rtnl_unlock (); +} + +static void cpsw_ndo_set_rx_mode (struct net_device *ndev) +{ +struct cpsw_priv *priv =netdev_priv (ndev); + +schedule_work (&priv->rx_mode_work); }static unsigned int cpsw_rxbuf_total_len (unsigned int len)
@@ -1398,6 +1416,7 @@ static int cpsw_create_ports(struct cpsw_common *cpsw) priv->msg_enable =netif_msg_init (debug_level,CPSW_DEBUG ); priv->emac_port = i +1 ; priv->tx_packet_min =CPSW_MIN_PACKET_SIZE ; +INIT_WORK (&priv->rx_mode_work, cpsw_ndo_set_rx_mode_work);if (is_valid_ether_addr (slave_data->mac_addr)) {ether_addr_copy (priv->mac_addr, slave_data->mac_addr);
@@ -1447,13 +1466,18 @@ static int cpsw_create_ports(struct cpsw_common *cpsw)static void cpsw_unregister_ports (struct cpsw_common *cpsw) { +struct net_device *ndev; +struct cpsw_priv *priv;int i =0 ;for (i =0 ; i < cpsw->data.slaves; i++) { -if (!cpsw->slaves[i].ndev) + ndev = cpsw->slaves[i].ndev; +if (!ndev)continue ; -unregister_netdev (cpsw->slaves[i].ndev); + priv =netdev_priv (ndev); +unregister_netdev (ndev); +disable_work_sync (&priv->rx_mode_work); } }
drivers/net/ethernet/ti/cpsw_priv.h +1 -0
@@ -391,6 +391,7 @@ struct cpsw_priv {u32 tx_packet_min;struct cpsw_ale_ratelimit ale_bc_ratelimit;struct cpsw_ale_ratelimit ale_mc_ratelimit; +struct work_struct rx_mode_work; };#define ndev_to_cpsw (ndev) (((struct cpsw_priv *)netdev_priv(ndev))->cpsw)
Signature · 1 line
2.52.0
PATCH v5 2/2 net: cpsw: Execute ndo_set_rx_mode callback in a work queue
In reply to Kevin HaoCommit 1767bb2d47b7 ("ipv6: mcast: Don't hold RTNL for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP.") removed the RTNL lock for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP operations. However, this change triggered the following call trace on my BeagleBone Black board:
WARNING: net/8021q/vlan_core.c:236 at vlan_for_each+0x120/0x124, CPU#0: rpcbind/481 RTNL: assertion failed at net/8021q/vlan_core.c (236) Modules linked in: CPU: 0 UID: 997 PID: 481 Comm: rpcbind Not tainted 6.19.0-rc7-next-20260130-yocto-standard+ #35 PREEMPT Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x28/0x2c show_stack from dump_stack_lvl+0x30/0x38 dump_stack_lvl from __warn+0xb8/0x11c __warn from warn_slowpath_fmt+0x130/0x194 warn_slowpath_fmt from vlan_for_each+0x120/0x124 vlan_for_each from cpsw_add_mc_addr+0x54/0x98 cpsw_add_mc_addr from __hw_addr_ref_sync_dev+0xc4/0xec __hw_addr_ref_sync_dev from __dev_mc_add+0x78/0x88 __dev_mc_add from igmp6_group_added+0x84/0xec igmp6_group_added from __ipv6_dev_mc_inc+0x1fc/0x2f0 __ipv6_dev_mc_inc from __ipv6_sock_mc_join+0x124/0x1b4 __ipv6_sock_mc_join from do_ipv6_setsockopt+0x84c/0x1168 do_ipv6_setsockopt from ipv6_setsockopt+0x88/0xc8 ipv6_setsockopt from do_sock_setsockopt+0xe8/0x19c do_sock_setsockopt from __sys_setsockopt+0x84/0xac __sys_setsockopt from ret_fast_syscall+0x0/0x54
This trace occurs because vlan_for_each() is called within cpsw_ndo_set_rx_mode(), which expects the RTNL lock to be held. Since modifying vlan_for_each() to operate without the RTNL lock is not straightforward, and because ndo_set_rx_mode() is invoked both with and without the RTNL lock across different code paths, simply adding rtnl_lock() in cpsw_ndo_set_rx_mode() is not a viable solution.
To resolve this issue, we opt to execute the actual processing within a work queue, following the approach used by the icssg-prueth driver.
Please note: To reproduce this issue, I manually reverted the changes to am335x-bone-common.dtsi from commit c477358e66a3 ("ARM: dts: am335x-bone:
switch to new cpsw switch drv") in order to revert to the legacy cpsw driver.
---
Cc: Siddharth Vadapalli <[email protected]>
Cc: Roger Quadros <[email protected]>
Cc: Andrew Lunn <[email protected]>
Cc: "David S. Miller" <[email protected]>
Cc: Eric Dumazet <[email protected]>
Cc: Jakub Kicinski <[email protected]>
Cc: Paolo Abeni <[email protected]>
Cc: Vladimir Oltean <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: [email protected]
--- drivers/net/ethernet/ti/cpsw.c | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-)
drivers/net/ethernet/ti/cpsw.c +35 -6
@@ -305,12 +305,19 @@ static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num)return 0 ; } -static void cpsw_ndo_set_rx_mode (struct net_device *ndev) +static void cpsw_ndo_set_rx_mode_work (struct work_struct *work) { -struct cpsw_priv *priv =netdev_priv (ndev); + struct cpsw_priv *priv = container_of(work, struct cpsw_priv, rx_mode_work);struct cpsw_common *cpsw = priv->cpsw; +struct net_device *ndev = priv->ndev;int slave_port =-1 ; +rtnl_lock (); +if (!netif_running (ndev)) + goto unlock_rtnl; + +netif_addr_lock_bh (ndev); +if (cpsw->data.dual_emac) slave_port = priv->emac_port +1 ;
@@ -318,7 +325,7 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev)/* Enable promiscuous mode */ cpsw_set_promiscious (ndev, true);cpsw_ale_set_allmulti (cpsw->ale,IFF_ALLMULTI , slave_port); -return ; + goto unlock_addr; }else {/* Disable promiscuous mode */ cpsw_set_promiscious (ndev, false);
@@ -331,6 +338,18 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev)/* add/remove mcast address either for real netdev or for vlan */ __hw_addr_ref_sync_dev (&ndev->mc, ndev, cpsw_add_mc_addr, cpsw_del_mc_addr); + +unlock_addr: +netif_addr_unlock_bh (ndev); +unlock_rtnl: +rtnl_unlock (); +} + +static void cpsw_ndo_set_rx_mode (struct net_device *ndev) +{ +struct cpsw_priv *priv =netdev_priv (ndev); + +schedule_work (&priv->rx_mode_work); }static unsigned int cpsw_rxbuf_total_len (unsigned int len)
@@ -1472,6 +1491,7 @@ static int cpsw_probe_dual_emac(struct cpsw_priv *priv) priv_sl2->ndev = ndev; priv_sl2->dev = &ndev->dev; priv_sl2->msg_enable =netif_msg_init (debug_level,CPSW_DEBUG ); +INIT_WORK (&priv_sl2->rx_mode_work, cpsw_ndo_set_rx_mode_work);if (is_valid_ether_addr (data->slave_data[1 ].mac_addr)) { memcpy(priv_sl2->mac_addr, data->slave_data[1].mac_addr,
@@ -1653,6 +1673,7 @@ static int cpsw_probe(struct platform_device *pdev) priv->dev = dev; priv->msg_enable =netif_msg_init (debug_level,CPSW_DEBUG ); priv->emac_port =0 ; +INIT_WORK (&priv->rx_mode_work, cpsw_ndo_set_rx_mode_work);if (is_valid_ether_addr (data->slave_data[0 ].mac_addr)) {memcpy (priv->mac_addr, data->slave_data[0 ].mac_addr,ETH_ALEN );
@@ -1758,6 +1779,8 @@ static int cpsw_probe(struct platform_device *pdev)static void cpsw_remove (struct platform_device *pdev) {struct cpsw_common *cpsw =platform_get_drvdata (pdev); +struct net_device *ndev; +struct cpsw_priv *priv;int i, ret; ret =pm_runtime_resume_and_get (&pdev->dev);
@@ -1770,9 +1793,15 @@ static void cpsw_remove(struct platform_device *pdev)return ; } -for (i =0 ; i < cpsw->data.slaves; i++) -if (cpsw->slaves[i].ndev) -unregister_netdev (cpsw->slaves[i].ndev); +for (i =0 ; i < cpsw->data.slaves; i++) { + ndev = cpsw->slaves[i].ndev; +if (!ndev) +continue ; + + priv =netdev_priv (ndev); +unregister_netdev (ndev); +disable_work_sync (&priv->rx_mode_work); + }cpts_release (cpsw->cpts);cpdma_ctlr_destroy (cpsw->dma);
Signature · 1 line
2.52.0
This series was applied to netdev/net.git (main) by Jakub Kicinski <[email protected]>:
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <[email protected]>:
Quoted 8 lines · On Tue, 03 Feb 2026 10:18:29 +0800 you wrote:
These two patches resolve an RTNL assertion call trace issue in both the legacy and new cpsw drivers.
Thanks,
Kevin[...]
Here is the summary with links:
- [net,v5,1/2] net: cpsw_new: Execute ndo_set_rx_mode callback in a work queue
https://git.kernel.org/netdev/net/c/c0b5dc73a38f - [net,v5,2/2] net: cpsw: Execute ndo_set_rx_mode callback in a work queue https://git.kernel.org/netdev/net/c/0b8c878d1173
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html