Commit 7fe2af16 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'mlxsw-add-support-for-802-1ad-bridging'

Ido Schimmel says:

====================
mlxsw: Add support for 802.1ad bridging

802.1ad, also known as QinQ, is an extension to the 802.1q standard,
which is concerned with passing possibly 802.1q-tagged packets through
another VLAN-like tunnel. The format of 802.1ad tag is the same as
802.1q, except it uses the EtherType of 0x88a8, unlike 802.1q's 0x8100.

Currently, mlxsw supports bridging with VLAN-unaware (802.1d) bridges
and with VLAN-aware bridges whose VLAN protocol is 802.1q. This set adds
support for VLAN-aware bridges whose VLAN protocol is 802.1ad.

From mlxsw perspective, 802.1ad support entails two main changes:

1. Ports member in an 802.1ad bridge need to be configured to classify
802.1ad packets as tagged and all other packets as untagged

2. When pushing a VLAN at ingress (PVID), its EtherType needs to be
0x88a8 instead of 802.1q's 0x8100

The rest stays the same as with 802.1q bridges.

A follow-up patch set will add support for QinQ with VXLAN, also known
as QinVNI. Currently, linking of a VXLAN netdev to an 802.1ad bridge is
vetoed and an error is returned to user space.

Patch set overview:

Patches #1-#2 add the registers required to configure the two changes
described above.

Patch #3 changes the device to only treat 802.1q packets as tagged by
default, as opposed to both 802.1q and 802.1ad packets. This is more
inline with the behavior supported by the driver.

Patch #4 adds the ability to configure the EtherType when pushing a PVID
at ingress.

Patch #5 performs small refactoring to allow for code re-use in the next
patch.

Patch #6 adds support for 802.1ad bridging and allows mlxsw ports and
their uppers to join such a bridge.

Patch #7 changes the bridge driver to notify about changes to its VLAN
protocol, so that these could be vetoed by mlxsw in the next patch.

Patches #8-#9 teach mlxsw to veto unsupported 802.1ad configurations and
add a corresponding selftest to make sure such configurations are indeed
vetoed.
====================

Link: https://lore.kernel.org/r/20201129125407.1391557-1-idosch@idosch.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents ac6e9185 008cb2ec
Loading
Loading
Loading
Loading
+113 −1
Original line number Diff line number Diff line
@@ -834,17 +834,30 @@ MLXSW_ITEM32(reg, spvid, local_port, 0x00, 16, 8);
 */
MLXSW_ITEM32(reg, spvid, sub_port, 0x00, 8, 8);

/* reg_spvid_et_vlan
 * EtherType used for when VLAN is pushed at ingress (for untagged
 * packets or for QinQ push mode).
 * 0: ether_type0 - (default)
 * 1: ether_type1
 * 2: ether_type2 - Reserved when Spectrum-1, supported by Spectrum-2
 * Ethertype IDs are configured by SVER.
 * Access: RW
 */
MLXSW_ITEM32(reg, spvid, et_vlan, 0x04, 16, 2);

/* reg_spvid_pvid
 * Port default VID
 * Access: RW
 */
MLXSW_ITEM32(reg, spvid, pvid, 0x04, 0, 12);

static inline void mlxsw_reg_spvid_pack(char *payload, u8 local_port, u16 pvid)
static inline void mlxsw_reg_spvid_pack(char *payload, u8 local_port, u16 pvid,
					u8 et_vlan)
{
	MLXSW_REG_ZERO(spvid, payload);
	mlxsw_reg_spvid_local_port_set(payload, local_port);
	mlxsw_reg_spvid_pvid_set(payload, pvid);
	mlxsw_reg_spvid_et_vlan_set(payload, et_vlan);
}

/* SPVM - Switch Port VLAN Membership
@@ -1857,6 +1870,104 @@ static inline void mlxsw_reg_spvmlr_pack(char *payload, u8 local_port,
	}
}

/* SPVC - Switch Port VLAN Classification Register
 * -----------------------------------------------
 * Configures the port to identify packets as untagged / single tagged /
 * double packets based on the packet EtherTypes.
 * Ethertype IDs are configured by SVER.
 */
#define MLXSW_REG_SPVC_ID 0x2026
#define MLXSW_REG_SPVC_LEN 0x0C

