|
|
@@ -200,6 +200,152 @@ out:
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int dsa_bridge_check_vlan_range(struct dsa_switch *ds,
|
|
|
+ const struct net_device *bridge,
|
|
|
+ u16 vid_begin, u16 vid_end)
|
|
|
+{
|
|
|
+ struct dsa_slave_priv *p;
|
|
|
+ struct net_device *dev, *vlan_br;
|
|
|
+ DECLARE_BITMAP(members, DSA_MAX_PORTS);
|
|
|
+ DECLARE_BITMAP(untagged, DSA_MAX_PORTS);
|
|
|
+ u16 vid;
|
|
|
+ int member, err;
|
|
|
+
|
|
|
+ if (!ds->drv->vlan_getnext || !vid_begin)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ vid = vid_begin - 1;
|
|
|
+
|
|
|
+ do {
|
|
|
+ err = ds->drv->vlan_getnext(ds, &vid, members, untagged);
|
|
|
+ if (err)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (vid > vid_end)
|
|
|
+ break;
|
|
|
+
|
|
|
+ member = find_first_bit(members, DSA_MAX_PORTS);
|
|
|
+ if (member == DSA_MAX_PORTS)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ dev = ds->ports[member];
|
|
|
+ p = netdev_priv(dev);
|
|
|
+ vlan_br = p->bridge_dev;
|
|
|
+ if (vlan_br == bridge)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ netdev_dbg(vlan_br, "hardware VLAN %d already in use\n", vid);
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+ } while (vid < vid_end);
|
|
|
+
|
|
|
+ return err == -ENOENT ? 0 : err;
|
|
|
+}
|
|
|
+
|
|
|
+static int dsa_slave_port_vlan_add(struct net_device *dev,
|
|
|
+ struct switchdev_obj *obj)
|
|
|
+{
|
|
|
+ struct switchdev_obj_vlan *vlan = &obj->u.vlan;
|
|
|
+ struct dsa_slave_priv *p = netdev_priv(dev);
|
|
|
+ struct dsa_switch *ds = p->parent;
|
|
|
+ u16 vid;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ switch (obj->trans) {
|
|
|
+ case SWITCHDEV_TRANS_PREPARE:
|
|
|
+ if (!ds->drv->port_vlan_add || !ds->drv->port_pvid_set)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ /* If the requested port doesn't belong to the same bridge as
|
|
|
+ * the VLAN members, fallback to software VLAN (hopefully).
|
|
|
+ */
|
|
|
+ err = dsa_bridge_check_vlan_range(ds, p->bridge_dev,
|
|
|
+ vlan->vid_begin,
|
|
|
+ vlan->vid_end);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ break;
|
|
|
+ case SWITCHDEV_TRANS_COMMIT:
|
|
|
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
|
|
|
+ err = ds->drv->port_vlan_add(ds, p->port, vid,
|
|
|
+ vlan->flags &
|
|
|
+ BRIDGE_VLAN_INFO_UNTAGGED);
|
|
|
+ if (!err && vlan->flags & BRIDGE_VLAN_INFO_PVID)
|
|
|
+ err = ds->drv->port_pvid_set(ds, p->port, vid);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int dsa_slave_port_vlan_del(struct net_device *dev,
|
|
|
+ struct switchdev_obj *obj)
|
|
|
+{
|
|
|
+ struct switchdev_obj_vlan *vlan = &obj->u.vlan;
|
|
|
+ struct dsa_slave_priv *p = netdev_priv(dev);
|
|
|
+ struct dsa_switch *ds = p->parent;
|
|
|
+ u16 vid;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ if (!ds->drv->port_vlan_del)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
|
|
|
+ err = ds->drv->port_vlan_del(ds, p->port, vid);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int dsa_slave_port_vlan_dump(struct net_device *dev,
|
|
|
+ struct switchdev_obj *obj)
|
|
|
+{
|
|
|
+ struct switchdev_obj_vlan *vlan = &obj->u.vlan;
|
|
|
+ struct dsa_slave_priv *p = netdev_priv(dev);
|
|
|
+ struct dsa_switch *ds = p->parent;
|
|
|
+ DECLARE_BITMAP(members, DSA_MAX_PORTS);
|
|
|
+ DECLARE_BITMAP(untagged, DSA_MAX_PORTS);
|
|
|
+ u16 pvid, vid = 0;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ if (!ds->drv->vlan_getnext || !ds->drv->port_pvid_get)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ err = ds->drv->port_pvid_get(ds, p->port, &pvid);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ for (;;) {
|
|
|
+ err = ds->drv->vlan_getnext(ds, &vid, members, untagged);
|
|
|
+ if (err)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (!test_bit(p->port, members))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ memset(vlan, 0, sizeof(*vlan));
|
|
|
+ vlan->vid_begin = vlan->vid_end = vid;
|
|
|
+
|
|
|
+ if (vid == pvid)
|
|
|
+ vlan->flags |= BRIDGE_VLAN_INFO_PVID;
|
|
|
+
|
|
|
+ if (test_bit(p->port, untagged))
|
|
|
+ vlan->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
|
|
|
+
|
|
|
+ err = obj->cb(dev, obj);
|
|
|
+ if (err)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return err == -ENOENT ? 0 : err;
|
|
|
+}
|
|
|
+
|
|
|
static int dsa_slave_port_fdb_add(struct net_device *dev,
|
|
|
struct switchdev_obj *obj)
|
|
|
{
|
|
|
@@ -341,6 +487,9 @@ static int dsa_slave_port_obj_add(struct net_device *dev,
|
|
|
case SWITCHDEV_OBJ_PORT_FDB:
|
|
|
err = dsa_slave_port_fdb_add(dev, obj);
|
|
|
break;
|
|
|
+ case SWITCHDEV_OBJ_PORT_VLAN:
|
|
|
+ err = dsa_slave_port_vlan_add(dev, obj);
|
|
|
+ break;
|
|
|
default:
|
|
|
err = -EOPNOTSUPP;
|
|
|
break;
|
|
|
@@ -358,6 +507,9 @@ static int dsa_slave_port_obj_del(struct net_device *dev,
|
|
|
case SWITCHDEV_OBJ_PORT_FDB:
|
|
|
err = dsa_slave_port_fdb_del(dev, obj);
|
|
|
break;
|
|
|
+ case SWITCHDEV_OBJ_PORT_VLAN:
|
|
|
+ err = dsa_slave_port_vlan_del(dev, obj);
|
|
|
+ break;
|
|
|
default:
|
|
|
err = -EOPNOTSUPP;
|
|
|
break;
|
|
|
@@ -375,6 +527,9 @@ static int dsa_slave_port_obj_dump(struct net_device *dev,
|
|
|
case SWITCHDEV_OBJ_PORT_FDB:
|
|
|
err = dsa_slave_port_fdb_dump(dev, obj);
|
|
|
break;
|
|
|
+ case SWITCHDEV_OBJ_PORT_VLAN:
|
|
|
+ err = dsa_slave_port_vlan_dump(dev, obj);
|
|
|
+ break;
|
|
|
default:
|
|
|
err = -EOPNOTSUPP;
|
|
|
break;
|
|
|
@@ -794,6 +949,9 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
|
|
|
.ndo_netpoll_cleanup = dsa_slave_netpoll_cleanup,
|
|
|
.ndo_poll_controller = dsa_slave_poll_controller,
|
|
|
#endif
|
|
|
+ .ndo_bridge_getlink = switchdev_port_bridge_getlink,
|
|
|
+ .ndo_bridge_setlink = switchdev_port_bridge_setlink,
|
|
|
+ .ndo_bridge_dellink = switchdev_port_bridge_dellink,
|
|
|
};
|
|
|
|
|
|
static const struct switchdev_ops dsa_slave_switchdev_ops = {
|