xref: /openbmc/linux/net/dsa/tag_8021q.c (revision 5899ee36)
1f9bbe447SVladimir Oltean // SPDX-License-Identifier: GPL-2.0
2f9bbe447SVladimir Oltean /* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
3f9bbe447SVladimir Oltean  *
4f9bbe447SVladimir Oltean  * This module is not a complete tagger implementation. It only provides
5f9bbe447SVladimir Oltean  * primitives for taggers that rely on 802.1Q VLAN tags to use. The
6f9bbe447SVladimir Oltean  * dsa_8021q_netdev_ops is registered for API compliance and not used
7f9bbe447SVladimir Oltean  * directly by callers.
8f9bbe447SVladimir Oltean  */
9f9bbe447SVladimir Oltean #include <linux/if_bridge.h>
10f9bbe447SVladimir Oltean #include <linux/if_vlan.h>
11ac02a451SVladimir Oltean #include <linux/dsa/8021q.h>
12f9bbe447SVladimir Oltean 
13f9bbe447SVladimir Oltean #include "dsa_priv.h"
14f9bbe447SVladimir Oltean 
150471dd42SVladimir Oltean /* Binary structure of the fake 12-bit VID field (when the TPID is
160471dd42SVladimir Oltean  * ETH_P_DSA_8021Q):
170471dd42SVladimir Oltean  *
180471dd42SVladimir Oltean  * | 11  | 10  |  9  |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
190471dd42SVladimir Oltean  * +-----------+-----+-----------------+-----------+-----------------------+
203eaae1d0SVladimir Oltean  * |    DIR    | SVL |    SWITCH_ID    |  SUBVLAN  |          PORT         |
210471dd42SVladimir Oltean  * +-----------+-----+-----------------+-----------+-----------------------+
220471dd42SVladimir Oltean  *
230471dd42SVladimir Oltean  * DIR - VID[11:10]:
240471dd42SVladimir Oltean  *	Direction flags.
250471dd42SVladimir Oltean  *	* 1 (0b01) for RX VLAN,
260471dd42SVladimir Oltean  *	* 2 (0b10) for TX VLAN.
270471dd42SVladimir Oltean  *	These values make the special VIDs of 0, 1 and 4095 to be left
280471dd42SVladimir Oltean  *	unused by this coding scheme.
290471dd42SVladimir Oltean  *
303eaae1d0SVladimir Oltean  * SVL/SUBVLAN - { VID[9], VID[5:4] }:
313eaae1d0SVladimir Oltean  *	Sub-VLAN encoding. Valid only when DIR indicates an RX VLAN.
323eaae1d0SVladimir Oltean  *	* 0 (0b000): Field does not encode a sub-VLAN, either because
333eaae1d0SVladimir Oltean  *	received traffic is untagged, PVID-tagged or because a second
343eaae1d0SVladimir Oltean  *	VLAN tag is present after this tag and not inside of it.
353eaae1d0SVladimir Oltean  *	* 1 (0b001): Received traffic is tagged with a VID value private
363eaae1d0SVladimir Oltean  *	to the host. This field encodes the index in the host's lookup
373eaae1d0SVladimir Oltean  *	table through which the value of the ingress VLAN ID can be
383eaae1d0SVladimir Oltean  *	recovered.
393eaae1d0SVladimir Oltean  *	* 2 (0b010): Field encodes a sub-VLAN.
403eaae1d0SVladimir Oltean  *	...
413eaae1d0SVladimir Oltean  *	* 7 (0b111): Field encodes a sub-VLAN.
423eaae1d0SVladimir Oltean  *	When DIR indicates a TX VLAN, SUBVLAN must be transmitted as zero
433eaae1d0SVladimir Oltean  *	(by the host) and ignored on receive (by the switch).
440471dd42SVladimir Oltean  *
450471dd42SVladimir Oltean  * SWITCH_ID - VID[8:6]:
46fcee85f1SVivien Didelot  *	Index of switch within DSA tree. Must be between 0 and 7.
470471dd42SVladimir Oltean  *
480471dd42SVladimir Oltean  * PORT - VID[3:0]:
49fcee85f1SVivien Didelot  *	Index of switch port. Must be between 0 and 15.
50f9bbe447SVladimir Oltean  */
510471dd42SVladimir Oltean 
520471dd42SVladimir Oltean #define DSA_8021Q_DIR_SHIFT		10
530471dd42SVladimir Oltean #define DSA_8021Q_DIR_MASK		GENMASK(11, 10)
540471dd42SVladimir Oltean #define DSA_8021Q_DIR(x)		(((x) << DSA_8021Q_DIR_SHIFT) & \
550471dd42SVladimir Oltean 						 DSA_8021Q_DIR_MASK)
560471dd42SVladimir Oltean #define DSA_8021Q_DIR_RX		DSA_8021Q_DIR(1)
570471dd42SVladimir Oltean #define DSA_8021Q_DIR_TX		DSA_8021Q_DIR(2)
580471dd42SVladimir Oltean 
590471dd42SVladimir Oltean #define DSA_8021Q_SWITCH_ID_SHIFT	6
600471dd42SVladimir Oltean #define DSA_8021Q_SWITCH_ID_MASK	GENMASK(8, 6)
610471dd42SVladimir Oltean #define DSA_8021Q_SWITCH_ID(x)		(((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \
620471dd42SVladimir Oltean 						 DSA_8021Q_SWITCH_ID_MASK)
630471dd42SVladimir Oltean 
643eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN_HI_SHIFT	9
653eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN_HI_MASK	GENMASK(9, 9)
663eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN_LO_SHIFT	4
673eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN_LO_MASK	GENMASK(4, 3)
683eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN_HI(x)		(((x) & GENMASK(2, 2)) >> 2)
693eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN_LO(x)		((x) & GENMASK(1, 0))
703eaae1d0SVladimir Oltean #define DSA_8021Q_SUBVLAN(x)		\
713eaae1d0SVladimir Oltean 		(((DSA_8021Q_SUBVLAN_LO(x) << DSA_8021Q_SUBVLAN_LO_SHIFT) & \
723eaae1d0SVladimir Oltean 		  DSA_8021Q_SUBVLAN_LO_MASK) | \
733eaae1d0SVladimir Oltean 		 ((DSA_8021Q_SUBVLAN_HI(x) << DSA_8021Q_SUBVLAN_HI_SHIFT) & \
743eaae1d0SVladimir Oltean 		  DSA_8021Q_SUBVLAN_HI_MASK))
753eaae1d0SVladimir Oltean 
760471dd42SVladimir Oltean #define DSA_8021Q_PORT_SHIFT		0
770471dd42SVladimir Oltean #define DSA_8021Q_PORT_MASK		GENMASK(3, 0)
780471dd42SVladimir Oltean #define DSA_8021Q_PORT(x)		(((x) << DSA_8021Q_PORT_SHIFT) & \
790471dd42SVladimir Oltean 						 DSA_8021Q_PORT_MASK)
80f9bbe447SVladimir Oltean 
81f9bbe447SVladimir Oltean /* Returns the VID to be inserted into the frame from xmit for switch steering
82f9bbe447SVladimir Oltean  * instructions on egress. Encodes switch ID and port ID.
83f9bbe447SVladimir Oltean  */
84f9bbe447SVladimir Oltean u16 dsa_8021q_tx_vid(struct dsa_switch *ds, int port)
85f9bbe447SVladimir Oltean {
860471dd42SVladimir Oltean 	return DSA_8021Q_DIR_TX | DSA_8021Q_SWITCH_ID(ds->index) |
870471dd42SVladimir Oltean 	       DSA_8021Q_PORT(port);
88f9bbe447SVladimir Oltean }
89f9bbe447SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_tx_vid);
90f9bbe447SVladimir Oltean 
91f9bbe447SVladimir Oltean /* Returns the VID that will be installed as pvid for this switch port, sent as
92f9bbe447SVladimir Oltean  * tagged egress towards the CPU port and decoded by the rcv function.
93f9bbe447SVladimir Oltean  */
94f9bbe447SVladimir Oltean u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port)
95f9bbe447SVladimir Oltean {
960471dd42SVladimir Oltean 	return DSA_8021Q_DIR_RX | DSA_8021Q_SWITCH_ID(ds->index) |
970471dd42SVladimir Oltean 	       DSA_8021Q_PORT(port);
98f9bbe447SVladimir Oltean }
99f9bbe447SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid);
100f9bbe447SVladimir Oltean 
1013eaae1d0SVladimir Oltean u16 dsa_8021q_rx_vid_subvlan(struct dsa_switch *ds, int port, u16 subvlan)
1023eaae1d0SVladimir Oltean {
1033eaae1d0SVladimir Oltean 	return DSA_8021Q_DIR_RX | DSA_8021Q_SWITCH_ID(ds->index) |
1043eaae1d0SVladimir Oltean 	       DSA_8021Q_PORT(port) | DSA_8021Q_SUBVLAN(subvlan);
1053eaae1d0SVladimir Oltean }
1063eaae1d0SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid_subvlan);
1073eaae1d0SVladimir Oltean 
108f9bbe447SVladimir Oltean /* Returns the decoded switch ID from the RX VID. */
109f9bbe447SVladimir Oltean int dsa_8021q_rx_switch_id(u16 vid)
110f9bbe447SVladimir Oltean {
1110471dd42SVladimir Oltean 	return (vid & DSA_8021Q_SWITCH_ID_MASK) >> DSA_8021Q_SWITCH_ID_SHIFT;
112f9bbe447SVladimir Oltean }
113f9bbe447SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_rx_switch_id);
114f9bbe447SVladimir Oltean 
115f9bbe447SVladimir Oltean /* Returns the decoded port ID from the RX VID. */
116f9bbe447SVladimir Oltean int dsa_8021q_rx_source_port(u16 vid)
117f9bbe447SVladimir Oltean {
1180471dd42SVladimir Oltean 	return (vid & DSA_8021Q_PORT_MASK) >> DSA_8021Q_PORT_SHIFT;
119f9bbe447SVladimir Oltean }
120f9bbe447SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port);
121f9bbe447SVladimir Oltean 
1223eaae1d0SVladimir Oltean /* Returns the decoded subvlan from the RX VID. */
1233eaae1d0SVladimir Oltean u16 dsa_8021q_rx_subvlan(u16 vid)
1243eaae1d0SVladimir Oltean {
1253eaae1d0SVladimir Oltean 	u16 svl_hi, svl_lo;
1263eaae1d0SVladimir Oltean 
1273eaae1d0SVladimir Oltean 	svl_hi = (vid & DSA_8021Q_SUBVLAN_HI_MASK) >>
1283eaae1d0SVladimir Oltean 		 DSA_8021Q_SUBVLAN_HI_SHIFT;
1293eaae1d0SVladimir Oltean 	svl_lo = (vid & DSA_8021Q_SUBVLAN_LO_MASK) >>
1303eaae1d0SVladimir Oltean 		 DSA_8021Q_SUBVLAN_LO_SHIFT;
1313eaae1d0SVladimir Oltean 
1323eaae1d0SVladimir Oltean 	return (svl_hi << 2) | svl_lo;
1333eaae1d0SVladimir Oltean }
1343eaae1d0SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_rx_subvlan);
1353eaae1d0SVladimir Oltean 
1361f66b0f0SVladimir Oltean bool vid_is_dsa_8021q(u16 vid)
1371f66b0f0SVladimir Oltean {
1381f66b0f0SVladimir Oltean 	return ((vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX ||
1391f66b0f0SVladimir Oltean 		(vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_TX);
1401f66b0f0SVladimir Oltean }
1411f66b0f0SVladimir Oltean EXPORT_SYMBOL_GPL(vid_is_dsa_8021q);
1421f66b0f0SVladimir Oltean 
1435f33183bSVladimir Oltean /* If @enabled is true, installs @vid with @flags into the switch port's HW
1445f33183bSVladimir Oltean  * filter.
1455f33183bSVladimir Oltean  * If @enabled is false, deletes @vid (ignores @flags) from the port. Had the
1465f33183bSVladimir Oltean  * user explicitly configured this @vid through the bridge core, then the @vid
1475f33183bSVladimir Oltean  * is installed again, but this time with the flags from the bridge layer.
1485f33183bSVladimir Oltean  */
1495899ee36SVladimir Oltean static int dsa_8021q_vid_apply(struct dsa_8021q_context *ctx, int port, u16 vid,
1505f33183bSVladimir Oltean 			       u16 flags, bool enabled)
1515f33183bSVladimir Oltean {
1525899ee36SVladimir Oltean 	struct dsa_port *dp = dsa_to_port(ctx->ds, port);
1535f33183bSVladimir Oltean 
1545f33183bSVladimir Oltean 	if (enabled)
1555899ee36SVladimir Oltean 		return ctx->ops->vlan_add(ctx->ds, dp->index, vid, flags);
1565f33183bSVladimir Oltean 
1575899ee36SVladimir Oltean 	return ctx->ops->vlan_del(ctx->ds, dp->index, vid);
1585f33183bSVladimir Oltean }
1595f33183bSVladimir Oltean 
160f9bbe447SVladimir Oltean /* RX VLAN tagging (left) and TX VLAN tagging (right) setup shown for a single
161f9bbe447SVladimir Oltean  * front-panel switch port (here swp0).
162f9bbe447SVladimir Oltean  *
163f9bbe447SVladimir Oltean  * Port identification through VLAN (802.1Q) tags has different requirements
164f9bbe447SVladimir Oltean  * for it to work effectively:
165f9bbe447SVladimir Oltean  *  - On RX (ingress from network): each front-panel port must have a pvid
166f9bbe447SVladimir Oltean  *    that uniquely identifies it, and the egress of this pvid must be tagged
167f9bbe447SVladimir Oltean  *    towards the CPU port, so that software can recover the source port based
168f9bbe447SVladimir Oltean  *    on the VID in the frame. But this would only work for standalone ports;
169f9bbe447SVladimir Oltean  *    if bridged, this VLAN setup would break autonomous forwarding and would
170f9bbe447SVladimir Oltean  *    force all switched traffic to pass through the CPU. So we must also make
171f9bbe447SVladimir Oltean  *    the other front-panel ports members of this VID we're adding, albeit
172f9bbe447SVladimir Oltean  *    we're not making it their PVID (they'll still have their own).
173f9bbe447SVladimir Oltean  *    By the way - just because we're installing the same VID in multiple
174f9bbe447SVladimir Oltean  *    switch ports doesn't mean that they'll start to talk to one another, even
175f9bbe447SVladimir Oltean  *    while not bridged: the final forwarding decision is still an AND between
176f9bbe447SVladimir Oltean  *    the L2 forwarding information (which is limiting forwarding in this case)
177f9bbe447SVladimir Oltean  *    and the VLAN-based restrictions (of which there are none in this case,
178f9bbe447SVladimir Oltean  *    since all ports are members).
179f9bbe447SVladimir Oltean  *  - On TX (ingress from CPU and towards network) we are faced with a problem.
180f9bbe447SVladimir Oltean  *    If we were to tag traffic (from within DSA) with the port's pvid, all
181f9bbe447SVladimir Oltean  *    would be well, assuming the switch ports were standalone. Frames would
182f9bbe447SVladimir Oltean  *    have no choice but to be directed towards the correct front-panel port.
183f9bbe447SVladimir Oltean  *    But because we also want the RX VLAN to not break bridging, then
184f9bbe447SVladimir Oltean  *    inevitably that means that we have to give them a choice (of what
185f9bbe447SVladimir Oltean  *    front-panel port to go out on), and therefore we cannot steer traffic
186f9bbe447SVladimir Oltean  *    based on the RX VID. So what we do is simply install one more VID on the
187f9bbe447SVladimir Oltean  *    front-panel and CPU ports, and profit off of the fact that steering will
188f9bbe447SVladimir Oltean  *    work just by virtue of the fact that there is only one other port that's
189f9bbe447SVladimir Oltean  *    a member of the VID we're tagging the traffic with - the desired one.
190f9bbe447SVladimir Oltean  *
191f9bbe447SVladimir Oltean  * So at the end, each front-panel port will have one RX VID (also the PVID),
192f9bbe447SVladimir Oltean  * the RX VID of all other front-panel ports, and one TX VID. Whereas the CPU
193f9bbe447SVladimir Oltean  * port will have the RX and TX VIDs of all front-panel ports, and on top of
194f9bbe447SVladimir Oltean  * that, is also tagged-input and tagged-output (VLAN trunk).
195f9bbe447SVladimir Oltean  *
196f9bbe447SVladimir Oltean  *               CPU port                               CPU port
197f9bbe447SVladimir Oltean  * +-------------+-----+-------------+    +-------------+-----+-------------+
198f9bbe447SVladimir Oltean  * |  RX VID     |     |             |    |  TX VID     |     |             |
199f9bbe447SVladimir Oltean  * |  of swp0    |     |             |    |  of swp0    |     |             |
200f9bbe447SVladimir Oltean  * |             +-----+             |    |             +-----+             |
201f9bbe447SVladimir Oltean  * |                ^ T              |    |                | Tagged         |
202f9bbe447SVladimir Oltean  * |                |                |    |                | ingress        |
203f9bbe447SVladimir Oltean  * |    +-------+---+---+-------+    |    |    +-----------+                |
204f9bbe447SVladimir Oltean  * |    |       |       |       |    |    |    | Untagged                   |
205f9bbe447SVladimir Oltean  * |    |     U v     U v     U v    |    |    v egress                     |
206f9bbe447SVladimir Oltean  * | +-----+ +-----+ +-----+ +-----+ |    | +-----+ +-----+ +-----+ +-----+ |
207f9bbe447SVladimir Oltean  * | |     | |     | |     | |     | |    | |     | |     | |     | |     | |
208f9bbe447SVladimir Oltean  * | |PVID | |     | |     | |     | |    | |     | |     | |     | |     | |
209f9bbe447SVladimir Oltean  * +-+-----+-+-----+-+-----+-+-----+-+    +-+-----+-+-----+-+-----+-+-----+-+
210f9bbe447SVladimir Oltean  *   swp0    swp1    swp2    swp3           swp0    swp1    swp2    swp3
211f9bbe447SVladimir Oltean  */
2125899ee36SVladimir Oltean static int dsa_8021q_setup_port(struct dsa_8021q_context *ctx, int port,
2135899ee36SVladimir Oltean 				bool enabled)
214f9bbe447SVladimir Oltean {
2155899ee36SVladimir Oltean 	int upstream = dsa_upstream_port(ctx->ds, port);
2165899ee36SVladimir Oltean 	u16 rx_vid = dsa_8021q_rx_vid(ctx->ds, port);
2175899ee36SVladimir Oltean 	u16 tx_vid = dsa_8021q_tx_vid(ctx->ds, port);
218f9bbe447SVladimir Oltean 	int i, err;
219f9bbe447SVladimir Oltean 
220f9bbe447SVladimir Oltean 	/* The CPU port is implicitly configured by
221f9bbe447SVladimir Oltean 	 * configuring the front-panel ports
222f9bbe447SVladimir Oltean 	 */
2235899ee36SVladimir Oltean 	if (!dsa_is_user_port(ctx->ds, port))
224f9bbe447SVladimir Oltean 		return 0;
225f9bbe447SVladimir Oltean 
226f9bbe447SVladimir Oltean 	/* Add this user port's RX VID to the membership list of all others
227f9bbe447SVladimir Oltean 	 * (including itself). This is so that bridging will not be hindered.
228f9bbe447SVladimir Oltean 	 * L2 forwarding rules still take precedence when there are no VLAN
229f9bbe447SVladimir Oltean 	 * restrictions, so there are no concerns about leaking traffic.
230f9bbe447SVladimir Oltean 	 */
2315899ee36SVladimir Oltean 	for (i = 0; i < ctx->ds->num_ports; i++) {
232f9bbe447SVladimir Oltean 		u16 flags;
233f9bbe447SVladimir Oltean 
234f9bbe447SVladimir Oltean 		if (i == upstream)
235d34d2baaSIoana Ciornei 			continue;
236f9bbe447SVladimir Oltean 		else if (i == port)
237f9bbe447SVladimir Oltean 			/* The RX VID is pvid on this port */
238f9bbe447SVladimir Oltean 			flags = BRIDGE_VLAN_INFO_UNTAGGED |
239f9bbe447SVladimir Oltean 				BRIDGE_VLAN_INFO_PVID;
240f9bbe447SVladimir Oltean 		else
241f9bbe447SVladimir Oltean 			/* The RX VID is a regular VLAN on all others */
242f9bbe447SVladimir Oltean 			flags = BRIDGE_VLAN_INFO_UNTAGGED;
243f9bbe447SVladimir Oltean 
2445899ee36SVladimir Oltean 		err = dsa_8021q_vid_apply(ctx, i, rx_vid, flags, enabled);
245f9bbe447SVladimir Oltean 		if (err) {
2465899ee36SVladimir Oltean 			dev_err(ctx->ds->dev,
2475899ee36SVladimir Oltean 				"Failed to apply RX VID %d to port %d: %d\n",
248f9bbe447SVladimir Oltean 				rx_vid, port, err);
249f9bbe447SVladimir Oltean 			return err;
250f9bbe447SVladimir Oltean 		}
251f9bbe447SVladimir Oltean 	}
252d34d2baaSIoana Ciornei 
253d34d2baaSIoana Ciornei 	/* CPU port needs to see this port's RX VID
254d34d2baaSIoana Ciornei 	 * as tagged egress.
255d34d2baaSIoana Ciornei 	 */
2565899ee36SVladimir Oltean 	err = dsa_8021q_vid_apply(ctx, upstream, rx_vid, 0, enabled);
257d34d2baaSIoana Ciornei 	if (err) {
2585899ee36SVladimir Oltean 		dev_err(ctx->ds->dev,
2595899ee36SVladimir Oltean 			"Failed to apply RX VID %d to port %d: %d\n",
260d34d2baaSIoana Ciornei 			rx_vid, port, err);
261d34d2baaSIoana Ciornei 		return err;
262d34d2baaSIoana Ciornei 	}
263d34d2baaSIoana Ciornei 
264f9bbe447SVladimir Oltean 	/* Finally apply the TX VID on this port and on the CPU port */
2655899ee36SVladimir Oltean 	err = dsa_8021q_vid_apply(ctx, port, tx_vid, BRIDGE_VLAN_INFO_UNTAGGED,
2665f33183bSVladimir Oltean 				  enabled);
267f9bbe447SVladimir Oltean 	if (err) {
2685899ee36SVladimir Oltean 		dev_err(ctx->ds->dev,
2695899ee36SVladimir Oltean 			"Failed to apply TX VID %d on port %d: %d\n",
270f9bbe447SVladimir Oltean 			tx_vid, port, err);
271f9bbe447SVladimir Oltean 		return err;
272f9bbe447SVladimir Oltean 	}
2735899ee36SVladimir Oltean 	err = dsa_8021q_vid_apply(ctx, upstream, tx_vid, 0, enabled);
274f9bbe447SVladimir Oltean 	if (err) {
2755899ee36SVladimir Oltean 		dev_err(ctx->ds->dev,
2765899ee36SVladimir Oltean 			"Failed to apply TX VID %d on port %d: %d\n",
277f9bbe447SVladimir Oltean 			tx_vid, upstream, err);
278f9bbe447SVladimir Oltean 		return err;
279f9bbe447SVladimir Oltean 	}
280f9bbe447SVladimir Oltean 
2815f33183bSVladimir Oltean 	return err;
282f9bbe447SVladimir Oltean }
2837e092af2SVladimir Oltean 
2845899ee36SVladimir Oltean int dsa_8021q_setup(struct dsa_8021q_context *ctx, bool enabled)
2857e092af2SVladimir Oltean {
2867e092af2SVladimir Oltean 	int rc, port;
2877e092af2SVladimir Oltean 
2885899ee36SVladimir Oltean 	for (port = 0; port < ctx->ds->num_ports; port++) {
2895899ee36SVladimir Oltean 		rc = dsa_8021q_setup_port(ctx, port, enabled);
2907e092af2SVladimir Oltean 		if (rc < 0) {
2915899ee36SVladimir Oltean 			dev_err(ctx->ds->dev,
2927e092af2SVladimir Oltean 				"Failed to setup VLAN tagging for port %d: %d\n",
2937e092af2SVladimir Oltean 				port, rc);
2947e092af2SVladimir Oltean 			return rc;
2957e092af2SVladimir Oltean 		}
2967e092af2SVladimir Oltean 	}
2977e092af2SVladimir Oltean 
2987e092af2SVladimir Oltean 	return 0;
2997e092af2SVladimir Oltean }
3007e092af2SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_setup);
301f9bbe447SVladimir Oltean 
3025899ee36SVladimir Oltean static int dsa_8021q_crosschip_link_apply(struct dsa_8021q_context *ctx,
3035899ee36SVladimir Oltean 					  int port,
3045899ee36SVladimir Oltean 					  struct dsa_8021q_context *other_ctx,
305ac02a451SVladimir Oltean 					  int other_port, bool enabled)
306ac02a451SVladimir Oltean {
3075899ee36SVladimir Oltean 	u16 rx_vid = dsa_8021q_rx_vid(ctx->ds, port);
308ac02a451SVladimir Oltean 
309ac02a451SVladimir Oltean 	/* @rx_vid of local @ds port @port goes to @other_port of
310ac02a451SVladimir Oltean 	 * @other_ds
311ac02a451SVladimir Oltean 	 */
3125899ee36SVladimir Oltean 	return dsa_8021q_vid_apply(other_ctx, other_port, rx_vid,
313ac02a451SVladimir Oltean 				   BRIDGE_VLAN_INFO_UNTAGGED, enabled);
314ac02a451SVladimir Oltean }
315ac02a451SVladimir Oltean 
3165899ee36SVladimir Oltean static int dsa_8021q_crosschip_link_add(struct dsa_8021q_context *ctx, int port,
3175899ee36SVladimir Oltean 					struct dsa_8021q_context *other_ctx,
3185899ee36SVladimir Oltean 					int other_port)
319ac02a451SVladimir Oltean {
320ac02a451SVladimir Oltean 	struct dsa_8021q_crosschip_link *c;
321ac02a451SVladimir Oltean 
3225899ee36SVladimir Oltean 	list_for_each_entry(c, &ctx->crosschip_links, list) {
3235899ee36SVladimir Oltean 		if (c->port == port && c->other_ctx == other_ctx &&
324ac02a451SVladimir Oltean 		    c->other_port == other_port) {
325ac02a451SVladimir Oltean 			refcount_inc(&c->refcount);
326ac02a451SVladimir Oltean 			return 0;
327ac02a451SVladimir Oltean 		}
328ac02a451SVladimir Oltean 	}
329ac02a451SVladimir Oltean 
3305899ee36SVladimir Oltean 	dev_dbg(ctx->ds->dev,
3315899ee36SVladimir Oltean 		"adding crosschip link from port %d to %s port %d\n",
3325899ee36SVladimir Oltean 		port, dev_name(other_ctx->ds->dev), other_port);
333ac02a451SVladimir Oltean 
334ac02a451SVladimir Oltean 	c = kzalloc(sizeof(*c), GFP_KERNEL);
335ac02a451SVladimir Oltean 	if (!c)
336ac02a451SVladimir Oltean 		return -ENOMEM;
337ac02a451SVladimir Oltean 
338ac02a451SVladimir Oltean 	c->port = port;
3395899ee36SVladimir Oltean 	c->other_ctx = other_ctx;
340ac02a451SVladimir Oltean 	c->other_port = other_port;
341ac02a451SVladimir Oltean 	refcount_set(&c->refcount, 1);
342ac02a451SVladimir Oltean 
3435899ee36SVladimir Oltean 	list_add(&c->list, &ctx->crosschip_links);
344ac02a451SVladimir Oltean 
345ac02a451SVladimir Oltean 	return 0;
346ac02a451SVladimir Oltean }
347ac02a451SVladimir Oltean 
3485899ee36SVladimir Oltean static void dsa_8021q_crosschip_link_del(struct dsa_8021q_context *ctx,
349ac02a451SVladimir Oltean 					 struct dsa_8021q_crosschip_link *c,
350ac02a451SVladimir Oltean 					 bool *keep)
351ac02a451SVladimir Oltean {
352ac02a451SVladimir Oltean 	*keep = !refcount_dec_and_test(&c->refcount);
353ac02a451SVladimir Oltean 
354ac02a451SVladimir Oltean 	if (*keep)
355ac02a451SVladimir Oltean 		return;
356ac02a451SVladimir Oltean 
3575899ee36SVladimir Oltean 	dev_dbg(ctx->ds->dev,
358ac02a451SVladimir Oltean 		"deleting crosschip link from port %d to %s port %d\n",
3595899ee36SVladimir Oltean 		c->port, dev_name(c->other_ctx->ds->dev), c->other_port);
360ac02a451SVladimir Oltean 
361ac02a451SVladimir Oltean 	list_del(&c->list);
362ac02a451SVladimir Oltean 	kfree(c);
363ac02a451SVladimir Oltean }
364ac02a451SVladimir Oltean 
365ac02a451SVladimir Oltean /* Make traffic from local port @port be received by remote port @other_port.
366ac02a451SVladimir Oltean  * This means that our @rx_vid needs to be installed on @other_ds's upstream
367ac02a451SVladimir Oltean  * and user ports. The user ports should be egress-untagged so that they can
368ac02a451SVladimir Oltean  * pop the dsa_8021q VLAN. But the @other_upstream can be either egress-tagged
369ac02a451SVladimir Oltean  * or untagged: it doesn't matter, since it should never egress a frame having
370ac02a451SVladimir Oltean  * our @rx_vid.
371ac02a451SVladimir Oltean  */
3725899ee36SVladimir Oltean int dsa_8021q_crosschip_bridge_join(struct dsa_8021q_context *ctx, int port,
3735899ee36SVladimir Oltean 				    struct dsa_8021q_context *other_ctx,
3745899ee36SVladimir Oltean 				    int other_port)
375ac02a451SVladimir Oltean {
376ac02a451SVladimir Oltean 	/* @other_upstream is how @other_ds reaches us. If we are part
377ac02a451SVladimir Oltean 	 * of disjoint trees, then we are probably connected through
378ac02a451SVladimir Oltean 	 * our CPU ports. If we're part of the same tree though, we should
379ac02a451SVladimir Oltean 	 * probably use dsa_towards_port.
380ac02a451SVladimir Oltean 	 */
3815899ee36SVladimir Oltean 	int other_upstream = dsa_upstream_port(other_ctx->ds, other_port);
382ac02a451SVladimir Oltean 	int rc;
383ac02a451SVladimir Oltean 
3845899ee36SVladimir Oltean 	rc = dsa_8021q_crosschip_link_add(ctx, port, other_ctx, other_port);
385ac02a451SVladimir Oltean 	if (rc)
386ac02a451SVladimir Oltean 		return rc;
387ac02a451SVladimir Oltean 
3885899ee36SVladimir Oltean 	rc = dsa_8021q_crosschip_link_apply(ctx, port, other_ctx,
389ac02a451SVladimir Oltean 					    other_port, true);
390ac02a451SVladimir Oltean 	if (rc)
391ac02a451SVladimir Oltean 		return rc;
392ac02a451SVladimir Oltean 
3935899ee36SVladimir Oltean 	rc = dsa_8021q_crosschip_link_add(ctx, port, other_ctx, other_upstream);
394ac02a451SVladimir Oltean 	if (rc)
395ac02a451SVladimir Oltean 		return rc;
396ac02a451SVladimir Oltean 
3975899ee36SVladimir Oltean 	return dsa_8021q_crosschip_link_apply(ctx, port, other_ctx,
398ac02a451SVladimir Oltean 					      other_upstream, true);
399ac02a451SVladimir Oltean }
400ac02a451SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_join);
401ac02a451SVladimir Oltean 
4025899ee36SVladimir Oltean int dsa_8021q_crosschip_bridge_leave(struct dsa_8021q_context *ctx, int port,
4035899ee36SVladimir Oltean 				     struct dsa_8021q_context *other_ctx,
4045899ee36SVladimir Oltean 				     int other_port)
405ac02a451SVladimir Oltean {
4065899ee36SVladimir Oltean 	int other_upstream = dsa_upstream_port(other_ctx->ds, other_port);
407ac02a451SVladimir Oltean 	struct dsa_8021q_crosschip_link *c, *n;
408ac02a451SVladimir Oltean 
4095899ee36SVladimir Oltean 	list_for_each_entry_safe(c, n, &ctx->crosschip_links, list) {
4105899ee36SVladimir Oltean 		if (c->port == port && c->other_ctx == other_ctx &&
411ac02a451SVladimir Oltean 		    (c->other_port == other_port ||
412ac02a451SVladimir Oltean 		     c->other_port == other_upstream)) {
4135899ee36SVladimir Oltean 			struct dsa_8021q_context *other_ctx = c->other_ctx;
414ac02a451SVladimir Oltean 			int other_port = c->other_port;
415ac02a451SVladimir Oltean 			bool keep;
416ac02a451SVladimir Oltean 			int rc;
417ac02a451SVladimir Oltean 
4185899ee36SVladimir Oltean 			dsa_8021q_crosschip_link_del(ctx, c, &keep);
419ac02a451SVladimir Oltean 			if (keep)
420ac02a451SVladimir Oltean 				continue;
421ac02a451SVladimir Oltean 
4225899ee36SVladimir Oltean 			rc = dsa_8021q_crosschip_link_apply(ctx, port,
4235899ee36SVladimir Oltean 							    other_ctx,
424ac02a451SVladimir Oltean 							    other_port,
425ac02a451SVladimir Oltean 							    false);
426ac02a451SVladimir Oltean 			if (rc)
427ac02a451SVladimir Oltean 				return rc;
428ac02a451SVladimir Oltean 		}
429ac02a451SVladimir Oltean 	}
430ac02a451SVladimir Oltean 
431ac02a451SVladimir Oltean 	return 0;
432ac02a451SVladimir Oltean }
433ac02a451SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_leave);
434ac02a451SVladimir Oltean 
435f9bbe447SVladimir Oltean struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
436f9bbe447SVladimir Oltean 			       u16 tpid, u16 tci)
437f9bbe447SVladimir Oltean {
438f9bbe447SVladimir Oltean 	/* skb->data points at skb_mac_header, which
439f9bbe447SVladimir Oltean 	 * is fine for vlan_insert_tag.
440f9bbe447SVladimir Oltean 	 */
441f9bbe447SVladimir Oltean 	return vlan_insert_tag(skb, htons(tpid), tci);
442f9bbe447SVladimir Oltean }
443f9bbe447SVladimir Oltean EXPORT_SYMBOL_GPL(dsa_8021q_xmit);
444f9bbe447SVladimir Oltean 
445f9bbe447SVladimir Oltean MODULE_LICENSE("GPL v2");
446