root/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */

#include "nv_param.h"
#include "mlx5_core.h"

enum {
        MLX5_CLASS_0_CTRL_ID_NV_GLOBAL_PCI_CONF               = 0x80,
        MLX5_CLASS_0_CTRL_ID_NV_GLOBAL_PCI_CAP                = 0x81,
        MLX5_CLASS_0_CTRL_ID_NV_SW_OFFLOAD_CONFIG             = 0x10a,
        MLX5_CLASS_0_CTRL_ID_NV_SW_OFFLOAD_CAP                = 0x10b,
        MLX5_CLASS_0_CTRL_ID_NV_SW_ACCELERATE_CONF            = 0x11d,

        MLX5_CLASS_3_CTRL_ID_NV_PF_PCI_CONF                   = 0x80,
};

struct mlx5_ifc_configuration_item_type_class_global_bits {
        u8         type_class[0x8];
        u8         parameter_index[0x18];
};

struct mlx5_ifc_configuration_item_type_class_per_host_pf_bits {
        u8         type_class[0x8];
        u8         pf_index[0x6];
        u8         pci_bus_index[0x8];
        u8         parameter_index[0xa];
};

union mlx5_ifc_config_item_type_auto_bits {
        struct mlx5_ifc_configuration_item_type_class_global_bits
                                configuration_item_type_class_global;
        struct mlx5_ifc_configuration_item_type_class_per_host_pf_bits
                                configuration_item_type_class_per_host_pf;
        u8 reserved_at_0[0x20];
};

enum {
        MLX5_ACCESS_MODE_NEXT = 0,
        MLX5_ACCESS_MODE_CURRENT,
        MLX5_ACCESS_MODE_DEFAULT,
};

struct mlx5_ifc_config_item_bits {
        u8         valid[0x2];
        u8         priority[0x2];
        u8         header_type[0x2];
        u8         ovr_en[0x1];
        u8         rd_en[0x1];
        u8         access_mode[0x2];
        u8         reserved_at_a[0x1];
        u8         writer_id[0x5];
        u8         version[0x4];
        u8         reserved_at_14[0x2];
        u8         host_id_valid[0x1];
        u8         length[0x9];

        union mlx5_ifc_config_item_type_auto_bits type;

        u8         reserved_at_40[0x10];
        u8         crc16[0x10];
};

struct mlx5_ifc_mnvda_reg_bits {
        struct mlx5_ifc_config_item_bits configuration_item_header;

        u8         configuration_item_data[64][0x20];
};

struct mlx5_ifc_nv_global_pci_conf_bits {
        u8         sriov_valid[0x1];
        u8         reserved_at_1[0x10];
        u8         per_pf_total_vf[0x1];
        u8         reserved_at_12[0xe];

        u8         sriov_en[0x1];
        u8         reserved_at_21[0xf];
        u8         total_vfs[0x10];

        u8         reserved_at_40[0x20];
};

struct mlx5_ifc_nv_global_pci_cap_bits {
        u8         max_vfs_per_pf_valid[0x1];
        u8         reserved_at_1[0x13];
        u8         per_pf_total_vf_supported[0x1];
        u8         reserved_at_15[0xb];

        u8         sriov_support[0x1];
        u8         reserved_at_21[0xf];
        u8         max_vfs_per_pf[0x10];

        u8         reserved_at_40[0x60];
};

struct mlx5_ifc_nv_pf_pci_conf_bits {
        u8         reserved_at_0[0x9];
        u8         pf_total_vf_en[0x1];
        u8         reserved_at_a[0x16];

        u8         reserved_at_20[0x20];

        u8         reserved_at_40[0x10];
        u8         total_vf[0x10];

        u8         reserved_at_60[0x20];
};

