From nobody Wed Apr 1 13:58:00 2026 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by smtp.subspace.kernel.org (Postfix) with ESMTP id D2B1A3CCA16; Tue, 31 Mar 2026 10:04:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.160.252.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774951495; cv=none; b=CWbYega3lhuhCeV1AF9mg7/i5KMzSls8Mk3DhZm6Owny2ECF1DgfmOMr3L1sL5Q3OcldFb2K4lJOY7tFIr1F24cmUh8dOJWRwSJrgAV3MfGKOKM3oNz43J5war79JrkUdg9OhKUCUBzAhKsXorT8CurzCSkyq7Sg/73Wb4+OtCs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774951495; c=relaxed/simple; bh=VYRRItLBgOCi0ClLnqF5xeQ8XlkkFxVvkBLa4dcF7ZQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YQj+08ZSZ8YIzVzqgNWe5tj0vjyGH98uAg5Wn6RusV2STSBfqBLZViuT/QRHTp1Y/zTIX9HKnepkteGqopwYpx5Gfy1EDp6+j6AmRxJNh81NqxMivrLwwnU//CMRT0x5L7AXpCrtPP/Mp2BTIR1mPvJmYM0dAGB6OYLmz2tvI6U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=renesas.com; spf=pass smtp.mailfrom=renesas.com; arc=none smtp.client-ip=210.160.252.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=renesas.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=renesas.com X-CSE-ConnectionGUID: gD5FOTcFS6a+mVutX1zB8A== X-CSE-MsgGUID: IXD9uUsQTziKM8hCWxwiUQ== Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie5.idc.renesas.com with ESMTP; 31 Mar 2026 19:04:52 +0900 Received: from [127.0.1.1] (unknown [10.226.78.135]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id 97544416C823; Tue, 31 Mar 2026 19:04:49 +0900 (JST) From: Michael Dege Date: Tue, 31 Mar 2026 12:04:07 +0200 Subject: [PATCH net-next v3 13/13] net: renesas: rswitch: add vlan aware switching Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260331-rswitch_add_vlans-v3-13-c37f41b1c556@renesas.com> References: <20260331-rswitch_add_vlans-v3-0-c37f41b1c556@renesas.com> In-Reply-To: <20260331-rswitch_add_vlans-v3-0-c37f41b1c556@renesas.com> To: Yoshihiro Shimoda , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , =?utf-8?q?Niklas_S=C3=B6derlund?= , Paul Barker Cc: netdev@vger.kernel.org, linux-renesas-soc@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Dege X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774951442; l=15544; i=michael.dege@renesas.com; s=20251023; h=from:subject:message-id; bh=VYRRItLBgOCi0ClLnqF5xeQ8XlkkFxVvkBLa4dcF7ZQ=; b=bZuu+1ACxF4jbuuBw7SViXDjz9/Pf5FFjAnYkxEBHIPd8EAQA/kGupzbOGpqSrL/kpxo4YWgn WoGmtQDe/sODtHmpunKCZ/dT5xsmB24XRZF27ymtoaPCt1lWI5yjD1C X-Developer-Key: i=michael.dege@renesas.com; a=ed25519; pk=gu1rwIcCrAxNMv2I8fIfiQvt51xzZwnQy4Ua/DscQt8= 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 --- drivers/net/ethernet/renesas/Kconfig | 1 + drivers/net/ethernet/renesas/rswitch_l2.c | 408 ++++++++++++++++++++++++++= ---- 2 files changed, 360 insertions(+), 49 deletions(-) diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/re= nesas/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=3Dn select CRC32 select MII select PHYLINK diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethern= et/renesas/rswitch_l2.c index ea95a87ed234..b7d0e7f94ecd 100644 --- a/drivers/net/ethernet/renesas/rswitch_l2.c +++ b/drivers/net/ethernet/renesas/rswitch_l2.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include =20 @@ -173,22 +174,6 @@ static void rswitch_port_update_brdev(struct net_devic= e *ndev, rswitch_update_offload_brdev(rdev->priv); } =20 -static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_s= tate) -{ - struct rswitch_device *rdev; - - if (!is_rdev(ndev)) - return -ENODEV; - - rdev =3D netdev_priv(ndev); - rdev->learning_requested =3D (stp_state =3D=3D BR_STATE_LEARNING || - stp_state =3D=3D BR_STATE_FORWARDING); - rdev->forwarding_requested =3D (stp_state =3D=3D 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,397 @@ static int rswitch_netdevice_event(struct notifier_b= lock *nb, return NOTIFY_OK; } =20 -static int rswitch_update_ageing_time(struct net_device *ndev, clock_t tim= e) +static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_s= tate) { - struct rswitch_device *rdev =3D netdev_priv(ndev); - u32 reg_val; + struct rswitch_device *rdev; =20 if (!is_rdev(ndev)) return -ENODEV; =20 + rdev =3D netdev_priv(ndev); + rdev->learning_requested =3D (stp_state =3D=3D BR_STATE_LEARNING || + stp_state =3D=3D BR_STATE_FORWARDING); + rdev->forwarding_requested =3D (stp_state =3D=3D 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; =20 reg_val =3D FIELD_PREP(FWMACAGC_MACAGT, time); reg_val |=3D FWMACAGC_MACAGE | FWMACAGC_MACAGSL; - iowrite32(reg_val, rdev->priv->addr + FWMACAGC); + iowrite32(reg_val, priv->addr + FWMACAGC); =20 return 0; } =20 -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 =3D info->attr; + struct rswitch_private *priv; + int err =3D 0; + + priv =3D container_of(nb, struct rswitch_private, rswitch_switchdev_block= ing_nb); + switch (attr->id) { case SWITCHDEV_ATTR_ID_PORT_STP_STATE: - return rswitch_port_update_stp_state(ndev, attr->u.stp_state); + err =3D 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 =3D 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 =3D 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 =3D 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 =3D (ioread32(priv->addr + FWVLANTSR1) & VLANSLVS); + *vlandvs =3D (ioread32(priv->addr + FWVLANTSR3) & VLANDVS); + } + + return 0; +} + +static int rswitch_write_vlan_table(struct rswitch_private *priv, u16 vid,= u32 index) +{ + u32 vlancsdl =3D priv->gwca.l2_shared_rx_queue->index; + u32 vlanslvs =3D 0, vlandvs =3D 0; + int err; + + err =3D 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 |=3D BIT(index); + vlandvs |=3D BIT(index); + iowrite32(FIELD_PREP(VLANSLVL, vlanslvs), priv->addr + FWVLANTL2); + iowrite32(FIELD_PREP(VLANCSDL, vlancsdl), priv->addr + FWVLANTL3(GWCA_IND= EX)); + 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 =3D 0, vlandvs =3D 0; + int err; + + err =3D rswitch_read_vlan_table(priv, vid, &vlanslvs, &vlandvs); + if (err < 0) + return err; + + vlanslvs &=3D ~BIT(index); + vlandvs &=3D ~BIT(index); + + /* only erase empty vlan table entries */ + if (vlanslvs =3D=3D 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 =3D 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) =3D=3D 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 =3D FIELD_PREP(VEM, C_TAG_VLAN); + else if (p_vlan->flags & BRIDGE_VLAN_INFO_PVID) + vem_val =3D FIELD_PREP(VEM, HW_C_TAG_VLAN); + else + vem_val =3D 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 =3D 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) =3D=3D 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 =3D FIELD_PREP(VEM, C_TAG_VLAN); + else if (p_vlan->flags & BRIDGE_VLAN_INFO_PVID) + vem_val =3D FIELD_PREP(VEM, HW_C_TAG_VLAN); + else + vem_val =3D 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 =3D netdev_priv(ndev); + struct rswitch_private *priv =3D rdev->priv; + struct rswitch_etha *etha =3D 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 =3D 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_V= LANRU); + + 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 =3D 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 =3D netdev_priv(ndev); + struct rswitch_private *priv =3D rdev->priv; + struct rswitch_etha *etha =3D rdev->etha; + int err; + + err =3D rswitch_port_set_vlan_tag(etha, p_vlan, true); + if (err < 0) + return err; + + rswitch_modify(priv->addr, FWPC0(etha->index), FWPC0_VLANSA | FWPC0_VLANR= U, 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 =3D 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 =3D SWITCHDEV_OBJ_PORT_VLAN(info->= obj); + struct rswitch_private *priv; + int err; + + priv =3D container_of(nb, struct rswitch_private, rswitch_switchdev_block= ing_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 =3D rswitch_port_obj_do_add_gwca(ndev, priv, p_vlan); + else + err =3D rswitch_port_obj_do_add(ndev, p_vlan); + + if (err < 0) + return err; + + break; default: return -EOPNOTSUPP; } + + info->handled =3D 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 =3D SWITCHDEV_OBJ_PORT_VLAN(info->= obj); + struct rswitch_private *priv; + int err; + + priv =3D container_of(nb, struct rswitch_private, rswitch_switchdev_block= ing_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 =3D rswitch_port_obj_do_del_gwca(ndev, priv, p_vlan); + else + err =3D rswitch_port_obj_do_del(ndev, p_vlan); + + if (err < 0) + return err; + + break; + default: + return -EOPNOTSUPP; + } + + info->handled =3D true; + + return NOTIFY_DONE; } =20 static int rswitch_switchdev_blocking_event(struct notifier_block *nb, - unsigned long event, void *ptr) + unsigned long event, + void *ptr) { struct net_device *ndev =3D switchdev_notifier_info_to_dev(ptr); - int ret; + int err; =20 switch (event) { case SWITCHDEV_PORT_OBJ_ADD: - return -EOPNOTSUPP; + err =3D rswitch_handle_port_obj_add(ndev, nb, ptr); + + return notifier_from_errno(err); case SWITCHDEV_PORT_OBJ_DEL: - return -EOPNOTSUPP; + err =3D rswitch_handle_port_obj_del(ndev, nb, ptr); + + return notifier_from_errno(err); case SWITCHDEV_PORT_ATTR_SET: - ret =3D switchdev_handle_port_attr_set(ndev, ptr, - is_rdev, - rswitch_port_attr_set); - break; - default: - if (!is_rdev(ndev)) - return NOTIFY_DONE; - ret =3D -EOPNOTSUPP; + err =3D rswitch_handle_port_attr_set(ndev, nb, ptr); + + return notifier_from_errno(err); } =20 - return notifier_from_errno(ret); + return NOTIFY_DONE; } =20 static int rswitch_gwca_write_mac_address(struct rswitch_private *priv, co= nst u8 *mac) @@ -389,7 +710,6 @@ static int rswitch_switchdev_event(struct notifier_bloc= k *nb, struct switchdev_notifier_fdb_info *fdb_info; struct switchdev_notifier_info *info =3D ptr; struct rswitch_private *priv; - int err; =20 priv =3D container_of(nb, struct rswitch_private, rswitch_switchdev_nb); =20 @@ -424,16 +744,6 @@ static int rswitch_switchdev_event(struct notifier_blo= ck *nb, queue_work(system_long_wq, &switchdev_work->work); =20 break; - case SWITCHDEV_PORT_ATTR_SET: - err =3D 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); } =20 return NOTIFY_DONE; --=20 2.43.0