951 lines · 24.0 KB · C
RawDownload
/* * net/core/dev_addr_lists.c - Functions for handling net device lists * Copyright (c) 2010 Jiri Pirko <jpirko@redhat.com> * * This file contains functions for working with unicast, multicast and device * addresses lists. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include <linux/netdevice.h>#include <linux/rtnetlink.h>#include <linux/export.h>#include <linux/list.h> /* * General list handling functions */ static int __hw_addr_create_ex(struct netdev_hw_addr_list *list,			       const unsigned char *addr, int addr_len,			       unsigned char addr_type, bool global,			       bool sync){	struct netdev_hw_addr *ha;	int alloc_size; 	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->global_use = global;	ha->synced = sync ? 1 : 0;	ha->sync_cnt = 0;	list_add_tail_rcu(&ha->list, &list->list);	list->count++; 	return 0;} static int __hw_addr_add_ex(struct netdev_hw_addr_list *list,			    const unsigned char *addr, int addr_len,			    unsigned char addr_type, bool global, bool sync,			    int sync_count){	struct netdev_hw_addr *ha; 	if (addr_len > MAX_ADDR_LEN)		return -EINVAL; 	list_for_each_entry(ha, &list->list, list) {		if (ha->type == addr_type &&		    !memcmp(ha->addr, addr, addr_len)) {			if (global) {				/* check if addr is already used as global */				if (ha->global_use)					return 0;				else					ha->global_use = true;			}			if (sync) {				if (ha->synced && sync_count)					return -EEXIST;				else					ha->synced++;			}			ha->refcount++;			return 0;		}	} 	return __hw_addr_create_ex(list, addr, addr_len, addr_type, global,				   sync);} static int __hw_addr_add(struct netdev_hw_addr_list *list,			 const unsigned char *addr, int addr_len,			 unsigned char addr_type){	return __hw_addr_add_ex(list, addr, addr_len, addr_type, false, false,				0);} static int __hw_addr_del_entry(struct netdev_hw_addr_list *list,			       struct netdev_hw_addr *ha, bool global,			       bool sync){	if (global && !ha->global_use)		return -ENOENT; 	if (sync && !ha->synced)		return -ENOENT; 	if (global)		ha->global_use = false; 	if (sync)		ha->synced--; 	if (--ha->refcount)		return 0;	list_del_rcu(&ha->list);	kfree_rcu(ha, rcu_head);	list->count--;	return 0;} static int __hw_addr_del_ex(struct netdev_hw_addr_list *list,			    const unsigned char *addr, int addr_len,			    unsigned char addr_type, bool global, bool sync){	struct netdev_hw_addr *ha; 	list_for_each_entry(ha, &list->list, list) {		if (!memcmp(ha->addr, addr, addr_len) &&		    (ha->type == addr_type || !addr_type))			return __hw_addr_del_entry(list, ha, global, sync);	}	return -ENOENT;} static int __hw_addr_del(struct netdev_hw_addr_list *list,			 const unsigned char *addr, int addr_len,			 unsigned char addr_type){	return __hw_addr_del_ex(list, addr, addr_len, addr_type, false, false);} static int __hw_addr_sync_one(struct netdev_hw_addr_list *to_list,			       struct netdev_hw_addr *ha,			       int addr_len){	int err; 	err = __hw_addr_add_ex(to_list, ha->addr, addr_len, ha->type,			       false, true, ha->sync_cnt);	if (err && err != -EEXIST)		return err; 	if (!err) {		ha->sync_cnt++;		ha->refcount++;	} 	return 0;} static void __hw_addr_unsync_one(struct netdev_hw_addr_list *to_list,				 struct netdev_hw_addr_list *from_list,				 struct netdev_hw_addr *ha,				 int addr_len){	int err; 	err = __hw_addr_del_ex(to_list, ha->addr, addr_len, ha->type,			       false, true);	if (err)		return;	ha->sync_cnt--;	/* address on from list is not marked synced */	__hw_addr_del_entry(from_list, ha, false, false);} static int __hw_addr_sync_multiple(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->sync_cnt == ha->refcount) {			__hw_addr_unsync_one(to_list, from_list, ha, addr_len);		} else {			err = __hw_addr_sync_one(to_list, ha, addr_len);			if (err)				break;		}	}	return err;} /* This function only works where there is a strict 1-1 relationship * between source and destionation of they synch. If you ever need to * sync addresses to more then 1 destination, you need to use * __hw_addr_sync_multiple(). */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->sync_cnt) {			err = __hw_addr_sync_one(to_list, ha, addr_len);			if (err)				break;		} else if (ha->refcount == 1)			__hw_addr_unsync_one(to_list, from_list, ha, addr_len);	}	return err;}EXPORT_SYMBOL(__hw_addr_sync); 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->sync_cnt)			__hw_addr_unsync_one(to_list, from_list, ha, addr_len);	}}EXPORT_SYMBOL(__hw_addr_unsync); /** *  __hw_addr_sync_dev - Synchonize device's multicast list *  @list: address list to syncronize *  @dev:  device to sync *  @sync: function to call if address should be added *  @unsync: function to call if address should be removed * *  This funciton is intended to be called from the ndo_set_rx_mode *  function of devices that require explicit address add/remove *  notifications.  The unsync function may be NULL in which case *  the addresses requiring removal will simply be removed without *  any notification to the device. **/int __hw_addr_sync_dev(struct netdev_hw_addr_list *list,		       struct net_device *dev,		       int (*sync)(struct net_device *, const unsigned char *),		       int (*unsync)(struct net_device *,				     const unsigned char *)){	struct netdev_hw_addr *ha, *tmp;	int err; 	/* first go through and flush out any stale entries */	list_for_each_entry_safe(ha, tmp, &list->list, list) {		if (!ha->sync_cnt || ha->refcount != 1)			continue; 		/* if unsync is defined and fails defer unsyncing address */		if (unsync && unsync(dev, ha->addr))			continue; 		ha->sync_cnt--;		__hw_addr_del_entry(list, ha, false, false);	} 	/* go through and sync new entries to the list */	list_for_each_entry_safe(ha, tmp, &list->list, list) {		if (ha->sync_cnt)			continue; 		err = sync(dev, ha->addr);		if (err)			return err; 		ha->sync_cnt++;		ha->refcount++;	} 	return 0;}EXPORT_SYMBOL(__hw_addr_sync_dev); /** *  __hw_addr_ref_sync_dev - Synchronize device's multicast address list taking *  into account references *  @list: address list to synchronize *  @dev:  device to sync *  @sync: function to call if address or reference on it should be added *  @unsync: function to call if address or some reference on it should removed * *  This function is intended to be called from the ndo_set_rx_mode *  function of devices that require explicit address or references on it *  add/remove notifications. The unsync function may be NULL in which case *  the addresses or references on it requiring removal will simply be *  removed without any notification to the device. That is responsibility of *  the driver to identify and distribute address or references on it between *  internal address tables. **/int __hw_addr_ref_sync_dev(struct netdev_hw_addr_list *list,			   struct net_device *dev,			   int (*sync)(struct net_device *,				       const unsigned char *, int),			   int (*unsync)(struct net_device *,					 const unsigned char *, int)){	struct netdev_hw_addr *ha, *tmp;	int err, ref_cnt; 	/* first go through and flush out any unsynced/stale entries */	list_for_each_entry_safe(ha, tmp, &list->list, list) {		/* sync if address is not used */		if ((ha->sync_cnt << 1) <= ha->refcount)			continue; 		/* if fails defer unsyncing address */		ref_cnt = ha->refcount - ha->sync_cnt;		if (unsync && unsync(dev, ha->addr, ref_cnt))			continue; 		ha->refcount = (ref_cnt << 1) + 1;		ha->sync_cnt = ref_cnt;		__hw_addr_del_entry(list, ha, false, false);	} 	/* go through and sync updated/new entries to the list */	list_for_each_entry_safe(ha, tmp, &list->list, list) {		/* sync if address added or reused */		if ((ha->sync_cnt << 1) >= ha->refcount)			continue; 		ref_cnt = ha->refcount - ha->sync_cnt;		err = sync(dev, ha->addr, ref_cnt);		if (err)			return err; 		ha->refcount = ref_cnt << 1;		ha->sync_cnt = ref_cnt;	} 	return 0;}EXPORT_SYMBOL(__hw_addr_ref_sync_dev); /** *  __hw_addr_ref_unsync_dev - Remove synchronized addresses and references on *  it from device *  @list: address list to remove synchronized addresses (references on it) from *  @dev:  device to sync *  @unsync: function to call if address and references on it should be removed * *  Remove all addresses that were added to the device by *  __hw_addr_ref_sync_dev(). This function is intended to be called from the *  ndo_stop or ndo_open functions on devices that require explicit address (or *  references on it) add/remove notifications. If the unsync function pointer *  is NULL then this function can be used to just reset the sync_cnt for the *  addresses in the list. **/void __hw_addr_ref_unsync_dev(struct netdev_hw_addr_list *list,			      struct net_device *dev,			      int (*unsync)(struct net_device *,					    const unsigned char *, int)){	struct netdev_hw_addr *ha, *tmp; 	list_for_each_entry_safe(ha, tmp, &list->list, list) {		if (!ha->sync_cnt)			continue; 		/* if fails defer unsyncing address */		if (unsync && unsync(dev, ha->addr, ha->sync_cnt))			continue; 		ha->refcount -= ha->sync_cnt - 1;		ha->sync_cnt = 0;		__hw_addr_del_entry(list, ha, false, false);	}}EXPORT_SYMBOL(__hw_addr_ref_unsync_dev); /** *  __hw_addr_unsync_dev - Remove synchronized addresses from device *  @list: address list to remove synchronized addresses from *  @dev:  device to sync *  @unsync: function to call if address should be removed * *  Remove all addresses that were added to the device by __hw_addr_sync_dev(). *  This function is intended to be called from the ndo_stop or ndo_open *  functions on devices that require explicit address add/remove *  notifications.  If the unsync function pointer is NULL then this function *  can be used to just reset the sync_cnt for the addresses in the list. **/void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,			  struct net_device *dev,			  int (*unsync)(struct net_device *,					const unsigned char *)){	struct netdev_hw_addr *ha, *tmp; 	list_for_each_entry_safe(ha, tmp, &list->list, list) {		if (!ha->sync_cnt)			continue; 		/* if unsync is defined and fails defer unsyncing address */		if (unsync && unsync(dev, ha->addr))			continue; 		ha->sync_cnt--;		__hw_addr_del_entry(list, ha, false, false);	}}EXPORT_SYMBOL(__hw_addr_unsync_dev); static void __hw_addr_flush(struct netdev_hw_addr_list *list){	struct netdev_hw_addr *ha, *tmp; 	list_for_each_entry_safe(ha, tmp, &list->list, list) {		list_del_rcu(&ha->list);		kfree_rcu(ha, rcu_head);	}	list->count = 0;} void __hw_addr_init(struct netdev_hw_addr_list *list){	INIT_LIST_HEAD(&list->list);	list->count = 0;}EXPORT_SYMBOL(__hw_addr_init); /* * Device addresses handling functions */ /** *	dev_addr_flush - Flush device address list *	@dev: device * *	Flush device address list and reset ->dev_addr. * *	The caller must hold the rtnl_mutex. */void dev_addr_flush(struct net_device *dev){	/* rtnl_mutex must be held here */ 	__hw_addr_flush(&dev->dev_addrs);	dev->dev_addr = NULL;}EXPORT_SYMBOL(dev_addr_flush); /** *	dev_addr_init - Init device address list *	@dev: device * *	Init device address list and create the first element, *	used by ->dev_addr. * *	The caller must hold the rtnl_mutex. */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);	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;}EXPORT_SYMBOL(dev_addr_init); /** *	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, const unsigned char *addr,		 unsigned char addr_type){	int err; 	ASSERT_RTNL(); 

Lines 1–500 of 951.