// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2017, Mellanox Technologies inc. All rights reserved. */

#include "mlx5_core.h"
#include "en.h"
#include "ipsec.h"
#include "lib/crypto.h"
#include "lib/ipsec_fs_roce.h"
#include "fs_core.h"
#include "eswitch.h"

enum {
	MLX5_IPSEC_ASO_REMOVE_FLOW_PKT_CNT_OFFSET,
	MLX5_IPSEC_ASO_REMOVE_FLOW_SOFT_LFT_OFFSET,
};

u32 mlx5_ipsec_device_caps(struct mlx5_core_dev *mdev)
{
	u32 caps = 0;

	if (!MLX5_CAP_GEN(mdev, ipsec_offload))
		return 0;

	if (!MLX5_CAP_GEN(mdev, log_max_dek))
		return 0;

	if (!(MLX5_CAP_GEN_64(mdev, general_obj_types) &
	    MLX5_HCA_CAP_GENERAL_OBJECT_TYPES_IPSEC))
		return 0;

	if (!MLX5_CAP_FLOWTABLE_NIC_TX(mdev, ipsec_encrypt) ||
	    !MLX5_CAP_FLOWTABLE_NIC_RX(mdev, ipsec_decrypt))
		return 0;

	if (!MLX5_CAP_IPSEC(mdev, ipsec_crypto_esp_aes_gcm_128_encrypt) ||
	    !MLX5_CAP_IPSEC(mdev, ipsec_crypto_esp_aes_gcm_128_decrypt))
		return 0;

	if (MLX5_CAP_IPSEC(mdev, ipsec_crypto_offload) &&
	    MLX5_CAP_ETH(mdev, insert_trailer) && MLX5_CAP_ETH(mdev, swp))
		caps |= MLX5_IPSEC_CAP_CRYPTO;

	if (MLX5_CAP_IPSEC(mdev, ipsec_full_offload) &&
	    (mdev->priv.steering->mode == MLX5_FLOW_STEERING_MODE_DMFS ||
	     (mdev->priv.steering->mode == MLX5_FLOW_STEERING_MODE_SMFS &&
	     is_mdev_legacy_mode(mdev)))) {
		if (MLX5_CAP_FLOWTABLE_NIC_TX(mdev,
					      reformat_add_esp_trasport) &&
		    MLX5_CAP_FLOWTABLE_NIC_RX(mdev,
					      reformat_del_esp_trasport) &&
		    MLX5_CAP_FLOWTABLE_NIC_RX(mdev, decap))
			caps |= MLX5_IPSEC_CAP_PACKET_OFFLOAD;

		if (IS_ENABLED(CONFIG_MLX5_CLS_ACT) &&
		    ((MLX5_CAP_FLOWTABLE_NIC_TX(mdev, ignore_flow_level) &&
		      MLX5_CAP_FLOWTABLE_NIC_RX(mdev, ignore_flow_level)) ||
		     MLX5_CAP_ESW_FLOWTABLE_FDB(mdev, ignore_flow_level)))
			caps |= MLX5_IPSEC_CAP_PRIO;

		if (MLX5_CAP_FLOWTABLE_NIC_TX(mdev,
					      reformat_l2_to_l3_esp_tunnel) &&
		    MLX5_CAP_FLOWTABLE_NIC_RX(mdev,
					      reformat_l3_esp_tunnel_to_l2))
			caps |= MLX5_IPSEC_CAP_TUNNEL;

		if (MLX5_CAP_FLOWTABLE_NIC_TX(mdev,
					      reformat_add_esp_transport_over_udp) &&
		    MLX5_CAP_FLOWTABLE_NIC_RX(mdev,
					      reformat_del_esp_transport_over_udp))
			caps |= MLX5_IPSEC_CAP_ESPINUDP;
	}

	if (mlx5_get_roce_state(mdev) && mlx5_ipsec_fs_is_mpv_roce_supported(mdev) &&
	    MLX5_CAP_GEN_2(mdev, flow_table_type_2_type) & MLX5_FT_NIC_RX_2_NIC_RX_RDMA &&
	    MLX5_CAP_GEN_2(mdev, flow_table_type_2_type) & MLX5_FT_NIC_TX_RDMA_2_NIC_TX)
		caps |= MLX5_IPSEC_CAP_ROCE;

	if (!caps)
		return 0;

	if (MLX5_CAP_IPSEC(mdev, ipsec_esn))
		caps |= MLX5_IPSEC_CAP_ESN;

	/* We can accommodate up to 2^24 different IPsec objects
	 * because we use up to 24 bit in flow table metadata
	 * to hold the IPsec Object unique handle.
	 */
	WARN_ON_ONCE(MLX5_CAP_IPSEC(mdev, log_max_ipsec_offload) > 24);
	return caps;
}
EXPORT_SYMBOL_GPL(mlx5_ipsec_device_caps);

