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

#include <net/macsec.h>
#include <linux/netdevice.h>
#include <linux/mlx5/qp.h>
#include <linux/mlx5/driver.h>
#include <linux/mlx5/fs_helpers.h>
#include "fs_core.h"
#include "en.h"
#include "en/fs.h"
#include "en_accel/macsec_fs.h"
#include "mlx5_core.h"

/* MACsec TX flow steering */
#define CRYPTO_NUM_MAXSEC_FTE BIT(15)
#define CRYPTO_TABLE_DEFAULT_RULE_GROUP_SIZE 1

#define TX_CRYPTO_TABLE_LEVEL 0
#define TX_CRYPTO_TABLE_NUM_GROUPS 3
#define TX_CRYPTO_TABLE_MKE_GROUP_SIZE 1
#define TX_CRYPTO_TABLE_SA_GROUP_SIZE \
	(CRYPTO_NUM_MAXSEC_FTE - (TX_CRYPTO_TABLE_MKE_GROUP_SIZE + \
				  CRYPTO_TABLE_DEFAULT_RULE_GROUP_SIZE))
#define TX_CHECK_TABLE_LEVEL 1
#define TX_CHECK_TABLE_NUM_FTE 2
#define RX_CRYPTO_TABLE_LEVEL 0
#define RX_CHECK_TABLE_LEVEL 1
#define RX_ROCE_TABLE_LEVEL 2
#define RX_CHECK_TABLE_NUM_FTE 3
#define RX_ROCE_TABLE_NUM_FTE 2
#define RX_CRYPTO_TABLE_NUM_GROUPS 3
#define RX_CRYPTO_TABLE_SA_RULE_WITH_SCI_GROUP_SIZE \
	((CRYPTO_NUM_MAXSEC_FTE - CRYPTO_TABLE_DEFAULT_RULE_GROUP_SIZE) / 2)
#define RX_CRYPTO_TABLE_SA_RULE_WITHOUT_SCI_GROUP_SIZE \
	(CRYPTO_NUM_MAXSEC_FTE - RX_CRYPTO_TABLE_SA_RULE_WITH_SCI_GROUP_SIZE)
#define RX_NUM_OF_RULES_PER_SA 2

#define RDMA_RX_ROCE_IP_TABLE_LEVEL 0
#define RDMA_RX_ROCE_MACSEC_OP_TABLE_LEVEL 1

#define MLX5_MACSEC_TAG_LEN 8 /* SecTAG length with ethertype and without the optional SCI */
#define MLX5_MACSEC_SECTAG_TCI_AN_FIELD_BITMASK 0x23
#define MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET 0x8
#define MLX5_MACSEC_SECTAG_TCI_SC_FIELD_OFFSET 0x5
#define MLX5_MACSEC_SECTAG_TCI_SC_FIELD_BIT (0x1 << MLX5_MACSEC_SECTAG_TCI_SC_FIELD_OFFSET)
#define MLX5_SECTAG_HEADER_SIZE_WITHOUT_SCI 0x8
#define MLX5_SECTAG_HEADER_SIZE_WITH_SCI (MLX5_SECTAG_HEADER_SIZE_WITHOUT_SCI + MACSEC_SCI_LEN)

/* MACsec RX flow steering */
#define MLX5_ETH_WQE_FT_META_MACSEC_MASK 0x3E

struct mlx5_sectag_header {
	__be16 ethertype;
	u8 tci_an;
	u8 sl;
	u32 pn;
	u8 sci[MACSEC_SCI_LEN]; /* optional */
}  __packed;

struct mlx5e_macsec_tx_rule {
	struct mlx5_flow_handle *rule;
	struct mlx5_pkt_reformat *pkt_reformat;
	u32 fs_id;

	struct mlx5_flow_handle *roce_rule_tx;
	struct mlx5_modify_hdr *roce_meta_modhdr;
};

struct mlx5e_macsec_tables {
	struct mlx5e_flow_table ft_crypto;
	struct mlx5_flow_handle *crypto_miss_rule;

	struct mlx5_flow_table *ft_check;
	struct mlx5_flow_group  *ft_check_group;
	struct mlx5_fc *check_miss_rule_counter;
	struct mlx5_flow_handle *check_miss_rule;
	struct mlx5_fc *check_rule_counter;

	u32 refcnt;
};

struct mlx5e_macsec_tx {
	struct mlx5_flow_handle *crypto_mke_rule;
	struct mlx5_flow_handle *check_rule;

	struct ida tx_halloc;

	struct mlx5e_macsec_tables tables;
	struct mlx5_flow_table *roce_ft_rdma_tx;
	struct mutex roce_lock;      /* serialize TX rules addition and deletion */
};

struct mlx5e_macsec_rx_rule {
	struct mlx5_flow_handle *rule[RX_NUM_OF_RULES_PER_SA];
	struct mlx5_modify_hdr *meta_modhdr;

	struct mlx5_flow_handle *roce_macsec_op_rule;
	struct mlx5_flow_handle *roce_ip_rule;
};

struct mlx5e_macsec_miss {
	struct mlx5_flow_group *group;
	struct mlx5_flow_handle *rule;
};

struct mlx5e_macsec_rx_roce {
	/* Flow table/rules in NIC domain, to check if it's a RoCE packet */
	struct mlx5_flow_group *nic_g;
	struct mlx5_flow_table *nic_ft;
	struct mlx5_flow_handle *nic_rule;
	struct mlx5_modify_hdr *copy_modify_hdr;
	struct mlx5e_macsec_miss roce_nic_miss;

	/* Flow table/rule in RDMA domain, to check dgid */
	struct mlx5_flow_table *ft_rdma_rx_ip_check;
	struct mlx5_flow_table *ft_rdma_rx_macsec_op_check;
	struct mlx5e_macsec_miss roce_rdma_macsec_op_miss;

	struct mutex lock;	/* serialize RX rules addition and deletion */
};

struct mlx5e_macsec_rx {
	struct mlx5_flow_handle *check_rule[2];
	struct mlx5_pkt_reformat *check_rule_pkt_reformat[2];

	struct mlx5e_macsec_tables tables;
	struct mlx5e_macsec_rx_roce roce;
};

union mlx5e_macsec_rule {
	struct mlx5e_macsec_tx_rule tx_rule;
	struct mlx5e_macsec_rx_rule rx_rule;
};

struct mlx5e_macsec_fs {
	struct mlx5_core_dev *mdev;
	struct net_device *netdev;
	struct mlx5e_macsec_tx *tx_fs;
	struct mlx5e_macsec_rx *rx_fs;
};

static void macsec_fs_rdma_tx_destroy(struct mlx5e_macsec_tx *tx_fs)
{
	if (!tx_fs->roce_ft_rdma_tx)
		return;

	mlx5_destroy_flow_table(tx_fs->roce_ft_rdma_tx);
	tx_fs->roce_ft_rdma_tx = NULL;
}

static void macsec_fs_tx_destroy(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct mlx5e_macsec_tables *tx_tables;

	macsec_fs_rdma_tx_destroy(tx_fs);

	tx_tables = &tx_fs->tables;

	/* Tx check table */
	if (tx_fs->check_rule) {
		mlx5_del_flow_rules(tx_fs->check_rule);
		tx_fs->check_rule = NULL;
	}

	if (tx_tables->check_miss_rule) {
		mlx5_del_flow_rules(tx_tables->check_miss_rule);
		tx_tables->check_miss_rule = NULL;
	}

	if (tx_tables->ft_check_group) {
		mlx5_destroy_flow_group(tx_tables->ft_check_group);
		tx_tables->ft_check_group = NULL;
	}

	if (tx_tables->ft_check) {
		mlx5_destroy_flow_table(tx_tables->ft_check);
		tx_tables->ft_check = NULL;
	}

	/* Tx crypto table */
	if (tx_fs->crypto_mke_rule) {
		mlx5_del_flow_rules(tx_fs->crypto_mke_rule);
		tx_fs->crypto_mke_rule = NULL;
	}

	if (tx_tables->crypto_miss_rule) {
		mlx5_del_flow_rules(tx_tables->crypto_miss_rule);
		tx_tables->crypto_miss_rule = NULL;
	}

	mlx5e_destroy_flow_table(&tx_tables->ft_crypto);
}

static int macsec_fs_tx_create_crypto_table_groups(struct mlx5e_flow_table *ft)
{
	int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
	int mclen = MLX5_ST_SZ_BYTES(fte_match_param);
	int ix = 0;
	u32 *in;
	int err;
	u8 *mc;

	ft->g = kcalloc(TX_CRYPTO_TABLE_NUM_GROUPS, sizeof(*ft->g), GFP_KERNEL);
	if (!ft->g)
		return -ENOMEM;
	in = kvzalloc(inlen, GFP_KERNEL);

	if (!in) {
		kfree(ft->g);
		return -ENOMEM;
	}

	mc = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria);

	/* Flow Group for MKE match */
	MLX5_SET_CFG(in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS);
	MLX5_SET_TO_ONES(fte_match_param, mc, outer_headers.ethertype);

	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += TX_CRYPTO_TABLE_MKE_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
	if (IS_ERR(ft->g[ft->num_groups]))
		goto err;
	ft->num_groups++;

	/* Flow Group for SA rules */
	memset(in, 0, inlen);
	memset(mc, 0, mclen);
	MLX5_SET_CFG(in, match_criteria_enable, MLX5_MATCH_MISC_PARAMETERS_2);
	MLX5_SET(fte_match_param, mc, misc_parameters_2.metadata_reg_a,
		 MLX5_ETH_WQE_FT_META_MACSEC_MASK);

	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += TX_CRYPTO_TABLE_SA_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
	if (IS_ERR(ft->g[ft->num_groups]))
		goto err;
	ft->num_groups++;

	/* Flow Group for l2 traps */
	memset(in, 0, inlen);
	memset(mc, 0, mclen);
	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += CRYPTO_TABLE_DEFAULT_RULE_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
	if (IS_ERR(ft->g[ft->num_groups]))
		goto err;
	ft->num_groups++;

	kvfree(in);
	return 0;

