// 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(