static void mlx5e_ipsec_packet_setup(void *obj, u32 pdn,
				     struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct mlx5_accel_esp_xfrm_attrs *attrs = &sa_entry->attrs;
	void *aso_ctx;

	aso_ctx = MLX5_ADDR_OF(ipsec_obj, obj, ipsec_aso);
	if (attrs->replay_esn.trigger) {
		MLX5_SET(ipsec_aso, aso_ctx, esn_event_arm, 1);

		if (attrs->dir == XFRM_DEV_OFFLOAD_IN) {
			MLX5_SET(ipsec_aso, aso_ctx, window_sz,
				 attrs->replay_esn.replay_window);
			MLX5_SET(ipsec_aso, aso_ctx, mode,
				 MLX5_IPSEC_ASO_REPLAY_PROTECTION);
		}
		MLX5_SET(ipsec_aso, aso_ctx, mode_parameter,
			 attrs->replay_esn.esn);
	}

	/* ASO context */
	MLX5_SET(ipsec_obj, obj, ipsec_aso_access_pd, pdn);
	MLX5_SET(ipsec_obj, obj, full_offload, 1);
	MLX5_SET(ipsec_aso, aso_ctx, valid, 1);
	/* MLX5_IPSEC_ASO_REG_C_4_5 is type C register that is used
	 * in flow steering to perform matching against. Please be
	 * aware that this register was chosen arbitrary and can't
	 * be used in other places as long as IPsec packet offload
	 * active.
	 */
	MLX5_SET(ipsec_obj, obj, aso_return_reg, MLX5_IPSEC_ASO_REG_C_4_5);
	if (attrs->dir == XFRM_DEV_OFFLOAD_OUT) {
		MLX5_SET(ipsec_aso, aso_ctx, mode, MLX5_IPSEC_ASO_INC_SN);
		if (!attrs->replay_esn.trigger)
			MLX5_SET(ipsec_aso, aso_ctx, mode_parameter,
				 sa_entry->esn_state.esn);
	}

	if (attrs->lft.hard_packet_limit != XFRM_INF) {
		MLX5_SET(ipsec_aso, aso_ctx, remove_flow_pkt_cnt,
			 attrs->lft.hard_packet_limit);
		MLX5_SET(ipsec_aso, aso_ctx, hard_lft_arm, 1);
		MLX5_SET(ipsec_aso, aso_ctx, remove_flow_enable, 1);
	}

	if (attrs->lft.soft_packet_limit != XFRM_INF) {
		MLX5_SET(ipsec_aso, aso_ctx, remove_flow_soft_lft,
			 attrs->lft.soft_packet_limit);

		MLX5_SET(ipsec_aso, aso_ctx, soft_lft_arm, 1);
	}
}