err:
	err = PTR_ERR(ft->g[ft->num_groups]);
	ft->g[ft->num_groups] = NULL;
	kvfree(in);

	return err;
}

static struct mlx5_flow_table
	*macsec_fs_auto_group_table_create(struct mlx5_flow_namespace *ns, int flags,
					   int level, int max_fte)
{
	struct mlx5_flow_table_attr ft_attr = {};
	struct mlx5_flow_table *fdb = NULL;

	/* reserve entry for the match all miss group and rule */
	ft_attr.autogroup.num_reserved_entries = 1;
	ft_attr.autogroup.max_num_groups = 1;
	ft_attr.prio = 0;
	ft_attr.flags = flags;
	ft_attr.level = level;
	ft_attr.max_fte = max_fte;

	fdb = mlx5_create_auto_grouped_flow_table(ns, &ft_attr);

	return fdb;
}

enum {
	RDMA_TX_MACSEC_LEVEL = 0,
};

#define NIC_RDMA_BOTH_DIRS_CAPS (MLX5_FT_NIC_RX_2_NIC_RX_RDMA | MLX5_FT_NIC_TX_RDMA_2_NIC_TX)

static bool is_macsec_roce_supported(struct mlx5_core_dev *mdev)
{
	if (((MLX5_CAP_GEN_2(mdev, flow_table_type_2_type) &
	     NIC_RDMA_BOTH_DIRS_CAPS) != NIC_RDMA_BOTH_DIRS_CAPS) ||
	     !MLX5_CAP_FLOWTABLE_RDMA_TX(mdev, max_modify_header_actions))
		return false;

	return true;
}

static int macsec_fs_tx_roce_create(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_table *ft;
	int err;

	if (!is_macsec_roce_supported(macsec_fs->mdev)) {
		netdev_err(macsec_fs->netdev, "Failed to init RoCE MACsec, capabilities not supported\n");
		return 0;
	}

	ns = mlx5_get_flow_namespace(macsec_fs->mdev, MLX5_FLOW_NAMESPACE_RDMA_TX_MACSEC);
	if (!ns)
		return -ENOMEM;

	/* Tx RoCE crypto table  */
	ft = macsec_fs_auto_group_table_create(ns, 0, RDMA_TX_MACSEC_LEVEL,
					       CRYPTO_NUM_MAXSEC_FTE);
	if (IS_ERR(ft)) {
		err = PTR_ERR(ft);
		netdev_err(macsec_fs->netdev, "Failed to create MACsec RoCE Tx crypto table err(%d)\n",
			   err);
		return err;
	}
	tx_fs->roce_ft_rdma_tx = ft;

	mutex_init(&tx_fs->roce_lock);
	return 0;
}

static int macsec_fs_tx_create(struct mlx5e_macsec_fs *macsec_fs)
{
	int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct net_device *netdev = macsec_fs->netdev;
	struct mlx5_flow_table_attr ft_attr = {};
	struct mlx5_flow_destination dest = {};
	struct mlx5e_macsec_tables *tx_tables;
	struct mlx5_flow_act flow_act = {};
	struct mlx5e_flow_table *ft_crypto;
	struct mlx5_flow_table *flow_table;
	struct mlx5_flow_group *flow_group;
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_handle *rule;
	struct mlx5_flow_spec *spec;
	u32 *flow_group_in;
	int err = 0;

	ns = mlx5_get_flow_namespace(macsec_fs->mdev, MLX5_FLOW_NAMESPACE_EGRESS_MACSEC);
	if (!ns)
		return -ENOMEM;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;

	flow_group_in = kvzalloc(inlen, GFP_KERNEL);
	if (!flow_group_in)
		goto out_spec;

	tx_tables = &tx_fs->tables;
	ft_crypto = &tx_tables->ft_crypto;

	/* Tx crypto table  */
	ft_attr.flags = MLX5_FLOW_TABLE_TUNNEL_EN_REFORMAT;
	ft_attr.level = TX_CRYPTO_TABLE_LEVEL;
	ft_attr.max_fte = CRYPTO_NUM_MAXSEC_FTE;

	flow_table = mlx5_create_flow_table(ns, &ft_attr);
	if (IS_ERR(flow_table)) {
		err = PTR_ERR(flow_table);
		netdev_err(netdev, "Failed to create MACsec Tx crypto table err(%d)\n", err);
		goto out_flow_group;
	}
	ft_crypto->t = flow_table;

	/* Tx crypto table groups */
	err = macsec_fs_tx_create_crypto_table_groups(ft_crypto);
	if (err) {
		netdev_err(netdev,
			   "Failed to create default flow group for MACsec Tx crypto table err(%d)\n",
			   err);
		goto err;
	}

	/* Tx crypto table MKE rule - MKE packets shouldn't be offloaded */
	memset(&flow_act, 0, sizeof(flow_act));
	memset(spec, 0, sizeof(*spec));
	spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;

	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.ethertype);
	MLX5_SET(fte_match_param, spec->match_value, outer_headers.ethertype, ETH_P_PAE);
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_ALLOW;

	rule = mlx5_add_flow_rules(ft_crypto->t, spec, &flow_act, NULL, 0);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add MACsec TX MKE rule, err=%d\n", err);
		goto err;
	}
	tx_fs->crypto_mke_rule = rule;

	/* Tx crypto table Default miss rule */
	memset(&flow_act, 0, sizeof(flow_act));
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_ALLOW;
	rule = mlx5_add_flow_rules(ft_crypto->t, NULL, &flow_act, NULL, 0);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add MACsec Tx table default miss rule %d\n", err);
		goto err;
	}
	tx_tables->crypto_miss_rule = rule;

	/* Tx check table */
	flow_table = macsec_fs_auto_group_table_create(ns, 0, TX_CHECK_TABLE_LEVEL,
						       TX_CHECK_TABLE_NUM_FTE);
	if (IS_ERR(flow_table)) {
		err = PTR_ERR(flow_table);
		netdev_err(netdev, "fail to create MACsec TX check table, err(%d)\n", err);
		goto err;
	}
	tx_tables->ft_check = flow_table;

	/* Tx check table Default miss group/rule */
	memset(flow_group_in, 0, inlen);
	MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index, flow_table->max_fte - 1);
	MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index, flow_table->max_fte - 1);
	flow_group = mlx5_create_flow_group(tx_tables->ft_check, flow_group_in);
	if (IS_ERR(flow_group)) {
		err = PTR_ERR(flow_group);
		netdev_err(netdev,
			   "Failed to create default flow group for MACsec Tx crypto table err(%d)\n",
			   err);
		goto err;
	}
	tx_tables->ft_check_group = flow_group;

	/* Tx check table default drop rule */
	memset(&dest, 0, sizeof(struct mlx5_flow_destination));
	memset(&flow_act, 0, sizeof(flow_act));
	dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
	dest.counter_id = mlx5_fc_id(tx_tables->check_miss_rule_counter);
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | MLX5_FLOW_CONTEXT_ACTION_COUNT;
	rule = mlx5_add_flow_rules(tx_tables->ft_check,  NULL, &flow_act, &dest, 1);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to added MACsec tx check drop rule, err(%d)\n", err);
		goto err;
	}
	tx_tables->check_miss_rule = rule;

	/* Tx check table rule */
	memset(spec, 0, sizeof(struct mlx5_flow_spec));
	memset(&dest, 0, sizeof(struct mlx5_flow_destination));
	memset(&flow_act, 0, sizeof(flow_act));

	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, misc_parameters_2.metadata_reg_c_4);
	MLX5_SET(fte_match_param, spec->match_value, misc_parameters_2.metadata_reg_c_4, 0);
	spec->match_criteria_enable = MLX5_MATCH_MISC_PARAMETERS_2;

	flow_act.flags = FLOW_ACT_NO_APPEND;
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_ALLOW | MLX5_FLOW_CONTEXT_ACTION_COUNT;
	dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
	dest.counter_id = mlx5_fc_id(tx_tables->check_rule_counter);
	rule = mlx5_add_flow_rules(tx_tables->ft_check, spec, &flow_act, &dest, 1);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add MACsec check rule, err=%d\n", err);
		goto err;
	}
	tx_fs->check_rule = rule;

	err = macsec_fs_tx_roce_create(macsec_fs);
	if (err)
		goto err;

	goto out_flow_group;

err:
	macsec_fs_tx_destroy(macsec_fs);
out_flow_group:
	kvfree(flow_group_in);
out_spec:
	kvfree(spec);
	return err;
}

static int macsec_fs_tx_ft_get(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct mlx5e_macsec_tables *tx_tables;
	int err = 0;

	tx_tables = &tx_fs->tables;
	if (tx_tables->refcnt)
		goto out;

	err = macsec_fs_tx_create(macsec_fs);
	if (err)
		return err;

out:
	tx_tables->refcnt++;
	return err;
}

static void macsec_fs_tx_ft_put(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tables *tx_tables = &macsec_fs->tx_fs->tables;

	if (--tx_tables->refcnt)
		return;

	macsec_fs_tx_destroy(macsec_fs);
}