MLXSW_REG_DEFINE(spvc, MLXSW_REG_SPVC_ID, MLXSW_REG_SPVC_LEN);

/* reg_spvc_local_port
 * Local port.
 * Access: Index
 *
 * Note: applies both to Rx port and Tx port, so if a packet traverses
 * through Rx port i and a Tx port j then port i and port j must have the
 * same configuration.
 */
MLXSW_ITEM32(reg, spvc, local_port, 0x00, 16, 8);

/* reg_spvc_inner_et2
 * Vlan Tag1 EtherType2 enable.
 * Packet is initially classified as double VLAN Tag if in addition to
 * being classified with a tag0 VLAN Tag its tag1 EtherType value is
 * equal to ether_type2.
 * 0: disable (default)
 * 1: enable
 * Access: RW
 */
MLXSW_ITEM32(reg, spvc, inner_et2, 0x08, 17, 1);

/* reg_spvc_et2
 * Vlan Tag0 EtherType2 enable.
 * Packet is initially classified as VLAN Tag if its tag0 EtherType is
 * equal to ether_type2.
 * 0: disable (default)
 * 1: enable
 * Access: RW
 */
MLXSW_ITEM32(reg, spvc, et2, 0x08, 16, 1);

/* reg_spvc_inner_et1
 * Vlan Tag1 EtherType1 enable.
 * Packet is initially classified as double VLAN Tag if in addition to
 * being classified with a tag0 VLAN Tag its tag1 EtherType value is
 * equal to ether_type1.
 * 0: disable
 * 1: enable (default)
 * Access: RW
 */
MLXSW_ITEM32(reg, spvc, inner_et1, 0x08, 9, 1);

/* reg_spvc_et1
 * Vlan Tag0 EtherType1 enable.
 * Packet is initially classified as VLAN Tag if its tag0 EtherType is
 * equal to ether_type1.
 * 0: disable
 * 1: enable (default)
 * Access: RW
 */
MLXSW_ITEM32(reg, spvc, et1, 0x08, 8, 1);

/* reg_inner_et0
 * Vlan Tag1 EtherType0 enable.
 * Packet is initially classified as double VLAN Tag if in addition to
 * being classified with a tag0 VLAN Tag its tag1 EtherType value is
 * equal to ether_type0.
 * 0: disable
 * 1: enable (default)
 * Access: RW
 */
MLXSW_ITEM32(reg, spvc, inner_et0, 0x08, 1, 1);

/* reg_et0
 * Vlan Tag0 EtherType0 enable.
 * Packet is initially classified as VLAN Tag if its tag0 EtherType is
 * equal to ether_type0.
 * 0: disable
 * 1: enable (default)
 * Access: RW
 */
MLXSW_ITEM32(reg, spvc, et0, 0x08, 0, 1);

static inline void mlxsw_reg_spvc_pack(char *payload, u8 local_port, bool et1,
				       bool et0)
{
	MLXSW_REG_ZERO(spvc, payload);
	mlxsw_reg_spvc_local_port_set(payload, local_port);
	/* Enable inner_et1 and inner_et0 to enable identification of double
	 * tagged packets.
	 */
	mlxsw_reg_spvc_inner_et1_set(payload, 1);
	mlxsw_reg_spvc_inner_et0_set(payload, 1);
	mlxsw_reg_spvc_et1_set(payload, et1);
	mlxsw_reg_spvc_et0_set(payload, et0);
}

/* CWTP - Congetion WRED ECN TClass Profile
 * ----------------------------------------
 * Configures the profiles for queues of egress port and traffic class
@@ -11212,6 +11323,7 @@ static const struct mlxsw_reg_info *mlxsw_reg_infos[] = {
	MLXSW_REG(svpe),
	MLXSW_REG(sfmr),
	MLXSW_REG(spvmlr),
	MLXSW_REG(spvc),
	MLXSW_REG(cwtp),
	MLXSW_REG(cwtpm),
	MLXSW_REG(pgcr),
+104 −7
Original line number Diff line number Diff line
@@ -384,13 +384,37 @@ int mlxsw_sp_port_vid_learning_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid,
	return err;
}

static int mlxsw_sp_ethtype_to_sver_type(u16 ethtype, u8 *p_sver_type)
{
	switch (ethtype) {
	case ETH_P_8021Q:
		*p_sver_type = 0;
		break;
	case ETH_P_8021AD:
		*p_sver_type = 1;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int __mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port,
				    u16 vid)
				    u16 vid, u16 ethtype)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
	char spvid_pl[MLXSW_REG_SPVID_LEN];
	u8 sver_type;
	int err;

	err = mlxsw_sp_ethtype_to_sver_type(ethtype, &sver_type);
	if (err)
		return err;

	mlxsw_reg_spvid_pack(spvid_pl, mlxsw_sp_port->local_port, vid,
			     sver_type);

	mlxsw_reg_spvid_pack(spvid_pl, mlxsw_sp_port->local_port, vid);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(spvid), spvid_pl);
}

@@ -404,7 +428,8 @@ static int mlxsw_sp_port_allow_untagged_set(struct mlxsw_sp_port *mlxsw_sp_port,
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(spaft), spaft_pl);
}

int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid)
int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid,
			   u16 ethtype)
{
	int err;

@@ -413,7 +438,7 @@ int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid)
		if (err)
			return err;
	} else {
		err = __mlxsw_sp_port_pvid_set(mlxsw_sp_port, vid);
		err = __mlxsw_sp_port_pvid_set(mlxsw_sp_port, vid, ethtype);
		if (err)
			return err;
		err = mlxsw_sp_port_allow_untagged_set(mlxsw_sp_port, true);
@@ -425,7 +450,7 @@ int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid)
	return 0;

err_port_allow_untagged_set:
	__mlxsw_sp_port_pvid_set(mlxsw_sp_port, mlxsw_sp_port->pvid);
	__mlxsw_sp_port_pvid_set(mlxsw_sp_port, mlxsw_sp_port->pvid, ethtype);
	return err;
}

@@ -1386,6 +1411,19 @@ static int mlxsw_sp_port_overheat_init_val_set(struct mlxsw_sp_port *mlxsw_sp_po
	return 0;
}

int
mlxsw_sp_port_vlan_classification_set(struct mlxsw_sp_port *mlxsw_sp_port,
				      bool is_8021ad_tagged,
				      bool is_8021q_tagged)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
	char spvc_pl[MLXSW_REG_SPVC_LEN];

	mlxsw_reg_spvc_pack(spvc_pl, mlxsw_sp_port->local_port,
			    is_8021ad_tagged, is_8021q_tagged);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(spvc), spvc_pl);
}

static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
				u8 split_base_local_port,
				struct mlxsw_sp_port_mapping *port_mapping)
@@ -1575,7 +1613,8 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
		goto err_port_nve_init;
	}

	err = mlxsw_sp_port_pvid_set(mlxsw_sp_port, MLXSW_SP_DEFAULT_VID);
	err = mlxsw_sp_port_pvid_set(mlxsw_sp_port, MLXSW_SP_DEFAULT_VID,
				     ETH_P_8021Q);
	if (err) {
		dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to set PVID\n",
			mlxsw_sp_port->local_port);
@@ -1592,6 +1631,16 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
	}
	mlxsw_sp_port->default_vlan = mlxsw_sp_port_vlan;

	/* Set SPVC.et0=true and SPVC.et1=false to make the local port to treat
	 * only packets with 802.1q header as tagged packets.
	 */
	err = mlxsw_sp_port_vlan_classification_set(mlxsw_sp_port, false, true);
	if (err) {
		dev_err(mlxsw_sp->bus_info->dev, "Port %d: Failed to set default VLAN classification\n",
			local_port);
		goto err_port_vlan_classification_set;
	}

	INIT_DELAYED_WORK(&mlxsw_sp_port->ptp.shaper_dw,
			  mlxsw_sp->ptp_ops->shaper_work);

@@ -1618,6 +1667,8 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,

err_register_netdev:
err_port_overheat_init_val_set:
	mlxsw_sp_port_vlan_classification_set(mlxsw_sp_port, true, true);
err_port_vlan_classification_set:
	mlxsw_sp->ports[local_port] = NULL;
	mlxsw_sp_port_vlan_destroy(mlxsw_sp_port_vlan);
err_port_vlan_create:
@@ -1664,6 +1715,7 @@ static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
	mlxsw_sp_port_ptp_clear(mlxsw_sp_port);
	mlxsw_core_port_clear(mlxsw_sp->core, local_port, mlxsw_sp);
	unregister_netdev(mlxsw_sp_port->dev); /* This calls ndo_stop */
	mlxsw_sp_port_vlan_classification_set(mlxsw_sp_port, true, true);
	mlxsw_sp->ports[local_port] = NULL;
	mlxsw_sp_port_vlan_flush(mlxsw_sp_port, true);
	mlxsw_sp_port_nve_fini(mlxsw_sp_port);
@@ -3618,7 +3670,8 @@ static void mlxsw_sp_port_lag_leave(struct mlxsw_sp_port *mlxsw_sp_port,
	lag->ref_count--;

	/* Make sure untagged frames are allowed to ingress */
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, MLXSW_SP_DEFAULT_VID);
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, MLXSW_SP_DEFAULT_VID,
			       ETH_P_8021Q);
}

static int mlxsw_sp_lag_dist_port_add(struct mlxsw_sp_port *mlxsw_sp_port,
@@ -3840,6 +3893,7 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
	struct net_device *upper_dev;
	struct mlxsw_sp *mlxsw_sp;
	int err = 0;
	u16 proto;

	mlxsw_sp_port = netdev_priv(dev);
	mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
@@ -3897,6 +3951,36 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
			NL_SET_ERR_MSG_MOD(extack, "Can not put a VLAN on an OVS port");
			return -EINVAL;
		}
		if (netif_is_bridge_master(upper_dev)) {
			br_vlan_get_proto(upper_dev, &proto);
			if (br_vlan_enabled(upper_dev) &&
			    proto != ETH_P_8021Q && proto != ETH_P_8021AD) {
				NL_SET_ERR_MSG_MOD(extack, "Enslaving a port to a bridge with unknown VLAN protocol is not supported");
				return -EOPNOTSUPP;
			}
			if (vlan_uses_dev(lower_dev) &&
			    br_vlan_enabled(upper_dev) &&
			    proto == ETH_P_8021AD) {
				NL_SET_ERR_MSG_MOD(extack, "Enslaving a port that already has a VLAN upper to an 802.1ad bridge is not supported");
				return -EOPNOTSUPP;
			}
		}
		if (netif_is_bridge_port(lower_dev) && is_vlan_dev(upper_dev)) {
			struct net_device *br_dev = netdev_master_upper_dev_get(lower_dev);

			if (br_vlan_enabled(br_dev)) {
				br_vlan_get_proto(br_dev, &proto);
				if (proto == ETH_P_8021AD) {
					NL_SET_ERR_MSG_MOD(extack, "VLAN uppers are not supported on a port enslaved to an 802.1ad bridge");
					return -EOPNOTSUPP;
				}
			}
		}
		if (is_vlan_dev(upper_dev) &&
		    ntohs(vlan_dev_vlan_proto(upper_dev)) != ETH_P_8021Q) {
			NL_SET_ERR_MSG_MOD(extack, "VLAN uppers are only supported with 802.1q VLAN protocol");
			return -EOPNOTSUPP;
		}
		break;
	case NETDEV_CHANGEUPPER:
		upper_dev = info->upper_dev;
