aboutsummaryrefslogtreecommitdiffstats
path: root/src/vnet/interface
diff options
context:
space:
mode:
Diffstat (limited to 'src/vnet/interface')
-rw-r--r--src/vnet/interface/caps.c63
-rw-r--r--src/vnet/interface/monitor.c121
-rw-r--r--src/vnet/interface/runtime.c90
-rw-r--r--src/vnet/interface/rx_queue.c21
-rw-r--r--src/vnet/interface/rx_queue_funcs.h7
-rw-r--r--src/vnet/interface/stats.c84
-rw-r--r--src/vnet/interface/tx_queue.rst159
-rw-r--r--src/vnet/interface/tx_queue_funcs.h17
8 files changed, 528 insertions, 34 deletions
diff --git a/src/vnet/interface/caps.c b/src/vnet/interface/caps.c
new file mode 100644
index 00000000000..54e8d90c471
--- /dev/null
+++ b/src/vnet/interface/caps.c
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2021 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/vnet.h>
+#include <vnet/interface.h>
+
+VLIB_REGISTER_LOG_CLASS (if_caps_log, static) = {
+ .class_name = "interface",
+ .subclass_name = "caps",
+};
+
+#define log_debug(fmt, ...) \
+ vlib_log_debug (if_caps_log.class, fmt, __VA_ARGS__)
+
+format_function_t format_vnet_hw_if_caps;
+
+void
+vnet_hw_if_change_caps (vnet_main_t *vnm, u32 hw_if_index,
+ vnet_hw_if_caps_change_t *caps)
+{
+ vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
+ vnet_hw_if_caps_t old = hi->caps;
+
+ hi->caps = (hi->caps & ~caps->mask) | caps->val;
+
+ log_debug ("change: interface %U, set: %U, cleared: %U",
+ format_vnet_hw_if_index_name, vnm, hw_if_index,
+ format_vnet_hw_if_caps, (old ^ hi->caps) & caps->val,
+ format_vnet_hw_if_caps, (old ^ hi->caps) & ~caps->val);
+}
+
+u8 *
+format_vnet_hw_if_caps (u8 *s, va_list *va)
+{
+ vnet_hw_if_caps_t caps = va_arg (*va, vnet_hw_if_caps_t);
+
+ const char *strings[sizeof (vnet_hw_if_caps_t) * 8] = {
+#define _(bit, sfx, str) [bit] = (str),
+ foreach_vnet_hw_if_caps
+#undef _
+ };
+
+ if (caps == 0)
+ return format (s, "none");
+
+ while (caps)
+ {
+ int bit = get_lowest_set_bit_index (caps);
+
+ if (strings[bit])
+ s = format (s, "%s", strings[bit]);
+ else
+ s = format (s, "unknown-%u", bit);
+
+ caps = clear_lowest_set_bit (caps);
+ if (caps)
+ vec_add1 (s, ' ');
+ }
+
+ return s;
+}
diff --git a/src/vnet/interface/monitor.c b/src/vnet/interface/monitor.c
new file mode 100644
index 00000000000..3ae1fd29156
--- /dev/null
+++ b/src/vnet/interface/monitor.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2021 Cisco and/or its affiliates.
+ * 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 <vnet/vnet.h>
+#include <vlib/vlib.h>
+
+static clib_error_t *
+monitor_interface_command_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ const vnet_main_t *vnm = vnet_get_main ();
+ const vlib_combined_counter_main_t *counters =
+ vnm->interface_main.combined_sw_if_counters;
+ f64 refresh_interval = 1.0;
+ u32 refresh_count = ~0;
+ clib_error_t *error = 0;
+ vlib_counter_t vrx[2], vtx[2];
+ f64 ts[2];
+ u32 hw_if_index = ~0;
+ u8 spin = 0;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%U", unformat_vnet_hw_interface, vnm,
+ &hw_if_index))
+ ;
+ else if (unformat (input, "interval %f", &refresh_interval))
+ ;
+ else if (unformat (input, "count %u", &refresh_count))
+ ;
+ else
+ {
+ error = clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (hw_if_index == ~0)
+ {
+ error = clib_error_return (0, "no interface passed");
+ goto done;
+ }
+
+ vlib_get_combined_counter (counters + VNET_INTERFACE_COUNTER_RX, hw_if_index,
+ &vrx[spin]);
+ vlib_get_combined_counter (counters + VNET_INTERFACE_COUNTER_TX, hw_if_index,
+ &vtx[spin]);
+ ts[spin] = vlib_time_now (vm);
+
+ while (refresh_count--)
+ {
+ f64 sleep_interval, tsd;
+
+ while (((sleep_interval =
+ ts[spin] + refresh_interval - vlib_time_now (vm)) > 0.0))
+ {
+ uword event_type, *event_data = 0;
+ vlib_process_wait_for_event_or_clock (vm, sleep_interval);
+ event_type = vlib_process_get_events (vm, &event_data);
+ switch (event_type)
+ {
+ case ~0: /* no events => timeout */
+ break;
+ default:
+ /* someone pressed a key, abort */
+ vlib_cli_output (vm, "Aborted due to a keypress.");
+ goto done;
+ }
+ vec_free (event_data);
+ }
+ spin ^= 1;
+ vlib_get_combined_counter (counters + VNET_INTERFACE_COUNTER_RX,
+ hw_if_index, &vrx[spin]);
+ vlib_get_combined_counter (counters + VNET_INTERFACE_COUNTER_TX,
+ hw_if_index, &vtx[spin]);
+ ts[spin] = vlib_time_now (vm);
+
+ tsd = ts[spin] - ts[spin ^ 1];
+ vlib_cli_output (
+ vm, "rx: %Upps %Ubps tx: %Upps %Ubps", format_base10,
+ (u64) ((vrx[spin].packets - vrx[spin ^ 1].packets) / tsd),
+ format_base10,
+ (u64) (8 * (vrx[spin].bytes - vrx[spin ^ 1].bytes) / tsd),
+ format_base10,
+ (u64) ((vtx[spin].packets - vtx[spin ^ 1].packets) / tsd),
+ format_base10,
+ (u64) (8 * (vtx[spin].bytes - vtx[spin ^ 1].bytes) / tsd));
+ }
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (monitor_interface_command, static) = {
+ .path = "monitor interface",
+ .short_help =
+ "monitor interface <interface> [interval <intv>] [count <count>]",
+ .function = monitor_interface_command_fn,
+ .is_mp_safe = 1,
+};
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/interface/runtime.c b/src/vnet/interface/runtime.c
index e63f1ecc2fc..a88a23bd4c9 100644
--- a/src/vnet/interface/runtime.c
+++ b/src/vnet/interface/runtime.c
@@ -184,39 +184,73 @@ vnet_hw_if_update_runtime_data (vnet_main_t *vnm, u32 hw_if_index)
}
}
- new_out_runtimes =
- vec_dup_aligned (hi->output_node_thread_runtimes, CLIB_CACHE_LINE_BYTES);
- vec_validate_aligned (new_out_runtimes, n_threads - 1,
- CLIB_CACHE_LINE_BYTES);
-
- if (vec_len (hi->output_node_thread_runtimes) != vec_len (new_out_runtimes))
- something_changed_on_tx = 1;
-
- for (int i = 0; i < vec_len (hi->tx_queue_indices); i++)
+ if (vec_len (hi->tx_queue_indices) > 0)
{
- u32 thread_index;
- u32 queue_index = hi->tx_queue_indices[i];
- vnet_hw_if_tx_queue_t *txq = vnet_hw_if_get_tx_queue (vnm, queue_index);
- uword n_threads = clib_bitmap_count_set_bits (txq->threads);
+ new_out_runtimes = vec_dup_aligned (hi->output_node_thread_runtimes,
+ CLIB_CACHE_LINE_BYTES);
+ vec_validate_aligned (new_out_runtimes, n_threads - 1,
+ CLIB_CACHE_LINE_BYTES);
- clib_bitmap_foreach (thread_index, txq->threads)
+ for (u32 i = 0; i < vec_len (new_out_runtimes); i++)
{
vnet_hw_if_output_node_runtime_t *rt;
- rt = vec_elt_at_index (new_out_runtimes, thread_index);
- if ((rt->frame.queue_id != txq->queue_id) ||
- (rt->n_threads != n_threads))
+ rt = vec_elt_at_index (new_out_runtimes, i);
+ u32 n_queues = 0, total_queues = vec_len (hi->tx_queue_indices);
+ rt->frame = 0;
+ rt->lookup_table = 0;
+
+ for (u32 j = 0; j < total_queues; j++)
{
+ u32 queue_index = hi->tx_queue_indices[j];
+ vnet_hw_if_tx_frame_t frame = { .shared_queue = 0,
+ .hints = 7,
+ .queue_id = ~0 };
+ vnet_hw_if_tx_queue_t *txq =
+ vnet_hw_if_get_tx_queue (vnm, queue_index);
+ if (!clib_bitmap_get (txq->threads, i))
+ continue;
+
log_debug ("tx queue data changed for interface %v, thread %u "
- "(queue_id %u -> %u, n_threads %u -> %u)",
- hi->name, thread_index, rt->frame.queue_id,
- txq->queue_id, rt->n_threads, n_threads);
+ "(queue_id %u)",
+ hi->name, i, txq->queue_id);
+ something_changed_on_tx = 1;
+
+ frame.queue_id = txq->queue_id;
+ frame.shared_queue = txq->shared_queue;
+ vec_add1 (rt->frame, frame);
+ n_queues++;
+ }
+
+ // don't initialize rt->n_queues above
+ if (rt->n_queues != n_queues)
+ {
something_changed_on_tx = 1;
- rt->frame.queue_id = txq->queue_id;
- rt->frame.shared_queue = txq->shared_queue;
- rt->n_threads = n_threads;
+ rt->n_queues = n_queues;
+ }
+ /*
+ * It is only used in case of multiple txq.
+ */
+ if (rt->n_queues > 0)
+ {
+ if (!is_pow2 (n_queues))
+ n_queues = max_pow2 (n_queues);
+
+ vec_validate_aligned (rt->lookup_table, n_queues - 1,
+ CLIB_CACHE_LINE_BYTES);
+
+ for (u32 k = 0; k < vec_len (rt->lookup_table); k++)
+ {
+ rt->lookup_table[k] = rt->frame[k % rt->n_queues].queue_id;
+ log_debug ("tx queue lookup table changed for interface %v, "
+ "(lookup table [%u]=%u)",
+ hi->name, k, rt->lookup_table[k]);
+ }
}
}
}
+ else
+ /* interface deleted */
+ something_changed_on_tx = 1;
if (something_changed_on_rx || something_changed_on_tx)
{
@@ -255,10 +289,9 @@ vnet_hw_if_update_runtime_data (vnet_main_t *vnm, u32 hw_if_index)
{
void *in = rt->rxq_interrupts;
int int_num = -1;
- while ((int_num = clib_interrupt_get_next (in, int_num)) !=
- -1)
+ while ((int_num = clib_interrupt_get_next_and_clear (
+ in, int_num)) != -1)
{
- clib_interrupt_clear (in, int_num);
pending_int = clib_bitmap_set (pending_int, int_num, 1);
last_int = clib_max (last_int, int_num);
}
@@ -303,6 +336,11 @@ vnet_hw_if_update_runtime_data (vnet_main_t *vnm, u32 hw_if_index)
{
vec_free (d[i]);
vec_free (a[i]);
+ if (new_out_runtimes)
+ {
+ vec_free (new_out_runtimes[i].frame);
+ vec_free (new_out_runtimes[i].lookup_table);
+ }
}
vec_free (d);
diff --git a/src/vnet/interface/rx_queue.c b/src/vnet/interface/rx_queue.c
index 7632b190705..b1fc82f38e9 100644
--- a/src/vnet/interface/rx_queue.c
+++ b/src/vnet/interface/rx_queue.c
@@ -124,7 +124,10 @@ vnet_hw_if_unregister_all_rx_queues (vnet_main_t *vnm, u32 hw_if_index)
vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
vnet_interface_main_t *im = &vnm->interface_main;
vnet_hw_if_rx_queue_t *rxq;
+ vlib_main_t *vm;
+ vnet_hw_if_rx_node_runtime_t *rt;
u64 key;
+ u32 queue_index;
log_debug ("unregister_all: interface %v", hi->name);
@@ -132,6 +135,15 @@ vnet_hw_if_unregister_all_rx_queues (vnet_main_t *vnm, u32 hw_if_index)
{
rxq = vnet_hw_if_get_rx_queue (vnm, hi->rx_queue_indices[i]);
key = rx_queue_key (rxq->hw_if_index, rxq->queue_id);
+ if (PREDICT_FALSE (rxq->mode == VNET_HW_IF_RX_MODE_INTERRUPT ||
+ rxq->mode == VNET_HW_IF_RX_MODE_ADAPTIVE))
+ {
+ vm = vlib_get_main_by_index (rxq->thread_index);
+ queue_index = vnet_hw_if_get_rx_queue_index_by_id (vnm, hw_if_index,
+ rxq->queue_id);
+ rt = vlib_node_get_runtime_data (vm, hi->input_node_index);
+ clib_interrupt_clear (rt->rxq_interrupts, queue_index);
+ }
hash_unset_mem_free (&im->rxq_index_by_hw_if_index_and_queue_id, &key);
pool_put_index (im->hw_if_rx_queues, hi->rx_queue_indices[i]);
@@ -228,7 +240,7 @@ vnet_hw_if_set_rx_queue_thread_index (vnet_main_t *vnm, u32 queue_index,
hi->name, rxq->queue_id, thread_index);
}
-void
+vnet_hw_if_rxq_poll_vector_t *
vnet_hw_if_generate_rxq_int_poll_vector (vlib_main_t *vm,
vlib_node_runtime_t *node)
{
@@ -240,18 +252,17 @@ vnet_hw_if_generate_rxq_int_poll_vector (vlib_main_t *vm,
vec_reset_length (rt->rxq_vector_int);
- while ((int_num = clib_interrupt_get_next (rt->rxq_interrupts, int_num)) !=
- -1)
+ while ((int_num = clib_interrupt_get_next_and_clear (rt->rxq_interrupts,
+ int_num)) != -1)
{
vnet_hw_if_rx_queue_t *rxq = vnet_hw_if_get_rx_queue (vnm, int_num);
vnet_hw_if_rxq_poll_vector_t *pv;
- clib_interrupt_clear (rt->rxq_interrupts, int_num);
-
vec_add2 (rt->rxq_vector_int, pv, 1);
pv->dev_instance = rxq->dev_instance;
pv->queue_id = rxq->queue_id;
}
+ return rt->rxq_vector_int;
}
/*
diff --git a/src/vnet/interface/rx_queue_funcs.h b/src/vnet/interface/rx_queue_funcs.h
index 83f1bac4e94..906d7118296 100644
--- a/src/vnet/interface/rx_queue_funcs.h
+++ b/src/vnet/interface/rx_queue_funcs.h
@@ -33,8 +33,9 @@ vnet_hw_if_rx_mode vnet_hw_if_get_rx_queue_mode (vnet_main_t *vnm,
u32 queue_index);
void vnet_hw_if_set_rx_queue_thread_index (vnet_main_t *vnm, u32 queue_index,
u32 thread_index);
-void vnet_hw_if_generate_rxq_int_poll_vector (vlib_main_t *vm,
- vlib_node_runtime_t *node);
+vnet_hw_if_rxq_poll_vector_t *
+vnet_hw_if_generate_rxq_int_poll_vector (vlib_main_t *vm,
+ vlib_node_runtime_t *node);
/* inline functions */
@@ -72,7 +73,7 @@ vnet_hw_if_get_rxq_poll_vector (vlib_main_t *vm, vlib_node_runtime_t *node)
vnet_hw_if_rxq_poll_vector_t *pv = rt->rxq_vector_int;
if (PREDICT_FALSE (node->state == VLIB_NODE_STATE_INTERRUPT))
- vnet_hw_if_generate_rxq_int_poll_vector (vm, node);
+ pv = vnet_hw_if_generate_rxq_int_poll_vector (vm, node);
else if (node->flags & VLIB_NODE_FLAG_ADAPTIVE_MODE)
pv = rt->rxq_vector_poll;
diff --git a/src/vnet/interface/stats.c b/src/vnet/interface/stats.c
new file mode 100644
index 00000000000..4f3213aafc3
--- /dev/null
+++ b/src/vnet/interface/stats.c
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlib/stats/stats.h>
+#include <vnet/vnet.h>
+#include <vnet/devices/devices.h> /* vnet_get_aggregate_rx_packets */
+#include <vnet/interface.h>
+
+vlib_stats_string_vector_t if_names = 0;
+static u32 **dir_entry_indices = 0;
+
+static struct
+{
+ char *prefix, *name;
+ u32 index;
+} if_counters[] = {
+#define _(e, n, p) { .prefix = #p, .name = #n },
+ foreach_simple_interface_counter_name foreach_combined_interface_counter_name
+#undef _
+};
+
+static clib_error_t *
+statseg_sw_interface_add_del (vnet_main_t *vnm, u32 sw_if_index, u32 is_add)
+{
+ u8 *name = 0;
+
+ if (if_names == 0)
+ {
+ if_names = vlib_stats_add_string_vector ("/if/names");
+
+ for (int i = 0; i < ARRAY_LEN (if_counters); i++)
+ if_counters[i].index = vlib_stats_find_entry_index (
+ "/%s/%s", if_counters[i].prefix, if_counters[i].name);
+ }
+
+ vec_validate (dir_entry_indices, sw_if_index);
+
+ vlib_stats_segment_lock ();
+
+ if (is_add)
+ {
+ vnet_sw_interface_t *si, *si_sup;
+ vnet_hw_interface_t *hi_sup;
+
+ si = vnet_get_sw_interface (vnm, sw_if_index);
+ si_sup = vnet_get_sup_sw_interface (vnm, si->sw_if_index);
+ ASSERT (si_sup->type == VNET_SW_INTERFACE_TYPE_HARDWARE);
+ hi_sup = vnet_get_hw_interface (vnm, si_sup->hw_if_index);
+
+ name = format (0, "%v", hi_sup->name);
+ if (si->type != VNET_SW_INTERFACE_TYPE_HARDWARE)
+ name = format (name, ".%d", si->sub.id);
+
+ vlib_stats_set_string_vector (&if_names, sw_if_index, "%v", name);
+
+ for (u32 index, i = 0; i < ARRAY_LEN (if_counters); i++)
+ {
+ index = vlib_stats_add_symlink (
+ if_counters[i].index, sw_if_index, "/interfaces/%U/%s",
+ format_vlib_stats_symlink, name, if_counters[i].name);
+ ASSERT (index != ~0);
+ vec_add1 (dir_entry_indices[sw_if_index], index);
+ }
+ }
+ else
+ {
+ name = format (0, "%s", "deleted");
+ vlib_stats_set_string_vector (&if_names, sw_if_index, "%v", name);
+ for (u32 i = 0; i < vec_len (dir_entry_indices[sw_if_index]); i++)
+ vlib_stats_remove_entry (dir_entry_indices[sw_if_index][i]);
+ vec_free (dir_entry_indices[sw_if_index]);
+ }
+
+ vec_free (name);
+
+ vlib_stats_segment_unlock ();
+
+ return 0;
+}
+
+VNET_SW_INTERFACE_ADD_DEL_FUNCTION (statseg_sw_interface_add_del);
diff --git a/src/vnet/interface/tx_queue.rst b/src/vnet/interface/tx_queue.rst
new file mode 100644
index 00000000000..e8f0e039b8e
--- /dev/null
+++ b/src/vnet/interface/tx_queue.rst
@@ -0,0 +1,159 @@
+.. _TX_Queue_doc:
+
+Transmit Queues
+===============
+
+Overview
+________
+
+VPP implements Transmit queues infra to access and manage them. It provides
+common registration functions to register or unregister interfaces’ transmit
+queues. It also provides functions for queues placement on given thread(s).
+
+The TXQ Infrastructure
+_______________________
+
+Infra registers each queue using a unique key which is formed by concatenating
+the hardware interface index ``hw_if_index`` and unique queue identifier for
+given interface ``queue_id``. As a result of registration of queue, infra
+returns back a unique global ``queue_index`` which can be used by driver to
+access that queue later.
+
+Interface output node uses pre-computed ``output_node_thread_runtime`` data
+which provides essential information related to queue placements on given
+thread of given interface. Transmit queue infra implements an algorithm to
+pre-compute this information. It also pre-computes scalar arguments of frame
+``vnet_hw_if_tx_frame_t``. It also pre-calculates a ``lookup_table`` for
+thread if there are multiple transmit queues are placed on that thread.
+Interface drivers call ``vnet_hw_if_update_runtime_data()`` to execute that
+algorithm after registering the transmit queues to TXQ infra.
+
+The algorithm makes the copy of existing runtime data and iterate through them
+for each vpp main and worker thread. In each iteration, algorithm loop through
+all the tx queues of given interface to fill the information in the frame data
+structure ``vnet_hw_if_tx_frame_t``. Algorithm also updates the information
+related to number of transmit queues of given interface on given vpp thread in
+data structure ``output_node_thread_runtime``. As a consequence of any update
+to the copy, triggers the function to update the actual working copy by taking
+the worker barrier and free the old copy of ``output_node_thread_runtime``.
+
+Multi-TXQ infra
+^^^^^^^^^^^^^^^
+
+Interface output node uses packet flow hash using hash infra in case of multi-txq
+on given thread. Each hardware interface class contains type of the hash required
+for interfaces from that hardware interface class i.e. ethernet interface hardware
+class contains type ``VNET_HASH_FN_TYPE_ETHERNET``. Though, the hash function
+itself is contained by hardware interface data structure of given interface. Default
+hashing function is selected upon interface creation based on priority. User can
+configure a different hash to an interface for multi-txq use case.
+
+Interface output node uses packet flow hash as an index to the pre-calculated lookup
+table to get the queue identifier for given transmit queue. Interface output node
+enqueues the packets to respective frame and also copies the ``vnet_hw_if_tx_frame_t``
+to frame scalar arguments. Drivers use scalar arguments ``vnet_hw_if_tx_frame_t``
+of the given frame to extract the information about the transmit queue to be used to
+transmit the packets. Drivers may need to acquire a lock on given queue before
+transmitting the packets based on the ``shared_queue`` bit status.
+
+Data structures
+^^^^^^^^^^^^^^^
+
+Queue information is stored in data structure ``vnet_hw_if_tx_queue_t``:
+
+.. code:: c
+
+ typedef struct
+ {
+ /* either this queue is shared among multiple threads */
+ u8 shared_queue : 1;
+ /* hw interface index */
+ u32 hw_if_index;
+
+ /* hardware queue identifier */
+ u32 queue_id;
+
+ /* bitmap of threads which use this queue */
+ clib_bitmap_t *threads;
+ } vnet_hw_if_tx_queue_t;
+
+
+Frame information is stored in data structure: ``vnet_hw_if_tx_frame_t``:
+
+.. code:: c
+
+ typedef enum
+ {
+ VNET_HW_IF_TX_FRAME_HINT_NOT_CHAINED = (1 << 0),
+ VNET_HW_IF_TX_FRAME_HINT_NO_GSO = (1 << 1),
+ VNET_HW_IF_TX_FRAME_HINT_NO_CKSUM_OFFLOAD = (1 << 2),
+ } vnet_hw_if_tx_frame_hint_t;
+
+ typedef struct
+ {
+ u8 shared_queue : 1;
+ vnet_hw_if_tx_frame_hint_t hints : 16;
+ u32 queue_id;
+ } vnet_hw_if_tx_frame_t;
+
+Output node runtime information is stored in data structure: ``output_node_thread_runtime``:
+
+.. code:: c
+
+ typedef struct
+ {
+ CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
+ vnet_hw_if_tx_frame_t *frame;
+ u32 *lookup_table;
+ u32 n_queues;
+ } vnet_hw_if_output_node_runtime_t;
+
+
+MultiTXQ API
+^^^^^^^^^^^^
+
+This API message is used to place tx queue of an interface to vpp main or worker(s) thread(s).
+
+.. code:: c
+
+ autoendian autoreply define sw_interface_set_tx_placement
+ {
+ u32 client_index;
+ u32 context;
+ vl_api_interface_index_t sw_if_index;
+ u32 queue_id;
+ u32 array_size;
+ u32 threads[array_size];
+ option vat_help = "<interface | sw_if_index <index>> queue <n> [threads <list> | mask <hex>]";
+ };
+
+Multi-TXQ CLI
+^^^^^^^^^^^^^
+
+::
+
+ set interface tx-queue set interface tx-queue <interface> queue <n> [threads <list>]
+ set interface tx-hash set interface tx-hash <interface> hash-name <hash-name>
+
+::
+
+ show hardware-interfaces
+
+ Name Idx Link Hardware
+ tap0 1 up tap0
+ Link speed: unknown
+ RX Queues:
+ queue thread mode
+ 0 main (0) polling
+ TX Queues:
+ TX Hash: [name: crc32c-5tuple priority: 50 description: IPv4/IPv6 header and TCP/UDP ports]
+ queue shared thread(s)
+ 0 no 0
+ Ethernet address 02:fe:27:69:5a:b5
+ VIRTIO interface
+ instance 0
+ RX QUEUE : Total Packets
+ 0 : 0
+ TX QUEUE : Total Packets
+ 0 : 0
+
diff --git a/src/vnet/interface/tx_queue_funcs.h b/src/vnet/interface/tx_queue_funcs.h
index 22956a4eb9b..8fcf7c336a8 100644
--- a/src/vnet/interface/tx_queue_funcs.h
+++ b/src/vnet/interface/tx_queue_funcs.h
@@ -27,3 +27,20 @@ vnet_hw_if_get_tx_queue (vnet_main_t *vnm, u32 queue_index)
return 0;
return pool_elt_at_index (im->hw_if_tx_queues, queue_index);
}
+
+static_always_inline int
+vnet_hw_if_txq_cmp_cli_api (vnet_hw_if_tx_queue_t **a,
+ vnet_hw_if_tx_queue_t **b)
+{
+ if (*a == *b)
+ return 0;
+
+ if (a[0]->hw_if_index != b[0]->hw_if_index)
+ return 2 * (a[0]->hw_if_index > b[0]->hw_if_index) - 1;
+
+ if (a[0]->queue_id != b[0]->queue_id)
+ return 2 * (a[0]->queue_id > b[0]->queue_id) - 1;
+
+ ASSERT (0);
+ return ~0;
+}