struct mlx5_ifc_nv_sw_offload_conf_bits {
        u8         ip_over_vxlan_port[0x10];
        u8         tunnel_ecn_copy_offload_disable[0x1];
        u8         pci_atomic_mode[0x3];
        u8         sr_enable[0x1];
        u8         ptp_cyc2realtime[0x1];
        u8         vector_calc_disable[0x1];
        u8         uctx_en[0x1];
        u8         prio_tag_required_en[0x1];
        u8         esw_fdb_ipv4_ttl_modify_enable[0x1];
        u8         mkey_by_name[0x1];
        u8         ip_over_vxlan_en[0x1];
        u8         one_qp_per_recovery[0x1];
        u8         cqe_compression[0x3];
        u8         tunnel_udp_entropy_proto_disable[0x1];
        u8         reserved_at_21[0x1];
        u8         ar_enable[0x1];
        u8         log_max_outstanding_wqe[0x5];
        u8         vf_migration[0x2];
        u8         log_tx_psn_win[0x6];
        u8         lro_log_timeout3[0x4];
        u8         lro_log_timeout2[0x4];
        u8         lro_log_timeout1[0x4];
        u8         lro_log_timeout0[0x4];
};

struct mlx5_ifc_nv_sw_offload_cap_bits {
        u8         reserved_at_0[0x19];
        u8         swp_l4_csum_mode_l4_only[0x1];
        u8         reserved_at_1a[0x6];
};

struct mlx5_ifc_nv_sw_accelerate_conf_bits {
        u8         swp_l4_csum_mode[0x2];
        u8         reserved_at_2[0x3e];
};

#define MNVDA_HDR_SZ \
        (MLX5_ST_SZ_BYTES(mnvda_reg) - \
         MLX5_BYTE_OFF(mnvda_reg, configuration_item_data))

#define MLX5_SET_CFG_ITEM_TYPE(_cls_name, _mnvda_ptr, _field, _val) \
        MLX5_SET(mnvda_reg, _mnvda_ptr, \
                 configuration_item_header.type.configuration_item_type_class_##_cls_name._field, \
                 _val)

#define MLX5_SET_CFG_HDR_LEN(_mnvda_ptr, _cls_name) \
        MLX5_SET(mnvda_reg, _mnvda_ptr, configuration_item_header.length, \
                 MLX5_ST_SZ_BYTES(_cls_name))

#define MLX5_GET_CFG_HDR_LEN(_mnvda_ptr) \
        MLX5_GET(mnvda_reg, _mnvda_ptr, configuration_item_header.length)

static int mlx5_nv_param_read(struct mlx5_core_dev *dev, void *mnvda,
                              size_t len)
{
        u32 param_idx, type_class;
        u32 header_len;
        void *cls_ptr;
        int err;

        if (WARN_ON(len > MLX5_ST_SZ_BYTES(mnvda_reg)) || len < MNVDA_HDR_SZ)
                return -EINVAL; /* A caller bug */

        err = mlx5_core_access_reg(dev, mnvda, len, mnvda, len, MLX5_REG_MNVDA,
                                   0, 0);
        if (!err)
                return 0;

        cls_ptr = MLX5_ADDR_OF(mnvda_reg, mnvda,
                               configuration_item_header.type.configuration_item_type_class_global);

        type_class = MLX5_GET(configuration_item_type_class_global, cls_ptr,
                              type_class);
        param_idx = MLX5_GET(configuration_item_type_class_global, cls_ptr,
                             parameter_index);
        header_len = MLX5_GET_CFG_HDR_LEN(mnvda);

        mlx5_core_warn(dev, "Failed to read mnvda reg: type_class 0x%x, param_idx 0x%x, header_len %u, err %d\n",
                       type_class, param_idx, header_len, err);

        return -EOPNOTSUPP;
}

