| // 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++; |
| } |
| |
| 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(sizeof(*geneve), GFP_KERNEL); |
| |
| 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); |
| } |