static int macsec_fs_tx_setup_fte(struct mlx5e_macsec_fs *macsec_fs,
				  struct mlx5_flow_spec *spec,
				  struct mlx5_flow_act *flow_act,
				  u32 macsec_obj_id,
				  u32 *fs_id)
{
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	int err = 0;
	u32 id;

	err = ida_alloc_range(&tx_fs->tx_halloc, 1,
			      MLX5_MACSEC_NUM_OF_SUPPORTED_INTERFACES,
			      GFP_KERNEL);
	if (err < 0)
		return err;

	id = err;
	spec->match_criteria_enable |= MLX5_MATCH_MISC_PARAMETERS_2;

	/* Metadata match */
	MLX5_SET(fte_match_param, spec->match_criteria, misc_parameters_2.metadata_reg_a,
		 MLX5_ETH_WQE_FT_META_MACSEC_MASK);
	MLX5_SET(fte_match_param, spec->match_value, misc_parameters_2.metadata_reg_a,
		 MLX5_ETH_WQE_FT_META_MACSEC | id << 2);

	*fs_id = id;
	flow_act->crypto.type = MLX5_FLOW_CONTEXT_ENCRYPT_DECRYPT_TYPE_MACSEC;
	flow_act->crypto.obj_id = macsec_obj_id;

	mlx5_core_dbg(macsec_fs->mdev, "Tx fte: macsec obj_id %u, fs_id %u\n", macsec_obj_id, id);
	return 0;
}

static void macsec_fs_tx_create_sectag_header(const struct macsec_context *ctx,
					      char *reformatbf,
					      size_t *reformat_size)
{
	const struct macsec_secy *secy = ctx->secy;
	bool sci_present = macsec_send_sci(secy);
	struct mlx5_sectag_header sectag = {};
	const struct macsec_tx_sc *tx_sc;

	tx_sc = &secy->tx_sc;
	sectag.ethertype = htons(ETH_P_MACSEC);

	if (sci_present) {
		sectag.tci_an |= MACSEC_TCI_SC;
		memcpy(&sectag.sci, &secy->sci,
		       sizeof(sectag.sci));
	} else {
		if (tx_sc->end_station)
			sectag.tci_an |= MACSEC_TCI_ES;
		if (tx_sc->scb)
			sectag.tci_an |= MACSEC_TCI_SCB;
	}

	/* With GCM, C/E clear for !encrypt, both set for encrypt */
	if (tx_sc->encrypt)
		sectag.tci_an |= MACSEC_TCI_CONFID;
	else if (secy->icv_len != MACSEC_DEFAULT_ICV_LEN)
		sectag.tci_an |= MACSEC_TCI_C;

	sectag.tci_an |= tx_sc->encoding_sa;

	*reformat_size = MLX5_MACSEC_TAG_LEN + (sci_present ? MACSEC_SCI_LEN : 0);

	memcpy(reformatbf, &sectag, *reformat_size);
}

static void macsec_fs_tx_del_rule(struct mlx5e_macsec_fs *macsec_fs,
				  struct mlx5e_macsec_tx_rule *tx_rule)
{
	if (tx_rule->rule) {
		mlx5_del_flow_rules(tx_rule->rule);
		tx_rule->rule = NULL;
	}

	if (tx_rule->pkt_reformat) {
		mlx5_packet_reformat_dealloc(macsec_fs->mdev, tx_rule->pkt_reformat);
		tx_rule->pkt_reformat = NULL;
	}

	if (tx_rule->fs_id) {
		ida_free(&macsec_fs->tx_fs->tx_halloc, tx_rule->fs_id);
		tx_rule->fs_id = 0;
	}

	kfree(tx_rule);

	macsec_fs_tx_ft_put(macsec_fs);
}

static union mlx5e_macsec_rule *
macsec_fs_tx_add_rule(struct mlx5e_macsec_fs *macsec_fs,
		      const struct macsec_context *macsec_ctx,
		      struct mlx5_macsec_rule_attrs *attrs,
		      u32 *sa_fs_id)
{
	char reformatbf[MLX5_MACSEC_TAG_LEN + MACSEC_SCI_LEN];
	struct mlx5_pkt_reformat_params reformat_params = {};
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct net_device *netdev = macsec_fs->netdev;
	union mlx5e_macsec_rule *macsec_rule = NULL;
	struct mlx5_flow_destination dest = {};
	struct mlx5e_macsec_tables *tx_tables;
	struct mlx5e_macsec_tx_rule *tx_rule;
	struct mlx5_flow_act flow_act = {};
	struct mlx5_flow_handle *rule;
	struct mlx5_flow_spec *spec;
	size_t reformat_size;
	int err = 0;
	u32 fs_id;

	tx_tables = &tx_fs->tables;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return NULL;

	err = macsec_fs_tx_ft_get(macsec_fs);
	if (err)
		goto out_spec;

	macsec_rule = kzalloc(sizeof(*macsec_rule), GFP_KERNEL);
	if (!macsec_rule) {
		macsec_fs_tx_ft_put(macsec_fs);
		goto out_spec;
	}

	tx_rule = &macsec_rule->tx_rule;

	/* Tx crypto table crypto rule */
	macsec_fs_tx_create_sectag_header(macsec_ctx, reformatbf, &reformat_size);

	reformat_params.type = MLX5_REFORMAT_TYPE_ADD_MACSEC;
	reformat_params.size = reformat_size;
	reformat_params.data = reformatbf;
	flow_act.pkt_reformat = mlx5_packet_reformat_alloc(macsec_fs->mdev,
							   &reformat_params,
							   MLX5_FLOW_NAMESPACE_EGRESS_MACSEC);
	if (IS_ERR(flow_act.pkt_reformat)) {
		err = PTR_ERR(flow_act.pkt_reformat);
		netdev_err(netdev, "Failed to allocate MACsec Tx reformat context err=%d\n",  err);
		goto err;
	}
	tx_rule->pkt_reformat = flow_act.pkt_reformat;

	err = macsec_fs_tx_setup_fte(macsec_fs, spec, &flow_act, attrs->macsec_obj_id, &fs_id);
	if (err) {
		netdev_err(netdev,
			   "Failed to add packet reformat for MACsec TX crypto rule, err=%d\n",
			   err);
		goto err;
	}

	tx_rule->fs_id = fs_id;
	*sa_fs_id = fs_id;

	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
			  MLX5_FLOW_CONTEXT_ACTION_CRYPTO_ENCRYPT |
			  MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT;
	dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
	dest.ft = tx_tables->ft_check;
	rule = mlx5_add_flow_rules(tx_tables->ft_crypto.t, spec, &flow_act, &dest, 1);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add MACsec TX crypto rule, err=%d\n", err);
		goto err;
	}
	tx_rule->rule = rule;

	goto out_spec;

err:
	macsec_fs_tx_del_rule(macsec_fs, tx_rule);
	macsec_rule = NULL;
out_spec:
	kvfree(spec);

	return macsec_rule;
}

static void macsec_fs_tx_cleanup(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct mlx5_core_dev *mdev = macsec_fs->mdev;
	struct mlx5e_macsec_tables *tx_tables;

	if (!tx_fs)
		return;

	tx_tables = &tx_fs->tables;
	if (tx_tables->refcnt) {
		netdev_err(macsec_fs->netdev,
			   "Can't destroy MACsec offload tx_fs, refcnt(%u) isn't 0\n",
			   tx_tables->refcnt);
		return;
	}

	ida_destroy(&tx_fs->tx_halloc);

	if (tx_tables->check_miss_rule_counter) {
		mlx5_fc_destroy(mdev, tx_tables->check_miss_rule_counter);
		tx_tables->check_miss_rule_counter = NULL;
	}

	if (tx_tables->check_rule_counter) {
		mlx5_fc_destroy(mdev, tx_tables->check_rule_counter);
		tx_tables->check_rule_counter = NULL;
	}

	kfree(tx_fs);
	macsec_fs->tx_fs = NULL;
}

static int macsec_fs_tx_init(struct mlx5e_macsec_fs *macsec_fs)
{
	struct net_device *netdev = macsec_fs->netdev;
	struct mlx5_core_dev *mdev = macsec_fs->mdev;
	struct mlx5e_macsec_tables *tx_tables;
	struct mlx5e_macsec_tx *tx_fs;
	struct mlx5_fc *flow_counter;
	int err;

	tx_fs = kzalloc(sizeof(*tx_fs), GFP_KERNEL);
	if (!tx_fs)
		return -ENOMEM;

	tx_tables = &tx_fs->tables;

	flow_counter = mlx5_fc_create(mdev, false);
	if (IS_ERR(flow_counter)) {
		err = PTR_ERR(flow_counter);
		netdev_err(netdev,
			   "Failed to create MACsec Tx encrypt flow counter, err(%d)\n",
			   err);
		goto err_encrypt_counter;
	}
	tx_tables->check_rule_counter = flow_counter;

	flow_counter = mlx5_fc_create(mdev, false);
	if (IS_ERR(flow_counter)) {
		err = PTR_ERR(flow_counter);
		netdev_err(netdev,
			   "Failed to create MACsec Tx drop flow counter, err(%d)\n",
			   err);
		goto err_drop_counter;
	}
	tx_tables->check_miss_rule_counter = flow_counter;

	ida_init(&tx_fs->tx_halloc);

	macsec_fs->tx_fs = tx_fs;

	return 0;

err_drop_counter:
	mlx5_fc_destroy(mdev, tx_tables->check_rule_counter);
	tx_tables->check_rule_counter = NULL;

err_encrypt_counter:
	kfree(tx_fs);
	macsec_fs->tx_fs = NULL;

	return err;
}

