On 10 May 2022, at 16:21, Emma Finn wrote:

> This commit introduces the initial infrastructure required to allow
> different implementations for OvS actions. The patch introduces action
> function pointers which allows user to switch between different action
> implementations available. This will allow for more performance and 
> flexibility
> so the user can choose the action implementation to best suite their use case.
>
> Signed-off-by: Emma Finn <[email protected]>
> Acked-by: Harry van Haaren <[email protected]>
> ---
>  lib/automake.mk           |  2 +
>  lib/dpif-netdev.c         |  4 ++
>  lib/odp-execute-private.c | 94 ++++++++++++++++++++++++++++++++++++++
>  lib/odp-execute-private.h | 96 +++++++++++++++++++++++++++++++++++++++
>  lib/odp-execute.c         | 39 ++++++++++++++--
>  lib/odp-execute.h         |  4 ++
>  6 files changed, 234 insertions(+), 5 deletions(-)
>  create mode 100644 lib/odp-execute-private.c
>  create mode 100644 lib/odp-execute-private.h
>
> diff --git a/lib/automake.mk b/lib/automake.mk
> index a23cdc4ad..625c0d9c9 100644
> --- a/lib/automake.mk
> +++ b/lib/automake.mk
> @@ -210,6 +210,8 @@ lib_libopenvswitch_la_SOURCES = \
>       lib/object-collection.h \
>       lib/odp-execute.c \
>       lib/odp-execute.h \
> +     lib/odp-execute-private.c \
> +     lib/odp-execute-private.h \
>       lib/odp-util.c \
>       lib/odp-util.h \
>       lib/ofp-actions.c \
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 61929049c..f74f8b864 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -1686,6 +1686,10 @@ create_dpif_netdev(struct dp_netdev *dp)
>      dpif->dp = dp;
>      dpif->last_port_seq = seq_read(dp->port_seq);
>
> +    /* Called once at initialization time. This handles setting up the state
> +     * of the actions functions at init time. */
> +    odp_execute_init();
> +
>      return &dpif->dpif;
>  }
>
> diff --git a/lib/odp-execute-private.c b/lib/odp-execute-private.c
> new file mode 100644
> index 000000000..b3a02745c
> --- /dev/null
> +++ b/lib/odp-execute-private.c
> @@ -0,0 +1,94 @@
> +/*
> + * Copyright (c) 2022 Intel.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include "dpdk.h"
> +#include "dp-packet.h"
> +#include "odp-execute-private.h"
> +#include "odp-netlink.h"
> +#include "odp-util.h"
> +#include "openvswitch/vlog.h"
> +
> +
> +int32_t action_autoval_init(struct odp_execute_action_impl *self);
> +VLOG_DEFINE_THIS_MODULE(odp_execute_private);

I don't think we need to add the _private extension here, as this is still the 
odp_execute framework.
However, I think we do not support duplicate module names, so you might need to 
add something :(
"private" does not feel like a user-friendly name the dpif module uses _impl, 
so maybe you can use that also?

> +
> +static struct odp_execute_action_impl action_impls[] = {
> +    [ACTION_IMPL_SCALAR] = {
> +        .available = 1,

Please use "true" here. Or should it be false, as the init function will take 
care of setting this to true?

> +        .name = "scalar",
> +        .probe = NULL,
> +        .init_func = NULL,
> +    },
> +};
> +
> +static void
> +action_impl_init_funcs(struct odp_execute_action_impl *to)
> +{
> +    for (uint32_t i = 0; i < __OVS_ACTION_ATTR_MAX; i++) {

Why not just int here? In OVS in general we do not XintXX_t unless really 
needed.

> +        atomic_init(&to->funcs[i], NULL);
> +    }
> +}
> +
> +static void
> +action_impl_copy_funcs(struct odp_execute_action_impl *to,
> +                       const struct odp_execute_action_impl *from)

nit: stdlib like functions seem to use dest and src as argument.

> +{
> +    for (uint32_t i = 0; i < __OVS_ACTION_ATTR_MAX; i++) {

Same int comment as above

> +        atomic_store_relaxed(&to->funcs[i], from->funcs[i]);
> +    }
> +}
> +
> +void
> +odp_execute_action_init(void)
> +{
> +    /* Call probe on each impl, and cache the result. */
> +    for (int i = 0; i < ACTION_IMPL_MAX; i++) {
> +        bool avail = true;

Newline?

> +        if (action_impls[i].probe) {
> +            /* Return zero is success, non-zero means error. */
> +            avail = (action_impls[i].probe() == 0);
> +        }
> +        VLOG_INFO("Action implementation %s (available: %s)\n",
> +                  action_impls[i].name, avail ? "available" : "not 
> available");
> +        action_impls[i].available = avail;
> +    }
> +
> +    uint32_t i;
> +    for (i = 0; i < ACTION_IMPL_MAX; i++) {

Please use int for i and define it in the for loop as you do elsewhere.

> +        /* Initialize Actions function pointers. */
> +        action_impl_init_funcs(&action_impls[i]);


Don't think we need to initialise them, as this is done through the 
action_impls[] structure definition.

> +
> +        /* Each impl's function array is initialized to reflect the scalar
> +         * implementation. This simplifies adding optimized implementations,
> +         * as the autovalidator can always compare all actions.
> +         *
> +         * Below copies the scalar functions to all other implementations.
> +         */
> +        if (i != ACTION_IMPL_SCALAR) {
> +            action_impl_copy_funcs(&action_impls[i],
> +                                   &action_impls[ACTION_IMPL_SCALAR]);
> +        }
> +
> +        if (action_impls[i].init_func) {
> +            action_impls[i].init_func(&action_impls[i]);

You do not check the return value of the init function. Guess it should be 
checked and if an error is returned its avail flag should be set to false.

> +        }
> +    }
> +}
> diff --git a/lib/odp-execute-private.h b/lib/odp-execute-private.h
> new file mode 100644
> index 000000000..869478ce9
> --- /dev/null
> +++ b/lib/odp-execute-private.h
> @@ -0,0 +1,96 @@
> +/*
> + * Copyright (c) 2022 Intel.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef ODP_EXTRACT_PRIVATE
> +#define ODP_EXTRACT_PRIVATE 1
> +
> +#include "dp-packet.h"
> +#include "odp-execute.h"
> +#include "odp-netlink.h"
> +#include "ovs-atomic.h"
> +
> +/* Forward declaration for typedef. */
> +struct odp_execute_action_impl;
> +
> +/* Typedef for an initialization function that can initialize each
> + * implementation, checking requirements such as CPU ISA.
> + */
> +typedef int32_t (*odp_execute_action_init_func)

