1050 lines · 26.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * 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. */ #include <linux/netdevice.h>#include <linux/rtnetlink.h>#include <linux/export.h>#include <linux/list.h> #include "dev.h" /* * General list handling functions */ static int __hw_addr_insert(struct netdev_hw_addr_list *list,			    struct netdev_hw_addr *new, int addr_len){	struct rb_node **ins_point = &list->tree.rb_node, *parent = NULL;	struct netdev_hw_addr *ha; 	while (*ins_point) {		int diff; 		ha = rb_entry(*ins_point, struct netdev_hw_addr, node);		diff = memcmp(new->addr, ha->addr, addr_len);		if (diff == 0)			diff = memcmp(&new->type, &ha->type, sizeof(new->type)); 		parent = *ins_point;		if (diff < 0)			ins_point = &parent->rb_left;		else if (diff > 0)			ins_point = &parent->rb_right;		else			return -EEXIST;	} 	rb_link_node_rcu(&new->node, parent, ins_point);	rb_insert_color(&new->node, &list->tree); 	return 0;} static struct netdev_hw_addr*__hw_addr_create(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 NULL;	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; 	return ha;} 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, bool exclusive){	struct rb_node **ins_point = &list->tree.rb_node, *parent = NULL;	struct netdev_hw_addr *ha; 	if (addr_len > MAX_ADDR_LEN)		return -EINVAL; 	while (*ins_point) {		int diff; 		ha = rb_entry(*ins_point, struct netdev_hw_addr, node);		diff = memcmp(addr, ha->addr, addr_len);		if (diff == 0)			diff = memcmp(&addr_type, &ha->type, sizeof(addr_type)); 		parent = *ins_point;		if (diff < 0) {			ins_point = &parent->rb_left;		} else if (diff > 0) {			ins_point = &parent->rb_right;		} else {			if (exclusive)				return -EEXIST;			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;		}	} 	ha = __hw_addr_create(addr, addr_len, addr_type, global, sync);	if (!ha)		return -ENOMEM; 	rb_link_node(&ha->node, parent, ins_point);	rb_insert_color(&ha->node, &list->tree); 	list_add_tail_rcu(&ha->list, &list->list);	list->count++; 	return 0;} 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, false);} 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; 	rb_erase(&ha->node, &list->tree); 	list_del_rcu(&ha->list);	kfree_rcu(ha, rcu_head);	list->count--;	return 0;} static struct netdev_hw_addr *__hw_addr_lookup(struct netdev_hw_addr_list *list,					       const unsigned char *addr, int addr_len,					       unsigned char addr_type){	struct rb_node *node; 	node = list->tree.rb_node; 	while (node) {		struct netdev_hw_addr *ha = rb_entry(node, struct netdev_hw_addr, node);		int diff = memcmp(addr, ha->addr, addr_len); 		if (diff == 0 && addr_type)			diff = memcmp(&addr_type, &ha->type, sizeof(addr_type)); 		if (diff < 0)			node = node->rb_left;		else if (diff > 0)			node = node->rb_right;		else			return ha;	} 	return NULL;} 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 = __hw_addr_lookup(list, addr, addr_len, addr_type); 	if (!ha)		return -ENOENT;	return __hw_addr_del_entry(list, ha, global, sync);} 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, false);	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 destination 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 - Synchronize device's multicast list *  @list: address list to synchronize *  @dev:  device to sync *  @sync: function to call if address should be added *  @unsync: function to call if address should be removed * *  This function 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->tree = RB_ROOT;	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;	list->tree = RB_ROOT;}

Lines 1–500 of 1050.