xref: /openbmc/linux/net/bridge/br_switchdev.c (revision 47211192)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
26bc506b4SIdo Schimmel #include <linux/kernel.h>
36bc506b4SIdo Schimmel #include <linux/list.h>
46bc506b4SIdo Schimmel #include <linux/netdevice.h>
56bc506b4SIdo Schimmel #include <linux/rtnetlink.h>
66bc506b4SIdo Schimmel #include <linux/skbuff.h>
76bc506b4SIdo Schimmel #include <net/switchdev.h>
86bc506b4SIdo Schimmel 
96bc506b4SIdo Schimmel #include "br_private.h"
106bc506b4SIdo Schimmel 
11*47211192STobias Waldekranz static struct static_key_false br_switchdev_tx_fwd_offload;
12*47211192STobias Waldekranz 
13*47211192STobias Waldekranz static bool nbp_switchdev_can_offload_tx_fwd(const struct net_bridge_port *p,
14*47211192STobias Waldekranz 					     const struct sk_buff *skb)
15*47211192STobias Waldekranz {
16*47211192STobias Waldekranz 	if (!static_branch_unlikely(&br_switchdev_tx_fwd_offload))
17*47211192STobias Waldekranz 		return false;
18*47211192STobias Waldekranz 
19*47211192STobias Waldekranz 	return (p->flags & BR_TX_FWD_OFFLOAD) &&
20*47211192STobias Waldekranz 	       (p->hwdom != BR_INPUT_SKB_CB(skb)->src_hwdom);
21*47211192STobias Waldekranz }
22*47211192STobias Waldekranz 
23*47211192STobias Waldekranz bool br_switchdev_frame_uses_tx_fwd_offload(struct sk_buff *skb)
24*47211192STobias Waldekranz {
25*47211192STobias Waldekranz 	if (!static_branch_unlikely(&br_switchdev_tx_fwd_offload))
26*47211192STobias Waldekranz 		return false;
27*47211192STobias Waldekranz 
28*47211192STobias Waldekranz 	return BR_INPUT_SKB_CB(skb)->tx_fwd_offload;
29*47211192STobias Waldekranz }
30*47211192STobias Waldekranz 
31*47211192STobias Waldekranz /* Mark the frame for TX forwarding offload if this egress port supports it */
32*47211192STobias Waldekranz void nbp_switchdev_frame_mark_tx_fwd_offload(const struct net_bridge_port *p,
33*47211192STobias Waldekranz 					     struct sk_buff *skb)
34*47211192STobias Waldekranz {
35*47211192STobias Waldekranz 	if (nbp_switchdev_can_offload_tx_fwd(p, skb))
36*47211192STobias Waldekranz 		BR_INPUT_SKB_CB(skb)->tx_fwd_offload = true;
37*47211192STobias Waldekranz }
38*47211192STobias Waldekranz 
39*47211192STobias Waldekranz /* Lazily adds the hwdom of the egress bridge port to the bit mask of hwdoms
40*47211192STobias Waldekranz  * that the skb has been already forwarded to, to avoid further cloning to
41*47211192STobias Waldekranz  * other ports in the same hwdom by making nbp_switchdev_allowed_egress()
42*47211192STobias Waldekranz  * return false.
43*47211192STobias Waldekranz  */
44*47211192STobias Waldekranz void nbp_switchdev_frame_mark_tx_fwd_to_hwdom(const struct net_bridge_port *p,
45*47211192STobias Waldekranz 					      struct sk_buff *skb)
46*47211192STobias Waldekranz {
47*47211192STobias Waldekranz 	if (nbp_switchdev_can_offload_tx_fwd(p, skb))
48*47211192STobias Waldekranz 		set_bit(p->hwdom, &BR_INPUT_SKB_CB(skb)->fwd_hwdoms);
49*47211192STobias Waldekranz }
50*47211192STobias Waldekranz 
516bc506b4SIdo Schimmel void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
526bc506b4SIdo Schimmel 			      struct sk_buff *skb)
536bc506b4SIdo Schimmel {
54f7cf972fSTobias Waldekranz 	if (p->hwdom)
55f7cf972fSTobias Waldekranz 		BR_INPUT_SKB_CB(skb)->src_hwdom = p->hwdom;
566bc506b4SIdo Schimmel }
576bc506b4SIdo Schimmel 
586bc506b4SIdo Schimmel bool nbp_switchdev_allowed_egress(const struct net_bridge_port *p,
596bc506b4SIdo Schimmel 				  const struct sk_buff *skb)
606bc506b4SIdo Schimmel {
61*47211192STobias Waldekranz 	struct br_input_skb_cb *cb = BR_INPUT_SKB_CB(skb);
62*47211192STobias Waldekranz 
63*47211192STobias Waldekranz 	return !test_bit(p->hwdom, &cb->fwd_hwdoms) &&
64*47211192STobias Waldekranz 		(!skb->offload_fwd_mark || cb->src_hwdom != p->hwdom);
656bc506b4SIdo Schimmel }
663922285dSArkadi Sharshevsky 
673922285dSArkadi Sharshevsky /* Flags that can be offloaded to hardware */
683922285dSArkadi Sharshevsky #define BR_PORT_FLAGS_HW_OFFLOAD (BR_LEARNING | BR_FLOOD | \
693922285dSArkadi Sharshevsky 				  BR_MCAST_FLOOD | BR_BCAST_FLOOD)
703922285dSArkadi Sharshevsky 
713922285dSArkadi Sharshevsky int br_switchdev_set_port_flag(struct net_bridge_port *p,
723922285dSArkadi Sharshevsky 			       unsigned long flags,
73078bbb85SVladimir Oltean 			       unsigned long mask,
74078bbb85SVladimir Oltean 			       struct netlink_ext_ack *extack)
753922285dSArkadi Sharshevsky {
763922285dSArkadi Sharshevsky 	struct switchdev_attr attr = {
773922285dSArkadi Sharshevsky 		.orig_dev = p->dev,
783922285dSArkadi Sharshevsky 	};
79d45224d6SFlorian Fainelli 	struct switchdev_notifier_port_attr_info info = {
80d45224d6SFlorian Fainelli 		.attr = &attr,
81d45224d6SFlorian Fainelli 	};
823922285dSArkadi Sharshevsky 	int err;
833922285dSArkadi Sharshevsky 
84304ae3bfSVladimir Oltean 	mask &= BR_PORT_FLAGS_HW_OFFLOAD;
85304ae3bfSVladimir Oltean 	if (!mask)
863922285dSArkadi Sharshevsky 		return 0;
873922285dSArkadi Sharshevsky 
88e18f4c18SVladimir Oltean 	attr.id = SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS;
89e18f4c18SVladimir Oltean 	attr.u.brport_flags.val = flags;
90e18f4c18SVladimir Oltean 	attr.u.brport_flags.mask = mask;
91304ae3bfSVladimir Oltean 
92d45224d6SFlorian Fainelli 	/* We run from atomic context here */
93d45224d6SFlorian Fainelli 	err = call_switchdev_notifiers(SWITCHDEV_PORT_ATTR_SET, p->dev,
94078bbb85SVladimir Oltean 				       &info.info, extack);
95d45224d6SFlorian Fainelli 	err = notifier_to_errno(err);
963922285dSArkadi Sharshevsky 	if (err == -EOPNOTSUPP)
973922285dSArkadi Sharshevsky 		return 0;
983922285dSArkadi Sharshevsky 
991ef07644SFlorian Fainelli 	if (err) {
100078bbb85SVladimir Oltean 		if (extack && !extack->_msg)
101078bbb85SVladimir Oltean 			NL_SET_ERR_MSG_MOD(extack,
102078bbb85SVladimir Oltean 					   "bridge flag offload is not supported");
1033922285dSArkadi Sharshevsky 		return -EOPNOTSUPP;
1043922285dSArkadi Sharshevsky 	}
1053922285dSArkadi Sharshevsky 
1063922285dSArkadi Sharshevsky 	attr.id = SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS;
1073922285dSArkadi Sharshevsky 	attr.flags = SWITCHDEV_F_DEFER;
1081ef07644SFlorian Fainelli 
109dcbdf135SVladimir Oltean 	err = switchdev_port_attr_set(p->dev, &attr, extack);
1103922285dSArkadi Sharshevsky 	if (err) {
111dcbdf135SVladimir Oltean 		if (extack && !extack->_msg)
112dcbdf135SVladimir Oltean 			NL_SET_ERR_MSG_MOD(extack,
113dcbdf135SVladimir Oltean 					   "error setting offload flag on port");
1143922285dSArkadi Sharshevsky 		return err;
1153922285dSArkadi Sharshevsky 	}
1163922285dSArkadi Sharshevsky 
1173922285dSArkadi Sharshevsky 	return 0;
1183922285dSArkadi Sharshevsky }
1196b26b51bSArkadi Sharshevsky 
1206b26b51bSArkadi Sharshevsky void
1216eb38bf8STobias Waldekranz br_switchdev_fdb_notify(struct net_bridge *br,
1226eb38bf8STobias Waldekranz 			const struct net_bridge_fdb_entry *fdb, int type)
1236b26b51bSArkadi Sharshevsky {
1243e19ae7cSVladimir Oltean 	const struct net_bridge_port *dst = READ_ONCE(fdb->dst);
1256eb38bf8STobias Waldekranz 	struct net_device *dev = dst ? dst->dev : br->dev;
126e5b4b898STobias Waldekranz 	struct switchdev_notifier_fdb_info info = {
127e5b4b898STobias Waldekranz 		.addr = fdb->key.addr.addr,
128e5b4b898STobias Waldekranz 		.vid = fdb->key.vlan_id,
129e5b4b898STobias Waldekranz 		.added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags),
1302c4eca3eSVladimir Oltean 		.is_local = test_bit(BR_FDB_LOCAL, &fdb->flags),
131e5b4b898STobias Waldekranz 		.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
132e5b4b898STobias Waldekranz 	};
133e5b4b898STobias Waldekranz 
1346b26b51bSArkadi Sharshevsky 	switch (type) {
1356b26b51bSArkadi Sharshevsky 	case RTM_DELNEIGH:
136e5b4b898STobias Waldekranz 		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
1376eb38bf8STobias Waldekranz 					 dev, &info.info, NULL);
1386b26b51bSArkadi Sharshevsky 		break;
1396b26b51bSArkadi Sharshevsky 	case RTM_NEWNEIGH:
140e5b4b898STobias Waldekranz 		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
1416eb38bf8STobias Waldekranz 					 dev, &info.info, NULL);
1426b26b51bSArkadi Sharshevsky 		break;
1436b26b51bSArkadi Sharshevsky 	}
1446b26b51bSArkadi Sharshevsky }
145d66e4348SPetr Machata 
146169327d5SPetr Machata int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
147169327d5SPetr Machata 			       struct netlink_ext_ack *extack)
148d66e4348SPetr Machata {
149d66e4348SPetr Machata 	struct switchdev_obj_port_vlan v = {
150d66e4348SPetr Machata 		.obj.orig_dev = dev,
151d66e4348SPetr Machata 		.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
152d66e4348SPetr Machata 		.flags = flags,
153b7a9e0daSVladimir Oltean 		.vid = vid,
154d66e4348SPetr Machata 	};
155d66e4348SPetr Machata 
15669b7320eSPetr Machata 	return switchdev_port_obj_add(dev, &v.obj, extack);
157d66e4348SPetr Machata }
158d66e4348SPetr Machata 
159d66e4348SPetr Machata int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid)
160d66e4348SPetr Machata {
161d66e4348SPetr Machata 	struct switchdev_obj_port_vlan v = {
162d66e4348SPetr Machata 		.obj.orig_dev = dev,
163d66e4348SPetr Machata 		.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
164b7a9e0daSVladimir Oltean 		.vid = vid,
165d66e4348SPetr Machata 	};
166d66e4348SPetr Machata 
167d66e4348SPetr Machata 	return switchdev_port_obj_del(dev, &v.obj);
168d66e4348SPetr Machata }
16985826610STobias Waldekranz 
17085826610STobias Waldekranz static int nbp_switchdev_hwdom_set(struct net_bridge_port *joining)
17185826610STobias Waldekranz {
17285826610STobias Waldekranz 	struct net_bridge *br = joining->br;
17385826610STobias Waldekranz 	struct net_bridge_port *p;
17485826610STobias Waldekranz 	int hwdom;
17585826610STobias Waldekranz 
17685826610STobias Waldekranz 	/* joining is yet to be added to the port list. */
17785826610STobias Waldekranz 	list_for_each_entry(p, &br->port_list, list) {
1782f5dc00fSVladimir Oltean 		if (netdev_phys_item_id_same(&joining->ppid, &p->ppid)) {
17985826610STobias Waldekranz 			joining->hwdom = p->hwdom;
18085826610STobias Waldekranz 			return 0;
18185826610STobias Waldekranz 		}
18285826610STobias Waldekranz 	}
18385826610STobias Waldekranz 
18485826610STobias Waldekranz 	hwdom = find_next_zero_bit(&br->busy_hwdoms, BR_HWDOM_MAX, 1);
18585826610STobias Waldekranz 	if (hwdom >= BR_HWDOM_MAX)
18685826610STobias Waldekranz 		return -EBUSY;
18785826610STobias Waldekranz 
18885826610STobias Waldekranz 	set_bit(hwdom, &br->busy_hwdoms);
18985826610STobias Waldekranz 	joining->hwdom = hwdom;
19085826610STobias Waldekranz 	return 0;
19185826610STobias Waldekranz }
19285826610STobias Waldekranz 
19385826610STobias Waldekranz static void nbp_switchdev_hwdom_put(struct net_bridge_port *leaving)
19485826610STobias Waldekranz {
19585826610STobias Waldekranz 	struct net_bridge *br = leaving->br;
19685826610STobias Waldekranz 	struct net_bridge_port *p;
19785826610STobias Waldekranz 
19885826610STobias Waldekranz 	/* leaving is no longer in the port list. */
19985826610STobias Waldekranz 	list_for_each_entry(p, &br->port_list, list) {
20085826610STobias Waldekranz 		if (p->hwdom == leaving->hwdom)
20185826610STobias Waldekranz 			return;
20285826610STobias Waldekranz 	}
20385826610STobias Waldekranz 
20485826610STobias Waldekranz 	clear_bit(leaving->hwdom, &br->busy_hwdoms);
20585826610STobias Waldekranz }
20685826610STobias Waldekranz 
2072f5dc00fSVladimir Oltean static int nbp_switchdev_add(struct net_bridge_port *p,
2082f5dc00fSVladimir Oltean 			     struct netdev_phys_item_id ppid,
209*47211192STobias Waldekranz 			     bool tx_fwd_offload,
2102f5dc00fSVladimir Oltean 			     struct netlink_ext_ack *extack)
21185826610STobias Waldekranz {
212*47211192STobias Waldekranz 	int err;
213*47211192STobias Waldekranz 
2142f5dc00fSVladimir Oltean 	if (p->offload_count) {
2152f5dc00fSVladimir Oltean 		/* Prevent unsupported configurations such as a bridge port
2162f5dc00fSVladimir Oltean 		 * which is a bonding interface, and the member ports are from
2172f5dc00fSVladimir Oltean 		 * different hardware switches.
2182f5dc00fSVladimir Oltean 		 */
2192f5dc00fSVladimir Oltean 		if (!netdev_phys_item_id_same(&p->ppid, &ppid)) {
2202f5dc00fSVladimir Oltean 			NL_SET_ERR_MSG_MOD(extack,
2212f5dc00fSVladimir Oltean 					   "Same bridge port cannot be offloaded by two physical switches");
2222f5dc00fSVladimir Oltean 			return -EBUSY;
22385826610STobias Waldekranz 		}
22485826610STobias Waldekranz 
2252f5dc00fSVladimir Oltean 		/* Tolerate drivers that call switchdev_bridge_port_offload()
2262f5dc00fSVladimir Oltean 		 * more than once for the same bridge port, such as when the
2272f5dc00fSVladimir Oltean 		 * bridge port is an offloaded bonding/team interface.
2282f5dc00fSVladimir Oltean 		 */
2292f5dc00fSVladimir Oltean 		p->offload_count++;
2302f5dc00fSVladimir Oltean 
2312f5dc00fSVladimir Oltean 		return 0;
2322f5dc00fSVladimir Oltean 	}
2332f5dc00fSVladimir Oltean 
2342f5dc00fSVladimir Oltean 	p->ppid = ppid;
2352f5dc00fSVladimir Oltean 	p->offload_count = 1;
2362f5dc00fSVladimir Oltean 
237*47211192STobias Waldekranz 	err = nbp_switchdev_hwdom_set(p);
238*47211192STobias Waldekranz 	if (err)
239*47211192STobias Waldekranz 		return err;
240*47211192STobias Waldekranz 
241*47211192STobias Waldekranz 	if (tx_fwd_offload) {
242*47211192STobias Waldekranz 		p->flags |= BR_TX_FWD_OFFLOAD;
243*47211192STobias Waldekranz 		static_branch_inc(&br_switchdev_tx_fwd_offload);
244*47211192STobias Waldekranz 	}
245*47211192STobias Waldekranz 
246*47211192STobias Waldekranz 	return 0;
24785826610STobias Waldekranz }
24885826610STobias Waldekranz 
2492f5dc00fSVladimir Oltean static void nbp_switchdev_del(struct net_bridge_port *p)
25085826610STobias Waldekranz {
2512f5dc00fSVladimir Oltean 	if (WARN_ON(!p->offload_count))
2522f5dc00fSVladimir Oltean 		return;
2532f5dc00fSVladimir Oltean 
2542f5dc00fSVladimir Oltean 	p->offload_count--;
2552f5dc00fSVladimir Oltean 
2562f5dc00fSVladimir Oltean 	if (p->offload_count)
2572f5dc00fSVladimir Oltean 		return;
25885826610STobias Waldekranz 
25985826610STobias Waldekranz 	if (p->hwdom)
26085826610STobias Waldekranz 		nbp_switchdev_hwdom_put(p);
261*47211192STobias Waldekranz 
262*47211192STobias Waldekranz 	if (p->flags & BR_TX_FWD_OFFLOAD) {
263*47211192STobias Waldekranz 		p->flags &= ~BR_TX_FWD_OFFLOAD;
264*47211192STobias Waldekranz 		static_branch_dec(&br_switchdev_tx_fwd_offload);
265*47211192STobias Waldekranz 	}
26685826610STobias Waldekranz }
2672f5dc00fSVladimir Oltean 
2684e51bf44SVladimir Oltean static int nbp_switchdev_sync_objs(struct net_bridge_port *p, const void *ctx,
2694e51bf44SVladimir Oltean 				   struct notifier_block *atomic_nb,
2704e51bf44SVladimir Oltean 				   struct notifier_block *blocking_nb,
2714e51bf44SVladimir Oltean 				   struct netlink_ext_ack *extack)
2724e51bf44SVladimir Oltean {
2734e51bf44SVladimir Oltean 	struct net_device *br_dev = p->br->dev;
2744e51bf44SVladimir Oltean 	struct net_device *dev = p->dev;
2754e51bf44SVladimir Oltean 	int err;
2764e51bf44SVladimir Oltean 
2774e51bf44SVladimir Oltean 	err = br_vlan_replay(br_dev, dev, ctx, true, blocking_nb, extack);
2784e51bf44SVladimir Oltean 	if (err && err != -EOPNOTSUPP)
2794e51bf44SVladimir Oltean 		return err;
2804e51bf44SVladimir Oltean 
2814e51bf44SVladimir Oltean 	err = br_mdb_replay(br_dev, dev, ctx, true, blocking_nb, extack);
2824e51bf44SVladimir Oltean 	if (err && err != -EOPNOTSUPP)
2834e51bf44SVladimir Oltean 		return err;
2844e51bf44SVladimir Oltean 
2854e51bf44SVladimir Oltean 	/* Forwarding and termination FDB entries on the port */
2864e51bf44SVladimir Oltean 	err = br_fdb_replay(br_dev, dev, ctx, true, atomic_nb);
2874e51bf44SVladimir Oltean 	if (err && err != -EOPNOTSUPP)
2884e51bf44SVladimir Oltean 		return err;
2894e51bf44SVladimir Oltean 
2904e51bf44SVladimir Oltean 	/* Termination FDB entries on the bridge itself */
2914e51bf44SVladimir Oltean 	err = br_fdb_replay(br_dev, br_dev, ctx, true, atomic_nb);
2924e51bf44SVladimir Oltean 	if (err && err != -EOPNOTSUPP)
2934e51bf44SVladimir Oltean 		return err;
2944e51bf44SVladimir Oltean 
2954e51bf44SVladimir Oltean 	return 0;
2964e51bf44SVladimir Oltean }
2974e51bf44SVladimir Oltean 
2984e51bf44SVladimir Oltean static void nbp_switchdev_unsync_objs(struct net_bridge_port *p,
2994e51bf44SVladimir Oltean 				      const void *ctx,
3004e51bf44SVladimir Oltean 				      struct notifier_block *atomic_nb,
3014e51bf44SVladimir Oltean 				      struct notifier_block *blocking_nb)
3024e51bf44SVladimir Oltean {
3034e51bf44SVladimir Oltean 	struct net_device *br_dev = p->br->dev;
3044e51bf44SVladimir Oltean 	struct net_device *dev = p->dev;
3054e51bf44SVladimir Oltean 
3064e51bf44SVladimir Oltean 	br_vlan_replay(br_dev, dev, ctx, false, blocking_nb, NULL);
3074e51bf44SVladimir Oltean 
3084e51bf44SVladimir Oltean 	br_mdb_replay(br_dev, dev, ctx, false, blocking_nb, NULL);
3094e51bf44SVladimir Oltean 
3104e51bf44SVladimir Oltean 	/* Forwarding and termination FDB entries on the port */
3114e51bf44SVladimir Oltean 	br_fdb_replay(br_dev, dev, ctx, false, atomic_nb);
3124e51bf44SVladimir Oltean 
3134e51bf44SVladimir Oltean 	/* Termination FDB entries on the bridge itself */
3144e51bf44SVladimir Oltean 	br_fdb_replay(br_dev, br_dev, ctx, false, atomic_nb);
3154e51bf44SVladimir Oltean }
3164e51bf44SVladimir Oltean 
3172f5dc00fSVladimir Oltean /* Let the bridge know that this port is offloaded, so that it can assign a
3182f5dc00fSVladimir Oltean  * switchdev hardware domain to it.
3192f5dc00fSVladimir Oltean  */
3202f5dc00fSVladimir Oltean int switchdev_bridge_port_offload(struct net_device *brport_dev,
3214e51bf44SVladimir Oltean 				  struct net_device *dev, const void *ctx,
3224e51bf44SVladimir Oltean 				  struct notifier_block *atomic_nb,
3234e51bf44SVladimir Oltean 				  struct notifier_block *blocking_nb,
324*47211192STobias Waldekranz 				  bool tx_fwd_offload,
3252f5dc00fSVladimir Oltean 				  struct netlink_ext_ack *extack)
3262f5dc00fSVladimir Oltean {
3272f5dc00fSVladimir Oltean 	struct netdev_phys_item_id ppid;
3282f5dc00fSVladimir Oltean 	struct net_bridge_port *p;
3292f5dc00fSVladimir Oltean 	int err;
3302f5dc00fSVladimir Oltean 
3312f5dc00fSVladimir Oltean 	ASSERT_RTNL();
3322f5dc00fSVladimir Oltean 
3332f5dc00fSVladimir Oltean 	p = br_port_get_rtnl(brport_dev);
3342f5dc00fSVladimir Oltean 	if (!p)
3352f5dc00fSVladimir Oltean 		return -ENODEV;
3362f5dc00fSVladimir Oltean 
3372f5dc00fSVladimir Oltean 	err = dev_get_port_parent_id(dev, &ppid, false);
3382f5dc00fSVladimir Oltean 	if (err)
3392f5dc00fSVladimir Oltean 		return err;
3402f5dc00fSVladimir Oltean 
341*47211192STobias Waldekranz 	err = nbp_switchdev_add(p, ppid, tx_fwd_offload, extack);
3424e51bf44SVladimir Oltean 	if (err)
3434e51bf44SVladimir Oltean 		return err;
3444e51bf44SVladimir Oltean 
3454e51bf44SVladimir Oltean 	err = nbp_switchdev_sync_objs(p, ctx, atomic_nb, blocking_nb, extack);
3464e51bf44SVladimir Oltean 	if (err)
3474e51bf44SVladimir Oltean 		goto out_switchdev_del;
3484e51bf44SVladimir Oltean 
3494e51bf44SVladimir Oltean 	return 0;
3504e51bf44SVladimir Oltean 
3514e51bf44SVladimir Oltean out_switchdev_del:
3524e51bf44SVladimir Oltean 	nbp_switchdev_del(p);
3534e51bf44SVladimir Oltean 
3544e51bf44SVladimir Oltean 	return err;
3552f5dc00fSVladimir Oltean }
3562f5dc00fSVladimir Oltean EXPORT_SYMBOL_GPL(switchdev_bridge_port_offload);
3572f5dc00fSVladimir Oltean 
3584e51bf44SVladimir Oltean void switchdev_bridge_port_unoffload(struct net_device *brport_dev,
3594e51bf44SVladimir Oltean 				     const void *ctx,
3604e51bf44SVladimir Oltean 				     struct notifier_block *atomic_nb,
3614e51bf44SVladimir Oltean 				     struct notifier_block *blocking_nb)
3622f5dc00fSVladimir Oltean {
3632f5dc00fSVladimir Oltean 	struct net_bridge_port *p;
3642f5dc00fSVladimir Oltean 
3652f5dc00fSVladimir Oltean 	ASSERT_RTNL();
3662f5dc00fSVladimir Oltean 
3672f5dc00fSVladimir Oltean 	p = br_port_get_rtnl(brport_dev);
3682f5dc00fSVladimir Oltean 	if (!p)
3692f5dc00fSVladimir Oltean 		return;
3702f5dc00fSVladimir Oltean 
3714e51bf44SVladimir Oltean 	nbp_switchdev_unsync_objs(p, ctx, atomic_nb, blocking_nb);
3724e51bf44SVladimir Oltean 
3732f5dc00fSVladimir Oltean 	nbp_switchdev_del(p);
3742f5dc00fSVladimir Oltean }
3752f5dc00fSVladimir Oltean EXPORT_SYMBOL_GPL(switchdev_bridge_port_unoffload);
376