* [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching
@ 2026-05-22 12:12 Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions Michael Dege
` (13 more replies)
0 siblings, 14 replies; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Hello!
The current R-Car S4 rswitch driver only supports basic HW offloading
for L2 switching/bridgeing. This patchset extends the driver with
support for VLAN aware switching.
1. For every port mode change from configuration to operation and
vice-versa you need to change to disabled mode first. The
functions rswitch_<port>_change_mode now take care of this.
2. In upcomming changes for adding vlan support struct net_device
will not be available in all cases, therefore use struct device
instead.
3. Fix typo in register define macro and remove duplicate macro.
4. Add register definitions needed fo vlan support.
5. Add exception path for packets with unknown destitination MAC
addresses.
6. Make the helper functions rswitch_reg_wait(),
rswitch_etha_change_mode() and rswitch_etha_change_mode()
available to the whole driver.
7. Add locking to protect CREC and RCDC clock register from concurent
accesses.
8. Add basic start-up time initialization needed to support VLANs.
9. Update ETHA and GWCA port HW initializations.
10. Clean up is_rdev() rswitch_device checking.
11. Provide struct rswitch_private to notifiers.
12. Add handler for FDB notifications to configure bridge MAC address
to GWCA registers and update static MAC table entry.
13. Add vlan support to L2 HW bridge.
Configuration example:
ip link add name br0 type bridge vlan_filtering 1
ip link set dev tsn0 master br0
ip link set dev tsn1 master br0
ip link set dev br0 up
ip link set dev tsn0 up
ip link set dev tsn1 up
bridge vlan add dev tsn0 vid 4
bridge vlan add dev tsn1 vid 4
bridge vlan add dev br0 vid 4 self
ip a a 192.168.1.20/24 dev br0
ip l a link br0 name br0.1 type vlan id 1
ip l a link br0 name br0.4 type vlan id 4
ip a a 192.168.2.20/24 dev br0.1
ip a a 192.168.4.20/24 dev br0.4
ip link set dev br0.1 up
ip link set dev br0.4 up
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
Changes in v5:
- Added NULL checking for calls to skb_free_frag() and revoked hw ownership
by clearing desc.die_dt.
- Added locking for queue swapping in the rx interrupt and bounds checking
for spn reading.
- Added locking for clock registers.
- Fixed initialization of GWVCC.
- Fixed return codes of rswitch_switchdev_blocking_event().
- Added clean-up should rswitch_register_notifiers() fail.
- Reworked reference tracking.
- Added flushing of work from system_long_wq.
- Link to v4: https://lore.kernel.org/r/20260511-rswitch_add_vlans-v4-0-a5a225f8faae@renesas.com
Changes in v4:
- fixed error checking in rswitch_gwca_change_mode() and rswitch_etha_change_mode().
- fixed typo in use of GENMASK macro.
- fixed comments from // to /* */.
- fixed shared queue ussage in rx interrupt.
- fixed assingment of fwpc2_clr in false branch.
- moved PORT_ATR_SET to rswitch_switchdev_blocking_event() to remove dead code.
- changed from dev_put/dev_hold to netdev_put/netdev_hold.
- reordered code to make sure the correct type is accessed with info->obj->id in
rswitch_port_obj_add()/rswitch_port_obj_del().
- Link to v3: https://lore.kernel.org/r/20260331-rswitch_add_vlans-v3-0-c37f41b1c556@renesas.com
Changes in v3:
- Fixed findings made by coccicheck
- Fixed missing 'depends on BRIDGE' in Kconfig found by test robot.
- Link to v2: https://lore.kernel.org/r/20260327-rswitch_add_vlans-v2-0-d7f4358ca57a@renesas.com
Changes in v2:
- Reworked reswitch_update_l2_hw_forwarding_gwca() as suggested.
- Fixed missing initialization found by test robot.
- Link to v1: https://lore.kernel.org/r/20260317-rswitch_add_vlans-v1-0-3a57bfa0f2d1@renesas.com
---
Michael Dege (14):
net: renesas: rswitch: improve port change mode functions
net: renesas: rswitch: use device instead of net_device
net: renesas: rswitch: fix FWPC2 register access macros
net: renesas: rswitch: add register definitions for vlan support
net: renesas: rswitch: add exception path for packets with unknown dst MAC
net: renesas: rswitch: add forwarding rules for gwca
net: renesas: rswitch: make helper functions available to whole driver
net: renesas: rswitch: add locking for agent clock control
net: renesas: rswitch: add basic vlan init to rswitch_fwd_init
net: renesas: rswitch: update port HW init
net: renesas: rswitch: clean up is_rdev rswitch_device checking
net: renesas: rswitch: add passing of rswitch_private into notifiers
net: renesas: rswitch: add handler for FDB notification
net: renesas: rswitch: add vlan aware switching
drivers/net/ethernet/renesas/Kconfig | 1 +
drivers/net/ethernet/renesas/rswitch.h | 187 +++++++-
drivers/net/ethernet/renesas/rswitch_l2.c | 649 ++++++++++++++++++++++++----
drivers/net/ethernet/renesas/rswitch_l2.h | 6 +-
drivers/net/ethernet/renesas/rswitch_main.c | 320 ++++++++++----
5 files changed, 975 insertions(+), 188 deletions(-)
---
base-commit: c26d7040520c4341b719063378d0bb6d7bc2d67f
change-id: 20260120-rswitch_add_vlans-39488bfb296c
Best regards,
--
Michael Dege <michael.dege@renesas.com>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 02/14] net: renesas: rswitch: use device instead of net_device Michael Dege
` (12 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
For ETHA and GWCA ports every mode change from operational to
configuration and vice-versa requires to change the mode to
"disable" first. This was done by calling the function twice.
Let the change_mode functione take care of this requiered step
in a single call.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch_main.c | 43 +++++++++++++++--------------
1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 6fe964816322..9b739594cc02 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -176,14 +176,21 @@ static int rswitch_gwca_change_mode(struct rswitch_private *priv,
if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
- iowrite32(mode, priv->addr + GWMC);
+ iowrite32(GWMC_OPC_DISABLE, priv->addr + GWMC);
- ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
+ ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, GWMC_OPC_DISABLE);
+ if (ret < 0)
+ return ret;
- if (mode == GWMC_OPC_DISABLE)
+ if (mode == GWMC_OPC_DISABLE) {
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
- return ret;
+ return ret;
+ }
+
+ iowrite32(mode, priv->addr + GWMC);
+
+ return rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
}
static int rswitch_gwca_mcast_table_reset(struct rswitch_private *priv)
@@ -682,9 +689,6 @@ static int rswitch_gwca_hw_init(struct rswitch_private *priv)
unsigned int i;
int err;
- err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
- if (err < 0)
- return err;
err = rswitch_gwca_change_mode(priv, GWMC_OPC_CONFIG);
if (err < 0)
return err;
@@ -717,9 +721,6 @@ static int rswitch_gwca_hw_init(struct rswitch_private *priv)
return err;
}
- err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
- if (err < 0)
- return err;
return rswitch_gwca_change_mode(priv, GWMC_OPC_OPERATION);
}
@@ -727,9 +728,6 @@ static int rswitch_gwca_hw_deinit(struct rswitch_private *priv)
{
int err;
- err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
- if (err < 0)
- return err;
err = rswitch_gwca_change_mode(priv, GWMC_OPC_RESET);
if (err < 0)
return err;
@@ -1116,14 +1114,20 @@ static int rswitch_etha_change_mode(struct rswitch_etha *etha,
if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index))
rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 1);
- iowrite32(mode, etha->addr + EAMC);
-
- ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, mode);
+ iowrite32(EAMC_OPC_DISABLE, etha->addr + EAMC);
+ ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, EAMC_OPC_DISABLE);
+ if (ret < 0)
+ return ret;
- if (mode == EAMC_OPC_DISABLE)
+ if (mode == EAMC_OPC_DISABLE) {
rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 0);
- return ret;
+ return ret;
+ }
+
+ iowrite32(mode, etha->addr + EAMC);
+
+ return rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, mode);
}
static void rswitch_etha_read_mac_address(struct rswitch_etha *etha)
@@ -1203,9 +1207,6 @@ static int rswitch_etha_hw_init(struct rswitch_etha *etha, const u8 *mac)
{
int err;
- err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
- if (err < 0)
- return err;
err = rswitch_etha_change_mode(etha, EAMC_OPC_CONFIG);
if (err < 0)
return err;
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 02/14] net: renesas: rswitch: use device instead of net_device
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 03/14] net: renesas: rswitch: fix FWPC2 register access macros Michael Dege
` (11 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
In upcomming changes for adding vlan support struct net_device
will not be available in all cases, therefore use struct device
instead.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch_main.c | 56 +++++++++++++++--------------
1 file changed, 30 insertions(+), 26 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 9b739594cc02..74d441ae1976 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -304,26 +304,30 @@ static int rswitch_gwca_queue_alloc_rx_buf(struct rswitch_gwca_queue *gq,
return -ENOMEM;
}
-static void rswitch_gwca_queue_free(struct net_device *ndev,
+static void rswitch_gwca_queue_free(struct device *dev,
struct rswitch_gwca_queue *gq)
{
unsigned int i;
if (!gq->dir_tx) {
- dma_free_coherent(ndev->dev.parent,
- sizeof(struct rswitch_ext_ts_desc) *
- (gq->ring_size + 1), gq->rx_ring, gq->ring_dma);
- gq->rx_ring = NULL;
-
+ if (gq->rx_ring) {
+ dma_free_coherent(dev,
+ sizeof(struct rswitch_ext_ts_desc) *
+ (gq->ring_size + 1), gq->rx_ring, gq->ring_dma);
+ gq->rx_ring = NULL;
+ }
for (i = 0; i < gq->ring_size; i++)
- skb_free_frag(gq->rx_bufs[i]);
+ if (gq->rx_bufs[i])
+ skb_free_frag(gq->rx_bufs[i]);
kfree(gq->rx_bufs);
gq->rx_bufs = NULL;
} else {
- dma_free_coherent(ndev->dev.parent,
- sizeof(struct rswitch_ext_desc) *
- (gq->ring_size + 1), gq->tx_ring, gq->ring_dma);
- gq->tx_ring = NULL;
+ if (gq->tx_ring) {
+ dma_free_coherent(dev,
+ sizeof(struct rswitch_ext_desc) *
+ (gq->ring_size + 1), gq->tx_ring, gq->ring_dma);
+ gq->tx_ring = NULL;
+ }
kfree(gq->skbs);
gq->skbs = NULL;
kfree(gq->unmap_addrs);
@@ -359,7 +363,7 @@ static int rswitch_gwca_queue_alloc(struct net_device *ndev,
if (rswitch_gwca_queue_alloc_rx_buf(gq, 0, gq->ring_size) < 0)
goto out;
- gq->rx_ring = dma_alloc_coherent(ndev->dev.parent,
+ gq->rx_ring = dma_alloc_coherent(&priv->pdev->dev,
sizeof(struct rswitch_ext_ts_desc) *
(gq->ring_size + 1), &gq->ring_dma, GFP_KERNEL);
} else {
@@ -369,7 +373,7 @@ static int rswitch_gwca_queue_alloc(struct net_device *ndev,
gq->unmap_addrs = kzalloc_objs(*gq->unmap_addrs, gq->ring_size);
if (!gq->unmap_addrs)
goto out;
- gq->tx_ring = dma_alloc_coherent(ndev->dev.parent,
+ gq->tx_ring = dma_alloc_coherent(&priv->pdev->dev,
sizeof(struct rswitch_ext_desc) *
(gq->ring_size + 1), &gq->ring_dma, GFP_KERNEL);
}
@@ -387,7 +391,7 @@ static int rswitch_gwca_queue_alloc(struct net_device *ndev,
return 0;
out:
- rswitch_gwca_queue_free(ndev, gq);
+ rswitch_gwca_queue_free(&priv->pdev->dev, gq);
return -ENOMEM;
}
@@ -469,12 +473,11 @@ static void rswitch_gwca_ts_queue_fill(struct rswitch_private *priv,
}
}
-static int rswitch_gwca_queue_ext_ts_fill(struct net_device *ndev,
+static int rswitch_gwca_queue_ext_ts_fill(struct device *dev,
struct rswitch_gwca_queue *gq,
unsigned int start_index,
unsigned int num)
{
- struct rswitch_device *rdev = netdev_priv(ndev);
struct rswitch_ext_ts_desc *desc;
unsigned int i, index;
dma_addr_t dma_addr;
@@ -483,18 +486,17 @@ static int rswitch_gwca_queue_ext_ts_fill(struct net_device *ndev,
index = (i + start_index) % gq->ring_size;
desc = &gq->rx_ring[index];
if (!gq->dir_tx) {
- dma_addr = dma_map_single(ndev->dev.parent,
+ dma_addr = dma_map_single(dev,
gq->rx_bufs[index] + RSWITCH_HEADROOM,
RSWITCH_MAP_BUF_SIZE,
DMA_FROM_DEVICE);
- if (dma_mapping_error(ndev->dev.parent, dma_addr))
+ if (dma_mapping_error(dev, dma_addr))
goto err;
desc->desc.info_ds = cpu_to_le16(RSWITCH_DESC_BUF_SIZE);
rswitch_desc_set_dptr(&desc->desc, dma_addr);
dma_wmb();
desc->desc.die_dt = DT_FEMPTY | DIE;
- desc->info1 = cpu_to_le64(INFO1_SPN(rdev->etha->index));
} else {
desc->desc.die_dt = DT_EEMPTY | DIE;
}
@@ -507,8 +509,10 @@ static int rswitch_gwca_queue_ext_ts_fill(struct net_device *ndev,
for (; i-- > 0; ) {
index = (i + start_index) % gq->ring_size;
desc = &gq->rx_ring[index];
+ desc->desc.die_dt = 0x00;
+ dma_wmb();
dma_addr = rswitch_desc_get_dptr(&desc->desc);
- dma_unmap_single(ndev->dev.parent, dma_addr,
+ dma_unmap_single(dev, dma_addr,
RSWITCH_MAP_BUF_SIZE, DMA_FROM_DEVICE);
}
}
@@ -516,7 +520,7 @@ static int rswitch_gwca_queue_ext_ts_fill(struct net_device *ndev,
return -ENOMEM;
}
-static int rswitch_gwca_queue_ext_ts_format(struct net_device *ndev,
+static int rswitch_gwca_queue_ext_ts_format(struct device *dev,
struct rswitch_private *priv,
struct rswitch_gwca_queue *gq)
{
@@ -526,7 +530,7 @@ static int rswitch_gwca_queue_ext_ts_format(struct net_device *ndev,
int err;
memset(gq->rx_ring, 0, ring_size);
- err = rswitch_gwca_queue_ext_ts_fill(ndev, gq, 0, gq->ring_size);
+ err = rswitch_gwca_queue_ext_ts_fill(dev, gq, 0, gq->ring_size);
if (err < 0)
return err;
@@ -638,7 +642,7 @@ static void rswitch_txdmac_free(struct net_device *ndev)
{
struct rswitch_device *rdev = netdev_priv(ndev);
- rswitch_gwca_queue_free(ndev, rdev->tx_queue);
+ rswitch_gwca_queue_free(ndev->dev.parent, rdev->tx_queue);
rswitch_gwca_put(rdev->priv, rdev->tx_queue);
}
@@ -672,7 +676,7 @@ static void rswitch_rxdmac_free(struct net_device *ndev)
{
struct rswitch_device *rdev = netdev_priv(ndev);
- rswitch_gwca_queue_free(ndev, rdev->rx_queue);
+ rswitch_gwca_queue_free(ndev->dev.parent, rdev->rx_queue);
rswitch_gwca_put(rdev->priv, rdev->rx_queue);
}
@@ -681,7 +685,7 @@ static int rswitch_rxdmac_init(struct rswitch_private *priv, unsigned int index)
struct rswitch_device *rdev = priv->rdev[index];
struct net_device *ndev = rdev->ndev;
- return rswitch_gwca_queue_ext_ts_format(ndev, priv, rdev->rx_queue);
+ return rswitch_gwca_queue_ext_ts_format(ndev->dev.parent, priv, rdev->rx_queue);
}
static int rswitch_gwca_hw_init(struct rswitch_private *priv)
@@ -872,7 +876,7 @@ static bool rswitch_rx(struct net_device *ndev, int *quota)
ret = rswitch_gwca_queue_alloc_rx_buf(gq, gq->dirty, num);
if (ret < 0)
goto err;
- ret = rswitch_gwca_queue_ext_ts_fill(ndev, gq, gq->dirty, num);
+ ret = rswitch_gwca_queue_ext_ts_fill(ndev->dev.parent, gq, gq->dirty, num);
if (ret < 0)
goto err;
gq->dirty = rswitch_next_queue_index(gq, false, num);
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 03/14] net: renesas: rswitch: fix FWPC2 register access macros
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 02/14] net: renesas: rswitch: use device instead of net_device Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 04/14] net: renesas: rswitch: add register definitions for vlan support Michael Dege
` (10 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Fix typo in macro name and remove duplicate macro definition.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 3 +--
drivers/net/ethernet/renesas/rswitch_l2.c | 2 +-
drivers/net/ethernet/renesas/rswitch_main.c | 2 +-
3 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index aa605304fed0..340524d995ac 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -826,8 +826,7 @@ enum rswitch_gwca_mode {
#define FWPC1_DDE BIT(0)
#define FWPC2(i) (FWPC20 + (i) * 0x10)
-#define FWCP2_LTWFW GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16)
-#define FWCP2_LTWFW_MASK GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16)
+#define FWPC2_LTWFW GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16)
#define FWPBFC(i) (FWPBFC0 + (i) * 0x10)
#define FWPBFC_PBDV GENMASK(RSWITCH_NUM_AGENTS - 1, 0)
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index 9433cd8adced..709524c8a5c4 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.c
+++ b/drivers/net/ethernet/renesas/rswitch_l2.c
@@ -82,7 +82,7 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
*
* Do not allow L2 forwarding to self for hw port.
*/
- iowrite32(FIELD_PREP(FWCP2_LTWFW_MASK, fwd_mask | BIT(rdev->port)),
+ iowrite32(FIELD_PREP(FWPC2_LTWFW, fwd_mask | BIT(rdev->port)),
priv->addr + FWPC2(rdev->port));
}
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 74d441ae1976..9510bcdf9e83 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -129,7 +129,7 @@ static int rswitch_fwd_init(struct rswitch_private *priv)
iowrite32(FIELD_PREP(FWCP1_LTHFW, all_ports_mask),
priv->addr + FWPC1(i));
/* Disallow L2 forwarding */
- iowrite32(FIELD_PREP(FWCP2_LTWFW, all_ports_mask),
+ iowrite32(FIELD_PREP(FWPC2_LTWFW, all_ports_mask),
priv->addr + FWPC2(i));
/* Disallow port based forwarding */
iowrite32(0, priv->addr + FWPBFC(i));
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 04/14] net: renesas: rswitch: add register definitions for vlan support
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (2 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 03/14] net: renesas: rswitch: fix FWPC2 register access macros Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 05/14] net: renesas: rswitch: add exception path for packets with unknown dst MAC Michael Dege
` (9 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Add missing register and bit definitions for vlan support.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 156 ++++++++++++++++++++++++++++++++-
1 file changed, 152 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index 340524d995ac..e56c15dd4ecd 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -7,8 +7,10 @@
#ifndef __RSWITCH_H__
#define __RSWITCH_H__
+#include <linux/if_vlan.h>
#include <linux/platform_device.h>
#include <linux/phy.h>
+#include <net/switchdev.h>
#include "rcar_gen4_ptp.h"
@@ -221,7 +223,7 @@ enum rswitch_reg {
FWMACTL1 = FWRO + 0x4634,
FWMACTL2 = FWRO + 0x4638,
FWMACTL3 = FWRO + 0x463c,
- FWMACTL4 = FWRO + 0x4640,
+ FWMACTL40 = FWRO + 0x4640,
FWMACTL5 = FWRO + 0x4650,
FWMACTLR = FWRO + 0x4654,
FWMACTIM = FWRO + 0x4660,
@@ -249,7 +251,7 @@ enum rswitch_reg {
FWVLANTL0 = FWRO + 0x4910,
FWVLANTL1 = FWRO + 0x4914,
FWVLANTL2 = FWRO + 0x4918,
- FWVLANTL3 = FWRO + 0x4920,
+ FWVLANTL30 = FWRO + 0x4920,
FWVLANTL4 = FWRO + 0x4930,
FWVLANTLR = FWRO + 0x4934,
FWVLANTIM = FWRO + 0x4940,
@@ -508,7 +510,7 @@ enum rswitch_reg {
EACTDQMLM = TARO + 0x010c,
EAVCC = TARO + 0x0130,
EAVTC = TARO + 0x0134,
- EATTFC = TARO + 0x0138,
+ EARTFC = TARO + 0x0138,
EACAEC = TARO + 0x0200,
EACC = TARO + 0x0204,
EACAIVC0 = TARO + 0x0220,
@@ -729,6 +731,41 @@ enum rswitch_etha_mode {
#define EAMS_OPS_MASK EAMC_OPC_OPERATION
+/* bit field definitions for EAVCC and GWVCC */
+#define VEM GENMASK(18, 16)
+#define VIM BIT(0)
+
+/* bit field definitions for EAVTC and GWVTC */
+#define STD BIT(31)
+#define STP GENMASK(30, 28)
+#define STV GENMASK(27, 16)
+#define CTD BIT(15)
+#define CTP GENMASK(14, 12)
+#define CTV GENMASK(11, 0)
+
+/* bit field definitions for EARTFC and GWTTCF */
+#define UT BIT(8)
+#define SCRT BIT(7)
+#define SCT BIT(6)
+#define CRT BIT(5)
+#define CT BIT(4)
+#define CSRT BIT(3)
+#define CST BIT(2)
+#define RT BIT(1)
+#define NT BIT(0)
+
+/* bit field definitions for EARDQDC and GWRDQDC */
+#define DQD GENMASK(10, 0)
+#define DES_RAM_DP 0x400
+
+enum vlan_egress_mode {
+ NO_VLAN,
+ C_TAG_VLAN,
+ HW_C_TAG_VLAN,
+ SC_TAG_VLAN,
+ HW_SC_TAG_VLAN,
+};
+
#define EAVCC_VEM_SC_TAG (0x3 << 16)
#define MPIC_PIS GENMASK(2, 0)
@@ -806,6 +843,22 @@ enum rswitch_gwca_mode {
#define CABPPFLC_INIT_VALUE 0x00800080
/* MFWD */
+#define FWGC_SVM GENMASK(1, 0)
+
+enum switch_vlan_mode {
+ NO_VLAN_MODE,
+ C_TAG,
+ SC_TAG,
+};
+
+/* FWCEPRC2 */
+#define FDMACSLFEF BIT(19)
+#define FDMACUFEF BIT(3)
+
+/* FWCEPTC */
+#define EPCS GENMASK(17, 16)
+#define EPCSD GENMASK(6, 0)
+
#define FWPC0(i) (FWPC00 + (i) * 0x10)
#define FWPC0_LTHTA BIT(0)
#define FWPC0_IP4UE BIT(3)
@@ -816,10 +869,13 @@ enum rswitch_gwca_mode {
#define FWPC0_IPDSA BIT(12)
#define FWPC0_IPHLA BIT(18)
#define FWPC0_MACDSA BIT(20)
+#define FWPC0_MACRUDA BIT(21)
#define FWPC0_MACSSA BIT(23)
#define FWPC0_MACHLA BIT(26)
#define FWPC0_MACHMA BIT(27)
#define FWPC0_VLANSA BIT(28)
+#define FWPC0_VLANRU BIT(29)
+#define FWPC0_VLANRUS BIT(30)
#define FWPC1(i) (FWPC10 + (i) * 0x10)
#define FWCP1_LTHFW GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16)
@@ -847,6 +903,98 @@ enum rswitch_gwca_mode {
#define FWMACAGC_MACAGOG BIT(28)
#define FWMACAGC_MACDESOG BIT(29)
+/* FWMACTL0 */
+#define FWMACTL0_ED BIT(16)
+#define FWMACTL0_HLD BIT(10)
+#define FWMACTL0_DE BIT(9)
+#define FWMACTL0_SL BIT(8)
+
+/* FWMACTL3 */
+#define FWMACTL3_DSLV GENMASK(16 + RSWITCH_NUM_AGENTS - 1, 16)
+#define FWMACTL3_SSLV GENMASK(RSWITCH_NUM_AGENTS - 1, 0)
+
+/* FWMACTL4 */
+#define FWMACTL4(i) (FWMACTL40 + (i) * 4)
+#define FWMACTL4_CSDL GENMASK(6, 0)
+
+/* FWMACTL5 */
+#define FWMACTL5_CME BIT(21)
+#define FWMACTL5_EME BIT(20)
+#define FWMACTL5_IPU BIT(19)
+#define FWMACTL5_IPV GENMASK(18, 16)
+#define FWMACTL5_DV GENMASK(6, 0)
+
+/* FWMACTLR */
+#define FWMACTLR_L BIT(31)
+#define FWMACTLR_LCN GENMASK(25, 16)
+#define FWMACTLR_LO BIT(3)
+#define FWMACTLR_LEF BIT(2)
+#define FWMACTLR_LSF BIT(1)
+#define FWMACTLR_LF BIT(0)
+
+/* FWVLANTEC */
+#define VLANTMUE GENMASK(28, 16)
+
+/* FWVLANTL0 */
+#define VLANED BIT(16)
+#define VLANHLDL BIT(10)
+#define VLANSLL BIT(8)
+
+/* FWVLANTL1 */
+#define VLANVIDL GENMASK(11, 0)
+
+/* FWVLANTL2 */
+#define VLANSLVL GENMASK(6, 0)
+
+/* FWVLANTL3 */
+#define FWVLANTL3(i) (FWVLANTL30 + (i) * 4)
+#define VLANCSDL GENMASK(6, 0)
+
+/* FWVLANTL4 */
+#define VLANCMEL BIT(21)
+#define VLANEMEL BIT(20)
+#define VLANIPUL BIT(19)
+#define VLANIPVL GENMASK(18, 16)
+#define VLANDVL GENMASK(6, 0)
+
+/* FWVLANTLR */
+#define VLANTL BIT(31)
+#define VLANLO BIT(3)
+#define VLANLEF BIT(2)
+#define VLANLSF BIT(1)
+#define VLANLF BIT(0)
+
+/* FWVLANTIM */
+#define VLANTR BIT(1)
+#define VLANTIOG BIT(0)
+
+/* FWVLANTEM */
+#define VLANTUEN GENMASK(28, 16)
+#define VLANTEN GENMASK(12, 0)
+
+/* FWVLANTS */
+#define VLANVIDS GENMASK(11, 0)
+
+/* FWVLANTSR0 */
+#define VLANTS BIT(31)
+#define VLANHLDS BIT(10)
+#define VLANSLS BIT(8)
+#define VLANSNF BIT(1)
+#define VLANSEF BIT(0)
+
+/* FWVLANTSR1 */
+#define VLANSLVS GENMASK(6, 0)
+
+/* FWVLANTSR2 */
+#define FWVLANTSR2(i) (FWVLANTSR20 + (i) * 4)
+
+/* FWVLANTSR3 */
+#define VLANCMES BIT(21)
+#define VLANEMES BIT(20)
+#define VLANIPUS BIT(19)
+#define VLANIPVS GENMASK(18, 16)
+#define VLANDVS GENMASK(6, 0)
+
#define RSW_AGEING_CLK_PER_US 0x140
#define RSW_AGEING_TIME 300
@@ -904,7 +1052,7 @@ enum DIE_DT {
#define INFO1_DV(port_vector) ((u64)(port_vector) << 48ULL)
/* For reception */
-#define INFO1_SPN(port) ((u64)(port) << 36ULL)
+#define SPN GENMASK_U64(38, 36)
/* For timestamp descriptor in dptrl (Byte 4 to 7) */
#define TS_DESC_TSUN(dptrl) ((dptrl) & GENMASK(7, 0))
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 05/14] net: renesas: rswitch: add exception path for packets with unknown dst MAC
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (3 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 04/14] net: renesas: rswitch: add register definitions for vlan support Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 06/14] net: renesas: rswitch: add forwarding rules for gwca Michael Dege
` (8 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Packets with unknown MAC address cannot be handled by the HW forwarding.
These need to be forwarded, via an exception path, to the network driver.
Creates a queue for the exeption path. Packets received with unknown
src/dst address need to be passed to the CPU. The received packet does not
have the correct source port information, this is derived from the
descriptor and added to the new queue. The received packet is added to the
new queue and sent to the CPU for MAC learning. The CPU will broadcast the
received packet, to all ports. This is how the HW learns the new MAC
address.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 2 +
drivers/net/ethernet/renesas/rswitch_main.c | 103 ++++++++++++++++++++++++++--
2 files changed, 98 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index e56c15dd4ecd..8415f52a239e 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -1148,6 +1148,7 @@ struct rswitch_gwca {
struct rswitch_gwca_queue *queues;
int num_queues;
struct rswitch_gwca_queue ts_queue;
+ struct rswitch_gwca_queue *l2_shared_rx_queue;
DECLARE_BITMAP(used, RSWITCH_MAX_NUM_QUEUES);
u32 tx_irq_bits[RSWITCH_NUM_IRQ_REGS];
u32 rx_irq_bits[RSWITCH_NUM_IRQ_REGS];
@@ -1162,6 +1163,7 @@ struct rswitch_device {
void __iomem *addr;
struct rswitch_gwca_queue *tx_queue;
struct rswitch_gwca_queue *rx_queue;
+ struct rswitch_gwca_queue *rx_old_queue;
struct sk_buff *ts_skb[TS_TAGS_PER_PORT];
DECLARE_BITMAP(ts_skb_used, TS_TAGS_PER_PORT);
bool disabled;
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 9510bcdf9e83..1f4ec65f6f9b 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -688,6 +688,38 @@ static int rswitch_rxdmac_init(struct rswitch_private *priv, unsigned int index)
return rswitch_gwca_queue_ext_ts_format(ndev->dev.parent, priv, rdev->rx_queue);
}
+static void rswitch_shared_rx_queue_free(struct rswitch_private *priv)
+{
+ struct rswitch_gwca *gwca = &priv->gwca;
+
+ rswitch_gwca_put(priv, gwca->l2_shared_rx_queue);
+ rswitch_gwca_queue_free(&priv->pdev->dev, gwca->l2_shared_rx_queue);
+}
+
+static int rswitch_shared_rx_queue_alloc(struct rswitch_private *priv)
+{
+ struct rswitch_gwca *gwca = &priv->gwca;
+ struct device *dev = &priv->pdev->dev;
+
+ int err;
+
+ gwca->l2_shared_rx_queue = rswitch_gwca_get(priv);
+ if (!gwca->l2_shared_rx_queue)
+ return -EBUSY;
+
+ err = rswitch_gwca_queue_alloc(NULL, priv, gwca->l2_shared_rx_queue, false, RX_RING_SIZE);
+ if (err < 0) {
+ rswitch_gwca_put(priv, gwca->l2_shared_rx_queue);
+ return err;
+ }
+
+ err = rswitch_gwca_queue_ext_ts_format(dev, priv, gwca->l2_shared_rx_queue);
+
+ if (err < 0)
+ rswitch_shared_rx_queue_free(priv);
+ return err;
+}
+
static int rswitch_gwca_hw_init(struct rswitch_private *priv)
{
unsigned int i;
@@ -725,6 +757,10 @@ static int rswitch_gwca_hw_init(struct rswitch_private *priv)
return err;
}
+ err = rswitch_shared_rx_queue_alloc(priv);
+ if (err < 0)
+ return err;
+
return rswitch_gwca_change_mode(priv, GWMC_OPC_OPERATION);
}
@@ -736,6 +772,8 @@ static int rswitch_gwca_hw_deinit(struct rswitch_private *priv)
if (err < 0)
return err;
+ rswitch_shared_rx_queue_free(priv);
+
return rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
}
@@ -944,10 +982,15 @@ static int rswitch_poll(struct napi_struct *napi, int budget)
if (napi_complete_done(napi, budget - quota)) {
spin_lock_irqsave(&priv->lock, flags);
+ if (rdev->rx_old_queue) {
+ rdev->rx_queue = rdev->rx_old_queue;
+ rdev->rx_old_queue = NULL;
+ }
if (test_bit(rdev->port, priv->opened_ports)) {
rswitch_enadis_data_irq(priv, rdev->tx_queue->index, true);
rswitch_enadis_data_irq(priv, rdev->rx_queue->index, true);
}
+ rswitch_enadis_data_irq(priv, priv->gwca.l2_shared_rx_queue->index, true);
spin_unlock_irqrestore(&priv->lock, flags);
}
@@ -960,15 +1003,41 @@ static int rswitch_poll(struct napi_struct *napi, int budget)
return 0;
}
-static void rswitch_queue_interrupt(struct net_device *ndev)
+static void rswitch_queue_interrupt(struct rswitch_private *priv, struct rswitch_gwca_queue *gq)
{
- struct rswitch_device *rdev = netdev_priv(ndev);
+ struct rswitch_ext_ts_desc *desc;
+ struct rswitch_device *rdev;
+ struct net_device *ndev;
+ unsigned long flags;
+ u32 spn;
+
+ /* If we receive a shared queue through the exception path, it will be missing the ndev
+ * pointer. This needs to be added to be able to determine from which port the packet was
+ * received. Then we temporarily exchange the rx_queue pointer in rdev. This will be
+ * restored after the packet has been processed.
+ */
+
+ if (gq->index == priv->gwca.l2_shared_rx_queue->index) {
+ desc = &gq->rx_ring[gq->cur];
+ spn = FIELD_GET(SPN, le64_to_cpu(desc->info1));
+ ndev = priv->rdev[spn]->ndev;
+ rdev = netdev_priv(ndev);
+ gq->ndev = ndev;
+ /* store original rx_queue */
+ spin_lock_irqsave(&priv->lock, flags);
+ rdev->rx_old_queue = rdev->rx_queue;
+ rdev->rx_queue = gq;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ } else {
+ rdev = netdev_priv(gq->ndev);
+ }
if (napi_schedule_prep(&rdev->napi)) {
- spin_lock(&rdev->priv->lock);
- rswitch_enadis_data_irq(rdev->priv, rdev->tx_queue->index, false);
- rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, false);
- spin_unlock(&rdev->priv->lock);
+ spin_lock(&priv->lock);
+ rswitch_enadis_data_irq(priv, rdev->tx_queue->index, false);
+ rswitch_enadis_data_irq(priv, rdev->rx_queue->index, false);
+ rswitch_enadis_data_irq(priv, priv->gwca.l2_shared_rx_queue->index, false);
+ spin_unlock(&priv->lock);
__napi_schedule(&rdev->napi);
}
}
@@ -986,7 +1055,7 @@ static irqreturn_t rswitch_data_irq(struct rswitch_private *priv, u32 *dis)
continue;
rswitch_ack_data_irq(priv, gq->index);
- rswitch_queue_interrupt(gq->ndev);
+ rswitch_queue_interrupt(priv, gq);
}
return IRQ_HANDLED;
@@ -1523,6 +1592,14 @@ static int rswitch_serdes_set_params(struct rswitch_device *rdev)
return phy_set_speed(rdev->serdes, rdev->etha->speed);
}
+static void rswitch_etha_set_exception_path(struct rswitch_private *priv)
+{
+ iowrite32(FDMACUFEF, priv->addr + FWCEPRC2);
+ iowrite32(FIELD_PREP(EPCS, GWCA_INDEX) |
+ FIELD_PREP(EPCSD, priv->gwca.l2_shared_rx_queue->index),
+ priv->addr + FWCEPTC);
+}
+
static int rswitch_ether_port_init_one(struct rswitch_device *rdev)
{
int err;
@@ -1576,6 +1653,8 @@ static int rswitch_ether_port_init_all(struct rswitch_private *priv)
unsigned int i;
int err;
+ rswitch_etha_set_exception_path(priv);
+
rswitch_for_each_enabled_port(priv, i) {
err = rswitch_ether_port_init_one(priv->rdev[i]);
if (err)
@@ -1626,6 +1705,7 @@ static int rswitch_open(struct net_device *ndev)
bitmap_set(rdev->priv->opened_ports, rdev->port, 1);
rswitch_enadis_data_irq(rdev->priv, rdev->tx_queue->index, true);
rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, true);
+ rswitch_enadis_data_irq(rdev->priv, rdev->priv->gwca.l2_shared_rx_queue->index, true);
spin_unlock_irqrestore(&rdev->priv->lock, flags);
phy_start(ndev->phydev);
@@ -1653,6 +1733,11 @@ static int rswitch_stop(struct net_device *ndev)
rswitch_enadis_data_irq(rdev->priv, rdev->tx_queue->index, false);
rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, false);
bitmap_clear(rdev->priv->opened_ports, rdev->port, 1);
+ if (bitmap_empty(rdev->priv->opened_ports, 1))
+ rswitch_enadis_data_irq(rdev->priv,
+ rdev->priv->gwca.l2_shared_rx_queue->index,
+ false);
+
spin_unlock_irqrestore(&rdev->priv->lock, flags);
napi_disable(&rdev->napi);
@@ -1959,6 +2044,7 @@ static int rswitch_device_alloc(struct rswitch_private *priv, unsigned int index
rdev->port = index;
rdev->etha = &priv->etha[index];
rdev->addr = priv->addr;
+ rdev->rx_old_queue = NULL;
ndev->base_addr = (unsigned long)rdev->addr;
snprintf(ndev->name, IFNAMSIZ, "tsn%d", index);
@@ -2176,6 +2262,9 @@ static int renesas_eth_sw_probe(struct platform_device *pdev)
priv->gwca.index = AGENT_INDEX_GWCA;
priv->gwca.num_queues = min(RSWITCH_NUM_PORTS * NUM_QUEUES_PER_NDEV,
RSWITCH_MAX_NUM_QUEUES);
+ /* One extra queue for L2 switch reception */
+ priv->gwca.num_queues = min(priv->gwca.num_queues + 1,
+ RSWITCH_MAX_NUM_QUEUES);
priv->gwca.queues = devm_kcalloc(&pdev->dev, priv->gwca.num_queues,
sizeof(*priv->gwca.queues), GFP_KERNEL);
if (!priv->gwca.queues)
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 06/14] net: renesas: rswitch: add forwarding rules for gwca
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (4 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 05/14] net: renesas: rswitch: add exception path for packets with unknown dst MAC Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 07/14] net: renesas: rswitch: make helper functions available to whole driver Michael Dege
` (7 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Add rules to forward packets from the Ethernet ports to the CPU port (GWCA)
using L2 forwarding instead of port forwarding.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch_l2.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index 709524c8a5c4..3cc0ce8762f3 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.c
+++ b/drivers/net/ethernet/renesas/rswitch_l2.c
@@ -93,10 +93,32 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
}
}
+static void rswitch_update_l2_hw_forwarding_gwca(struct rswitch_private *priv)
+{
+ struct rswitch_device *rdev;
+ u32 fwpc0_set, fwpc0_clr, fwpc2_set, fwpc2_clr;
+
+ fwpc0_clr = FWPC0_MACSSA | FWPC0_MACDSA | FWPC0_MACRUDA;
+ fwpc0_set = fwpc0_clr;
+ fwpc2_clr = FIELD_PREP(FWPC2_LTWFW, BIT(AGENT_INDEX_GWCA));
+ fwpc2_set = fwpc2_clr;
+
+ (priv->offload_brdev) ? (fwpc0_clr = 0, fwpc2_set = 0)
+ : (fwpc0_set = 0, fwpc2_clr = 0);
+
+ rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA), fwpc0_clr, fwpc0_set);
+
+ rswitch_for_all_ports(priv, rdev) {
+ rswitch_modify(priv->addr, FWPC2(rdev->etha->index),
+ fwpc2_clr, fwpc2_set);
+ }
+}
+
void rswitch_update_l2_offload(struct rswitch_private *priv)
{
rswitch_update_l2_hw_learning(priv);
rswitch_update_l2_hw_forwarding(priv);
+ rswitch_update_l2_hw_forwarding_gwca(priv);
}
static void rswitch_update_offload_brdev(struct rswitch_private *priv)
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 07/14] net: renesas: rswitch: make helper functions available to whole driver
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (5 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 06/14] net: renesas: rswitch: add forwarding rules for gwca Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 08/14] net: renesas: rswitch: add locking for agent clock control Michael Dege
` (6 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
These functions will be needed in L2 and vlan handling. Remove static
declaration and add function prototype to rswitch.h.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 4 +++-
drivers/net/ethernet/renesas/rswitch_main.c | 14 ++++++--------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index 8415f52a239e..5feeb555e9a8 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -1219,5 +1219,7 @@ struct rswitch_private {
bool is_rdev(const struct net_device *ndev);
void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set);
-
+int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected);
+int rswitch_gwca_change_mode(struct rswitch_private *priv, enum rswitch_gwca_mode mode);
+int rswitch_etha_change_mode(struct rswitch_etha *etha, enum rswitch_etha_mode mode);
#endif /* #ifndef __RSWITCH_H__ */
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 1f4ec65f6f9b..20ac8f3b9357 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/* Renesas Ethernet Switch device driver
*
- * Copyright (C) 2022-2025 Renesas Electronics Corporation
+ * Copyright (C) 2022-2026 Renesas Electronics Corporation
*/
#include <linux/clk.h>
@@ -32,7 +32,7 @@
#define RSWITCH_GPTP_OFFSET_S4 0x00018000
-static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected)
+int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected)
{
u32 val;
@@ -168,14 +168,13 @@ static int rswitch_fwd_init(struct rswitch_private *priv)
}
/* Gateway CPU agent block (GWCA) */
-static int rswitch_gwca_change_mode(struct rswitch_private *priv,
- enum rswitch_gwca_mode mode)
+int rswitch_gwca_change_mode(struct rswitch_private *priv,
+ enum rswitch_gwca_mode mode)
{
int ret;
if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
-
iowrite32(GWMC_OPC_DISABLE, priv->addr + GWMC);
ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, GWMC_OPC_DISABLE);
@@ -1179,14 +1178,13 @@ static int rswitch_gwca_ts_request_irqs(struct rswitch_private *priv)
}
/* Ethernet TSN Agent block (ETHA) and Ethernet MAC IP block (RMAC) */
-static int rswitch_etha_change_mode(struct rswitch_etha *etha,
- enum rswitch_etha_mode mode)
+int rswitch_etha_change_mode(struct rswitch_etha *etha,
+ enum rswitch_etha_mode mode)
{
int ret;
if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index))
rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 1);
-
iowrite32(EAMC_OPC_DISABLE, etha->addr + EAMC);
ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, EAMC_OPC_DISABLE);
if (ret < 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 08/14] net: renesas: rswitch: add locking for agent clock control
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (6 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 07/14] net: renesas: rswitch: make helper functions available to whole driver Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 09/14] net: renesas: rswitch: add basic vlan init to rswitch_fwd_init Michael Dege
` (5 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
RCEC and RCDC clock registers are shared by all ports (ETHA and GWCA),
there is a potential risk of a race condition leading to an undefined
state.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 3 +++
drivers/net/ethernet/renesas/rswitch_main.c | 17 +++++++++++++++--
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index 5feeb555e9a8..f85dd96a9bfb 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -1089,6 +1089,7 @@ struct rswitch_etha {
unsigned int index;
void __iomem *addr;
void __iomem *coma_addr;
+ struct rswitch_private *priv;
bool external_phy;
struct mii_bus *mii;
phy_interface_t phy_interface;
@@ -1207,6 +1208,8 @@ struct rswitch_private {
struct list_head port_list;
spinlock_t lock; /* lock interrupt registers' control */
+ spinlock_t agent_lock; /* rswitch agent clock control */
+
struct clk *clk;
bool etha_no_runtime_change;
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 20ac8f3b9357..fc80c65c2131 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -173,8 +173,12 @@ int rswitch_gwca_change_mode(struct rswitch_private *priv,
{
int ret;
- if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
+ if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index)) {
+ spin_lock(&priv->agent_lock);
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
+ spin_lock(&priv->agent_lock);
+ }
+
iowrite32(GWMC_OPC_DISABLE, priv->addr + GWMC);
ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, GWMC_OPC_DISABLE);
@@ -182,7 +186,9 @@ int rswitch_gwca_change_mode(struct rswitch_private *priv,
return ret;
if (mode == GWMC_OPC_DISABLE) {
+ spin_lock(&priv->agent_lock);
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
+ spin_unlock(&priv->agent_lock);
return ret;
}
@@ -1183,15 +1189,21 @@ int rswitch_etha_change_mode(struct rswitch_etha *etha,
{
int ret;
- if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index))
+ if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index)) {
+ spin_lock(ða->priv->agent_lock);
rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 1);
+ spin_unlock(ða->priv->agent_lock);
+ }
+
iowrite32(EAMC_OPC_DISABLE, etha->addr + EAMC);
ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, EAMC_OPC_DISABLE);
if (ret < 0)
return ret;
if (mode == EAMC_OPC_DISABLE) {
+ spin_lock(ða->priv->agent_lock);
rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 0);
+ spin_unlock(ða->priv->agent_lock);
return ret;
}
@@ -2010,6 +2022,7 @@ static void rswitch_etha_init(struct rswitch_private *priv, unsigned int index)
etha->index = index;
etha->addr = priv->addr + RSWITCH_ETHA_OFFSET + index * RSWITCH_ETHA_SIZE;
etha->coma_addr = priv->addr;
+ etha->priv = priv;
/* MPIC.PSMCS = (clk [MHz] / (MDC frequency [MHz] * 2) - 1.
* Calculating PSMCS value as MDC frequency = 2.5MHz. So, multiply
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 09/14] net: renesas: rswitch: add basic vlan init to rswitch_fwd_init
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (7 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 08/14] net: renesas: rswitch: add locking for agent clock control Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 10/14] net: renesas: rswitch: update port HW init Michael Dege
` (4 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Add basic vlan related register initialization.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch_main.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index fc80c65c2131..a445d9a8505d 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -120,6 +120,7 @@ static int rswitch_fwd_init(struct rswitch_private *priv)
u32 all_ports_mask = GENMASK(RSWITCH_NUM_AGENTS - 1, 0);
unsigned int i;
u32 reg_val;
+ int ret;
/* Start with empty configuration */
for (i = 0; i < RSWITCH_NUM_AGENTS; i++) {
@@ -154,17 +155,27 @@ static int rswitch_fwd_init(struct rswitch_private *priv)
}
/* For GWCA port, allow direct descriptor forwarding */
- rswitch_modify(priv->addr, FWPC1(priv->gwca.index), FWPC1_DDE, FWPC1_DDE);
+ rswitch_modify(priv->addr, FWPC1(priv->gwca.index), 0, FWPC1_DDE);
/* Initialize hardware L2 forwarding table */
- /* Allow entire table to be used for "unsecure" entries */
+ /* Allow entire table to be used for "un-secure" entries */
rswitch_modify(priv->addr, FWMACHEC, 0, FWMACHEC_MACHMUE_MASK);
/* Initialize MAC hash table */
iowrite32(FWMACTIM_MACTIOG, priv->addr + FWMACTIM);
- return rswitch_reg_wait(priv->addr, FWMACTIM, FWMACTIM_MACTIOG, 0);
+ ret = rswitch_reg_wait(priv->addr, FWMACTIM, FWMACTIM_MACTIOG, 0);
+ if (ret)
+ return ret;
+
+ /* Allow entire VLAN table to be used for "un-secure" entries */
+ iowrite32(VLANTMUE, priv->addr + FWVLANTEC);
+
+ /* Initialize VLAN table */
+ iowrite32(VLANTIOG, priv->addr + FWVLANTIM);
+
+ return rswitch_reg_wait(priv->addr, FWVLANTIM, VLANTIOG, 0);
}
/* Gateway CPU agent block (GWCA) */
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 10/14] net: renesas: rswitch: update port HW init
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (8 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 09/14] net: renesas: rswitch: add basic vlan init to rswitch_fwd_init Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 11/14] net: renesas: rswitch: clean up is_rdev rswitch_device checking Michael Dege
` (3 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Certain Ethernet and cpu port settings need to be updated for vlan
support.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 4 ----
drivers/net/ethernet/renesas/rswitch_main.c | 16 +++++++++++++---
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index f85dd96a9bfb..736762912acf 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -766,8 +766,6 @@ enum vlan_egress_mode {
HW_SC_TAG_VLAN,
};
-#define EAVCC_VEM_SC_TAG (0x3 << 16)
-
#define MPIC_PIS GENMASK(2, 0)
#define MPIC_PIS_GMII 2
#define MPIC_PIS_XGMII 4
@@ -806,8 +804,6 @@ enum rswitch_gwca_mode {
#define GWMTIRM_MTIOG BIT(0)
#define GWMTIRM_MTR BIT(1)
-#define GWVCC_VEM_SC_TAG (0x3 << 16)
-
#define GWARIRM_ARIOG BIT(0)
#define GWARIRM_ARR BIT(1)
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index a445d9a8505d..13e59d2d0828 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -752,8 +752,12 @@ static int rswitch_gwca_hw_init(struct rswitch_private *priv)
if (err < 0)
return err;
- iowrite32(GWVCC_VEM_SC_TAG, priv->addr + GWVCC);
- iowrite32(0, priv->addr + GWTTFC);
+ iowrite32(0, priv->addr + GWVCC);
+ iowrite32(0, priv->addr + GWIRC);
+ iowrite32(FIELD_PREP(DQD, DES_RAM_DP), priv->addr + GWRDQDC0);
+ /* Drop frames with unknown tags */
+ iowrite32(UT, priv->addr + GWTTFC);
+
iowrite32(lower_32_bits(priv->gwca.linkfix_table_dma), priv->addr + GWDCBAC1);
iowrite32(upper_32_bits(priv->gwca.linkfix_table_dma), priv->addr + GWDCBAC0);
iowrite32(lower_32_bits(priv->gwca.ts_queue.ring_dma), priv->addr + GWTDCAC10);
@@ -895,6 +899,7 @@ static bool rswitch_rx(struct net_device *ndev, int *quota)
limit = boguscnt;
desc = &gq->rx_ring[gq->cur];
+
while ((desc->desc.die_dt & DT_MASK) != DT_FEMPTY) {
dma_rmb();
skb = rswitch_rx_handle_desc(ndev, gq, desc);
@@ -1305,7 +1310,12 @@ static int rswitch_etha_hw_init(struct rswitch_etha *etha, const u8 *mac)
if (err < 0)
return err;
- iowrite32(EAVCC_VEM_SC_TAG, etha->addr + EAVCC);
+ iowrite32(0, etha->addr + EAIRC);
+ iowrite32(FIELD_PREP(DQD, DES_RAM_DP), etha->addr + EATDQDC0);
+
+ /* Drop frames with unknown tags */
+ iowrite32(UT, etha->addr + EARTFC);
+
rswitch_rmac_setting(etha, mac);
rswitch_etha_enable_mii(etha);
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 11/14] net: renesas: rswitch: clean up is_rdev rswitch_device checking
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (9 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 10/14] net: renesas: rswitch: update port HW init Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 12/14] net: renesas: rswitch: add passing of rswitch_private into notifiers Michael Dege
` (2 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Add null pointer checking for ndev and remove unnecessary
rswitch_port_check() wrapper function.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch_l2.c | 19 +++++++------------
drivers/net/ethernet/renesas/rswitch_main.c | 2 +-
2 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index 3cc0ce8762f3..c76fc8121ef0 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.c
+++ b/drivers/net/ethernet/renesas/rswitch_l2.c
@@ -158,11 +158,6 @@ static void rswitch_update_offload_brdev(struct rswitch_private *priv)
rswitch_update_l2_offload(priv);
}
-static bool rswitch_port_check(const struct net_device *ndev)
-{
- return is_rdev(ndev);
-}
-
static void rswitch_port_update_brdev(struct net_device *ndev,
struct net_device *brdev)
{
@@ -199,7 +194,7 @@ static int rswitch_netdevice_event(struct notifier_block *nb,
struct netdev_notifier_changeupper_info *info;
struct net_device *brdev;
- if (!rswitch_port_check(ndev))
+ if (!is_rdev(ndev))
return NOTIFY_DONE;
if (event != NETDEV_CHANGEUPPER)
return NOTIFY_DONE;
@@ -254,12 +249,12 @@ static int rswitch_switchdev_event(struct notifier_block *nb,
if (event == SWITCHDEV_PORT_ATTR_SET) {
ret = switchdev_handle_port_attr_set(ndev, ptr,
- rswitch_port_check,
+ is_rdev,
rswitch_port_attr_set);
return notifier_from_errno(ret);
}
- if (!rswitch_port_check(ndev))
+ if (!is_rdev(ndev))
return NOTIFY_DONE;
return notifier_from_errno(-EOPNOTSUPP);
@@ -278,13 +273,13 @@ static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
return -EOPNOTSUPP;
case SWITCHDEV_PORT_ATTR_SET:
ret = switchdev_handle_port_attr_set(ndev, ptr,
- rswitch_port_check,
+ is_rdev,
rswitch_port_attr_set);
break;
default:
- if (!rswitch_port_check(ndev))
- return NOTIFY_DONE;
- ret = -EOPNOTSUPP;
+ if (is_rdev(ndev))
+ return -EOPNOTSUPP;
+ return NOTIFY_DONE;
}
return notifier_from_errno(ret);
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index 13e59d2d0828..da400020a8f1 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -2005,7 +2005,7 @@ static const struct net_device_ops rswitch_netdev_ops = {
bool is_rdev(const struct net_device *ndev)
{
- return (ndev->netdev_ops == &rswitch_netdev_ops);
+ return ndev && (ndev->netdev_ops == &rswitch_netdev_ops);
}
static int rswitch_get_ts_info(struct net_device *ndev, struct kernel_ethtool_ts_info *info)
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 12/14] net: renesas: rswitch: add passing of rswitch_private into notifiers
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (10 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 11/14] net: renesas: rswitch: clean up is_rdev rswitch_device checking Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 14/14] net: renesas: rswitch: add vlan aware switching Michael Dege
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Provide struct rswitch_private to notifiers. This will be needed
to access the HW in the notification handlers.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 5 +++
drivers/net/ethernet/renesas/rswitch_l2.c | 45 ++++++++++-------------
drivers/net/ethernet/renesas/rswitch_l2.h | 6 ++--
drivers/net/ethernet/renesas/rswitch_main.c | 56 +++++++++++++++--------------
4 files changed, 56 insertions(+), 56 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index 736762912acf..9777966ebe0e 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -1201,6 +1201,11 @@ struct rswitch_private {
struct rswitch_etha etha[RSWITCH_NUM_PORTS];
struct rswitch_mfwd mfwd;
+ /* Notifiers */
+ struct notifier_block rswitch_netdevice_nb;
+ struct notifier_block rswitch_switchdev_nb;
+ struct notifier_block rswitch_switchdev_blocking_nb;
+
struct list_head port_list;
spinlock_t lock; /* lock interrupt registers' control */
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index c76fc8121ef0..d2deb7cb7634 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.c
+++ b/drivers/net/ethernet/renesas/rswitch_l2.c
@@ -285,48 +285,39 @@ static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
return notifier_from_errno(ret);
}
-static struct notifier_block rswitch_netdevice_nb = {
- .notifier_call = rswitch_netdevice_event,
-};
-
-static struct notifier_block rswitch_switchdev_nb = {
- .notifier_call = rswitch_switchdev_event,
-};
-
-static struct notifier_block rswitch_switchdev_blocking_nb = {
- .notifier_call = rswitch_switchdev_blocking_event,
-};
-
-int rswitch_register_notifiers(void)
+int rswitch_register_notifiers(struct rswitch_private *priv)
{
- int ret;
+ int err;
- ret = register_netdevice_notifier(&rswitch_netdevice_nb);
- if (ret)
+ priv->rswitch_netdevice_nb.notifier_call = rswitch_netdevice_event;
+ err = register_netdevice_notifier(&priv->rswitch_netdevice_nb);
+ if (err)
goto register_netdevice_notifier_failed;
- ret = register_switchdev_notifier(&rswitch_switchdev_nb);
- if (ret)
+ priv->rswitch_switchdev_nb.notifier_call = rswitch_switchdev_event;
+ err = register_switchdev_notifier(&priv->rswitch_switchdev_nb);
+ if (err)
goto register_switchdev_notifier_failed;
- ret = register_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb);
- if (ret)
+ priv->rswitch_switchdev_blocking_nb.notifier_call = rswitch_switchdev_blocking_event;
+ err = register_switchdev_blocking_notifier(&priv->rswitch_switchdev_blocking_nb);
+ if (err)
goto register_switchdev_blocking_notifier_failed;
return 0;
register_switchdev_blocking_notifier_failed:
- unregister_switchdev_notifier(&rswitch_switchdev_nb);
+ unregister_switchdev_notifier(&priv->rswitch_switchdev_nb);
register_switchdev_notifier_failed:
- unregister_netdevice_notifier(&rswitch_netdevice_nb);
+ unregister_netdevice_notifier(&priv->rswitch_netdevice_nb);
register_netdevice_notifier_failed:
- return ret;
+ return err;
}
-void rswitch_unregister_notifiers(void)
+void rswitch_unregister_notifiers(struct rswitch_private *priv)
{
- unregister_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb);
- unregister_switchdev_notifier(&rswitch_switchdev_nb);
- unregister_netdevice_notifier(&rswitch_netdevice_nb);
+ unregister_switchdev_blocking_notifier(&priv->rswitch_switchdev_blocking_nb);
+ unregister_switchdev_notifier(&priv->rswitch_switchdev_nb);
+ unregister_netdevice_notifier(&priv->rswitch_netdevice_nb);
}
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.h b/drivers/net/ethernet/renesas/rswitch_l2.h
index 57050ede8f31..af9780c63c9a 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.h
+++ b/drivers/net/ethernet/renesas/rswitch_l2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Renesas Ethernet Switch device driver
*
- * Copyright (C) 2025 Renesas Electronics Corporation
+ * Copyright (C) 2025 - 2026 Renesas Electronics Corporation
*/
#ifndef __RSWITCH_L2_H__
@@ -9,7 +9,7 @@
void rswitch_update_l2_offload(struct rswitch_private *priv);
-int rswitch_register_notifiers(void);
-void rswitch_unregister_notifiers(void);
+int rswitch_register_notifiers(struct rswitch_private *priv);
+void rswitch_unregister_notifiers(struct rswitch_private *priv);
#endif /* #ifndef __RSWITCH_L2_H__ */
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index da400020a8f1..cfe7bfe6546b 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -2241,6 +2241,30 @@ static int rswitch_init(struct rswitch_private *priv)
return err;
}
+static void rswitch_deinit(struct rswitch_private *priv)
+{
+ unsigned int i;
+
+ rswitch_gwca_hw_deinit(priv);
+ rcar_gen4_ptp_unregister(priv->ptp_priv);
+
+ rswitch_for_each_enabled_port(priv, i) {
+ struct rswitch_device *rdev = priv->rdev[i];
+
+ unregister_netdev(rdev->ndev);
+ rswitch_ether_port_deinit_one(rdev);
+ phy_exit(priv->rdev[i]->serdes);
+ }
+
+ for (i = 0; i < RSWITCH_NUM_PORTS; i++)
+ rswitch_device_free(priv, i);
+
+ rswitch_gwca_ts_queue_free(priv);
+ rswitch_gwca_linkfix_free(priv);
+
+ rswitch_clock_disable(priv);
+}
+
static const struct soc_device_attribute rswitch_soc_no_speed_change[] = {
{ .soc_id = "r8a779f0", .revision = "ES1.0" },
{ /* Sentinel */ }
@@ -2317,9 +2341,13 @@ static int renesas_eth_sw_probe(struct platform_device *pdev)
if (list_empty(&priv->port_list))
dev_warn(&pdev->dev, "could not initialize any ports\n");
- ret = rswitch_register_notifiers();
+ ret = rswitch_register_notifiers(priv);
if (ret) {
+ rswitch_deinit(priv);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
dev_err(&pdev->dev, "could not register notifiers\n");
+
return ret;
}
@@ -2328,35 +2356,11 @@ static int renesas_eth_sw_probe(struct platform_device *pdev)
return ret;
}
-static void rswitch_deinit(struct rswitch_private *priv)
-{
- unsigned int i;
-
- rswitch_gwca_hw_deinit(priv);
- rcar_gen4_ptp_unregister(priv->ptp_priv);
-
- rswitch_for_each_enabled_port(priv, i) {
- struct rswitch_device *rdev = priv->rdev[i];
-
- unregister_netdev(rdev->ndev);
- rswitch_ether_port_deinit_one(rdev);
- phy_exit(priv->rdev[i]->serdes);
- }
-
- for (i = 0; i < RSWITCH_NUM_PORTS; i++)
- rswitch_device_free(priv, i);
-
- rswitch_gwca_ts_queue_free(priv);
- rswitch_gwca_linkfix_free(priv);
-
- rswitch_clock_disable(priv);
-}
-
static void renesas_eth_sw_remove(struct platform_device *pdev)
{
struct rswitch_private *priv = platform_get_drvdata(pdev);
- rswitch_unregister_notifiers();
+ rswitch_unregister_notifiers(priv);
rswitch_deinit(priv);
pm_runtime_put(&pdev->dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (11 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 12/14] net: renesas: rswitch: add passing of rswitch_private into notifiers Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:40 ` Jakub Kicinski
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 14/14] net: renesas: rswitch: add vlan aware switching Michael Dege
13 siblings, 2 replies; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
The SWITCHDEV_FDB_ADD_TO_DEVICE notification writes the bridge MAC address
to the GWCA and adds a static rule to the MAC table.
The SWITCHDEV_FDB_DEL_TO_DEVICE removes the static rule from the MAC table.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/rswitch.h | 12 ++
drivers/net/ethernet/renesas/rswitch_l2.c | 200 ++++++++++++++++++++++++----
drivers/net/ethernet/renesas/rswitch_main.c | 2 +
3 files changed, 191 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
index 9777966ebe0e..202f877a9cc8 100644
--- a/drivers/net/ethernet/renesas/rswitch.h
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -1173,6 +1173,7 @@ struct rswitch_device {
struct phy *serdes;
struct net_device *brdev; /* master bridge device */
+
unsigned int learning_requested : 1;
unsigned int learning_offloaded : 1;
unsigned int forwarding_requested : 1;
@@ -1219,6 +1220,17 @@ struct rswitch_private {
enum hwtstamp_tx_types tstamp_tx_ctrl;
enum hwtstamp_rx_filters tstamp_rx_ctrl;
+
+ struct rswitch_switchdev_event_work *switchdev_work;
+};
+
+struct rswitch_switchdev_event_work {
+ struct work_struct work;
+ netdevice_tracker dev_tracker;
+ struct switchdev_notifier_fdb_info fdb_info;
+ struct net_device *ndev;
+ struct rswitch_private *priv;
+ unsigned long event;
};
bool is_rdev(const struct net_device *ndev);
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index d2deb7cb7634..4d0646283f88 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.c
+++ b/drivers/net/ethernet/renesas/rswitch_l2.c
@@ -23,7 +23,8 @@ static bool rdev_for_l2_offload(struct rswitch_device *rdev)
static void rswitch_change_l2_hw_offloading(struct rswitch_device *rdev,
bool start, bool learning)
{
- u32 bits = learning ? FWPC0_MACSSA | FWPC0_MACHLA | FWPC0_MACHMA : FWPC0_MACDSA;
+ u32 bits = learning ? FWPC0_MACSSA | FWPC0_MACHLA | FWPC0_MACHMA | FWPC0_MACRUDA :
+ FWPC0_MACDSA;
u32 clear = start ? 0 : bits;
u32 set = start ? bits : 0;
@@ -82,8 +83,9 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
*
* Do not allow L2 forwarding to self for hw port.
*/
- iowrite32(FIELD_PREP(FWPC2_LTWFW, fwd_mask | BIT(rdev->port)),
- priv->addr + FWPC2(rdev->port));
+ rswitch_modify(priv->addr, FWPC2(rdev->port),
+ FIELD_PREP(FWPC2_LTWFW, ~(fwd_mask | BIT(rdev->port))),
+ 0);
}
if (new_forwarding_offload && !rdev->forwarding_offloaded)
@@ -188,7 +190,8 @@ static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_state)
}
static int rswitch_netdevice_event(struct notifier_block *nb,
- unsigned long event, void *ptr)
+ unsigned long event,
+ void *ptr)
{
struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *info;
@@ -241,25 +244,6 @@ static int rswitch_port_attr_set(struct net_device *ndev, const void *ctx,
}
}
-static int rswitch_switchdev_event(struct notifier_block *nb,
- unsigned long event, void *ptr)
-{
- struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
- int ret;
-
- if (event == SWITCHDEV_PORT_ATTR_SET) {
- ret = switchdev_handle_port_attr_set(ndev, ptr,
- is_rdev,
- rswitch_port_attr_set);
- return notifier_from_errno(ret);
- }
-
- if (!is_rdev(ndev))
- return NOTIFY_DONE;
-
- return notifier_from_errno(-EOPNOTSUPP);
-}
-
static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
@@ -285,6 +269,176 @@ static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
return notifier_from_errno(ret);
}
+static int rswitch_gwca_write_mac_address(struct rswitch_private *priv, const u8 *mac)
+{
+ int err;
+
+ err = rswitch_gwca_change_mode(priv, GWMC_OPC_CONFIG);
+ if (err < 0)
+ return err;
+
+ iowrite32((mac[0] << 8) | mac[1], priv->addr + GWMAC0);
+ iowrite32((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5],
+ priv->addr + GWMAC1);
+
+ return rswitch_gwca_change_mode(priv, GWMC_OPC_OPERATION);
+}
+
+static int rswitch_add_addr_to_mactable(struct rswitch_private *priv, const u8 *mac)
+{
+ u32 index = priv->gwca.l2_shared_rx_queue->index;
+ int err;
+
+ rswitch_modify(priv->addr, FWMACTL0, FWMACTL0_ED, 0);
+ iowrite32((mac[0] << 8) | mac[1], priv->addr + FWMACTL1);
+ iowrite32((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5],
+ priv->addr + FWMACTL2);
+ iowrite32(FWMACTL3_DSLV | FWMACTL3_SSLV,
+ priv->addr + FWMACTL3);
+ iowrite32(FIELD_PREP(FWMACTL4_CSDL, index),
+ priv->addr + FWMACTL4(GWCA_INDEX));
+ iowrite32(FIELD_PREP(FWMACTL5_DV, BIT(AGENT_INDEX_GWCA)),
+ priv->addr + FWMACTL5);
+
+ err = rswitch_reg_wait(priv->addr, FWMACTLR, FWMACTLR_L, 0);
+ if (err < 0)
+ return err;
+
+ if (ioread32(priv->addr + FWMACTLR))
+ return NOTIFY_BAD;
+
+ return NOTIFY_DONE;
+}
+
+static int rswitch_del_addr_from_mactable(struct rswitch_private *priv, const u8 *mac)
+{
+ int err;
+
+ rswitch_modify(priv->addr, FWMACTL0, 0, FWMACTL0_ED);
+ iowrite32((mac[0] << 8) | mac[1], priv->addr + FWMACTL1);
+ iowrite32((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5],
+ priv->addr + FWMACTL2);
+ iowrite32(FWMACTL3_DSLV | FWMACTL3_SSLV,
+ priv->addr + FWMACTL3);
+ iowrite32(FIELD_PREP(FWMACTL4_CSDL, BIT(0)),
+ priv->addr + FWMACTL4(GWCA_INDEX));
+ iowrite32(FIELD_PREP(FWMACTL5_DV, BIT(AGENT_INDEX_GWCA)),
+ priv->addr + FWMACTL5);
+
+ err = rswitch_reg_wait(priv->addr, FWMACTLR, FWMACTLR_L, 0);
+ if (err < 0)
+ return err;
+
+ err = ioread32(priv->addr + FWMACTLR);
+ if (err)
+ return NOTIFY_BAD;
+
+ return NOTIFY_DONE;
+}
+
+static void rswitch_switchdev_bridge_fdb_event_work(struct work_struct *work)
+{
+ struct rswitch_switchdev_event_work *switchdev_work;
+ const unsigned char addr[] = {0, 0, 0, 0, 0, 0};
+ struct rswitch_device *rdev;
+ struct net_device *ndev;
+
+ switchdev_work = container_of(work, struct rswitch_switchdev_event_work, work);
+ ndev = switchdev_work->ndev;
+
+ rtnl_lock();
+
+ /* Unfortunately all net_device members point to br0, there is no simple way to check
+ * if the event was triggered by a port device setting.
+ */
+ rswitch_for_all_ports(switchdev_work->priv, rdev) {
+ if (ether_addr_equal(rdev->ndev->dev_addr, switchdev_work->fdb_info.addr))
+ goto out;
+ }
+
+ /* Handle only bridge device */
+ if (is_rdev(ndev) || !netif_is_bridge_master(ndev))
+ goto out;
+
+ switch (switchdev_work->event) {
+ case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ rswitch_gwca_write_mac_address(switchdev_work->priv, switchdev_work->fdb_info.addr);
+ rswitch_add_addr_to_mactable(switchdev_work->priv, switchdev_work->fdb_info.addr);
+ break;
+ case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ rswitch_del_addr_from_mactable(switchdev_work->priv, switchdev_work->fdb_info.addr);
+ rswitch_gwca_write_mac_address(switchdev_work->priv, addr);
+ break;
+ default:
+ break;
+ }
+
+out:
+ rtnl_unlock();
+
+ kfree(switchdev_work->fdb_info.addr);
+ rdev = netdev_priv(ndev);
+ netdev_put(ndev, &switchdev_work->dev_tracker);
+ kfree(switchdev_work);
+}
+
+/* called under rcu_read_lock() */
+static int rswitch_switchdev_event(struct notifier_block *nb,
+ unsigned long event,
+ void *ptr)
+{
+ struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
+ struct rswitch_switchdev_event_work *switchdev_work;
+ struct switchdev_notifier_fdb_info *fdb_info;
+ struct switchdev_notifier_info *info = ptr;
+ struct rswitch_private *priv;
+ struct rswitch_device *rdev;
+
+ priv = container_of(nb, struct rswitch_private, rswitch_switchdev_nb);
+
+ switch (event) {
+ case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ fallthrough;
+ case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+ if (!switchdev_work)
+ return NOTIFY_BAD;
+
+ switchdev_work->ndev = info->dev;
+ switchdev_work->priv = priv;
+ switchdev_work->event = event;
+ rdev = netdev_priv(switchdev_work->ndev);
+
+ fdb_info = container_of(info,
+ struct switchdev_notifier_fdb_info,
+ info);
+
+ INIT_WORK(&switchdev_work->work, rswitch_switchdev_bridge_fdb_event_work);
+
+ memcpy(&switchdev_work->fdb_info, ptr, sizeof(switchdev_work->fdb_info));
+
+ switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
+ if (!switchdev_work->fdb_info.addr)
+ goto err_addr_alloc;
+
+ priv->switchdev_work = switchdev_work;
+
+ ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
+ fdb_info->addr);
+ netdev_hold(ndev, &switchdev_work->dev_tracker, GFP_ATOMIC);
+ queue_work(system_long_wq, &switchdev_work->work);
+
+ break;
+ }
+
+ return NOTIFY_DONE;
+
+err_addr_alloc:
+ kfree(switchdev_work);
+
+ return NOTIFY_BAD;
+}
+
int rswitch_register_notifiers(struct rswitch_private *priv)
{
int err;
diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
index cfe7bfe6546b..6fa37533feff 100644
--- a/drivers/net/ethernet/renesas/rswitch_main.c
+++ b/drivers/net/ethernet/renesas/rswitch_main.c
@@ -2262,6 +2262,8 @@ static void rswitch_deinit(struct rswitch_private *priv)
rswitch_gwca_ts_queue_free(priv);
rswitch_gwca_linkfix_free(priv);
+ cancel_work_sync(&priv->switchdev_work->work);
+
rswitch_clock_disable(priv);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v5 14/14] net: renesas: rswitch: add vlan aware switching
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
` (12 preceding siblings ...)
2026-05-22 12:12 ` [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification Michael Dege
@ 2026-05-22 12:12 ` Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
13 siblings, 1 reply; 27+ messages in thread
From: Michael Dege @ 2026-05-22 12:12 UTC (permalink / raw)
To: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Niklas Söderlund, Paul Barker,
Richard Cochran
Cc: netdev, linux-renesas-soc, linux-kernel, Michael Dege
Add vlan support to L2 HW bridge.
On R-Car S4 there is limited vlan support, which is not fully
802.1Q compliant. The aim of this driver addition is to get as
close as possible to the correct behavior. Limitations are:
- all ports should be in the same default vlan
- default vlans are not stripped on egress.
Signed-off-by: Michael Dege <michael.dege@renesas.com>
---
drivers/net/ethernet/renesas/Kconfig | 1 +
drivers/net/ethernet/renesas/rswitch_l2.c | 401 +++++++++++++++++++++++++++---
2 files changed, 364 insertions(+), 38 deletions(-)
diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/renesas/Kconfig
index 9b7559c88bee..2494ff60e8c2 100644
--- a/drivers/net/ethernet/renesas/Kconfig
+++ b/drivers/net/ethernet/renesas/Kconfig
@@ -43,6 +43,7 @@ config RENESAS_ETHER_SWITCH
tristate "Renesas Ethernet Switch support"
depends on ARCH_RENESAS || COMPILE_TEST
depends on PTP_1588_CLOCK
+ depends on BRIDGE || BRIDGE=n
select CRC32
select MII
select PHYLINK
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index 4d0646283f88..d2e22bee9c0d 100644
--- a/drivers/net/ethernet/renesas/rswitch_l2.c
+++ b/drivers/net/ethernet/renesas/rswitch_l2.c
@@ -7,6 +7,7 @@
#include <linux/err.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
#include <linux/kernel.h>
#include <net/switchdev.h>
@@ -173,22 +174,6 @@ static void rswitch_port_update_brdev(struct net_device *ndev,
rswitch_update_offload_brdev(rdev->priv);
}
-static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_state)
-{
- struct rswitch_device *rdev;
-
- if (!is_rdev(ndev))
- return -ENODEV;
-
- rdev = netdev_priv(ndev);
- rdev->learning_requested = (stp_state == BR_STATE_LEARNING ||
- stp_state == BR_STATE_FORWARDING);
- rdev->forwarding_requested = (stp_state == BR_STATE_FORWARDING);
- rswitch_update_l2_offload(rdev->priv);
-
- return 0;
-}
-
static int rswitch_netdevice_event(struct notifier_block *nb,
unsigned long event,
void *ptr)
@@ -212,61 +197,401 @@ static int rswitch_netdevice_event(struct notifier_block *nb,
return NOTIFY_OK;
}
-static int rswitch_update_ageing_time(struct net_device *ndev, clock_t time)
+static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_state)
{
- struct rswitch_device *rdev = netdev_priv(ndev);
- u32 reg_val;
+ struct rswitch_device *rdev;
if (!is_rdev(ndev))
return -ENODEV;
+ rdev = netdev_priv(ndev);
+ rdev->learning_requested = (stp_state == BR_STATE_LEARNING ||
+ stp_state == BR_STATE_FORWARDING);
+ rdev->forwarding_requested = (stp_state == BR_STATE_FORWARDING);
+ rswitch_update_l2_offload(rdev->priv);
+
+ return 0;
+}
+
+static int rswitch_update_ageing_time(struct rswitch_private *priv, clock_t time)
+{
+ u32 reg_val;
+
if (!FIELD_FIT(FWMACAGC_MACAGT, time))
return -EINVAL;
reg_val = FIELD_PREP(FWMACAGC_MACAGT, time);
reg_val |= FWMACAGC_MACAGE | FWMACAGC_MACAGSL;
- iowrite32(reg_val, rdev->priv->addr + FWMACAGC);
+ iowrite32(reg_val, priv->addr + FWMACAGC);
return 0;
}
-static int rswitch_port_attr_set(struct net_device *ndev, const void *ctx,
- const struct switchdev_attr *attr,
- struct netlink_ext_ack *extack)
+static void rswitch_update_vlan_filtering(struct rswitch_private *priv,
+ bool vlan_filtering)
{
+ if (vlan_filtering)
+ rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA),
+ 0, FWPC0_VLANSA | FWPC0_VLANRU);
+ else
+ rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA),
+ FWPC0_VLANSA | FWPC0_VLANRU, 0);
+}
+
+static int rswitch_handle_port_attr_set(struct net_device *ndev,
+ struct notifier_block *nb,
+ struct switchdev_notifier_port_attr_info *info)
+{
+ const struct switchdev_attr *attr = info->attr;
+ struct rswitch_private *priv;
+ int err = 0;
+
+ priv = container_of(nb, struct rswitch_private, rswitch_switchdev_blocking_nb);
+
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
- return rswitch_port_update_stp_state(ndev, attr->u.stp_state);
+ err = rswitch_port_update_stp_state(ndev, attr->u.stp_state);
+
+ break;
case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
- return rswitch_update_ageing_time(ndev, attr->u.ageing_time);
+ err = rswitch_update_ageing_time(priv, attr->u.ageing_time);
+
+ break;
+ case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
+ rswitch_update_vlan_filtering(priv, attr->u.vlan_filtering);
+
+ break;
+ case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
+
+ break;
default:
return -EOPNOTSUPP;
}
+
+ if (err < 0)
+ return err;
+
+ info->handled = true;
+
+ return NOTIFY_DONE;
+}
+
+static int rswitch_read_vlan_table(struct rswitch_private *priv, u16 vid,
+ u32 *vlanslvs, u32 *vlandvs)
+{
+ int err;
+
+ iowrite32(FIELD_PREP(VLANVIDS, vid), priv->addr + FWVLANTS);
+ err = rswitch_reg_wait(priv->addr, FWVLANTSR0, VLANTS, 0);
+ if (err < 0)
+ return err;
+
+ /* check if vlans are present in table */
+ if (!(ioread32(priv->addr + FWVLANTSR0) & VLANSNF)) {
+ *vlanslvs = (ioread32(priv->addr + FWVLANTSR1) & VLANSLVS);
+ *vlandvs = (ioread32(priv->addr + FWVLANTSR3) & VLANDVS);
+ }
+
+ return 0;
+}
+
+static int rswitch_write_vlan_table(struct rswitch_private *priv, u16 vid, u32 index)
+{
+ u32 vlancsdl = priv->gwca.l2_shared_rx_queue->index;
+ u32 vlanslvs = 0, vlandvs = 0;
+ int err;
+
+ err = rswitch_read_vlan_table(priv, vid, &vlanslvs, &vlandvs);
+ if (err < 0)
+ return err;
+
+ rswitch_modify(priv->addr, FWVLANTL0, VLANED, 0);
+ iowrite32(FIELD_PREP(VLANVIDL, vid), priv->addr + FWVLANTL1);
+
+ vlanslvs |= BIT(index);
+ vlandvs |= BIT(index);
+ iowrite32(FIELD_PREP(VLANSLVL, vlanslvs), priv->addr + FWVLANTL2);
+ iowrite32(FIELD_PREP(VLANCSDL, vlancsdl), priv->addr + FWVLANTL3(GWCA_INDEX));
+ iowrite32(FIELD_PREP(VLANDVL, vlandvs), priv->addr + FWVLANTL4);
+
+ return rswitch_reg_wait(priv->addr, FWVLANTLR, VLANTL, 0);
+}
+
+static int rswitch_erase_vlan_table(struct rswitch_private *priv, u16 vid, u32 index)
+{
+ u32 vlanslvs = 0, vlandvs = 0;
+ int err;
+
+ err = rswitch_read_vlan_table(priv, vid, &vlanslvs, &vlandvs);
+ if (err < 0)
+ return err;
+
+ vlanslvs &= ~BIT(index);
+ vlandvs &= ~BIT(index);
+
+ /* only erase empty vlan table entries */
+ if (vlanslvs == 0)
+ rswitch_modify(priv->addr, FWVLANTL0, 0, VLANED);
+
+ iowrite32(FIELD_PREP(VLANVIDL, vid), priv->addr + FWVLANTL1);
+ iowrite32(FIELD_PREP(VLANSLVL, vlanslvs), priv->addr + FWVLANTL2);
+ iowrite32(FIELD_PREP(VLANDVL, vlandvs), priv->addr + FWVLANTL4);
+
+ return rswitch_reg_wait(priv->addr, FWVLANTLR, VLANTL, 0);
+}
+
+static int rswitch_port_set_vlan_tag(struct rswitch_etha *etha,
+ struct switchdev_obj_port_vlan *p_vlan,
+ bool delete)
+{
+ u32 vem_val;
+ int err;
+
+ err = rswitch_etha_change_mode(etha, EAMC_OPC_CONFIG);
+ if (err < 0)
+ return err;
+
+ rswitch_modify(etha->addr, EAVCC, VIM, 0);
+
+ if (((ioread32(etha->addr + EAVTC) & CTV) == p_vlan->vid) && delete) {
+ rswitch_modify(etha->addr, EAVTC, CTV, 0);
+ rswitch_modify(etha->addr, EAVCC, VEM, 0);
+ } else if (!delete) {
+ if ((p_vlan->flags & BRIDGE_VLAN_INFO_PVID) &&
+ (p_vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED))
+ vem_val = FIELD_PREP(VEM, C_TAG_VLAN);
+ else if (p_vlan->flags & BRIDGE_VLAN_INFO_PVID)
+ vem_val = FIELD_PREP(VEM, HW_C_TAG_VLAN);
+ else
+ vem_val = 0;
+ rswitch_modify(etha->addr, EAVCC, VEM, vem_val);
+ rswitch_modify(etha->addr, EAVTC, CTV, FIELD_PREP(CTV, p_vlan->vid));
+ }
+
+ return rswitch_etha_change_mode(etha, EAMC_OPC_OPERATION);
+}
+
+static int rswitch_gwca_set_vlan_tag(struct rswitch_private *priv,
+ struct switchdev_obj_port_vlan *p_vlan,
+ bool delete)
+{
+ u32 vem_val;
+ int err;
+
+ err = rswitch_gwca_change_mode(priv, GWMC_OPC_CONFIG);
+ if (err < 0)
+ return err;
+
+ rswitch_modify(priv->addr, GWVCC, VIM, 0);
+
+ if (((ioread32(priv->addr + GWVTC) & CTV) == p_vlan->vid) && delete) {
+ rswitch_modify(priv->addr, GWVTC, CTV, 0);
+ rswitch_modify(priv->addr, GWVCC, VEM, 0);
+ } else if (!delete) {
+ if ((p_vlan->flags & BRIDGE_VLAN_INFO_PVID) &&
+ (p_vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED))
+ vem_val = FIELD_PREP(VEM, C_TAG_VLAN);
+ else if (p_vlan->flags & BRIDGE_VLAN_INFO_PVID)
+ vem_val = FIELD_PREP(VEM, HW_C_TAG_VLAN);
+ else
+ vem_val = 0;
+ rswitch_modify(priv->addr, GWVCC, VEM, vem_val);
+ rswitch_modify(priv->addr, GWVTC, CTV, FIELD_PREP(CTV, p_vlan->vid));
+ }
+
+ return rswitch_gwca_change_mode(priv, GWMC_OPC_OPERATION);
+}
+
+static int rswitch_port_obj_do_add(struct net_device *ndev,
+ struct switchdev_obj_port_vlan *p_vlan)
+{
+ struct rswitch_device *rdev = netdev_priv(ndev);
+ struct rswitch_private *priv = rdev->priv;
+ struct rswitch_etha *etha = rdev->etha;
+ int err;
+
+ /* Set Rswitch VLAN mode */
+ iowrite32(br_vlan_enabled(rdev->brdev) ? FIELD_PREP(FWGC_SVM, C_TAG) : 0,
+ priv->addr + FWGC);
+
+ err = rswitch_write_vlan_table(priv, p_vlan->vid, etha->index);
+ if (err < 0)
+ return err;
+
+ /* If the default vlan for this port has been set, don't overwrite it. */
+ if (ioread32(etha->addr + EAVCC))
+ return NOTIFY_DONE;
+
+ if (br_vlan_enabled(rdev->brdev))
+ rswitch_modify(priv->addr, FWPC0(etha->index), 0, FWPC0_VLANSA | FWPC0_VLANRU);
+
+ rswitch_modify(priv->addr, FWPC2(AGENT_INDEX_GWCA),
+ FIELD_PREP(FWPC2_LTWFW, BIT(etha->index)),
+ 0);
+
+ return rswitch_port_set_vlan_tag(etha, p_vlan, false);
+}
+
+static int rswitch_port_obj_do_add_gwca(struct net_device *ndev,
+ struct rswitch_private *priv,
+ struct switchdev_obj_port_vlan *p_vlan)
+{
+ int err;
+
+ if (!(p_vlan->flags & BRIDGE_VLAN_INFO_BRENTRY))
+ return NOTIFY_DONE;
+
+ /* Set Rswitch VLAN mode */
+ iowrite32(br_vlan_enabled(ndev) ? FIELD_PREP(FWGC_SVM, C_TAG) : 0, priv->addr + FWGC);
+
+ err = rswitch_write_vlan_table(priv, p_vlan->vid, AGENT_INDEX_GWCA);
+ if (err < 0)
+ return err;
+
+ /* If the default vlan for this port has been set, don't overwrite it. */
+ if (ioread32(priv->addr + GWVCC))
+ return NOTIFY_DONE;
+
+ return rswitch_gwca_set_vlan_tag(priv, p_vlan, false);
+}
+
+static int rswitch_port_obj_do_del(struct net_device *ndev,
+ struct switchdev_obj_port_vlan *p_vlan)
+{
+ struct rswitch_device *rdev = netdev_priv(ndev);
+ struct rswitch_private *priv = rdev->priv;
+ struct rswitch_etha *etha = rdev->etha;
+ int err;
+
+ err = rswitch_port_set_vlan_tag(etha, p_vlan, true);
+ if (err < 0)
+ return err;
+
+ rswitch_modify(priv->addr, FWPC0(etha->index), FWPC0_VLANSA | FWPC0_VLANRU, 0);
+ rswitch_modify(priv->addr, FWPC2(AGENT_INDEX_GWCA), 0,
+ FIELD_PREP(FWPC2_LTWFW, BIT(etha->index)));
+ rswitch_modify(priv->addr, FWPC2(rdev->port),
+ 0, FIELD_PREP(FWPC2_LTWFW, GENMASK(RSWITCH_NUM_AGENTS - 1, 0)));
+
+ return rswitch_erase_vlan_table(priv, p_vlan->vid, etha->index);
+}
+
+static int rswitch_port_obj_do_del_gwca(struct net_device *ndev,
+ struct rswitch_private *priv,
+ struct switchdev_obj_port_vlan *p_vlan)
+{
+ int err;
+
+ err = rswitch_gwca_set_vlan_tag(priv, p_vlan, true);
+ if (err < 0)
+ return err;
+
+ rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA),
+ FWPC0_VLANSA | FWPC0_VLANRU,
+ 0);
+
+ return rswitch_erase_vlan_table(priv, p_vlan->vid, AGENT_INDEX_GWCA);
+}
+
+static int rswitch_handle_port_obj_add(struct net_device *ndev,
+ struct notifier_block *nb,
+ struct switchdev_notifier_port_obj_info *info)
+{
+ struct switchdev_obj_port_vlan *p_vlan;
+ struct rswitch_private *priv;
+ int err;
+
+ switch (info->obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
+ p_vlan = SWITCHDEV_OBJ_PORT_VLAN(info->obj);
+ if ((p_vlan->flags & BRIDGE_VLAN_INFO_MASTER) ||
+ (p_vlan->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) ||
+ (p_vlan->flags & BRIDGE_VLAN_INFO_RANGE_END) ||
+ (p_vlan->flags & BRIDGE_VLAN_INFO_ONLY_OPTS))
+ return NOTIFY_DONE;
+
+ priv = container_of(nb, struct rswitch_private,
+ rswitch_switchdev_blocking_nb);
+
+ if (!is_rdev(ndev))
+ err = rswitch_port_obj_do_add_gwca(ndev, priv, p_vlan);
+ else
+ err = rswitch_port_obj_do_add(ndev, p_vlan);
+
+ if (err < 0)
+ return err;
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ info->handled = true;
+
+ return NOTIFY_DONE;
+}
+
+static int rswitch_handle_port_obj_del(struct net_device *ndev,
+ struct notifier_block *nb,
+ struct switchdev_notifier_port_obj_info *info)
+{
+ struct switchdev_obj_port_vlan *p_vlan;
+ struct rswitch_private *priv;
+ int err;
+
+ switch (info->obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
+ p_vlan = SWITCHDEV_OBJ_PORT_VLAN(info->obj);
+ if ((p_vlan->flags & BRIDGE_VLAN_INFO_MASTER) ||
+ (p_vlan->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) ||
+ (p_vlan->flags & BRIDGE_VLAN_INFO_RANGE_END) ||
+ (p_vlan->flags & BRIDGE_VLAN_INFO_ONLY_OPTS))
+ return NOTIFY_DONE;
+
+ priv = container_of(nb, struct rswitch_private,
+ rswitch_switchdev_blocking_nb);
+
+ if (!is_rdev(ndev))
+ err = rswitch_port_obj_do_del_gwca(ndev, priv, p_vlan);
+ else
+ err = rswitch_port_obj_do_del(ndev, p_vlan);
+
+ if (err < 0)
+ return err;
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ info->handled = true;
+
+ return NOTIFY_DONE;
}
static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
- unsigned long event, void *ptr)
+ unsigned long event,
+ void *ptr)
{
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
- int ret;
+ int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
- return -EOPNOTSUPP;
+ err = rswitch_handle_port_obj_add(ndev, nb, ptr);
+
+ return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
- return -EOPNOTSUPP;
+ err = rswitch_handle_port_obj_del(ndev, nb, ptr);
+
+ return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET:
- ret = switchdev_handle_port_attr_set(ndev, ptr,
- is_rdev,
- rswitch_port_attr_set);
- break;
- default:
- if (is_rdev(ndev))
- return -EOPNOTSUPP;
- return NOTIFY_DONE;
+ err = rswitch_handle_port_attr_set(ndev, nb, ptr);
+
+ return notifier_from_errno(err);
}
- return notifier_from_errno(ret);
+ return NOTIFY_DONE;
}
static int rswitch_gwca_write_mac_address(struct rswitch_private *priv, const u8 *mac)
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification
2026-05-22 12:12 ` [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification Michael Dege
@ 2026-05-25 20:40 ` Jakub Kicinski
2026-05-25 20:41 ` Jakub Kicinski
1 sibling, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:40 UTC (permalink / raw)
To: Michael Dege
Cc: Yoshihiro Shimoda, Andrew Lunn, David S. Miller, Eric Dumazet,
Paolo Abeni, Niklas Söderlund, Paul Barker, Richard Cochran,
netdev, linux-renesas-soc, linux-kernel
On Fri, 22 May 2026 14:12:42 +0200 Michael Dege wrote:
> The SWITCHDEV_FDB_ADD_TO_DEVICE notification writes the bridge MAC address
> to the GWCA and adds a static rule to the MAC table.
> The SWITCHDEV_FDB_DEL_TO_DEVICE removes the static rule from the MAC table.
drivers/net/ethernet/renesas/rswitch_l2.c:395:25: warning: variable 'rdev' set but not used [-Wunused-but-set-variable]
395 | struct rswitch_device *rdev;
| ^
I'm going to send out the AI reviews, too, FWIW.
Not sure if any of the comments are valid.
--
pw-bot: cr
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions
2026-05-22 12:12 ` [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: improve port change mode functions
This commit consolidates the mode change logic for ETHA and GWCA ports
in the Renesas switch driver. It ensures the required transition to
the "disable" mode is handled automatically within a single call to
the mode change functions, removing redundant manual calls.
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index 6fe964816322a..9b739594cc028 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
> @@ -176,14 +176,21 @@ static int rswitch_gwca_change_mode(struct rswitch_private *priv,
> if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
> rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
>
> - iowrite32(mode, priv->addr + GWMC);
> + iowrite32(GWMC_OPC_DISABLE, priv->addr + GWMC);
>
> - ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
> + ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, GWMC_OPC_DISABLE);
> + if (ret < 0)
> + return ret;
[Severity: Medium]
Does this error path leak the agent clock resource?
In the pre-patch code, the clock was disabled unconditionally if the target
mode was DISABLE, even if rswitch_reg_wait() timed out. By returning early
here when ret < 0, it appears the rswitch_agent_clock_ctrl(..., 0) cleanup
is skipped if the hardware times out during the transition.
>
> - if (mode == GWMC_OPC_DISABLE)
> + if (mode == GWMC_OPC_DISABLE) {
> rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
>
> - return ret;
> + return ret;
> + }
[ ... ]
> @@ -727,9 +728,6 @@ static int rswitch_gwca_hw_deinit(struct rswitch_private *priv)
> {
> int err;
>
[Severity: High]
This isn't a bug introduced by this patch, but does the hardware teardown
sequence leave transmitted SKBs queued indefinitely?
When shutting down the driver, rswitch_deinit() invokes this function
to disable the hardware data paths before the unregister_netdev() loop
executes. While the netdevs are still registered and up, the network stack
can continue to invoke ndo_start_xmit.
Because the hardware is disabled early, are these transmitted SKBs and their
DMA mappings queued without ever completing, causing them to be leaked later
when rswitch_txdmac_free() frees the rings? Should unregister_netdev() be
called before shutting down the hardware?
> - err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
> - if (err < 0)
> - return err;
> err = rswitch_gwca_change_mode(priv, GWMC_OPC_RESET);
> if (err < 0)
> return err;
[ ... ]
> @@ -1116,14 +1114,20 @@ static int rswitch_etha_change_mode(struct rswitch_etha *etha,
> if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index))
> rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 1);
>
> - iowrite32(mode, etha->addr + EAMC);
> -
> - ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, mode);
> + iowrite32(EAMC_OPC_DISABLE, etha->addr + EAMC);
> + ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, EAMC_OPC_DISABLE);
> + if (ret < 0)
> + return ret;
[Severity: Medium]
Will this return cause a similar clock resource leak if the hardware wait times
out, skipping the clock cleanup for EAMC_OPC_DISABLE?
>
> - if (mode == EAMC_OPC_DISABLE)
> + if (mode == EAMC_OPC_DISABLE) {
> rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 0);
>
> - return ret;
> + return ret;
> + }
> +
> + iowrite32(mode, etha->addr + EAMC);
> +
> + return rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, mode);
> }
[ ... ]
> @@ -1203,9 +1207,6 @@ static int rswitch_etha_hw_init(struct rswitch_etha *etha, const u8 *mac)
> {
> int err;
>
> - err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
> - if (err < 0)
> - return err;
[Severity: Low]
Is this explicit DISABLE mode transition still necessary?
Looking at the end of this function after the patch is applied, there is
another transition to DISABLE before switching to OPERATION:
err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
if (err < 0)
return err;
return rswitch_etha_change_mode(etha, EAMC_OPC_OPERATION);
Since rswitch_etha_change_mode() now handles the DISABLE transition
internally, does leaving this explicit call result in redundant transitions
and unnecessary clock toggling?
> err = rswitch_etha_change_mode(etha, EAMC_OPC_CONFIG);
> if (err < 0)
> return err;
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 02/14] net: renesas: rswitch: use device instead of net_device
2026-05-22 12:12 ` [PATCH net-next v5 02/14] net: renesas: rswitch: use device instead of net_device Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: use device instead of net_device
In upcomming changes for adding vlan support struct net_device
will not be available in all cases, therefore use struct device
instead.
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index 9b739594cc028..74d441ae19769 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
[ ... ]
> @@ -304,26 +304,30 @@ static int rswitch_gwca_queue_alloc_rx_buf(struct rswitch_gwca_queue *gq,
> return -ENOMEM;
> }
>
> -static void rswitch_gwca_queue_free(struct net_device *ndev,
> +static void rswitch_gwca_queue_free(struct device *dev,
> struct rswitch_gwca_queue *gq)
> {
> unsigned int i;
>
> if (!gq->dir_tx) {
> - dma_free_coherent(ndev->dev.parent,
> - sizeof(struct rswitch_ext_ts_desc) *
> - (gq->ring_size + 1), gq->rx_ring, gq->ring_dma);
> - gq->rx_ring = NULL;
> -
> + if (gq->rx_ring) {
> + dma_free_coherent(dev,
> + sizeof(struct rswitch_ext_ts_desc) *
> + (gq->ring_size + 1), gq->rx_ring, gq->ring_dma);
> + gq->rx_ring = NULL;
> + }
> for (i = 0; i < gq->ring_size; i++)
> - skb_free_frag(gq->rx_bufs[i]);
> + if (gq->rx_bufs[i])
> + skb_free_frag(gq->rx_bufs[i]);
[Severity: High]
This is a pre-existing issue, but does this cleanup path leak RX DMA
mappings?
It looks like we call skb_free_frag() for the RX buffers but we never call
dma_unmap_single(). Furthermore, because the rx_ring is freed before the
loop, any DMA addresses stored in the descriptors are lost, making it
impossible to unmap them.
> kfree(gq->rx_bufs);
> gq->rx_bufs = NULL;
> } else {
> - dma_free_coherent(ndev->dev.parent,
> - sizeof(struct rswitch_ext_desc) *
> - (gq->ring_size + 1), gq->tx_ring, gq->ring_dma);
> - gq->tx_ring = NULL;
> + if (gq->tx_ring) {
> + dma_free_coherent(dev,
> + sizeof(struct rswitch_ext_desc) *
> + (gq->ring_size + 1), gq->tx_ring, gq->ring_dma);
> + gq->tx_ring = NULL;
> + }
> kfree(gq->skbs);
> gq->skbs = NULL;
> kfree(gq->unmap_addrs);
[Severity: High]
This isn't a bug introduced by this patch, but does the TX teardown also
leak resources?
It appears it does not iterate over pending gq->skbs to unmap and free
them before freeing the array itself.
[ ... ]
> @@ -483,18 +486,17 @@ static int rswitch_gwca_queue_ext_ts_fill(struct net_device *ndev,
> index = (i + start_index) % gq->ring_size;
> desc = &gq->rx_ring[index];
> if (!gq->dir_tx) {
> - dma_addr = dma_map_single(ndev->dev.parent,
> + dma_addr = dma_map_single(dev,
> gq->rx_bufs[index] + RSWITCH_HEADROOM,
> RSWITCH_MAP_BUF_SIZE,
> DMA_FROM_DEVICE);
> - if (dma_mapping_error(ndev->dev.parent, dma_addr))
> + if (dma_mapping_error(dev, dma_addr))
> goto err;
>
> desc->desc.info_ds = cpu_to_le16(RSWITCH_DESC_BUF_SIZE);
> rswitch_desc_set_dptr(&desc->desc, dma_addr);
> dma_wmb();
> desc->desc.die_dt = DT_FEMPTY | DIE;
> - desc->info1 = cpu_to_le64(INFO1_SPN(rdev->etha->index));
> } else {
> desc->desc.die_dt = DT_EEMPTY | DIE;
> }
> @@ -507,8 +509,10 @@ static int rswitch_gwca_queue_ext_ts_fill(struct net_device *ndev,
> for (; i-- > 0; ) {
> index = (i + start_index) % gq->ring_size;
> desc = &gq->rx_ring[index];
> + desc->desc.die_dt = 0x00;
> + dma_wmb();
> dma_addr = rswitch_desc_get_dptr(&desc->desc);
> - dma_unmap_single(ndev->dev.parent, dma_addr,
> + dma_unmap_single(dev, dma_addr,
> RSWITCH_MAP_BUF_SIZE, DMA_FROM_DEVICE);
> }
> }
[Severity: High]
This is a pre-existing issue, but is there a hardware race condition in
this error path?
Earlier in the loop, buffers are mapped and immediately exposed to the
running hardware by setting desc->desc.die_dt = DT_FEMPTY | DIE. If a
subsequent allocation in the batch fails, we jump to this error path to
revoke previously armed descriptors by clearing die_dt and calling
dma_unmap_single().
If the active hardware has already fetched the descriptor and started a
DMA write before we revoke it, will unmapping it lead to IOMMU faults or
memory corruption?
Should the descriptors only be armed after all allocations and mappings in
the batch have succeeded?
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 05/14] net: renesas: rswitch: add exception path for packets with unknown dst MAC
2026-05-22 12:12 ` [PATCH net-next v5 05/14] net: renesas: rswitch: add exception path for packets with unknown dst MAC Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: add exception path for packets with unknown dst MAC
Packets with unknown MAC address cannot be handled by the HW forwarding.
Creates an exception path with a shared queue to forward these packets to
the network driver for MAC learning.
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index 9510bcdf9e830..1f4ec65f6f9bd 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
[ ... ]
> @@ -736,6 +772,8 @@ static int rswitch_gwca_hw_deinit(struct rswitch_private *priv)
> if (err < 0)
> return err;
>
[Severity: Medium]
Will this leak the shared RX queue memory if rswitch_gwca_change_mode() fails
during teardown and returns early?
> + rswitch_shared_rx_queue_free(priv);
> +
> return rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
> }
[ ... ]
> @@ -944,10 +982,15 @@ static int rswitch_poll(struct napi_struct *napi, int budget)
>
> if (napi_complete_done(napi, budget - quota)) {
> spin_lock_irqsave(&priv->lock, flags);
> + if (rdev->rx_old_queue) {
> + rdev->rx_queue = rdev->rx_old_queue;
> + rdev->rx_old_queue = NULL;
> + }
> if (test_bit(rdev->port, priv->opened_ports)) {
> rswitch_enadis_data_irq(priv, rdev->tx_queue->index, true);
> rswitch_enadis_data_irq(priv, rdev->rx_queue->index, true);
> }
> + rswitch_enadis_data_irq(priv, priv->gwca.l2_shared_rx_queue->index, true);
[Severity: Critical]
Does unmasking the shared RX queue globally here create a race condition?
If Port A completes NAPI while Port B's NAPI is still actively polling the
shared queue, couldn't another interrupt fire, scheduling another NAPI and
resulting in multiple NAPIs running concurrently on the same shared queue?
This might cause lockless concurrent modifications to gq->cur and gq->dirty,
leading to list corruption.
> spin_unlock_irqrestore(&priv->lock, flags);
> }
>
[ ... ]
> @@ -960,15 +1003,41 @@ static int rswitch_poll(struct napi_struct *napi, int budget)
> return 0;
> }
>
> -static void rswitch_queue_interrupt(struct net_device *ndev)
> +static void rswitch_queue_interrupt(struct rswitch_private *priv, struct rswitch_gwca_queue *gq)
> {
> - struct rswitch_device *rdev = netdev_priv(ndev);
> + struct rswitch_ext_ts_desc *desc;
> + struct rswitch_device *rdev;
> + struct net_device *ndev;
> + unsigned long flags;
> + u32 spn;
> +
> + /* If we receive a shared queue through the exception path, it will be missing the ndev
> + * pointer. This needs to be added to be able to determine from which port the packet was
> + * received. Then we temporarily exchange the rx_queue pointer in rdev. This will be
> + * restored after the packet has been processed.
> + */
> +
> + if (gq->index == priv->gwca.l2_shared_rx_queue->index) {
> + desc = &gq->rx_ring[gq->cur];
> + spn = FIELD_GET(SPN, le64_to_cpu(desc->info1));
[Severity: Medium]
Could this read stale or garbage data from the descriptor?
It appears to read the DMA descriptor without verifying that the hardware
has handed ownership back to the CPU (e.g., checking DT_FEMPTY) and without
a dma_rmb() barrier before the read.
[Severity: High]
Is it possible for the hardware to provide an out-of-bounds SPN value here?
Since spn is used to index priv->rdev[] on the next line, an unexpected or
corrupted value might cause an out-of-bounds memory access.
> + ndev = priv->rdev[spn]->ndev;
> + rdev = netdev_priv(ndev);
> + gq->ndev = ndev;
[Severity: High]
Since the shared queue collects exception packets from all ports, will this
assign the entire queue to the NAPI of the first packet's port?
When NAPI polls and drains the queue, it seems subsequent packets might be
injected into the network stack under the first packet's ndev, even if they
originated from different physical ports.
> + /* store original rx_queue */
> + spin_lock_irqsave(&priv->lock, flags);
> + rdev->rx_old_queue = rdev->rx_queue;
> + rdev->rx_queue = gq;
[Severity: High]
Can rdev->rx_old_queue be permanently overwritten with the shared queue
pointer here?
If a second shared queue interrupt fires before NAPI completes and restores
the pointer, the handler will execute this path again and overwrite the
saved normal queue pointer with gq, leaking the normal RX queue.
> + spin_unlock_irqrestore(&priv->lock, flags);
> + } else {
> + rdev = netdev_priv(gq->ndev);
> + }
>
> if (napi_schedule_prep(&rdev->napi)) {
> - spin_lock(&rdev->priv->lock);
> - rswitch_enadis_data_irq(rdev->priv, rdev->tx_queue->index, false);
> - rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, false);
> - spin_unlock(&rdev->priv->lock);
> + spin_lock(&priv->lock);
> + rswitch_enadis_data_irq(priv, rdev->tx_queue->index, false);
> + rswitch_enadis_data_irq(priv, rdev->rx_queue->index, false);
> + rswitch_enadis_data_irq(priv, priv->gwca.l2_shared_rx_queue->index, false);
[Severity: Critical]
If napi_schedule_prep() returns false (because the target NAPI is already
running), hardware interrupts are not disabled. Could this lead to an
immediate interrupt storm for the shared RX queue?
> + spin_unlock(&priv->lock);
> __napi_schedule(&rdev->napi);
> }
> }
[ ... ]
> @@ -1653,6 +1733,11 @@ static int rswitch_stop(struct net_device *ndev)
> rswitch_enadis_data_irq(rdev->priv, rdev->tx_queue->index, false);
> rswitch_enadis_data_irq(rdev->priv, rdev->rx_queue->index, false);
> bitmap_clear(rdev->priv->opened_ports, rdev->port, 1);
> + if (bitmap_empty(rdev->priv->opened_ports, 1))
[Severity: High]
Does this check use the wrong size argument?
The second argument to bitmap_empty() is nbits. By passing 1, it seems it
only checks if bit 0 is empty. If port 0 is closed while other ports are
open, this would evaluate to true and prematurely disable the shared RX
queue interrupt for the remaining active ports.
> + rswitch_enadis_data_irq(rdev->priv,
> + rdev->priv->gwca.l2_shared_rx_queue->index,
> + false);
> +
> spin_unlock_irqrestore(&rdev->priv->lock, flags);
>
> napi_disable(&rdev->napi);
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 06/14] net: renesas: rswitch: add forwarding rules for gwca
2026-05-22 12:12 ` [PATCH net-next v5 06/14] net: renesas: rswitch: add forwarding rules for gwca Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
Subject: net: renesas: rswitch: add forwarding rules for gwca
This commit adds rules to forward packets from the Ethernet ports to the
CPU port (GWCA) using L2 forwarding. It introduces a new function to
configure GWCA forwarding rules and integrates it into the L2 offload
updates.
> Add rules to forward packets from the Ethernet ports to the CPU port (GWCA)
> using L2 forwarding instead of port forwarding.
[Severity: Medium]
The commit message mentions using L2 forwarding instead of port forwarding,
but is the existing port forwarding code actually removed or disabled?
If rswitch_fwd_init() still configures FWPBFC to unconditionally forward all
packets to the GWCA port, does having both L2 forwarding and port-based
forwarding active simultaneously lead to duplicated packets and unnecessary
CPU overhead?
> diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
> index 709524c8a5c4d..3cc0ce8762f3c 100644
> --- a/drivers/net/ethernet/renesas/rswitch_l2.c
> +++ b/drivers/net/ethernet/renesas/rswitch_l2.c
[ ... ]
> @@ -93,10 +93,32 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
> }
> }
>
> +static void rswitch_update_l2_hw_forwarding_gwca(struct rswitch_private *priv)
> +{
> + struct rswitch_device *rdev;
> + u32 fwpc0_set, fwpc0_clr, fwpc2_set, fwpc2_clr;
> +
> + fwpc0_clr = FWPC0_MACSSA | FWPC0_MACDSA | FWPC0_MACRUDA;
> + fwpc0_set = fwpc0_clr;
> + fwpc2_clr = FIELD_PREP(FWPC2_LTWFW, BIT(AGENT_INDEX_GWCA));
> + fwpc2_set = fwpc2_clr;
> +
> + (priv->offload_brdev) ? (fwpc0_clr = 0, fwpc2_set = 0)
> + : (fwpc0_set = 0, fwpc2_clr = 0);
> +
> + rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA), fwpc0_clr, fwpc0_set);
> +
> + rswitch_for_all_ports(priv, rdev) {
> + rswitch_modify(priv->addr, FWPC2(rdev->etha->index),
> + fwpc2_clr, fwpc2_set);
> + }
> +}
> +
> void rswitch_update_l2_offload(struct rswitch_private *priv)
> {
> rswitch_update_l2_hw_learning(priv);
> rswitch_update_l2_hw_forwarding(priv);
> + rswitch_update_l2_hw_forwarding_gwca(priv);
[Severity: Medium]
Do non-atomic updates to FWPC2 momentarily disable forwarding to the CPU port?
In rswitch_update_l2_hw_forwarding(), iowrite32() is used to overwrite
FWPC2, which unconditionally sets the GWCA bit to 1, disabling hardware L2
forwarding to the CPU port.
Immediately after, rswitch_update_l2_hw_forwarding_gwca() uses
rswitch_modify() to clear the GWCA bit back to 0.
Does this consecutive write sequence create a microsecond window where L2
forwarding to the CPU is incorrectly denied, risking packet drops for
hardware packets destined for the CPU?
> }
[Severity: High]
This is a pre-existing issue, but I noticed a potential kernel panic in
rswitch_update_offload_brdev() when logging is enabled:
drivers/net/ethernet/renesas/rswitch_l2.c:rswitch_update_offload_brdev() {
...
if (offload_brdev == priv->offload_brdev)
dev_dbg(&priv->pdev->dev,
"changing l2 offload from %s to %s\n",
netdev_name(priv->offload_brdev),
netdev_name(offload_brdev));
...
}
If a single port is added to a bridge, the loop searching for a shared
bridge exits early, leaving offload_brdev as NULL.
If priv->offload_brdev is also NULL (the initial state), the condition
offload_brdev == priv->offload_brdev evaluates to true.
Will this cause a kernel panic when dynamic debugging is active, since
netdev_name(NULL) unconditionally dereferences its argument?
[Severity: High]
This is a pre-existing issue, but do concurrent transmissions cause lost
writes to the shared GWTRC register?
In rswitch_start_xmit(), transmission is triggered by modifying the GWTRC
register:
drivers/net/ethernet/renesas/rswitch_main.c:rswitch_start_xmit() {
...
rswitch_modify(priv->addr, GWTRC, ...);
...
}
Because rswitch_modify() performs a lockless read-modify-write:
drivers/net/ethernet/renesas/rswitch_main.c:rswitch_modify() {
iowrite32((ioread32(addr + reg) & ~clear) | set, addr + reg);
}
Will concurrent ndo_start_xmit calls from different CPUs on different
ports race and overwrite each other's modifications, resulting in lost
transmit triggers and stalled TX queues?
[Severity: High]
This is a pre-existing issue, but does the driver fail to set
skb->offload_fwd_mark for hardware-forwarded L2 packets?
The driver supports hardware L2 forwarding but does not appear to set
skb->offload_fwd_mark = 1 before passing received packets to the network
stack.
Without this flag, the software bridge is unaware that the hardware has
already flooded the packet to other ports.
Will this cause the bridge to re-flood broadcast and multicast packets,
leading to packet duplication and potential network storms?
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 07/14] net: renesas: rswitch: make helper functions available to whole driver
2026-05-22 12:12 ` [PATCH net-next v5 07/14] net: renesas: rswitch: make helper functions available to whole driver Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: make helper functions available to whole driver
These functions will be needed in L2 and vlan handling. Remove static
declaration and add function prototype to rswitch.h.
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index 1f4ec65f6f9bd..20ac8f3b9357f 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
[ ... ]
> @@ -168,14 +168,13 @@ static int rswitch_fwd_init(struct rswitch_private *priv)
> }
>
> /* Gateway CPU agent block (GWCA) */
> -static int rswitch_gwca_change_mode(struct rswitch_private *priv,
> - enum rswitch_gwca_mode mode)
> +int rswitch_gwca_change_mode(struct rswitch_private *priv,
> + enum rswitch_gwca_mode mode)
> {
> int ret;
>
> if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
> rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
[Severity: High]
This isn't a bug introduced by this patch, but does making these mode change
functions globally available expose an unsynchronized read-modify-write data
race?
Looking at rswitch_agent_clock_ctrl() being called here, it performs an
unprotected read-modify-write on the shared RCEC and RCDC hardware registers:
static void rswitch_agent_clock_ctrl(...)
{
...
if (enable) {
val = ioread32(coma_addr + RCEC);
iowrite32(val | RCEC_RCE | BIT(port), coma_addr + RCEC);
}
...
}
Could concurrent calls from L2 or VLAN paths cause lost clock enable/disable
commands, leading to silent initialization failures for network ports?
I noticed a subsequent commit in this series ("net: renesas: rswitch: add
locking for agent clock control") attempts to fix this using agent_lock.
However, doesn't that fix introduce a hard deadlock by calling
spin_lock(&priv->agent_lock) twice consecutively in
rswitch_gwca_change_mode(), while also failing to initialize the spinlock?
Would it be better to address the missing synchronization correctly before
exporting these functions for broader concurrent use?
> -
> iowrite32(GWMC_OPC_DISABLE, priv->addr + GWMC);
>
> ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, GWMC_OPC_DISABLE);
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 08/14] net: renesas: rswitch: add locking for agent clock control
2026-05-22 12:12 ` [PATCH net-next v5 08/14] net: renesas: rswitch: add locking for agent clock control Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: add locking for agent clock control
This commit introduces an agent_lock to serialize access to the shared RCEC
and RCDC agent clock registers. This aims to prevent potential race
conditions between ETHA and GWCA ports when enabling or disabling clocks.
> diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
> --- a/drivers/net/ethernet/renesas/rswitch.h
> +++ b/drivers/net/ethernet/renesas/rswitch.h
[ ... ]
> @@ -1207,6 +1208,8 @@ struct rswitch_private {
> struct list_head port_list;
>
> spinlock_t lock; /* lock interrupt registers' control */
> + spinlock_t agent_lock; /* rswitch agent clock control */
> +
[Severity: High]
Is this lock initialized anywhere before it is used?
It looks like agent_lock is added to the structure, but spin_lock_init() is
missing from the driver probe path. Depending on kzalloc for
zero-initialization is not valid for spinlocks and can cause issues on
architectures or kernel configurations that require explicit initialization.
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
> @@ -173,8 +173,12 @@ int rswitch_gwca_change_mode(struct rswitch_private *priv,
> {
> int ret;
>
> - if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
> + if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index)) {
> + spin_lock(&priv->agent_lock);
> rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
> + spin_lock(&priv->agent_lock);
> + }
[Severity: High]
Does this code cause an immediate deadlock?
Because spin_lock() is called twice consecutively without a spin_unlock(), it
appears the CPU will spin indefinitely waiting for the lock it already holds.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 10/14] net: renesas: rswitch: update port HW init
2026-05-22 12:12 ` [PATCH net-next v5 10/14] net: renesas: rswitch: update port HW init Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: update port HW init
Certain Ethernet and cpu port settings need to be updated for vlan
support.
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index a445d9a8505d8..13e59d2d08282 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
[ ... ]
> @@ -895,6 +899,7 @@ static bool rswitch_rx(struct net_device *ndev, int *quota)
> limit = boguscnt;
>
> desc = &gq->rx_ring[gq->cur];
> +
> while ((desc->desc.die_dt & DT_MASK) != DT_FEMPTY) {
> dma_rmb();
> skb = rswitch_rx_handle_desc(ndev, gq, desc);
[Severity: High]
This is a pre-existing issue, but does this code leak receive buffer
fragments allocated via netdev_alloc_frag() when packet assembly fails?
In rswitch_rx_handle_desc(), if build_skb() fails for a start fragment,
or if a middle/end fragment arrives without a preceding start fragment,
the function returns NULL.
The caller rswitch_rx() then jumps to the out label:
skb = rswitch_rx_handle_desc(ndev, gq, desc);
if (!skb)
goto out;
...
out:
gq->rx_bufs[gq->cur] = NULL;
This executes gq->rx_bufs[gq->cur] = NULL, discarding the pointer to the
memory previously allocated by netdev_alloc_frag(). Because the fragment
is neither attached to an SKB nor explicitly freed before the pointer is
zeroed, this appears to cause an unrecoverable memory leak.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 11/14] net: renesas: rswitch: clean up is_rdev rswitch_device checking
2026-05-22 12:12 ` [PATCH net-next v5 11/14] net: renesas: rswitch: clean up is_rdev rswitch_device checking Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: clean up is_rdev rswitch_device checking
This commit cleans up the rswitch_device checking by replacing the
rswitch_port_check wrapper with direct calls to is_rdev. It also adds
a null pointer check for the net_device pointer in is_rdev.
> diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
> index 3cc0ce8762f3c..c76fc8121ef05 100644
> --- a/drivers/net/ethernet/renesas/rswitch_l2.c
> +++ b/drivers/net/ethernet/renesas/rswitch_l2.c
[ ... ]
> @@ -278,13 +273,13 @@ static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
> return -EOPNOTSUPP;
[Severity: High]
This is a pre-existing issue, but does this unconditionally intercept
and reject port object addition and deletion events for all devices
in the system?
Returning -EOPNOTSUPP directly without checking if the device belongs to
the rswitch driver stops the global notification chain prematurely
because raw negative values have the NOTIFY_STOP_MASK bit set. Could
this break switchdev object offloading (such as VLAN configurations)
for all other unrelated network interfaces in the system?
I note this is fixed later in the series by commit 17583b9d88b1b
("net: renesas: rswitch: add vlan aware switching").
> case SWITCHDEV_PORT_ATTR_SET:
> ret = switchdev_handle_port_attr_set(ndev, ptr,
> - rswitch_port_check,
> + is_rdev,
> rswitch_port_attr_set);
> break;
> default:
> - if (!rswitch_port_check(ndev))
> - return NOTIFY_DONE;
> - ret = -EOPNOTSUPP;
> + if (is_rdev(ndev))
> + return -EOPNOTSUPP;
[Severity: High]
Does returning a raw negative error code directly from the notifier
callback cause the kernel to misinterpret the failure as a success
and abruptly halt the notifier chain?
By returning -EOPNOTSUPP directly instead of assigning it to ret and
relying on notifier_from_errno() at the end of the function, this
returns a negative integer. The binary representation has the
NOTIFY_STOP_MASK bit set, which incorrectly halts the entire notifier
chain.
Furthermore, when the switchdev core decodes this value using
notifier_to_errno(), the stripped integer evaluates to 0 (success).
Could this cause the kernel to silently assume unsupported operations
on rswitch devices succeeded?
I see this was also fixed later in the series in commit 17583b9d88b1b
("net: renesas: rswitch: add vlan aware switching").
> + return NOTIFY_DONE;
> }
>
> return notifier_from_errno(ret);
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 12/14] net: renesas: rswitch: add passing of rswitch_private into notifiers
2026-05-22 12:12 ` [PATCH net-next v5 12/14] net: renesas: rswitch: add passing of rswitch_private into notifiers Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: add passing of rswitch_private into notifiers
This commit provides the rswitch_private structure to the network notifiers.
This allows the notification handlers to access the hardware state and
device-specific data.
> diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
> index c76fc8121ef05..d2deb7cb76349 100644
> --- a/drivers/net/ethernet/renesas/rswitch_l2.c
> +++ b/drivers/net/ethernet/renesas/rswitch_l2.c
[ ... ]
> -int rswitch_register_notifiers(void)
> +int rswitch_register_notifiers(struct rswitch_private *priv)
> {
> - int ret;
> + int err;
>
> - ret = register_netdevice_notifier(&rswitch_netdevice_nb);
> - if (ret)
> + priv->rswitch_netdevice_nb.notifier_call = rswitch_netdevice_event;
> + err = register_netdevice_notifier(&priv->rswitch_netdevice_nb);
[Severity: High]
Does moving the notifier blocks into the per-device structure without
filtering by instance cause duplicate event processing?
Since callbacks like rswitch_netdevice_event only check if the device is an
rswitch netdevice, rather than verifying it belongs to this specific priv
instance, it seems every registered callback will process the same event if
multiple driver instances exist. Could this lead to events on one instance
inadvertently modifying the hardware registers of another?
> + if (err)
> goto register_netdevice_notifier_failed;
[ ... ]
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index da400020a8f19..cfe7bfe6546b2 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
> @@ -2241,6 +2241,30 @@ static int rswitch_init(struct rswitch_private *priv)
> return err;
> }
>
> +static void rswitch_deinit(struct rswitch_private *priv)
> +{
> + unsigned int i;
> +
> + rswitch_gwca_hw_deinit(priv);
> + rcar_gen4_ptp_unregister(priv->ptp_priv);
> +
> + rswitch_for_each_enabled_port(priv, i) {
> + struct rswitch_device *rdev = priv->rdev[i];
> +
> + unregister_netdev(rdev->ndev);
[Severity: High]
This is a pre-existing issue, but does the ordering in this teardown path
cause a use-after-free?
The hardware is reset and DMA queues are freed via rswitch_gwca_hw_deinit()
before the loop that calls unregister_netdev(). If NAPI is actively polling
or user space transmits packets while the queues are being freed, could this
crash? Should unregister_netdev() be called to synchronously stop queues
before destroying the hardware state?
> + rswitch_ether_port_deinit_one(rdev);
> + phy_exit(priv->rdev[i]->serdes);
> + }
[ ... ]
> @@ -2317,9 +2341,13 @@ static int renesas_eth_sw_probe(struct platform_device *pdev)
> if (list_empty(&priv->port_list))
> dev_warn(&pdev->dev, "could not initialize any ports\n");
>
> - ret = rswitch_register_notifiers();
> + ret = rswitch_register_notifiers(priv);
> if (ret) {
> + rswitch_deinit(priv);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
[Severity: Medium]
Can this asynchronous put leave the hardware in an active power state?
The call to pm_runtime_put() queues the idle work asynchronously, but
pm_runtime_disable() includes a barrier that might cancel the pending
suspend work before it executes. Should this use pm_runtime_put_sync()
instead to ensure the suspend completes before the device is disabled?
> dev_err(&pdev->dev, "could not register notifiers\n");
> +
> return ret;
> }
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification
2026-05-22 12:12 ` [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification Michael Dege
2026-05-25 20:40 ` Jakub Kicinski
@ 2026-05-25 20:41 ` Jakub Kicinski
1 sibling, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: renesas: rswitch: add handler for FDB notification
The SWITCHDEV_FDB_ADD_TO_DEVICE notification writes the bridge MAC address
to the GWCA and adds a static rule to the MAC table.
The SWITCHDEV_FDB_DEL_TO_DEVICE removes the static rule from the MAC table.
> diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
> index d2deb7cb76349..4d0646283f88f 100644
> --- a/drivers/net/ethernet/renesas/rswitch_l2.c
> +++ b/drivers/net/ethernet/renesas/rswitch_l2.c
[ ... ]
> @@ -82,8 +83,9 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
> *
> * Do not allow L2 forwarding to self for hw port.
> */
> - iowrite32(FIELD_PREP(FWPC2_LTWFW, fwd_mask | BIT(rdev->port)),
> - priv->addr + FWPC2(rdev->port));
> + rswitch_modify(priv->addr, FWPC2(rdev->port),
> + FIELD_PREP(FWPC2_LTWFW, ~(fwd_mask | BIT(rdev->port))),
> + 0);
[Severity: High]
Does this bitwise logic correctly disable hardware forwarding for inactive
ports?
Since fwd_mask contains 0s for active ports and 1s for inactive ports, the
inverted mask ~(fwd_mask | BIT(rdev->port)) yields 1s for active ports and
0s for inactive ports.
This means the clear argument to rswitch_modify() will be 0 for inactive
ports. Combined with a set argument of 0, does this mean once a port is
active, it can never be disabled, potentially breaking Spanning Tree Protocol
(STP) blocking states?
[ ... ]
> @@ -285,6 +269,176 @@ static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
> return notifier_from_errno(ret);
> }
>
[ ... ]
> +static void rswitch_switchdev_bridge_fdb_event_work(struct work_struct *work)
> +{
> + struct rswitch_switchdev_event_work *switchdev_work;
> + const unsigned char addr[] = {0, 0, 0, 0, 0, 0};
> + struct rswitch_device *rdev;
> + struct net_device *ndev;
> +
> + switchdev_work = container_of(work, struct rswitch_switchdev_event_work, work);
> + ndev = switchdev_work->ndev;
> +
> + rtnl_lock();
> +
> + /* Unfortunately all net_device members point to br0, there is no simple way to check
> + * if the event was triggered by a port device setting.
> + */
> + rswitch_for_all_ports(switchdev_work->priv, rdev) {
> + if (ether_addr_equal(rdev->ndev->dev_addr, switchdev_work->fdb_info.addr))
> + goto out;
> + }
> +
> + /* Handle only bridge device */
> + if (is_rdev(ndev) || !netif_is_bridge_master(ndev))
> + goto out;
> +
> + switch (switchdev_work->event) {
> + case SWITCHDEV_FDB_ADD_TO_DEVICE:
> + rswitch_gwca_write_mac_address(switchdev_work->priv, switchdev_work->fdb_info.addr);
> + rswitch_add_addr_to_mactable(switchdev_work->priv, switchdev_work->fdb_info.addr);
> + break;
[Severity: High]
Could this code process FDB events for unrelated software bridges on the
system?
While netif_is_bridge_master() checks if the device is a bridge, it doesn't
seem to verify if the bridge actually enslaves any of the switch's ports
(for example, checking if ndev == rdev->brdev).
Also, does writing the FDB address to the GWCA MAC address here via
rswitch_gwca_write_mac_address() unconditionally overwrite the switch's
Gateway MAC address with any static FDB entry?
[ ... ]
> +static int rswitch_switchdev_event(struct notifier_block *nb,
> + unsigned long event,
> + void *ptr)
> +{
[ ... ]
> + switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
> + if (!switchdev_work->fdb_info.addr)
> + goto err_addr_alloc;
> +
> + priv->switchdev_work = switchdev_work;
[Severity: High]
Can storing the dynamically allocated work item pointer in a single
priv->switchdev_work field lead to memory leaks or use-after-free
conditions?
If multiple events are queued rapidly, this pointer appears to be overwritten,
losing track of previous work items and leaking the memory for both the work
item and the fdb_info.addr reference.
> +
> + ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
> + fdb_info->addr);
> + netdev_hold(ndev, &switchdev_work->dev_tracker, GFP_ATOMIC);
> + queue_work(system_long_wq, &switchdev_work->work);
> +
> + break;
> + }
[Severity: Medium]
Is system_long_wq the right workqueue to use here?
Since system_long_wq allows concurrent execution across different CPUs, rapid
ADD and DEL events for the same MAC address could race to acquire rtnl_lock()
in the worker.
Could this lack of strict FIFO ordering allow a DEL event to execute before
an ADD event, leaving a stale, permanent entry in the MAC table? Would a
dedicated ordered workqueue be more appropriate to preserve switchdev event
ordering?
> diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c
> index cfe7bfe6546b2..6fa37533feffd 100644
> --- a/drivers/net/ethernet/renesas/rswitch_main.c
> +++ b/drivers/net/ethernet/renesas/rswitch_main.c
[ ... ]
> @@ -2262,6 +2262,8 @@ static void rswitch_deinit(struct rswitch_private *priv)
> rswitch_gwca_ts_queue_free(priv);
> rswitch_gwca_linkfix_free(priv);
>
> + cancel_work_sync(&priv->switchdev_work->work);
> +
> rswitch_clock_disable(priv);
> }
[Severity: High]
Can this cancel_work_sync() call cause a NULL pointer dereference or
use-after-free?
If no FDB events were processed, priv->switchdev_work will be NULL, causing
an immediate dereference when accessing ->work.
If an event did finish, the struct is freed by its own worker in
rswitch_switchdev_bridge_fdb_event_work(), meaning cancel_work_sync() would
operate on freed memory.
[Severity: Critical]
This is a pre-existing issue, but earlier in the same series, there appears
to be a double lock acquisition without an unlock.
Looking at rswitch_gwca_change_mode() in
drivers/net/ethernet/renesas/rswitch_main.c:
int rswitch_gwca_change_mode(...)
{
...
if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index)) {
spin_lock(&priv->agent_lock);
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
spin_lock(&priv->agent_lock);
}
...
Could this second spin_lock() cause an immediate deadlock whenever the agent
clock is enabled?
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v5 14/14] net: renesas: rswitch: add vlan aware switching
2026-05-22 12:12 ` [PATCH net-next v5 14/14] net: renesas: rswitch: add vlan aware switching Michael Dege
@ 2026-05-25 20:41 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-05-25 20:41 UTC (permalink / raw)
To: michael.dege
Cc: Jakub Kicinski, yoshihiro.shimoda.uh, andrew+netdev, davem,
edumazet, pabeni, niklas.soderlund, paul, richardcochran, netdev,
linux-renesas-soc, linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
Subject: net: renesas: rswitch: add vlan aware switching
This patch adds VLAN support to the L2 HW bridge for the Renesas switch.
It implements the standard switchdev callbacks for configuring STP state,
aging time, and VLAN filtering. The implementation works around hardware
limitations by keeping all ports in a default VLAN.
> diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
> --- a/drivers/net/ethernet/renesas/rswitch_l2.c
> +++ b/drivers/net/ethernet/renesas/rswitch_l2.c
[ ... ]
> +static void rswitch_update_vlan_filtering(struct rswitch_private *priv,
> + bool vlan_filtering)
> +{
> + if (vlan_filtering)
> + rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA),
> + 0, FWPC0_VLANSA | FWPC0_VLANRU);
> + else
> + rswitch_modify(priv->addr, FWPC0(AGENT_INDEX_GWCA),
> + FWPC0_VLANSA | FWPC0_VLANRU, 0);
> +}
[Severity: Medium]
Does this properly synchronize the VLAN filtering state to the physical
switch ports? This seems to update the FWPC0 registers only for the CPU
port (AGENT_INDEX_GWCA) without iterating over the external physical
ports, which might leave the hardware in an inconsistent state.
[ ... ]
> +static int rswitch_handle_port_attr_set(struct net_device *ndev,
> + struct notifier_block *nb,
> + struct switchdev_notifier_port_attr_info *info)
> +{
[ ... ]
> + switch (attr->id) {
> + case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
> + err = rswitch_port_update_stp_state(ndev, attr->u.stp_state);
> +
> + break;
[ ... ]
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + if (err < 0)
> + return err;
> +
> + info->handled = true;
> +
> + return NOTIFY_DONE;
> +}
[Severity: High]
Could this block switchdev configurations for other network devices on the
system?
The switchdev notifiers are broadcast globally, and without a check
verifying the target ndev belongs to this driver, the default case here
returns -EOPNOTSUPP for unrelated devices. When passed to
notifier_from_errno(), this stops the global notifier chain.
[ ... ]
> +static int rswitch_gwca_set_vlan_tag(struct rswitch_private *priv,
> + struct switchdev_obj_port_vlan *p_vlan,
> + bool delete)
> +{
> + u32 vem_val;
> + int err;
> +
> + err = rswitch_gwca_change_mode(priv, GWMC_OPC_CONFIG);
[Severity: High]
This isn't a bug introduced by this patch, but there appears to be a self-
deadlock in rswitch_gwca_change_mode() when trying to enable the agent
clock:
drivers/net/ethernet/renesas/rswitch_main.c:rswitch_gwca_change_mode() {
if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index)) {
spin_lock(&priv->agent_lock);
rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
spin_lock(&priv->agent_lock);
}
...
}
Will the second spin_lock() cause a deadlock on the same CPU?
> + if (err < 0)
> + return err;
[ ... ]
> +static int rswitch_port_obj_do_add_gwca(struct net_device *ndev,
> + struct rswitch_private *priv,
> + struct switchdev_obj_port_vlan *p_vlan)
> +{
> + int err;
> +
> + if (!(p_vlan->flags & BRIDGE_VLAN_INFO_BRENTRY))
> + return NOTIFY_DONE;
> +
> + /* Set Rswitch VLAN mode */
> + iowrite32(br_vlan_enabled(ndev) ? FIELD_PREP(FWGC_SVM, C_TAG) : 0, priv->addr + FWGC);
[Severity: High]
Is it safe to pass ndev directly to br_vlan_enabled() here?
Because the notifier runs globally and routes non-rdev devices into this
function, ndev might be an unrelated physical port rather than a bridge
master. This seems like it could lead to out-of-bounds memory reads when
the function dereferences the foreign driver's private data.
[ ... ]
> +static int rswitch_port_obj_do_del(struct net_device *ndev,
> + struct switchdev_obj_port_vlan *p_vlan)
> +{
> + struct rswitch_device *rdev = netdev_priv(ndev);
> + struct rswitch_private *priv = rdev->priv;
> + struct rswitch_etha *etha = rdev->etha;
> + int err;
> +
> + err = rswitch_port_set_vlan_tag(etha, p_vlan, true);
> + if (err < 0)
> + return err;
> +
> + rswitch_modify(priv->addr, FWPC0(etha->index), FWPC0_VLANSA | FWPC0_VLANRU, 0);
> + rswitch_modify(priv->addr, FWPC2(AGENT_INDEX_GWCA), 0,
> + FIELD_PREP(FWPC2_LTWFW, BIT(etha->index)));
> + rswitch_modify(priv->addr, FWPC2(rdev->port),
> + 0, FIELD_PREP(FWPC2_LTWFW, GENMASK(RSWITCH_NUM_AGENTS - 1, 0)));
[Severity: High]
Does this unconditionally break connectivity for all remaining active VLANs
on this port?
When handling a VLAN deletion event, clearing these forwarding masks happens
regardless of whether the port is still a member of other VLANs.
> +
> + return rswitch_erase_vlan_table(priv, p_vlan->vid, etha->index);
> +}
[ ... ]
> static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
> - unsigned long event, void *ptr)
> + unsigned long event,
> + void *ptr)
> {
> struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
> - int ret;
>
> switch (event) {
> case SWITCHDEV_PORT_OBJ_ADD:
> - return -EOPNOTSUPP;
> + err = rswitch_handle_port_obj_add(ndev, nb, ptr);
> +
> + return notifier_from_errno(err);
> case SWITCHDEV_PORT_OBJ_DEL:
> - return -EOPNOTSUPP;
> + err = rswitch_handle_port_obj_del(ndev, nb, ptr);
> +
> + return notifier_from_errno(err);
> case SWITCHDEV_PORT_ATTR_SET:
> - ret = switchdev_handle_port_attr_set(ndev, ptr,
> - is_rdev,
> - rswitch_port_attr_set);
> - break;
> - default:
> - if (is_rdev(ndev))
> - return -EOPNOTSUPP;
> - return NOTIFY_DONE;
> + err = rswitch_handle_port_attr_set(ndev, nb, ptr);
> +
> + return notifier_from_errno(err);
> }
[Severity: High]
Could removing the is_rdev check from the attribute and object handler
paths cause the driver to wrongly modify its own MMIO registers based on
events meant for other hardware?
Since switchdev_handle_port_attr_set previously guarded against unrelated
devices, removing it exposes the handlers to global broadcast events.
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-05-25 20:41 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-22 12:12 [net-next PATCH v5 00/14] net: renesas: rswitch: R-Car S4 add VLAN aware switching Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 01/14] net: renesas: rswitch: improve port change mode functions Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 02/14] net: renesas: rswitch: use device instead of net_device Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 03/14] net: renesas: rswitch: fix FWPC2 register access macros Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 04/14] net: renesas: rswitch: add register definitions for vlan support Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 05/14] net: renesas: rswitch: add exception path for packets with unknown dst MAC Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 06/14] net: renesas: rswitch: add forwarding rules for gwca Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 07/14] net: renesas: rswitch: make helper functions available to whole driver Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 08/14] net: renesas: rswitch: add locking for agent clock control Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 09/14] net: renesas: rswitch: add basic vlan init to rswitch_fwd_init Michael Dege
2026-05-22 12:12 ` [PATCH net-next v5 10/14] net: renesas: rswitch: update port HW init Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 11/14] net: renesas: rswitch: clean up is_rdev rswitch_device checking Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 12/14] net: renesas: rswitch: add passing of rswitch_private into notifiers Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 13/14] net: renesas: rswitch: add handler for FDB notification Michael Dege
2026-05-25 20:40 ` Jakub Kicinski
2026-05-25 20:41 ` Jakub Kicinski
2026-05-22 12:12 ` [PATCH net-next v5 14/14] net: renesas: rswitch: add vlan aware switching Michael Dege
2026-05-25 20:41 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox