/* * Copyright (c) 2023-2026 NVIDIA CORPORATION AND AFFILIATES. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include "flow_switch_common.h" DOCA_LOG_REGISTER(FLOW_HASH_PIPE); #define NB_ENTRIES 2 /* number of entries in the created hash pipe */ static struct doca_flow_pipe_entry *entries[NB_ENTRIES]; /* array for storing created entries */ /* * Create DOCA Flow hash pipe on the switch port. * The hash pipe calculates the entry index based on IPv4 destination address; * the indicated fields in match_mask variable. * * @port [in]: port of the pipe * @pipe [out]: created pipe pointer * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t create_hash_pipe(struct doca_flow_port *port, struct doca_flow_pipe **pipe) { struct doca_flow_match match_mask; struct doca_flow_monitor monitor; struct doca_flow_fwd fwd; struct doca_flow_pipe_cfg *pipe_cfg; doca_error_t result; memset(&match_mask, 0, sizeof(match_mask)); memset(&monitor, 0, sizeof(monitor)); memset(&fwd, 0, sizeof(fwd)); /* match mask defines which header fields to use in order to calculate the entry index */ match_mask.outer.l3_type = DOCA_FLOW_L3_TYPE_IP4; match_mask.outer.ip4.dst_ip = 0xffffffff; monitor.counter_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED; result = doca_flow_pipe_cfg_create(&pipe_cfg, port); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); return result; } result = set_flow_pipe_cfg(pipe_cfg, "HASH_PIPE", DOCA_FLOW_PIPE_HASH, true); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_nr_entries(pipe_cfg, NB_ENTRIES); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg nr_entries: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_match(pipe_cfg, NULL, &match_mask); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg match: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_monitor(pipe_cfg, &monitor); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg monitor: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } /* FWD component is defined per entry */ fwd.type = DOCA_FLOW_FWD_PORT; fwd.port_id = 0xffff; result = doca_flow_pipe_create(pipe_cfg, &fwd, NULL, pipe); destroy_pipe_cfg: doca_flow_pipe_cfg_destroy(pipe_cfg); return result; } /* * Create DOCA Flow hash pipe on the switch port. * The hash pipe is used only by SW to manually calculate the hash * * @port [in]: port of the pipe * @pipe [out]: created pipe pointer * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t create_hash_pipe_sw(struct doca_flow_port *port, struct doca_flow_pipe **pipe) { struct doca_flow_match match_mask; struct doca_flow_monitor monitor; struct doca_flow_fwd fwd; struct doca_flow_pipe_cfg *pipe_cfg; doca_error_t result; memset(&match_mask, 0, sizeof(match_mask)); memset(&monitor, 0, sizeof(monitor)); memset(&fwd, 0, sizeof(fwd)); /* * Since we want to calculate the hash for the pipe in the HW * we must use exactly the same matching. */ match_mask.outer.l3_type = DOCA_FLOW_L3_TYPE_IP4; match_mask.outer.ip4.dst_ip = 0xffffffff; result = doca_flow_pipe_cfg_create(&pipe_cfg, port); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); return result; } result = set_flow_pipe_cfg(pipe_cfg, "HASH_PIPE", DOCA_FLOW_PIPE_HASH, false); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_nr_entries(pipe_cfg, NB_ENTRIES); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg nr_entries: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_match(pipe_cfg, NULL, &match_mask); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg match: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_monitor(pipe_cfg, &monitor); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg monitor: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } /* The action must be fixed */ fwd.type = DOCA_FLOW_FWD_PORT; fwd.port_id = 0x1; result = doca_flow_pipe_create(pipe_cfg, &fwd, NULL, pipe); destroy_pipe_cfg: doca_flow_pipe_cfg_destroy(pipe_cfg); return result; } /* * Add DOCA Flow pipe entries to the hash pipe. * First entry forwards "matched" packets to the first port representor, and the second entry forwards "matched" packets * to the second one. * * @pipe [in]: pipe of the entry * @status [in]: user context for adding entry * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t add_hash_pipe_entries(struct doca_flow_pipe *pipe, struct entries_status *status) { struct doca_flow_fwd fwd; uint32_t flags = DOCA_FLOW_ENTRY_FLAGS_WAIT_FOR_BATCH; doca_error_t result; int entry_index = 0; memset(&fwd, 0, sizeof(fwd)); for (entry_index = 0; entry_index < NB_ENTRIES; entry_index++) { /* entry index is calculated as follows: hash_func( destination IPv4 address ) mod nb_flows; the hash * output is the entry index to be used */ fwd.type = DOCA_FLOW_FWD_PORT; fwd.port_id = entry_index + 1; /* The port to forward to is defined based on the entry index */ /* last entry should be inserted with DOCA_FLOW_ENTRY_FLAGS_NO_WAIT flag */ if (entry_index == NB_ENTRIES - 1) flags = DOCA_FLOW_ENTRY_FLAGS_NO_WAIT; result = doca_flow_pipe_hash_add_entry(0, pipe, entry_index, 0, NULL, NULL, &fwd, flags, status, &entries[entry_index]); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to add hash pipe entry: %s", doca_error_get_descr(result)); return result; } } return DOCA_SUCCESS; } /* * Calculate hash for a given pipe * * @pipe [in]: pipe to be used for hash calculation * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t calc_hash(struct doca_flow_pipe *pipe) { struct doca_flow_match match; uint32_t hash; memset(&match, 0, sizeof(match)); /* match mask defines which header fields to use in order to calculate the entry index */ match.outer.l3_type = DOCA_FLOW_L3_TYPE_IP4; match.outer.ip4.dst_ip = htobe32(0xc0a80101); /* 192.168.1.1 */ doca_flow_pipe_calc_hash(pipe, &match, &hash); DOCA_LOG_INFO("Hash value for %x is %u", match.outer.ip4.dst_ip, hash); return DOCA_SUCCESS; } /* * Run flow_hash_pipe sample * * @nb_queues [in]: number of queues the sample will use * @nb_ports [in]: number of ports the sample will use * @ctx [in]: flow switch context the sample will use * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ /* Context structure for statistics printing */ struct hash_pipe_stats_context { struct doca_flow_pipe_entry **entries; }; /* * Print hash pipe statistics * * @entries [in]: array of flow entries */ static void print_hash_pipe_stats(struct doca_flow_pipe_entry *entries[]) { doca_error_t result; struct doca_flow_resource_query query_stats; int entry_idx; /* dump entries counters */ for (entry_idx = 0; entry_idx < NB_ENTRIES; entry_idx++) { result = doca_flow_resource_query_entry(entries[entry_idx], &query_stats); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to query entry: %s", doca_error_get_descr(result)); return; } DOCA_LOG_INFO("Entry in index: %d", entry_idx); DOCA_LOG_INFO("Total bytes: %ld", query_stats.counter.total_bytes); DOCA_LOG_INFO("Total packets: %ld", query_stats.counter.total_pkts); } } /* * Wrapper function for statistics printing compatible with flow_wait_for_packets * * @context [in]: hash_pipe_stats_context structure */ static void print_hash_pipe_stats_wrapper(void *context) { struct hash_pipe_stats_context *ctx = (struct hash_pipe_stats_context *)context; print_hash_pipe_stats(ctx->entries); } doca_error_t flow_hash_pipe(int nb_queues, int nb_ports, struct flow_switch_ctx *ctx) { struct flow_resources resource = {0}; uint32_t nr_shared_resources[SHARED_RESOURCE_NUM_VALUES] = {0}; struct doca_flow_port *ports[nb_ports]; uint32_t actions_mem_size[nb_ports]; struct doca_flow_pipe *hash_pipe; struct doca_flow_pipe *hash_pipe_sw; struct entries_status status; int num_of_entries = NB_ENTRIES; doca_error_t result; memset(&status, 0, sizeof(status)); resource.mode = DOCA_FLOW_RESOURCE_MODE_PORT; resource.nr_counters = NB_ENTRIES; /* counter per entry */ result = init_doca_flow(nb_queues, "switch,hws", &resource, nr_shared_resources); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to init DOCA Flow: %s", doca_error_get_descr(result)); return result; } ARRAY_INIT(actions_mem_size, ACTIONS_MEM_SIZE(num_of_entries)); result = init_doca_flow_switch_ports(ctx->devs_ctx.devs_manager, ctx->devs_ctx.nb_devs, ports, nb_ports, actions_mem_size, &resource); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to init DOCA ports: %s", doca_error_get_descr(result)); doca_flow_destroy(); return result; } result = create_hash_pipe(doca_flow_port_switch_get(NULL), &hash_pipe); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create pipe: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } result = create_hash_pipe_sw(doca_flow_port_switch_get(NULL), &hash_pipe_sw); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create pipe: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } result = add_hash_pipe_entries(hash_pipe, &status); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to add entries to hash pipe: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } calc_hash(hash_pipe_sw); result = doca_flow_pipe_queue_drain(doca_flow_port_switch_get(NULL), 0, NULL); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to process entries: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } if (status.nb_processed != num_of_entries || status.failure) { DOCA_LOG_ERR("Failed to process entries"); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return DOCA_ERROR_BAD_STATE; } /* Setup statistics context and wait for packets */ struct hash_pipe_stats_context stats_ctx = {.entries = entries}; flow_wait_for_packets(15, print_hash_pipe_stats_wrapper, &stats_ctx); result = stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; }