static int mlx5_create_ipsec_obj(struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct mlx5_accel_esp_xfrm_attrs *attrs = &sa_entry->attrs;
	struct mlx5_core_dev *mdev = mlx5e_ipsec_sa2dev(sa_entry);
	struct aes_gcm_keymat *aes_gcm = &attrs->aes_gcm;
	u32 out[MLX5_ST_SZ_DW(general_obj_out_cmd_hdr)];
	u32 in[MLX5_ST_SZ_DW(create_ipsec_obj_in)] = {};
	void *obj, *salt_p, *salt_iv_p;
	struct mlx5e_hw_objs *res;
	int err;

	obj = MLX5_ADDR_OF(create_ipsec_obj_in, in, ipsec_object);

	/* salt and seq_iv */
	salt_p = MLX5_ADDR_OF(ipsec_obj, obj, salt);
	memcpy(salt_p, &aes_gcm->salt, sizeof(aes_gcm->salt));

	MLX5_SET(ipsec_obj, obj, icv_length, MLX5_IPSEC_OBJECT_ICV_LEN_16B);
	salt_iv_p = MLX5_ADDR_OF(ipsec_obj, obj, implicit_iv);
	memcpy(salt_iv_p, &aes_gcm->seq_iv, sizeof(aes_gcm->seq_iv));
	/* esn */
	if (attrs->replay_esn.trigger) {
		MLX5_SET(ipsec_obj, obj, esn_en, 1);
		MLX5_SET(ipsec_obj, obj, esn_msb, attrs->replay_esn.esn_msb);
		MLX5_SET(ipsec_obj, obj, esn_overlap, attrs->replay_esn.overlap);
	}

	MLX5_SET(ipsec_obj, obj, dekn, sa_entry->enc_key_id);

	/* general object fields set */
	MLX5_SET(general_obj_in_cmd_hdr, in, opcode,
		 MLX5_CMD_OP_CREATE_GENERAL_OBJECT);
	MLX5_SET(general_obj_in_cmd_hdr, in, obj_type,
		 MLX5_GENERAL_OBJECT_TYPES_IPSEC);

	res = &mdev->mlx5e_res.hw_objs;
	if (attrs->type == XFRM_DEV_OFFLOAD_PACKET)
		mlx5e_ipsec_packet_setup(obj, res->pdn, sa_entry);

	err = mlx5_cmd_exec(mdev, in, sizeof(in), out, sizeof(out));
	if (!err)
		sa_entry->ipsec_obj_id =
			MLX5_GET(general_obj_out_cmd_hdr, out, obj_id);

	return err;
}

