Skip to content
Snippets Groups Projects
dev.c 83.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    			      "compressed multicast|bytes    packets errs "
    			      "drop fifo colls carrier compressed\n");
    	else
    		dev_seq_printf_stats(seq, v);
    	return 0;
    }
    
    static struct netif_rx_stats *softnet_get_online(loff_t *pos)
    {
    	struct netif_rx_stats *rc = NULL;
    
    	while (*pos < NR_CPUS)
    	       	if (cpu_online(*pos)) {
    			rc = &per_cpu(netdev_rx_stat, *pos);
    			break;
    		} else
    			++*pos;
    	return rc;
    }
    
    static void *softnet_seq_start(struct seq_file *seq, loff_t *pos)
    {
    	return softnet_get_online(pos);
    }
    
    static void *softnet_seq_next(struct seq_file *seq, void *v, loff_t *pos)
    {
    	++*pos;
    	return softnet_get_online(pos);
    }
    
    static void softnet_seq_stop(struct seq_file *seq, void *v)
    {
    }
    
    static int softnet_seq_show(struct seq_file *seq, void *v)
    {
    	struct netif_rx_stats *s = v;
    
    	seq_printf(seq, "%08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
    
    		   s->total, s->dropped, s->time_squeeze, 0,
    
    		   0, 0, 0, 0, /* was fastroute */
    		   s->cpu_collision );
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    static struct seq_operations dev_seq_ops = {
    	.start = dev_seq_start,
    	.next  = dev_seq_next,
    	.stop  = dev_seq_stop,
    	.show  = dev_seq_show,
    };
    
    static int dev_seq_open(struct inode *inode, struct file *file)
    {
    	return seq_open(file, &dev_seq_ops);
    }
    
    static struct file_operations dev_seq_fops = {
    	.owner	 = THIS_MODULE,
    	.open    = dev_seq_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    	.release = seq_release,
    };
    
    static struct seq_operations softnet_seq_ops = {
    	.start = softnet_seq_start,
    	.next  = softnet_seq_next,
    	.stop  = softnet_seq_stop,
    	.show  = softnet_seq_show,
    };
    
    static int softnet_seq_open(struct inode *inode, struct file *file)
    {
    	return seq_open(file, &softnet_seq_ops);
    }
    
    static struct file_operations softnet_seq_fops = {
    	.owner	 = THIS_MODULE,
    	.open    = softnet_seq_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    	.release = seq_release,
    };
    
    
    #ifdef CONFIG_WIRELESS_EXT
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    extern int wireless_proc_init(void);
    #else
    #define wireless_proc_init() 0
    #endif
    
    static int __init dev_proc_init(void)
    {
    	int rc = -ENOMEM;
    
    	if (!proc_net_fops_create("dev", S_IRUGO, &dev_seq_fops))
    		goto out;
    	if (!proc_net_fops_create("softnet_stat", S_IRUGO, &softnet_seq_fops))
    		goto out_dev;
    	if (wireless_proc_init())
    		goto out_softnet;
    	rc = 0;
    out:
    	return rc;
    out_softnet:
    	proc_net_remove("softnet_stat");
    out_dev:
    	proc_net_remove("dev");
    	goto out;
    }
    #else
    #define dev_proc_init() 0
    #endif	/* CONFIG_PROC_FS */
    
    
    /**
     *	netdev_set_master	-	set up master/slave pair
     *	@slave: slave device
     *	@master: new master device
     *
     *	Changes the master device of the slave. Pass %NULL to break the
     *	bonding. The caller must hold the RTNL semaphore. On a failure
     *	a negative errno code is returned. On success the reference counts
     *	are adjusted, %RTM_NEWLINK is sent to the routing socket and the
     *	function returns zero.
     */
    int netdev_set_master(struct net_device *slave, struct net_device *master)
    {
    	struct net_device *old = slave->master;
    
    	ASSERT_RTNL();
    
    	if (master) {
    		if (old)
    			return -EBUSY;
    		dev_hold(master);
    	}
    
    	slave->master = master;
    	
    	synchronize_net();
    
    	if (old)
    		dev_put(old);
    
    	if (master)
    		slave->flags |= IFF_SLAVE;
    	else
    		slave->flags &= ~IFF_SLAVE;
    
    	rtmsg_ifinfo(RTM_NEWLINK, slave, IFF_SLAVE);
    	return 0;
    }
    
    /**
     *	dev_set_promiscuity	- update promiscuity count on a device
     *	@dev: device
     *	@inc: modifier
     *
    
     *	Add or remove promiscuity from a device. While the count in the device
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *	remains above zero the interface remains promiscuous. Once it hits zero
     *	the device reverts back to normal filtering operation. A negative inc
     *	value is used to drop promiscuity on the device.
     */
    void dev_set_promiscuity(struct net_device *dev, int inc)
    {
    	unsigned short old_flags = dev->flags;
    
    	if ((dev->promiscuity += inc) == 0)
    		dev->flags &= ~IFF_PROMISC;
    
    	else
    		dev->flags |= IFF_PROMISC;
    	if (dev->flags != old_flags) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		dev_mc_upload(dev);
    		printk(KERN_INFO "device %s %s promiscuous mode\n",
    		       dev->name, (dev->flags & IFF_PROMISC) ? "entered" :
    		       					       "left");
    
    Steve Grubb's avatar
    Steve Grubb committed
    		audit_log(current->audit_context, GFP_ATOMIC,
    			AUDIT_ANOM_PROMISCUOUS,
    			"dev=%s prom=%d old_prom=%d auid=%u",
    			dev->name, (dev->flags & IFF_PROMISC),
    			(old_flags & IFF_PROMISC),
    			audit_get_loginuid(current->audit_context)); 
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    }
    
    /**
     *	dev_set_allmulti	- update allmulti count on a device
     *	@dev: device
     *	@inc: modifier
     *
     *	Add or remove reception of all multicast frames to a device. While the
     *	count in the device remains above zero the interface remains listening
     *	to all interfaces. Once it hits zero the device reverts back to normal
     *	filtering operation. A negative @inc value is used to drop the counter
     *	when releasing a resource needing all multicasts.
     */
    
    void dev_set_allmulti(struct net_device *dev, int inc)
    {
    	unsigned short old_flags = dev->flags;
    
    	dev->flags |= IFF_ALLMULTI;
    	if ((dev->allmulti += inc) == 0)
    		dev->flags &= ~IFF_ALLMULTI;
    	if (dev->flags ^ old_flags)
    		dev_mc_upload(dev);
    }
    
    unsigned dev_get_flags(const struct net_device *dev)
    {
    	unsigned flags;
    
    	flags = (dev->flags & ~(IFF_PROMISC |
    				IFF_ALLMULTI |
    
    				IFF_RUNNING |
    				IFF_LOWER_UP |
    				IFF_DORMANT)) |
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		(dev->gflags & (IFF_PROMISC |
    				IFF_ALLMULTI));
    
    
    	if (netif_running(dev)) {
    		if (netif_oper_up(dev))
    			flags |= IFF_RUNNING;
    		if (netif_carrier_ok(dev))
    			flags |= IFF_LOWER_UP;
    		if (netif_dormant(dev))
    			flags |= IFF_DORMANT;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return flags;
    }
    
    int dev_change_flags(struct net_device *dev, unsigned flags)
    {
    	int ret;
    	int old_flags = dev->flags;
    
    	/*
    	 *	Set the flags on our device.
    	 */
    
    	dev->flags = (flags & (IFF_DEBUG | IFF_NOTRAILERS | IFF_NOARP |
    			       IFF_DYNAMIC | IFF_MULTICAST | IFF_PORTSEL |
    			       IFF_AUTOMEDIA)) |
    		     (dev->flags & (IFF_UP | IFF_VOLATILE | IFF_PROMISC |
    				    IFF_ALLMULTI));
    
    	/*
    	 *	Load in the correct multicast list now the flags have changed.
    	 */
    
    	dev_mc_upload(dev);
    
    	/*
    	 *	Have we downed the interface. We handle IFF_UP ourselves
    	 *	according to user attempts to set it, rather than blindly
    	 *	setting it.
    	 */
    
    	ret = 0;
    	if ((old_flags ^ flags) & IFF_UP) {	/* Bit is different  ? */
    		ret = ((old_flags & IFF_UP) ? dev_close : dev_open)(dev);
    
    		if (!ret)
    			dev_mc_upload(dev);
    	}
    
    	if (dev->flags & IFF_UP &&
    	    ((old_flags ^ dev->flags) &~ (IFF_UP | IFF_PROMISC | IFF_ALLMULTI |
    					  IFF_VOLATILE)))
    
    		raw_notifier_call_chain(&netdev_chain,
    
    				NETDEV_CHANGE, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if ((flags ^ dev->gflags) & IFF_PROMISC) {
    		int inc = (flags & IFF_PROMISC) ? +1 : -1;
    		dev->gflags ^= IFF_PROMISC;
    		dev_set_promiscuity(dev, inc);
    	}
    
    	/* NOTE: order of synchronization of IFF_PROMISC and IFF_ALLMULTI
    	   is important. Some (broken) drivers set IFF_PROMISC, when
    	   IFF_ALLMULTI is requested not asking us and not reporting.
    	 */
    	if ((flags ^ dev->gflags) & IFF_ALLMULTI) {
    		int inc = (flags & IFF_ALLMULTI) ? +1 : -1;
    		dev->gflags ^= IFF_ALLMULTI;
    		dev_set_allmulti(dev, inc);
    	}
    
    	if (old_flags ^ dev->flags)
    		rtmsg_ifinfo(RTM_NEWLINK, dev, old_flags ^ dev->flags);
    
    	return ret;
    }
    
    int dev_set_mtu(struct net_device *dev, int new_mtu)
    {
    	int err;
    
    	if (new_mtu == dev->mtu)
    		return 0;
    
    	/*	MTU must be positive.	 */
    	if (new_mtu < 0)
    		return -EINVAL;
    
    	if (!netif_device_present(dev))
    		return -ENODEV;
    
    	err = 0;
    	if (dev->change_mtu)
    		err = dev->change_mtu(dev, new_mtu);
    	else
    		dev->mtu = new_mtu;
    	if (!err && dev->flags & IFF_UP)
    
    		raw_notifier_call_chain(&netdev_chain,
    
    				NETDEV_CHANGEMTU, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    int dev_set_mac_address(struct net_device *dev, struct sockaddr *sa)
    {
    	int err;
    
    	if (!dev->set_mac_address)
    		return -EOPNOTSUPP;
    	if (sa->sa_family != dev->type)
    		return -EINVAL;
    	if (!netif_device_present(dev))
    		return -ENODEV;
    	err = dev->set_mac_address(dev, sa);
    	if (!err)
    
    		raw_notifier_call_chain(&netdev_chain,
    
    				NETDEV_CHANGEADDR, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    /*
     *	Perform the SIOCxIFxxx calls.
     */
    static int dev_ifsioc(struct ifreq *ifr, unsigned int cmd)
    {
    	int err;
    	struct net_device *dev = __dev_get_by_name(ifr->ifr_name);
    
    	if (!dev)
    		return -ENODEV;
    
    	switch (cmd) {
    		case SIOCGIFFLAGS:	/* Get interface flags */
    			ifr->ifr_flags = dev_get_flags(dev);
    			return 0;
    
    		case SIOCSIFFLAGS:	/* Set interface flags */
    			return dev_change_flags(dev, ifr->ifr_flags);
    
    		case SIOCGIFMETRIC:	/* Get the metric on the interface
    					   (currently unused) */
    			ifr->ifr_metric = 0;
    			return 0;
    
    		case SIOCSIFMETRIC:	/* Set the metric on the interface
    					   (currently unused) */
    			return -EOPNOTSUPP;
    
    		case SIOCGIFMTU:	/* Get the MTU of a device */
    			ifr->ifr_mtu = dev->mtu;
    			return 0;
    
    		case SIOCSIFMTU:	/* Set the MTU of a device */
    			return dev_set_mtu(dev, ifr->ifr_mtu);
    
    		case SIOCGIFHWADDR:
    			if (!dev->addr_len)
    				memset(ifr->ifr_hwaddr.sa_data, 0, sizeof ifr->ifr_hwaddr.sa_data);
    			else
    				memcpy(ifr->ifr_hwaddr.sa_data, dev->dev_addr,
    				       min(sizeof ifr->ifr_hwaddr.sa_data, (size_t) dev->addr_len));
    			ifr->ifr_hwaddr.sa_family = dev->type;
    			return 0;
    
    		case SIOCSIFHWADDR:
    			return dev_set_mac_address(dev, &ifr->ifr_hwaddr);
    
    		case SIOCSIFHWBROADCAST:
    			if (ifr->ifr_hwaddr.sa_family != dev->type)
    				return -EINVAL;
    			memcpy(dev->broadcast, ifr->ifr_hwaddr.sa_data,
    			       min(sizeof ifr->ifr_hwaddr.sa_data, (size_t) dev->addr_len));
    
    			raw_notifier_call_chain(&netdev_chain,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					    NETDEV_CHANGEADDR, dev);
    			return 0;
    
    		case SIOCGIFMAP:
    			ifr->ifr_map.mem_start = dev->mem_start;
    			ifr->ifr_map.mem_end   = dev->mem_end;
    			ifr->ifr_map.base_addr = dev->base_addr;
    			ifr->ifr_map.irq       = dev->irq;
    			ifr->ifr_map.dma       = dev->dma;
    			ifr->ifr_map.port      = dev->if_port;
    			return 0;
    
    		case SIOCSIFMAP:
    			if (dev->set_config) {
    				if (!netif_device_present(dev))
    					return -ENODEV;
    				return dev->set_config(dev, &ifr->ifr_map);
    			}
    			return -EOPNOTSUPP;
    
    		case SIOCADDMULTI:
    			if (!dev->set_multicast_list ||
    			    ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
    				return -EINVAL;
    			if (!netif_device_present(dev))
    				return -ENODEV;
    			return dev_mc_add(dev, ifr->ifr_hwaddr.sa_data,
    					  dev->addr_len, 1);
    
    		case SIOCDELMULTI:
    			if (!dev->set_multicast_list ||
    			    ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
    				return -EINVAL;
    			if (!netif_device_present(dev))
    				return -ENODEV;
    			return dev_mc_delete(dev, ifr->ifr_hwaddr.sa_data,
    					     dev->addr_len, 1);
    
    		case SIOCGIFINDEX:
    			ifr->ifr_ifindex = dev->ifindex;
    			return 0;
    
    		case SIOCGIFTXQLEN:
    			ifr->ifr_qlen = dev->tx_queue_len;
    			return 0;
    
    		case SIOCSIFTXQLEN:
    			if (ifr->ifr_qlen < 0)
    				return -EINVAL;
    			dev->tx_queue_len = ifr->ifr_qlen;
    			return 0;
    
    		case SIOCSIFNAME:
    			ifr->ifr_newname[IFNAMSIZ-1] = '\0';
    			return dev_change_name(dev, ifr->ifr_newname);
    
    		/*
    		 *	Unknown or private ioctl
    		 */
    
    		default:
    			if ((cmd >= SIOCDEVPRIVATE &&
    			    cmd <= SIOCDEVPRIVATE + 15) ||
    			    cmd == SIOCBONDENSLAVE ||
    			    cmd == SIOCBONDRELEASE ||
    			    cmd == SIOCBONDSETHWADDR ||
    			    cmd == SIOCBONDSLAVEINFOQUERY ||
    			    cmd == SIOCBONDINFOQUERY ||
    			    cmd == SIOCBONDCHANGEACTIVE ||
    			    cmd == SIOCGMIIPHY ||
    			    cmd == SIOCGMIIREG ||
    			    cmd == SIOCSMIIREG ||
    			    cmd == SIOCBRADDIF ||
    			    cmd == SIOCBRDELIF ||
    			    cmd == SIOCWANDEV) {
    				err = -EOPNOTSUPP;
    				if (dev->do_ioctl) {
    					if (netif_device_present(dev))
    						err = dev->do_ioctl(dev, ifr,
    								    cmd);
    					else
    						err = -ENODEV;
    				}
    			} else
    				err = -EINVAL;
    
    	}
    	return err;
    }
    
    /*
     *	This function handles all "interface"-type I/O control requests. The actual
     *	'doing' part of this is dev_ifsioc above.
     */
    
    /**
     *	dev_ioctl	-	network device ioctl
     *	@cmd: command to issue
     *	@arg: pointer to a struct ifreq in user space
     *
     *	Issue ioctl functions to devices. This is normally called by the
     *	user space syscall interfaces but can sometimes be useful for
     *	other purposes. The return value is the return from the syscall if
     *	positive or a negative errno code on error.
     */
    
    int dev_ioctl(unsigned int cmd, void __user *arg)
    {
    	struct ifreq ifr;
    	int ret;
    	char *colon;
    
    	/* One special case: SIOCGIFCONF takes ifconf argument
    	   and requires shared lock, because it sleeps writing
    	   to user space.
    	 */
    
    	if (cmd == SIOCGIFCONF) {
    
    		rtnl_lock();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ret = dev_ifconf((char __user *) arg);
    
    		rtnl_unlock();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return ret;
    	}
    	if (cmd == SIOCGIFNAME)
    		return dev_ifname((struct ifreq __user *)arg);
    
    	if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
    		return -EFAULT;
    
    	ifr.ifr_name[IFNAMSIZ-1] = 0;
    
    	colon = strchr(ifr.ifr_name, ':');
    	if (colon)
    		*colon = 0;
    
    	/*
    	 *	See which interface the caller is talking about.
    	 */
    
    	switch (cmd) {
    		/*
    		 *	These ioctl calls:
    		 *	- can be done by all.
    		 *	- atomic and do not require locking.
    		 *	- return a value
    		 */
    		case SIOCGIFFLAGS:
    		case SIOCGIFMETRIC:
    		case SIOCGIFMTU:
    		case SIOCGIFHWADDR:
    		case SIOCGIFSLAVE:
    		case SIOCGIFMAP:
    		case SIOCGIFINDEX:
    		case SIOCGIFTXQLEN:
    			dev_load(ifr.ifr_name);
    			read_lock(&dev_base_lock);
    			ret = dev_ifsioc(&ifr, cmd);
    			read_unlock(&dev_base_lock);
    			if (!ret) {
    				if (colon)
    					*colon = ':';
    				if (copy_to_user(arg, &ifr,
    						 sizeof(struct ifreq)))
    					ret = -EFAULT;
    			}
    			return ret;
    
    		case SIOCETHTOOL:
    			dev_load(ifr.ifr_name);
    			rtnl_lock();
    			ret = dev_ethtool(&ifr);
    			rtnl_unlock();
    			if (!ret) {
    				if (colon)
    					*colon = ':';
    				if (copy_to_user(arg, &ifr,
    						 sizeof(struct ifreq)))
    					ret = -EFAULT;
    			}
    			return ret;
    
    		/*
    		 *	These ioctl calls:
    		 *	- require superuser power.
    		 *	- require strict serialization.
    		 *	- return a value
    		 */
    		case SIOCGMIIPHY:
    		case SIOCGMIIREG:
    		case SIOCSIFNAME:
    			if (!capable(CAP_NET_ADMIN))
    				return -EPERM;
    			dev_load(ifr.ifr_name);
    			rtnl_lock();
    			ret = dev_ifsioc(&ifr, cmd);
    			rtnl_unlock();
    			if (!ret) {
    				if (colon)
    					*colon = ':';
    				if (copy_to_user(arg, &ifr,
    						 sizeof(struct ifreq)))
    					ret = -EFAULT;
    			}
    			return ret;
    
    		/*
    		 *	These ioctl calls:
    		 *	- require superuser power.
    		 *	- require strict serialization.
    		 *	- do not return a value
    		 */
    		case SIOCSIFFLAGS:
    		case SIOCSIFMETRIC:
    		case SIOCSIFMTU:
    		case SIOCSIFMAP:
    		case SIOCSIFHWADDR:
    		case SIOCSIFSLAVE:
    		case SIOCADDMULTI:
    		case SIOCDELMULTI:
    		case SIOCSIFHWBROADCAST:
    		case SIOCSIFTXQLEN:
    		case SIOCSMIIREG:
    		case SIOCBONDENSLAVE:
    		case SIOCBONDRELEASE:
    		case SIOCBONDSETHWADDR:
    		case SIOCBONDCHANGEACTIVE:
    		case SIOCBRADDIF:
    		case SIOCBRDELIF:
    			if (!capable(CAP_NET_ADMIN))
    				return -EPERM;
    
    			/* fall through */
    		case SIOCBONDSLAVEINFOQUERY:
    		case SIOCBONDINFOQUERY:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			dev_load(ifr.ifr_name);
    			rtnl_lock();
    			ret = dev_ifsioc(&ifr, cmd);
    			rtnl_unlock();
    			return ret;
    
    		case SIOCGIFMEM:
    			/* Get the per device memory space. We can add this but
    			 * currently do not support it */
    		case SIOCSIFMEM:
    			/* Set the per device memory buffer space.
    			 * Not applicable in our case */
    		case SIOCSIFLINK:
    			return -EINVAL;
    
    		/*
    		 *	Unknown or private ioctl.
    		 */
    		default:
    			if (cmd == SIOCWANDEV ||
    			    (cmd >= SIOCDEVPRIVATE &&
    			     cmd <= SIOCDEVPRIVATE + 15)) {
    				dev_load(ifr.ifr_name);
    				rtnl_lock();
    				ret = dev_ifsioc(&ifr, cmd);
    				rtnl_unlock();
    				if (!ret && copy_to_user(arg, &ifr,
    							 sizeof(struct ifreq)))
    					ret = -EFAULT;
    				return ret;
    			}
    
    #ifdef CONFIG_WIRELESS_EXT
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			/* Take care of Wireless Extensions */
    			if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
    				/* If command is `set a parameter', or
    				 * `get the encoding parameters', check if
    				 * the user has the right to do it */
    
    				if (IW_IS_SET(cmd) || cmd == SIOCGIWENCODE
    				    || cmd == SIOCGIWENCODEEXT) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					if (!capable(CAP_NET_ADMIN))
    						return -EPERM;
    				}
    				dev_load(ifr.ifr_name);
    				rtnl_lock();
    				/* Follow me in net/core/wireless.c */
    				ret = wireless_process_ioctl(&ifr, cmd);
    				rtnl_unlock();
    				if (IW_IS_GET(cmd) &&
    				    copy_to_user(arg, &ifr,
    					    	 sizeof(struct ifreq)))
    					ret = -EFAULT;
    				return ret;
    			}
    
    #endif	/* CONFIG_WIRELESS_EXT */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return -EINVAL;
    	}
    }
    
    
    /**
     *	dev_new_index	-	allocate an ifindex
     *
     *	Returns a suitable unique value for a new device interface
     *	number.  The caller must hold the rtnl semaphore or the
     *	dev_base_lock to be sure it remains unique.
     */
    static int dev_new_index(void)
    {
    	static int ifindex;
    	for (;;) {
    		if (++ifindex <= 0)
    			ifindex = 1;
    		if (!__dev_get_by_index(ifindex))
    			return ifindex;
    	}
    }
    
    static int dev_boot_phase = 1;
    
    /* Delayed registration/unregisteration */
    static DEFINE_SPINLOCK(net_todo_list_lock);
    static struct list_head net_todo_list = LIST_HEAD_INIT(net_todo_list);
    
    static inline void net_set_todo(struct net_device *dev)
    {
    	spin_lock(&net_todo_list_lock);
    	list_add_tail(&dev->todo_list, &net_todo_list);
    	spin_unlock(&net_todo_list_lock);
    }
    
    /**
     *	register_netdevice	- register a network device
     *	@dev: device to register
     *
     *	Take a completed network device structure and add it to the kernel
     *	interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
     *	chain. 0 is returned on success. A negative errno code is returned
     *	on a failure to set up the device, or if the name is a duplicate.
     *
     *	Callers must hold the rtnl semaphore. You may want
     *	register_netdev() instead of this.
     *
     *	BUGS:
     *	The locking appears insufficient to guarantee two parallel registers
     *	will not get the same name.
     */
    
    int register_netdevice(struct net_device *dev)
    {
    	struct hlist_head *head;
    	struct hlist_node *p;
    	int ret;
    
    	BUG_ON(dev_boot_phase);
    	ASSERT_RTNL();
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* When net_device's are persistent, this will be fatal. */
    	BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
    
    	spin_lock_init(&dev->queue_lock);
    
    Herbert Xu's avatar
    Herbert Xu committed
    	spin_lock_init(&dev->_xmit_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dev->xmit_lock_owner = -1;
    #ifdef CONFIG_NET_CLS_ACT
    	spin_lock_init(&dev->ingress_lock);
    #endif
    
    	ret = alloc_divert_blk(dev);
    	if (ret)
    		goto out;
    
    	dev->iflink = -1;
    
    	/* Init, if this function is available */
    	if (dev->init) {
    		ret = dev->init(dev);
    		if (ret) {
    			if (ret > 0)
    				ret = -EIO;
    			goto out_err;
    		}
    	}
     
    	if (!dev_valid_name(dev->name)) {
    		ret = -EINVAL;
    		goto out_err;
    	}
    
    	dev->ifindex = dev_new_index();
    	if (dev->iflink == -1)
    		dev->iflink = dev->ifindex;
    
    	/* Check for existence of name */
    	head = dev_name_hash(dev->name);
    	hlist_for_each(p, head) {
    		struct net_device *d
    			= hlist_entry(p, struct net_device, name_hlist);
    		if (!strncmp(d->name, dev->name, IFNAMSIZ)) {
    			ret = -EEXIST;
     			goto out_err;
    		}
     	}
    
    	/* Fix illegal SG+CSUM combinations. */
    	if ((dev->features & NETIF_F_SG) &&
    
    	    !(dev->features & NETIF_F_ALL_CSUM)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk("%s: Dropping NETIF_F_SG since no checksum feature.\n",
    		       dev->name);
    		dev->features &= ~NETIF_F_SG;
    	}
    
    	/* TSO requires that SG is present as well. */
    	if ((dev->features & NETIF_F_TSO) &&
    	    !(dev->features & NETIF_F_SG)) {
    		printk("%s: Dropping NETIF_F_TSO since no SG feature.\n",
    		       dev->name);
    		dev->features &= ~NETIF_F_TSO;
    	}
    
    	if (dev->features & NETIF_F_UFO) {
    		if (!(dev->features & NETIF_F_HW_CSUM)) {
    			printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no "
    					"NETIF_F_HW_CSUM feature.\n",
    							dev->name);
    			dev->features &= ~NETIF_F_UFO;
    		}
    		if (!(dev->features & NETIF_F_SG)) {
    			printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no "
    					"NETIF_F_SG feature.\n",
    					dev->name);
    			dev->features &= ~NETIF_F_UFO;
    		}
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	nil rebuild_header routine,
    	 *	that should be never called and used as just bug trap.
    	 */
    
    	if (!dev->rebuild_header)
    		dev->rebuild_header = default_rebuild_header;
    
    
    	ret = netdev_register_sysfs(dev);
    	if (ret)
    		goto out_err;
    	dev->reg_state = NETREG_REGISTERED;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/*
    	 *	Default initial state at registry is that the
    	 *	device is present.
    	 */
    
    	set_bit(__LINK_STATE_PRESENT, &dev->state);
    
    	dev->next = NULL;
    	dev_init_scheduler(dev);
    	write_lock_bh(&dev_base_lock);
    	*dev_tail = dev;
    	dev_tail = &dev->next;
    	hlist_add_head(&dev->name_hlist, head);
    	hlist_add_head(&dev->index_hlist, dev_index_hash(dev->ifindex));
    	dev_hold(dev);
    	write_unlock_bh(&dev_base_lock);
    
    	/* Notify protocols, that a new device appeared. */
    
    	raw_notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	ret = 0;
    
    out:
    	return ret;
    out_err:
    	free_divert_blk(dev);
    	goto out;
    }
    
    /**
     *	register_netdev	- register a network device
     *	@dev: device to register
     *
     *	Take a completed network device structure and add it to the kernel
     *	interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
     *	chain. 0 is returned on success. A negative errno code is returned
     *	on a failure to set up the device, or if the name is a duplicate.
     *
     *	This is a wrapper around register_netdev that takes the rtnl semaphore
     *	and expands the device name if you passed a format string to
     *	alloc_netdev.
     */
    int register_netdev(struct net_device *dev)
    {
    	int err;
    
    	rtnl_lock();
    
    	/*
    	 * If the name is a format string the caller wants us to do a
    	 * name allocation.
    	 */
    	if (strchr(dev->name, '%')) {
    		err = dev_alloc_name(dev, dev->name);
    		if (err < 0)
    			goto out;
    	}
    	
    	/*
    	 * Back compatibility hook. Kill this one in 2.5
    	 */
    	if (dev->name[0] == 0 || dev->name[0] == ' ') {
    		err = dev_alloc_name(dev, "eth%d");
    		if (err < 0)
    			goto out;
    	}
    
    	err = register_netdevice(dev);
    out:
    	rtnl_unlock();
    	return err;
    }
    EXPORT_SYMBOL(register_netdev);
    
    /*
     * netdev_wait_allrefs - wait until all references are gone.
     *
     * This is called when unregistering network devices.
     *
     * Any protocol or device that holds a reference should register
     * for netdevice notification, and cleanup and put back the
     * reference if they receive an UNREGISTER event.
     * We can get stuck here if buggy protocols don't correctly
     * call dev_put. 
     */
    static void netdev_wait_allrefs(struct net_device *dev)
    {
    	unsigned long rebroadcast_time, warning_time;
    
    	rebroadcast_time = warning_time = jiffies;
    	while (atomic_read(&dev->refcnt) != 0) {
    		if (time_after(jiffies, rebroadcast_time + 1 * HZ)) {
    
    			rtnl_lock();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			/* Rebroadcast unregister notification */
    
    			raw_notifier_call_chain(&netdev_chain,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					    NETDEV_UNREGISTER, dev);
    
    			if (test_bit(__LINK_STATE_LINKWATCH_PENDING,
    				     &dev->state)) {
    				/* We must not have linkwatch events
    				 * pending on unregister. If this
    				 * happens, we simply run the queue
    				 * unscheduled, resulting in a noop
    				 * for this device.
    				 */
    				linkwatch_run_queue();
    			}
    
    
    			__rtnl_unlock();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			rebroadcast_time = jiffies;
    		}
    
    		msleep(250);
    
    		if (time_after(jiffies, warning_time + 10 * HZ)) {
    			printk(KERN_EMERG "unregister_netdevice: "
    			       "waiting for %s to become free. Usage "
    			       "count = %d\n",
    			       dev->name, atomic_read(&dev->refcnt));
    			warning_time = jiffies;
    		}
    	}
    }
    
    /* The sequence is:
     *
     *	rtnl_lock();
     *	...
     *	register_netdevice(x1);
     *	register_netdevice(x2);
     *	...
     *	unregister_netdevice(y1);
     *	unregister_netdevice(y2);
     *      ...
     *	rtnl_unlock();
     *	free_netdev(y1);
     *	free_netdev(y2);
     *
     * We are invoked by rtnl_unlock() after it drops the semaphore.
     * This allows us to deal with problems:
    
     * 1) We can delete sysfs objects which invoke hotplug
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *    without deadlocking with linkwatch via keventd.
     * 2) Since we run with the RTNL semaphore not held, we can sleep
     *    safely in order to wait for the netdev refcnt to drop to zero.
     */
    
    Arjan van de Ven's avatar
    Arjan van de Ven committed
    static DEFINE_MUTEX(net_todo_run_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    void netdev_run_todo(void)
    {
    	struct list_head list = LIST_HEAD_INIT(list);
    
    	/* Need to guard against multiple cpu's getting out of order. */
    
    Arjan van de Ven's avatar
    Arjan van de Ven committed
    	mutex_lock(&net_todo_run_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Not safe to do outside the semaphore.  We must not return
    	 * until all unregister events invoked by the local processor
    	 * have been completed (either by this todo run, or one on
    	 * another cpu).
    	 */
    	if (list_empty(&net_todo_list))
    		goto out;
    
    	/* Snapshot list, allow later requests */
    	spin_lock(&net_todo_list_lock);
    	list_splice_init(&net_todo_list, &list);
    	spin_unlock(&net_todo_list_lock);