if ((label_name[len] - '0') == hw->pf_id) {
                                hw->tnl.tbl[hw->tnl.count].type = tnls[i].type;
                                hw->tnl.tbl[hw->tnl.count].valid = false;
-                               hw->tnl.tbl[hw->tnl.count].in_use = false;
-                               hw->tnl.tbl[hw->tnl.count].marked = false;
                                hw->tnl.tbl[hw->tnl.count].boost_addr = val;
                                hw->tnl.tbl[hw->tnl.count].port = 0;
                                hw->tnl.count++;
        for (i = 0; i < hw->tnl.count; i++) {
                ice_find_boost_entry(ice_seg, hw->tnl.tbl[i].boost_addr,
                                     &hw->tnl.tbl[i].boost_entry);
-               if (hw->tnl.tbl[i].boost_entry)
+               if (hw->tnl.tbl[i].boost_entry) {
                        hw->tnl.tbl[i].valid = true;
+                       if (hw->tnl.tbl[i].type < __TNL_TYPE_CNT)
+                               hw->tnl.valid_count[hw->tnl.tbl[i].type]++;
+               }
        }
 }
 
 }
 
 /**
- * ice_tunnel_port_in_use_hlpr - helper function to determine tunnel usage
+ * ice_get_open_tunnel_port - retrieve an open tunnel port
  * @hw: pointer to the HW structure
- * @port: port to search for
- * @index: optionally returns index
- *
- * Returns whether a port is already in use as a tunnel, and optionally its
- * index
+ * @port: returns open port
  */
