DOCA offload uses 'pipes' which are determined by their match/actions template, that the user should manage. This module adds an abstraction for that.
The user passes a set of match/actions (and some other) parameters. The module manages creation of new "group" and "mask_ctx" (which has a specific pipe) if needed (first use), keeping a those objects reference-counted. Upon deletion free the resources. A group is a set of pipes. A packet follow the rules in the set of pipes. In case of a hit, the actions of the hit rule are performed. In case of a miss, it proceeds to the next mask-ctx. If no hit at all, the packet goes to the miss-path, to SW processing. +------------------------------------------------------+ | | | | | +--->----+ Prio 0 | | | ^ +----------------------+ | | | | | | | | | | | | +---+ | | | | | | | | | | v | | Mask A v | | | Group | | +-------+---------+ | Group | | Start ^ | | Rule | | End | | Anchor | | | Rule | | Anchor | | +-------+ | | | Rule | | +-------+ | | | Empty | | | | Rule | | | Empty | | Doca | | Basic | ^ | +-------+---------+ | | Basic +----> Offloads | | Pipe | | | | | | Pipe | | Miss Path | +--+----+ | | |Miss FWD | +--+----+ | Pipe | | | | | | ^ | | | | | Mask B v | | | | +--->---+ | +-------+---------+ | | | | Miss FWD | | Rule | | | | | | | Rule | | | | | | | Rule | | | | | | | Rule | | | | | | +-------+---------+ | ^ | | | | | | | | +----------------------+ | | | | | | | +----->----------------+ | | Miss FWD | +------------------------------------------------------+ Signed-off-by: Eli Britstein <[email protected]> --- lib/automake.mk | 3 + lib/doca-pipe-group.c | 1291 +++++++++++++++++++++++++++++++ lib/doca-pipe-group.h | 80 ++ lib/dpif-offload-doca-private.h | 29 + lib/netdev-doca.c | 10 +- lib/netdev-doca.h | 8 + lib/ovs-doca.h | 2 + 7 files changed, 1422 insertions(+), 1 deletion(-) create mode 100644 lib/doca-pipe-group.c create mode 100644 lib/doca-pipe-group.h create mode 100644 lib/dpif-offload-doca-private.h diff --git a/lib/automake.mk b/lib/automake.mk index 0bc48cc72..7006839cd 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -455,6 +455,9 @@ endif if DOCA_NETDEV lib_libopenvswitch_la_SOURCES += \ + lib/doca-pipe-group.c \ + lib/doca-pipe-group.h \ + lib/dpif-offload-doca-private.h \ lib/netdev-doca.c \ lib/netdev-doca.h endif diff --git a/lib/doca-pipe-group.c b/lib/doca-pipe-group.c new file mode 100644 index 000000000..cd418e58c --- /dev/null +++ b/lib/doca-pipe-group.c @@ -0,0 +1,1291 @@ +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * 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 "doca-pipe-group.h" + +#include <sys/types.h> + +#include <doca_flow.h> + +#include "dpif.h" +#include "dpif-offload-doca-private.h" +#include "dp-packet.h" +#include "odp-util.h" +#include "ovs-doca.h" +#include "netdev-doca.h" +#include "netdev-vport.h" +#include "refmap.h" +#include "unixctl.h" +#include "util.h" + +#include "openvswitch/vlog.h" +#include "openvswitch/list.h" + +VLOG_DEFINE_THIS_MODULE(doca_pipe_group); +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(600, 600); + +/* Design of the doca pipe groups and doca pipe group masks: + * + * +-----------------+ + * | Rule outside of | + * | group X pointing| + * | to it | + * +------+----------+ + * | + * | + * | Group X + * +------------------------------------------------------+ + * | | | + * | | +--->----+ Prio 0 | + * | | ^ +----------------------+ | + * | | | | | | | + * | | | | +---+ | | + * | | | | | | | + * | v | | Mask A v | | + * | Group | | +-------+---------+ | Group | + * | Start ^ | | Rule | | End | + * | Anchor | | | Rule | | Anchor | + * | +-------+ | | | Rule | | +-------+ | + * | | Empty | | | | Rule | | | Empty | | Doca + * | | Basic | ^ | +-------+---------+ | | Basic +----> Offloads + * | | Pipe | | | | | | Pipe | | Miss Path + * | +--+----+ | | |Miss FWD | +--+----+ | Pipe + * | | | | | | ^ | + * | | | | Mask B v | | | + * | +--->---+ | +-------+---------+ | | | + * | Miss FWD | | Rule | | | | + * | | | Rule | | | | + * | | | Rule | | | | + * | | | Rule | | | | + * | | +-------+---------+ | ^ | + * | | | | | | + * | +----------------------+ | | + * | | | | + * | +----->----------------+ | + * | Miss FWD | + * +------------------------------------------------------+ */ + +struct doca_pipe_group_ctx { + struct doca_flow_pipe *start_anchor; + struct doca_flow_pipe *end_anchor; + struct ovs_list list; + struct ovs_mutex lock; + struct netdev *esw_netdev; + bool is_root; + char *name; +}; + +struct doca_pipe_group_key { + odp_port_t vport; + enum doca_pipe_group_type group_type; +}; + +struct doca_pipe_group_arg { + struct doca_pipe_group_key k; + struct doca_flow_pipe *start_anchor; + struct netdev *netdev; +}; + +struct doca_pipe_group_mask_ctx { + struct doca_pipe_group_ctx *group_ctx; + struct doca_flow_pipe *pipe; + uint32_t priority; + struct ovs_list list; + uint16_t qid; + char *match_str; + char *actions_str; + uint32_t pipe_size; + char *name; +}; + +struct doca_pipe_group_mask_key { + uint32_t priority; + char pad[4]; + struct doca_pipe_group_ctx *group_ctx; + struct ovs_doca_flow_match match_mask; + struct ovs_doca_flow_match match_value; + struct ovs_doca_flow_actions actions_mask; + struct ovs_doca_flow_actions actions_value; + struct doca_flow_action_desc desc; + struct doca_flow_monitor monitor; + struct doca_flow_fwd fwd; + uint32_t encap_type; + uint32_t encap_size; +}; + +struct doca_pipe_group_mask_arg { + struct doca_pipe_group_mask_key *key; + struct netdev *netdev; + struct ds match_ds; + struct ds actions_ds; + uint64_t allowed_queues_bitmap; +}; + +/* DOCA has three types of fields in the pipe match value, see below: */ +enum doca_field_type { + DOCA_FIELD_TYPE_IGNORED, + /**< ignored field type - not matched. */ + DOCA_FIELD_TYPE_SPECIFIC, + /**< specific field type - constant match for all pipe entries. */ + DOCA_FIELD_TYPE_CHANGEABLE, + /**< changeable field type - match determined by entry value and pipe + * match mask. */ +}; + +static void +doca_pipe_group_dump(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED); + +static enum +doca_field_type field_get_type(const void *field_ptr, uint16_t field_len) +{ + uint16_t changeable_bytes, ignored_bytes; + const uint8_t *data_array; + + data_array = (uint8_t *) field_ptr; + ignored_bytes = 0; + changeable_bytes = 0; + + for (uint16_t i = 0; i < field_len; i++) { + if (data_array[i] == 0) { + ignored_bytes++; + } else if (data_array[i] == 0xFF) { + changeable_bytes++; + } else { + return DOCA_FIELD_TYPE_SPECIFIC; + } + + if (unlikely(ignored_bytes > 0 && changeable_bytes > 0)) { + return DOCA_FIELD_TYPE_SPECIFIC; + } + } + + if (changeable_bytes > 0) { + return DOCA_FIELD_TYPE_CHANGEABLE; + } + + return DOCA_FIELD_TYPE_IGNORED; +} + +static uint32_t +get_pipe_size(struct doca_pipe_group_ctx *group_ctx OVS_UNUSED) +{ + return 1024; +} + +static doca_error_t +doca_pipe_group_create_anchor_pipe(struct netdev *netdev, char *pipe_name, + struct doca_flow_pipe *next_pipe, + struct doca_flow_pipe **anchor_pipe) +{ + struct doca_flow_fwd miss_fwd; + doca_error_t ret; + + memset(&miss_fwd, 0, sizeof miss_fwd); + if (next_pipe) { + miss_fwd.type = DOCA_FLOW_FWD_PIPE; + miss_fwd.next_pipe = next_pipe; + + } else { + miss_fwd.type = DOCA_FLOW_FWD_DROP; + } + + ret = ovs_doca_pipe_create(netdev, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &miss_fwd, 0, false, false, 0, pipe_name, + anchor_pipe); + + return ret; +} + +static const char * +group_type_str(enum doca_pipe_group_type group_type) +{ + switch (group_type) { + case DOCA_PIPE_GROUP_TYPE_NONE: + return "none"; + } + + return "ERR"; +} + +static uint32_t +doca_pipe_group_name_vport(odp_port_t vport) +{ + return vport == ODPP_NONE ? 0 : vport; +} + +static int +doca_pipe_group_ctx_init(void *ctx_, void *arg_) +{ + struct doca_pipe_group_ctx *group_ctx = ctx_; + struct doca_flow_pipe *end_anchor_next_pipe; + char pipe_name[OVS_DOCA_MAX_PIPE_NAME_LEN]; + struct doca_pipe_group_arg *arg = arg_; + struct netdev_doca *dev; + struct netdev *netdev; + int ret; + + dev = netdev_doca_cast(arg->netdev); + + group_ctx->is_root = !!arg->start_anchor; + group_ctx->esw_netdev = dev->esw_ctx->esw_netdev; + netdev = group_ctx->esw_netdev; + group_ctx->name = xasprintf("v%"PRIu32"-%s", + doca_pipe_group_name_vport(arg->k.vport), + group_type_str(arg->k.group_type)); + VLOG_DBG_RL(&rl, "%s: %p: %s", __FUNCTION__, group_ctx, group_ctx->name); + + ovs_list_init(&group_ctx->list); + ovs_mutex_init(&group_ctx->lock); + + snprintf(pipe_name, sizeof pipe_name, "%s-EA", group_ctx->name); + end_anchor_next_pipe = dev->esw_ctx->pre_miss_pipe; + + ret = doca_pipe_group_create_anchor_pipe(netdev, pipe_name, + end_anchor_next_pipe, + &group_ctx->end_anchor); + if (ret) { + VLOG_ERR("Failed to create %s: %d (%s)", pipe_name, ret, + doca_error_get_descr(ret)); + goto err_end_anchor; + } + + VLOG_DBG_RL(&rl, "%s: end_anchor=%p ->%p", pipe_name, + group_ctx->end_anchor, end_anchor_next_pipe); + + if (arg->start_anchor) { + struct doca_flow_fwd miss_fwd = { + .type = DOCA_FLOW_FWD_PIPE, + .next_pipe = group_ctx->end_anchor, + }; + + ret = doca_flow_pipe_update_miss(arg->start_anchor, &miss_fwd); + if (ret) { + VLOG_ERR("%s: Failed to update root start-anchor miss: %d (%s)", + netdev_get_name(netdev), ret, + doca_error_get_descr(ret)); + goto err_start_anchor; + } + + VLOG_DBG_RL(&rl, "miss update: root start-anchor=%p, pipe=%p ->%p", + arg->start_anchor, end_anchor_next_pipe, + miss_fwd.next_pipe); + group_ctx->start_anchor = arg->start_anchor; + } else { + snprintf(pipe_name, sizeof pipe_name, "%s-SA", group_ctx->name); + ret = doca_pipe_group_create_anchor_pipe(netdev, pipe_name, + group_ctx->end_anchor, + &group_ctx->start_anchor); + if (ret) { + VLOG_ERR("Failed to create %s: %d (%s)", pipe_name, ret, + doca_error_get_descr(ret)); + goto err_start_anchor; + } + } + + VLOG_DBG_RL(&rl, "%s: start_anchor=%p ->%p", pipe_name, + group_ctx->start_anchor, group_ctx->end_anchor); + + return 0; + +err_start_anchor: + doca_flow_pipe_destroy(group_ctx->end_anchor); +err_end_anchor: + free(group_ctx->name); + return ret; +} + +static void +doca_pipe_group_ctx_uninit(void *ctx_) +{ + struct doca_pipe_group_ctx *group_ctx = ctx_; + doca_error_t ret; + + VLOG_DBG_RL(&rl, "%s: %p: %s", __FUNCTION__, group_ctx, group_ctx->name); + + ovs_mutex_destroy(&group_ctx->lock); + if (group_ctx->is_root) { + struct netdev_doca *dev = netdev_doca_cast(group_ctx->esw_netdev); + struct doca_flow_fwd miss_fwd = { + .type = DOCA_FLOW_FWD_PIPE, + .next_pipe = dev->esw_ctx->pre_miss_pipe, + }; + + ret = doca_flow_pipe_update_miss(group_ctx->start_anchor, &miss_fwd); + if (ret) { + VLOG_ERR("%s: Failed to update root start-anchor miss: %d (%s)", + netdev_get_name(group_ctx->esw_netdev), ret, + doca_error_get_descr(ret)); + } + } else { + doca_flow_pipe_destroy(group_ctx->start_anchor); + } + + doca_flow_pipe_destroy(group_ctx->end_anchor); + free(group_ctx->name); +} + +static struct ds * +doca_pipe_group_ctx_dump(struct ds *s, void *key_ OVS_UNUSED, void *ctx) +{ + struct doca_pipe_group_ctx *group_ctx = ctx; + + ds_put_format(s, "%s", group_ctx->name); + + return s; +} + +struct doca_pipe_group_ctx * +doca_pipe_group_ctx_ref(struct netdev *netdev, + odp_port_t vport, + enum doca_pipe_group_type group_type) +{ + struct netdev_doca *dev = netdev_doca_cast(netdev); + struct doca_offload_esw_data *offload_data = + netdev_doca_get_esw_offload_data(netdev); + struct doca_pipe_group_key key = { + .vport = vport, + .group_type = group_type, + }; + struct doca_pipe_group_arg arg = { + .k = key, + .netdev = netdev, + }; + + if (vport == ODPP_NONE && group_type == DOCA_PIPE_GROUP_TYPE_NONE) { + arg.start_anchor = dev->esw_ctx->root_pipe; + } + + return refmap_ref(offload_data->group_rfm, &key, &arg); +} + +void +doca_pipe_group_ctx_unref(struct doca_pipe_group_ctx *group_ctx) +{ + struct doca_offload_esw_data *offload_data; + + if (!group_ctx) { + return; + } + + offload_data = netdev_doca_get_esw_offload_data(group_ctx->esw_netdev); + refmap_unref(offload_data->group_rfm, group_ctx); +} + +struct doca_flow_pipe * +doca_pipe_group_get_pipe(struct doca_pipe_group_ctx *group_ctx) +{ + return group_ctx->start_anchor; +} + +static void +doca_pipe_group_put_match_field_to_ds(struct ds *ds, char *prefix, char *field, + unsigned char *value, + unsigned char *mask, + size_t size) +{ + enum doca_field_type type = field_get_type(value, size); + + if (type == DOCA_FIELD_TYPE_IGNORED) { + return; + } + + ds_put_format(ds, "%s.%s[%d,%s]=", prefix, field, (int) size, + type == DOCA_FIELD_TYPE_CHANGEABLE + ? "changeable" + : "specific"); + + ds_put_cstr(ds, "0x"); + for (size_t i = 0; i < size; i++) { + ds_put_format(ds, "%02x", value[i]); + } + + ds_put_format(ds, "/"); + + ds_put_cstr(ds, "0x"); + for (size_t i = 0; i < size; i++) { + ds_put_format(ds, "%02x", mask[i]); + } + + ds_put_format(ds, ", "); +} + +#define log_field(field) \ + doca_pipe_group_put_match_field_to_ds(ds, prefix, #field, \ + (unsigned char *) &value->field, \ + (unsigned char *) &used->field, \ + sizeof(value->field)) + +/* Verify a field is either ignored, or specifically matched, and set it in + * used match as to mark that it was handled (so we can assert that no new + * fields were missed). */ +#define verify_specific_field(field) \ + do { \ + size_t __size = sizeof value->field; \ + enum doca_field_type __type = field_get_type(&value->field, __size); \ + char *__used = \ + ((char *) used) + \ + (size_t) (((char *) &value->field) - \ + ((char *) value)); \ + \ + /* Must be ignored or specific */ \ + ovs_assert(__type != DOCA_FIELD_TYPE_CHANGEABLE); \ + log_field(field); \ + memset(__used, 0, __size); \ + } while (0) + +/* Same as above, but class id is special as it's always specific even if its + * all 0xFF.. */ +#define verify_specific_field_class_id(field) \ + do { \ + size_t __size = sizeof value->field; \ + char *__used = \ + ((char *) used) + \ + (size_t) (((char *) &value->field) - \ + ((char *) value)); \ + \ + log_field(field); \ + memset(__used, 0, __size); \ + } while (0) + +/* Same as above, just with a pointer to field instead of a field name. */ +#define mark_specific_field_ptr(field_ptr) \ + do { \ + size_t __size = sizeof value->field; \ + enum doca_field_type __type = field_get_type(&value->field, __size); \ + char *__used = \ + ((char *) used) + \ + (size_t) (((char *) &value->field) - \ + ((char *) value)); \ + \ + /* Must be ignored or specific */ \ + ovs_assert(__type != DOCA_FIELD_TYPE_CHANGEABLE); \ + log_field(field); \ + memset(__used, 0, __size); \ + } while (0) + +/* Extend specifically matched fields masks so they will be considered + * CHANGEABLE by doca and set it in used match as to mark that it was handled. + * (so we can assert that no new fields were missed). */ +#define extend_field_to_changeable(field) \ + do { \ + size_t __size = sizeof value->field; \ + enum doca_field_type __type = field_get_type(&mask->field, __size); \ + char *__used = \ + ((char *) used) + \ + (size_t) (((char *) &value->field) - \ + ((char *) value)); \ + \ + /* If field isn't ignored, set field as changeable. */ \ + if (__type != DOCA_FIELD_TYPE_IGNORED) { \ + memset(&value->field, 0xFF, __size); \ + } \ + log_field(field); \ + memset(__used, 0, __size); \ + } while (0) + +/* The following functions take in a match mask (or action mask), and from + * it create a pipe match (value) where fields that can be CHANGEABLE will be + * CHANGEABLE by extending the pipe match value to the full field size. + * They also verify we didn't miss and new fields that might have been added. + */ +static void +mask_to_changeable_value_header_format(struct ds *ds, char *prefix, + struct doca_flow_header_format *value, + struct doca_flow_header_format *mask, + struct doca_flow_header_format *used, + enum doca_flow_tun_type encap_tun_type) +{ + /* Doca enforces this field to be specific for tunnel encap. */ + if (encap_tun_type) { + mask->eth.type = 0; + verify_specific_field(eth.type); + } else { + extend_field_to_changeable(eth.type); + } + + extend_field_to_changeable(eth.dst_mac); + extend_field_to_changeable(eth.src_mac); + + verify_specific_field(l2_valid_headers); + if (value->l2_valid_headers & DOCA_FLOW_L2_VALID_HEADER_VLAN_0) { + extend_field_to_changeable(eth_vlan[0]); + } + + verify_specific_field(l3_type); + if (value->l3_type == DOCA_FLOW_L3_TYPE_IP4) { + extend_field_to_changeable(ip4.src_ip); + extend_field_to_changeable(ip4.dst_ip); + extend_field_to_changeable(ip4.dscp_ecn); + extend_field_to_changeable(ip4.next_proto); + extend_field_to_changeable(ip4.ttl); + } else if (value->l3_type == DOCA_FLOW_L3_TYPE_IP6) { + extend_field_to_changeable(ip6.src_ip); + extend_field_to_changeable(ip6.dst_ip); + extend_field_to_changeable(ip6.traffic_class); + extend_field_to_changeable(ip6.next_proto); + extend_field_to_changeable(ip6.hop_limit); + } + + verify_specific_field(l4_type_ext); + switch (value->l4_type_ext) { + case DOCA_FLOW_L4_TYPE_EXT_TCP: + extend_field_to_changeable(tcp.l4_port.src_port); + extend_field_to_changeable(tcp.l4_port.dst_port); + extend_field_to_changeable(tcp.flags); + break; + case DOCA_FLOW_L4_TYPE_EXT_UDP: + extend_field_to_changeable(udp.l4_port.src_port); + if (encap_tun_type == DOCA_FLOW_TUN_GENEVE) { + /* Doca enforces specific geneve dst port. */ + mask->udp.l4_port.dst_port = 0; + verify_specific_field(udp.l4_port.dst_port); + } else { + extend_field_to_changeable(udp.l4_port.dst_port); + } + + break; + case DOCA_FLOW_L4_TYPE_EXT_ICMP: + case DOCA_FLOW_L4_TYPE_EXT_ICMP6: + extend_field_to_changeable(icmp.type); + extend_field_to_changeable(icmp.code); + break; + + case DOCA_FLOW_L4_TYPE_EXT_TRANSPORT: + case DOCA_FLOW_L4_TYPE_EXT_ROCE_V2: + case DOCA_FLOW_L4_TYPE_EXT_NONE: + break; + } +} + +static void +mask_to_changeable_value_tun(struct ds *ds, char *prefix, + struct doca_flow_tun *value, + struct doca_flow_tun *mask, + struct doca_flow_tun *used) +{ + union doca_flow_geneve_option *opt; + + verify_specific_field(type); + switch (value->type) { + case DOCA_FLOW_TUN_NONE: + case DOCA_FLOW_TUN_MAX: + case DOCA_FLOW_TUN_GTPU: + case DOCA_FLOW_TUN_ESP: + case DOCA_FLOW_TUN_MPLS_O_UDP: + case DOCA_FLOW_TUN_PSP: + case DOCA_FLOW_TUN_IP_IN_IP: + break; + case DOCA_FLOW_TUN_VXLAN: + extend_field_to_changeable(vxlan_tun_id); + verify_specific_field(vxlan_type); + if (value->vxlan_type == DOCA_FLOW_TUN_EXT_VXLAN_GBP) { + extend_field_to_changeable(vxlan_gbp_group_policy_id); + } + + break; + case DOCA_FLOW_TUN_GRE: + verify_specific_field(key_present); + if (value->key_present) { + extend_field_to_changeable(gre_key); + } + + extend_field_to_changeable(protocol); + break; + case DOCA_FLOW_TUN_GENEVE: + extend_field_to_changeable(geneve.vni); + extend_field_to_changeable(geneve.next_proto); + extend_field_to_changeable(geneve.o_c); + verify_specific_field(geneve.ver_opt_len); + + for (int opt_idx = 0; + opt_idx < DOCA_FLOW_GENEVE_OPT_LEN_MAX; + opt_idx++) { + opt = &value->geneve_options[opt_idx]; + uint8_t len = opt->length; + uint8_t i; + + if (!len || !opt->type) { + break; + } + + verify_specific_field(geneve_options[opt_idx].length); + verify_specific_field(geneve_options[opt_idx].type); + verify_specific_field_class_id(geneve_options[opt_idx].class_id); + + for (i = 0; i < len; i++) { + opt_idx++; + if (opt_idx >= DOCA_FLOW_GENEVE_OPT_LEN_MAX) { + break; + } + + extend_field_to_changeable(geneve_options[opt_idx].data); + } + } + + break; + } +} + +static void +mask_to_changeable_value_parser_meta(struct ds *ds, char *prefix, + struct doca_flow_parser_meta *value, + struct doca_flow_parser_meta *mask, + struct doca_flow_parser_meta *used) +{ + extend_field_to_changeable(port_id); + extend_field_to_changeable(meter_color); + extend_field_to_changeable(random); + extend_field_to_changeable(outer_ip_fragmented); + extend_field_to_changeable(inner_ip_fragmented); + verify_specific_field(outer_l2_type); + verify_specific_field(inner_l2_type); + verify_specific_field(outer_l3_type); + verify_specific_field(inner_l3_type); + verify_specific_field(outer_l4_type); + verify_specific_field(inner_l4_type); +} + +static void +mask_to_changeable_value_meta(struct ds *ds, char *prefix, + struct doca_flow_meta *value, + struct doca_flow_meta *mask, + struct doca_flow_meta *used) +{ + for (int i = 0; i < DOCA_FLOW_META_SCRATCH_PAD_MAX; i++) { + extend_field_to_changeable(u32[i]); + } + + extend_field_to_changeable(pkt_meta); +} + +static void +mask_to_changeable_value_mark(struct ds *ds, char *prefix, + struct ovs_doca_flow_actions *value, + struct ovs_doca_flow_actions *mask, + struct ovs_doca_flow_actions *used) +{ + extend_field_to_changeable(mark); +} + +static void +action_mask_to_changeable_value(struct ovs_doca_flow_actions *ovalue, + struct ovs_doca_flow_actions *omask, + struct ds *ds) +{ + struct doca_flow_actions *value = &ovalue->d, *mask = &omask->d; + struct ovs_doca_flow_actions u; + struct doca_flow_actions *used; + char *prefix = "actions"; + + memcpy(&u, omask, sizeof u); + used = &u.d; + + verify_specific_field(pop_vlan); + + mask_to_changeable_value_meta(ds, "actions.meta", + &value->meta, &mask->meta, &used->meta); + mask_to_changeable_value_mark(ds, "actions.mark", ovalue, omask, &u); + mask_to_changeable_value_parser_meta(ds, "actions.parser_meta", + &value->parser_meta, + &mask->parser_meta, + &used->parser_meta); + mask_to_changeable_value_header_format(ds, "actions.outer", + &value->outer, &mask->outer, + &used->outer, DOCA_FLOW_TUN_NONE); + mask_to_changeable_value_tun(ds, "actions.tun", + &value->tun, &mask->tun, &used->tun); + + verify_specific_field(encap_type); + if (value->encap_type) { + if (value->encap_type == DOCA_FLOW_RESOURCE_TYPE_SHARED) { + extend_field_to_changeable(shared_encap_id); + value->shared_encap_id = mask->shared_encap_id; + } else { + verify_specific_field(encap_cfg.is_l2); + mask_to_changeable_value_header_format( + ds, "actions.encap.outer", + &value->encap_cfg.encap.outer, + &mask->encap_cfg.encap.outer, + &used->encap_cfg.encap.outer, + value->encap_cfg.encap.tun.type); + mask_to_changeable_value_tun(ds, "actions.encap.tun", + &value->encap_cfg.encap.tun, + &mask->encap_cfg.encap.tun, + &used->encap_cfg.encap.tun); + } + } + + verify_specific_field(decap_type); + verify_specific_field(decap_cfg.is_l2); + + verify_specific_field(has_push); + if (value->has_push) { + verify_specific_field(push.type); + if (value->push.type == DOCA_FLOW_PUSH_ACTION_VLAN) { + extend_field_to_changeable(push.vlan.vlan_hdr.tci); + extend_field_to_changeable(push.vlan.eth_type); + } + } + + ovs_assert(is_all_zeros(used, sizeof *used)); +} + +static void +match_mask_to_changeable_value(struct ovs_doca_flow_match *ovalue, + struct ovs_doca_flow_match *omask, + struct ds *ds) +{ + struct doca_flow_match *value = &ovalue->d, *mask = &omask->d; + struct ovs_doca_flow_match u; + struct doca_flow_match *used; + + memcpy(&u, omask, sizeof u); + used = &u.d; + + mask_to_changeable_value_meta(ds, "match.meta", + &value->meta, &mask->meta, &used->meta); + mask_to_changeable_value_parser_meta(ds, "match.parser_meta", + &value->parser_meta, + &mask->parser_meta, + &used->parser_meta); + mask_to_changeable_value_tun(ds, "match.tun", + &value->tun, &mask->tun, &used->tun); + mask_to_changeable_value_header_format(ds, "match.inner", + &value->inner, &mask->inner, + &used->inner, DOCA_FLOW_TUN_NONE); + mask_to_changeable_value_header_format(ds, "match.outer", + &value->outer, &mask->outer, + &used->outer, DOCA_FLOW_TUN_NONE); + + ovs_assert(is_all_zeros(&u, sizeof u)); +} + +static void +fwd_to_changeable_value(struct doca_flow_fwd *value, + const struct doca_flow_fwd *fwd) +{ + /* Try and make forward value changeable per entry. */ + switch (fwd->type) { + case DOCA_FLOW_FWD_PIPE: + value->type = fwd->type; + value->next_pipe = 0; + break; + case DOCA_FLOW_FWD_PORT: + memset(value, 0xFF, sizeof(*value)); + value->type = fwd->type; + break; + case DOCA_FLOW_FWD_NONE: + case DOCA_FLOW_FWD_DROP: + case DOCA_FLOW_FWD_RSS: + case DOCA_FLOW_FWD_TARGET: + case DOCA_FLOW_FWD_CHANGEABLE: + case DOCA_FLOW_FWD_ORDERED_LIST_PIPE: + case DOCA_FLOW_FWD_TRIM: + /* Use specific action */ + *value = *fwd; + break; + case DOCA_FLOW_FWD_HASH_PIPE: + OVS_NOT_REACHED(); + break; + } +} + +static int +doca_pipe_group_mask_ctx_init(void *ctx_, void *arg_) +{ + struct doca_pipe_group_mask_ctx *mask_ctx = ctx_, *next, *prev; + struct doca_pipe_group_mask_arg *arg = arg_; + struct doca_flow_pipe *prev_pipe; + struct doca_flow_fwd miss_fwd; + struct netdev *netdev; + int ret; + + netdev = arg->key->group_ctx->esw_netdev; + + mask_ctx->priority = arg->key->priority; + mask_ctx->group_ctx = arg->key->group_ctx; + mask_ctx->name = xasprintf("%s-p%d", mask_ctx->group_ctx->name, + mask_ctx->priority); + VLOG_DBG_RL(&rl, "%s: %p: group_ctx=%p, %s", __FUNCTION__, mask_ctx, + mask_ctx->group_ctx, mask_ctx->name); + + prev = NULL; + ovs_mutex_lock(&mask_ctx->group_ctx->lock); + LIST_FOR_EACH (next, list, &mask_ctx->group_ctx->list) { + if (mask_ctx->priority < next->priority) { + break; + } + + prev = next; + continue; + } + + ovs_mutex_unlock(&mask_ctx->group_ctx->lock); + + prev_pipe = prev ? prev->pipe : mask_ctx->group_ctx->start_anchor; + + mask_ctx->pipe_size = get_pipe_size(mask_ctx->group_ctx); + memset(&miss_fwd, 0, sizeof miss_fwd); + miss_fwd.type = DOCA_FLOW_FWD_PIPE; + miss_fwd.next_pipe = next ? next->pipe : mask_ctx->group_ctx->end_anchor; + + ret = ovs_doca_pipe_create(netdev, &arg->key->match_value, + &arg->key->match_value, &arg->key->monitor, + &arg->key->actions_value, + &arg->key->actions_mask, &arg->key->desc, + &arg->key->fwd, &miss_fwd, mask_ctx->pipe_size, + false, false, arg->allowed_queues_bitmap, + mask_ctx->name, &mask_ctx->pipe); + if (ret) { + if (ret == DOCA_ERROR_TOO_BIG) { + VLOG_DBG_RL(&rl, "%s: Failed to create group mask prio %d " + "main pipe: Match too big", + netdev_get_name(netdev), mask_ctx->priority); + } else { + VLOG_ERR("%s: Failed to create group mask prio %d main pipe: %d " + "(%s)", netdev_get_name(netdev), mask_ctx->priority, + ret, doca_error_get_descr(ret)); + } + + goto err_pipe_create_main; + } + + VLOG_DBG_RL(&rl, "%s: mask_ctx=%p, pipe=%p ->%p", mask_ctx->name, + mask_ctx, mask_ctx->pipe, miss_fwd.next_pipe); + + memset(&miss_fwd, 0, sizeof miss_fwd); + miss_fwd.type = DOCA_FLOW_FWD_PIPE; + miss_fwd.next_pipe = mask_ctx->pipe; + + ret = doca_flow_pipe_update_miss(prev_pipe, &miss_fwd); + VLOG_DBG_RL(&rl, "miss update: mask_ctx=%p, pipe=%p ->%p", prev, prev_pipe, + miss_fwd.next_pipe); + if (ret) { + VLOG_ERR("%s: Failed to update group mask miss: %d (%s)", + netdev_get_name(netdev), ret, doca_error_get_descr(ret)); + goto err_miss_update_prev; + } + + ovs_list_insert(next ? &next->list : &mask_ctx->group_ctx->list, + &mask_ctx->list); + + mask_ctx->match_str = ds_steal_cstr(&arg->match_ds); + mask_ctx->actions_str = ds_steal_cstr(&arg->actions_ds); + + return 0; + +err_miss_update_prev: + doca_flow_pipe_destroy(mask_ctx->pipe); +err_pipe_create_main: + free(mask_ctx->name); + return ret; +} + +static void +doca_pipe_group_mask_ctx_uninit(void *ctx_) +{ + struct doca_pipe_group_mask_ctx *mask_ctx = ctx_, *prev, *next; + struct doca_flow_pipe *prev_pipe; + struct doca_flow_fwd miss_fwd; + int ret; + + VLOG_DBG_RL(&rl, "%s: %p: group_ctx=%p, %s", __FUNCTION__, mask_ctx, + mask_ctx->group_ctx, mask_ctx->name); + + ovs_mutex_lock(&mask_ctx->group_ctx->lock); + next = mask_ctx->list.next == &mask_ctx->group_ctx->list + ? NULL + : CONTAINER_OF(mask_ctx->list.next, + struct doca_pipe_group_mask_ctx, list); + prev = mask_ctx->list.prev == &mask_ctx->group_ctx->list + ? NULL + : CONTAINER_OF(mask_ctx->list.prev, + struct doca_pipe_group_mask_ctx, list); + prev_pipe = prev ? prev->pipe : mask_ctx->group_ctx->start_anchor; + ovs_list_remove(&mask_ctx->list); + ovs_mutex_unlock(&mask_ctx->group_ctx->lock); + + memset(&miss_fwd, 0, sizeof miss_fwd); + miss_fwd.type = DOCA_FLOW_FWD_PIPE; + miss_fwd.next_pipe = next ? next->pipe : mask_ctx->group_ctx->end_anchor; + + ret = doca_flow_pipe_update_miss(prev_pipe, &miss_fwd); + VLOG_DBG_RL(&rl, "miss update: mask_ctx=%p, pipe=%p, ->%p", prev, + prev_pipe, miss_fwd.next_pipe); + ovs_assert(!ret); + + doca_flow_pipe_destroy(mask_ctx->pipe); + + free(mask_ctx->match_str); + free(mask_ctx->actions_str); + free(mask_ctx->name); +} + +static const char * +doca_pipe_fwd_type_str(enum doca_flow_fwd_type type) +{ + switch (type) { + case DOCA_FLOW_FWD_NONE: + return "none"; + case DOCA_FLOW_FWD_RSS: + return "rss"; + case DOCA_FLOW_FWD_PORT: + return "port"; + case DOCA_FLOW_FWD_PIPE: + return "pipe"; + case DOCA_FLOW_FWD_DROP: + return "drop"; + case DOCA_FLOW_FWD_TARGET: + return "target"; + case DOCA_FLOW_FWD_ORDERED_LIST_PIPE: + return "ordered_list_pipe"; + case DOCA_FLOW_FWD_CHANGEABLE: + return "changeable"; + case DOCA_FLOW_FWD_HASH_PIPE: + return "fwd_hash_pipe"; + case DOCA_FLOW_FWD_TRIM: + return "trim"; + } + + return "ERR"; +} + +static struct ds * +doca_pipe_group_mask_ctx_dump(struct ds *s, void *key_, void *ctx) +{ + struct doca_pipe_group_mask_ctx *mask_ctx = ctx; + struct doca_pipe_group_mask_key *key = key_; + struct doca_offload_esw_data *offload_data; + struct refmap *group_mask_rfm; + + if (!mask_ctx) { + ds_put_cstr(s, "mask_ctx creation failed"); + return s; + } + + offload_data = + netdev_doca_get_esw_offload_data(mask_ctx->group_ctx->esw_netdev); + group_mask_rfm = offload_data->group_mask_rfm; + ds_put_format(s, "%s: fwd.type=%s, nr_entries=%u/%u, ", mask_ctx->name, + doca_pipe_fwd_type_str(key->fwd.type), + refmap_value_refcount_read(group_mask_rfm, mask_ctx), + mask_ctx->pipe_size); + ds_put_format(s, "%s, %s", mask_ctx->match_str, mask_ctx->actions_str); + + return s; +} + +static struct doca_pipe_group_mask_ctx * +doca_pipe_group_mask_ctx_ref(struct netdev *netdev, + struct doca_pipe_group_ctx *group_ctx, + const struct ovs_doca_flow_match *match, + struct ovs_doca_flow_match *match_mask, + struct ovs_doca_flow_actions *actions, + struct ovs_doca_flow_actions *actions_mask, + const struct doca_flow_action_desc *desc, + const struct doca_flow_monitor *monitor, + const struct doca_flow_fwd *fwd, + uint32_t priority, bool *too_big OVS_UNUSED, + uint64_t allowed_queues_bitmap) +{ + struct doca_offload_esw_data *offload_data = + netdev_doca_get_esw_offload_data(netdev); + struct doca_pipe_group_mask_ctx *mask_ctx; + struct doca_pipe_group_mask_key key = { + .group_ctx = group_ctx, + .priority = priority, + .encap_type = actions ? actions->encap_type : 0, + .encap_size = actions ? actions->encap_size : 0, + }; + struct doca_pipe_group_mask_arg arg = { + .netdev = netdev, + .key = &key, + .match_ds = DS_EMPTY_INITIALIZER, + .actions_ds = DS_EMPTY_INITIALIZER, + .allowed_queues_bitmap = allowed_queues_bitmap, + }; + + /* Make fields which don't support changeable to specific. */ + if (actions_mask && actions->d.tun.type == DOCA_FLOW_TUN_GRE) { + actions_mask->d.tun.key_present = actions->d.tun.key_present; + } + + if (match && match_mask) { + memcpy(&key.match_value, match, sizeof *match); + memcpy(&key.match_mask, match_mask, sizeof *match_mask); + match_mask_to_changeable_value(&key.match_value, &key.match_mask, + &arg.match_ds); + } + + if (is_all_zeros(&key.match_value, sizeof key.match_value)) { + ds_put_format(&arg.match_ds, "empty_match"); + } else { + ds_chomp(&arg.match_ds, ' '); + ds_chomp(&arg.match_ds, ','); + } + + if (actions_mask) { + memcpy(&key.actions_value, actions, sizeof *actions); + memcpy(&key.actions_mask, actions_mask, sizeof *actions_mask); + action_mask_to_changeable_value(&key.actions_value, &key.actions_mask, + &arg.actions_ds); + } + + if (is_all_zeros(&key.actions_value, sizeof key.actions_value)) { + ds_put_format(&arg.actions_ds, "empty_actions_mask"); + } else { + ds_chomp(&arg.actions_ds, ' '); + ds_chomp(&arg.actions_ds, ','); + } + + fwd_to_changeable_value(&key.fwd, fwd); + + if (monitor) { + memcpy(&key.monitor, monitor, sizeof *monitor); + + /* Change a shared counter/monitor to be specified per entry to + * reuse such pipe. */ + if (monitor->meter_type == DOCA_FLOW_RESOURCE_TYPE_SHARED) { + key.monitor.shared_meter.shared_meter_id = UINT32_MAX; + } + + if (monitor->counter_type == DOCA_FLOW_RESOURCE_TYPE_SHARED) { + key.monitor.shared_counter.shared_counter_id = UINT32_MAX; + } + } + + if (desc) { + memcpy(&key.desc, desc, sizeof *desc); + } + + mask_ctx = refmap_ref(offload_data->group_mask_rfm, &key, &arg); + + ds_destroy(&arg.match_ds); + ds_destroy(&arg.actions_ds); + return mask_ctx; +} + +static void +doca_pipe_group_mask_ctx_unref(struct doca_pipe_group_mask_ctx *mask_ctx) +{ + struct doca_offload_esw_data *offload_data; + + if (!mask_ctx) { + return; + } + + offload_data = + netdev_doca_get_esw_offload_data(mask_ctx->group_ctx->esw_netdev); + refmap_unref(offload_data->group_mask_rfm, mask_ctx); +} + +doca_error_t +doca_pipe_group_add_deh(struct netdev *netdev, + uint16_t qid, + odp_port_t vport, + enum doca_pipe_group_type group_type, + uint32_t priority, + const struct ovs_doca_flow_match *match, + struct ovs_doca_flow_match *match_mask, + struct ovs_doca_flow_actions *actions, + struct ovs_doca_flow_actions *actions_mask, + const struct doca_flow_action_descs *action_descs, + struct doca_flow_monitor *monitor, + const struct doca_flow_fwd *fwd, + struct doca_entry_handle *deh) + OVS_EXCLUDED(mgmt_queue_lock) +{ + struct doca_pipe_group_ctx *group_ctx; + doca_error_t ret = 0; + + memset(deh, 0, sizeof *deh); + + group_ctx = doca_pipe_group_ctx_ref(netdev, vport, group_type); + if (!group_ctx) { + return DOCA_ERROR_INVALID_VALUE; + } + + deh->mctx = + doca_pipe_group_mask_ctx_ref(netdev, group_ctx, match, match_mask, + actions, actions_mask, + action_descs ? + action_descs->desc_array : NULL, + monitor, fwd, priority, NULL, + UINT64_C(1) << qid); + if (!deh->mctx) { + ret = DOCA_ERROR_INVALID_VALUE; + goto err_mctx; + } + + ret = ovs_doca_add_entry(netdev, qid, deh->mctx->pipe, match, + actions, monitor, fwd, + DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, &deh->entry); + if (ret) { + goto err_entry; + } + + return 0; + +err_entry: + doca_pipe_group_mask_ctx_unref(deh->mctx); + deh->mctx = NULL; +err_mctx: + doca_pipe_group_ctx_unref(group_ctx); + return ret; +} + +doca_error_t +doca_pipe_group_del_deh(struct netdev *netdev, + uint16_t qid, + struct doca_entry_handle *deh) +{ + struct netdev_doca *dev = netdev_doca_cast(netdev); + struct netdev_doca_esw_ctx *esw = dev->esw_ctx; + doca_error_t ret; + + ret = ovs_doca_remove_entry(esw, qid, + DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, &deh->entry); + if (ret) { + return ret; + } + + doca_pipe_group_mask_ctx_unref(deh->mctx); + if (deh->mctx) { + doca_pipe_group_ctx_unref(deh->mctx->group_ctx); + } + + deh->mctx = NULL; + + return 0; +} + +static void +doca_pipe_group_ctx_dump_masks(struct doca_pipe_group_ctx *group_ctx, + struct doca_offload_esw_data *offload_data, + struct ds *out) +{ + struct doca_pipe_group_mask_ctx *mask_ctx; + + ovs_mutex_lock(&group_ctx->lock); + LIST_FOR_EACH (mask_ctx, list, &group_ctx->list) { + void *value; + + value = refmap_key_from_value(offload_data->group_mask_rfm, mask_ctx); + doca_pipe_group_mask_ctx_dump(out, value, mask_ctx); + ds_put_format(out, "\n"); + } + + ovs_mutex_unlock(&group_ctx->lock); +} + +static void +doca_pipe_group_dump_netdev(struct netdev *netdev, struct ds *out) +{ + struct doca_offload_esw_data *offload_data = + netdev_doca_get_esw_offload_data(netdev); + void *key, *value; + + REFMAP_FOR_EACH (value, key, offload_data->group_rfm) { + struct doca_pipe_group_ctx *group_ctx = value; + struct doca_pipe_group_key *group_key = key; + + doca_pipe_group_ctx_dump(out, group_key, group_ctx); + ds_put_format(out, "\n"); + doca_pipe_group_ctx_dump_masks(group_ctx, offload_data, out); + } +} + +static void +doca_pipe_group_dump(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) +{ + struct ds out = DS_EMPTY_INITIALIZER; + struct dpif_port_dump dump; + struct dpif_port dpif_port; + struct netdev *netdev; + struct dpif *dpif; + int rv; + + rv = dpif_open("ovs-netdev", "netdev", &dpif); + if (rv) { + unixctl_command_reply_error(conn, "Cannot open netdev datapath"); + return; + } + + DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) { + if (strcmp(dpif_port.type, "doca")) { + continue; + } + + rv = netdev_open(dpif_port.name, dpif_port.type, &netdev); + if (rv) { + continue; + } + + if (netdev_doca_is_esw_mgr(netdev)) { + doca_pipe_group_dump_netdev(netdev, &out); + } + + netdev_close(netdev); + } + + dpif_close(dpif); + unixctl_command_reply(conn, ds_cstr(&out)); + ds_destroy(&out); +} + +void +doca_pipe_group_init(struct netdev *netdev) +{ + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; + struct doca_offload_esw_data *offload_data = + netdev_doca_get_esw_offload_data(netdev); + + offload_data->group_rfm = + refmap_create("doca-group", sizeof(struct doca_pipe_group_key), + sizeof(struct doca_pipe_group_ctx), + doca_pipe_group_ctx_init, + doca_pipe_group_ctx_uninit, + doca_pipe_group_ctx_dump); + offload_data->group_mask_rfm = + refmap_create("doca-group-pipe-mask", + sizeof(struct doca_pipe_group_mask_key), + sizeof(struct doca_pipe_group_mask_ctx), + doca_pipe_group_mask_ctx_init, + doca_pipe_group_mask_ctx_uninit, + doca_pipe_group_mask_ctx_dump); + if (ovsthread_once_start(&once)) { + unixctl_command_register("doca/pipe-group-dump", "", 0, 0, + doca_pipe_group_dump, NULL); + ovsthread_once_done(&once); + } +} + +void +doca_pipe_group_uninit(struct netdev *netdev) +{ + struct doca_offload_esw_data *offload_data = + netdev_doca_get_esw_offload_data(netdev); + + refmap_destroy(offload_data->group_mask_rfm); + refmap_destroy(offload_data->group_rfm); + + offload_data->group_mask_rfm = NULL; + offload_data->group_rfm = NULL; +} diff --git a/lib/doca-pipe-group.h b/lib/doca-pipe-group.h new file mode 100644 index 000000000..a5fb11580 --- /dev/null +++ b/lib/doca-pipe-group.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * 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 DOCA_PIPE_GROUP_H +#define DOCA_PIPE_GROUP_H + +#include <doca_flow.h> + +#include "cmap.h" +#include "netdev-doca.h" +#include "ovs-doca.h" + +#include "openvswitch/list.h" +#include "openvswitch/thread.h" + +struct doca_pipe_group_ctx; +struct doca_pipe_group_mask_ctx; + +enum doca_pipe_group_type { + DOCA_PIPE_GROUP_TYPE_NONE, +}; + +struct doca_entry_handle { + struct doca_flow_pipe_entry *entry; + struct doca_pipe_group_mask_ctx *mctx; +}; + +struct doca_pipe_group_ctx * +doca_pipe_group_ctx_ref(struct netdev *netdev, + odp_port_t vport, + enum doca_pipe_group_type group_type); + +void +doca_pipe_group_ctx_unref(struct doca_pipe_group_ctx *group_ctx); + +struct doca_flow_pipe * +doca_pipe_group_get_pipe(struct doca_pipe_group_ctx *group_ctx); + +doca_error_t +doca_pipe_group_add_deh(struct netdev *netdev, + uint16_t qid, + odp_port_t vport, + enum doca_pipe_group_type group_type, + uint32_t priority, + const struct ovs_doca_flow_match *match, + struct ovs_doca_flow_match *match_mask, + struct ovs_doca_flow_actions *actions, + struct ovs_doca_flow_actions *actions_mask, + const struct doca_flow_action_descs *action_descs, + struct doca_flow_monitor *monitor, + const struct doca_flow_fwd *fwd, + struct doca_entry_handle *deh) + OVS_EXCLUDED(mgmt_queue_lock); + +doca_error_t +doca_pipe_group_del_deh(struct netdev *netdev, + uint16_t qid, + struct doca_entry_handle *deh) + OVS_EXCLUDED(mgmt_queue_lock); + +void +doca_pipe_group_init(struct netdev *netdev); + +void +doca_pipe_group_uninit(struct netdev *netdev); + +#endif /* DOCA_PIPE_GROUP_H */ diff --git a/lib/dpif-offload-doca-private.h b/lib/dpif-offload-doca-private.h new file mode 100644 index 000000000..94907f298 --- /dev/null +++ b/lib/dpif-offload-doca-private.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * 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 DPIF_OFFLOAD_DOCA_PRIVATE_H +#define DPIF_OFFLOAD_DOCA_PRIVATE_H + +#include <config.h> + +#include "refmap.h" + +struct doca_offload_esw_data { + struct refmap *group_rfm; + struct refmap *group_mask_rfm; +}; + +#endif /* DPIF_OFFLOAD_DOCA_PRIVATE_H */ diff --git a/lib/netdev-doca.c b/lib/netdev-doca.c index 7035f3c63..6ff85338a 100644 --- a/lib/netdev-doca.c +++ b/lib/netdev-doca.c @@ -123,7 +123,7 @@ netdev_doca_get_port_id(const struct netdev *netdev) return dev->common.port_id; } -static bool +bool netdev_doca_is_esw_mgr(const struct netdev *netdev) { dpdk_port_t esw_mgr_id = netdev_doca_get_esw_mgr_port_id(netdev); @@ -2723,6 +2723,14 @@ netdev_doca_send(struct netdev *netdev, int qid, return 0; } +struct doca_offload_esw_data * +netdev_doca_get_esw_offload_data(struct netdev *netdev) +{ + struct netdev_doca *dev = netdev_doca_cast(netdev); + + return dev->esw_ctx ? &dev->esw_ctx->offload_data : NULL; +} + #define NETDEV_DOCA_CLASS_COMMON \ .is_pmd = true, \ .alloc = netdev_doca_alloc, \ diff --git a/lib/netdev-doca.h b/lib/netdev-doca.h index e99bfbbaf..0b16d7717 100644 --- a/lib/netdev-doca.h +++ b/lib/netdev-doca.h @@ -23,6 +23,7 @@ #include <doca_flow.h> +#include "dpif-offload-doca-private.h" #include "netdev-dpdk-common.h" #include "netdev-provider.h" #include "ovs-doca.h" @@ -90,6 +91,7 @@ struct netdev_doca_esw_ctx { struct doca_dev *dev; uint32_t op_state; int cmd_fd; + struct doca_offload_esw_data offload_data; }; struct netdev_doca { @@ -113,4 +115,10 @@ void netdev_doca_register(void); struct netdev_doca *netdev_doca_cast(const struct netdev *netdev); +struct doca_offload_esw_data * +netdev_doca_get_esw_offload_data(struct netdev *netdev); + +bool +netdev_doca_is_esw_mgr(const struct netdev *netdev); + #endif /* NETDEV_DOCA_H */ diff --git a/lib/ovs-doca.h b/lib/ovs-doca.h index 3ec31e60b..2dc2653c2 100644 --- a/lib/ovs-doca.h +++ b/lib/ovs-doca.h @@ -46,6 +46,8 @@ struct ovs_doca_steering_queue { struct ovs_doca_flow_actions { struct doca_flow_actions d; ovs_be32 mark; + uint32_t encap_type; + uint32_t encap_size; }; BUILD_ASSERT_DECL(offsetof(struct ovs_doca_flow_actions, d) == 0); -- 2.43.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
