@@ -122,26 +122,82 @@ void dsa_port_disable(struct dsa_port *dp)
rtnl_unlock();
}
-static void dsa_port_change_brport_flags(struct dsa_port *dp,
- bool bridge_offload)
+static int dsa_port_inherit_brport_flags(struct dsa_port *dp,
+ struct netlink_ext_ack *extack)
{
- struct switchdev_brport_flags flags;
- int flag;
+ const unsigned long mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD |
+ BR_BCAST_FLOOD;
+ struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
+ int flag, err;
- flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
- if (bridge_offload)
- flags.val = flags.mask;
- else
- flags.val = flags.mask & ~BR_LEARNING;
+ for_each_set_bit(flag, &mask, 32) {
+ struct switchdev_brport_flags flags = {0};
- for_each_set_bit(flag, &flags.mask, 32) {
- struct switchdev_brport_flags tmp;
+ flags.mask = BIT(flag);
- tmp.val = flags.val & BIT(flag);
- tmp.mask = BIT(flag);
+ if (br_port_flag_is_set(brport_dev, BIT(flag)))
+ flags.val = BIT(flag);
- dsa_port_bridge_flags(dp, tmp, NULL);
+ err = dsa_port_bridge_flags(dp, flags, extack);
+ if (err && err != -EOPNOTSUPP)
+ return err;
}
+
+ return 0;
+}
+
+static void dsa_port_clear_brport_flags(struct dsa_port *dp,
+ struct netlink_ext_ack *extack)
+{
+ const unsigned long val = BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
+ const unsigned long mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD |
+ BR_BCAST_FLOOD;
+ int flag, err;
+
+ for_each_set_bit(flag, &mask, 32) {
+ struct switchdev_brport_flags flags = {0};
+
+ flags.mask = BIT(flag);
+ flags.val = val & BIT(flag);
+
+ err = dsa_port_bridge_flags(dp, flags, extack);
+ if (err && err != -EOPNOTSUPP)
+ dev_err(dp->ds->dev,
+ "failed to clear bridge port flag %d: %d (%pe)\n",
+ flags.val, err, ERR_PTR(err));
+ }
+}
+
+static int dsa_port_switchdev_sync(struct dsa_port *dp,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+
+ err = dsa_port_inherit_brport_flags(dp, extack);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/* Configure the port for standalone mode (no address learning, flood
+ * everything, BR_STATE_FORWARDING, etc).
+ * The bridge only emits SWITCHDEV_ATTR_ID_PORT_* events when the user
+ * requests it through netlink or sysfs, but not automatically at port
+ * join or leave, so we need to handle resetting the brport flags ourselves.
+ * But we even prefer it that way, because otherwise, some setups might never
+ * get the notification they need, for example, when a port leaves a LAG that
+ * offloads the bridge, it becomes standalone, but as far as the bridge is
+ * concerned, no port ever left.
+ */
+static void dsa_port_switchdev_unsync(struct dsa_port *dp)
+{
+ dsa_port_clear_brport_flags(dp, NULL);
+
+ /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
+ * so allow it to be in BR_STATE_FORWARDING to be kept functional
+ */
+ dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
}
int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
@@ -155,24 +211,25 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
};
int err;
- /* Notify the port driver to set its configurable flags in a way that
- * matches the initial settings of a bridge port.
- */
- dsa_port_change_brport_flags(dp, true);
-
/* Here the interface is already bridged. Reflect the current
* configuration so that drivers can program their chips accordingly.
*/
dp->bridge_dev = br;
err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_JOIN, &info);
+ if (err)
+ goto out_rollback;
- /* The bridging is rolled back on error */
- if (err) {
- dsa_port_change_brport_flags(dp, false);
- dp->bridge_dev = NULL;
- }
+ err = dsa_port_switchdev_sync(dp, extack);
+ if (err)
+ goto out_rollback_unbridge;
+ return 0;
+
+out_rollback_unbridge:
+ dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
+out_rollback:
+ dp->bridge_dev = NULL;
return err;
}
@@ -186,6 +243,8 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
};
int err;
+ dsa_port_switchdev_unsync(dp);
+
/* Here the port is already unbridged. Reflect the current configuration
* so that drivers can program their chips accordingly.
*/
@@ -194,24 +253,6 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
if (err)
pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
-
- /* Configure the port for standalone mode (no address learning,
- * flood everything).
- * The bridge only emits SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS events
- * when the user requests it through netlink or sysfs, but not
- * automatically at port join or leave, so we need to handle resetting
- * the brport flags ourselves. But we even prefer it that way, because
- * otherwise, some setups might never get the notification they need,
- * for example, when a port leaves a LAG that offloads the bridge,
- * it becomes standalone, but as far as the bridge is concerned, no
- * port ever left.
- */
- dsa_port_change_brport_flags(dp, false);
-
- /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
- * so allow it to be in BR_STATE_FORWARDING to be kept functional
- */
- dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
}
int dsa_port_lag_change(struct dsa_port *dp,