// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
// Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

#include <linux/mlx5/device.h>
#include <linux/mlx5/vport.h>
#include "mlx5_core.h"
#include "eswitch.h"

static int esw_ipsec_vf_query_generic(struct mlx5_core_dev *dev, u16 vport_num, bool *result)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	void *hca_cap, *query_cap;
	int err;

	if (!MLX5_CAP_GEN(dev, vhca_resource_manager))
		return -EOPNOTSUPP;

	if (!mlx5_esw_ipsec_vf_offload_supported(dev)) {
		*result = false;
		return 0;
	}

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	if (!query_cap)
		return -ENOMEM;

	err = mlx5_vport_get_other_func_general_cap(dev, vport_num, query_cap);
	if (err)
		goto free;

	hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
	*result = MLX5_GET(cmd_hca_cap, hca_cap, ipsec_offload);
free:
	kvfree(query_cap);
	return err;
}

enum esw_vport_ipsec_offload {
	MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD,
	MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD,
};

int mlx5_esw_ipsec_vf_offload_get(struct mlx5_core_dev *dev, struct mlx5_vport *vport)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	void *hca_cap, *query_cap;
	bool ipsec_enabled = false;
	int err;

	/* Querying IPsec caps only makes sense when generic ipsec_offload
	 * HCA cap is enabled
	 */
	err = esw_ipsec_vf_query_generic(dev, vport->vport, &ipsec_enabled);
	if (err)
		return err;

	if (!ipsec_enabled) {
		vport->info.ipsec_crypto_enabled = false;
		vport->info.ipsec_packet_enabled = false;
		return 0;
	}

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	if (!query_cap)
		return -ENOMEM;

	err = mlx5_vport_get_other_func_cap(dev, vport->vport, query_cap, MLX5_CAP_IPSEC);
	if (err)
		goto free;

	hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
	vport->info.ipsec_crypto_enabled =
		MLX5_GET(ipsec_cap, hca_cap, ipsec_crypto_offload);
	vport->info.ipsec_packet_enabled =
		MLX5_GET(ipsec_cap, hca_cap, ipsec_full_offload);
free:
	kvfree(query_cap);
	return err;
}

static int esw_ipsec_vf_set_generic(struct mlx5_core_dev *dev, u16 vport_num, bool ipsec_ofld)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	int set_sz = MLX5_ST_SZ_BYTES(set_hca_cap_in);
	void *hca_cap, *query_cap, *cap;
	int ret;

	if (!MLX5_CAP_GEN(dev, vhca_resource_manager))
		return -EOPNOTSUPP;

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	hca_cap = kvzalloc(set_sz, GFP_KERNEL);
	if (!hca_cap || !query_cap) {
		ret = -ENOMEM;
		goto free;
	}

	ret = mlx5_vport_get_other_func_general_cap(dev, vport_num, query_cap);
	if (ret)
		goto free;

	cap = MLX5_ADDR_OF(set_hca_cap_in, hca_cap, capability);
	memcpy(cap, MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability),
	       MLX5_UN_SZ_BYTES(hca_cap_union));
	MLX5_SET(cmd_hca_cap, cap, ipsec_offload, ipsec_ofld);

	MLX5_SET(set_hca_cap_in, hca_cap, opcode, MLX5_CMD_OP_SET_HCA_CAP);
	MLX5_SET(set_hca_cap_in, hca_cap, other_function, 1);
	MLX5_SET(set_hca_cap_in, hca_cap, function_id, vport_num);

	MLX5_SET(set_hca_cap_in, hca_cap, op_mod,
		 MLX5_SET_HCA_CAP_OP_MOD_GENERAL_DEVICE << 1);
	ret = mlx5_cmd_exec_in(dev, set_hca_cap, hca_cap);
free:
	kvfree(hca_cap);
	kvfree(query_cap);
	return ret;
}