@@ -4162,6 +4246,7 @@ static int mlxsw_sp_netdevice_bridge_event(struct net_device *br_dev,
	struct netdev_notifier_changeupper_info *info = ptr;
	struct netlink_ext_ack *extack;
	struct net_device *upper_dev;
	u16 proto;

	if (!mlxsw_sp)
		return 0;
@@ -4177,6 +4262,18 @@ static int mlxsw_sp_netdevice_bridge_event(struct net_device *br_dev,
		}
		if (!info->linking)
			break;
		if (br_vlan_enabled(br_dev)) {
			br_vlan_get_proto(br_dev, &proto);
			if (proto == ETH_P_8021AD) {
				NL_SET_ERR_MSG_MOD(extack, "Uppers are not supported on top of an 802.1ad bridge");
				return -EOPNOTSUPP;
			}
		}
		if (is_vlan_dev(upper_dev) &&
		    ntohs(vlan_dev_vlan_proto(upper_dev)) != ETH_P_8021Q) {
			NL_SET_ERR_MSG_MOD(extack, "VLAN uppers are only supported with 802.1q VLAN protocol");
			return -EOPNOTSUPP;
		}
		if (netif_is_macvlan(upper_dev) &&
		    !mlxsw_sp_rif_exists(mlxsw_sp, br_dev)) {
			NL_SET_ERR_MSG_MOD(extack, "macvlan is only supported on top of router interfaces");
+6 −1
Original line number Diff line number Diff line
@@ -428,6 +428,10 @@ int mlxsw_sp_port_get_stats_raw(struct net_device *dev, int grp,
				int prio, char *ppcnt_pl);
int mlxsw_sp_port_admin_status_set(struct mlxsw_sp_port *mlxsw_sp_port,
				   bool is_up);
int
mlxsw_sp_port_vlan_classification_set(struct mlxsw_sp_port *mlxsw_sp_port,
				      bool is_8021ad_tagged,
				      bool is_8021q_tagged);

/* spectrum_buffers.c */
struct mlxsw_sp_hdroom_prio {
@@ -580,7 +584,8 @@ int mlxsw_sp_port_vid_stp_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid,
int mlxsw_sp_port_vp_mode_set(struct mlxsw_sp_port *mlxsw_sp_port, bool enable);
int mlxsw_sp_port_vid_learning_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid,
				   bool learn_enable);
int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid);
int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid,
			   u16 ethtype);
