diff mbox series

[ovs-dev,RFC,v2,3/5] dpif-netdev: Support partial-action-offload of VXLAN encap flow

Message ID 20200518192731.30804-4-sriharsha.basavapatna@broadcom.com
State New
Headers show
Series RFC for Partial action offload | expand

Commit Message

Sriharsha Basavapatna May 18, 2020, 7:27 p.m. UTC
In this patch, we support offloading of VXLAN_ENCAP action for a vhost-user
port (aka "partial-action-offload"). At the time of offloading the flow, we
determine if the flow can be offloaded to an egress device, if the input
port is not offload capable such as a vhost-user port. We then offload the
flow with a VXLAN_ENCAP RTE action, to the egress device. We do not add
the OUTPUT RTE action, which indicates to the PMD that is is a partial
action offload request. Note that since the action is being offloaded in
egress direction, classification is expected to be done by OVS SW datapath
and hence there's no need to offload a MARK action.

If offload succeeds, we save the information in 'dp_netdev_flow' so that we
skip execution of the corresponding action (subsequent patch) during SW
datapath processing.

Signed-off-by: Sriharsha Basavapatna <sriharsha.basavapatna@broadcom.com>
---
 lib/dpif-netdev.c         | 216 ++++++++++++++++++++++++++++++++++++--
 lib/netdev-offload-dpdk.c |  50 ++++++---
 lib/netdev-offload.h      |   2 +
 3 files changed, 246 insertions(+), 22 deletions(-)

Comments

