igc: Integrate flex filter into ethtool ops
authorKurt Kanzenbach <kurt@linutronix.de>
Tue, 29 Jun 2021 04:43:29 +0000 (21:43 -0700)
committerTony Nguyen <anthony.l.nguyen@intel.com>
Fri, 16 Jul 2021 21:07:33 +0000 (14:07 -0700)
Use the flex filter mechanism to extend the current ethtool filter
operations by intercoperating the user data. This allows to match
eight more bytes within a Ethernet frame in addition to macs, ether
types and vlan.

The matching pattern looks like this:

 * dest_mac [6]
 * src_mac [6]
 * tpid [2]
 * vlan tci [2]
 * ether type [2]
 * user data [8]

This can be used to match Profinet traffic classes by FrameID range.

Signed-off-by: Kurt Kanzenbach <kurt@linutronix.de>
Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Tested-by: Dvora Fuxbrumer <dvorax.fuxbrumer@linux.intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
drivers/net/ethernet/intel/igc/igc.h
drivers/net/ethernet/intel/igc/igc_defines.h
drivers/net/ethernet/intel/igc/igc_ethtool.c
drivers/net/ethernet/intel/igc/igc_main.c

index 6016c13..c21441c 100644 (file)
@@ -478,18 +478,28 @@ struct igc_q_vector {
 };
 
 enum igc_filter_match_flags {
-       IGC_FILTER_FLAG_ETHER_TYPE =    0x1,
-       IGC_FILTER_FLAG_VLAN_TCI   =    0x2,
-       IGC_FILTER_FLAG_SRC_MAC_ADDR =  0x4,
-       IGC_FILTER_FLAG_DST_MAC_ADDR =  0x8,
+       IGC_FILTER_FLAG_ETHER_TYPE =    BIT(0),
+       IGC_FILTER_FLAG_VLAN_TCI   =    BIT(1),
+       IGC_FILTER_FLAG_SRC_MAC_ADDR =  BIT(2),
+       IGC_FILTER_FLAG_DST_MAC_ADDR =  BIT(3),
+       IGC_FILTER_FLAG_USER_DATA =     BIT(4),
+       IGC_FILTER_FLAG_VLAN_ETYPE =    BIT(5),
 };
 
 struct igc_nfc_filter {
        u8 match_flags;
        u16 etype;
+       __be16 vlan_etype;
        u16 vlan_tci;
        u8 src_addr[ETH_ALEN];
        u8 dst_addr[ETH_ALEN];
+       u8 user_data[8];
+       u8 user_mask[8];
+       u8 flex_index;
+       u8 rx_queue;
+       u8 prio;
+       u8 immediate_irq;
+       u8 drop;
 };
 
 struct igc_nfc_rule {
@@ -499,10 +509,10 @@ struct igc_nfc_rule {
        u16 action;
 };
 
-/* IGC supports a total of 32 NFC rules: 16 MAC address based,, 8 VLAN priority
- * based, and 8 ethertype based.
+/* IGC supports a total of 32 NFC rules: 16 MAC address based, 8 VLAN priority
+ * based, 8 ethertype based and 32 Flex filter based rules.
  */
-#define IGC_MAX_RXNFC_RULES            32
+#define IGC_MAX_RXNFC_RULES            64
 
 struct igc_flex_filter {
        u8 index;
index 6d6267d..c631569 100644 (file)
@@ -32,6 +32,8 @@
 #define IGC_WUFC_FLX6          BIT(22)    /* Flexible Filter 6 Enable */
 #define IGC_WUFC_FLX7          BIT(23)    /* Flexible Filter 7 Enable */
 
+#define IGC_WUFC_FILTER_MASK GENMASK(23, 14)
+
 #define IGC_CTRL_ADVD3WUC      0x00100000  /* D3 WUC */
 
 /* Wake Up Status */
@@ -81,6 +83,8 @@
 #define IGC_WUFC_EXT_FLX30     BIT(30) /* Flexible Filter 30 Enable */
 #define IGC_WUFC_EXT_FLX31     BIT(31) /* Flexible Filter 31 Enable */
 
+#define IGC_WUFC_EXT_FILTER_MASK GENMASK(31, 8)
+
 /* Physical Func Reset Done Indication */
 #define IGC_CTRL_EXT_LINK_MODE_MASK    0x00C00000
 
index fa41718..3d46eff 100644 (file)
@@ -979,6 +979,12 @@ static int igc_ethtool_get_nfc_rule(struct igc_adapter *adapter,
                eth_broadcast_addr(fsp->m_u.ether_spec.h_source);
        }
 
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_USER_DATA) {
+               fsp->flow_type |= FLOW_EXT;
+               memcpy(fsp->h_ext.data, rule->filter.user_data, sizeof(fsp->h_ext.data));
+               memcpy(fsp->m_ext.data, rule->filter.user_mask, sizeof(fsp->m_ext.data));
+       }
+
        mutex_unlock(&adapter->nfc_rule_lock);
        return 0;
 
@@ -1215,6 +1221,20 @@ static void igc_ethtool_init_nfc_rule(struct igc_nfc_rule *rule,
                ether_addr_copy(rule->filter.dst_addr,
                                fsp->h_u.ether_spec.h_dest);
        }
+
+       /* Check for user defined data */
+       if ((fsp->flow_type & FLOW_EXT) &&
+           (fsp->h_ext.data[0] || fsp->h_ext.data[1])) {
+               rule->filter.match_flags |= IGC_FILTER_FLAG_USER_DATA;
+               memcpy(rule->filter.user_data, fsp->h_ext.data, sizeof(fsp->h_ext.data));
+               memcpy(rule->filter.user_mask, fsp->m_ext.data, sizeof(fsp->m_ext.data));
+
+               /* VLAN etype matching is only valid using flex filter */
+               if ((fsp->flow_type & FLOW_EXT) && fsp->h_ext.vlan_etype) {
+                       rule->filter.vlan_etype = fsp->h_ext.vlan_etype;
+                       rule->filter.match_flags |= IGC_FILTER_FLAG_VLAN_ETYPE;
+               }
+       }
 }
 
 /**
index 0f8cd22..9999d8f 100644 (file)
@@ -3116,8 +3116,8 @@ static int igc_flex_filter_select(struct igc_adapter *adapter,
        return 0;
 }
 
-static int __maybe_unused igc_write_flex_filter_ll(struct igc_adapter *adapter,
-                                                  struct igc_flex_filter *input)
+static int igc_write_flex_filter_ll(struct igc_adapter *adapter,
+                                   struct igc_flex_filter *input)
 {
        struct device *dev = &adapter->pdev->dev;
        struct igc_hw *hw = &adapter->hw;
@@ -3209,11 +3209,192 @@ static int __maybe_unused igc_write_flex_filter_ll(struct igc_adapter *adapter,
        return 0;
 }
 
+static void igc_flex_filter_add_field(struct igc_flex_filter *flex,
+                                     const void *src, unsigned int offset,
+                                     size_t len, const void *mask)
+{
+       int i;
+
+       /* data */
+       memcpy(&flex->data[offset], src, len);
+
+       /* mask */
+       for (i = 0; i < len; ++i) {
+               const unsigned int idx = i + offset;
+               const u8 *ptr = mask;
+
+               if (mask) {
+                       if (ptr[i] & 0xff)
+                               flex->mask[idx / 8] |= BIT(idx % 8);
+
+                       continue;
+               }
+
+               flex->mask[idx / 8] |= BIT(idx % 8);
+       }
+}
+
+static int igc_find_avail_flex_filter_slot(struct igc_adapter *adapter)
+{
+       struct igc_hw *hw = &adapter->hw;
+       u32 wufc, wufc_ext;
+       int i;
+
+       wufc = rd32(IGC_WUFC);
+       wufc_ext = rd32(IGC_WUFC_EXT);
+
+       for (i = 0; i < MAX_FLEX_FILTER; i++) {
+               if (i < 8) {
+                       if (!(wufc & (IGC_WUFC_FLX0 << i)))
+                               return i;
+               } else {
+                       if (!(wufc_ext & (IGC_WUFC_EXT_FLX8 << (i - 8))))
+                               return i;
+               }
+       }
+
+       return -ENOSPC;
+}
+
+static bool igc_flex_filter_in_use(struct igc_adapter *adapter)
+{
+       struct igc_hw *hw = &adapter->hw;
+       u32 wufc, wufc_ext;
+
+       wufc = rd32(IGC_WUFC);
+       wufc_ext = rd32(IGC_WUFC_EXT);
+
+       if (wufc & IGC_WUFC_FILTER_MASK)
+               return true;
+
+       if (wufc_ext & IGC_WUFC_EXT_FILTER_MASK)
+               return true;
+
+       return false;
+}
+
+static int igc_add_flex_filter(struct igc_adapter *adapter,
+                              struct igc_nfc_rule *rule)
+{
+       struct igc_flex_filter flex = { };
+       struct igc_nfc_filter *filter = &rule->filter;
+       unsigned int eth_offset, user_offset;
+       int ret, index;
+       bool vlan;
+
+       index = igc_find_avail_flex_filter_slot(adapter);
+       if (index < 0)
+               return -ENOSPC;
+
+       /* Construct the flex filter:
+        *  -> dest_mac [6]
+        *  -> src_mac [6]
+        *  -> tpid [2]
+        *  -> vlan tci [2]
+        *  -> ether type [2]
+        *  -> user data [8]
+        *  -> = 26 bytes => 32 length
+        */
+       flex.index    = index;
+       flex.length   = 32;
+       flex.rx_queue = rule->action;
+
+       vlan = rule->filter.vlan_tci || rule->filter.vlan_etype;
+       eth_offset = vlan ? 16 : 12;
+       user_offset = vlan ? 18 : 14;
+
+       /* Add destination MAC  */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_DST_MAC_ADDR)
+               igc_flex_filter_add_field(&flex, &filter->dst_addr, 0,
+                                         ETH_ALEN, NULL);
+
+       /* Add source MAC */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_SRC_MAC_ADDR)
+               igc_flex_filter_add_field(&flex, &filter->src_addr, 6,
+                                         ETH_ALEN, NULL);
+
+       /* Add VLAN etype */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_VLAN_ETYPE)
+               igc_flex_filter_add_field(&flex, &filter->vlan_etype, 12,
+                                         sizeof(filter->vlan_etype),
+                                         NULL);
+
+       /* Add VLAN TCI */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_VLAN_TCI)
+               igc_flex_filter_add_field(&flex, &filter->vlan_tci, 14,
+                                         sizeof(filter->vlan_tci), NULL);
+
+       /* Add Ether type */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_ETHER_TYPE) {
+               __be16 etype = cpu_to_be16(filter->etype);
+
+               igc_flex_filter_add_field(&flex, &etype, eth_offset,
+                                         sizeof(etype), NULL);
+       }
+
+       /* Add user data */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_USER_DATA)
+               igc_flex_filter_add_field(&flex, &filter->user_data,
+                                         user_offset,
+                                         sizeof(filter->user_data),
+                                         filter->user_mask);
+
+       /* Add it down to the hardware and enable it. */
+       ret = igc_write_flex_filter_ll(adapter, &flex);
+       if (ret)
+               return ret;
+
+       filter->flex_index = index;
+
+       return 0;
+}
+
+static void igc_del_flex_filter(struct igc_adapter *adapter,
+                               u16 reg_index)
+{
+       struct igc_hw *hw = &adapter->hw;
+       u32 wufc;
+
+       /* Just disable the filter. The filter table itself is kept
+        * intact. Another flex_filter_add() should override the "old" data
+        * then.
+        */
+       if (reg_index > 8) {
+               u32 wufc_ext = rd32(IGC_WUFC_EXT);
+
+               wufc_ext &= ~(IGC_WUFC_EXT_FLX8 << (reg_index - 8));
+               wr32(IGC_WUFC_EXT, wufc_ext);
+       } else {
+               wufc = rd32(IGC_WUFC);
+
+               wufc &= ~(IGC_WUFC_FLX0 << reg_index);
+               wr32(IGC_WUFC, wufc);
+       }
+
+       if (igc_flex_filter_in_use(adapter))
+               return;
+
+       /* No filters are in use, we may disable flex filters */
+       wufc = rd32(IGC_WUFC);
+       wufc &= ~IGC_WUFC_FLEX_HQ;
+       wr32(IGC_WUFC, wufc);
+}
+
 static int igc_enable_nfc_rule(struct igc_adapter *adapter,
-                              const struct igc_nfc_rule *rule)
+                              struct igc_nfc_rule *rule)
 {
        int err;
 
+       /* Check for user data first: When user data is set, the only option is
+        * to use a flex filter. When more options are set (ethertype, vlan tci,
+        * ...) construct a flex filter matching all of that.
+        */
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_USER_DATA) {
+               err = igc_add_flex_filter(adapter, rule);
+               if (err)
+                       return err;
+       }
+
        if (rule->filter.match_flags & IGC_FILTER_FLAG_ETHER_TYPE) {
                err = igc_add_etype_filter(adapter, rule->filter.etype,
                                           rule->action);
@@ -3250,6 +3431,9 @@ static int igc_enable_nfc_rule(struct igc_adapter *adapter,
 static void igc_disable_nfc_rule(struct igc_adapter *adapter,
                                 const struct igc_nfc_rule *rule)
 {
+       if (rule->filter.match_flags & IGC_FILTER_FLAG_USER_DATA)
+               igc_del_flex_filter(adapter, rule->filter.flex_index);
+
        if (rule->filter.match_flags & IGC_FILTER_FLAG_ETHER_TYPE)
                igc_del_etype_filter(adapter, rule->filter.etype);