From nobody Wed Apr 1 20:42:57 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EEC4A43D4EC; Wed, 1 Apr 2026 13:45:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775051112; cv=none; b=L2VZOd0Z7gFrdB9FFX0NS6ElIm5//DV62RMi87Jdb+o0HHKi61uy8zIPxP4+wUPr1TngTJB0iVYPx5rOkCnQlKv1qK9/lZk5/RDXCXeJHG3kKabL5D8/xMTBwY08HY6prmYX8lkP14HyHvf8KVwaB4y5a8WtFGJx42bIK3MnOO4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775051112; c=relaxed/simple; bh=JEkpcUu5tmbXpB+CAQqzBj/teBni9I2iyT4pxd/il8k=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=ZRFVRO7TjwcK1Vfkcq00KLd73CV/NtNtWlOmcTf7mReTXzeq4xOlvHaG6kluX0CPp+69DRD0D/JqmoJncDlz8yDLNxDeDPSQ1Mi8gmkr3wPpJJKGxgsHASAnq0zfrM4hM+s4vGThI+JVdhunL5649p34muMnVF4y6i2KIZ79i3s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1w7viL-000000001en-0Oze; Wed, 01 Apr 2026 13:34:33 +0000 Date: Wed, 1 Apr 2026 14:34:30 +0100 From: Daniel Golle To: Daniel Golle , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , David Yang , Simon Horman , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frank Wunderlich , Chad Monroe , Cezary Wilmanski , Liang Xu , "Benny (Ying-Tsan) Weng" , Jose Maria Verdu Munoz , Avinash Jayaraman , John Crispin Subject: [PATCH net-next v9 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h Message-ID: <4f8bbfce3e4e3a02064fc4dc366263136c6e0383.1775049897.git.daniel@makrotopia.org> References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The yt921x driver contains a helper to create a bitmap of ports which are members of a bridge. Move the helper as static inline function into dsa.h, so other driver can make use of it as well. Signed-off-by: Daniel Golle --- v9: no changes v8: no changes v7: new patch --- drivers/net/dsa/yt921x.c | 13 ------------- include/net/dsa.h | 13 +++++++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c index 904613f4694ad..5b66109ecc235 100644 --- a/drivers/net/dsa/yt921x.c +++ b/drivers/net/dsa/yt921x.c @@ -2154,19 +2154,6 @@ yt921x_bridge_join(struct yt921x_priv *priv, int por= t, u16 ports_mask) return 0; } =20 -static u32 -dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev) -{ - struct dsa_port *dp; - u32 mask =3D 0; - - dsa_switch_for_each_user_port(dp, ds) - if (dsa_port_offloads_bridge_dev(dp, bdev)) - mask |=3D BIT(dp->index); - - return mask; -} - static int yt921x_bridge_flags(struct yt921x_priv *priv, int port, struct switchdev_brport_flags flags) diff --git a/include/net/dsa.h b/include/net/dsa.h index 6c17446f3dcc2..e93b4feaca966 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -831,6 +831,19 @@ dsa_tree_offloads_bridge_dev(struct dsa_switch_tree *d= st, return false; } =20 +static inline u32 +dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev) +{ + struct dsa_port *dp; + u32 mask =3D 0; + + dsa_switch_for_each_user_port(dp, ds) + if (dsa_port_offloads_bridge_dev(dp, bdev)) + mask |=3D BIT(dp->index); + + return mask; +} + static inline bool dsa_port_tree_same(const struct dsa_port *a, const struct dsa_port *b) { --=20 2.53.0 From nobody Wed Apr 1 20:42:57 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9308D223328; Wed, 1 Apr 2026 13:34:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775050491; cv=none; b=GtgDjPhK5f0eAmnXZHWrlpx9nUDSgU5sHhlW3pOS3b8vO4k8ldYV3zpWC0dPeRdiImdiN9XQI2WEfPoI0ZbUrZy6i3ZQWbv+7S7ZQvxV8Fi4tHSIArhLqwoEdzsR12tPlxdzVzaS7Lwqs6RLPVRuD3Lca4h+27YG26Eiz1/s5bg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775050491; c=relaxed/simple; bh=w2vx123z0kiho2g6MGL9WtrhSk5cuYR6E2U8kW/NcVA=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=W2ZlsMpWikVjD0h6nBoCEdfyjjxMOvumrR6jJGap0pAlbvGHSMXPIXKoVdMJgoTtuOWwsSILI8O1ndFo0WSomHmlDyGRZIw3stkNrZPpej54xTScudryvoJXhzHUFsC90VHFBn8/ej07LWd8ZlM9+/CZazUuFqqDYy2N8AR23Uk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1w7viX-000000001gF-2CpV; Wed, 01 Apr 2026 13:34:45 +0000 Date: Wed, 1 Apr 2026 14:34:42 +0100 From: Daniel Golle To: Daniel Golle , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , David Yang , Simon Horman , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frank Wunderlich , Chad Monroe , Cezary Wilmanski , Liang Xu , "Benny (Ying-Tsan) Weng" , Jose Maria Verdu Munoz , Avinash Jayaraman , John Crispin Subject: [PATCH net-next v9 2/4] net: dsa: add bridge member iteration macro Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Drivers that offload bridges need to iterate over the ports that are members of a given bridge, for example to rebuild per-port forwarding bitmaps when membership changes. Currently drivers typically open-code this by combining dsa_switch_for_each_user_port() with a dsa_port_offloads_bridge_dev() check, or cache bridge membership within the driver. Add dsa_switch_for_each_bridge_member() macro to express this pattern directly, and use it for the existing dsa_bridge_ports() inline helper. Signed-off-by: Daniel Golle --- v9: no changes v8: no changes v7: reuse existing helper v6: new patch --- include/net/dsa.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/net/dsa.h b/include/net/dsa.h index e93b4feaca966..8b6d34e8a6f0d 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -831,15 +831,18 @@ dsa_tree_offloads_bridge_dev(struct dsa_switch_tree *= dst, return false; } =20 +#define dsa_switch_for_each_bridge_member(_dp, _ds, _bdev) \ + dsa_switch_for_each_user_port(_dp, _ds) \ + if (dsa_port_offloads_bridge_dev(_dp, _bdev)) + static inline u32 dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev) { struct dsa_port *dp; u32 mask =3D 0; =20 - dsa_switch_for_each_user_port(dp, ds) - if (dsa_port_offloads_bridge_dev(dp, bdev)) - mask |=3D BIT(dp->index); + dsa_switch_for_each_bridge_member(dp, ds, bdev) + mask |=3D BIT(dp->index); =20 return mask; } --=20 2.53.0 From nobody Wed Apr 1 20:42:57 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DBB1D223328; Wed, 1 Apr 2026 13:34:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775050502; cv=none; b=quTrFWQQtDHrB3JczfmJKF00G0jPJDLZgYLZ55TZ1dXm/oZFTdOcB9MntH2Yf2Lf1dqo4UemnSdA33Gvx3z4h15tz9GqSukijIAD/yO9q6f1rZHRah3qrdNKN4dQgFxya+E/Vyh0KZjb70QxKkfLqXEGyNst2mnjGegUw4M0HaM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775050502; c=relaxed/simple; bh=H3Gw14nM510sXuAGsvRumnG/EGCrXZ7NnSy7MfOQXSY=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=tQYQp6xSGlN+XJgNYf3TtL+7LE9Q7Egcu8d4hqI2sbvBH0JPNiW2LfIfdYQnoafobUldVn3uQe1WFh+uu2GxTMm8n9C6wiZ4mWbh5b4fKDGTxtbO6Fa0jrNxoomDs0ZyjCu8Pzai+fJ50Inuv35NXx3wXDVWV9PpY64928LG8cY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1w7vih-000000001gd-44IX; Wed, 01 Apr 2026 13:34:56 +0000 Date: Wed, 1 Apr 2026 14:34:52 +0100 From: Daniel Golle To: Daniel Golle , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , David Yang , Simon Horman , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frank Wunderlich , Chad Monroe , Cezary Wilmanski , Liang Xu , "Benny (Ying-Tsan) Weng" , Jose Maria Verdu Munoz , Avinash Jayaraman , John Crispin Subject: [PATCH net-next v9 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark() Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The MxL862xx offloads bridge forwarding in hardware, so set dsa_default_offload_fwd_mark() to avoid duplicate forwarding of packets of (eg. flooded) frames arriving at the CPU port. Link-local frames are directly trapped to the CPU port only, so don't set dsa_default_offload_fwd_mark() on those. Signed-off-by: Daniel Golle --- v9: no changes v8: no changes v7: no changes v6: no changes v5: move link-local check before dsa_strip_etype_header() v4: no changes v3: no changes v2: don't set dsa_default_offload_fwd_mark() on link-local frames --- net/dsa/tag_mxl862xx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c index 01f2158682718..8daefeb8d49df 100644 --- a/net/dsa/tag_mxl862xx.c +++ b/net/dsa/tag_mxl862xx.c @@ -86,6 +86,9 @@ static struct sk_buff *mxl862_tag_rcv(struct sk_buff *skb, return NULL; } =20 + if (likely(!is_link_local_ether_addr(eth_hdr(skb)->h_dest))) + dsa_default_offload_fwd_mark(skb); + /* remove the MxL862xx special tag between the MAC addresses and the * current ethertype field. */ --=20 2.53.0 From nobody Wed Apr 1 20:42:57 2026 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 19C46426D0B; Wed, 1 Apr 2026 13:35:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.142.180.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775050513; cv=none; b=K+Fup3VwbUjEfqF6MU247WBbH+zDA+O+XCVKOs6refiK5f9bQX9hczdbtRUxWXVPBISv1tZriLyn7bouc5mHwSYVTgnu+80FlZLcwRKLF4Vl6FGVGZd1Ee33M/0FpqFBQSe2IZP6yfhYu3JIQLVyId5O1jJ9WmgaPCW7ZqmSURA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775050513; c=relaxed/simple; bh=kkqaXNCDD7bLubzWR/g8DtpI+fBycA3uyRJx/n2jDtA=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=uI/s4xusWGUZfzj6gvPhMqrVAljS7ohQiZ0AICk+MWKqfJVoHmoKwbIge1fAVIepbr/kKEdtP9SUIGejMUZUFTjsyqe3j+H8vzS/AzF4MYmUpHmVf0I6IjMZfFWR1ErWqI09G8/jhdmcybwGiEBxj2l3xwZJG3CC1jEYA71+5jQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org; spf=pass smtp.mailfrom=makrotopia.org; arc=none smtp.client-ip=185.142.180.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=makrotopia.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=makrotopia.org Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.99) (envelope-from ) id 1w7viq-000000001hD-251e; Wed, 01 Apr 2026 13:35:04 +0000 Date: Wed, 1 Apr 2026 14:35:01 +0100 From: Daniel Golle To: Daniel Golle , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , David Yang , Simon Horman , Russell King , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frank Wunderlich , Chad Monroe , Cezary Wilmanski , Liang Xu , "Benny (Ying-Tsan) Weng" , Jose Maria Verdu Munoz , Avinash Jayaraman , John Crispin Subject: [PATCH net-next v9 4/4] net: dsa: mxl862xx: implement bridge offloading Message-ID: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Implement joining and leaving bridges as well as add, delete and dump operations on isolated FDBs, port MDB membership management, and setting a port's STP state. The switch supports a maximum of 63 bridges, however, up to 12 may be used as "single-port bridges" to isolate standalone ports. Allowing up to 48 bridges to be offloaded seems more than enough on that hardware, hence that is set as max_num_bridges. A total of 128 bridge ports are supported in the bridge portmap, and virtual bridge ports have to be used eg. for link-aggregation, hence potentially exceeding the number of hardware ports. The firmware-assigned bridge identifier (FID) for each offloaded bridge is stored in an array used to map DSA bridge num to firmware bridge ID, avoiding the need for a driver-private bridge tracking structure. Bridge member portmaps are rebuilt on join/leave using dsa_switch_for_each_bridge_member(). As there are now more users of the BRIDGEPORT_CONFIG_SET API and the state of each port is cached locally, introduce a helper function mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which applies the cached per-port state to hardware. For standalone user ports (dp->bridge =3D=3D NULL), it additionally resets the port to single-port bridge state: CPU-only portmap, learning and flooding disabled. The CPU port path sets its state explicitly before calling this helper and is therefore not affected by the reset. Note that MASK_VLAN_BASED_MAC_LEARNING is intentionally absent from the firmware write mask. After mxl862xx_reset(), the firmware initialises all VLAN-based MAC learning fields to 0 (disabled), so SVL is the active mode by default without having to set it explicitly. Note that there is no convenient way to control flooding on per-port level, so the driver is using a 0-rate QoS meter setup as a stopper in lack of any better option. In order to be perfect the firmware-enforced minimum bucket size is bypassed by directly writing 0s to the relevant registers -- without that at least one 64-byte packet could still pass before the meter would change from 'yellow' into 'red' state. Signed-off-by: Daniel Golle --- v9: * get rid of cached portmap, DSA bridge membership should be the only source of truth * always apply host-flood setting to single-port bridge (do not loose state) * cancel any pending host_flood_work in probe error path * fix missing p->learning reset when reverting port to standalone on bridge leave * remove mxl862xx_add_single_port_bridge(); fold bridge FID allocation and mxl862xx_bridge_config_fwd() call into port_setup() directly, and move standalone port state reset (portmap, learning, flood_block) into mxl862xx_set_bridge_port() for the !dp->bridge user-port path * refactor mxl862xx_allocate_bridge() to return the allocated FID directly, with a negative value indicating error * document in commit message that MASK_VLAN_BASED_MAC_LEARNING is intentionally absent as SVL is the firmware's reset default * add comment noting that a FID leaked on port_setup() failure is tolerable as the port transitions to unused and the pool accounts for this v8: * add missing fields to kernel-doc for struct mxl862xx_priv * const'ify some function parameters v7: * use simple array to track firmware bridge to dsa bridge mapping * use prototype of existing dsa_bridge_ports() helper moved from yt921x.c to dsa.h which takes struct net_device * instead of struct dsa_bridge * parameter. * use new dsa_switch_for_each_bridge_member helper * fix bridge allocation resource leak in case of mxl862xx_sync_bridge_members() failing for the first port to join a bridge * zero-initialize struct mxl862xx_cfg in mxl862xx_set_ageing_time * set initial=3D1 to always reset cursor at start when interating over FDB in .fdb_dump * hack zero-rate meter to truly always block * use asynchronous worker for port_set_host_flood which runs from atomic context and hence cannot sleep * reorder function to minimize diffstat of planned follow-up commits v6: * eliminate struct mxl862xx_bridge and driver-private bridge list, store firmware bridge FID in new dsa_bridge->priv instead * rework sync_bridge_members() to use dsa_bridge_for_each_member() instead of for_each_set_bit() on driver-private portmap * rework port_bridge_join/leave to use dsa_bridge.priv for first- member detection and dsa_bridge_ports() for empty-bridge check * derive active FID from dp->bridge->priv in set_bridge_port() * simplify allocate_bridge()/free_bridge() to take struct dsa_bridge pointer directly, drop kzalloc/kfree/list management * simplify get_fid() to read db.bridge.priv directly * remove mxl862xx_find_bridge() * remove unnecessary default bridge config call in setup() v5: * introduce port_map helpers * properly implement port_mdb_{add,del} operations v4: * add missing cpu_to_le32 in mxl862xx_bridge_config_fwd() * use little-endian 32-bit type for (unused) age_timer API field * better comment in port_set_host_flood() documenting architectural limitation * fix typo in comment "matche" should be "matches" * few whitespace fixes v3: * refactor .port_bridge_join and .port_bridge_leave as requested by Vladimir Oltean * include linux/etherdevice.h which was missing and causing build to fail (it accidentally slipped into a follow-up patch) * remove left-over manual reset of learning state for port leaving bridge * remove unnecessary call to mxl862xx_port_fast_age() for port leaving bridge * add kernel-doc comments in mxl862xx.h instead of sporadic inline comments covering only some of the struct members * some other minor cosmetics (linebreaks, whitespace) here and there v2: * fix kernel-doc comments in API header * use bitfield helpers for compound tci field in fdb API * add missing endian conversion for mxl862xx_stp_port_cfg.port_state as well as mxl862xx_mac_table_read.tci (spotted by AI review) * drop manually resetting port learning state on bridge<->standalone transitions, DSA framework takes care of that * don't abort updating bridge ports on error, return error at the end * report error in mxl862xx_port_bridge_leave() * create mxl862xx_get_fid() helper and use it in mxl862xx_port_fdb_add() and mxl862xx_port_fdb_del() * propagete error of callback function in mxl862xx_port_fdb_dump() * manually mxl862xx_port_fast_age() in mxl862xx_port_stp_state_set() to avoid FDB poisoning due to race condition --- drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++- drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +- drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++-- drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++ 4 files changed, 1022 insertions(+), 58 deletions(-) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl8= 62xx/mxl862xx-api.h index a9f599dbca25b..8677763544d78 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h @@ -3,6 +3,7 @@ #ifndef __MXL862XX_API_H #define __MXL862XX_API_H =20 +#include #include =20 /** @@ -34,6 +35,168 @@ struct mxl862xx_register_mod { __le16 mask; } __packed; =20 +/** + * enum mxl862xx_mac_table_filter - Source/Destination MAC address filteri= ng + * + * @MXL862XX_MAC_FILTER_NONE: no filter + * @MXL862XX_MAC_FILTER_SRC: source address filter + * @MXL862XX_MAC_FILTER_DEST: destination address filter + * @MXL862XX_MAC_FILTER_BOTH: both source and destination filter + */ +enum mxl862xx_mac_table_filter { + MXL862XX_MAC_FILTER_NONE =3D 0, + MXL862XX_MAC_FILTER_SRC =3D BIT(0), + MXL862XX_MAC_FILTER_DEST =3D BIT(1), + MXL862XX_MAC_FILTER_BOTH =3D BIT(0) | BIT(1), +}; + +#define MXL862XX_TCI_VLAN_ID GENMASK(11, 0) +#define MXL862XX_TCI_VLAN_CFI_DEI BIT(12) +#define MXL862XX_TCI_VLAN_PRI GENMASK(15, 13) + +/* Set in port_id to use port_map[] as a portmap bitmap instead of a single + * port ID. When clear, port_id selects one port; when set, the firmware + * ignores the lower bits of port_id and writes port_map[] directly into + * the PCE bridge port map. + */ +#define MXL862XX_PORTMAP_FLAG BIT(31) + +/** + * struct mxl862xx_mac_table_add - MAC Table Entry to be added + * @fid: Filtering Identifier (FID) (not supported by all switches) + * @port_id: Ethernet Port number + * @port_map: Bridge Port Map + * @sub_if_id: Sub-Interface Identifier Destination + * @age_timer: Aging Time in seconds + * @vlan_id: STAG VLAN Id + * @static_entry: Static Entry (value will be aged out if not set to stati= c) + * @traffic_class: Egress queue traffic class + * @mac: MAC Address to add to the table + * @filter_flag: See &enum mxl862xx_mac_table_filter + * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC + * address matches MAC in this entry + * @associated_mac: Associated Mac address + * @tci: TCI for B-Step + * Bit [0:11] - VLAN ID + * Bit [12] - VLAN CFI/DEI + * Bit [13:15] - VLAN PRI + */ +struct mxl862xx_mac_table_add { + __le16 fid; + __le32 port_id; + __le16 port_map[8]; + __le16 sub_if_id; + __le32 age_timer; + __le16 vlan_id; + u8 static_entry; + u8 traffic_class; + u8 mac[ETH_ALEN]; + u8 filter_flag; + u8 igmp_controlled; + u8 associated_mac[ETH_ALEN]; + __le16 tci; +} __packed; + +/** + * struct mxl862xx_mac_table_remove - MAC Table Entry to be removed + * @fid: Filtering Identifier (FID) + * @mac: MAC Address to be removed from the table. + * @filter_flag: See &enum mxl862xx_mac_table_filter + * @tci: TCI for B-Step + * Bit [0:11] - VLAN ID + * Bit [12] - VLAN CFI/DEI + * Bit [13:15] - VLAN PRI + */ +struct mxl862xx_mac_table_remove { + __le16 fid; + u8 mac[ETH_ALEN]; + u8 filter_flag; + __le16 tci; +} __packed; + +/** + * struct mxl862xx_mac_table_read - MAC Table Entry to be read + * @initial: Restart the get operation from the beginning of the table + * @last: Indicates that the read operation returned last entry + * @fid: Get the MAC table entry belonging to the given Filtering Identifi= er + * @port_id: The Bridge Port ID + * @port_map: Bridge Port Map + * @age_timer: Aging Time + * @vlan_id: STAG VLAN Id + * @static_entry: Indicates if this is a Static Entry + * @sub_if_id: Sub-Interface Identifier Destination + * @mac: MAC Address. Filled out by the switch API implementation. + * @filter_flag: See &enum mxl862xx_mac_table_filter + * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC + * address matches the MAC in this entry + * @entry_changed: Indicate if the Entry has Changed + * @associated_mac: Associated MAC address + * @hit_status: MAC Table Hit Status Update + * @tci: TCI for B-Step + * Bit [0:11] - VLAN ID + * Bit [12] - VLAN CFI/DEI + * Bit [13:15] - VLAN PRI + * @first_bridge_port_id: The port this MAC address has first been learned. + * This is used for loop detection. + */ +struct mxl862xx_mac_table_read { + u8 initial; + u8 last; + __le16 fid; + __le32 port_id; + __le16 port_map[8]; + __le32 age_timer; + __le16 vlan_id; + u8 static_entry; + __le16 sub_if_id; + u8 mac[ETH_ALEN]; + u8 filter_flag; + u8 igmp_controlled; + u8 entry_changed; + u8 associated_mac[ETH_ALEN]; + u8 hit_status; + __le16 tci; + __le16 first_bridge_port_id; +} __packed; + +/** + * struct mxl862xx_mac_table_query - MAC Table Entry key-based lookup + * @mac: MAC Address to search for (input) + * @fid: Filtering Identifier (input) + * @found: Set by firmware: 1 if entry was found, 0 if not + * @port_id: Bridge Port ID (output; MSB set if portmap mode) + * @port_map: Bridge Port Map (output; valid for static entries) + * @sub_if_id: Sub-Interface Identifier Destination + * @age_timer: Aging Time + * @vlan_id: STAG VLAN Id + * @static_entry: Indicates if this is a Static Entry + * @filter_flag: See &enum mxl862xx_mac_table_filter (input+output) + * @igmp_controlled: IGMP controlled flag + * @entry_changed: Entry changed flag + * @associated_mac: Associated MAC address + * @hit_status: MAC Table Hit Status Update + * @tci: TCI (VLAN ID + CFI/DEI + PRI) (input) + * @first_bridge_port_id: First learned bridge port + */ +struct mxl862xx_mac_table_query { + u8 mac[ETH_ALEN]; + __le16 fid; + u8 found; + __le32 port_id; + __le16 port_map[8]; + __le16 sub_if_id; + __le32 age_timer; + __le16 vlan_id; + u8 static_entry; + u8 filter_flag; + u8 igmp_controlled; + u8 entry_changed; + u8 associated_mac[ETH_ALEN]; + u8 hit_status; + __le16 tci; + __le16 first_bridge_port_id; +} __packed; + /** * enum mxl862xx_mac_clear_type - MAC table clear type * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id @@ -138,6 +301,40 @@ enum mxl862xx_bridge_port_egress_meter { MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX, }; =20 +/** + * struct mxl862xx_qos_meter_cfg - Rate meter configuration + * @enable: Enable/disable meter + * @meter_id: Meter ID (assigned by firmware on alloc) + * @meter_name: Meter name string + * @meter_type: Meter algorithm type (srTCM =3D 0, trTCM =3D 1) + * @cbs: Committed Burst Size (in bytes) + * @res1: Reserved + * @ebs: Excess Burst Size (in bytes) + * @res2: Reserved + * @rate: Committed Information Rate (in kbit/s) + * @pi_rate: Peak Information Rate (in kbit/s) + * @colour_blind_mode: Colour-blind mode enable + * @pkt_mode: Packet mode enable + * @local_overhd: Local overhead accounting enable + * @local_overhd_val: Local overhead accounting value + */ +struct mxl862xx_qos_meter_cfg { + u8 enable; + __le16 meter_id; + char meter_name[32]; + __le32 meter_type; + __le32 cbs; + __le32 res1; + __le32 ebs; + __le32 res2; + __le32 rate; + __le32 pi_rate; + u8 colour_blind_mode; + u8 pkt_mode; + u8 local_overhd; + __le16 local_overhd_val; +} __packed; + /** * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of @@ -456,7 +653,7 @@ struct mxl862xx_pmapper { */ struct mxl862xx_bridge_port_config { __le16 bridge_port_id; - __le32 mask; /* enum mxl862xx_bridge_port_config_mask */ + __le32 mask; /* enum mxl862xx_bridge_port_config_mask */ __le16 bridge_id; u8 ingress_extended_vlan_enable; __le16 ingress_extended_vlan_block_id; @@ -658,6 +855,32 @@ struct mxl862xx_ctp_port_assignment { __le16 bridge_port_id; } __packed; =20 +/** + * enum mxl862xx_stp_port_state - Spanning Tree Protocol port states + * @MXL862XX_STP_PORT_STATE_FORWARD: Forwarding state + * @MXL862XX_STP_PORT_STATE_DISABLE: Disabled/Discarding state + * @MXL862XX_STP_PORT_STATE_LEARNING: Learning state + * @MXL862XX_STP_PORT_STATE_BLOCKING: Blocking/Listening + */ +enum mxl862xx_stp_port_state { + MXL862XX_STP_PORT_STATE_FORWARD =3D 0, + MXL862XX_STP_PORT_STATE_DISABLE, + MXL862XX_STP_PORT_STATE_LEARNING, + MXL862XX_STP_PORT_STATE_BLOCKING, +}; + +/** + * struct mxl862xx_stp_port_cfg - Configures the Spanning Tree Protocol st= ate + * @port_id: Port number + * @fid: Filtering Identifier (FID) + * @port_state: See &enum mxl862xx_stp_port_state + */ +struct mxl862xx_stp_port_cfg { + __le16 port_id; + __le16 fid; + __le32 port_state; /* enum mxl862xx_stp_port_state */ +} __packed; + /** * struct mxl862xx_sys_fw_image_version - Firmware version information * @iv_major: firmware major version diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl8= 62xx/mxl862xx-cmd.h index f6852ade64e7c..9f6c5bf9fdf21 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h @@ -15,12 +15,15 @@ #define MXL862XX_BRDG_MAGIC 0x300 #define MXL862XX_BRDGPORT_MAGIC 0x400 #define MXL862XX_CTP_MAGIC 0x500 +#define MXL862XX_QOS_MAGIC 0x600 #define MXL862XX_SWMAC_MAGIC 0xa00 +#define MXL862XX_STP_MAGIC 0xf00 #define MXL862XX_SS_MAGIC 0x1600 #define GPY_GPY2XX_MAGIC 0x1800 #define SYS_MISC_MAGIC 0x1900 =20 #define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9) +#define MXL862XX_COMMON_CFGSET (MXL862XX_COMMON_MAGIC + 0xa) #define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11) =20 #define MXL862XX_BRIDGE_ALLOC (MXL862XX_BRDG_MAGIC + 0x1) @@ -35,14 +38,23 @@ =20 #define MXL862XX_CTP_PORTASSIGNMENTSET (MXL862XX_CTP_MAGIC + 0x3) =20 +#define MXL862XX_QOS_METERCFGSET (MXL862XX_QOS_MAGIC + 0x2) +#define MXL862XX_QOS_METERALLOC (MXL862XX_QOS_MAGIC + 0x2a) + +#define MXL862XX_MAC_TABLEENTRYADD (MXL862XX_SWMAC_MAGIC + 0x2) +#define MXL862XX_MAC_TABLEENTRYREAD (MXL862XX_SWMAC_MAGIC + 0x3) +#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4) +#define MXL862XX_MAC_TABLEENTRYREMOVE (MXL862XX_SWMAC_MAGIC + 0x5) #define MXL862XX_MAC_TABLECLEARCOND (MXL862XX_SWMAC_MAGIC + 0x8) =20 -#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x02) +#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x2) + +#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2) =20 -#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x01) -#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x02) +#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1) +#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2) =20 -#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x02) +#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2) =20 #define MMD_API_MAXIMUM_ID 0x7fff =20 diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx= /mxl862xx.c index af9b00b2d2c43..e4e16c7207630 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -7,8 +7,11 @@ * Copyright (C) 2025 Daniel Golle */ =20 -#include +#include #include +#include +#include +#include #include #include #include @@ -36,6 +39,17 @@ #define MXL862XX_READY_TIMEOUT_MS 10000 #define MXL862XX_READY_POLL_MS 100 =20 +#define MXL862XX_TCM_INST_SEL 0xe00 +#define MXL862XX_TCM_CBS 0xe12 +#define MXL862XX_TCM_EBS 0xe13 + +static const int mxl862xx_flood_meters[] =3D { + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP, + MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST, +}; + static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *= ds, int port, enum dsa_tag_protocol m) @@ -168,6 +182,199 @@ static int mxl862xx_setup_mdio(struct dsa_switch *ds) return ret; } =20 +static int mxl862xx_bridge_config_fwd(struct dsa_switch *ds, u16 bridge_id, + bool ucast_flood, bool mcast_flood, + bool bcast_flood) +{ + struct mxl862xx_bridge_config bridge_config =3D {}; + struct mxl862xx_priv *priv =3D ds->priv; + int ret; + + bridge_config.mask =3D cpu_to_le32(MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING= _MODE); + bridge_config.bridge_id =3D cpu_to_le16(bridge_id); + + bridge_config.forward_unknown_unicast =3D cpu_to_le32(ucast_flood ? + MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD); + + bridge_config.forward_unknown_multicast_ip =3D cpu_to_le32(mcast_flood ? + MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD); + bridge_config.forward_unknown_multicast_non_ip =3D + bridge_config.forward_unknown_multicast_ip; + + bridge_config.forward_broadcast =3D cpu_to_le32(bcast_flood ? + MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD); + + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_CONFIGSET, bridge_config= ); + if (ret) + dev_err(ds->dev, "failed to configure bridge %u forwarding: %d\n", + bridge_id, ret); + + return ret; +} + +/* Allocate a single zero-rate meter shared by all ports and flood types. + * All flood-blocking egress sub-meters point to this one meter so that any + * packet hitting this meter is unconditionally dropped. + * + * The firmware API requires CBS >=3D 64 (its bs2ls encoder clamps smaller + * values), so the meter is initially configured with CBS=3DEBS=3D64. + * A zero-rate bucket starts full at CBS bytes, which would let one packet + * through before the bucket empties. To eliminate this one-packet leak we + * override CBS and EBS to zero via direct register writes after the API c= all; + * the hardware accepts CBS=3D0 and immediately flags the bucket as exceed= ed, + * so no traffic can ever pass. + */ +static int mxl862xx_setup_drop_meter(struct dsa_switch *ds) +{ + struct mxl862xx_qos_meter_cfg meter =3D {}; + struct mxl862xx_priv *priv =3D ds->priv; + struct mxl862xx_register_mod reg; + int ret; + + /* meter_id=3D0 means auto-alloc */ + ret =3D MXL862XX_API_READ(priv, MXL862XX_QOS_METERALLOC, meter); + if (ret) + return ret; + + meter.enable =3D true; + meter.cbs =3D cpu_to_le32(64); + meter.ebs =3D cpu_to_le32(64); + snprintf(meter.meter_name, sizeof(meter.meter_name), "drop"); + + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_QOS_METERCFGSET, meter); + if (ret) + return ret; + + priv->drop_meter =3D le16_to_cpu(meter.meter_id); + + /* Select the meter instance for subsequent TCM register access. */ + reg.addr =3D cpu_to_le16(MXL862XX_TCM_INST_SEL); + reg.data =3D cpu_to_le16(priv->drop_meter); + reg.mask =3D cpu_to_le16(0xffff); + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg); + if (ret) + return ret; + + /* Zero CBS so the committed bucket starts empty (exceeded). */ + reg.addr =3D cpu_to_le16(MXL862XX_TCM_CBS); + reg.data =3D 0; + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg); + if (ret) + return ret; + + /* Zero EBS so the excess bucket starts empty (exceeded). */ + reg.addr =3D cpu_to_le16(MXL862XX_TCM_EBS); + return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg); +} + +static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) +{ + struct mxl862xx_bridge_port_config br_port_cfg =3D {}; + struct dsa_port *dp =3D dsa_to_port(ds, port); + struct mxl862xx_priv *priv =3D ds->priv; + struct mxl862xx_port *p =3D &priv->ports[port]; + struct dsa_port *member_dp; + u16 bridge_id; + bool enable; + int i, idx; + + if (!p->setup_done) + return 0; + + if (dsa_port_is_cpu(dp)) { + dsa_switch_for_each_user_port(member_dp, ds) { + if (member_dp->cpu_dp->index !=3D port) + continue; + mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map, + member_dp->index); + } + } else if (dp->bridge) { + dsa_switch_for_each_bridge_member(member_dp, ds, + dp->bridge->dev) { + if (member_dp->index =3D=3D port) + continue; + mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map, + member_dp->index); + } + mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map, + dp->cpu_dp->index); + } else { + mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map, + dp->cpu_dp->index); + p->flood_block =3D 0; + p->learning =3D false; + } + + bridge_id =3D dp->bridge ? priv->bridges[dp->bridge->num] : p->fid; + + br_port_cfg.bridge_port_id =3D cpu_to_le16(port); + br_port_cfg.bridge_id =3D cpu_to_le16(bridge_id); + br_port_cfg.mask =3D cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_= ID | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER); + br_port_cfg.src_mac_learning_disable =3D !p->learning; + + for (i =3D 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) { + idx =3D mxl862xx_flood_meters[i]; + enable =3D !!(p->flood_block & BIT(idx)); + + br_port_cfg.egress_traffic_sub_meter_id[idx] =3D + enable ? cpu_to_le16(priv->drop_meter) : 0; + br_port_cfg.egress_sub_metering_enable[idx] =3D enable; + } + + return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, + br_port_cfg); +} + +static int mxl862xx_sync_bridge_members(struct dsa_switch *ds, + const struct dsa_bridge *bridge) +{ + struct dsa_port *dp; + int ret =3D 0, err; + + dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) { + err =3D mxl862xx_set_bridge_port(ds, dp->index); + if (err) + ret =3D err; + } + + return ret; +} + +static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv) +{ + struct mxl862xx_bridge_alloc br_alloc =3D {}; + int ret; + + ret =3D MXL862XX_API_READ(priv, MXL862XX_BRIDGE_ALLOC, br_alloc); + if (ret) + return ret; + + return le16_to_cpu(br_alloc.bridge_id); +} + +static void mxl862xx_free_bridge(struct dsa_switch *ds, + const struct dsa_bridge *bridge) +{ + struct mxl862xx_priv *priv =3D ds->priv; + u16 fw_id =3D priv->bridges[bridge->num]; + struct mxl862xx_bridge_alloc br_alloc =3D { + .bridge_id =3D cpu_to_le16(fw_id), + }; + int ret; + + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_FREE, br_alloc); + if (ret) { + dev_err(ds->dev, "failed to free fw bridge %u: %pe\n", + fw_id, ERR_PTR(ret)); + return; + } + + priv->bridges[bridge->num] =3D 0; +} + static int mxl862xx_setup(struct dsa_switch *ds) { struct mxl862xx_priv *priv =3D ds->priv; @@ -181,6 +388,10 @@ static int mxl862xx_setup(struct dsa_switch *ds) if (ret) return ret; =20 + ret =3D mxl862xx_setup_drop_meter(ds); + if (ret) + return ret; + return mxl862xx_setup_mdio(ds); } =20 @@ -260,99 +471,137 @@ static int mxl862xx_configure_sp_tag_proto(struct ds= a_switch *ds, int port, =20 static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port) { - struct mxl862xx_bridge_port_config br_port_cfg =3D {}; struct mxl862xx_priv *priv =3D ds->priv; - u16 bridge_port_map =3D 0; - struct dsa_port *dp; =20 - /* CPU port bridge setup */ - br_port_cfg.mask =3D cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_= PORT_MAP | - MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | - MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); + priv->ports[port].fid =3D MXL862XX_DEFAULT_BRIDGE; + priv->ports[port].learning =3D true; =20 - br_port_cfg.bridge_port_id =3D cpu_to_le16(port); - br_port_cfg.src_mac_learning_disable =3D false; - br_port_cfg.vlan_src_mac_vid_enable =3D true; - br_port_cfg.vlan_dst_mac_vid_enable =3D true; + return mxl862xx_set_bridge_port(ds, port); +} + +static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port, + const struct dsa_bridge bridge, + bool *tx_fwd_offload, + struct netlink_ext_ack *extack) +{ + struct mxl862xx_priv *priv =3D ds->priv; + int ret; + + if (!priv->bridges[bridge.num]) { + ret =3D mxl862xx_allocate_bridge(priv); + if (ret < 0) + return ret; + + priv->bridges[bridge.num] =3D ret; =20 - /* include all assigned user ports in the CPU portmap */ - dsa_switch_for_each_user_port(dp, ds) { - /* it's safe to rely on cpu_dp being valid for user ports */ - if (dp->cpu_dp->index !=3D port) - continue; + /* Free bridge here on error, DSA rollback won't. */ + ret =3D mxl862xx_sync_bridge_members(ds, &bridge); + if (ret) { + mxl862xx_free_bridge(ds, &bridge); + return ret; + } =20 - bridge_port_map |=3D BIT(dp->index); + return 0; } - br_port_cfg.bridge_port_map[0] |=3D cpu_to_le16(bridge_port_map); =20 - return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cf= g); + return mxl862xx_sync_bridge_members(ds, &bridge); } =20 -static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port) +static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port, + const struct dsa_bridge bridge) { - struct mxl862xx_bridge_port_config br_port_cfg =3D {}; - struct dsa_port *dp =3D dsa_to_port(ds, port); - struct mxl862xx_bridge_alloc br_alloc =3D {}; - int ret; + int err; =20 - ret =3D MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc); - if (ret) { - dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port); - return ret; - } + err =3D mxl862xx_sync_bridge_members(ds, &bridge); + if (err) + dev_err(ds->dev, + "failed to sync bridge members after port %d left: %pe\n", + port, ERR_PTR(err)); =20 - br_port_cfg.bridge_id =3D br_alloc.bridge_id; - br_port_cfg.bridge_port_id =3D cpu_to_le16(port); - br_port_cfg.mask =3D cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_= ID | - MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | - MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | - MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); - br_port_cfg.src_mac_learning_disable =3D true; - br_port_cfg.vlan_src_mac_vid_enable =3D false; - br_port_cfg.vlan_dst_mac_vid_enable =3D false; - /* As this function is only called for user ports it is safe to rely on - * cpu_dp being valid + /* Revert leaving port, omitted by the sync above, to its + * single-port bridge */ - br_port_cfg.bridge_port_map[0] =3D cpu_to_le16(BIT(dp->cpu_dp->index)); + err =3D mxl862xx_set_bridge_port(ds, port); + if (err) + dev_err(ds->dev, + "failed to update bridge port %d state: %pe\n", port, + ERR_PTR(err)); =20 - return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_por= t_cfg); + if (!dsa_bridge_ports(ds, bridge.dev)) + mxl862xx_free_bridge(ds, &bridge); } =20 static int mxl862xx_port_setup(struct dsa_switch *ds, int port) { + struct mxl862xx_priv *priv =3D ds->priv; struct dsa_port *dp =3D dsa_to_port(ds, port); bool is_cpu_port =3D dsa_port_is_cpu(dp); int ret; =20 - /* disable port and flush MAC entries */ ret =3D mxl862xx_port_state(ds, port, false); if (ret) return ret; =20 mxl862xx_port_fast_age(ds, port); =20 - /* skip setup for unused and DSA ports */ if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp)) return 0; =20 - /* configure tag protocol */ ret =3D mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port); if (ret) return ret; =20 - /* assign CTP port IDs */ ret =3D mxl862xx_configure_ctp_port(ds, port, port, is_cpu_port ? 32 - port : 1); if (ret) return ret; =20 if (is_cpu_port) - /* assign user ports to CPU port bridge */ return mxl862xx_setup_cpu_bridge(ds, port); =20 - /* setup single-port bridge for user ports */ - return mxl862xx_add_single_port_bridge(ds, port); + /* setup single-port bridge for user ports. + * If this fails, the FID is leaked -- but the port then transitions + * to unused, and the FID pool is sized to tolerate this. + */ + ret =3D mxl862xx_allocate_bridge(priv); + if (ret < 0) { + dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port); + return ret; + } + priv->ports[port].fid =3D ret; + /* Standalone ports should not flood unknown unicast or multicast + * towards the CPU by default; only broadcast is needed initially. + */ + ret =3D mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid, + false, false, true); + if (ret) + return ret; + ret =3D mxl862xx_set_bridge_port(ds, port); + if (ret) + return ret; + + priv->ports[port].setup_done =3D true; + + return 0; +} + +static void mxl862xx_port_teardown(struct dsa_switch *ds, int port) +{ + struct mxl862xx_priv *priv =3D ds->priv; + struct dsa_port *dp =3D dsa_to_port(ds, port); + + if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp)) + return; + + /* Prevent deferred host_flood_work from acting on stale state. + * The flag is checked under rtnl_lock() by the worker; since + * teardown also runs under RTNL, this is race-free. + * + * HW EVLAN/VF blocks are not freed here -- the firmware receives + * a full reset on the next probe, which reclaims all resources. + */ + priv->ports[port].setup_done =3D false; } =20 static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port, @@ -365,14 +614,371 @@ static void mxl862xx_phylink_get_caps(struct dsa_swi= tch *ds, int port, config->supported_interfaces); } =20 +static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db) +{ + struct mxl862xx_priv *priv =3D ds->priv; + + switch (db.type) { + case DSA_DB_PORT: + return priv->ports[db.dp->index].fid; + + case DSA_DB_BRIDGE: + if (!priv->bridges[db.bridge.num]) + return -ENOENT; + return priv->bridges[db.bridge.num]; + + default: + return -EOPNOTSUPP; + } +} + +static int mxl862xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, struct dsa_db db) +{ + struct mxl862xx_mac_table_add param =3D {}; + int fid =3D mxl862xx_get_fid(ds, db), ret; + struct mxl862xx_priv *priv =3D ds->priv; + + if (fid < 0) + return fid; + + param.port_id =3D cpu_to_le32(port); + param.static_entry =3D true; + param.fid =3D cpu_to_le16(fid); + param.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid)); + ether_addr_copy(param.mac, addr); + + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, param); + if (ret) + dev_err(ds->dev, "failed to add FDB entry on port %d\n", port); + + return ret; +} + +static int mxl862xx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, const struct dsa_db db) +{ + struct mxl862xx_mac_table_remove param =3D {}; + int fid =3D mxl862xx_get_fid(ds, db), ret; + struct mxl862xx_priv *priv =3D ds->priv; + + if (fid < 0) + return fid; + + param.fid =3D cpu_to_le16(fid); + param.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid)); + ether_addr_copy(param.mac, addr); + + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param); + if (ret) + dev_err(ds->dev, "failed to remove FDB entry on port %d\n", port); + + return ret; +} + +static int mxl862xx_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct mxl862xx_mac_table_read param =3D { .initial =3D 1 }; + struct mxl862xx_priv *priv =3D ds->priv; + u32 entry_port_id; + int ret; + + while (true) { + ret =3D MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYREAD, param); + if (ret) + return ret; + + if (param.last) + break; + + entry_port_id =3D le32_to_cpu(param.port_id); + + if (entry_port_id =3D=3D port) { + ret =3D cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID, + le16_to_cpu(param.tci)), + param.static_entry, data); + if (ret) + return ret; + } + + memset(¶m, 0, sizeof(param)); + } + + return 0; +} + +static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + const struct dsa_db db) +{ + struct mxl862xx_mac_table_query qparam =3D {}; + struct mxl862xx_mac_table_add aparam =3D {}; + struct mxl862xx_priv *priv =3D ds->priv; + int fid, ret; + + fid =3D mxl862xx_get_fid(ds, db); + if (fid < 0) + return fid; + + ether_addr_copy(qparam.mac, mdb->addr); + qparam.fid =3D cpu_to_le16(fid); + qparam.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid)); + + ret =3D MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam); + if (ret) + return ret; + + /* Build the ADD command using portmap mode */ + ether_addr_copy(aparam.mac, mdb->addr); + aparam.fid =3D cpu_to_le16(fid); + aparam.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid)); + aparam.static_entry =3D true; + aparam.port_id =3D cpu_to_le32(MXL862XX_PORTMAP_FLAG); + + if (qparam.found) + memcpy(aparam.port_map, qparam.port_map, + sizeof(aparam.port_map)); + + mxl862xx_fw_portmap_set_bit(aparam.port_map, port); + + return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam); +} + +static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + const struct dsa_db db) +{ + struct mxl862xx_mac_table_remove rparam =3D {}; + struct mxl862xx_mac_table_query qparam =3D {}; + struct mxl862xx_mac_table_add aparam =3D {}; + int fid =3D mxl862xx_get_fid(ds, db), ret; + struct mxl862xx_priv *priv =3D ds->priv; + + if (fid < 0) + return fid; + + qparam.fid =3D cpu_to_le16(fid); + qparam.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid)); + ether_addr_copy(qparam.mac, mdb->addr); + + ret =3D MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam); + if (ret) + return ret; + + if (!qparam.found) + return 0; + + mxl862xx_fw_portmap_clear_bit(qparam.port_map, port); + + if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) { + rparam.fid =3D cpu_to_le16(fid); + rparam.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid)); + ether_addr_copy(rparam.mac, mdb->addr); + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, rparam); + } else { + /* Write back with reduced portmap */ + aparam.fid =3D cpu_to_le16(fid); + aparam.tci =3D cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid)); + ether_addr_copy(aparam.mac, mdb->addr); + aparam.static_entry =3D true; + aparam.port_id =3D cpu_to_le32(MXL862XX_PORTMAP_FLAG); + memcpy(aparam.port_map, qparam.port_map, sizeof(aparam.port_map)); + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam); + } + + return ret; +} + +static int mxl862xx_set_ageing_time(struct dsa_switch *ds, unsigned int ms= ecs) +{ + struct mxl862xx_cfg param =3D {}; + int ret; + + ret =3D MXL862XX_API_READ(ds->priv, MXL862XX_COMMON_CFGGET, param); + if (ret) { + dev_err(ds->dev, "failed to read switch config\n"); + return ret; + } + + param.mac_table_age_timer =3D cpu_to_le32(MXL862XX_AGETIMER_CUSTOM); + param.age_timer =3D cpu_to_le32(msecs / 1000); + ret =3D MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_CFGSET, param); + if (ret) + dev_err(ds->dev, "failed to set ageing\n"); + + return ret; +} + +static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port, + u8 state) +{ + struct mxl862xx_stp_port_cfg param =3D { + .port_id =3D cpu_to_le16(port), + }; + struct mxl862xx_priv *priv =3D ds->priv; + int ret; + + switch (state) { + case BR_STATE_DISABLED: + param.port_state =3D cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE); + break; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + param.port_state =3D cpu_to_le32(MXL862XX_STP_PORT_STATE_BLOCKING); + break; + case BR_STATE_LEARNING: + param.port_state =3D cpu_to_le32(MXL862XX_STP_PORT_STATE_LEARNING); + break; + case BR_STATE_FORWARDING: + param.port_state =3D cpu_to_le32(MXL862XX_STP_PORT_STATE_FORWARD); + break; + default: + dev_err(ds->dev, "invalid STP state: %d\n", state); + return; + } + + ret =3D MXL862XX_API_WRITE(priv, MXL862XX_STP_PORTCFGSET, param); + if (ret) { + dev_err(ds->dev, "failed to set STP state on port %d\n", port); + return; + } + + /* The firmware may re-enable MAC learning as a side-effect of entering + * LEARNING or FORWARDING state (per 802.1D defaults). + * Re-apply the driver's intended learning and metering config so that + * standalone ports keep learning disabled. + */ + ret =3D mxl862xx_set_bridge_port(ds, port); + if (ret) + dev_err(ds->dev, "failed to reapply brport flags on port %d\n", + port); + + mxl862xx_port_fast_age(ds, port); +} + +/* Deferred work handler for host flood configuration. + * + * port_set_host_flood is called from atomic context (under + * netif_addr_lock), so firmware calls must be deferred. The worker + * acquires rtnl_lock() to serialize with DSA callbacks that access the + * same driver state. + */ +static void mxl862xx_host_flood_work_fn(struct work_struct *work) +{ + struct mxl862xx_port *p =3D container_of(work, struct mxl862xx_port, + host_flood_work); + struct mxl862xx_priv *priv =3D p->priv; + struct dsa_switch *ds =3D priv->ds; + + rtnl_lock(); + + /* Port may have been torn down between scheduling and now. */ + if (!p->setup_done) { + rtnl_unlock(); + return; + } + + /* Always write to the standalone FID. When standalone it takes effect + * immediately; when bridged the port uses the shared bridge FID so the + * write is a no-op for current forwarding, but the state is preserved + * in hardware and is ready once the port returns to standalone. + */ + mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc, + p->host_flood_mc, true); + + rtnl_unlock(); +} + +static void mxl862xx_port_set_host_flood(struct dsa_switch *ds, int port, + bool uc, bool mc) +{ + struct mxl862xx_priv *priv =3D ds->priv; + struct mxl862xx_port *p =3D &priv->ports[port]; + + p->host_flood_uc =3D uc; + p->host_flood_mc =3D mc; + schedule_work(&p->host_flood_work); +} + +static int mxl862xx_port_pre_bridge_flags(struct dsa_switch *ds, int port, + const struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD | + BR_LEARNING)) + return -EINVAL; + + return 0; +} + +static int mxl862xx_port_bridge_flags(struct dsa_switch *ds, int port, + const struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct mxl862xx_priv *priv =3D ds->priv; + unsigned long old_block =3D priv->ports[port].flood_block; + unsigned long block =3D old_block; + int ret; + + if (flags.mask & BR_FLOOD) { + if (flags.val & BR_FLOOD) + block &=3D ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC); + else + block |=3D BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC); + } + + if (flags.mask & BR_MCAST_FLOOD) { + if (flags.val & BR_MCAST_FLOOD) { + block &=3D ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP); + block &=3D ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP); + } else { + block |=3D BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP); + block |=3D BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP); + } + } + + if (flags.mask & BR_BCAST_FLOOD) { + if (flags.val & BR_BCAST_FLOOD) + block &=3D ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST); + else + block |=3D BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST); + } + + if (flags.mask & BR_LEARNING) + priv->ports[port].learning =3D !!(flags.val & BR_LEARNING); + + if (block !=3D old_block || (flags.mask & BR_LEARNING)) { + priv->ports[port].flood_block =3D block; + ret =3D mxl862xx_set_bridge_port(ds, port); + if (ret) + return ret; + } + + return 0; +} + static const struct dsa_switch_ops mxl862xx_switch_ops =3D { .get_tag_protocol =3D mxl862xx_get_tag_protocol, .setup =3D mxl862xx_setup, .port_setup =3D mxl862xx_port_setup, + .port_teardown =3D mxl862xx_port_teardown, .phylink_get_caps =3D mxl862xx_phylink_get_caps, .port_enable =3D mxl862xx_port_enable, .port_disable =3D mxl862xx_port_disable, .port_fast_age =3D mxl862xx_port_fast_age, + .set_ageing_time =3D mxl862xx_set_ageing_time, + .port_bridge_join =3D mxl862xx_port_bridge_join, + .port_bridge_leave =3D mxl862xx_port_bridge_leave, + .port_pre_bridge_flags =3D mxl862xx_port_pre_bridge_flags, + .port_bridge_flags =3D mxl862xx_port_bridge_flags, + .port_stp_state_set =3D mxl862xx_port_stp_state_set, + .port_set_host_flood =3D mxl862xx_port_set_host_flood, + .port_fdb_add =3D mxl862xx_port_fdb_add, + .port_fdb_del =3D mxl862xx_port_fdb_del, + .port_fdb_dump =3D mxl862xx_port_fdb_dump, + .port_mdb_add =3D mxl862xx_port_mdb_add, + .port_mdb_del =3D mxl862xx_port_mdb_del, }; =20 static void mxl862xx_phylink_mac_config(struct phylink_config *config, @@ -407,7 +1013,7 @@ static int mxl862xx_probe(struct mdio_device *mdiodev) struct device *dev =3D &mdiodev->dev; struct mxl862xx_priv *priv; struct dsa_switch *ds; - int err; + int err, i; =20 priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -425,14 +1031,25 @@ static int mxl862xx_probe(struct mdio_device *mdiode= v) ds->ops =3D &mxl862xx_switch_ops; ds->phylink_mac_ops =3D &mxl862xx_phylink_mac_ops; ds->num_ports =3D MXL862XX_MAX_PORTS; + ds->fdb_isolation =3D true; + ds->max_num_bridges =3D MXL862XX_MAX_BRIDGES; + mxl862xx_host_init(priv); =20 + for (i =3D 0; i < MXL862XX_MAX_PORTS; i++) { + priv->ports[i].priv =3D priv; + INIT_WORK(&priv->ports[i].host_flood_work, + mxl862xx_host_flood_work_fn); + } + dev_set_drvdata(dev, ds); =20 err =3D dsa_register_switch(ds); - if (err) + if (err) { mxl862xx_host_shutdown(priv); - + for (i =3D 0; i < MXL862XX_MAX_PORTS; i++) + cancel_work_sync(&priv->ports[i].host_flood_work); + } return err; } =20 @@ -440,6 +1057,7 @@ static void mxl862xx_remove(struct mdio_device *mdiode= v) { struct dsa_switch *ds =3D dev_get_drvdata(&mdiodev->dev); struct mxl862xx_priv *priv; + int i; =20 if (!ds) return; @@ -449,12 +1067,21 @@ static void mxl862xx_remove(struct mdio_device *mdio= dev) dsa_unregister_switch(ds); =20 mxl862xx_host_shutdown(priv); + + /* Cancel any pending host flood work. dsa_unregister_switch() + * has already called port_teardown (which sets setup_done=3Dfalse), + * but a worker could still be blocked on rtnl_lock(). Since we + * are now outside RTNL, cancel_work_sync() will not deadlock. + */ + for (i =3D 0; i < MXL862XX_MAX_PORTS; i++) + cancel_work_sync(&priv->ports[i].host_flood_work); } =20 static void mxl862xx_shutdown(struct mdio_device *mdiodev) { struct dsa_switch *ds =3D dev_get_drvdata(&mdiodev->dev); struct mxl862xx_priv *priv; + int i; =20 if (!ds) return; @@ -465,6 +1092,9 @@ static void mxl862xx_shutdown(struct mdio_device *mdio= dev) =20 mxl862xx_host_shutdown(priv); =20 + for (i =3D 0; i < MXL862XX_MAX_PORTS; i++) + cancel_work_sync(&priv->ports[i].host_flood_work); + dev_set_drvdata(&mdiodev->dev, NULL); } =20 diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx= /mxl862xx.h index 3ca0d386f3e8f..8d03dd24faeeb 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h @@ -4,15 +4,114 @@ #define __MXL862XX_H =20 #include +#include #include =20 +struct mxl862xx_priv; + #define MXL862XX_MAX_PORTS 17 +#define MXL862XX_DEFAULT_BRIDGE 0 +#define MXL862XX_MAX_BRIDGES 48 +#define MXL862XX_MAX_BRIDGE_PORTS 128 + +/* Number of __le16 words in a firmware portmap (128-bit bitmap). */ +#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16) + +/** + * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portm= ap + * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries) + * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1) + */ +static inline void mxl862xx_fw_portmap_set_bit(__le16 *map, int port) +{ + map[port / 16] |=3D cpu_to_le16(BIT(port % 16)); +} + +/** + * mxl862xx_fw_portmap_clear_bit - clear a single port bit in a firmware p= ortmap + * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries) + * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1) + */ +static inline void mxl862xx_fw_portmap_clear_bit(__le16 *map, int port) +{ + map[port / 16] &=3D ~cpu_to_le16(BIT(port % 16)); +} + +/** + * mxl862xx_fw_portmap_is_empty - check whether a firmware portmap has no + * bits set + * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries) + * + * Return: true if every word in @map is zero. + */ +static inline bool mxl862xx_fw_portmap_is_empty(const __le16 *map) +{ + int i; + + for (i =3D 0; i < MXL862XX_FW_PORTMAP_WORDS; i++) + if (map[i]) + return false; + return true; +} + +/** + * struct mxl862xx_port - per-port state tracked by the driver + * @priv: back-pointer to switch private data; needed by + * deferred work handlers to access ds and priv + * @fid: firmware FID for the permanent single-port bridge; + * kept alive for the lifetime of the port so traffi= c is + * never forwarded while the port is unbridged + * @flood_block: bitmask of firmware meter indices that are curren= tly + * rate-limiting flood traffic on this port (zero-ra= te + * meters used to block flooding) + * @learning: true when address learning is enabled on this port + * @setup_done: set at end of port_setup, cleared at start of + * port_teardown; guards deferred work against + * acting on torn-down state + * @host_flood_uc: desired host unicast flood state (true =3D flood); + * updated atomically by port_set_host_flood, consum= ed + * by the deferred host_flood_work + * @host_flood_mc: desired host multicast flood state (true =3D floo= d) + * @host_flood_work: deferred work for applying host flood changes; + * port_set_host_flood runs in atomic context (under + * netif_addr_lock) so firmware calls must be deferr= ed. + * The worker acquires rtnl_lock() to serialize with + * DSA callbacks and checks @setup_done to avoid + * acting on torn-down ports. + */ +struct mxl862xx_port { + struct mxl862xx_priv *priv; + u16 fid; + unsigned long flood_block; + bool learning; + bool setup_done; + bool host_flood_uc; + bool host_flood_mc; + struct work_struct host_flood_work; +}; =20 +/** + * struct mxl862xx_priv - driver private data for an MxL862xx switch + * @ds: pointer to the DSA switch instance + * @mdiodev: MDIO device used to communicate with the switch firmware + * @crc_err_work: deferred work for shutting down all ports on MDIO CRC e= rrors + * @crc_err: set atomically before CRC-triggered shutdown, cleared a= fter + * @drop_meter: index of the single shared zero-rate firmware meter used + * to unconditionally drop traffic (used to block flooding) + * @ports: per-port state, indexed by switch port number + * @bridges: maps DSA bridge number to firmware bridge ID; + * zero means no firmware bridge allocated for that + * DSA bridge number. Indexed by dsa_bridge.num + * (0 .. ds->max_num_bridges). + */ struct mxl862xx_priv { struct dsa_switch *ds; struct mdio_device *mdiodev; struct work_struct crc_err_work; unsigned long crc_err; + u16 drop_meter; + struct mxl862xx_port ports[MXL862XX_MAX_PORTS]; + u16 bridges[MXL862XX_MAX_BRIDGES + 1]; }; =20 #endif /* __MXL862XX_H */ --=20 2.53.0