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