Skip to content
Snippets Groups Projects
dev.c 136 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    
    void dev_seq_stop(struct seq_file *seq, void *v)
    
    	__releases(dev_base_lock)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	read_unlock(&dev_base_lock);
    }
    
    static void dev_seq_printf_stats(struct seq_file *seq, struct net_device *dev)
    {
    
    	const struct net_device_stats *stats = dev_get_stats(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	seq_printf(seq, "%6s:%8lu %7lu %4lu %4lu %4lu %5lu %10lu %9lu "
    		   "%8lu %7lu %4lu %4lu %4lu %5lu %7lu %10lu\n",
    		   dev->name, stats->rx_bytes, stats->rx_packets,
    		   stats->rx_errors,
    		   stats->rx_dropped + stats->rx_missed_errors,
    		   stats->rx_fifo_errors,
    		   stats->rx_length_errors + stats->rx_over_errors +
    		    stats->rx_crc_errors + stats->rx_frame_errors,
    		   stats->rx_compressed, stats->multicast,
    		   stats->tx_bytes, stats->tx_packets,
    		   stats->tx_errors, stats->tx_dropped,
    		   stats->tx_fifo_errors, stats->collisions,
    		   stats->tx_carrier_errors +
    		    stats->tx_aborted_errors +
    		    stats->tx_window_errors +
    		    stats->tx_heartbeat_errors,
    		   stats->tx_compressed);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     *	Called from the PROCfs module. This now uses the new arbitrary sized
     *	/proc/net interface to create /proc/net/dev
     */
    static int dev_seq_show(struct seq_file *seq, void *v)
    {
    	if (v == SEQ_START_TOKEN)
    		seq_puts(seq, "Inter-|   Receive                            "
    			      "                    |  Transmit\n"
    			      " face |bytes    packets errs drop fifo frame "
    			      "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_cpu_ids)
    
    		if (cpu_online(*pos)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			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 const struct seq_operations dev_seq_ops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.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_net(inode, file, &dev_seq_ops,
    			    sizeof(struct seq_net_private));
    
    static const struct file_operations dev_seq_fops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.owner	 = THIS_MODULE,
    	.open    = dev_seq_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    
    static const struct seq_operations softnet_seq_ops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.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 const struct file_operations softnet_seq_fops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.owner	 = THIS_MODULE,
    	.open    = softnet_seq_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    	.release = seq_release,
    };
    
    
    static void *ptype_get_idx(loff_t pos)
    {
    	struct packet_type *pt = NULL;
    	loff_t i = 0;
    	int t;
    
    	list_for_each_entry_rcu(pt, &ptype_all, list) {
    		if (i == pos)
    			return pt;
    		++i;
    	}
    
    
    	for (t = 0; t < PTYPE_HASH_SIZE; t++) {
    
    		list_for_each_entry_rcu(pt, &ptype_base[t], list) {
    			if (i == pos)
    				return pt;
    			++i;
    		}
    	}
    	return NULL;
    }
    
    static void *ptype_seq_start(struct seq_file *seq, loff_t *pos)
    
    {
    	rcu_read_lock();
    	return *pos ? ptype_get_idx(*pos - 1) : SEQ_START_TOKEN;
    }
    
    static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
    {
    	struct packet_type *pt;
    	struct list_head *nxt;
    	int hash;
    
    	++*pos;
    	if (v == SEQ_START_TOKEN)
    		return ptype_get_idx(0);
    
    	pt = v;
    	nxt = pt->list.next;
    	if (pt->type == htons(ETH_P_ALL)) {
    		if (nxt != &ptype_all)
    			goto found;
    		hash = 0;
    		nxt = ptype_base[0].next;
    	} else
    
    		hash = ntohs(pt->type) & PTYPE_HASH_MASK;
    
    
    	while (nxt == &ptype_base[hash]) {
    
    		if (++hash >= PTYPE_HASH_SIZE)
    
    			return NULL;
    		nxt = ptype_base[hash].next;
    	}
    found:
    	return list_entry(nxt, struct packet_type, list);
    }
    
    static void ptype_seq_stop(struct seq_file *seq, void *v)
    
    {
    	rcu_read_unlock();
    }
    
    static int ptype_seq_show(struct seq_file *seq, void *v)
    {
    	struct packet_type *pt = v;
    
    	if (v == SEQ_START_TOKEN)
    		seq_puts(seq, "Type Device      Function\n");
    
    	else if (pt->dev == NULL || dev_net(pt->dev) == seq_file_net(seq)) {
    
    		if (pt->type == htons(ETH_P_ALL))
    			seq_puts(seq, "ALL ");
    		else
    			seq_printf(seq, "%04x", ntohs(pt->type));
    
    
    		seq_printf(seq, " %-8s %pF\n",
    			   pt->dev ? pt->dev->name : "", pt->func);
    
    	}
    
    	return 0;
    }
    
    static const struct seq_operations ptype_seq_ops = {
    	.start = ptype_seq_start,
    	.next  = ptype_seq_next,
    	.stop  = ptype_seq_stop,
    	.show  = ptype_seq_show,
    };
    
    static int ptype_seq_open(struct inode *inode, struct file *file)
    {
    
    	return seq_open_net(inode, file, &ptype_seq_ops,
    			sizeof(struct seq_net_private));
    
    }
    
    static const struct file_operations ptype_seq_fops = {
    	.owner	 = THIS_MODULE,
    	.open    = ptype_seq_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    
    	.release = seq_release_net,
    
    static int __net_init dev_proc_net_init(struct net *net)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int rc = -ENOMEM;
    
    
    	if (!proc_net_fops_create(net, "dev", S_IRUGO, &dev_seq_fops))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out;
    
    	if (!proc_net_fops_create(net, "softnet_stat", S_IRUGO, &softnet_seq_fops))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out_dev;
    
    	if (!proc_net_fops_create(net, "ptype", S_IRUGO, &ptype_seq_fops))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	rc = 0;
    out:
    	return rc;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out_softnet:
    
    	proc_net_remove(net, "softnet_stat");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out_dev:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	goto out;
    }
    
    static void __net_exit dev_proc_net_exit(struct net *net)
    
    {
    	wext_proc_exit(net);
    
    	proc_net_remove(net, "ptype");
    	proc_net_remove(net, "softnet_stat");
    	proc_net_remove(net, "dev");
    }
    
    
    static struct pernet_operations __net_initdata dev_proc_ops = {
    
    	.init = dev_proc_net_init,
    	.exit = dev_proc_net_exit,
    };
    
    static int __init dev_proc_init(void)
    {
    	return register_pernet_subsys(&dev_proc_ops);
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #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;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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;
    }
    
    
    static void dev_change_rx_flags(struct net_device *dev, int flags)
    {
    
    	const struct net_device_ops *ops = dev->netdev_ops;
    
    	if ((dev->flags & IFF_UP) && ops->ndo_change_rx_flags)
    		ops->ndo_change_rx_flags(dev, flags);
    
    static int __dev_set_promiscuity(struct net_device *dev, int inc)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned short old_flags = dev->flags;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	dev->flags |= IFF_PROMISC;
    	dev->promiscuity += inc;
    	if (dev->promiscuity == 0) {
    		/*
    		 * Avoid overflow.
    		 * If inc causes overflow, untouch promisc and return error.
    		 */
    		if (inc < 0)
    			dev->flags &= ~IFF_PROMISC;
    		else {
    			dev->promiscuity -= inc;
    			printk(KERN_WARNING "%s: promiscuity touches roof, "
    				"set promiscuity failed, promiscuity feature "
    				"of device might be broken.\n", dev->name);
    			return -EOVERFLOW;
    		}
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(KERN_INFO "device %s %s promiscuous mode\n",
    		       dev->name, (dev->flags & IFF_PROMISC) ? "entered" :
    
    		if (audit_enabled) {
    			current_uid_gid(&uid, &gid);
    
    			audit_log(current->audit_context, GFP_ATOMIC,
    				AUDIT_ANOM_PROMISCUOUS,
    				"dev=%s prom=%d old_prom=%d auid=%u uid=%u gid=%u ses=%u",
    				dev->name, (dev->flags & IFF_PROMISC),
    				(old_flags & IFF_PROMISC),
    				audit_get_loginuid(current),
    
    				audit_get_sessionid(current));
    
    		dev_change_rx_flags(dev, IFF_PROMISC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    /**
     *	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
     *	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.
    
     *	Return 0 if successful or a negative errno code on error.
    
    int dev_set_promiscuity(struct net_device *dev, int inc)
    
    {
    	unsigned short old_flags = dev->flags;
    
    	err = __dev_set_promiscuity(dev, inc);
    
    	if (dev->flags != old_flags)
    		dev_set_rx_mode(dev);
    
    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.
    
     *	Return 0 if successful or a negative errno code on error.
    
    int dev_set_allmulti(struct net_device *dev, int inc)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned short old_flags = dev->flags;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dev->flags |= IFF_ALLMULTI;
    
    	dev->allmulti += inc;
    	if (dev->allmulti == 0) {
    		/*
    		 * Avoid overflow.
    		 * If inc causes overflow, untouch allmulti and return error.
    		 */
    		if (inc < 0)
    			dev->flags &= ~IFF_ALLMULTI;
    		else {
    			dev->allmulti -= inc;
    			printk(KERN_WARNING "%s: allmulti touches roof, "
    				"set allmulti failed, allmulti feature of "
    				"device might be broken.\n", dev->name);
    			return -EOVERFLOW;
    		}
    	}
    
    	if (dev->flags ^ old_flags) {
    
    		dev_change_rx_flags(dev, IFF_ALLMULTI);
    
    		dev_set_rx_mode(dev);
    
    }
    
    /*
     *	Upload unicast and multicast address lists to device and
     *	configure RX filtering. When the device doesn't support unicast
    
     *	filtering it is put in promiscuous mode while unicast addresses
    
     *	are present.
     */
    void __dev_set_rx_mode(struct net_device *dev)
    {
    
    	const struct net_device_ops *ops = dev->netdev_ops;
    
    
    	/* dev_open will call this function so the list will stay sane. */
    	if (!(dev->flags&IFF_UP))
    		return;
    
    	if (!netif_device_present(dev))
    
    	if (ops->ndo_set_rx_mode)
    		ops->ndo_set_rx_mode(dev);
    
    	else {
    		/* Unicast addresses changes may only happen under the rtnl,
    		 * therefore calling __dev_set_promiscuity here is safe.
    		 */
    
    		if (dev->uc.count > 0 && !dev->uc_promisc) {
    
    			__dev_set_promiscuity(dev, 1);
    			dev->uc_promisc = 1;
    
    		} else if (dev->uc.count == 0 && dev->uc_promisc) {
    
    			__dev_set_promiscuity(dev, -1);
    			dev->uc_promisc = 0;
    		}
    
    
    		if (ops->ndo_set_multicast_list)
    			ops->ndo_set_multicast_list(dev);
    
    	}
    }
    
    void dev_set_rx_mode(struct net_device *dev)
    {
    
    	netif_addr_lock_bh(dev);
    
    	__dev_set_rx_mode(dev);
    
    	netif_addr_unlock_bh(dev);
    
    /* hw addresses list handling functions */
    
    
    static int __hw_addr_add(struct netdev_hw_addr_list *list, unsigned char *addr,
    			 int addr_len, unsigned char addr_type)
    
    {
    	struct netdev_hw_addr *ha;
    	int alloc_size;
    
    	if (addr_len > MAX_ADDR_LEN)
    		return -EINVAL;
    
    
    	list_for_each_entry(ha, &list->list, list) {
    
    		if (!memcmp(ha->addr, addr, addr_len) &&
    		    ha->type == addr_type) {
    			ha->refcount++;
    			return 0;
    		}
    	}
    
    
    
    	alloc_size = sizeof(*ha);
    	if (alloc_size < L1_CACHE_BYTES)
    		alloc_size = L1_CACHE_BYTES;
    	ha = kmalloc(alloc_size, GFP_ATOMIC);
    	if (!ha)
    		return -ENOMEM;
    	memcpy(ha->addr, addr, addr_len);
    	ha->type = addr_type;
    
    	ha->refcount = 1;
    	ha->synced = false;
    
    	list_add_tail_rcu(&ha->list, &list->list);
    	list->count++;
    
    	return 0;
    }
    
    static void ha_rcu_free(struct rcu_head *head)
    {
    	struct netdev_hw_addr *ha;
    
    	ha = container_of(head, struct netdev_hw_addr, rcu_head);
    	kfree(ha);
    }
    
    
    static int __hw_addr_del(struct netdev_hw_addr_list *list, unsigned char *addr,
    			 int addr_len, unsigned char addr_type)
    
    	list_for_each_entry(ha, &list->list, list) {
    
    		if (!memcmp(ha->addr, addr, addr_len) &&
    
    		    (ha->type == addr_type || !addr_type)) {
    
    			if (--ha->refcount)
    				return 0;
    
    			list_del_rcu(&ha->list);
    			call_rcu(&ha->rcu_head, ha_rcu_free);
    
    			list->count--;
    
    static int __hw_addr_add_multiple(struct netdev_hw_addr_list *to_list,
    				  struct netdev_hw_addr_list *from_list,
    				  int addr_len,
    
    				  unsigned char addr_type)
    
    {
    	int err;
    	struct netdev_hw_addr *ha, *ha2;
    	unsigned char type;
    
    
    	list_for_each_entry(ha, &from_list->list, list) {
    
    		type = addr_type ? addr_type : ha->type;
    
    		err = __hw_addr_add(to_list, ha->addr, addr_len, type);
    
    	list_for_each_entry(ha2, &from_list->list, list) {
    
    		if (ha2 == ha)
    			break;
    		type = addr_type ? addr_type : ha2->type;
    
    		__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)
    
    	err = __hw_addr_del(&dev->uc, addr, dev->addr_len,
    			    NETDEV_HW_ADDR_T_UNICAST);
    
    		__dev_set_rx_mode(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)
    
    	err = __hw_addr_add(&dev->uc, addr, dev->addr_len,
    			    NETDEV_HW_ADDR_T_UNICAST);
    
    		__dev_set_rx_mode(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;
    }
    
    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;
    	}
    }
    
    /**
     *	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.
    
     *
     *	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;
    
    
    	ASSERT_RTNL();
    
    	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);
    	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)
    {
    
    	ASSERT_RTNL();
    
    	if (to->addr_len != from->addr_len)
    		return;
    
    	__hw_addr_unsync(&to->uc, &from->uc, to->addr_len);
    
    	__dev_set_rx_mode(to);
    
    }
    EXPORT_SYMBOL(dev_unicast_unsync);
    
    
    static void dev_unicast_flush(struct net_device *dev)
    {
    	/* rtnl_mutex must be held here */
    
    
    	__hw_addr_flush(&dev->uc);