summaryrefslogtreecommitdiff
path: root/net/dsa/tag_lan9303.c
blob: e526c8967b98b0e29784d0687e8ba715dd916033 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
 * Copyright (C) 2017 Pengutronix, Juergen Borleis <jbe@pengutronix.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
#include <linux/dsa/lan9303.h>
#include <linux/etherdevice.h>
#include <linux/list.h>
#include <linux/slab.h>

#include "dsa_priv.h"

/* To define the outgoing port and to discover the incoming port a regular
 * VLAN tag is used by the LAN9303. But its VID meaning is 'special':
 *
 *       Dest MAC       Src MAC        TAG    Type
 * ...| 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 | 1 2 |...
 *                                |<------->|
 * TAG:
 *    |<------------->|
 *    |  1  2 | 3  4  |
 *      TPID    VID
 *     0x8100
 *
 * VID bit 3 indicates a request for an ALR lookup.
 *
 * If VID bit 3 is zero, then bits 0 and 1 specify the destination port
 * (0, 1, 2) or broadcast (3) or the source port (1, 2).
 *
 * VID bit 4 is used to specify if the STP port state should be overridden.
 * Required when no forwarding between the external ports should happen.
 */

#define LAN9303_TAG_LEN 4
# define LAN9303_TAG_TX_USE_ALR BIT(3)
# define LAN9303_TAG_TX_STP_OVERRIDE BIT(4)

/* Decide whether to transmit using ALR lookup, or transmit directly to
 * port using tag. ALR learning is performed only when using ALR lookup.
 * If the two external ports are bridged and the frame is unicast,
 * then use ALR lookup to allow ALR learning on CPU port.
 * Otherwise transmit directly to port with STP state override.
 * See also: lan9303_separate_ports() and lan9303.pdf 6.4.10.1
 */
static int lan9303_xmit_use_arl(struct dsa_port *dp, u8 *dest_addr)
{
	struct lan9303 *chip = dp->ds->priv;

	return chip->is_bridged && !is_multicast_ether_addr(dest_addr);
}

static struct sk_buff *lan9303_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct dsa_port *dp = dsa_slave_to_port(dev);
	u16 *lan9303_tag;

	/* insert a special VLAN tag between the MAC addresses
	 * and the current ethertype field.
	 */
	if (skb_cow_head(skb, LAN9303_TAG_LEN) < 0) {
		dev_dbg(&dev->dev,
			"Cannot make room for the special tag. Dropping packet\n");
		return NULL;
	}

	/* provide 'LAN9303_TAG_LEN' bytes additional space */
	skb_push(skb, LAN9303_TAG_LEN);

	/* make room between MACs and Ether-Type */
	memmove(skb->data, skb->data + LAN9303_TAG_LEN, 2 * ETH_ALEN);

	lan9303_tag = (u16 *)(skb->data + 2 * ETH_ALEN);
	lan9303_tag[0] = htons(ETH_P_8021Q);
	lan9303_tag[1] = lan9303_xmit_use_arl(dp, skb->data) ?
				LAN9303_TAG_TX_USE_ALR :
				dp->index | LAN9303_TAG_TX_STP_OVERRIDE;
	lan9303_tag[1] = htons(lan9303_tag[1]);

	return skb;
}

static struct sk_buff *lan9303_rcv(struct sk_buff *skb, struct net_device *dev,
			struct packet_type *pt)
{
	u16 *lan9303_tag;
	unsigned int source_port;

	if (unlikely(!pskb_may_pull(skb, LAN9303_TAG_LEN))) {
		dev_warn_ratelimited(&dev->dev,
				     "Dropping packet, cannot pull\n");
		return NULL;
	}

	/* '->data' points into the middle of our special VLAN tag information:
	 *
	 * ~ MAC src   | 0x81 | 0x00 | 0xyy | 0xzz | ether type
	 *                           ^
	 *                        ->data
	 */
	lan9303_tag = (u16 *)(skb->data - 2);

	if (lan9303_tag[0] != htons(ETH_P_8021Q)) {
		dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid VLAN marker\n");
		return NULL;
	}

	source_port = ntohs(lan9303_tag[1]) & 0x3;

	skb->dev = dsa_master_find_slave(dev, 0, source_port);
	if (!skb->dev) {
		dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid source port\n");
		return NULL;
	}

	/* remove the special VLAN tag between the MAC addresses
	 * and the current ethertype field.
	 */
	skb_pull_rcsum(skb, 2 + 2);
	memmove(skb->data - ETH_HLEN, skb->data - (ETH_HLEN + LAN9303_TAG_LEN),
		2 * ETH_ALEN);
	skb->offload_fwd_mark = !ether_addr_equal(skb->data - ETH_HLEN,
						  eth_stp_addr);

	return skb;
}

const struct dsa_device_ops lan9303_netdev_ops = {
	.xmit = lan9303_xmit,
	.rcv = lan9303_rcv,
};