net: dsa: Add ndo_vlan_rx_{add, kill}_vid implementation
In order to properly support VLAN filtering being enabled/disabled on a bridge, while having other ports being non bridge port members, we need to support the ndo_vlan_rx_{add,kill}_vid callbacks in order to make sure the non-bridge ports can continue receiving VLAN tags, even when the switch is globally configured to do ingress/egress VID checking. Since we can call dsa_port_vlan_{add,del} with a bridge_dev pointer NULL, we now need to check that in these two functions. We specifically deal with two possibly problematic cases: - creating a bridge VLAN entry while there is an existing VLAN device claiming that same VID - creating a VLAN device while there is an existing bridge VLAN entry with that VID Those are both resolved with returning -EBUSY back to user-space. Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
cc1d5bda17
commit
061f6a505a
|
@ -291,7 +291,10 @@ int dsa_port_vlan_add(struct dsa_port *dp,
|
|||
.vlan = vlan,
|
||||
};
|
||||
|
||||
if (br_vlan_enabled(dp->bridge_dev))
|
||||
/* Can be called from dsa_slave_port_obj_add() or
|
||||
* dsa_slave_vlan_rx_add_vid()
|
||||
*/
|
||||
if (!dp->bridge_dev || br_vlan_enabled(dp->bridge_dev))
|
||||
return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_ADD, &info);
|
||||
|
||||
return 0;
|
||||
|
@ -306,10 +309,13 @@ int dsa_port_vlan_del(struct dsa_port *dp,
|
|||
.vlan = vlan,
|
||||
};
|
||||
|
||||
if (netif_is_bridge_master(vlan->obj.orig_dev))
|
||||
if (vlan->obj.orig_dev && netif_is_bridge_master(vlan->obj.orig_dev))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (br_vlan_enabled(dp->bridge_dev))
|
||||
/* Can be called from dsa_slave_port_obj_del() or
|
||||
* dsa_slave_vlan_rx_kill_vid()
|
||||
*/
|
||||
if (!dp->bridge_dev || br_vlan_enabled(dp->bridge_dev))
|
||||
return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_DEL, &info);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -983,6 +983,72 @@ static int dsa_slave_get_ts_info(struct net_device *dev,
|
|||
return ds->ops->get_ts_info(ds, p->dp->index, ts);
|
||||
}
|
||||
|
||||
static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
|
||||
u16 vid)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan vlan = {
|
||||
.vid_begin = vid,
|
||||
.vid_end = vid,
|
||||
/* This API only allows programming tagged, non-PVID VIDs */
|
||||
.flags = 0,
|
||||
};
|
||||
struct bridge_vlan_info info;
|
||||
int ret;
|
||||
|
||||
/* Check for a possible bridge VLAN entry now since there is no
|
||||
* need to emulate the switchdev prepare + commit phase.
|
||||
*/
|
||||
if (dp->bridge_dev) {
|
||||
/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
|
||||
* device, respectively the VID is not found, returning
|
||||
* 0 means success, which is a failure for us here.
|
||||
*/
|
||||
ret = br_vlan_get_info(dp->bridge_dev, vid, &info);
|
||||
if (ret == 0)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = dsa_port_vlan_add(dp, &vlan, NULL);
|
||||
if (ret == -EOPNOTSUPP)
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
|
||||
u16 vid)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan vlan = {
|
||||
.vid_begin = vid,
|
||||
.vid_end = vid,
|
||||
/* This API only allows programming tagged, non-PVID VIDs */
|
||||
.flags = 0,
|
||||
};
|
||||
struct bridge_vlan_info info;
|
||||
int ret;
|
||||
|
||||
/* Check for a possible bridge VLAN entry now since there is no
|
||||
* need to emulate the switchdev prepare + commit phase.
|
||||
*/
|
||||
if (dp->bridge_dev) {
|
||||
/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
|
||||
* device, respectively the VID is not found, returning
|
||||
* 0 means success, which is a failure for us here.
|
||||
*/
|
||||
ret = br_vlan_get_info(dp->bridge_dev, vid, &info);
|
||||
if (ret == 0)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = dsa_port_vlan_del(dp, &vlan);
|
||||
if (ret == -EOPNOTSUPP)
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct ethtool_ops dsa_slave_ethtool_ops = {
|
||||
.get_drvinfo = dsa_slave_get_drvinfo,
|
||||
.get_regs_len = dsa_slave_get_regs_len,
|
||||
|
@ -1048,6 +1114,8 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
|
|||
.ndo_setup_tc = dsa_slave_setup_tc,
|
||||
.ndo_get_stats64 = dsa_slave_get_stats64,
|
||||
.ndo_get_port_parent_id = dsa_slave_get_port_parent_id,
|
||||
.ndo_vlan_rx_add_vid = dsa_slave_vlan_rx_add_vid,
|
||||
.ndo_vlan_rx_kill_vid = dsa_slave_vlan_rx_kill_vid,
|
||||
};
|
||||
|
||||
static const struct switchdev_ops dsa_slave_switchdev_ops = {
|
||||
|
@ -1307,7 +1375,8 @@ int dsa_slave_create(struct dsa_port *port)
|
|||
if (slave_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
slave_dev->features = master->vlan_features | NETIF_F_HW_TC;
|
||||
slave_dev->features = master->vlan_features | NETIF_F_HW_TC |
|
||||
NETIF_F_HW_VLAN_CTAG_FILTER;
|
||||
slave_dev->hw_features |= NETIF_F_HW_TC;
|
||||
slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
|
||||
eth_hw_addr_inherit(slave_dev, master);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/if_vlan.h>
|
||||
#include <net/switchdev.h>
|
||||
|
||||
#include "dsa_priv.h"
|
||||
|
@ -168,6 +169,43 @@ static int dsa_switch_mdb_del(struct dsa_switch *ds,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_port_vlan_device_check(struct net_device *vlan_dev,
|
||||
int vlan_dev_vid,
|
||||
void *arg)
|
||||
{
|
||||
struct switchdev_obj_port_vlan *vlan = arg;
|
||||
u16 vid;
|
||||
|
||||
for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
|
||||
if (vid == vlan_dev_vid)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_port_vlan_check(struct dsa_switch *ds, int port,
|
||||
const struct switchdev_obj_port_vlan *vlan)
|
||||
{
|
||||
const struct dsa_port *dp = dsa_to_port(ds, port);
|
||||
int err = 0;
|
||||
|
||||
/* Device is not bridged, let it proceed with the VLAN device
|
||||
* creation.
|
||||
*/
|
||||
if (!dp->bridge_dev)
|
||||
return err;
|
||||
|
||||
/* dsa_slave_vlan_rx_{add,kill}_vid() cannot use the prepare pharse and
|
||||
* already checks whether there is an overlapping bridge VLAN entry
|
||||
* with the same VID, so here we only need to check that if we are
|
||||
* adding a bridge VLAN entry there is not an overlapping VLAN device
|
||||
* claiming that VID.
|
||||
*/
|
||||
return vlan_for_each(dp->slave, dsa_port_vlan_device_check,
|
||||
(void *)vlan);
|
||||
}
|
||||
|
||||
static int
|
||||
dsa_switch_vlan_prepare_bitmap(struct dsa_switch *ds,
|
||||
const struct switchdev_obj_port_vlan *vlan,
|
||||
|
@ -179,6 +217,10 @@ dsa_switch_vlan_prepare_bitmap(struct dsa_switch *ds,
|
|||
return -EOPNOTSUPP;
|
||||
|
||||
for_each_set_bit(port, bitmap, ds->num_ports) {
|
||||
err = dsa_port_vlan_check(ds, port, vlan);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = ds->ops->port_vlan_prepare(ds, port, vlan);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
Loading…
Reference in New Issue