Skip to content
Snippets Groups Projects
dev.c 150 KiB
Newer Older
  • Learn to ignore specific revisions
  • 		__hw_addr_del(to_list, ha2->addr, addr_len, type);
    
    static void __hw_addr_del_multiple(struct netdev_hw_addr_list *to_list,
    				   struct netdev_hw_addr_list *from_list,
    				   int addr_len,
    
    				   unsigned char addr_type)
    
    {
    	struct netdev_hw_addr *ha;
    	unsigned char type;
    
    
    	list_for_each_entry(ha, &from_list->list, list) {
    
    		type = addr_type ? addr_type : ha->type;
    
    		__hw_addr_del(to_list, ha->addr, addr_len, addr_type);
    
    static int __hw_addr_sync(struct netdev_hw_addr_list *to_list,
    			  struct netdev_hw_addr_list *from_list,
    
    			  int addr_len)
    {
    	int err = 0;
    	struct netdev_hw_addr *ha, *tmp;
    
    
    	list_for_each_entry_safe(ha, tmp, &from_list->list, list) {
    
    		if (!ha->synced) {
    
    			err = __hw_addr_add(to_list, ha->addr,
    
    					    addr_len, ha->type);
    			if (err)
    				break;
    			ha->synced = true;
    			ha->refcount++;
    		} else if (ha->refcount == 1) {
    
    			__hw_addr_del(to_list, ha->addr, addr_len, ha->type);
    			__hw_addr_del(from_list, ha->addr, addr_len, ha->type);
    
    	return err;
    
    static void __hw_addr_unsync(struct netdev_hw_addr_list *to_list,
    			     struct netdev_hw_addr_list *from_list,
    
    			     int addr_len)
    {
    	struct netdev_hw_addr *ha, *tmp;
    
    
    	list_for_each_entry_safe(ha, tmp, &from_list->list, list) {
    
    		if (ha->synced) {
    
    			__hw_addr_del(to_list, ha->addr,
    
    				      addr_len, ha->type);
    			ha->synced = false;
    
    			__hw_addr_del(from_list, ha->addr,
    
    				      addr_len, ha->type);
    		}
    	}
    }
    
    
    static void __hw_addr_flush(struct netdev_hw_addr_list *list)
    
    	list_for_each_entry_safe(ha, tmp, &list->list, list) {
    
    		list_del_rcu(&ha->list);
    		call_rcu(&ha->rcu_head, ha_rcu_free);
    	}
    
    	list->count = 0;
    }
    
    static void __hw_addr_init(struct netdev_hw_addr_list *list)
    {
    	INIT_LIST_HEAD(&list->list);
    	list->count = 0;
    
    }
    
    /* Device addresses handling functions */
    
    static void dev_addr_flush(struct net_device *dev)
    {
    	/* rtnl_mutex must be held here */
    
    
    	__hw_addr_flush(&dev->dev_addrs);
    
    	dev->dev_addr = NULL;
    }
    
    static int dev_addr_init(struct net_device *dev)
    {
    	unsigned char addr[MAX_ADDR_LEN];
    	struct netdev_hw_addr *ha;
    	int err;
    
    	/* rtnl_mutex must be held here */
    
    
    	__hw_addr_init(&dev->dev_addrs);
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	memset(addr, 0, sizeof(addr));
    
    	err = __hw_addr_add(&dev->dev_addrs, addr, sizeof(addr),
    
    			    NETDEV_HW_ADDR_T_LAN);
    	if (!err) {
    		/*
    		 * Get the first (previously created) address from the list
    		 * and set dev_addr pointer to this location.
    		 */
    
    		ha = list_first_entry(&dev->dev_addrs.list,
    
    				      struct netdev_hw_addr, list);
    		dev->dev_addr = ha->addr;
    	}
    	return err;
    }
    
    /**
     *	dev_addr_add	- Add a device address
     *	@dev: device
     *	@addr: address to add
     *	@addr_type: address type
     *
     *	Add a device address to the device or increase the reference count if
     *	it already exists.
     *
     *	The caller must hold the rtnl_mutex.
     */
    int dev_addr_add(struct net_device *dev, unsigned char *addr,
    		 unsigned char addr_type)
    {
    	int err;
    
    	ASSERT_RTNL();
    
    
    	err = __hw_addr_add(&dev->dev_addrs, addr, dev->addr_len, addr_type);
    
    	if (!err)
    		call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
    	return err;
    }
    EXPORT_SYMBOL(dev_addr_add);
    
    /**
     *	dev_addr_del	- Release a device address.
     *	@dev: device
     *	@addr: address to delete
     *	@addr_type: address type
     *
     *	Release reference to a device address and remove it from the device
     *	if the reference count drops to zero.
     *
     *	The caller must hold the rtnl_mutex.
     */
    int dev_addr_del(struct net_device *dev, unsigned char *addr,
    		 unsigned char addr_type)
    {
    	int err;
    
    	struct netdev_hw_addr *ha;
    
    	/*
    	 * We can not remove the first address from the list because
    	 * dev->dev_addr points to that.
    	 */
    
    	ha = list_first_entry(&dev->dev_addrs.list,
    			      struct netdev_hw_addr, list);
    
    	if (ha->addr == dev->dev_addr && ha->refcount == 1)
    		return -ENOENT;
    
    
    	err = __hw_addr_del(&dev->dev_addrs, addr, dev->addr_len,
    
    			    addr_type);
    
    	if (!err)
    		call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
    	return err;
    }
    EXPORT_SYMBOL(dev_addr_del);
    
    /**
     *	dev_addr_add_multiple	- Add device addresses from another device
     *	@to_dev: device to which addresses will be added
     *	@from_dev: device from which addresses will be added
     *	@addr_type: address type - 0 means type will be used from from_dev
     *
     *	Add device addresses of the one device to another.
     **
     *	The caller must hold the rtnl_mutex.
     */
    int dev_addr_add_multiple(struct net_device *to_dev,
    			  struct net_device *from_dev,
    			  unsigned char addr_type)
    {
    	int err;
    
    	ASSERT_RTNL();
    
    	if (from_dev->addr_len != to_dev->addr_len)
    		return -EINVAL;
    
    	err = __hw_addr_add_multiple(&to_dev->dev_addrs, &from_dev->dev_addrs,
    
    				     to_dev->addr_len, addr_type);
    
    	if (!err)
    		call_netdevice_notifiers(NETDEV_CHANGEADDR, to_dev);
    	return err;
    }
    EXPORT_SYMBOL(dev_addr_add_multiple);
    
    /**
     *	dev_addr_del_multiple	- Delete device addresses by another device
     *	@to_dev: device where the addresses will be deleted
     *	@from_dev: device by which addresses the addresses will be deleted
     *	@addr_type: address type - 0 means type will used from from_dev
     *
     *	Deletes addresses in to device by the list of addresses in from device.
     *
     *	The caller must hold the rtnl_mutex.
     */
    int dev_addr_del_multiple(struct net_device *to_dev,
    			  struct net_device *from_dev,
    			  unsigned char addr_type)
    {
    	ASSERT_RTNL();
    
    	if (from_dev->addr_len != to_dev->addr_len)
    		return -EINVAL;
    
    	__hw_addr_del_multiple(&to_dev->dev_addrs, &from_dev->dev_addrs,
    
    			       to_dev->addr_len, addr_type);
    
    	call_netdevice_notifiers(NETDEV_CHANGEADDR, to_dev);
    	return 0;
    }
    EXPORT_SYMBOL(dev_addr_del_multiple);
    
    
    /* multicast addresses handling functions */
    
    int __dev_addr_delete(struct dev_addr_list **list, int *count,
    		      void *addr, int alen, int glbl)
    
    {
    	struct dev_addr_list *da;
    
    	for (; (da = *list) != NULL; list = &da->next) {
    		if (memcmp(da->da_addr, addr, da->da_addrlen) == 0 &&
    		    alen == da->da_addrlen) {
    			if (glbl) {
    				int old_glbl = da->da_gusers;
    				da->da_gusers = 0;
    				if (old_glbl == 0)
    					break;
    			}
    			if (--da->da_users)
    				return 0;
    
    			*list = da->next;
    			kfree(da);
    
    int __dev_addr_add(struct dev_addr_list **list, int *count,
    		   void *addr, int alen, int glbl)
    
    {
    	struct dev_addr_list *da;
    
    	for (da = *list; da != NULL; da = da->next) {
    		if (memcmp(da->da_addr, addr, da->da_addrlen) == 0 &&
    		    da->da_addrlen == alen) {
    			if (glbl) {
    				int old_glbl = da->da_gusers;
    				da->da_gusers = 1;
    				if (old_glbl)
    					return 0;
    			}
    			da->da_users++;
    			return 0;
    		}
    	}
    
    
    	da = kzalloc(sizeof(*da), GFP_ATOMIC);
    
    	if (da == NULL)
    		return -ENOMEM;
    	memcpy(da->da_addr, addr, alen);
    	da->da_addrlen = alen;
    	da->da_users = 1;
    	da->da_gusers = glbl ? 1 : 0;
    	da->next = *list;
    	*list = da;
    
    /**
     *	dev_unicast_delete	- Release secondary unicast address.
     *	@dev: device
    
    Randy Dunlap's avatar
    Randy Dunlap committed
     *	@addr: address to delete
    
     *
     *	Release reference to a secondary unicast address and remove it
    
    Randy Dunlap's avatar
    Randy Dunlap committed
     *	from the device if the reference count drops to zero.
    
     *
     * 	The caller must hold the rtnl_mutex.
     */
    
    int dev_unicast_delete(struct net_device *dev, void *addr)
    
    	netif_addr_lock_bh(dev);
    
    	err = __hw_addr_del(&dev->uc, addr, dev->addr_len,
    			    NETDEV_HW_ADDR_T_UNICAST);
    
    		__dev_set_rx_mode(dev);
    
    	netif_addr_unlock_bh(dev);
    
    	return err;
    }
    EXPORT_SYMBOL(dev_unicast_delete);
    
    /**
     *	dev_unicast_add		- add a secondary unicast address
     *	@dev: device
    
     *	@addr: address to add
    
     *
     *	Add a secondary unicast address to the device or increase
     *	the reference count if it already exists.
     *
     *	The caller must hold the rtnl_mutex.
     */
    
    int dev_unicast_add(struct net_device *dev, void *addr)
    
    	netif_addr_lock_bh(dev);
    
    	err = __hw_addr_add(&dev->uc, addr, dev->addr_len,
    			    NETDEV_HW_ADDR_T_UNICAST);
    
    		__dev_set_rx_mode(dev);
    
    	netif_addr_unlock_bh(dev);
    
    	return err;
    }
    EXPORT_SYMBOL(dev_unicast_add);
    
    
    int __dev_addr_sync(struct dev_addr_list **to, int *to_count,
    		    struct dev_addr_list **from, int *from_count)
    {
    	struct dev_addr_list *da, *next;
    	int err = 0;
    
    	da = *from;
    	while (da != NULL) {
    		next = da->next;
    		if (!da->da_synced) {
    			err = __dev_addr_add(to, to_count,
    					     da->da_addr, da->da_addrlen, 0);
    			if (err < 0)
    				break;
    			da->da_synced = 1;
    			da->da_users++;
    		} else if (da->da_users == 1) {
    			__dev_addr_delete(to, to_count,
    					  da->da_addr, da->da_addrlen, 0);
    			__dev_addr_delete(from, from_count,
    					  da->da_addr, da->da_addrlen, 0);
    		}
    		da = next;
    	}
    	return err;
    }
    
    EXPORT_SYMBOL_GPL(__dev_addr_sync);
    
    
    void __dev_addr_unsync(struct dev_addr_list **to, int *to_count,
    		       struct dev_addr_list **from, int *from_count)
    {
    	struct dev_addr_list *da, *next;
    
    	da = *from;
    	while (da != NULL) {
    		next = da->next;
    		if (da->da_synced) {
    			__dev_addr_delete(to, to_count,
    					  da->da_addr, da->da_addrlen, 0);
    			da->da_synced = 0;
    			__dev_addr_delete(from, from_count,
    					  da->da_addr, da->da_addrlen, 0);
    		}
    		da = next;
    	}
    }
    
    EXPORT_SYMBOL_GPL(__dev_addr_unsync);
    
    
    /**
     *	dev_unicast_sync - Synchronize device's unicast list to another device
     *	@to: destination device
     *	@from: source device
     *
     *	Add newly added addresses to the destination device and release
    
     *	addresses that have no users left. The source device must be
     *	locked by netif_tx_lock_bh.
    
     *
     *	This function is intended to be called from the dev->set_rx_mode
     *	function of layered software devices.
     */
    int dev_unicast_sync(struct net_device *to, struct net_device *from)
    {
    	int err = 0;
    
    
    	if (to->addr_len != from->addr_len)
    		return -EINVAL;
    
    
    	err = __hw_addr_sync(&to->uc, &from->uc, to->addr_len);
    
    	if (!err)
    		__dev_set_rx_mode(to);
    
    	netif_addr_unlock_bh(to);
    
    	return err;
    }
    EXPORT_SYMBOL(dev_unicast_sync);
    
    /**
    
     *	dev_unicast_unsync - Remove synchronized addresses from the destination device
    
     *	@to: destination device
     *	@from: source device
     *
     *	Remove all addresses that were added to the destination device by
     *	dev_unicast_sync(). This function is intended to be called from the
     *	dev->stop function of layered software devices.
     */
    void dev_unicast_unsync(struct net_device *to, struct net_device *from)
    {
    
    	if (to->addr_len != from->addr_len)
    		return;
    
    	netif_addr_lock_bh(from);
    	netif_addr_lock(to);
    
    	__hw_addr_unsync(&to->uc, &from->uc, to->addr_len);
    
    	__dev_set_rx_mode(to);
    
    	netif_addr_unlock(to);
    	netif_addr_unlock_bh(from);
    
    }
    EXPORT_SYMBOL(dev_unicast_unsync);
    
    
    static void dev_unicast_flush(struct net_device *dev)
    {
    
    	netif_addr_lock_bh(dev);
    
    	__hw_addr_flush(&dev->uc);
    
    	netif_addr_unlock_bh(dev);
    
    }
    
    static void dev_unicast_init(struct net_device *dev)
    {
    
    	__hw_addr_init(&dev->uc);
    
    static void __dev_addr_discard(struct dev_addr_list **list)
    {
    	struct dev_addr_list *tmp;
    
    	while (*list != NULL) {
    		tmp = *list;
    		*list = tmp->next;
    		if (tmp->da_users > tmp->da_gusers)
    			printk("__dev_addr_discard: address leakage! "
    			       "da_users=%d\n", tmp->da_users);
    		kfree(tmp);
    	}
    }
    
    
    static void dev_addr_discard(struct net_device *dev)
    
    	netif_addr_lock_bh(dev);
    
    	__dev_addr_discard(&dev->mc_list);
    
    	netif_addr_unlock_bh(dev);
    
    /**
     *	dev_get_flags - get flags reported to userspace
     *	@dev: device
     *
     *	Get the combination of flag bits exported through APIs to userspace.
     */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    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;
    }
    
    EXPORT_SYMBOL(dev_get_flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    int __dev_change_flags(struct net_device *dev, unsigned int flags)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int old_flags = dev->flags;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/*
    	 *	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.
    	 */
    
    
    	if ((old_flags ^ flags) & IFF_MULTICAST)
    		dev_change_rx_flags(dev, IFF_MULTICAST);
    
    	dev_set_rx_mode(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (!ret)
    
    			dev_set_rx_mode(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if ((flags ^ dev->gflags) & IFF_PROMISC) {
    
    		int inc = (flags & IFF_PROMISC) ? 1 : -1;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		dev->gflags ^= IFF_ALLMULTI;
    		dev_set_allmulti(dev, inc);
    	}
    
    
    	return ret;
    }
    
    void __dev_notify_flags(struct net_device *dev, unsigned int old_flags)
    {
    	unsigned int changes = dev->flags ^ old_flags;
    
    	if (changes & IFF_UP) {
    		if (dev->flags & IFF_UP)
    			call_netdevice_notifiers(NETDEV_UP, dev);
    		else
    			call_netdevice_notifiers(NETDEV_DOWN, dev);
    	}
    
    	if (dev->flags & IFF_UP &&
    	    (changes & ~(IFF_UP | IFF_PROMISC | IFF_ALLMULTI | IFF_VOLATILE)))
    		call_netdevice_notifiers(NETDEV_CHANGE, dev);
    }
    
    /**
     *	dev_change_flags - change device settings
     *	@dev: device
     *	@flags: device state flags
     *
     *	Change settings on device based state flags. The flags are
     *	in the userspace exported format.
     */
    int dev_change_flags(struct net_device *dev, unsigned flags)
    {
    	int ret, changes;
    	int old_flags = dev->flags;
    
    	ret = __dev_change_flags(dev, flags);
    	if (ret < 0)
    		return ret;
    
    	changes = old_flags ^ dev->flags;
    
    	if (changes)
    		rtmsg_ifinfo(RTM_NEWLINK, dev, changes);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	__dev_notify_flags(dev, old_flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return ret;
    }
    
    EXPORT_SYMBOL(dev_change_flags);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /**
     *	dev_set_mtu - Change maximum transfer unit
     *	@dev: device
     *	@new_mtu: new transfer unit
     *
     *	Change the maximum transfer size of the network device.
     */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    int dev_set_mtu(struct net_device *dev, int new_mtu)
    {
    
    	const struct net_device_ops *ops = dev->netdev_ops;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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 (ops->ndo_change_mtu)
    		err = ops->ndo_change_mtu(dev, new_mtu);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else
    		dev->mtu = new_mtu;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!err && dev->flags & IFF_UP)
    
    		call_netdevice_notifiers(NETDEV_CHANGEMTU, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    EXPORT_SYMBOL(dev_set_mtu);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /**
     *	dev_set_mac_address - Change Media Access Control Address
     *	@dev: device
     *	@sa: new address
     *
     *	Change the hardware (MAC) address of the device
     */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    int dev_set_mac_address(struct net_device *dev, struct sockaddr *sa)
    {
    
    	const struct net_device_ops *ops = dev->netdev_ops;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int err;
    
    
    	if (!ops->ndo_set_mac_address)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EOPNOTSUPP;
    	if (sa->sa_family != dev->type)
    		return -EINVAL;
    	if (!netif_device_present(dev))
    		return -ENODEV;
    
    	err = ops->ndo_set_mac_address(dev, sa);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!err)
    
    		call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    EXPORT_SYMBOL(dev_set_mac_address);
    
     *	Perform the SIOCxIFxxx calls, inside rcu_read_lock()
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    static int dev_ifsioc_locked(struct net *net, struct ifreq *ifr, unsigned int cmd)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int err;
    
    	struct net_device *dev = dev_get_by_name_rcu(net, ifr->ifr_name);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!dev)
    		return -ENODEV;
    
    	switch (cmd) {
    
    	case SIOCGIFFLAGS:	/* Get interface flags */
    		ifr->ifr_flags = (short) dev_get_flags(dev);
    		return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCGIFMETRIC:	/* Get the metric on the interface
    				   (currently unused) */
    		ifr->ifr_metric = 0;
    		return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCGIFMTU:	/* Get the MTU of a device */
    		ifr->ifr_mtu = dev->mtu;
    		return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	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;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCGIFSLAVE:
    		err = -EINVAL;
    		break;
    
    	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 SIOCGIFINDEX:
    		ifr->ifr_ifindex = dev->ifindex;
    		return 0;
    
    	case SIOCGIFTXQLEN:
    		ifr->ifr_qlen = dev->tx_queue_len;
    		return 0;
    
    	default:
    		/* dev_ioctl() should ensure this case
    		 * is never reached
    		 */
    		WARN_ON(1);
    		err = -EINVAL;
    		break;
    
    
    	}
    	return err;
    }
    
    /*
     *	Perform the SIOCxIFxxx calls, inside rtnl_lock()
     */
    static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd)
    {
    	int err;
    	struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);
    
    	const struct net_device_ops *ops;
    
    	ops = dev->netdev_ops;
    
    
    	case SIOCSIFFLAGS:	/* Set interface flags */
    		return dev_change_flags(dev, ifr->ifr_flags);
    
    	case SIOCSIFMETRIC:	/* Set the metric on the interface
    				   (currently unused) */
    		return -EOPNOTSUPP;
    
    	case SIOCSIFMTU:	/* Set the MTU of a device */
    		return dev_set_mtu(dev, ifr->ifr_mtu);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCSIFHWADDR:
    		return dev_set_mac_address(dev, &ifr->ifr_hwaddr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	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));
    		call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
    		return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCSIFMAP:
    		if (ops->ndo_set_config) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (!netif_device_present(dev))
    				return -ENODEV;
    
    			return ops->ndo_set_config(dev, &ifr->ifr_map);
    		}
    		return -EOPNOTSUPP;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCADDMULTI:
    		if ((!ops->ndo_set_multicast_list && !ops->ndo_set_rx_mode) ||
    		    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 ((!ops->ndo_set_multicast_list && !ops->ndo_set_rx_mode) ||
    		    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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCSIFTXQLEN:
    		if (ifr->ifr_qlen < 0)
    			return -EINVAL;
    		dev->tx_queue_len = ifr->ifr_qlen;
    		return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCSIFNAME:
    		ifr->ifr_newname[IFNAMSIZ-1] = '\0';
    		return dev_change_name(dev, ifr->ifr_newname);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/*
    	 *	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 == SIOCSHWTSTAMP ||
    		    cmd == SIOCWANDEV) {
    			err = -EOPNOTSUPP;
    			if (ops->ndo_do_ioctl) {
    				if (netif_device_present(dev))
    					err = ops->ndo_do_ioctl(dev, ifr, cmd);
    				else
    					err = -ENODEV;
    			}
    		} else
    			err = -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	}
    	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
    
     *	@net: the applicable net namespace
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *	@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(struct net *net, unsigned int cmd, void __user *arg)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	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();
    
    		ret = dev_ifconf(net, (char __user *) arg);
    
    		rtnl_unlock();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return ret;
    	}
    	if (cmd == SIOCGIFNAME)
    
    		return dev_ifname(net, (struct ifreq __user *)arg);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	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(net, ifr.ifr_name);
    
    		rcu_read_lock();
    
    		ret = dev_ifsioc_locked(net, &ifr, cmd);
    
    		rcu_read_unlock();
    
    		if (!ret) {
    			if (colon)
    				*colon = ':';
    			if (copy_to_user(arg, &ifr,
    					 sizeof(struct ifreq)))
    				ret = -EFAULT;
    		}
    		return ret;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	case SIOCETHTOOL:
    		dev_load(net, ifr.ifr_name);
    		rtnl_lock();
    		ret = dev_ethtool(net, &ifr);
    		rtnl_unlock();
    		if (!ret) {
    			if (colon)
    				*colon = ':';
    			if (copy_to_user(arg, &ifr,
    					 sizeof(struct ifreq)))
    				ret = -EFAULT;
    		}
    		return ret;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/*
    	 *	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(net, ifr.ifr_name);
    		rtnl_lock();
    		ret = dev_ifsioc(net, &ifr, cmd);
    		rtnl_unlock();
    		if (!ret) {
    			if (colon)
    				*colon = ':';
    			if (copy_to_user(arg, &ifr,
    					 sizeof(struct ifreq)))
    				ret = -EFAULT;
    		}
    		return ret;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/*
    	 *	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:
    	case SIOCSHWTSTAMP:
    		if (!capable(CAP_NET_ADMIN))
    			return -EPERM;
    		/* fall through */
    	case SIOCBONDSLAVEINFOQUERY:
    	case SIOCBONDINFOQUERY:
    		dev_load(net, ifr.ifr_name);
    		rtnl_lock();
    		ret = dev_ifsioc(net, &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.
    	 */