static int esw_ipsec_vf_set_bytype(struct mlx5_core_dev *dev, struct mlx5_vport *vport,
				   bool enable, enum esw_vport_ipsec_offload type)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	int set_sz = MLX5_ST_SZ_BYTES(set_hca_cap_in);
	void *hca_cap, *query_cap, *cap;
	int ret;

	if (!MLX5_CAP_GEN(dev, vhca_resource_manager))
		return -EOPNOTSUPP;

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	hca_cap = kvzalloc(set_sz, GFP_KERNEL);
	if (!hca_cap || !query_cap) {
		ret = -ENOMEM;
		goto free;
	}

	ret = mlx5_vport_get_other_func_cap(dev, vport->vport, query_cap, MLX5_CAP_IPSEC);
	if (ret)
		goto free;

	cap = MLX5_ADDR_OF(set_hca_cap_in, hca_cap, capability);
	memcpy(cap, MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability),
	       MLX5_UN_SZ_BYTES(hca_cap_union));

	switch (type) {
	case MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD:
		MLX5_SET(ipsec_cap, cap, ipsec_crypto_offload, enable);
		break;
	case MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD:
		MLX5_SET(ipsec_cap, cap, ipsec_full_offload, enable);
		break;
	default:
		ret = -EOPNOTSUPP;
		goto free;
	}

	MLX5_SET(set_hca_cap_in, hca_cap, opcode, MLX5_CMD_OP_SET_HCA_CAP);
	MLX5_SET(set_hca_cap_in, hca_cap, other_function, 1);
	MLX5_SET(set_hca_cap_in, hca_cap, function_id, vport->vport);

	MLX5_SET(set_hca_cap_in, hca_cap, op_mod,
		 MLX5_SET_HCA_CAP_OP_MOD_IPSEC << 1);
	ret = mlx5_cmd_exec_in(dev, set_hca_cap, hca_cap);
free:
	kvfree(hca_cap);
	kvfree(query_cap);
	return ret;
}

static int esw_ipsec_vf_crypto_aux_caps_set(struct mlx5_core_dev *dev, u16 vport_num, bool enable)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	int set_sz = MLX5_ST_SZ_BYTES(set_hca_cap_in);
	struct mlx5_eswitch *esw = dev->priv.eswitch;
	void *hca_cap, *query_cap, *cap;
	int ret;

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	hca_cap = kvzalloc(set_sz, GFP_KERNEL);
	if (!hca_cap || !query_cap) {
		ret = -ENOMEM;
		goto free;
	}

	ret = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_ETHERNET_OFFLOADS);
	if (ret)
		goto free;

	cap = MLX5_ADDR_OF(set_hca_cap_in, hca_cap, capability);
	memcpy(cap, MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability),
	       MLX5_UN_SZ_BYTES(hca_cap_union));
	MLX5_SET(per_protocol_networking_offload_caps, cap, insert_trailer, enable);
	MLX5_SET(set_hca_cap_in, hca_cap, opcode, MLX5_CMD_OP_SET_HCA_CAP);
	MLX5_SET(set_hca_cap_in, hca_cap, other_function, 1);
	MLX5_SET(set_hca_cap_in, hca_cap, function_id, vport_num);
	MLX5_SET(set_hca_cap_in, hca_cap, op_mod,
		 MLX5_SET_HCA_CAP_OP_MOD_ETHERNET_OFFLOADS << 1);
	ret = mlx5_cmd_exec_in(esw->dev, set_hca_cap, hca_cap);
free:
	kvfree(hca_cap);
	kvfree(query_cap);
	return ret;
}