-static bool ice_tunnel_port_in_use_hlpr(struct ice_hw *hw, u16 port, u16 *index)
+bool
+ice_get_open_tunnel_port(struct ice_hw *hw, u16 *port)
 {
+       bool res = false;
        u16 i;
 
+       mutex_lock(&hw->tnl_lock);
+
        for (i = 0; i < hw->tnl.count && i < ICE_TUNNEL_MAX_ENTRIES; i++)
-               if (hw->tnl.tbl[i].in_use && hw->tnl.tbl[i].port == port) {
-                       if (index)
-                               *index = i;
-                       return true;
+               if (hw->tnl.tbl[i].valid && hw->tnl.tbl[i].port) {
+                       *port = hw->tnl.tbl[i].port;
+                       res = true;
+                       break;
                }
 
-       return false;
-}
-
-/**
- * ice_tunnel_port_in_use
- * @hw: pointer to the HW structure
- * @port: port to search for
- * @index: optionally returns index
- *
- * Returns whether a port is already in use as a tunnel, and optionally its
- * index
- */
-bool ice_tunnel_port_in_use(struct ice_hw *hw, u16 port, u16 *index)
-{
-       bool res;
-
-       mutex_lock(&hw->tnl_lock);
-       res = ice_tunnel_port_in_use_hlpr(hw, port, index);
        mutex_unlock(&hw->tnl_lock);
 
        return res;
 }
 
 /**
- * ice_find_free_tunnel_entry
+ * ice_tunnel_idx_to_entry - convert linear index to the sparse one
  * @hw: pointer to the HW structure
- * @type: tunnel type
- * @index: optionally returns index
+ * @type: type of tunnel
+ * @idx: linear index
  *
- * Returns whether there is a free tunnel entry, and optionally its index
- */
-static bool
-ice_find_free_tunnel_entry(struct ice_hw *hw, enum ice_tunnel_type type,
-                          u16 *index)
-{
-       u16 i;
-
-       for (i = 0; i < hw->tnl.count && i < ICE_TUNNEL_MAX_ENTRIES; i++)
-               if (hw->tnl.tbl[i].valid && !hw->tnl.tbl[i].in_use &&
-                   hw->tnl.tbl[i].type == type) {
-                       if (index)
-                               *index = i;
-                       return true;
-               }
-
-       return false;
-}
-
-/**
- * ice_get_open_tunnel_port - retrieve an open tunnel port
- * @hw: pointer to the HW structure
- * @port: returns open port
+ * Stack assumes we have 2 linear tables with indexes [0, count_valid),
+ * but really the port table may be sprase, and types are mixed, so convert
+ * the stack index into the device index.
  */
-bool
-ice_get_open_tunnel_port(struct ice_hw *hw, u16 *port)
+static u16 ice_tunnel_idx_to_entry(struct ice_hw *hw, enum ice_tunnel_type type,
+                                  u16 idx)
 {
-       bool res = false;
        u16 i;
 
-       mutex_lock(&hw->tnl_lock);
-
        for (i = 0; i < hw->tnl.count && i < ICE_TUNNEL_MAX_ENTRIES; i++)
-               if (hw->tnl.tbl[i].valid && hw->tnl.tbl[i].in_use) {
-                       *port = hw->tnl.tbl[i].port;
-                       res = true;
-                       break;
-               }
-
-       mutex_unlock(&hw->tnl_lock);
+               if (hw->tnl.tbl[i].valid &&
+                   hw->tnl.tbl[i].type == type &&
+                   idx--)
+                       return i;
 
-       return res;
+       WARN_ON_ONCE(1);
+       return 0;
 }
 
 /**
  * ice_create_tunnel
  * @hw: pointer to the HW structure
+ * @index: device table entry
  * @type: type of tunnel
  * @port: port of tunnel to create
  *
  * creating a package buffer with the tunnel info and issuing an update package
  * command.
  */
-enum ice_status
-ice_create_tunnel(struct ice_hw *hw, enum ice_tunnel_type type, u16 port)
+static enum ice_status
+ice_create_tunnel(struct ice_hw *hw, u16 index,
+                 enum ice_tunnel_type type, u16 port)
 {
        struct ice_boost_tcam_section *sect_rx, *sect_tx;
        enum ice_status status = ICE_ERR_MAX_LIMIT;
        struct ice_buf_build *bld;
-       u16 index;
 
        mutex_lock(&hw->tnl_lock);
 
-       if (ice_tunnel_port_in_use_hlpr(hw, port, &index)) {
-               hw->tnl.tbl[index].ref++;
-               status = 0;
-               goto ice_create_tunnel_end;
-       }
-
-       if (!ice_find_free_tunnel_entry(hw, type, &index)) {
-               status = ICE_ERR_OUT_OF_RANGE;
-               goto ice_create_tunnel_end;
-       }
-
        bld = ice_pkg_buf_alloc(hw);
        if (!bld) {
                status = ICE_ERR_NO_MEMORY;
        memcpy(sect_tx->tcam, sect_rx->tcam, sizeof(*sect_tx->tcam));
 
        status = ice_update_pkg(hw, ice_pkg_buf(bld), 1);
-       if (!status) {
+       if (!status)
                hw->tnl.tbl[index].port = port;
-               hw->tnl.tbl[index].in_use = true;
-               hw->tnl.tbl[index].ref = 1;
-       }
 
 ice_create_tunnel_err:
        ice_pkg_buf_free(hw, bld);
 /**
  * ice_destroy_tunnel
  * @hw: pointer to the HW structure
+ * @index: device table entry
+ * @type: type of tunnel
  * @port: port of tunnel to destroy (ignored if the all parameter is true)
- * @all: flag that states to destroy all tunnels
  *
  * Destroys a tunnel or all tunnels by creating an update package buffer
  * targeting the specific updates requested and then performing an update
  * package.
  */
-enum ice_status ice_destroy_tunnel(struct ice_hw *hw, u16 port, bool all)
+static enum ice_status
+ice_destroy_tunnel(struct ice_hw *hw, u16 index, enum ice_tunnel_type type,
+                  u16 port)
 {
        struct ice_boost_tcam_section *sect_rx, *sect_tx;
        enum ice_status status = ICE_ERR_MAX_LIMIT;
        struct ice_buf_build *bld;
-       u16 count = 0;
-       u16 index;
-       u16 size;
-       u16 i;
 
        mutex_lock(&hw->tnl_lock);
 
-       if (!all && ice_tunnel_port_in_use_hlpr(hw, port, &index))
-               if (hw->tnl.tbl[index].ref > 1) {
-                       hw->tnl.tbl[index].ref--;
-                       status = 0;
-                       goto ice_destroy_tunnel_end;
-               }
-
-       /* determine count */
-       for (i = 0; i < hw->tnl.count && i < ICE_TUNNEL_MAX_ENTRIES; i++)
-               if (hw->tnl.tbl[i].valid && hw->tnl.tbl[i].in_use &&
-                   (all || hw->tnl.tbl[i].port == port))
-                       count++;
-
-       if (!count) {
-               status = ICE_ERR_PARAM;
+       if (WARN_ON(!hw->tnl.tbl[index].valid ||
+                   hw->tnl.tbl[index].type != type ||
+                   hw->tnl.tbl[index].port != port)) {
+               status = ICE_ERR_OUT_OF_RANGE;
                goto ice_destroy_tunnel_end;
        }
 
-       /* size of section - there is at least one entry */
-       size = struct_size(sect_rx, tcam, count);
-
        bld = ice_pkg_buf_alloc(hw);
        if (!bld) {
                status = ICE_ERR_NO_MEMORY;
                goto ice_destroy_tunnel_err;
 
        sect_rx = ice_pkg_buf_alloc_section(bld, ICE_SID_RXPARSER_BOOST_TCAM,
-                                           size);
+                                           struct_size(sect_rx, tcam, 1));
        if (!sect_rx)
                goto ice_destroy_tunnel_err;
        sect_rx->count = cpu_to_le16(1);
 
        sect_tx = ice_pkg_buf_alloc_section(bld, ICE_SID_TXPARSER_BOOST_TCAM,
-                                           size);
+                                           struct_size(sect_tx, tcam, 1));
        if (!sect_tx)
                goto ice_destroy_tunnel_err;
        sect_tx->count = cpu_to_le16(1);
        /* copy original boost entry to update package buffer, one copy to Rx
         * section, another copy to the Tx section
         */
-       for (i = 0; i < hw->tnl.count && i < ICE_TUNNEL_MAX_ENTRIES; i++)
-               if (hw->tnl.tbl[i].valid && hw->tnl.tbl[i].in_use &&
-                   (all || hw->tnl.tbl[i].port == port)) {
-                       memcpy(sect_rx->tcam + i, hw->tnl.tbl[i].boost_entry,
-                              sizeof(*sect_rx->tcam));
-                       memcpy(sect_tx->tcam + i, hw->tnl.tbl[i].boost_entry,
-                              sizeof(*sect_tx->tcam));
-                       hw->tnl.tbl[i].marked = true;
-               }
+       memcpy(sect_rx->tcam, hw->tnl.tbl[index].boost_entry,
+              sizeof(*sect_rx->tcam));
+       memcpy(sect_tx->tcam, hw->tnl.tbl[index].boost_entry,
+              sizeof(*sect_tx->tcam));
 
        status = ice_update_pkg(hw, ice_pkg_buf(bld), 1);
        if (!status)
-               for (i = 0; i < hw->tnl.count &&
-                    i < ICE_TUNNEL_MAX_ENTRIES; i++)
-                       if (hw->tnl.tbl[i].marked) {
-                               hw->tnl.tbl[i].ref = 0;
-                               hw->tnl.tbl[i].port = 0;
-                               hw->tnl.tbl[i].in_use = false;
-                               hw->tnl.tbl[i].marked = false;
-                       }
+               hw->tnl.tbl[index].port = 0;
 
 ice_destroy_tunnel_err:
        ice_pkg_buf_free(hw, bld);
        return status;
 }
 
+int ice_udp_tunnel_set_port(struct net_device *netdev, unsigned int table,
+                           unsigned int idx, struct udp_tunnel_info *ti)
+{
+       struct ice_netdev_priv *np = netdev_priv(netdev);
+       struct ice_vsi *vsi = np->vsi;
+       struct ice_pf *pf = vsi->back;
+       enum ice_tunnel_type tnl_type;
+       enum ice_status status;
+       u16 index;
+
+       tnl_type = ti->type == UDP_TUNNEL_TYPE_VXLAN ? TNL_VXLAN : TNL_GENEVE;
+       index = ice_tunnel_idx_to_entry(&pf->hw, idx, tnl_type);
+
+       status = ice_create_tunnel(&pf->hw, index, tnl_type, ntohs(ti->port));
+       if (status) {
+               netdev_err(netdev, "Error adding UDP tunnel - %s\n",
+                          ice_stat_str(status));
+               return -EIO;
+       }
+
+       udp_tunnel_nic_set_port_priv(netdev, table, idx, index);
+       return 0;
+}
+
+int ice_udp_tunnel_unset_port(struct net_device *netdev, unsigned int table,
+                             unsigned int idx, struct udp_tunnel_info *ti)
+{
+       struct ice_netdev_priv *np = netdev_priv(netdev);
+       struct ice_vsi *vsi = np->vsi;
+       struct ice_pf *pf = vsi->back;
+       enum ice_tunnel_type tnl_type;
+       enum ice_status status;
+
+       tnl_type = ti->type == UDP_TUNNEL_TYPE_VXLAN ? TNL_VXLAN : TNL_GENEVE;
+
+       status = ice_destroy_tunnel(&pf->hw, ti->hw_priv, tnl_type,
+                                   ntohs(ti->port));
+       if (status) {
+               netdev_err(netdev, "Error removing UDP tunnel - %s\n",
+                          ice_stat_str(status));
+               return -EIO;
+       }
+
+       return 0;
+}
+
 /* PTG Management */
 
 /**
 
        }
 
        netdev->netdev_ops = &ice_netdev_ops;
+       netdev->udp_tunnel_nic_info = &pf->hw.udp_tunnel_nic;
        ice_set_ethtool_ops(netdev);
 }
 
        struct device *dev = &pdev->dev;
        struct ice_pf *pf;
        struct ice_hw *hw;
-       int err;
+       int i, err;
 
        /* this driver uses devres, see
         * Documentation/driver-api/driver-model/devres.rst
 
        ice_devlink_init_regions(pf);
 
+       pf->hw.udp_tunnel_nic.set_port = ice_udp_tunnel_set_port;
+       pf->hw.udp_tunnel_nic.unset_port = ice_udp_tunnel_unset_port;
+       pf->hw.udp_tunnel_nic.flags = UDP_TUNNEL_NIC_INFO_MAY_SLEEP;
+       pf->hw.udp_tunnel_nic.shared = &pf->hw.udp_tunnel_shared;
+       i = 0;
+       if (pf->hw.tnl.valid_count[TNL_VXLAN]) {
+               pf->hw.udp_tunnel_nic.tables[i].n_entries =
+                       pf->hw.tnl.valid_count[TNL_VXLAN];
+               pf->hw.udp_tunnel_nic.tables[i].tunnel_types =
+                       UDP_TUNNEL_TYPE_VXLAN;
+               i++;
+       }
+       if (pf->hw.tnl.valid_count[TNL_GENEVE]) {
+               pf->hw.udp_tunnel_nic.tables[i].n_entries =
+                       pf->hw.tnl.valid_count[TNL_GENEVE];
+               pf->hw.udp_tunnel_nic.tables[i].tunnel_types =
+                       UDP_TUNNEL_TYPE_GENEVE;
+               i++;
+       }
+
        pf->num_alloc_vsi = hw->func_caps.guar_num_vsi;
        if (!pf->num_alloc_vsi) {
                err = -EIO;
                goto err_init_pf_unroll;
        }
+       if (pf->num_alloc_vsi > UDP_TUNNEL_NIC_MAX_SHARING_DEVICES) {
+               dev_warn(&pf->pdev->dev,
+                        "limiting the VSI count due to UDP tunnel limitation %d > %d\n",
+                        pf->num_alloc_vsi, UDP_TUNNEL_NIC_MAX_SHARING_DEVICES);
+               pf->num_alloc_vsi = UDP_TUNNEL_NIC_MAX_SHARING_DEVICES;
+       }
 
        pf->vsi = devm_kcalloc(dev, pf->num_alloc_vsi, sizeof(*pf->vsi),
                               GFP_KERNEL);
        pf->tx_timeout_recovery_level++;
 }
 
-/**
- * ice_udp_tunnel_add - Get notifications about UDP tunnel ports that come up
- * @netdev: This physical port's netdev
- * @ti: Tunnel endpoint information
- */
-static void
-ice_udp_tunnel_add(struct net_device *netdev, struct udp_tunnel_info *ti)
-{
-       struct ice_netdev_priv *np = netdev_priv(netdev);
-       struct ice_vsi *vsi = np->vsi;
-       struct ice_pf *pf = vsi->back;
-       enum ice_tunnel_type tnl_type;
-       u16 port = ntohs(ti->port);
-       enum ice_status status;
-
-       switch (ti->type) {
-       case UDP_TUNNEL_TYPE_VXLAN:
-               tnl_type = TNL_VXLAN;
-               break;
-       case UDP_TUNNEL_TYPE_GENEVE:
-               tnl_type = TNL_GENEVE;
-               break;
-       default:
-               netdev_err(netdev, "Unknown tunnel type\n");
-               return;
-       }
-
-       status = ice_create_tunnel(&pf->hw, tnl_type, port);
-       if (status == ICE_ERR_OUT_OF_RANGE)
-               netdev_info(netdev, "Max tunneled UDP ports reached, port %d not added\n",
-                           port);
-       else if (status)
-               netdev_err(netdev, "Error adding UDP tunnel - %s\n",
-                          ice_stat_str(status));
-}
-
-/**
- * ice_udp_tunnel_del - Get notifications about UDP tunnel ports that go away
- * @netdev: This physical port's netdev
- * @ti: Tunnel endpoint information
- */
-static void
-ice_udp_tunnel_del(struct net_device *netdev, struct udp_tunnel_info *ti)
-{
-       struct ice_netdev_priv *np = netdev_priv(netdev);
-       struct ice_vsi *vsi = np->vsi;
-       struct ice_pf *pf = vsi->back;
-       u16 port = ntohs(ti->port);
-       enum ice_status status;
-       bool retval;
-
-       retval = ice_tunnel_port_in_use(&pf->hw, port, NULL);
-       if (!retval) {
-               netdev_info(netdev, "port %d not found in UDP tunnels list\n",
-                           port);
-               return;
-       }
-
-       status = ice_destroy_tunnel(&pf->hw, port, false);
-       if (status)
-               netdev_err(netdev, "error deleting port %d from UDP tunnels list\n",
-                          port);
-}
-
 /**
  * ice_open - Called when a network interface becomes active
  * @netdev: network interface device structure
        .ndo_bpf = ice_xdp,
        .ndo_xdp_xmit = ice_xdp_xmit,
        .ndo_xsk_wakeup = ice_xsk_wakeup,
-       .ndo_udp_tunnel_add = ice_udp_tunnel_add,
-       .ndo_udp_tunnel_del = ice_udp_tunnel_del,
+       .ndo_udp_tunnel_add = udp_tunnel_nic_add_port,
+       .ndo_udp_tunnel_del = udp_tunnel_nic_del_port,
 };