From: Stephen Hemminger <stephen@networkplumber.org>
To: Shani Peretz <shperetz@nvidia.com>
Cc: <dev@dpdk.org>, Aman Singh <aman.deep.singh@intel.com>,
Dariusz Sosnowski <dsosnowski@nvidia.com>,
Viacheslav Ovsiienko <viacheslavo@nvidia.com>,
Bing Zhao <bingz@nvidia.com>, Ori Kam <orika@nvidia.com>,
Suanming Mou <suanmingm@nvidia.com>,
Matan Azrad <matan@nvidia.com>,
Thomas Monjalon <thomas@monjalon.net>,
Ferruh Yigit <ferruh.yigit@amd.com>,
Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>,
Anatoly Burakov <anatoly.burakov@intel.com>
Subject: Re: [RFC PATCH] ethdev: add new API for enable/disable xstat counters by ID
Date: Thu, 9 Jan 2025 20:36:30 -0800 [thread overview]
Message-ID: <20250109203630.13322c14@hermes.local> (raw)
In-Reply-To: <20241222153819.62519-1-shperetz@nvidia.com>
On Sun, 22 Dec 2024 17:38:19 +0200
Shani Peretz <shperetz@nvidia.com> wrote:
> This change introduces a new API to dynamically enable or disable
> xstat counters by their IDs. Some counters may require hardware
> resources that are potentially limited, so providing the ability
> to toggle them on or off makes sense.
> In addition, adding an API to query the current state
> (enabled, disabled, or not supported) of an xstat counter is added.
> New APIs:
> - rte_eth_xstats_enable_counter(struct rte_eth_dev *dev, uint64_t id);
> - rte_eth_xstats_disable_counter(struct rte_eth_dev *dev, uint64_t id);
> - rte_eth_xstats_query_state(struct rte_eth_dev *dev, uint64_t id);
>
> Added the following testpmd subcommands:
> toggle the counter on and off
> > port (port_id) enable|disable <counter_name>
> query the state of counters:
> > set xstats-show-state on|off
>
> Note that by default, generic stats (like those provided by
> eth_basic_stats_get()) will be considered unsupported and
> will not be toggleable.
> Also all xstats will be considered unsupported for dynamic enable/disable,
> and each PMD will be able to override this in its implementation.
>
> Specifically in mlx5 PMD, expose a new xstat to track packet drops of
> hairpin Rx queue:
> - Hairpin_out_of_buffer - Port-level counter -
> counts drops from all the hairpin queues
> - Hairpin_out_of_buffer_rxq* - Queue-level counter -
> counts drops of a specific queue
> Both the port-level and per-queue counters can be
> enabled, disabled, and queried.
>
> Signed-off-by: Shani Peretz <shperetz@nvidia.com>
> ---
> app/test-pmd/cmdline.c | 92 ++++++++
> app/test-pmd/config.c | 89 ++++++++
> app/test-pmd/testpmd.c | 5 +
> app/test-pmd/testpmd.h | 3 +
> drivers/common/mlx5/mlx5_devx_cmds.c | 8 +-
> drivers/common/mlx5/mlx5_devx_cmds.h | 2 +-
> drivers/common/mlx5/mlx5_prm.h | 3 +
> drivers/net/mlx5/linux/mlx5_ethdev_os.c | 5 +
> drivers/net/mlx5/linux/mlx5_os.c | 21 +-
> drivers/net/mlx5/mlx5.c | 292 +++++++++++++++++++++++-
> drivers/net/mlx5/mlx5.h | 29 ++-
> drivers/net/mlx5/mlx5_devx.c | 75 ++----
> drivers/net/mlx5/mlx5_rx.h | 20 ++
> drivers/net/mlx5/mlx5_rxq.c | 36 +++
> drivers/net/mlx5/mlx5_stats.c | 292 +++++++++++++++++++++++-
> drivers/net/mlx5/windows/mlx5_os.c | 3 +-
> lib/ethdev/ethdev_driver.h | 13 ++
> lib/ethdev/rte_ethdev.c | 66 ++++++
> lib/ethdev/rte_ethdev.h | 44 ++++
> lib/ethdev/version.map | 3 +
> 20 files changed, 1010 insertions(+), 91 deletions(-)
Please resubmit as three patches since there maybe bugfixes
to only one.
1. Ethdev changes
2. Testpmd changes
3. Mlx5
>
> diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
> index 7e0666e9f6..76b68b74ae 100644
> --- a/app/test-pmd/cmdline.c
> +++ b/app/test-pmd/cmdline.c
> @@ -7960,6 +7960,96 @@ static cmdline_parse_inst_t cmd_set_xstats_hide_zero = {
> },
> };
>
> +/* *** SET OPTION TO DISPLAY XSTAT STATE *** */
> +struct cmd_set_xstats_show_state_result {
> + cmdline_fixed_string_t keyword;
> + cmdline_fixed_string_t name;
> + cmdline_fixed_string_t on_off;
> +};
> +
> +static void
> +cmd_set_xstats_show_state_parsed(void *parsed_result,
> + __rte_unused struct cmdline *cl,
> + __rte_unused void *data)
> +{
> + struct cmd_set_xstats_show_state_result *res;
> + uint16_t on_off = 0;
Initialization not needed, you are setting it two lines later.
> + res = parsed_result;
> + on_off = !strcmp(res->on_off, "on") ? 1 : 0;
> + set_xstats_show_state(on_off);
> +}
I wonder if on/off bool should be handled generically like etheraddr ipaddr num string etc.
If you are copy/paste the same code multiple times, indicates to me it should be an object.
> +static cmdline_parse_token_string_t cmd_set_xstats_show_state_keyword =
> + TOKEN_STRING_INITIALIZER(struct cmd_set_xstats_show_state_result,
> + keyword, "set");
> +static cmdline_parse_token_string_t cmd_set_xstats_show_state_name =
> + TOKEN_STRING_INITIALIZER(struct cmd_set_xstats_show_state_result,
> + name, "xstats-show-state");
> +static cmdline_parse_token_string_t cmd_set_xstats_show_state_on_off =
> + TOKEN_STRING_INITIALIZER(struct cmd_set_xstats_show_state_result,
> + on_off, "on#off");
> +
> +static cmdline_parse_inst_t cmd_set_xstats_show_state = {
> + .f = cmd_set_xstats_show_state_parsed,
> + .data = NULL,
> + .help_str = "set xstats-show-state on|off",
> + .tokens = {
> + (void *)&cmd_set_xstats_show_state_keyword,
> + (void *)&cmd_set_xstats_show_state_name,
> + (void *)&cmd_set_xstats_show_state_on_off,
> + NULL,
> + },
> +};
> +
> +/* *** enable/disable counter by name *** */
> +struct cmd_operate_set_counter_result {
> + cmdline_fixed_string_t port;
> + portid_t port_id;
> + cmdline_fixed_string_t what;
> + cmdline_multi_string_t counter_name;
> +};
> +
> +static void
> +cmd_operate_set_counter_parsed(void *parsed_result,
> + __rte_unused struct cmdline *cl,
> + __rte_unused void *data)
> +{
> + struct cmd_operate_set_counter_result *res = parsed_result;
> + uint16_t on_off = 0;
ditto
> + on_off = strcmp(res->what, "enable") ? 0 : 1;
> +
> + if ((strcmp(res->port, "port") == 0))
> + nic_xstats_set_counter(res->port_id, res->counter_name, on_off);
> +}
> +
> +static cmdline_parse_token_string_t cmd_operate_set_counter_port =
> + TOKEN_STRING_INITIALIZER(struct cmd_operate_set_counter_result,
> + port, "port");
> +static cmdline_parse_token_num_t cmd_operate_set_counter_port_id =
> + TOKEN_NUM_INITIALIZER(struct cmd_operate_set_counter_result,
> + port_id, RTE_UINT16);
> +static cmdline_parse_token_string_t cmd_operate_set_counter_what =
> + TOKEN_STRING_INITIALIZER(struct cmd_operate_set_counter_result,
> + what, "enable#disable");
> +static cmdline_parse_token_string_t cmd_operate_set_counter_name =
> + TOKEN_STRING_INITIALIZER(struct cmd_operate_set_counter_result,
> + counter_name, TOKEN_STRING_MULTI);
> +
> +static cmdline_parse_inst_t cmd_operate_set_counter = {
> + .f = cmd_operate_set_counter_parsed,
> + .data = NULL,
> + .help_str = "port (port_id) enable|disable <counter_name>",
> + .tokens = {
> + (void *)&cmd_operate_set_counter_port,
> + (void *)&cmd_operate_set_counter_port_id,
> + (void *)&cmd_operate_set_counter_what,
> + (void *)&cmd_operate_set_counter_name,
> + NULL,
> + },
> +};
> +
> /* *** SET OPTION TO ENABLE MEASUREMENT OF CPU CYCLES *** */
> struct cmd_set_record_core_cycles_result {
> cmdline_fixed_string_t keyword;
> @@ -13648,6 +13738,8 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
> &cmd_set_fwd_eth_peer,
> &cmd_set_qmap,
> &cmd_set_xstats_hide_zero,
> + &cmd_set_xstats_show_state,
> + &cmd_operate_set_counter,
> &cmd_set_record_core_cycles,
> &cmd_set_record_burst_stats,
> &cmd_operate_port,
> diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
> index 4e7fb69183..a0a5736857 100644
> --- a/app/test-pmd/config.c
> +++ b/app/test-pmd/config.c
> @@ -393,6 +393,7 @@ nic_xstats_display(portid_t port_id)
> struct rte_eth_xstat *xstats;
> int cnt_xstats, idx_xstat;
> struct rte_eth_xstat_name *xstats_names;
> + int state;
>
> if (port_id_is_invalid(port_id, ENABLED_WARN)) {
> print_valid_ports();
> @@ -442,6 +443,12 @@ nic_xstats_display(portid_t port_id)
> for (idx_xstat = 0; idx_xstat < cnt_xstats; idx_xstat++) {
> if (xstats_hide_zero && !xstats[idx_xstat].value)
> continue;
> + if (xstats_show_state) {
> + char opt[3] = {'D', 'E', '-'};
> + state = rte_eth_xstats_query_state(port_id, idx_xstat);
> + printf("state: %c ", state < 0 ? opt[2] : opt[state]);
> + }
Since there is hide_zero, why not hide_disabled?
> printf("%s: %"PRIu64"\n",
> xstats_names[idx_xstat].name,
> xstats[idx_xstat].value);
> @@ -6437,6 +6444,82 @@ rx_vlan_strip_set_on_queue(portid_t port_id, uint16_t queue_id, int on)
> __func__, port_id, queue_id, on, diag);
> }
>
> +void
> +nic_xstats_set_counter(portid_t port_id, char *counter_name, int on)
> +{
> + struct rte_eth_xstat_name *xstats_names;
> + int cnt_xstats;
> + int ret;
> + uint64_t id;
> +
> + if (port_id_is_invalid(port_id, ENABLED_WARN)) {
> + print_valid_ports();
> + return;
> + }
> + if (!rte_eth_dev_is_valid_port(port_id)) {
> + fprintf(stderr, "Error: Invalid port number %i\n", port_id);
> + return;
> + }
> +
> + if (counter_name == NULL) {
> + fprintf(stderr, "Error: Invalid counter name\n");
> + return;
> + }
> +
> + /* Get count */
> + cnt_xstats = rte_eth_xstats_get_names(port_id, NULL, 0);
> + if (cnt_xstats < 0) {
> + fprintf(stderr, "Error: Cannot get count of xstats\n");
> + return;
> + }
> +
> + /* Get id-name lookup table */
> + xstats_names = malloc(sizeof(struct rte_eth_xstat_name) * cnt_xstats);
> + if (xstats_names == NULL) {
> + fprintf(stderr, "Cannot allocate memory for xstats lookup\n");
> + return;
> + }
> + if (cnt_xstats != rte_eth_xstats_get_names(
> + port_id, xstats_names, cnt_xstats)) {
> + fprintf(stderr, "Error: Cannot get xstats lookup\n");
> + free(xstats_names);
> + return;
> + }
> +
> + if (rte_eth_xstats_get_id_by_name(port_id, counter_name, &id) < 0) {
> + fprintf(stderr, "Cannot find xstats with a given name\n");
> + free(xstats_names);
> + return;
> + }
> +
> + /* set counter */
> + if (on)
> + ret = rte_eth_xstats_enable_counter(port_id, id);
> + else
> + ret = rte_eth_xstats_disable_counter(port_id, id);
> +
> + if (ret < 0) {
> + switch (ret) {
> + case -EINVAL:
> + fprintf(stderr, "failed to find %s\n", counter_name);
> + break;
> + case -ENOTSUP:
> + fprintf(stderr, "operation not supported by device\n");
> + break;
> + case -ENOSPC:
> + fprintf(stderr, "there were not enough available counters\n");
> + break;
> + case -EPERM:
> + fprintf(stderr, "operation not premitted\n");
> + break;
> + default:
> + fprintf(stderr, "operation failed - diag=%d\n", ret);
> + break;
> + }
> + }
> + free(xstats_names);
> +}
> +
> void
> rx_vlan_filter_set(portid_t port_id, int on)
> {
> @@ -6665,6 +6748,12 @@ set_xstats_hide_zero(uint8_t on_off)
> xstats_hide_zero = on_off;
> }
>
> +void
> +set_xstats_show_state(uint8_t on_off)
> +{
> + xstats_show_state = on_off;
> +}
> +
> void
> set_record_core_cycles(uint8_t on_off)
> {
> diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
> index ac654048df..24be87204c 100644
> --- a/app/test-pmd/testpmd.c
> +++ b/app/test-pmd/testpmd.c
> @@ -500,6 +500,11 @@ volatile int test_done = 1; /* stop packet forwarding when set to 1. */
> */
> uint8_t xstats_hide_zero;
>
> +/*
> + * Display of xstats without their state disabled by default
> + */
> +uint8_t xstats_show_state;
> +
> /*
> * Measure of CPU cycles disabled by default
> */
> diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
> index 314482e69c..16a0348eb2 100644
> --- a/app/test-pmd/testpmd.h
> +++ b/app/test-pmd/testpmd.h
> @@ -498,6 +498,7 @@ enum dcb_mode_enable
> };
>
> extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
> +extern uint8_t xstats_show_state; /**< Show xstat state in xstats display */
>
> /* globals used for configuration */
> extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
> @@ -933,6 +934,7 @@ void nic_stats_display(portid_t port_id);
> void nic_stats_clear(portid_t port_id);
> void nic_xstats_display(portid_t port_id);
> void nic_xstats_clear(portid_t port_id);
> +void nic_xstats_set_counter(portid_t port_id, char *counter_name, int on);
> void device_infos_display(const char *identifier);
> void port_infos_display(portid_t port_id);
> void port_summary_display(portid_t port_id);
> @@ -1111,6 +1113,7 @@ void tx_vlan_pvid_set(portid_t port_id, uint16_t vlan_id, int on);
> void set_qmap(portid_t port_id, uint8_t is_rx, uint16_t queue_id, uint8_t map_value);
>
> void set_xstats_hide_zero(uint8_t on_off);
> +void set_xstats_show_state(uint8_t on_off);
>
> void set_record_core_cycles(uint8_t on_off);
> void set_record_burst_stats(uint8_t on_off);
> diff --git a/drivers/common/mlx5/mlx5_devx_cmds.c b/drivers/common/mlx5/mlx5_devx_cmds.c
> index a75f011750..2149449885 100644
> --- a/drivers/common/mlx5/mlx5_devx_cmds.c
> +++ b/drivers/common/mlx5/mlx5_devx_cmds.c
> @@ -3091,14 +3091,17 @@ mlx5_devx_cmd_wq_query(void *wq, uint32_t *counter_set_id)
> *
> * @param[in] ctx
> * Context returned from mlx5 open_device() glue function.
> + * @param[out] syndrome
> + * Get syndrome of devx command response.
> *
> * @return
> * Pointer to counter object on success, a NULL value otherwise and
> * rte_errno is set.
> */
> struct mlx5_devx_obj *
> -mlx5_devx_cmd_queue_counter_alloc(void *ctx)
> +mlx5_devx_cmd_queue_counter_alloc(void *ctx, int *syndrome)
> {
> + int status;
> struct mlx5_devx_obj *dcs = mlx5_malloc(MLX5_MEM_ZERO, sizeof(*dcs), 0,
> SOCKET_ID_ANY);
> uint32_t in[MLX5_ST_SZ_DW(alloc_q_counter_in)] = {0};
> @@ -3113,6 +3116,9 @@ mlx5_devx_cmd_queue_counter_alloc(void *ctx)
> sizeof(out));
> if (!dcs->obj) {
> DEVX_DRV_LOG(DEBUG, out, "create q counter set", NULL, 0);
> + status = MLX5_GET(alloc_q_counter_out, out, status);
> + if (status && syndrome)
> + *syndrome = MLX5_GET(alloc_q_counter_out, out, syndrome);
> mlx5_free(dcs);
> return NULL;
> }
> diff --git a/drivers/common/mlx5/mlx5_devx_cmds.h b/drivers/common/mlx5/mlx5_devx_cmds.h
> index f523bf8529..38548b4c9f 100644
> --- a/drivers/common/mlx5/mlx5_devx_cmds.h
> +++ b/drivers/common/mlx5/mlx5_devx_cmds.h
> @@ -848,7 +848,7 @@ __rte_internal
> int mlx5_devx_cmd_wq_query(void *wq, uint32_t *counter_set_id);
>
> __rte_internal
> -struct mlx5_devx_obj *mlx5_devx_cmd_queue_counter_alloc(void *ctx);
> +struct mlx5_devx_obj *mlx5_devx_cmd_queue_counter_alloc(void *ctx, int *syndrome);
> __rte_internal
> int mlx5_devx_cmd_queue_counter_query(struct mlx5_devx_obj *dcs, int clear,
> uint32_t *out_of_buffers);
> diff --git a/drivers/common/mlx5/mlx5_prm.h b/drivers/common/mlx5/mlx5_prm.h
> index 2d82807bc2..fded81d43d 100644
> --- a/drivers/common/mlx5/mlx5_prm.h
> +++ b/drivers/common/mlx5/mlx5_prm.h
> @@ -275,6 +275,9 @@
> #define MLX5_ERROR_CQE_SYNDROME_OFFSET 52
> #endif
>
> +/* Firmware error code for allocating the maximum number of queue counters */
> +#define MLX5_Q_COUNTERS_LIMIT_REACHED 0x587239
> +
> /* The completion mode offset in the WQE control segment line 2. */
> #define MLX5_COMP_MODE_OFFSET 2
>
> diff --git a/drivers/net/mlx5/linux/mlx5_ethdev_os.c b/drivers/net/mlx5/linux/mlx5_ethdev_os.c
> index 5d64984022..63277fc4ed 100644
> --- a/drivers/net/mlx5/linux/mlx5_ethdev_os.c
> +++ b/drivers/net/mlx5/linux/mlx5_ethdev_os.c
> @@ -1425,6 +1425,11 @@ static const struct mlx5_counter_ctrl mlx5_counters_init[] = {
> .dpdk_name = "hairpin_out_of_buffer",
> .ctr_name = "hairpin_out_of_buffer",
> .dev = 1,
> + .ctrl = {
> + .enable = mlx5_enable_port_level_hairpin_counter,
> + .disable = mlx5_disable_port_level_hairpin_counter,
> + .enabled = 0,
> + }
> },
> {
> .dpdk_name = "dev_internal_queue_oob",
> diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
> index 69a80b9ddc..0c63ab228e 100644
> --- a/drivers/net/mlx5/linux/mlx5_os.c
> +++ b/drivers/net/mlx5/linux/mlx5_os.c
> @@ -973,7 +973,7 @@ mlx5_queue_counter_id_prepare(struct rte_eth_dev *dev)
> struct mlx5_priv *priv = dev->data->dev_private;
> void *ctx = priv->sh->cdev->ctx;
>
> - priv->q_counters = mlx5_devx_cmd_queue_counter_alloc(ctx);
> + priv->q_counters = mlx5_devx_cmd_queue_counter_alloc(ctx, NULL);
> if (!priv->q_counters) {
> struct ibv_cq *cq = mlx5_glue->create_cq(ctx, 1, NULL, NULL, 0);
> struct ibv_wq *wq;
> @@ -981,7 +981,6 @@ mlx5_queue_counter_id_prepare(struct rte_eth_dev *dev)
> DRV_LOG(DEBUG, "Port %d queue counter object cannot be created "
> "by DevX - fall-back to use the kernel driver global "
> "queue counter.", dev->data->port_id);
> - priv->q_counters_allocation_failure = 1;
>
> /* Create WQ by kernel and query its queue counter ID. */
> if (cq) {
> @@ -3052,23 +3051,11 @@ mlx5_os_read_dev_stat(struct mlx5_priv *priv, const char *ctr_name,
> if (priv->sh) {
> if (priv->q_counters != NULL &&
> strcmp(ctr_name, "out_of_buffer") == 0) {
> - if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
> - DRV_LOG(WARNING, "DevX out_of_buffer counter is not supported in the secondary process");
> - rte_errno = ENOTSUP;
> - return 1;
> - }
> - return mlx5_devx_cmd_queue_counter_query
> - (priv->q_counters, 0, (uint32_t *)stat);
> + return mlx5_read_queue_counter(priv->q_counters, ctr_name, stat);
> }
> - if (priv->q_counters_hairpin != NULL &&
> + if (priv->q_counter_hairpin != NULL &&
> strcmp(ctr_name, "hairpin_out_of_buffer") == 0) {
> - if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
> - DRV_LOG(WARNING, "DevX out_of_buffer counter is not supported in the secondary process");
> - rte_errno = ENOTSUP;
> - return 1;
> - }
> - return mlx5_devx_cmd_queue_counter_query
> - (priv->q_counters_hairpin, 0, (uint32_t *)stat);
> + return mlx5_read_queue_counter(priv->q_counter_hairpin, ctr_name, stat);
> }
> MKSTR(path, "%s/ports/%d/hw_counters/%s",
> priv->sh->ibdev_path,
> diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
> index 6e4473e2f4..1e48c43da0 100644
> --- a/drivers/net/mlx5/mlx5.c
> +++ b/drivers/net/mlx5/mlx5.c
> @@ -2373,6 +2373,7 @@ mlx5_dev_close(struct rte_eth_dev *dev)
> priv->ptype_rss_groups = NULL;
> }
> #endif
> + mlx5_q_counters_destroy(dev);
> if (priv->rxq_privs != NULL) {
> /* XXX race condition if mlx5_rx_burst() is still running. */
> rte_delay_us_sleep(1000);
> @@ -2393,14 +2394,6 @@ mlx5_dev_close(struct rte_eth_dev *dev)
> mlx5_proc_priv_uninit(dev);
> if (priv->drop_queue.hrxq)
> mlx5_drop_action_destroy(dev);
> - if (priv->q_counters) {
> - mlx5_devx_cmd_destroy(priv->q_counters);
> - priv->q_counters = NULL;
> - }
> - if (priv->q_counters_hairpin) {
> - mlx5_devx_cmd_destroy(priv->q_counters_hairpin);
> - priv->q_counters_hairpin = NULL;
> - }
> mlx5_mprq_free_mp(dev);
> mlx5_os_free_shared_dr(priv);
> #ifdef HAVE_MLX5_HWS_SUPPORT
> @@ -2520,6 +2513,11 @@ const struct eth_dev_ops mlx5_dev_ops = {
> .xstats_get = mlx5_xstats_get,
> .xstats_reset = mlx5_xstats_reset,
> .xstats_get_names = mlx5_xstats_get_names,
> +
> + .xstats_enable = mlx5_xstats_enable,
> + .xstats_disable = mlx5_xstats_disable,
> + .xstats_query_state = mlx5_xstats_query_state,
> +
> .fw_version_get = mlx5_fw_version_get,
> .dev_infos_get = mlx5_dev_infos_get,
> .representor_info_get = mlx5_representor_info_get,
> @@ -3393,6 +3391,284 @@ mlx5_eth_find_next(uint16_t port_id, struct rte_device *odev)
> return port_id;
> }
>
> +static int
> +mlx5_hairpin_queue_counter_supported(struct mlx5_priv *priv)
> +{
> + if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
> + DRV_LOG(WARNING,
> + "DevX counter is not supported in the secondary process");
> + return -ENOTSUP;
> + }
> +
> + if (priv->obj_ops.rxq_obj_modify_counter_set_id == NULL) {
> + DRV_LOG(WARNING,
> + "DevX counter is not supported in this device");
> + return -ENOTSUP;
> + }
> + return 0;
> +}
> +
> +/**
> + * Disables the port-level hairpin counter.
> + *
> + * This function iterates over each RXQ, detaches it from the global
> + * counter if it's a hairpin counter, and then destroys the global counter object if
> + * it exists.
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + * @param id
> + * The counter ID to disable (not used in this implementation).
> + *
> + * @return
> + * 0 on success, error code otherwise.
> + */
> +int
> +mlx5_disable_port_level_hairpin_counter(struct rte_eth_dev *dev, uint64_t id __rte_unused)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + unsigned int num_rxqs = priv->rxqs_n;
> + unsigned int i;
> +
> + if (priv->q_counter_hairpin == NULL)
> + return 0;
> +
> + /* Detach each RXQ from the global hairpin counter */
> + for (i = 0; i < num_rxqs; ++i) {
> + struct mlx5_rxq_priv *rxq = mlx5_rxq_get(dev, i);
> +
> + if (rxq == NULL || rxq->ctrl->obj->rq == NULL || !rxq->ctrl->is_hairpin)
> + continue;
> +
> + if (priv->obj_ops.rxq_obj_modify_counter_set_id(rxq, 0) != 0)
> + DRV_LOG(ERR, "Port %u failed to modify rq object %s",
> + priv->dev_data->port_id, strerror(rte_errno));
> + }
> +
> + mlx5_devx_cmd_destroy(priv->q_counter_hairpin);
> + priv->q_counter_hairpin = NULL;
> +
> + /* Reset oob stats. */
> + mlx5_reset_xstats_by_name(priv, "hairpin_out_of_buffer");
> + return 0;
> +}
> +
> +/**
> + * Enables the port-level hairpin counter.
> + *
> + * This function iterates over each RXQ, allocate a q counter and attach it to each
> + * hairpin queue.
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + * @param id
> + * The counter ID to disable (not used in this implementation).
> + *
> + * @return
> + * 0 on success, error code otherwise.
> + */
> +int
> +mlx5_enable_port_level_hairpin_counter(struct rte_eth_dev *dev, uint64_t id __rte_unused)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_rxq_priv *rxq;
> + unsigned int i;
> + int ret = 0;
> +
> + ret = mlx5_hairpin_queue_counter_supported(priv);
> + if (ret) {
> + DRV_LOG(DEBUG, "Hairpin out of buffer counter "
> + "is not available on this NIC.");
> + return ret;
> + }
> +
> + /* check if counter is enable per queue - if yes - fail to enable per port */
> + if (priv->num_of_hairpin_q_counter_enabled != 0) {
> + DRV_LOG(WARNING, "Hairpin out of buffer counter is enabled per queue.");
> + return -EINVAL;
> + }
> +
> + /* Alloc global hairpin queue counter. */
> + priv->q_counter_hairpin = mlx5_devx_cmd_queue_counter_alloc
> + (priv->sh->cdev->ctx, NULL);
> +
> + if (!priv->q_counter_hairpin) {
> + if (ret == MLX5_Q_COUNTERS_LIMIT_REACHED) {
> + DRV_LOG(WARNING, "Maximum number of queue counters reached. "
> + "Unable to create counter object for Port %d using DevX.",
> + priv->dev_data->port_id);
> + return -ENOSPC;
> + }
> + DRV_LOG(WARNING, "Port %d global hairpin queue counter object cannot be created "
> + "by DevX.", priv->dev_data->port_id);
> + return -ENOMEM;
> + }
> +
> + /* go over each queue and attach to global counter */
> + for (i = 0; (i != priv->rxqs_n); ++i) {
> + rxq = mlx5_rxq_get(dev, i);
> +
> + if (rxq == NULL || rxq->ctrl->obj->rq == NULL || !rxq->ctrl->is_hairpin)
> + continue;
> +
> + ret = priv->obj_ops.rxq_obj_modify_counter_set_id(rxq, priv->q_counter_hairpin->id);
> + if (ret) {
> + DRV_LOG(ERR, "failed to modify rq object for port %u"
> + "%s", priv->dev_data->port_id, strerror(rte_errno));
> + return ret;
> + }
> + }
> +
> + /* Reset oob stats. */
> + mlx5_reset_xstats_by_name(priv, "hairpin_out_of_buffer");
> + return 0;
> +}
> +
> +/**
> + * Creates a queue counter for hairpin Rx queue.
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + * @param id
> + * Index of the RX queue to disable the hairpin queue counter for.
> + * @return
> + * 0 on success, a negative errno value otherwise and rte_errno is set.
> + */
> +int
> +mlx5_enable_per_queue_hairpin_counter(struct rte_eth_dev *dev, uint64_t id)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_rxq_priv *rxq;
> + struct mlx5_rxq_data *rxq_data;
> +
> + int ret = mlx5_hairpin_queue_counter_supported(priv);
> + if (ret) {
> + DRV_LOG(DEBUG, "Hairpin out of buffer counter "
> + "is not available on this NIC.");
> + return ret;
> + }
> +
> + /* check if we have port level counter enabled. if yes, don't set the queue level counter */
> + if (priv->q_counter_hairpin) {
> + DRV_LOG(WARNING, "Hairpin out of buffer counter is enabled per port.");
> + return -EINVAL;
> + }
> +
> + rxq = mlx5_rxq_get(dev, id);
> + if (rxq == NULL || rxq->ctrl->obj->rq == NULL || !rxq->ctrl->is_hairpin)
> + return -EINVAL;
> +
> + if (rxq->q_counter != NULL)
> + return 0;
> +
> + /* Alloc hairpin queue counter. */
> + rxq->q_counter = mlx5_devx_cmd_queue_counter_alloc
> + (priv->sh->cdev->ctx, NULL);
> + if (rxq->q_counter == NULL) {
> + if (ret == MLX5_Q_COUNTERS_LIMIT_REACHED) {
> + DRV_LOG(WARNING, "Maximum number of queue counters reached. "
> + "Unable to create counter object for Port %d, Queue %d "
> + "using DevX. The counter from this queue will not increment.",
> + priv->dev_data->port_id, rxq->idx);
> + return -ENOSPC;
> + }
> + DRV_LOG(WARNING, "Port %d queue %d counter object cannot be created "
> + "by DevX. Counter from this queue will not increment.",
> + priv->dev_data->port_id, rxq->idx);
> + return -ENOMEM;
> + }
> +
> + ret = priv->obj_ops.rxq_obj_modify_counter_set_id(rxq, rxq->q_counter->id);
> + if (ret) {
> + DRV_LOG(ERR, "failed to modify rq object for port %u"
> + "%s", priv->dev_data->port_id, strerror(rte_errno));
> + return ret;
> + }
> +
> + rxq_data = mlx5_rxq_data_get(dev, id);
> + if (rxq_data != NULL)
> + rxq_data->stats.oobs.ctrl.enabled = 1;
> +
> + priv->num_of_hairpin_q_counter_enabled++;
> + return 0;
> +}
> +
> +/**
> + * Disables the hairpin queue counter for a specified RX queue.
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + * @param id
> + * Index of the RX queue to disable the hairpin queue counter for.
> + * @return
> + * 0 on success, a negative errno value otherwise and rte_errno is set.
> + */
> +int
> +mlx5_disable_per_queue_hairpin_counter(struct rte_eth_dev *dev, uint64_t id)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_rxq_priv *rxq;
> + struct mlx5_rxq_data *rxq_data;
> + int ret = 0;
> +
> + rxq = mlx5_rxq_get(dev, id);
> + rxq_data = mlx5_rxq_data_get(dev, id);
> +
> + if (rxq == NULL || rxq->ctrl->obj->rq == NULL || !rxq->ctrl->is_hairpin)
> + return 0;
> +
> + if (rxq->q_counter != NULL) {
> + /* Modify rxq. */
> + ret = priv->obj_ops.rxq_obj_modify_counter_set_id(rxq, 0);
> + if (ret)
> + DRV_LOG(ERR, "Port %u failed to modify rq object "
> + " %s", priv->dev_data->port_id, strerror(rte_errno));
> +
> + mlx5_devx_cmd_destroy(rxq->q_counter);
> + rxq->q_counter = NULL;
> + }
> +
> + /* Reset queue oob stats. */
> + if (rxq_data != NULL) {
> + rxq_data->stats.oobs.count = 0;
> + rxq_data->stats_reset.oobs.count = 0;
> + rxq_data->stats.oobs.ctrl.enabled = 0;
> + }
> +
> + priv->num_of_hairpin_q_counter_enabled--;
> + return 0;
> +}
> +
> +/**
> + * Read statistics per queue by a named counter.
> + *
> + * @param[in] q_counter
> + * Pointer to the queue's counter object.
> + * @param[in] ctr_name
> + * Pointer to the name of the statistic counter to read
> + * @param[out] stat
> + * Pointer to read statistic value.
> + * @return
> + * 0 on success and stat is valid, 1 if failed to read the value
> + * rte_errno is set.
> + *
> + */
> +int
> +mlx5_read_queue_counter(struct mlx5_devx_obj *q_counter, const char *ctr_name,
> + uint64_t *stat)
> +{
> + if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
> + DRV_LOG(WARNING,
> + "DevX %s counter is not supported in the secondary process", ctr_name);
> + return -ENOTSUP;
> + }
> +
> + if (q_counter == NULL)
> + return -EINVAL;
> +
> + return mlx5_devx_cmd_queue_counter_query(q_counter, 0, (uint32_t *)stat);
> +}
> +
> /**
> * Callback to remove a device.
> *
> diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
> index 89d277b523..a67d7166cc 100644
> --- a/drivers/net/mlx5/mlx5.h
> +++ b/drivers/net/mlx5/mlx5.h
> @@ -261,17 +261,32 @@ struct mlx5_local_data {
>
> extern struct mlx5_shared_data *mlx5_shared_data;
>
> +int mlx5_xstats_enable(struct rte_eth_dev *dev, uint64_t id);
> +int mlx5_xstats_disable(struct rte_eth_dev *dev, uint64_t id);
> +int mlx5_xstats_query_state(struct rte_eth_dev *dev, uint64_t id);
> +
> /* Dev ops structs */
> extern const struct eth_dev_ops mlx5_dev_ops;
> extern const struct eth_dev_ops mlx5_dev_sec_ops;
> extern const struct eth_dev_ops mlx5_dev_ops_isolate;
>
> +typedef int (*mlx5_enable_counter_t)(struct rte_eth_dev *dev, uint64_t id);
> +typedef int (*mlx5_disable_counter_t)(struct rte_eth_dev *dev, uint64_t id);
> +typedef int (*mlx5_xstats_query_state_t)(struct rte_eth_dev *dev, uint64_t id);
> +
> +struct mlx5_stat_counter_ctrl {
> + mlx5_enable_counter_t enable;
> + mlx5_disable_counter_t disable;
> + uint32_t enabled;
> +};
> +
> struct mlx5_counter_ctrl {
> /* Name of the counter. */
> char dpdk_name[RTE_ETH_XSTATS_NAME_SIZE];
> /* Name of the counter on the device table. */
> char ctr_name[RTE_ETH_XSTATS_NAME_SIZE];
> uint32_t dev:1; /**< Nonzero for dev counters. */
> + struct mlx5_stat_counter_ctrl ctrl;
> };
>
> struct mlx5_xstats_ctrl {
> @@ -1783,6 +1798,7 @@ struct mlx5_priv;
> /* HW objects operations structure. */
> struct mlx5_obj_ops {
> int (*rxq_obj_modify_vlan_strip)(struct mlx5_rxq_priv *rxq, int on);
> + int (*rxq_obj_modify_counter_set_id)(struct mlx5_rxq_priv *rxq, uint32_t counter_set_id);
> int (*rxq_obj_new)(struct mlx5_rxq_priv *rxq);
> int (*rxq_event_get)(struct mlx5_rxq_obj *rxq_obj);
> int (*rxq_obj_modify)(struct mlx5_rxq_priv *rxq, uint8_t type);
> @@ -2044,12 +2060,12 @@ struct mlx5_priv {
> LIST_HEAD(fdir, mlx5_fdir_flow) fdir_flows; /* fdir flows. */
> rte_spinlock_t shared_act_sl; /* Shared actions spinlock. */
> uint32_t rss_shared_actions; /* RSS shared actions. */
> - /* If true, indicates that we failed to allocate a q counter in the past. */
> - bool q_counters_allocation_failure;
> + /**< Total number of hairpin queues attach to q counters. */
> + uint64_t num_of_hairpin_q_counter_enabled;
> struct mlx5_devx_obj *q_counters; /* DevX queue counter object. */
> uint32_t counter_set_id; /* Queue counter ID to set in DevX objects. */
> /* DevX queue counter object for all hairpin queues of the port. */
> - struct mlx5_devx_obj *q_counters_hairpin;
> + struct mlx5_devx_obj *q_counter_hairpin;
> uint32_t lag_affinity_idx; /* LAG mode queue 0 affinity starting. */
> rte_spinlock_t flex_item_sl; /* Flex item list spinlock. */
> struct mlx5_flex_item flex_item[MLX5_PORT_FLEX_ITEM_NUM];
> @@ -2224,6 +2240,10 @@ bool mlx5_is_sf_repr(struct rte_eth_dev *dev);
> void mlx5_age_event_prepare(struct mlx5_dev_ctx_shared *sh);
> int mlx5_lwm_setup(struct mlx5_priv *priv);
> void mlx5_lwm_unset(struct mlx5_dev_ctx_shared *sh);
> +int mlx5_enable_port_level_hairpin_counter(struct rte_eth_dev *dev, uint64_t id);
> +int mlx5_disable_port_level_hairpin_counter(struct rte_eth_dev *dev, uint64_t id);
> +int mlx5_enable_per_queue_hairpin_counter(struct rte_eth_dev *dev, uint64_t id);
> +int mlx5_disable_per_queue_hairpin_counter(struct rte_eth_dev *dev, uint64_t id);
>
> /* Macro to iterate over all valid ports for mlx5 driver. */
> #define MLX5_ETH_FOREACH_DEV(port_id, dev) \
> @@ -2257,6 +2277,7 @@ int mlx5_flow_aso_ct_mng_init(struct mlx5_dev_ctx_shared *sh);
> struct mlx5_physical_device *
> mlx5_get_locked_physical_device(struct mlx5_priv *priv);
> void mlx5_unlock_physical_device(void);
> +int mlx5_read_queue_counter(struct mlx5_devx_obj *q_counter, const char *ctr_name, uint64_t *stat);
>
> /* mlx5_ethdev.c */
>
> @@ -2364,6 +2385,8 @@ int mlx5_xstats_reset(struct rte_eth_dev *dev);
> int mlx5_xstats_get_names(struct rte_eth_dev *dev __rte_unused,
> struct rte_eth_xstat_name *xstats_names,
> unsigned int n);
> +void mlx5_reset_xstats_by_name(struct mlx5_priv *priv, const char *ctr_name);
> +void mlx5_reset_xstats_rq(struct rte_eth_dev *dev);
>
> /* mlx5_vlan.c */
>
> diff --git a/drivers/net/mlx5/mlx5_devx.c b/drivers/net/mlx5/mlx5_devx.c
> index 8ebe784000..a12891a983 100644
> --- a/drivers/net/mlx5/mlx5_devx.c
> +++ b/drivers/net/mlx5/mlx5_devx.c
> @@ -91,6 +91,30 @@ mlx5_rxq_obj_modify_rq_vlan_strip(struct mlx5_rxq_priv *rxq, int on)
> return mlx5_devx_cmd_modify_rq(rxq->devx_rq.rq, &rq_attr);
> }
>
> +/**
> + * Modify the q counter of a given RQ
> + *
> + * @param rxq
> + * Rx queue.
> + * @param counter_set_id
> + * Q counter id to set
> + *
> + * @return
> + * 0 on success, non-0 otherwise
> + */
> +static int
> +mlx5_rxq_obj_modify_counter(struct mlx5_rxq_priv *rxq, uint32_t counter_set_id)
> +{
> + struct mlx5_devx_modify_rq_attr rq_attr;
> +
> + memset(&rq_attr, 0, sizeof(rq_attr));
> + rq_attr.rq_state = MLX5_RQC_STATE_RDY;
> + rq_attr.state = MLX5_RQC_STATE_RDY;
> + rq_attr.counter_set_id = counter_set_id;
> + rq_attr.modify_bitmask = MLX5_MODIFY_RQ_IN_MODIFY_BITMASK_RQ_COUNTER_SET_ID;
> + return mlx5_devx_cmd_modify_rq(rxq->ctrl->obj->rq, &rq_attr);
> +}
> +
> /**
> * Modify RQ using DevX API.
> *
> @@ -496,55 +520,6 @@ mlx5_rxq_create_devx_cq_resources(struct mlx5_rxq_priv *rxq)
> return 0;
> }
>
> -/**
> - * Create a global queue counter for all the port hairpin queues.
> - *
> - * @param priv
> - * Device private data.
> - *
> - * @return
> - * The counter_set_id of the queue counter object, 0 otherwise.
> - */
> -static uint32_t
> -mlx5_set_hairpin_queue_counter_obj(struct mlx5_priv *priv)
> -{
> - if (priv->q_counters_hairpin != NULL)
> - return priv->q_counters_hairpin->id;
> -
> - /* Queue counter allocation failed in the past - don't try again. */
> - if (priv->q_counters_allocation_failure != 0)
> - return 0;
> -
> - if (priv->pci_dev == NULL) {
> - DRV_LOG(DEBUG, "Hairpin out of buffer counter is "
> - "only supported on PCI device.");
> - priv->q_counters_allocation_failure = 1;
> - return 0;
> - }
> -
> - switch (priv->pci_dev->id.device_id) {
> - /* Counting out of buffer drops on hairpin queues is supported only on CX7 and up. */
> - case PCI_DEVICE_ID_MELLANOX_CONNECTX7:
> - case PCI_DEVICE_ID_MELLANOX_CONNECTXVF:
> - case PCI_DEVICE_ID_MELLANOX_BLUEFIELD3:
> - case PCI_DEVICE_ID_MELLANOX_BLUEFIELDVF:
> -
> - priv->q_counters_hairpin = mlx5_devx_cmd_queue_counter_alloc(priv->sh->cdev->ctx);
> - if (priv->q_counters_hairpin == NULL) {
> - /* Failed to allocate */
> - DRV_LOG(DEBUG, "Some of the statistics of port %d "
> - "will not be available.", priv->dev_data->port_id);
> - priv->q_counters_allocation_failure = 1;
> - return 0;
> - }
> - return priv->q_counters_hairpin->id;
> - default:
> - DRV_LOG(DEBUG, "Hairpin out of buffer counter "
> - "is not available on this NIC.");
> - priv->q_counters_allocation_failure = 1;
> - return 0;
> - }
> -}
>
> /**
> * Create the Rx hairpin queue object.
> @@ -592,7 +567,6 @@ mlx5_rxq_obj_hairpin_new(struct mlx5_rxq_priv *rxq)
> unlocked_attr.wq_attr.log_hairpin_data_sz -
> MLX5_HAIRPIN_QUEUE_STRIDE;
>
> - unlocked_attr.counter_set_id = mlx5_set_hairpin_queue_counter_obj(priv);
>
> rxq_ctrl->rxq.delay_drop = priv->config.hp_delay_drop;
> unlocked_attr.delay_drop_en = priv->config.hp_delay_drop;
> @@ -1710,6 +1684,7 @@ mlx5_txq_devx_obj_release(struct mlx5_txq_obj *txq_obj)
>
> struct mlx5_obj_ops devx_obj_ops = {
> .rxq_obj_modify_vlan_strip = mlx5_rxq_obj_modify_rq_vlan_strip,
> + .rxq_obj_modify_counter_set_id = mlx5_rxq_obj_modify_counter,
> .rxq_obj_new = mlx5_rxq_devx_obj_new,
> .rxq_event_get = mlx5_rx_devx_get_event,
> .rxq_obj_modify = mlx5_devx_modify_rq,
> diff --git a/drivers/net/mlx5/mlx5_rx.h b/drivers/net/mlx5/mlx5_rx.h
> index 1a6f174c40..f80a2e3227 100644
> --- a/drivers/net/mlx5/mlx5_rx.h
> +++ b/drivers/net/mlx5/mlx5_rx.h
> @@ -30,6 +30,12 @@
> /* First entry must be NULL for comparison. */
> #define mlx5_mr_btree_len(bt) ((bt)->len - 1)
>
> +struct mlx5_rxq_stat {
> + int id;
> + uint64_t count;
> + struct mlx5_stat_counter_ctrl ctrl;
> +};
> +
> struct mlx5_rxq_stats {
> #ifdef MLX5_PMD_SOFT_COUNTERS
> uint64_t ipackets; /**< Total of successfully received packets. */
> @@ -37,6 +43,18 @@ struct mlx5_rxq_stats {
> #endif
> uint64_t idropped; /**< Total of packets dropped when RX ring full. */
> uint64_t rx_nombuf; /**< Total of RX mbuf allocation failures. */
> + struct mlx5_rxq_stat oobs; /**< Total of hairpin queue out of buffers. */
> +};
> +
> +/* store statistics names and its offset in stats structure */
> +struct mlx5_xstats_name_off {
> + char name[RTE_ETH_XSTATS_NAME_SIZE];
> + unsigned int offset;
> +};
> +
> +struct mlx5_rq_stats {
> + /** Total number of hairpin queue packets received that are dropped. */
> + uint64_t q_oobs[RTE_ETHDEV_QUEUE_STAT_CNTRS];
> };
>
> /* Compressed CQE context. */
> @@ -183,6 +201,7 @@ struct mlx5_rxq_priv {
> uint32_t lwm:16;
> uint32_t lwm_event_pending:1;
> uint32_t lwm_devx_subscribed:1;
> + struct mlx5_devx_obj *q_counter; /* DevX hairpin queue counter object. */
> };
>
> /* mlx5_rxq.c */
> @@ -208,6 +227,7 @@ void mlx5_rx_intr_vec_disable(struct rte_eth_dev *dev);
> int mlx5_rx_intr_enable(struct rte_eth_dev *dev, uint16_t rx_queue_id);
> int mlx5_rx_intr_disable(struct rte_eth_dev *dev, uint16_t rx_queue_id);
> int mlx5_rxq_obj_verify(struct rte_eth_dev *dev);
> +void mlx5_q_counters_destroy(struct rte_eth_dev *dev);
> struct mlx5_rxq_ctrl *mlx5_rxq_new(struct rte_eth_dev *dev, uint16_t idx,
> uint16_t desc, unsigned int socket,
> const struct rte_eth_rxconf *conf,
> diff --git a/drivers/net/mlx5/mlx5_rxq.c b/drivers/net/mlx5/mlx5_rxq.c
> index 126b1970e6..a5971b5cdd 100644
> --- a/drivers/net/mlx5/mlx5_rxq.c
> +++ b/drivers/net/mlx5/mlx5_rxq.c
> @@ -1314,6 +1314,42 @@ mlx5_rxq_obj_verify(struct rte_eth_dev *dev)
> return ret;
> }
>
> +/**
> + * Destroy all queue counters.
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + */
> +void
> +mlx5_q_counters_destroy(struct rte_eth_dev *dev)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + unsigned int i;
> +
> + /* Destroy port q counter */
> + if (priv->q_counters) {
> + mlx5_devx_cmd_destroy(priv->q_counters);
> + priv->q_counters = NULL;
> + }
> +
> + /* Destroy port global hairpin q counter */
> + if (priv->q_counter_hairpin) {
> + mlx5_devx_cmd_destroy(priv->q_counter_hairpin);
> + priv->q_counter_hairpin = NULL;
> + }
> +
> + /* Destroy per hairpin queue counter */
> + for (i = 0; i != priv->rxqs_n; ++i) {
> + struct mlx5_rxq_priv *rxq = mlx5_rxq_get(dev, i);
> +
> + if (rxq == NULL || rxq->q_counter == NULL)
> + continue;
> +
> + mlx5_devx_cmd_destroy(rxq->q_counter);
> + rxq->q_counter = NULL;
> + }
> +}
> +
> /**
> * Callback function to initialize mbufs for Multi-Packet RQ.
> */
> diff --git a/drivers/net/mlx5/mlx5_stats.c b/drivers/net/mlx5/mlx5_stats.c
> index f4ac58e2f9..fce774c849 100644
> --- a/drivers/net/mlx5/mlx5_stats.c
> +++ b/drivers/net/mlx5/mlx5_stats.c
> @@ -20,6 +20,107 @@
> #include "mlx5_tx.h"
> #include "mlx5_malloc.h"
>
> +
> +static const struct mlx5_xstats_name_off mlx5_rxq_stats_strings[] = {
> + {"out_of_buffer", offsetof(struct mlx5_rq_stats, q_oobs)},
> +};
> +
> +#define NB_RXQ_STATS RTE_DIM(mlx5_rxq_stats_strings)
> +
> +/**
> + * Retrieve extended device statistics
> + * for Rx queues. It appends the specific statistics
> + * before the parts filled by preceding modules (eth stats, etc.)
> + *
> + * @param dev
> + * Pointer to Ethernet device.
> + * @param[out] stats
> + * Pointer to an array to store the retrieved statistics.
> + * @return
> + * Number of extended stats is filled,
> + * negative on error and rte_errno is set.
> + */
> +static int
> +mlx5_rq_xstats_get(struct rte_eth_dev *dev,
> + struct rte_eth_xstat *stats)
> +{
> + uint16_t n_stats_rq = RTE_MIN(dev->data->nb_rx_queues, RTE_ETHDEV_QUEUE_STAT_CNTRS);
> + int cnt_used_entries = 0;
> +
> + for (unsigned int idx = 0; idx < n_stats_rq; idx++) {
> + struct mlx5_rxq_data *rxq_data = mlx5_rxq_data_get(dev, idx);
> + struct mlx5_rxq_priv *rxq_priv = mlx5_rxq_get(dev, idx);
> +
> + if (rxq_data == NULL)
> + continue;
> +
> + struct mlx5_rxq_stat *rxq_stat = &rxq_data->stats.oobs;
> + if (rxq_stat == NULL)
> + continue;
> +
> + /* Handle initial stats setup - Flag uninitialized stat */
> + rxq_stat->id = -1;
> +
> + /* Handle hairpin statistics */
> + if (rxq_priv && rxq_priv->ctrl->is_hairpin) {
> + if (stats) {
> + mlx5_read_queue_counter(rxq_priv->q_counter, "hairpin_out_of_buffer",
> + &rxq_stat->count);
> + rxq_stat->ctrl.enable = mlx5_enable_per_queue_hairpin_counter;
> + rxq_stat->ctrl.disable = mlx5_disable_per_queue_hairpin_counter;
> +
> + stats[cnt_used_entries].id = cnt_used_entries;
> + stats[cnt_used_entries].value = rxq_stat->count -
> + rxq_data->stats_reset.oobs.count;
> + }
> + rxq_stat->id = cnt_used_entries;
> + cnt_used_entries++;
> + }
> + }
> + return cnt_used_entries;
> +}
> +
> +/**
> + * Retrieve names of extended device statistics
> + * for Rx queues. It appends the specific stats names
> + * before the parts filled by preceding modules (eth stats, etc.)
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + * @param[out] xstats_names
> + * Buffer to insert names into.
> + *
> + * @return
> + * Number of xstats names.
> + */
> +static int
> +mlx5_rq_xstats_get_names(struct rte_eth_dev *dev __rte_unused,
> + struct rte_eth_xstat_name *xstats_names)
> +{
> + struct mlx5_rxq_priv *rxq;
> + unsigned int i;
> + int cnt_used_entries = 0;
> +
> + uint16_t n_stats_rq = RTE_MIN(dev->data->nb_rx_queues, RTE_ETHDEV_QUEUE_STAT_CNTRS);
> +
> + for (i = 0; (i != n_stats_rq); ++i) {
> + rxq = mlx5_rxq_get(dev, i);
> +
> + if (rxq == NULL)
> + continue;
> +
> + if (rxq->ctrl->is_hairpin) {
> + if (xstats_names)
> + snprintf(xstats_names[cnt_used_entries].name,
> + sizeof(xstats_names[0].name),
> + "hairpin_%s_rxq%u",
> + mlx5_rxq_stats_strings[0].name, i);
> + cnt_used_entries++;
> + }
> + }
> + return cnt_used_entries;
> +}
> +
> /**
> * DPDK callback to get extended device statistics.
> *
> @@ -46,6 +147,7 @@ mlx5_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *stats,
> uint16_t stats_n_2nd = 0;
> uint16_t mlx5_stats_n = xstats_ctrl->mlx5_stats_n;
> bool bond_master = (priv->master && priv->pf_bond >= 0);
> + int n_used = mlx5_rq_xstats_get(dev, stats);
>
> if (n >= mlx5_stats_n && stats) {
> int ret;
> @@ -69,27 +171,27 @@ mlx5_xstats_get(struct rte_eth_dev *dev, struct rte_eth_xstat *stats,
> if (ret < 0)
> return ret;
> for (i = 0; i != mlx5_stats_n; i++) {
> - stats[i].id = i;
> + stats[i + n_used].id = i + n_used;
> if (xstats_ctrl->info[i].dev) {
> uint64_t wrap_n;
> uint64_t hw_stat = xstats_ctrl->hw_stats[i];
>
> - stats[i].value = (counters[i] -
> + stats[i + n_used].value = (counters[i] -
> xstats_ctrl->base[i]) &
> (uint64_t)UINT32_MAX;
> wrap_n = hw_stat >> 32;
> - if (stats[i].value <
> + if (stats[i + n_used].value <
> (hw_stat & (uint64_t)UINT32_MAX))
> wrap_n++;
> - stats[i].value |= (wrap_n) << 32;
> - xstats_ctrl->hw_stats[i] = stats[i].value;
> + stats[i + n_used].value |= (wrap_n) << 32;
> + xstats_ctrl->hw_stats[i] = stats[i + n_used].value;
> } else {
> - stats[i].value =
> + stats[i + n_used].value =
> (counters[i] - xstats_ctrl->base[i]);
> }
> }
> }
> - mlx5_stats_n = mlx5_txpp_xstats_get(dev, stats, n, mlx5_stats_n);
> + mlx5_stats_n = mlx5_txpp_xstats_get(dev, stats, n, mlx5_stats_n + n_used);
> return mlx5_stats_n;
> }
>
> @@ -273,11 +375,58 @@ mlx5_xstats_reset(struct rte_eth_dev *dev)
> xstats_ctrl->base[i] = counters[i];
> xstats_ctrl->hw_stats[i] = 0;
> }
> + mlx5_reset_xstats_rq(dev);
> mlx5_txpp_xstats_reset(dev);
> mlx5_free(counters);
> return 0;
> }
>
> +void
> +mlx5_reset_xstats_by_name(struct mlx5_priv *priv, const char *ctr_name)
> +{
> + struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
> + unsigned int mlx5_xstats_n = xstats_ctrl->mlx5_stats_n;
> + unsigned int i;
> +
> + for (i = 0; i != mlx5_xstats_n; ++i) {
> + if (strcmp(xstats_ctrl->info[i].ctr_name, ctr_name) == 0) {
> + xstats_ctrl->base[i] = 0;
> + xstats_ctrl->hw_stats[i] = 0;
> + xstats_ctrl->xstats[i] = 0;
> + return;
> + }
> + }
> +}
> +
> +/**
> + * Clear device extended statistics for each Rx queue.
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + */
> +void
> +mlx5_reset_xstats_rq(struct rte_eth_dev *dev)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_rxq_priv *rxq;
> + struct mlx5_rxq_data *rxq_data;
> + unsigned int i;
> +
> + for (i = 0; (i != priv->rxqs_n); ++i) {
> + rxq = mlx5_rxq_get(dev, i);
> + rxq_data = mlx5_rxq_data_get(dev, i);
> +
> + if (rxq == NULL || rxq_data == NULL || rxq->q_counter == NULL)
> + continue;
> + if (rxq->ctrl->is_hairpin)
> + mlx5_read_queue_counter(rxq->q_counter,
> + "hairpin_out_of_buffer", &rxq_data->stats_reset.oobs.count);
> + else
> + mlx5_read_queue_counter(rxq->q_counter,
> + "out_of_buffer", &rxq_data->stats_reset.oobs.count);
> + }
> +}
> +
> /**
> * DPDK callback to retrieve names of extended device statistics
> *
> @@ -299,15 +448,140 @@ mlx5_xstats_get_names(struct rte_eth_dev *dev,
> struct mlx5_priv *priv = dev->data->dev_private;
> struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
> unsigned int mlx5_xstats_n = xstats_ctrl->mlx5_stats_n;
> + unsigned int n_used = mlx5_rq_xstats_get_names(dev, xstats_names);
>
> if (n >= mlx5_xstats_n && xstats_names) {
> for (i = 0; i != mlx5_xstats_n; ++i) {
> - strlcpy(xstats_names[i].name,
> + rte_strscpy(xstats_names[i + n_used].name,
> xstats_ctrl->info[i].dpdk_name,
> RTE_ETH_XSTATS_NAME_SIZE);
> + xstats_names[i + n_used].name[RTE_ETH_XSTATS_NAME_SIZE - 1] = 0;
> }
> }
> mlx5_xstats_n = mlx5_txpp_xstats_get_names(dev, xstats_names,
> - n, mlx5_xstats_n);
> + n, mlx5_xstats_n + n_used);
> return mlx5_xstats_n;
> }
> +
> +static struct mlx5_stat_counter_ctrl*
> +mlx5_rxq_get_counter_by_id(struct rte_eth_dev *dev, uint64_t id, uint64_t *rq_id)
> +{
> + uint16_t n_stats_rq = RTE_MIN(dev->data->nb_rx_queues, RTE_ETHDEV_QUEUE_STAT_CNTRS);
> +
> + for (int i = 0; (i != n_stats_rq); i++) {
> + struct mlx5_rxq_data *rxq_data = mlx5_rxq_data_get(dev, i);
> + if (rxq_data == NULL || rxq_data->stats.oobs.id == -1)
> + continue;
> +
> + if ((uint64_t)rxq_data->stats.oobs.id == id) {
> + *rq_id = rxq_data->idx;
> + return &rxq_data->stats.oobs.ctrl;
> + }
> + }
> +
> + return NULL;
> +}
> +
> +/**
> + * Callback to enable an xstat counter of the given id.
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + * @param id
> + * The ID of the counter to enable
> + *
> + * @return
> + * 1 xstat is enabled, 0 if xstat is disabled,
> + * -ENOTSUP if enabling/disabling is not implemented and -EINVAL if xstat id is invalid.
> + */
> +int
> +mlx5_xstats_enable(struct rte_eth_dev *dev, uint64_t id)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
> + struct mlx5_stat_counter_ctrl *counter_ctrl = NULL;
> + uint16_t n_stats_rq = mlx5_rq_xstats_get(dev, NULL);
> +
> + if (id < n_stats_rq)
> + counter_ctrl = mlx5_rxq_get_counter_by_id(dev, id, &id);
> + else
> + counter_ctrl = &xstats_ctrl->info[id - n_stats_rq].ctrl;
> +
> + if (counter_ctrl == NULL)
> + return -EINVAL;
> +
> + if (counter_ctrl->enable == NULL)
> + return -ENOTSUP;
> +
> + counter_ctrl->enabled = counter_ctrl->enable(dev, id) == 0 ? 1 : 0;
> + return counter_ctrl->enabled;
> +}
> +
> +/**
> + * Callback to disable an xstat counter of the given id.
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + * @param id
> + * The ID of the counter to enable
> + *
> + * @return
> + * 1 if xstat is disabled, 0 xstat is enabled,
> + * -ENOTSUP if enabling/disabling is not implemented and -EINVAL if xstat id is invalid.
> + */
> +int
> +mlx5_xstats_disable(struct rte_eth_dev *dev, uint64_t id)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
> + struct mlx5_stat_counter_ctrl *counter_ctrl = NULL;
> +
> + uint16_t n_stats_rq = mlx5_rq_xstats_get(dev, NULL);
> + if (id < n_stats_rq)
> + counter_ctrl = mlx5_rxq_get_counter_by_id(dev, id, &id);
> + else
> + counter_ctrl = &xstats_ctrl->info[id - n_stats_rq].ctrl;
> +
> + if (counter_ctrl == NULL)
> + return -EINVAL;
> +
> + if (counter_ctrl->disable == NULL)
> + return -ENOTSUP;
> +
> + counter_ctrl->enabled = counter_ctrl->disable(dev, id) == 0 ? 0 : 1;
> + return counter_ctrl->enabled;
> +}
> +
> +/**
> + * Query the state of the xstat counter.
> + *
> + * @param dev
> + * Pointer to Ethernet device structure.
> + * @param id
> + * The ID of the counter to enable
> + *
> + * @return
> + * 1 if xstat is disabled, 0 xstat is enabled,
> + * -ENOTSUP if enabling/disabling is not implemented and -EINVAL if xstat id is invalid.
> + */
> +int
> +mlx5_xstats_query_state(struct rte_eth_dev *dev, uint64_t id)
> +{
> + struct mlx5_priv *priv = dev->data->dev_private;
> + struct mlx5_xstats_ctrl *xstats_ctrl = &priv->xstats_ctrl;
> + struct mlx5_stat_counter_ctrl *counter_ctrl = NULL;
> +
> + uint16_t n_stats_rq = mlx5_rq_xstats_get(dev, NULL);
> + if (id < n_stats_rq)
> + counter_ctrl = mlx5_rxq_get_counter_by_id(dev, id, &id);
> + else
> + counter_ctrl = &xstats_ctrl->info[id - n_stats_rq].ctrl;
> +
> + if (counter_ctrl == NULL)
> + return -EINVAL;
> +
> + if (counter_ctrl->disable == NULL)
> + return -ENOTSUP;
> +
> + return counter_ctrl->enabled;
> +}
> diff --git a/drivers/net/mlx5/windows/mlx5_os.c b/drivers/net/mlx5/windows/mlx5_os.c
> index 268598f209..d583730066 100644
> --- a/drivers/net/mlx5/windows/mlx5_os.c
> +++ b/drivers/net/mlx5/windows/mlx5_os.c
> @@ -78,12 +78,11 @@ mlx5_queue_counter_id_prepare(struct rte_eth_dev *dev)
> struct mlx5_priv *priv = dev->data->dev_private;
> void *ctx = priv->sh->cdev->ctx;
>
> - priv->q_counters = mlx5_devx_cmd_queue_counter_alloc(ctx);
> + priv->q_counters = mlx5_devx_cmd_queue_counter_alloc(ctx, NULL);
> if (!priv->q_counters) {
> DRV_LOG(ERR, "Port %d queue counter object cannot be created "
> "by DevX - imissed counter will be unavailable",
> dev->data->port_id);
> - priv->q_counters_allocation_failure = 1;
> return;
> }
> priv->counter_set_id = priv->q_counters->id;
> diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h
> index 1fd4562b40..74995df7f4 100644
> --- a/lib/ethdev/ethdev_driver.h
> +++ b/lib/ethdev/ethdev_driver.h
> @@ -570,6 +570,15 @@ typedef const uint32_t *(*eth_dev_supported_ptypes_get_t)(struct rte_eth_dev *de
> typedef int (*eth_dev_ptypes_set_t)(struct rte_eth_dev *dev,
> uint32_t ptype_mask);
>
> +/** @internal Enable an xstat of an Ethernet device. */
> +typedef int (*eth_xstats_enable_counter_t)(struct rte_eth_dev *dev, uint64_t id);
> +
> +/** @internal Disable an xstat of an Ethernet device. */
> +typedef int (*eth_xstats_disable_counter_t)(struct rte_eth_dev *dev, uint64_t id);
> +
> +/** @internal Query the state of an xstat the can be enabled and disabled in runtime. */
> +typedef int (*eth_xstats_query_state_t)(struct rte_eth_dev *dev, uint64_t id);
> +
> /** @internal Start Rx and Tx of a queue of an Ethernet device. */
> typedef int (*eth_queue_start_t)(struct rte_eth_dev *dev,
> uint16_t queue_id);
> @@ -1528,6 +1537,10 @@ struct eth_dev_ops {
> /** Get name of extended device statistics by ID */
> eth_xstats_get_names_by_id_t xstats_get_names_by_id;
>
> + eth_xstats_enable_counter_t xstats_enable;
> + eth_xstats_disable_counter_t xstats_disable;
> + eth_xstats_query_state_t xstats_query_state;
> +
> /** Get Traffic Management (TM) operations */
> eth_tm_ops_get_t tm_ops_get;
>
> diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
> index 6413c54e3b..6964d69901 100644
> --- a/lib/ethdev/rte_ethdev.c
> +++ b/lib/ethdev/rte_ethdev.c
> @@ -3776,6 +3776,72 @@ rte_eth_xstats_reset(uint16_t port_id)
> return rte_eth_stats_reset(port_id);
> }
>
> +int
> +rte_eth_xstats_enable_counter(uint16_t port_id, uint64_t id)
> +{
> + struct rte_eth_dev *dev;
> + unsigned int basic_count;
> +
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> +
> + if (rte_eth_xstats_query_state(port_id, id) == 1)
> + return -EEXIST;
> +
> + dev = &rte_eth_devices[port_id];
> + basic_count = eth_dev_get_xstats_basic_count(dev);
> + if (id < basic_count)
> + return -EINVAL;
> +
> + /* implemented by the driver */
> + if (dev->dev_ops->xstats_enable != NULL)
> + return (*dev->dev_ops->xstats_enable)(dev, id - basic_count);
> +
> + return -ENOTSUP;
> +}
> +
> +int
> +rte_eth_xstats_disable_counter(uint16_t port_id, uint64_t id)
> +{
> + struct rte_eth_dev *dev;
> + unsigned int basic_count;
> +
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> +
> + if (rte_eth_xstats_query_state(port_id, id) == 0)
> + return 0;
> +
> + dev = &rte_eth_devices[port_id];
> + basic_count = eth_dev_get_xstats_basic_count(dev);
> + if (id < basic_count)
> + return -EINVAL;
> +
> + /* implemented by the driver */
> + if (dev->dev_ops->xstats_disable != NULL)
> + return (*dev->dev_ops->xstats_disable)(dev, id - basic_count);
> +
> + return -ENOTSUP;
> +}
> +
> +int
> +rte_eth_xstats_query_state(uint16_t port_id, uint64_t id)
> +{
> + struct rte_eth_dev *dev;
> + unsigned int basic_count;
> +
> + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
> +
> + dev = &rte_eth_devices[port_id];
> + basic_count = eth_dev_get_xstats_basic_count(dev);
> + if (id < basic_count)
> + return -ENOTSUP;
> +
> + /* implemented by the driver */
> + if (dev->dev_ops->xstats_query_state != NULL)
> + return (*dev->dev_ops->xstats_query_state)(dev, id - basic_count);
> +
> + return -ENOTSUP;
> +}
> +
> static int
> eth_dev_set_queue_stats_mapping(uint16_t port_id, uint16_t queue_id,
> uint8_t stat_idx, uint8_t is_rx)
> diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
> index 1f71cad244..c3b3761f15 100644
> --- a/lib/ethdev/rte_ethdev.h
> +++ b/lib/ethdev/rte_ethdev.h
> @@ -3386,6 +3386,50 @@ int rte_eth_xstats_get_by_id(uint16_t port_id, const uint64_t *ids,
> int rte_eth_xstats_get_id_by_name(uint16_t port_id, const char *xstat_name,
> uint64_t *id);
>
> +/**
> + * Enable the xstat counter of the given id.
> + *
> + * @param port_id The port to look up statistics from
> + * @param id The ID of the counter to enable
> + * @return
> + * - (0) on success
> + * - (-EEXIST) counter already enabled
> + * - (-ENOTSUP) enable is not implemented
> + * - (-EINVAL) xstat id is invalid
> + * - (-EPERM) enabling this counter is not permitted
> + * - (-ENOSPC) no resources
> + */
> +__rte_experimental
> +int rte_eth_xstats_enable_counter(uint16_t port_id, uint64_t id);
> +
> +/**
> + * Disable the xstat counter of the given id.
> + *
> + * @param port_id The port to look up statistics from
> + * @param id The ID of the counter to disable
> + * @return
> + * - (0) disabled successfully or already disabled
> + * - (-ENOTSUP) enable is not implemented
> + * - (-EINVAL) xstat id is invalid
> + * - (-EPERM) disabling this counter is not permitted
> + */
> +__rte_experimental
> +int rte_eth_xstats_disable_counter(uint16_t port_id, uint64_t id);
Rather than two calls, why not one hook with enable/disable bool argument.
Lots of the code ends up being duplicated.
> +/**
> + * Query the state of the xstat counter.
> + *
> + * @param port_id The port to look up statistics from
> + * @param id The ID of the counter to query
> + * @return
> + * - (0) xstat is enabled
> + * - (1) xstat is disabled
> + * - (-ENOTSUP) enable/disabling is not implemented
> + * - (-EINVAL) xstat id is invalid
> + */
> +__rte_experimental
> +int rte_eth_xstats_query_state(uint16_t port_id, uint64_t id);
> +
> /**
> * Reset extended statistics of an Ethernet device.
> *
> diff --git a/lib/ethdev/version.map b/lib/ethdev/version.map
> index 12f48c70a0..51eeaf2103 100644
> --- a/lib/ethdev/version.map
> +++ b/lib/ethdev/version.map
> @@ -337,6 +337,9 @@ EXPERIMENTAL {
> rte_eth_timesync_adjust_freq;
> rte_flow_async_create_by_index_with_pattern;
> rte_tm_node_query;
> + rte_eth_xstats_enable_counter;
> + rte_eth_xstats_disable_counter;
> + rte_eth_xstats_query_state;
> };
>
> INTERNAL {
prev parent reply other threads:[~2025-01-10 4:36 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-12-22 15:38 Shani Peretz
2024-12-22 16:46 ` Stephen Hemminger
2024-12-23 10:46 ` Shani Peretz
2024-12-24 17:19 ` Stephen Hemminger
2025-01-01 19:05 ` Shani Peretz
2025-01-10 4:36 ` Stephen Hemminger [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250109203630.13322c14@hermes.local \
--to=stephen@networkplumber.org \
--cc=aman.deep.singh@intel.com \
--cc=anatoly.burakov@intel.com \
--cc=andrew.rybchenko@oktetlabs.ru \
--cc=bingz@nvidia.com \
--cc=dev@dpdk.org \
--cc=dsosnowski@nvidia.com \
--cc=ferruh.yigit@amd.com \
--cc=matan@nvidia.com \
--cc=orika@nvidia.com \
--cc=shperetz@nvidia.com \
--cc=suanmingm@nvidia.com \
--cc=thomas@monjalon.net \
--cc=viacheslavo@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).