12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2f515f192SVivien Didelot /* 3f515f192SVivien Didelot * Handling of a single switch chip, part of a switch fabric 4f515f192SVivien Didelot * 54333d619SVivien Didelot * Copyright (c) 2017 Savoir-faire Linux Inc. 64333d619SVivien Didelot * Vivien Didelot <vivien.didelot@savoirfairelinux.com> 7f515f192SVivien Didelot */ 8f515f192SVivien Didelot 9d371b7c9SVladimir Oltean #include <linux/if_bridge.h> 10f515f192SVivien Didelot #include <linux/netdevice.h> 11f515f192SVivien Didelot #include <linux/notifier.h> 12061f6a50SFlorian Fainelli #include <linux/if_vlan.h> 131faabf74SVivien Didelot #include <net/switchdev.h> 14ea5dd34bSVivien Didelot 15ea5dd34bSVivien Didelot #include "dsa_priv.h" 16f515f192SVivien Didelot 171faabf74SVivien Didelot static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds, 181faabf74SVivien Didelot unsigned int ageing_time) 191faabf74SVivien Didelot { 201faabf74SVivien Didelot int i; 211faabf74SVivien Didelot 221faabf74SVivien Didelot for (i = 0; i < ds->num_ports; ++i) { 2368bb8ea8SVivien Didelot struct dsa_port *dp = dsa_to_port(ds, i); 241faabf74SVivien Didelot 251faabf74SVivien Didelot if (dp->ageing_time && dp->ageing_time < ageing_time) 261faabf74SVivien Didelot ageing_time = dp->ageing_time; 271faabf74SVivien Didelot } 281faabf74SVivien Didelot 291faabf74SVivien Didelot return ageing_time; 301faabf74SVivien Didelot } 311faabf74SVivien Didelot 321faabf74SVivien Didelot static int dsa_switch_ageing_time(struct dsa_switch *ds, 331faabf74SVivien Didelot struct dsa_notifier_ageing_time_info *info) 341faabf74SVivien Didelot { 351faabf74SVivien Didelot unsigned int ageing_time = info->ageing_time; 361faabf74SVivien Didelot 371faabf74SVivien Didelot if (ds->ageing_time_min && ageing_time < ds->ageing_time_min) 381faabf74SVivien Didelot return -ERANGE; 3977b61365SVladimir Oltean 401faabf74SVivien Didelot if (ds->ageing_time_max && ageing_time > ds->ageing_time_max) 411faabf74SVivien Didelot return -ERANGE; 421faabf74SVivien Didelot 431faabf74SVivien Didelot /* Program the fastest ageing time in case of multiple bridges */ 441faabf74SVivien Didelot ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time); 451faabf74SVivien Didelot 461faabf74SVivien Didelot if (ds->ops->set_ageing_time) 471faabf74SVivien Didelot return ds->ops->set_ageing_time(ds, ageing_time); 481faabf74SVivien Didelot 491faabf74SVivien Didelot return 0; 501faabf74SVivien Didelot } 511faabf74SVivien Didelot 52bfcb8132SVladimir Oltean static bool dsa_switch_mtu_match(struct dsa_switch *ds, int port, 53bfcb8132SVladimir Oltean struct dsa_notifier_mtu_info *info) 54bfcb8132SVladimir Oltean { 55bfcb8132SVladimir Oltean if (ds->index == info->sw_index) 56bfcb8132SVladimir Oltean return (port == info->port) || dsa_is_dsa_port(ds, port); 57bfcb8132SVladimir Oltean 58bfcb8132SVladimir Oltean if (!info->propagate_upstream) 59bfcb8132SVladimir Oltean return false; 60bfcb8132SVladimir Oltean 61bfcb8132SVladimir Oltean if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port)) 62bfcb8132SVladimir Oltean return true; 63bfcb8132SVladimir Oltean 64bfcb8132SVladimir Oltean return false; 65bfcb8132SVladimir Oltean } 66bfcb8132SVladimir Oltean 67bfcb8132SVladimir Oltean static int dsa_switch_mtu(struct dsa_switch *ds, 68bfcb8132SVladimir Oltean struct dsa_notifier_mtu_info *info) 69bfcb8132SVladimir Oltean { 70bfcb8132SVladimir Oltean int port, ret; 71bfcb8132SVladimir Oltean 72bfcb8132SVladimir Oltean if (!ds->ops->port_change_mtu) 73bfcb8132SVladimir Oltean return -EOPNOTSUPP; 74bfcb8132SVladimir Oltean 75bfcb8132SVladimir Oltean for (port = 0; port < ds->num_ports; port++) { 76bfcb8132SVladimir Oltean if (dsa_switch_mtu_match(ds, port, info)) { 77bfcb8132SVladimir Oltean ret = ds->ops->port_change_mtu(ds, port, info->mtu); 78bfcb8132SVladimir Oltean if (ret) 79bfcb8132SVladimir Oltean return ret; 80bfcb8132SVladimir Oltean } 81bfcb8132SVladimir Oltean } 82bfcb8132SVladimir Oltean 83bfcb8132SVladimir Oltean return 0; 84bfcb8132SVladimir Oltean } 85bfcb8132SVladimir Oltean 8604d3a4c6SVivien Didelot static int dsa_switch_bridge_join(struct dsa_switch *ds, 8704d3a4c6SVivien Didelot struct dsa_notifier_bridge_info *info) 8804d3a4c6SVivien Didelot { 89f66a6a69SVladimir Oltean struct dsa_switch_tree *dst = ds->dst; 90f66a6a69SVladimir Oltean 91f66a6a69SVladimir Oltean if (dst->index == info->tree_index && ds->index == info->sw_index && 92f66a6a69SVladimir Oltean ds->ops->port_bridge_join) 9304d3a4c6SVivien Didelot return ds->ops->port_bridge_join(ds, info->port, info->br); 9404d3a4c6SVivien Didelot 95f66a6a69SVladimir Oltean if ((dst->index != info->tree_index || ds->index != info->sw_index) && 96f66a6a69SVladimir Oltean ds->ops->crosschip_bridge_join) 97f66a6a69SVladimir Oltean return ds->ops->crosschip_bridge_join(ds, info->tree_index, 98f66a6a69SVladimir Oltean info->sw_index, 9940ef2c93SVivien Didelot info->port, info->br); 10004d3a4c6SVivien Didelot 10104d3a4c6SVivien Didelot return 0; 10204d3a4c6SVivien Didelot } 10304d3a4c6SVivien Didelot 10404d3a4c6SVivien Didelot static int dsa_switch_bridge_leave(struct dsa_switch *ds, 10504d3a4c6SVivien Didelot struct dsa_notifier_bridge_info *info) 10604d3a4c6SVivien Didelot { 107d371b7c9SVladimir Oltean bool unset_vlan_filtering = br_vlan_enabled(info->br); 108f66a6a69SVladimir Oltean struct dsa_switch_tree *dst = ds->dst; 109d371b7c9SVladimir Oltean int err, i; 110d371b7c9SVladimir Oltean 111f66a6a69SVladimir Oltean if (dst->index == info->tree_index && ds->index == info->sw_index && 112f66a6a69SVladimir Oltean ds->ops->port_bridge_join) 11304d3a4c6SVivien Didelot ds->ops->port_bridge_leave(ds, info->port, info->br); 11404d3a4c6SVivien Didelot 115f66a6a69SVladimir Oltean if ((dst->index != info->tree_index || ds->index != info->sw_index) && 116f66a6a69SVladimir Oltean ds->ops->crosschip_bridge_join) 117f66a6a69SVladimir Oltean ds->ops->crosschip_bridge_leave(ds, info->tree_index, 118f66a6a69SVladimir Oltean info->sw_index, info->port, 11940ef2c93SVivien Didelot info->br); 12004d3a4c6SVivien Didelot 121d371b7c9SVladimir Oltean /* If the bridge was vlan_filtering, the bridge core doesn't trigger an 122d371b7c9SVladimir Oltean * event for changing vlan_filtering setting upon slave ports leaving 123d371b7c9SVladimir Oltean * it. That is a good thing, because that lets us handle it and also 124d371b7c9SVladimir Oltean * handle the case where the switch's vlan_filtering setting is global 125d371b7c9SVladimir Oltean * (not per port). When that happens, the correct moment to trigger the 126d371b7c9SVladimir Oltean * vlan_filtering callback is only when the last port left this bridge. 127d371b7c9SVladimir Oltean */ 128d371b7c9SVladimir Oltean if (unset_vlan_filtering && ds->vlan_filtering_is_global) { 129d371b7c9SVladimir Oltean for (i = 0; i < ds->num_ports; i++) { 130d371b7c9SVladimir Oltean if (i == info->port) 131d371b7c9SVladimir Oltean continue; 132d371b7c9SVladimir Oltean if (dsa_to_port(ds, i)->bridge_dev == info->br) { 133d371b7c9SVladimir Oltean unset_vlan_filtering = false; 134d371b7c9SVladimir Oltean break; 135d371b7c9SVladimir Oltean } 136d371b7c9SVladimir Oltean } 137d371b7c9SVladimir Oltean } 138d371b7c9SVladimir Oltean if (unset_vlan_filtering) { 1392e554a7aSVladimir Oltean err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port), 140bae33f2bSVladimir Oltean false); 141d371b7c9SVladimir Oltean if (err && err != EOPNOTSUPP) 142d371b7c9SVladimir Oltean return err; 143d371b7c9SVladimir Oltean } 14404d3a4c6SVivien Didelot return 0; 14504d3a4c6SVivien Didelot } 14604d3a4c6SVivien Didelot 147685fb6a4SVivien Didelot static int dsa_switch_fdb_add(struct dsa_switch *ds, 148685fb6a4SVivien Didelot struct dsa_notifier_fdb_info *info) 149685fb6a4SVivien Didelot { 1503169241fSVivien Didelot int port = dsa_towards_port(ds, info->sw_index, info->port); 151685fb6a4SVivien Didelot 1521b6dd556SArkadi Sharshevsky if (!ds->ops->port_fdb_add) 153685fb6a4SVivien Didelot return -EOPNOTSUPP; 154685fb6a4SVivien Didelot 1553169241fSVivien Didelot return ds->ops->port_fdb_add(ds, port, info->addr, info->vid); 156685fb6a4SVivien Didelot } 157685fb6a4SVivien Didelot 158685fb6a4SVivien Didelot static int dsa_switch_fdb_del(struct dsa_switch *ds, 159685fb6a4SVivien Didelot struct dsa_notifier_fdb_info *info) 160685fb6a4SVivien Didelot { 1613169241fSVivien Didelot int port = dsa_towards_port(ds, info->sw_index, info->port); 162685fb6a4SVivien Didelot 163685fb6a4SVivien Didelot if (!ds->ops->port_fdb_del) 164685fb6a4SVivien Didelot return -EOPNOTSUPP; 165685fb6a4SVivien Didelot 1663169241fSVivien Didelot return ds->ops->port_fdb_del(ds, port, info->addr, info->vid); 167685fb6a4SVivien Didelot } 168685fb6a4SVivien Didelot 169e65d45ccSVivien Didelot static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port, 170e65d45ccSVivien Didelot struct dsa_notifier_mdb_info *info) 171e65d45ccSVivien Didelot { 172e65d45ccSVivien Didelot if (ds->index == info->sw_index && port == info->port) 173e65d45ccSVivien Didelot return true; 174e65d45ccSVivien Didelot 175e65d45ccSVivien Didelot if (dsa_is_dsa_port(ds, port)) 176e65d45ccSVivien Didelot return true; 177e65d45ccSVivien Didelot 178e65d45ccSVivien Didelot return false; 179e65d45ccSVivien Didelot } 180e65d45ccSVivien Didelot 181ffb68fc5SVladimir Oltean static int dsa_switch_mdb_add(struct dsa_switch *ds, 182e65d45ccSVivien Didelot struct dsa_notifier_mdb_info *info) 183e6db98dbSVivien Didelot { 184a52b2da7SVladimir Oltean int err = 0; 185a52b2da7SVladimir Oltean int port; 186e6db98dbSVivien Didelot 187a52b2da7SVladimir Oltean if (!ds->ops->port_mdb_add) 188e6db98dbSVivien Didelot return -EOPNOTSUPP; 189e6db98dbSVivien Didelot 190e65d45ccSVivien Didelot for (port = 0; port < ds->num_ports; port++) { 191e65d45ccSVivien Didelot if (dsa_switch_mdb_match(ds, port, info)) { 192a52b2da7SVladimir Oltean err = ds->ops->port_mdb_add(ds, port, info->mdb); 193e6db98dbSVivien Didelot if (err) 194a52b2da7SVladimir Oltean break; 195a52b2da7SVladimir Oltean } 196a52b2da7SVladimir Oltean } 197a52b2da7SVladimir Oltean 198e6db98dbSVivien Didelot return err; 199e6db98dbSVivien Didelot } 2008ae5bcdcSVivien Didelot 2018ae5bcdcSVivien Didelot static int dsa_switch_mdb_del(struct dsa_switch *ds, 2028ae5bcdcSVivien Didelot struct dsa_notifier_mdb_info *info) 2038ae5bcdcSVivien Didelot { 2048ae5bcdcSVivien Didelot if (!ds->ops->port_mdb_del) 2058ae5bcdcSVivien Didelot return -EOPNOTSUPP; 2068ae5bcdcSVivien Didelot 207a1a6b7eaSVivien Didelot if (ds->index == info->sw_index) 208e65d45ccSVivien Didelot return ds->ops->port_mdb_del(ds, info->port, info->mdb); 209a1a6b7eaSVivien Didelot 210a1a6b7eaSVivien Didelot return 0; 2118ae5bcdcSVivien Didelot } 2128ae5bcdcSVivien Didelot 213e65d45ccSVivien Didelot static bool dsa_switch_vlan_match(struct dsa_switch *ds, int port, 214e65d45ccSVivien Didelot struct dsa_notifier_vlan_info *info) 215e65d45ccSVivien Didelot { 216e65d45ccSVivien Didelot if (ds->index == info->sw_index && port == info->port) 217e65d45ccSVivien Didelot return true; 218e65d45ccSVivien Didelot 2197e1741b4SVivien Didelot if (dsa_is_dsa_port(ds, port)) 220e65d45ccSVivien Didelot return true; 221e65d45ccSVivien Didelot 222e65d45ccSVivien Didelot return false; 223e65d45ccSVivien Didelot } 224e65d45ccSVivien Didelot 225ffb68fc5SVladimir Oltean static int dsa_switch_vlan_add(struct dsa_switch *ds, 226e65d45ccSVivien Didelot struct dsa_notifier_vlan_info *info) 2279c428c59SVivien Didelot { 2289c428c59SVivien Didelot int port, err; 2299c428c59SVivien Didelot 230*1958d581SVladimir Oltean if (!ds->ops->port_vlan_add) 2319c428c59SVivien Didelot return -EOPNOTSUPP; 2329c428c59SVivien Didelot 233e65d45ccSVivien Didelot for (port = 0; port < ds->num_ports; port++) { 234e65d45ccSVivien Didelot if (dsa_switch_vlan_match(ds, port, info)) { 235*1958d581SVladimir Oltean err = ds->ops->port_vlan_add(ds, port, info->vlan); 2369c428c59SVivien Didelot if (err) 2379c428c59SVivien Didelot return err; 2389c428c59SVivien Didelot } 239e65d45ccSVivien Didelot } 2409c428c59SVivien Didelot 241d0c627b8SVivien Didelot return 0; 242d0c627b8SVivien Didelot } 243d0c627b8SVivien Didelot 244d0c627b8SVivien Didelot static int dsa_switch_vlan_del(struct dsa_switch *ds, 245d0c627b8SVivien Didelot struct dsa_notifier_vlan_info *info) 246d0c627b8SVivien Didelot { 247d0c627b8SVivien Didelot if (!ds->ops->port_vlan_del) 248d0c627b8SVivien Didelot return -EOPNOTSUPP; 249d0c627b8SVivien Didelot 2501ca4aa9cSVivien Didelot if (ds->index == info->sw_index) 251e65d45ccSVivien Didelot return ds->ops->port_vlan_del(ds, info->port, info->vlan); 2521ca4aa9cSVivien Didelot 2537e1741b4SVivien Didelot /* Do not deprogram the DSA links as they may be used as conduit 2547e1741b4SVivien Didelot * for other VLAN members in the fabric. 2557e1741b4SVivien Didelot */ 2561ca4aa9cSVivien Didelot return 0; 257d0c627b8SVivien Didelot } 258d0c627b8SVivien Didelot 259f515f192SVivien Didelot static int dsa_switch_event(struct notifier_block *nb, 260f515f192SVivien Didelot unsigned long event, void *info) 261f515f192SVivien Didelot { 262f515f192SVivien Didelot struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb); 263f515f192SVivien Didelot int err; 264f515f192SVivien Didelot 265f515f192SVivien Didelot switch (event) { 2661faabf74SVivien Didelot case DSA_NOTIFIER_AGEING_TIME: 2671faabf74SVivien Didelot err = dsa_switch_ageing_time(ds, info); 2681faabf74SVivien Didelot break; 26904d3a4c6SVivien Didelot case DSA_NOTIFIER_BRIDGE_JOIN: 27004d3a4c6SVivien Didelot err = dsa_switch_bridge_join(ds, info); 27104d3a4c6SVivien Didelot break; 27204d3a4c6SVivien Didelot case DSA_NOTIFIER_BRIDGE_LEAVE: 27304d3a4c6SVivien Didelot err = dsa_switch_bridge_leave(ds, info); 27404d3a4c6SVivien Didelot break; 275685fb6a4SVivien Didelot case DSA_NOTIFIER_FDB_ADD: 276685fb6a4SVivien Didelot err = dsa_switch_fdb_add(ds, info); 277685fb6a4SVivien Didelot break; 278685fb6a4SVivien Didelot case DSA_NOTIFIER_FDB_DEL: 279685fb6a4SVivien Didelot err = dsa_switch_fdb_del(ds, info); 280685fb6a4SVivien Didelot break; 2818ae5bcdcSVivien Didelot case DSA_NOTIFIER_MDB_ADD: 2828ae5bcdcSVivien Didelot err = dsa_switch_mdb_add(ds, info); 2838ae5bcdcSVivien Didelot break; 2848ae5bcdcSVivien Didelot case DSA_NOTIFIER_MDB_DEL: 2858ae5bcdcSVivien Didelot err = dsa_switch_mdb_del(ds, info); 2868ae5bcdcSVivien Didelot break; 287d0c627b8SVivien Didelot case DSA_NOTIFIER_VLAN_ADD: 288d0c627b8SVivien Didelot err = dsa_switch_vlan_add(ds, info); 289d0c627b8SVivien Didelot break; 290d0c627b8SVivien Didelot case DSA_NOTIFIER_VLAN_DEL: 291d0c627b8SVivien Didelot err = dsa_switch_vlan_del(ds, info); 292d0c627b8SVivien Didelot break; 293bfcb8132SVladimir Oltean case DSA_NOTIFIER_MTU: 294bfcb8132SVladimir Oltean err = dsa_switch_mtu(ds, info); 295bfcb8132SVladimir Oltean break; 296f515f192SVivien Didelot default: 297f515f192SVivien Didelot err = -EOPNOTSUPP; 298f515f192SVivien Didelot break; 299f515f192SVivien Didelot } 300f515f192SVivien Didelot 301f515f192SVivien Didelot /* Non-switchdev operations cannot be rolled back. If a DSA driver 302f515f192SVivien Didelot * returns an error during the chained call, switch chips may be in an 303f515f192SVivien Didelot * inconsistent state. 304f515f192SVivien Didelot */ 305f515f192SVivien Didelot if (err) 306f515f192SVivien Didelot dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n", 307f515f192SVivien Didelot event, err); 308f515f192SVivien Didelot 309f515f192SVivien Didelot return notifier_from_errno(err); 310f515f192SVivien Didelot } 311f515f192SVivien Didelot 312f515f192SVivien Didelot int dsa_switch_register_notifier(struct dsa_switch *ds) 313f515f192SVivien Didelot { 314f515f192SVivien Didelot ds->nb.notifier_call = dsa_switch_event; 315f515f192SVivien Didelot 316f515f192SVivien Didelot return raw_notifier_chain_register(&ds->dst->nh, &ds->nb); 317f515f192SVivien Didelot } 318f515f192SVivien Didelot 319f515f192SVivien Didelot void dsa_switch_unregister_notifier(struct dsa_switch *ds) 320f515f192SVivien Didelot { 321f515f192SVivien Didelot int err; 322f515f192SVivien Didelot 323f515f192SVivien Didelot err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb); 324f515f192SVivien Didelot if (err) 325f515f192SVivien Didelot dev_err(ds->dev, "failed to unregister notifier (%d)\n", err); 326f515f192SVivien Didelot } 327