root/drivers/net/ethernet/mellanox/mlx5/core/lib/geneve.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2019 Mellanox Technologies. */

#include <linux/kernel.h>
#include "mlx5_core.h"
#include "geneve.h"

struct mlx5_geneve {
        struct mlx5_core_dev *mdev;
        __be16 opt_class;
        u8 opt_type;
        u32 obj_id;
        struct mutex sync_lock; /* protect GENEVE obj operations */
        u32 refcount;
};

static int mlx5_geneve_tlv_option_create(struct mlx5_core_dev *mdev,
                                         __be16 class,
                                         u8 type,
                                         u8 len)
{
        u32 in[MLX5_ST_SZ_DW(create_geneve_tlv_option_in)] = {};
        u32 out[MLX5_ST_SZ_DW(general_obj_out_cmd_hdr)] = {};
        u64 general_obj_types;
        void *hdr, *opt;
        u16 obj_id;
        int err;

        general_obj_types = MLX5_CAP_GEN_64(mdev, general_obj_types);
        if (!(general_obj_types & MLX5_GENERAL_OBJ_TYPES_CAP_GENEVE_TLV_OPT))
                return -EINVAL;

        hdr = MLX5_ADDR_OF(create_geneve_tlv_option_in, in, hdr);
        opt = MLX5_ADDR_OF(create_geneve_tlv_option_in, in, geneve_tlv_opt);

        MLX5_SET(general_obj_in_cmd_hdr, hdr, opcode, MLX5_CMD_OP_CREATE_GENERAL_OBJECT);
        MLX5_SET(general_obj_in_cmd_hdr, hdr, obj_type, MLX5_OBJ_TYPE_GENEVE_TLV_OPT);

        MLX5_SET(geneve_tlv_option, opt, option_class, be16_to_cpu(class));
        MLX5_SET(geneve_tlv_option, opt, option_type, type);
        MLX5_SET(geneve_tlv_option, opt, option_data_length, len);

        err = mlx5_cmd_exec(mdev, in, sizeof(in), out, sizeof(out));
        if (err)
                return err;

        obj_id = MLX5_GET(general_obj_out_cmd_hdr, out, obj_id);
        return obj_id;
}

static void mlx5_geneve_tlv_option_destroy(struct mlx5_core_dev *mdev, u16 obj_id)
{
        u32 out[MLX5_ST_SZ_DW(general_obj_out_cmd_hdr)] = {};
        u32 in[MLX5_ST_SZ_DW(general_obj_in_cmd_hdr)] = {};

        MLX5_SET(general_obj_in_cmd_hdr, in, opcode, MLX5_CMD_OP_DESTROY_GENERAL_OBJECT);
        MLX5_SET(general_obj_in_cmd_hdr, in, obj_type, MLX5_OBJ_TYPE_GENEVE_TLV_OPT);
        MLX5_SET(general_obj_in_cmd_hdr, in, obj_id, obj_id);

        mlx5_cmd_exec(mdev, in, sizeof(in), out, sizeof(out));
}

int mlx5_geneve_tlv_option_add(struct mlx5_geneve *geneve, struct geneve_opt *opt)
{
        int res = 0;

        if (IS_ERR_OR_NULL(geneve))
                return -EOPNOTSUPP;

        mutex_lock(&geneve->sync_lock);

        if (geneve->refcount) {
                if (geneve->opt_class == opt->opt_class &&
                    geneve->opt_type == opt->type) {
                        /* We already have TLV options obj allocated */
                        geneve->refcount++;
                } else {
                        /* TLV options obj allocated, but its params
                         * do not match the new request.
                         * We support only one such object.
                         */
                        mlx5_core_warn(geneve->mdev,
                                       "Won't create Geneve TLV opt object with class:type:len = 0x%x:0x%x:%d (another class:type already exists)\n",
                                       be16_to_cpu(opt->opt_class),
                                       opt->type,
                                       opt->length);
                        res = -EOPNOTSUPP;
                        goto unlock;
                }
        } else {
                /* We don't have any TLV options obj allocated */

                res = mlx5_geneve_tlv_option_create(geneve->mdev,
                                                    opt->opt_class,
                                                    opt->type,
                                                    opt->length);
                if (res < 0) {
                        mlx5_core_warn(geneve->mdev,
                                       "Failed creating Geneve TLV opt object class:type:len = 0x%x:0x%x:%d (err=%d)\n",
                                       be16_to_cpu(opt->opt_class),
                                       opt->type, opt->length, res);
                        goto unlock;
                }
                geneve->opt_class = opt->opt_class;
                geneve->opt_type = opt->type;
                geneve->obj_id = res;
                geneve->refcount++;
                res = 0;
        }

unlock:
        mutex_unlock(&geneve->sync_lock);
        return res;
}

void mlx5_geneve_tlv_option_del(struct mlx5_geneve *geneve)
{
        if (IS_ERR_OR_NULL(geneve))
                return;

        mutex_lock(&geneve->sync_lock);
        if (--geneve->refcount == 0) {
                /* We've just removed the last user of Geneve option.
                 * Now delete the object in FW.
                 */
                mlx5_geneve_tlv_option_destroy(geneve->mdev, geneve->obj_id);

                geneve->opt_class = 0;
                geneve->opt_type = 0;
                geneve->obj_id = 0;
        }
        mutex_unlock(&geneve->sync_lock);
}

struct mlx5_geneve *mlx5_geneve_create(struct mlx5_core_dev *mdev)
{
        struct mlx5_geneve *geneve = kzalloc_obj(*geneve);

        if (!geneve)
                return ERR_PTR(-ENOMEM);
        geneve->mdev = mdev;
        mutex_init(&geneve->sync_lock);

        return geneve;
}

void mlx5_geneve_destroy(struct mlx5_geneve *geneve)
{
        if (IS_ERR_OR_NULL(geneve))
                return;

        /* Lockless since we are unloading */
        if (geneve->refcount)
                mlx5_geneve_tlv_option_destroy(geneve->mdev, geneve->obj_id);

        kfree(geneve);
}