diff options
Diffstat (limited to 'drivers/net/macvtap.c')
| -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 25689e9df3b..1b7082d08f3 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;  	}  |