root/sys/dev/mlx5/mlx5_core/mlx5_diagnostics.c
/*-
 * Copyright (c) 2013-2017, Mellanox Technologies, Ltd.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR OR CONTRIBUTORS 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 TORT (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 "opt_rss.h"
#include "opt_ratelimit.h"

#define _WANT_SFF_8024_ID

#include <dev/mlx5/driver.h>
#include <dev/mlx5/port.h>
#include <dev/mlx5/diagnostics.h>
#include <dev/mlx5/mlx5_core/mlx5_core.h>
#include <net/sff8472.h>

const struct mlx5_core_diagnostics_entry
        mlx5_core_pci_diagnostics_table[
                MLX5_CORE_PCI_DIAGNOSTICS_NUM] = {
        MLX5_CORE_PCI_DIAGNOSTICS(MLX5_CORE_DIAGNOSTICS_ENTRY)
};

const struct mlx5_core_diagnostics_entry
        mlx5_core_general_diagnostics_table[
                MLX5_CORE_GENERAL_DIAGNOSTICS_NUM] = {
        MLX5_CORE_GENERAL_DIAGNOSTICS(MLX5_CORE_DIAGNOSTICS_ENTRY)
};

static int mlx5_core_get_index_of_diag_counter(
        const struct mlx5_core_diagnostics_entry *entry,
        int size, u16 counter_id)
{
        int x;

        /* check for invalid counter ID */
        if (counter_id == 0)
                return -1;

        /* lookup counter ID in table */
        for (x = 0; x != size; x++) {
                if (entry[x].counter_id == counter_id)
                        return x;
        }
        return -1;
}

static void mlx5_core_put_diag_counter(
        const struct mlx5_core_diagnostics_entry *entry,
        u64 *array, int size, u16 counter_id, u64 value)
{
        int x;

        /* check for invalid counter ID */
        if (counter_id == 0)
                return;

        /* lookup counter ID in table */
        for (x = 0; x != size; x++) {
                if (entry[x].counter_id == counter_id) {
                        array[x] = value;
                        break;
                }
        }
}

int mlx5_core_set_diagnostics_full(struct mlx5_core_dev *dev,
                                   u8 enable_pci, u8 enable_general)
{
        void *diag_params_ctx;
        void *in;
        int numcounters;
        int inlen;
        int err;
        int x;
        int y;

        if (MLX5_CAP_GEN(dev, debug) == 0)
                return 0;

        numcounters = MLX5_CAP_GEN(dev, num_of_diagnostic_counters);
        if (numcounters == 0)
                return 0;

        inlen = MLX5_ST_SZ_BYTES(set_diagnostic_params_in) +
            MLX5_ST_SZ_BYTES(diagnostic_counter) * numcounters;
        in = mlx5_vzalloc(inlen);
        if (in == NULL)
                return -ENOMEM;

        diag_params_ctx = MLX5_ADDR_OF(set_diagnostic_params_in, in,
                                       diagnostic_params_ctx);

        MLX5_SET(diagnostic_params_context, diag_params_ctx,
                 enable, enable_pci || enable_general);
        MLX5_SET(diagnostic_params_context, diag_params_ctx,
                 single, 1);
        MLX5_SET(diagnostic_params_context, diag_params_ctx,
                 on_demand, 1);

        /* collect the counters we want to enable */
        for (x = y = 0; x != numcounters; x++) {
                u16 counter_id =
                        MLX5_CAP_DEBUG(dev, diagnostic_counter[x].counter_id);
                int index = -1;

                if (index < 0 && enable_pci != 0) {
                        /* check if counter ID exists in local table */
                        index = mlx5_core_get_index_of_diag_counter(
                            mlx5_core_pci_diagnostics_table,
                            MLX5_CORE_PCI_DIAGNOSTICS_NUM,
                            counter_id);
                }
                if (index < 0 && enable_general != 0) {
                        /* check if counter ID exists in local table */
                        index = mlx5_core_get_index_of_diag_counter(
                            mlx5_core_general_diagnostics_table,
                            MLX5_CORE_GENERAL_DIAGNOSTICS_NUM,
                            counter_id);
                }
                if (index < 0)
                        continue;

                MLX5_SET(diagnostic_params_context,
                         diag_params_ctx,
                         counter_id[y].counter_id,
                         counter_id);
                y++;
        }

        /* recompute input length */
        inlen = MLX5_ST_SZ_BYTES(set_diagnostic_params_in) +
            MLX5_ST_SZ_BYTES(diagnostic_counter) * y;

        /* set number of counters */
        MLX5_SET(diagnostic_params_context, diag_params_ctx,
                 num_of_counters, y);

        /* execute firmware command */
        err = mlx5_set_diagnostic_params(dev, in, inlen);

        kvfree(in);

        return err;
}

int mlx5_core_get_diagnostics_full(struct mlx5_core_dev *dev,
                                   union mlx5_core_pci_diagnostics *pdiag,
                                   union mlx5_core_general_diagnostics *pgen)
{
        void *out;
        void *in;
        int numcounters;
        int outlen;
        int inlen;
        int err;
        int x;

