diff options
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/macvtap.c | 87 |
1 files changed, 71 insertions, 16 deletions
diff --git a/drivers/net/macvtap.c b/drivers/net/macvtap.c index 25689e9df3b7..1b7082d08f33 100644 --- a/drivers/net/macvtap.c +++ b/drivers/net/macvtap.c @@ -51,15 +51,13 @@ static struct proto macvtap_proto = { }; /* - * Minor number matches netdev->ifindex, so need a potentially - * large value. This also makes it possible to split the - * tap functionality out again in the future by offering it - * from other drivers besides macvtap. As long as every device - * only has one tap, the interface numbers assure that the - * device nodes are unique. + * Variables for dealing with macvtaps device numbers. */ static dev_t macvtap_major; -#define MACVTAP_NUM_DEVS 65536 +#define MACVTAP_NUM_DEVS (1U << MINORBITS) +static DEFINE_MUTEX(minor_lock); +static DEFINE_IDR(minor_idr); + #define GOODCOPY_LEN 128 static struct class *macvtap_class; static struct cdev macvtap_cdev; @@ -275,6 +273,58 @@ static int macvtap_receive(struct sk_buff *skb) return macvtap_forward(skb->dev, skb); } +static int macvtap_get_minor(struct macvlan_dev *vlan) +{ + int retval = -ENOMEM; + int id; + + mutex_lock(&minor_lock); + if (idr_pre_get(&minor_idr, GFP_KERNEL) == 0) + goto exit; + + retval = idr_get_new_above(&minor_idr, vlan, 1, &id); + if (retval < 0) { + if (retval == -EAGAIN) + retval = -ENOMEM; + goto exit; + } + if (id < MACVTAP_NUM_DEVS) { + vlan->minor = id; + } else { + printk(KERN_ERR "too many macvtap devices\n"); + retval = -EINVAL; + idr_remove(&minor_idr, id); + } +exit: + mutex_unlock(&minor_lock); + return retval; +} + +static void macvtap_free_minor(struct macvlan_dev *vlan) +{ + mutex_lock(&minor_lock); + if (vlan->minor) { + idr_remove(&minor_idr, vlan->minor); + vlan->minor = 0; + } + mutex_unlock(&minor_lock); +} + +static struct net_device *dev_get_by_macvtap_minor(int minor) +{ + struct net_device *dev = NULL; + struct macvlan_dev *vlan; + + mutex_lock(&minor_lock); + vlan = idr_find(&minor_idr, minor); + if (vlan) { + dev = vlan->dev; + dev_hold(dev); + } + mutex_unlock(&minor_lock); + return dev; +} + static int macvtap_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[], @@ -329,7 +379,7 @@ static void macvtap_sock_destruct(struct sock *sk) static int macvtap_open(struct inode *inode, struct file *file) { struct net *net = current->nsproxy->net_ns; - struct net_device *dev = dev_get_by_index(net, iminor(inode)); + struct net_device *dev = dev_get_by_macvtap_minor(iminor(inode)); struct macvtap_queue *q; int err; @@ -337,11 +387,6 @@ static int macvtap_open(struct inode *inode, struct file *file) if (!dev) goto out; - /* check if this is a macvtap device */ - err = -EINVAL; - if (dev->rtnl_link_ops != &macvtap_link_ops) - goto out; - err = -ENOMEM; q = (struct macvtap_queue *)sk_alloc(net, AF_UNSPEC, GFP_KERNEL, &macvtap_proto); @@ -961,12 +1006,15 @@ static int macvtap_device_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct net_device *dev = ptr; + struct macvlan_dev *vlan; struct device *classdev; dev_t devt; + int err; if (dev->rtnl_link_ops != &macvtap_link_ops) return NOTIFY_DONE; + vlan = netdev_priv(dev); switch (event) { case NETDEV_REGISTER: @@ -974,15 +1022,22 @@ static int macvtap_device_event(struct notifier_block *unused, * been registered but before register_netdevice has * finished running. */ - devt = MKDEV(MAJOR(macvtap_major), dev->ifindex); + err = macvtap_get_minor(vlan); + if (err) + return notifier_from_errno(err); + + devt = MKDEV(MAJOR(macvtap_major), vlan->minor); classdev = device_create(macvtap_class, &dev->dev, devt, dev, "tap%d", dev->ifindex); - if (IS_ERR(classdev)) + if (IS_ERR(classdev)) { + macvtap_free_minor(vlan); return notifier_from_errno(PTR_ERR(classdev)); + } break; case NETDEV_UNREGISTER: - devt = MKDEV(MAJOR(macvtap_major), dev->ifindex); + devt = MKDEV(MAJOR(macvtap_major), vlan->minor); device_destroy(macvtap_class, devt); + macvtap_free_minor(vlan); break; } |