static void mlx5_destroy_ipsec_obj(struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct mlx5_core_dev *mdev = mlx5e_ipsec_sa2dev(sa_entry);
	u32 in[MLX5_ST_SZ_DW(general_obj_in_cmd_hdr)] = {};
	u32 out[MLX5_ST_SZ_DW(general_obj_out_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_GENERAL_OBJECT_TYPES_IPSEC);
	MLX5_SET(general_obj_in_cmd_hdr, in, obj_id, sa_entry->ipsec_obj_id);

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

int mlx5_ipsec_create_sa_ctx(struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct aes_gcm_keymat *aes_gcm = &sa_entry->attrs.aes_gcm;
	struct mlx5_core_dev *mdev = mlx5e_ipsec_sa2dev(sa_entry);
	int err;

	/* key */
	err = mlx5_create_encryption_key(mdev, aes_gcm->aes_key,
					 aes_gcm->key_len / BITS_PER_BYTE,
					 MLX5_ACCEL_OBJ_IPSEC_KEY,
					 &sa_entry->enc_key_id);
	if (err) {
		mlx5_core_dbg(mdev, "Failed to create encryption key (err = %d)\n", err);
		return err;
	}

	err = mlx5_create_ipsec_obj(sa_entry);
	if (err) {
		mlx5_core_dbg(mdev, "Failed to create IPsec object (err = %d)\n", err);
		goto err_enc_key;
	}

	return 0;

err_enc_key:
	mlx5_destroy_encryption_key(mdev, sa_entry->enc_key_id);
	return err;
}

void mlx5_ipsec_free_sa_ctx(struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct mlx5_core_dev *mdev = mlx5e_ipsec_sa2dev(sa_entry);

	mlx5_destroy_ipsec_obj(sa_entry);
	mlx5_destroy_encryption_key(mdev, sa_entry->enc_key_id);
}

static int mlx5_modify_ipsec_obj(struct mlx5e_ipsec_sa_entry *sa_entry,
				 const struct mlx5_accel_esp_xfrm_attrs *attrs)
{
	struct mlx5_core_dev *mdev = mlx5e_ipsec_sa2dev(sa_entry);
	u32 in[MLX5_ST_SZ_DW(modify_ipsec_obj_in)] = {};
	u32 out[MLX5_ST_SZ_DW(query_ipsec_obj_out)];
	u64 modify_field_select = 0;
	u64 general_obj_types;
	void *obj;
	int err;

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

	/* general object fields set */
	MLX5_SET(general_obj_in_cmd_hdr, in, opcode, MLX5_CMD_OP_QUERY_GENERAL_OBJECT);
	MLX5_SET(general_obj_in_cmd_hdr, in, obj_type, MLX5_GENERAL_OBJECT_TYPES_IPSEC);
	MLX5_SET(general_obj_in_cmd_hdr, in, obj_id, sa_entry->ipsec_obj_id);
	err = mlx5_cmd_exec(mdev, in, sizeof(in), out, sizeof(out));
	if (err) {
		mlx5_core_err(mdev, "Query IPsec object failed (Object id %d), err = %d\n",
			      sa_entry->ipsec_obj_id, err);
		return err;
	}

	obj = MLX5_ADDR_OF(query_ipsec_obj_out, out, ipsec_object);
	modify_field_select = MLX5_GET64(ipsec_obj, obj, modify_field_select);

	/* esn */
	if (!(modify_field_select & MLX5_MODIFY_IPSEC_BITMASK_ESN_OVERLAP) ||
	    !(modify_field_select & MLX5_MODIFY_IPSEC_BITMASK_ESN_MSB))
		return -EOPNOTSUPP;

	obj = MLX5_ADDR_OF(modify_ipsec_obj_in, in, ipsec_object);
	MLX5_SET64(ipsec_obj, obj, modify_field_select,
		   MLX5_MODIFY_IPSEC_BITMASK_ESN_OVERLAP |
			   MLX5_MODIFY_IPSEC_BITMASK_ESN_MSB);
	MLX5_SET(ipsec_obj, obj, esn_msb, attrs->replay_esn.esn_msb);
	MLX5_SET(ipsec_obj, obj, esn_overlap, attrs->replay_esn.overlap);

	/* general object fields set */
	MLX5_SET(general_obj_in_cmd_hdr, in, opcode, MLX5_CMD_OP_MODIFY_GENERAL_OBJECT);

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

void mlx5_accel_esp_modify_xfrm(struct mlx5e_ipsec_sa_entry *sa_entry,
				const struct mlx5_accel_esp_xfrm_attrs *attrs)
{
	int err;

	err = mlx5_modify_ipsec_obj(sa_entry, attrs);
	if (err)
		return;

	memcpy(&sa_entry->attrs, attrs, sizeof(sa_entry->attrs));
}

static void mlx5e_ipsec_aso_update(struct mlx5e_ipsec_sa_entry *sa_entry,
				   struct mlx5_wqe_aso_ctrl_seg *data)
{
	data->data_mask_mode = MLX5_ASO_DATA_MASK_MODE_BITWISE_64BIT << 6;
	data->condition_1_0_operand = MLX5_ASO_ALWAYS_TRUE |
				      MLX5_ASO_ALWAYS_TRUE << 4;

	mlx5e_ipsec_aso_query(sa_entry, data);
}

static void mlx5e_ipsec_update_esn_state(struct mlx5e_ipsec_sa_entry *sa_entry,
					 u32 mode_param)
{
	struct mlx5_accel_esp_xfrm_attrs attrs = {};
	struct mlx5_wqe_aso_ctrl_seg data = {};

	if (mode_param < MLX5E_IPSEC_ESN_SCOPE_MID) {
		sa_entry->esn_state.esn_msb++;
		sa_entry->esn_state.overlap = 0;
	} else {
		sa_entry->esn_state.overlap = 1;
	}

	mlx5e_ipsec_build_accel_xfrm_attrs(sa_entry, &attrs);

	/* It is safe to execute the modify below unlocked since the only flows
	 * that could affect this HW object, are create, destroy and this work.
	 *
	 * Creation flow can't co-exist with this modify work, the destruction
	 * flow would cancel this work, and this work is a single entity that
	 * can't conflict with it self.
	 */
	spin_unlock_bh(&sa_entry->x->lock);
	mlx5_accel_esp_modify_xfrm(sa_entry, &attrs);
	spin_lock_bh(&sa_entry->x->lock);

	data.data_offset_condition_operand =
		MLX5_IPSEC_ASO_REMOVE_FLOW_PKT_CNT_OFFSET;
	data.bitwise_data = cpu_to_be64(BIT_ULL(54));
	data.data_mask = data.bitwise_data;

	mlx5e_ipsec_aso_update(sa_entry, &data);
}

static void mlx5e_ipsec_aso_update_hard(struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct mlx5_wqe_aso_ctrl_seg data = {};

	data.data_offset_condition_operand =
		MLX5_IPSEC_ASO_REMOVE_FLOW_PKT_CNT_OFFSET;
	data.bitwise_data = cpu_to_be64(BIT_ULL(57) + BIT_ULL(31));
	data.data_mask = data.bitwise_data;
	mlx5e_ipsec_aso_update(sa_entry, &data);
}

static void mlx5e_ipsec_aso_update_soft(struct mlx5e_ipsec_sa_entry *sa_entry,
					u32 val)
{
	struct mlx5_wqe_aso_ctrl_seg data = {};

	data.data_offset_condition_operand =
		MLX5_IPSEC_ASO_REMOVE_FLOW_SOFT_LFT_OFFSET;
	data.bitwise_data = cpu_to_be64(val);
	data.data_mask = cpu_to_be64(U32_MAX);
	mlx5e_ipsec_aso_update(sa_entry, &data);
}

static void mlx5e_ipsec_handle_limits(struct mlx5e_ipsec_sa_entry *sa_entry)
{
	struct mlx5_accel_esp_xfrm_attrs *attrs = &sa_entry->attrs;
	struct mlx5e_ipsec *ipsec = sa_entry->ipsec;
	struct mlx5e_ipsec_aso *aso = ipsec->aso;
	bool soft_arm, hard_arm;
	u64 hard_cnt;

	lockdep_assert_held(&sa_entry->x->lock);

	soft_arm = !MLX5_GET(ipsec_aso, aso->ctx, soft_lft_arm);
	hard_arm = !MLX5_GET(ipsec_aso, aso->ctx, hard_lft_arm);
	if (!soft_arm && !hard_arm)
		/* It is not lifetime event */
		return;

	hard_cnt = MLX5_GET(ipsec_aso, aso->ctx, remove_flow_pkt_cnt);
	if (!hard_cnt || hard_arm) {
		/* It is possible to see packet counter equal to zero without
		 * hard limit event armed. Such situation can be if packet
		 * decreased, while we handled soft limit event.
		 *
		 * However it will be HW/FW bug if hard limit event is raised
		 * and packet counter is not zero.
		 */
		WARN_ON_ONCE(hard_arm && hard_cnt);

		/* Notify about hard limit */
		xfrm_state_check_expire(sa_entry->x);
		return;
	}

	/* We are in soft limit event. */
	if (!sa_entry->limits.soft_limit_hit &&
	    sa_entry->limits.round == attrs->lft.numb_rounds_soft) {
		sa_entry->limits.soft_limit_hit = true;
		/* Notify about soft limit */
		xfrm_state_check_expire(sa_entry->x);

		if (sa_entry->limits.round == attrs->lft.numb_rounds_hard)
			goto hard;

		if (attrs->lft.soft_packet_limit > BIT_ULL(31)) {
			/* We cannot avoid a soft_value that might have the high
			 * bit set. For instance soft_value=2^31+1 cannot be
			 * adjusted to the low bit clear version of soft_value=1
			 * because it is too close to 0.
			 *
			 * Thus we have this corner case where we can hit the
			 * soft_limit with the high bit set, but cannot adjust
			 * the counter. Thus we set a temporary interrupt_value
			 * at least 2^30 away from here and do the adjustment
			 * then.
			 */
			mlx5e_ipsec_aso_update_soft(sa_entry,
						    BIT_ULL(31) - BIT_ULL(30));
			sa_entry->limits.fix_limit = true;
			return;
		}

		sa_entry->limits.fix_limit = true;
	}

hard:
	if (sa_entry->limits.round == attrs->lft.numb_rounds_hard) {
		mlx5e_ipsec_aso_update_soft(sa_entry, 0);
		attrs->lft.soft_packet_limit = XFRM_INF;
		return;
	}

	mlx5e_ipsec_aso_update_hard(sa_entry);
	sa_entry->limits.round++;
	if (sa_entry->limits.round == attrs->lft.numb_rounds_soft)
		mlx5e_ipsec_aso_update_soft(sa_entry,
					    attrs->lft.soft_packet_limit);
	if (sa_entry->limits.fix_limit) {
		sa_entry->limits.fix_limit = false;
		mlx5e_ipsec_aso_update_soft(sa_entry, BIT_ULL(31) - 1);
	}
}

static void mlx5e_ipsec_handle_event(struct work_struct *_work)
{
	struct mlx5e_ipsec_work *work =
		container_of(_work, struct mlx5e_ipsec_work, work);
	struct mlx5e_ipsec_sa_entry *sa_entry = work->data;
	struct mlx5_accel_esp_xfrm_attrs *attrs;
	struct mlx5e_ipsec_aso *aso;
	int ret;

	aso = sa_entry->ipsec->aso;
	attrs = &sa_entry->attrs;

	spin_lock_bh(&sa_entry->x->lock);
	ret = mlx5e_ipsec_aso_query(sa_entry, NULL);
	if (ret)
		goto unlock;

	if (attrs->replay_esn.trigger &&
	    !MLX5_GET(ipsec_aso, aso->ctx, esn_event_arm)) {
		u32 mode_param = MLX5_GET(ipsec_aso, aso->ctx, mode_parameter);

		mlx5e_ipsec_update_esn_state(sa_entry, mode_param);
	}

	if (attrs->lft.soft_packet_limit != XFRM_INF)
		mlx5e_ipsec_handle_limits(sa_entry);

unlock:
	spin_unlock_bh(&sa_entry->x->lock);
	kfree(work);
}

static int mlx5e_ipsec_event(struct notifier_block *nb, unsigned long event,
			     void *data)
{
	struct mlx5e_ipsec *ipsec = container_of(nb, struct mlx5e_ipsec, nb);
	struct mlx5e_ipsec_sa_entry *sa_entry;
	struct mlx5_eqe_obj_change *object;
	struct mlx5e_ipsec_work *work;
	struct mlx5_eqe *eqe = data;
	u16 type;

	if (event != MLX5_EVENT_TYPE_OBJECT_CHANGE)
		return NOTIFY_DONE;

	object = &eqe->data.obj_change;
	type = be16_to_cpu(object->obj_type);

	if (type != MLX5_GENERAL_OBJECT_TYPES_IPSEC)
		return NOTIFY_DONE;

	sa_entry = xa_load(&ipsec->sadb, be32_to_cpu(object->obj_id));
	if (!sa_entry)
		return NOTIFY_DONE;

	work = kmalloc(sizeof(*work), GFP_ATOMIC);
	if (!work)
		return NOTIFY_DONE;

	INIT_WORK(&work->work, mlx5e_ipsec_handle_event);
	work->data = sa_entry;

	queue_work(ipsec->wq, &work->work);
	return NOTIFY_OK;
}

int mlx5e_ipsec_aso_init(struct mlx5e_ipsec *ipsec)
{
	struct mlx5_core_dev *mdev = ipsec->mdev;
	struct mlx5e_ipsec_aso *aso;
	struct mlx5e_hw_objs *res;
	struct device *pdev;
	int err;

	aso = kzalloc(sizeof(*ipsec->aso), GFP_KERNEL);
	if (!aso)
		return -ENOMEM;

	res = &mdev->mlx5e_res.hw_objs;

	pdev = mlx5_core_dma_dev(mdev);
	aso->dma_addr = dma_map_single(pdev, aso->ctx, sizeof(aso->ctx),
				       DMA_BIDIRECTIONAL);
	err = dma_mapping_error(pdev, aso->dma_addr);
	if (err)
		goto err_dma;

	aso->aso = mlx5_aso_create(mdev, res->pdn);
	if (IS_ERR(aso->aso)) {
		err = PTR_ERR(aso->aso);
		goto err_aso_create;
	}

	spin_lock_init(&aso->lock);
	ipsec->nb.notifier_call = mlx5e_ipsec_event;
	mlx5_notifier_register(mdev, &ipsec->nb);

	ipsec->aso = aso;
	return 0;

err_aso_create:
	dma_unmap_single(pdev, aso->dma_addr, sizeof(aso->ctx),
			 DMA_BIDIRECTIONAL);
err_dma:
	kfree(aso);
	return err;
}

void mlx5e_ipsec_aso_cleanup(struct mlx5e_ipsec *ipsec)
{
	struct mlx5_core_dev *mdev = ipsec->mdev;
	struct mlx5e_ipsec_aso *aso;
	struct device *pdev;

	aso = ipsec->aso;
	pdev = mlx5_core_dma_dev(mdev);

	mlx5_notifier_unregister(mdev, &ipsec->nb);
	mlx5_aso_destroy(aso->aso);
	dma_unmap_single(pdev, aso->dma_addr, sizeof(aso->ctx),
			 DMA_BIDIRECTIONAL);
	kfree(aso);
	ipsec->aso = NULL;
}

static void mlx5e_ipsec_aso_copy(struct mlx5_wqe_aso_ctrl_seg *ctrl,
				 struct mlx5_wqe_aso_ctrl_seg *data)
{
	if (!data)
		return;

	ctrl->data_mask_mode = data->data_mask_mode;
	ctrl->condition_1_0_operand = data->condition_1_0_operand;
	ctrl->condition_1_0_offset = data->condition_1_0_offset;
	ctrl->data_offset_condition_operand = data->data_offset_condition_operand;
	ctrl->condition_0_data = data->condition_0_data;
	ctrl->condition_0_mask = data->condition_0_mask;
	ctrl->condition_1_data = data->condition_1_data;
	ctrl->condition_1_mask = data->condition_1_mask;
	ctrl->bitwise_data = data->bitwise_data;
	ctrl->data_mask = data->data_mask;
}

int mlx5e_ipsec_aso_query(struct mlx5e_ipsec_sa_entry *sa_entry,
			  struct mlx5_wqe_aso_ctrl_seg *data)
{
	struct mlx5e_ipsec *ipsec = sa_entry->ipsec;
	struct mlx5e_ipsec_aso *aso = ipsec->aso;
	struct mlx5_core_dev *mdev = ipsec->mdev;
	struct mlx5_wqe_aso_ctrl_seg *ctrl;
	struct mlx5e_hw_objs *res;
	struct mlx5_aso_wqe *wqe;
	unsigned long expires;
	u8 ds_cnt;
	int ret;

	lockdep_assert_held(&sa_entry->x->lock);
	res = &mdev->mlx5e_res.hw_objs;

	spin_lock_bh(&aso->lock);
	memset(aso->ctx, 0, sizeof(aso->ctx));
	wqe = mlx5_aso_get_wqe(aso->aso);
	ds_cnt = DIV_ROUND_UP(sizeof(*wqe), MLX5_SEND_WQE_DS);
	mlx5_aso_build_wqe(aso->aso, ds_cnt, wqe, sa_entry->ipsec_obj_id,
			   MLX5_ACCESS_ASO_OPC_MOD_IPSEC);

	ctrl = &wqe->aso_ctrl;
	ctrl->va_l =
		cpu_to_be32(lower_32_bits(aso->dma_addr) | ASO_CTRL_READ_EN);
	ctrl->va_h = cpu_to_be32(upper_32_bits(aso->dma_addr));
	ctrl->l_key = cpu_to_be32(res->mkey);
	mlx5e_ipsec_aso_copy(ctrl, data);

	mlx5_aso_post_wqe(aso->aso, false, &wqe->ctrl);
	expires = jiffies + msecs_to_jiffies(10);
	do {
		ret = mlx5_aso_poll_cq(aso->aso, false);
		if (ret)
			/* We are in atomic context */
			udelay(10);
	} while (ret && time_is_after_jiffies(expires));
	spin_unlock_bh(&aso->lock);
	return ret;
}