static int esw_ipsec_vf_offload_set_bytype(struct mlx5_eswitch *esw, struct mlx5_vport *vport,
					   bool enable, enum esw_vport_ipsec_offload type)
{
	struct mlx5_core_dev *dev = esw->dev;
	int err;

	if (vport->vport == MLX5_VPORT_PF)
		return -EOPNOTSUPP;

	if (type == MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD) {
		err = esw_ipsec_vf_crypto_aux_caps_set(dev, vport->vport, enable);
		if (err)
			return err;
	}

	if (enable) {
		err = esw_ipsec_vf_set_generic(dev, vport->vport, enable);
		if (err)
			return err;
		err = esw_ipsec_vf_set_bytype(dev, vport, enable, type);
		if (err)
			return err;
	} else {
		err = esw_ipsec_vf_set_bytype(dev, vport, enable, type);
		if (err)
			return err;
		err = mlx5_esw_ipsec_vf_offload_get(dev, vport);
		if (err)
			return err;

		/* The generic ipsec_offload cap can be disabled only if both
		 * ipsec_crypto_offload and ipsec_full_offload aren't enabled.
		 */
		if (!vport->info.ipsec_crypto_enabled &&
		    !vport->info.ipsec_packet_enabled) {
			err = esw_ipsec_vf_set_generic(dev, vport->vport, enable);
			if (err)
				return err;
		}
	}

	switch (type) {
	case MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD:
		vport->info.ipsec_crypto_enabled = enable;
		break;
	case MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD:
		vport->info.ipsec_packet_enabled = enable;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int esw_ipsec_offload_supported(struct mlx5_core_dev *dev, u16 vport_num)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	void *hca_cap, *query_cap;
	int ret;

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	if (!query_cap)
		return -ENOMEM;

	ret = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_GENERAL);
	if (ret)
		goto free;

	hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
	if (!MLX5_GET(cmd_hca_cap, hca_cap, log_max_dek))
		ret = -EOPNOTSUPP;
free:
	kvfree(query_cap);
	return ret;
}

bool mlx5_esw_ipsec_vf_offload_supported(struct mlx5_core_dev *dev)
{
	/* Old firmware doesn't support ipsec_offload capability for VFs. This
	 * can be detected by checking reformat_add_esp_trasport capability -
	 * when this cap isn't supported it means firmware cannot be trusted
	 * about what it reports for ipsec_offload cap.
	 */
	return MLX5_CAP_FLOWTABLE_NIC_TX(dev, reformat_add_esp_trasport);
}

int mlx5_esw_ipsec_vf_crypto_offload_supported(struct mlx5_core_dev *dev,
					       u16 vport_num)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	void *hca_cap, *query_cap;
	int err;

	if (!mlx5_esw_ipsec_vf_offload_supported(dev))
		return -EOPNOTSUPP;

	err = esw_ipsec_offload_supported(dev, vport_num);
	if (err)
		return err;

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	if (!query_cap)
		return -ENOMEM;

	err = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_ETHERNET_OFFLOADS);
	if (err)
		goto free;

	hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
	if (!MLX5_GET(per_protocol_networking_offload_caps, hca_cap, swp))
		goto free;

free:
	kvfree(query_cap);
	return err;
}

int mlx5_esw_ipsec_vf_packet_offload_supported(struct mlx5_core_dev *dev,
					       u16 vport_num)
{
	int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
	void *hca_cap, *query_cap;
	int ret;

	if (!mlx5_esw_ipsec_vf_offload_supported(dev))
		return -EOPNOTSUPP;

	ret = esw_ipsec_offload_supported(dev, vport_num);
	if (ret)
		return ret;

	query_cap = kvzalloc(query_sz, GFP_KERNEL);
	if (!query_cap)
		return -ENOMEM;

	ret = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_FLOW_TABLE);
	if (ret)
		goto out;

	hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
	if (!MLX5_GET(flow_table_nic_cap, hca_cap, flow_table_properties_nic_receive.decap)) {
		ret = -EOPNOTSUPP;
		goto out;
	}

out:
	kvfree(query_cap);
	return ret;
}

int mlx5_esw_ipsec_vf_crypto_offload_set(struct mlx5_eswitch *esw, struct mlx5_vport *vport,
					 bool enable)
{
	return esw_ipsec_vf_offload_set_bytype(esw, vport, enable,
					       MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD);
}

int mlx5_esw_ipsec_vf_packet_offload_set(struct mlx5_eswitch *esw, struct mlx5_vport *vport,
					 bool enable)
{
	return esw_ipsec_vf_offload_set_bytype(esw, vport, enable,
					       MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD);
}
