/*
* net/core/dev_addr_lists.c - Functions for handling net device 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/list.h>
#include <linux/proc_fs.h>
/*
* General list handling functions
*/
static int __hw_addr_add_ex(struct netdev_hw_addr_list *list,
unsigned char *addr, int addr_len,
unsigned char addr_type, bool global)
{
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) {
if (global) {
/* check if addr is already used as global */
if (ha->global_use)
return 0;
else
ha->global_use = true;
}
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->global_use = global;
ha->synced = false;
list_add_tail_rcu(&ha->list, &list->list);
list->count++;
return 0;
}
static int __hw_addr_add(struct netdev_hw_addr_list *list, unsigned char *addr,
int addr_len, unsigned char addr_type)
{
return __hw_addr_add_ex(list, addr, addr_len, addr_type, false);
}
static int __hw_addr_del_ex(struct netdev_hw_addr_list *list,
unsigned char *addr, int addr_len,
unsigned char addr_type, bool global)
{
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)) {
if (global) {
if (!ha->global_use)
break;
else
ha->global_use = false;
}
if (--ha->refcount)
return 0;
list_del_rcu(&ha->list);
kfree_rcu(ha, rcu_head);
list->count--;
return 0;
}
}
return -ENOENT;
}
static int __hw_addr_del(struct netdev_hw_addr_list *list, unsigned char *addr,
int addr_len, unsigned char addr_type)
{
return __hw_addr_del_ex(list, addr, addr_len, addr_type, false);
}
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);
if (err)
goto unroll;
}
return 0;
unroll:
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);
}
return err;
}
EXPORT_SYMBOL(__hw_addr_add_multiple);
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, type);
}
}
EXPORT_SYMBOL(__hw_addr_del_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->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;
}
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->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);
}
}
}
EXPORT_SYMBOL(__hw_addr_unsync);
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;
}
EXPORT_SYMBOL(__hw_addr_flush);
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, 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;
ASSERT_RTNL();
/*
* 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 !