        if (MLX5_CAP_GEN(dev, debug) == 0)
                return 0;

        numcounters = MLX5_CAP_GEN(dev, num_of_diagnostic_counters);
        if (numcounters == 0)
                return 0;

        outlen = MLX5_ST_SZ_BYTES(query_diagnostic_counters_out) +
            MLX5_ST_SZ_BYTES(diagnostic_counter) * numcounters;

        out = mlx5_vzalloc(outlen);
        if (out == NULL)
                return -ENOMEM;

        err = mlx5_query_diagnostic_counters(dev, 1, 0, out, outlen);
        if (err == 0) {
                for (x = 0; x != numcounters; x++) {
                        u16 counter_id = MLX5_GET(
                            query_diagnostic_counters_out,
                            out, diag_counter[x].counter_id);
                        u64 counter_value = MLX5_GET64(
                            query_diagnostic_counters_out,
                            out, diag_counter[x].counter_value_h);

                        if (pdiag != NULL) {
                                mlx5_core_put_diag_counter(
                                    mlx5_core_pci_diagnostics_table,
                                    pdiag->array,
                                    MLX5_CORE_PCI_DIAGNOSTICS_NUM,
                                    counter_id, counter_value);
                        }
                        if (pgen != NULL) {
                                mlx5_core_put_diag_counter(
                                    mlx5_core_general_diagnostics_table,
                                    pgen->array,
                                    MLX5_CORE_GENERAL_DIAGNOSTICS_NUM,
                                    counter_id, counter_value);
                        }
                }
        }
        kvfree(out);

        if (pdiag != NULL) {
                inlen = MLX5_ST_SZ_BYTES(mpcnt_reg);
                outlen = MLX5_ST_SZ_BYTES(mpcnt_reg);

                in = mlx5_vzalloc(inlen);
                if (in == NULL)
                        return -ENOMEM;

                out = mlx5_vzalloc(outlen);
                if (out == NULL) {
                        kvfree(in);
                        return -ENOMEM;
                }
                MLX5_SET(mpcnt_reg, in, grp,
                         MLX5_PCIE_PERFORMANCE_COUNTERS_GROUP);

                err = mlx5_core_access_reg(dev, in, inlen, out, outlen,
                                           MLX5_REG_MPCNT, 0, 0);
                if (err == 0) {
                        void *pcounters = MLX5_ADDR_OF(mpcnt_reg, out,
                            counter_set.pcie_perf_counters);

                        pdiag->counter.rx_pci_errors =
                            MLX5_GET(pcie_perf_counters,
                                     pcounters, rx_errors);
                        pdiag->counter.tx_pci_errors =
                            MLX5_GET(pcie_perf_counters,
                                     pcounters, tx_errors);
                }
                MLX5_SET(mpcnt_reg, in, grp,
                         MLX5_PCIE_TIMERS_AND_STATES_COUNTERS_GROUP);

                err = mlx5_core_access_reg(dev, in, inlen, out, outlen,
                    MLX5_REG_MPCNT, 0, 0);
                if (err == 0) {
                        void *pcounters = MLX5_ADDR_OF(mpcnt_reg, out,
                            counter_set.pcie_timers_states);

                        pdiag->counter.tx_pci_non_fatal_errors =
                            MLX5_GET(pcie_timers_states,
                                     pcounters, non_fatal_err_msg_sent);
                        pdiag->counter.tx_pci_fatal_errors =
                            MLX5_GET(pcie_timers_states,
                                     pcounters, fatal_err_msg_sent);
                }
                kvfree(in);
                kvfree(out);
        }
        return 0;
}

int mlx5_core_supports_diagnostics(struct mlx5_core_dev *dev, u16 counter_id)
{
        int numcounters;
        int x;

        if (MLX5_CAP_GEN(dev, debug) == 0)
                return 0;

        /* check for any counter */
        if (counter_id == 0)
                return 1;

        numcounters = MLX5_CAP_GEN(dev, num_of_diagnostic_counters);

        /* check if counter ID exists in debug capability */
        for (x = 0; x != numcounters; x++) {
                if (MLX5_CAP_DEBUG(dev, diagnostic_counter[x].counter_id) ==
                    counter_id)
                        return 1;
        }
        return 0;                       /* not supported counter */
}

/*
 * Read the first three bytes of the eeprom in order to get the needed info
 * for the whole reading.
 * Byte 0 - Identifier byte
 * Byte 1 - Revision byte
 * Byte 2 - Status byte
 */
