brintos

brintos / linux-shallow public Read only

0
0
Text · 12.1 KiB · 67e2927 Raw
478 lines · c
1// SPDX-License-Identifier: GPL-2.02 3/* Texas Instruments K3 ICSSG Ethernet Switchdev Driver4 *5 * Copyright (C) 2021 Texas Instruments Incorporated - https://www.ti.com/6 *7 */8 9#include <linux/etherdevice.h>10#include <linux/if_bridge.h>11#include <linux/netdevice.h>12#include <linux/workqueue.h>13#include <net/switchdev.h>14 15#include "icssg_prueth.h"16#include "icssg_switchdev.h"17#include "icssg_mii_rt.h"18 19struct prueth_switchdev_event_work {20	struct work_struct work;21	struct switchdev_notifier_fdb_info fdb_info;22	struct prueth_emac *emac;23	unsigned long event;24};25 26static int prueth_switchdev_stp_state_set(struct prueth_emac *emac,27					  u8 state)28{29	enum icssg_port_state_cmd emac_state;30	int ret = 0;31 32	switch (state) {33	case BR_STATE_FORWARDING:34		emac_state = ICSSG_EMAC_PORT_FORWARD;35		break;36	case BR_STATE_DISABLED:37		emac_state = ICSSG_EMAC_PORT_DISABLE;38		break;39	case BR_STATE_LISTENING:40	case BR_STATE_BLOCKING:41		emac_state = ICSSG_EMAC_PORT_BLOCK;42		break;43	default:44		return -EOPNOTSUPP;45	}46 47	icssg_set_port_state(emac, emac_state);48	netdev_dbg(emac->ndev, "STP state: %u\n", emac_state);49 50	return ret;51}52 53static int prueth_switchdev_attr_br_flags_set(struct prueth_emac *emac,54					      struct net_device *orig_dev,55					      struct switchdev_brport_flags brport_flags)56{57	enum icssg_port_state_cmd emac_state;58 59	if (brport_flags.mask & BR_MCAST_FLOOD)60		emac_state = ICSSG_EMAC_PORT_MC_FLOODING_ENABLE;61	else62		emac_state = ICSSG_EMAC_PORT_MC_FLOODING_DISABLE;63 64	netdev_dbg(emac->ndev, "BR_MCAST_FLOOD: %d port %u\n",65		   emac_state, emac->port_id);66 67	icssg_set_port_state(emac, emac_state);68 69	return 0;70}71 72static int prueth_switchdev_attr_br_flags_pre_set(struct net_device *netdev,73						  struct switchdev_brport_flags brport_flags)74{75	if (brport_flags.mask & ~(BR_LEARNING | BR_MCAST_FLOOD))76		return -EINVAL;77 78	return 0;79}80 81static int prueth_switchdev_attr_set(struct net_device *ndev, const void *ctx,82				     const struct switchdev_attr *attr,83				     struct netlink_ext_ack *extack)84{85	struct prueth_emac *emac = netdev_priv(ndev);86	int ret;87 88	netdev_dbg(ndev, "attr: id %u port: %u\n", attr->id, emac->port_id);89 90	switch (attr->id) {91	case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:92		ret = prueth_switchdev_attr_br_flags_pre_set(ndev,93							     attr->u.brport_flags);94		break;95	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:96		ret = prueth_switchdev_stp_state_set(emac,97						     attr->u.stp_state);98		netdev_dbg(ndev, "stp state: %u\n", attr->u.stp_state);99		break;100	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:101		ret = prueth_switchdev_attr_br_flags_set(emac, attr->orig_dev,102							 attr->u.brport_flags);103		break;104	default:105		ret = -EOPNOTSUPP;106		break;107	}108 109	return ret;110}111 112static void prueth_switchdev_fdb_offload_notify(struct net_device *ndev,113						struct switchdev_notifier_fdb_info *rcv)114{115	struct switchdev_notifier_fdb_info info;116 117	memset(&info, 0, sizeof(info));118	info.addr = rcv->addr;119	info.vid = rcv->vid;120	info.offloaded = true;121	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,122				 ndev, &info.info, NULL);123}124 125static void prueth_switchdev_event_work(struct work_struct *work)126{127	struct prueth_switchdev_event_work *switchdev_work =128		container_of(work, struct prueth_switchdev_event_work, work);129	struct prueth_emac *emac = switchdev_work->emac;130	struct switchdev_notifier_fdb_info *fdb;131	int port_id = emac->port_id;132	int ret;133 134	rtnl_lock();135	switch (switchdev_work->event) {136	case SWITCHDEV_FDB_ADD_TO_DEVICE:137		fdb = &switchdev_work->fdb_info;138 139		netdev_dbg(emac->ndev, "prueth_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",140			   fdb->addr, fdb->vid, fdb->added_by_user,141			   fdb->offloaded, port_id);142 143		if (!fdb->added_by_user)144			break;145		if (!ether_addr_equal(emac->mac_addr, fdb->addr))146			break;147 148		ret = icssg_fdb_add_del(emac, fdb->addr, fdb->vid,149					BIT(port_id), true);150		if (!ret)151			prueth_switchdev_fdb_offload_notify(emac->ndev, fdb);152		break;153	case SWITCHDEV_FDB_DEL_TO_DEVICE:154		fdb = &switchdev_work->fdb_info;155 156		netdev_dbg(emac->ndev, "prueth_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",157			   fdb->addr, fdb->vid, fdb->added_by_user,158			   fdb->offloaded, port_id);159 160		if (!fdb->added_by_user)161			break;162		if (!ether_addr_equal(emac->mac_addr, fdb->addr))163			break;164		icssg_fdb_add_del(emac, fdb->addr, fdb->vid,165				  BIT(port_id), false);166		break;167	default:168		break;169	}170	rtnl_unlock();171 172	kfree(switchdev_work->fdb_info.addr);173	kfree(switchdev_work);174	dev_put(emac->ndev);175}176 177static int prueth_switchdev_event(struct notifier_block *unused,178				  unsigned long event, void *ptr)179{180	struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);181	struct prueth_switchdev_event_work *switchdev_work;182	struct switchdev_notifier_fdb_info *fdb_info = ptr;183	struct prueth_emac *emac = netdev_priv(ndev);184	int err;185 186	if (!prueth_dev_check(ndev))187		return NOTIFY_DONE;188 189	if (event == SWITCHDEV_PORT_ATTR_SET) {190		err = switchdev_handle_port_attr_set(ndev, ptr,191						     prueth_dev_check,192						     prueth_switchdev_attr_set);193		return notifier_from_errno(err);194	}195 196	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);197	if (WARN_ON(!switchdev_work))198		return NOTIFY_BAD;199 200	INIT_WORK(&switchdev_work->work, prueth_switchdev_event_work);201	switchdev_work->emac = emac;202	switchdev_work->event = event;203 204	switch (event) {205	case SWITCHDEV_FDB_ADD_TO_DEVICE:206	case SWITCHDEV_FDB_DEL_TO_DEVICE:207		memcpy(&switchdev_work->fdb_info, ptr,208		       sizeof(switchdev_work->fdb_info));209		switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);210		if (!switchdev_work->fdb_info.addr)211			goto err_addr_alloc;212		ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,213				fdb_info->addr);214		dev_hold(ndev);215		break;216	default:217		kfree(switchdev_work);218		return NOTIFY_DONE;219	}220 221	queue_work(system_long_wq, &switchdev_work->work);222 223	return NOTIFY_DONE;224 225err_addr_alloc:226	kfree(switchdev_work);227	return NOTIFY_BAD;228}229 230static int prueth_switchdev_vlan_add(struct prueth_emac *emac, bool untag, bool pvid,231				     u8 vid, struct net_device *orig_dev)232{233	bool cpu_port = netif_is_bridge_master(orig_dev);234	int untag_mask = 0;235	int port_mask;236	int ret = 0;237 238	if (cpu_port)239		port_mask = BIT(PRUETH_PORT_HOST);240	else241		port_mask = BIT(emac->port_id);242 243	if (untag)244		untag_mask = port_mask;245 246	icssg_vtbl_modify(emac, vid, port_mask, untag_mask, true);247 248	netdev_dbg(emac->ndev, "VID add vid:%u port_mask:%X untag_mask %X PVID %d\n",249		   vid, port_mask, untag_mask, pvid);250 251	if (!pvid)252		return ret;253 254	icssg_set_pvid(emac->prueth, vid, emac->port_id);255 256	return ret;257}258 259static int prueth_switchdev_vlan_del(struct prueth_emac *emac, u16 vid,260				     struct net_device *orig_dev)261{262	bool cpu_port = netif_is_bridge_master(orig_dev);263	int port_mask;264	int ret = 0;265 266	if (cpu_port)267		port_mask = BIT(PRUETH_PORT_HOST);268	else269		port_mask = BIT(emac->port_id);270 271	icssg_vtbl_modify(emac, vid, port_mask, 0, false);272 273	if (cpu_port)274		icssg_fdb_add_del(emac, emac->mac_addr, vid,275				  BIT(PRUETH_PORT_HOST), false);276 277	if (vid == icssg_get_pvid(emac))278		icssg_set_pvid(emac->prueth, 0, emac->port_id);279 280	netdev_dbg(emac->ndev, "VID del vid:%u port_mask:%X\n",281		   vid, port_mask);282 283	return ret;284}285 286static int prueth_switchdev_vlans_add(struct prueth_emac *emac,287				      const struct switchdev_obj_port_vlan *vlan)288{289	bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;290	struct net_device *orig_dev = vlan->obj.orig_dev;291	bool cpu_port = netif_is_bridge_master(orig_dev);292	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;293 294	netdev_dbg(emac->ndev, "VID add vid:%u flags:%X\n",295		   vlan->vid, vlan->flags);296 297	if (cpu_port && !(vlan->flags & BRIDGE_VLAN_INFO_BRENTRY))298		return 0;299 300	if (vlan->vid > 0xff)301		return 0;302 303	return prueth_switchdev_vlan_add(emac, untag, pvid, vlan->vid,304					 orig_dev);305}306 307static int prueth_switchdev_vlans_del(struct prueth_emac *emac,308				      const struct switchdev_obj_port_vlan *vlan)309{310	if (vlan->vid > 0xff)311		return 0;312 313	return prueth_switchdev_vlan_del(emac, vlan->vid,314					 vlan->obj.orig_dev);315}316 317static int prueth_switchdev_mdb_add(struct prueth_emac *emac,318				    struct switchdev_obj_port_mdb *mdb)319{320	struct net_device *orig_dev = mdb->obj.orig_dev;321	u8 port_mask, fid_c2;322	bool cpu_port;323	int err;324 325	cpu_port = netif_is_bridge_master(orig_dev);326 327	if (cpu_port)328		port_mask = BIT(PRUETH_PORT_HOST);329	else330		port_mask = BIT(emac->port_id);331 332	fid_c2 = icssg_fdb_lookup(emac, mdb->addr, mdb->vid);333 334	err = icssg_fdb_add_del(emac, mdb->addr, mdb->vid, fid_c2 | port_mask, true);335	netdev_dbg(emac->ndev, "MDB add vid %u:%pM  ports: %X\n",336		   mdb->vid, mdb->addr, port_mask);337 338	return err;339}340 341static int prueth_switchdev_mdb_del(struct prueth_emac *emac,342				    struct switchdev_obj_port_mdb *mdb)343{344	struct net_device *orig_dev = mdb->obj.orig_dev;345	int del_mask, ret, fid_c2;346	bool cpu_port;347 348	cpu_port = netif_is_bridge_master(orig_dev);349 350	if (cpu_port)351		del_mask = BIT(PRUETH_PORT_HOST);352	else353		del_mask = BIT(emac->port_id);354 355	fid_c2 = icssg_fdb_lookup(emac, mdb->addr, mdb->vid);356 357	if (fid_c2 & ~del_mask)358		ret = icssg_fdb_add_del(emac, mdb->addr, mdb->vid, fid_c2 & ~del_mask, true);359	else360		ret = icssg_fdb_add_del(emac, mdb->addr, mdb->vid, 0, false);361 362	netdev_dbg(emac->ndev, "MDB del vid %u:%pM  ports: %X\n",363		   mdb->vid, mdb->addr, del_mask);364 365	return ret;366}367 368static int prueth_switchdev_obj_add(struct net_device *ndev, const void *ctx,369				    const struct switchdev_obj *obj,370				    struct netlink_ext_ack *extack)371{372	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);373	struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);374	struct prueth_emac *emac = netdev_priv(ndev);375	int err = 0;376 377	netdev_dbg(ndev, "obj_add: id %u port: %u\n", obj->id, emac->port_id);378 379	switch (obj->id) {380	case SWITCHDEV_OBJ_ID_PORT_VLAN:381		err = prueth_switchdev_vlans_add(emac, vlan);382		break;383	case SWITCHDEV_OBJ_ID_PORT_MDB:384	case SWITCHDEV_OBJ_ID_HOST_MDB:385		err = prueth_switchdev_mdb_add(emac, mdb);386		break;387	default:388		err = -EOPNOTSUPP;389		break;390	}391 392	return err;393}394 395static int prueth_switchdev_obj_del(struct net_device *ndev, const void *ctx,396				    const struct switchdev_obj *obj)397{398	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);399	struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);400	struct prueth_emac *emac = netdev_priv(ndev);401	int err = 0;402 403	netdev_dbg(ndev, "obj_del: id %u port: %u\n", obj->id, emac->port_id);404 405	switch (obj->id) {406	case SWITCHDEV_OBJ_ID_PORT_VLAN:407		err = prueth_switchdev_vlans_del(emac, vlan);408		break;409	case SWITCHDEV_OBJ_ID_PORT_MDB:410	case SWITCHDEV_OBJ_ID_HOST_MDB:411		err = prueth_switchdev_mdb_del(emac, mdb);412		break;413	default:414		err = -EOPNOTSUPP;415		break;416	}417 418	return err;419}420 421static int prueth_switchdev_blocking_event(struct notifier_block *unused,422					   unsigned long event, void *ptr)423{424	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);425	int err;426 427	switch (event) {428	case SWITCHDEV_PORT_OBJ_ADD:429		err = switchdev_handle_port_obj_add(dev, ptr,430						    prueth_dev_check,431						    prueth_switchdev_obj_add);432		return notifier_from_errno(err);433	case SWITCHDEV_PORT_OBJ_DEL:434		err = switchdev_handle_port_obj_del(dev, ptr,435						    prueth_dev_check,436						    prueth_switchdev_obj_del);437		return notifier_from_errno(err);438	case SWITCHDEV_PORT_ATTR_SET:439		err = switchdev_handle_port_attr_set(dev, ptr,440						     prueth_dev_check,441						     prueth_switchdev_attr_set);442		return notifier_from_errno(err);443	default:444		break;445	}446 447	return NOTIFY_DONE;448}449 450int prueth_switchdev_register_notifiers(struct prueth *prueth)451{452	int ret = 0;453 454	prueth->prueth_switchdev_nb.notifier_call = &prueth_switchdev_event;455	ret = register_switchdev_notifier(&prueth->prueth_switchdev_nb);456	if (ret) {457		dev_err(prueth->dev, "register switchdev notifier fail ret:%d\n",458			ret);459		return ret;460	}461 462	prueth->prueth_switchdev_bl_nb.notifier_call = &prueth_switchdev_blocking_event;463	ret = register_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb);464	if (ret) {465		dev_err(prueth->dev, "register switchdev blocking notifier ret:%d\n",466			ret);467		unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);468	}469 470	return ret;471}472 473void prueth_switchdev_unregister_notifiers(struct prueth *prueth)474{475	unregister_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb);476	unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);477}478