|
@@ -3434,6 +3434,252 @@ void dev_set_rx_mode(struct net_device *dev)
|
|
netif_addr_unlock_bh(dev);
|
|
netif_addr_unlock_bh(dev);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* hw addresses list handling functions */
|
|
|
|
+
|
|
|
|
+static int __hw_addr_add(struct list_head *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;
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+ list_add_tail_rcu(&ha->list, list);
|
|
|
|
+ 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_ii(struct list_head *list, unsigned char *addr,
|
|
|
|
+ int addr_len, unsigned char addr_type,
|
|
|
|
+ int ignore_index)
|
|
|
|
+{
|
|
|
|
+ struct netdev_hw_addr *ha;
|
|
|
|
+ int i = 0;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry(ha, list, list) {
|
|
|
|
+ if (i++ != ignore_index &&
|
|
|
|
+ !memcmp(ha->addr, addr, addr_len) &&
|
|
|
|
+ (ha->type == addr_type || !addr_type)) {
|
|
|
|
+ list_del_rcu(&ha->list);
|
|
|
|
+ call_rcu(&ha->rcu_head, ha_rcu_free);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return -ENOENT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int __hw_addr_add_multiple_ii(struct list_head *to_list,
|
|
|
|
+ struct list_head *from_list,
|
|
|
|
+ int addr_len, unsigned char addr_type,
|
|
|
|
+ int ignore_index)
|
|
|
|
+{
|
|
|
|
+ int err;
|
|
|
|
+ struct netdev_hw_addr *ha, *ha2;
|
|
|
|
+ unsigned char type;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry(ha, from_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) {
|
|
|
|
+ if (ha2 == ha)
|
|
|
|
+ break;
|
|
|
|
+ type = addr_type ? addr_type : ha2->type;
|
|
|
|
+ __hw_addr_del_ii(to_list, ha2->addr, addr_len, type,
|
|
|
|
+ ignore_index);
|
|
|
|
+ }
|
|
|
|
+ return err;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __hw_addr_del_multiple_ii(struct list_head *to_list,
|
|
|
|
+ struct list_head *from_list,
|
|
|
|
+ int addr_len, unsigned char addr_type,
|
|
|
|
+ int ignore_index)
|
|
|
|
+{
|
|
|
|
+ struct netdev_hw_addr *ha;
|
|
|
|
+ unsigned char type;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry(ha, from_list, list) {
|
|
|
|
+ type = addr_type ? addr_type : ha->type;
|
|
|
|
+ __hw_addr_del_ii(to_list, ha->addr, addr_len, addr_type,
|
|
|
|
+ ignore_index);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __hw_addr_flush(struct list_head *list)
|
|
|
|
+{
|
|
|
|
+ struct netdev_hw_addr *ha, *tmp;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry_safe(ha, tmp, list, list) {
|
|
|
|
+ list_del_rcu(&ha->list);
|
|
|
|
+ call_rcu(&ha->rcu_head, ha_rcu_free);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Device addresses handling functions */
|
|
|
|
+
|
|
|
|
+static void dev_addr_flush(struct net_device *dev)
|
|
|
|
+{
|
|
|
|
+ /* rtnl_mutex must be held here */
|
|
|
|
+
|
|
|
|
+ __hw_addr_flush(&dev->dev_addr_list);
|
|
|
|
+ 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 */
|
|
|
|
+
|
|
|
|
+ INIT_LIST_HEAD(&dev->dev_addr_list);
|
|
|
|
+ memset(addr, 0, sizeof(*addr));
|
|
|
|
+ err = __hw_addr_add(&dev->dev_addr_list, 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_addr_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_addr_list, 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;
|
|
|
|
+
|
|
|
|
+ ASSERT_RTNL();
|
|
|
|
+
|
|
|
|
+ err = __hw_addr_del_ii(&dev->dev_addr_list, addr, dev->addr_len,
|
|
|
|
+ addr_type, 0);
|
|
|
|
+ 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_ii(&to_dev->dev_addr_list,
|
|
|
|
+ &from_dev->dev_addr_list,
|
|
|
|
+ to_dev->addr_len, addr_type, 0);
|
|
|
|
+ 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_ii(&to_dev->dev_addr_list,
|
|
|
|
+ &from_dev->dev_addr_list,
|
|
|
|
+ to_dev->addr_len, addr_type, 0);
|
|
|
|
+ call_netdevice_notifiers(NETDEV_CHANGEADDR, to_dev);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL(dev_addr_del_multiple);
|
|
|
|
+
|
|
|
|
+/* unicast and multicast addresses handling functions */
|
|
|
|
+
|
|
int __dev_addr_delete(struct dev_addr_list **list, int *count,
|
|
int __dev_addr_delete(struct dev_addr_list **list, int *count,
|
|
void *addr, int alen, int glbl)
|
|
void *addr, int alen, int glbl)
|
|
{
|
|
{
|
|
@@ -4776,6 +5022,7 @@ struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
|
|
|
|
|
|
dev->gso_max_size = GSO_MAX_SIZE;
|
|
dev->gso_max_size = GSO_MAX_SIZE;
|
|
|
|
|
|
|
|
+ dev_addr_init(dev);
|
|
netdev_init_queues(dev);
|
|
netdev_init_queues(dev);
|
|
|
|
|
|
INIT_LIST_HEAD(&dev->napi_list);
|
|
INIT_LIST_HEAD(&dev->napi_list);
|
|
@@ -4801,6 +5048,9 @@ void free_netdev(struct net_device *dev)
|
|
|
|
|
|
kfree(dev->_tx);
|
|
kfree(dev->_tx);
|
|
|
|
|
|
|
|
+ /* Flush device addresses */
|
|
|
|
+ dev_addr_flush(dev);
|
|
|
|
+
|
|
list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
|
|
list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
|
|
netif_napi_del(p);
|
|
netif_napi_del(p);
|
|
|
|
|