static void macsec_fs_rdma_rx_destroy(struct mlx5e_macsec_rx_roce *roce, struct mlx5_core_dev *mdev)
{
	if (!roce->nic_ft)
		return;

	mlx5_del_flow_rules(roce->roce_nic_miss.rule);
	mlx5_del_flow_rules(roce->nic_rule);
	mlx5_modify_header_dealloc(mdev, roce->copy_modify_hdr);
	roce->copy_modify_hdr = NULL;
	mlx5_destroy_flow_group(roce->roce_nic_miss.group);
	mlx5_destroy_flow_group(roce->nic_g);
	mlx5_destroy_flow_table(roce->nic_ft);
	roce->nic_ft = NULL;

	mlx5_del_flow_rules(roce->roce_rdma_macsec_op_miss.rule);
	mlx5_destroy_flow_group(roce->roce_rdma_macsec_op_miss.group);
	mlx5_destroy_flow_table(roce->ft_rdma_rx_macsec_op_check);
	mlx5_destroy_flow_table(roce->ft_rdma_rx_ip_check);
	roce->ft_rdma_rx_macsec_op_check = NULL;
	roce->ft_rdma_rx_ip_check = NULL;
}

static void macsec_fs_rx_destroy(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct mlx5e_macsec_tables *rx_tables;
	int i;

	/* Rx check table */
	for (i = 1; i >= 0; --i) {
		if (rx_fs->check_rule[i]) {
			mlx5_del_flow_rules(rx_fs->check_rule[i]);
			rx_fs->check_rule[i] = NULL;
		}

		if (rx_fs->check_rule_pkt_reformat[i]) {
			mlx5_packet_reformat_dealloc(macsec_fs->mdev,
						     rx_fs->check_rule_pkt_reformat[i]);
			rx_fs->check_rule_pkt_reformat[i] = NULL;
		}
	}

	rx_tables = &rx_fs->tables;

	if (rx_tables->check_miss_rule) {
		mlx5_del_flow_rules(rx_tables->check_miss_rule);
		rx_tables->check_miss_rule = NULL;
	}

	if (rx_tables->ft_check_group) {
		mlx5_destroy_flow_group(rx_tables->ft_check_group);
		rx_tables->ft_check_group = NULL;
	}

	if (rx_tables->ft_check) {
		mlx5_destroy_flow_table(rx_tables->ft_check);
		rx_tables->ft_check = NULL;
	}

	/* Rx crypto table */
	if (rx_tables->crypto_miss_rule) {
		mlx5_del_flow_rules(rx_tables->crypto_miss_rule);
		rx_tables->crypto_miss_rule = NULL;
	}

	mlx5e_destroy_flow_table(&rx_tables->ft_crypto);

	macsec_fs_rdma_rx_destroy(&macsec_fs->rx_fs->roce, macsec_fs->mdev);
}

static int macsec_fs_rx_create_crypto_table_groups(struct mlx5e_flow_table *ft)
{
	int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
	int mclen = MLX5_ST_SZ_BYTES(fte_match_param);
	int ix = 0;
	u32 *in;
	int err;
	u8 *mc;

	ft->g = kcalloc(RX_CRYPTO_TABLE_NUM_GROUPS, sizeof(*ft->g), GFP_KERNEL);
	if (!ft->g)
		return -ENOMEM;

	in = kvzalloc(inlen, GFP_KERNEL);
	if (!in) {
		kfree(ft->g);
		return -ENOMEM;
	}

	mc = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria);

	/* Flow group for SA rule with SCI */
	MLX5_SET_CFG(in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS |
						MLX5_MATCH_MISC_PARAMETERS_5);
	MLX5_SET_TO_ONES(fte_match_param, mc, outer_headers.ethertype);

	MLX5_SET(fte_match_param, mc, misc_parameters_5.macsec_tag_0,
		 MLX5_MACSEC_SECTAG_TCI_AN_FIELD_BITMASK <<
		 MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET);
	MLX5_SET_TO_ONES(fte_match_param, mc, misc_parameters_5.macsec_tag_2);
	MLX5_SET_TO_ONES(fte_match_param, mc, misc_parameters_5.macsec_tag_3);

	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += RX_CRYPTO_TABLE_SA_RULE_WITH_SCI_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
	if (IS_ERR(ft->g[ft->num_groups]))
		goto err;
	ft->num_groups++;

	/* Flow group for SA rule without SCI */
	memset(in, 0, inlen);
	memset(mc, 0, mclen);
	MLX5_SET_CFG(in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS |
						MLX5_MATCH_MISC_PARAMETERS_5);
	MLX5_SET_TO_ONES(fte_match_param, mc, outer_headers.smac_47_16);
	MLX5_SET_TO_ONES(fte_match_param, mc, outer_headers.smac_15_0);
	MLX5_SET_TO_ONES(fte_match_param, mc, outer_headers.ethertype);

	MLX5_SET(fte_match_param, mc, misc_parameters_5.macsec_tag_0,
		 MLX5_MACSEC_SECTAG_TCI_AN_FIELD_BITMASK << MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET);

	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += RX_CRYPTO_TABLE_SA_RULE_WITHOUT_SCI_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
	if (IS_ERR(ft->g[ft->num_groups]))
		goto err;
	ft->num_groups++;

	/* Flow Group for l2 traps */
	memset(in, 0, inlen);
	memset(mc, 0, mclen);
	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += CRYPTO_TABLE_DEFAULT_RULE_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	ft->g[ft->num_groups] = mlx5_create_flow_group(ft->t, in);
	if (IS_ERR(ft->g[ft->num_groups]))
		goto err;
	ft->num_groups++;

	kvfree(in);
	return 0;

err:
	err = PTR_ERR(ft->g[ft->num_groups]);
	ft->g[ft->num_groups] = NULL;
	kvfree(in);

	return err;
}

static int macsec_fs_rx_create_check_decap_rule(struct mlx5e_macsec_fs *macsec_fs,
						struct mlx5_flow_destination *dest,
						struct mlx5_flow_act *flow_act,
						struct mlx5_flow_spec *spec,
						int reformat_param_size)
{
	int rule_index = (reformat_param_size == MLX5_SECTAG_HEADER_SIZE_WITH_SCI) ? 0 : 1;
	u8 mlx5_reformat_buf[MLX5_SECTAG_HEADER_SIZE_WITH_SCI];
	struct mlx5_pkt_reformat_params reformat_params = {};
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct net_device *netdev = macsec_fs->netdev;
	struct mlx5_flow_destination roce_dest[2];
	struct mlx5e_macsec_tables *rx_tables;
	struct mlx5_flow_handle *rule;
	int err = 0, dstn = 0;

	rx_tables = &rx_fs->tables;

	/* Rx check table decap 16B rule */
	memset(dest, 0, sizeof(*dest));
	memset(flow_act, 0, sizeof(*flow_act));
	memset(spec, 0, sizeof(*spec));

	reformat_params.type = MLX5_REFORMAT_TYPE_DEL_MACSEC;
	reformat_params.size = reformat_param_size;
	reformat_params.data = mlx5_reformat_buf;
	flow_act->pkt_reformat = mlx5_packet_reformat_alloc(macsec_fs->mdev,
							    &reformat_params,
							    MLX5_FLOW_NAMESPACE_KERNEL_RX_MACSEC);
	if (IS_ERR(flow_act->pkt_reformat)) {
		err = PTR_ERR(flow_act->pkt_reformat);
		netdev_err(netdev, "Failed to allocate MACsec Rx reformat context err=%d\n", err);
		return err;
	}
	rx_fs->check_rule_pkt_reformat[rule_index] = flow_act->pkt_reformat;

	spec->match_criteria_enable = MLX5_MATCH_MISC_PARAMETERS_2;
	/* MACsec syndrome match */
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, misc_parameters_2.macsec_syndrome);
	MLX5_SET(fte_match_param, spec->match_value, misc_parameters_2.macsec_syndrome, 0);
	/* ASO return reg syndrome match */
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, misc_parameters_2.metadata_reg_c_4);
	MLX5_SET(fte_match_param, spec->match_value, misc_parameters_2.metadata_reg_c_4, 0);

	spec->match_criteria_enable |= MLX5_MATCH_MISC_PARAMETERS_5;
	/* Sectag TCI SC present bit*/
	MLX5_SET(fte_match_param, spec->match_criteria, misc_parameters_5.macsec_tag_0,
		 MLX5_MACSEC_SECTAG_TCI_SC_FIELD_BIT << MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET);

	if (reformat_param_size == MLX5_SECTAG_HEADER_SIZE_WITH_SCI)
		MLX5_SET(fte_match_param, spec->match_value, misc_parameters_5.macsec_tag_0,
			 MLX5_MACSEC_SECTAG_TCI_SC_FIELD_BIT <<
			 MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET);

	flow_act->flags = FLOW_ACT_NO_APPEND;

	if (rx_fs->roce.nic_ft) {
		flow_act->action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
		roce_dest[dstn].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
		roce_dest[dstn].ft = rx_fs->roce.nic_ft;
		dstn++;
	} else {
		flow_act->action = MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO;
	}

	flow_act->action |= MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT |
			    MLX5_FLOW_CONTEXT_ACTION_COUNT;
	roce_dest[dstn].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
	roce_dest[dstn].counter_id = mlx5_fc_id(rx_tables->check_rule_counter);
	rule = mlx5_add_flow_rules(rx_tables->ft_check, spec, flow_act, roce_dest, dstn + 1);

	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add MACsec Rx check rule, err=%d\n", err);
		return err;
	}

	rx_fs->check_rule[rule_index] = rule;

	return 0;
}

static int macsec_fs_rx_roce_miss_create(struct net_device *netdev,
					 struct mlx5e_macsec_rx_roce *roce)
{
	struct mlx5_flow_act flow_act = {};
	struct mlx5_flow_group *flow_group;
	struct mlx5_flow_handle *rule;
	u32 *flow_group_in;
	int err;

	flow_group_in = kvzalloc(MLX5_ST_SZ_BYTES(create_flow_group_in), GFP_KERNEL);
	if (!flow_group_in)
		return -ENOMEM;