Please use int as a return value.

> +                    (struct odp_execute_action_impl *self);
> +
> +/* Probe function is used to detect if this CPU has the ISA required
> + * to run the optimized action implementation.
> + * Returns zero on successful probe and available will be true.
> + * Returns negative errno on failure and available will be false.
> + */
> +typedef int (*odp_execute_action_probe)(void);
> +
> +/* Structure represents an implementation of the odp actions. */
> +struct odp_execute_action_impl {
> +    /* When set, the CPU ISA required for this implementation is available
> +     * and the implementation can be used.
> +     */
> +    bool available;
> +
> +    /* Name of the implementation. */
> +    const char *name;
> +
> +    /* Probe function is used to detect if this CPU has the ISA required
> +     * to run the optimized miniflow implementation. It is optional and
> +     * if it is not used, then it must be null.
> +     */
> +    odp_execute_action_probe probe;
> +
> +    /* Called to check requirements and if usable, initializes the
> +     * implementation for use.
> +     */
> +    odp_execute_action_init_func init_func;

Can we not combine the probe and init function together? From the code above, I 
see no reason for them being separate?

> +
> +    /* An array of callback functions, one for each action. */
> +    ATOMIC(odp_execute_cb) funcs[__OVS_KEY_ATTR_MAX];

Guess this is cut/paste error, this should be __OVS_ACTION_ATTR_MAX

> +};
> +
> +/* Order of Actions implementations. */
> +enum odp_execute_action_impl_idx {
> +    ACTION_IMPL_SCALAR,
> +    /* See ACTION_IMPL_BEGIN below, for "first to-be-validated" impl.
> +     * Do not change the autovalidator position in this list without updating
> +     * the define below.
> +     */
> +
> +    ACTION_IMPL_MAX,
> +};
> +
> +/* Index to start verifying implementations from. */
> +BUILD_ASSERT_DECL(ACTION_IMPL_SCALAR == 0);
> +
> +/* Odp execute init handles setting up the state of the actions functions at
> + * initialization time. It cannot return errors, as it must always succeed in
> + * initializing the scalar/generic codepath.
> + */
> +void odp_execute_action_init(void);
> +
> +/* Update the current active functions to those requested in name. */
> +void odp_execute_action_get(struct ds *name);
> +int32_t odp_execute_action_set(const char *name,

Should be int ?

> +                               struct odp_execute_action_impl *active);
> +
> +/* Init function for the scalar implementation. Calls into the odp-execute.c
> + * file, and initializes the function pointers for optimized action types.
> + */
> +int32_t odp_action_scalar_init(struct odp_execute_action_impl *self);

Should be int.

I do not see the last three function definitions in this patch?

