static void efx_ef10_filter_del_vlan_internal(struct efx_nic *efx,
struct efx_ef10_filter_vlan *vlan);
static void efx_ef10_filter_del_vlan(struct efx_nic *efx, u16 vid);
+static int efx_ef10_set_udp_tnl_ports(struct efx_nic *efx, bool unloading);
static int efx_ef10_get_warm_boot_count(struct efx_nic *efx)
{
if (rc)
goto fail2;
+ mutex_init(&nic_data->udp_tunnels_lock);
+
/* Reset (most) configuration for this function */
rc = efx_mcdi_reset(efx, RESET_TYPE_ALL);
if (rc)
fail4:
device_remove_file(&efx->pci_dev->dev, &dev_attr_link_control_flag);
fail3:
+ efx_mcdi_detach(efx);
+
+ mutex_lock(&nic_data->udp_tunnels_lock);
+ memset(nic_data->udp_tunnels, 0, sizeof(nic_data->udp_tunnels));
+ (void)efx_ef10_set_udp_tnl_ports(efx, true);
+ mutex_unlock(&nic_data->udp_tunnels_lock);
+ mutex_destroy(&nic_data->udp_tunnels_lock);
+
efx_mcdi_fini(efx);
fail2:
efx_nic_free_buffer(efx, &nic_data->mcdi_buf);
device_remove_file(&efx->pci_dev->dev, &dev_attr_primary_flag);
device_remove_file(&efx->pci_dev->dev, &dev_attr_link_control_flag);
+ efx_mcdi_detach(efx);
+
+ memset(nic_data->udp_tunnels, 0, sizeof(nic_data->udp_tunnels));
+ mutex_lock(&nic_data->udp_tunnels_lock);
+ (void)efx_ef10_set_udp_tnl_ports(efx, true);
+ mutex_unlock(&nic_data->udp_tunnels_lock);
+
+ mutex_destroy(&nic_data->udp_tunnels_lock);
+
efx_mcdi_fini(efx);
efx_nic_free_buffer(efx, &nic_data->mcdi_buf);
kfree(nic_data);
return efx_ef10_del_vlan(efx, vid);
}
+/* We rely on the MCDI wiping out our TX rings if it made any changes to the
+ * ports table, ensuring that any TSO descriptors that were made on a now-
+ * removed tunnel port will be blown away and won't break things when we try
+ * to transmit them using the new ports table.
+ */
+static int efx_ef10_set_udp_tnl_ports(struct efx_nic *efx, bool unloading)
+{
+ struct efx_ef10_nic_data *nic_data = efx->nic_data;
+ MCDI_DECLARE_BUF(inbuf, MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_LENMAX);
+ MCDI_DECLARE_BUF(outbuf, MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_OUT_LEN);
+ bool will_reset = false;
+ size_t num_entries = 0;
+ size_t inlen, outlen;
+ size_t i;
+ int rc;
+ efx_dword_t flags_and_num_entries;
+
+ WARN_ON(!mutex_is_locked(&nic_data->udp_tunnels_lock));
+
+ nic_data->udp_tunnels_dirty = false;
+
+ if (!(nic_data->datapath_caps &
+ (1 << MC_CMD_GET_CAPABILITIES_OUT_VXLAN_NVGRE_LBN))) {
+ netif_device_attach(efx->net_dev);
+ return 0;
+ }
+
+ BUILD_BUG_ON(ARRAY_SIZE(nic_data->udp_tunnels) >
+ MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_ENTRIES_MAXNUM);
+
+ for (i = 0; i < ARRAY_SIZE(nic_data->udp_tunnels); ++i) {
+ if (nic_data->udp_tunnels[i].count &&
+ nic_data->udp_tunnels[i].port) {
+ efx_dword_t entry;
+
+ EFX_POPULATE_DWORD_2(entry,
+ TUNNEL_ENCAP_UDP_PORT_ENTRY_UDP_PORT,
+ ntohs(nic_data->udp_tunnels[i].port),
+ TUNNEL_ENCAP_UDP_PORT_ENTRY_PROTOCOL,
+ nic_data->udp_tunnels[i].type);
+ *_MCDI_ARRAY_DWORD(inbuf,
+ SET_TUNNEL_ENCAP_UDP_PORTS_IN_ENTRIES,
+ num_entries++) = entry;
+ }
+ }
+
+ BUILD_BUG_ON((MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_NUM_ENTRIES_OFST -
+ MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_FLAGS_OFST) * 8 !=
+ EFX_WORD_1_LBN);
+ BUILD_BUG_ON(MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_NUM_ENTRIES_LEN * 8 !=
+ EFX_WORD_1_WIDTH);
+ EFX_POPULATE_DWORD_2(flags_and_num_entries,
+ MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_UNLOADING,
+ !!unloading,
+ EFX_WORD_1, num_entries);
+ *_MCDI_DWORD(inbuf, SET_TUNNEL_ENCAP_UDP_PORTS_IN_FLAGS) =
+ flags_and_num_entries;
+
+ inlen = MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_IN_LEN(num_entries);
+
+ rc = efx_mcdi_rpc_quiet(efx, MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS,
+ inbuf, inlen, outbuf, sizeof(outbuf), &outlen);
+ if (rc == -EIO) {
+ /* Most likely the MC rebooted due to another function also
+ * setting its tunnel port list. Mark the tunnel port list as
+ * dirty, so it will be pushed upon coming up from the reboot.
+ */
+ nic_data->udp_tunnels_dirty = true;
+ return 0;
+ }
+
+ if (rc) {
+ /* expected not available on unprivileged functions */
+ if (rc != -EPERM)
+ netif_warn(efx, drv, efx->net_dev,
+ "Unable to set UDP tunnel ports; rc=%d.\n", rc);
+ } else if (MCDI_DWORD(outbuf, SET_TUNNEL_ENCAP_UDP_PORTS_OUT_FLAGS) &
+ (1 << MC_CMD_SET_TUNNEL_ENCAP_UDP_PORTS_OUT_RESETTING_LBN)) {
+ netif_info(efx, drv, efx->net_dev,
+ "Rebooting MC due to UDP tunnel port list change\n");
+ will_reset = true;
+ if (unloading)
+ /* Delay for the MC reset to complete. This will make
+ * unloading other functions a bit smoother. This is a
+ * race, but the other unload will work whichever way
+ * it goes, this just avoids an unnecessary error
+ * message.
+ */
+ msleep(100);
+ }
+ if (!will_reset && !unloading) {
+ /* The caller will have detached, relying on the MC reset to
+ * trigger a re-attach. Since there won't be an MC reset, we
+ * have to do the attach ourselves.
+ */
+ netif_device_attach(efx->net_dev);
+ }
+
+ return rc;
+}
+
+static int efx_ef10_udp_tnl_push_ports(struct efx_nic *efx)
+{
+ struct efx_ef10_nic_data *nic_data = efx->nic_data;
+ int rc = 0;
+
+ mutex_lock(&nic_data->udp_tunnels_lock);
+ if (nic_data->udp_tunnels_dirty) {
+ /* Make sure all TX are stopped while we modify the table, else
+ * we might race against an efx_features_check().
+ */
+ efx_device_detach_sync(efx);
+ rc = efx_ef10_set_udp_tnl_ports(efx, false);
+ }
+ mutex_unlock(&nic_data->udp_tunnels_lock);
+ return rc;
+}
+
+static struct efx_udp_tunnel *__efx_ef10_udp_tnl_lookup_port(struct efx_nic *efx,
+ __be16 port)
+{
+ struct efx_ef10_nic_data *nic_data = efx->nic_data;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(nic_data->udp_tunnels); ++i) {
+ if (!nic_data->udp_tunnels[i].count)
+ continue;
+ if (nic_data->udp_tunnels[i].port == port)
+ return &nic_data->udp_tunnels[i];
+ }
+ return NULL;
+}
+
+static int efx_ef10_udp_tnl_add_port(struct efx_nic *efx,
+ struct efx_udp_tunnel tnl)
+{
+ struct efx_ef10_nic_data *nic_data = efx->nic_data;
+ struct efx_udp_tunnel *match;
+ char typebuf[8];
+ size_t i;
+ int rc;
+
+ if (!(nic_data->datapath_caps &
+ (1 << MC_CMD_GET_CAPABILITIES_OUT_VXLAN_NVGRE_LBN)))
+ return 0;
+
+ efx_get_udp_tunnel_type_name(tnl.type, typebuf, sizeof(typebuf));
+ netif_dbg(efx, drv, efx->net_dev, "Adding UDP tunnel (%s) port %d\n",
+ typebuf, ntohs(tnl.port));
+
+ mutex_lock(&nic_data->udp_tunnels_lock);
+ /* Make sure all TX are stopped while we add to the table, else we
+ * might race against an efx_features_check().
+ */
+ efx_device_detach_sync(efx);
+
+ match = __efx_ef10_udp_tnl_lookup_port(efx, tnl.port);
+ if (match != NULL) {
+ if (match->type == tnl.type) {
+ netif_dbg(efx, drv, efx->net_dev,
+ "Referencing existing tunnel entry\n");
+ match->count++;
+ /* No need to cause an MCDI update */
+ rc = 0;
+ goto unlock_out;
+ }
+ efx_get_udp_tunnel_type_name(match->type,
+ typebuf, sizeof(typebuf));
+ netif_dbg(efx, drv, efx->net_dev,
+ "UDP port %d is already in use by %s\n",
+ ntohs(tnl.port), typebuf);
+ rc = -EEXIST;
+ goto unlock_out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(nic_data->udp_tunnels); ++i)
+ if (!nic_data->udp_tunnels[i].count) {
+ nic_data->udp_tunnels[i] = tnl;
+ nic_data->udp_tunnels[i].count = 1;
+ rc = efx_ef10_set_udp_tnl_ports(efx, false);
+ goto unlock_out;
+ }
+
+ netif_dbg(efx, drv, efx->net_dev,
+ "Unable to add UDP tunnel (%s) port %d; insufficient resources.\n",
+ typebuf, ntohs(tnl.port));
+
+ rc = -ENOMEM;
+
+unlock_out:
+ mutex_unlock(&nic_data->udp_tunnels_lock);
+ return rc;
+}
+
+/* Called under the TX lock with the TX queue running, hence no-one can be
+ * in the middle of updating the UDP tunnels table. However, they could
+ * have tried and failed the MCDI, in which case they'll have set the dirty
+ * flag before dropping their locks.
+ */
+static bool efx_ef10_udp_tnl_has_port(struct efx_nic *efx, __be16 port)
+{
+ struct efx_ef10_nic_data *nic_data = efx->nic_data;
+
+ if (!(nic_data->datapath_caps &
+ (1 << MC_CMD_GET_CAPABILITIES_OUT_VXLAN_NVGRE_LBN)))
+ return false;
+
+ if (nic_data->udp_tunnels_dirty)
+ /* SW table may not match HW state, so just assume we can't
+ * use any UDP tunnel offloads.
+ */
+ return false;
+
+ return __efx_ef10_udp_tnl_lookup_port(efx, port) != NULL;
+}
+
+static int efx_ef10_udp_tnl_del_port(struct efx_nic *efx,
+ struct efx_udp_tunnel tnl)
+{
+ struct efx_ef10_nic_data *nic_data = efx->nic_data;
+ struct efx_udp_tunnel *match;
+ char typebuf[8];
+ int rc;
+
+ if (!(nic_data->datapath_caps &
+ (1 << MC_CMD_GET_CAPABILITIES_OUT_VXLAN_NVGRE_LBN)))
+ return 0;
+
+ efx_get_udp_tunnel_type_name(tnl.type, typebuf, sizeof(typebuf));
+ netif_dbg(efx, drv, efx->net_dev, "Removing UDP tunnel (%s) port %d\n",
+ typebuf, ntohs(tnl.port));
+
+ mutex_lock(&nic_data->udp_tunnels_lock);
+ /* Make sure all TX are stopped while we remove from the table, else we
+ * might race against an efx_features_check().
+ */
+ efx_device_detach_sync(efx);
+
+ match = __efx_ef10_udp_tnl_lookup_port(efx, tnl.port);
+ if (match != NULL) {
+ if (match->type == tnl.type) {
+ if (--match->count) {
+ /* Port is still in use, so nothing to do */
+ netif_dbg(efx, drv, efx->net_dev,
+ "UDP tunnel port %d remains active\n",
+ ntohs(tnl.port));
+ rc = 0;
+ goto out_unlock;
+ }
+ rc = efx_ef10_set_udp_tnl_ports(efx, false);
+ goto out_unlock;
+ }
+ efx_get_udp_tunnel_type_name(match->type,
+ typebuf, sizeof(typebuf));
+ netif_warn(efx, drv, efx->net_dev,
+ "UDP port %d is actually in use by %s, not removing\n",
+ ntohs(tnl.port), typebuf);
+ }
+ rc = -ENOENT;
+
+out_unlock:
+ mutex_unlock(&nic_data->udp_tunnels_lock);
+ return rc;
+}
+
#define EF10_OFFLOAD_FEATURES \
(NETIF_F_IP_CSUM | \
NETIF_F_HW_VLAN_CTAG_FILTER | \
.ptp_set_ts_config = efx_ef10_ptp_set_ts_config,
.vlan_rx_add_vid = efx_ef10_vlan_rx_add_vid,
.vlan_rx_kill_vid = efx_ef10_vlan_rx_kill_vid,
+ .udp_tnl_push_ports = efx_ef10_udp_tnl_push_ports,
+ .udp_tnl_add_port = efx_ef10_udp_tnl_add_port,
+ .udp_tnl_has_port = efx_ef10_udp_tnl_has_port,
+ .udp_tnl_del_port = efx_ef10_udp_tnl_del_port,
#ifdef CONFIG_SFC_SRIOV
.sriov_configure = efx_ef10_sriov_configure,
.sriov_init = efx_ef10_sriov_init,
#include <linux/aer.h>
#include <linux/interrupt.h>
#include "net_driver.h"
+#include <net/gre.h>
+#include <net/udp_tunnel.h>
#include "efx.h"
#include "nic.h"
#include "selftest.h"
#include "sriov.h"
#include "mcdi.h"
+#include "mcdi_pcol.h"
#include "workarounds.h"
/**************************************************************************
[RESET_TYPE_MCDI_TIMEOUT] = "MCDI_TIMEOUT (FLR)",
};
+/* UDP tunnel type names */
+static const char *const efx_udp_tunnel_type_names[] = {
+ [TUNNEL_ENCAP_UDP_PORT_ENTRY_VXLAN] = "vxlan",
+ [TUNNEL_ENCAP_UDP_PORT_ENTRY_GENEVE] = "geneve",
+};
+
+void efx_get_udp_tunnel_type_name(u16 type, char *buf, size_t buflen)
+{
+ if (type < ARRAY_SIZE(efx_udp_tunnel_type_names) &&
+ efx_udp_tunnel_type_names[type] != NULL)
+ snprintf(buf, buflen, "%s", efx_udp_tunnel_type_names[type]);
+ else
+ snprintf(buf, buflen, "type %d", type);
+}
+
/* Reset workqueue. If any NIC has a hardware failure then a reset will be
* queued onto this work queue. This is not a per-nic work queue, because
* efx_reset_work() acquires the rtnl lock, so resets are naturally serialised.
return -EOPNOTSUPP;
}
+static int efx_udp_tunnel_type_map(enum udp_parsable_tunnel_type in)
+{
+ switch (in) {
+ case UDP_TUNNEL_TYPE_VXLAN:
+ return TUNNEL_ENCAP_UDP_PORT_ENTRY_VXLAN;
+ case UDP_TUNNEL_TYPE_GENEVE:
+ return TUNNEL_ENCAP_UDP_PORT_ENTRY_GENEVE;
+ default:
+ return -1;
+ }
+}
+
+static void efx_udp_tunnel_add(struct net_device *dev, struct udp_tunnel_info *ti)
+{
+ struct efx_nic *efx = netdev_priv(dev);
+ struct efx_udp_tunnel tnl;
+ int efx_tunnel_type;
+
+ efx_tunnel_type = efx_udp_tunnel_type_map(ti->type);
+ if (efx_tunnel_type < 0)
+ return;
+
+ tnl.type = (u16)efx_tunnel_type;
+ tnl.port = ti->port;
+
+ if (efx->type->udp_tnl_add_port)
+ (void)efx->type->udp_tnl_add_port(efx, tnl);
+}
+
+static void efx_udp_tunnel_del(struct net_device *dev, struct udp_tunnel_info *ti)
+{
+ struct efx_nic *efx = netdev_priv(dev);
+ struct efx_udp_tunnel tnl;
+ int efx_tunnel_type;
+
+ efx_tunnel_type = efx_udp_tunnel_type_map(ti->type);
+ if (efx_tunnel_type < 0)
+ return;
+
+ tnl.type = (u16)efx_tunnel_type;
+ tnl.port = ti->port;
+
+ if (efx->type->udp_tnl_add_port)
+ (void)efx->type->udp_tnl_del_port(efx, tnl);
+}
+
static const struct net_device_ops efx_netdev_ops = {
.ndo_open = efx_net_open,
.ndo_stop = efx_net_stop,
#ifdef CONFIG_RFS_ACCEL
.ndo_rx_flow_steer = efx_filter_rfs,
#endif
+ .ndo_udp_tunnel_add = efx_udp_tunnel_add,
+ .ndo_udp_tunnel_del = efx_udp_tunnel_del,
};
static void efx_update_name(struct efx_nic *efx)
efx_start_all(efx);
+ if (efx->type->udp_tnl_push_ports)
+ efx->type->udp_tnl_push_ports(efx);
+
return 0;
fail:
"PCIE error reporting unavailable (%d).\n",
rc);
+ if (efx->type->udp_tnl_push_ports)
+ efx->type->udp_tnl_push_ports(efx);
+
return 0;
fail3: