Skip to content

Commit 27c5f74

Browse files
vladimirolteandavem330
authored andcommitted
net: bridge: vlan: notify switchdev only when something changed
Currently, when a VLAN entry is added multiple times in a row to a bridge port, nbp_vlan_add() calls br_switchdev_port_vlan_add() each time, even if the VLAN already exists and nothing about it has changed: bridge vlan add dev lan12 vid 100 master static Similarly, when a VLAN is added multiple times in a row to a bridge, br_vlan_add_existing() doesn't filter at all the calls to br_switchdev_port_vlan_add(): bridge vlan add dev br0 vid 100 self This behavior makes driver-level accounting of VLANs impossible, since it is enough for a single deletion event to remove a VLAN, but the addition event can be emitted an unlimited number of times. The cause for this can be identified as follows: we rely on __vlan_add_flags() to retroactively tell us whether it has changed anything about the VLAN flags or VLAN group pvid. So we'd first have to call __vlan_add_flags() before calling br_switchdev_port_vlan_add(), in order to have access to the "bool *changed" information. But we don't want to change the event ordering, because we'd have to revert the struct net_bridge_vlan changes we've made if switchdev returns an error. So to solve this, we need another function that tells us whether any change is going to occur in the VLAN or VLAN group, _prior_ to calling __vlan_add_flags(). Split __vlan_add_flags() into a precommit and a commit stage, and rename it to __vlan_flags_update(). The precommit stage, __vlan_flags_would_change(), will determine whether there is any reason to notify switchdev due to a change of flags (note: the BRENTRY flag transition from false to true is treated separately: as a new switchdev entry, because we skipped notifying the master VLAN when it wasn't a brentry yet, and therefore not as a change of flags). With this lookahead/precommit function in place, we can avoid notifying switchdev if nothing changed for the VLAN and VLAN group. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent cab2cd7 commit 27c5f74

File tree

1 file changed

+65
-30
lines changed

1 file changed

+65
-30
lines changed

net/bridge/br_vlan.c

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,55 +34,70 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
3434
return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
3535
}
3636

37-
static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg,
37+
static void __vlan_add_pvid(struct net_bridge_vlan_group *vg,
3838
const struct net_bridge_vlan *v)
3939
{
4040
if (vg->pvid == v->vid)
41-
return false;
41+
return;
4242

4343
smp_wmb();
4444
br_vlan_set_pvid_state(vg, v->state);
4545
vg->pvid = v->vid;
46-
47-
return true;
4846
}
4947

50-
static bool __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
48+
static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
5149
{
5250
if (vg->pvid != vid)
53-
return false;
51+
return;
5452

5553
smp_wmb();
5654
vg->pvid = 0;
57-
58-
return true;
5955
}
6056

61-
/* Returns true if the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED bits
62-
* of @flags produced any change onto @v, false otherwise
57+
/* Update the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED flags of @v.
58+
* If @commit is false, return just whether the BRIDGE_VLAN_INFO_PVID and
59+
* BRIDGE_VLAN_INFO_UNTAGGED bits of @flags would produce any change onto @v.
6360
*/
64-
static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
61+
static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
62+
bool commit)
6563
{
6664
struct net_bridge_vlan_group *vg;
67-
u16 old_flags = v->flags;
68-
bool ret;
65+
bool change;
6966

7067
if (br_vlan_is_master(v))
7168
vg = br_vlan_group(v->br);
7269
else
7370
vg = nbp_vlan_group(v->port);
7471

72+
/* check if anything would be changed on commit */
73+
change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(vg->pvid != v->vid) ||
74+
((flags ^ v->flags) & BRIDGE_VLAN_INFO_UNTAGGED);
75+
76+
if (!commit)
77+
goto out;
78+
7579
if (flags & BRIDGE_VLAN_INFO_PVID)
76-
ret = __vlan_add_pvid(vg, v);
80+
__vlan_add_pvid(vg, v);
7781
else
78-
ret = __vlan_delete_pvid(vg, v->vid);
82+
__vlan_delete_pvid(vg, v->vid);
7983

8084
if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
8185
v->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
8286
else
8387
v->flags &= ~BRIDGE_VLAN_INFO_UNTAGGED;
8488

85-
return ret || !!((old_flags ^ v->flags) & BRIDGE_VLAN_INFO_UNTAGGED);
89+
out:
90+
return change;
91+
}
92+
93+
static bool __vlan_flags_would_change(struct net_bridge_vlan *v, u16 flags)
94+
{
95+
return __vlan_flags_update(v, flags, false);
96+
}
97+
98+
static void __vlan_flags_commit(struct net_bridge_vlan *v, u16 flags)
99+
{
100+
__vlan_flags_update(v, flags, true);
86101
}
87102