struct mlxsw_sp_port_vlan *
mlxsw_sp_port_vlan_create(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid);
void mlxsw_sp_port_vlan_destroy(struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan);
+9 −0
Original line number Diff line number Diff line
@@ -7857,6 +7857,15 @@ static int mlxsw_sp_inetaddr_bridge_event(struct mlxsw_sp *mlxsw_sp,

	switch (event) {
	case NETDEV_UP:
		if (netif_is_bridge_master(l3_dev) && br_vlan_enabled(l3_dev)) {
			u16 proto;

			br_vlan_get_proto(l3_dev, &proto);
			if (proto == ETH_P_8021AD) {
				NL_SET_ERR_MSG_MOD(extack, "Adding an IP address to 802.1ad bridge is not supported");
				return -EOPNOTSUPP;
			}
		}
		rif = mlxsw_sp_rif_create(mlxsw_sp, &params, extack);
		if (IS_ERR(rif))
			return PTR_ERR(rif);
+113 −10
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ struct mlxsw_sp_bridge {
	DECLARE_BITMAP(mids_bitmap, MLXSW_SP_MID_MAX);
	const struct mlxsw_sp_bridge_ops *bridge_8021q_ops;
	const struct mlxsw_sp_bridge_ops *bridge_8021d_ops;
	const struct mlxsw_sp_bridge_ops *bridge_8021ad_ops;
};

struct mlxsw_sp_bridge_device {
@@ -228,7 +229,13 @@ mlxsw_sp_bridge_device_create(struct mlxsw_sp_bridge *bridge,
	bridge_device->mrouter = br_multicast_router(br_dev);
	INIT_LIST_HEAD(&bridge_device->ports_list);
	if (vlan_enabled) {
		u16 proto;

		bridge->vlan_enabled_exists = true;
		br_vlan_get_proto(br_dev, &proto);
		if (proto == ETH_P_8021AD)
			bridge_device->ops = bridge->bridge_8021ad_ops;
		else
			bridge_device->ops = bridge->bridge_8021q_ops;
	} else {
		bridge_device->ops = bridge->bridge_8021d_ops;
@@ -757,6 +764,25 @@ static int mlxsw_sp_port_attr_br_vlan_set(struct mlxsw_sp_port *mlxsw_sp_port,
	return -EINVAL;
}

static int mlxsw_sp_port_attr_br_vlan_proto_set(struct mlxsw_sp_port *mlxsw_sp_port,
						struct switchdev_trans *trans,
						struct net_device *orig_dev,
						u16 vlan_proto)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
	struct mlxsw_sp_bridge_device *bridge_device;

	if (!switchdev_trans_ph_prepare(trans))
		return 0;

	bridge_device = mlxsw_sp_bridge_device_find(mlxsw_sp->bridge, orig_dev);
	if (WARN_ON(!bridge_device))
		return -EINVAL;

	netdev_err(bridge_device->dev, "VLAN protocol can't be changed on existing bridge\n");
	return -EINVAL;
}

static int mlxsw_sp_port_attr_mrouter_set(struct mlxsw_sp_port *mlxsw_sp_port,
					  struct switchdev_trans *trans,
					  struct net_device *orig_dev,
@@ -926,6 +952,11 @@ static int mlxsw_sp_port_attr_set(struct net_device *dev,
						     attr->orig_dev,
						     attr->u.vlan_filtering);
		break;
	case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_PROTOCOL:
		err = mlxsw_sp_port_attr_br_vlan_proto_set(mlxsw_sp_port, trans,
							   attr->orig_dev,
							   attr->u.vlan_protocol);
		break;
	case SWITCHDEV_ATTR_ID_PORT_MROUTER:
		err = mlxsw_sp_port_attr_mrouter_set(mlxsw_sp_port, trans,
						     attr->orig_dev,
@@ -1129,6 +1160,7 @@ mlxsw_sp_bridge_port_vlan_add(struct mlxsw_sp_port *mlxsw_sp_port,
	u16 pvid = mlxsw_sp_port_pvid_determine(mlxsw_sp_port, vid, is_pvid);
	struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan;
	u16 old_pvid = mlxsw_sp_port->pvid;
	u16 proto;
	int err;

	/* The only valid scenario in which a port-vlan already exists, is if
@@ -1152,7 +1184,8 @@ mlxsw_sp_bridge_port_vlan_add(struct mlxsw_sp_port *mlxsw_sp_port,
	if (err)
		goto err_port_vlan_set;

	err = mlxsw_sp_port_pvid_set(mlxsw_sp_port, pvid);
	br_vlan_get_proto(bridge_port->bridge_device->dev, &proto);
	err = mlxsw_sp_port_pvid_set(mlxsw_sp_port, pvid, proto);
	if (err)
		goto err_port_pvid_set;

@@ -1164,7 +1197,7 @@ mlxsw_sp_bridge_port_vlan_add(struct mlxsw_sp_port *mlxsw_sp_port,
	return 0;

err_port_vlan_bridge_join:
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, old_pvid);
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, old_pvid, proto);
err_port_pvid_set:
	mlxsw_sp_port_vlan_set(mlxsw_sp_port, vid, vid, false, false);
err_port_vlan_set:
@@ -1821,13 +1854,15 @@ mlxsw_sp_bridge_port_vlan_del(struct mlxsw_sp_port *mlxsw_sp_port,
{
	u16 pvid = mlxsw_sp_port->pvid == vid ? 0 : mlxsw_sp_port->pvid;
	struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan;
	u16 proto;

	mlxsw_sp_port_vlan = mlxsw_sp_port_vlan_find_by_vid(mlxsw_sp_port, vid);
	if (WARN_ON(!mlxsw_sp_port_vlan))
		return;

	mlxsw_sp_port_vlan_bridge_leave(mlxsw_sp_port_vlan);
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, pvid);
	br_vlan_get_proto(bridge_port->bridge_device->dev, &proto);
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, pvid, proto);
	mlxsw_sp_port_vlan_set(mlxsw_sp_port, vid, vid, false, false);
	mlxsw_sp_port_vlan_destroy(mlxsw_sp_port_vlan);
}
@@ -1975,8 +2010,7 @@ static struct mlxsw_sp_port *mlxsw_sp_lag_rep_port(struct mlxsw_sp *mlxsw_sp,
}

static int
mlxsw_sp_bridge_8021q_port_join(struct mlxsw_sp_bridge_device *bridge_device,
				struct mlxsw_sp_bridge_port *bridge_port,
mlxsw_sp_bridge_vlan_aware_port_join(struct mlxsw_sp_bridge_port *bridge_port,
				     struct mlxsw_sp_port *mlxsw_sp_port,
				     struct netlink_ext_ack *extack)
{
@@ -1992,13 +2026,30 @@ mlxsw_sp_bridge_8021q_port_join(struct mlxsw_sp_bridge_device *bridge_device,
	return 0;
}

static int
mlxsw_sp_bridge_8021q_port_join(struct mlxsw_sp_bridge_device *bridge_device,
				struct mlxsw_sp_bridge_port *bridge_port,
				struct mlxsw_sp_port *mlxsw_sp_port,
				struct netlink_ext_ack *extack)
{
	return mlxsw_sp_bridge_vlan_aware_port_join(bridge_port, mlxsw_sp_port,
						    extack);
}

static void
mlxsw_sp_bridge_vlan_aware_port_leave(struct mlxsw_sp_port *mlxsw_sp_port)
{
	/* Make sure untagged frames are allowed to ingress */
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, MLXSW_SP_DEFAULT_VID,
			       ETH_P_8021Q);
}

static void
mlxsw_sp_bridge_8021q_port_leave(struct mlxsw_sp_bridge_device *bridge_device,
				 struct mlxsw_sp_bridge_port *bridge_port,
				 struct mlxsw_sp_port *mlxsw_sp_port)
{
	/* Make sure untagged frames are allowed to ingress */
	mlxsw_sp_port_pvid_set(mlxsw_sp_port, MLXSW_SP_DEFAULT_VID);
	mlxsw_sp_bridge_vlan_aware_port_leave(mlxsw_sp_port);
}

static int
@@ -2246,6 +2297,57 @@ static const struct mlxsw_sp_bridge_ops mlxsw_sp_bridge_8021d_ops = {
	.fid_vid	= mlxsw_sp_bridge_8021d_fid_vid,
};

static int
mlxsw_sp_bridge_8021ad_port_join(struct mlxsw_sp_bridge_device *bridge_device,
				 struct mlxsw_sp_bridge_port *bridge_port,
				 struct mlxsw_sp_port *mlxsw_sp_port,
				 struct netlink_ext_ack *extack)
{
	int err;

	err = mlxsw_sp_port_vlan_classification_set(mlxsw_sp_port, true, false);
	if (err)
		return err;

	err = mlxsw_sp_bridge_vlan_aware_port_join(bridge_port, mlxsw_sp_port,
						   extack);
	if (err)
		goto err_bridge_vlan_aware_port_join;

	return 0;

err_bridge_vlan_aware_port_join:
	mlxsw_sp_port_vlan_classification_set(mlxsw_sp_port, false, true);
	return err;
}

static void
mlxsw_sp_bridge_8021ad_port_leave(struct mlxsw_sp_bridge_device *bridge_device,
				  struct mlxsw_sp_bridge_port *bridge_port,
				  struct mlxsw_sp_port *mlxsw_sp_port)
{
	mlxsw_sp_bridge_vlan_aware_port_leave(mlxsw_sp_port);
	mlxsw_sp_port_vlan_classification_set(mlxsw_sp_port, false, true);
}

static int
mlxsw_sp_bridge_8021ad_vxlan_join(struct mlxsw_sp_bridge_device *bridge_device,
				  const struct net_device *vxlan_dev, u16 vid,
				  struct netlink_ext_ack *extack)
{
	NL_SET_ERR_MSG_MOD(extack, "VXLAN is not supported with 802.1ad");
	return -EOPNOTSUPP;
}

static const struct mlxsw_sp_bridge_ops mlxsw_sp_bridge_8021ad_ops = {
	.port_join	= mlxsw_sp_bridge_8021ad_port_join,
	.port_leave	= mlxsw_sp_bridge_8021ad_port_leave,
	.vxlan_join	= mlxsw_sp_bridge_8021ad_vxlan_join,
	.fid_get	= mlxsw_sp_bridge_8021q_fid_get,
	.fid_lookup	= mlxsw_sp_bridge_8021q_fid_lookup,
	.fid_vid	= mlxsw_sp_bridge_8021q_fid_vid,
};

int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
			      struct net_device *brport_dev,
			      struct net_device *br_dev,
@@ -3507,6 +3609,7 @@ int mlxsw_sp_switchdev_init(struct mlxsw_sp *mlxsw_sp)

	bridge->bridge_8021q_ops = &mlxsw_sp_bridge_8021q_ops;
	bridge->bridge_8021d_ops = &mlxsw_sp_bridge_8021d_ops;
	bridge->bridge_8021ad_ops = &mlxsw_sp_bridge_8021ad_ops;

	return mlxsw_sp_fdb_init(mlxsw_sp);
}
Loading