static int mlx5_nv_param_write(struct mlx5_core_dev *dev, void *mnvda,
                               size_t len)
{
        if (WARN_ON(len > MLX5_ST_SZ_BYTES(mnvda_reg)) || len < MNVDA_HDR_SZ)
                return -EINVAL;

        if (WARN_ON(MLX5_GET_CFG_HDR_LEN(mnvda) == 0))
                return -EINVAL;

        return mlx5_core_access_reg(dev, mnvda, len, mnvda, len, MLX5_REG_MNVDA,
                                    0, 1);
}

static int
mlx5_nv_param_read_sw_offload_conf(struct mlx5_core_dev *dev, void *mnvda,
                                   size_t len)
{
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, type_class, 0);
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, parameter_index,
                               MLX5_CLASS_0_CTRL_ID_NV_SW_OFFLOAD_CONFIG);
        MLX5_SET_CFG_HDR_LEN(mnvda, nv_sw_offload_conf);

        return mlx5_nv_param_read(dev, mnvda, len);
}

static int
mlx5_nv_param_read_sw_offload_cap(struct mlx5_core_dev *dev, void *mnvda,
                                  size_t len)
{
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, type_class, 0);
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, parameter_index,
                               MLX5_CLASS_0_CTRL_ID_NV_SW_OFFLOAD_CAP);
        MLX5_SET_CFG_HDR_LEN(mnvda, nv_sw_offload_cap);

        return mlx5_nv_param_read(dev, mnvda, len);
}

static int
mlx5_nv_param_read_sw_accelerate_conf(struct mlx5_core_dev *dev, void *mnvda,
                                      size_t len, int access_mode)
{
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, type_class, 0);
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, parameter_index,
                               MLX5_CLASS_0_CTRL_ID_NV_SW_ACCELERATE_CONF);
        MLX5_SET_CFG_HDR_LEN(mnvda, nv_sw_accelerate_conf);
        MLX5_SET(mnvda_reg, mnvda, configuration_item_header.access_mode,
                 access_mode);

        return mlx5_nv_param_read(dev, mnvda, len);
}

static const char *const
        cqe_compress_str[] = { "balanced", "aggressive" };

static int
mlx5_nv_param_devlink_cqe_compress_get(struct devlink *devlink, u32 id,
                                       struct devlink_param_gset_ctx *ctx,
                                       struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        u8 value = U8_MAX;
        void *data;
        int err;

        err = mlx5_nv_param_read_sw_offload_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        value = MLX5_GET(nv_sw_offload_conf, data, cqe_compression);

        if (value >= ARRAY_SIZE(cqe_compress_str))
                return -EOPNOTSUPP;

        strscpy(ctx->val.vstr, cqe_compress_str[value], sizeof(ctx->val.vstr));
        return 0;
}

static int
mlx5_nv_param_devlink_cqe_compress_validate(struct devlink *devlink, u32 id,
                                            union devlink_param_value val,
                                            struct netlink_ext_ack *extack)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(cqe_compress_str); i++) {
                if (!strcmp(val.vstr, cqe_compress_str[i]))
                        return 0;
        }

        NL_SET_ERR_MSG_MOD(extack,
                           "Invalid value, supported values are balanced/aggressive");
        return -EOPNOTSUPP;
}

static int
mlx5_nv_param_devlink_cqe_compress_set(struct devlink *devlink, u32 id,
                                       struct devlink_param_gset_ctx *ctx,
                                       struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        int err = 0;
        void *data;
        u8 value;

        if (!strcmp(ctx->val.vstr, "aggressive"))
                value = 1;
        else /* balanced: can't be anything else already validated above */
                value = 0;

        err = mlx5_nv_param_read_sw_offload_conf(dev, mnvda, sizeof(mnvda));
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Failed to read sw_offload_conf mnvda reg");
                return err;
        }

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        MLX5_SET(nv_sw_offload_conf, data, cqe_compression, value);

        return mlx5_nv_param_write(dev, mnvda, sizeof(mnvda));
}

