root/drivers/net/ethernet/mellanox/mlx5/core/en/monitor_stats.c
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2018 Mellanox Technologies. */

#include "en.h"
#include "monitor_stats.h"
#include "lib/eq.h"

/* Driver will set the following watch counters list:
 * Ppcnt.802_3:
 * a_in_range_length_errors      Type: 0x0, Counter:  0x0, group_id = N/A
 * a_out_of_range_length_field   Type: 0x0, Counter:  0x1, group_id = N/A
 * a_frame_too_long_errors       Type: 0x0, Counter:  0x2, group_id = N/A
 * a_frame_check_sequence_errors Type: 0x0, Counter:  0x3, group_id = N/A
 * a_alignment_errors            Type: 0x0, Counter:  0x4, group_id = N/A
 * if_out_discards               Type: 0x0, Counter:  0x5, group_id = N/A
 * Q_Counters:
 * Q[index].rx_out_of_buffer   Type: 0x1, Counter:  0x4, group_id = counter_ix
 */

#define NUM_REQ_PPCNT_COUNTER_S1 MLX5_CMD_SET_MONITOR_NUM_PPCNT_COUNTER_SET1
#define NUM_REQ_Q_COUNTERS_S1    MLX5_CMD_SET_MONITOR_NUM_Q_COUNTERS_SET1

static int mlx5e_monitor_counter_cap(struct mlx5_core_dev *mdev)
{
        if (!MLX5_CAP_GEN(mdev, max_num_of_monitor_counters))
                return false;
        if (MLX5_CAP_PCAM_REG(mdev, ppcnt) &&
            MLX5_CAP_GEN(mdev, num_ppcnt_monitor_counters) <
            NUM_REQ_PPCNT_COUNTER_S1)
                return false;
        if (MLX5_CAP_GEN(mdev, num_q_monitor_counters) <
            NUM_REQ_Q_COUNTERS_S1)
                return false;
        return true;
}

int mlx5e_monitor_counter_supported(struct mlx5e_priv *priv)
{
        struct mlx5_core_dev *pos;
        int i;

        mlx5_sd_for_each_dev(i, priv->mdev, pos)
                if (!mlx5e_monitor_counter_cap(pos))
                        return false;
        return true;
}

static void mlx5e_monitor_counter_arm(struct mlx5_core_dev *mdev)
{
        u32 in[MLX5_ST_SZ_DW(arm_monitor_counter_in)] = {};

        MLX5_SET(arm_monitor_counter_in, in, opcode,
                 MLX5_CMD_OP_ARM_MONITOR_COUNTER);
        mlx5_cmd_exec_in(mdev, arm_monitor_counter, in);
}

static void mlx5e_monitor_counters_work(struct work_struct *work)
{
        struct mlx5e_priv *priv = container_of(work, struct mlx5e_priv,
                                               monitor_counters_work);
        struct mlx5_core_dev *pos;
        int i;

        mutex_lock(&priv->state_lock);
        mlx5e_stats_update_ndo_stats(priv);
        mutex_unlock(&priv->state_lock);
        mlx5_sd_for_each_dev(i, priv->mdev, pos)
                mlx5e_monitor_counter_arm(pos);
}

static int mlx5e_monitor_event_handler(struct notifier_block *nb,
                                       unsigned long event, void *eqe)
{
        struct mlx5e_priv *priv = mlx5_nb_cof(nb, struct mlx5e_priv,
                                              monitor_counters_nb);
        queue_work(priv->wq, &priv->monitor_counters_work);
        return NOTIFY_OK;
}

static int fill_monitor_counter_ppcnt_set1(int cnt, u32 *in)
{
        enum mlx5_monitor_counter_ppcnt ppcnt_cnt;

        for (ppcnt_cnt = 0;
             ppcnt_cnt < NUM_REQ_PPCNT_COUNTER_S1;
             ppcnt_cnt++, cnt++) {
                MLX5_SET(set_monitor_counter_in, in,
                         monitor_counter[cnt].type,
                         MLX5_QUERY_MONITOR_CNT_TYPE_PPCNT);
                MLX5_SET(set_monitor_counter_in, in,
                         monitor_counter[cnt].counter,
                         ppcnt_cnt);
        }
        return ppcnt_cnt;
}