	/* IP check ft has no miss rule since we use default miss action which is go to next PRIO */
	MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index,
		 roce->ft_rdma_rx_macsec_op_check->max_fte - 1);
	MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index,
		 roce->ft_rdma_rx_macsec_op_check->max_fte - 1);
	flow_group = mlx5_create_flow_group(roce->ft_rdma_rx_macsec_op_check, flow_group_in);
	if (IS_ERR(flow_group)) {
		err = PTR_ERR(flow_group);
		netdev_err(netdev,
			   "Failed to create miss flow group for MACsec RoCE MACSEC_OP_CHECK table err(%d)\n",
			   err);
		goto macsec_op_miss_group_err;
	}
	roce->roce_rdma_macsec_op_miss.group = flow_group;

	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP;
	rule = mlx5_add_flow_rules(roce->ft_rdma_rx_macsec_op_check,  NULL, &flow_act, NULL, 0);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add miss rule to MACsec RoCE MACSEC_OP_CHECK table err(%d)\n",
			   err);
		goto macsec_op_rule_err;
	}
	roce->roce_rdma_macsec_op_miss.rule = rule;

	kvfree(flow_group_in);
	return 0;

macsec_op_rule_err:
	mlx5_destroy_flow_group(roce->roce_rdma_macsec_op_miss.group);
macsec_op_miss_group_err:
	kvfree(flow_group_in);
	return err;
}

#define MLX5E_RX_ROCE_GROUP_SIZE BIT(0)

static int create_nic_ft_udp_group_and_rules(struct mlx5e_macsec_fs *macsec_fs,
					     struct mlx5e_macsec_rx_roce *roce)
{
	u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {};
	struct net_device *netdev = macsec_fs->netdev;
	struct mlx5_flow_destination dst = {};
	struct mlx5_modify_hdr *modify_hdr;
	MLX5_DECLARE_FLOW_ACT(flow_act);
	struct mlx5_flow_handle *rule;
	struct mlx5_flow_spec *spec;
	struct mlx5_flow_group *g;
	void *outer_headers_c;
	int ix = 0;
	u32 *in;
	int err;
	u8 *mc;

	in = kvzalloc(MLX5_ST_SZ_BYTES(create_flow_group_in), GFP_KERNEL);
	if (!in)
		return -ENOMEM;

	mc = MLX5_ADDR_OF(create_flow_group_in, in, match_criteria);
	outer_headers_c = MLX5_ADDR_OF(fte_match_param, mc, outer_headers);
	MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, ip_protocol);
	MLX5_SET_TO_ONES(fte_match_set_lyr_2_4, outer_headers_c, udp_dport);

	MLX5_SET_CFG(in, match_criteria_enable, MLX5_MATCH_OUTER_HEADERS);
	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += MLX5E_RX_ROCE_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	g = mlx5_create_flow_group(roce->nic_ft, in);
	if (IS_ERR(g)) {
		err = PTR_ERR(g);
		netdev_err(netdev, "Failed to create main flow group for MACsec RoCE NIC UDP table err(%d)\n",
			   err);
		goto err_udp_group;
	}
	roce->nic_g = g;

	memset(in, 0, MLX5_ST_SZ_BYTES(create_flow_group_in));
	MLX5_SET_CFG(in, start_flow_index, ix);
	ix += MLX5E_RX_ROCE_GROUP_SIZE;
	MLX5_SET_CFG(in, end_flow_index, ix - 1);
	g = mlx5_create_flow_group(roce->nic_ft, in);
	if (IS_ERR(g)) {
		err = PTR_ERR(g);
		netdev_err(netdev, "Failed to create miss flow group for MACsec RoCE NIC UDP table err(%d)\n",
			   err);
		goto err_udp_miss_group;
	}
	roce->roce_nic_miss.group = g;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec) {
		err = -ENOMEM;
		goto fail_alloc;
	}

	spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
			 outer_headers.ip_protocol);
	MLX5_SET(fte_match_param, spec->match_value,
		 outer_headers.ip_protocol, IPPROTO_UDP);
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
			 outer_headers.udp_dport);
	MLX5_SET(fte_match_param, spec->match_value,
		 outer_headers.udp_dport, ROCE_V2_UDP_DPORT);

	MLX5_SET(copy_action_in, action, action_type, MLX5_ACTION_TYPE_COPY);
	MLX5_SET(copy_action_in, action, src_field, MLX5_ACTION_IN_FIELD_METADATA_REG_B);
	MLX5_SET(copy_action_in, action, src_offset, 0);
	MLX5_SET(copy_action_in, action, length, 32);
	MLX5_SET(copy_action_in, action, dst_field, MLX5_ACTION_IN_FIELD_METADATA_REG_C_5);
	MLX5_SET(copy_action_in, action, dst_offset, 0);

	modify_hdr = mlx5_modify_header_alloc(macsec_fs->mdev, MLX5_FLOW_NAMESPACE_KERNEL_RX_MACSEC,
					      1, action);

	if (IS_ERR(modify_hdr)) {
		err = PTR_ERR(modify_hdr);
		netdev_err(netdev,
			   "Fail to alloc macsec copy modify_header_id err(%d)\n", err);
		goto fail_alloc_hdr;
	}

	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_MOD_HDR |
			  MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
	flow_act.modify_hdr = modify_hdr;
	dst.type = MLX5_FLOW_DESTINATION_TYPE_TABLE_TYPE;
	dst.ft = roce->ft_rdma_rx_ip_check;
	rule = mlx5_add_flow_rules(roce->nic_ft, spec, &flow_act, &dst, 1);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add rule to MACsec RoCE NIC UDP table err(%d)\n",
			   err);
		goto fail_add_rule;
	}
	roce->nic_rule = rule;
	roce->copy_modify_hdr = modify_hdr;

	memset(&flow_act, 0, sizeof(flow_act));
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO;
	rule = mlx5_add_flow_rules(roce->nic_ft, NULL, &flow_act, NULL, 0);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to add miss rule to MACsec RoCE NIC UDP table err(%d)\n",
			   err);
		goto fail_add_rule2;
	}
	roce->roce_nic_miss.rule = rule;

	kvfree(in);
	kvfree(spec);
	return 0;

fail_add_rule2:
	mlx5_del_flow_rules(roce->nic_rule);
fail_add_rule:
	mlx5_modify_header_dealloc(macsec_fs->mdev, modify_hdr);
fail_alloc_hdr:
	kvfree(spec);
fail_alloc:
	mlx5_destroy_flow_group(roce->roce_nic_miss.group);
err_udp_miss_group:
	mlx5_destroy_flow_group(roce->nic_g);
err_udp_group:
	kvfree(in);
	return err;
}

static int macsec_fs_rx_roce_create(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct mlx5_flow_table_attr ft_attr = {};
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_table *ft;
	int err = 0;

	if (!is_macsec_roce_supported(macsec_fs->mdev)) {
		netdev_err(macsec_fs->netdev, "Failed to init RoCE MACsec, capabilities not supported\n");
		return 0;
	}

	ns = mlx5_get_flow_namespace(macsec_fs->mdev,
				     MLX5_FLOW_NAMESPACE_RDMA_RX_MACSEC);
	if (!ns)
		return -ENOMEM;

	ft = macsec_fs_auto_group_table_create(ns, 0, RDMA_RX_ROCE_IP_TABLE_LEVEL,
					       CRYPTO_NUM_MAXSEC_FTE);
	if (IS_ERR(ft)) {
		err = PTR_ERR(ft);
		netdev_err(macsec_fs->netdev,
			   "Failed to create MACsec rdma_rx_ip_check roce table err(%d)\n", err);
		return err;
	}
	rx_fs->roce.ft_rdma_rx_ip_check = ft;

	ft = macsec_fs_auto_group_table_create(ns, 0, RDMA_RX_ROCE_MACSEC_OP_TABLE_LEVEL,
					       CRYPTO_NUM_MAXSEC_FTE);
	if (IS_ERR(ft)) {
		err = PTR_ERR(ft);
		netdev_err(macsec_fs->netdev,
			   "Failed to create MACsec rdma_rx_macsec_op_check roce table err(%d)\n",
			   err);
		goto fail_macsec_op;
	}
	rx_fs->roce.ft_rdma_rx_macsec_op_check = ft;

	err = macsec_fs_rx_roce_miss_create(macsec_fs->netdev, &rx_fs->roce);
	if (err)
		goto fail_miss_create;

	ns = mlx5_get_flow_namespace(macsec_fs->mdev,
				     MLX5_FLOW_NAMESPACE_KERNEL_RX_MACSEC);
	if (!ns) {
		err = -EOPNOTSUPP;
		goto fail_ns;
	}

	ft_attr.level = RX_ROCE_TABLE_LEVEL;
	ft_attr.max_fte = RX_ROCE_TABLE_NUM_FTE;
	ft = mlx5_create_flow_table(ns, &ft_attr);
	if (IS_ERR(ft)) {
		err = PTR_ERR(ft);
		netdev_err(macsec_fs->netdev,
			   "Failed to create MACsec Rx roce NIC table err(%d)\n", err);
		goto fail_ns;
	}
	rx_fs->roce.nic_ft = ft;

	err = create_nic_ft_udp_group_and_rules(macsec_fs, &rx_fs->roce);
	if (err)
		goto udp_ft_fail;

	mutex_init(&rx_fs->roce.lock);
	return 0;

udp_ft_fail:
	mlx5_destroy_flow_table(rx_fs->roce.nic_ft);
	rx_fs->roce.nic_ft = NULL;
fail_ns:
	mlx5_del_flow_rules(rx_fs->roce.roce_rdma_macsec_op_miss.rule);
	mlx5_destroy_flow_group(rx_fs->roce.roce_rdma_macsec_op_miss.group);
fail_miss_create:
	mlx5_destroy_flow_table(rx_fs->roce.ft_rdma_rx_macsec_op_check);
	rx_fs->roce.ft_rdma_rx_macsec_op_check = NULL;
fail_macsec_op:
	mlx5_destroy_flow_table(rx_fs->roce.ft_rdma_rx_ip_check);
	rx_fs->roce.ft_rdma_rx_ip_check = NULL;
	return err;
}

