Files
linux-stable-mirror/net/dsa/dsa.c
T
Vladimir Oltean b358fc6ff3 net: dsa: properly keep track of conduit reference
[ Upstream commit 06e219f6a7 ]

Problem description
-------------------

DSA has a mumbo-jumbo of reference handling of the conduit net device
and its kobject which, sadly, is just wrong and doesn't make sense.

There are two distinct problems.

1. The OF path, which uses of_find_net_device_by_node(), never releases
   the elevated refcount on the conduit's kobject. Nominally, the OF and
   non-OF paths should result in objects having identical reference
   counts taken, and it is already suspicious that
   dsa_dev_to_net_device() has a put_device() call which is missing in
   dsa_port_parse_of(), but we can actually even verify that an issue
   exists. With CONFIG_DEBUG_KOBJECT_RELEASE=y, if we run this command
   "before" and "after" applying this patch:

(unbind the conduit driver for net device eno2)
echo 0000:00:00.2 > /sys/bus/pci/drivers/fsl_enetc/unbind

we see these lines in the output diff which appear only with the patch
applied:

kobject: 'eno2' (ffff002009a3a6b8): kobject_release, parent 0000000000000000 (delayed 1000)
kobject: '109' (ffff0020099d59a0): kobject_release, parent 0000000000000000 (delayed 1000)

2. After we find the conduit interface one way (OF) or another (non-OF),
   it can get unregistered at any time, and DSA remains with a long-lived,
   but in this case stale, cpu_dp->conduit pointer. Holding the net
   device's underlying kobject isn't actually of much help, it just
   prevents it from being freed (but we never need that kobject
   directly). What helps us to prevent the net device from being
   unregistered is the parallel netdev reference mechanism (dev_hold()
   and dev_put()).