int
mlx5_get_eeprom_info(struct mlx5_core_dev *dev, struct mlx5_eeprom *eeprom)
{
        u32 data = 0;
        int size_read = 0;
        int ret;

        ret = mlx5_query_module_num(dev, &eeprom->module_num);
        if (ret) {
                mlx5_core_err(dev, "Failed query module error=%d\n", ret);
                return (-ret);
        }

        /* Read the first three bytes to get Identifier, Revision and Status */
        ret = mlx5_query_eeprom(dev, eeprom->i2c_addr, eeprom->page_num,
            eeprom->device_addr, MLX5_EEPROM_INFO_BYTES, eeprom->module_num, &data,
            &size_read);
        if (ret) {
                mlx5_core_err(dev,
                    "Failed query EEPROM module error=0x%x\n", ret);
                return (-ret);
        }

        switch (data & MLX5_EEPROM_IDENTIFIER_BYTE_MASK) {
        case SFF_8024_ID_QSFP:
                eeprom->type = MLX5_ETH_MODULE_SFF_8436;
                eeprom->len = MLX5_ETH_MODULE_SFF_8436_LEN;
                break;
        case SFF_8024_ID_QSFPPLUS:
        case SFF_8024_ID_QSFP28:
                if ((data & MLX5_EEPROM_IDENTIFIER_BYTE_MASK) == SFF_8024_ID_QSFP28 ||
                    ((data & MLX5_EEPROM_REVISION_ID_BYTE_MASK) >> 8) >= 0x3) {
                        eeprom->type = MLX5_ETH_MODULE_SFF_8636;
                        eeprom->len = MLX5_ETH_MODULE_SFF_8636_LEN;
                } else {
                        eeprom->type = MLX5_ETH_MODULE_SFF_8436;
                        eeprom->len = MLX5_ETH_MODULE_SFF_8436_LEN;
                }
                if ((data & MLX5_EEPROM_PAGE_3_VALID_BIT_MASK) == 0)
                        eeprom->page_valid = 1;
                break;
        case SFF_8024_ID_SFP:
                eeprom->type = MLX5_ETH_MODULE_SFF_8472;
                eeprom->len = MLX5_ETH_MODULE_SFF_8472_LEN;
                break;
        default:
                mlx5_core_err(dev, "Not recognized cable type = 0x%x(%s)\n",
                    data & MLX5_EEPROM_IDENTIFIER_BYTE_MASK,
                    sff_8024_id[data & MLX5_EEPROM_IDENTIFIER_BYTE_MASK]);
                return (EINVAL);
        }
        return (0);
}

/* Read both low and high pages of the eeprom */
int
mlx5_get_eeprom(struct mlx5_core_dev *dev, struct mlx5_eeprom *ee)
{
        int size_read = 0;
        int ret;

        if (ee->len == 0)
                return (EINVAL);

        /* Read low page of the eeprom */
        while (ee->device_addr < ee->len) {
                ret = mlx5_query_eeprom(dev, ee->i2c_addr, ee->page_num, ee->device_addr,
                    ee->len - ee->device_addr, ee->module_num,
                    ee->data + (ee->device_addr / 4), &size_read);
                if (ret) {
                        mlx5_core_err(dev,
                            "Failed reading EEPROM, error = 0x%02x\n", ret);
                        return (-ret);
                }
                ee->device_addr += size_read;
        }

        /* Read high page of the eeprom */
        if (ee->page_valid == 1) {
                ee->device_addr = MLX5_EEPROM_HIGH_PAGE_OFFSET;
                ee->page_num = MLX5_EEPROM_HIGH_PAGE;
                size_read = 0;
                while (ee->device_addr < MLX5_EEPROM_PAGE_LENGTH) {
                        ret = mlx5_query_eeprom(dev, ee->i2c_addr, ee->page_num,
                            ee->device_addr, MLX5_EEPROM_PAGE_LENGTH - ee->device_addr,
                            ee->module_num, ee->data + (ee->len / 4) +
                            ((ee->device_addr - MLX5_EEPROM_HIGH_PAGE_OFFSET) / 4),
                            &size_read);
                        if (ret) {
                                mlx5_core_err(dev,
                                    "Failed reading EEPROM, error = 0x%02x\n",
                                    ret);
                                return (-ret);
                        }
                        ee->device_addr += size_read;
                }
        }
        return (0);
}

/*
 * Read cable EEPROM module information by first inspecting the first
 * three bytes to get the initial information for a whole reading.
 * Information will be printed to dmesg.
 */
int
mlx5_read_eeprom(struct mlx5_core_dev *dev, struct mlx5_eeprom *eeprom)
{
        int error;

        eeprom->i2c_addr = MLX5_I2C_ADDR_LOW;
        eeprom->device_addr = 0;
        eeprom->page_num = MLX5_EEPROM_LOW_PAGE;
        eeprom->page_valid = 0;

        /* Read three first bytes to get important info */
        error = mlx5_get_eeprom_info(dev, eeprom);
        if (error) {
                mlx5_core_err(dev,
                    "Failed reading EEPROM initial information\n");
                return (error);
        }
        /*
         * Allocate needed length buffer and additional space for
         * page 0x03
         */
        eeprom->data = malloc(eeprom->len + MLX5_EEPROM_PAGE_LENGTH,
            M_MLX5_EEPROM, M_WAITOK | M_ZERO);

        /* Read the whole eeprom information */
        error = mlx5_get_eeprom(dev, eeprom);
        if (error) {
                mlx5_core_err(dev, "Failed reading EEPROM\n");
                error = 0;
                /*
                 * Continue printing partial information in case of
                 * an error
                 */
        }
        free(eeprom->data, M_MLX5_EEPROM);

        return (error);
}