static int macsec_fs_rx_create(struct mlx5e_macsec_fs *macsec_fs)
{
	int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in);
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct net_device *netdev = macsec_fs->netdev;
	struct mlx5_flow_table_attr ft_attr = {};
	struct mlx5_flow_destination dest = {};
	struct mlx5e_macsec_tables *rx_tables;
	struct mlx5e_flow_table *ft_crypto;
	struct mlx5_flow_table *flow_table;
	struct mlx5_flow_group *flow_group;
	struct mlx5_flow_act flow_act = {};
	struct mlx5_flow_namespace *ns;
	struct mlx5_flow_handle *rule;
	struct mlx5_flow_spec *spec;
	u32 *flow_group_in;
	int err = 0;

	ns = mlx5_get_flow_namespace(macsec_fs->mdev, MLX5_FLOW_NAMESPACE_KERNEL_RX_MACSEC);
	if (!ns)
		return -ENOMEM;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;

	flow_group_in = kvzalloc(inlen, GFP_KERNEL);
	if (!flow_group_in)
		goto free_spec;

	rx_tables = &rx_fs->tables;
	ft_crypto = &rx_tables->ft_crypto;

	/* Rx crypto table */
	ft_attr.level = RX_CRYPTO_TABLE_LEVEL;
	ft_attr.max_fte = CRYPTO_NUM_MAXSEC_FTE;

	flow_table = mlx5_create_flow_table(ns, &ft_attr);
	if (IS_ERR(flow_table)) {
		err = PTR_ERR(flow_table);
		netdev_err(netdev, "Failed to create MACsec Rx crypto table err(%d)\n", err);
		goto out_flow_group;
	}
	ft_crypto->t = flow_table;

	/* Rx crypto table groups */
	err = macsec_fs_rx_create_crypto_table_groups(ft_crypto);
	if (err) {
		netdev_err(netdev,
			   "Failed to create default flow group for MACsec Tx crypto table err(%d)\n",
			   err);
		goto err;
	}

	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_NEXT_PRIO;
	rule = mlx5_add_flow_rules(ft_crypto->t, NULL, &flow_act, NULL, 0);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev,
			   "Failed to add MACsec Rx crypto table default miss rule %d\n",
			   err);
		goto err;
	}
	rx_tables->crypto_miss_rule = rule;

	/* Rx check table */
	flow_table = macsec_fs_auto_group_table_create(ns,
						       MLX5_FLOW_TABLE_TUNNEL_EN_REFORMAT,
						       RX_CHECK_TABLE_LEVEL,
						       RX_CHECK_TABLE_NUM_FTE);
	if (IS_ERR(flow_table)) {
		err = PTR_ERR(flow_table);
		netdev_err(netdev, "fail to create MACsec RX check table, err(%d)\n", err);
		goto err;
	}
	rx_tables->ft_check = flow_table;

	/* Rx check table Default miss group/rule */
	MLX5_SET(create_flow_group_in, flow_group_in, start_flow_index, flow_table->max_fte - 1);
	MLX5_SET(create_flow_group_in, flow_group_in, end_flow_index, flow_table->max_fte - 1);
	flow_group = mlx5_create_flow_group(rx_tables->ft_check, flow_group_in);
	if (IS_ERR(flow_group)) {
		err = PTR_ERR(flow_group);
		netdev_err(netdev,
			   "Failed to create default flow group for MACsec Rx check table err(%d)\n",
			   err);
		goto err;
	}
	rx_tables->ft_check_group = flow_group;

	/* Rx check table default drop rule */
	memset(&flow_act, 0, sizeof(flow_act));

	dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
	dest.counter_id = mlx5_fc_id(rx_tables->check_miss_rule_counter);
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_DROP | MLX5_FLOW_CONTEXT_ACTION_COUNT;
	rule = mlx5_add_flow_rules(rx_tables->ft_check,  NULL, &flow_act, &dest, 1);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev, "Failed to added MACsec Rx check drop rule, err(%d)\n", err);
		goto err;
	}
	rx_tables->check_miss_rule = rule;

	err = macsec_fs_rx_roce_create(macsec_fs);
	if (err)
		goto err;

	/* Rx check table decap rules */
	err = macsec_fs_rx_create_check_decap_rule(macsec_fs, &dest, &flow_act, spec,
						   MLX5_SECTAG_HEADER_SIZE_WITH_SCI);
	if (err)
		goto err_with_roce;

	err = macsec_fs_rx_create_check_decap_rule(macsec_fs, &dest, &flow_act, spec,
						   MLX5_SECTAG_HEADER_SIZE_WITHOUT_SCI);
	if (err)
		goto err_with_roce;

	goto out_flow_group;

err_with_roce:
	macsec_fs_rdma_rx_destroy(&macsec_fs->rx_fs->roce, macsec_fs->mdev);
err:
	macsec_fs_rx_destroy(macsec_fs);
out_flow_group:
	kvfree(flow_group_in);
free_spec:
	kvfree(spec);
	return err;
}

static int macsec_fs_rx_ft_get(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tables *rx_tables = &macsec_fs->rx_fs->tables;
	int err = 0;

	if (rx_tables->refcnt)
		goto out;

	err = macsec_fs_rx_create(macsec_fs);
	if (err)
		return err;

out:
	rx_tables->refcnt++;
	return err;
}

static void macsec_fs_rx_ft_put(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_tables *rx_tables = &macsec_fs->rx_fs->tables;

	if (--rx_tables->refcnt)
		return;

	macsec_fs_rx_destroy(macsec_fs);
}

static void macsec_fs_rx_del_rule(struct mlx5e_macsec_fs *macsec_fs,
				  struct mlx5e_macsec_rx_rule *rx_rule)
{
	int i;

	for (i = 0; i < RX_NUM_OF_RULES_PER_SA; ++i) {
		if (rx_rule->rule[i]) {
			mlx5_del_flow_rules(rx_rule->rule[i]);
			rx_rule->rule[i] = NULL;
		}
	}

	if (rx_rule->meta_modhdr) {
		mlx5_modify_header_dealloc(macsec_fs->mdev, rx_rule->meta_modhdr);
		rx_rule->meta_modhdr = NULL;
	}

	kfree(rx_rule);

	macsec_fs_rx_ft_put(macsec_fs);
}

static void macsec_fs_rx_setup_fte(struct mlx5_flow_spec *spec,
				   struct mlx5_flow_act *flow_act,
				   struct mlx5_macsec_rule_attrs *attrs,
				   bool sci_present)
{
	u8 tci_an = (sci_present << MLX5_MACSEC_SECTAG_TCI_SC_FIELD_OFFSET) | attrs->assoc_num;
	struct mlx5_flow_act_crypto_params *crypto_params = &flow_act->crypto;
	__be32 *sci_p = (__be32 *)(&attrs->sci);

	spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;

	/* MACsec ethertype */
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.ethertype);
	MLX5_SET(fte_match_param, spec->match_value, outer_headers.ethertype, ETH_P_MACSEC);

	spec->match_criteria_enable |= MLX5_MATCH_MISC_PARAMETERS_5;

	/* Sectag AN + TCI SC present bit*/
	MLX5_SET(fte_match_param, spec->match_criteria, misc_parameters_5.macsec_tag_0,
		 MLX5_MACSEC_SECTAG_TCI_AN_FIELD_BITMASK << MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET);
	MLX5_SET(fte_match_param, spec->match_value, misc_parameters_5.macsec_tag_0,
		 tci_an << MLX5_MACSEC_SECTAG_TCI_AN_FIELD_OFFSET);

	if (sci_present) {
		MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
				 misc_parameters_5.macsec_tag_2);
		MLX5_SET(fte_match_param, spec->match_value, misc_parameters_5.macsec_tag_2,
			 be32_to_cpu(sci_p[0]));

		MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
				 misc_parameters_5.macsec_tag_3);
		MLX5_SET(fte_match_param, spec->match_value, misc_parameters_5.macsec_tag_3,
			 be32_to_cpu(sci_p[1]));
	} else {
		/* When SCI isn't present in the Sectag, need to match the source */
		/* MAC address only if the SCI contains the default MACsec PORT	  */
		MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.smac_47_16);
		MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, outer_headers.smac_15_0);
		memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.smac_47_16),
		       sci_p, ETH_ALEN);
	}

	crypto_params->type = MLX5_FLOW_CONTEXT_ENCRYPT_DECRYPT_TYPE_MACSEC;
	crypto_params->obj_id = attrs->macsec_obj_id;
}

