When an ice port is part of a vlan-filtering bridge with a wide VLAN
trunk and the netdev is in IFF_PROMISC (typical for bond slaves
attached to a bridge), the driver installs per-VLAN
ICE_SW_LKUP_PROMISC_VLAN entries (recipe 9) in addition to the broad
ICE_SW_LKUP_DFLT VSI Rx rule (recipe 5). Each per-VLAN rule consumes
one Flow Lookup Unit (FLU) entry from a fixed hardware pool of "up to
32K FLU entries" per device, documented in the E810 datasheet
(613875-009 section 7.8.10, Table 7-18, page 1015).

With three active PFs sharing one switch context and a bridge trunk of
vid 2-4094, the configuration would require roughly

  3 PFs * 4093 VLANs * 3 rules per VLAN per PF ~= 36,800 rules

which exceeds the 32K FLU budget. Firmware then responds to further
Add Switch Rules requests with AQ retval 0x10 (LIBIE_AQ_RC_ENOSPC) and
the user-visible failure surfaces as

  ice 0000:5c:00.1: Failed to set VSI 14 as the default forwarding
                    VSI, error -5
  ice 0000:5c:00.1 ens1f1: Error -5 setting default VSI 14 Rx rule

After a switch context has been driven into overrun, subsequent
retries can come back as AQ retval 0x2 (LIBIE_AQ_RC_ENOENT), which has
misled triage attempts toward a perceived recipe binding defect
rather than a capacity issue.

When the DFLT VSI Rx rule is in place it catches every packet on the
lport regardless of VLAN tag, so the per-VLAN PROMISC_VLAN expansion
is redundant. The recipe 4 VLAN prune entries are still installed
per VLAN and continue to track the allowed VID set, but the
IFF_PROMISC sync path disables their enforcement on the VSI via
vlan_ops->dis_rx_filtering() before ice_set_promisc() runs.
ena_rx_filtering() is restored when IFF_PROMISC is cleared.

Skip the per-VLAN expansion at the two call sites that drive it:
ice_set_promisc() falls through to ice_fltr_set_vsi_promisc() and
ice_vlan_rx_add_vid() omits the per-VLAN ICE_MCAST_VLAN_PROMISC_BITS
add. Plain IFF_ALLMULTI without an installed DFLT VSI rule is
unchanged and still installs per-VLAN multicast promisc rules.

Both checks use ice_is_vsi_dflt_vsi() which inspects the recipe
filter list for an installed DFLT rule on this VSI, not
netdev->flags & IFF_PROMISC. The HW-state predicate avoids two
regression vectors that a user-intent predicate would introduce:

1. ice_lag_is_switchdev_running() short-circuits ice_set_dflt_vsi()
   to return 0 without installing the DFLT rule for a PF in
   switchdev LAG mode. An IFF_PROMISC-only check would also
   suppress the per-VLAN fallback, leaving the PF with no rule.

2. When ice_set_dflt_vsi() returns a non-EEXIST error (FLU
   exhausted, switch context divergence), the driver clears
   IFF_PROMISC from vsi->current_netdev_flags but the netdev's own
   flags retain IFF_PROMISC. The user-intent predicate would still
   suppress the per-VLAN fallback even though DFLT failed to
   install.

The predicate is install-time only. The IFF_PROMISC off path closes
the lifecycle gap in ice_vsi_exit_dflt_promisc(): for an IFF_ALLMULTI
VSI with VLANs it reinstates the per-VID rules before clearing the
default rule, so multicast coverage never lapses. If that AQ call
fails the default rule is left in place, ice_vsi_exit_dflt_promisc()
returns the error, and the sync_fltr pass bails with
vsi->current_netdev_flags |= IFF_PROMISC; the current/netdev flag
mismatch re-fires the IFF_PROMISC off path on the next sync. Clearing
the default rule first would instead expose a window where neither
the default rule nor the per-VID rules carry multicast.

If ice_clear_dflt_vsi() fails after the per-VID rules were reinstated
they are deliberately not rolled back. Clearing the default rule is a
removal that frees an FLU entry rather than allocating one, so it
cannot fail for lack of space; a failure is a transient AdminQ error.
The per-VID rules are the steady state for an IFF_ALLMULTI VLAN VSI,
so the only redundant entry left behind is the single un-removed
default rule, not the per-VID set. The retry re-enters this path,
ice_fltr_set_vlan_vsi_promisc() returns -EEXIST for the rules that
already exist so nothing is reallocated, and the default rule is
removed on the next attempt. Rolling the per-VID rules back here would
instead churn thousands of removes and re-adds on every retry.

