summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/linux/if_vlan.h1
-rw-r--r--net/8021q/vlan.c76
-rw-r--r--net/8021q/vlan.h2
-rw-r--r--net/8021q/vlan_netlink.c7
4 files changed, 23 insertions, 63 deletions
diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h
index 07db4169463e..129fa876dbe4 100644
--- a/include/linux/if_vlan.h
+++ b/include/linux/if_vlan.h
@@ -82,6 +82,7 @@ extern void vlan_ioctl_set(int (*hook)(struct net *, void __user *));
struct vlan_group {
int real_dev_ifindex; /* The ifindex of the ethernet(like) device the vlan is attached to. */
+ unsigned int nr_vlans;
struct hlist_node hlist; /* linked list */
struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS];
struct rcu_head rcu;
diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c
index ad34e4a0326a..ac7963854103 100644
--- a/net/8021q/vlan.c
+++ b/net/8021q/vlan.c
@@ -132,33 +132,17 @@ static void vlan_rcu_free(struct rcu_head *rcu)
vlan_group_free(container_of(rcu, struct vlan_group, rcu));
}
-
-/* This returns 0 if everything went fine.
- * It will return 1 if the group was killed as a result.
- * A negative return indicates failure.
- *
- * The RTNL lock must be held.
- */
-static int unregister_vlan_dev(struct net_device *real_dev,
- unsigned short vlan_id)
+void unregister_vlan_dev(struct net_device *dev)
{
- struct net_device *dev;
- int real_dev_ifindex = real_dev->ifindex;
+ struct vlan_dev_info *vlan = VLAN_DEV_INFO(dev);
+ struct net_device *real_dev = vlan->real_dev;
struct vlan_group *grp;
- unsigned int i;
- int ret;
-
- if (vlan_id >= VLAN_VID_MASK)
- return -EINVAL;
+ unsigned short vlan_id = vlan->vlan_id;
ASSERT_RTNL();
- grp = __vlan_find_group(real_dev_ifindex);
- if (!grp)
- return -ENOENT;
- dev = vlan_group_get_device(grp, vlan_id);
- if (!dev)
- return -ENOENT;
+ grp = __vlan_find_group(real_dev->ifindex);
+ BUG_ON(!grp);
vlan_proc_rem_dev(dev);
@@ -169,20 +153,12 @@ static int unregister_vlan_dev(struct net_device *real_dev,
real_dev->vlan_rx_kill_vid(real_dev, vlan_id);
vlan_group_set_device(grp, vlan_id, NULL);
- synchronize_net();
+ grp->nr_vlans--;
- /* Caller unregisters (and if necessary, puts) VLAN device, but we
- * get rid of the reference to real_dev here.
- */
- dev_put(real_dev);
+ synchronize_net();
/* If the group is now empty, kill off the group. */
- ret = 0;
- for (i = 0; i < VLAN_VID_MASK; i++)
- if (vlan_group_get_device(grp, i))
- break;
-
- if (i == VLAN_VID_MASK) {
+ if (grp->nr_vlans == 0) {
if (real_dev->features & NETIF_F_HW_VLAN_RX)
real_dev->vlan_rx_register(real_dev, NULL);
@@ -190,23 +166,12 @@ static int unregister_vlan_dev(struct net_device *real_dev,
/* Free the group, after all cpu's are done. */
call_rcu(&grp->rcu, vlan_rcu_free);
- ret = 1;
}
- return ret;
-}
-
-int unregister_vlan_device(struct net_device *dev)
-{
- int ret;
+ /* Get rid of the vlan's reference to real_dev */
+ dev_put(real_dev);
- ret = unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev,
- VLAN_DEV_INFO(dev)->vlan_id);
unregister_netdevice(dev);
-
- if (ret == 1)
- ret = 0;
- return ret;
}
static void vlan_transfer_operstate(const struct net_device *dev, struct net_device *vlandev)
@@ -291,6 +256,8 @@ int register_vlan_dev(struct net_device *dev)
* it into our local structure.
*/
vlan_group_set_device(grp, vlan_id, dev);
+ grp->nr_vlans++;
+
if (ngrp && real_dev->features & NETIF_F_HW_VLAN_RX)
real_dev->vlan_rx_register(real_dev, ngrp);
if (real_dev->features & NETIF_F_HW_VLAN_FILTER)
@@ -479,20 +446,16 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,
case NETDEV_UNREGISTER:
/* Delete all VLANs for this dev. */
for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) {
- int ret;
-
vlandev = vlan_group_get_device(grp, i);
if (!vlandev)
continue;
- ret = unregister_vlan_dev(dev,
- VLAN_DEV_INFO(vlandev)->vlan_id);
-
- unregister_netdevice(vlandev);
+ /* unregistration of last vlan destroys group, abort
+ * afterwards */
+ if (grp->nr_vlans == 1)
+ i = VLAN_GROUP_ARRAY_LEN;
- /* Group was destroyed? */
- if (ret == 1)
- break;
+ unregister_vlan_dev(vlandev);
}
break;
}
@@ -598,7 +561,8 @@ static int vlan_ioctl_handler(struct net *net, void __user *arg)
err = -EPERM;
if (!capable(CAP_NET_ADMIN))
break;
- err = unregister_vlan_device(dev);
+ unregister_vlan_dev(dev);
+ err = 0;
break;
case GET_VLAN_REALDEV_NAME_CMD:
diff --git a/net/8021q/vlan.h b/net/8021q/vlan.h
index 56378651cc46..0cfdf77b497c 100644
--- a/net/8021q/vlan.h
+++ b/net/8021q/vlan.h
@@ -38,7 +38,7 @@ void vlan_dev_get_vid(const struct net_device *dev, unsigned short *result);
int vlan_check_real_dev(struct net_device *real_dev, unsigned short vlan_id);
void vlan_setup(struct net_device *dev);
int register_vlan_dev(struct net_device *dev);
-int unregister_vlan_device(struct net_device *dev);
+void unregister_vlan_dev(struct net_device *dev);
int vlan_netlink_init(void);
void vlan_netlink_fini(void);
diff --git a/net/8021q/vlan_netlink.c b/net/8021q/vlan_netlink.c
index 0996185e2ed5..9ee63583ed27 100644
--- a/net/8021q/vlan_netlink.c
+++ b/net/8021q/vlan_netlink.c
@@ -137,11 +137,6 @@ static int vlan_newlink(struct net_device *dev,
return register_vlan_dev(dev);
}
-static void vlan_dellink(struct net_device *dev)
-{
- unregister_vlan_device(dev);
-}
-
static inline size_t vlan_qos_map_size(unsigned int n)
{
if (n == 0)
@@ -226,7 +221,7 @@ struct rtnl_link_ops vlan_link_ops __read_mostly = {
.validate = vlan_validate,
.newlink = vlan_newlink,
.changelink = vlan_changelink,
- .dellink = vlan_dellink,
+ .dellink = unregister_vlan_dev,
.get_size = vlan_get_size,
.fill_info = vlan_fill_info,
};