static union mlx5e_macsec_rule *
macsec_fs_rx_add_rule(struct mlx5e_macsec_fs *macsec_fs,
		      const struct macsec_context *macsec_ctx,
		      struct mlx5_macsec_rule_attrs *attrs,
		      u32 fs_id)
{
	u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {};
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct net_device *netdev = macsec_fs->netdev;
	union mlx5e_macsec_rule *macsec_rule = NULL;
	struct mlx5_modify_hdr *modify_hdr = NULL;
	struct mlx5_flow_destination dest = {};
	struct mlx5e_macsec_tables *rx_tables;
	struct mlx5e_macsec_rx_rule *rx_rule;
	struct mlx5_flow_act flow_act = {};
	struct mlx5e_flow_table *ft_crypto;
	struct mlx5_flow_handle *rule;
	struct mlx5_flow_spec *spec;
	int err = 0;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return NULL;

	err = macsec_fs_rx_ft_get(macsec_fs);
	if (err)
		goto out_spec;

	macsec_rule = kzalloc(sizeof(*macsec_rule), GFP_KERNEL);
	if (!macsec_rule) {
		macsec_fs_rx_ft_put(macsec_fs);
		goto out_spec;
	}

	rx_rule = &macsec_rule->rx_rule;
	rx_tables = &rx_fs->tables;
	ft_crypto = &rx_tables->ft_crypto;

	/* Set bit[31 - 30] macsec marker - 0x01 */
	/* Set bit[15-0] fs id */
	MLX5_SET(set_action_in, action, action_type, MLX5_ACTION_TYPE_SET);
	MLX5_SET(set_action_in, action, field, MLX5_ACTION_IN_FIELD_METADATA_REG_B);
	MLX5_SET(set_action_in, action, data, MLX5_MACSEC_RX_METADAT_HANDLE(fs_id) | BIT(30));
	MLX5_SET(set_action_in, action, offset, 0);
	MLX5_SET(set_action_in, action, length, 32);

	modify_hdr = mlx5_modify_header_alloc(macsec_fs->mdev, MLX5_FLOW_NAMESPACE_KERNEL_RX_MACSEC,
					      1, action);
	if (IS_ERR(modify_hdr)) {
		err = PTR_ERR(modify_hdr);
		netdev_err(netdev, "fail to alloc MACsec set modify_header_id err=%d\n", err);
		modify_hdr = NULL;
		goto err;
	}
	rx_rule->meta_modhdr = modify_hdr;

	/* Rx crypto table with SCI rule */
	macsec_fs_rx_setup_fte(spec, &flow_act, attrs, true);

	flow_act.modify_hdr = modify_hdr;
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
			  MLX5_FLOW_CONTEXT_ACTION_CRYPTO_DECRYPT |
			  MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;

	dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
	dest.ft = rx_tables->ft_check;
	rule = mlx5_add_flow_rules(ft_crypto->t, spec, &flow_act, &dest, 1);
	if (IS_ERR(rule)) {
		err = PTR_ERR(rule);
		netdev_err(netdev,
			   "Failed to add SA with SCI rule to Rx crypto rule, err=%d\n",
			   err);
		goto err;
	}
	rx_rule->rule[0] = rule;

	/* Rx crypto table without SCI rule */
	if (cpu_to_be64((__force u64)attrs->sci) & ntohs(MACSEC_PORT_ES)) {
		memset(spec, 0, sizeof(struct mlx5_flow_spec));
		memset(&dest, 0, sizeof(struct mlx5_flow_destination));
		memset(&flow_act, 0, sizeof(flow_act));

		macsec_fs_rx_setup_fte(spec, &flow_act, attrs, false);

		flow_act.modify_hdr = modify_hdr;
		flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
				  MLX5_FLOW_CONTEXT_ACTION_CRYPTO_DECRYPT |
				  MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;

		dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
		dest.ft = rx_tables->ft_check;
		rule = mlx5_add_flow_rules(ft_crypto->t, spec, &flow_act, &dest, 1);
		if (IS_ERR(rule)) {
			err = PTR_ERR(rule);
			netdev_err(netdev,
				   "Failed to add SA without SCI rule to Rx crypto rule, err=%d\n",
				   err);
			goto err;
		}
		rx_rule->rule[1] = rule;
	}

	kvfree(spec);
	return macsec_rule;

err:
	macsec_fs_rx_del_rule(macsec_fs, rx_rule);
	macsec_rule = NULL;
out_spec:
	kvfree(spec);
	return macsec_rule;
}

static int macsec_fs_rx_init(struct mlx5e_macsec_fs *macsec_fs)
{
	struct net_device *netdev = macsec_fs->netdev;
	struct mlx5_core_dev *mdev = macsec_fs->mdev;
	struct mlx5e_macsec_tables *rx_tables;
	struct mlx5e_macsec_rx *rx_fs;
	struct mlx5_fc *flow_counter;
	int err;

	rx_fs =	kzalloc(sizeof(*rx_fs), GFP_KERNEL);
	if (!rx_fs)
		return -ENOMEM;

	flow_counter = mlx5_fc_create(mdev, false);
	if (IS_ERR(flow_counter)) {
		err = PTR_ERR(flow_counter);
		netdev_err(netdev,
			   "Failed to create MACsec Rx encrypt flow counter, err(%d)\n",
			   err);
		goto err_encrypt_counter;
	}

	rx_tables = &rx_fs->tables;
	rx_tables->check_rule_counter = flow_counter;

	flow_counter = mlx5_fc_create(mdev, false);
	if (IS_ERR(flow_counter)) {
		err = PTR_ERR(flow_counter);
		netdev_err(netdev,
			   "Failed to create MACsec Rx drop flow counter, err(%d)\n",
			   err);
		goto err_drop_counter;
	}
	rx_tables->check_miss_rule_counter = flow_counter;

	macsec_fs->rx_fs = rx_fs;

	return 0;

err_drop_counter:
	mlx5_fc_destroy(mdev, rx_tables->check_rule_counter);
	rx_tables->check_rule_counter = NULL;

err_encrypt_counter:
	kfree(rx_fs);
	macsec_fs->rx_fs = NULL;

	return err;
}

static void macsec_fs_rx_cleanup(struct mlx5e_macsec_fs *macsec_fs)
{
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct mlx5_core_dev *mdev = macsec_fs->mdev;
	struct mlx5e_macsec_tables *rx_tables;

	if (!rx_fs)
		return;

	rx_tables = &rx_fs->tables;

	if (rx_tables->refcnt) {
		netdev_err(macsec_fs->netdev,
			   "Can't destroy MACsec offload rx_fs, refcnt(%u) isn't 0\n",
			   rx_tables->refcnt);
		return;
	}

	if (rx_tables->check_miss_rule_counter) {
		mlx5_fc_destroy(mdev, rx_tables->check_miss_rule_counter);
		rx_tables->check_miss_rule_counter = NULL;
	}

	if (rx_tables->check_rule_counter) {
		mlx5_fc_destroy(mdev, rx_tables->check_rule_counter);
		rx_tables->check_rule_counter = NULL;
	}

	kfree(rx_fs);
	macsec_fs->rx_fs = NULL;
}

void mlx5e_macsec_fs_get_stats_fill(struct mlx5e_macsec_fs *macsec_fs, void *macsec_stats)
{
	struct mlx5e_macsec_stats *stats = (struct mlx5e_macsec_stats *)macsec_stats;
	struct mlx5e_macsec_tables *tx_tables = &macsec_fs->tx_fs->tables;
	struct mlx5e_macsec_tables *rx_tables = &macsec_fs->rx_fs->tables;
	struct mlx5_core_dev *mdev = macsec_fs->mdev;

	if (tx_tables->check_rule_counter)
		mlx5_fc_query(mdev, tx_tables->check_rule_counter,
			      &stats->macsec_tx_pkts, &stats->macsec_tx_bytes);

	if (tx_tables->check_miss_rule_counter)
		mlx5_fc_query(mdev, tx_tables->check_miss_rule_counter,
			      &stats->macsec_tx_pkts_drop, &stats->macsec_tx_bytes_drop);

	if (rx_tables->check_rule_counter)
		mlx5_fc_query(mdev, rx_tables->check_rule_counter,
			      &stats->macsec_rx_pkts, &stats->macsec_rx_bytes);

	if (rx_tables->check_miss_rule_counter)
		mlx5_fc_query(mdev, rx_tables->check_miss_rule_counter,
			      &stats->macsec_rx_pkts_drop, &stats->macsec_rx_bytes_drop);
}

union mlx5e_macsec_rule *
mlx5e_macsec_fs_add_rule(struct mlx5e_macsec_fs *macsec_fs,
			 const struct macsec_context *macsec_ctx,
			 struct mlx5_macsec_rule_attrs *attrs,
			 u32 *sa_fs_id)
{
	return (attrs->action == MLX5_ACCEL_MACSEC_ACTION_ENCRYPT) ?
		macsec_fs_tx_add_rule(macsec_fs, macsec_ctx, attrs, sa_fs_id) :
		macsec_fs_rx_add_rule(macsec_fs, macsec_ctx, attrs, *sa_fs_id);
}

void mlx5e_macsec_fs_del_rule(struct mlx5e_macsec_fs *macsec_fs,
			      union mlx5e_macsec_rule *macsec_rule,
			      int action)
{
	(action == MLX5_ACCEL_MACSEC_ACTION_ENCRYPT) ?
		macsec_fs_tx_del_rule(macsec_fs, &macsec_rule->tx_rule) :
		macsec_fs_rx_del_rule(macsec_fs, &macsec_rule->rx_rule);
}

static void set_ipaddr_spec(const struct sockaddr *addr,
			    struct mlx5_flow_spec *spec, bool is_dst_ip)
{
	spec->match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
			 outer_headers.ip_version);

	if (addr->sa_family == AF_INET) {
		struct sockaddr_in *in = (struct sockaddr_in *)addr;

		MLX5_SET(fte_match_param, spec->match_value,
			 outer_headers.ip_version, MLX5_FS_IPV4_VERSION);

		if (is_dst_ip) {
			MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
					 outer_headers.dst_ipv4_dst_ipv6.ipv4_layout.ipv4);
			memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
					    outer_headers.dst_ipv4_dst_ipv6.ipv4_layout.ipv4),
			       &in->sin_addr.s_addr, 4);
		} else {
			MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria,
					 outer_headers.src_ipv4_src_ipv6.ipv4_layout.ipv4);
			memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
					    outer_headers.src_ipv4_src_ipv6.ipv4_layout.ipv4),
			       &in->sin_addr.s_addr, 4);
		}

	} else {
		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)addr;

		MLX5_SET(fte_match_param, spec->match_value,
			 outer_headers.ip_version, MLX5_FS_IPV6_VERSION);

		if (is_dst_ip) {
			memset(MLX5_ADDR_OF(fte_match_param, spec->match_criteria,
					    outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6),
			       0xff, 16);
			memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
					    outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6),
			       &in6->sin6_addr.s6_addr, 16);
		} else {
			memset(MLX5_ADDR_OF(fte_match_param, spec->match_criteria,
					    outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6),
			       0xff, 16);
			memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value,
					    outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6),
			       &in6->sin6_addr.s6_addr, 16);
		}
	}
}