enum swp_l4_csum_mode {
        SWP_L4_CSUM_MODE_DEFAULT = 0,
        SWP_L4_CSUM_MODE_FULL_CSUM = 1,
        SWP_L4_CSUM_MODE_L4_ONLY = 2,
};

static const char *const
        swp_l4_csum_mode_str[] = { "default", "full_csum", "l4_only" };

static int
mlx5_swp_l4_csum_mode_get(struct devlink *devlink, u32 id,
                          int access_mode, u8 *value,
                          struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        void *data;
        int err;

        err = mlx5_nv_param_read_sw_accelerate_conf(dev, mnvda, sizeof(mnvda),
                                                    access_mode);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Failed to read sw_accelerate_conf mnvda reg");
                return err;
        }

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        *value = MLX5_GET(nv_sw_accelerate_conf, data, swp_l4_csum_mode);

        if (*value >= ARRAY_SIZE(swp_l4_csum_mode_str)) {
                NL_SET_ERR_MSG_FMT_MOD(extack,
                                       "Invalid swp_l4_csum_mode value %u read from device",
                                       *value);
                return -EINVAL;
        }

        return 0;
}

static int
mlx5_devlink_swp_l4_csum_mode_get(struct devlink *devlink, u32 id,
                                  struct devlink_param_gset_ctx *ctx,
                                  struct netlink_ext_ack *extack)
{
        u8 value;
        int err;

        err = mlx5_swp_l4_csum_mode_get(devlink, id, MLX5_ACCESS_MODE_NEXT,
                                        &value, extack);
        if (err)
                return err;

        strscpy(ctx->val.vstr, swp_l4_csum_mode_str[value],
                sizeof(ctx->val.vstr));
        return 0;
}

static int
mlx5_devlink_swp_l4_csum_mode_validate(struct devlink *devlink, u32 id,
                                       union devlink_param_value val,
                                       struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 cap[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        void *data;
        int err, i;

        for (i = 0; i < ARRAY_SIZE(swp_l4_csum_mode_str); i++) {
                if (!strcmp(val.vstr, swp_l4_csum_mode_str[i]))
                        break;
        }

        if (i >= ARRAY_SIZE(swp_l4_csum_mode_str) ||
            i == SWP_L4_CSUM_MODE_DEFAULT) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Invalid value, supported values are full_csum/l4_only");
                return -EINVAL;
        }

        if (i == SWP_L4_CSUM_MODE_L4_ONLY) {
                err = mlx5_nv_param_read_sw_offload_cap(dev, cap, sizeof(cap));
                if (err) {
                        NL_SET_ERR_MSG_MOD(extack,
                                           "Failed to read sw_offload_cap");
                        return err;
                }

                data = MLX5_ADDR_OF(mnvda_reg, cap, configuration_item_data);
                if (!MLX5_GET(nv_sw_offload_cap, data, swp_l4_csum_mode_l4_only)) {
                        NL_SET_ERR_MSG_MOD(extack,
                                           "l4_only mode is not supported on this device");
                        return -EOPNOTSUPP;
                }
        }

        return 0;
}

static int
mlx5_swp_l4_csum_mode_set(struct devlink *devlink, u32 id, u8 value,
                          struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        void *data;
        int err;

        err = mlx5_nv_param_read_sw_accelerate_conf(dev, mnvda, sizeof(mnvda),
                                                    MLX5_ACCESS_MODE_NEXT);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Failed to read sw_accelerate_conf mnvda reg");
                return err;
        }

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        MLX5_SET(nv_sw_accelerate_conf, data, swp_l4_csum_mode, value);

        err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda));
        if (err)
                NL_SET_ERR_MSG_MOD(extack,
                                   "Failed to write sw_accelerate_conf mnvda reg");

        return err;
}

static int
mlx5_devlink_swp_l4_csum_mode_set(struct devlink *devlink, u32 id,
                                  struct devlink_param_gset_ctx *ctx,
                                  struct netlink_ext_ack *extack)
{
        u8 value;

