net/enic: support VXLAN decap action combined with VLAN pop
[dpdk.git] / drivers / net / enic / enic_fm_flow.c
index e299b32..34c9005 100644 (file)
@@ -97,6 +97,8 @@ struct enic_fm_flow {
        uint64_t action_handle;
        struct enic_fm_counter *counter;
        struct enic_fm_fet *fet;
+       /* Auto-added steer action for hairpin flows (e.g. vnic->vnic) */
+       struct enic_fm_flow *hairpin_steer_flow;
 };
 
 struct enic_fm_jump_flow {
@@ -166,6 +168,9 @@ struct enic_flowman {
        int action_op_count;
        /* Tags used for representor flows */
        uint8_t vf_rep_tag;
+       /* For auto-added steer action for hairpin */
+       int need_hairpin_steer;
+       uint64_t hairpin_steer_vnic_h;
 };
 
 static int enic_fm_tbl_free(struct enic_flowman *fm, uint64_t handle);
@@ -244,6 +249,7 @@ static const enum rte_flow_action_type enic_fm_supported_eg_actions[] = {
        RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN,
        RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP,
        RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID,
+       RTE_FLOW_ACTION_TYPE_PORT_ID,
        RTE_FLOW_ACTION_TYPE_PASSTHRU,
        RTE_FLOW_ACTION_TYPE_VOID,
        RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP,
@@ -386,8 +392,11 @@ enic_fm_copy_item_vlan(struct copy_item_args *arg)
        eth_mask = (void *)&fm_mask->l2.eth;
        eth_val = (void *)&fm_data->l2.eth;
 
-       /* Outer TPID cannot be matched */
-       if (eth_mask->ether_type)
+       /*
+        * Outer TPID cannot be matched. If inner_type is 0, use what is
+        * in the eth header.
+        */
+       if (eth_mask->ether_type && mask->inner_type)
                return -ENOTSUP;
 
        /*
@@ -395,8 +404,10 @@ enic_fm_copy_item_vlan(struct copy_item_args *arg)
         * L2, regardless of vlan stripping settings. So, the inner type
         * from vlan becomes the ether type of the eth header.
         */
-       eth_mask->ether_type = mask->inner_type;
-       eth_val->ether_type = spec->inner_type;
+       if (mask->inner_type) {
+               eth_mask->ether_type = mask->inner_type;
+               eth_val->ether_type = spec->inner_type;
+       }
        fm_data->fk_header_select |= FKH_ETHER | FKH_QTAG;
        fm_mask->fk_header_select |= FKH_ETHER | FKH_QTAG;
        fm_data->fk_vlan = rte_be_to_cpu_16(spec->tci);
@@ -912,6 +923,20 @@ enic_fm_append_action_op(struct enic_flowman *fm,
        return 0;
 }
 
+static struct fm_action_op *
+find_prev_action_op(struct enic_flowman *fm, uint32_t opcode)
+{
+       struct fm_action_op *op;
+       int i;
+
+       for (i = 0; i < fm->action_op_count; i++) {
+               op = &fm->action.fma_action_ops[i];
+               if (op->fa_op == opcode)
+                       return op;
+       }
+       return NULL;
+}
+
 /* NIC requires that 1st steer appear before decap.
  * Correct example: steer, decap, steer, steer, ...
  */
@@ -927,7 +952,8 @@ enic_fm_reorder_action_op(struct enic_flowman *fm)
        steer = NULL;
        decap = NULL;
        while (op->fa_op != FMOP_END) {
-               if (!decap && op->fa_op == FMOP_DECAP_NOSTRIP)
+               if (!decap && (op->fa_op == FMOP_DECAP_NOSTRIP ||
+                              op->fa_op == FMOP_DECAP_STRIP))
                        decap = op;
                else if (!steer && op->fa_op == FMOP_RQ_STEER)
                        steer = op;
@@ -967,6 +993,17 @@ enic_fm_copy_vxlan_decap(struct enic_flowman *fm,
        return enic_fm_append_action_op(fm, &fm_op, error);
 }
 
+/* Generate a reasonable source port number */
+static uint16_t
+gen_src_port(void)
+{
+       /* Min/max below are the default values in OVS-DPDK and Linux */
+       uint16_t p = rte_rand();
+       p = RTE_MAX(p, 32768);
+       p = RTE_MIN(p, 61000);
+       return rte_cpu_to_be_16(p);
+}
+
 /* VXLAN encap is done via flowman compound action */
 static int
 enic_fm_copy_vxlan_encap(struct enic_flowman *fm,
@@ -975,6 +1012,7 @@ enic_fm_copy_vxlan_encap(struct enic_flowman *fm,
 {
        struct fm_action_op fm_op;
        struct rte_ether_hdr *eth;
+       struct rte_udp_hdr *udp;
        uint16_t *ethertype;
        void *template;
        uint8_t off;
@@ -1073,8 +1111,17 @@ enic_fm_copy_vxlan_encap(struct enic_flowman *fm,
                off + offsetof(struct rte_udp_hdr, dgram_len);
        fm_op.encap.len2_delta =
                sizeof(struct rte_udp_hdr) + sizeof(struct rte_vxlan_hdr);
+       udp = (struct rte_udp_hdr *)template;
        append_template(&template, &off, item->spec,
                        sizeof(struct rte_udp_hdr));
+       /*
+        * Firmware does not hash/fill source port yet. Generate a
+        * random port, as there is *usually* one rte_flow for the
+        * given inner packet stream (i.e. a single stream has one
+        * random port).
+        */
+       if (udp->src_port == 0)
+               udp->src_port = gen_src_port();
        item++;
        flow_item_skip_void(&item);
 
@@ -1120,6 +1167,68 @@ enic_fm_find_vnic(struct enic *enic, const struct rte_pci_addr *addr,
        return 0;
 }
 
+/*
+ * Egress: target port should be either PF uplink or VF.
+ * Supported cases
+ * 1. VF egress -> PF uplink
+ *   PF may be this VF's PF, or another PF, as long as they are on the same VIC.
+ * 2. VF egress -> VF
+ *
+ * Unsupported cases
+ * 1. PF egress -> VF
+ *   App should be using representor to pass packets to VF
+ */
+static int
+vf_egress_port_id_action(struct enic_flowman *fm,
+                        struct rte_eth_dev *dst_dev,
+                        uint64_t dst_vnic_h,
+                        struct fm_action_op *fm_op,
+                        struct rte_flow_error *error)
+{
+       struct enic *src_enic, *dst_enic;
+       struct enic_vf_representor *vf;
+       uint8_t uif;
+       int ret;
+
+       ENICPMD_FUNC_TRACE();
+       src_enic = fm->user_enic;
+       dst_enic = pmd_priv(dst_dev);
+       if (!(src_enic->rte_dev->data->dev_flags & RTE_ETH_DEV_REPRESENTOR)) {
+               return rte_flow_error_set(error, EINVAL,
+                       RTE_FLOW_ERROR_TYPE_ACTION,
+                       NULL, "source port is not VF representor");
+       }
+
+       /* VF -> PF uplink. dst is not VF representor */
+       if (!(dst_dev->data->dev_flags & RTE_ETH_DEV_REPRESENTOR)) {
+               /* PF is the VF's PF? Then nothing to do */
+               vf = VF_ENIC_TO_VF_REP(src_enic);
+               if (vf->pf == dst_enic) {
+                       ENICPMD_LOG(DEBUG, "destination port is VF's PF");
+                       return 0;
+               }
+               /* If not, steer to the remote PF's uplink */
+               uif = dst_enic->fm_vnic_uif;
+               ENICPMD_LOG(DEBUG, "steer to uplink %u", uif);
+               memset(fm_op, 0, sizeof(*fm_op));
+               fm_op->fa_op = FMOP_SET_EGPORT;
+               fm_op->set_egport.egport = uif;
+               ret = enic_fm_append_action_op(fm, fm_op, error);
+               return ret;
+       }
+
+       /* VF -> VF loopback. Hairpin and steer to vnic */
+       memset(fm_op, 0, sizeof(*fm_op));
+       fm_op->fa_op = FMOP_EG_HAIRPIN;
+       ret = enic_fm_append_action_op(fm, fm_op, error);
+       if (ret)
+               return ret;
+       ENICPMD_LOG(DEBUG, "egress hairpin");
+       fm->hairpin_steer_vnic_h = dst_vnic_h;
+       fm->need_hairpin_steer = 1;
+       return 0;
+}
+
 /* Translate flow actions to flowman TCAM entry actions */
 static int
 enic_fm_copy_action(struct enic_flowman *fm,
@@ -1311,6 +1420,10 @@ enic_fm_copy_action(struct enic_flowman *fm,
                        const struct rte_flow_action_port_id *port;
                        struct rte_eth_dev *dev;
 
+                       if (!ingress && (overlap & PORT_ID)) {
+                               ENICPMD_LOG(DEBUG, "cannot have multiple egress PORT_ID actions");
+                               goto unsupported;
+                       }
                        port = actions->conf;
                        if (port->original) {
                                vnic_h = enic->fm_vnic_handle; /* This port */
@@ -1340,6 +1453,13 @@ enic_fm_copy_action(struct enic_flowman *fm,
                         * Ingress. Nothing more to do. We add an implicit
                         * steer at the end if needed.
                         */
+                       if (ingress)
+                               break;
+                       /* Egress */
+                       ret = vf_egress_port_id_action(fm, dev, vnic_h, &fm_op,
+                               error);
+                       if (ret)
+                               return ret;
                        break;
                }
                case RTE_FLOW_ACTION_TYPE_VXLAN_DECAP: {
@@ -1367,6 +1487,19 @@ enic_fm_copy_action(struct enic_flowman *fm,
                        break;
                }
                case RTE_FLOW_ACTION_TYPE_OF_POP_VLAN: {
+                       struct fm_action_op *decap;
+
+                       /*
+                        * If decap-nostrip appears before pop vlan, this pop
+                        * applies to the inner packet vlan. Turn it into
+                        * decap-strip.
+                        */
+                       decap = find_prev_action_op(fm, FMOP_DECAP_NOSTRIP);
+                       if (decap) {
+                               ENICPMD_LOG(DEBUG, "pop-vlan inner: decap-nostrip => decap-strip");
+                               decap->fa_op = FMOP_DECAP_STRIP;
+                               break;
+                       }
                        memset(&fm_op, 0, sizeof(fm_op));
                        fm_op.fa_op = FMOP_POP_VLAN;
                        ret = enic_fm_append_action_op(fm, &fm_op, error);
@@ -1633,9 +1766,10 @@ enic_fm_dump_tcam_match(const struct fm_tcam_match_entry *match,
        memset(buf, 0, sizeof(buf));
        __enic_fm_dump_tcam_match(&match->ftm_mask.fk_hdrset[0],
                                  buf, sizeof(buf));
-       ENICPMD_LOG(DEBUG, " TCAM %s Outer: %s %scounter",
+       ENICPMD_LOG(DEBUG, " TCAM %s Outer: %s %scounter position %u",
                    (ingress) ? "IG" : "EG", buf,
-                   (match->ftm_flags & FMEF_COUNTER) ? "" : "no ");
+                   (match->ftm_flags & FMEF_COUNTER) ? "" : "no ",
+                   match->ftm_position);
        memset(buf, 0, sizeof(buf));
        __enic_fm_dump_tcam_match(&match->ftm_mask.fk_hdrset[1],
                                  buf, sizeof(buf));
@@ -1682,11 +1816,11 @@ enic_fm_flow_parse(struct enic_flowman *fm,
        }
 
        if (attrs) {
-               if (attrs->priority) {
+               if (attrs->group != FM_TCAM_RTE_GROUP && attrs->priority) {
                        rte_flow_error_set(error, ENOTSUP,
                                           RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
                                           NULL,
-                                          "priorities are not supported");
+                                          "priorities are not supported for non-default (0) groups");
                        return -rte_errno;
                } else if (!fm->owner_enic->switchdev_mode && attrs->transfer) {
                        rte_flow_error_set(error, ENOTSUP,
@@ -1921,9 +2055,15 @@ __enic_fm_flow_free(struct enic_flowman *fm, struct enic_fm_flow *fm_flow)
 static void
 enic_fm_flow_free(struct enic_flowman *fm, struct rte_flow *flow)
 {
+       struct enic_fm_flow *steer = flow->fm->hairpin_steer_flow;
+
        if (flow->fm->fet && flow->fm->fet->default_key)
                remove_jump_flow(fm, flow);
        __enic_fm_flow_free(fm, flow->fm);
+       if (steer) {
+               __enic_fm_flow_free(fm, steer);
+               free(steer);
+       }
        free(flow->fm);
        free(flow);
 }
@@ -2108,6 +2248,7 @@ enic_fm_flow_add_entry(struct enic_flowman *fm,
        struct rte_flow *flow;
 
        ENICPMD_FUNC_TRACE();
+       match_in->ftm_position = attrs->priority;
        enic_fm_dump_tcam_entry(match_in, action_in, attrs->ingress);
        flow = calloc(1, sizeof(*flow));
        fm_flow = calloc(1, sizeof(*fm_flow));
@@ -2174,11 +2315,71 @@ convert_jump_flows(struct enic_flowman *fm, struct enic_fm_fet *fet,
        }
 }
 
+static int
+add_hairpin_steer(struct enic_flowman *fm, struct rte_flow *flow,
+                 struct rte_flow_error *error)
+{
+       struct fm_tcam_match_entry *fm_tcam_entry;
+       struct enic_fm_flow *fm_flow;
+       struct fm_action *fm_action;
+       struct fm_action_op fm_op;
+       int ret;
+
+       ENICPMD_FUNC_TRACE();
+       fm_flow = calloc(1, sizeof(*fm_flow));
+       if (fm_flow == NULL) {
+               rte_flow_error_set(error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
+                       NULL, "enic: cannot allocate rte_flow");
+               return -ENOMEM;
+       }
+       /* Original egress hairpin flow */
+       fm_tcam_entry = &fm->tcam_entry;
+       fm_action = &fm->action;
+       /* Use the match pattern of the egress flow as is, without counters */
+       fm_tcam_entry->ftm_flags &= ~FMEF_COUNTER;
+       /* The only action is steer to vnic */
+       fm->action_op_count = 0;
+       memset(fm_action, 0, sizeof(*fm_action));
+       memset(&fm_op, 0, sizeof(fm_op));
+       /* Always to queue 0 for now */
+       fm_op.fa_op = FMOP_RQ_STEER;
+       fm_op.rq_steer.rq_index = 0;
+       fm_op.rq_steer.vnic_handle = fm->hairpin_steer_vnic_h;
+       ret = enic_fm_append_action_op(fm, &fm_op, error);
+       if (ret)
+               goto error_with_flow;
+       ENICPMD_LOG(DEBUG, "add steer op");
+       /* Add required END */
+       memset(&fm_op, 0, sizeof(fm_op));
+       fm_op.fa_op = FMOP_END;
+       ret = enic_fm_append_action_op(fm, &fm_op, error);
+       if (ret)
+               goto error_with_flow;
+       /* Add the ingress flow */
+       fm_flow->action_handle = FM_INVALID_HANDLE;
+       fm_flow->entry_handle = FM_INVALID_HANDLE;
+       ret = __enic_fm_flow_add_entry(fm, fm_flow, fm_tcam_entry, fm_action,
+                                      FM_TCAM_RTE_GROUP, 1 /* ingress */, error);
+       if (ret) {
+               ENICPMD_LOG(ERR, "cannot add hairpin-steer flow");
+               goto error_with_flow;
+       }
+       /* The new flow is now the egress flow's paired flow */
+       flow->fm->hairpin_steer_flow = fm_flow;
+       return 0;
+
+error_with_flow:
+       free(fm_flow);
+       return ret;
+}
+
 static void
 enic_fm_open_scratch(struct enic_flowman *fm)
 {
        fm->action_op_count = 0;
        fm->fet = NULL;
+       fm->need_hairpin_steer = 0;
+       fm->hairpin_steer_vnic_h = 0;
        memset(&fm->tcam_entry, 0, sizeof(fm->tcam_entry));
        memset(&fm->action, 0, sizeof(fm->action));
 }
@@ -2326,6 +2527,15 @@ enic_fm_flow_create(struct rte_eth_dev *dev,
        flow = enic_fm_flow_add_entry(fm, fm_tcam_entry, fm_action,
                                      attrs, error);
        if (flow) {
+               /* Add ingress rule that pairs with hairpin rule */
+               if (fm->need_hairpin_steer) {
+                       ret = add_hairpin_steer(fm, flow, error);
+                       if (ret) {
+                               enic_fm_flow_free(fm, flow);
+                               flow = NULL;
+                               goto error_with_scratch;
+                       }
+               }
                LIST_INSERT_HEAD(&enic->flows, flow, next);
                fet = flow->fm->fet;
                if (fet && fet->default_key) {