Eli Britstein May 26, 2020, 1:42 p.m. UTC | #1
On 5/18/2020 10:27 PM, Sriharsha Basavapatna wrote:
> In this patch, we support offloading of VXLAN_ENCAP action for a vhost-user
> port (aka "partial-action-offload"). At the time of offloading the flow, we
> determine if the flow can be offloaded to an egress device, if the input
> port is not offload capable such as a vhost-user port. We then offload the
> flow with a VXLAN_ENCAP RTE action, to the egress device. We do not add
> the OUTPUT RTE action, which indicates to the PMD that is is a partial
> action offload request. Note that since the action is being offloaded in
> egress direction, classification is expected to be done by OVS SW datapath
> and hence there's no need to offload a MARK action.
>
> If offload succeeds, we save the information in 'dp_netdev_flow' so that we
> skip execution of the corresponding action (subsequent patch) during SW
> datapath processing.
>
> Signed-off-by: Sriharsha Basavapatna <sriharsha.basavapatna@broadcom.com>
> ---
>   lib/dpif-netdev.c         | 216 ++++++++++++++++++++++++++++++++++++--
>   lib/netdev-offload-dpdk.c |  50 ++++++---
>   lib/netdev-offload.h      |   2 +
>   3 files changed, 246 insertions(+), 22 deletions(-)
>
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 781b233f4..4315f237c 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -538,6 +538,16 @@ struct dp_netdev_flow {
>       bool dead;
>       uint32_t mark;               /* Unique flow mark assigned to a flow */
>   
> +    /* The next two members are used to support partial offloading of
> +     * actions. The boolean flag tells if this flow has its actions partially
> +     * offloaded. The egress port# tells if the action should be offloaded
> +     * on the egress (output) port instead of the in-port for the flow. Note
> +     * that we support flows with a single egress port action.
> +     * (see MAX_ACTION_ATTRS for related comments).
> +     */
> +    bool partial_actions_offloaded;
> +    odp_port_t  egress_offload_port;
> +
>       /* Statistics. */
>       struct dp_netdev_flow_stats stats;
>   
> @@ -2351,10 +2361,176 @@ dp_netdev_append_flow_offload(struct dp_flow_offload_item *offload)
>       ovs_mutex_unlock(&dp_flow_offload.mutex);
>   }
>   
> +static int
> +partial_offload_egress_flow_del(struct dp_netdev_pmd_thread *pmd,
> +                                struct dp_netdev_flow *flow)
> +{
> +    int ret;
> +    struct netdev *port;
> +    odp_port_t out_port = flow->egress_offload_port;
> +    const char *dpif_type_str = dpif_normalize_type(pmd->dp->class->type);
> +
> +    port = netdev_ports_get(out_port, dpif_type_str);
> +    if (!port) {
> +        return -1;
> +    }
> +
> +    /* Taking a global 'port_mutex' to fulfill thread safety
> +     * restrictions for the netdev-offload-dpdk module. */
> +    ovs_mutex_lock(&pmd->dp->port_mutex);
> +    ret = netdev_flow_del(port, &flow->mega_ufid, NULL);
> +    ovs_mutex_unlock(&pmd->dp->port_mutex);
> +    netdev_close(port);
> +
> +    return ret;
> +}
> +
>   static int
>   dp_netdev_flow_offload_del(struct dp_flow_offload_item *offload)
>   {
> -    return mark_to_flow_disassociate(offload->pmd, offload->flow);
> +    if (offload->flow->partial_actions_offloaded &&
> +        offload->flow->egress_offload_port != ODPP_NONE) {
> +        return partial_offload_egress_flow_del(offload->pmd, offload->flow);
> +    } else {
> +        return mark_to_flow_disassociate(offload->pmd, offload->flow);
> +    }
> +}
> +
> +/* Structure to hold a nl_parsed OVS action */
> +struct action_attr {
> +    int type;                /* OVS action type */
> +    struct nlattr *action;   /* action attribute */
> +};
> +
> +/*
> + * Maxium number of actions to be parsed while selecting a flow for partial
> + * action offload. This number is currently based on the minimum number of
> + * attributes seen with the tunnel encap action (clone, tunnel_push, output).
> + * This number includes output action to a single egress device (uplink) and
> + * supports neither multiple clone() actions nor multiple output actions.
> + * This number could change if and when we support other actions or
> + * combinations of actions for partial offload.
> + */
> +#define MAX_ACTION_ATTRS    3 /* Max # action attributes supported */
> +
> +/*
> + * This function parses the list of OVS "actions" of length "actions_len",
> + * and returns them in an array of action "attrs", of size "max_attrs".
> + * The parsed number of actions is returned in "num_attrs". If the number
> + * of actions exceeds "max_attrs", parsing is stopped and E2BIG is returned.
> + * Otherwise, returns success (0).
> + */
> +static int
> +parse_nlattr_actions(struct nlattr *actions, size_t actions_len,
> +                     struct action_attr *attrs, int max_attrs, int *num_attrs)
> +{
> +    const struct nlattr *a;
> +    unsigned int left;
> +    int num_actions = 0;
> +    int n_attrs = 0;
> +    int rc = 0;
> +    int type;
> +
> +    *num_attrs = 0;
> +
> +    NL_ATTR_FOR_EACH (a, left, actions, actions_len) {
> +        type = nl_attr_type(a);
> +
> +        if (num_actions >= max_attrs) {
> +            *num_attrs = num_actions;
> +            return E2BIG;
> +        }
> +
> +        attrs[num_actions].type = type;
> +        attrs[num_actions].action = a;
> +        num_actions++;
> +        if (type == OVS_ACTION_ATTR_CLONE) {
> +            rc = parse_nlattr_actions(nl_attr_get(a), nl_attr_get_size(a),
> +                                      &attrs[num_actions],
> +                                      (max_attrs - num_actions), &n_attrs);
> +            num_actions += n_attrs;
> +            if (rc == E2BIG) {
> +                *num_attrs = num_actions;
> +                return rc;
> +            }
> +        }
> +    }
> +
> +    *num_attrs = num_actions;
> +    return 0;
> +}
> +
> +/* This function determines if the given flow should be partially offloaded
> + * on the egress device, when the in-port is not offload-capable like a
> + * vhost-user port. The function currently supports offloading of only
> + * tunnel encap action.
> + */
> +static bool
> +should_partial_offload_egress(struct netdev *in_netdev,
> +                              struct dp_flow_offload_item *offload,
> +                              struct netdev **egress_netdev)
> +{
> +    const char *dpif_type_str =
> +        dpif_normalize_type(offload->pmd->dp->class->type);
> +    struct action_attr attrs[MAX_ACTION_ATTRS];
> +    odp_port_t out_port = ODPP_NONE;
> +    struct netdev *out_netdev;
> +    int num_attrs = 0;
> +    int type;
> +    int rc;
> +
> +    /* Support egress partial-offload only when in-port is vhost-user. */
> +    if (!is_dpdk_vhost_netdev(in_netdev)) {
> +        return false;
> +    }
> +
> +    rc = parse_nlattr_actions(offload->actions, offload->actions_len, attrs,
> +                              MAX_ACTION_ATTRS, &num_attrs);
> +    if (rc == E2BIG) {
> +        /* Action list too big; decline partial offload */
> +        return false;
> +    }
> +
> +    /* Number of attrs expected with tunnel encap action */
> +    if (num_attrs < MAX_ACTION_ATTRS) {
> +        return false;
> +    }
> +
> +    /* Only support clone sub-actions for now, tnl-push specifically. */
> +    if (attrs[0].type != OVS_ACTION_ATTR_CLONE ||
> +        attrs[1].type != OVS_ACTION_ATTR_TUNNEL_PUSH ||
> +        attrs[2].type != OVS_ACTION_ATTR_OUTPUT) {
> +        return false;
> +    }
> +
> +    /* Egress partial-offload needs an output action at the end. */
> +    out_port = nl_attr_get_odp_port(attrs[2].action);
> +    if (out_port == ODPP_NONE) {
> +        return false;
> +    }
> +
> +    /* Support egress partial-offload only when out-port is offload capable. */
> +    out_netdev = netdev_ports_get(out_port, dpif_type_str);
> +    if (!out_netdev || !netdev_dpdk_flow_api_supported(out_netdev)) {
> +        return false;
> +    }
> +
> +    /* Flow can be egress partial-offloaded. */
> +    *egress_netdev = out_netdev;
> +    offload->flow->egress_offload_port = out_port;
> +    return true;
> +}
> +
> +/* This function determines if the given flow actions can be partially
> + * offloaded. Partial action offload is attempted when the in-port for
> + * the flow is a vhost-user port (egress direction).
> + */
> +static bool
> +dp_netdev_partial_offload_supported(struct netdev *in_netdev,
> +                                   struct dp_flow_offload_item *offload,
> +                                   struct netdev **egress_netdev)
> +{
> +    return should_partial_offload_egress(in_netdev, offload, egress_netdev);
why not move the logic from should_partial_offload_egress to here and 
eliminate the other function.
>   }
>   
>   static int
> @@ -2415,7 +2591,9 @@ dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
>       bool modification = offload->op == DP_NETDEV_FLOW_OFFLOAD_OP_MOD;
>       struct offload_info info;
>       struct netdev *port;
> -    uint32_t mark;
> +    struct netdev *egress_port = NULL;
> +    bool alloc_mark = true;
> +    uint32_t mark = INVALID_FLOW_MARK;
>       int ret;
>   
>       if (flow->dead) {
> @@ -2427,7 +2605,20 @@ dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
>           return -1;
>       }
>   
> -    if (dp_netdev_alloc_flow_mark(flow, modification, &mark)) {
> +    info.attr_egress = 0;
> +    info.partial_actions = 0;
> +
> +    if (dp_netdev_partial_offload_supported(port, offload, &egress_port)) {
missing netdev_close(egress_port)
> +        if (egress_port) {
> +            netdev_close(port);
> +            port = egress_port;
> +            info.attr_egress = 1;
> +            alloc_mark = false;
> +        }
> +        info.partial_actions = 1;
> +    }
> +
> +    if (alloc_mark && dp_netdev_alloc_flow_mark(flow, modification, &mark)) {
>               /* flow already offloaded */
>               netdev_close(port);
>               return 0;
> @@ -2448,17 +2639,21 @@ dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
>           goto err_free;
>       }
>   
> -    if (!modification) {
> +    if (info.partial_actions) {
> +        flow->partial_actions_offloaded = true;
> +    } else if (!modification) {
>           megaflow_to_mark_associate(&flow->mega_ufid, mark);
>           mark_to_flow_associate(mark, flow);
>       }
>       return 0;
>   
>   err_free:
> -    if (!modification) {
> -        netdev_offload_flow_mark_free(mark);
> -    } else {
> -        mark_to_flow_disassociate(pmd, flow);
> +    if (mark != INVALID_FLOW_MARK) {
> +        if (!modification) {
> +            netdev_offload_flow_mark_free(mark);
> +        } else {
> +            mark_to_flow_disassociate(pmd, flow);
> +        }
>       }
>       return -1;
>   }
> @@ -2573,7 +2768,8 @@ dp_netdev_pmd_remove_flow(struct dp_netdev_pmd_thread *pmd,
>       ovs_assert(cls != NULL);
>       dpcls_remove(cls, &flow->cr);
>       cmap_remove(&pmd->flow_table, node, dp_netdev_flow_hash(&flow->ufid));
> -    if (flow->mark != INVALID_FLOW_MARK) {
> +    if (flow->mark != INVALID_FLOW_MARK || (flow->partial_actions_offloaded
> +        && flow->egress_offload_port != ODPP_NONE)) {
>           queue_netdev_flow_del(pmd, flow);
>       }
>       flow->dead = true;
> @@ -3325,6 +3521,8 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
>       flow->dead = false;
>       flow->batch = NULL;
>       flow->mark = INVALID_FLOW_MARK;
> +    flow->partial_actions_offloaded = false;
> +    flow->egress_offload_port = ODPP_NONE;
>       *CONST_CAST(unsigned *, &flow->pmd_id) = pmd->core_id;
>       *CONST_CAST(struct flow *, &flow->flow) = match->flow;
>       *CONST_CAST(ovs_u128 *, &flow->ufid) = *ufid;
> diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
> index 7be504688..44c5445c9 100644
> --- a/lib/netdev-offload-dpdk.c
> +++ b/lib/netdev-offload-dpdk.c
> @@ -1698,7 +1698,8 @@ static int
>   parse_clone_actions(struct netdev *netdev,
>                       struct flow_actions *actions,
>                       const struct nlattr *clone_actions,
> -                    const size_t clone_actions_len)
> +                    const size_t clone_actions_len,
> +                    struct offload_info *info)
>   {
>       const struct nlattr *ca;
>       unsigned int cleft;
> @@ -1723,8 +1724,11 @@ parse_clone_actions(struct netdev *netdev,
>               add_flow_action(actions, RTE_FLOW_ACTION_TYPE_RAW_ENCAP,
>                               raw_encap);
>           } else if (clone_type == OVS_ACTION_ATTR_OUTPUT) {
> -            if (add_output_action(netdev, actions, ca)) {
> -                return -1;
> +            /* add output action only if full-offload */
> +            if (!info->partial_actions) {
> +                if (add_output_action(netdev, actions, ca)) {
> +                    return -1;
> +                }
>               }
>           } else {
>               VLOG_DBG_RL(&rl,
> @@ -1788,7 +1792,8 @@ parse_flow_actions(struct netdev *netdev,
>                      struct flow_actions *actions,
>                      struct nlattr *nl_actions,
>                      size_t nl_actions_len,
> -                   struct act_resources *act_resources)
> +                   struct act_resources *act_resources,
> +                   struct offload_info *info)
>   {
>       struct nlattr *nla;
>       size_t left;
> @@ -1796,11 +1801,16 @@ parse_flow_actions(struct netdev *netdev,
>       if (!strcmp(netdev_get_type(netdev), "vxlan")) {
>           add_vxlan_decap_action(actions);
>       }
> -    add_count_action(actions);
> +    if (!info->partial_actions) {
> +        add_count_action(actions);
> +    }
>       NL_ATTR_FOR_EACH_UNSAFE (nla, left, nl_actions, nl_actions_len) {
>           if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
> -            if (add_output_action(netdev, actions, nla)) {
> -                return -1;
> +            /* add output action only if full-offload */
> +            if (!info->partial_actions) {
> +                if (add_output_action(netdev, actions, nla)) {
> +                    return -1;
> +                }
there is no need for this hunk. the partial offload handles only 
clone/output, here it's only output.
>               }
>           } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_DROP) {
>               free_flow_actions(actions);
> @@ -1821,7 +1831,7 @@ parse_flow_actions(struct netdev *netdev,
>               size_t clone_actions_len = nl_attr_get_size(nla);
>   
>               if (parse_clone_actions(netdev, actions, clone_actions,
> -                                    clone_actions_len)) {
> +                                    clone_actions_len, info)) {
>                   return -1;
>               }
>           } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_TUNNEL_POP) {
> @@ -1899,16 +1909,23 @@ netdev_offload_dpdk_actions(struct netdev *netdev,
>                               size_t actions_len,
>                               const ovs_u128 *ufid,
>                               struct act_resources *act_resources,
> -                            struct flows_handle *flows)
> +                            struct flows_handle *flows,
> +                            struct offload_info *info)
>   {
> -    const struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 };
> +    struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 };
>       struct flow_actions actions = { .actions = NULL, .cnt = 0 };
>       struct flow_item flow_item = { .devargs = NULL };
>       struct rte_flow_error error;
>       int ret;
>   
> +    if (info->attr_egress) {
> +        flow_attr.ingress = 0;
> +        flow_attr.egress = 1;
> +        flow_attr.transfer = 0;
> +    }
> +
>       ret = parse_flow_actions(netdev, &actions, nl_actions, actions_len,
> -                             act_resources);
> +                             act_resources, info);
>       if (ret) {
>           goto out;
>       }
> @@ -1957,8 +1974,15 @@ netdev_offload_dpdk_add_flow(struct netdev *netdev,
>   
>       ret = netdev_offload_dpdk_actions(netdev, &patterns, nl_actions,
>                                         actions_len, ufid, &act_resources,
> -                                      &flows);
> -    if (ret) {
> +                                      &flows, info);
> +    if (!ret) {
> +        if (info->partial_actions && info->attr_egress) {
> +            /* actions_offloaded should be set to false with partial actions,
> +             * since it is still considered as partial-offload and not
> +             * full-offload. */
> +            actions_offloaded = false;
> +        }
> +    } else if (!(info->partial_actions && info->attr_egress)) {
>           /* If we failed to offload the rule actions fallback to MARK+RSS
>            * actions.
>            */
> diff --git a/lib/netdev-offload.h b/lib/netdev-offload.h
> index d6dd98367..3c079cc9d 100644
> --- a/lib/netdev-offload.h
> +++ b/lib/netdev-offload.h
> @@ -67,6 +67,8 @@ struct offload_info {
>   
>       bool recirc_id_shared_with_tc;  /* Indicates whever tc chains will be in
>                                        * sync with datapath recirc ids. */
> +    uint8_t attr_egress;      /* Egress direction offload */
> +    uint8_t partial_actions;  /* Partial action offload; no forward action */
>   
>       /*
>        * The flow mark id assigened to the flow. If any pkts hit the flow,
Sriharsha Basavapatna May 28, 2020, 8:11 a.m. UTC | #2
On Tue, May 26, 2020 at 7:12 PM Eli Britstein <elibr@mellanox.com> wrote:
>
>
> On 5/18/2020 10:27 PM, Sriharsha Basavapatna wrote:
> > In this patch, we support offloading of VXLAN_ENCAP action for a vhost-user
> > port (aka "partial-action-offload"). At the time of offloading the flow, we
> > determine if the flow can be offloaded to an egress device, if the input
> > port is not offload capable such as a vhost-user port. We then offload the
> > flow with a VXLAN_ENCAP RTE action, to the egress device. We do not add
> > the OUTPUT RTE action, which indicates to the PMD that is is a partial
> > action offload request. Note that since the action is being offloaded in
> > egress direction, classification is expected to be done by OVS SW datapath
> > and hence there's no need to offload a MARK action.
> >
> > If offload succeeds, we save the information in 'dp_netdev_flow' so that we
> > skip execution of the corresponding action (subsequent patch) during SW
> > datapath processing.
> >
> > Signed-off-by: Sriharsha Basavapatna <sriharsha.basavapatna@broadcom.com>
> > ---
> >   lib/dpif-netdev.c         | 216 ++++++++++++++++++++++++++++++++++++--
> >   lib/netdev-offload-dpdk.c |  50 ++++++---
> >   lib/netdev-offload.h      |   2 +
> >   3 files changed, 246 insertions(+), 22 deletions(-)
> >
> > diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> > index 781b233f4..4315f237c 100644
> > --- a/lib/dpif-netdev.c
> > +++ b/lib/dpif-netdev.c
> > @@ -538,6 +538,16 @@ struct dp_netdev_flow {
> >       bool dead;
> >       uint32_t mark;               /* Unique flow mark assigned to a flow */
> >
> > +    /* The next two members are used to support partial offloading of
> > +     * actions. The boolean flag tells if this flow has its actions partially
> > +     * offloaded. The egress port# tells if the action should be offloaded
> > +     * on the egress (output) port instead of the in-port for the flow. Note
> > +     * that we support flows with a single egress port action.
> > +     * (see MAX_ACTION_ATTRS for related comments).
> > +     */
> > +    bool partial_actions_offloaded;
> > +    odp_port_t  egress_offload_port;
> > +
> >       /* Statistics. */
> >       struct dp_netdev_flow_stats stats;
> >
> > @@ -2351,10 +2361,176 @@ dp_netdev_append_flow_offload(struct dp_flow_offload_item *offload)
> >       ovs_mutex_unlock(&dp_flow_offload.mutex);
> >   }
> >
> > +static int
> > +partial_offload_egress_flow_del(struct dp_netdev_pmd_thread *pmd,
> > +                                struct dp_netdev_flow *flow)
> > +{
> > +    int ret;
> > +    struct netdev *port;
> > +    odp_port_t out_port = flow->egress_offload_port;
> > +    const char *dpif_type_str = dpif_normalize_type(pmd->dp->class->type);
> > +
> > +    port = netdev_ports_get(out_port, dpif_type_str);
> > +    if (!port) {
> > +        return -1;
> > +    }
> > +
> > +    /* Taking a global 'port_mutex' to fulfill thread safety
> > +     * restrictions for the netdev-offload-dpdk module. */
> > +    ovs_mutex_lock(&pmd->dp->port_mutex);
> > +    ret = netdev_flow_del(port, &flow->mega_ufid, NULL);
> > +    ovs_mutex_unlock(&pmd->dp->port_mutex);
> > +    netdev_close(port);
> > +
> > +    return ret;
> > +}
> > +
> >   static int
> >   dp_netdev_flow_offload_del(struct dp_flow_offload_item *offload)
> >   {
> > -    return mark_to_flow_disassociate(offload->pmd, offload->flow);
> > +    if (offload->flow->partial_actions_offloaded &&
> > +        offload->flow->egress_offload_port != ODPP_NONE) {
> > +        return partial_offload_egress_flow_del(offload->pmd, offload->flow);
> > +    } else {
> > +        return mark_to_flow_disassociate(offload->pmd, offload->flow);
> > +    }
> > +}
> > +
> > +/* Structure to hold a nl_parsed OVS action */
> > +struct action_attr {
> > +    int type;                /* OVS action type */
> > +    struct nlattr *action;   /* action attribute */
> > +};
> > +
> > +/*
> > + * Maxium number of actions to be parsed while selecting a flow for partial
> > + * action offload. This number is currently based on the minimum number of
> > + * attributes seen with the tunnel encap action (clone, tunnel_push, output).
> > + * This number includes output action to a single egress device (uplink) and
> > + * supports neither multiple clone() actions nor multiple output actions.
> > + * This number could change if and when we support other actions or
> > + * combinations of actions for partial offload.
> > + */
> > +#define MAX_ACTION_ATTRS    3 /* Max # action attributes supported */
> > +
> > +/*
> > + * This function parses the list of OVS "actions" of length "actions_len",
> > + * and returns them in an array of action "attrs", of size "max_attrs".
> > + * The parsed number of actions is returned in "num_attrs". If the number
> > + * of actions exceeds "max_attrs", parsing is stopped and E2BIG is returned.
> > + * Otherwise, returns success (0).
> > + */
> > +static int
> > +parse_nlattr_actions(struct nlattr *actions, size_t actions_len,
> > +                     struct action_attr *attrs, int max_attrs, int *num_attrs)
> > +{
> > +    const struct nlattr *a;
> > +    unsigned int left;
> > +    int num_actions = 0;
> > +    int n_attrs = 0;
> > +    int rc = 0;
> > +    int type;
> > +
> > +    *num_attrs = 0;
> > +
> > +    NL_ATTR_FOR_EACH (a, left, actions, actions_len) {
> > +        type = nl_attr_type(a);
> > +
> > +        if (num_actions >= max_attrs) {
> > +            *num_attrs = num_actions;
> > +            return E2BIG;
> > +        }
> > +
> > +        attrs[num_actions].type = type;
> > +        attrs[num_actions].action = a;
> > +        num_actions++;
> > +        if (type == OVS_ACTION_ATTR_CLONE) {
> > +            rc = parse_nlattr_actions(nl_attr_get(a), nl_attr_get_size(a),
> > +                                      &attrs[num_actions],
> > +                                      (max_attrs - num_actions), &n_attrs);
> > +            num_actions += n_attrs;
> > +            if (rc == E2BIG) {
> > +                *num_attrs = num_actions;
> > +                return rc;
> > +            }
> > +        }
> > +    }
> > +
> > +    *num_attrs = num_actions;
> > +    return 0;
> > +}
> > +
> > +/* This function determines if the given flow should be partially offloaded
> > + * on the egress device, when the in-port is not offload-capable like a
> > + * vhost-user port. The function currently supports offloading of only
> > + * tunnel encap action.
> > + */
> > +static bool
> > +should_partial_offload_egress(struct netdev *in_netdev,
> > +                              struct dp_flow_offload_item *offload,
> > +                              struct netdev **egress_netdev)
> > +{
> > +    const char *dpif_type_str =
> > +        dpif_normalize_type(offload->pmd->dp->class->type);
> > +    struct action_attr attrs[MAX_ACTION_ATTRS];
> > +    odp_port_t out_port = ODPP_NONE;
> > +    struct netdev *out_netdev;
> > +    int num_attrs = 0;
> > +    int type;
> > +    int rc;
> > +
> > +    /* Support egress partial-offload only when in-port is vhost-user. */
> > +    if (!is_dpdk_vhost_netdev(in_netdev)) {
> > +        return false;
> > +    }
> > +
> > +    rc = parse_nlattr_actions(offload->actions, offload->actions_len, attrs,
> > +                              MAX_ACTION_ATTRS, &num_attrs);
> > +    if (rc == E2BIG) {
> > +        /* Action list too big; decline partial offload */
> > +        return false;
> > +    }
> > +
> > +    /* Number of attrs expected with tunnel encap action */
> > +    if (num_attrs < MAX_ACTION_ATTRS) {
> > +        return false;
> > +    }
> > +
> > +    /* Only support clone sub-actions for now, tnl-push specifically. */
> > +    if (attrs[0].type != OVS_ACTION_ATTR_CLONE ||
> > +        attrs[1].type != OVS_ACTION_ATTR_TUNNEL_PUSH ||
> > +        attrs[2].type != OVS_ACTION_ATTR_OUTPUT) {
> > +        return false;
> > +    }
> > +
> > +    /* Egress partial-offload needs an output action at the end. */
> > +    out_port = nl_attr_get_odp_port(attrs[2].action);
> > +    if (out_port == ODPP_NONE) {
> > +        return false;
> > +    }
> > +
> > +    /* Support egress partial-offload only when out-port is offload capable. */
> > +    out_netdev = netdev_ports_get(out_port, dpif_type_str);
> > +    if (!out_netdev || !netdev_dpdk_flow_api_supported(out_netdev)) {
> > +        return false;
> > +    }
> > +
> > +    /* Flow can be egress partial-offloaded. */
> > +    *egress_netdev = out_netdev;
> > +    offload->flow->egress_offload_port = out_port;
> > +    return true;
> > +}
> > +
> > +/* This function determines if the given flow actions can be partially
> > + * offloaded. Partial action offload is attempted when the in-port for
> > + * the flow is a vhost-user port (egress direction).
> > + */
> > +static bool
> > +dp_netdev_partial_offload_supported(struct netdev *in_netdev,
> > +                                   struct dp_flow_offload_item *offload,
> > +                                   struct netdev **egress_netdev)
> > +{
> > +    return should_partial_offload_egress(in_netdev, offload, egress_netdev);
> why not move the logic from should_partial_offload_egress to here and
> eliminate the other function.

Done.
> >   }
> >
> >   static int
> > @@ -2415,7 +2591,9 @@ dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
> >       bool modification = offload->op == DP_NETDEV_FLOW_OFFLOAD_OP_MOD;
> >       struct offload_info info;
> >       struct netdev *port;
> > -    uint32_t mark;
> > +    struct netdev *egress_port = NULL;
> > +    bool alloc_mark = true;
> > +    uint32_t mark = INVALID_FLOW_MARK;
> >       int ret;
> >
> >       if (flow->dead) {
> > @@ -2427,7 +2605,20 @@ dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
> >           return -1;
> >       }
> >
> > -    if (dp_netdev_alloc_flow_mark(flow, modification, &mark)) {
> > +    info.attr_egress = 0;
> > +    info.partial_actions = 0;
> > +
> > +    if (dp_netdev_partial_offload_supported(port, offload, &egress_port)) {
> missing netdev_close(egress_port)

It is not missing, but it is not shown in the diffs since it is part
of existing code. As "egress_port" is assigned to "port", it gets
handled as netdev_close(port) further down.
    .....
    ovs_mutex_unlock(&pmd->dp->port_mutex);
    netdev_close(port);


> > +        if (egress_port) {
> > +            netdev_close(port);
> > +            port = egress_port;
> > +            info.attr_egress = 1;
> > +            alloc_mark = false;
> > +        }
> > +        info.partial_actions = 1;
> > +    }
> > +
> > +    if (alloc_mark && dp_netdev_alloc_flow_mark(flow, modification, &mark)) {
> >               /* flow already offloaded */
> >               netdev_close(port);
> >               return 0;
> > @@ -2448,17 +2639,21 @@ dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
> >           goto err_free;
> >       }
> >
> > -    if (!modification) {
> > +    if (info.partial_actions) {
> > +        flow->partial_actions_offloaded = true;
> > +    } else if (!modification) {
> >           megaflow_to_mark_associate(&flow->mega_ufid, mark);
> >           mark_to_flow_associate(mark, flow);
> >       }
> >       return 0;
> >
> >   err_free:
> > -    if (!modification) {
> > -        netdev_offload_flow_mark_free(mark);
> > -    } else {
> > -        mark_to_flow_disassociate(pmd, flow);
> > +    if (mark != INVALID_FLOW_MARK) {
> > +        if (!modification) {
> > +            netdev_offload_flow_mark_free(mark);
> > +        } else {
> > +            mark_to_flow_disassociate(pmd, flow);
> > +        }
> >       }
> >       return -1;
> >   }
> > @@ -2573,7 +2768,8 @@ dp_netdev_pmd_remove_flow(struct dp_netdev_pmd_thread *pmd,
> >       ovs_assert(cls != NULL);
> >       dpcls_remove(cls, &flow->cr);
> >       cmap_remove(&pmd->flow_table, node, dp_netdev_flow_hash(&flow->ufid));
> > -    if (flow->mark != INVALID_FLOW_MARK) {
> > +    if (flow->mark != INVALID_FLOW_MARK || (flow->partial_actions_offloaded
> > +        && flow->egress_offload_port != ODPP_NONE)) {
> >           queue_netdev_flow_del(pmd, flow);
> >       }
> >       flow->dead = true;
> > @@ -3325,6 +3521,8 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
> >       flow->dead = false;
> >       flow->batch = NULL;
> >       flow->mark = INVALID_FLOW_MARK;
> > +    flow->partial_actions_offloaded = false;
> > +    flow->egress_offload_port = ODPP_NONE;
> >       *CONST_CAST(unsigned *, &flow->pmd_id) = pmd->core_id;
> >       *CONST_CAST(struct flow *, &flow->flow) = match->flow;
> >       *CONST_CAST(ovs_u128 *, &flow->ufid) = *ufid;
> > diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
> > index 7be504688..44c5445c9 100644
> > --- a/lib/netdev-offload-dpdk.c
> > +++ b/lib/netdev-offload-dpdk.c
> > @@ -1698,7 +1698,8 @@ static int
> >   parse_clone_actions(struct netdev *netdev,
> >                       struct flow_actions *actions,
> >                       const struct nlattr *clone_actions,
> > -                    const size_t clone_actions_len)
> > +                    const size_t clone_actions_len,
> > +                    struct offload_info *info)
> >   {
> >       const struct nlattr *ca;
> >       unsigned int cleft;
> > @@ -1723,8 +1724,11 @@ parse_clone_actions(struct netdev *netdev,
> >               add_flow_action(actions, RTE_FLOW_ACTION_TYPE_RAW_ENCAP,
> >                               raw_encap);
> >           } else if (clone_type == OVS_ACTION_ATTR_OUTPUT) {
> > -            if (add_output_action(netdev, actions, ca)) {
> > -                return -1;
> > +            /* add output action only if full-offload */
> > +            if (!info->partial_actions) {
> > +                if (add_output_action(netdev, actions, ca)) {
> > +                    return -1;
> > +                }
> >               }
> >           } else {
> >               VLOG_DBG_RL(&rl,
> > @@ -1788,7 +1792,8 @@ parse_flow_actions(struct netdev *netdev,
> >                      struct flow_actions *actions,
> >                      struct nlattr *nl_actions,
> >                      size_t nl_actions_len,
> > -                   struct act_resources *act_resources)
> > +                   struct act_resources *act_resources,
> > +                   struct offload_info *info)
> >   {
> >       struct nlattr *nla;
> >       size_t left;
> > @@ -1796,11 +1801,16 @@ parse_flow_actions(struct netdev *netdev,
> >       if (!strcmp(netdev_get_type(netdev), "vxlan")) {
> >           add_vxlan_decap_action(actions);
> >       }
> > -    add_count_action(actions);
> > +    if (!info->partial_actions) {
> > +        add_count_action(actions);
> > +    }
> >       NL_ATTR_FOR_EACH_UNSAFE (nla, left, nl_actions, nl_actions_len) {
> >           if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
> > -            if (add_output_action(netdev, actions, nla)) {
> > -                return -1;
> > +            /* add output action only if full-offload */
> > +            if (!info->partial_actions) {
> > +                if (add_output_action(netdev, actions, nla)) {
> > +                    return -1;
> > +                }
> there is no need for this hunk. the partial offload handles only
> clone/output, here it's only output.

Removed it.

> >               }
> >           } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_DROP) {
> >               free_flow_actions(actions);
> > @@ -1821,7 +1831,7 @@ parse_flow_actions(struct netdev *netdev,
> >               size_t clone_actions_len = nl_attr_get_size(nla);
> >
> >               if (parse_clone_actions(netdev, actions, clone_actions,
> > -                                    clone_actions_len)) {
> > +                                    clone_actions_len, info)) {
> >                   return -1;
> >               }
> >           } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_TUNNEL_POP) {
> > @@ -1899,16 +1909,23 @@ netdev_offload_dpdk_actions(struct netdev *netdev,
> >                               size_t actions_len,
> >                               const ovs_u128 *ufid,
> >                               struct act_resources *act_resources,
> > -                            struct flows_handle *flows)
> > +                            struct flows_handle *flows,
> > +                            struct offload_info *info)
> >   {
> > -    const struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 };
> > +    struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 };
> >       struct flow_actions actions = { .actions = NULL, .cnt = 0 };
> >       struct flow_item flow_item = { .devargs = NULL };
> >       struct rte_flow_error error;
> >       int ret;
> >
> > +    if (info->attr_egress) {
> > +        flow_attr.ingress = 0;
> > +        flow_attr.egress = 1;
> > +        flow_attr.transfer = 0;
> > +    }
> > +
> >       ret = parse_flow_actions(netdev, &actions, nl_actions, actions_len,
> > -                             act_resources);
> > +                             act_resources, info);
> >       if (ret) {
> >           goto out;
> >       }
> > @@ -1957,8 +1974,15 @@ netdev_offload_dpdk_add_flow(struct netdev *netdev,
> >
> >       ret = netdev_offload_dpdk_actions(netdev, &patterns, nl_actions,
> >                                         actions_len, ufid, &act_resources,
> > -                                      &flows);
> > -    if (ret) {
> > +                                      &flows, info);
> > +    if (!ret) {
> > +        if (info->partial_actions && info->attr_egress) {
> > +            /* actions_offloaded should be set to false with partial actions,
> > +             * since it is still considered as partial-offload and not
> > +             * full-offload. */
> > +            actions_offloaded = false;
> > +        }
> > +    } else if (!(info->partial_actions && info->attr_egress)) {
> >           /* If we failed to offload the rule actions fallback to MARK+RSS
> >            * actions.
> >            */
> > diff --git a/lib/netdev-offload.h b/lib/netdev-offload.h
> > index d6dd98367..3c079cc9d 100644
> > --- a/lib/netdev-offload.h
> > +++ b/lib/netdev-offload.h
> > @@ -67,6 +67,8 @@ struct offload_info {
> >
> >       bool recirc_id_shared_with_tc;  /* Indicates whever tc chains will be in
> >                                        * sync with datapath recirc ids. */
> > +    uint8_t attr_egress;      /* Egress direction offload */
> > +    uint8_t partial_actions;  /* Partial action offload; no forward action */
> >
> >       /*
> >        * The flow mark id assigened to the flow. If any pkts hit the flow,
diff mbox series

Patch

diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 781b233f4..4315f237c 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -538,6 +538,16 @@  struct dp_netdev_flow {
     bool dead;
     uint32_t mark;               /* Unique flow mark assigned to a flow */
 
+    /* The next two members are used to support partial offloading of
+     * actions. The boolean flag tells if this flow has its actions partially
+     * offloaded. The egress port# tells if the action should be offloaded
+     * on the egress (output) port instead of the in-port for the flow. Note
+     * that we support flows with a single egress port action.
+     * (see MAX_ACTION_ATTRS for related comments).
+     */
+    bool partial_actions_offloaded;
+    odp_port_t  egress_offload_port;
+
     /* Statistics. */
     struct dp_netdev_flow_stats stats;
 
@@ -2351,10 +2361,176 @@  dp_netdev_append_flow_offload(struct dp_flow_offload_item *offload)
     ovs_mutex_unlock(&dp_flow_offload.mutex);
 }
 
+static int
+partial_offload_egress_flow_del(struct dp_netdev_pmd_thread *pmd,
+                                struct dp_netdev_flow *flow)
+{
+    int ret;
+    struct netdev *port;
+    odp_port_t out_port = flow->egress_offload_port;
+    const char *dpif_type_str = dpif_normalize_type(pmd->dp->class->type);
+
+    port = netdev_ports_get(out_port, dpif_type_str);
+    if (!port) {
+        return -1;
+    }
+
+    /* Taking a global 'port_mutex' to fulfill thread safety
+     * restrictions for the netdev-offload-dpdk module. */
+    ovs_mutex_lock(&pmd->dp->port_mutex);
+    ret = netdev_flow_del(port, &flow->mega_ufid, NULL);
+    ovs_mutex_unlock(&pmd->dp->port_mutex);
+    netdev_close(port);
+
+    return ret;
+}
+
 static int
 dp_netdev_flow_offload_del(struct dp_flow_offload_item *offload)
 {
-    return mark_to_flow_disassociate(offload->pmd, offload->flow);
+    if (offload->flow->partial_actions_offloaded &&
+        offload->flow->egress_offload_port != ODPP_NONE) {
+        return partial_offload_egress_flow_del(offload->pmd, offload->flow);
+    } else {
+        return mark_to_flow_disassociate(offload->pmd, offload->flow);
+    }
+}
+
+/* Structure to hold a nl_parsed OVS action */
+struct action_attr {
+    int type;                /* OVS action type */
+    struct nlattr *action;   /* action attribute */
+};
+
+/*
+ * Maxium number of actions to be parsed while selecting a flow for partial
+ * action offload. This number is currently based on the minimum number of
+ * attributes seen with the tunnel encap action (clone, tunnel_push, output).
+ * This number includes output action to a single egress device (uplink) and
+ * supports neither multiple clone() actions nor multiple output actions.
+ * This number could change if and when we support other actions or
+ * combinations of actions for partial offload.
+ */
+#define MAX_ACTION_ATTRS    3 /* Max # action attributes supported */
+
+/*
+ * This function parses the list of OVS "actions" of length "actions_len",
+ * and returns them in an array of action "attrs", of size "max_attrs".
+ * The parsed number of actions is returned in "num_attrs". If the number
+ * of actions exceeds "max_attrs", parsing is stopped and E2BIG is returned.
+ * Otherwise, returns success (0).
+ */
+static int
+parse_nlattr_actions(struct nlattr *actions, size_t actions_len,
+                     struct action_attr *attrs, int max_attrs, int *num_attrs)
+{
+    const struct nlattr *a;
+    unsigned int left;
+    int num_actions = 0;
+    int n_attrs = 0;
+    int rc = 0;
+    int type;
+
+    *num_attrs = 0;
+
+    NL_ATTR_FOR_EACH (a, left, actions, actions_len) {
+        type = nl_attr_type(a);
+
+        if (num_actions >= max_attrs) {
+            *num_attrs = num_actions;
+            return E2BIG;
+        }
+
+        attrs[num_actions].type = type;
+        attrs[num_actions].action = a;
+        num_actions++;
+        if (type == OVS_ACTION_ATTR_CLONE) {
+            rc = parse_nlattr_actions(nl_attr_get(a), nl_attr_get_size(a),
+                                      &attrs[num_actions],
+                                      (max_attrs - num_actions), &n_attrs);
+            num_actions += n_attrs;
+            if (rc == E2BIG) {
+                *num_attrs = num_actions;
+                return rc;
+            }
+        }
+    }
+
+    *num_attrs = num_actions;
+    return 0;
+}
+
+/* This function determines if the given flow should be partially offloaded
+ * on the egress device, when the in-port is not offload-capable like a
+ * vhost-user port. The function currently supports offloading of only
+ * tunnel encap action.
+ */
+static bool
+should_partial_offload_egress(struct netdev *in_netdev,
+                              struct dp_flow_offload_item *offload,
+                              struct netdev **egress_netdev)
+{
+    const char *dpif_type_str =
+        dpif_normalize_type(offload->pmd->dp->class->type);
+    struct action_attr attrs[MAX_ACTION_ATTRS];
+    odp_port_t out_port = ODPP_NONE;
+    struct netdev *out_netdev;
+    int num_attrs = 0;
+    int type;
+    int rc;
+
+    /* Support egress partial-offload only when in-port is vhost-user. */
+    if (!is_dpdk_vhost_netdev(in_netdev)) {
+        return false;
+    }
+
+    rc = parse_nlattr_actions(offload->actions, offload->actions_len, attrs,
+                              MAX_ACTION_ATTRS, &num_attrs);
+    if (rc == E2BIG) {
+        /* Action list too big; decline partial offload */
+        return false;
+    }
+
+    /* Number of attrs expected with tunnel encap action */
+    if (num_attrs < MAX_ACTION_ATTRS) {
+        return false;
+    }
+
+    /* Only support clone sub-actions for now, tnl-push specifically. */
+    if (attrs[0].type != OVS_ACTION_ATTR_CLONE ||
+        attrs[1].type != OVS_ACTION_ATTR_TUNNEL_PUSH ||
+        attrs[2].type != OVS_ACTION_ATTR_OUTPUT) {
+        return false;
+    }
+
+    /* Egress partial-offload needs an output action at the end. */
+    out_port = nl_attr_get_odp_port(attrs[2].action);
+    if (out_port == ODPP_NONE) {
+        return false;
+    }
+
+    /* Support egress partial-offload only when out-port is offload capable. */
+    out_netdev = netdev_ports_get(out_port, dpif_type_str);
+    if (!out_netdev || !netdev_dpdk_flow_api_supported(out_netdev)) {
+        return false;
+    }
+
+    /* Flow can be egress partial-offloaded. */
+    *egress_netdev = out_netdev;
+    offload->flow->egress_offload_port = out_port;
+    return true;
+}
+
+/* This function determines if the given flow actions can be partially
+ * offloaded. Partial action offload is attempted when the in-port for
+ * the flow is a vhost-user port (egress direction).
+ */
+static bool
+dp_netdev_partial_offload_supported(struct netdev *in_netdev,
+                                   struct dp_flow_offload_item *offload,
+                                   struct netdev **egress_netdev)
+{
+    return should_partial_offload_egress(in_netdev, offload, egress_netdev);
 }
 
 static int
@@ -2415,7 +2591,9 @@  dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
     bool modification = offload->op == DP_NETDEV_FLOW_OFFLOAD_OP_MOD;
     struct offload_info info;
     struct netdev *port;
-    uint32_t mark;
+    struct netdev *egress_port = NULL;
+    bool alloc_mark = true;
+    uint32_t mark = INVALID_FLOW_MARK;
     int ret;
 
     if (flow->dead) {
@@ -2427,7 +2605,20 @@  dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
         return -1;
     }
 
-    if (dp_netdev_alloc_flow_mark(flow, modification, &mark)) {
+    info.attr_egress = 0;
+    info.partial_actions = 0;
+
+    if (dp_netdev_partial_offload_supported(port, offload, &egress_port)) {
+        if (egress_port) {
+            netdev_close(port);
+            port = egress_port;
+            info.attr_egress = 1;
+            alloc_mark = false;
+        }
+        info.partial_actions = 1;
+    }
+
+    if (alloc_mark && dp_netdev_alloc_flow_mark(flow, modification, &mark)) {
             /* flow already offloaded */
             netdev_close(port);
             return 0;
@@ -2448,17 +2639,21 @@  dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
         goto err_free;
     }
 
-    if (!modification) {
+    if (info.partial_actions) {
+        flow->partial_actions_offloaded = true;
+    } else if (!modification) {
         megaflow_to_mark_associate(&flow->mega_ufid, mark);
         mark_to_flow_associate(mark, flow);
     }
     return 0;
 
 err_free:
-    if (!modification) {
-        netdev_offload_flow_mark_free(mark);
-    } else {
-        mark_to_flow_disassociate(pmd, flow);
+    if (mark != INVALID_FLOW_MARK) {
+        if (!modification) {
+            netdev_offload_flow_mark_free(mark);
+        } else {
+            mark_to_flow_disassociate(pmd, flow);
+        }
     }
     return -1;
 }
@@ -2573,7 +2768,8 @@  dp_netdev_pmd_remove_flow(struct dp_netdev_pmd_thread *pmd,
     ovs_assert(cls != NULL);
     dpcls_remove(cls, &flow->cr);
     cmap_remove(&pmd->flow_table, node, dp_netdev_flow_hash(&flow->ufid));
-    if (flow->mark != INVALID_FLOW_MARK) {
+    if (flow->mark != INVALID_FLOW_MARK || (flow->partial_actions_offloaded
+        && flow->egress_offload_port != ODPP_NONE)) {
         queue_netdev_flow_del(pmd, flow);
     }
     flow->dead = true;
@@ -3325,6 +3521,8 @@  dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
     flow->dead = false;
     flow->batch = NULL;
     flow->mark = INVALID_FLOW_MARK;
+    flow->partial_actions_offloaded = false;
+    flow->egress_offload_port = ODPP_NONE;
     *CONST_CAST(unsigned *, &flow->pmd_id) = pmd->core_id;
     *CONST_CAST(struct flow *, &flow->flow) = match->flow;
     *CONST_CAST(ovs_u128 *, &flow->ufid) = *ufid;
diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
index 7be504688..44c5445c9 100644
--- a/lib/netdev-offload-dpdk.c
+++ b/lib/netdev-offload-dpdk.c
@@ -1698,7 +1698,8 @@  static int
 parse_clone_actions(struct netdev *netdev,
                     struct flow_actions *actions,
                     const struct nlattr *clone_actions,
-                    const size_t clone_actions_len)
+                    const size_t clone_actions_len,
+                    struct offload_info *info)
 {
     const struct nlattr *ca;
     unsigned int cleft;
@@ -1723,8 +1724,11 @@  parse_clone_actions(struct netdev *netdev,
             add_flow_action(actions, RTE_FLOW_ACTION_TYPE_RAW_ENCAP,
                             raw_encap);
         } else if (clone_type == OVS_ACTION_ATTR_OUTPUT) {
-            if (add_output_action(netdev, actions, ca)) {
-                return -1;
+            /* add output action only if full-offload */
+            if (!info->partial_actions) {
+                if (add_output_action(netdev, actions, ca)) {
+                    return -1;
+                }
             }
         } else {
             VLOG_DBG_RL(&rl,
@@ -1788,7 +1792,8 @@  parse_flow_actions(struct netdev *netdev,
                    struct flow_actions *actions,
                    struct nlattr *nl_actions,
                    size_t nl_actions_len,
-                   struct act_resources *act_resources)
+                   struct act_resources *act_resources,
+                   struct offload_info *info)
 {
     struct nlattr *nla;
     size_t left;
@@ -1796,11 +1801,16 @@  parse_flow_actions(struct netdev *netdev,
     if (!strcmp(netdev_get_type(netdev), "vxlan")) {
         add_vxlan_decap_action(actions);
     }
-    add_count_action(actions);
+    if (!info->partial_actions) {
+        add_count_action(actions);
+    }
     NL_ATTR_FOR_EACH_UNSAFE (nla, left, nl_actions, nl_actions_len) {
         if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
-            if (add_output_action(netdev, actions, nla)) {
-                return -1;
+            /* add output action only if full-offload */
+            if (!info->partial_actions) {
+                if (add_output_action(netdev, actions, nla)) {
+                    return -1;
+                }
             }
         } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_DROP) {
             free_flow_actions(actions);
@@ -1821,7 +1831,7 @@  parse_flow_actions(struct netdev *netdev,
             size_t clone_actions_len = nl_attr_get_size(nla);
 
             if (parse_clone_actions(netdev, actions, clone_actions,
-                                    clone_actions_len)) {
+                                    clone_actions_len, info)) {
                 return -1;
             }
         } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_TUNNEL_POP) {
@@ -1899,16 +1909,23 @@  netdev_offload_dpdk_actions(struct netdev *netdev,
                             size_t actions_len,
                             const ovs_u128 *ufid,
                             struct act_resources *act_resources,
-                            struct flows_handle *flows)
+                            struct flows_handle *flows,
+                            struct offload_info *info)
 {
-    const struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 };
+    struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 };
     struct flow_actions actions = { .actions = NULL, .cnt = 0 };
     struct flow_item flow_item = { .devargs = NULL };
     struct rte_flow_error error;
     int ret;
 
+    if (info->attr_egress) {
+        flow_attr.ingress = 0;
+        flow_attr.egress = 1;
+        flow_attr.transfer = 0;
+    }
+
     ret = parse_flow_actions(netdev, &actions, nl_actions, actions_len,
-                             act_resources);
+                             act_resources, info);
     if (ret) {
         goto out;
     }
@@ -1957,8 +1974,15 @@  netdev_offload_dpdk_add_flow(struct netdev *netdev,
 
     ret = netdev_offload_dpdk_actions(netdev, &patterns, nl_actions,
                                       actions_len, ufid, &act_resources,
-                                      &flows);
-    if (ret) {
+                                      &flows, info);
+    if (!ret) {
+        if (info->partial_actions && info->attr_egress) {
+            /* actions_offloaded should be set to false with partial actions,
+             * since it is still considered as partial-offload and not
+             * full-offload. */
+            actions_offloaded = false;
+        }
+    } else if (!(info->partial_actions && info->attr_egress)) {
         /* If we failed to offload the rule actions fallback to MARK+RSS
          * actions.
          */
diff --git a/lib/netdev-offload.h b/lib/netdev-offload.h
index d6dd98367..3c079cc9d 100644
--- a/lib/netdev-offload.h
+++ b/lib/netdev-offload.h
@@ -67,6 +67,8 @@  struct offload_info {
 
     bool recirc_id_shared_with_tc;  /* Indicates whever tc chains will be in
                                      * sync with datapath recirc ids. */
+    uint8_t attr_egress;      /* Egress direction offload */
+    uint8_t partial_actions;  /* Partial action offload; no forward action */
 
     /*
      * The flow mark id assigened to the flow. If any pkts hit the flow,