Commit a6f0b26d authored by Jakub Kicinski's avatar Jakub Kicinski

Merge branch 'cross-chip-bridging-for-disjoint-dsa-trees'

Vladimir Oltean says:

====================
This series adds support for boards where DSA switches of multiple types
are cascaded together. Actually this type of setup was brought up before
on netdev, and it looks like utilizing disjoint trees is the way to go:

https://lkml.org/lkml/2019/7/7/225

The trouble with disjoint trees (prior to this patch series) is that only
bridging of ports within the same hardware switch can be offloaded.
After scratching my head for a while, it looks like the easiest way to
support hardware bridging between different DSA trees is to bridge their
DSA masters and extend the crosschip bridging operations.

I have given some thought to bridging the DSA masters with the slaves
themselves, but given the hardware topology described in the commit
message of patch 4/4, virtually any number (and combination) of bridges
(forwarding domains) can be created on top of those 3x4-port front-panel
switches. So it becomes a lot less obvious, when the front-panel ports
are enslaved to more than 1 bridge, which bridge should the DSA masters
be enslaved to.

So the least awkward approach was to just create a completely separate
bridge for the DSA masters, whose entire purpose is to permit hardware
forwarding between the discrete switches beneath it.

This is a direct resend of v3, which was deferred due to lack of review.
In the meantime Florian has reviewed and tested some of them.

v1 was submitted here:
https://patchwork.ozlabs.org/project/netdev/cover/20200429161952.17769-1-olteanv@gmail.com/

v2 was submitted here:
https://patchwork.ozlabs.org/project/netdev/cover/20200430202542.11797-1-olteanv@gmail.com/

