// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2020 Facebook Inc.
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <net/udp_tunnel.h>
#include <net/vxlan.h>
enum udp_tunnel_nic_table_entry_flags {
UDP_TUNNEL_NIC_ENTRY_ADD = BIT(0),
UDP_TUNNEL_NIC_ENTRY_DEL = BIT(1),
UDP_TUNNEL_NIC_ENTRY_OP_FAIL = BIT(2),
UDP_TUNNEL_NIC_ENTRY_FROZEN = BIT(3),
};
struct udp_tunnel_nic_table_entry {
__be16 port;
u8 type;
u8 flags;
u16 use_cnt;
#define UDP_TUNNEL_NIC_USE_CNT_MAX U16_MAX
u8 hw_priv;
};
/**
* struct udp_tunnel_nic - UDP tunnel port offload state
* @work: async work for talking to hardware from process context
* @dev: netdev pointer
* @need_sync: at least one port start changed
* @need_replay: space was freed, we need a replay of all ports
* @work_pending: @work is currently scheduled
* @n_tables: number of tables under @entries
* @missed: bitmap of tables which overflown
* @entries: table of tables of ports currently offloaded
*/
struct udp_tunnel_nic {
struct work_struct work;
struct net_device *dev;
u8 need_sync:1;
u8 need_replay:1;
u8 work_pending:1;
unsigned int n_tables;
unsigned long missed;
struct udp_tunnel_nic_table_entry **entries;
};
/* We ensure all work structs are done using driver state, but not the code.
* We need a workqueue we can flush before module gets removed.
*/
static struct workqueue_struct *udp_tunnel_nic_workqueue;
static const char *udp_tunnel_nic_tunnel_type_name(unsigned int type)
{
switch (type) {
case UDP_TUNNEL_TYPE_VXLAN:
return "vxlan";
case UDP_TUNNEL_TYPE_GENEVE:
return "geneve";
case UDP_TUNNEL_TYPE_VXLAN_GPE:
return "vxlan-gpe";
default:
return "unknown";
}
}
static bool
udp_tunnel_nic_entry_is_free(struct udp_tunnel_nic_table_entry *entry)
{
return entry->use_cnt == 0 && !entry->flags;
}
static bool
udp_tunnel_nic_entry_is_present(struct udp_tunnel_nic_table_entry *entry)
{
return entry->use_cnt && !(entry->flags & ~UDP_TUNNEL_NIC_ENTRY_FROZEN);
}
static bool
udp_tunnel_nic_entry_is_frozen(struct udp_tunnel_nic_table_entry *entry)
{
return entry->flags & UDP_TUNNEL_NIC_ENTRY_FROZEN;
}
static void
udp_tunnel_nic_entry_freeze_used(struct udp_tunnel_nic_table_entry *entry)
{
if (!udp_tunnel_nic_entry_is_free(entry))
entry->flags |= UDP_TUNNEL_NIC_ENTRY_FROZEN;
}
static void
udp_tunnel_nic_entry_unfreeze(struct udp_tunnel_nic_table_entry *entry)
{
entry->flags &= ~UDP_TUNNEL_NIC_ENTRY_FROZEN;
}
static bool
udp_tunnel_nic_entry_is_queued(struct udp_tunnel_nic_table_entry *entry)
{
return entry->flags & (UDP_TUNNEL_NIC_ENTRY_ADD |
UDP_TUNNEL_NIC_ENTRY_DEL);
}
static void
udp_tunnel_nic_entry_queue(struct udp_tunnel_nic *utn,
struct udp_tunnel_nic_table_entry *entry,
unsigned int flag)
{
entry->flags |= flag;
utn->need_sync = 1;
}
static void
udp_tunnel_nic_ti_from_entry(struct udp_tunnel_nic_table_entry *entry,
struct udp_tunnel_info *ti)
{
memset(ti, 0, sizeof(*ti));
ti->port = entry->port;
ti->type = entry->type;
ti->hw_priv = entry->hw_priv;
}
static bool
udp_tunnel_nic_is_empty(struct net_device *dev, struct udp_tunnel_nic *utn)
{
const struct udp_tunnel_nic_info *info = dev->udp_tunnel_nic_info;
unsigned int i, j;
for (i = 0; i < utn->n_tables; i++)
for (j = 0; j < info->tables[i].n_entries; j++)
if (!udp_tunnel_nic_entry_is_free(&utn->entries[i][j]))
return false;
return true;
}
static bool
udp_tunnel_nic_should_replay(struct net_device *dev, struct udp_tunnel_nic *utn)
{
const struct udp_tunnel_nic_table_info *table;
unsigned int i, j;
if (!utn->missed)
return false;
for (i = 0; i < utn->n_tables; i++) {
table = &dev->udp_tunnel_nic_info->tables[i];
if (!test_bit(i, &utn->missed))
continue;
for (j = 0; j < table->n_entries; j++)
if (udp_tunnel_nic_entry_is_free(&utn->entries[i][j]))
return true;
}
return false;
}
static void
__udp_tunnel_nic_get_port(struct net_device *dev, unsigned int table,
unsigned