162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci// Copyright (c) 2020 Facebook Inc. 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <linux/ethtool_netlink.h> 562306a36Sopenharmony_ci#include <linux/netdevice.h> 662306a36Sopenharmony_ci#include <linux/slab.h> 762306a36Sopenharmony_ci#include <linux/types.h> 862306a36Sopenharmony_ci#include <linux/workqueue.h> 962306a36Sopenharmony_ci#include <net/udp_tunnel.h> 1062306a36Sopenharmony_ci#include <net/vxlan.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cienum udp_tunnel_nic_table_entry_flags { 1362306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_ADD = BIT(0), 1462306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_DEL = BIT(1), 1562306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_OP_FAIL = BIT(2), 1662306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_FROZEN = BIT(3), 1762306a36Sopenharmony_ci}; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct udp_tunnel_nic_table_entry { 2062306a36Sopenharmony_ci __be16 port; 2162306a36Sopenharmony_ci u8 type; 2262306a36Sopenharmony_ci u8 flags; 2362306a36Sopenharmony_ci u16 use_cnt; 2462306a36Sopenharmony_ci#define UDP_TUNNEL_NIC_USE_CNT_MAX U16_MAX 2562306a36Sopenharmony_ci u8 hw_priv; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/** 2962306a36Sopenharmony_ci * struct udp_tunnel_nic - UDP tunnel port offload state 3062306a36Sopenharmony_ci * @work: async work for talking to hardware from process context 3162306a36Sopenharmony_ci * @dev: netdev pointer 3262306a36Sopenharmony_ci * @need_sync: at least one port start changed 3362306a36Sopenharmony_ci * @need_replay: space was freed, we need a replay of all ports 3462306a36Sopenharmony_ci * @work_pending: @work is currently scheduled 3562306a36Sopenharmony_ci * @n_tables: number of tables under @entries 3662306a36Sopenharmony_ci * @missed: bitmap of tables which overflown 3762306a36Sopenharmony_ci * @entries: table of tables of ports currently offloaded 3862306a36Sopenharmony_ci */ 3962306a36Sopenharmony_cistruct udp_tunnel_nic { 4062306a36Sopenharmony_ci struct work_struct work; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci struct net_device *dev; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci u8 need_sync:1; 4562306a36Sopenharmony_ci u8 need_replay:1; 4662306a36Sopenharmony_ci u8 work_pending:1; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci unsigned int n_tables; 4962306a36Sopenharmony_ci unsigned long missed; 5062306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry **entries; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* We ensure all work structs are done using driver state, but not the code. 5462306a36Sopenharmony_ci * We need a workqueue we can flush before module gets removed. 5562306a36Sopenharmony_ci */ 5662306a36Sopenharmony_cistatic struct workqueue_struct *udp_tunnel_nic_workqueue; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic const char *udp_tunnel_nic_tunnel_type_name(unsigned int type) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci switch (type) { 6162306a36Sopenharmony_ci case UDP_TUNNEL_TYPE_VXLAN: 6262306a36Sopenharmony_ci return "vxlan"; 6362306a36Sopenharmony_ci case UDP_TUNNEL_TYPE_GENEVE: 6462306a36Sopenharmony_ci return "geneve"; 6562306a36Sopenharmony_ci case UDP_TUNNEL_TYPE_VXLAN_GPE: 6662306a36Sopenharmony_ci return "vxlan-gpe"; 6762306a36Sopenharmony_ci default: 6862306a36Sopenharmony_ci return "unknown"; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic bool 7362306a36Sopenharmony_ciudp_tunnel_nic_entry_is_free(struct udp_tunnel_nic_table_entry *entry) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci return entry->use_cnt == 0 && !entry->flags; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic bool 7962306a36Sopenharmony_ciudp_tunnel_nic_entry_is_present(struct udp_tunnel_nic_table_entry *entry) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci return entry->use_cnt && !(entry->flags & ~UDP_TUNNEL_NIC_ENTRY_FROZEN); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic bool 8562306a36Sopenharmony_ciudp_tunnel_nic_entry_is_frozen(struct udp_tunnel_nic_table_entry *entry) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci return entry->flags & UDP_TUNNEL_NIC_ENTRY_FROZEN; 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic void 9162306a36Sopenharmony_ciudp_tunnel_nic_entry_freeze_used(struct udp_tunnel_nic_table_entry *entry) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_free(entry)) 9462306a36Sopenharmony_ci entry->flags |= UDP_TUNNEL_NIC_ENTRY_FROZEN; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic void 9862306a36Sopenharmony_ciudp_tunnel_nic_entry_unfreeze(struct udp_tunnel_nic_table_entry *entry) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci entry->flags &= ~UDP_TUNNEL_NIC_ENTRY_FROZEN; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic bool 10462306a36Sopenharmony_ciudp_tunnel_nic_entry_is_queued(struct udp_tunnel_nic_table_entry *entry) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci return entry->flags & (UDP_TUNNEL_NIC_ENTRY_ADD | 10762306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_DEL); 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic void 11162306a36Sopenharmony_ciudp_tunnel_nic_entry_queue(struct udp_tunnel_nic *utn, 11262306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry, 11362306a36Sopenharmony_ci unsigned int flag) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci entry->flags |= flag; 11662306a36Sopenharmony_ci utn->need_sync = 1; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic void 12062306a36Sopenharmony_ciudp_tunnel_nic_ti_from_entry(struct udp_tunnel_nic_table_entry *entry, 12162306a36Sopenharmony_ci struct udp_tunnel_info *ti) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci memset(ti, 0, sizeof(*ti)); 12462306a36Sopenharmony_ci ti->port = entry->port; 12562306a36Sopenharmony_ci ti->type = entry->type; 12662306a36Sopenharmony_ci ti->hw_priv = entry->hw_priv; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic bool 13062306a36Sopenharmony_ciudp_tunnel_nic_is_empty(struct net_device *dev, struct udp_tunnel_nic *utn) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 13362306a36Sopenharmony_ci unsigned int i, j; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 13662306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) 13762306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_free(&utn->entries[i][j])) 13862306a36Sopenharmony_ci return false; 13962306a36Sopenharmony_ci return true; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic bool 14362306a36Sopenharmony_ciudp_tunnel_nic_should_replay(struct net_device *dev, struct udp_tunnel_nic *utn) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci const struct udp_tunnel_nic_table_info *table; 14662306a36Sopenharmony_ci unsigned int i, j; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (!utn->missed) 14962306a36Sopenharmony_ci return false; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) { 15262306a36Sopenharmony_ci table = &dev->udp_tunnel_nic_info->tables[i]; 15362306a36Sopenharmony_ci if (!test_bit(i, &utn->missed)) 15462306a36Sopenharmony_ci continue; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci for (j = 0; j < table->n_entries; j++) 15762306a36Sopenharmony_ci if (udp_tunnel_nic_entry_is_free(&utn->entries[i][j])) 15862306a36Sopenharmony_ci return true; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return false; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void 16562306a36Sopenharmony_ci__udp_tunnel_nic_get_port(struct net_device *dev, unsigned int table, 16662306a36Sopenharmony_ci unsigned int idx, struct udp_tunnel_info *ti) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry; 16962306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 17262306a36Sopenharmony_ci entry = &utn->entries[table][idx]; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (entry->use_cnt) 17562306a36Sopenharmony_ci udp_tunnel_nic_ti_from_entry(entry, ti); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic void 17962306a36Sopenharmony_ci__udp_tunnel_nic_set_port_priv(struct net_device *dev, unsigned int table, 18062306a36Sopenharmony_ci unsigned int idx, u8 priv) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci dev->udp_tunnel_nic->entries[table][idx].hw_priv = priv; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic void 18662306a36Sopenharmony_ciudp_tunnel_nic_entry_update_done(struct udp_tunnel_nic_table_entry *entry, 18762306a36Sopenharmony_ci int err) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci bool dodgy = entry->flags & UDP_TUNNEL_NIC_ENTRY_OP_FAIL; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci WARN_ON_ONCE(entry->flags & UDP_TUNNEL_NIC_ENTRY_ADD && 19262306a36Sopenharmony_ci entry->flags & UDP_TUNNEL_NIC_ENTRY_DEL); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (entry->flags & UDP_TUNNEL_NIC_ENTRY_ADD && 19562306a36Sopenharmony_ci (!err || (err == -EEXIST && dodgy))) 19662306a36Sopenharmony_ci entry->flags &= ~UDP_TUNNEL_NIC_ENTRY_ADD; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (entry->flags & UDP_TUNNEL_NIC_ENTRY_DEL && 19962306a36Sopenharmony_ci (!err || (err == -ENOENT && dodgy))) 20062306a36Sopenharmony_ci entry->flags &= ~UDP_TUNNEL_NIC_ENTRY_DEL; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci if (!err) 20362306a36Sopenharmony_ci entry->flags &= ~UDP_TUNNEL_NIC_ENTRY_OP_FAIL; 20462306a36Sopenharmony_ci else 20562306a36Sopenharmony_ci entry->flags |= UDP_TUNNEL_NIC_ENTRY_OP_FAIL; 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic void 20962306a36Sopenharmony_ciudp_tunnel_nic_device_sync_one(struct net_device *dev, 21062306a36Sopenharmony_ci struct udp_tunnel_nic *utn, 21162306a36Sopenharmony_ci unsigned int table, unsigned int idx) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry; 21462306a36Sopenharmony_ci struct udp_tunnel_info ti; 21562306a36Sopenharmony_ci int err; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci entry = &utn->entries[table][idx]; 21862306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_queued(entry)) 21962306a36Sopenharmony_ci return; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci udp_tunnel_nic_ti_from_entry(entry, &ti); 22262306a36Sopenharmony_ci if (entry->flags & UDP_TUNNEL_NIC_ENTRY_ADD) 22362306a36Sopenharmony_ci err = dev->udp_tunnel_nic_info->set_port(dev, table, idx, &ti); 22462306a36Sopenharmony_ci else 22562306a36Sopenharmony_ci err = dev->udp_tunnel_nic_info->unset_port(dev, table, idx, 22662306a36Sopenharmony_ci &ti); 22762306a36Sopenharmony_ci udp_tunnel_nic_entry_update_done(entry, err); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (err) 23062306a36Sopenharmony_ci netdev_warn(dev, 23162306a36Sopenharmony_ci "UDP tunnel port sync failed port %d type %s: %d\n", 23262306a36Sopenharmony_ci be16_to_cpu(entry->port), 23362306a36Sopenharmony_ci udp_tunnel_nic_tunnel_type_name(entry->type), 23462306a36Sopenharmony_ci err); 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic void 23862306a36Sopenharmony_ciudp_tunnel_nic_device_sync_by_port(struct net_device *dev, 23962306a36Sopenharmony_ci struct udp_tunnel_nic *utn) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 24262306a36Sopenharmony_ci unsigned int i, j; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 24562306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) 24662306a36Sopenharmony_ci udp_tunnel_nic_device_sync_one(dev, utn, i, j); 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic void 25062306a36Sopenharmony_ciudp_tunnel_nic_device_sync_by_table(struct net_device *dev, 25162306a36Sopenharmony_ci struct udp_tunnel_nic *utn) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 25462306a36Sopenharmony_ci unsigned int i, j; 25562306a36Sopenharmony_ci int err; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) { 25862306a36Sopenharmony_ci /* Find something that needs sync in this table */ 25962306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) 26062306a36Sopenharmony_ci if (udp_tunnel_nic_entry_is_queued(&utn->entries[i][j])) 26162306a36Sopenharmony_ci break; 26262306a36Sopenharmony_ci if (j == info->tables[i].n_entries) 26362306a36Sopenharmony_ci continue; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci err = info->sync_table(dev, i); 26662306a36Sopenharmony_ci if (err) 26762306a36Sopenharmony_ci netdev_warn(dev, "UDP tunnel port sync failed for table %d: %d\n", 26862306a36Sopenharmony_ci i, err); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) { 27162306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci entry = &utn->entries[i][j]; 27462306a36Sopenharmony_ci if (udp_tunnel_nic_entry_is_queued(entry)) 27562306a36Sopenharmony_ci udp_tunnel_nic_entry_update_done(entry, err); 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic void 28162306a36Sopenharmony_ci__udp_tunnel_nic_device_sync(struct net_device *dev, struct udp_tunnel_nic *utn) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci if (!utn->need_sync) 28462306a36Sopenharmony_ci return; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (dev->udp_tunnel_nic_info->sync_table) 28762306a36Sopenharmony_ci udp_tunnel_nic_device_sync_by_table(dev, utn); 28862306a36Sopenharmony_ci else 28962306a36Sopenharmony_ci udp_tunnel_nic_device_sync_by_port(dev, utn); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci utn->need_sync = 0; 29262306a36Sopenharmony_ci /* Can't replay directly here, in case we come from the tunnel driver's 29362306a36Sopenharmony_ci * notification - trying to replay may deadlock inside tunnel driver. 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci utn->need_replay = udp_tunnel_nic_should_replay(dev, utn); 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cistatic void 29962306a36Sopenharmony_ciudp_tunnel_nic_device_sync(struct net_device *dev, struct udp_tunnel_nic *utn) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 30262306a36Sopenharmony_ci bool may_sleep; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci if (!utn->need_sync) 30562306a36Sopenharmony_ci return; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci /* Drivers which sleep in the callback need to update from 30862306a36Sopenharmony_ci * the workqueue, if we come from the tunnel driver's notification. 30962306a36Sopenharmony_ci */ 31062306a36Sopenharmony_ci may_sleep = info->flags & UDP_TUNNEL_NIC_INFO_MAY_SLEEP; 31162306a36Sopenharmony_ci if (!may_sleep) 31262306a36Sopenharmony_ci __udp_tunnel_nic_device_sync(dev, utn); 31362306a36Sopenharmony_ci if (may_sleep || utn->need_replay) { 31462306a36Sopenharmony_ci queue_work(udp_tunnel_nic_workqueue, &utn->work); 31562306a36Sopenharmony_ci utn->work_pending = 1; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic bool 32062306a36Sopenharmony_ciudp_tunnel_nic_table_is_capable(const struct udp_tunnel_nic_table_info *table, 32162306a36Sopenharmony_ci struct udp_tunnel_info *ti) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci return table->tunnel_types & ti->type; 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_cistatic bool 32762306a36Sopenharmony_ciudp_tunnel_nic_is_capable(struct net_device *dev, struct udp_tunnel_nic *utn, 32862306a36Sopenharmony_ci struct udp_tunnel_info *ti) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 33162306a36Sopenharmony_ci unsigned int i; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci /* Special case IPv4-only NICs */ 33462306a36Sopenharmony_ci if (info->flags & UDP_TUNNEL_NIC_INFO_IPV4_ONLY && 33562306a36Sopenharmony_ci ti->sa_family != AF_INET) 33662306a36Sopenharmony_ci return false; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 33962306a36Sopenharmony_ci if (udp_tunnel_nic_table_is_capable(&info->tables[i], ti)) 34062306a36Sopenharmony_ci return true; 34162306a36Sopenharmony_ci return false; 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_cistatic int 34562306a36Sopenharmony_ciudp_tunnel_nic_has_collision(struct net_device *dev, struct udp_tunnel_nic *utn, 34662306a36Sopenharmony_ci struct udp_tunnel_info *ti) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 34962306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry; 35062306a36Sopenharmony_ci unsigned int i, j; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 35362306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) { 35462306a36Sopenharmony_ci entry = &utn->entries[i][j]; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_free(entry) && 35762306a36Sopenharmony_ci entry->port == ti->port && 35862306a36Sopenharmony_ci entry->type != ti->type) { 35962306a36Sopenharmony_ci __set_bit(i, &utn->missed); 36062306a36Sopenharmony_ci return true; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci return false; 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_cistatic void 36762306a36Sopenharmony_ciudp_tunnel_nic_entry_adj(struct udp_tunnel_nic *utn, 36862306a36Sopenharmony_ci unsigned int table, unsigned int idx, int use_cnt_adj) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry = &utn->entries[table][idx]; 37162306a36Sopenharmony_ci bool dodgy = entry->flags & UDP_TUNNEL_NIC_ENTRY_OP_FAIL; 37262306a36Sopenharmony_ci unsigned int from, to; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci WARN_ON(entry->use_cnt + (u32)use_cnt_adj > U16_MAX); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci /* If not going from used to unused or vice versa - all done. 37762306a36Sopenharmony_ci * For dodgy entries make sure we try to sync again (queue the entry). 37862306a36Sopenharmony_ci */ 37962306a36Sopenharmony_ci entry->use_cnt += use_cnt_adj; 38062306a36Sopenharmony_ci if (!dodgy && !entry->use_cnt == !(entry->use_cnt - use_cnt_adj)) 38162306a36Sopenharmony_ci return; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci /* Cancel the op before it was sent to the device, if possible, 38462306a36Sopenharmony_ci * otherwise we'd need to take special care to issue commands 38562306a36Sopenharmony_ci * in the same order the ports arrived. 38662306a36Sopenharmony_ci */ 38762306a36Sopenharmony_ci if (use_cnt_adj < 0) { 38862306a36Sopenharmony_ci from = UDP_TUNNEL_NIC_ENTRY_ADD; 38962306a36Sopenharmony_ci to = UDP_TUNNEL_NIC_ENTRY_DEL; 39062306a36Sopenharmony_ci } else { 39162306a36Sopenharmony_ci from = UDP_TUNNEL_NIC_ENTRY_DEL; 39262306a36Sopenharmony_ci to = UDP_TUNNEL_NIC_ENTRY_ADD; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci if (entry->flags & from) { 39662306a36Sopenharmony_ci entry->flags &= ~from; 39762306a36Sopenharmony_ci if (!dodgy) 39862306a36Sopenharmony_ci return; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci udp_tunnel_nic_entry_queue(utn, entry, to); 40262306a36Sopenharmony_ci} 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cistatic bool 40562306a36Sopenharmony_ciudp_tunnel_nic_entry_try_adj(struct udp_tunnel_nic *utn, 40662306a36Sopenharmony_ci unsigned int table, unsigned int idx, 40762306a36Sopenharmony_ci struct udp_tunnel_info *ti, int use_cnt_adj) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry = &utn->entries[table][idx]; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci if (udp_tunnel_nic_entry_is_free(entry) || 41262306a36Sopenharmony_ci entry->port != ti->port || 41362306a36Sopenharmony_ci entry->type != ti->type) 41462306a36Sopenharmony_ci return false; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci if (udp_tunnel_nic_entry_is_frozen(entry)) 41762306a36Sopenharmony_ci return true; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci udp_tunnel_nic_entry_adj(utn, table, idx, use_cnt_adj); 42062306a36Sopenharmony_ci return true; 42162306a36Sopenharmony_ci} 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci/* Try to find existing matching entry and adjust its use count, instead of 42462306a36Sopenharmony_ci * adding a new one. Returns true if entry was found. In case of delete the 42562306a36Sopenharmony_ci * entry may have gotten removed in the process, in which case it will be 42662306a36Sopenharmony_ci * queued for removal. 42762306a36Sopenharmony_ci */ 42862306a36Sopenharmony_cistatic bool 42962306a36Sopenharmony_ciudp_tunnel_nic_try_existing(struct net_device *dev, struct udp_tunnel_nic *utn, 43062306a36Sopenharmony_ci struct udp_tunnel_info *ti, int use_cnt_adj) 43162306a36Sopenharmony_ci{ 43262306a36Sopenharmony_ci const struct udp_tunnel_nic_table_info *table; 43362306a36Sopenharmony_ci unsigned int i, j; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) { 43662306a36Sopenharmony_ci table = &dev->udp_tunnel_nic_info->tables[i]; 43762306a36Sopenharmony_ci if (!udp_tunnel_nic_table_is_capable(table, ti)) 43862306a36Sopenharmony_ci continue; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci for (j = 0; j < table->n_entries; j++) 44162306a36Sopenharmony_ci if (udp_tunnel_nic_entry_try_adj(utn, i, j, ti, 44262306a36Sopenharmony_ci use_cnt_adj)) 44362306a36Sopenharmony_ci return true; 44462306a36Sopenharmony_ci } 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci return false; 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_cistatic bool 45062306a36Sopenharmony_ciudp_tunnel_nic_add_existing(struct net_device *dev, struct udp_tunnel_nic *utn, 45162306a36Sopenharmony_ci struct udp_tunnel_info *ti) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci return udp_tunnel_nic_try_existing(dev, utn, ti, +1); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic bool 45762306a36Sopenharmony_ciudp_tunnel_nic_del_existing(struct net_device *dev, struct udp_tunnel_nic *utn, 45862306a36Sopenharmony_ci struct udp_tunnel_info *ti) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci return udp_tunnel_nic_try_existing(dev, utn, ti, -1); 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_cistatic bool 46462306a36Sopenharmony_ciudp_tunnel_nic_add_new(struct net_device *dev, struct udp_tunnel_nic *utn, 46562306a36Sopenharmony_ci struct udp_tunnel_info *ti) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci const struct udp_tunnel_nic_table_info *table; 46862306a36Sopenharmony_ci unsigned int i, j; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) { 47162306a36Sopenharmony_ci table = &dev->udp_tunnel_nic_info->tables[i]; 47262306a36Sopenharmony_ci if (!udp_tunnel_nic_table_is_capable(table, ti)) 47362306a36Sopenharmony_ci continue; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci for (j = 0; j < table->n_entries; j++) { 47662306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci entry = &utn->entries[i][j]; 47962306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_free(entry)) 48062306a36Sopenharmony_ci continue; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci entry->port = ti->port; 48362306a36Sopenharmony_ci entry->type = ti->type; 48462306a36Sopenharmony_ci entry->use_cnt = 1; 48562306a36Sopenharmony_ci udp_tunnel_nic_entry_queue(utn, entry, 48662306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_ADD); 48762306a36Sopenharmony_ci return true; 48862306a36Sopenharmony_ci } 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci /* The different table may still fit this port in, but there 49162306a36Sopenharmony_ci * are no devices currently which have multiple tables accepting 49262306a36Sopenharmony_ci * the same tunnel type, and false positives are okay. 49362306a36Sopenharmony_ci */ 49462306a36Sopenharmony_ci __set_bit(i, &utn->missed); 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci return false; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic void 50162306a36Sopenharmony_ci__udp_tunnel_nic_add_port(struct net_device *dev, struct udp_tunnel_info *ti) 50262306a36Sopenharmony_ci{ 50362306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 50462306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 50762306a36Sopenharmony_ci if (!utn) 50862306a36Sopenharmony_ci return; 50962306a36Sopenharmony_ci if (!netif_running(dev) && info->flags & UDP_TUNNEL_NIC_INFO_OPEN_ONLY) 51062306a36Sopenharmony_ci return; 51162306a36Sopenharmony_ci if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN && 51262306a36Sopenharmony_ci ti->port == htons(IANA_VXLAN_UDP_PORT)) { 51362306a36Sopenharmony_ci if (ti->type != UDP_TUNNEL_TYPE_VXLAN) 51462306a36Sopenharmony_ci netdev_warn(dev, "device assumes port 4789 will be used by vxlan tunnels\n"); 51562306a36Sopenharmony_ci return; 51662306a36Sopenharmony_ci } 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci if (!udp_tunnel_nic_is_capable(dev, utn, ti)) 51962306a36Sopenharmony_ci return; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci /* It may happen that a tunnel of one type is removed and different 52262306a36Sopenharmony_ci * tunnel type tries to reuse its port before the device was informed. 52362306a36Sopenharmony_ci * Rely on utn->missed to re-add this port later. 52462306a36Sopenharmony_ci */ 52562306a36Sopenharmony_ci if (udp_tunnel_nic_has_collision(dev, utn, ti)) 52662306a36Sopenharmony_ci return; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci if (!udp_tunnel_nic_add_existing(dev, utn, ti)) 52962306a36Sopenharmony_ci udp_tunnel_nic_add_new(dev, utn, ti); 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci udp_tunnel_nic_device_sync(dev, utn); 53262306a36Sopenharmony_ci} 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_cistatic void 53562306a36Sopenharmony_ci__udp_tunnel_nic_del_port(struct net_device *dev, struct udp_tunnel_info *ti) 53662306a36Sopenharmony_ci{ 53762306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 54062306a36Sopenharmony_ci if (!utn) 54162306a36Sopenharmony_ci return; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci if (!udp_tunnel_nic_is_capable(dev, utn, ti)) 54462306a36Sopenharmony_ci return; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci udp_tunnel_nic_del_existing(dev, utn, ti); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci udp_tunnel_nic_device_sync(dev, utn); 54962306a36Sopenharmony_ci} 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_cistatic void __udp_tunnel_nic_reset_ntf(struct net_device *dev) 55262306a36Sopenharmony_ci{ 55362306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 55462306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 55562306a36Sopenharmony_ci unsigned int i, j; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci ASSERT_RTNL(); 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 56062306a36Sopenharmony_ci if (!utn) 56162306a36Sopenharmony_ci return; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci utn->need_sync = false; 56462306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 56562306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) { 56662306a36Sopenharmony_ci struct udp_tunnel_nic_table_entry *entry; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci entry = &utn->entries[i][j]; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci entry->flags &= ~(UDP_TUNNEL_NIC_ENTRY_DEL | 57162306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_OP_FAIL); 57262306a36Sopenharmony_ci /* We don't release rtnl across ops */ 57362306a36Sopenharmony_ci WARN_ON(entry->flags & UDP_TUNNEL_NIC_ENTRY_FROZEN); 57462306a36Sopenharmony_ci if (!entry->use_cnt) 57562306a36Sopenharmony_ci continue; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci udp_tunnel_nic_entry_queue(utn, entry, 57862306a36Sopenharmony_ci UDP_TUNNEL_NIC_ENTRY_ADD); 57962306a36Sopenharmony_ci } 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci __udp_tunnel_nic_device_sync(dev, utn); 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_cistatic size_t 58562306a36Sopenharmony_ci__udp_tunnel_nic_dump_size(struct net_device *dev, unsigned int table) 58662306a36Sopenharmony_ci{ 58762306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 58862306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 58962306a36Sopenharmony_ci unsigned int j; 59062306a36Sopenharmony_ci size_t size; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 59362306a36Sopenharmony_ci if (!utn) 59462306a36Sopenharmony_ci return 0; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci size = 0; 59762306a36Sopenharmony_ci for (j = 0; j < info->tables[table].n_entries; j++) { 59862306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_present(&utn->entries[table][j])) 59962306a36Sopenharmony_ci continue; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci size += nla_total_size(0) + /* _TABLE_ENTRY */ 60262306a36Sopenharmony_ci nla_total_size(sizeof(__be16)) + /* _ENTRY_PORT */ 60362306a36Sopenharmony_ci nla_total_size(sizeof(u32)); /* _ENTRY_TYPE */ 60462306a36Sopenharmony_ci } 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci return size; 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic int 61062306a36Sopenharmony_ci__udp_tunnel_nic_dump_write(struct net_device *dev, unsigned int table, 61162306a36Sopenharmony_ci struct sk_buff *skb) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 61462306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 61562306a36Sopenharmony_ci struct nlattr *nest; 61662306a36Sopenharmony_ci unsigned int j; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 61962306a36Sopenharmony_ci if (!utn) 62062306a36Sopenharmony_ci return 0; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci for (j = 0; j < info->tables[table].n_entries; j++) { 62362306a36Sopenharmony_ci if (!udp_tunnel_nic_entry_is_present(&utn->entries[table][j])) 62462306a36Sopenharmony_ci continue; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci nest = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY); 62762306a36Sopenharmony_ci if (!nest) 62862306a36Sopenharmony_ci return -EMSGSIZE; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci if (nla_put_be16(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT, 63162306a36Sopenharmony_ci utn->entries[table][j].port) || 63262306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE, 63362306a36Sopenharmony_ci ilog2(utn->entries[table][j].type))) 63462306a36Sopenharmony_ci goto err_cancel; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci nla_nest_end(skb, nest); 63762306a36Sopenharmony_ci } 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci return 0; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_cierr_cancel: 64262306a36Sopenharmony_ci nla_nest_cancel(skb, nest); 64362306a36Sopenharmony_ci return -EMSGSIZE; 64462306a36Sopenharmony_ci} 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_cistatic const struct udp_tunnel_nic_ops __udp_tunnel_nic_ops = { 64762306a36Sopenharmony_ci .get_port = __udp_tunnel_nic_get_port, 64862306a36Sopenharmony_ci .set_port_priv = __udp_tunnel_nic_set_port_priv, 64962306a36Sopenharmony_ci .add_port = __udp_tunnel_nic_add_port, 65062306a36Sopenharmony_ci .del_port = __udp_tunnel_nic_del_port, 65162306a36Sopenharmony_ci .reset_ntf = __udp_tunnel_nic_reset_ntf, 65262306a36Sopenharmony_ci .dump_size = __udp_tunnel_nic_dump_size, 65362306a36Sopenharmony_ci .dump_write = __udp_tunnel_nic_dump_write, 65462306a36Sopenharmony_ci}; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_cistatic void 65762306a36Sopenharmony_ciudp_tunnel_nic_flush(struct net_device *dev, struct udp_tunnel_nic *utn) 65862306a36Sopenharmony_ci{ 65962306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 66062306a36Sopenharmony_ci unsigned int i, j; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 66362306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) { 66462306a36Sopenharmony_ci int adj_cnt = -utn->entries[i][j].use_cnt; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci if (adj_cnt) 66762306a36Sopenharmony_ci udp_tunnel_nic_entry_adj(utn, i, j, adj_cnt); 66862306a36Sopenharmony_ci } 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci __udp_tunnel_nic_device_sync(dev, utn); 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 67362306a36Sopenharmony_ci memset(utn->entries[i], 0, array_size(info->tables[i].n_entries, 67462306a36Sopenharmony_ci sizeof(**utn->entries))); 67562306a36Sopenharmony_ci WARN_ON(utn->need_sync); 67662306a36Sopenharmony_ci utn->need_replay = 0; 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_cistatic void 68062306a36Sopenharmony_ciudp_tunnel_nic_replay(struct net_device *dev, struct udp_tunnel_nic *utn) 68162306a36Sopenharmony_ci{ 68262306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 68362306a36Sopenharmony_ci struct udp_tunnel_nic_shared_node *node; 68462306a36Sopenharmony_ci unsigned int i, j; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci /* Freeze all the ports we are already tracking so that the replay 68762306a36Sopenharmony_ci * does not double up the refcount. 68862306a36Sopenharmony_ci */ 68962306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 69062306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) 69162306a36Sopenharmony_ci udp_tunnel_nic_entry_freeze_used(&utn->entries[i][j]); 69262306a36Sopenharmony_ci utn->missed = 0; 69362306a36Sopenharmony_ci utn->need_replay = 0; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci if (!info->shared) { 69662306a36Sopenharmony_ci udp_tunnel_get_rx_info(dev); 69762306a36Sopenharmony_ci } else { 69862306a36Sopenharmony_ci list_for_each_entry(node, &info->shared->devices, list) 69962306a36Sopenharmony_ci udp_tunnel_get_rx_info(node->dev); 70062306a36Sopenharmony_ci } 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 70362306a36Sopenharmony_ci for (j = 0; j < info->tables[i].n_entries; j++) 70462306a36Sopenharmony_ci udp_tunnel_nic_entry_unfreeze(&utn->entries[i][j]); 70562306a36Sopenharmony_ci} 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_cistatic void udp_tunnel_nic_device_sync_work(struct work_struct *work) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci struct udp_tunnel_nic *utn = 71062306a36Sopenharmony_ci container_of(work, struct udp_tunnel_nic, work); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci rtnl_lock(); 71362306a36Sopenharmony_ci utn->work_pending = 0; 71462306a36Sopenharmony_ci __udp_tunnel_nic_device_sync(utn->dev, utn); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci if (utn->need_replay) 71762306a36Sopenharmony_ci udp_tunnel_nic_replay(utn->dev, utn); 71862306a36Sopenharmony_ci rtnl_unlock(); 71962306a36Sopenharmony_ci} 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic struct udp_tunnel_nic * 72262306a36Sopenharmony_ciudp_tunnel_nic_alloc(const struct udp_tunnel_nic_info *info, 72362306a36Sopenharmony_ci unsigned int n_tables) 72462306a36Sopenharmony_ci{ 72562306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 72662306a36Sopenharmony_ci unsigned int i; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci utn = kzalloc(sizeof(*utn), GFP_KERNEL); 72962306a36Sopenharmony_ci if (!utn) 73062306a36Sopenharmony_ci return NULL; 73162306a36Sopenharmony_ci utn->n_tables = n_tables; 73262306a36Sopenharmony_ci INIT_WORK(&utn->work, udp_tunnel_nic_device_sync_work); 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci utn->entries = kmalloc_array(n_tables, sizeof(void *), GFP_KERNEL); 73562306a36Sopenharmony_ci if (!utn->entries) 73662306a36Sopenharmony_ci goto err_free_utn; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci for (i = 0; i < n_tables; i++) { 73962306a36Sopenharmony_ci utn->entries[i] = kcalloc(info->tables[i].n_entries, 74062306a36Sopenharmony_ci sizeof(*utn->entries[i]), GFP_KERNEL); 74162306a36Sopenharmony_ci if (!utn->entries[i]) 74262306a36Sopenharmony_ci goto err_free_prev_entries; 74362306a36Sopenharmony_ci } 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci return utn; 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_cierr_free_prev_entries: 74862306a36Sopenharmony_ci while (i--) 74962306a36Sopenharmony_ci kfree(utn->entries[i]); 75062306a36Sopenharmony_ci kfree(utn->entries); 75162306a36Sopenharmony_cierr_free_utn: 75262306a36Sopenharmony_ci kfree(utn); 75362306a36Sopenharmony_ci return NULL; 75462306a36Sopenharmony_ci} 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_cistatic void udp_tunnel_nic_free(struct udp_tunnel_nic *utn) 75762306a36Sopenharmony_ci{ 75862306a36Sopenharmony_ci unsigned int i; 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci for (i = 0; i < utn->n_tables; i++) 76162306a36Sopenharmony_ci kfree(utn->entries[i]); 76262306a36Sopenharmony_ci kfree(utn->entries); 76362306a36Sopenharmony_ci kfree(utn); 76462306a36Sopenharmony_ci} 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_cistatic int udp_tunnel_nic_register(struct net_device *dev) 76762306a36Sopenharmony_ci{ 76862306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 76962306a36Sopenharmony_ci struct udp_tunnel_nic_shared_node *node = NULL; 77062306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 77162306a36Sopenharmony_ci unsigned int n_tables, i; 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(utn->missed) * BITS_PER_BYTE < 77462306a36Sopenharmony_ci UDP_TUNNEL_NIC_MAX_TABLES); 77562306a36Sopenharmony_ci /* Expect use count of at most 2 (IPv4, IPv6) per device */ 77662306a36Sopenharmony_ci BUILD_BUG_ON(UDP_TUNNEL_NIC_USE_CNT_MAX < 77762306a36Sopenharmony_ci UDP_TUNNEL_NIC_MAX_SHARING_DEVICES * 2); 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci /* Check that the driver info is sane */ 78062306a36Sopenharmony_ci if (WARN_ON(!info->set_port != !info->unset_port) || 78162306a36Sopenharmony_ci WARN_ON(!info->set_port == !info->sync_table) || 78262306a36Sopenharmony_ci WARN_ON(!info->tables[0].n_entries)) 78362306a36Sopenharmony_ci return -EINVAL; 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci if (WARN_ON(info->shared && 78662306a36Sopenharmony_ci info->flags & UDP_TUNNEL_NIC_INFO_OPEN_ONLY)) 78762306a36Sopenharmony_ci return -EINVAL; 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci n_tables = 1; 79062306a36Sopenharmony_ci for (i = 1; i < UDP_TUNNEL_NIC_MAX_TABLES; i++) { 79162306a36Sopenharmony_ci if (!info->tables[i].n_entries) 79262306a36Sopenharmony_ci continue; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci n_tables++; 79562306a36Sopenharmony_ci if (WARN_ON(!info->tables[i - 1].n_entries)) 79662306a36Sopenharmony_ci return -EINVAL; 79762306a36Sopenharmony_ci } 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci /* Create UDP tunnel state structures */ 80062306a36Sopenharmony_ci if (info->shared) { 80162306a36Sopenharmony_ci node = kzalloc(sizeof(*node), GFP_KERNEL); 80262306a36Sopenharmony_ci if (!node) 80362306a36Sopenharmony_ci return -ENOMEM; 80462306a36Sopenharmony_ci 80562306a36Sopenharmony_ci node->dev = dev; 80662306a36Sopenharmony_ci } 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci if (info->shared && info->shared->udp_tunnel_nic_info) { 80962306a36Sopenharmony_ci utn = info->shared->udp_tunnel_nic_info; 81062306a36Sopenharmony_ci } else { 81162306a36Sopenharmony_ci utn = udp_tunnel_nic_alloc(info, n_tables); 81262306a36Sopenharmony_ci if (!utn) { 81362306a36Sopenharmony_ci kfree(node); 81462306a36Sopenharmony_ci return -ENOMEM; 81562306a36Sopenharmony_ci } 81662306a36Sopenharmony_ci } 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci if (info->shared) { 81962306a36Sopenharmony_ci if (!info->shared->udp_tunnel_nic_info) { 82062306a36Sopenharmony_ci INIT_LIST_HEAD(&info->shared->devices); 82162306a36Sopenharmony_ci info->shared->udp_tunnel_nic_info = utn; 82262306a36Sopenharmony_ci } 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci list_add_tail(&node->list, &info->shared->devices); 82562306a36Sopenharmony_ci } 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci utn->dev = dev; 82862306a36Sopenharmony_ci dev_hold(dev); 82962306a36Sopenharmony_ci dev->udp_tunnel_nic = utn; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci if (!(info->flags & UDP_TUNNEL_NIC_INFO_OPEN_ONLY)) 83262306a36Sopenharmony_ci udp_tunnel_get_rx_info(dev); 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci return 0; 83562306a36Sopenharmony_ci} 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_cistatic void 83862306a36Sopenharmony_ciudp_tunnel_nic_unregister(struct net_device *dev, struct udp_tunnel_nic *utn) 83962306a36Sopenharmony_ci{ 84062306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci /* For a shared table remove this dev from the list of sharing devices 84362306a36Sopenharmony_ci * and if there are other devices just detach. 84462306a36Sopenharmony_ci */ 84562306a36Sopenharmony_ci if (info->shared) { 84662306a36Sopenharmony_ci struct udp_tunnel_nic_shared_node *node, *first; 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci list_for_each_entry(node, &info->shared->devices, list) 84962306a36Sopenharmony_ci if (node->dev == dev) 85062306a36Sopenharmony_ci break; 85162306a36Sopenharmony_ci if (list_entry_is_head(node, &info->shared->devices, list)) 85262306a36Sopenharmony_ci return; 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci list_del(&node->list); 85562306a36Sopenharmony_ci kfree(node); 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci first = list_first_entry_or_null(&info->shared->devices, 85862306a36Sopenharmony_ci typeof(*first), list); 85962306a36Sopenharmony_ci if (first) { 86062306a36Sopenharmony_ci udp_tunnel_drop_rx_info(dev); 86162306a36Sopenharmony_ci utn->dev = first->dev; 86262306a36Sopenharmony_ci goto release_dev; 86362306a36Sopenharmony_ci } 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci info->shared->udp_tunnel_nic_info = NULL; 86662306a36Sopenharmony_ci } 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci /* Flush before we check work, so we don't waste time adding entries 86962306a36Sopenharmony_ci * from the work which we will boot immediately. 87062306a36Sopenharmony_ci */ 87162306a36Sopenharmony_ci udp_tunnel_nic_flush(dev, utn); 87262306a36Sopenharmony_ci 87362306a36Sopenharmony_ci /* Wait for the work to be done using the state, netdev core will 87462306a36Sopenharmony_ci * retry unregister until we give up our reference on this device. 87562306a36Sopenharmony_ci */ 87662306a36Sopenharmony_ci if (utn->work_pending) 87762306a36Sopenharmony_ci return; 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_ci udp_tunnel_nic_free(utn); 88062306a36Sopenharmony_cirelease_dev: 88162306a36Sopenharmony_ci dev->udp_tunnel_nic = NULL; 88262306a36Sopenharmony_ci dev_put(dev); 88362306a36Sopenharmony_ci} 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_cistatic int 88662306a36Sopenharmony_ciudp_tunnel_nic_netdevice_event(struct notifier_block *unused, 88762306a36Sopenharmony_ci unsigned long event, void *ptr) 88862306a36Sopenharmony_ci{ 88962306a36Sopenharmony_ci struct net_device *dev = netdev_notifier_info_to_dev(ptr); 89062306a36Sopenharmony_ci const struct udp_tunnel_nic_info *info; 89162306a36Sopenharmony_ci struct udp_tunnel_nic *utn; 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ci info = dev->udp_tunnel_nic_info; 89462306a36Sopenharmony_ci if (!info) 89562306a36Sopenharmony_ci return NOTIFY_DONE; 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci if (event == NETDEV_REGISTER) { 89862306a36Sopenharmony_ci int err; 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci err = udp_tunnel_nic_register(dev); 90162306a36Sopenharmony_ci if (err) 90262306a36Sopenharmony_ci netdev_WARN(dev, "failed to register for UDP tunnel offloads: %d", err); 90362306a36Sopenharmony_ci return notifier_from_errno(err); 90462306a36Sopenharmony_ci } 90562306a36Sopenharmony_ci /* All other events will need the udp_tunnel_nic state */ 90662306a36Sopenharmony_ci utn = dev->udp_tunnel_nic; 90762306a36Sopenharmony_ci if (!utn) 90862306a36Sopenharmony_ci return NOTIFY_DONE; 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci if (event == NETDEV_UNREGISTER) { 91162306a36Sopenharmony_ci udp_tunnel_nic_unregister(dev, utn); 91262306a36Sopenharmony_ci return NOTIFY_OK; 91362306a36Sopenharmony_ci } 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci /* All other events only matter if NIC has to be programmed open */ 91662306a36Sopenharmony_ci if (!(info->flags & UDP_TUNNEL_NIC_INFO_OPEN_ONLY)) 91762306a36Sopenharmony_ci return NOTIFY_DONE; 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci if (event == NETDEV_UP) { 92062306a36Sopenharmony_ci WARN_ON(!udp_tunnel_nic_is_empty(dev, utn)); 92162306a36Sopenharmony_ci udp_tunnel_get_rx_info(dev); 92262306a36Sopenharmony_ci return NOTIFY_OK; 92362306a36Sopenharmony_ci } 92462306a36Sopenharmony_ci if (event == NETDEV_GOING_DOWN) { 92562306a36Sopenharmony_ci udp_tunnel_nic_flush(dev, utn); 92662306a36Sopenharmony_ci return NOTIFY_OK; 92762306a36Sopenharmony_ci } 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_ci return NOTIFY_DONE; 93062306a36Sopenharmony_ci} 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_cistatic struct notifier_block udp_tunnel_nic_notifier_block __read_mostly = { 93362306a36Sopenharmony_ci .notifier_call = udp_tunnel_nic_netdevice_event, 93462306a36Sopenharmony_ci}; 93562306a36Sopenharmony_ci 93662306a36Sopenharmony_cistatic int __init udp_tunnel_nic_init_module(void) 93762306a36Sopenharmony_ci{ 93862306a36Sopenharmony_ci int err; 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci udp_tunnel_nic_workqueue = alloc_ordered_workqueue("udp_tunnel_nic", 0); 94162306a36Sopenharmony_ci if (!udp_tunnel_nic_workqueue) 94262306a36Sopenharmony_ci return -ENOMEM; 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci rtnl_lock(); 94562306a36Sopenharmony_ci udp_tunnel_nic_ops = &__udp_tunnel_nic_ops; 94662306a36Sopenharmony_ci rtnl_unlock(); 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci err = register_netdevice_notifier(&udp_tunnel_nic_notifier_block); 94962306a36Sopenharmony_ci if (err) 95062306a36Sopenharmony_ci goto err_unset_ops; 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci return 0; 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_cierr_unset_ops: 95562306a36Sopenharmony_ci rtnl_lock(); 95662306a36Sopenharmony_ci udp_tunnel_nic_ops = NULL; 95762306a36Sopenharmony_ci rtnl_unlock(); 95862306a36Sopenharmony_ci destroy_workqueue(udp_tunnel_nic_workqueue); 95962306a36Sopenharmony_ci return err; 96062306a36Sopenharmony_ci} 96162306a36Sopenharmony_cilate_initcall(udp_tunnel_nic_init_module); 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_cistatic void __exit udp_tunnel_nic_cleanup_module(void) 96462306a36Sopenharmony_ci{ 96562306a36Sopenharmony_ci unregister_netdevice_notifier(&udp_tunnel_nic_notifier_block); 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ci rtnl_lock(); 96862306a36Sopenharmony_ci udp_tunnel_nic_ops = NULL; 96962306a36Sopenharmony_ci rtnl_unlock(); 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci destroy_workqueue(udp_tunnel_nic_workqueue); 97262306a36Sopenharmony_ci} 97362306a36Sopenharmony_cimodule_exit(udp_tunnel_nic_cleanup_module); 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 976