        if (!strcmp(ctx->val.vstr, "full_csum"))
                value = SWP_L4_CSUM_MODE_FULL_CSUM;
        else
                value = SWP_L4_CSUM_MODE_L4_ONLY;

        return mlx5_swp_l4_csum_mode_set(devlink, id, value, extack);
}

static int
mlx5_devlink_swp_l4_csum_mode_get_default(struct devlink *devlink, u32 id,
                                          struct devlink_param_gset_ctx *ctx,
                                          struct netlink_ext_ack *extack)
{
        u8 value;
        int err;

        err = mlx5_swp_l4_csum_mode_get(devlink, id, MLX5_ACCESS_MODE_DEFAULT,
                                        &value, extack);
        if (err)
                return err;

        strscpy(ctx->val.vstr, swp_l4_csum_mode_str[value],
                sizeof(ctx->val.vstr));
        return 0;
}

static int
mlx5_devlink_swp_l4_csum_mode_set_default(struct devlink *devlink, u32 id,
                                          enum devlink_param_cmode cmode,
                                          struct netlink_ext_ack *extack)
{
        u8 value;
        int err;

        err = mlx5_swp_l4_csum_mode_get(devlink, id, MLX5_ACCESS_MODE_DEFAULT,
                                        &value, extack);
        if (err)
                return err;

        return mlx5_swp_l4_csum_mode_set(devlink, id, value, extack);
}

static int mlx5_nv_param_read_global_pci_conf(struct mlx5_core_dev *dev,
                                              void *mnvda, size_t len)
{
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, type_class, 0);
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, parameter_index,
                               MLX5_CLASS_0_CTRL_ID_NV_GLOBAL_PCI_CONF);
        MLX5_SET_CFG_HDR_LEN(mnvda, nv_global_pci_conf);

        return mlx5_nv_param_read(dev, mnvda, len);
}

static int mlx5_nv_param_read_global_pci_cap(struct mlx5_core_dev *dev,
                                             void *mnvda, size_t len)
{
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, type_class, 0);
        MLX5_SET_CFG_ITEM_TYPE(global, mnvda, parameter_index,
                               MLX5_CLASS_0_CTRL_ID_NV_GLOBAL_PCI_CAP);
        MLX5_SET_CFG_HDR_LEN(mnvda, nv_global_pci_cap);

        return mlx5_nv_param_read(dev, mnvda, len);
}

static int mlx5_nv_param_read_per_host_pf_conf(struct mlx5_core_dev *dev,
                                               void *mnvda, size_t len)
{
        MLX5_SET_CFG_ITEM_TYPE(per_host_pf, mnvda, type_class, 3);
        MLX5_SET_CFG_ITEM_TYPE(per_host_pf, mnvda, parameter_index,
                               MLX5_CLASS_3_CTRL_ID_NV_PF_PCI_CONF);
        MLX5_SET_CFG_HDR_LEN(mnvda, nv_pf_pci_conf);

        return mlx5_nv_param_read(dev, mnvda, len);
}