v3 was submitted here:
https://patchwork.ozlabs.org/project/netdev/cover/20200503221228.10928-1-olteanv@gmail.com/
====================
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 90d9834e ac02a451
......@@ -2233,26 +2233,34 @@ static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port,
mv88e6xxx_reg_unlock(chip);
}
static int mv88e6xxx_crosschip_bridge_join(struct dsa_switch *ds, int dev,
static int mv88e6xxx_crosschip_bridge_join(struct dsa_switch *ds,
int tree_index, int sw_index,
int port, struct net_device *br)
{
struct mv88e6xxx_chip *chip = ds->priv;
int err;
if (tree_index != ds->dst->index)
return 0;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_pvt_map(chip, dev, port);
err = mv88e6xxx_pvt_map(chip, sw_index, port);
mv88e6xxx_reg_unlock(chip);
return err;
}
static void mv88e6xxx_crosschip_bridge_leave(struct dsa_switch *ds, int dev,
static void mv88e6xxx_crosschip_bridge_leave(struct dsa_switch *ds,
int tree_index, int sw_index,
int port, struct net_device *br)
{
struct mv88e6xxx_chip *chip = ds->priv;
if (tree_index != ds->dst->index)
return;
mv88e6xxx_reg_lock(chip);
if (mv88e6xxx_pvt_map(chip, dev, port))
if (mv88e6xxx_pvt_map(chip, sw_index, port))
dev_err(ds->dev, "failed to remap cross-chip Port VLAN\n");
mv88e6xxx_reg_unlock(chip);
}
......
......@@ -8,6 +8,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <linux/dsa/sja1105.h>
#include <linux/dsa/8021q.h>
#include <net/dsa.h>
#include <linux/mutex.h>
#include "sja1105_static_config.h"
......@@ -185,6 +186,7 @@ struct sja1105_private {
struct gpio_desc *reset_gpio;
struct spi_device *spidev;
struct dsa_switch *ds;
struct list_head crosschip_links;
struct sja1105_flow_block flow_block;
struct sja1105_port ports[SJA1105_NUM_PORTS];
/* Serializes transmission of management frames so that
......
......@@ -25,6 +25,8 @@
#include "sja1105_sgmii.h"
#include "sja1105_tas.h"
static const struct dsa_switch_ops sja1105_switch_ops;
static void sja1105_hw_reset(struct gpio_desc *gpio, unsigned int pulse_len,
unsigned int startup_delay)
{
......@@ -1791,6 +1793,84 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
return 0;
}
static int sja1105_crosschip_bridge_join(struct dsa_switch *ds,
int tree_index, int sw_index,
int other_port, struct net_device *br)
{
struct dsa_switch *other_ds = dsa_switch_find(tree_index, sw_index);
struct sja1105_private *other_priv = other_ds->priv;
struct sja1105_private *priv = ds->priv;
int port, rc;
if (other_ds->ops != &sja1105_switch_ops)
return 0;
for (port = 0; port < ds->num_ports; port++) {
if (!dsa_is_user_port(ds, port))
continue;
if (dsa_to_port(ds, port)->bridge_dev != br)
continue;
rc = dsa_8021q_crosschip_bridge_join(ds, port, other_ds,
other_port, br,
&priv->crosschip_links);
if (rc)
return rc;
rc = dsa_8021q_crosschip_bridge_join(other_ds, other_port, ds,
port, br,
&other_priv->crosschip_links);
if (rc)
return rc;
}
return 0;
}
static void sja1105_crosschip_bridge_leave(struct dsa_switch *ds,
int tree_index, int sw_index,
int other_port,
struct net_device *br)
{
struct dsa_switch *other_ds = dsa_switch_find(tree_index, sw_index);
struct sja1105_private *other_priv = other_ds->priv;
struct sja1105_private *priv = ds->priv;
int port;
if (other_ds->ops != &sja1105_switch_ops)
return;
for (port = 0; port < ds->num_ports; port++) {
if (!dsa_is_user_port(ds, port))
continue;
if (dsa_to_port(ds, port)->bridge_dev != br)
continue;
dsa_8021q_crosschip_bridge_leave(ds, port, other_ds, other_port,
br, &priv->crosschip_links);
dsa_8021q_crosschip_bridge_leave(other_ds, other_port, ds,
port, br,
&other_priv->crosschip_links);
}
}
static int sja1105_replay_crosschip_vlans(struct dsa_switch *ds, bool enabled)
{
struct sja1105_private *priv = ds->priv;
struct dsa_8021q_crosschip_link *c;
int rc;
list_for_each_entry(c, &priv->crosschip_links, list) {
rc = dsa_8021q_crosschip_link_apply(ds, c->port, c->other_ds,
c->other_port, enabled);
if (rc)
break;
}
return rc;
}
static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
{
int rc, i;
......@@ -1803,6 +1883,12 @@ static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
return rc;
}
}
rc = sja1105_replay_crosschip_vlans(ds, enabled);
if (rc) {
dev_err(ds->dev, "Failed to replay crosschip VLANs: %d\n", rc);
return rc;
}
dev_info(ds->dev, "%s switch tagging\n",
enabled ? "Enabled" : "Disabled");
return 0;
......@@ -2370,6 +2456,8 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
.cls_flower_add = sja1105_cls_flower_add,
.cls_flower_del = sja1105_cls_flower_del,
.cls_flower_stats = sja1105_cls_flower_stats,
.crosschip_bridge_join = sja1105_crosschip_bridge_join,
.crosschip_bridge_leave = sja1105_crosschip_bridge_leave,
};
static int sja1105_check_device_id(struct sja1105_private *priv)
......@@ -2472,6 +2560,8 @@ static int sja1105_probe(struct spi_device *spi)
mutex_init(&priv->ptp_data.lock);
mutex_init(&priv->mgmt_lock);
INIT_LIST_HEAD(&priv->crosschip_links);
sja1105_tas_setup(ds);
sja1105_flower_setup(ds);
......
......@@ -12,11 +12,33 @@ struct sk_buff;
struct net_device;
struct packet_type;
struct dsa_8021q_crosschip_link {
struct list_head list;
int port;
struct dsa_switch *other_ds;
int other_port;
refcount_t refcount;
};
#if IS_ENABLED(CONFIG_NET_DSA_TAG_8021Q)
int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
bool enabled);
int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, bool enabled);
int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, struct net_device *br,
struct list_head *crosschip_links);
int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, struct net_device *br,
struct list_head *crosschip_links);
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci);
......@@ -36,6 +58,29 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int index,
return 0;
}
int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, bool enabled)
{
return 0;
}
int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, struct net_device *br,
struct list_head *crosschip_links)
{
return 0;
}
int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, struct net_device *br,
struct list_head *crosschip_links)
{
return 0;
}
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci)
{
......
......@@ -574,10 +574,12 @@ struct dsa_switch_ops {
/*
* Cross-chip operations
*/
int (*crosschip_bridge_join)(struct dsa_switch *ds, int sw_index,
int port, struct net_device *br);
void (*crosschip_bridge_leave)(struct dsa_switch *ds, int sw_index,
int port, struct net_device *br);
int (*crosschip_bridge_join)(struct dsa_switch *ds, int tree_index,
int sw_index, int port,
struct net_device *br);
void (*crosschip_bridge_leave)(struct dsa_switch *ds, int tree_index,
int sw_index, int port,
struct net_device *br);
/*
* PTP functionality
......@@ -651,7 +653,7 @@ struct dsa_switch_driver {
struct net_device *dsa_dev_to_net_device(struct device *dev);
/* Keep inline for faster access in hot path */
static inline bool netdev_uses_dsa(struct net_device *dev)
static inline bool netdev_uses_dsa(const struct net_device *dev)
{
#if IS_ENABLED(CONFIG_NET_DSA)
return dev->dsa_ptr && dev->dsa_ptr->rcv;
......@@ -670,6 +672,7 @@ static inline bool dsa_can_decode(const struct sk_buff *skb,
void dsa_unregister_switch(struct dsa_switch *ds);
int dsa_register_switch(struct dsa_switch *ds);
struct dsa_switch *dsa_switch_find(int tree_index, int sw_index);
#ifdef CONFIG_PM_SLEEP
int dsa_switch_suspend(struct dsa_switch *ds);
int dsa_switch_resume(struct dsa_switch *ds);
......
......@@ -563,18 +563,32 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
unsigned br_hr, dev_hr;
bool changed_addr;
/* Don't allow bridging non-ethernet like devices, or DSA-enabled
* master network devices since the bridge layer rx_handler prevents
* the DSA fake ethertype handler to be invoked, so we do not strip off
* the DSA switch tag protocol header and the bridge layer just return
* RX_HANDLER_CONSUMED, stopping RX processing for these frames.
*/
/* Don't allow bridging non-ethernet like devices. */
if ((dev->flags & IFF_LOOPBACK) ||
dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
!is_valid_ether_addr(dev->dev_addr) ||
netdev_uses_dsa(dev))
!is_valid_ether_addr(dev->dev_addr))
return -EINVAL;
/* Also don't allow bridging of net devices that are DSA masters, since
* the bridge layer rx_handler prevents the DSA fake ethertype handler
* to be invoked, so we don't get the chance to strip off and parse the
* DSA switch tag protocol header (the bridge layer just returns
* RX_HANDLER_CONSUMED, stopping RX processing for these frames).
* The only case where that would not be an issue is when bridging can
* already be offloaded, such as when the DSA master is itself a DSA
* or plain switchdev port, and is bridged only with other ports from
* the same hardware device.
*/
if (netdev_uses_dsa(dev)) {
list_for_each_entry(p, &br->port_list, list) {
if (!netdev_port_same_parent_id(dev, p->dev)) {
NL_SET_ERR_MSG(extack,
"Cannot do software bridging with a DSA master");
return -EINVAL;
}
}
}
/* No bridging of bridges */
if (dev->netdev_ops->ndo_start_xmit == br_dev_xmit) {
NL_SET_ERR_MSG(extack,
......@@ -618,7 +632,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
if (err)
goto err3;
err = netdev_rx_handler_register(dev, br_handle_frame, p);
err = netdev_rx_handler_register(dev, br_get_rx_handler(dev), p);
if (err)
goto err4;
......
......@@ -17,6 +17,7 @@
#endif
#include <linux/neighbour.h>
#include <net/arp.h>
#include <net/dsa.h>
#include <linux/export.h>
#include <linux/rculist.h>
#include "br_private.h"
......@@ -257,7 +258,7 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb)
* Return NULL if skb is handled
* note: already called with rcu_read_lock
*/
rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{
struct net_bridge_port *p;
struct sk_buff *skb = *pskb;
......@@ -359,3 +360,23 @@ rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
}
return RX_HANDLER_CONSUMED;
}
/* This function has no purpose other than to appease the br_port_get_rcu/rtnl
* helpers which identify bridged ports according to the rx_handler installed
* on them (so there _needs_ to be a bridge rx_handler even if we don't need it
* to do anything useful). This bridge won't support traffic to/from the stack,
* but only hardware bridging. So return RX_HANDLER_PASS so we don't steal
* frames from the ETH_P_XDSA packet_type handler.
*/
static rx_handler_result_t br_handle_frame_dummy(struct sk_buff **pskb)
{
return RX_HANDLER_PASS;
}
rx_handler_func_t *br_get_rx_handler(const struct net_device *dev)
{
if (netdev_uses_dsa(dev))
return br_handle_frame_dummy;
return br_handle_frame;
}
......@@ -702,16 +702,16 @@ int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev);
/* br_input.c */
int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
rx_handler_result_t br_handle_frame(struct sk_buff **pskb);
rx_handler_func_t *br_get_rx_handler(const struct net_device *dev);
static inline bool br_rx_handler_check_rcu(const struct net_device *dev)
{
return rcu_dereference(dev->rx_handler) == br_handle_frame;
return rcu_dereference(dev->rx_handler) == br_get_rx_handler(dev);
}
static inline bool br_rx_handler_check_rtnl(const struct net_device *dev)
{
return rcu_dereference_rtnl(dev->rx_handler) == br_handle_frame;
return rcu_dereference_rtnl(dev->rx_handler) == br_get_rx_handler(dev);
}
static inline struct net_bridge_port *br_port_get_check_rcu(const struct net_device *dev)
......
......@@ -24,6 +24,27 @@ LIST_HEAD(dsa_tree_list);
static const struct devlink_ops dsa_devlink_ops = {
};
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;
......
......@@ -35,6 +35,7 @@ struct dsa_notifier_ageing_time_info {
/* DSA_NOTIFIER_BRIDGE_* */
struct dsa_notifier_bridge_info {
struct net_device *br;
int tree_index;
int sw_index;
int port;
};
......
......@@ -13,6 +13,23 @@
#include "dsa_priv.h"
static int dsa_broadcast(unsigned long e, void *v)
{
struct dsa_switch_tree *dst;
int err = 0;
list_for_each_entry(dst, &dsa_tree_list, list) {
struct raw_notifier_head *nh = &dst->nh;
err = raw_notifier_call_chain(nh, e, v);
err = notifier_to_errno(err);
if (err)
break;
}
return err;
}
static int dsa_port_notify(const struct dsa_port *dp, unsigned long e, void *v)
{
struct raw_notifier_head *nh = &dp->ds->dst->nh;
......@@ -120,6 +137,7 @@ void dsa_port_disable(struct dsa_port *dp)
int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br)
{
struct dsa_notifier_bridge_info info = {
.tree_index = dp->ds->dst->index,
.sw_index = dp->ds->index,
.port = dp->index,
.br = br,
......@@ -136,7 +154,7 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br)
*/
dp->bridge_dev = br;
err = dsa_port_notify(dp, DSA_NOTIFIER_BRIDGE_JOIN, &info);
err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_JOIN, &info);
/* The bridging is rolled back on error */
if (err) {
......@@ -150,6 +168,7 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br)
void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
{
struct dsa_notifier_bridge_info info = {
.tree_index = dp->ds->dst->index,
.sw_index = dp->ds->index,
.port = dp->index,
.br = br,
......@@ -161,7 +180,7 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
*/
dp->bridge_dev = NULL;
err = dsa_port_notify(dp, DSA_NOTIFIER_BRIDGE_LEAVE, &info);
err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
if (err)
pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
......
......@@ -89,11 +89,16 @@ static int dsa_switch_mtu(struct dsa_switch *ds,
static int dsa_switch_bridge_join(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info)
{
if (ds->index == info->sw_index && ds->ops->port_bridge_join)
struct dsa_switch_tree *dst = ds->dst;
if (dst->index == info->tree_index && ds->index == info->sw_index &&
ds->ops->port_bridge_join)
return ds->ops->port_bridge_join(ds, info->port, info->br);
if (ds->index != info->sw_index && ds->ops->crosschip_bridge_join)
return ds->ops->crosschip_bridge_join(ds, info->sw_index,
if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
ds->ops->crosschip_bridge_join)
return ds->ops->crosschip_bridge_join(ds, info->tree_index,
info->sw_index,
info->port, info->br);
return 0;
......@@ -103,13 +108,17 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info)
{
bool unset_vlan_filtering = br_vlan_enabled(info->br);
struct dsa_switch_tree *dst = ds->dst;
int err, i;
if (ds->index == info->sw_index && ds->ops->port_bridge_leave)
if (dst->index == info->tree_index && ds->index == info->sw_index &&
ds->ops->port_bridge_join)
ds->ops->port_bridge_leave(ds, info->port, info->br);
if (ds->index != info->sw_index && ds->ops->crosschip_bridge_leave)
ds->ops->crosschip_bridge_leave(ds, info->sw_index, info->port,
if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
ds->ops->crosschip_bridge_join)
ds->ops->crosschip_bridge_leave(ds, info->tree_index,
info->sw_index, info->port,
info->br);
/* If the bridge was vlan_filtering, the bridge core doesn't trigger an
......
......@@ -8,6 +8,7 @@
*/
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/dsa/8021q.h>
#include "dsa_priv.h"
......@@ -288,6 +289,156 @@ int dsa_port_setup_8021q_tagging(struct dsa_switch *ds, int port, bool enabled)
}
EXPORT_SYMBOL_GPL(dsa_port_setup_8021q_tagging);
int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, bool enabled)
{
u16 rx_vid = dsa_8021q_rx_vid(ds, port);
/* @rx_vid of local @ds port @port goes to @other_port of
* @other_ds
*/
return dsa_8021q_vid_apply(other_ds, other_port, rx_vid,
BRIDGE_VLAN_INFO_UNTAGGED, enabled);
}
EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_link_apply);
static int dsa_8021q_crosschip_link_add(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port,
struct list_head *crosschip_links)
{
struct dsa_8021q_crosschip_link *c;
list_for_each_entry(c, crosschip_links, list) {
if (c->port == port && c->other_ds == other_ds &&
c->other_port == other_port) {
refcount_inc(&c->refcount);
return 0;
}
}
dev_dbg(ds->dev, "adding crosschip link from port %d to %s port %d\n",
port, dev_name(other_ds->dev), other_port);
c = kzalloc(sizeof(*c), GFP_KERNEL);
if (!c)
return -ENOMEM;
c->port = port;
c->other_ds = other_ds;
c->other_port = other_port;
refcount_set(&c->refcount, 1);
list_add(&c->list, crosschip_links);
return 0;
}
static void dsa_8021q_crosschip_link_del(struct dsa_switch *ds,
struct dsa_8021q_crosschip_link *c,
struct list_head *crosschip_links,
bool *keep)
{
*keep = !refcount_dec_and_test(&c->refcount);
if (*keep)
return;
dev_dbg(ds->dev,
"deleting crosschip link from port %d to %s port %d\n",
c->port, dev_name(c->other_ds->dev), c->other_port);
list_del(&c->list);
kfree(c);
}
/* Make traffic from local port @port be received by remote port @other_port.
* This means that our @rx_vid needs to be installed on @other_ds's upstream
* and user ports. The user ports should be egress-untagged so that they can
* pop the dsa_8021q VLAN. But the @other_upstream can be either egress-tagged
* or untagged: it doesn't matter, since it should never egress a frame having
* our @rx_vid.
*/
int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, struct net_device *br,
struct list_head *crosschip_links)
{
/* @other_upstream is how @other_ds reaches us. If we are part
* of disjoint trees, then we are probably connected through
* our CPU ports. If we're part of the same tree though, we should
* probably use dsa_towards_port.
*/
int other_upstream = dsa_upstream_port(other_ds, other_port);
int rc;
rc = dsa_8021q_crosschip_link_add(ds, port, other_ds,
other_port, crosschip_links);
if (rc)
return rc;
if (!br_vlan_enabled(br)) {
rc = dsa_8021q_crosschip_link_apply(ds, port, other_ds,
other_port, true);
if (rc)
return rc;
}
rc = dsa_8021q_crosschip_link_add(ds, port, other_ds,
other_upstream,
crosschip_links);
if (rc)
return rc;
if (!br_vlan_enabled(br)) {
rc = dsa_8021q_crosschip_link_apply(ds, port, other_ds,
other_upstream, true);
if (rc)
return rc;
}
return 0;
}
EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_join);
int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_switch *other_ds,
int other_port, struct net_device *br,
struct list_head *crosschip_links)
{
int other_upstream = dsa_upstream_port(other_ds, other_port);
struct dsa_8021q_crosschip_link *c, *n;
list_for_each_entry_safe(c, n, crosschip_links, list) {
if (c->port == port && c->other_ds == other_ds &&
(c->other_port == other_port ||
c->other_port == other_upstream)) {
struct dsa_switch *other_ds = c->other_ds;
int other_port = c->other_port;
bool keep;
int rc;
dsa_8021q_crosschip_link_del(ds, c, crosschip_links,
&keep);
if (keep)
continue;
if (!br_vlan_enabled(br)) {
rc = dsa_8021q_crosschip_link_apply(ds, port,
other_ds,
other_port,
false);
if (rc)
return rc;
}
}
}
return 0;
}
EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_leave);
struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev,
u16 tpid, u16 tci)
{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment