Eelco Chaudron via dev <[email protected]> writes:

> This patch extends the dummy offload provider to simulate full
> hardware offload behavior, where packets matching offloaded
> flows can be processed entirely in "hardware" without falling
> back to the software datapath.
>
> Previously, the dummy offload provider only simulated partial
> offload, where flows were marked as offloaded but all packet
> processing still occurred in software.
>
> Signed-off-by: Eelco Chaudron <[email protected]>
> ---

Hi Eelco,

Thanks for the patch - I think I can also see how to integrate the
ct-offload series with this.

>  lib/dpif-offload-dummy.c | 294 +++++++++++++++++++++++++++++++++++----
>  lib/dummy.h              |   8 +-
>  lib/netdev-dummy.c       |  56 ++++++--
>  tests/dpif-netdev.at     | 124 +++++++++++++++++
>  tests/ofproto-dpif.at    |  90 ++++++++++--
>  5 files changed, 517 insertions(+), 55 deletions(-)
>
> diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c
> index b7b24d6064..28f1f40013 100644
> --- a/lib/dpif-offload-dummy.c
> +++ b/lib/dpif-offload-dummy.c
> @@ -42,8 +42,11 @@ struct pmd_id_data {
>  struct dummy_offloaded_flow {
>      struct hmap_node node;
>      struct match match;
> +    const struct nlattr *actions;
> +    size_t actions_len;
>      ovs_u128 ufid;
>      uint32_t mark;
> +    struct dpif_flow_stats stats;
>  
>      /* The pmd_id_map below is also protected by the port_mutex. */
>      struct hmap pmd_id_map;
> @@ -63,6 +66,19 @@ struct dummy_offload_port {
>  
>      struct ovs_mutex port_mutex; /* Protect all below members. */
>      struct hmap offloaded_flows OVS_GUARDED;
> +    struct ovs_list hw_recv_queue OVS_GUARDED;
> +
> +    /* Some simulated offload statistics. */
> +    uint64_t rx_offload_partial OVS_GUARDED; /* Match found, CPU continues. 
> */
> +    uint64_t rx_offload_full OVS_GUARDED; /* Fully offloaded, CPU bypassed. 
> */
> +    uint64_t rx_offload_miss OVS_GUARDED; /* No HW offload rule matched. */
> +    uint64_t rx_offload_pipe_abort OVS_GUARDED; /* Pipeline abort. */
> +};
> +
> +struct hw_pkt_node {
> +    struct dp_packet *pkt;
> +    int queue_id;
> +    struct ovs_list list_node;
>  };
>  
>  static void dummy_flow_unreference(struct dummy_offload *, unsigned pmd_id,
> @@ -228,6 +244,7 @@ dummy_free_flow(struct dummy_offload_port *port,
>      ovs_assert(!hmap_count(&off_flow->pmd_id_map));
>  
>      hmap_destroy(&off_flow->pmd_id_map);
> +    free(CONST_CAST(struct nlattr *, off_flow->actions));
>      free(off_flow);
>  }
>  
> @@ -288,6 +305,7 @@ dummy_free_port__(struct dummy_offload *offload,
>                    struct dummy_offload_port *port, bool close_netdev)
>  {
>      struct dummy_offloaded_flow *off_flow;
> +    struct hw_pkt_node *pkt;
>  
>      ovs_mutex_lock(&port->port_mutex);
>      HMAP_FOR_EACH_POP (off_flow, node, &port->offloaded_flows) {
> @@ -295,6 +313,12 @@ dummy_free_port__(struct dummy_offload *offload,
>          dummy_free_flow(port, off_flow, false);
>      }
>      hmap_destroy(&port->offloaded_flows);
> +
> +    LIST_FOR_EACH_POP (pkt, list_node, &port->hw_recv_queue) {
> +        dp_packet_delete(pkt->pkt);
> +        free(pkt);
> +    }
> +
>      ovs_mutex_unlock(&port->port_mutex);
>      ovs_mutex_destroy(&port->port_mutex);
>      if (close_netdev) {
> @@ -330,11 +354,12 @@ dummy_offload_port_add(struct dpif_offload 
> *dpif_offload,
>                         struct netdev *netdev, odp_port_t port_no)
>  {
>      struct dummy_offload *offload = dummy_offload_cast(dpif_offload);
> -    struct dummy_offload_port *port = xmalloc(sizeof *port);
> +    struct dummy_offload_port *port = xzalloc(sizeof *port);
>  
>      ovs_mutex_init(&port->port_mutex);
>      ovs_mutex_lock(&port->port_mutex);
>      hmap_init(&port->offloaded_flows);
> +    ovs_list_init(&port->hw_recv_queue);
>      ovs_mutex_unlock(&port->port_mutex);
>  
>      if (dpif_offload_port_mgr_add(dpif_offload, &port->pm_port, netdev,
> @@ -445,15 +470,27 @@ dummy_offload_get_debug(const struct dpif_offload 
> *offload, struct ds *ds,
>  {
>      if (json) {
>          struct json *json_ports = json_object_create();
> -        struct dpif_offload_port *port;
> +        struct dpif_offload_port *port_;
>  
> -        DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) {
> +        DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
> +            struct dummy_offload_port *port = dummy_offload_port_cast(port_);
>              struct json *json_port = json_object_create();
>  
>              json_object_put(json_port, "port_no",
> -                            json_integer_create(odp_to_u32(port->port_no)));
> -
> -            json_object_put(json_ports, netdev_get_name(port->netdev),
> +                            json_integer_create(odp_to_u32(port_->port_no)));
> +
> +            ovs_mutex_lock(&port->port_mutex);
> +            json_object_put(json_port, "rx_offload_partial",
> +                            json_integer_create(port->rx_offload_partial));
> +            json_object_put(json_port, "rx_offload_full",
> +                            json_integer_create(port->rx_offload_full));
> +            json_object_put(json_port, "rx_offload_miss",
> +                            json_integer_create(port->rx_offload_miss));
> +            json_object_put(json_port, "rx_offload_pipe_abort",
> +                            
> json_integer_create(port->rx_offload_pipe_abort));
> +            ovs_mutex_unlock(&port->port_mutex);
> +
> +            json_object_put(json_ports, netdev_get_name(port_->netdev),
>                              json_port);
>          }
>  
> @@ -463,11 +500,22 @@ dummy_offload_get_debug(const struct dpif_offload 
> *offload, struct ds *ds,
>              json_destroy(json_ports);
>          }
>      } else if (ds) {
> -        struct dpif_offload_port *port;
> -
> -        DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) {
> -            ds_put_format(ds, "  - %s: port_no: %u\n",
> -                          netdev_get_name(port->netdev), port->port_no);
> +        struct dpif_offload_port *port_;
> +
> +        DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
> +            struct dummy_offload_port *port = dummy_offload_port_cast(port_);
> +
> +            ovs_mutex_lock(&port->port_mutex);
> +            ds_put_format(ds,
> +                          "  - %s: port_no: %u\n"
> +                          "    rx_offload_partial   : %" PRIu64 "\n"
> +                          "    rx_offload_full      : %" PRIu64 "\n"
> +                          "    rx_offload_miss      : %" PRIu64 "\n"
> +                          "    rx_offload_pipe_abort: %" PRIu64 "\n",
> +                          netdev_get_name(port_->netdev), port_->port_no,
> +                          port->rx_offload_partial, port->rx_offload_full,
> +                          port->rx_offload_miss, 
> port->rx_offload_pipe_abort);
> +            ovs_mutex_unlock(&port->port_mutex);
>          }
>      }
>  }
> @@ -515,6 +563,19 @@ dummy_offload_get_port_by_netdev(const struct 
> dpif_offload *offload,
>      return dummy_offload_port_cast(port);
>  }
>  
> +static struct dummy_offload_port *
> +dummy_offload_get_port_by_odp_port(const struct dpif_offload *offload_,
> +                                   odp_port_t port_no)
> +{
> +    struct dpif_offload_port *port;
> +
> +    port = dpif_offload_port_mgr_find_by_odp_port(offload_, port_no);
> +    if (!port) {
> +        return NULL;
> +    }
> +    return dummy_offload_port_cast(port);
> +}
> +
>  static int
>  dummy_offload_hw_post_process(const struct dpif_offload *offload_,
>                                struct netdev *netdev, unsigned pmd_id,
> @@ -549,6 +610,86 @@ dummy_offload_hw_post_process(const struct dpif_offload 
> *offload_,
>      return 0;
>  }
>  
> +static bool
> +dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_,
> +                                        odp_port_t in_odp,
> +                                        const struct nlattr *actions,
> +                                        size_t actions_len)
> +{
> +    const struct nlattr *nla;
> +    size_t left;
> +
> +    /* Can we fully offload this flow? For now, only output actions are
> +     * supported, and only to dummy-pmd netdevs where the egress port differs
> +     * from the ingress port.  The latter restriction ensures that the 
> partial
> +     * offload test cases pass.
> +     *
> +     * The reason for supporting only dummy-pmd netdevs as output targets is
> +     * that they provide full protection when calling netdev_send() from any
> +     * thread, via a netdev-level mutex. */
> +    NL_ATTR_FOR_EACH (nla, left, actions, actions_len) {
> +        if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
> +            odp_port_t out_odp = nl_attr_get_odp_port(nla);
> +            struct dummy_offload_port *out_port;
> +
> +            out_port = dummy_offload_get_port_by_odp_port(offload_, out_odp);
> +            if (out_odp == in_odp || !out_port
> +                || strcmp("dummy-pmd",
> +                          netdev_get_type(out_port->pm_port.netdev))) {
> +                return false;
> +            }
> +        } else {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static bool
> +dummy_offload_hw_process_pkt(const struct dpif_offload *offload_,
> +                             struct dummy_offloaded_flow *flow,
> +                             struct dp_packet *pkt)
> +{
> +    uint32_t hash = dp_packet_get_rss_hash(pkt);
> +    uint32_t pkt_size = dp_packet_size(pkt);
> +    const struct nlattr *nla;
> +    size_t left;
> +
> +    if (!flow->actions) {
> +        return false;
> +    }
> +
> +    NL_ATTR_FOR_EACH (nla, left, flow->actions, flow->actions_len) {
> +        bool last_action = (left <= NLA_ALIGN(nla->nla_len));
> +
> +        if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
> +            odp_port_t odp_port = nl_attr_get_odp_port(nla);
> +            struct dummy_offload_port *port;
> +            struct dp_packet_batch batch;
> +            int n_txq;
> +
> +            port = dummy_offload_get_port_by_odp_port(offload_, odp_port);
> +            if (!port) {
> +                return false;
> +            }
> +
> +            n_txq = netdev_n_txq(port->pm_port.netdev);
> +            dp_packet_batch_init_packet(&batch, last_action
> +                                                ? pkt
> +                                                : dp_packet_clone(pkt));
> +            /* As the tx-steering option is not exposed to hardware offload,
> +             * for now we assume hash steering based on the number of queues
> +             * configured for the dummy-netdev. */
> +            netdev_send(port->pm_port.netdev, hash % n_txq, &batch, false);
> +        }
> +    }
> +
> +    flow->stats.n_bytes += pkt_size;
> +    flow->stats.n_packets++;
> +    flow->stats.used = time_msec();

Maybe it doesn't matter too much in this case, but these stats are being
updated even if netdev_send() fails above.  Also, I guess technically
there could be some kind of race with the flow_put since that does an
assignment (but I might have missed a lock).

> +    return true;
> +}
> +
>  static int
>  dummy_flow_put(const struct dpif_offload *offload_, struct netdev *netdev,
>                 struct dpif_offload_flow_put *put,
> @@ -558,6 +699,7 @@ dummy_flow_put(const struct dpif_offload *offload_, 
> struct netdev *netdev,
>      struct dummy_offloaded_flow *off_flow;
>      struct dummy_offload_port *port;
>      bool modify = true;
> +    bool full_offload;
>      int error = 0;
>  
>      port = dummy_offload_get_port_by_netdev(offload_, netdev);
> @@ -566,6 +708,10 @@ dummy_flow_put(const struct dpif_offload *offload_, 
> struct netdev *netdev,
>          goto exit;
>      }
>  
> +    full_offload = dummy_offload_are_all_actions_supported(
> +                        offload_, put->match->flow.in_port.odp_port,
> +                        put->actions, put->actions_len);
> +
>      ovs_mutex_lock(&port->port_mutex);
>  
>      off_flow = dummy_find_offloaded_flow_and_update(
> @@ -587,6 +733,14 @@ dummy_flow_put(const struct dpif_offload *offload_, 
> struct netdev *netdev,
>          *previous_flow_reference = NULL;
>      }
>      memcpy(&off_flow->match, put->match, sizeof *put->match);
> +    free(CONST_CAST(struct nlattr *, off_flow->actions));
> +    if (full_offload) {
> +        off_flow->actions = xmemdup(put->actions, put->actions_len);
> +        off_flow->actions_len = put->actions_len;
> +    } else {
> +        off_flow->actions = NULL;
> +        off_flow->actions_len = 0;
> +    }
>  
>      /* As we have per-netdev 'offloaded_flows', we don't need to match
>       * the 'in_port' for received packets.  This will also allow offloading
> @@ -609,13 +763,13 @@ dummy_flow_put(const struct dpif_offload *offload_, 
> struct netdev *netdev,
>      }
>  
>  exit_unlock:
> -    ovs_mutex_unlock(&port->port_mutex);
> -
> -exit:
>      if (put->stats) {
> -        memset(put->stats, 0, sizeof *put->stats);
> +        *put->stats = off_flow->stats;
>      }
>  
> +    ovs_mutex_unlock(&port->port_mutex);
> +
> +exit:
>      dummy_offload_log_operation(modify ? "modify" : "add", error, put->ufid);
>      return error;
>  }
> @@ -650,6 +804,10 @@ dummy_flow_del(const struct dpif_offload *offload_, 
> struct netdev *netdev,
>          goto exit_unlock;
>      }
>  
> +    if (del->stats) {
> +        memcpy(del->stats, &off_flow->stats, sizeof *del->stats);
> +    }
> +
>      mark = off_flow->mark;
>      if (!hmap_count(&off_flow->pmd_id_map)) {
>          dummy_free_flow_mark(offload, mark);
> @@ -678,10 +836,6 @@ exit:
>          ds_destroy(&ds);
>      }
>  
> -    if (del->stats) {
> -        memset(del->stats, 0, sizeof *del->stats);
> -    }
> -
>      dummy_offload_log_operation("delete", error ? -1 : 0, del->ufid);
>      return error ? ENOENT : 0;
>  }
> @@ -701,14 +855,19 @@ dummy_flow_stats(const struct dpif_offload *offload_, 
> struct netdev *netdev,
>  
>      ovs_mutex_lock(&port->port_mutex);
>      off_flow = dummy_find_offloaded_flow(port, ufid);
> +    if (off_flow) {
> +        memcpy(stats, &off_flow->stats, sizeof *stats);
> +        attrs->dp_layer = off_flow->actions ? "dummy" : "ovs";
> +        attrs->dp_extra_info = NULL;
> +        attrs->offloaded = true;
> +    }
>      ovs_mutex_unlock(&port->port_mutex);
>  
> -    memset(stats, 0, sizeof *stats);
> -    attrs->offloaded = off_flow ? true : false;
> -    attrs->dp_layer = "ovs"; /* 'ovs', since this is a partial offload. */
> -    attrs->dp_extra_info = NULL;
> +    if (!off_flow) {
> +        return false;
> +    }
>  
> -    return off_flow ? true : false;
> +    return true;
>  }
>  
>  static void
> @@ -729,23 +888,26 @@ dummy_flow_unreference(struct dummy_offload *offload, 
> unsigned pmd_id,
>      }
>  }
>  
> -void
> +bool
>  dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet 
> *packet,
> -                              struct flow *flow)
> +                              int queue_id, struct flow *flow)
>  {
>      const struct dpif_offload *offload = ovsrcu_get(
>          const struct dpif_offload *, &netdev->dpif_offload);
>      struct dummy_offloaded_flow *data;
>      struct dummy_offload_port *port;
> +    bool packet_stolen = false;
>      struct flow packet_flow;
> +    bool offloaded = false;
>  
> -    if (!offload || strcmp(dpif_offload_type(offload), "dummy")) {
> -        return;
> +    if (!dpif_offload_enabled() || !offload
> +        || strcmp(dpif_offload_type(offload), "dummy")) {
> +        return false;
>      }
>  
>      port = dummy_offload_get_port_by_netdev(offload, netdev);
>      if (!port) {
> -        return;
> +        return false;
>      }
>  
>      if (!flow) {
> @@ -778,10 +940,84 @@ dummy_netdev_simulate_offload(struct netdev *netdev, 
> struct dp_packet *packet,
>                  VLOG_DBG("%s", ds_cstr(&ds));
>                  ds_destroy(&ds);
>              }
> +
> +            if (data->actions) {
> +                /* Perform hardware offload simulation.  The packet is stolen
> +                 * here and handed off to the PMD thread callback for
> +                 * processing. */
> +                struct hw_pkt_node *pkt_node = xmalloc(sizeof *pkt_node);
> +
> +                pkt_node->pkt = packet;
> +                pkt_node->queue_id = queue_id;
> +                ovs_list_push_back(&port->hw_recv_queue, 
> &pkt_node->list_node);
> +                packet_stolen = true;
> +                port->rx_offload_full++;
> +            } else {
> +                port->rx_offload_partial++;
> +            }
> +
> +            offloaded = true;
>              break;
>          }
>      }
> +
> +    if (!offloaded) {
> +        port->rx_offload_miss++;
> +    }
> +
>      ovs_mutex_unlock(&port->port_mutex);
> +    return packet_stolen;
> +}
> +
> +void
> +dummy_netdev_hw_offload_run(struct netdev *netdev)
> +{
> +    const struct dpif_offload *offload = ovsrcu_get(
> +        const struct dpif_offload *, &netdev->dpif_offload);
> +    struct dpif_offload_port *port_;
> +
> +    if (!dpif_offload_enabled() || !offload
> +        || strcmp(dpif_offload_type(offload), "dummy")) {
> +        return;
> +    }
> +
> +    DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
> +        struct dummy_offload_port *port;
> +        struct hw_pkt_node *pkt_node;
> +
> +        port = dummy_offload_port_cast(port_);
> +
> +        if (ovs_mutex_trylock(&port->port_mutex)) {
> +            continue;
> +        }
> +
> +        LIST_FOR_EACH_POP (pkt_node, list_node, &port->hw_recv_queue) {
> +            struct dummy_offloaded_flow *offloaded_flow;
> +            struct dp_packet *pkt = pkt_node->pkt;
> +            bool processed = false;
> +            struct flow flow;
> +
> +            flow_extract(pkt, &flow);
> +            HMAP_FOR_EACH (offloaded_flow, node, &port->offloaded_flows) {
> +                if (flow_equal_except(&flow, &offloaded_flow->match.flow,
> +                                      &offloaded_flow->match.wc)) {
> +
> +                    processed = dummy_offload_hw_process_pkt(
> +                                    offload, offloaded_flow, pkt);
> +                    break;
> +                }
> +            }
> +
> +            if (!processed) {
> +                VLOG_DBG("Failed HW pipeline, sent to sw!");
> +                port->rx_offload_pipe_abort++;
> +                netdev_dummy_queue_simulate_offload_packet(
> +                    port->pm_port.netdev, pkt, pkt_node->queue_id);
> +            }
> +            free(pkt_node);
> +        }
> +        ovs_mutex_unlock(&port->port_mutex);
> +    }
>  }
>  
>  #define DEFINE_DPIF_DUMMY_CLASS(NAME, TYPE_STR)                             \
> diff --git a/lib/dummy.h b/lib/dummy.h
> index e5c9d4071c..f250ae5933 100644
> --- a/lib/dummy.h
> +++ b/lib/dummy.h
> @@ -41,10 +41,14 @@ void dummy_enable(const char *arg);
>  /* Implementation details. */
>  void dpif_dummy_register(enum dummy_level);
>  void netdev_dummy_register(enum dummy_level);
> +void netdev_dummy_queue_simulate_offload_packet(const struct netdev *,
> +                                                struct dp_packet *,
> +                                                int queue_id);
>  void timeval_dummy_register(void);
>  void ofpact_dummy_enable(void);
>  bool is_dummy_netdev_class(const struct netdev_class *);
> -void dummy_netdev_simulate_offload(struct netdev *, struct dp_packet *,
> -                                   struct flow *);
> +bool dummy_netdev_simulate_offload(struct netdev *, struct dp_packet *,
> +                                   int queue_id, struct flow *);
> +void dummy_netdev_hw_offload_run(struct netdev *);
>  
>  #endif /* dummy.h */
> diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
> index 7d3a7b9682..d5e8599349 100644
> --- a/lib/netdev-dummy.c
> +++ b/lib/netdev-dummy.c
> @@ -695,6 +695,7 @@ netdev_dummy_run(const struct netdev_class *netdev_class)
>          ovs_mutex_lock(&dev->mutex);
>          dummy_packet_conn_run(dev);
>          ovs_mutex_unlock(&dev->mutex);
> +        dummy_netdev_hw_offload_run(&dev->up);
>      }
>      ovs_mutex_unlock(&dummy_list_mutex);
>  }
> @@ -1875,7 +1876,7 @@ eth_from_flow_str(const char *s, size_t packet_size,
>  }
>  
>  static void
> -netdev_dummy_queue_packet__(struct netdev_rxq_dummy *rx, struct dp_packet 
> *packet)
> +netdev_dummy_rxq_enqueue(struct netdev_rxq_dummy *rx, struct dp_packet 
> *packet)
>  {
>      struct pkt_list_node *pkt_node = xmalloc(sizeof *pkt_node);
>  
> @@ -1886,35 +1887,64 @@ netdev_dummy_queue_packet__(struct netdev_rxq_dummy 
> *rx, struct dp_packet *packe
>  }
>  
>  static void
> -netdev_dummy_queue_packet(struct netdev_dummy *dummy, struct dp_packet 
> *packet,
> -                          struct flow *flow, int queue_id)
> +netdev_dummy_queue_packet__(struct netdev_dummy *dummy,
> +                            struct dp_packet *packet, int queue_id)
>      OVS_REQUIRES(dummy->mutex)
>  {
> -    struct netdev_rxq_dummy *rx, *prev;
> -
> -    if (dummy->rxq_pcap) {
> -        ovs_pcap_write(dummy->rxq_pcap, packet);
> -    }
> +    struct netdev_rxq_dummy *rx, *prev = NULL;
>  
> -    dummy_netdev_simulate_offload(&dummy->up, packet, flow);
> -
> -    prev = NULL;
>      LIST_FOR_EACH (rx, node, &dummy->rxes) {
>          if (rx->up.queue_id == queue_id &&
>              rx->recv_queue_len < NETDEV_DUMMY_MAX_QUEUE) {
>              if (prev) {
> -                netdev_dummy_queue_packet__(prev, dp_packet_clone(packet));
> +                netdev_dummy_rxq_enqueue(prev, dp_packet_clone(packet));
>              }
>              prev = rx;
>          }
>      }
>      if (prev) {
> -        netdev_dummy_queue_packet__(prev, packet);
> +        netdev_dummy_rxq_enqueue(prev, packet);
>      } else {
>          dp_packet_delete(packet);
>      }
>  }
>  
> +static void
> +netdev_dummy_queue_packet(struct netdev_dummy *dummy, struct dp_packet 
> *packet,
> +                          struct flow *flow, int queue_id)
> +    OVS_REQUIRES(dummy->mutex)
> +{
> +    if (dummy->rxq_pcap) {
> +        ovs_pcap_write(dummy->rxq_pcap, packet);
> +    }
> +
> +    if (dummy_netdev_simulate_offload(&dummy->up, packet,
> +                                                   queue_id, flow)) {
> +        /* Packet was stolen for full HW offload simulation. */
> +        return;
> +    }
> +
> +    netdev_dummy_queue_packet__(dummy, packet, queue_id);
> +}
> +
> +void netdev_dummy_queue_simulate_offload_packet(const struct netdev *netdev,
> +                                                struct dp_packet *packet,
> +                                                int queue_id)
> +{
> +    struct netdev_dummy *dummy;
> +
> +    if (!netdev || !is_dummy_netdev_class(netdev->netdev_class)) {
> +        dp_packet_delete(packet);
> +        return;
> +    }
> +
> +    dummy = netdev_dummy_cast(netdev);
> +
> +    ovs_mutex_lock(&dummy->mutex);
> +    netdev_dummy_queue_packet__(dummy, packet, queue_id);
> +    ovs_mutex_unlock(&dummy->mutex);
> +}
> +
>  static void
>  netdev_dummy_receive(struct unixctl_conn *conn,
>                       int argc, const char *argv[], void *aux OVS_UNUSED)
> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
> index 2311979709..0480730cab 100644
> --- a/tests/dpif-netdev.at
> +++ b/tests/dpif-netdev.at
> @@ -32,6 +32,20 @@ strip_xout_keep_actions () {
>  ' | sort
>  }
>  
> +strip_hw_offload () {
> +    sed '
> +    /^flow-dump from/d
> +    s/ufid:[-0-9a-f]*,//
> +    s/used:[0-9\.][0-9\.]*/used:0.0/
> +    s/[^,]*(0\/0),//g
> +    s/eth([^)]*)/eth()/
> +    s/ipv4([^)]*)/ipv4()/
> +    s/udp([^)]*)/udp()/
> +    s/, dp-extra-info.*//
> +    s/^[[:space:]]*//
> +' | sort
> +}
> +
>  filter_flow_install () {
>      grep 'flow_add' | sed 's/.*flow_add: //' | sort | uniq
>  }
> @@ -637,6 +651,116 @@ 
> arp,in_port=ANY,dl_vlan=11,dl_vlan_pcp=7,vlan_tci1=0x0000,dl_src=00:06:07:08:09:
>  DPIF_NETDEV_FLOW_HW_OFFLOAD_OFFSETS_VID_ARP([dummy])
>  DPIF_NETDEV_FLOW_HW_OFFLOAD_OFFSETS_VID_ARP([dummy-pmd])
>  
> +AT_SETUP([dpif-netdev - full hw offload - dummy-pmd])
> +OVS_VSWITCHD_START(
> +  [add-port br0 p1 -- \
> +   add-port br0 p2 -- \
> +   set interface p1 type=dummy-pmd ofport_request=1 options:ifindex=1100 -- \
> +   set interface p2 type=dummy-pmd ofport_request=2 options:ifindex=1200 \
> +     options:tx_pcap=p2.pcap -- \
> +   set bridge br0 datapath-type=dummy other-config:datapath-id=1234 \
> +     fail-mode=secure], [], [], [--dummy-numa="0,0,0,0,1,1,1,1"])
> +AT_CHECK([ovs-appctl vlog/set dpif:file:dbg dpif_netdev:file:dbg \
> +          dpif_offload_dummy:file:dbg])
> +
> +AT_CHECK([ovs-vsctl set Open_vSwitch . other_config:hw-offload=true])
> +OVS_WAIT_UNTIL([grep "Flow HW offload is enabled" ovs-vswitchd.log])
> +
> +AT_CHECK([ovs-ofctl del-flows br0])
> +AT_CHECK([ovs-ofctl add-flow br0 in_port=1,actions=p2])
> +
> +packet="\
> +  eth_src=00:06:07:08:09:0a,eth_dst=00:01:02:03:04:05,\
> +  udp,ip_src=127.0.0.1,ip_dst=127.0.0.1,nw_ttl=64,\
> +  udp_src=54392,udp_dst=5201"
> +packet_hex=$(ovs-ofctl compose-packet --bare "${packet}")
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +
> +OVS_WAIT_UNTIL([grep "miss upcall" ovs-vswitchd.log])
> +AT_CHECK([grep -A 1 'miss upcall' ovs-vswitchd.log | tail -n 1], [0], [dnl
> +recirc_id(0),dp_hash(0),skb_priority(0),in_port(1),skb_mark(0),ct_state(0),ct_zone(0),ct_mark(0),ct_label(0),packet_type(ns=0,id=0),eth(src=00:06:07:08:09:0a,dst=00:01:02:03:04:05),eth_type(0x0800),ipv4(src=127.0.0.1,dst=127.0.0.1,proto=17,tos=0,ttl=64,frag=no),udp(src=54392,dst=5201)
> +])
> +
> +# Check that flow successfully offloaded.
> +OVS_WAIT_UNTIL([grep "succeed to add netdev flow" ovs-vswitchd.log])
> +AT_CHECK([filter_hw_flow_install < ovs-vswitchd.log | strip_xout], [0], [dnl
> +p1: flow put[[create]]: flow match: 
> recirc_id=0,eth,ip,in_port=1,vlan_tci=0x0000/0x1fff,nw_frag=no, mark: 1
> +])
> +
> +# Check that datapath flow installed successfully.
> +AT_CHECK([filter_flow_install < ovs-vswitchd.log | strip_xout], [0], [dnl
> +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no),
>  actions: <del>
> +])
> +
> +# Inject the same packet again.
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +
> +# Check for succesfull packet matching with installed offloaded flow.
> +AT_CHECK([filter_hw_packet_netdev_dummy < ovs-vswitchd.log | strip_xout], 
> [0], [dnl
> +p1: packet: 
> udp,vlan_tci=0x0000,dl_src=00:06:07:08:09:0a,dl_dst=00:01:02:03:04:05,nw_src=127.0.0.1,nw_dst=127.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,nw_frag=no,tp_src=54392,tp_dst=5201
>  matches with flow: recirc_id=0,eth,ip,vlan_tci=0x0000/0x1fff,nw_frag=no with 
> mark: 1
> +])
> +
> +# Dump the datapath flow to see that actions was executed for a packet.
> +AT_CHECK([ovs-appctl dpctl/dump-flows -m | strip_hw_offload], [0], [dnl
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth(),eth_type(0x0800),ipv4(),udp(),
>  packets:1, bytes:106, used:0.0s, offloaded:yes, dp:dummy, actions:p2
> +])
> +
> +# Wait for datapath flow expiration.
> +ovs-appctl time/stop
> +ovs-appctl time/warp 15000
> +ovs-appctl revalidator/wait
> +
> +# Check that flow successfully deleted from HW.
> +OVS_WAIT_UNTIL([grep "succeed to delete netdev flow" ovs-vswitchd.log])
> +AT_CHECK([filter_hw_flow_del < ovs-vswitchd.log | strip_xout], [0], [dnl
> +p1: flow del: mark: 1
> +])
> +
> +# Check if we do not hit partial hw offload.
> +AT_CHECK([ovs-appctl dpif-netdev/pmd-perf-show \
> +          | grep -q "PHWOL hits:                  0 "])
> +
> +# Verify two packets where received.
> +AT_CHECK([[[ $(ovs-pcap p2.pcap | grep -c "$packet_hex") -eq 2 ]]])
> +
> +# Verify that we observe one miss, one packet processed by hardware,
> +# and no offload pipeline aborts.
> +AT_CHECK(
> +  [ovs-appctl --format json dpif/offload/show \
> +     | sed 's/.*"p1":{\([[^}]]*\)}.*/\1/; s/,/\n/g; s/"//g' \
> +     | sed -n '/^rx_offload_/p' | sort], [0], [dnl
> +rx_offload_full:1
> +rx_offload_miss:1
> +rx_offload_partial:0
> +rx_offload_pipe_abort:0
> +])
> +
> +# In this test, we remove an output port while the revalidator is disabled.
> +# The hardware flow remains, but the port can no longer be found, causing
> +# the offload simulation to fail egressing.  This simulates an offload
> +# pipeline abort, so the packet should fall back to normal processing.
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +AT_CHECK([ovs-appctl revalidator/pause])
> +AT_CHECK([ovs-vsctl del-port br0 p2])
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +AT_CHECK([ovs-appctl dpctl/dump-flows -m | strip_hw_offload], [0], [dnl
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth(),eth_type(0x0800),ipv4(),udp(),
>  packets:1, bytes:106, used:0.0s, offloaded:yes, dp:dummy, actions:2
> +])
> +AT_CHECK([ovs-appctl revalidator/resume])
> +AT_CHECK(
> +  [ovs-appctl --format json dpif/offload/show \
> +     | sed 's/.*"p1":{\([[^}]]*\)}.*/\1/; s/,/\n/g; s/"//g' \
> +     | sed -n '/^rx_offload_/p' | sort], [0], [dnl
> +rx_offload_full:2
> +rx_offload_miss:2
> +rx_offload_partial:0
> +rx_offload_pipe_abort:1
> +])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([dpif-netdev - check dpctl/add-flow in_port exact match])
>  OVS_VSWITCHD_START(
>    [add-port br0 p1 \
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 39e43d3768..04dcb9a8ee 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -10196,6 +10196,18 @@ Globally enabled: false
>  Datapaths:
>  dummy@ovs-dummy:
>    dummy
> +    rx_offload_partial   : 0
> +    rx_offload_full      : 0
> +    rx_offload_miss      : 0
> +    rx_offload_pipe_abort: 0
> +    rx_offload_partial   : 0
> +    rx_offload_full      : 0
> +    rx_offload_miss      : 0
> +    rx_offload_pipe_abort: 0
> +    rx_offload_partial   : 0
> +    rx_offload_full      : 0
> +    rx_offload_miss      : 0
> +    rx_offload_pipe_abort: 0
>    dummy_x
>    - br0: port_no: 100
>    - br1: port_no: 101
> @@ -10212,11 +10224,23 @@ AT_CHECK([ovs-appctl --format json --pretty 
> dpif/offload/show], [0], [dnl
>        "dummy": {
>          "ports": {
>            "br0": {
> -            "port_no": 100},
> +            "port_no": 100,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "br1": {
> -            "port_no": 101},
> +            "port_no": 101,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "ovs-dummy": {
> -            "port_no": 0}}},
> +            "port_no": 0,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0}}},
>        "dummy_x": {
>          }}},
>    "enabled": false}
> @@ -10248,6 +10272,18 @@ Globally enabled: false
>  Datapaths:
>  dummy@ovs-dummy:
>    dummy_x
> +    rx_offload_partial   : 0
> +    rx_offload_full      : 0
> +    rx_offload_miss      : 0
> +    rx_offload_pipe_abort: 0
> +    rx_offload_partial   : 0
> +    rx_offload_full      : 0
> +    rx_offload_miss      : 0
> +    rx_offload_pipe_abort: 0
> +    rx_offload_partial   : 0
> +    rx_offload_full      : 0
> +    rx_offload_miss      : 0
> +    rx_offload_pipe_abort: 0
>    dummy
>    - br0: port_no: 100
>    - br1: port_no: 101
> @@ -10266,11 +10302,23 @@ AT_CHECK([ovs-appctl --format json --pretty 
> dpif/offload/show], [0], [dnl
>        "dummy_x": {
>          "ports": {
>            "br0": {
> -            "port_no": 100},
> +            "port_no": 100,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "br1": {
> -            "port_no": 101},
> +            "port_no": 101,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "ovs-dummy": {
> -            "port_no": 0}}}}},
> +            "port_no": 0,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0}}}}},
>    "enabled": false}
>  ])
>  
> @@ -10302,17 +10350,37 @@ AT_CHECK([ovs-appctl --format json --pretty 
> dpif/offload/show], [0], [dnl
>        "dummy": {
>          "ports": {
>            "br0": {
> -            "port_no": 100},
> +            "port_no": 100,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "ovs-dummy": {
> -            "port_no": 0},
> +            "port_no": 0,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "p4": {
> -            "port_no": 4}}},
> +            "port_no": 4,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0}}},
>        "dummy_x": {
>          "ports": {
>            "p1": {
> -            "port_no": 1},
> +            "port_no": 1,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0},
>            "p3": {
> -            "port_no": 3}}}}},
> +            "port_no": 3,
> +            "rx_offload_full": 0,
> +            "rx_offload_miss": 0,
> +            "rx_offload_partial": 0,
> +            "rx_offload_pipe_abort": 0}}}}},
>    "enabled": false}
>  ])

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

Reply via email to