static int mlx5_devlink_enable_sriov_get(struct devlink *devlink, u32 id,
                                         struct devlink_param_gset_ctx *ctx,
                                         struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        bool sriov_en = false;
        void *data;
        int err;

        err = mlx5_nv_param_read_global_pci_cap(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        if (!MLX5_GET(nv_global_pci_cap, data, sriov_support)) {
                ctx->val.vbool = false;
                return 0;
        }

        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        sriov_en = MLX5_GET(nv_global_pci_conf, data, sriov_en);
        if (!MLX5_GET(nv_global_pci_conf, data, per_pf_total_vf)) {
                ctx->val.vbool = sriov_en;
                return 0;
        }

        /* SRIOV is per PF */
        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        ctx->val.vbool = sriov_en &&
                         MLX5_GET(nv_pf_pci_conf, data, pf_total_vf_en);
        return 0;
}

static int mlx5_devlink_enable_sriov_set(struct devlink *devlink, u32 id,
                                         struct devlink_param_gset_ctx *ctx,
                                         struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        bool per_pf_support;
        void *cap, *data;
        int err;

        err = mlx5_nv_param_read_global_pci_cap(dev, mnvda, sizeof(mnvda));
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Failed to read global PCI capability");
                return err;
        }

        cap = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        per_pf_support = MLX5_GET(nv_global_pci_cap, cap,
                                  per_pf_total_vf_supported);

        if (!MLX5_GET(nv_global_pci_cap, cap, sriov_support)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "SRIOV is not supported on this device");
                return -EOPNOTSUPP;
        }

        if (!per_pf_support) {
                /* We don't allow global SRIOV setting on per PF devlink */
                NL_SET_ERR_MSG_MOD(extack,
                                   "SRIOV is not per PF on this device");
                return -EOPNOTSUPP;
        }

        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, sizeof(mnvda));
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Unable to read global PCI configuration");
                return err;
        }

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);

        /* setup per PF sriov mode */
        MLX5_SET(nv_global_pci_conf, data, sriov_valid, 1);
        MLX5_SET(nv_global_pci_conf, data, sriov_en, 1);
        MLX5_SET(nv_global_pci_conf, data, per_pf_total_vf, 1);

        err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda));
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Unable to write global PCI configuration");
                return err;
        }

        /* enable/disable sriov on this PF */
        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda));
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Unable to read per host PF configuration");
                return err;
        }
        MLX5_SET(nv_pf_pci_conf, data, pf_total_vf_en, ctx->val.vbool);
        return mlx5_nv_param_write(dev, mnvda, sizeof(mnvda));
}

static int mlx5_devlink_total_vfs_get(struct devlink *devlink, u32 id,
                                      struct devlink_param_gset_ctx *ctx,
                                      struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)] = {};
        void *data;
        int err;

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);

        err = mlx5_nv_param_read_global_pci_cap(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        if (!MLX5_GET(nv_global_pci_cap, data, sriov_support)) {
                ctx->val.vu32 = 0;
                return 0;
        }

        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        if (!MLX5_GET(nv_global_pci_conf, data, per_pf_total_vf)) {
                ctx->val.vu32 = MLX5_GET(nv_global_pci_conf, data, total_vfs);
                return 0;
        }

        /* SRIOV is per PF */
        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        ctx->val.vu32 = MLX5_GET(nv_pf_pci_conf, data, total_vf);

        return 0;
}

static int mlx5_devlink_total_vfs_set(struct devlink *devlink, u32 id,
                                      struct devlink_param_gset_ctx *ctx,
                                      struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 mnvda[MLX5_ST_SZ_DW(mnvda_reg)];
        void *data;
        int err;

        err = mlx5_nv_param_read_global_pci_cap(dev, mnvda, sizeof(mnvda));
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to read global pci cap");
                return err;
        }

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        if (!MLX5_GET(nv_global_pci_cap, data, sriov_support)) {
                NL_SET_ERR_MSG_MOD(extack, "Not configurable on this device");
                return -EOPNOTSUPP;
        }

        if (!MLX5_GET(nv_global_pci_cap, data, per_pf_total_vf_supported)) {
                /* We don't allow global SRIOV setting on per PF devlink */
                NL_SET_ERR_MSG_MOD(extack,
                                   "SRIOV is not per PF on this device");
                return -EOPNOTSUPP;
        }

        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_global_pci_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        MLX5_SET(nv_global_pci_conf, data, sriov_valid, 1);
        MLX5_SET(nv_global_pci_conf, data, per_pf_total_vf, 1);

        err = mlx5_nv_param_write(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        memset(mnvda, 0, sizeof(mnvda));
        err = mlx5_nv_param_read_per_host_pf_conf(dev, mnvda, sizeof(mnvda));
        if (err)
                return err;

        data = MLX5_ADDR_OF(mnvda_reg, mnvda, configuration_item_data);
        MLX5_SET(nv_pf_pci_conf, data, total_vf, ctx->val.vu32);
        return mlx5_nv_param_write(dev, mnvda, sizeof(mnvda));
}