After the default rule is gone the vid=0 PROMISC rule that paired
with it is redundant and is dropped, but only to reclaim a filter
entry, so a failure there is logged and does not abort the
transition.

ice_set_vsi_promisc() and ice_clear_vsi_promisc() dispatch the
recipe based on whether ICE_PROMISC_VLAN_RX/TX bits are present in
the mask: with the bits set, recipe ICE_SW_LKUP_PROMISC_VLAN is
used; otherwise ICE_SW_LKUP_PROMISC. The else branch in
ice_set_promisc() installs the vid=0 rule in ICE_SW_LKUP_PROMISC.
Because ice_clear_promisc() with VLANs present adds the VLAN bits
and would search ICE_SW_LKUP_PROMISC_VLAN, the recipe mismatch
would leave the vid=0 ICE_SW_LKUP_PROMISC rule orphaned when VLANs
are configured. This is a single stale rule, not a per-cycle leak:
re-adding it on the next promisc on returns -EEXIST rather than
allocating a new entry. The set-time recipe is not recorded, so
ice_clear_promisc() clears both recipes; clearing a rule that is not
present succeeds, both clears run unconditionally, and the first
error is returned.

The two VLAN-0 recipe transition blocks in ice_vlan_rx_add_vid()
and ice_vlan_rx_kill_vid() that promote / demote the vid=0 rule
between ICE_SW_LKUP_PROMISC and ICE_SW_LKUP_PROMISC_VLAN are
likewise guarded by !ice_is_vsi_dflt_vsi(). With DFLT in place the
vid=0 rule already covers every VID and a recipe swap would only
install a redundant rule.

Lab reproduction on an E810-C with the same firmware family (4.80,
NVM 1.3805.0, DDP 1.3.43.0) using four PFs in vlan-filtering bridges
with vid 2-4094 and the slaves brought to IFF_PROMISC before the
bridge VLAN bulk add:

  before fix:  ~12,279 AQ Add Switch Rules per PF, ENOSPC and ENOENT
               responses in dmesg, DFLT VSI Rx rule install fails on
               the affected PF
  after fix:   ~4,093 AQ Add Switch Rules per PF, no AQ errors, DFLT
               VSI Rx rule installs on every PF

The 66.7% reduction in installed switch rules per PF matches the
expected per-VLAN saving: a single DFLT rule replaces the per-VID
PROMISC_VLAN expansion.

