1f515f192SVivien Didelot /* 2f515f192SVivien Didelot * Handling of a single switch chip, part of a switch fabric 3f515f192SVivien Didelot * 44333d619SVivien Didelot * Copyright (c) 2017 Savoir-faire Linux Inc. 54333d619SVivien Didelot * Vivien Didelot <vivien.didelot@savoirfairelinux.com> 6f515f192SVivien Didelot * 7f515f192SVivien Didelot * This program is free software; you can redistribute it and/or modify 8f515f192SVivien Didelot * it under the terms of the GNU General Public License as published by 9f515f192SVivien Didelot * the Free Software Foundation; either version 2 of the License, or 10f515f192SVivien Didelot * (at your option) any later version. 11f515f192SVivien Didelot */ 12f515f192SVivien Didelot 13f515f192SVivien Didelot #include <linux/netdevice.h> 14f515f192SVivien Didelot #include <linux/notifier.h> 151faabf74SVivien Didelot #include <net/switchdev.h> 16ea5dd34bSVivien Didelot 17ea5dd34bSVivien Didelot #include "dsa_priv.h" 18f515f192SVivien Didelot 191faabf74SVivien Didelot static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds, 201faabf74SVivien Didelot unsigned int ageing_time) 211faabf74SVivien Didelot { 221faabf74SVivien Didelot int i; 231faabf74SVivien Didelot 241faabf74SVivien Didelot for (i = 0; i < ds->num_ports; ++i) { 251faabf74SVivien Didelot struct dsa_port *dp = &ds->ports[i]; 261faabf74SVivien Didelot 271faabf74SVivien Didelot if (dp->ageing_time && dp->ageing_time < ageing_time) 281faabf74SVivien Didelot ageing_time = dp->ageing_time; 291faabf74SVivien Didelot } 301faabf74SVivien Didelot 311faabf74SVivien Didelot return ageing_time; 321faabf74SVivien Didelot } 331faabf74SVivien Didelot 341faabf74SVivien Didelot static int dsa_switch_ageing_time(struct dsa_switch *ds, 351faabf74SVivien Didelot struct dsa_notifier_ageing_time_info *info) 361faabf74SVivien Didelot { 371faabf74SVivien Didelot unsigned int ageing_time = info->ageing_time; 381faabf74SVivien Didelot struct switchdev_trans *trans = info->trans; 391faabf74SVivien Didelot 401faabf74SVivien Didelot if (switchdev_trans_ph_prepare(trans)) { 411faabf74SVivien Didelot if (ds->ageing_time_min && ageing_time < ds->ageing_time_min) 421faabf74SVivien Didelot return -ERANGE; 431faabf74SVivien Didelot if (ds->ageing_time_max && ageing_time > ds->ageing_time_max) 441faabf74SVivien Didelot return -ERANGE; 451faabf74SVivien Didelot return 0; 461faabf74SVivien Didelot } 471faabf74SVivien Didelot 481faabf74SVivien Didelot /* Program the fastest ageing time in case of multiple bridges */ 491faabf74SVivien Didelot ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time); 501faabf74SVivien Didelot 511faabf74SVivien Didelot if (ds->ops->set_ageing_time) 521faabf74SVivien Didelot return ds->ops->set_ageing_time(ds, ageing_time); 531faabf74SVivien Didelot 541faabf74SVivien Didelot return 0; 551faabf74SVivien Didelot } 561faabf74SVivien Didelot 5704d3a4c6SVivien Didelot static int dsa_switch_bridge_join(struct dsa_switch *ds, 5804d3a4c6SVivien Didelot struct dsa_notifier_bridge_info *info) 5904d3a4c6SVivien Didelot { 6004d3a4c6SVivien Didelot if (ds->index == info->sw_index && ds->ops->port_bridge_join) 6104d3a4c6SVivien Didelot return ds->ops->port_bridge_join(ds, info->port, info->br); 6204d3a4c6SVivien Didelot 6340ef2c93SVivien Didelot if (ds->index != info->sw_index && ds->ops->crosschip_bridge_join) 6440ef2c93SVivien Didelot return ds->ops->crosschip_bridge_join(ds, info->sw_index, 6540ef2c93SVivien Didelot info->port, info->br); 6604d3a4c6SVivien Didelot 6704d3a4c6SVivien Didelot return 0; 6804d3a4c6SVivien Didelot } 6904d3a4c6SVivien Didelot 7004d3a4c6SVivien Didelot static int dsa_switch_bridge_leave(struct dsa_switch *ds, 7104d3a4c6SVivien Didelot struct dsa_notifier_bridge_info *info) 7204d3a4c6SVivien Didelot { 7304d3a4c6SVivien Didelot if (ds->index == info->sw_index && ds->ops->port_bridge_leave) 7404d3a4c6SVivien Didelot ds->ops->port_bridge_leave(ds, info->port, info->br); 7504d3a4c6SVivien Didelot 7640ef2c93SVivien Didelot if (ds->index != info->sw_index && ds->ops->crosschip_bridge_leave) 7740ef2c93SVivien Didelot ds->ops->crosschip_bridge_leave(ds, info->sw_index, info->port, 7840ef2c93SVivien Didelot info->br); 7904d3a4c6SVivien Didelot 8004d3a4c6SVivien Didelot return 0; 8104d3a4c6SVivien Didelot } 8204d3a4c6SVivien Didelot 83685fb6a4SVivien Didelot static int dsa_switch_fdb_add(struct dsa_switch *ds, 84685fb6a4SVivien Didelot struct dsa_notifier_fdb_info *info) 85685fb6a4SVivien Didelot { 86685fb6a4SVivien Didelot const struct switchdev_obj_port_fdb *fdb = info->fdb; 87685fb6a4SVivien Didelot struct switchdev_trans *trans = info->trans; 88685fb6a4SVivien Didelot 89685fb6a4SVivien Didelot /* Do not care yet about other switch chips of the fabric */ 90685fb6a4SVivien Didelot if (ds->index != info->sw_index) 91685fb6a4SVivien Didelot return 0; 92685fb6a4SVivien Didelot 93685fb6a4SVivien Didelot if (switchdev_trans_ph_prepare(trans)) { 94685fb6a4SVivien Didelot if (!ds->ops->port_fdb_prepare || !ds->ops->port_fdb_add) 95685fb6a4SVivien Didelot return -EOPNOTSUPP; 96685fb6a4SVivien Didelot 97685fb6a4SVivien Didelot return ds->ops->port_fdb_prepare(ds, info->port, fdb, trans); 98685fb6a4SVivien Didelot } 99685fb6a4SVivien Didelot 100685fb6a4SVivien Didelot ds->ops->port_fdb_add(ds, info->port, fdb, trans); 101685fb6a4SVivien Didelot 102685fb6a4SVivien Didelot return 0; 103685fb6a4SVivien Didelot } 104685fb6a4SVivien Didelot 105685fb6a4SVivien Didelot static int dsa_switch_fdb_del(struct dsa_switch *ds, 106685fb6a4SVivien Didelot struct dsa_notifier_fdb_info *info) 107685fb6a4SVivien Didelot { 108685fb6a4SVivien Didelot const struct switchdev_obj_port_fdb *fdb = info->fdb; 109685fb6a4SVivien Didelot 110685fb6a4SVivien Didelot /* Do not care yet about other switch chips of the fabric */ 111685fb6a4SVivien Didelot if (ds->index != info->sw_index) 112685fb6a4SVivien Didelot return 0; 113685fb6a4SVivien Didelot 114685fb6a4SVivien Didelot if (!ds->ops->port_fdb_del) 115685fb6a4SVivien Didelot return -EOPNOTSUPP; 116685fb6a4SVivien Didelot 117685fb6a4SVivien Didelot return ds->ops->port_fdb_del(ds, info->port, fdb); 118685fb6a4SVivien Didelot } 119685fb6a4SVivien Didelot 1208ae5bcdcSVivien Didelot static int dsa_switch_mdb_add(struct dsa_switch *ds, 1218ae5bcdcSVivien Didelot struct dsa_notifier_mdb_info *info) 1228ae5bcdcSVivien Didelot { 1238ae5bcdcSVivien Didelot const struct switchdev_obj_port_mdb *mdb = info->mdb; 1248ae5bcdcSVivien Didelot struct switchdev_trans *trans = info->trans; 1258ae5bcdcSVivien Didelot 1268ae5bcdcSVivien Didelot /* Do not care yet about other switch chips of the fabric */ 1278ae5bcdcSVivien Didelot if (ds->index != info->sw_index) 1288ae5bcdcSVivien Didelot return 0; 1298ae5bcdcSVivien Didelot 1308ae5bcdcSVivien Didelot if (switchdev_trans_ph_prepare(trans)) { 1318ae5bcdcSVivien Didelot if (!ds->ops->port_mdb_prepare || !ds->ops->port_mdb_add) 1328ae5bcdcSVivien Didelot return -EOPNOTSUPP; 1338ae5bcdcSVivien Didelot 1348ae5bcdcSVivien Didelot return ds->ops->port_mdb_prepare(ds, info->port, mdb, trans); 1358ae5bcdcSVivien Didelot } 1368ae5bcdcSVivien Didelot 1378ae5bcdcSVivien Didelot ds->ops->port_mdb_add(ds, info->port, mdb, trans); 1388ae5bcdcSVivien Didelot 1398ae5bcdcSVivien Didelot return 0; 1408ae5bcdcSVivien Didelot } 1418ae5bcdcSVivien Didelot 1428ae5bcdcSVivien Didelot static int dsa_switch_mdb_del(struct dsa_switch *ds, 1438ae5bcdcSVivien Didelot struct dsa_notifier_mdb_info *info) 1448ae5bcdcSVivien Didelot { 1458ae5bcdcSVivien Didelot const struct switchdev_obj_port_mdb *mdb = info->mdb; 1468ae5bcdcSVivien Didelot 1478ae5bcdcSVivien Didelot /* Do not care yet about other switch chips of the fabric */ 1488ae5bcdcSVivien Didelot if (ds->index != info->sw_index) 1498ae5bcdcSVivien Didelot return 0; 1508ae5bcdcSVivien Didelot 1518ae5bcdcSVivien Didelot if (!ds->ops->port_mdb_del) 1528ae5bcdcSVivien Didelot return -EOPNOTSUPP; 1538ae5bcdcSVivien Didelot 1548ae5bcdcSVivien Didelot return ds->ops->port_mdb_del(ds, info->port, mdb); 1558ae5bcdcSVivien Didelot } 1568ae5bcdcSVivien Didelot 157d0c627b8SVivien Didelot static int dsa_switch_vlan_add(struct dsa_switch *ds, 158d0c627b8SVivien Didelot struct dsa_notifier_vlan_info *info) 159d0c627b8SVivien Didelot { 160d0c627b8SVivien Didelot const struct switchdev_obj_port_vlan *vlan = info->vlan; 161d0c627b8SVivien Didelot struct switchdev_trans *trans = info->trans; 162*1ca4aa9cSVivien Didelot DECLARE_BITMAP(members, ds->num_ports); 163*1ca4aa9cSVivien Didelot int port, err; 164d0c627b8SVivien Didelot 165*1ca4aa9cSVivien Didelot /* Build a mask of VLAN members */ 166*1ca4aa9cSVivien Didelot bitmap_zero(members, ds->num_ports); 167*1ca4aa9cSVivien Didelot if (ds->index == info->sw_index) 168*1ca4aa9cSVivien Didelot set_bit(info->port, members); 169d0c627b8SVivien Didelot 170d0c627b8SVivien Didelot if (switchdev_trans_ph_prepare(trans)) { 171d0c627b8SVivien Didelot if (!ds->ops->port_vlan_prepare || !ds->ops->port_vlan_add) 172d0c627b8SVivien Didelot return -EOPNOTSUPP; 173d0c627b8SVivien Didelot 174*1ca4aa9cSVivien Didelot for_each_set_bit(port, members, ds->num_ports) { 175*1ca4aa9cSVivien Didelot err = ds->ops->port_vlan_prepare(ds, port, vlan, trans); 176*1ca4aa9cSVivien Didelot if (err) 177*1ca4aa9cSVivien Didelot return err; 178*1ca4aa9cSVivien Didelot } 179d0c627b8SVivien Didelot } 180d0c627b8SVivien Didelot 181*1ca4aa9cSVivien Didelot for_each_set_bit(port, members, ds->num_ports) 182*1ca4aa9cSVivien Didelot ds->ops->port_vlan_add(ds, port, vlan, trans); 183d0c627b8SVivien Didelot 184d0c627b8SVivien Didelot return 0; 185d0c627b8SVivien Didelot } 186d0c627b8SVivien Didelot 187d0c627b8SVivien Didelot static int dsa_switch_vlan_del(struct dsa_switch *ds, 188d0c627b8SVivien Didelot struct dsa_notifier_vlan_info *info) 189d0c627b8SVivien Didelot { 190d0c627b8SVivien Didelot const struct switchdev_obj_port_vlan *vlan = info->vlan; 191d0c627b8SVivien Didelot 192d0c627b8SVivien Didelot if (!ds->ops->port_vlan_del) 193d0c627b8SVivien Didelot return -EOPNOTSUPP; 194d0c627b8SVivien Didelot 195*1ca4aa9cSVivien Didelot if (ds->index == info->sw_index) 196d0c627b8SVivien Didelot return ds->ops->port_vlan_del(ds, info->port, vlan); 197*1ca4aa9cSVivien Didelot 198*1ca4aa9cSVivien Didelot return 0; 199d0c627b8SVivien Didelot } 200d0c627b8SVivien Didelot 201f515f192SVivien Didelot static int dsa_switch_event(struct notifier_block *nb, 202f515f192SVivien Didelot unsigned long event, void *info) 203f515f192SVivien Didelot { 204f515f192SVivien Didelot struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb); 205f515f192SVivien Didelot int err; 206f515f192SVivien Didelot 207f515f192SVivien Didelot switch (event) { 2081faabf74SVivien Didelot case DSA_NOTIFIER_AGEING_TIME: 2091faabf74SVivien Didelot err = dsa_switch_ageing_time(ds, info); 2101faabf74SVivien Didelot break; 21104d3a4c6SVivien Didelot case DSA_NOTIFIER_BRIDGE_JOIN: 21204d3a4c6SVivien Didelot err = dsa_switch_bridge_join(ds, info); 21304d3a4c6SVivien Didelot break; 21404d3a4c6SVivien Didelot case DSA_NOTIFIER_BRIDGE_LEAVE: 21504d3a4c6SVivien Didelot err = dsa_switch_bridge_leave(ds, info); 21604d3a4c6SVivien Didelot break; 217685fb6a4SVivien Didelot case DSA_NOTIFIER_FDB_ADD: 218685fb6a4SVivien Didelot err = dsa_switch_fdb_add(ds, info); 219685fb6a4SVivien Didelot break; 220685fb6a4SVivien Didelot case DSA_NOTIFIER_FDB_DEL: 221685fb6a4SVivien Didelot err = dsa_switch_fdb_del(ds, info); 222685fb6a4SVivien Didelot break; 2238ae5bcdcSVivien Didelot case DSA_NOTIFIER_MDB_ADD: 2248ae5bcdcSVivien Didelot err = dsa_switch_mdb_add(ds, info); 2258ae5bcdcSVivien Didelot break; 2268ae5bcdcSVivien Didelot case DSA_NOTIFIER_MDB_DEL: 2278ae5bcdcSVivien Didelot err = dsa_switch_mdb_del(ds, info); 2288ae5bcdcSVivien Didelot break; 229d0c627b8SVivien Didelot case DSA_NOTIFIER_VLAN_ADD: 230d0c627b8SVivien Didelot err = dsa_switch_vlan_add(ds, info); 231d0c627b8SVivien Didelot break; 232d0c627b8SVivien Didelot case DSA_NOTIFIER_VLAN_DEL: 233d0c627b8SVivien Didelot err = dsa_switch_vlan_del(ds, info); 234d0c627b8SVivien Didelot break; 235f515f192SVivien Didelot default: 236f515f192SVivien Didelot err = -EOPNOTSUPP; 237f515f192SVivien Didelot break; 238f515f192SVivien Didelot } 239f515f192SVivien Didelot 240f515f192SVivien Didelot /* Non-switchdev operations cannot be rolled back. If a DSA driver 241f515f192SVivien Didelot * returns an error during the chained call, switch chips may be in an 242f515f192SVivien Didelot * inconsistent state. 243f515f192SVivien Didelot */ 244f515f192SVivien Didelot if (err) 245f515f192SVivien Didelot dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n", 246f515f192SVivien Didelot event, err); 247f515f192SVivien Didelot 248f515f192SVivien Didelot return notifier_from_errno(err); 249f515f192SVivien Didelot } 250f515f192SVivien Didelot 251f515f192SVivien Didelot int dsa_switch_register_notifier(struct dsa_switch *ds) 252f515f192SVivien Didelot { 253f515f192SVivien Didelot ds->nb.notifier_call = dsa_switch_event; 254f515f192SVivien Didelot 255f515f192SVivien Didelot return raw_notifier_chain_register(&ds->dst->nh, &ds->nb); 256f515f192SVivien Didelot } 257f515f192SVivien Didelot 258f515f192SVivien Didelot void dsa_switch_unregister_notifier(struct dsa_switch *ds) 259f515f192SVivien Didelot { 260f515f192SVivien Didelot int err; 261f515f192SVivien Didelot 262f515f192SVivien Didelot err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb); 263f515f192SVivien Didelot if (err) 264f515f192SVivien Didelot dev_err(ds->dev, "failed to unregister notifier (%d)\n", err); 265f515f192SVivien Didelot } 266