static int fill_monitor_counter_q_counter_set1(int cnt, int q_counter, u32 *in)
{
        MLX5_SET(set_monitor_counter_in, in,
                 monitor_counter[cnt].type,
                 MLX5_QUERY_MONITOR_CNT_TYPE_Q_COUNTER);
        MLX5_SET(set_monitor_counter_in, in,
                 monitor_counter[cnt].counter,
                 MLX5_QUERY_MONITOR_Q_COUNTER_RX_OUT_OF_BUFFER);
        MLX5_SET(set_monitor_counter_in, in,
                 monitor_counter[cnt].counter_group_id,
                 q_counter);
        return 1;
}

/* check if mlx5e_monitor_counter_supported before calling this function*/
static void mlx5e_set_monitor_counter(struct mlx5_core_dev *mdev, int q_counter)
{
        int max_num_of_counters = MLX5_CAP_GEN(mdev, max_num_of_monitor_counters);
        int num_q_counters      = MLX5_CAP_GEN(mdev, num_q_monitor_counters);
        int num_ppcnt_counters  = !MLX5_CAP_PCAM_REG(mdev, ppcnt) ? 0 :
                                  MLX5_CAP_GEN(mdev, num_ppcnt_monitor_counters);
        u32 in[MLX5_ST_SZ_DW(set_monitor_counter_in)] = {};
        int cnt = 0;

        if (num_ppcnt_counters  >=  NUM_REQ_PPCNT_COUNTER_S1 &&
            max_num_of_counters >= (NUM_REQ_PPCNT_COUNTER_S1 + cnt))
                cnt += fill_monitor_counter_ppcnt_set1(cnt, in);

        if (num_q_counters      >=  NUM_REQ_Q_COUNTERS_S1 &&
            max_num_of_counters >= (NUM_REQ_Q_COUNTERS_S1 + cnt) &&
            q_counter)
                cnt += fill_monitor_counter_q_counter_set1(cnt, q_counter, in);

        MLX5_SET(set_monitor_counter_in, in, num_of_counters, cnt);
        MLX5_SET(set_monitor_counter_in, in, opcode,
                 MLX5_CMD_OP_SET_MONITOR_COUNTER);

        mlx5_cmd_exec_in(mdev, set_monitor_counter, in);
}

/* check if mlx5e_monitor_counter_supported before calling this function*/
void mlx5e_monitor_counter_init(struct mlx5e_priv *priv)
{
        struct mlx5_core_dev *pos;
        int i;

        INIT_WORK(&priv->monitor_counters_work, mlx5e_monitor_counters_work);
        MLX5_NB_INIT(&priv->monitor_counters_nb, mlx5e_monitor_event_handler,
                     MONITOR_COUNTER);
        mlx5_sd_for_each_dev(i, priv->mdev, pos) {
                mlx5_eq_notifier_register(pos, &priv->monitor_counters_nb);
                mlx5e_set_monitor_counter(pos, priv->q_counter[i]);
                mlx5e_monitor_counter_arm(pos);
        }
        queue_work(priv->wq, &priv->update_stats_work);
}

/* check if mlx5e_monitor_counter_supported before calling this function*/
void mlx5e_monitor_counter_cleanup(struct mlx5e_priv *priv)
{
        u32 in[MLX5_ST_SZ_DW(set_monitor_counter_in)] = {};
        struct mlx5_core_dev *pos;
        int i;

        MLX5_SET(set_monitor_counter_in, in, opcode,
                 MLX5_CMD_OP_SET_MONITOR_COUNTER);

        mlx5_sd_for_each_dev(i, priv->mdev, pos) {
                mlx5_cmd_exec_in(pos, set_monitor_counter, in);
                mlx5_eq_notifier_unregister(pos, &priv->monitor_counters_nb);
        }
        cancel_work_sync(&priv->monitor_counters_work);
}