Functional regression test with vid 2-100 trunk between two ice
ports through the lab switch (40/40 PASS, 0 AQ errors, 0 ENOSPC
at 4093-VID customer scale):

  vid 50 unicast, vid 100 unicast, vid 50 broadcast ARP,
    vid 100 multicast IPv6 ND
  vid 200/500/1500/4000 isolation (out-of-trunk) and untagged not
    leaked: 0 packets reach any bridge endpoint
  IGMP/MLD snooping, Jumbo MTU 9000, reserved-multicast STP BPDU
  IFF_PROMISC + IFF_ALLMULTI transition (off while allmulti stays)
  Regression reproducer for commit 1273f89578f2 ("ice: Fix broken
    IFF_ALLMULTI handling"): allmulti on -> add vid -> allmulti off
    -> allmulti on plus the orphan-rule Scenario 2; both converge
    with no stale rules
  100-VID, 1000-VID, 4093-VID stress cycles (5/3/2 iterations each)
  switchdev mode toggle preserves IFF_PROMISC pruning state across
    the session (vid 999 multicast received before and after the
    legacy -> switchdev -> legacy cycle)
  SR-IOV: VFs unaffected because ice_set_promisc() early-returns
    for non-PF VSI and VF representors do not register
    ndo_vlan_rx_add_vid

Fixes: 1273f89578f2 ("ice: Fix broken IFF_ALLMULTI handling")
Signed-off-by: Petr Oros <[email protected]>
---
 drivers/net/ethernet/intel/ice/ice_main.c | 90 ++++++++++++++++++-----
 1 file changed, 70 insertions(+), 20 deletions(-)

diff --git a/drivers/net/ethernet/intel/ice/ice_main.c 
b/drivers/net/ethernet/intel/ice/ice_main.c
index 6d24056c247cf4..af8df81fc45623 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -274,7 +274,8 @@ static int ice_set_promisc(struct ice_vsi *vsi, u8 
promisc_m)
        if (vsi->type != ICE_VSI_PF)
                return 0;
 
-       if (ice_vsi_has_non_zero_vlans(vsi)) {
+       /* skip per-VID expansion; the DFLT Rx rule already covers every VID */
+       if (ice_vsi_has_non_zero_vlans(vsi) && !ice_is_vsi_dflt_vsi(vsi)) {
                promisc_m |= (ICE_PROMISC_VLAN_RX | ICE_PROMISC_VLAN_TX);
                status = ice_fltr_set_vlan_vsi_promisc(&vsi->back->hw, vsi,
                                                       promisc_m);
@@ -304,9 +305,19 @@ static int ice_clear_promisc(struct ice_vsi *vsi, u8 
promisc_m)
                return 0;
 
        if (ice_vsi_has_non_zero_vlans(vsi)) {
-               promisc_m |= (ICE_PROMISC_VLAN_RX | ICE_PROMISC_VLAN_TX);
+               int vid0_status;
+
+               /* set time used either recipe (per-VID PROMISC_VLAN, or vid=0
+                * PROMISC via the ice_set_promisc() else branch), so clear
+                * both; clearing an absent rule succeeds
+                */
                status = ice_fltr_clear_vlan_vsi_promisc(&vsi->back->hw, vsi,
-                                                        promisc_m);
+                               promisc_m | ICE_PROMISC_VLAN_RX |
+                               ICE_PROMISC_VLAN_TX);
+               vid0_status = ice_fltr_clear_vsi_promisc(&vsi->back->hw,
+                                                        vsi->idx, promisc_m, 
0);
+               if (!status)
+                       status = vid0_status;
        } else {
                status = ice_fltr_clear_vsi_promisc(&vsi->back->hw, vsi->idx,
                                                    promisc_m, 0);
@@ -317,6 +328,49 @@ static int ice_clear_promisc(struct ice_vsi *vsi, u8 
promisc_m)
        return status;
 }
 
+/**
+ * ice_vsi_exit_dflt_promisc - drop the default VSI Rx rule on promisc off
+ * @vsi: the VSI leaving promiscuous mode
+ *
+ * For an IFF_ALLMULTI VSI with VLANs the per-VID multicast rules are
+ * reinstated before the default rule is cleared so coverage never lapses;
+ * the then redundant vid=0 rule is dropped best-effort. The callees log
+ * their own failures, so error returns are not re-logged here.
+ *
+ * Return: 0 on success, negative on error with the default rule left in place.
+ */
+static int ice_vsi_exit_dflt_promisc(struct ice_vsi *vsi)
+{
+       struct ice_vsi_vlan_ops *vlan_ops = ice_get_compat_vsi_vlan_ops(vsi);
+       struct net_device *netdev = vsi->netdev;
+       struct ice_hw *hw = &vsi->back->hw;
+       bool restore_mc;
+       int err;
+
+       restore_mc = (vsi->current_netdev_flags & IFF_ALLMULTI) &&
+                    ice_vsi_has_non_zero_vlans(vsi);
+
+       if (restore_mc) {
+               err = ice_fltr_set_vlan_vsi_promisc(hw, vsi,
+                                                   
ICE_MCAST_VLAN_PROMISC_BITS);
+               if (err && err != -EEXIST)
+                       return err;
+       }
+
+       err = ice_clear_dflt_vsi(vsi);
+       if (err)
+               return err;
+
+       if (netdev->features & NETIF_F_HW_VLAN_CTAG_FILTER)
+               vlan_ops->ena_rx_filtering(vsi);
+
+       if (restore_mc)
+               ice_fltr_clear_vsi_promisc(hw, vsi->idx, ICE_MCAST_PROMISC_BITS,
+                                          0);
+
+       return 0;
+}
+
 /**
  * ice_vsi_sync_fltr - Update the VSI filter list to the HW
  * @vsi: ptr to the VSI
@@ -442,17 +496,12 @@ static int ice_vsi_sync_fltr(struct ice_vsi *vsi)
                } else {
                        /* Clear Rx filter to remove traffic from wire */
                        if (ice_is_vsi_dflt_vsi(vsi)) {
-                               err = ice_clear_dflt_vsi(vsi);
+                               err = ice_vsi_exit_dflt_promisc(vsi);
                                if (err) {
-                                       netdev_err(netdev, "Error %d clearing 
default VSI %i Rx rule\n",
-                                                  err, vsi->vsi_num);
                                        vsi->current_netdev_flags |=
                                                IFF_PROMISC;
                                        goto out_promisc;
                                }
-                               if (vsi->netdev->features &
-                                   NETIF_F_HW_VLAN_CTAG_FILTER)
-                                       vlan_ops->ena_rx_filtering(vsi);
                        }
 
                        /* disable allmulti here, but only if allmulti is not
@@ -3676,10 +3725,9 @@ int ice_vlan_rx_add_vid(struct net_device *netdev, 
__be16 proto, u16 vid)
        while (test_and_set_bit(ICE_CFG_BUSY, vsi->state))
                usleep_range(1000, 2000);
 
-       /* Add multicast promisc rule for the VLAN ID to be added if
-        * all-multicast is currently enabled.
-        */
-       if (vsi->current_netdev_flags & IFF_ALLMULTI) {
+       /* skip the per-VID rule when the DFLT Rx rule already covers this VID 
*/
+       if ((vsi->current_netdev_flags & IFF_ALLMULTI) &&
+           !ice_is_vsi_dflt_vsi(vsi)) {
                ret = ice_fltr_set_vsi_promisc(&vsi->back->hw, vsi->idx,
                                               ICE_MCAST_VLAN_PROMISC_BITS,
                                               vid);
@@ -3697,11 +3745,12 @@ int ice_vlan_rx_add_vid(struct net_device *netdev, 
__be16 proto, u16 vid)
        if (ret)
                goto finish;
 
-       /* If all-multicast is currently enabled and this VLAN ID is only one
-        * besides VLAN-0 we have to update look-up type of multicast promisc
-        * rule for VLAN-0 from ICE_SW_LKUP_PROMISC to ICE_SW_LKUP_PROMISC_VLAN.
+       /* On the first non-zero VLAN, promote the VLAN-0 multicast promisc
+        * rule from ICE_SW_LKUP_PROMISC to ICE_SW_LKUP_PROMISC_VLAN. Skip when
+        * the DFLT Rx rule is installed; it already covers every VID.
         */
        if ((vsi->current_netdev_flags & IFF_ALLMULTI) &&
+           !ice_is_vsi_dflt_vsi(vsi) &&
            ice_vsi_num_non_zero_vlans(vsi) == 1) {
                ice_fltr_clear_vsi_promisc(&vsi->back->hw, vsi->idx,
                                           ICE_MCAST_PROMISC_BITS, 0);
@@ -3764,11 +3813,12 @@ int ice_vlan_rx_kill_vid(struct net_device *netdev, 
__be16 proto, u16 vid)
                                           ICE_MCAST_VLAN_PROMISC_BITS, vid);
 
        if (!ice_vsi_has_non_zero_vlans(vsi)) {
-               /* Update look-up type of multicast promisc rule for VLAN 0
-                * from ICE_SW_LKUP_PROMISC_VLAN to ICE_SW_LKUP_PROMISC when
-                * all-multicast is enabled and VLAN 0 is the only VLAN rule.
+               /* Last non-zero VLAN gone: demote the VLAN-0 multicast promisc
+                * rule back to ICE_SW_LKUP_PROMISC. Skip when the DFLT Rx rule
+                * is installed; no recipe swap is needed.
                 */
-               if (vsi->current_netdev_flags & IFF_ALLMULTI) {
+               if ((vsi->current_netdev_flags & IFF_ALLMULTI) &&
+                   !ice_is_vsi_dflt_vsi(vsi)) {
                        ice_fltr_clear_vsi_promisc(&vsi->back->hw, vsi->idx,
                                                   ICE_MCAST_VLAN_PROMISC_BITS,
                                                   0);
-- 
2.53.0

Reply via email to