> +
> +#endif /* ODP_EXTRACT_PRIVATE */
> diff --git a/lib/odp-execute.c b/lib/odp-execute.c
> index 7da56793d..165386e66 100644
> --- a/lib/odp-execute.c
> +++ b/lib/odp-execute.c
> @@ -17,6 +17,7 @@
>
>  #include <config.h>
>  #include "odp-execute.h"
> +#include "odp-execute-private.h"
>  #include <sys/types.h>
>  #include <netinet/in.h>
>  #include <arpa/inet.h>
> @@ -833,6 +834,23 @@ requires_datapath_assistance(const struct nlattr *a)
>      return false;
>  }
>
> +/* The active function pointers on the datapath. ISA optimized 
> implementations
> + * are enabled by plugging them into this static arary, which is consulted 
> when
> + * applying actions on the datapath.
> + */
> +static struct odp_execute_action_impl actions_active_impl;
> +
> +void
> +odp_execute_init(void)
> +{
> +    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
> +    if (ovsthread_once_start(&once)) {
> +        odp_execute_action_init();
> +        ovsthread_once_done(&once);
> +    }
> +}
> +
> +
>  /* Executes all of the 'actions_len' bytes of datapath actions in 'actions' 
> on
>   * the packets in 'batch'.  If 'steal' is true, possibly modifies and
>   * definitely free the packets in 'batch', otherwise leaves 'batch' 
> unchanged.
> @@ -858,13 +876,12 @@ odp_execute_actions(void *dp, struct dp_packet_batch 
> *batch, bool steal,
>      NL_ATTR_FOR_EACH_UNSAFE (a, left, actions, actions_len) {
>          int type = nl_attr_type(a);
>          bool last_action = (left <= NLA_ALIGN(a->nla_len));
> +        /* Allow 'dp_execute_action' to steal the packet data if we do
> +         * not need it any more. */

I think the comment doesn’t make sense anymore in this place, so I would just 
remove it.

> +        bool should_steal = steal && last_action;
>
>          if (requires_datapath_assistance(a)) {
>              if (dp_execute_action) {
> -                /* Allow 'dp_execute_action' to steal the packet data if we 
> do
> -                 * not need it any more. */
> -                bool should_steal = steal && last_action;
> -
>                  dp_execute_action(dp, batch, a, should_steal);
>
>                  if (last_action || dp_packet_batch_is_empty(batch)) {
> @@ -879,8 +896,20 @@ odp_execute_actions(void *dp, struct dp_packet_batch 
> *batch, bool steal,

Do we really need the steal flag here? As we are only handling modification 
actions.

>              continue;
>          }
>
> -        switch ((enum ovs_action_attr) type) {
> +        /* If type is set in the active actions implementation, call the
> +         * function-pointer and continue to the next action.
> +         */
> +        enum ovs_action_attr attr_type = (enum ovs_action_attr) type;

Rather than do this here, fix it at the place where type gets define, i.e. 
something like:

enum ovs_action_attr type = (enum ovs_action_attr) nl_attr_type(a);

> +        if (actions_active_impl.funcs[attr_type]) {

We should also check the attr_type is in bound of __OVS_ACTION_ATTR_MAX.

> +            actions_active_impl.funcs[attr_type](NULL, batch, a, 
> should_steal);

Why is the first parameter NULL? Could we pass in dp? However I do not see it 
used anywhere, so why not remove it completely?

Same for should_steal, we do not need it as we handle only modification of 
packets, i.e. none data path actions.

I have another general architecture question? What if for a specific packet (or 
batch of packets) the SIMD implementation is not able to process the 
packet/batch?
How can we handle this in a uniform way? Can you give this some thought and add 
support?

> +            continue;
> +        }
> +
> +        /* If the action was not handled by the active function pointers 
> above,
> +         * process them by switching on the type below.
> +         */
>
> +        switch (attr_type) {
>          case OVS_ACTION_ATTR_HASH: {
>              const struct ovs_action_hash *hash_act = nl_attr_get(a);

Maybe add something like the below to avoid potential issues in the future.

diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 165386e66..975f6e2cb 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -1123,6 +1123,8 @@ odp_execute_actions(void *dp, struct dp_packet_batch 
*batch, bool steal,
         case __OVS_ACTION_ATTR_MAX:
             OVS_NOT_REACHED();
         }
+        /* Do not add any generic processing here, as it won't be executed when
+         * an ISA-specific action implementation exists. */
     }

>
> diff --git a/lib/odp-execute.h b/lib/odp-execute.h
> index a3578a575..c4f5303e7 100644
> --- a/lib/odp-execute.h
> +++ b/lib/odp-execute.h
> @@ -28,6 +28,10 @@ struct dp_packet;
>  struct pkt_metadata;
>  struct dp_packet_batch;
>
> +
> +/* Called once at initialization time. */
> +void odp_execute_init(void);
> +
>  typedef void (*odp_execute_cb)(void *dp, struct dp_packet_batch *batch,
>                                 const struct nlattr *action, bool 
> should_steal);
>
> -- 
> 2.25.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to