Actually we actually use that netdev tracker mechanism implicitly on
user ports since commit 2f1e8ea726 ("net: dsa: link interfaces with
the DSA master to get rid of lockdep warnings"), via netdev_upper_dev_link().
But time still passes at DSA switch probe time between the initial
of_find_net_device_by_node() code and the user port creation time, time
during which the conduit could unregister itself and DSA wouldn't know
about it.

So we have to run of_find_net_device_by_node() under rtnl_lock() to
prevent that from happening, and release the lock only with the netdev
tracker having acquired the reference.

Do we need to keep the reference until dsa_unregister_switch() /
dsa_switch_shutdown()?
1: Maybe yes. A switch device will still be registered even if all user
   ports failed to probe, see commit 86f8b1c01a ("net: dsa: Do not
   make user port errors fatal"), and the cpu_dp->conduit pointers
   remain valid.  I haven't audited all call paths to see whether they
   will actually use the conduit in lack of any user port, but if they
   do, it seems safer to not rely on user ports for that reference.
2. Definitely yes. We support changing the conduit which a user port is
   associated to, and we can get into a situation where we've moved all
   user ports away from a conduit, thus no longer hold any reference to
   it via the net device tracker. But we shouldn't let it go nonetheless
   - see the next change in relation to dsa_tree_find_first_conduit()
   and LAG conduits which disappear.
   We have to be prepared to return to the physical conduit, so the CPU
   port must explicitly keep another reference to it. This is also to
   say: the user ports and their CPU ports may not always keep a
   reference to the same conduit net device, and both are needed.

As for the conduit's kobject for the /sys/class/net/ entry, we don't
care about it, we can release it as soon as we hold the net device
object itself.

History and blame attribution
-----------------------------

The code has been refactored so many times, it is very difficult to
follow and properly attribute a blame, but I'll try to make a short
history which I hope to be correct.

We have two distinct probing paths:
- one for OF, introduced in 2016 in commit 83c0afaec7 ("net: dsa: Add
  new binding implementation")
- one for non-OF, introduced in 2017 in commit 71e0bbde0d ("net: dsa:
  Add support for platform data")

These are both complete rewrites of the original probing paths (which
used struct dsa_switch_driver and other weird stuff, instead of regular
devices on their respective buses for register access, like MDIO, SPI,
I2C etc):
- one for OF, introduced in 2013 in commit 5e95329b70 ("dsa: add
  device tree bindings to register DSA switches")
- one for non-OF, introduced in 2008 in commit 91da11f870 ("net:
  Distributed Switch Architecture protocol support")

except for tiny bits and pieces like dsa_dev_to_net_device() which were
seemingly carried over since the original commit, and used to this day.

The point is that the original probing paths received a fix in 2015 in
the form of commit 679fb46c57 ("net: dsa: Add missing master netdev
dev_put() calls"), but the fix never made it into the "new" (dsa2)
probing paths that can still be traced to today, and the fixed probing
path was later deleted in 2019 in commit 93e86b3bc8 ("net: dsa: Remove
legacy probing support").

That is to say, the new probing paths were never quite correct in this
area.

The existence of the legacy probing support which was deleted in 2019
explains why dsa_dev_to_net_device() returns a conduit with elevated
refcount (because it was supposed to be released during
dsa_remove_dst()). After the removal of the legacy code, the only user
of dsa_dev_to_net_device() calls dev_put(conduit) immediately after this
function returns. This pattern makes no sense today, and can only be
interpreted historically to understand why dev_hold() was there in the
first place.

Change details
--------------

Today we have a better netdev tracking infrastructure which we should
use. Logically netdev_hold() belongs in common code
(dsa_port_parse_cpu(), where dp->conduit is assigned), but there is a
tradeoff to be made with the rtnl_lock() section which would become a
bit too long if we did that - dsa_port_parse_cpu() also calls
request_module(). So we duplicate a bit of logic in order for the
callers of dsa_port_parse_cpu() to be the ones responsible of holding
the conduit reference and releasing it on error. This shortens the
rtnl_lock() section significantly.

In the dsa_switch_probe() error path, dsa_switch_release_ports() will be
called in a number of situations, one being where dsa_port_parse_cpu()
maybe didn't get the chance to run at all (a different port failed
earlier, etc). So we have to test for the conduit being NULL prior to
calling netdev_put().

There have still been so many transformations to the code since the
blamed commits (rename master -> conduit, commit 0650bf52b3 ("net:
dsa: be compatible with masters which unregister on shutdown")), that it
only makes sense to fix the code using the best methods available today
and see how it can be backported to stable later. I suspect the fix
cannot even be backported to kernels which lack dsa_switch_shutdown(),
and I suspect this is also maybe why the long-lived conduit reference
didn't make it into the new DSA probing paths at the time (problems
during shutdown).

Because dsa_dev_to_net_device() has a single call site and has to be
changed anyway, the logic was just absorbed into the non-OF
dsa_port_parse().

Tested on the ocelot/felix switch and on dsa_loop, both on the NXP
LS1028A with CONFIG_DEBUG_KOBJECT_RELEASE=y.

Reported-by: Ma Ke <make24@iscas.ac.cn>
Closes: https://lore.kernel.org/netdev/20251214131204.4684-1-make24@iscas.ac.cn/
Fixes: 83c0afaec7 ("net: dsa: Add new binding implementation")
Fixes: 71e0bbde0d ("net: dsa: Add support for platform data")
Reviewed-by: Jonas Gorski <jonas.gorski@gmail.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://patch.msgid.link/20251215150236.3931670-1-vladimir.oltean@nxp.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Robert Garcia <rob_garcia@163.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2026-03-25 11:08:45 +01:00

1843 lines
39 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DSA topology and switch handling
*
* Copyright (c) 2008-2009 Marvell Semiconductor
* Copyright (c) 2013 Florian Fainelli <florian@openwrt.org>
* Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <linux/rtnetlink.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <net/dsa_stubs.h>
#include <net/sch_generic.h>
#include "conduit.h"
#include "devlink.h"
#include "dsa.h"
#include "netlink.h"
#include "port.h"
#include "switch.h"
#include "tag.h"
#include "user.h"
#define DSA_MAX_NUM_OFFLOADING_BRIDGES BITS_PER_LONG
static DEFINE_MUTEX(dsa2_mutex);
LIST_HEAD(dsa_tree_list);
static struct workqueue_struct *dsa_owq;
/* Track the bridges with forwarding offload enabled */
static unsigned long dsa_fwd_offloading_bridges;
bool dsa_schedule_work(struct work_struct *work)
{
return queue_work(dsa_owq, work);
}
void dsa_flush_workqueue(void)
{
flush_workqueue(dsa_owq);
}
EXPORT_SYMBOL_GPL(dsa_flush_workqueue);
/**
* dsa_lag_map() - Map LAG structure to a linear LAG array
* @dst: Tree in which to record the mapping.
* @lag: LAG structure that is to be mapped to the tree's array.
*
* dsa_lag_id/dsa_lag_by_id can then be used to translate between the
* two spaces. The size of the mapping space is determined by the
* driver by setting ds->num_lag_ids. It is perfectly legal to leave
* it unset if it is not needed, in which case these functions become
* no-ops.
*/
void dsa_lag_map(struct dsa_switch_tree *dst, struct dsa_lag *lag)
{
unsigned int id;
for (id = 1; id <= dst->lags_len; id++) {
if (!dsa_lag_by_id(dst, id)) {
dst->lags[id - 1] = lag;
lag->id = id;
return;
}
}
/* No IDs left, which is OK. Some drivers do not need it. The
* ones that do, e.g. mv88e6xxx, will discover that dsa_lag_id
* returns an error for this device when joining the LAG. The
* driver can then return -EOPNOTSUPP back to DSA, which will
* fall back to a software LAG.
*/
}
/**
* dsa_lag_unmap() - Remove a LAG ID mapping
* @dst: Tree in which the mapping is recorded.
* @lag: LAG structure that was mapped.
*
* As there may be multiple users of the mapping, it is only removed
* if there are no other references to it.
*/
void dsa_lag_unmap(struct dsa_switch_tree *dst, struct dsa_lag *lag)
{
unsigned int id;
dsa_lags_foreach_id(id, dst) {
if (dsa_lag_by_id(dst, id) == lag) {
dst->lags[id - 1] = NULL;
lag->id = 0;
break;
}
}
}
struct dsa_lag *dsa_tree_lag_find(struct dsa_switch_tree *dst,
const struct net_device *lag_dev)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_lag_dev_get(dp) == lag_dev)
return dp->lag;
return NULL;
}
struct dsa_bridge *dsa_tree_bridge_find(struct dsa_switch_tree *dst,
const struct net_device *br)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_bridge_dev_get(dp) == br)
return dp->bridge;
return NULL;
}
static int dsa_bridge_num_find(const struct net_device *bridge_dev)
{
struct dsa_switch_tree *dst;
list_for_each_entry(dst, &dsa_tree_list, list) {
struct dsa_bridge *bridge;
bridge = dsa_tree_bridge_find(dst, bridge_dev);
if (bridge)
return bridge->num;
}
return 0;
}
unsigned int dsa_bridge_num_get(const struct net_device *bridge_dev, int max)
{
unsigned int bridge_num = dsa_bridge_num_find(bridge_dev);
/* Switches without FDB isolation support don't get unique
* bridge numbering
*/
if (!max)
return 0;
if (!bridge_num) {
/* First port that requests FDB isolation or TX forwarding
* offload for this bridge
*/
bridge_num = find_next_zero_bit(&dsa_fwd_offloading_bridges,
DSA_MAX_NUM_OFFLOADING_BRIDGES,
1);
if (bridge_num > max)
return 0;
set_bit(bridge_num, &dsa_fwd_offloading_bridges);
}
return bridge_num;
}
void dsa_bridge_num_put(const struct net_device *bridge_dev,
unsigned int bridge_num)
{
/* Since we refcount bridges, we know that when we call this function
* it is no longer in use, so we can just go ahead and remove it from
* the bit mask.
*/
clear_bit(bridge_num, &dsa_fwd_offloading_bridges);
}
struct dsa_switch *dsa_switch_find(int tree_index, int sw_index)
{
struct dsa_switch_tree *dst;
struct dsa_port *dp;
list_for_each_entry(dst, &dsa_tree_list, list) {
if (dst->index != tree_index)
continue;
list_for_each_entry(dp, &dst->ports, list) {
if (dp->ds->index != sw_index)
continue;
return dp->ds;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(dsa_switch_find);
static struct dsa_switch_tree *dsa_tree_find(int index)
{
struct dsa_switch_tree *dst;
list_for_each_entry(dst, &dsa_tree_list, list)
if (dst->index == index)
return dst;
return NULL;
}
static struct dsa_switch_tree *dsa_tree_alloc(int index)
{
struct dsa_switch_tree *dst;
dst = kzalloc(sizeof(*dst), GFP_KERNEL);
if (!dst)
return NULL;
dst->index = index;
INIT_LIST_HEAD(&dst->rtable);
INIT_LIST_HEAD(&dst->ports);
INIT_LIST_HEAD(&dst->list);
list_add_tail(&dst->list, &dsa_tree_list);
kref_init(&dst->refcount);
return dst;
}
static void dsa_tree_free(struct dsa_switch_tree *dst)
{
if (dst->tag_ops)
dsa_tag_driver_put(dst->tag_ops);
list_del(&dst->list);
kfree(dst);
}
static struct dsa_switch_tree *dsa_tree_get(struct dsa_switch_tree *dst)
{
if (dst)
kref_get(&dst->refcount);
return dst;
}
static struct dsa_switch_tree *dsa_tree_touch(int index)
{
struct dsa_switch_tree *dst;
dst = dsa_tree_find(index);
if (dst)
return dsa_tree_get(dst);
else
return dsa_tree_alloc(index);
}
static void dsa_tree_release(struct kref *ref)
{
struct dsa_switch_tree *dst;
dst = container_of(ref, struct dsa_switch_tree, refcount);
dsa_tree_free(dst);
}
static void dsa_tree_put(struct dsa_switch_tree *dst)
{
if (dst)
kref_put(&dst->refcount, dsa_tree_release);
}
static struct dsa_port *dsa_tree_find_port_by_node(struct dsa_switch_tree *dst,
struct device_node *dn)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dp->dn == dn)
return dp;
return NULL;
}
static struct dsa_link *dsa_link_touch(struct dsa_port *dp,
struct dsa_port *link_dp)
{
struct dsa_switch *ds = dp->ds;
struct dsa_switch_tree *dst;
struct dsa_link *dl;
dst = ds->dst;
list_for_each_entry(dl, &dst->rtable, list)
if (dl->dp == dp && dl->link_dp == link_dp)
return dl;
dl = kzalloc(sizeof(*dl), GFP_KERNEL);
if (!dl)
return NULL;
dl->dp = dp;
dl->link_dp = link_dp;
INIT_LIST_HEAD(&dl->list);
list_add_tail(&dl->list, &dst->rtable);
return dl;
}
static bool dsa_port_setup_routing_table(struct dsa_port *dp)
{
struct dsa_switch *ds = dp->ds;
struct dsa_switch_tree *dst = ds->dst;
struct device_node *dn = dp->dn;
struct of_phandle_iterator it;
struct dsa_port *link_dp;
struct dsa_link *dl;
int err;
of_for_each_phandle(&it, err, dn, "link", NULL, 0) {
link_dp = dsa_tree_find_port_by_node(dst, it.node);
if (!link_dp) {
of_node_put(it.node);
return false;
}
dl = dsa_link_touch(dp, link_dp);
if (!dl) {
of_node_put(it.node);
return false;
}
}
return true;
}
static bool dsa_tree_setup_routing_table(struct dsa_switch_tree *dst)
{
bool complete = true;
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list) {
if (dsa_port_is_dsa(dp)) {
complete = dsa_port_setup_routing_table(dp);
if (!complete)
break;
}
}
return complete;
}
static struct dsa_port *dsa_tree_find_first_cpu(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_is_cpu(dp))
return dp;
return NULL;
}
struct net_device *dsa_tree_find_first_conduit(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp;
cpu_dp = dsa_tree_find_first_cpu(dst);
return cpu_dp->conduit;
}
/* Assign the default CPU port (the first one in the tree) to all ports of the
* fabric which don't already have one as part of their own switch.
*/
static int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp, *dp;
cpu_dp = dsa_tree_find_first_cpu(dst);
if (!cpu_dp) {
pr_err("DSA: tree %d has no CPU port\n", dst->index);
return -EINVAL;
}
list_for_each_entry(dp, &dst->ports, list) {
if (dp->cpu_dp)
continue;
if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
dp->cpu_dp = cpu_dp;
}
return 0;
}
static struct dsa_port *
dsa_switch_preferred_default_local_cpu_port(struct dsa_switch *ds)
{
struct dsa_port *cpu_dp;
if (!ds->ops->preferred_default_local_cpu_port)
return NULL;
cpu_dp = ds->ops->preferred_default_local_cpu_port(ds);
if (!cpu_dp)
return NULL;
if (WARN_ON(!dsa_port_is_cpu(cpu_dp) || cpu_dp->ds != ds))
return NULL;
return cpu_dp;
}
/* Perform initial assignment of CPU ports to user ports and DSA links in the
* fabric, giving preference to CPU ports local to each switch. Default to
* using the first CPU port in the switch tree if the port does not have a CPU
* port local to this switch.
*/
static int dsa_tree_setup_cpu_ports(struct dsa_switch_tree *dst)
{
struct dsa_port *preferred_cpu_dp, *cpu_dp, *dp;
list_for_each_entry(cpu_dp, &dst->ports, list) {
if (!dsa_port_is_cpu(cpu_dp))
continue;
preferred_cpu_dp = dsa_switch_preferred_default_local_cpu_port(cpu_dp->ds);
if (preferred_cpu_dp && preferred_cpu_dp != cpu_dp)
continue;
/* Prefer a local CPU port */
dsa_switch_for_each_port(dp, cpu_dp->ds) {
/* Prefer the first local CPU port found */
if (dp->cpu_dp)
continue;
if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
dp->cpu_dp = cpu_dp;
}
}
return dsa_tree_setup_default_cpu(dst);
}
static void dsa_tree_teardown_cpu_ports(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
dp->cpu_dp = NULL;
}
static int dsa_port_setup(struct dsa_port *dp)
{
bool dsa_port_link_registered = false;
struct dsa_switch *ds = dp->ds;
bool dsa_port_enabled = false;
int err = 0;
if (dp->setup)
return 0;
err = dsa_port_devlink_setup(dp);
if (err)
return err;
switch (dp->type) {
case DSA_PORT_TYPE_UNUSED:
dsa_port_disable(dp);
break;
case DSA_PORT_TYPE_CPU:
if (dp->dn) {
err = dsa_shared_port_link_register_of(dp);
if (err)
break;
dsa_port_link_registered = true;
} else {
dev_warn(ds->dev,
"skipping link registration for CPU port %d\n",
dp->index);
}
err = dsa_port_enable(dp, NULL);
if (err)
break;
dsa_port_enabled = true;
break;
case DSA_PORT_TYPE_DSA:
if (dp->dn) {
err = dsa_shared_port_link_register_of(dp);
if (err)
break;
dsa_port_link_registered = true;
} else {
dev_warn(ds->dev,
"skipping link registration for DSA port %d\n",
dp->index);
}
err = dsa_port_enable(dp, NULL);
if (err)
break;
dsa_port_enabled = true;
break;
case DSA_PORT_TYPE_USER:
of_get_mac_address(dp->dn, dp->mac);
err = dsa_user_create(dp);
break;
}
if (err && dsa_port_enabled)
dsa_port_disable(dp);
if (err && dsa_port_link_registered)
dsa_shared_port_link_unregister_of(dp);
if (err) {
dsa_port_devlink_teardown(dp);
return err;
}
dp->setup = true;
return 0;
}
static void dsa_port_teardown(struct dsa_port *dp)
{
if (!dp->setup)
return;
switch (dp->type) {
case DSA_PORT_TYPE_UNUSED:
break;
case DSA_PORT_TYPE_CPU:
dsa_port_disable(dp);
if (dp->dn)
dsa_shared_port_link_unregister_of(dp);
break;
case DSA_PORT_TYPE_DSA:
dsa_port_disable(dp);
if (dp->dn)
dsa_shared_port_link_unregister_of(dp);
break;
case DSA_PORT_TYPE_USER:
if (dp->user) {
dsa_user_destroy(dp->user);
dp->user = NULL;
}
break;
}
dsa_port_devlink_teardown(dp);
dp->setup = false;
}
static int dsa_port_setup_as_unused(struct dsa_port *dp)
{
dp->type = DSA_PORT_TYPE_UNUSED;
return dsa_port_setup(dp);
}
static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)
{
const struct dsa_device_ops *tag_ops = ds->dst->tag_ops;
struct dsa_switch_tree *dst = ds->dst;
int err;
if (tag_ops->proto == dst->default_proto)
goto connect;
rtnl_lock();
err = ds->ops->change_tag_protocol(ds, tag_ops->proto);
rtnl_unlock();
if (err) {
dev_err(ds->dev, "Unable to use tag protocol \"%s\": %pe\n",
tag_ops->name, ERR_PTR(err));
return err;
}
connect:
if (tag_ops->connect) {
err = tag_ops->connect(ds);
if (err)
return err;
}
if (ds->ops->connect_tag_protocol) {
err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
if (err) {
dev_err(ds->dev,
"Unable to connect to tag protocol \"%s\": %pe\n",
tag_ops->name, ERR_PTR(err));
goto disconnect;
}
}
return 0;
disconnect:
if (tag_ops->disconnect)
tag_ops->disconnect(ds);
return err;
}
static void dsa_switch_teardown_tag_protocol(struct dsa_switch *ds)
{
const struct dsa_device_ops *tag_ops = ds->dst->tag_ops;
if (tag_ops->disconnect)
tag_ops->disconnect(ds);
}
static int dsa_switch_setup(struct dsa_switch *ds)
{
int err;
if (ds->setup)
return 0;
/* Initialize ds->phys_mii_mask before registering the user MDIO bus
* driver and before ops->setup() has run, since the switch drivers and
* the user MDIO bus driver rely on these values for probing PHY
* devices or not
*/
ds->phys_mii_mask |= dsa_user_ports(ds);
err = dsa_switch_devlink_alloc(ds);
if (err)
return err;
err = dsa_switch_register_notifier(ds);
if (err)
goto devlink_free;
ds->configure_vlan_while_not_filtering = true;
err = ds->ops->setup(ds);
if (err < 0)
goto unregister_notifier;
err = dsa_switch_setup_tag_protocol(ds);
if (err)
goto teardown;
if (!ds->user_mii_bus && ds->ops->phy_read) {
ds->user_mii_bus = mdiobus_alloc();
if (!ds->user_mii_bus) {
err = -ENOMEM;
goto teardown;
}
dsa_user_mii_bus_init(ds);
err = mdiobus_register(ds->user_mii_bus);
if (err < 0)
goto free_user_mii_bus;
}
dsa_switch_devlink_register(ds);
ds->setup = true;
return 0;
free_user_mii_bus:
if (ds->user_mii_bus && ds->ops->phy_read)
mdiobus_free(ds->user_mii_bus);
teardown:
if (ds->ops->teardown)
ds->ops->teardown(ds);
unregister_notifier:
dsa_switch_unregister_notifier(ds);
devlink_free:
dsa_switch_devlink_free(ds);
return err;
}
static void dsa_switch_teardown(struct dsa_switch *ds)
{
if (!ds->setup)
return;
dsa_switch_devlink_unregister(ds);
if (ds->user_mii_bus && ds->ops->phy_read) {
mdiobus_unregister(ds->user_mii_bus);
mdiobus_free(ds->user_mii_bus);
ds->user_mii_bus = NULL;
}
dsa_switch_teardown_tag_protocol(ds);
if (ds->ops->teardown)
ds->ops->teardown(ds);
dsa_switch_unregister_notifier(ds);
dsa_switch_devlink_free(ds);
ds->setup = false;
}
/* First tear down the non-shared, then the shared ports. This ensures that
* all work items scheduled by our switchdev handlers for user ports have
* completed before we destroy the refcounting kept on the shared ports.
*/
static void dsa_tree_teardown_ports(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_is_user(dp) || dsa_port_is_unused(dp))
dsa_port_teardown(dp);
dsa_flush_workqueue();
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp))
dsa_port_teardown(dp);
}
static void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
dsa_switch_teardown(dp->ds);
}
/* Bring shared ports up first, then non-shared ports */
static int dsa_tree_setup_ports(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
int err = 0;
list_for_each_entry(dp, &dst->ports, list) {
if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) {
err = dsa_port_setup(dp);
if (err)
goto teardown;
}
}
list_for_each_entry(dp, &dst->ports, list) {
if (dsa_port_is_user(dp) || dsa_port_is_unused(dp)) {
err = dsa_port_setup(dp);
if (err) {
err = dsa_port_setup_as_unused(dp);
if (err)
goto teardown;
}
}
}
return 0;
teardown:
dsa_tree_teardown_ports(dst);
return err;
}
static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
int err = 0;
list_for_each_entry(dp, &dst->ports, list) {
err = dsa_switch_setup(dp->ds);
if (err) {
dsa_tree_teardown_switches(dst);
break;
}
}
return err;
}
static int dsa_tree_setup_conduit(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp;
int err = 0;
rtnl_lock();
dsa_tree_for_each_cpu_port(cpu_dp, dst) {
struct net_device *conduit = cpu_dp->conduit;
bool admin_up = (conduit->flags & IFF_UP) &&
!qdisc_tx_is_noop(conduit);
err = dsa_conduit_setup(conduit, cpu_dp);
if (err)
break;
/* Replay conduit state event */
dsa_tree_conduit_admin_state_change(dst, conduit, admin_up);
dsa_tree_conduit_oper_state_change(dst, conduit,
netif_oper_up(conduit));
}
rtnl_unlock();
return err;
}
static void dsa_tree_teardown_conduit(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp;
rtnl_lock();
dsa_tree_for_each_cpu_port(cpu_dp, dst) {
struct net_device *conduit = cpu_dp->conduit;
/* Synthesizing an "admin down" state is sufficient for
* the switches to get a notification if the conduit is
* currently up and running.
*/
dsa_tree_conduit_admin_state_change(dst, conduit, false);
dsa_conduit_teardown(conduit);
}
rtnl_unlock();
}
static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
{
unsigned int len = 0;
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list) {
if (dp->ds->num_lag_ids > len)
len = dp->ds->num_lag_ids;
}
if (!len)
return 0;
dst->lags = kcalloc(len, sizeof(*dst->lags), GFP_KERNEL);
if (!dst->lags)
return -ENOMEM;
dst->lags_len = len;
return 0;
}
static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst)
{
kfree(dst->lags);
}
static void dsa_tree_teardown_routing_table(struct dsa_switch_tree *dst)
{
struct dsa_link *dl, *next;
list_for_each_entry_safe(dl, next, &dst->rtable, list) {
list_del(&dl->list);
kfree(dl);
}
}
static int dsa_tree_setup(struct dsa_switch_tree *dst)
{
bool complete;
int err;
if (dst->setup) {
pr_err("DSA: tree %d already setup! Disjoint trees?\n",
dst->index);
return -EEXIST;
}
complete = dsa_tree_setup_routing_table(dst);
if (!complete)
return 0;
err = dsa_tree_setup_cpu_ports(dst);
if (err)
goto teardown_rtable;
err = dsa_tree_setup_switches(dst);
if (err)
goto teardown_cpu_ports;
err = dsa_tree_setup_ports(dst);
if (err)
goto teardown_switches;
err = dsa_tree_setup_conduit(dst);
if (err)
goto teardown_ports;
err = dsa_tree_setup_lags(dst);
if (err)
goto teardown_conduit;
dst->setup = true;
pr_info("DSA: tree %d setup\n", dst->index);
return 0;
teardown_conduit:
dsa_tree_teardown_conduit(dst);
teardown_ports:
dsa_tree_teardown_ports(dst);
teardown_switches:
dsa_tree_teardown_switches(dst);
teardown_cpu_ports:
dsa_tree_teardown_cpu_ports(dst);
teardown_rtable:
dsa_tree_teardown_routing_table(dst);
return err;
}
static void dsa_tree_teardown(struct dsa_switch_tree *dst)
{
if (!dst->setup)
return;
dsa_tree_teardown_lags(dst);
dsa_tree_teardown_conduit(dst);
dsa_tree_teardown_ports(dst);
dsa_tree_teardown_switches(dst);
dsa_tree_teardown_cpu_ports(dst);
dsa_tree_teardown_routing_table(dst);
pr_info("DSA: tree %d torn down\n", dst->index);
dst->setup = false;
}
static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
const struct dsa_device_ops *tag_ops)
{
const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
struct dsa_notifier_tag_proto_info info;
int err;
dst->tag_ops = tag_ops;
/* Notify the switches from this tree about the connection
* to the new tagger
*/
info.tag_ops = tag_ops;
err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
if (err && err != -EOPNOTSUPP)
goto out_disconnect;
/* Notify the old tagger about the disconnection from this tree */
info.tag_ops = old_tag_ops;
dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
return 0;
out_disconnect:
info.tag_ops = tag_ops;
dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
dst->tag_ops = old_tag_ops;
return err;
}
/* Since the dsa/tagging sysfs device attribute is per conduit, the assumption
* is that all DSA switches within a tree share the same tagger, otherwise
* they would have formed disjoint trees (different "dsa,member" values).
*/
int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
const struct dsa_device_ops *tag_ops,
const struct dsa_device_ops *old_tag_ops)
{
struct dsa_notifier_tag_proto_info info;
struct dsa_port *dp;
int err = -EBUSY;
if (!rtnl_trylock())
return restart_syscall();
/* At the moment we don't allow changing the tag protocol under
* traffic. The rtnl_mutex also happens to serialize concurrent
* attempts to change the tagging protocol. If we ever lift the IFF_UP
* restriction, there needs to be another mutex which serializes this.
*/
dsa_tree_for_each_user_port(dp, dst) {
if (dsa_port_to_conduit(dp)->flags & IFF_UP)
goto out_unlock;
if (dp->user->flags & IFF_UP)
goto out_unlock;
}
/* Notify the tag protocol change */
info.tag_ops = tag_ops;
err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
if (err)
goto out_unwind_tagger;
err = dsa_tree_bind_tag_proto(dst, tag_ops);
if (err)
goto out_unwind_tagger;
rtnl_unlock();
return 0;
out_unwind_tagger:
info.tag_ops = old_tag_ops;
dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
out_unlock:
rtnl_unlock();
return err;
}
static void dsa_tree_conduit_state_change(struct dsa_switch_tree *dst,
struct net_device *conduit)
{
struct dsa_notifier_conduit_state_info info;
struct dsa_port *cpu_dp = conduit->dsa_ptr;
info.conduit = conduit;
info.operational = dsa_port_conduit_is_operational(cpu_dp);
dsa_tree_notify(dst, DSA_NOTIFIER_CONDUIT_STATE_CHANGE, &info);
}
void dsa_tree_conduit_admin_state_change(struct dsa_switch_tree *dst,
struct net_device *conduit,
bool up)
{
struct dsa_port *cpu_dp = conduit->dsa_ptr;
bool notify = false;
/* Don't keep track of admin state on LAG DSA conduits,
* but rather just of physical DSA conduits
*/
if (netif_is_lag_master(conduit))
return;
if ((dsa_port_conduit_is_operational(cpu_dp)) !=
(up && cpu_dp->conduit_oper_up))
notify = true;
cpu_dp->conduit_admin_up = up;
if (notify)
dsa_tree_conduit_state_change(dst, conduit);
}
void dsa_tree_conduit_oper_state_change(struct dsa_switch_tree *dst,
struct net_device *conduit,
bool up)
{
struct dsa_port *cpu_dp = conduit->dsa_ptr;
bool notify = false;
/* Don't keep track of oper state on LAG DSA conduits,
* but rather just of physical DSA conduits
*/
if (netif_is_lag_master(conduit))
return;
if ((dsa_port_conduit_is_operational(cpu_dp)) !=
(cpu_dp->conduit_admin_up && up))
notify = true;
cpu_dp->conduit_oper_up = up;
if (notify)
dsa_tree_conduit_state_change(dst, conduit);
}
static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
{
struct dsa_switch_tree *dst = ds->dst;
struct dsa_port *dp;
dsa_switch_for_each_port(dp, ds)
if (dp->index == index)
return dp;
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
if (!dp)
return NULL;
dp->ds = ds;
dp->index = index;
mutex_init(&dp->addr_lists_lock);
mutex_init(&dp->vlans_lock);
INIT_LIST_HEAD(&dp->fdbs);
INIT_LIST_HEAD(&dp->mdbs);
INIT_LIST_HEAD(&dp->vlans); /* also initializes &dp->user_vlans */
INIT_LIST_HEAD(&dp->list);
list_add_tail(&dp->list, &dst->ports);
return dp;
}
static int dsa_port_parse_user(struct dsa_port *dp, const char *name)
{
dp->type = DSA_PORT_TYPE_USER;
dp->name = name;
return 0;
}
static int dsa_port_parse_dsa(struct dsa_port *dp)
{
dp->type = DSA_PORT_TYPE_DSA;
return 0;
}
static enum dsa_tag_protocol dsa_get_tag_protocol(struct dsa_port *dp,
struct net_device *conduit)
{
enum dsa_tag_protocol tag_protocol = DSA_TAG_PROTO_NONE;
struct dsa_switch *mds, *ds = dp->ds;
unsigned int mdp_upstream;
struct dsa_port *mdp;
/* It is possible to stack DSA switches onto one another when that
* happens the switch driver may want to know if its tagging protocol
* is going to work in such a configuration.
*/
if (dsa_user_dev_check(conduit)) {
mdp = dsa_user_to_port(conduit);
mds = mdp->ds;
mdp_upstream = dsa_upstream_port(mds, mdp->index);
tag_protocol = mds->ops->get_tag_protocol(mds, mdp_upstream,
DSA_TAG_PROTO_NONE);
}
/* If the conduit device is not itself a DSA user in a disjoint DSA
* tree, then return immediately.
*/
return ds->ops->get_tag_protocol(ds, dp->index, tag_protocol);
}
static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *conduit,
const char *user_protocol)
{
const struct dsa_device_ops *tag_ops = NULL;
struct dsa_switch *ds = dp->ds;
struct dsa_switch_tree *dst = ds->dst;
enum dsa_tag_protocol default_proto;
/* Find out which protocol the switch would prefer. */
default_proto = dsa_get_tag_protocol(dp, conduit);
if (dst->default_proto) {
if (dst->default_proto != default_proto) {
dev_err(ds->dev,
"A DSA switch tree can have only one tagging protocol\n");
return -EINVAL;
}
} else {
dst->default_proto = default_proto;
}
/* See if the user wants to override that preference. */
if (user_protocol) {
if (!ds->ops->change_tag_protocol) {
dev_err(ds->dev, "Tag protocol cannot be modified\n");
return -EINVAL;
}
tag_ops = dsa_tag_driver_get_by_name(user_protocol);
if (IS_ERR(tag_ops)) {
dev_warn(ds->dev,
"Failed to find a tagging driver for protocol %s, using default\n",
user_protocol);
tag_ops = NULL;
}
}
if (!tag_ops)
tag_ops = dsa_tag_driver_get_by_id(default_proto);
if (IS_ERR(tag_ops)) {
if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
return -EPROBE_DEFER;
dev_warn(ds->dev, "No tagger for this switch\n");
return PTR_ERR(tag_ops);
}
if (dst->tag_ops) {
if (dst->tag_ops != tag_ops) {
dev_err(ds->dev,
"A DSA switch tree can have only one tagging protocol\n");
dsa_tag_driver_put(tag_ops);
return -EINVAL;
}
/* In the case of multiple CPU ports per switch, the tagging
* protocol is still reference-counted only per switch tree.
*/
dsa_tag_driver_put(tag_ops);
} else {
dst->tag_ops = tag_ops;
}
dp->conduit = conduit;
dp->type = DSA_PORT_TYPE_CPU;
dsa_port_set_tag_protocol(dp, dst->tag_ops);
dp->dst = dst;
/* At this point, the tree may be configured to use a different
* tagger than the one chosen by the switch driver during
* .setup, in the case when a user selects a custom protocol
* through the DT.
*
* This is resolved by syncing the driver with the tree in
* dsa_switch_setup_tag_protocol once .setup has run and the
* driver is ready to accept calls to .change_tag_protocol. If
* the driver does not support the custom protocol at that
* point, the tree is wholly rejected, thereby ensuring that the
* tree and driver are always in agreement on the protocol to
* use.
*/
return 0;
}
static int dsa_port_parse_of(struct dsa_port *dp, struct device_node *dn)
{
struct device_node *ethernet = of_parse_phandle(dn, "ethernet", 0);
const char *name = of_get_property(dn, "label", NULL);
bool link = of_property_read_bool(dn, "link");
dp->dn = dn;
if (ethernet) {
struct net_device *conduit;
const char *user_protocol;
int err;
rtnl_lock();
conduit = of_find_net_device_by_node(ethernet);
of_node_put(ethernet);
if (!conduit) {
rtnl_unlock();
return -EPROBE_DEFER;
}
netdev_hold(conduit, &dp->conduit_tracker, GFP_KERNEL);
put_device(&conduit->dev);
rtnl_unlock();
user_protocol = of_get_property(dn, "dsa-tag-protocol", NULL);
err = dsa_port_parse_cpu(dp, conduit, user_protocol);
if (err)
netdev_put(conduit, &dp->conduit_tracker);
return err;
}
if (link)
return dsa_port_parse_dsa(dp);
return dsa_port_parse_user(dp, name);
}
static int dsa_switch_parse_ports_of(struct dsa_switch *ds,
struct device_node *dn)
{
struct device_node *ports, *port;
struct dsa_port *dp;
int err = 0;
u32 reg;
ports = of_get_child_by_name(dn, "ports");
if (!ports) {
/* The second possibility is "ethernet-ports" */
ports = of_get_child_by_name(dn, "ethernet-ports");
if (!ports) {
dev_err(ds->dev, "no ports child node found\n");
return -EINVAL;
}
}
for_each_available_child_of_node(ports, port) {
err = of_property_read_u32(port, "reg", &reg);
if (err) {
of_node_put(port);
goto out_put_node;
}
if (reg >= ds->num_ports) {
dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%u)\n",
port, reg, ds->num_ports);
of_node_put(port);
err = -EINVAL;
goto out_put_node;
}
dp = dsa_to_port(ds, reg);
err = dsa_port_parse_of(dp, port);
if (err) {
of_node_put(port);
goto out_put_node;
}
}
out_put_node:
of_node_put(ports);
return err;
}
static int dsa_switch_parse_member_of(struct dsa_switch *ds,
struct device_node *dn)
{
u32 m[2] = { 0, 0 };
int sz;
/* Don't error out if this optional property isn't found */
sz = of_property_read_variable_u32_array(dn, "dsa,member", m, 2, 2);
if (sz < 0 && sz != -EINVAL)
return sz;
ds->index = m[1];
ds->dst = dsa_tree_touch(m[0]);
if (!ds->dst)
return -ENOMEM;
if (dsa_switch_find(ds->dst->index, ds->index)) {
dev_err(ds->dev,
"A DSA switch with index %d already exists in tree %d\n",
ds->index, ds->dst->index);
return -EEXIST;
}
if (ds->dst->last_switch < ds->index)
ds->dst->last_switch = ds->index;
return 0;
}
static int dsa_switch_touch_ports(struct dsa_switch *ds)
{
struct dsa_port *dp;
int port;
for (port = 0; port < ds->num_ports; port++) {
dp = dsa_port_touch(ds, port);
if (!dp)
return -ENOMEM;
}
return 0;
}
static int dsa_switch_parse_of(struct dsa_switch *ds, struct device_node *dn)
{
int err;
err = dsa_switch_parse_member_of(ds, dn);
if (err)
return err;
err = dsa_switch_touch_ports(ds);
if (err)
return err;
return dsa_switch_parse_ports_of(ds, dn);
}
static int dev_is_class(struct device *dev, void *class)
{
if (dev->class != NULL && !strcmp(dev->class->name, class))
return 1;
return 0;
}
static struct device *dev_find_class(struct device *parent, char *class)
{
if (dev_is_class(parent, class)) {
get_device(parent);
return parent;
}
return device_find_child(parent, class, dev_is_class);
}
static int dsa_port_parse(struct dsa_port *dp, const char *name,
struct device *dev)
{
if (!strcmp(name, "cpu")) {
struct net_device *conduit;
struct device *d;
int err;
rtnl_lock();
d = dev_find_class(dev, "net");
if (!d) {
rtnl_unlock();
return -EPROBE_DEFER;
}
conduit = to_net_dev(d);
netdev_hold(conduit, &dp->conduit_tracker, GFP_KERNEL);
put_device(d);
rtnl_unlock();
err = dsa_port_parse_cpu(dp, conduit, NULL);
if (err)
netdev_put(conduit, &dp->conduit_tracker);
return err;
}
if (!strcmp(name, "dsa"))
return dsa_port_parse_dsa(dp);
return dsa_port_parse_user(dp, name);
}
static int dsa_switch_parse_ports(struct dsa_switch *ds,
struct dsa_chip_data *cd)
{
bool valid_name_found = false;
struct dsa_port *dp;
struct device *dev;
const char *name;
unsigned int i;
int err;
for (i = 0; i < DSA_MAX_PORTS; i++) {
name = cd->port_names[i];
dev = cd->netdev[i];
dp = dsa_to_port(ds, i);
if (!name)
continue;
err = dsa_port_parse(dp, name, dev);
if (err)
return err;
valid_name_found = true;
}
if (!valid_name_found && i == DSA_MAX_PORTS)
return -EINVAL;
return 0;
}
static int dsa_switch_parse(struct dsa_switch *ds, struct dsa_chip_data *cd)
{
int err;
ds->cd = cd;
/* We don't support interconnected switches nor multiple trees via
* platform data, so this is the unique switch of the tree.
*/
ds->index = 0;
ds->dst = dsa_tree_touch(0);
if (!ds->dst)
return -ENOMEM;
err = dsa_switch_touch_ports(ds);
if (err)
return err;
return dsa_switch_parse_ports(ds, cd);
}
static void dsa_switch_release_ports(struct dsa_switch *ds)
{
struct dsa_mac_addr *a, *tmp;
struct dsa_port *dp, *next;
struct dsa_vlan *v, *n;
dsa_switch_for_each_port_safe(dp, next, ds) {
if (dsa_port_is_cpu(dp) && dp->conduit)
netdev_put(dp->conduit, &dp->conduit_tracker);
/* These are either entries that upper layers lost track of
* (probably due to bugs), or installed through interfaces
* where one does not necessarily have to remove them, like
* ndo_dflt_fdb_add().
*/
list_for_each_entry_safe(a, tmp, &dp->fdbs, list) {
dev_info(ds->dev,
"Cleaning up unicast address %pM vid %u from port %d\n",
a->addr, a->vid, dp->index);
list_del(&a->list);
kfree(a);
}
list_for_each_entry_safe(a, tmp, &dp->mdbs, list) {
dev_info(ds->dev,
"Cleaning up multicast address %pM vid %u from port %d\n",
a->addr, a->vid, dp->index);
list_del(&a->list);
kfree(a);
}
/* These are entries that upper layers have lost track of,
* probably due to bugs, but also due to dsa_port_do_vlan_del()
* having failed and the VLAN entry still lingering on.
*/
list_for_each_entry_safe(v, n, &dp->vlans, list) {
dev_info(ds->dev,
"Cleaning up vid %u from port %d\n",
v->vid, dp->index);
list_del(&v->list);
kfree(v);
}
list_del(&dp->list);
kfree(dp);
}
}
static int dsa_switch_probe(struct dsa_switch *ds)
{
struct dsa_switch_tree *dst;
struct dsa_chip_data *pdata;
struct device_node *np;
int err;
if (!ds->dev)
return -ENODEV;
pdata = ds->dev->platform_data;
np = ds->dev->of_node;
if (!ds->num_ports)
return -EINVAL;
if (ds->phylink_mac_ops) {
if (ds->ops->phylink_mac_select_pcs ||
ds->ops->phylink_mac_config ||
ds->ops->phylink_mac_link_down ||
ds->ops->phylink_mac_link_up)
return -EINVAL;
}
if (np) {
err = dsa_switch_parse_of(ds, np);
if (err)
dsa_switch_release_ports(ds);
} else if (pdata) {
err = dsa_switch_parse(ds, pdata);
if (err)
dsa_switch_release_ports(ds);
} else {
err = -ENODEV;
}
if (err)
return err;
dst = ds->dst;
dsa_tree_get(dst);
err = dsa_tree_setup(dst);
if (err) {
dsa_switch_release_ports(ds);
dsa_tree_put(dst);
}
return err;
}
int dsa_register_switch(struct dsa_switch *ds)
{
int err;
mutex_lock(&dsa2_mutex);
err = dsa_switch_probe(ds);
dsa_tree_put(ds->dst);
mutex_unlock(&dsa2_mutex);
return err;
}
EXPORT_SYMBOL_GPL(dsa_register_switch);
static void dsa_switch_remove(struct dsa_switch *ds)
{
struct dsa_switch_tree *dst = ds->dst;
dsa_tree_teardown(dst);
dsa_switch_release_ports(ds);
dsa_tree_put(dst);
}
void dsa_unregister_switch(struct dsa_switch *ds)
{
mutex_lock(&dsa2_mutex);
dsa_switch_remove(ds);
mutex_unlock(&dsa2_mutex);
}
EXPORT_SYMBOL_GPL(dsa_unregister_switch);
/* If the DSA conduit chooses to unregister its net_device on .shutdown, DSA is
* blocking that operation from completion, due to the dev_hold taken inside
* netdev_upper_dev_link. Unlink the DSA user interfaces from being uppers of
* the DSA conduit, so that the system can reboot successfully.
*/
void dsa_switch_shutdown(struct dsa_switch *ds)
{
struct net_device *conduit, *user_dev;
LIST_HEAD(close_list);
struct dsa_port *dp;
mutex_lock(&dsa2_mutex);
if (!ds->setup)
goto out;
rtnl_lock();
dsa_switch_for_each_cpu_port(dp, ds)
list_add(&dp->conduit->close_list, &close_list);
dev_close_many(&close_list, true);
dsa_switch_for_each_user_port(dp, ds) {
conduit = dsa_port_to_conduit(dp);
user_dev = dp->user;
netif_device_detach(user_dev);
netdev_upper_dev_unlink(conduit, user_dev);
}
/* Disconnect from further netdevice notifiers on the conduit,
* since netdev_uses_dsa() will now return false.
*/
dsa_switch_for_each_cpu_port(dp, ds) {
dp->conduit->dsa_ptr = NULL;
netdev_put(dp->conduit, &dp->conduit_tracker);
}
rtnl_unlock();
out:
mutex_unlock(&dsa2_mutex);
}
EXPORT_SYMBOL_GPL(dsa_switch_shutdown);
#ifdef CONFIG_PM_SLEEP
static bool dsa_port_is_initialized(const struct dsa_port *dp)
{
return dp->type == DSA_PORT_TYPE_USER && dp->user;
}
int dsa_switch_suspend(struct dsa_switch *ds)
{
struct dsa_port *dp;
int ret = 0;
/* Suspend user network devices */
dsa_switch_for_each_port(dp, ds) {
if (!dsa_port_is_initialized(dp))
continue;
ret = dsa_user_suspend(dp->user);
if (ret)
return ret;
}
if (ds->ops->suspend)
ret = ds->ops->suspend(ds);
return ret;
}
EXPORT_SYMBOL_GPL(dsa_switch_suspend);
int dsa_switch_resume(struct dsa_switch *ds)
{
struct dsa_port *dp;
int ret = 0;
if (ds->ops->resume)
ret = ds->ops->resume(ds);
if (ret)
return ret;
/* Resume user network devices */
dsa_switch_for_each_port(dp, ds) {
if (!dsa_port_is_initialized(dp))
continue;
ret = dsa_user_resume(dp->user);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(dsa_switch_resume);
#endif
struct dsa_port *dsa_port_from_netdev(struct net_device *netdev)
{
if (!netdev || !dsa_user_dev_check(netdev))
return ERR_PTR(-ENODEV);
return dsa_user_to_port(netdev);
}
EXPORT_SYMBOL_GPL(dsa_port_from_netdev);
bool dsa_db_equal(const struct dsa_db *a, const struct dsa_db *b)
{
if (a->type != b->type)
return false;
switch (a->type) {
case DSA_DB_PORT:
return a->dp == b->dp;
case DSA_DB_LAG:
return a->lag.dev == b->lag.dev;
case DSA_DB_BRIDGE:
return a->bridge.num == b->bridge.num;
default:
WARN_ON(1);
return false;
}
}
bool dsa_fdb_present_in_other_db(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
struct dsa_port *dp = dsa_to_port(ds, port);
struct dsa_mac_addr *a;
lockdep_assert_held(&dp->addr_lists_lock);
list_for_each_entry(a, &dp->fdbs, list) {
if (!ether_addr_equal(a->addr, addr) || a->vid != vid)
continue;
if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(dsa_fdb_present_in_other_db);
bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db)
{
struct dsa_port *dp = dsa_to_port(ds, port);
struct dsa_mac_addr *a;
lockdep_assert_held(&dp->addr_lists_lock);
list_for_each_entry(a, &dp->mdbs, list) {
if (!ether_addr_equal(a->addr, mdb->addr) || a->vid != mdb->vid)
continue;
if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(dsa_mdb_present_in_other_db);
static const struct dsa_stubs __dsa_stubs = {
.conduit_hwtstamp_validate = __dsa_conduit_hwtstamp_validate,
};
static void dsa_register_stubs(void)
{
dsa_stubs = &__dsa_stubs;
}
static void dsa_unregister_stubs(void)
{
dsa_stubs = NULL;
}
static int __init dsa_init_module(void)
{
int rc;
dsa_owq = alloc_ordered_workqueue("dsa_ordered",
WQ_MEM_RECLAIM);
if (!dsa_owq)
return -ENOMEM;
rc = dsa_user_register_notifier();
if (rc)
goto register_notifier_fail;
dev_add_pack(&dsa_pack_type);
rc = rtnl_link_register(&dsa_link_ops);
if (rc)
goto netlink_register_fail;
dsa_register_stubs();
return 0;
netlink_register_fail:
dsa_user_unregister_notifier();
dev_remove_pack(&dsa_pack_type);
register_notifier_fail:
destroy_workqueue(dsa_owq);
return rc;
}
module_init(dsa_init_module);
static void __exit dsa_cleanup_module(void)
{
dsa_unregister_stubs();
rtnl_link_unregister(&dsa_link_ops);
dsa_user_unregister_notifier();
dev_remove_pack(&dsa_pack_type);
destroy_workqueue(dsa_owq);
}
module_exit(dsa_cleanup_module);
MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
MODULE_DESCRIPTION("Driver for Distributed Switch Architecture switch chips");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:dsa");