static int mlx5_devlink_total_vfs_validate(struct devlink *devlink, u32 id,
                                           union devlink_param_value val,
                                           struct netlink_ext_ack *extack)
{
        struct mlx5_core_dev *dev = devlink_priv(devlink);
        u32 cap[MLX5_ST_SZ_DW(mnvda_reg)];
        void *data;
        u16 max;
        int err;

        data = MLX5_ADDR_OF(mnvda_reg, cap, configuration_item_data);

        err = mlx5_nv_param_read_global_pci_cap(dev, cap, sizeof(cap));
        if (err)
                return err;

        if (!MLX5_GET(nv_global_pci_cap, data, max_vfs_per_pf_valid))
                return 0; /* optimistic, but set might fail later */

        max = MLX5_GET(nv_global_pci_cap, data, max_vfs_per_pf);
        if (val.vu16 > max) {
                NL_SET_ERR_MSG_FMT_MOD(extack,
                                       "Max allowed by device is %u", max);
                return -EINVAL;
        }

        return 0;
}

static const struct devlink_param mlx5_nv_param_devlink_params[] = {
        DEVLINK_PARAM_GENERIC(ENABLE_SRIOV, BIT(DEVLINK_PARAM_CMODE_PERMANENT),
                              mlx5_devlink_enable_sriov_get,
                              mlx5_devlink_enable_sriov_set, NULL),
        DEVLINK_PARAM_GENERIC(TOTAL_VFS, BIT(DEVLINK_PARAM_CMODE_PERMANENT),
                              mlx5_devlink_total_vfs_get,
                              mlx5_devlink_total_vfs_set,
                              mlx5_devlink_total_vfs_validate),
        DEVLINK_PARAM_DRIVER(MLX5_DEVLINK_PARAM_ID_CQE_COMPRESSION_TYPE,
                             "cqe_compress_type", DEVLINK_PARAM_TYPE_STRING,
                             BIT(DEVLINK_PARAM_CMODE_PERMANENT),
                             mlx5_nv_param_devlink_cqe_compress_get,
                             mlx5_nv_param_devlink_cqe_compress_set,
                             mlx5_nv_param_devlink_cqe_compress_validate),
        DEVLINK_PARAM_DRIVER_WITH_DEFAULTS(MLX5_DEVLINK_PARAM_ID_SWP_L4_CSUM_MODE,
                                           "swp_l4_csum_mode", DEVLINK_PARAM_TYPE_STRING,
                                           BIT(DEVLINK_PARAM_CMODE_PERMANENT),
                                           mlx5_devlink_swp_l4_csum_mode_get,
                                           mlx5_devlink_swp_l4_csum_mode_set,
                                           mlx5_devlink_swp_l4_csum_mode_validate,
                                           mlx5_devlink_swp_l4_csum_mode_get_default,
                                           mlx5_devlink_swp_l4_csum_mode_set_default),
};

int mlx5_nv_param_register_dl_params(struct devlink *devlink)
{
        if (!mlx5_core_is_pf(devlink_priv(devlink)))
                return 0;

        return devl_params_register(devlink, mlx5_nv_param_devlink_params,
                                    ARRAY_SIZE(mlx5_nv_param_devlink_params));
}

void mlx5_nv_param_unregister_dl_params(struct devlink *devlink)
{
        if (!mlx5_core_is_pf(devlink_priv(devlink)))
                return;

        devl_params_unregister(devlink, mlx5_nv_param_devlink_params,
                               ARRAY_SIZE(mlx5_nv_param_devlink_params));
}