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