xref: /kernel/linux/linux-6.6/net/dsa/dsa.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * DSA topology and switch handling
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2008-2009 Marvell Semiconductor
662306a36Sopenharmony_ci * Copyright (c) 2013 Florian Fainelli <florian@openwrt.org>
762306a36Sopenharmony_ci * Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/device.h>
1162306a36Sopenharmony_ci#include <linux/err.h>
1262306a36Sopenharmony_ci#include <linux/list.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/netdevice.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/rtnetlink.h>
1762306a36Sopenharmony_ci#include <linux/of.h>
1862306a36Sopenharmony_ci#include <linux/of_mdio.h>
1962306a36Sopenharmony_ci#include <linux/of_net.h>
2062306a36Sopenharmony_ci#include <net/dsa_stubs.h>
2162306a36Sopenharmony_ci#include <net/sch_generic.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "devlink.h"
2462306a36Sopenharmony_ci#include "dsa.h"
2562306a36Sopenharmony_ci#include "master.h"
2662306a36Sopenharmony_ci#include "netlink.h"
2762306a36Sopenharmony_ci#include "port.h"
2862306a36Sopenharmony_ci#include "slave.h"
2962306a36Sopenharmony_ci#include "switch.h"
3062306a36Sopenharmony_ci#include "tag.h"
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define DSA_MAX_NUM_OFFLOADING_BRIDGES		BITS_PER_LONG
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic DEFINE_MUTEX(dsa2_mutex);
3562306a36Sopenharmony_ciLIST_HEAD(dsa_tree_list);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic struct workqueue_struct *dsa_owq;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* Track the bridges with forwarding offload enabled */
4062306a36Sopenharmony_cistatic unsigned long dsa_fwd_offloading_bridges;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cibool dsa_schedule_work(struct work_struct *work)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	return queue_work(dsa_owq, work);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_civoid dsa_flush_workqueue(void)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	flush_workqueue(dsa_owq);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_flush_workqueue);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/**
5462306a36Sopenharmony_ci * dsa_lag_map() - Map LAG structure to a linear LAG array
5562306a36Sopenharmony_ci * @dst: Tree in which to record the mapping.
5662306a36Sopenharmony_ci * @lag: LAG structure that is to be mapped to the tree's array.
5762306a36Sopenharmony_ci *
5862306a36Sopenharmony_ci * dsa_lag_id/dsa_lag_by_id can then be used to translate between the
5962306a36Sopenharmony_ci * two spaces. The size of the mapping space is determined by the
6062306a36Sopenharmony_ci * driver by setting ds->num_lag_ids. It is perfectly legal to leave
6162306a36Sopenharmony_ci * it unset if it is not needed, in which case these functions become
6262306a36Sopenharmony_ci * no-ops.
6362306a36Sopenharmony_ci */
6462306a36Sopenharmony_civoid dsa_lag_map(struct dsa_switch_tree *dst, struct dsa_lag *lag)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	unsigned int id;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	for (id = 1; id <= dst->lags_len; id++) {
6962306a36Sopenharmony_ci		if (!dsa_lag_by_id(dst, id)) {
7062306a36Sopenharmony_ci			dst->lags[id - 1] = lag;
7162306a36Sopenharmony_ci			lag->id = id;
7262306a36Sopenharmony_ci			return;
7362306a36Sopenharmony_ci		}
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/* No IDs left, which is OK. Some drivers do not need it. The
7762306a36Sopenharmony_ci	 * ones that do, e.g. mv88e6xxx, will discover that dsa_lag_id
7862306a36Sopenharmony_ci	 * returns an error for this device when joining the LAG. The
7962306a36Sopenharmony_ci	 * driver can then return -EOPNOTSUPP back to DSA, which will
8062306a36Sopenharmony_ci	 * fall back to a software LAG.
8162306a36Sopenharmony_ci	 */
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/**
8562306a36Sopenharmony_ci * dsa_lag_unmap() - Remove a LAG ID mapping
8662306a36Sopenharmony_ci * @dst: Tree in which the mapping is recorded.
8762306a36Sopenharmony_ci * @lag: LAG structure that was mapped.
8862306a36Sopenharmony_ci *
8962306a36Sopenharmony_ci * As there may be multiple users of the mapping, it is only removed
9062306a36Sopenharmony_ci * if there are no other references to it.
9162306a36Sopenharmony_ci */
9262306a36Sopenharmony_civoid dsa_lag_unmap(struct dsa_switch_tree *dst, struct dsa_lag *lag)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	unsigned int id;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	dsa_lags_foreach_id(id, dst) {
9762306a36Sopenharmony_ci		if (dsa_lag_by_id(dst, id) == lag) {
9862306a36Sopenharmony_ci			dst->lags[id - 1] = NULL;
9962306a36Sopenharmony_ci			lag->id = 0;
10062306a36Sopenharmony_ci			break;
10162306a36Sopenharmony_ci		}
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistruct dsa_lag *dsa_tree_lag_find(struct dsa_switch_tree *dst,
10662306a36Sopenharmony_ci				  const struct net_device *lag_dev)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct dsa_port *dp;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
11162306a36Sopenharmony_ci		if (dsa_port_lag_dev_get(dp) == lag_dev)
11262306a36Sopenharmony_ci			return dp->lag;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	return NULL;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistruct dsa_bridge *dsa_tree_bridge_find(struct dsa_switch_tree *dst,
11862306a36Sopenharmony_ci					const struct net_device *br)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct dsa_port *dp;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
12362306a36Sopenharmony_ci		if (dsa_port_bridge_dev_get(dp) == br)
12462306a36Sopenharmony_ci			return dp->bridge;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return NULL;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int dsa_bridge_num_find(const struct net_device *bridge_dev)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	list_for_each_entry(dst, &dsa_tree_list, list) {
13462306a36Sopenharmony_ci		struct dsa_bridge *bridge;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		bridge = dsa_tree_bridge_find(dst, bridge_dev);
13762306a36Sopenharmony_ci		if (bridge)
13862306a36Sopenharmony_ci			return bridge->num;
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ciunsigned int dsa_bridge_num_get(const struct net_device *bridge_dev, int max)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	unsigned int bridge_num = dsa_bridge_num_find(bridge_dev);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* Switches without FDB isolation support don't get unique
14962306a36Sopenharmony_ci	 * bridge numbering
15062306a36Sopenharmony_ci	 */
15162306a36Sopenharmony_ci	if (!max)
15262306a36Sopenharmony_ci		return 0;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (!bridge_num) {
15562306a36Sopenharmony_ci		/* First port that requests FDB isolation or TX forwarding
15662306a36Sopenharmony_ci		 * offload for this bridge
15762306a36Sopenharmony_ci		 */
15862306a36Sopenharmony_ci		bridge_num = find_next_zero_bit(&dsa_fwd_offloading_bridges,
15962306a36Sopenharmony_ci						DSA_MAX_NUM_OFFLOADING_BRIDGES,
16062306a36Sopenharmony_ci						1);
16162306a36Sopenharmony_ci		if (bridge_num >= max)
16262306a36Sopenharmony_ci			return 0;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		set_bit(bridge_num, &dsa_fwd_offloading_bridges);
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return bridge_num;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_civoid dsa_bridge_num_put(const struct net_device *bridge_dev,
17162306a36Sopenharmony_ci			unsigned int bridge_num)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	/* Since we refcount bridges, we know that when we call this function
17462306a36Sopenharmony_ci	 * it is no longer in use, so we can just go ahead and remove it from
17562306a36Sopenharmony_ci	 * the bit mask.
17662306a36Sopenharmony_ci	 */
17762306a36Sopenharmony_ci	clear_bit(bridge_num, &dsa_fwd_offloading_bridges);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistruct dsa_switch *dsa_switch_find(int tree_index, int sw_index)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
18362306a36Sopenharmony_ci	struct dsa_port *dp;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	list_for_each_entry(dst, &dsa_tree_list, list) {
18662306a36Sopenharmony_ci		if (dst->index != tree_index)
18762306a36Sopenharmony_ci			continue;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		list_for_each_entry(dp, &dst->ports, list) {
19062306a36Sopenharmony_ci			if (dp->ds->index != sw_index)
19162306a36Sopenharmony_ci				continue;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci			return dp->ds;
19462306a36Sopenharmony_ci		}
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return NULL;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_switch_find);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic struct dsa_switch_tree *dsa_tree_find(int index)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	list_for_each_entry(dst, &dsa_tree_list, list)
20662306a36Sopenharmony_ci		if (dst->index == index)
20762306a36Sopenharmony_ci			return dst;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return NULL;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic struct dsa_switch_tree *dsa_tree_alloc(int index)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	dst = kzalloc(sizeof(*dst), GFP_KERNEL);
21762306a36Sopenharmony_ci	if (!dst)
21862306a36Sopenharmony_ci		return NULL;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	dst->index = index;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	INIT_LIST_HEAD(&dst->rtable);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	INIT_LIST_HEAD(&dst->ports);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	INIT_LIST_HEAD(&dst->list);
22762306a36Sopenharmony_ci	list_add_tail(&dst->list, &dsa_tree_list);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	kref_init(&dst->refcount);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	return dst;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic void dsa_tree_free(struct dsa_switch_tree *dst)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	if (dst->tag_ops)
23762306a36Sopenharmony_ci		dsa_tag_driver_put(dst->tag_ops);
23862306a36Sopenharmony_ci	list_del(&dst->list);
23962306a36Sopenharmony_ci	kfree(dst);
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic struct dsa_switch_tree *dsa_tree_get(struct dsa_switch_tree *dst)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	if (dst)
24562306a36Sopenharmony_ci		kref_get(&dst->refcount);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return dst;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic struct dsa_switch_tree *dsa_tree_touch(int index)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	dst = dsa_tree_find(index);
25562306a36Sopenharmony_ci	if (dst)
25662306a36Sopenharmony_ci		return dsa_tree_get(dst);
25762306a36Sopenharmony_ci	else
25862306a36Sopenharmony_ci		return dsa_tree_alloc(index);
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic void dsa_tree_release(struct kref *ref)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	dst = container_of(ref, struct dsa_switch_tree, refcount);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	dsa_tree_free(dst);
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic void dsa_tree_put(struct dsa_switch_tree *dst)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	if (dst)
27362306a36Sopenharmony_ci		kref_put(&dst->refcount, dsa_tree_release);
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic struct dsa_port *dsa_tree_find_port_by_node(struct dsa_switch_tree *dst,
27762306a36Sopenharmony_ci						   struct device_node *dn)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	struct dsa_port *dp;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
28262306a36Sopenharmony_ci		if (dp->dn == dn)
28362306a36Sopenharmony_ci			return dp;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	return NULL;
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic struct dsa_link *dsa_link_touch(struct dsa_port *dp,
28962306a36Sopenharmony_ci				       struct dsa_port *link_dp)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	struct dsa_switch *ds = dp->ds;
29262306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
29362306a36Sopenharmony_ci	struct dsa_link *dl;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	dst = ds->dst;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	list_for_each_entry(dl, &dst->rtable, list)
29862306a36Sopenharmony_ci		if (dl->dp == dp && dl->link_dp == link_dp)
29962306a36Sopenharmony_ci			return dl;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	dl = kzalloc(sizeof(*dl), GFP_KERNEL);
30262306a36Sopenharmony_ci	if (!dl)
30362306a36Sopenharmony_ci		return NULL;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	dl->dp = dp;
30662306a36Sopenharmony_ci	dl->link_dp = link_dp;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	INIT_LIST_HEAD(&dl->list);
30962306a36Sopenharmony_ci	list_add_tail(&dl->list, &dst->rtable);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	return dl;
31262306a36Sopenharmony_ci}
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cistatic bool dsa_port_setup_routing_table(struct dsa_port *dp)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct dsa_switch *ds = dp->ds;
31762306a36Sopenharmony_ci	struct dsa_switch_tree *dst = ds->dst;
31862306a36Sopenharmony_ci	struct device_node *dn = dp->dn;
31962306a36Sopenharmony_ci	struct of_phandle_iterator it;
32062306a36Sopenharmony_ci	struct dsa_port *link_dp;
32162306a36Sopenharmony_ci	struct dsa_link *dl;
32262306a36Sopenharmony_ci	int err;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	of_for_each_phandle(&it, err, dn, "link", NULL, 0) {
32562306a36Sopenharmony_ci		link_dp = dsa_tree_find_port_by_node(dst, it.node);
32662306a36Sopenharmony_ci		if (!link_dp) {
32762306a36Sopenharmony_ci			of_node_put(it.node);
32862306a36Sopenharmony_ci			return false;
32962306a36Sopenharmony_ci		}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci		dl = dsa_link_touch(dp, link_dp);
33262306a36Sopenharmony_ci		if (!dl) {
33362306a36Sopenharmony_ci			of_node_put(it.node);
33462306a36Sopenharmony_ci			return false;
33562306a36Sopenharmony_ci		}
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	return true;
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic bool dsa_tree_setup_routing_table(struct dsa_switch_tree *dst)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	bool complete = true;
34462306a36Sopenharmony_ci	struct dsa_port *dp;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list) {
34762306a36Sopenharmony_ci		if (dsa_port_is_dsa(dp)) {
34862306a36Sopenharmony_ci			complete = dsa_port_setup_routing_table(dp);
34962306a36Sopenharmony_ci			if (!complete)
35062306a36Sopenharmony_ci				break;
35162306a36Sopenharmony_ci		}
35262306a36Sopenharmony_ci	}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	return complete;
35562306a36Sopenharmony_ci}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic struct dsa_port *dsa_tree_find_first_cpu(struct dsa_switch_tree *dst)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	struct dsa_port *dp;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
36262306a36Sopenharmony_ci		if (dsa_port_is_cpu(dp))
36362306a36Sopenharmony_ci			return dp;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return NULL;
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistruct net_device *dsa_tree_find_first_master(struct dsa_switch_tree *dst)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	struct device_node *ethernet;
37162306a36Sopenharmony_ci	struct net_device *master;
37262306a36Sopenharmony_ci	struct dsa_port *cpu_dp;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	cpu_dp = dsa_tree_find_first_cpu(dst);
37562306a36Sopenharmony_ci	ethernet = of_parse_phandle(cpu_dp->dn, "ethernet", 0);
37662306a36Sopenharmony_ci	master = of_find_net_device_by_node(ethernet);
37762306a36Sopenharmony_ci	of_node_put(ethernet);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	return master;
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci/* Assign the default CPU port (the first one in the tree) to all ports of the
38362306a36Sopenharmony_ci * fabric which don't already have one as part of their own switch.
38462306a36Sopenharmony_ci */
38562306a36Sopenharmony_cistatic int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	struct dsa_port *cpu_dp, *dp;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	cpu_dp = dsa_tree_find_first_cpu(dst);
39062306a36Sopenharmony_ci	if (!cpu_dp) {
39162306a36Sopenharmony_ci		pr_err("DSA: tree %d has no CPU port\n", dst->index);
39262306a36Sopenharmony_ci		return -EINVAL;
39362306a36Sopenharmony_ci	}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list) {
39662306a36Sopenharmony_ci		if (dp->cpu_dp)
39762306a36Sopenharmony_ci			continue;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci		if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
40062306a36Sopenharmony_ci			dp->cpu_dp = cpu_dp;
40162306a36Sopenharmony_ci	}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	return 0;
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistatic struct dsa_port *
40762306a36Sopenharmony_cidsa_switch_preferred_default_local_cpu_port(struct dsa_switch *ds)
40862306a36Sopenharmony_ci{
40962306a36Sopenharmony_ci	struct dsa_port *cpu_dp;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	if (!ds->ops->preferred_default_local_cpu_port)
41262306a36Sopenharmony_ci		return NULL;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	cpu_dp = ds->ops->preferred_default_local_cpu_port(ds);
41562306a36Sopenharmony_ci	if (!cpu_dp)
41662306a36Sopenharmony_ci		return NULL;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	if (WARN_ON(!dsa_port_is_cpu(cpu_dp) || cpu_dp->ds != ds))
41962306a36Sopenharmony_ci		return NULL;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	return cpu_dp;
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci/* Perform initial assignment of CPU ports to user ports and DSA links in the
42562306a36Sopenharmony_ci * fabric, giving preference to CPU ports local to each switch. Default to
42662306a36Sopenharmony_ci * using the first CPU port in the switch tree if the port does not have a CPU
42762306a36Sopenharmony_ci * port local to this switch.
42862306a36Sopenharmony_ci */
42962306a36Sopenharmony_cistatic int dsa_tree_setup_cpu_ports(struct dsa_switch_tree *dst)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	struct dsa_port *preferred_cpu_dp, *cpu_dp, *dp;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	list_for_each_entry(cpu_dp, &dst->ports, list) {
43462306a36Sopenharmony_ci		if (!dsa_port_is_cpu(cpu_dp))
43562306a36Sopenharmony_ci			continue;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci		preferred_cpu_dp = dsa_switch_preferred_default_local_cpu_port(cpu_dp->ds);
43862306a36Sopenharmony_ci		if (preferred_cpu_dp && preferred_cpu_dp != cpu_dp)
43962306a36Sopenharmony_ci			continue;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci		/* Prefer a local CPU port */
44262306a36Sopenharmony_ci		dsa_switch_for_each_port(dp, cpu_dp->ds) {
44362306a36Sopenharmony_ci			/* Prefer the first local CPU port found */
44462306a36Sopenharmony_ci			if (dp->cpu_dp)
44562306a36Sopenharmony_ci				continue;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci			if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
44862306a36Sopenharmony_ci				dp->cpu_dp = cpu_dp;
44962306a36Sopenharmony_ci		}
45062306a36Sopenharmony_ci	}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	return dsa_tree_setup_default_cpu(dst);
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic void dsa_tree_teardown_cpu_ports(struct dsa_switch_tree *dst)
45662306a36Sopenharmony_ci{
45762306a36Sopenharmony_ci	struct dsa_port *dp;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
46062306a36Sopenharmony_ci		if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
46162306a36Sopenharmony_ci			dp->cpu_dp = NULL;
46262306a36Sopenharmony_ci}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_cistatic int dsa_port_setup(struct dsa_port *dp)
46562306a36Sopenharmony_ci{
46662306a36Sopenharmony_ci	bool dsa_port_link_registered = false;
46762306a36Sopenharmony_ci	struct dsa_switch *ds = dp->ds;
46862306a36Sopenharmony_ci	bool dsa_port_enabled = false;
46962306a36Sopenharmony_ci	int err = 0;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	if (dp->setup)
47262306a36Sopenharmony_ci		return 0;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	err = dsa_port_devlink_setup(dp);
47562306a36Sopenharmony_ci	if (err)
47662306a36Sopenharmony_ci		return err;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	switch (dp->type) {
47962306a36Sopenharmony_ci	case DSA_PORT_TYPE_UNUSED:
48062306a36Sopenharmony_ci		dsa_port_disable(dp);
48162306a36Sopenharmony_ci		break;
48262306a36Sopenharmony_ci	case DSA_PORT_TYPE_CPU:
48362306a36Sopenharmony_ci		if (dp->dn) {
48462306a36Sopenharmony_ci			err = dsa_shared_port_link_register_of(dp);
48562306a36Sopenharmony_ci			if (err)
48662306a36Sopenharmony_ci				break;
48762306a36Sopenharmony_ci			dsa_port_link_registered = true;
48862306a36Sopenharmony_ci		} else {
48962306a36Sopenharmony_ci			dev_warn(ds->dev,
49062306a36Sopenharmony_ci				 "skipping link registration for CPU port %d\n",
49162306a36Sopenharmony_ci				 dp->index);
49262306a36Sopenharmony_ci		}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci		err = dsa_port_enable(dp, NULL);
49562306a36Sopenharmony_ci		if (err)
49662306a36Sopenharmony_ci			break;
49762306a36Sopenharmony_ci		dsa_port_enabled = true;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci		break;
50062306a36Sopenharmony_ci	case DSA_PORT_TYPE_DSA:
50162306a36Sopenharmony_ci		if (dp->dn) {
50262306a36Sopenharmony_ci			err = dsa_shared_port_link_register_of(dp);
50362306a36Sopenharmony_ci			if (err)
50462306a36Sopenharmony_ci				break;
50562306a36Sopenharmony_ci			dsa_port_link_registered = true;
50662306a36Sopenharmony_ci		} else {
50762306a36Sopenharmony_ci			dev_warn(ds->dev,
50862306a36Sopenharmony_ci				 "skipping link registration for DSA port %d\n",
50962306a36Sopenharmony_ci				 dp->index);
51062306a36Sopenharmony_ci		}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci		err = dsa_port_enable(dp, NULL);
51362306a36Sopenharmony_ci		if (err)
51462306a36Sopenharmony_ci			break;
51562306a36Sopenharmony_ci		dsa_port_enabled = true;
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		break;
51862306a36Sopenharmony_ci	case DSA_PORT_TYPE_USER:
51962306a36Sopenharmony_ci		of_get_mac_address(dp->dn, dp->mac);
52062306a36Sopenharmony_ci		err = dsa_slave_create(dp);
52162306a36Sopenharmony_ci		break;
52262306a36Sopenharmony_ci	}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	if (err && dsa_port_enabled)
52562306a36Sopenharmony_ci		dsa_port_disable(dp);
52662306a36Sopenharmony_ci	if (err && dsa_port_link_registered)
52762306a36Sopenharmony_ci		dsa_shared_port_link_unregister_of(dp);
52862306a36Sopenharmony_ci	if (err) {
52962306a36Sopenharmony_ci		dsa_port_devlink_teardown(dp);
53062306a36Sopenharmony_ci		return err;
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	dp->setup = true;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	return 0;
53662306a36Sopenharmony_ci}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_cistatic void dsa_port_teardown(struct dsa_port *dp)
53962306a36Sopenharmony_ci{
54062306a36Sopenharmony_ci	if (!dp->setup)
54162306a36Sopenharmony_ci		return;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	switch (dp->type) {
54462306a36Sopenharmony_ci	case DSA_PORT_TYPE_UNUSED:
54562306a36Sopenharmony_ci		break;
54662306a36Sopenharmony_ci	case DSA_PORT_TYPE_CPU:
54762306a36Sopenharmony_ci		dsa_port_disable(dp);
54862306a36Sopenharmony_ci		if (dp->dn)
54962306a36Sopenharmony_ci			dsa_shared_port_link_unregister_of(dp);
55062306a36Sopenharmony_ci		break;
55162306a36Sopenharmony_ci	case DSA_PORT_TYPE_DSA:
55262306a36Sopenharmony_ci		dsa_port_disable(dp);
55362306a36Sopenharmony_ci		if (dp->dn)
55462306a36Sopenharmony_ci			dsa_shared_port_link_unregister_of(dp);
55562306a36Sopenharmony_ci		break;
55662306a36Sopenharmony_ci	case DSA_PORT_TYPE_USER:
55762306a36Sopenharmony_ci		if (dp->slave) {
55862306a36Sopenharmony_ci			dsa_slave_destroy(dp->slave);
55962306a36Sopenharmony_ci			dp->slave = NULL;
56062306a36Sopenharmony_ci		}
56162306a36Sopenharmony_ci		break;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	dsa_port_devlink_teardown(dp);
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	dp->setup = false;
56762306a36Sopenharmony_ci}
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_cistatic int dsa_port_setup_as_unused(struct dsa_port *dp)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	dp->type = DSA_PORT_TYPE_UNUSED;
57262306a36Sopenharmony_ci	return dsa_port_setup(dp);
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_cistatic int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)
57662306a36Sopenharmony_ci{
57762306a36Sopenharmony_ci	const struct dsa_device_ops *tag_ops = ds->dst->tag_ops;
57862306a36Sopenharmony_ci	struct dsa_switch_tree *dst = ds->dst;
57962306a36Sopenharmony_ci	int err;
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	if (tag_ops->proto == dst->default_proto)
58262306a36Sopenharmony_ci		goto connect;
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	rtnl_lock();
58562306a36Sopenharmony_ci	err = ds->ops->change_tag_protocol(ds, tag_ops->proto);
58662306a36Sopenharmony_ci	rtnl_unlock();
58762306a36Sopenharmony_ci	if (err) {
58862306a36Sopenharmony_ci		dev_err(ds->dev, "Unable to use tag protocol \"%s\": %pe\n",
58962306a36Sopenharmony_ci			tag_ops->name, ERR_PTR(err));
59062306a36Sopenharmony_ci		return err;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ciconnect:
59462306a36Sopenharmony_ci	if (tag_ops->connect) {
59562306a36Sopenharmony_ci		err = tag_ops->connect(ds);
59662306a36Sopenharmony_ci		if (err)
59762306a36Sopenharmony_ci			return err;
59862306a36Sopenharmony_ci	}
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	if (ds->ops->connect_tag_protocol) {
60162306a36Sopenharmony_ci		err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
60262306a36Sopenharmony_ci		if (err) {
60362306a36Sopenharmony_ci			dev_err(ds->dev,
60462306a36Sopenharmony_ci				"Unable to connect to tag protocol \"%s\": %pe\n",
60562306a36Sopenharmony_ci				tag_ops->name, ERR_PTR(err));
60662306a36Sopenharmony_ci			goto disconnect;
60762306a36Sopenharmony_ci		}
60862306a36Sopenharmony_ci	}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	return 0;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_cidisconnect:
61362306a36Sopenharmony_ci	if (tag_ops->disconnect)
61462306a36Sopenharmony_ci		tag_ops->disconnect(ds);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	return err;
61762306a36Sopenharmony_ci}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_cistatic void dsa_switch_teardown_tag_protocol(struct dsa_switch *ds)
62062306a36Sopenharmony_ci{
62162306a36Sopenharmony_ci	const struct dsa_device_ops *tag_ops = ds->dst->tag_ops;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	if (tag_ops->disconnect)
62462306a36Sopenharmony_ci		tag_ops->disconnect(ds);
62562306a36Sopenharmony_ci}
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_cistatic int dsa_switch_setup(struct dsa_switch *ds)
62862306a36Sopenharmony_ci{
62962306a36Sopenharmony_ci	struct device_node *dn;
63062306a36Sopenharmony_ci	int err;
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	if (ds->setup)
63362306a36Sopenharmony_ci		return 0;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	/* Initialize ds->phys_mii_mask before registering the slave MDIO bus
63662306a36Sopenharmony_ci	 * driver and before ops->setup() has run, since the switch drivers and
63762306a36Sopenharmony_ci	 * the slave MDIO bus driver rely on these values for probing PHY
63862306a36Sopenharmony_ci	 * devices or not
63962306a36Sopenharmony_ci	 */
64062306a36Sopenharmony_ci	ds->phys_mii_mask |= dsa_user_ports(ds);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	err = dsa_switch_devlink_alloc(ds);
64362306a36Sopenharmony_ci	if (err)
64462306a36Sopenharmony_ci		return err;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	err = dsa_switch_register_notifier(ds);
64762306a36Sopenharmony_ci	if (err)
64862306a36Sopenharmony_ci		goto devlink_free;
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	ds->configure_vlan_while_not_filtering = true;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	err = ds->ops->setup(ds);
65362306a36Sopenharmony_ci	if (err < 0)
65462306a36Sopenharmony_ci		goto unregister_notifier;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	err = dsa_switch_setup_tag_protocol(ds);
65762306a36Sopenharmony_ci	if (err)
65862306a36Sopenharmony_ci		goto teardown;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	if (!ds->slave_mii_bus && ds->ops->phy_read) {
66162306a36Sopenharmony_ci		ds->slave_mii_bus = mdiobus_alloc();
66262306a36Sopenharmony_ci		if (!ds->slave_mii_bus) {
66362306a36Sopenharmony_ci			err = -ENOMEM;
66462306a36Sopenharmony_ci			goto teardown;
66562306a36Sopenharmony_ci		}
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci		dsa_slave_mii_bus_init(ds);
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci		dn = of_get_child_by_name(ds->dev->of_node, "mdio");
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci		err = of_mdiobus_register(ds->slave_mii_bus, dn);
67262306a36Sopenharmony_ci		of_node_put(dn);
67362306a36Sopenharmony_ci		if (err < 0)
67462306a36Sopenharmony_ci			goto free_slave_mii_bus;
67562306a36Sopenharmony_ci	}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	dsa_switch_devlink_register(ds);
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	ds->setup = true;
68062306a36Sopenharmony_ci	return 0;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cifree_slave_mii_bus:
68362306a36Sopenharmony_ci	if (ds->slave_mii_bus && ds->ops->phy_read)
68462306a36Sopenharmony_ci		mdiobus_free(ds->slave_mii_bus);
68562306a36Sopenharmony_citeardown:
68662306a36Sopenharmony_ci	if (ds->ops->teardown)
68762306a36Sopenharmony_ci		ds->ops->teardown(ds);
68862306a36Sopenharmony_ciunregister_notifier:
68962306a36Sopenharmony_ci	dsa_switch_unregister_notifier(ds);
69062306a36Sopenharmony_cidevlink_free:
69162306a36Sopenharmony_ci	dsa_switch_devlink_free(ds);
69262306a36Sopenharmony_ci	return err;
69362306a36Sopenharmony_ci}
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_cistatic void dsa_switch_teardown(struct dsa_switch *ds)
69662306a36Sopenharmony_ci{
69762306a36Sopenharmony_ci	if (!ds->setup)
69862306a36Sopenharmony_ci		return;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	dsa_switch_devlink_unregister(ds);
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	if (ds->slave_mii_bus && ds->ops->phy_read) {
70362306a36Sopenharmony_ci		mdiobus_unregister(ds->slave_mii_bus);
70462306a36Sopenharmony_ci		mdiobus_free(ds->slave_mii_bus);
70562306a36Sopenharmony_ci		ds->slave_mii_bus = NULL;
70662306a36Sopenharmony_ci	}
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	dsa_switch_teardown_tag_protocol(ds);
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	if (ds->ops->teardown)
71162306a36Sopenharmony_ci		ds->ops->teardown(ds);
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci	dsa_switch_unregister_notifier(ds);
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci	dsa_switch_devlink_free(ds);
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	ds->setup = false;
71862306a36Sopenharmony_ci}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci/* First tear down the non-shared, then the shared ports. This ensures that
72162306a36Sopenharmony_ci * all work items scheduled by our switchdev handlers for user ports have
72262306a36Sopenharmony_ci * completed before we destroy the refcounting kept on the shared ports.
72362306a36Sopenharmony_ci */
72462306a36Sopenharmony_cistatic void dsa_tree_teardown_ports(struct dsa_switch_tree *dst)
72562306a36Sopenharmony_ci{
72662306a36Sopenharmony_ci	struct dsa_port *dp;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
72962306a36Sopenharmony_ci		if (dsa_port_is_user(dp) || dsa_port_is_unused(dp))
73062306a36Sopenharmony_ci			dsa_port_teardown(dp);
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ci	dsa_flush_workqueue();
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
73562306a36Sopenharmony_ci		if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp))
73662306a36Sopenharmony_ci			dsa_port_teardown(dp);
73762306a36Sopenharmony_ci}
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_cistatic void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)
74062306a36Sopenharmony_ci{
74162306a36Sopenharmony_ci	struct dsa_port *dp;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list)
74462306a36Sopenharmony_ci		dsa_switch_teardown(dp->ds);
74562306a36Sopenharmony_ci}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci/* Bring shared ports up first, then non-shared ports */
74862306a36Sopenharmony_cistatic int dsa_tree_setup_ports(struct dsa_switch_tree *dst)
74962306a36Sopenharmony_ci{
75062306a36Sopenharmony_ci	struct dsa_port *dp;
75162306a36Sopenharmony_ci	int err = 0;
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list) {
75462306a36Sopenharmony_ci		if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) {
75562306a36Sopenharmony_ci			err = dsa_port_setup(dp);
75662306a36Sopenharmony_ci			if (err)
75762306a36Sopenharmony_ci				goto teardown;
75862306a36Sopenharmony_ci		}
75962306a36Sopenharmony_ci	}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list) {
76262306a36Sopenharmony_ci		if (dsa_port_is_user(dp) || dsa_port_is_unused(dp)) {
76362306a36Sopenharmony_ci			err = dsa_port_setup(dp);
76462306a36Sopenharmony_ci			if (err) {
76562306a36Sopenharmony_ci				err = dsa_port_setup_as_unused(dp);
76662306a36Sopenharmony_ci				if (err)
76762306a36Sopenharmony_ci					goto teardown;
76862306a36Sopenharmony_ci			}
76962306a36Sopenharmony_ci		}
77062306a36Sopenharmony_ci	}
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	return 0;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_citeardown:
77562306a36Sopenharmony_ci	dsa_tree_teardown_ports(dst);
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	return err;
77862306a36Sopenharmony_ci}
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_cistatic int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
78162306a36Sopenharmony_ci{
78262306a36Sopenharmony_ci	struct dsa_port *dp;
78362306a36Sopenharmony_ci	int err = 0;
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list) {
78662306a36Sopenharmony_ci		err = dsa_switch_setup(dp->ds);
78762306a36Sopenharmony_ci		if (err) {
78862306a36Sopenharmony_ci			dsa_tree_teardown_switches(dst);
78962306a36Sopenharmony_ci			break;
79062306a36Sopenharmony_ci		}
79162306a36Sopenharmony_ci	}
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	return err;
79462306a36Sopenharmony_ci}
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_cistatic int dsa_tree_setup_master(struct dsa_switch_tree *dst)
79762306a36Sopenharmony_ci{
79862306a36Sopenharmony_ci	struct dsa_port *cpu_dp;
79962306a36Sopenharmony_ci	int err = 0;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	rtnl_lock();
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci	dsa_tree_for_each_cpu_port(cpu_dp, dst) {
80462306a36Sopenharmony_ci		struct net_device *master = cpu_dp->master;
80562306a36Sopenharmony_ci		bool admin_up = (master->flags & IFF_UP) &&
80662306a36Sopenharmony_ci				!qdisc_tx_is_noop(master);
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci		err = dsa_master_setup(master, cpu_dp);
80962306a36Sopenharmony_ci		if (err)
81062306a36Sopenharmony_ci			break;
81162306a36Sopenharmony_ci
81262306a36Sopenharmony_ci		/* Replay master state event */
81362306a36Sopenharmony_ci		dsa_tree_master_admin_state_change(dst, master, admin_up);
81462306a36Sopenharmony_ci		dsa_tree_master_oper_state_change(dst, master,
81562306a36Sopenharmony_ci						  netif_oper_up(master));
81662306a36Sopenharmony_ci	}
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci	rtnl_unlock();
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ci	return err;
82162306a36Sopenharmony_ci}
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_cistatic void dsa_tree_teardown_master(struct dsa_switch_tree *dst)
82462306a36Sopenharmony_ci{
82562306a36Sopenharmony_ci	struct dsa_port *cpu_dp;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	rtnl_lock();
82862306a36Sopenharmony_ci
82962306a36Sopenharmony_ci	dsa_tree_for_each_cpu_port(cpu_dp, dst) {
83062306a36Sopenharmony_ci		struct net_device *master = cpu_dp->master;
83162306a36Sopenharmony_ci
83262306a36Sopenharmony_ci		/* Synthesizing an "admin down" state is sufficient for
83362306a36Sopenharmony_ci		 * the switches to get a notification if the master is
83462306a36Sopenharmony_ci		 * currently up and running.
83562306a36Sopenharmony_ci		 */
83662306a36Sopenharmony_ci		dsa_tree_master_admin_state_change(dst, master, false);
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci		dsa_master_teardown(master);
83962306a36Sopenharmony_ci	}
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci	rtnl_unlock();
84262306a36Sopenharmony_ci}
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_cistatic int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
84562306a36Sopenharmony_ci{
84662306a36Sopenharmony_ci	unsigned int len = 0;
84762306a36Sopenharmony_ci	struct dsa_port *dp;
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	list_for_each_entry(dp, &dst->ports, list) {
85062306a36Sopenharmony_ci		if (dp->ds->num_lag_ids > len)
85162306a36Sopenharmony_ci			len = dp->ds->num_lag_ids;
85262306a36Sopenharmony_ci	}
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci	if (!len)
85562306a36Sopenharmony_ci		return 0;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	dst->lags = kcalloc(len, sizeof(*dst->lags), GFP_KERNEL);
85862306a36Sopenharmony_ci	if (!dst->lags)
85962306a36Sopenharmony_ci		return -ENOMEM;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	dst->lags_len = len;
86262306a36Sopenharmony_ci	return 0;
86362306a36Sopenharmony_ci}
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_cistatic void dsa_tree_teardown_lags(struct dsa_switch_tree *dst)
86662306a36Sopenharmony_ci{
86762306a36Sopenharmony_ci	kfree(dst->lags);
86862306a36Sopenharmony_ci}
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_cistatic int dsa_tree_setup(struct dsa_switch_tree *dst)
87162306a36Sopenharmony_ci{
87262306a36Sopenharmony_ci	bool complete;
87362306a36Sopenharmony_ci	int err;
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_ci	if (dst->setup) {
87662306a36Sopenharmony_ci		pr_err("DSA: tree %d already setup! Disjoint trees?\n",
87762306a36Sopenharmony_ci		       dst->index);
87862306a36Sopenharmony_ci		return -EEXIST;
87962306a36Sopenharmony_ci	}
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ci	complete = dsa_tree_setup_routing_table(dst);
88262306a36Sopenharmony_ci	if (!complete)
88362306a36Sopenharmony_ci		return 0;
88462306a36Sopenharmony_ci
88562306a36Sopenharmony_ci	err = dsa_tree_setup_cpu_ports(dst);
88662306a36Sopenharmony_ci	if (err)
88762306a36Sopenharmony_ci		return err;
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	err = dsa_tree_setup_switches(dst);
89062306a36Sopenharmony_ci	if (err)
89162306a36Sopenharmony_ci		goto teardown_cpu_ports;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	err = dsa_tree_setup_ports(dst);
89462306a36Sopenharmony_ci	if (err)
89562306a36Sopenharmony_ci		goto teardown_switches;
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_ci	err = dsa_tree_setup_master(dst);
89862306a36Sopenharmony_ci	if (err)
89962306a36Sopenharmony_ci		goto teardown_ports;
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	err = dsa_tree_setup_lags(dst);
90262306a36Sopenharmony_ci	if (err)
90362306a36Sopenharmony_ci		goto teardown_master;
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ci	dst->setup = true;
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	pr_info("DSA: tree %d setup\n", dst->index);
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_ci	return 0;
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_citeardown_master:
91262306a36Sopenharmony_ci	dsa_tree_teardown_master(dst);
91362306a36Sopenharmony_citeardown_ports:
91462306a36Sopenharmony_ci	dsa_tree_teardown_ports(dst);
91562306a36Sopenharmony_citeardown_switches:
91662306a36Sopenharmony_ci	dsa_tree_teardown_switches(dst);
91762306a36Sopenharmony_citeardown_cpu_ports:
91862306a36Sopenharmony_ci	dsa_tree_teardown_cpu_ports(dst);
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	return err;
92162306a36Sopenharmony_ci}
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_cistatic void dsa_tree_teardown(struct dsa_switch_tree *dst)
92462306a36Sopenharmony_ci{
92562306a36Sopenharmony_ci	struct dsa_link *dl, *next;
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ci	if (!dst->setup)
92862306a36Sopenharmony_ci		return;
92962306a36Sopenharmony_ci
93062306a36Sopenharmony_ci	dsa_tree_teardown_lags(dst);
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	dsa_tree_teardown_master(dst);
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	dsa_tree_teardown_ports(dst);
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	dsa_tree_teardown_switches(dst);
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_ci	dsa_tree_teardown_cpu_ports(dst);
93962306a36Sopenharmony_ci
94062306a36Sopenharmony_ci	list_for_each_entry_safe(dl, next, &dst->rtable, list) {
94162306a36Sopenharmony_ci		list_del(&dl->list);
94262306a36Sopenharmony_ci		kfree(dl);
94362306a36Sopenharmony_ci	}
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	pr_info("DSA: tree %d torn down\n", dst->index);
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ci	dst->setup = false;
94862306a36Sopenharmony_ci}
94962306a36Sopenharmony_ci
95062306a36Sopenharmony_cistatic int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
95162306a36Sopenharmony_ci				   const struct dsa_device_ops *tag_ops)
95262306a36Sopenharmony_ci{
95362306a36Sopenharmony_ci	const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
95462306a36Sopenharmony_ci	struct dsa_notifier_tag_proto_info info;
95562306a36Sopenharmony_ci	int err;
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_ci	dst->tag_ops = tag_ops;
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	/* Notify the switches from this tree about the connection
96062306a36Sopenharmony_ci	 * to the new tagger
96162306a36Sopenharmony_ci	 */
96262306a36Sopenharmony_ci	info.tag_ops = tag_ops;
96362306a36Sopenharmony_ci	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
96462306a36Sopenharmony_ci	if (err && err != -EOPNOTSUPP)
96562306a36Sopenharmony_ci		goto out_disconnect;
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci	/* Notify the old tagger about the disconnection from this tree */
96862306a36Sopenharmony_ci	info.tag_ops = old_tag_ops;
96962306a36Sopenharmony_ci	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
97062306a36Sopenharmony_ci
97162306a36Sopenharmony_ci	return 0;
97262306a36Sopenharmony_ci
97362306a36Sopenharmony_ciout_disconnect:
97462306a36Sopenharmony_ci	info.tag_ops = tag_ops;
97562306a36Sopenharmony_ci	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info);
97662306a36Sopenharmony_ci	dst->tag_ops = old_tag_ops;
97762306a36Sopenharmony_ci
97862306a36Sopenharmony_ci	return err;
97962306a36Sopenharmony_ci}
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci/* Since the dsa/tagging sysfs device attribute is per master, the assumption
98262306a36Sopenharmony_ci * is that all DSA switches within a tree share the same tagger, otherwise
98362306a36Sopenharmony_ci * they would have formed disjoint trees (different "dsa,member" values).
98462306a36Sopenharmony_ci */
98562306a36Sopenharmony_ciint dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
98662306a36Sopenharmony_ci			      const struct dsa_device_ops *tag_ops,
98762306a36Sopenharmony_ci			      const struct dsa_device_ops *old_tag_ops)
98862306a36Sopenharmony_ci{
98962306a36Sopenharmony_ci	struct dsa_notifier_tag_proto_info info;
99062306a36Sopenharmony_ci	struct dsa_port *dp;
99162306a36Sopenharmony_ci	int err = -EBUSY;
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_ci	if (!rtnl_trylock())
99462306a36Sopenharmony_ci		return restart_syscall();
99562306a36Sopenharmony_ci
99662306a36Sopenharmony_ci	/* At the moment we don't allow changing the tag protocol under
99762306a36Sopenharmony_ci	 * traffic. The rtnl_mutex also happens to serialize concurrent
99862306a36Sopenharmony_ci	 * attempts to change the tagging protocol. If we ever lift the IFF_UP
99962306a36Sopenharmony_ci	 * restriction, there needs to be another mutex which serializes this.
100062306a36Sopenharmony_ci	 */
100162306a36Sopenharmony_ci	dsa_tree_for_each_user_port(dp, dst) {
100262306a36Sopenharmony_ci		if (dsa_port_to_master(dp)->flags & IFF_UP)
100362306a36Sopenharmony_ci			goto out_unlock;
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci		if (dp->slave->flags & IFF_UP)
100662306a36Sopenharmony_ci			goto out_unlock;
100762306a36Sopenharmony_ci	}
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	/* Notify the tag protocol change */
101062306a36Sopenharmony_ci	info.tag_ops = tag_ops;
101162306a36Sopenharmony_ci	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
101262306a36Sopenharmony_ci	if (err)
101362306a36Sopenharmony_ci		goto out_unwind_tagger;
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_ci	err = dsa_tree_bind_tag_proto(dst, tag_ops);
101662306a36Sopenharmony_ci	if (err)
101762306a36Sopenharmony_ci		goto out_unwind_tagger;
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	rtnl_unlock();
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci	return 0;
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_ciout_unwind_tagger:
102462306a36Sopenharmony_ci	info.tag_ops = old_tag_ops;
102562306a36Sopenharmony_ci	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
102662306a36Sopenharmony_ciout_unlock:
102762306a36Sopenharmony_ci	rtnl_unlock();
102862306a36Sopenharmony_ci	return err;
102962306a36Sopenharmony_ci}
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_cistatic void dsa_tree_master_state_change(struct dsa_switch_tree *dst,
103262306a36Sopenharmony_ci					 struct net_device *master)
103362306a36Sopenharmony_ci{
103462306a36Sopenharmony_ci	struct dsa_notifier_master_state_info info;
103562306a36Sopenharmony_ci	struct dsa_port *cpu_dp = master->dsa_ptr;
103662306a36Sopenharmony_ci
103762306a36Sopenharmony_ci	info.master = master;
103862306a36Sopenharmony_ci	info.operational = dsa_port_master_is_operational(cpu_dp);
103962306a36Sopenharmony_ci
104062306a36Sopenharmony_ci	dsa_tree_notify(dst, DSA_NOTIFIER_MASTER_STATE_CHANGE, &info);
104162306a36Sopenharmony_ci}
104262306a36Sopenharmony_ci
104362306a36Sopenharmony_civoid dsa_tree_master_admin_state_change(struct dsa_switch_tree *dst,
104462306a36Sopenharmony_ci					struct net_device *master,
104562306a36Sopenharmony_ci					bool up)
104662306a36Sopenharmony_ci{
104762306a36Sopenharmony_ci	struct dsa_port *cpu_dp = master->dsa_ptr;
104862306a36Sopenharmony_ci	bool notify = false;
104962306a36Sopenharmony_ci
105062306a36Sopenharmony_ci	/* Don't keep track of admin state on LAG DSA masters,
105162306a36Sopenharmony_ci	 * but rather just of physical DSA masters
105262306a36Sopenharmony_ci	 */
105362306a36Sopenharmony_ci	if (netif_is_lag_master(master))
105462306a36Sopenharmony_ci		return;
105562306a36Sopenharmony_ci
105662306a36Sopenharmony_ci	if ((dsa_port_master_is_operational(cpu_dp)) !=
105762306a36Sopenharmony_ci	    (up && cpu_dp->master_oper_up))
105862306a36Sopenharmony_ci		notify = true;
105962306a36Sopenharmony_ci
106062306a36Sopenharmony_ci	cpu_dp->master_admin_up = up;
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	if (notify)
106362306a36Sopenharmony_ci		dsa_tree_master_state_change(dst, master);
106462306a36Sopenharmony_ci}
106562306a36Sopenharmony_ci
106662306a36Sopenharmony_civoid dsa_tree_master_oper_state_change(struct dsa_switch_tree *dst,
106762306a36Sopenharmony_ci				       struct net_device *master,
106862306a36Sopenharmony_ci				       bool up)
106962306a36Sopenharmony_ci{
107062306a36Sopenharmony_ci	struct dsa_port *cpu_dp = master->dsa_ptr;
107162306a36Sopenharmony_ci	bool notify = false;
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	/* Don't keep track of oper state on LAG DSA masters,
107462306a36Sopenharmony_ci	 * but rather just of physical DSA masters
107562306a36Sopenharmony_ci	 */
107662306a36Sopenharmony_ci	if (netif_is_lag_master(master))
107762306a36Sopenharmony_ci		return;
107862306a36Sopenharmony_ci
107962306a36Sopenharmony_ci	if ((dsa_port_master_is_operational(cpu_dp)) !=
108062306a36Sopenharmony_ci	    (cpu_dp->master_admin_up && up))
108162306a36Sopenharmony_ci		notify = true;
108262306a36Sopenharmony_ci
108362306a36Sopenharmony_ci	cpu_dp->master_oper_up = up;
108462306a36Sopenharmony_ci
108562306a36Sopenharmony_ci	if (notify)
108662306a36Sopenharmony_ci		dsa_tree_master_state_change(dst, master);
108762306a36Sopenharmony_ci}
108862306a36Sopenharmony_ci
108962306a36Sopenharmony_cistatic struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
109062306a36Sopenharmony_ci{
109162306a36Sopenharmony_ci	struct dsa_switch_tree *dst = ds->dst;
109262306a36Sopenharmony_ci	struct dsa_port *dp;
109362306a36Sopenharmony_ci
109462306a36Sopenharmony_ci	dsa_switch_for_each_port(dp, ds)
109562306a36Sopenharmony_ci		if (dp->index == index)
109662306a36Sopenharmony_ci			return dp;
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_ci	dp = kzalloc(sizeof(*dp), GFP_KERNEL);
109962306a36Sopenharmony_ci	if (!dp)
110062306a36Sopenharmony_ci		return NULL;
110162306a36Sopenharmony_ci
110262306a36Sopenharmony_ci	dp->ds = ds;
110362306a36Sopenharmony_ci	dp->index = index;
110462306a36Sopenharmony_ci
110562306a36Sopenharmony_ci	mutex_init(&dp->addr_lists_lock);
110662306a36Sopenharmony_ci	mutex_init(&dp->vlans_lock);
110762306a36Sopenharmony_ci	INIT_LIST_HEAD(&dp->fdbs);
110862306a36Sopenharmony_ci	INIT_LIST_HEAD(&dp->mdbs);
110962306a36Sopenharmony_ci	INIT_LIST_HEAD(&dp->vlans); /* also initializes &dp->user_vlans */
111062306a36Sopenharmony_ci	INIT_LIST_HEAD(&dp->list);
111162306a36Sopenharmony_ci	list_add_tail(&dp->list, &dst->ports);
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci	return dp;
111462306a36Sopenharmony_ci}
111562306a36Sopenharmony_ci
111662306a36Sopenharmony_cistatic int dsa_port_parse_user(struct dsa_port *dp, const char *name)
111762306a36Sopenharmony_ci{
111862306a36Sopenharmony_ci	dp->type = DSA_PORT_TYPE_USER;
111962306a36Sopenharmony_ci	dp->name = name;
112062306a36Sopenharmony_ci
112162306a36Sopenharmony_ci	return 0;
112262306a36Sopenharmony_ci}
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_cistatic int dsa_port_parse_dsa(struct dsa_port *dp)
112562306a36Sopenharmony_ci{
112662306a36Sopenharmony_ci	dp->type = DSA_PORT_TYPE_DSA;
112762306a36Sopenharmony_ci
112862306a36Sopenharmony_ci	return 0;
112962306a36Sopenharmony_ci}
113062306a36Sopenharmony_ci
113162306a36Sopenharmony_cistatic enum dsa_tag_protocol dsa_get_tag_protocol(struct dsa_port *dp,
113262306a36Sopenharmony_ci						  struct net_device *master)
113362306a36Sopenharmony_ci{
113462306a36Sopenharmony_ci	enum dsa_tag_protocol tag_protocol = DSA_TAG_PROTO_NONE;
113562306a36Sopenharmony_ci	struct dsa_switch *mds, *ds = dp->ds;
113662306a36Sopenharmony_ci	unsigned int mdp_upstream;
113762306a36Sopenharmony_ci	struct dsa_port *mdp;
113862306a36Sopenharmony_ci
113962306a36Sopenharmony_ci	/* It is possible to stack DSA switches onto one another when that
114062306a36Sopenharmony_ci	 * happens the switch driver may want to know if its tagging protocol
114162306a36Sopenharmony_ci	 * is going to work in such a configuration.
114262306a36Sopenharmony_ci	 */
114362306a36Sopenharmony_ci	if (dsa_slave_dev_check(master)) {
114462306a36Sopenharmony_ci		mdp = dsa_slave_to_port(master);
114562306a36Sopenharmony_ci		mds = mdp->ds;
114662306a36Sopenharmony_ci		mdp_upstream = dsa_upstream_port(mds, mdp->index);
114762306a36Sopenharmony_ci		tag_protocol = mds->ops->get_tag_protocol(mds, mdp_upstream,
114862306a36Sopenharmony_ci							  DSA_TAG_PROTO_NONE);
114962306a36Sopenharmony_ci	}
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci	/* If the master device is not itself a DSA slave in a disjoint DSA
115262306a36Sopenharmony_ci	 * tree, then return immediately.
115362306a36Sopenharmony_ci	 */
115462306a36Sopenharmony_ci	return ds->ops->get_tag_protocol(ds, dp->index, tag_protocol);
115562306a36Sopenharmony_ci}
115662306a36Sopenharmony_ci
115762306a36Sopenharmony_cistatic int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master,
115862306a36Sopenharmony_ci			      const char *user_protocol)
115962306a36Sopenharmony_ci{
116062306a36Sopenharmony_ci	const struct dsa_device_ops *tag_ops = NULL;
116162306a36Sopenharmony_ci	struct dsa_switch *ds = dp->ds;
116262306a36Sopenharmony_ci	struct dsa_switch_tree *dst = ds->dst;
116362306a36Sopenharmony_ci	enum dsa_tag_protocol default_proto;
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_ci	/* Find out which protocol the switch would prefer. */
116662306a36Sopenharmony_ci	default_proto = dsa_get_tag_protocol(dp, master);
116762306a36Sopenharmony_ci	if (dst->default_proto) {
116862306a36Sopenharmony_ci		if (dst->default_proto != default_proto) {
116962306a36Sopenharmony_ci			dev_err(ds->dev,
117062306a36Sopenharmony_ci				"A DSA switch tree can have only one tagging protocol\n");
117162306a36Sopenharmony_ci			return -EINVAL;
117262306a36Sopenharmony_ci		}
117362306a36Sopenharmony_ci	} else {
117462306a36Sopenharmony_ci		dst->default_proto = default_proto;
117562306a36Sopenharmony_ci	}
117662306a36Sopenharmony_ci
117762306a36Sopenharmony_ci	/* See if the user wants to override that preference. */
117862306a36Sopenharmony_ci	if (user_protocol) {
117962306a36Sopenharmony_ci		if (!ds->ops->change_tag_protocol) {
118062306a36Sopenharmony_ci			dev_err(ds->dev, "Tag protocol cannot be modified\n");
118162306a36Sopenharmony_ci			return -EINVAL;
118262306a36Sopenharmony_ci		}
118362306a36Sopenharmony_ci
118462306a36Sopenharmony_ci		tag_ops = dsa_tag_driver_get_by_name(user_protocol);
118562306a36Sopenharmony_ci		if (IS_ERR(tag_ops)) {
118662306a36Sopenharmony_ci			dev_warn(ds->dev,
118762306a36Sopenharmony_ci				 "Failed to find a tagging driver for protocol %s, using default\n",
118862306a36Sopenharmony_ci				 user_protocol);
118962306a36Sopenharmony_ci			tag_ops = NULL;
119062306a36Sopenharmony_ci		}
119162306a36Sopenharmony_ci	}
119262306a36Sopenharmony_ci
119362306a36Sopenharmony_ci	if (!tag_ops)
119462306a36Sopenharmony_ci		tag_ops = dsa_tag_driver_get_by_id(default_proto);
119562306a36Sopenharmony_ci
119662306a36Sopenharmony_ci	if (IS_ERR(tag_ops)) {
119762306a36Sopenharmony_ci		if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
119862306a36Sopenharmony_ci			return -EPROBE_DEFER;
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci		dev_warn(ds->dev, "No tagger for this switch\n");
120162306a36Sopenharmony_ci		return PTR_ERR(tag_ops);
120262306a36Sopenharmony_ci	}
120362306a36Sopenharmony_ci
120462306a36Sopenharmony_ci	if (dst->tag_ops) {
120562306a36Sopenharmony_ci		if (dst->tag_ops != tag_ops) {
120662306a36Sopenharmony_ci			dev_err(ds->dev,
120762306a36Sopenharmony_ci				"A DSA switch tree can have only one tagging protocol\n");
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci			dsa_tag_driver_put(tag_ops);
121062306a36Sopenharmony_ci			return -EINVAL;
121162306a36Sopenharmony_ci		}
121262306a36Sopenharmony_ci
121362306a36Sopenharmony_ci		/* In the case of multiple CPU ports per switch, the tagging
121462306a36Sopenharmony_ci		 * protocol is still reference-counted only per switch tree.
121562306a36Sopenharmony_ci		 */
121662306a36Sopenharmony_ci		dsa_tag_driver_put(tag_ops);
121762306a36Sopenharmony_ci	} else {
121862306a36Sopenharmony_ci		dst->tag_ops = tag_ops;
121962306a36Sopenharmony_ci	}
122062306a36Sopenharmony_ci
122162306a36Sopenharmony_ci	dp->master = master;
122262306a36Sopenharmony_ci	dp->type = DSA_PORT_TYPE_CPU;
122362306a36Sopenharmony_ci	dsa_port_set_tag_protocol(dp, dst->tag_ops);
122462306a36Sopenharmony_ci	dp->dst = dst;
122562306a36Sopenharmony_ci
122662306a36Sopenharmony_ci	/* At this point, the tree may be configured to use a different
122762306a36Sopenharmony_ci	 * tagger than the one chosen by the switch driver during
122862306a36Sopenharmony_ci	 * .setup, in the case when a user selects a custom protocol
122962306a36Sopenharmony_ci	 * through the DT.
123062306a36Sopenharmony_ci	 *
123162306a36Sopenharmony_ci	 * This is resolved by syncing the driver with the tree in
123262306a36Sopenharmony_ci	 * dsa_switch_setup_tag_protocol once .setup has run and the
123362306a36Sopenharmony_ci	 * driver is ready to accept calls to .change_tag_protocol. If
123462306a36Sopenharmony_ci	 * the driver does not support the custom protocol at that
123562306a36Sopenharmony_ci	 * point, the tree is wholly rejected, thereby ensuring that the
123662306a36Sopenharmony_ci	 * tree and driver are always in agreement on the protocol to
123762306a36Sopenharmony_ci	 * use.
123862306a36Sopenharmony_ci	 */
123962306a36Sopenharmony_ci	return 0;
124062306a36Sopenharmony_ci}
124162306a36Sopenharmony_ci
124262306a36Sopenharmony_cistatic int dsa_port_parse_of(struct dsa_port *dp, struct device_node *dn)
124362306a36Sopenharmony_ci{
124462306a36Sopenharmony_ci	struct device_node *ethernet = of_parse_phandle(dn, "ethernet", 0);
124562306a36Sopenharmony_ci	const char *name = of_get_property(dn, "label", NULL);
124662306a36Sopenharmony_ci	bool link = of_property_read_bool(dn, "link");
124762306a36Sopenharmony_ci
124862306a36Sopenharmony_ci	dp->dn = dn;
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	if (ethernet) {
125162306a36Sopenharmony_ci		struct net_device *master;
125262306a36Sopenharmony_ci		const char *user_protocol;
125362306a36Sopenharmony_ci
125462306a36Sopenharmony_ci		master = of_find_net_device_by_node(ethernet);
125562306a36Sopenharmony_ci		of_node_put(ethernet);
125662306a36Sopenharmony_ci		if (!master)
125762306a36Sopenharmony_ci			return -EPROBE_DEFER;
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ci		user_protocol = of_get_property(dn, "dsa-tag-protocol", NULL);
126062306a36Sopenharmony_ci		return dsa_port_parse_cpu(dp, master, user_protocol);
126162306a36Sopenharmony_ci	}
126262306a36Sopenharmony_ci
126362306a36Sopenharmony_ci	if (link)
126462306a36Sopenharmony_ci		return dsa_port_parse_dsa(dp);
126562306a36Sopenharmony_ci
126662306a36Sopenharmony_ci	return dsa_port_parse_user(dp, name);
126762306a36Sopenharmony_ci}
126862306a36Sopenharmony_ci
126962306a36Sopenharmony_cistatic int dsa_switch_parse_ports_of(struct dsa_switch *ds,
127062306a36Sopenharmony_ci				     struct device_node *dn)
127162306a36Sopenharmony_ci{
127262306a36Sopenharmony_ci	struct device_node *ports, *port;
127362306a36Sopenharmony_ci	struct dsa_port *dp;
127462306a36Sopenharmony_ci	int err = 0;
127562306a36Sopenharmony_ci	u32 reg;
127662306a36Sopenharmony_ci
127762306a36Sopenharmony_ci	ports = of_get_child_by_name(dn, "ports");
127862306a36Sopenharmony_ci	if (!ports) {
127962306a36Sopenharmony_ci		/* The second possibility is "ethernet-ports" */
128062306a36Sopenharmony_ci		ports = of_get_child_by_name(dn, "ethernet-ports");
128162306a36Sopenharmony_ci		if (!ports) {
128262306a36Sopenharmony_ci			dev_err(ds->dev, "no ports child node found\n");
128362306a36Sopenharmony_ci			return -EINVAL;
128462306a36Sopenharmony_ci		}
128562306a36Sopenharmony_ci	}
128662306a36Sopenharmony_ci
128762306a36Sopenharmony_ci	for_each_available_child_of_node(ports, port) {
128862306a36Sopenharmony_ci		err = of_property_read_u32(port, "reg", &reg);
128962306a36Sopenharmony_ci		if (err) {
129062306a36Sopenharmony_ci			of_node_put(port);
129162306a36Sopenharmony_ci			goto out_put_node;
129262306a36Sopenharmony_ci		}
129362306a36Sopenharmony_ci
129462306a36Sopenharmony_ci		if (reg >= ds->num_ports) {
129562306a36Sopenharmony_ci			dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%u)\n",
129662306a36Sopenharmony_ci				port, reg, ds->num_ports);
129762306a36Sopenharmony_ci			of_node_put(port);
129862306a36Sopenharmony_ci			err = -EINVAL;
129962306a36Sopenharmony_ci			goto out_put_node;
130062306a36Sopenharmony_ci		}
130162306a36Sopenharmony_ci
130262306a36Sopenharmony_ci		dp = dsa_to_port(ds, reg);
130362306a36Sopenharmony_ci
130462306a36Sopenharmony_ci		err = dsa_port_parse_of(dp, port);
130562306a36Sopenharmony_ci		if (err) {
130662306a36Sopenharmony_ci			of_node_put(port);
130762306a36Sopenharmony_ci			goto out_put_node;
130862306a36Sopenharmony_ci		}
130962306a36Sopenharmony_ci	}
131062306a36Sopenharmony_ci
131162306a36Sopenharmony_ciout_put_node:
131262306a36Sopenharmony_ci	of_node_put(ports);
131362306a36Sopenharmony_ci	return err;
131462306a36Sopenharmony_ci}
131562306a36Sopenharmony_ci
131662306a36Sopenharmony_cistatic int dsa_switch_parse_member_of(struct dsa_switch *ds,
131762306a36Sopenharmony_ci				      struct device_node *dn)
131862306a36Sopenharmony_ci{
131962306a36Sopenharmony_ci	u32 m[2] = { 0, 0 };
132062306a36Sopenharmony_ci	int sz;
132162306a36Sopenharmony_ci
132262306a36Sopenharmony_ci	/* Don't error out if this optional property isn't found */
132362306a36Sopenharmony_ci	sz = of_property_read_variable_u32_array(dn, "dsa,member", m, 2, 2);
132462306a36Sopenharmony_ci	if (sz < 0 && sz != -EINVAL)
132562306a36Sopenharmony_ci		return sz;
132662306a36Sopenharmony_ci
132762306a36Sopenharmony_ci	ds->index = m[1];
132862306a36Sopenharmony_ci
132962306a36Sopenharmony_ci	ds->dst = dsa_tree_touch(m[0]);
133062306a36Sopenharmony_ci	if (!ds->dst)
133162306a36Sopenharmony_ci		return -ENOMEM;
133262306a36Sopenharmony_ci
133362306a36Sopenharmony_ci	if (dsa_switch_find(ds->dst->index, ds->index)) {
133462306a36Sopenharmony_ci		dev_err(ds->dev,
133562306a36Sopenharmony_ci			"A DSA switch with index %d already exists in tree %d\n",
133662306a36Sopenharmony_ci			ds->index, ds->dst->index);
133762306a36Sopenharmony_ci		return -EEXIST;
133862306a36Sopenharmony_ci	}
133962306a36Sopenharmony_ci
134062306a36Sopenharmony_ci	if (ds->dst->last_switch < ds->index)
134162306a36Sopenharmony_ci		ds->dst->last_switch = ds->index;
134262306a36Sopenharmony_ci
134362306a36Sopenharmony_ci	return 0;
134462306a36Sopenharmony_ci}
134562306a36Sopenharmony_ci
134662306a36Sopenharmony_cistatic int dsa_switch_touch_ports(struct dsa_switch *ds)
134762306a36Sopenharmony_ci{
134862306a36Sopenharmony_ci	struct dsa_port *dp;
134962306a36Sopenharmony_ci	int port;
135062306a36Sopenharmony_ci
135162306a36Sopenharmony_ci	for (port = 0; port < ds->num_ports; port++) {
135262306a36Sopenharmony_ci		dp = dsa_port_touch(ds, port);
135362306a36Sopenharmony_ci		if (!dp)
135462306a36Sopenharmony_ci			return -ENOMEM;
135562306a36Sopenharmony_ci	}
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	return 0;
135862306a36Sopenharmony_ci}
135962306a36Sopenharmony_ci
136062306a36Sopenharmony_cistatic int dsa_switch_parse_of(struct dsa_switch *ds, struct device_node *dn)
136162306a36Sopenharmony_ci{
136262306a36Sopenharmony_ci	int err;
136362306a36Sopenharmony_ci
136462306a36Sopenharmony_ci	err = dsa_switch_parse_member_of(ds, dn);
136562306a36Sopenharmony_ci	if (err)
136662306a36Sopenharmony_ci		return err;
136762306a36Sopenharmony_ci
136862306a36Sopenharmony_ci	err = dsa_switch_touch_ports(ds);
136962306a36Sopenharmony_ci	if (err)
137062306a36Sopenharmony_ci		return err;
137162306a36Sopenharmony_ci
137262306a36Sopenharmony_ci	return dsa_switch_parse_ports_of(ds, dn);
137362306a36Sopenharmony_ci}
137462306a36Sopenharmony_ci
137562306a36Sopenharmony_cistatic int dev_is_class(struct device *dev, void *class)
137662306a36Sopenharmony_ci{
137762306a36Sopenharmony_ci	if (dev->class != NULL && !strcmp(dev->class->name, class))
137862306a36Sopenharmony_ci		return 1;
137962306a36Sopenharmony_ci
138062306a36Sopenharmony_ci	return 0;
138162306a36Sopenharmony_ci}
138262306a36Sopenharmony_ci
138362306a36Sopenharmony_cistatic struct device *dev_find_class(struct device *parent, char *class)
138462306a36Sopenharmony_ci{
138562306a36Sopenharmony_ci	if (dev_is_class(parent, class)) {
138662306a36Sopenharmony_ci		get_device(parent);
138762306a36Sopenharmony_ci		return parent;
138862306a36Sopenharmony_ci	}
138962306a36Sopenharmony_ci
139062306a36Sopenharmony_ci	return device_find_child(parent, class, dev_is_class);
139162306a36Sopenharmony_ci}
139262306a36Sopenharmony_ci
139362306a36Sopenharmony_cistatic struct net_device *dsa_dev_to_net_device(struct device *dev)
139462306a36Sopenharmony_ci{
139562306a36Sopenharmony_ci	struct device *d;
139662306a36Sopenharmony_ci
139762306a36Sopenharmony_ci	d = dev_find_class(dev, "net");
139862306a36Sopenharmony_ci	if (d != NULL) {
139962306a36Sopenharmony_ci		struct net_device *nd;
140062306a36Sopenharmony_ci
140162306a36Sopenharmony_ci		nd = to_net_dev(d);
140262306a36Sopenharmony_ci		dev_hold(nd);
140362306a36Sopenharmony_ci		put_device(d);
140462306a36Sopenharmony_ci
140562306a36Sopenharmony_ci		return nd;
140662306a36Sopenharmony_ci	}
140762306a36Sopenharmony_ci
140862306a36Sopenharmony_ci	return NULL;
140962306a36Sopenharmony_ci}
141062306a36Sopenharmony_ci
141162306a36Sopenharmony_cistatic int dsa_port_parse(struct dsa_port *dp, const char *name,
141262306a36Sopenharmony_ci			  struct device *dev)
141362306a36Sopenharmony_ci{
141462306a36Sopenharmony_ci	if (!strcmp(name, "cpu")) {
141562306a36Sopenharmony_ci		struct net_device *master;
141662306a36Sopenharmony_ci
141762306a36Sopenharmony_ci		master = dsa_dev_to_net_device(dev);
141862306a36Sopenharmony_ci		if (!master)
141962306a36Sopenharmony_ci			return -EPROBE_DEFER;
142062306a36Sopenharmony_ci
142162306a36Sopenharmony_ci		dev_put(master);
142262306a36Sopenharmony_ci
142362306a36Sopenharmony_ci		return dsa_port_parse_cpu(dp, master, NULL);
142462306a36Sopenharmony_ci	}
142562306a36Sopenharmony_ci
142662306a36Sopenharmony_ci	if (!strcmp(name, "dsa"))
142762306a36Sopenharmony_ci		return dsa_port_parse_dsa(dp);
142862306a36Sopenharmony_ci
142962306a36Sopenharmony_ci	return dsa_port_parse_user(dp, name);
143062306a36Sopenharmony_ci}
143162306a36Sopenharmony_ci
143262306a36Sopenharmony_cistatic int dsa_switch_parse_ports(struct dsa_switch *ds,
143362306a36Sopenharmony_ci				  struct dsa_chip_data *cd)
143462306a36Sopenharmony_ci{
143562306a36Sopenharmony_ci	bool valid_name_found = false;
143662306a36Sopenharmony_ci	struct dsa_port *dp;
143762306a36Sopenharmony_ci	struct device *dev;
143862306a36Sopenharmony_ci	const char *name;
143962306a36Sopenharmony_ci	unsigned int i;
144062306a36Sopenharmony_ci	int err;
144162306a36Sopenharmony_ci
144262306a36Sopenharmony_ci	for (i = 0; i < DSA_MAX_PORTS; i++) {
144362306a36Sopenharmony_ci		name = cd->port_names[i];
144462306a36Sopenharmony_ci		dev = cd->netdev[i];
144562306a36Sopenharmony_ci		dp = dsa_to_port(ds, i);
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_ci		if (!name)
144862306a36Sopenharmony_ci			continue;
144962306a36Sopenharmony_ci
145062306a36Sopenharmony_ci		err = dsa_port_parse(dp, name, dev);
145162306a36Sopenharmony_ci		if (err)
145262306a36Sopenharmony_ci			return err;
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_ci		valid_name_found = true;
145562306a36Sopenharmony_ci	}
145662306a36Sopenharmony_ci
145762306a36Sopenharmony_ci	if (!valid_name_found && i == DSA_MAX_PORTS)
145862306a36Sopenharmony_ci		return -EINVAL;
145962306a36Sopenharmony_ci
146062306a36Sopenharmony_ci	return 0;
146162306a36Sopenharmony_ci}
146262306a36Sopenharmony_ci
146362306a36Sopenharmony_cistatic int dsa_switch_parse(struct dsa_switch *ds, struct dsa_chip_data *cd)
146462306a36Sopenharmony_ci{
146562306a36Sopenharmony_ci	int err;
146662306a36Sopenharmony_ci
146762306a36Sopenharmony_ci	ds->cd = cd;
146862306a36Sopenharmony_ci
146962306a36Sopenharmony_ci	/* We don't support interconnected switches nor multiple trees via
147062306a36Sopenharmony_ci	 * platform data, so this is the unique switch of the tree.
147162306a36Sopenharmony_ci	 */
147262306a36Sopenharmony_ci	ds->index = 0;
147362306a36Sopenharmony_ci	ds->dst = dsa_tree_touch(0);
147462306a36Sopenharmony_ci	if (!ds->dst)
147562306a36Sopenharmony_ci		return -ENOMEM;
147662306a36Sopenharmony_ci
147762306a36Sopenharmony_ci	err = dsa_switch_touch_ports(ds);
147862306a36Sopenharmony_ci	if (err)
147962306a36Sopenharmony_ci		return err;
148062306a36Sopenharmony_ci
148162306a36Sopenharmony_ci	return dsa_switch_parse_ports(ds, cd);
148262306a36Sopenharmony_ci}
148362306a36Sopenharmony_ci
148462306a36Sopenharmony_cistatic void dsa_switch_release_ports(struct dsa_switch *ds)
148562306a36Sopenharmony_ci{
148662306a36Sopenharmony_ci	struct dsa_port *dp, *next;
148762306a36Sopenharmony_ci
148862306a36Sopenharmony_ci	dsa_switch_for_each_port_safe(dp, next, ds) {
148962306a36Sopenharmony_ci		WARN_ON(!list_empty(&dp->fdbs));
149062306a36Sopenharmony_ci		WARN_ON(!list_empty(&dp->mdbs));
149162306a36Sopenharmony_ci		WARN_ON(!list_empty(&dp->vlans));
149262306a36Sopenharmony_ci		list_del(&dp->list);
149362306a36Sopenharmony_ci		kfree(dp);
149462306a36Sopenharmony_ci	}
149562306a36Sopenharmony_ci}
149662306a36Sopenharmony_ci
149762306a36Sopenharmony_cistatic int dsa_switch_probe(struct dsa_switch *ds)
149862306a36Sopenharmony_ci{
149962306a36Sopenharmony_ci	struct dsa_switch_tree *dst;
150062306a36Sopenharmony_ci	struct dsa_chip_data *pdata;
150162306a36Sopenharmony_ci	struct device_node *np;
150262306a36Sopenharmony_ci	int err;
150362306a36Sopenharmony_ci
150462306a36Sopenharmony_ci	if (!ds->dev)
150562306a36Sopenharmony_ci		return -ENODEV;
150662306a36Sopenharmony_ci
150762306a36Sopenharmony_ci	pdata = ds->dev->platform_data;
150862306a36Sopenharmony_ci	np = ds->dev->of_node;
150962306a36Sopenharmony_ci
151062306a36Sopenharmony_ci	if (!ds->num_ports)
151162306a36Sopenharmony_ci		return -EINVAL;
151262306a36Sopenharmony_ci
151362306a36Sopenharmony_ci	if (np) {
151462306a36Sopenharmony_ci		err = dsa_switch_parse_of(ds, np);
151562306a36Sopenharmony_ci		if (err)
151662306a36Sopenharmony_ci			dsa_switch_release_ports(ds);
151762306a36Sopenharmony_ci	} else if (pdata) {
151862306a36Sopenharmony_ci		err = dsa_switch_parse(ds, pdata);
151962306a36Sopenharmony_ci		if (err)
152062306a36Sopenharmony_ci			dsa_switch_release_ports(ds);
152162306a36Sopenharmony_ci	} else {
152262306a36Sopenharmony_ci		err = -ENODEV;
152362306a36Sopenharmony_ci	}
152462306a36Sopenharmony_ci
152562306a36Sopenharmony_ci	if (err)
152662306a36Sopenharmony_ci		return err;
152762306a36Sopenharmony_ci
152862306a36Sopenharmony_ci	dst = ds->dst;
152962306a36Sopenharmony_ci	dsa_tree_get(dst);
153062306a36Sopenharmony_ci	err = dsa_tree_setup(dst);
153162306a36Sopenharmony_ci	if (err) {
153262306a36Sopenharmony_ci		dsa_switch_release_ports(ds);
153362306a36Sopenharmony_ci		dsa_tree_put(dst);
153462306a36Sopenharmony_ci	}
153562306a36Sopenharmony_ci
153662306a36Sopenharmony_ci	return err;
153762306a36Sopenharmony_ci}
153862306a36Sopenharmony_ci
153962306a36Sopenharmony_ciint dsa_register_switch(struct dsa_switch *ds)
154062306a36Sopenharmony_ci{
154162306a36Sopenharmony_ci	int err;
154262306a36Sopenharmony_ci
154362306a36Sopenharmony_ci	mutex_lock(&dsa2_mutex);
154462306a36Sopenharmony_ci	err = dsa_switch_probe(ds);
154562306a36Sopenharmony_ci	dsa_tree_put(ds->dst);
154662306a36Sopenharmony_ci	mutex_unlock(&dsa2_mutex);
154762306a36Sopenharmony_ci
154862306a36Sopenharmony_ci	return err;
154962306a36Sopenharmony_ci}
155062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_register_switch);
155162306a36Sopenharmony_ci
155262306a36Sopenharmony_cistatic void dsa_switch_remove(struct dsa_switch *ds)
155362306a36Sopenharmony_ci{
155462306a36Sopenharmony_ci	struct dsa_switch_tree *dst = ds->dst;
155562306a36Sopenharmony_ci
155662306a36Sopenharmony_ci	dsa_tree_teardown(dst);
155762306a36Sopenharmony_ci	dsa_switch_release_ports(ds);
155862306a36Sopenharmony_ci	dsa_tree_put(dst);
155962306a36Sopenharmony_ci}
156062306a36Sopenharmony_ci
156162306a36Sopenharmony_civoid dsa_unregister_switch(struct dsa_switch *ds)
156262306a36Sopenharmony_ci{
156362306a36Sopenharmony_ci	mutex_lock(&dsa2_mutex);
156462306a36Sopenharmony_ci	dsa_switch_remove(ds);
156562306a36Sopenharmony_ci	mutex_unlock(&dsa2_mutex);
156662306a36Sopenharmony_ci}
156762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_unregister_switch);
156862306a36Sopenharmony_ci
156962306a36Sopenharmony_ci/* If the DSA master chooses to unregister its net_device on .shutdown, DSA is
157062306a36Sopenharmony_ci * blocking that operation from completion, due to the dev_hold taken inside
157162306a36Sopenharmony_ci * netdev_upper_dev_link. Unlink the DSA slave interfaces from being uppers of
157262306a36Sopenharmony_ci * the DSA master, so that the system can reboot successfully.
157362306a36Sopenharmony_ci */
157462306a36Sopenharmony_civoid dsa_switch_shutdown(struct dsa_switch *ds)
157562306a36Sopenharmony_ci{
157662306a36Sopenharmony_ci	struct net_device *master, *slave_dev;
157762306a36Sopenharmony_ci	struct dsa_port *dp;
157862306a36Sopenharmony_ci
157962306a36Sopenharmony_ci	mutex_lock(&dsa2_mutex);
158062306a36Sopenharmony_ci
158162306a36Sopenharmony_ci	if (!ds->setup)
158262306a36Sopenharmony_ci		goto out;
158362306a36Sopenharmony_ci
158462306a36Sopenharmony_ci	rtnl_lock();
158562306a36Sopenharmony_ci
158662306a36Sopenharmony_ci	dsa_switch_for_each_user_port(dp, ds) {
158762306a36Sopenharmony_ci		master = dsa_port_to_master(dp);
158862306a36Sopenharmony_ci		slave_dev = dp->slave;
158962306a36Sopenharmony_ci
159062306a36Sopenharmony_ci		netdev_upper_dev_unlink(master, slave_dev);
159162306a36Sopenharmony_ci	}
159262306a36Sopenharmony_ci
159362306a36Sopenharmony_ci	/* Disconnect from further netdevice notifiers on the master,
159462306a36Sopenharmony_ci	 * since netdev_uses_dsa() will now return false.
159562306a36Sopenharmony_ci	 */
159662306a36Sopenharmony_ci	dsa_switch_for_each_cpu_port(dp, ds)
159762306a36Sopenharmony_ci		dp->master->dsa_ptr = NULL;
159862306a36Sopenharmony_ci
159962306a36Sopenharmony_ci	rtnl_unlock();
160062306a36Sopenharmony_ciout:
160162306a36Sopenharmony_ci	mutex_unlock(&dsa2_mutex);
160262306a36Sopenharmony_ci}
160362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_switch_shutdown);
160462306a36Sopenharmony_ci
160562306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
160662306a36Sopenharmony_cistatic bool dsa_port_is_initialized(const struct dsa_port *dp)
160762306a36Sopenharmony_ci{
160862306a36Sopenharmony_ci	return dp->type == DSA_PORT_TYPE_USER && dp->slave;
160962306a36Sopenharmony_ci}
161062306a36Sopenharmony_ci
161162306a36Sopenharmony_ciint dsa_switch_suspend(struct dsa_switch *ds)
161262306a36Sopenharmony_ci{
161362306a36Sopenharmony_ci	struct dsa_port *dp;
161462306a36Sopenharmony_ci	int ret = 0;
161562306a36Sopenharmony_ci
161662306a36Sopenharmony_ci	/* Suspend slave network devices */
161762306a36Sopenharmony_ci	dsa_switch_for_each_port(dp, ds) {
161862306a36Sopenharmony_ci		if (!dsa_port_is_initialized(dp))
161962306a36Sopenharmony_ci			continue;
162062306a36Sopenharmony_ci
162162306a36Sopenharmony_ci		ret = dsa_slave_suspend(dp->slave);
162262306a36Sopenharmony_ci		if (ret)
162362306a36Sopenharmony_ci			return ret;
162462306a36Sopenharmony_ci	}
162562306a36Sopenharmony_ci
162662306a36Sopenharmony_ci	if (ds->ops->suspend)
162762306a36Sopenharmony_ci		ret = ds->ops->suspend(ds);
162862306a36Sopenharmony_ci
162962306a36Sopenharmony_ci	return ret;
163062306a36Sopenharmony_ci}
163162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_switch_suspend);
163262306a36Sopenharmony_ci
163362306a36Sopenharmony_ciint dsa_switch_resume(struct dsa_switch *ds)
163462306a36Sopenharmony_ci{
163562306a36Sopenharmony_ci	struct dsa_port *dp;
163662306a36Sopenharmony_ci	int ret = 0;
163762306a36Sopenharmony_ci
163862306a36Sopenharmony_ci	if (ds->ops->resume)
163962306a36Sopenharmony_ci		ret = ds->ops->resume(ds);
164062306a36Sopenharmony_ci
164162306a36Sopenharmony_ci	if (ret)
164262306a36Sopenharmony_ci		return ret;
164362306a36Sopenharmony_ci
164462306a36Sopenharmony_ci	/* Resume slave network devices */
164562306a36Sopenharmony_ci	dsa_switch_for_each_port(dp, ds) {
164662306a36Sopenharmony_ci		if (!dsa_port_is_initialized(dp))
164762306a36Sopenharmony_ci			continue;
164862306a36Sopenharmony_ci
164962306a36Sopenharmony_ci		ret = dsa_slave_resume(dp->slave);
165062306a36Sopenharmony_ci		if (ret)
165162306a36Sopenharmony_ci			return ret;
165262306a36Sopenharmony_ci	}
165362306a36Sopenharmony_ci
165462306a36Sopenharmony_ci	return 0;
165562306a36Sopenharmony_ci}
165662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_switch_resume);
165762306a36Sopenharmony_ci#endif
165862306a36Sopenharmony_ci
165962306a36Sopenharmony_cistruct dsa_port *dsa_port_from_netdev(struct net_device *netdev)
166062306a36Sopenharmony_ci{
166162306a36Sopenharmony_ci	if (!netdev || !dsa_slave_dev_check(netdev))
166262306a36Sopenharmony_ci		return ERR_PTR(-ENODEV);
166362306a36Sopenharmony_ci
166462306a36Sopenharmony_ci	return dsa_slave_to_port(netdev);
166562306a36Sopenharmony_ci}
166662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_port_from_netdev);
166762306a36Sopenharmony_ci
166862306a36Sopenharmony_cibool dsa_db_equal(const struct dsa_db *a, const struct dsa_db *b)
166962306a36Sopenharmony_ci{
167062306a36Sopenharmony_ci	if (a->type != b->type)
167162306a36Sopenharmony_ci		return false;
167262306a36Sopenharmony_ci
167362306a36Sopenharmony_ci	switch (a->type) {
167462306a36Sopenharmony_ci	case DSA_DB_PORT:
167562306a36Sopenharmony_ci		return a->dp == b->dp;
167662306a36Sopenharmony_ci	case DSA_DB_LAG:
167762306a36Sopenharmony_ci		return a->lag.dev == b->lag.dev;
167862306a36Sopenharmony_ci	case DSA_DB_BRIDGE:
167962306a36Sopenharmony_ci		return a->bridge.num == b->bridge.num;
168062306a36Sopenharmony_ci	default:
168162306a36Sopenharmony_ci		WARN_ON(1);
168262306a36Sopenharmony_ci		return false;
168362306a36Sopenharmony_ci	}
168462306a36Sopenharmony_ci}
168562306a36Sopenharmony_ci
168662306a36Sopenharmony_cibool dsa_fdb_present_in_other_db(struct dsa_switch *ds, int port,
168762306a36Sopenharmony_ci				 const unsigned char *addr, u16 vid,
168862306a36Sopenharmony_ci				 struct dsa_db db)
168962306a36Sopenharmony_ci{
169062306a36Sopenharmony_ci	struct dsa_port *dp = dsa_to_port(ds, port);
169162306a36Sopenharmony_ci	struct dsa_mac_addr *a;
169262306a36Sopenharmony_ci
169362306a36Sopenharmony_ci	lockdep_assert_held(&dp->addr_lists_lock);
169462306a36Sopenharmony_ci
169562306a36Sopenharmony_ci	list_for_each_entry(a, &dp->fdbs, list) {
169662306a36Sopenharmony_ci		if (!ether_addr_equal(a->addr, addr) || a->vid != vid)
169762306a36Sopenharmony_ci			continue;
169862306a36Sopenharmony_ci
169962306a36Sopenharmony_ci		if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
170062306a36Sopenharmony_ci			return true;
170162306a36Sopenharmony_ci	}
170262306a36Sopenharmony_ci
170362306a36Sopenharmony_ci	return false;
170462306a36Sopenharmony_ci}
170562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_fdb_present_in_other_db);
170662306a36Sopenharmony_ci
170762306a36Sopenharmony_cibool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
170862306a36Sopenharmony_ci				 const struct switchdev_obj_port_mdb *mdb,
170962306a36Sopenharmony_ci				 struct dsa_db db)
171062306a36Sopenharmony_ci{
171162306a36Sopenharmony_ci	struct dsa_port *dp = dsa_to_port(ds, port);
171262306a36Sopenharmony_ci	struct dsa_mac_addr *a;
171362306a36Sopenharmony_ci
171462306a36Sopenharmony_ci	lockdep_assert_held(&dp->addr_lists_lock);
171562306a36Sopenharmony_ci
171662306a36Sopenharmony_ci	list_for_each_entry(a, &dp->mdbs, list) {
171762306a36Sopenharmony_ci		if (!ether_addr_equal(a->addr, mdb->addr) || a->vid != mdb->vid)
171862306a36Sopenharmony_ci			continue;
171962306a36Sopenharmony_ci
172062306a36Sopenharmony_ci		if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
172162306a36Sopenharmony_ci			return true;
172262306a36Sopenharmony_ci	}
172362306a36Sopenharmony_ci
172462306a36Sopenharmony_ci	return false;
172562306a36Sopenharmony_ci}
172662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dsa_mdb_present_in_other_db);
172762306a36Sopenharmony_ci
172862306a36Sopenharmony_cistatic const struct dsa_stubs __dsa_stubs = {
172962306a36Sopenharmony_ci	.master_hwtstamp_validate = __dsa_master_hwtstamp_validate,
173062306a36Sopenharmony_ci};
173162306a36Sopenharmony_ci
173262306a36Sopenharmony_cistatic void dsa_register_stubs(void)
173362306a36Sopenharmony_ci{
173462306a36Sopenharmony_ci	dsa_stubs = &__dsa_stubs;
173562306a36Sopenharmony_ci}
173662306a36Sopenharmony_ci
173762306a36Sopenharmony_cistatic void dsa_unregister_stubs(void)
173862306a36Sopenharmony_ci{
173962306a36Sopenharmony_ci	dsa_stubs = NULL;
174062306a36Sopenharmony_ci}
174162306a36Sopenharmony_ci
174262306a36Sopenharmony_cistatic int __init dsa_init_module(void)
174362306a36Sopenharmony_ci{
174462306a36Sopenharmony_ci	int rc;
174562306a36Sopenharmony_ci
174662306a36Sopenharmony_ci	dsa_owq = alloc_ordered_workqueue("dsa_ordered",
174762306a36Sopenharmony_ci					  WQ_MEM_RECLAIM);
174862306a36Sopenharmony_ci	if (!dsa_owq)
174962306a36Sopenharmony_ci		return -ENOMEM;
175062306a36Sopenharmony_ci
175162306a36Sopenharmony_ci	rc = dsa_slave_register_notifier();
175262306a36Sopenharmony_ci	if (rc)
175362306a36Sopenharmony_ci		goto register_notifier_fail;
175462306a36Sopenharmony_ci
175562306a36Sopenharmony_ci	dev_add_pack(&dsa_pack_type);
175662306a36Sopenharmony_ci
175762306a36Sopenharmony_ci	rc = rtnl_link_register(&dsa_link_ops);
175862306a36Sopenharmony_ci	if (rc)
175962306a36Sopenharmony_ci		goto netlink_register_fail;
176062306a36Sopenharmony_ci
176162306a36Sopenharmony_ci	dsa_register_stubs();
176262306a36Sopenharmony_ci
176362306a36Sopenharmony_ci	return 0;
176462306a36Sopenharmony_ci
176562306a36Sopenharmony_cinetlink_register_fail:
176662306a36Sopenharmony_ci	dsa_slave_unregister_notifier();
176762306a36Sopenharmony_ci	dev_remove_pack(&dsa_pack_type);
176862306a36Sopenharmony_ciregister_notifier_fail:
176962306a36Sopenharmony_ci	destroy_workqueue(dsa_owq);
177062306a36Sopenharmony_ci
177162306a36Sopenharmony_ci	return rc;
177262306a36Sopenharmony_ci}
177362306a36Sopenharmony_cimodule_init(dsa_init_module);
177462306a36Sopenharmony_ci
177562306a36Sopenharmony_cistatic void __exit dsa_cleanup_module(void)
177662306a36Sopenharmony_ci{
177762306a36Sopenharmony_ci	dsa_unregister_stubs();
177862306a36Sopenharmony_ci
177962306a36Sopenharmony_ci	rtnl_link_unregister(&dsa_link_ops);
178062306a36Sopenharmony_ci
178162306a36Sopenharmony_ci	dsa_slave_unregister_notifier();
178262306a36Sopenharmony_ci	dev_remove_pack(&dsa_pack_type);
178362306a36Sopenharmony_ci	destroy_workqueue(dsa_owq);
178462306a36Sopenharmony_ci}
178562306a36Sopenharmony_cimodule_exit(dsa_cleanup_module);
178662306a36Sopenharmony_ci
178762306a36Sopenharmony_ciMODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
178862306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for Distributed Switch Architecture switch chips");
178962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
179062306a36Sopenharmony_ciMODULE_ALIAS("platform:dsa");
1791