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/rswitch_l2.c | 406 ++++++++++++++++++++++++++----
1 file changed, 357 insertions(+), 49 deletions(-)
diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c
index edfa22676faa..16a519961725 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>
@@ -177,22 +178,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)
@@ -216,61 +201,395 @@ 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;
+
+ 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 err, vem_val;
+
+ 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 err, vem_val;
+
+ 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;
+ u32 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 = SWITCHDEV_OBJ_PORT_VLAN(info->obj);
+ struct rswitch_private *priv;
+ int err;
+
+ priv = container_of(nb, struct rswitch_private, rswitch_switchdev_blocking_nb);
+
+ 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;
+
+ switch (info->obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
+ 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 = SWITCHDEV_OBJ_PORT_VLAN(info->obj);
+ struct rswitch_private *priv;
+ int err;
+
+ priv = container_of(nb, struct rswitch_private, rswitch_switchdev_blocking_nb);
+
+ 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;
+
+ switch (info->obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
+ 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 NOTIFY_DONE;
- ret = -EOPNOTSUPP;
+ 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)
@@ -393,7 +712,6 @@ static int rswitch_switchdev_event(struct notifier_block *nb,
struct switchdev_notifier_fdb_info *fdb_info;
struct switchdev_notifier_info *info = ptr;
struct rswitch_private *priv;
- int err;
priv = container_of(nb, struct rswitch_private, rswitch_switchdev_nb);
@@ -428,16 +746,6 @@ static int rswitch_switchdev_event(struct notifier_block *nb,
queue_work(system_long_wq, &switchdev_work->work);
break;
- case SWITCHDEV_PORT_ATTR_SET:
- err = switchdev_handle_port_attr_set(ndev, ptr,
- is_rdev,
- rswitch_port_attr_set);
- return notifier_from_errno(err);
-
- if (!is_rdev(ndev))
- return NOTIFY_DONE;
-
- return notifier_from_errno(-EOPNOTSUPP);
}
return NOTIFY_DONE;
--
2.43.0
Hi Michael,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 1f318b96cc84d7c2ab792fcc0bfd42a7ca890681]
url: https://github.com/intel-lab-lkp/linux/commits/Michael-Dege/net-renesas-rswitch-improve-port-change-mode-functions/20260318-221709
base: 1f318b96cc84d7c2ab792fcc0bfd42a7ca890681
patch link: https://lore.kernel.org/r/20260317-rswitch_add_vlans-v1-13-3a57bfa0f2d1%40renesas.com
patch subject: [PATCH net-next 13/13] net: renesas: rswitch: add vlan aware switching
config: sparc64-allmodconfig (https://download.01.org/0day-ci/archive/20260319/202603190927.5oLw58VC-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 4abb927bacf37f18f6359a41639a6d1b3bffffb5)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260319/202603190927.5oLw58VC-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603190927.5oLw58VC-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/net/ethernet/renesas/rswitch_l2.c:268:7: warning: variable 'err' is used uninitialized whenever switch case is taken [-Wsometimes-uninitialized]
268 | case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/net/ethernet/renesas/rswitch_l2.c:275:6: note: uninitialized use occurs here
275 | if (err < 0)
| ^~~
drivers/net/ethernet/renesas/rswitch_l2.c:264:7: warning: variable 'err' is used uninitialized whenever switch case is taken [-Wsometimes-uninitialized]
264 | case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/net/ethernet/renesas/rswitch_l2.c:275:6: note: uninitialized use occurs here
275 | if (err < 0)
| ^~~
drivers/net/ethernet/renesas/rswitch_l2.c:251:9: note: initialize the variable 'err' to silence this warning
251 | int err;
| ^
| = 0
2 warnings generated.
vim +/err +268 drivers/net/ethernet/renesas/rswitch_l2.c
244
245 static int rswitch_handle_port_attr_set(struct net_device *ndev,
246 struct notifier_block *nb,
247 struct switchdev_notifier_port_attr_info *info)
248 {
249 const struct switchdev_attr *attr = info->attr;
250 struct rswitch_private *priv;
251 int err;
252
253 priv = container_of(nb, struct rswitch_private, rswitch_switchdev_blocking_nb);
254
255 switch (attr->id) {
256 case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
257 err = rswitch_port_update_stp_state(ndev, attr->u.stp_state);
258
259 break;
260 case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
261 err = rswitch_update_ageing_time(priv, attr->u.ageing_time);
262
263 break;
264 case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
265 rswitch_update_vlan_filtering(priv, attr->u.vlan_filtering);
266
267 break;
> 268 case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
269
270 break;
271 default:
272 return -EOPNOTSUPP;
273 }
274
275 if (err < 0)
276 return err;
277
278 info->handled = true;
279
280 return NOTIFY_DONE;
281 }
282
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2026 Red Hat, Inc.