net/mlx4: restore promisc and allmulti support
[dpdk.git] / drivers / net / mlx4 / mlx4_flow.c
index 14d2ed3..41423cd 100644 (file)
@@ -107,7 +107,9 @@ struct mlx4_drop {
  *
  * Additional mlx4-specific constraints on supported fields:
  *
- * - No support for partial masks.
+ * - No support for partial masks, except in the specific case of matching
+ *   all multicast traffic (@p spec->dst and @p mask->dst equal to
+ *   01:00:00:00:00:00).
  * - Not providing @p item->spec or providing an empty @p mask->dst is
  *   *only* supported if the rule doesn't specify additional matching
  *   criteria (i.e. rule is promiscuous-like).
@@ -152,6 +154,13 @@ mlx4_flow_merge_eth(struct rte_flow *flow,
                        goto error;
                } else if (!sum_dst) {
                        flow->promisc = 1;
+               } else if (sum_dst == 1 && mask->dst.addr_bytes[0] == 1) {
+                       if (!(spec->dst.addr_bytes[0] & 1)) {
+                               msg = "mlx4 does not support the explicit"
+                                       " exclusion of all multicast traffic";
+                               goto error;
+                       }
+                       flow->allmulti = 1;
                } else if (sum_dst != (UINT8_C(0xff) * ETHER_ADDR_LEN)) {
                        msg = "mlx4 does not support matching partial"
                                " Ethernet fields";
@@ -164,6 +173,10 @@ mlx4_flow_merge_eth(struct rte_flow *flow,
                flow->ibv_attr->type = IBV_FLOW_ATTR_ALL_DEFAULT;
                return 0;
        }
+       if (flow->allmulti) {
+               flow->ibv_attr->type = IBV_FLOW_ATTR_MC_DEFAULT;
+               return 0;
+       }
        ++flow->ibv_attr->num_of_specs;
        eth = (void *)((uintptr_t)flow->ibv_attr + flow->ibv_attr_size);
        *eth = (struct ibv_flow_spec_eth) {
@@ -615,7 +628,7 @@ fill:
                        flow->internal = 1;
                        continue;
                }
-               if (flow->promisc) {
+               if (flow->promisc || flow->allmulti) {
                        msg = "mlx4 does not support additional matching"
                                " criteria combined with indiscriminate"
                                " matching on Ethernet headers";
@@ -1008,12 +1021,45 @@ mlx4_flow_flush(struct rte_eth_dev *dev,
        return 0;
 }
 
+/**
+ * Helper function to determine the next configured VLAN filter.
+ *
+ * @param priv
+ *   Pointer to private structure.
+ * @param vlan
+ *   VLAN ID to use as a starting point.
+ *
+ * @return
+ *   Next configured VLAN ID or a high value (>= 4096) if there is none.
+ */
+static uint16_t
+mlx4_flow_internal_next_vlan(struct priv *priv, uint16_t vlan)
+{
+       while (vlan < 4096) {
+               if (priv->dev->data->vlan_filter_conf.ids[vlan / 64] &
+                   (UINT64_C(1) << (vlan % 64)))
+                       return vlan;
+               ++vlan;
+       }
+       return vlan;
+}
+
 /**
  * Generate internal flow rules.
  *
+ * Various flow rules are created depending on the mode the device is in:
+ *
+ * 1. Promiscuous: port MAC + catch-all (VLAN filtering is ignored).
+ * 2. All multicast: port MAC/VLAN + catch-all multicast.
+ * 3. Otherwise: port MAC/VLAN + broadcast MAC/VLAN.
+ *
+ * About MAC flow rules:
+ *
  * - MAC flow rules are generated from @p dev->data->mac_addrs
  *   (@p priv->mac array).
  * - An additional flow rule for Ethernet broadcasts is also generated.
+ * - All these are per-VLAN if @p dev->data->dev_conf.rxmode.hw_vlan_filter
+ *   is enabled and VLAN filters are configured.
  *
  * @param priv
  *   Pointer to private structure.
@@ -1034,6 +1080,13 @@ mlx4_flow_internal(struct priv *priv, struct rte_flow_error *error)
        const struct rte_flow_item_eth eth_mask = {
                .dst.addr_bytes = "\xff\xff\xff\xff\xff\xff",
        };
+       const struct rte_flow_item_eth eth_allmulti = {
+               .dst.addr_bytes = "\x01\x00\x00\x00\x00\x00",
+       };
+       struct rte_flow_item_vlan vlan_spec;
+       const struct rte_flow_item_vlan vlan_mask = {
+               .tci = RTE_BE16(0x0fff),
+       };
        struct rte_flow_item pattern[] = {
                {
                        .type = MLX4_FLOW_ITEM_TYPE_INTERNAL,
@@ -1043,6 +1096,10 @@ mlx4_flow_internal(struct priv *priv, struct rte_flow_error *error)
                        .spec = &eth_spec,
                        .mask = &eth_mask,
                },
+               {
+                       /* Replaced with VLAN if filtering is enabled. */
+                       .type = RTE_FLOW_ITEM_TYPE_END,
+               },
                {
                        .type = RTE_FLOW_ITEM_TYPE_END,
                },
@@ -1059,11 +1116,38 @@ mlx4_flow_internal(struct priv *priv, struct rte_flow_error *error)
                },
        };
        struct ether_addr *rule_mac = &eth_spec.dst;
+       rte_be16_t *rule_vlan =
+               priv->dev->data->dev_conf.rxmode.hw_vlan_filter &&
+               !priv->dev->data->promiscuous ?
+               &vlan_spec.tci :
+               NULL;
+       int broadcast =
+               !priv->dev->data->promiscuous &&
+               !priv->dev->data->all_multicast;
+       uint16_t vlan = 0;
        struct rte_flow *flow;
        unsigned int i;
        int err = 0;
 
-       for (i = 0; i != RTE_DIM(priv->mac) + 1; ++i) {
+       /*
+        * Set up VLAN item if filtering is enabled and at least one VLAN
+        * filter is configured.
+        */
+       if (rule_vlan) {
+               vlan = mlx4_flow_internal_next_vlan(priv, 0);
+               if (vlan < 4096) {
+                       pattern[2] = (struct rte_flow_item){
+                               .type = RTE_FLOW_ITEM_TYPE_VLAN,
+                               .spec = &vlan_spec,
+                               .mask = &vlan_mask,
+                       };
+next_vlan:
+                       *rule_vlan = rte_cpu_to_be_16(vlan);
+               } else {
+                       rule_vlan = NULL;
+               }
+       }
+       for (i = 0; i != RTE_DIM(priv->mac) + broadcast; ++i) {
                const struct ether_addr *mac;
 
                /* Broadcasts are handled by an extra iteration. */
@@ -1087,6 +1171,12 @@ mlx4_flow_internal(struct priv *priv, struct rte_flow_error *error)
                        assert(flow->ibv_attr->type == IBV_FLOW_ATTR_NORMAL);
                        assert(flow->ibv_attr->num_of_specs == 1);
                        assert(eth->type == IBV_FLOW_SPEC_ETH);
+                       if (rule_vlan &&
+                           (eth->val.vlan_tag != *rule_vlan ||
+                            eth->mask.vlan_tag != RTE_BE16(0x0fff)))
+                               continue;
+                       if (!rule_vlan && eth->mask.vlan_tag)
+                               continue;
                        for (j = 0; j != sizeof(mac->addr_bytes); ++j)
                                if (eth->val.dst_mac[j] != mac->addr_bytes[j] ||
                                    eth->mask.dst_mac[j] != UINT8_C(0xff) ||
@@ -1103,18 +1193,59 @@ mlx4_flow_internal(struct priv *priv, struct rte_flow_error *error)
                                                actions, error);
                        if (!flow) {
                                err = -rte_errno;
-                               break;
+                               goto error;
                        }
                }
                flow->select = 1;
                flow->mac = 1;
        }
-       /* Clear selection and clean up stale MAC flow rules. */
+       if (rule_vlan) {
+               vlan = mlx4_flow_internal_next_vlan(priv, vlan + 1);
+               if (vlan < 4096)
+                       goto next_vlan;
+       }
+       /* Take care of promiscuous and all multicast flow rules. */
+       if (!broadcast) {
+               for (flow = LIST_FIRST(&priv->flows);
+                    flow && flow->internal;
+                    flow = LIST_NEXT(flow, next)) {
+                       if (priv->dev->data->promiscuous) {
+                               if (flow->promisc)
+                                       break;
+                       } else {
+                               assert(priv->dev->data->all_multicast);
+                               if (flow->allmulti)
+                                       break;
+                       }
+               }
+               if (!flow || !flow->internal) {
+                       /* Not found, create a new flow rule. */
+                       if (priv->dev->data->promiscuous) {
+                               pattern[1].spec = NULL;
+                               pattern[1].mask = NULL;
+                       } else {
+                               assert(priv->dev->data->all_multicast);
+                               pattern[1].spec = &eth_allmulti;
+                               pattern[1].mask = &eth_allmulti;
+                       }
+                       pattern[2] = pattern[3];
+                       flow = mlx4_flow_create(priv->dev, &attr, pattern,
+                                               actions, error);
+                       if (!flow) {
+                               err = -rte_errno;
+                               goto error;
+                       }
+               }
+               assert(flow->promisc || flow->allmulti);
+               flow->select = 1;
+       }
+error:
+       /* Clear selection and clean up stale internal flow rules. */
        flow = LIST_FIRST(&priv->flows);
        while (flow && flow->internal) {
                struct rte_flow *next = LIST_NEXT(flow, next);
 
-               if (flow->mac && !flow->select)
+               if (!flow->select)
                        claim_zero(mlx4_flow_destroy(priv->dev, flow, error));
                else
                        flow->select = 0;