1111cf1abSIlias Apalodimas // SPDX-License-Identifier: GPL-2.0
2111cf1abSIlias Apalodimas /*
3111cf1abSIlias Apalodimas  * Texas Instruments switchdev Driver
4111cf1abSIlias Apalodimas  *
5111cf1abSIlias Apalodimas  * Copyright (C) 2019 Texas Instruments
6111cf1abSIlias Apalodimas  *
7111cf1abSIlias Apalodimas  */
8111cf1abSIlias Apalodimas 
9111cf1abSIlias Apalodimas #include <linux/etherdevice.h>
10111cf1abSIlias Apalodimas #include <linux/if_bridge.h>
11111cf1abSIlias Apalodimas #include <linux/netdevice.h>
12111cf1abSIlias Apalodimas #include <linux/workqueue.h>
13111cf1abSIlias Apalodimas #include <net/switchdev.h>
14111cf1abSIlias Apalodimas 
15111cf1abSIlias Apalodimas #include "cpsw.h"
16111cf1abSIlias Apalodimas #include "cpsw_ale.h"
17111cf1abSIlias Apalodimas #include "cpsw_priv.h"
18111cf1abSIlias Apalodimas #include "cpsw_switchdev.h"
19111cf1abSIlias Apalodimas 
20111cf1abSIlias Apalodimas struct cpsw_switchdev_event_work {
21111cf1abSIlias Apalodimas 	struct work_struct work;
22111cf1abSIlias Apalodimas 	struct switchdev_notifier_fdb_info fdb_info;
23111cf1abSIlias Apalodimas 	struct cpsw_priv *priv;
24111cf1abSIlias Apalodimas 	unsigned long event;
25111cf1abSIlias Apalodimas };
26111cf1abSIlias Apalodimas 
cpsw_port_stp_state_set(struct cpsw_priv * priv,u8 state)27bae33f2bSVladimir Oltean static int cpsw_port_stp_state_set(struct cpsw_priv *priv, u8 state)
28111cf1abSIlias Apalodimas {
29111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
30111cf1abSIlias Apalodimas 	u8 cpsw_state;
31111cf1abSIlias Apalodimas 	int ret = 0;
32111cf1abSIlias Apalodimas 
33111cf1abSIlias Apalodimas 	switch (state) {
34111cf1abSIlias Apalodimas 	case BR_STATE_FORWARDING:
35111cf1abSIlias Apalodimas 		cpsw_state = ALE_PORT_STATE_FORWARD;
36111cf1abSIlias Apalodimas 		break;
37111cf1abSIlias Apalodimas 	case BR_STATE_LEARNING:
38111cf1abSIlias Apalodimas 		cpsw_state = ALE_PORT_STATE_LEARN;
39111cf1abSIlias Apalodimas 		break;
40111cf1abSIlias Apalodimas 	case BR_STATE_DISABLED:
41111cf1abSIlias Apalodimas 		cpsw_state = ALE_PORT_STATE_DISABLE;
42111cf1abSIlias Apalodimas 		break;
43111cf1abSIlias Apalodimas 	case BR_STATE_LISTENING:
44111cf1abSIlias Apalodimas 	case BR_STATE_BLOCKING:
45111cf1abSIlias Apalodimas 		cpsw_state = ALE_PORT_STATE_BLOCK;
46111cf1abSIlias Apalodimas 		break;
47111cf1abSIlias Apalodimas 	default:
48111cf1abSIlias Apalodimas 		return -EOPNOTSUPP;
49111cf1abSIlias Apalodimas 	}
50111cf1abSIlias Apalodimas 
51111cf1abSIlias Apalodimas 	ret = cpsw_ale_control_set(cpsw->ale, priv->emac_port,
52111cf1abSIlias Apalodimas 				   ALE_PORT_STATE, cpsw_state);
53111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "ale state: %u\n", cpsw_state);
54111cf1abSIlias Apalodimas 
55111cf1abSIlias Apalodimas 	return ret;
56111cf1abSIlias Apalodimas }
57111cf1abSIlias Apalodimas 
cpsw_port_attr_br_flags_set(struct cpsw_priv * priv,struct net_device * orig_dev,struct switchdev_brport_flags flags)58111cf1abSIlias Apalodimas static int cpsw_port_attr_br_flags_set(struct cpsw_priv *priv,
59111cf1abSIlias Apalodimas 				       struct net_device *orig_dev,
60e18f4c18SVladimir Oltean 				       struct switchdev_brport_flags flags)
61111cf1abSIlias Apalodimas {
62111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
63e18f4c18SVladimir Oltean 
64e18f4c18SVladimir Oltean 	if (flags.mask & BR_MCAST_FLOOD) {
65111cf1abSIlias Apalodimas 		bool unreg_mcast_add = false;
66111cf1abSIlias Apalodimas 
67e18f4c18SVladimir Oltean 		if (flags.val & BR_MCAST_FLOOD)
68111cf1abSIlias Apalodimas 			unreg_mcast_add = true;
69e18f4c18SVladimir Oltean 
70111cf1abSIlias Apalodimas 		dev_dbg(priv->dev, "BR_MCAST_FLOOD: %d port %u\n",
71111cf1abSIlias Apalodimas 			unreg_mcast_add, priv->emac_port);
72111cf1abSIlias Apalodimas 
73111cf1abSIlias Apalodimas 		cpsw_ale_set_unreg_mcast(cpsw->ale, BIT(priv->emac_port),
74111cf1abSIlias Apalodimas 					 unreg_mcast_add);
75e18f4c18SVladimir Oltean 	}
76111cf1abSIlias Apalodimas 
77111cf1abSIlias Apalodimas 	return 0;
78111cf1abSIlias Apalodimas }
79111cf1abSIlias Apalodimas 
cpsw_port_attr_br_flags_pre_set(struct net_device * netdev,struct switchdev_brport_flags flags)80111cf1abSIlias Apalodimas static int cpsw_port_attr_br_flags_pre_set(struct net_device *netdev,
81e18f4c18SVladimir Oltean 					   struct switchdev_brport_flags flags)
82111cf1abSIlias Apalodimas {
83e18f4c18SVladimir Oltean 	if (flags.mask & ~(BR_LEARNING | BR_MCAST_FLOOD))
84111cf1abSIlias Apalodimas 		return -EINVAL;
85111cf1abSIlias Apalodimas 
86111cf1abSIlias Apalodimas 	return 0;
87111cf1abSIlias Apalodimas }
88111cf1abSIlias Apalodimas 
cpsw_port_attr_set(struct net_device * ndev,const void * ctx,const struct switchdev_attr * attr,struct netlink_ext_ack * extack)8969bfac96SVladimir Oltean static int cpsw_port_attr_set(struct net_device *ndev, const void *ctx,
904c08c586SVladimir Oltean 			      const struct switchdev_attr *attr,
914c08c586SVladimir Oltean 			      struct netlink_ext_ack *extack)
92111cf1abSIlias Apalodimas {
93111cf1abSIlias Apalodimas 	struct cpsw_priv *priv = netdev_priv(ndev);
94111cf1abSIlias Apalodimas 	int ret;
95111cf1abSIlias Apalodimas 
96111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "attr: id %u port: %u\n", attr->id, priv->emac_port);
97111cf1abSIlias Apalodimas 
98111cf1abSIlias Apalodimas 	switch (attr->id) {
99111cf1abSIlias Apalodimas 	case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
100bae33f2bSVladimir Oltean 		ret = cpsw_port_attr_br_flags_pre_set(ndev,
101111cf1abSIlias Apalodimas 						      attr->u.brport_flags);
102111cf1abSIlias Apalodimas 		break;
103111cf1abSIlias Apalodimas 	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
104bae33f2bSVladimir Oltean 		ret = cpsw_port_stp_state_set(priv, attr->u.stp_state);
105111cf1abSIlias Apalodimas 		dev_dbg(priv->dev, "stp state: %u\n", attr->u.stp_state);
106111cf1abSIlias Apalodimas 		break;
107111cf1abSIlias Apalodimas 	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
108bae33f2bSVladimir Oltean 		ret = cpsw_port_attr_br_flags_set(priv, attr->orig_dev,
109111cf1abSIlias Apalodimas 						  attr->u.brport_flags);
110111cf1abSIlias Apalodimas 		break;
111111cf1abSIlias Apalodimas 	default:
112111cf1abSIlias Apalodimas 		ret = -EOPNOTSUPP;
113111cf1abSIlias Apalodimas 		break;
114111cf1abSIlias Apalodimas 	}
115111cf1abSIlias Apalodimas 
116111cf1abSIlias Apalodimas 	return ret;
117111cf1abSIlias Apalodimas }
118111cf1abSIlias Apalodimas 
cpsw_get_pvid(struct cpsw_priv * priv)119111cf1abSIlias Apalodimas static u16 cpsw_get_pvid(struct cpsw_priv *priv)
120111cf1abSIlias Apalodimas {
121111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
122111cf1abSIlias Apalodimas 	u32 __iomem *port_vlan_reg;
123111cf1abSIlias Apalodimas 	u32 pvid;
124111cf1abSIlias Apalodimas 
125111cf1abSIlias Apalodimas 	if (priv->emac_port) {
126111cf1abSIlias Apalodimas 		int reg = CPSW2_PORT_VLAN;
127111cf1abSIlias Apalodimas 
128111cf1abSIlias Apalodimas 		if (cpsw->version == CPSW_VERSION_1)
129111cf1abSIlias Apalodimas 			reg = CPSW1_PORT_VLAN;
130111cf1abSIlias Apalodimas 		pvid = slave_read(cpsw->slaves + (priv->emac_port - 1), reg);
131111cf1abSIlias Apalodimas 	} else {
132111cf1abSIlias Apalodimas 		port_vlan_reg = &cpsw->host_port_regs->port_vlan;
133111cf1abSIlias Apalodimas 		pvid = readl(port_vlan_reg);
134111cf1abSIlias Apalodimas 	}
135111cf1abSIlias Apalodimas 
136111cf1abSIlias Apalodimas 	pvid = pvid & 0xfff;
137111cf1abSIlias Apalodimas 
138111cf1abSIlias Apalodimas 	return pvid;
139111cf1abSIlias Apalodimas }
140111cf1abSIlias Apalodimas 
cpsw_set_pvid(struct cpsw_priv * priv,u16 vid,bool cfi,u32 cos)141111cf1abSIlias Apalodimas static void cpsw_set_pvid(struct cpsw_priv *priv, u16 vid, bool cfi, u32 cos)
142111cf1abSIlias Apalodimas {
143111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
144111cf1abSIlias Apalodimas 	void __iomem *port_vlan_reg;
145111cf1abSIlias Apalodimas 	u32 pvid;
146111cf1abSIlias Apalodimas 
147111cf1abSIlias Apalodimas 	pvid = vid;
148111cf1abSIlias Apalodimas 	pvid |= cfi ? BIT(12) : 0;
149111cf1abSIlias Apalodimas 	pvid |= (cos & 0x7) << 13;
150111cf1abSIlias Apalodimas 
151111cf1abSIlias Apalodimas 	if (priv->emac_port) {
152111cf1abSIlias Apalodimas 		int reg = CPSW2_PORT_VLAN;
153111cf1abSIlias Apalodimas 
154111cf1abSIlias Apalodimas 		if (cpsw->version == CPSW_VERSION_1)
155111cf1abSIlias Apalodimas 			reg = CPSW1_PORT_VLAN;
156111cf1abSIlias Apalodimas 		/* no barrier */
157111cf1abSIlias Apalodimas 		slave_write(cpsw->slaves + (priv->emac_port - 1), pvid, reg);
158111cf1abSIlias Apalodimas 	} else {
159111cf1abSIlias Apalodimas 		/* CPU port */
160111cf1abSIlias Apalodimas 		port_vlan_reg = &cpsw->host_port_regs->port_vlan;
161111cf1abSIlias Apalodimas 		writel(pvid, port_vlan_reg);
162111cf1abSIlias Apalodimas 	}
163111cf1abSIlias Apalodimas }
164111cf1abSIlias Apalodimas 
cpsw_port_vlan_add(struct cpsw_priv * priv,bool untag,bool pvid,u16 vid,struct net_device * orig_dev)165111cf1abSIlias Apalodimas static int cpsw_port_vlan_add(struct cpsw_priv *priv, bool untag, bool pvid,
166111cf1abSIlias Apalodimas 			      u16 vid, struct net_device *orig_dev)
167111cf1abSIlias Apalodimas {
168111cf1abSIlias Apalodimas 	bool cpu_port = netif_is_bridge_master(orig_dev);
169111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
170111cf1abSIlias Apalodimas 	int unreg_mcast_mask = 0;
171111cf1abSIlias Apalodimas 	int reg_mcast_mask = 0;
172111cf1abSIlias Apalodimas 	int untag_mask = 0;
173111cf1abSIlias Apalodimas 	int port_mask;
174111cf1abSIlias Apalodimas 	int ret = 0;
175111cf1abSIlias Apalodimas 	u32 flags;
176111cf1abSIlias Apalodimas 
177111cf1abSIlias Apalodimas 	if (cpu_port) {
178111cf1abSIlias Apalodimas 		port_mask = BIT(HOST_PORT_NUM);
179111cf1abSIlias Apalodimas 		flags = orig_dev->flags;
180111cf1abSIlias Apalodimas 		unreg_mcast_mask = port_mask;
181111cf1abSIlias Apalodimas 	} else {
182111cf1abSIlias Apalodimas 		port_mask = BIT(priv->emac_port);
183111cf1abSIlias Apalodimas 		flags = priv->ndev->flags;
184111cf1abSIlias Apalodimas 	}
185111cf1abSIlias Apalodimas 
186111cf1abSIlias Apalodimas 	if (flags & IFF_MULTICAST)
187111cf1abSIlias Apalodimas 		reg_mcast_mask = port_mask;
188111cf1abSIlias Apalodimas 
189111cf1abSIlias Apalodimas 	if (untag)
190111cf1abSIlias Apalodimas 		untag_mask = port_mask;
191111cf1abSIlias Apalodimas 
192111cf1abSIlias Apalodimas 	ret = cpsw_ale_vlan_add_modify(cpsw->ale, vid, port_mask, untag_mask,
193111cf1abSIlias Apalodimas 				       reg_mcast_mask, unreg_mcast_mask);
194111cf1abSIlias Apalodimas 	if (ret) {
195111cf1abSIlias Apalodimas 		dev_err(priv->dev, "Unable to add vlan\n");
196111cf1abSIlias Apalodimas 		return ret;
197111cf1abSIlias Apalodimas 	}
198111cf1abSIlias Apalodimas 
199111cf1abSIlias Apalodimas 	if (cpu_port)
200111cf1abSIlias Apalodimas 		cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr,
201111cf1abSIlias Apalodimas 				   HOST_PORT_NUM, ALE_VLAN, vid);
202111cf1abSIlias Apalodimas 	if (!pvid)
203111cf1abSIlias Apalodimas 		return ret;
204111cf1abSIlias Apalodimas 
205111cf1abSIlias Apalodimas 	cpsw_set_pvid(priv, vid, 0, 0);
206111cf1abSIlias Apalodimas 
207111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "VID add: %s: vid:%u ports:%X\n",
208111cf1abSIlias Apalodimas 		priv->ndev->name, vid, port_mask);
209111cf1abSIlias Apalodimas 	return ret;
210111cf1abSIlias Apalodimas }
211111cf1abSIlias Apalodimas 
cpsw_port_vlan_del(struct cpsw_priv * priv,u16 vid,struct net_device * orig_dev)212111cf1abSIlias Apalodimas static int cpsw_port_vlan_del(struct cpsw_priv *priv, u16 vid,
213111cf1abSIlias Apalodimas 			      struct net_device *orig_dev)
214111cf1abSIlias Apalodimas {
215111cf1abSIlias Apalodimas 	bool cpu_port = netif_is_bridge_master(orig_dev);
216111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
217111cf1abSIlias Apalodimas 	int port_mask;
218111cf1abSIlias Apalodimas 	int ret = 0;
219111cf1abSIlias Apalodimas 
220111cf1abSIlias Apalodimas 	if (cpu_port)
221111cf1abSIlias Apalodimas 		port_mask = BIT(HOST_PORT_NUM);
222111cf1abSIlias Apalodimas 	else
223111cf1abSIlias Apalodimas 		port_mask = BIT(priv->emac_port);
224111cf1abSIlias Apalodimas 
22582882bd5SGrygorii Strashko 	ret = cpsw_ale_vlan_del_modify(cpsw->ale, vid, port_mask);
226111cf1abSIlias Apalodimas 	if (ret != 0)
227111cf1abSIlias Apalodimas 		return ret;
228111cf1abSIlias Apalodimas 
229111cf1abSIlias Apalodimas 	/* We don't care for the return value here, error is returned only if
230111cf1abSIlias Apalodimas 	 * the unicast entry is not present
231111cf1abSIlias Apalodimas 	 */
232111cf1abSIlias Apalodimas 	if (cpu_port)
233111cf1abSIlias Apalodimas 		cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr,
234111cf1abSIlias Apalodimas 				   HOST_PORT_NUM, ALE_VLAN, vid);
235111cf1abSIlias Apalodimas 
236111cf1abSIlias Apalodimas 	if (vid == cpsw_get_pvid(priv))
237111cf1abSIlias Apalodimas 		cpsw_set_pvid(priv, 0, 0, 0);
238111cf1abSIlias Apalodimas 
239111cf1abSIlias Apalodimas 	/* We don't care for the return value here, error is returned only if
240111cf1abSIlias Apalodimas 	 * the multicast entry is not present
241111cf1abSIlias Apalodimas 	 */
242111cf1abSIlias Apalodimas 	cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast,
243111cf1abSIlias Apalodimas 			   port_mask, ALE_VLAN, vid);
244111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "VID del: %s: vid:%u ports:%X\n",
245111cf1abSIlias Apalodimas 		priv->ndev->name, vid, port_mask);
246111cf1abSIlias Apalodimas 
247111cf1abSIlias Apalodimas 	return ret;
248111cf1abSIlias Apalodimas }
249111cf1abSIlias Apalodimas 
cpsw_port_vlans_add(struct cpsw_priv * priv,const struct switchdev_obj_port_vlan * vlan)250111cf1abSIlias Apalodimas static int cpsw_port_vlans_add(struct cpsw_priv *priv,
251ffb68fc5SVladimir Oltean 			       const struct switchdev_obj_port_vlan *vlan)
252111cf1abSIlias Apalodimas {
253111cf1abSIlias Apalodimas 	bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
254111cf1abSIlias Apalodimas 	struct net_device *orig_dev = vlan->obj.orig_dev;
255111cf1abSIlias Apalodimas 	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
256111cf1abSIlias Apalodimas 
257111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "VID add: %s: vid:%u flags:%X\n",
258b7a9e0daSVladimir Oltean 		priv->ndev->name, vlan->vid, vlan->flags);
259111cf1abSIlias Apalodimas 
260b7a9e0daSVladimir Oltean 	return cpsw_port_vlan_add(priv, untag, pvid, vlan->vid, orig_dev);
261111cf1abSIlias Apalodimas }
262111cf1abSIlias Apalodimas 
cpsw_port_mdb_add(struct cpsw_priv * priv,struct switchdev_obj_port_mdb * mdb)263111cf1abSIlias Apalodimas static int cpsw_port_mdb_add(struct cpsw_priv *priv,
264ffb68fc5SVladimir Oltean 			     struct switchdev_obj_port_mdb *mdb)
265111cf1abSIlias Apalodimas 
266111cf1abSIlias Apalodimas {
267111cf1abSIlias Apalodimas 	struct net_device *orig_dev = mdb->obj.orig_dev;
268111cf1abSIlias Apalodimas 	bool cpu_port = netif_is_bridge_master(orig_dev);
269111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
270111cf1abSIlias Apalodimas 	int port_mask;
271111cf1abSIlias Apalodimas 	int err;
272111cf1abSIlias Apalodimas 
273111cf1abSIlias Apalodimas 	if (cpu_port)
274111cf1abSIlias Apalodimas 		port_mask = BIT(HOST_PORT_NUM);
275111cf1abSIlias Apalodimas 	else
276111cf1abSIlias Apalodimas 		port_mask = BIT(priv->emac_port);
277111cf1abSIlias Apalodimas 
278111cf1abSIlias Apalodimas 	err = cpsw_ale_add_mcast(cpsw->ale, mdb->addr, port_mask,
279111cf1abSIlias Apalodimas 				 ALE_VLAN, mdb->vid, 0);
280111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "MDB add: %s: vid %u:%pM  ports: %X\n",
281111cf1abSIlias Apalodimas 		priv->ndev->name, mdb->vid, mdb->addr, port_mask);
282111cf1abSIlias Apalodimas 
283111cf1abSIlias Apalodimas 	return err;
284111cf1abSIlias Apalodimas }
285111cf1abSIlias Apalodimas 
cpsw_port_mdb_del(struct cpsw_priv * priv,struct switchdev_obj_port_mdb * mdb)286111cf1abSIlias Apalodimas static int cpsw_port_mdb_del(struct cpsw_priv *priv,
287111cf1abSIlias Apalodimas 			     struct switchdev_obj_port_mdb *mdb)
288111cf1abSIlias Apalodimas 
289111cf1abSIlias Apalodimas {
290111cf1abSIlias Apalodimas 	struct net_device *orig_dev = mdb->obj.orig_dev;
291111cf1abSIlias Apalodimas 	bool cpu_port = netif_is_bridge_master(orig_dev);
292111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
293111cf1abSIlias Apalodimas 	int del_mask;
294111cf1abSIlias Apalodimas 	int err;
295111cf1abSIlias Apalodimas 
296111cf1abSIlias Apalodimas 	if (cpu_port)
297111cf1abSIlias Apalodimas 		del_mask = BIT(HOST_PORT_NUM);
298111cf1abSIlias Apalodimas 	else
299111cf1abSIlias Apalodimas 		del_mask = BIT(priv->emac_port);
300111cf1abSIlias Apalodimas 
301111cf1abSIlias Apalodimas 	err = cpsw_ale_del_mcast(cpsw->ale, mdb->addr, del_mask,
302111cf1abSIlias Apalodimas 				 ALE_VLAN, mdb->vid);
303111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "MDB del: %s: vid %u:%pM  ports: %X\n",
304111cf1abSIlias Apalodimas 		priv->ndev->name, mdb->vid, mdb->addr, del_mask);
305111cf1abSIlias Apalodimas 
306111cf1abSIlias Apalodimas 	return err;
307111cf1abSIlias Apalodimas }
308111cf1abSIlias Apalodimas 
cpsw_port_obj_add(struct net_device * ndev,const void * ctx,const struct switchdev_obj * obj,struct netlink_ext_ack * extack)30969bfac96SVladimir Oltean static int cpsw_port_obj_add(struct net_device *ndev, const void *ctx,
310111cf1abSIlias Apalodimas 			     const struct switchdev_obj *obj,
311111cf1abSIlias Apalodimas 			     struct netlink_ext_ack *extack)
312111cf1abSIlias Apalodimas {
313111cf1abSIlias Apalodimas 	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
314111cf1abSIlias Apalodimas 	struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
315111cf1abSIlias Apalodimas 	struct cpsw_priv *priv = netdev_priv(ndev);
316111cf1abSIlias Apalodimas 	int err = 0;
317111cf1abSIlias Apalodimas 
318111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "obj_add: id %u port: %u\n",
319111cf1abSIlias Apalodimas 		obj->id, priv->emac_port);
320111cf1abSIlias Apalodimas 
321111cf1abSIlias Apalodimas 	switch (obj->id) {
322111cf1abSIlias Apalodimas 	case SWITCHDEV_OBJ_ID_PORT_VLAN:
323ffb68fc5SVladimir Oltean 		err = cpsw_port_vlans_add(priv, vlan);
324111cf1abSIlias Apalodimas 		break;
325111cf1abSIlias Apalodimas 	case SWITCHDEV_OBJ_ID_PORT_MDB:
326111cf1abSIlias Apalodimas 	case SWITCHDEV_OBJ_ID_HOST_MDB:
327ffb68fc5SVladimir Oltean 		err = cpsw_port_mdb_add(priv, mdb);
328111cf1abSIlias Apalodimas 		break;
329111cf1abSIlias Apalodimas 	default:
330111cf1abSIlias Apalodimas 		err = -EOPNOTSUPP;
331111cf1abSIlias Apalodimas 		break;
332111cf1abSIlias Apalodimas 	}
333111cf1abSIlias Apalodimas 
334111cf1abSIlias Apalodimas 	return err;
335111cf1abSIlias Apalodimas }
336111cf1abSIlias Apalodimas 
cpsw_port_obj_del(struct net_device * ndev,const void * ctx,const struct switchdev_obj * obj)33769bfac96SVladimir Oltean static int cpsw_port_obj_del(struct net_device *ndev, const void *ctx,
338111cf1abSIlias Apalodimas 			     const struct switchdev_obj *obj)
339111cf1abSIlias Apalodimas {
340111cf1abSIlias Apalodimas 	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
341111cf1abSIlias Apalodimas 	struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
342111cf1abSIlias Apalodimas 	struct cpsw_priv *priv = netdev_priv(ndev);
343111cf1abSIlias Apalodimas 	int err = 0;
344111cf1abSIlias Apalodimas 
345111cf1abSIlias Apalodimas 	dev_dbg(priv->dev, "obj_del: id %u port: %u\n",
346111cf1abSIlias Apalodimas 		obj->id, priv->emac_port);
347111cf1abSIlias Apalodimas 
348111cf1abSIlias Apalodimas 	switch (obj->id) {
349111cf1abSIlias Apalodimas 	case SWITCHDEV_OBJ_ID_PORT_VLAN:
350b7a9e0daSVladimir Oltean 		err = cpsw_port_vlan_del(priv, vlan->vid, vlan->obj.orig_dev);
351111cf1abSIlias Apalodimas 		break;
352111cf1abSIlias Apalodimas 	case SWITCHDEV_OBJ_ID_PORT_MDB:
353111cf1abSIlias Apalodimas 	case SWITCHDEV_OBJ_ID_HOST_MDB:
354111cf1abSIlias Apalodimas 		err = cpsw_port_mdb_del(priv, mdb);
355111cf1abSIlias Apalodimas 		break;
356111cf1abSIlias Apalodimas 	default:
357111cf1abSIlias Apalodimas 		err = -EOPNOTSUPP;
358111cf1abSIlias Apalodimas 		break;
359111cf1abSIlias Apalodimas 	}
360111cf1abSIlias Apalodimas 
361111cf1abSIlias Apalodimas 	return err;
362111cf1abSIlias Apalodimas }
363111cf1abSIlias Apalodimas 
cpsw_fdb_offload_notify(struct net_device * ndev,struct switchdev_notifier_fdb_info * rcv)364111cf1abSIlias Apalodimas static void cpsw_fdb_offload_notify(struct net_device *ndev,
365111cf1abSIlias Apalodimas 				    struct switchdev_notifier_fdb_info *rcv)
366111cf1abSIlias Apalodimas {
367*c35b57ceSVladimir Oltean 	struct switchdev_notifier_fdb_info info = {};
368111cf1abSIlias Apalodimas 
369111cf1abSIlias Apalodimas 	info.addr = rcv->addr;
370111cf1abSIlias Apalodimas 	info.vid = rcv->vid;
371111cf1abSIlias Apalodimas 	info.offloaded = true;
372111cf1abSIlias Apalodimas 	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
373111cf1abSIlias Apalodimas 				 ndev, &info.info, NULL);
374111cf1abSIlias Apalodimas }
375111cf1abSIlias Apalodimas 
cpsw_switchdev_event_work(struct work_struct * work)376111cf1abSIlias Apalodimas static void cpsw_switchdev_event_work(struct work_struct *work)
377111cf1abSIlias Apalodimas {
378111cf1abSIlias Apalodimas 	struct cpsw_switchdev_event_work *switchdev_work =
379111cf1abSIlias Apalodimas 		container_of(work, struct cpsw_switchdev_event_work, work);
380111cf1abSIlias Apalodimas 	struct cpsw_priv *priv = switchdev_work->priv;
381111cf1abSIlias Apalodimas 	struct switchdev_notifier_fdb_info *fdb;
382111cf1abSIlias Apalodimas 	struct cpsw_common *cpsw = priv->cpsw;
383111cf1abSIlias Apalodimas 	int port = priv->emac_port;
384111cf1abSIlias Apalodimas 
385111cf1abSIlias Apalodimas 	rtnl_lock();
386111cf1abSIlias Apalodimas 	switch (switchdev_work->event) {
387111cf1abSIlias Apalodimas 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
388111cf1abSIlias Apalodimas 		fdb = &switchdev_work->fdb_info;
389111cf1abSIlias Apalodimas 
390111cf1abSIlias Apalodimas 		dev_dbg(cpsw->dev, "cpsw_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",
391111cf1abSIlias Apalodimas 			fdb->addr, fdb->vid, fdb->added_by_user,
392111cf1abSIlias Apalodimas 			fdb->offloaded, port);
393111cf1abSIlias Apalodimas 
3942c4eca3eSVladimir Oltean 		if (!fdb->added_by_user || fdb->is_local)
395111cf1abSIlias Apalodimas 			break;
396111cf1abSIlias Apalodimas 		if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
397111cf1abSIlias Apalodimas 			port = HOST_PORT_NUM;
398111cf1abSIlias Apalodimas 
399111cf1abSIlias Apalodimas 		cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, port,
400111cf1abSIlias Apalodimas 				   fdb->vid ? ALE_VLAN : 0, fdb->vid);
401111cf1abSIlias Apalodimas 		cpsw_fdb_offload_notify(priv->ndev, fdb);
402111cf1abSIlias Apalodimas 		break;
403111cf1abSIlias Apalodimas 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
404111cf1abSIlias Apalodimas 		fdb = &switchdev_work->fdb_info;
405111cf1abSIlias Apalodimas 
406111cf1abSIlias Apalodimas 		dev_dbg(cpsw->dev, "cpsw_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",
407111cf1abSIlias Apalodimas 			fdb->addr, fdb->vid, fdb->added_by_user,
408111cf1abSIlias Apalodimas 			fdb->offloaded, port);
409111cf1abSIlias Apalodimas 
4102c4eca3eSVladimir Oltean 		if (!fdb->added_by_user || fdb->is_local)
411111cf1abSIlias Apalodimas 			break;
412111cf1abSIlias Apalodimas 		if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
413111cf1abSIlias Apalodimas 			port = HOST_PORT_NUM;
414111cf1abSIlias Apalodimas 
415111cf1abSIlias Apalodimas 		cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port,
416111cf1abSIlias Apalodimas 				   fdb->vid ? ALE_VLAN : 0, fdb->vid);
417111cf1abSIlias Apalodimas 		break;
418111cf1abSIlias Apalodimas 	default:
419111cf1abSIlias Apalodimas 		break;
420111cf1abSIlias Apalodimas 	}
421111cf1abSIlias Apalodimas 	rtnl_unlock();
422111cf1abSIlias Apalodimas 
423111cf1abSIlias Apalodimas 	kfree(switchdev_work->fdb_info.addr);
424111cf1abSIlias Apalodimas 	kfree(switchdev_work);
425111cf1abSIlias Apalodimas 	dev_put(priv->ndev);
426111cf1abSIlias Apalodimas }
427111cf1abSIlias Apalodimas 
428111cf1abSIlias Apalodimas /* called under rcu_read_lock() */
cpsw_switchdev_event(struct notifier_block * unused,unsigned long event,void * ptr)429111cf1abSIlias Apalodimas static int cpsw_switchdev_event(struct notifier_block *unused,
430111cf1abSIlias Apalodimas 				unsigned long event, void *ptr)
431111cf1abSIlias Apalodimas {
432111cf1abSIlias Apalodimas 	struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
433111cf1abSIlias Apalodimas 	struct switchdev_notifier_fdb_info *fdb_info = ptr;
434111cf1abSIlias Apalodimas 	struct cpsw_switchdev_event_work *switchdev_work;
435111cf1abSIlias Apalodimas 	struct cpsw_priv *priv = netdev_priv(ndev);
436111cf1abSIlias Apalodimas 	int err;
437111cf1abSIlias Apalodimas 
438111cf1abSIlias Apalodimas 	if (event == SWITCHDEV_PORT_ATTR_SET) {
439111cf1abSIlias Apalodimas 		err = switchdev_handle_port_attr_set(ndev, ptr,
440111cf1abSIlias Apalodimas 						     cpsw_port_dev_check,
441111cf1abSIlias Apalodimas 						     cpsw_port_attr_set);
442111cf1abSIlias Apalodimas 		return notifier_from_errno(err);
443111cf1abSIlias Apalodimas 	}
444111cf1abSIlias Apalodimas 
445111cf1abSIlias Apalodimas 	if (!cpsw_port_dev_check(ndev))
446111cf1abSIlias Apalodimas 		return NOTIFY_DONE;
447111cf1abSIlias Apalodimas 
448111cf1abSIlias Apalodimas 	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
449111cf1abSIlias Apalodimas 	if (WARN_ON(!switchdev_work))
450111cf1abSIlias Apalodimas 		return NOTIFY_BAD;
451111cf1abSIlias Apalodimas 
452111cf1abSIlias Apalodimas 	INIT_WORK(&switchdev_work->work, cpsw_switchdev_event_work);
453111cf1abSIlias Apalodimas 	switchdev_work->priv = priv;
454111cf1abSIlias Apalodimas 	switchdev_work->event = event;
455111cf1abSIlias Apalodimas 
456111cf1abSIlias Apalodimas 	switch (event) {
457111cf1abSIlias Apalodimas 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
458111cf1abSIlias Apalodimas 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
459111cf1abSIlias Apalodimas 		memcpy(&switchdev_work->fdb_info, ptr,
460111cf1abSIlias Apalodimas 		       sizeof(switchdev_work->fdb_info));
461111cf1abSIlias Apalodimas 		switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
462111cf1abSIlias Apalodimas 		if (!switchdev_work->fdb_info.addr)
463111cf1abSIlias Apalodimas 			goto err_addr_alloc;
464111cf1abSIlias Apalodimas 		ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
465111cf1abSIlias Apalodimas 				fdb_info->addr);
466111cf1abSIlias Apalodimas 		dev_hold(ndev);
467111cf1abSIlias Apalodimas 		break;
468111cf1abSIlias Apalodimas 	default:
469111cf1abSIlias Apalodimas 		kfree(switchdev_work);
470111cf1abSIlias Apalodimas 		return NOTIFY_DONE;
471111cf1abSIlias Apalodimas 	}
472111cf1abSIlias Apalodimas 
473111cf1abSIlias Apalodimas 	queue_work(system_long_wq, &switchdev_work->work);
474111cf1abSIlias Apalodimas 
475111cf1abSIlias Apalodimas 	return NOTIFY_DONE;
476111cf1abSIlias Apalodimas 
477111cf1abSIlias Apalodimas err_addr_alloc:
478111cf1abSIlias Apalodimas 	kfree(switchdev_work);
479111cf1abSIlias Apalodimas 	return NOTIFY_BAD;
480111cf1abSIlias Apalodimas }
481111cf1abSIlias Apalodimas 
482111cf1abSIlias Apalodimas static struct notifier_block cpsw_switchdev_notifier = {
483111cf1abSIlias Apalodimas 	.notifier_call = cpsw_switchdev_event,
484111cf1abSIlias Apalodimas };
485111cf1abSIlias Apalodimas 
cpsw_switchdev_blocking_event(struct notifier_block * unused,unsigned long event,void * ptr)486111cf1abSIlias Apalodimas static int cpsw_switchdev_blocking_event(struct notifier_block *unused,
487111cf1abSIlias Apalodimas 					 unsigned long event, void *ptr)
488111cf1abSIlias Apalodimas {
489111cf1abSIlias Apalodimas 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
490111cf1abSIlias Apalodimas 	int err;
491111cf1abSIlias Apalodimas 
492111cf1abSIlias Apalodimas 	switch (event) {
493111cf1abSIlias Apalodimas 	case SWITCHDEV_PORT_OBJ_ADD:
494111cf1abSIlias Apalodimas 		err = switchdev_handle_port_obj_add(dev, ptr,
495111cf1abSIlias Apalodimas 						    cpsw_port_dev_check,
496111cf1abSIlias Apalodimas 						    cpsw_port_obj_add);
497111cf1abSIlias Apalodimas 		return notifier_from_errno(err);
498111cf1abSIlias Apalodimas 	case SWITCHDEV_PORT_OBJ_DEL:
499111cf1abSIlias Apalodimas 		err = switchdev_handle_port_obj_del(dev, ptr,
500111cf1abSIlias Apalodimas 						    cpsw_port_dev_check,
501111cf1abSIlias Apalodimas 						    cpsw_port_obj_del);
502111cf1abSIlias Apalodimas 		return notifier_from_errno(err);
503111cf1abSIlias Apalodimas 	case SWITCHDEV_PORT_ATTR_SET:
504111cf1abSIlias Apalodimas 		err = switchdev_handle_port_attr_set(dev, ptr,
505111cf1abSIlias Apalodimas 						     cpsw_port_dev_check,
506111cf1abSIlias Apalodimas 						     cpsw_port_attr_set);
507111cf1abSIlias Apalodimas 		return notifier_from_errno(err);
508111cf1abSIlias Apalodimas 	default:
509111cf1abSIlias Apalodimas 		break;
510111cf1abSIlias Apalodimas 	}
511111cf1abSIlias Apalodimas 
512111cf1abSIlias Apalodimas 	return NOTIFY_DONE;
513111cf1abSIlias Apalodimas }
514111cf1abSIlias Apalodimas 
515111cf1abSIlias Apalodimas static struct notifier_block cpsw_switchdev_bl_notifier = {
516111cf1abSIlias Apalodimas 	.notifier_call = cpsw_switchdev_blocking_event,
517111cf1abSIlias Apalodimas };
518111cf1abSIlias Apalodimas 
cpsw_switchdev_register_notifiers(struct cpsw_common * cpsw)519111cf1abSIlias Apalodimas int cpsw_switchdev_register_notifiers(struct cpsw_common *cpsw)
520111cf1abSIlias Apalodimas {
521111cf1abSIlias Apalodimas 	int ret = 0;
522111cf1abSIlias Apalodimas 
523111cf1abSIlias Apalodimas 	ret = register_switchdev_notifier(&cpsw_switchdev_notifier);
524111cf1abSIlias Apalodimas 	if (ret) {
525111cf1abSIlias Apalodimas 		dev_err(cpsw->dev, "register switchdev notifier fail ret:%d\n",
526111cf1abSIlias Apalodimas 			ret);
527111cf1abSIlias Apalodimas 		return ret;
528111cf1abSIlias Apalodimas 	}
529111cf1abSIlias Apalodimas 
530111cf1abSIlias Apalodimas 	ret = register_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
531111cf1abSIlias Apalodimas 	if (ret) {
532111cf1abSIlias Apalodimas 		dev_err(cpsw->dev, "register switchdev blocking notifier ret:%d\n",
533111cf1abSIlias Apalodimas 			ret);
534111cf1abSIlias Apalodimas 		unregister_switchdev_notifier(&cpsw_switchdev_notifier);
535111cf1abSIlias Apalodimas 	}
536111cf1abSIlias Apalodimas 
537111cf1abSIlias Apalodimas 	return ret;
538111cf1abSIlias Apalodimas }
539111cf1abSIlias Apalodimas 
cpsw_switchdev_unregister_notifiers(struct cpsw_common * cpsw)540111cf1abSIlias Apalodimas void cpsw_switchdev_unregister_notifiers(struct cpsw_common *cpsw)
541111cf1abSIlias Apalodimas {
542111cf1abSIlias Apalodimas 	unregister_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
543111cf1abSIlias Apalodimas 	unregister_switchdev_notifier(&cpsw_switchdev_notifier);
544111cf1abSIlias Apalodimas }
545