Exercise the alt_actions parameter added to the netdev_hw_post_process() callback. When a flow contains only OUTPUT actions with a mix of dummy-pmd and non-dummy-pmd egress ports, the dummy driver executes the dummy-pmd outputs in the simulated hardware pipeline and returns the non-dummy-pmd outputs as alternative actions for the PMD thread. A new test case validates the new API.
Signed-off-by: Eelco Chaudron <[email protected]> --- lib/dpif-offload-dummy.c | 133 ++++++++++++++++++++++++++++++++------- tests/dpif-netdev.at | 103 +++++++++++++++++++++++++++++- tests/ofproto-dpif.at | 65 ++++++++++++------- 3 files changed, 255 insertions(+), 46 deletions(-) diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c index 022c48ede..309158fbd 100644 --- a/lib/dpif-offload-dummy.c +++ b/lib/dpif-offload-dummy.c @@ -45,6 +45,7 @@ struct dummy_offloaded_flow { struct match match; const struct nlattr *actions; size_t actions_len; + struct offload_actions *alt_actions; ovs_u128 ufid; uint32_t mark; struct dpif_flow_stats stats; @@ -74,6 +75,7 @@ struct dummy_offload_port { 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. */ + uint64_t rx_offload_alt_actions OVS_GUARDED; /* Partial action offload. */ }; struct hw_pkt_node { @@ -246,6 +248,7 @@ dummy_free_flow(struct dummy_offload_port *port, hmap_destroy(&off_flow->pmd_id_map); free(CONST_CAST(struct nlattr *, off_flow->actions)); + ovsrcu_postpone(free, off_flow->alt_actions); free(off_flow); } @@ -489,6 +492,8 @@ dummy_offload_get_debug(const struct dpif_offload *offload, struct ds *ds, json_integer_create(port->rx_offload_miss)); json_object_put(json_port, "rx_offload_pipe_abort", json_integer_create(port->rx_offload_pipe_abort)); + json_object_put(json_port, "rx_offload_alt_actions", + json_integer_create(port->rx_offload_alt_actions)); ovs_mutex_unlock(&port->port_mutex); json_object_put(json_ports, netdev_get_name(port_->netdev), @@ -509,13 +514,15 @@ dummy_offload_get_debug(const struct dpif_offload *offload, struct ds *ds, 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", + " rx_offload_partial : %" PRIu64 "\n" + " rx_offload_full : %" PRIu64 "\n" + " rx_offload_miss : %" PRIu64 "\n" + " rx_offload_pipe_abort : %" PRIu64 "\n" + " rx_offload_alt_actions: %" 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); + port->rx_offload_miss, port->rx_offload_pipe_abort, + port->rx_offload_alt_actions); ovs_mutex_unlock(&port->port_mutex); } } @@ -589,10 +596,10 @@ dummy_offload_hw_post_process(const struct dpif_offload *offload_, void *flow_reference = NULL; uint32_t flow_mark; + *alt_actions = NULL; port = dummy_offload_get_port_by_netdev(offload_, netdev); if (!port || !dp_packet_has_flow_mark(packet, &flow_mark)) { *flow_reference_ = NULL; - *alt_actions = NULL; return 0; } @@ -605,13 +612,15 @@ dummy_offload_hw_post_process(const struct dpif_offload *offload_, if (pmd_data) { flow_reference = pmd_data->flow_reference; } + if (off_flow->alt_actions) { + *alt_actions = off_flow->alt_actions; + } break; } } ovs_mutex_unlock(&port->port_mutex); *flow_reference_ = flow_reference; - *alt_actions = NULL; return 0; } @@ -724,22 +733,23 @@ dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_, return true; } -static bool +static int dummy_offload_hw_process_pkt(const struct dpif_offload *offload_, struct dummy_offloaded_flow *flow, struct dp_packet *pkt) { + bool partial_act = flow->alt_actions != NULL; 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; + return -ENOENT; } NL_ATTR_FOR_EACH (nla, left, flow->actions, flow->actions_len) { - bool last_action = (left <= NLA_ALIGN(nla->nla_len)); + bool last_action = !partial_act && (left <= NLA_ALIGN(nla->nla_len)); enum ovs_action_attr action = nl_attr_type(nla); switch (action) { @@ -751,7 +761,14 @@ dummy_offload_hw_process_pkt(const struct dpif_offload *offload_, port = dummy_offload_get_port_by_odp_port(offload_, odp_port); if (!port) { - return false; + return -ENODEV; + } + + /* Skip non-dummy-pmd ports here, as they are handled via the + * alternative actions returned by the hw_post_process callback. */ + if (partial_act && strcmp("dummy-pmd", + netdev_get_type(port->pm_port.netdev))) { + break; } n_txq = netdev_n_txq(port->pm_port.netdev); @@ -817,7 +834,60 @@ dummy_offload_hw_process_pkt(const struct dpif_offload *offload_, flow->stats.n_bytes += pkt_size; flow->stats.n_packets++; flow->stats.used = time_msec(); - return true; + + /* For mixed offload, return -EAGAIN so the caller re-injects the packet + * to the CPU for alt_actions processing. */ + return partial_act ? -EAGAIN : 0; +} + +/* Build a set of alternative actions to exercise the alt_actions support in + * the netdev_hw_post_process() API. The dummy implementation returns + * alternative actions only when the action list consists solely of + * OVS_ACTION_ATTR_OUTPUT actions, and at least one egress port is not of + * type dummy-pmd. In that case, the dummy-pmd outputs are handled in + * 'hardware', while the non-dummy-pmd outputs are returned as alternative + * actions to be executed by the PMD thread. */ +static struct offload_actions * +dummy_build_alt_actions(const struct dpif_offload *offload_, + odp_port_t in_odp, + const struct nlattr *actions, + size_t actions_len) +{ + struct offload_actions *alt_actions = NULL; + const struct nlattr *nla; + struct ofpbuf buf; + size_t left; + + ofpbuf_init(&buf, 0); + + NL_ATTR_FOR_EACH (nla, left, actions, actions_len) { + enum ovs_action_attr action = nl_attr_type(nla); + struct dummy_offload_port *out_port; + odp_port_t out_odp; + + if (action != OVS_ACTION_ATTR_OUTPUT) { + goto out; + } + + out_odp = nl_attr_get_odp_port(nla); + 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)))) { + ofpbuf_put(&buf, nla, NLA_ALIGN(nla->nla_len)); + } + } + + if (buf.size) { + alt_actions = xmalloc(sizeof *alt_actions + buf.size); + alt_actions->size = buf.size; + memcpy(alt_actions->actions, buf.data, buf.size); + } + +out: + ofpbuf_uninit(&buf); + return alt_actions; } static int @@ -864,12 +934,24 @@ dummy_flow_put(const struct dpif_offload *offload_, struct netdev *netdev, } memcpy(&off_flow->match, put->match, sizeof *put->match); free(CONST_CAST(struct nlattr *, off_flow->actions)); + ovsrcu_postpone(free, off_flow->alt_actions); + off_flow->alt_actions = NULL; 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; + off_flow->alt_actions = dummy_build_alt_actions( + offload_, put->match->flow.in_port.odp_port, + put->actions, put->actions_len); + if (off_flow->alt_actions) { + /* Partial action offload to test the alt_actions API. */ + off_flow->actions = xmemdup(put->actions, put->actions_len); + off_flow->actions_len = put->actions_len; + } else { + /* Classic partial offload, i.e., only flow matching. */ + off_flow->actions = NULL; + off_flow->actions_len = 0; + } } /* As we have per-netdev 'offloaded_flows', we don't need to match @@ -1028,6 +1110,7 @@ dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet *packet, struct dummy_offload_port *port; bool packet_stolen = false; struct flow packet_flow; + uint32_t existing_mark; bool offloaded = false; if (!dpif_offload_enabled() || !offload @@ -1040,6 +1123,12 @@ dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet *packet, return false; } + /* If the packet already has a flow mark, it was re-injected, so let it + * continue to the CPU so hw_post_process can return alt_actions. */ + if (dp_packet_has_flow_mark(packet, &existing_mark)) { + return false; + } + if (!flow) { flow = &packet_flow; flow_extract(packet, flow); @@ -1072,9 +1161,6 @@ dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet *packet, } 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; @@ -1124,7 +1210,7 @@ dummy_netdev_hw_offload_run(struct netdev *netdev) 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; + int err = -ENOENT; struct flow flow; flow_extract(pkt, &flow); @@ -1132,13 +1218,18 @@ dummy_netdev_hw_offload_run(struct netdev *netdev) if (flow_equal_except(&flow, &offloaded_flow->match.flow, &offloaded_flow->match.wc)) { - processed = dummy_offload_hw_process_pkt( - offload, offloaded_flow, pkt); + err = dummy_offload_hw_process_pkt(offload, offloaded_flow, + pkt); break; } } - if (!processed) { + if (err == -EAGAIN) { + /* We failed full processing as we have alt_actions. */ + port->rx_offload_alt_actions++; + netdev_dummy_queue_simulate_offload_packet( + port->pm_port.netdev, pkt, pkt_node->queue_id); + } else if (err) { VLOG_DBG("Failed HW pipeline, sent to sw!"); port->rx_offload_pipe_abort++; netdev_dummy_queue_simulate_offload_packet( diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at index 14f238e62..b14cc6d27 100644 --- a/tests/dpif-netdev.at +++ b/tests/dpif-netdev.at @@ -728,7 +728,7 @@ p1: flow del: mark: 1 AT_CHECK([ovs-appctl dpif-netdev/pmd-perf-show \ | grep -q "PHWOL hits: 0 "]) -# Verify two packets where received. +# Verify two packets were received. AT_CHECK([[[ $(ovs-pcap p2.pcap | grep -c "$packet_hex") -eq 2 ]]]) # Verify that we observe one miss, one packet processed by hardware, @@ -737,6 +737,7 @@ 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_alt_actions:0 rx_offload_full:1 rx_offload_miss:1 rx_offload_partial:0 @@ -759,6 +760,7 @@ 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_alt_actions:0 rx_offload_full:2 rx_offload_miss:2 rx_offload_partial:0 @@ -768,6 +770,104 @@ rx_offload_pipe_abort:1 OVS_VSWITCHD_STOP AT_CLEANUP +AT_SETUP([dpif-netdev - full hw offload - alt actions - dummy-pmd]) +OVS_VSWITCHD_START( + [add-port br0 p1 -- \ + add-port br0 p2 -- \ + set interface br0 options:tx_pcap=br0.pcap -- \ + 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]) + +# Install a flow that outputs to both a dummy-pmd port (p2, offloadable +# in hardware) and the bridge's internal LOCAL port (br0, dummy-internal, +# not offloadable). Hardware handles the match; the dummy-pmd output is +# treated as the HW-side action, while the LOCAL output is returned as +# alt_actions for the CPU to execute. +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flow br0 in_port=1,actions=p2,LOCAL]) + +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 successful 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:2, bytes:212, used:0.0s, offloaded:yes, dp:dummy, actions:p2,br0 +]) + +# 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 hit partial hw offload. +AT_CHECK([ovs-appctl dpif-netdev/pmd-perf-show \ + | grep -q "PHWOL hits: 1 "]) + +# Verify two packets were received. +AT_CHECK([[[ $(ovs-pcap p2.pcap | grep -c "$packet_hex") -eq 2 ]]]) + +# Verify we received the packet on br0. +AT_CHECK([[[ $(ovs-pcap br0.pcap | grep -c "$packet_hex") -eq 2 ]]]) + +# Verify offload statistics: one miss (first packet), one full (second +# packet matched in HW), and one alt_actions return from hw_post_process. +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_alt_actions:1 +rx_offload_full:1 +rx_offload_miss:1 +rx_offload_partial:0 +rx_offload_pipe_abort:0 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + AT_SETUP([dpif-netdev - full hw offload - tunnel entropy - dummy-pmd]) OVS_VSWITCHD_START( [add-br br1 -- set bridge br1 datapath-type=dummy -- \ @@ -838,6 +938,7 @@ 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_alt_actions:0 rx_offload_full:1 rx_offload_miss:1 rx_offload_partial:0 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index efb36e058..2cba6c81d 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -10485,18 +10485,21 @@ 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 + rx_offload_partial : 0 + rx_offload_full : 0 + rx_offload_miss : 0 + rx_offload_pipe_abort : 0 + rx_offload_alt_actions: 0 + rx_offload_partial : 0 + rx_offload_full : 0 + rx_offload_miss : 0 + rx_offload_pipe_abort : 0 + rx_offload_alt_actions: 0 + rx_offload_partial : 0 + rx_offload_full : 0 + rx_offload_miss : 0 + rx_offload_pipe_abort : 0 + rx_offload_alt_actions: 0 dummy_x - br0: port_no: 100 - br1: port_no: 101 @@ -10514,18 +10517,21 @@ AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl "ports": { "br0": { "port_no": 100, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "br1": { "port_no": 101, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "ovs-dummy": { "port_no": 0, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, @@ -10561,18 +10567,21 @@ 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 + rx_offload_partial : 0 + rx_offload_full : 0 + rx_offload_miss : 0 + rx_offload_pipe_abort : 0 + rx_offload_alt_actions: 0 + rx_offload_partial : 0 + rx_offload_full : 0 + rx_offload_miss : 0 + rx_offload_pipe_abort : 0 + rx_offload_alt_actions: 0 + rx_offload_partial : 0 + rx_offload_full : 0 + rx_offload_miss : 0 + rx_offload_pipe_abort : 0 + rx_offload_alt_actions: 0 dummy - br0: port_no: 100 - br1: port_no: 101 @@ -10592,18 +10601,21 @@ AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl "ports": { "br0": { "port_no": 100, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "br1": { "port_no": 101, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "ovs-dummy": { "port_no": 0, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, @@ -10640,18 +10652,21 @@ AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl "ports": { "br0": { "port_no": 100, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "ovs-dummy": { "port_no": 0, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "p4": { "port_no": 4, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, @@ -10660,12 +10675,14 @@ AT_CHECK([ovs-appctl --format json --pretty dpif/offload/show], [0], [dnl "ports": { "p1": { "port_no": 1, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, "rx_offload_pipe_abort": 0}, "p3": { "port_no": 3, + "rx_offload_alt_actions": 0, "rx_offload_full": 0, "rx_offload_miss": 0, "rx_offload_partial": 0, -- 2.55.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
