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 { 184*a52b2da7SVladimir Oltean int err = 0; 185*a52b2da7SVladimir Oltean int port; 186e6db98dbSVivien Didelot 187*a52b2da7SVladimir 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)) { 192*a52b2da7SVladimir Oltean err = ds->ops->port_mdb_add(ds, port, info->mdb); 193e6db98dbSVivien Didelot if (err) 194*a52b2da7SVladimir Oltean break; 195*a52b2da7SVladimir Oltean } 196*a52b2da7SVladimir Oltean } 197*a52b2da7SVladimir 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 2309c428c59SVivien Didelot if (!ds->ops->port_vlan_prepare || !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)) { 235e65d45ccSVivien Didelot err = ds->ops->port_vlan_prepare(ds, port, info->vlan); 2369c428c59SVivien Didelot if (err) 2379c428c59SVivien Didelot return err; 2389c428c59SVivien Didelot } 239e65d45ccSVivien Didelot } 2409c428c59SVivien Didelot 241b2f81d30SVivien Didelot for (port = 0; port < ds->num_ports; port++) 242e65d45ccSVivien Didelot if (dsa_switch_vlan_match(ds, port, info)) 243e65d45ccSVivien Didelot ds->ops->port_vlan_add(ds, port, info->vlan); 244d0c627b8SVivien Didelot 245d0c627b8SVivien Didelot return 0; 246d0c627b8SVivien Didelot } 247d0c627b8SVivien Didelot 248d0c627b8SVivien Didelot static int dsa_switch_vlan_del(struct dsa_switch *ds, 249d0c627b8SVivien Didelot struct dsa_notifier_vlan_info *info) 250d0c627b8SVivien Didelot { 251d0c627b8SVivien Didelot if (!ds->ops->port_vlan_del) 252d0c627b8SVivien Didelot return -EOPNOTSUPP; 253d0c627b8SVivien Didelot 2541ca4aa9cSVivien Didelot if (ds->index == info->sw_index) 255e65d45ccSVivien Didelot return ds->ops->port_vlan_del(ds, info->port, info->vlan); 2561ca4aa9cSVivien Didelot 2577e1741b4SVivien Didelot /* Do not deprogram the DSA links as they may be used as conduit 2587e1741b4SVivien Didelot * for other VLAN members in the fabric. 2597e1741b4SVivien Didelot */ 2601ca4aa9cSVivien Didelot return 0; 261d0c627b8SVivien Didelot } 262d0c627b8SVivien Didelot 263f515f192SVivien Didelot static int dsa_switch_event(struct notifier_block *nb, 264f515f192SVivien Didelot unsigned long event, void *info) 265f515f192SVivien Didelot { 266f515f192SVivien Didelot struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb); 267f515f192SVivien Didelot int err; 268f515f192SVivien Didelot 269f515f192SVivien Didelot switch (event) { 2701faabf74SVivien Didelot case DSA_NOTIFIER_AGEING_TIME: 2711faabf74SVivien Didelot err = dsa_switch_ageing_time(ds, info); 2721faabf74SVivien Didelot break; 27304d3a4c6SVivien Didelot case DSA_NOTIFIER_BRIDGE_JOIN: 27404d3a4c6SVivien Didelot err = dsa_switch_bridge_join(ds, info); 27504d3a4c6SVivien Didelot break; 27604d3a4c6SVivien Didelot case DSA_NOTIFIER_BRIDGE_LEAVE: 27704d3a4c6SVivien Didelot err = dsa_switch_bridge_leave(ds, info); 27804d3a4c6SVivien Didelot break; 279685fb6a4SVivien Didelot case DSA_NOTIFIER_FDB_ADD: 280685fb6a4SVivien Didelot err = dsa_switch_fdb_add(ds, info); 281685fb6a4SVivien Didelot break; 282685fb6a4SVivien Didelot case DSA_NOTIFIER_FDB_DEL: 283685fb6a4SVivien Didelot err = dsa_switch_fdb_del(ds, info); 284685fb6a4SVivien Didelot break; 2858ae5bcdcSVivien Didelot case DSA_NOTIFIER_MDB_ADD: 2868ae5bcdcSVivien Didelot err = dsa_switch_mdb_add(ds, info); 2878ae5bcdcSVivien Didelot break; 2888ae5bcdcSVivien Didelot case DSA_NOTIFIER_MDB_DEL: 2898ae5bcdcSVivien Didelot err = dsa_switch_mdb_del(ds, info); 2908ae5bcdcSVivien Didelot break; 291d0c627b8SVivien Didelot case DSA_NOTIFIER_VLAN_ADD: 292d0c627b8SVivien Didelot err = dsa_switch_vlan_add(ds, info); 293d0c627b8SVivien Didelot break; 294d0c627b8SVivien Didelot case DSA_NOTIFIER_VLAN_DEL: 295d0c627b8SVivien Didelot err = dsa_switch_vlan_del(ds, info); 296d0c627b8SVivien Didelot break; 297bfcb8132SVladimir Oltean case DSA_NOTIFIER_MTU: 298bfcb8132SVladimir Oltean err = dsa_switch_mtu(ds, info); 299bfcb8132SVladimir Oltean break; 300f515f192SVivien Didelot default: 301f515f192SVivien Didelot err = -EOPNOTSUPP; 302f515f192SVivien Didelot break; 303f515f192SVivien Didelot } 304f515f192SVivien Didelot 305f515f192SVivien Didelot /* Non-switchdev operations cannot be rolled back. If a DSA driver 306f515f192SVivien Didelot * returns an error during the chained call, switch chips may be in an 307f515f192SVivien Didelot * inconsistent state. 308f515f192SVivien Didelot */ 309f515f192SVivien Didelot if (err) 310f515f192SVivien Didelot dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n", 311f515f192SVivien Didelot event, err); 312f515f192SVivien Didelot 313f515f192SVivien Didelot return notifier_from_errno(err); 314f515f192SVivien Didelot } 315f515f192SVivien Didelot 316f515f192SVivien Didelot int dsa_switch_register_notifier(struct dsa_switch *ds) 317f515f192SVivien Didelot { 318f515f192SVivien Didelot ds->nb.notifier_call = dsa_switch_event; 319f515f192SVivien Didelot 320f515f192SVivien Didelot return raw_notifier_chain_register(&ds->dst->nh, &ds->nb); 321f515f192SVivien Didelot } 322f515f192SVivien Didelot 323f515f192SVivien Didelot void dsa_switch_unregister_notifier(struct dsa_switch *ds) 324f515f192SVivien Didelot { 325f515f192SVivien Didelot int err; 326f515f192SVivien Didelot 327f515f192SVivien Didelot err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb); 328f515f192SVivien Didelot if (err) 329f515f192SVivien Didelot dev_err(ds->dev, "failed to unregister notifier (%d)\n", err); 330f515f192SVivien Didelot } 331