1 // SPDX-License-Identifier: GPL-2.0 2 3 /* Texas Instruments ICSSM Ethernet Driver 4 * 5 * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/ 6 * 7 */ 8 9 #include <linux/etherdevice.h> 10 #include <linux/kernel.h> 11 #include <linux/remoteproc.h> 12 #include <net/switchdev.h> 13 14 #include "icssm_prueth.h" 15 #include "icssm_prueth_switch.h" 16 #include "icssm_prueth_fdb_tbl.h" 17 18 /* switchev event work */ 19 struct icssm_sw_event_work { 20 netdevice_tracker ndev_tracker; 21 struct work_struct work; 22 struct switchdev_notifier_fdb_info fdb_info; 23 struct prueth_emac *emac; 24 unsigned long event; 25 }; 26 27 void icssm_prueth_sw_set_stp_state(struct prueth *prueth, 28 enum prueth_port port, u8 state) 29 { 30 struct fdb_tbl *t = prueth->fdb_tbl; 31 32 writeb(state, port - 1 ? (void __iomem *)&t->port2_stp_cfg->state : 33 (void __iomem *)&t->port1_stp_cfg->state); 34 } 35 36 u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth, enum prueth_port port) 37 { 38 struct fdb_tbl *t = prueth->fdb_tbl; 39 u8 state; 40 41 state = readb(port - 1 ? (void __iomem *)&t->port2_stp_cfg->state : 42 (void __iomem *)&t->port1_stp_cfg->state); 43 return state; 44 } 45 46 static int icssm_prueth_sw_attr_set(struct net_device *ndev, const void *ctx, 47 const struct switchdev_attr *attr, 48 struct netlink_ext_ack *extack) 49 { 50 struct prueth_emac *emac = netdev_priv(ndev); 51 struct prueth *prueth = emac->prueth; 52 int err = 0; 53 u8 o_state; 54 55 /* Interface is not up */ 56 if (!prueth->fdb_tbl) 57 return 0; 58 59 switch (attr->id) { 60 case SWITCHDEV_ATTR_ID_PORT_STP_STATE: 61 o_state = icssm_prueth_sw_get_stp_state(prueth, emac->port_id); 62 icssm_prueth_sw_set_stp_state(prueth, emac->port_id, 63 attr->u.stp_state); 64 65 if (o_state != attr->u.stp_state) 66 icssm_prueth_sw_purge_fdb(emac); 67 68 dev_dbg(prueth->dev, "attr set: stp state:%u port:%u\n", 69 attr->u.stp_state, emac->port_id); 70 break; 71 default: 72 err = -EOPNOTSUPP; 73 break; 74 } 75 76 return err; 77 } 78 79 static void icssm_prueth_sw_fdb_offload(struct net_device *ndev, 80 struct switchdev_notifier_fdb_info *rcv) 81 { 82 struct switchdev_notifier_fdb_info info; 83 84 info.addr = rcv->addr; 85 info.vid = rcv->vid; 86 call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, ndev, &info.info, 87 NULL); 88 } 89 90 /** 91 * icssm_sw_event_work - insert/delete fdb entry 92 * 93 * @work: work structure 94 * 95 */ 96 static void icssm_sw_event_work(struct work_struct *work) 97 { 98 struct icssm_sw_event_work *switchdev_work = 99 container_of(work, struct icssm_sw_event_work, work); 100 struct prueth_emac *emac = switchdev_work->emac; 101 struct switchdev_notifier_fdb_info *fdb; 102 struct prueth *prueth = emac->prueth; 103 int port = emac->port_id; 104 105 rtnl_lock(); 106 107 /* Interface is not up */ 108 if (!emac->prueth->fdb_tbl) 109 goto free; 110 111 switch (switchdev_work->event) { 112 case SWITCHDEV_FDB_ADD_TO_DEVICE: 113 fdb = &switchdev_work->fdb_info; 114 dev_dbg(prueth->dev, 115 "prueth fdb add: MACID = %pM vid = %u flags = %u -- port %d\n", 116 fdb->addr, fdb->vid, fdb->added_by_user, port); 117 118 if (!fdb->added_by_user) 119 break; 120 121 if (fdb->is_local) 122 break; 123 124 icssm_prueth_sw_fdb_add(emac, fdb); 125 icssm_prueth_sw_fdb_offload(emac->ndev, fdb); 126 break; 127 case SWITCHDEV_FDB_DEL_TO_DEVICE: 128 fdb = &switchdev_work->fdb_info; 129 dev_dbg(prueth->dev, 130 "prueth fdb del: MACID = %pM vid = %u flags = %u -- port %d\n", 131 fdb->addr, fdb->vid, fdb->added_by_user, port); 132 133 if (fdb->is_local) 134 break; 135 136 icssm_prueth_sw_fdb_del(emac, fdb); 137 break; 138 default: 139 break; 140 } 141 142 free: 143 rtnl_unlock(); 144 145 netdev_put(emac->ndev, &switchdev_work->ndev_tracker); 146 kfree(switchdev_work->fdb_info.addr); 147 kfree(switchdev_work); 148 } 149 150 /* called under rcu_read_lock() */ 151 static int icssm_prueth_sw_switchdev_event(struct notifier_block *unused, 152 unsigned long event, void *ptr) 153 { 154 struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); 155 struct switchdev_notifier_fdb_info *fdb_info = ptr; 156 struct prueth_emac *emac = netdev_priv(ndev); 157 struct icssm_sw_event_work *switchdev_work; 158 int err; 159 160 if (!icssm_prueth_sw_port_dev_check(ndev)) 161 return NOTIFY_DONE; 162 163 if (event == SWITCHDEV_PORT_ATTR_SET) { 164 err = switchdev_handle_port_attr_set 165 (ndev, ptr, icssm_prueth_sw_port_dev_check, 166 icssm_prueth_sw_attr_set); 167 return notifier_from_errno(err); 168 } 169 170 switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC); 171 if (WARN_ON(!switchdev_work)) 172 return NOTIFY_BAD; 173 174 INIT_WORK(&switchdev_work->work, icssm_sw_event_work); 175 switchdev_work->emac = emac; 176 switchdev_work->event = event; 177 178 switch (event) { 179 case SWITCHDEV_FDB_ADD_TO_DEVICE: 180 case SWITCHDEV_FDB_DEL_TO_DEVICE: 181 memcpy(&switchdev_work->fdb_info, ptr, 182 sizeof(switchdev_work->fdb_info)); 183 switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); 184 if (!switchdev_work->fdb_info.addr) 185 goto err_addr_alloc; 186 ether_addr_copy((u8 *)switchdev_work->fdb_info.addr, 187 fdb_info->addr); 188 netdev_hold(ndev, &switchdev_work->ndev_tracker, GFP_ATOMIC); 189 break; 190 default: 191 kfree(switchdev_work); 192 return NOTIFY_DONE; 193 } 194 195 queue_work(system_long_wq, &switchdev_work->work); 196 197 return NOTIFY_DONE; 198 199 err_addr_alloc: 200 kfree(switchdev_work); 201 return NOTIFY_BAD; 202 } 203 204 static int icssm_prueth_switchdev_obj_add(struct net_device *ndev, 205 const void *ctx, 206 const struct switchdev_obj *obj, 207 struct netlink_ext_ack *extack) 208 { 209 struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); 210 struct prueth_emac *emac = netdev_priv(ndev); 211 struct prueth *prueth = emac->prueth; 212 int ret = 0; 213 u8 hash; 214 215 switch (obj->id) { 216 case SWITCHDEV_OBJ_ID_HOST_MDB: 217 dev_dbg(prueth->dev, "MDB add: %s: vid %u:%pM port: %x\n", 218 ndev->name, mdb->vid, mdb->addr, emac->port_id); 219 hash = icssm_emac_get_mc_hash(mdb->addr, emac->mc_filter_mask); 220 icssm_emac_mc_filter_bin_allow(emac, hash); 221 break; 222 default: 223 ret = -EOPNOTSUPP; 224 break; 225 } 226 227 return ret; 228 } 229 230 static int icssm_prueth_switchdev_obj_del(struct net_device *ndev, 231 const void *ctx, 232 const struct switchdev_obj *obj) 233 { 234 struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); 235 struct prueth_emac *emac = netdev_priv(ndev); 236 struct prueth *prueth = emac->prueth; 237 struct netdev_hw_addr *ha; 238 u8 hash, tmp_hash; 239 int ret = 0; 240 u8 *mask; 241 242 switch (obj->id) { 243 case SWITCHDEV_OBJ_ID_HOST_MDB: 244 dev_dbg(prueth->dev, "MDB del: %s: vid %u:%pM port: %x\n", 245 ndev->name, mdb->vid, mdb->addr, emac->port_id); 246 if (prueth->hw_bridge_dev) { 247 mask = emac->mc_filter_mask; 248 hash = icssm_emac_get_mc_hash(mdb->addr, mask); 249 netdev_for_each_mc_addr(ha, prueth->hw_bridge_dev) { 250 tmp_hash = icssm_emac_get_mc_hash(ha->addr, 251 mask); 252 /* Another MC address is in the bin. 253 * Don't disable. 254 */ 255 if (tmp_hash == hash) 256 return 0; 257 } 258 icssm_emac_mc_filter_bin_disallow(emac, hash); 259 } 260 break; 261 default: 262 ret = -EOPNOTSUPP; 263 break; 264 } 265 266 return ret; 267 } 268 269 /* switchdev notifiers */ 270 static int icssm_prueth_sw_blocking_event(struct notifier_block *unused, 271 unsigned long event, void *ptr) 272 { 273 struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); 274 int err; 275 276 switch (event) { 277 case SWITCHDEV_PORT_OBJ_ADD: 278 err = switchdev_handle_port_obj_add 279 (ndev, ptr, icssm_prueth_sw_port_dev_check, 280 icssm_prueth_switchdev_obj_add); 281 return notifier_from_errno(err); 282 283 case SWITCHDEV_PORT_OBJ_DEL: 284 err = switchdev_handle_port_obj_del 285 (ndev, ptr, icssm_prueth_sw_port_dev_check, 286 icssm_prueth_switchdev_obj_del); 287 return notifier_from_errno(err); 288 289 case SWITCHDEV_PORT_ATTR_SET: 290 err = switchdev_handle_port_attr_set 291 (ndev, ptr, icssm_prueth_sw_port_dev_check, 292 icssm_prueth_sw_attr_set); 293 return notifier_from_errno(err); 294 295 default: 296 break; 297 } 298 299 return NOTIFY_DONE; 300 } 301 302 int icssm_prueth_sw_register_notifiers(struct prueth *prueth) 303 { 304 int ret = 0; 305 306 prueth->prueth_switchdev_nb.notifier_call = 307 &icssm_prueth_sw_switchdev_event; 308 ret = register_switchdev_notifier(&prueth->prueth_switchdev_nb); 309 if (ret) { 310 dev_err(prueth->dev, 311 "register switchdev notifier failed ret:%d\n", ret); 312 return ret; 313 } 314 315 prueth->prueth_switchdev_bl_nb.notifier_call = 316 &icssm_prueth_sw_blocking_event; 317 ret = register_switchdev_blocking_notifier 318 (&prueth->prueth_switchdev_bl_nb); 319 if (ret) { 320 dev_err(prueth->dev, 321 "register switchdev blocking notifier failed ret:%d\n", 322 ret); 323 unregister_switchdev_notifier(&prueth->prueth_switchdev_nb); 324 } 325 326 return ret; 327 } 328 329 void icssm_prueth_sw_unregister_notifiers(struct prueth *prueth) 330 { 331 unregister_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb); 332 unregister_switchdev_notifier(&prueth->prueth_switchdev_nb); 333 } 334