88103
static int __vlan_vid_add(struct net_device *dev, struct net_bridge *br,
@@ -315,7 +330,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
315330
goto out_fdb_insert;
316331

317332
__vlan_add_list(v);
318-
__vlan_add_flags(v, flags);
333+
__vlan_flags_commit(v, flags);
319334
br_multicast_toggle_one_vlan(v, true);
320335

321336
if (p)
@@ -682,17 +697,29 @@ static int br_vlan_add_existing(struct net_bridge *br,
682697
u16 flags, bool *changed,
683698
struct netlink_ext_ack *extack)
684699
{
700+
bool would_change = __vlan_flags_would_change(vlan, flags);
701+
bool becomes_brentry = false;
685702
int err;
686703

687-
/* Trying to change flags of non-existent bridge vlan */
688-
if (!br_vlan_is_brentry(vlan) && !(flags & BRIDGE_VLAN_INFO_BRENTRY))
689-
return -EINVAL;
704+
if (!br_vlan_is_brentry(vlan)) {
705+
/* Trying to change flags of non-existent bridge vlan */
706+
if (!(flags & BRIDGE_VLAN_INFO_BRENTRY))
707+
return -EINVAL;
690708

691-
err = br_switchdev_port_vlan_add(br->dev, vlan->vid, flags, extack);
692-
if (err && err != -EOPNOTSUPP)
693-
return err;
709+
becomes_brentry = true;
710+
}
694711

695-
if (!br_vlan_is_brentry(vlan)) {
712+
/* Master VLANs that aren't brentries weren't notified before,
713+
* time to notify them now.
714+
*/
715+
if (becomes_brentry || would_change) {
716+
err = br_switchdev_port_vlan_add(br->dev, vlan->vid, flags,
717+
extack);
718+
if (err && err != -EOPNOTSUPP)
719+
return err;
720+
}
721+
722+
if (becomes_brentry) {
696723
/* It was only kept for port vlans, now make it real */
697724
err = br_fdb_add_local(br, NULL, br->dev->dev_addr, vlan->vid);
698725
if (err) {
@@ -707,7 +734,8 @@ static int br_vlan_add_existing(struct net_bridge *br,
707734
br_multicast_toggle_one_vlan(vlan, true);
708735
}
709736

710-
if (__vlan_add_flags(vlan, flags))
737+
__vlan_flags_commit(vlan, flags);
738+
if (would_change)
711739
*changed = true;
712740

713741
return 0;
@@ -1257,11 +1285,18 @@ int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags,
12571285
*changed = false;
12581286
vlan = br_vlan_find(nbp_vlan_group(port), vid);
12591287
if (vlan) {
1260-
/* Pass the flags to the hardware bridge */
1261-
ret = br_switchdev_port_vlan_add(port->dev, vid, flags, extack);
1262-
if (ret && ret != -EOPNOTSUPP)
1263-
return ret;
1264-
*changed = __vlan_add_flags(vlan, flags);
1288+
bool would_change = __vlan_flags_would_change(vlan, flags);
1289+
1290+
if (would_change) {
1291+
/* Pass the flags to the hardware bridge */
1292+
ret = br_switchdev_port_vlan_add(port->dev, vid,
1293+
flags, extack);
1294+
if (ret && ret != -EOPNOTSUPP)
1295+
return ret;
1296+
}
1297+
1298+
__vlan_flags_commit(vlan, flags);
1299+
*changed = would_change;
12651300

12661301
return 0;
12671302
}

0 commit comments

Comments
 (0)