int macsec_fs_add_roce_rule_rx(struct mlx5e_macsec_fs *macsec_fs, u32 fs_id,
			       const struct sockaddr *addr, union mlx5e_macsec_rule *rule)
{
	struct mlx5e_macsec_rx_rule *rx_rule = &rule->rx_rule;
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;
	struct mlx5_flow_destination dest = {};
	struct mlx5_flow_act flow_act = {};
	struct mlx5_flow_handle *new_rule;
	struct mlx5_flow_spec *spec;
	int err = 0;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;

	set_ipaddr_spec(addr, spec, true);

	mutex_lock(&rx_fs->roce.lock);

	if (!rx_fs->roce.nic_ft || !rx_fs->roce.ft_rdma_rx_ip_check) {
		err = -EOPNOTSUPP;
		goto out;
	}

	if (rx_rule->roce_ip_rule) { /* Rules that were added for an outdated IP */
		mlx5_del_flow_rules(rx_rule->roce_macsec_op_rule);
		mlx5_del_flow_rules(rx_rule->roce_ip_rule);
		rx_rule->roce_macsec_op_rule = NULL;
		rx_rule->roce_ip_rule = NULL;
	}

	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST;
	dest.ft = rx_fs->roce.ft_rdma_rx_macsec_op_check;
	dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
	new_rule = mlx5_add_flow_rules(rx_fs->roce.ft_rdma_rx_ip_check, spec, &flow_act,
				       &dest, 1);
	if (IS_ERR(new_rule)) {
		err = PTR_ERR(new_rule);
		goto out;
	}
	rx_rule->roce_ip_rule = new_rule;

	memset(&flow_act, 0, sizeof(flow_act));
	memset(spec, 0, sizeof(*spec));

	spec->match_criteria_enable |= MLX5_MATCH_MISC_PARAMETERS_2;
	MLX5_SET_TO_ONES(fte_match_param, spec->match_criteria, misc_parameters_2.metadata_reg_c_5);
	MLX5_SET(fte_match_param, spec->match_value, misc_parameters_2.metadata_reg_c_5,
		 fs_id | BIT(30));
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_ALLOW;
	new_rule = mlx5_add_flow_rules(rx_fs->roce.ft_rdma_rx_macsec_op_check, spec, &flow_act,
				       NULL, 0);
	if (IS_ERR(new_rule)) {
		err = PTR_ERR(new_rule);
		goto macsec_rule_err;
	}
	rx_rule->roce_macsec_op_rule = new_rule;

	goto out;

macsec_rule_err:
	mlx5_del_flow_rules(rx_rule->roce_ip_rule);
	rx_rule->roce_ip_rule = NULL;
out:
	mutex_unlock(&rx_fs->roce.lock);
	kvfree(spec);

	return err;
}

void macsec_fs_del_roce_rule_rx(struct mlx5e_macsec_fs *macsec_fs,
				union mlx5e_macsec_rule *rule)
{
	struct mlx5e_macsec_rx_rule *rx_rule = &rule->rx_rule;
	struct mlx5e_macsec_rx *rx_fs = macsec_fs->rx_fs;

	if (!is_macsec_roce_supported(macsec_fs->mdev))
		return;

	mutex_lock(&rx_fs->roce.lock);

	if (!rx_rule->roce_ip_rule)
		goto out;
	mlx5_del_flow_rules(rx_rule->roce_macsec_op_rule);
	mlx5_del_flow_rules(rx_rule->roce_ip_rule);
	rx_rule->roce_macsec_op_rule = NULL;
	rx_rule->roce_ip_rule = NULL;
out:
	mutex_unlock(&rx_fs->roce.lock);
}

int macsec_fs_add_roce_rule_tx(struct mlx5e_macsec_fs *macsec_fs, u32 fs_id,
			       const struct sockaddr *addr, union mlx5e_macsec_rule *rule)
{
	u8 action[MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)] = {};
	struct mlx5e_macsec_tx_rule *tx_rule = &rule->tx_rule;
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;
	struct mlx5_modify_hdr *modify_hdr = NULL;
	struct mlx5_flow_destination dest = {};
	struct mlx5_flow_act flow_act = {};
	struct mlx5_flow_handle *new_rule;
	struct mlx5_flow_spec *spec;
	int err = 0;

	spec = kvzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;

	set_ipaddr_spec(addr, spec, false);

	mutex_lock(&tx_fs->roce_lock);

	if (!tx_fs->roce_ft_rdma_tx) {
		err = -EOPNOTSUPP;
		goto out;
	}

	if (tx_rule->roce_rule_tx) { /* Rules that were added for an outdated IP */
		mlx5_del_flow_rules(tx_rule->roce_rule_tx);
		tx_rule->roce_rule_tx = NULL;
		mlx5_modify_header_dealloc(macsec_fs->mdev, tx_rule->roce_meta_modhdr);
		tx_rule->roce_meta_modhdr = NULL;
	}

	MLX5_SET(set_action_in, action, action_type, MLX5_ACTION_TYPE_SET);
	MLX5_SET(set_action_in, action, field, MLX5_ACTION_IN_FIELD_METADATA_REG_A);
	MLX5_SET(set_action_in, action, data, MLX5_ETH_WQE_FT_META_MACSEC | fs_id << 2);
	MLX5_SET(set_action_in, action, offset, 0);
	MLX5_SET(set_action_in, action, length, 32);

	modify_hdr = mlx5_modify_header_alloc(macsec_fs->mdev, MLX5_FLOW_NAMESPACE_RDMA_TX_MACSEC,
					      1, action);
	if (IS_ERR(modify_hdr)) {
		err = PTR_ERR(modify_hdr);
		netdev_err(macsec_fs->netdev, "fail to alloc ROCE MACsec set modify_header_id err=%d\n",
			   err);
		modify_hdr = NULL;
		goto out;
	}
	tx_rule->roce_meta_modhdr = modify_hdr;

	flow_act.modify_hdr = modify_hdr;
	flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
			  MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;

	dest.type = MLX5_FLOW_DESTINATION_TYPE_TABLE_TYPE;
	dest.ft = tx_fs->tables.ft_crypto.t;
	new_rule = mlx5_add_flow_rules(tx_fs->roce_ft_rdma_tx, spec, &flow_act, &dest, 1);
	if (IS_ERR(new_rule)) {
		err = PTR_ERR(new_rule);
		netdev_err(macsec_fs->netdev, "Failed to add ROCE TX rule, err=%d\n", err);
		goto rule_err;
	}
	tx_rule->roce_rule_tx = new_rule;

	goto out;

rule_err:
	mlx5_modify_header_dealloc(macsec_fs->mdev, tx_rule->roce_meta_modhdr);
out:
	mutex_unlock(&tx_fs->roce_lock);
	kvfree(spec);
	return err;
}

void macsec_fs_del_roce_rule_tx(struct mlx5e_macsec_fs *macsec_fs,
				union mlx5e_macsec_rule *rule)
{
	struct mlx5e_macsec_tx_rule *tx_rule = &rule->tx_rule;
	struct mlx5e_macsec_tx *tx_fs = macsec_fs->tx_fs;

	if (!is_macsec_roce_supported(macsec_fs->mdev))
		return;

	mutex_lock(&tx_fs->roce_lock);

	if (!tx_rule->roce_rule_tx)
		goto out;

	mlx5_del_flow_rules(tx_rule->roce_rule_tx);
	tx_rule->roce_rule_tx = NULL;
	mlx5_modify_header_dealloc(macsec_fs->mdev, tx_rule->roce_meta_modhdr);
	tx_rule->roce_meta_modhdr = NULL;
out:
	mutex_unlock(&tx_fs->roce_lock);
}

void mlx5e_macsec_fs_cleanup(struct mlx5e_macsec_fs *macsec_fs)
{
	macsec_fs_rx_cleanup(macsec_fs);
	macsec_fs_tx_cleanup(macsec_fs);
	kfree(macsec_fs);
}

struct mlx5e_macsec_fs *
mlx5e_macsec_fs_init(struct mlx5_core_dev *mdev,
		     struct net_device *netdev)
{
	struct mlx5e_macsec_fs *macsec_fs;
	int err;

	macsec_fs = kzalloc(sizeof(*macsec_fs), GFP_KERNEL);
	if (!macsec_fs)
		return NULL;

	macsec_fs->mdev = mdev;
	macsec_fs->netdev = netdev;

	err = macsec_fs_tx_init(macsec_fs);
	if (err) {
		netdev_err(netdev, "MACsec offload: Failed to init tx_fs, err=%d\n", err);
		goto err;
	}

	err = macsec_fs_rx_init(macsec_fs);
	if (err) {
		netdev_err(netdev, "MACsec offload: Failed to init tx_fs, err=%d\n", err);
		goto tx_cleanup;
	}

	return macsec_fs;

tx_cleanup:
	macsec_fs_tx_cleanup(macsec_fs);
err:
	kfree(macsec_fs);
	return NULL;
}
